diff --git a/omeroweb/webclient/static/webclient/javascript/ome.right_panel_mapanns_pane.js b/omeroweb/webclient/static/webclient/javascript/ome.right_panel_mapanns_pane.js index 2ab23ea62d..8fca4b09a1 100644 --- a/omeroweb/webclient/static/webclient/javascript/ome.right_panel_mapanns_pane.js +++ b/omeroweb/webclient/static/webclient/javascript/ome.right_panel_mapanns_pane.js @@ -104,7 +104,7 @@ var MapAnnsPane = function MapAnnsPane($element, opts) { } // convert objects to json data - ajaxdata = {"type": "map"}; + ajaxdata = {"type": "map", "parents": "true"}; for (var i=0; i < objects.length; i++) { var o = objects[i].split(/-(.+)/); if (typeof ajaxdata[o[0]] !== 'undefined') { @@ -132,18 +132,39 @@ var MapAnnsPane = function MapAnnsPane($element, opts) { }, {}); } - // Populate experimenters within anns - var anns = data.annotations.map(function(ann){ + var populate_experimenter = function(ann) { if (data.experimenters.length > 0) { ann.owner = experimenters[ann.owner.id]; } if (ann.link && ann.link.owner) { ann.link.owner = experimenters[ann.link.owner.id]; // AddedBy IDs for filtering - ann.addedBy = [ann.link.owner.id]; + ann.addedBy = [ann.link.owner.id]; } return ann; - }); + }; + + // Populate experimenters within anns + var anns = data.annotations.map(populate_experimenter); + + var inh_map_annotations = []; + if (data.hasOwnProperty("parents")){ + data.parents.annotations.forEach(function(ann) { + ann = populate_experimenter(ann); + let class_ = ann.link.parent.class; + let id_ = '' + ann.link.parent.id; + children = data.parents.lineage[class_][id_]; + class_ = children[0].class; + ann.childClass = class_.substring(0, class_.length - 1); + ann.childNames = []; + if (children[0].hasOwnProperty("name")){ + for(j = 0; j < children.length; j++){ + ann.childNames.push(children[j].name); + } + } + inh_map_annotations.push(ann); + }); + } // Sort map anns into 3 lists... var client_map_annotations = []; @@ -179,10 +200,13 @@ var MapAnnsPane = function MapAnnsPane($element, opts) { // In batch_annotate view, we show which object each map is linked to var showParent = batchAnn; html = html + mapAnnsTempl({'anns': my_client_map_annotations, 'objCount': objects.length, - 'showTableHead': showHead, 'showNs': false, 'clientMapAnn': true, 'showParent': showParent}); - html = html + mapAnnsTempl({'anns': client_map_annotations, + 'showTableHead': showHead, 'showNs': false, 'clientMapAnn': true, 'showParent': showParent, + 'isInherited': false}); + html = html + mapAnnsTempl({'anns': client_map_annotations, 'isInherited': false, 'showTableHead': false, 'showNs': false, 'clientMapAnn': true, 'showParent': showParent}); - html = html + mapAnnsTempl({'anns': map_annotations, + html = html + mapAnnsTempl({'anns': map_annotations, 'isInherited': false, + 'showTableHead': false, 'showNs': true, 'clientMapAnn': false, 'showParent': showParent}); + html = html + mapAnnsTempl({'anns': inh_map_annotations, 'isInherited': true, 'showTableHead': false, 'showNs': true, 'clientMapAnn': false, 'showParent': showParent}); $mapAnnContainer.html(html); diff --git a/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html b/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html index fc28427993..71f809b7aa 100644 --- a/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html +++ b/omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html @@ -7,10 +7,10 @@ data-added-by="<%= WEBCLIENT.USER.id %>" <% } %> class="keyValueTable - <% if (!ann.id || (ann.permissions.canEdit && clientMapAnn)){ %> editableKeyValueTable <% } %> + <% if (!ann.id || (ann.permissions.canEdit && clientMapAnn && !isInherited)){ %> editableKeyValueTable <% } %> "> - <% if (showNs && ann.ns) { %> + <% if (showNs && ann.ns && !isInherited) { %> <%- ann.ns.slice(0, 50) %> @@ -20,6 +20,9 @@ <% if (ann.id) { %> + <% if (isInherited) { %> + Added on <%- ann.link.parent.class.substring(0, ann.link.parent.class.length - 1) %> <%- ann.link.parent.name.slice(0, 30) %> + <% } else { %> Added by: <%- ann.owner.firstName %> <%- ann.owner.lastName %> <% if (showParent && ann.link.parent.name){ %>
@@ -30,14 +33,15 @@ <% } %> <%- ann.parentNames ? (ann.parentNames.length + " objects") : ann.link.parent.name %> <% } %> + <% } %> <% } else if (objCount && objCount > 1) { %> diff --git a/omeroweb/webclient/tree.py b/omeroweb/webclient/tree.py index 643dafaa69..8f6827acd4 100644 --- a/omeroweb/webclient/tree.py +++ b/omeroweb/webclient/tree.py @@ -24,13 +24,13 @@ import pytz import omero from builtins import bytes +from collections import defaultdict from omero.rtypes import rlong, unwrap, wrap from django.conf import settings from datetime import datetime from copy import deepcopy -from omero.gateway import _letterGridLabel - +from omero.gateway import _letterGridLabel, _PlateWrapper logger = logging.getLogger(__name__) @@ -1907,8 +1907,17 @@ def _marshal_annotation(conn, annotation, link=None): "id": link.parent.id.val, "class": link.parent.__class__.__name__, } - if hasattr(link.parent, "name"): + if hasattr(link.parent, "name") and link.parent.name: ann["link"]["parent"]["name"] = unwrap(link.parent.name) + elif type(link.parent) is omero.model.PlateAcquisitionI: + # Name is optional for PlateAcquisition + ann["link"]["parent"]["name"] = f"Run {link.parent.id._val}" + elif type(link.parent) is omero.model.WellI: + # Well name constructed from plate + plate = _PlateWrapper(conn, link.parent.plate) + row = plate.getRowLabels()[link.parent.row._val] + col = plate.getColumnLabels()[link.parent.column._val] + ann["link"]["parent"]["name"] = f"{row}{col}" linkCreation = link.details.creationEvent._time ann["link"]["date"] = _marshal_date(unwrap(linkCreation)) p = link.details.permissions @@ -2052,11 +2061,12 @@ def marshal_annotations( left outer join fetch oal.child as ch left outer join fetch oal.parent as pa join fetch ch.details.creationEvent - join fetch ch.details.owner + join fetch ch.details.owner %s left outer join fetch ch.file as file where %s order by ch.ns """ % ( dtype, + "join fetch pa.plate" if dtype == "Well" else "", " and ".join(where_clause), ) @@ -2074,3 +2084,134 @@ def marshal_annotations( experimenters.sort(key=lambda x: x["id"]) return annotations, experimenters + + +def marshal_lineage( + conn, + project_ids=None, + dataset_ids=None, + image_ids=None, + screen_ids=None, + plate_ids=None, + run_ids=None, + well_ids=None, +): + def add_refs( + ref_, dataset=None, project=None, well=None, run=None, plate=None, screen=None + ): + def _add_refs(obj_type, obj_id): + if obj_id: + all_obj_ids[obj_type].add(obj_id) + lineage_d[obj_type][obj_id].append(ref_) + + _add_refs("DatasetI", dataset) + _add_refs("ProjectI", project) + _add_refs("PlateAcquisitionI", run) + _add_refs("WellI", well) + _add_refs("PlateI", plate) + _add_refs("ScreenI", screen) + + def parse_hql_id(hql_ids): + return ("" if x is None else x.getValue() for x in hql_ids) + + requested = { + "ImageI": image_ids, + "DatasetI": dataset_ids, + "ProjectI": project_ids, + "PlateAcquisitionI": run_ids, + "WellI": well_ids, + "PlateI": plate_ids, + "ScreenI": screen_ids, + } + + child_ref_d = defaultdict(dict) # Children references dict + lineage_d, all_obj_ids = {}, {} # Result dictionnaries + for obj_type in [ + "ImageI", + "DatasetI", + "ProjectI", + "PlateI", + "PlateAcquisitionI", + "WellI", + "ScreenI", + ]: + lineage_d[obj_type] = defaultdict(list) + all_obj_ids[obj_type] = set(requested[obj_type]) + + if len(requested[obj_type]) > 0: + for o in conn.getObjects(obj_type[:-1], requested[obj_type]): + details = {"id": o.getId(), "class": obj_type} + if obj_type == "WellI": + name = o.getWellPos() + else: + name = o.getName() + if obj_type == "PlateAcquisitionI" and name is None: + name = f"Run {o.getId()}" + details["name"] = name + child_ref_d[obj_type][details["id"]] = details + + service_opts = conn.SERVICE_OPTS.copy() + qs = conn.getQueryService() + hql_image = ( + "select img.id, dset.id, pdl.parent.id, smp.well.id," + " smp.plateAcquisition.id, plt.id, spl.parent.id" + " from Image img" + " left outer join img.datasetLinks dil" + " left outer join dil.parent dset" + " left outer join img.wellSamples smp" + " left outer join dset.projectLinks pdl" + " left outer join smp.well.plate plt" + " left outer join plt.screenLinks spl" + " where img.id in (:ids)" + ) + + hql_wellrun = ( + "select child_o.id, plt.id, pdl.parent.id" + " from {} child_o" + " join child_o.plate plt" + " left outer join plt.screenLinks pdl" + " where child_o.id in (:ids)" + ) + + hql_dsetplate = ( + "select child_o.id, opl.parent.id FROM {} child_o" + " left outer join child_o.{}Links opl " + " where child_o.id in (:ids)" + ) + + if len(requested["ImageI"]) > 0: + params = omero.sys.ParametersI() + params.addIds(requested["ImageI"]) + for im, ds, pr, wl, rn, pl, sc in map( + parse_hql_id, qs.projection(hql_image, params, service_opts) + ): + ref_ = child_ref_d["ImageI"][im] + add_refs(ref_, ds, pr, wl, rn, pl, sc) + + for _type in ["WellI", "PlateAcquisitionI"]: + if len(requested[_type]) > 0: + params = omero.sys.ParametersI() + params.addIds(requested[_type]) + for ob, pl, sc in map( + parse_hql_id, + qs.projection(hql_wellrun.format(_type[:-1]), params, service_opts), + ): + ref_ = child_ref_d[_type][ob] + add_refs(ref_, plate=pl, screen=sc) + + for _type, parent_type in zip(["PlateI", "DatasetI"], ["screen", "project"]): + if len(requested[_type]) > 0: + params = omero.sys.ParametersI() + params.addIds(requested[_type]) + for ob, p in map( + parse_hql_id, + qs.projection( + hql_dsetplate.format(_type[:-1], parent_type), params, service_opts + ), + ): + ref_ = child_ref_d[_type][ob] + if _type == "PlateI": + add_refs(ref_, screen=p) + else: + add_refs(ref_, project=p) + return lineage_d, all_obj_ids diff --git a/omeroweb/webclient/views.py b/omeroweb/webclient/views.py index 971eb17fce..b0a17a99a2 100755 --- a/omeroweb/webclient/views.py +++ b/omeroweb/webclient/views.py @@ -35,6 +35,7 @@ import re import sys import warnings +from collections import defaultdict from past.builtins import unicode from future.utils import bytes_to_native_str from django.utils.html import escape @@ -1341,35 +1342,74 @@ def api_tags_and_tagged_list_DELETE(request, conn=None, **kwargs): @login_required() def api_annotations(request, conn=None, **kwargs): r = request.GET - image_ids = get_list(request, "image") - dataset_ids = get_list(request, "dataset") - project_ids = get_list(request, "project") - screen_ids = get_list(request, "screen") - plate_ids = get_list(request, "plate") - run_ids = get_list(request, "acquisition") - well_ids = get_list(request, "well") page = get_long_or_default(request, "page", 1) limit = get_long_or_default(request, "limit", ANNOTATIONS_LIMIT) - ann_type = r.get("type", None) ns = r.get("ns", None) + with_parents = r.get("parents", "false") == "true" + + to_query = defaultdict(set) + for type_ in ( + "Image", + "Dataset", + "Project", + "Well", + "Acquisition", + "Plate", + "Screen", + ): + ids = get_list(request, type_.lower()) + if type_ == "Acquisition": + type_ = "PlateAcquisition" + # Important to cast to int to match marshal_lineage output + to_query[f"{type_}I"] = list(map(int, ids)) + + if with_parents: + requested = to_query.copy() + lineage_d, to_query = tree.marshal_lineage( + conn, + project_ids=to_query["ProjectI"], + dataset_ids=to_query["DatasetI"], + image_ids=to_query["ImageI"], + screen_ids=to_query["ScreenI"], + plate_ids=to_query["PlateI"], + run_ids=to_query["PlateAcquisitionI"], + well_ids=to_query["WellI"], + ) - anns, exps = tree.marshal_annotations( + all_anns, exps = tree.marshal_annotations( conn, - project_ids=project_ids, - dataset_ids=dataset_ids, - image_ids=image_ids, - screen_ids=screen_ids, - plate_ids=plate_ids, - run_ids=run_ids, - well_ids=well_ids, + project_ids=to_query["ProjectI"], + dataset_ids=to_query["DatasetI"], + image_ids=to_query["ImageI"], + screen_ids=to_query["ScreenI"], + plate_ids=to_query["PlateI"], + run_ids=to_query["PlateAcquisitionI"], + well_ids=to_query["WellI"], ann_type=ann_type, ns=ns, page=page, limit=limit, ) - - return JsonResponse({"annotations": anns, "experimenters": exps}) + if not with_parents: + return JsonResponse({"annotations": all_anns, "experimenters": exps}) + + anns = [] + parent_anns = {"annotations": [], "lineage": defaultdict(dict)} + for ann in all_anns: + p = ann["link"]["parent"] + pclass, pid = p["class"], p["id"] + if pid in requested[pclass]: + anns.append(ann) + if pclass != "ImageI": + lineage = lineage_d[pclass][pid] + if len(lineage) > 0: + parent_anns["annotations"].append(ann) + parent_anns["lineage"][pclass][pid] = lineage + + return JsonResponse( + {"annotations": anns, "parents": parent_anns, "experimenters": exps} + ) @login_required()