From 60f2d5c34f5ea98b49e2349a4a36a93619d0b3c5 Mon Sep 17 00:00:00 2001 From: Dominik Lindner Date: Tue, 30 Jan 2024 11:23:49 +0000 Subject: [PATCH 1/6] Add export and impo(rt) commands --- src/omero_cli_render.py | 141 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 5 deletions(-) diff --git a/src/omero_cli_render.py b/src/omero_cli_render.py index 9b680b3..c8c3fe0 100755 --- a/src/omero_cli_render.py +++ b/src/omero_cli_render.py @@ -28,14 +28,14 @@ from omero.cli import BaseControl from omero.cli import CLI from omero.cli import ProxyStringType -from omero.gateway import BlitzGateway +from omero.gateway import BlitzGateway, DatasetWrapper from omero.model import Image from omero.model import Plate from omero.model import Screen from omero.model import Dataset from omero.model import Project from omero.model import StatsInfoI -from omero.rtypes import rint, rdouble +from omero.rtypes import rint, rdouble, rstring, rlong from omero.util import pydict_text_io from omero import UnloadedEntityException @@ -370,11 +370,13 @@ def _configure(self, parser): set_cmd = parser.add(sub, self.set, SET_HELP) edit = parser.add(sub, self.edit, EDIT_HELP) test = parser.add(sub, self.test, TEST_HELP) + export = parser.add(sub, self.export, "Export rendering settings as yaml files") + impo = parser.add(sub, self.impo, "Import rendering settings from yaml files") render_type = ProxyStringType("Image") src_help = ("Rendering settings source") - for x in (get, info, copy, test): + for x in (get, info, copy, test, export): x.add_argument("object", type=render_type, help=src_help) tgt_help = ("Objects to apply the rendering settings to") @@ -382,7 +384,7 @@ def _configure(self, parser): x.add_argument("object", type=render_type, help=tgt_help, nargs="+") - for x in (copy, set_cmd, edit): + for x in (copy, set_cmd, edit, impo): x.add_argument( "--skipthumbs", help="Do not regenerate thumbnails " "immediately", action="store_true") @@ -399,7 +401,7 @@ def _configure(self, parser): copy.add_argument("target", type=render_type, help=tgt_help, nargs="+") - for x in (set_cmd, edit): + for x in (set_cmd, edit, impo): x.add_argument( "--disable", help="Disable non specified channels ", action="store_true") @@ -421,6 +423,14 @@ def _configure(self, parser): help="If underlying pixel data available test thumbnail retrieval" ) + export.add_argument("--sep", default="|", help="Separator character (default: |)") + export.add_argument("--traverse", action="store_true", help="Export settings for all images of a dataset (default: just first one)") + + impo.add_argument("--sep", default="|", help="Separator character (default: |)") + impo.add_argument("--dataset", action="store_true", help="Rendering settings should be applied to the whole dataset") + impo.add_argument("--spw", action="store_true", help="If target is Screen/Plate/Well (NOT SUPPORTED YET)") + + def _lookup(self, gateway, type, oid): # TODO: move _lookup to a _configure type gateway.SERVICE_OPTS.setOmeroGroup('-1') @@ -530,6 +540,127 @@ def __info(self, args): finally: img._closeRE() + + def _get_datasets_for_image(self, gateway, img_id): + """ + Get all datasets an image is part of. + :param gateway: Ref to gateway + :param img: The image + :return: The datasets + """ + query = "select i from Image i join fetch i.datasetLinks idl " \ + "join fetch idl.parent d where i.id = :id" + params = omero.sys.ParametersI() + params.map['id'] = rlong(img_id) + img = gateway.getQueryService().findByQuery(query, params) + datasets = [] + for link in img.iterateDatasetLinks(): + datasets.append(link.parent) + return datasets + + + @gateway_required + def impo(self, args): + """Implements the impo(rt) command""" + filename = args.channels + filename = filename.replace(".yml", "") + ds_name = filename.split(args.sep)[0] + img_name = filename.split(args.sep)[1] + + set_args = args + if args.dataset: + ds_query = "select d from Dataset d where d.name = :name" + params = omero.sys.ParametersI() + params.map['name'] = rstring(ds_name) + datasets = self.gateway.getQueryService().findAllByQuery(ds_query, params) + if not datasets: + self.ctx.err(f"No dataset with name {ds_name} found.") + return + elif len(datasets) > 1: + self.ctx.dbg("More than one dataset found, using latest one") + target_ds = max(datasets, key=lambda ds: ds.getId().getValue()) + else: + target_ds = datasets[0] + set_args.object = target_ds + self.set(set_args) + else: + img_query = "select c from DatasetImageLink l " \ + "join l.child as c " \ + "join l.parent as p " \ + "where p.name = :name1 and c.name = :name2" + params = omero.sys.ParametersI() + params.map['name1'] = rstring(ds_name) + params.map['name2'] = rstring(img_name) + images = self.gateway.getQueryService().findAllByQuery(img_query, params) + if not images: + self.ctx.err(f"No image with name {img_name} found within a dataset {ds_name}.") + return + elif len(images) > 1: + self.ctx.dbg("More than one images found, using latest one.") + target_img = max(images, key=lambda img: img.getId().getValue()) + else: + target_img = images[0] + set_args.object = target_img + self.set(set_args) + + + def __do_export(self, ds, img, sep): + """ + Exports the rendering settings of an image to a file + called .yml + :param ds: The dataset + :param img: The image + :param sep: The separator + :return: None + """ + try: + ro = RenderObject(img) + name = f"{ds.getName()}{sep}{img.getName()}.yml" + with open(name, "w") as out: + out.write(yaml.dump(ro.to_dict(), + explicit_start=True, + width=80, indent=4, + default_flow_style=False).rstrip()) + except Exception as e: + self.ctx.err('ERROR: %s' % e) + finally: + img._closeRE() + + + @gateway_required + def export(self, args): + """Implements the export command""" + if isinstance(args.object, Project): + prj = self._lookup(self.gateway, "Project", args.object.id) + for ds in prj.listChildren(): + ds = self._lookup(self.gateway, "Dataset", ds.id) + for img in ds.listChildren(): + self.__do_export(ds, img, args.sep) + if not args.traverse: + break + + elif isinstance(args.object, Dataset): + ds = self._lookup(self.gateway, "Dataset", args.object.id) + for img in ds.listChildren(): + self.__do_export(ds, img, args.sep) + if not args.traverse: + break + + elif isinstance(args.object, Image): + img = self._lookup(self.gateway, "Image", args.object.id) + ds = self._get_datasets_for_image(self.gateway, args.object.id.getValue()) + if len(ds) > 1: + self.ctx.out("Warning, more than one dataset found, using latest one") + res = max(ds, key=lambda d: d.getId().getValue()) + else: + res = ds[0] + res = DatasetWrapper(conn=self.gateway, obj=res) + self.__do_export(res, img, args.sep) + + else: + self.ctx.err(f"{args.object.__class__.__name__} not supported yet ") + + @gateway_required def copy(self, args): """ Implements the 'copy' command """ From ae0cc311749f5651bc755d9cefbb54d753a62502 Mon Sep 17 00:00:00 2001 From: Dominik Lindner Date: Thu, 1 Feb 2024 09:19:23 +0000 Subject: [PATCH 2/6] Use directory name instead of separator --- src/omero_cli_render.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/omero_cli_render.py b/src/omero_cli_render.py index c8c3fe0..fb3a457 100755 --- a/src/omero_cli_render.py +++ b/src/omero_cli_render.py @@ -25,6 +25,8 @@ from functools import wraps +from pathlib import Path + from omero.cli import BaseControl from omero.cli import CLI from omero.cli import ProxyStringType @@ -562,10 +564,9 @@ def _get_datasets_for_image(self, gateway, img_id): @gateway_required def impo(self, args): """Implements the impo(rt) command""" - filename = args.channels - filename = filename.replace(".yml", "") - ds_name = filename.split(args.sep)[0] - img_name = filename.split(args.sep)[1] + yml_path = Path(args.channels) + ds_name = yml_path.parent.name + img_name = yml_path.name.replace(".yml", "") set_args = args if args.dataset: @@ -604,7 +605,7 @@ def impo(self, args): self.set(set_args) - def __do_export(self, ds, img, sep): + def __do_export(self, ds, img): """ Exports the rendering settings of an image to a file called .yml @@ -615,8 +616,10 @@ def __do_export(self, ds, img, sep): """ try: ro = RenderObject(img) - name = f"{ds.getName()}{sep}{img.getName()}.yml" - with open(name, "w") as out: + target_dir = Path(ds.getName()) + target_dir.mkdir(exist_ok=True) + target = target_dir / Path(f"{img.getName()}.yml") + with target.open(mode="w", encoding="utf-8") as out: out.write(yaml.dump(ro.to_dict(), explicit_start=True, width=80, indent=4, @@ -635,14 +638,14 @@ def export(self, args): for ds in prj.listChildren(): ds = self._lookup(self.gateway, "Dataset", ds.id) for img in ds.listChildren(): - self.__do_export(ds, img, args.sep) + self.__do_export(ds, img) if not args.traverse: break elif isinstance(args.object, Dataset): ds = self._lookup(self.gateway, "Dataset", args.object.id) for img in ds.listChildren(): - self.__do_export(ds, img, args.sep) + self.__do_export(ds, img) if not args.traverse: break @@ -655,7 +658,7 @@ def export(self, args): else: res = ds[0] res = DatasetWrapper(conn=self.gateway, obj=res) - self.__do_export(res, img, args.sep) + self.__do_export(res, img) else: self.ctx.err(f"{args.object.__class__.__name__} not supported yet ") From c92919dbdd1e5e4eb454aaa0c30882abfa44a1bb Mon Sep 17 00:00:00 2001 From: Dominik Lindner Date: Thu, 1 Feb 2024 10:07:37 +0000 Subject: [PATCH 3/6] Improve help and fix flake8 --- src/omero_cli_render.py | 78 ++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/src/omero_cli_render.py b/src/omero_cli_render.py index fb3a457..e4300a7 100755 --- a/src/omero_cli_render.py +++ b/src/omero_cli_render.py @@ -147,6 +147,36 @@ omero render test Image:1 """ +EXPORT_HELP = """Export rendering settings as yaml files + +Examples: + Export one yaml file for each Dataset in a Project (first image) + (into ./DATASET_NAME/IMAGE_NAME.yml): + omero export Project:1 + + Export one yaml file for each Image of a Dataset (into + ./DATASET_NAME/IMAGE_NAME.yml): + omero export --traverse Dataset:1 +""" + +IMPO_HELP = """Import rendering settings as yaml files + +Yaml files have to be in directories with the name of the dataset, +i.e. ./DATASET_NAME/IMAGE_NAME.yml ! + +If DATASET_NAME/IMAGE_NAME is not unique, the last imported +dataset/image will be used. + +(channels argument has to be a yaml file for this command) + +Examples: + Set the rendering settings for one image: + omero import DATASET_NAME/IMAGE_NAME.yml + + Set the rendering settings for the whole dataset: + omero import --dataset DATASET_NAME/IMAGE_NAME.yml +""" + # Current version for specifying rendering settings # in the yaml / json files SPEC_VERSION = 2 @@ -372,8 +402,8 @@ def _configure(self, parser): set_cmd = parser.add(sub, self.set, SET_HELP) edit = parser.add(sub, self.edit, EDIT_HELP) test = parser.add(sub, self.test, TEST_HELP) - export = parser.add(sub, self.export, "Export rendering settings as yaml files") - impo = parser.add(sub, self.impo, "Import rendering settings from yaml files") + export = parser.add(sub, self.export, EXPORT_HELP) + impo = parser.add(sub, self.impo, IMPO_HELP) render_type = ProxyStringType("Image") src_help = ("Rendering settings source") @@ -425,13 +455,16 @@ def _configure(self, parser): help="If underlying pixel data available test thumbnail retrieval" ) - export.add_argument("--sep", default="|", help="Separator character (default: |)") - export.add_argument("--traverse", action="store_true", help="Export settings for all images of a dataset (default: just first one)") - - impo.add_argument("--sep", default="|", help="Separator character (default: |)") - impo.add_argument("--dataset", action="store_true", help="Rendering settings should be applied to the whole dataset") - impo.add_argument("--spw", action="store_true", help="If target is Screen/Plate/Well (NOT SUPPORTED YET)") + export.add_argument("--traverse", action="store_true", + help="Export settings for all images of a " + "dataset (default: just first one)") + impo.add_argument("--dataset", action="store_true", + help="Rendering settings should be applied to " + "the whole dataset") + impo.add_argument("--spw", action="store_true", + help="If target is Screen/Plate/Well " + "(NOT SUPPORTED YET)") def _lookup(self, gateway, type, oid): # TODO: move _lookup to a _configure type @@ -542,7 +575,6 @@ def __info(self, args): finally: img._closeRE() - def _get_datasets_for_image(self, gateway, img_id): """ Get all datasets an image is part of. @@ -560,7 +592,6 @@ def _get_datasets_for_image(self, gateway, img_id): datasets.append(link.parent) return datasets - @gateway_required def impo(self, args): """Implements the impo(rt) command""" @@ -573,7 +604,8 @@ def impo(self, args): ds_query = "select d from Dataset d where d.name = :name" params = omero.sys.ParametersI() params.map['name'] = rstring(ds_name) - datasets = self.gateway.getQueryService().findAllByQuery(ds_query, params) + datasets = self.gateway.getQueryService().findAllByQuery(ds_query, + params) if not datasets: self.ctx.err(f"No dataset with name {ds_name} found.") return @@ -592,19 +624,22 @@ def impo(self, args): params = omero.sys.ParametersI() params.map['name1'] = rstring(ds_name) params.map['name2'] = rstring(img_name) - images = self.gateway.getQueryService().findAllByQuery(img_query, params) + images = self.gateway.getQueryService().findAllByQuery(img_query, + params) if not images: - self.ctx.err(f"No image with name {img_name} found within a dataset {ds_name}.") + self.ctx.err(f"No image with name {img_name} found " + f"within a dataset {ds_name}.") return elif len(images) > 1: - self.ctx.dbg("More than one images found, using latest one.") - target_img = max(images, key=lambda img: img.getId().getValue()) + self.ctx.dbg("More than one images found, " + "using latest one.") + target_img = max(images, + key=lambda img: img.getId().getValue()) else: target_img = images[0] set_args.object = target_img self.set(set_args) - def __do_export(self, ds, img): """ Exports the rendering settings of an image to a file @@ -629,7 +664,6 @@ def __do_export(self, ds, img): finally: img._closeRE() - @gateway_required def export(self, args): """Implements the export command""" @@ -651,9 +685,11 @@ def export(self, args): elif isinstance(args.object, Image): img = self._lookup(self.gateway, "Image", args.object.id) - ds = self._get_datasets_for_image(self.gateway, args.object.id.getValue()) + ds = self._get_datasets_for_image(self.gateway, + args.object.id.getValue()) if len(ds) > 1: - self.ctx.out("Warning, more than one dataset found, using latest one") + self.ctx.out("Warning, more than one dataset found, using " + "latest one") res = max(ds, key=lambda d: d.getId().getValue()) else: res = ds[0] @@ -661,8 +697,8 @@ def export(self, args): self.__do_export(res, img) else: - self.ctx.err(f"{args.object.__class__.__name__} not supported yet ") - + self.ctx.err(f"{args.object.__class__.__name__} not supported " + f"yet") @gateway_required def copy(self, args): From b5b62c28704c7c74fc7f793706a8ac8e220cf455 Mon Sep 17 00:00:00 2001 From: Dominik Lindner Date: Fri, 22 Nov 2024 09:39:14 +0000 Subject: [PATCH 4/6] Add intellj stuff to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 95c4bf4..1066c8c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ omero-cli-render-* .*un~ *.pyc .omero +*.iml +.idea + From 3fc04e098e7db94f736d8df7fc1ea05c3c59878a Mon Sep 17 00:00:00 2001 From: Dominik Lindner Date: Mon, 25 Nov 2024 12:52:50 +0000 Subject: [PATCH 5/6] Add missing import --- src/omero_cli_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/omero_cli_render.py b/src/omero_cli_render.py index e4300a7..3d2d15e 100755 --- a/src/omero_cli_render.py +++ b/src/omero_cli_render.py @@ -22,6 +22,7 @@ import time import json import yaml +import omero from functools import wraps From cce8000887478954fb00ebc06def7cc353669051 Mon Sep 17 00:00:00 2001 From: Dominik Lindner Date: Tue, 3 Dec 2024 12:15:44 +0000 Subject: [PATCH 6/6] Add warning about Screen/Plate not supported --- src/omero_cli_render.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/omero_cli_render.py b/src/omero_cli_render.py index 3d2d15e..83ce7c0 100755 --- a/src/omero_cli_render.py +++ b/src/omero_cli_render.py @@ -150,6 +150,8 @@ EXPORT_HELP = """Export rendering settings as yaml files +Note: Does not for Screen/Plate yet! + Examples: Export one yaml file for each Dataset in a Project (first image) (into ./DATASET_NAME/IMAGE_NAME.yml): @@ -162,6 +164,8 @@ IMPO_HELP = """Import rendering settings as yaml files +Note: Does not for Screen/Plate yet! + Yaml files have to be in directories with the name of the dataset, i.e. ./DATASET_NAME/IMAGE_NAME.yml !