Skip to content
Open
Changes from 9 commits
Commits
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
46 changes: 44 additions & 2 deletions src/omero/gateway/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5164,13 +5164,55 @@ def _getQueryString(cls, opts=None):
Returns a tuple of (query, clauses, params).

:param opts: Dictionary of optional parameters.
NB: No options supported for this class.
ann_type: (optional) "tag", "file", "comment", "long", "map"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguably this could use the full annotation type TagAnnotation, FileAnnotation and save the mapping below.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean "TagAnnotation" (string) or omero.model.TagAnnotation (class)?

I feel that it's more user-friendly for the BlitzGateway methods to take strings rather than omero.model objects. E.g. conn.getObject("Image") rather than conn.getObject(omero.model.ImageI).
So I'd like to do the mapping here. Otherwise we'd have to do the mapping in omero-web for ome/omero-web#682 and probably other places too.

I have just realised that the more natural way to do this (since we only support a single ann type) is to do:

e.g. get Tags on datasets... NB: we already support conn.getObject("TagAnnotation")

conn.getObject("TagAnnotation", opts={"parent_type": "dataset", "parent_ids": [1, 2]})

instead of

conn.getObject("Annotation", opts={"ann_type": "tag", "parent_type": "dataset", "parent_ids": [1, 2]})

@sbesson sbesson Jul 30, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, that's a more natural extension of the existing API and substantially simplifies the logic.
I think it would make sense to use the same semantics for the parent_type e.g. when retrieving all the tags under a tagset, one could use

conn.getObjects("TagAnnotation", opts={"parent_type": "TagAnnotation", "parent_ids": [1], ns: omero.constants.metadata.NSINSIGHTTAGSET})

This would remove the special casing discussed in #489 (comment)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, conn.getObjects("dataset') or conn.getObjects("tagannotation") is already case-insensitive, so lets stick with that here too for consistency.

parent_type: (optional) "project", "dataset", "image" etc

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a list of all objects that can be annotated?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually know where to find such a list or if it's possible to create one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parent_ids: (optional) list of IDs for the parent type
ns: (optional) namespace string to filter by
:return: Tuple of string, list, ParametersI
"""

query, clauses, params = super(
AnnotationWrapper, cls)._getQueryString(opts)
if opts is None:
opts = {}

query = ("select obj from Annotation obj "
"left outer join fetch obj.file as file "
"join fetch obj.details.owner as owner "
"join fetch obj.details.creationEvent")
return query, [], omero.sys.ParametersI()

ann_type = opts.get('ann_type', None)
if ann_type == "tag":
clauses.append("obj.class=TagAnnotation")
elif ann_type == "file":
clauses.append("obj.class=FileAnnotation")
elif ann_type == "comment":
clauses.append("obj.class=CommentAnnotation")
elif ann_type == "long":
clauses.append("obj.class=LongAnnotation")
elif ann_type == "map":
clauses.append("obj.class=MapAnnotation")
elif ann_type is not None:
msg = ("ann_type '%s' not recognised. Must be one of 'tag', 'file', "

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be implemented for all supported annotations (BooleanAnnotation, TermAnnotation, XmlAnnotation...)

"'comment', 'long', 'map'" % ann_type)
raise AttributeError(msg)

if 'parent_type' in opts:
obj_type = opts['parent_type'].title().replace("Plateacquisition", "PlateAcquisition")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, PlateAcquisition is not the only object that will need this special handling to convert from its lowercase version. Other examples include all annotation types (which can be annotated themselves), some instrument objects (LightPath, LightSource...) as well as OriginalFile.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this list (from https://omero.readthedocs.io/en/stable/developers/Model/EveryObject.html#annotation)

AnnotationAnnotationLink
ChannelAnnotationLink
DatasetAnnotationLink
DetectorAnnotationLink
DichroicAnnotationLink
ExperimenterAnnotationLink
ExperimenterGroupAnnotationLink
FilesetAnnotationLink
FilterAnnotationLink
FolderAnnotationLink
ImageAnnotationLink
InstrumentAnnotationLink
LightPathAnnotationLink
LightSourceAnnotationLink
NamespaceAnnotationLink
NodeAnnotationLink
ObjectiveAnnotationLink
OriginalFileAnnotationLink
PlaneInfoAnnotationLink
PlateAcquisitionAnnotationLink
PlateAnnotationLink
ProjectAnnotationLink
ReagentAnnotationLink
RoiAnnotationLink
ScreenAnnotationLink
SessionAnnotationLink
ShapeAnnotationLink
WellAnnotationLink

I see only these cases:

ExperimenterGroup
LightPath
LightSource
OriginalFile
PlaneInfo
PlateAcquisition

If the parent_type is annotation then this will query for AnnotationAnnotationLink without any other handling needed.

ids_clause = ""
if 'parent_ids' in opts:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the real-world use case associated with specifying parent_type but no parent_ids?
I think it should be easy to enforce that parent_ids must be supplied if parent_type is specified. And that can always be relaxed later on if there is a real need.

ids_clause = f"and link.parent.id in (:parent_ids)"
params.add("parent_ids", rlist([rlong(i) for i in opts['parent_ids']]))

clause = f"""exists (from {obj_type}AnnotationLink as link
where link.child.id = obj.id {ids_clause})"""
clauses.append(clause)

if 'ns' in opts:
clauses.append("obj.ns=:ns")
params.add("ns", rstring(opts['ns']))

return (query, clauses, params)

@classmethod
def _register(cls, regklass):
Expand Down