From fdb9ea69911fd5ed10ebea3e06ea16a4806c9324 Mon Sep 17 00:00:00 2001 From: schilling40 Date: Thu, 4 Dec 2025 15:40:59 +0100 Subject: [PATCH] Create collections based on JSON dictionary --- biohack_utils/omero_annotation.py | 48 ++++++++- development/connect_annotations.py | 126 ++++++++++++++++++++---- development/human_cells.json | 48 +++++++++ development/template_parse_imageid.json | 29 ++++++ development/upload_data.py | 2 +- 5 files changed, 234 insertions(+), 19 deletions(-) create mode 100644 development/human_cells.json create mode 100644 development/template_parse_imageid.json diff --git a/biohack_utils/omero_annotation.py b/biohack_utils/omero_annotation.py index aae68fd..c103905 100644 --- a/biohack_utils/omero_annotation.py +++ b/biohack_utils/omero_annotation.py @@ -86,6 +86,20 @@ def _create_collection(conn, name, version="0.x"): return saved.getId().getValue() +def _create_collection_from_dict(conn, json_dict): + map_annotation = MapAnnotationI() + map_annotation.setNs(rstring(NS_COLLECTION)) + + kv_pairs = [NamedValue(key, value) for (key, value) in json_dict.items()] + map_annotation.setMapValue(kv_pairs) + + # We save this in a server. + update_service = conn.getUpdateService() + saved = update_service.saveAndReturnObject(map_annotation) + + return saved.getId().getValue() + + def _link_collection_to_image(conn, collection_ann_id, image_id): """Link an existing collection annotation to an image. """ @@ -97,7 +111,12 @@ def _link_collection_to_image(conn, collection_ann_id, image_id): if annotation is None: raise ValueError("Annotation {} not found".format(collection_ann_id)) - image.linkAnnotation(annotation) + # Check if image is already linked to collection + ann_ids = [annotation.getId() for annotation in image.listAnnotations()] + if collection_ann_id in ann_ids: + print("Collection ID is already linked to image.") + else: + image.linkAnnotation(annotation) def _create_map_annotation(conn, kv, namespace): @@ -139,6 +158,33 @@ def _add_node_annotation( image.linkAnnotation(ann) return ann.getId() + +def _add_node_annotation_from_dict( + conn, image_id, collection_id, node_dict +): + """Add a node annotation to an image describing its role in the collection. + Returns the created annotation id. + """ + kv = {"collection_id": str(collection_id)} + for (key, value) in node_dict.items(): + if key == "attributes": + for (a_key, a_val) in node_dict["attributes"]: + kv[f"attributes.{a_key}"] = a_val + else: + kv[key] = value + + image = conn.getObject("Image", image_id) + if image is None: + raise ValueError(f"Image {image_id} not found") + + # Check if collection id already exists within MapAnnotations + for annotation in image.listAnnotations(): + kvpairs = annotation.getMapValue() + for kv in kvpairs: + if kv.name == "collection_id" and kv.value == str(collection_id): + print(f"Node annotation already exists for image id {image_id}.") + return annotation.getId() + ann = _create_map_annotation(conn, kv, NS_NODE) image.linkAnnotation(ann) return ann.getId() diff --git a/development/connect_annotations.py b/development/connect_annotations.py index 6ccc721..eb6c1ba 100644 --- a/development/connect_annotations.py +++ b/development/connect_annotations.py @@ -1,5 +1,19 @@ -from biohack_utils.util import omero_credential_parser, connect_to_omero +import argparse +import json +from typing import List, Optional + +import numpy as np + +from biohack_utils.util import connect_to_omero, _upload_image, _upload_volume from biohack_utils import omero_annotation +from biohack_utils.omero_annotation import NS_NODE + + +DEFAULT_COLLECTION = { + "version": "0.0.1", + "type": "collection", + "name": "cells", +} def load_omero_labels_in_napari(conn, image_id, is_3d=False): @@ -29,6 +43,65 @@ def load_omero_labels_in_napari(conn, image_id, is_3d=False): napari.run() +def get_collection_dict(json_dict, target_key="type", target_value="collection"): + for (key, value) in json_dict.items(): + if isinstance(value, dict): + if target_key in list(value.keys()): + if value[target_key] == target_value: + return value + return None + + +def write_annotation_to_image( + conn, + json_dict: str, + image_ids: Optional[List[int]] = None, + image_arr: Optional[np.ndarray] = None, + collection_id: Optional[int] = None, +): + if image_ids is None: + image_ids = [int(key) for key in json_dict[NS_NODE]] + + print(image_ids) + if image_ids is None and image_arr is None: + raise ValueError("Supply either image ID or an array.") + + if image_arr is not None: + # TODO get correct entry of name + name = json_dict["name"] + + if len(image_arr.shape) == 2: + image_id = _upload_image(conn, image_arr, name) + + elif len(image_arr.shape) == 3: + image_id = _upload_volume(conn, image_arr, name) + + else: + raise ValueError("Input data must have 2D or 3D shape.") + image_ids = [image_id] + + # create new collection if collection_id is not supplied + if collection_id is None: + collection_dict = get_collection_dict(json_dict) + if collection_dict is None: + collection_dict = DEFAULT_COLLECTION + + collection_id = omero_annotation._create_collection_from_dict(conn, collection_dict) + + for image_id in image_ids: + print("Link collection to an image") + omero_annotation._link_collection_to_image(conn, collection_id, image_id) + + node_dict = json_dict[NS_NODE][str(image_id)] + + omero_annotation._add_node_annotation_from_dict(conn, image_id, collection_id, node_dict) + + for iid in image_ids: + print("Build image url") + link = omero_annotation._build_image_url(iid) + omero_annotation._append_link_to_node_annotation(conn, iid, link) + + def write_annotations_to_image_and_labels(conn, image_id, label_id): # Creates a new collection based on label images. ann_id = omero_annotation._create_collection(conn, "cells", "0.0.1") @@ -55,33 +128,52 @@ def write_annotations_to_image_and_labels(conn, image_id, label_id): def main(): - parser = omero_credential_parser() + parser = argparse.ArgumentParser( + description="Connect ids.") + + parser.add_argument("-u", "--username", type=str, required=True) + parser.add_argument("-p", "--password", type=str, required=True) + + parser.add_argument("--raw_id", nargs="+", type=int, default=None) + parser.add_argument("--label_id", type=int, default=None) + + parser.add_argument("--json", type=str, default=None, + help="Path to dictionary in JSON format.") + parser.add_argument("--collection_id", type=str, default=None, + help="Collection ID to add images to. Per default, a new collection will be created.") + args = parser.parse_args() conn = connect_to_omero(args) # Scripts to drop metadata. + if args.raw_id is not None and args.label_id is not None: - # 1. For LIVECell (2d) - raw_id = 35394 # The available LIVECell image on the OMERO server. - label_id = 35395 # The corresponding labels image for LIVECell on the OMERO server. + # 1. For LIVECell (2d) + # raw_id = 35394 # The available LIVECell image on the OMERO server. + # label_id = 35395 # The corresponding labels image for LIVECell on the OMERO server. - # 2. For CochleaNet (3d) - # raw_id = 35499 - # label_id = 35500 + # 2. For CochleaNet (3d) + # raw_id = 35499 + # label_id = 35500 - # 3. For multi-label images (2d) - # raw_id = 35478 + # 3. For multi-label images (2d) + # raw_id = 35478 - # 4. For CovidIF HCS data (2d) - # raw_id = [35501, 35502] - # label_id = 35503 + # 4. For CovidIF HCS data (2d) + # raw_id = [35501, 35502] + # label_id = 35503 - # Writes annotations in expected format. - # write_annotations_to_image_and_labels(conn, raw_id, label_id) + # Writes annotations in expected format. + write_annotations_to_image_and_labels(conn, args.raw_id, args.label_id) - # Loading existing stuff. - load_omero_labels_in_napari(conn, raw_id) + elif args.json is not None: + with open(args.json) as f: + json_dict = json.load(f) + write_annotation_to_image(conn, json_dict, collection_id=args.collection_id) + else: + conn.close() + raise ValueError("Supply either raw or label id or a JSON dictionary.") conn.close() diff --git a/development/human_cells.json b/development/human_cells.json new file mode 100644 index 0000000..850fcda --- /dev/null +++ b/development/human_cells.json @@ -0,0 +1,48 @@ +{ + "ome/collection": { + "version": "0.0.1", + "type": "collection", + "name": "cell" + }, + "ome/collection/nodes": { + "35515": { + "category": "Intensities", + "origin": "Raw", + "name": "human_cells a", + "description": "Human HT29 from Moffat et al. doi: 10.1016/j.cell.2006.01.040" + }, + "35507": { + "category": "Annotations", + "origin": "Masks", + "name": "micro-sam a", + "description": "Automatic micro-sam segmentation", + "source_image_id": 35515 + }, + "35514": { + "category": "Intensities", + "origin": "Raw", + "name": "human_cells b", + "description": "Human HT29 from Moffat et al. doi: 10.1016/j.cell.2006.01.040" + }, + "35508": { + "category": "Annotations", + "origin": "Masks", + "name": "micro-sam b", + "description": "Automatic micro-sam segmentation", + "source_image_id": 35514 + }, + "35513": { + "category": "Intensities", + "origin": "Raw", + "name": "human_cells c", + "description": "Human HT29 from Moffat et al. doi: 10.1016/j.cell.2006.01.040" + }, + "35509": { + "category": "Annotations", + "origin": "Masks", + "name": "micro-sam c", + "description": "Automatic micro-sam segmentation", + "source_image_id": 35513 + } + } +} diff --git a/development/template_parse_imageid.json b/development/template_parse_imageid.json new file mode 100644 index 0000000..6b0402e --- /dev/null +++ b/development/template_parse_imageid.json @@ -0,0 +1,29 @@ +{ + "ome/collection": { + "version": "0.0.1", + "type": "collection", + "name": "cell" + }, + "ome/collection/nodes": { + "": { + "category": "Intensities", + "origin": "Raw", + "name": "raw image", + "description": "Lorem ipsum" + }, + "": { + "category": "Intensities", + "origin": "Processed", + "name": "processed image", + "description": "Lorem ipsum", + "source_image_id": [""] + }, + "": { + "category": "Annotations", + "origin": "Masks", + "name": "masks", + "description": "Lorem ipsum", + "source_image_id": ["", ""] + } + } +} diff --git a/development/upload_data.py b/development/upload_data.py index 436538d..8d8a2b9 100644 --- a/development/upload_data.py +++ b/development/upload_data.py @@ -33,7 +33,7 @@ def upload_data(conn, fpath, name, labels=False): def main(): parser = argparse.ArgumentParser( - description="Upload_ ata to Omero web.") + description="Upload data to Omero web.") parser.add_argument("-u", "--username", type=str, required=True) parser.add_argument("-p", "--password", type=str, required=True)