Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key-value pair scripts enhancement #216

Open
wants to merge 130 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
c7258bc
Changed UI to new (broke param names)
Tom-TBT Oct 23, 2023
0b22f16
Assertion on Source Target relationship
Tom-TBT Oct 23, 2023
c875d38
Rename param and changed AnnID to list input
Tom-TBT Oct 23, 2023
1b611bd
Stop AnnotationFile search on first item found
Tom-TBT Oct 23, 2023
f570f73
Great refactor and use new inputs
Tom-TBT Oct 23, 2023
7348f2f
Changed csv file selection when multiple (highest ID)
Tom-TBT Oct 23, 2023
518d84e
file attachment to sources fix for no AnnID provided
Tom-TBT Oct 23, 2023
6d87ace
added namespace to KV
Tom-TBT Oct 23, 2023
8181454
removed existing kvpairs update/deletion
Tom-TBT Oct 24, 2023
f932f28
Updated script info
Tom-TBT Oct 24, 2023
cbe1595
compare file annotation on date rather than ID
Tom-TBT Oct 24, 2023
64fd963
Minor tip update
Tom-TBT Oct 24, 2023
4f26db0
Modified deletion according to KeyVal_import_from_csv logic
Tom-TBT Oct 24, 2023
2ab42d9
Checkbox to confirm consequences
Tom-TBT Oct 24, 2023
a1e01d2
special name handling for wells
Tom-TBT Oct 24, 2023
e5f880f
fixed message
Tom-TBT Oct 24, 2023
d48f685
uppercasing well names to match OMERO style
Tom-TBT Oct 24, 2023
12cb5a4
typo fix
Tom-TBT Oct 24, 2023
66d1f9e
Initial refactoring of the csv export
Tom-TBT Oct 24, 2023
b746e0d
Change user input 'same as source' for target
Tom-TBT Oct 24, 2023
b6815fa
Support of tags as source for delete or export
Tom-TBT Oct 24, 2023
3a97d45
Added supprt of ann from Tag as source and on source directly
Tom-TBT Oct 25, 2023
f2407b2
minor rearangements
Tom-TBT Oct 25, 2023
50e3402
Reworking the UI
Tom-TBT Oct 25, 2023
90f1fbc
Added option to add ancestry as columns
Tom-TBT Oct 25, 2023
3eee5bf
fixes
Tom-TBT Oct 25, 2023
da83ee3
more parameter for csv import
Tom-TBT Oct 25, 2023
4e9e8d5
Auto ID input and doc update
Tom-TBT Oct 25, 2023
9a802aa
Auto ID input and doc update
Tom-TBT Oct 25, 2023
182aeb1
Auto ID input and doc update for delete script
Tom-TBT Oct 25, 2023
6993841
target object name sorting before export
Tom-TBT Oct 25, 2023
8ca4ef9
New script to convert namespace
Tom-TBT Oct 25, 2023
5cdaba9
renamed convert namespace script
Tom-TBT Oct 25, 2023
4467d54
packed advanced parameters in the UI
Tom-TBT Oct 26, 2023
63d5cd1
multiple namespace support
Tom-TBT Oct 26, 2023
8c7c087
multi namespace support for delete and export
Tom-TBT Oct 26, 2023
a557ed7
Return result object
Tom-TBT Oct 26, 2023
d3859c3
flake8 and print output
Tom-TBT Oct 26, 2023
4f376e9
minor print change
Tom-TBT Oct 26, 2023
a51a9e7
minor output change
Tom-TBT Oct 26, 2023
cec68b4
replaced object column names to OBJECT_ID and OBJECT_NAME
Tom-TBT Oct 26, 2023
1634052
permission check on annotation before copy/delete
Tom-TBT Oct 26, 2023
54dc166
Bug fix with tag and other improvements
Tom-TBT Nov 7, 2023
b21c778
Export csv now always generate a single csv file
Tom-TBT Nov 7, 2023
18b827a
fixed inline string comment
Tom-TBT Nov 7, 2023
60ef433
output condition on object not null
Tom-TBT Nov 7, 2023
3118d63
Added parent target parameter validation
Tom-TBT Nov 7, 2023
70edfb3
added Run support
Tom-TBT Nov 7, 2023
3c62f16
added default namespace to script doc
Tom-TBT Nov 8, 2023
acc776d
Fail script when sniffer fails
Tom-TBT Nov 8, 2023
59eb5c5
added functionality for Namespaces
JensWendt Nov 16, 2023
e7f0fc3
a bit more documentation comments
JensWendt Nov 16, 2023
1e40d7b
flake8 shit ...
JensWendt Nov 16, 2023
67ad416
sniffing on one line and error on rows len mismatch
Tom-TBT Nov 17, 2023
e3d4c60
option to attach files and debug
Tom-TBT Nov 17, 2023
df326b1
default separator to tab
Tom-TBT Nov 17, 2023
6bdce17
allow .tsv file in search
Tom-TBT Nov 17, 2023
cc48eec
export namespace option
Tom-TBT Nov 17, 2023
f1c9b37
Fixed NS import and add exclude-empty-val parameter
Tom-TBT Nov 17, 2023
2842a0e
Added parameter to split value for key duplications
Tom-TBT Nov 17, 2023
87a1085
removed unnecessary parameter
JensWendt Nov 20, 2023
3de4ac1
implemented annotating Tags
JensWendt Nov 20, 2023
99d41c5
simplify code and fix row sorting
Tom-TBT Nov 23, 2023
e3c4519
Reorganized the code
Tom-TBT Nov 24, 2023
e63adf4
implemented Boolean to use only ones own tags
JensWendt Nov 27, 2023
6a8878c
Implemented Tag sets
JensWendt Dec 4, 2023
a7c3379
a lot of bugfixes and fixing the tag-tagSet interaction; still not do…
JensWendt Dec 19, 2023
be2d54a
implemented check for creation of new Tag; implemented Tag[TagId] fea…
JensWendt Jan 2, 2024
d7377ae
NOW REALLY implemented check for creation of new Tag; implemented Tag…
JensWendt Jan 2, 2024
821654d
fixed bugs which showed up in testing
JensWendt Jan 3, 2024
f453367
various bug fixes
JensWendt Jan 10, 2024
17d1d72
tag feature validation
Tom-TBT Jan 16, 2024
ab0a41d
Add exclusion of parent columns by default (same as generated by export)
Tom-TBT Jan 16, 2024
0bc3b2b
Small fixes
Tom-TBT Jan 16, 2024
858dd01
Added PlateACquisition in ancestry of plate images
Tom-TBT Jan 16, 2024
dd89ce4
Export of tags support
Tom-TBT Jan 29, 2024
f517eb7
Shortened script doc and pointed to annscript-omero-guide repo
Tom-TBT Jan 31, 2024
74f3e98
modified param name to lowercase
Tom-TBT Jan 31, 2024
be0beb5
Support * for all namespace selection
Tom-TBT Jan 31, 2024
f45bc7f
* namespace input validation and fix for export
Tom-TBT Jan 31, 2024
f4ad5db
doc minor changes
Tom-TBT Feb 1, 2024
67ca206
changed namespace param name
Tom-TBT Feb 2, 2024
6c87387
changed namespace param name fix
Tom-TBT Feb 2, 2024
5eb050e
ns param override csv ns
Tom-TBT Feb 2, 2024
042dc2a
Refactor param names with global variable
Tom-TBT Feb 2, 2024
3aa53f2
changed run to acquisition for autofill
Tom-TBT Feb 2, 2024
16407e6
script names change
Tom-TBT Feb 2, 2024
51cd204
renamed import script
Tom-TBT Feb 2, 2024
133b7e6
param name adjustements
Tom-TBT Feb 2, 2024
e7b5263
minor error message change
Tom-TBT Feb 15, 2024
7d7762a
Option to create&merge KV into a new one
Tom-TBT Feb 16, 2024
6fd5cae
restricted rights limit case handling
Tom-TBT Feb 17, 2024
440fead
Merge branch 'xtnd_support_kvpairs' of https://github.com/German-BioI…
Tom-TBT Feb 17, 2024
df7b078
Merge remote-tracking branch 'ome/develop' into xtnd_support_kvpairs
Tom-TBT Feb 28, 2024
cecb66b
explicit handling of csv encoding with utf-8
Tom-TBT Mar 1, 2024
ebbcf1b
capitalize namespace output
Tom-TBT Mar 1, 2024
0d36407
Added script version
Tom-TBT Mar 1, 2024
bbf79ed
added version 2.0.0 to the scripts
Tom-TBT Mar 1, 2024
011dcf9
Merge branch 'xtnd_support_kvpairs' of https://github.com/German-BioI…
Tom-TBT Mar 1, 2024
dae6a36
replace doc link to the current readthedoc
Tom-TBT Mar 1, 2024
5d6df32
fix line too long
Tom-TBT Mar 1, 2024
144f691
updated README
Tom-TBT Mar 1, 2024
0c8d969
set optional true for bool param
Tom-TBT Mar 16, 2024
936915e
added tag in the script description
Tom-TBT Mar 16, 2024
582a46a
changed main loop description
Tom-TBT Mar 16, 2024
6d51b6b
exclude tag namespace error fix
Tom-TBT Mar 20, 2024
a7234a9
Remove problematic assertion on inner separator
Tom-TBT Mar 20, 2024
4e05a96
first test annotation
Tom-TBT Mar 22, 2024
e727912
tests import and fix
Tom-TBT Apr 6, 2024
8a39780
test for remove and convert
Tom-TBT Apr 6, 2024
d8fc5d6
Revert file encoding to utf-8-sig
Tom-TBT Apr 10, 2024
a25a298
flake8 fix
Tom-TBT Apr 16, 2024
e77a535
file attachment without option
Tom-TBT Apr 16, 2024
83a3efa
removed attachFile param from test
Tom-TBT Apr 16, 2024
a437292
fix tooltip delete mention
Tom-TBT Apr 30, 2024
051381a
clarified tooltips
Tom-TBT May 5, 2024
d66f818
convert test with and without merge
Tom-TBT May 5, 2024
ae9436b
export script tests
Tom-TBT May 5, 2024
e73217a
added missing True test parameter
Tom-TBT May 8, 2024
e05c3d3
remove convert test duplicate with parametrization
Tom-TBT May 8, 2024
5d94201
delete agreement parametrization
Tom-TBT May 8, 2024
8dc923b
added docstring to tests
Tom-TBT May 8, 2024
48e01e6
year of update to 2024
Tom-TBT Jun 5, 2024
3fd54e9
added affiliation
Tom-TBT Jun 5, 2024
d9054e0
inlude existing KV having target ns with merge option
Tom-TBT Jul 16, 2024
9ad2ff4
output with csv.writer and includes sep metadata
Tom-TBT Jul 16, 2024
3d4098d
handle sep= metadata of the csv
Tom-TBT Jul 16, 2024
e227863
added | option for csv separator
Tom-TBT Jul 16, 2024
000b1ce
handle new sep= metadata in export
Tom-TBT Jul 16, 2024
a48786f
replaced \r\n ending by \n
Tom-TBT Jul 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
339 changes: 339 additions & 0 deletions omero/annotation_scripts/Convert_KeyVal_namespace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
# coding=utf-8
"""
Convert_KeyVal_namespace.py

Convert the namespace of objects key-value pairs.
-----------------------------------------------------------------------------
Copyright (C) 2024
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
------------------------------------------------------------------------------
Created by Tom Boissonnet

"""

import omero
from omero.gateway import BlitzGateway
from omero.rtypes import rstring, rlong, robject
import omero.scripts as scripts
from omero.constants.metadata import NSCLIENTMAPANNOTATION


CHILD_OBJECTS = {
"Project": "Dataset",
"Dataset": "Image",
"Screen": "Plate",
"Plate": "Well",
"Well": "WellSample",
"WellSample": "Image"
}

ALLOWED_PARAM = {
"Project": ["Project", "Dataset", "Image"],
"Dataset": ["Dataset", "Image"],
"Image": ["Image"],
"Screen": ["Screen", "Plate", "Well", "Acquisition", "Image"],
"Plate": ["Plate", "Well", "Acquisition", "Image"],
"Well": ["Well", "Image"],
"Acquisition": ["Acquisition", "Image"],
"Tag": ["Project", "Dataset", "Image",
"Screen", "Plate", "Well", "Acquisition"]
}

P_DTYPE = "Data_Type" # Do not change
P_IDS = "IDs" # Do not change
P_TARG_DTYPE = "Target Data_Type"
P_OLD_NS = "Old Namespace (blank for default)"
P_NEW_NS = "New Namespace (blank for default)"
P_MERGE = "Create new and merge"


def get_children_recursive(source_object, target_type):
if CHILD_OBJECTS[source_object.OMERO_CLASS] == target_type:
# Stop condition, we return the source_obj children
if source_object.OMERO_CLASS != "WellSample":
return source_object.listChildren()
else:
return [source_object.getImage()]
else: # Not yet the target
result = []
for child_obj in source_object.listChildren():
# Going down in the Hierarchy list
result.extend(get_children_recursive(child_obj, target_type))
return result


def target_iterator(conn, source_object, target_type, is_tag):
if target_type == source_object.OMERO_CLASS:
target_obj_l = [source_object]
elif source_object.OMERO_CLASS == "PlateAcquisition":
# Check if there is more than one Run, otherwise
# it's equivalent to start from a plate (and faster this way)
plate_o = source_object.getParent()
wellsamp_l = get_children_recursive(plate_o, "WellSample")
if len(list(plate_o.listPlateAcquisitions())) > 1:
# Only case where we need to filter on PlateAcquisition
run_id = source_object.getId()
wellsamp_l = filter(lambda x: x._obj.plateAcquisition._id._val
== run_id, wellsamp_l)
target_obj_l = [wellsamp.getImage() for wellsamp in wellsamp_l]
elif target_type == "PlateAcquisition":
# No direct children access from a plate
if source_object.OMERO_CLASS == "Screen":
plate_l = get_children_recursive(source_object, "Plate")
elif source_object.OMERO_CLASS == "Plate":
plate_l = [source_object]
target_obj_l = [r for p in plate_l for r in p.listPlateAcquisitions()]
elif is_tag:
target_obj_l = conn.getObjectsByAnnotations(target_type,
[source_object.getId()])
# Need that to load objects
obj_ids = [o.getId() for o in target_obj_l]
target_obj_l = list(conn.getObjects(target_type, obj_ids))
else:
target_obj_l = get_children_recursive(source_object,
target_type)

print(f"Iterating objects from {source_object}:")
for target_obj in target_obj_l:
print(f"\t- {target_obj}")
yield target_obj


def main_loop(conn, script_params):
"""
For every object:
- Find annotations in the namespace
- If merge:
- Remove annotations with old namespace
- Create a merged annotation with new namespace
- Else change the namespace of the annotation (default)
"""
source_type = script_params[P_DTYPE]
target_type = script_params[P_TARG_DTYPE]
source_ids = script_params[P_IDS]
old_namespace = script_params[P_OLD_NS]
new_namespace = script_params[P_NEW_NS]
merge = script_params[P_MERGE]

ntarget_processed = 0
ntarget_updated = 0
result_obj = None

# One file output per given ID
for source_object in conn.getObjects(source_type, source_ids):
is_tag = source_type == "TagAnnotation"
for target_obj in target_iterator(conn, source_object,
target_type, is_tag):
ntarget_processed += 1
keyval_l, ann_l = get_existing_map_annotations(target_obj,
old_namespace)
if len(keyval_l) > 0:
if merge:
annotate_object(conn, target_obj, keyval_l,
new_namespace)
remove_map_annotations(conn, ann_l)
else:
for ann in ann_l:
try:
ann.setNs(new_namespace)
ann.save()
except Exception:
print(f"Failed to edit {ann}")
continue
ntarget_updated += 1
if result_obj is None:
result_obj = target_obj
else:
print("\tNo MapAnnotation found with that namespace\n")
print("\n------------------------------------\n")
message = (
"Updated kv pairs to " +
f"{ntarget_updated}/{ntarget_processed} {target_type}"
)

return message, result_obj


def get_existing_map_annotations(obj, namespace_l):
keyval_l, ann_l = [], []
forbidden_deletion = []
for namespace in namespace_l:
p = {} if namespace == "*" else {"ns": namespace}
for ann in obj.listAnnotations(**p):
if isinstance(ann, omero.gateway.MapAnnotationWrapper):
if ann.canEdit(): # If not, skipping it
keyval_l.extend([(k, v) for (k, v) in ann.getValue()])
ann_l.append(ann)
else:
forbidden_deletion.append(ann.id)
if len(forbidden_deletion) > 0:
print("\tMap Annotation IDs skipped (not permitted):",
f"{forbidden_deletion}")
return keyval_l, ann_l


def remove_map_annotations(conn, ann_l):
mapann_ids = [ann.id for ann in ann_l]

if len(mapann_ids) == 0:
return 0
print(f"\tMap Annotation IDs to delete: {mapann_ids}\n")
try:
conn.deleteObjects("Annotation", mapann_ids)
return 1
except Exception:
print(f"Failed to delete old annotations {mapann_ids}")
return 0


def annotate_object(conn, obj, kv_list, namespace):

map_ann = omero.gateway.MapAnnotationWrapper(conn)
map_ann.setNs(namespace)
map_ann.setValue(kv_list)
map_ann.save()

print("\tMap Annotation created", map_ann.id)
obj.linkAnnotation(map_ann)


def run_script():
# Cannot add fancy layout if we want auto fill and selct of object ID
source_types = [
rstring("Project"), rstring("Dataset"), rstring("Image"),
rstring("Screen"), rstring("Plate"), rstring("Well"),
rstring("Acquisition"), rstring("Image"), rstring("Tag"),
]

# Duplicate Image for UI, but not a problem for script
target_types = [
rstring("<on current>"), rstring("Project"),
rstring("- Dataset"), rstring("-- Image"),
rstring("Screen"), rstring("- Plate"),
rstring("-- Well"), rstring("-- Acquisition"),
rstring("--- Image")
]

client = scripts.client(
'Convert Key-Value pairs namespace',
"""
Converts the namespace of key-value pairs.
\t
Check the guide for more information on parameters and errors:
https://guide-kvpairs-scripts.readthedocs.io/en/latest/index.html
\t
Default namespace: openmicroscopy.org/omero/client/mapAnnotation
""", # Tabs are needed to add line breaks in the HTML

scripts.String(
P_DTYPE, optional=False, grouping="1",
description="Data type of the parent objects.",
values=source_types, default="Dataset"),

scripts.List(
P_IDS, optional=False, grouping="1.1",
description="IDs of the parent objects").ofType(rlong(0)),

scripts.String(
P_TARG_DTYPE, optional=False, grouping="1.2",
description="Data type to process from the selected " +
"parent objects.",
values=target_types, default="<on current>"),

scripts.List(
P_OLD_NS, optional=True, grouping="1.4",
description="Namespace(s) of the key-value pairs to " +
"process. Client namespace by default, " +
"'*' for all.").ofType(rstring("")),

scripts.String(
P_NEW_NS, optional=True, grouping="1.5",
description="The new namespace for the annotations."),

scripts.Bool(
P_MERGE, optional=True, grouping="1.6",
description="Check to merge selected key-value pairs " +
"into a single new one (will also include " +
"existing key-value pairs having the New Namespace)",
default=False),

authors=["Tom Boissonnet"],
institutions=["CAi HHU"],
contact="https://forum.image.sc/tag/omero",
version="2.0.0",
)

try:
params = parameters_parsing(client)
print("Input parameters:")
keys = [P_DTYPE, P_IDS, P_TARG_DTYPE, P_OLD_NS, P_NEW_NS]
for k in keys:
print(f"\t- {k}: {params[k]}")
print("\n####################################\n")

# wrap client to use the Blitz Gateway
conn = BlitzGateway(client_obj=client)
message, robj = main_loop(conn, params)
client.setOutput("Message", rstring(message))
if robj is not None:
client.setOutput("Result", robject(robj._obj))

except AssertionError as err:
# Display assertion errors in OMERO.web activities
client.setOutput("ERROR", rstring(err))
raise AssertionError(str(err))

finally:
client.closeSession()


def parameters_parsing(client):
params = {}
# Param dict with defaults for optional parameters
params[P_OLD_NS] = [NSCLIENTMAPANNOTATION]
params[P_NEW_NS] = NSCLIENTMAPANNOTATION

for key in client.getInputKeys():
if client.getInput(key):
params[key] = client.getInput(key, unwrap=True)

if params[P_TARG_DTYPE] == "<on current>":
params[P_TARG_DTYPE] = params[P_DTYPE]
elif " " in params[P_TARG_DTYPE]:
# Getting rid of the trailing '---' added for the UI
params[P_TARG_DTYPE] = params[P_TARG_DTYPE].split(" ")[1]

assert params[P_TARG_DTYPE] in ALLOWED_PARAM[params[P_DTYPE]], \
(f"{params['Target Data_Type']} is not a valid target for " +
f"{params['Data_Type']}.")

if params[P_DTYPE] == "Tag":
params[P_DTYPE] = "TagAnnotation"

if params[P_TARG_DTYPE] == "Acquisition":
params[P_TARG_DTYPE] = "PlateAcquisition"

if params[P_MERGE]:
# If merge, also include existing target NS
params[P_OLD_NS].append(params[P_NEW_NS])
# Remove duplicate entries from namespace list
tmp = params[P_OLD_NS]
if "*" in tmp:
tmp = ["*"]
params[P_OLD_NS] = list(set(tmp))

return params


if __name__ == "__main__":
run_script()
Loading