From 37da1aa1d1f6143b33c3369a0cd6a345d59aeb92 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 21 Apr 2021 12:57:54 +0100 Subject: [PATCH 01/18] Add --ignore_datasets option to idr_get_map_annotations.py --- maintenance/scripts/delete_ROIs.py | 2 +- .../scripts/idr_get_map_annotations.py | 59 +++++++++++++------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/maintenance/scripts/delete_ROIs.py b/maintenance/scripts/delete_ROIs.py index bacb84ac..d58c845c 100644 --- a/maintenance/scripts/delete_ROIs.py +++ b/maintenance/scripts/delete_ROIs.py @@ -30,7 +30,7 @@ def run(name, password, dataset_name, dataset_id, host, port): conn.connect() roi_service = conn.getRoiService() datasets = [] - if dataset_id >= 0: + if int(dataset_id) >= 0: datasets.append(conn.getObject("Dataset", dataset_id)) else: datasets = conn.getObjects("Dataset", diff --git a/maintenance/scripts/idr_get_map_annotations.py b/maintenance/scripts/idr_get_map_annotations.py index 0d20b8ae..d3513826 100644 --- a/maintenance/scripts/idr_get_map_annotations.py +++ b/maintenance/scripts/idr_get_map_annotations.py @@ -48,13 +48,21 @@ def get_idr_datasets_as_dict(project_id): return by_name -def get_idr_images_as_dict(dataset_id): +def get_idr_images_as_dict(obj_id, dtype="Dataset"): """Get a dict of {name: {id: 1}} for Images in IDR Dataset.""" - url = webclient_api_url + "images/?id=%s" % dataset_id - images = session.get(url).json()['images'] by_name = {} - for i in images: - by_name[i['name']] = i + if dtype == "Dataset": + dataset_ids = [obj_id] + elif dtype == "Project": + datasets_url = webclient_api_url + "datasets/?id=%s" % obj_id + datasets = session.get(datasets_url).json()['datasets'] + dataset_ids = [d['id'] for d in datasets] + print('dataset_ids', dataset_ids) + for dataset_id in dataset_ids: + url = webclient_api_url + "images/?id=%s" % dataset_id + images = session.get(url).json()['images'] + for i in images: + by_name[i['name']] = i return by_name @@ -73,19 +81,22 @@ def get_idr_wells_as_grid(plate_id): return session.get(url).json()['grid'] -def annotate_project(conn, local_id, idr_id): +def annotate_project(conn, local_id, idr_id, ignore_datasets=False): project = conn.getObject("Project", local_id) - idr_datasets = get_idr_datasets_as_dict(idr_id) + idr_images = {} + if ignore_datasets: + idr_images = get_idr_images_as_dict(idr_id, "Project") + else: + idr_datasets = get_idr_datasets_as_dict(idr_id) for dataset in project.listChildren(): - print("\n\nDataset", dataset.id, dataset.name) - # Get IDR Dataset with same name: - idr_dataset = idr_datasets.get(dataset.name) - if idr_dataset is None: - print(" NO IDR Dataset found!") - continue - - idr_images = get_idr_images_as_dict(idr_dataset['id']) + if not ignore_datasets: + # Get IDR Dataset with same name: + idr_dataset = idr_datasets.get(dataset.name) + if idr_dataset is None: + print(" NO IDR Dataset found!") + continue + idr_images = get_idr_images_as_dict(idr_dataset['id']) for image in dataset.listChildren(): print("Image", image.id, image.name) @@ -138,7 +149,16 @@ def annotate_plate(conn, plate, idr_plate_id): add_new_map_anns(conn, well, url) -def run(username, password, idr_obj, local_obj, host, port): +def run(args): + + username = args.username + password = args.password + idr_obj = args.idr_obj + local_obj = args.local_obj + host = args.server + port = args.port + ignore_datasets = args.ignore_datasets + print('ignore_datasets', ignore_datasets) conn = BlitzGateway(username, password, host=host, port=port) try: @@ -155,7 +175,7 @@ def run(username, password, idr_obj, local_obj, host, port): return local_id = local_obj.split(':')[1] if dtype == 'Project': - annotate_project(conn, local_id, idr_id) + annotate_project(conn, local_id, idr_id, ignore_datasets) elif dtype == 'Plate': plate = conn.getObject('Plate', local_id) annotate_plate(conn, plate, idr_id) @@ -183,12 +203,13 @@ def main(args): parser.add_argument('password') parser.add_argument('idr_obj', help=obj_help), parser.add_argument('local_obj', help=obj_help) + parser.add_argument('--ignore_datasets', action='store_true', + help="Can ignore Dataset names IF Images have unique names in Project") parser.add_argument('--server', default="workshop.openmicroscopy.org", help="OMERO server hostname") parser.add_argument('--port', default=4064, help="OMERO server port") args = parser.parse_args(args) - run(args.username, args.password, args.idr_obj, args.local_obj, - args.server, args.port) + run(args) if __name__ == '__main__': From dc0352ebd0ca1e3fea3ff73f223e5ca1995f8eba Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 21 Apr 2021 13:31:40 +0100 Subject: [PATCH 02/18] flake8 fix --- maintenance/scripts/idr_get_map_annotations.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/maintenance/scripts/idr_get_map_annotations.py b/maintenance/scripts/idr_get_map_annotations.py index d3513826..a6ead8d4 100644 --- a/maintenance/scripts/idr_get_map_annotations.py +++ b/maintenance/scripts/idr_get_map_annotations.py @@ -203,8 +203,9 @@ def main(args): parser.add_argument('password') parser.add_argument('idr_obj', help=obj_help), parser.add_argument('local_obj', help=obj_help) - parser.add_argument('--ignore_datasets', action='store_true', - help="Can ignore Dataset names IF Images have unique names in Project") + parser.add_argument( + '--ignore_datasets', action='store_true', + help="Can ignore Dataset names IF Images have unique names in Project") parser.add_argument('--server', default="workshop.openmicroscopy.org", help="OMERO server hostname") parser.add_argument('--port', default=4064, help="OMERO server port") From 8ddbc5ddb440f1038a7ace0eff2c98ca64f0851b Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 21 Apr 2021 14:03:14 +0100 Subject: [PATCH 03/18] Add --use_stain option to channel_names_from_maps.py --- .../scripts/channel_names_from_maps.py | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/maintenance/scripts/channel_names_from_maps.py b/maintenance/scripts/channel_names_from_maps.py index 156d40af..c1e0863b 100644 --- a/maintenance/scripts/channel_names_from_maps.py +++ b/maintenance/scripts/channel_names_from_maps.py @@ -27,7 +27,16 @@ MAP_KEY = "Channels" -def run(username, password, project_id, host, port): +def run(args): + + username = args.username + password = args.password + project_id = args.project_id + host = args.server + port = args.port + use_stain = args.use_stain + + token_index = 0 if use_stain else 1 conn = BlitzGateway(username, password, host=host, port=port) try: @@ -51,7 +60,12 @@ def run(username, password, project_id, host, port): print("Channels", channels) name_dict = {} for c, ch_name in enumerate(channels): - name_dict[c + 1] = ch_name.split(":")[1] + tokens = ch_name.split(":") + if len(tokens) > token_index: + label = tokens[token_index] + else: + label = ch_name + name_dict[c + 1] = label conn.setChannelNames("Image", [image.id], name_dict, channelCount=None) except Exception as exc: @@ -65,11 +79,15 @@ def main(args): parser.add_argument('username') parser.add_argument('password') parser.add_argument('project_id') + parser.add_argument( + '--use_stain', action='store_true', + help="""Map Ann Channels are in the form stain:label, e.g. DAPI:DNA. +If use_stain, channels will be named with the stain instead of the label""") parser.add_argument('--server', default="workshop.openmicroscopy.org", help="OMERO server hostname") parser.add_argument('--port', default=4064, help="OMERO server port") args = parser.parse_args(args) - run(args.username, args.password, args.project_id, args.server, args.port) + run(args) if __name__ == '__main__': From a6bc39980cb1c550aefb3610d99441c4638faa39 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 21 Apr 2021 17:54:52 +0100 Subject: [PATCH 04/18] channel_names_from_maps.py has --add_map_anns option --- .../scripts/channel_names_from_maps.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/maintenance/scripts/channel_names_from_maps.py b/maintenance/scripts/channel_names_from_maps.py index c1e0863b..fca2b559 100644 --- a/maintenance/scripts/channel_names_from_maps.py +++ b/maintenance/scripts/channel_names_from_maps.py @@ -20,12 +20,18 @@ # Script uses map annotations on each Image to rename channels import argparse -from omero.gateway import BlitzGateway +from omero.gateway import BlitzGateway, MapAnnotationWrapper NAMESPACE = "openmicroscopy.org/omero/bulk_annotations" MAP_KEY = "Channels" +def create_map_ann(conn, obj, key_value_data): + map_ann = MapAnnotationWrapper(conn) + map_ann.setValue(key_value_data) + map_ann.setNs('from.channels.keyvaluepair') + map_ann.save() + obj.linkAnnotation(map_ann) def run(args): @@ -35,6 +41,7 @@ def run(args): host = args.server port = args.port use_stain = args.use_stain + add_map_anns = args.add_map_anns token_index = 0 if use_stain else 1 @@ -59,8 +66,13 @@ def run(args): channels = values[0].split("; ") print("Channels", channels) name_dict = {} + key_value_pairs = [] for c, ch_name in enumerate(channels): tokens = ch_name.split(":") + if add_map_anns and len(tokens) > 1: + key_value_pairs.extend( + [["Ch%s_Stain" % c, tokens[0]], ["Ch%s_Label" % c, tokens[1]]] + ) if len(tokens) > token_index: label = tokens[token_index] else: @@ -68,6 +80,8 @@ def run(args): name_dict[c + 1] = label conn.setChannelNames("Image", [image.id], name_dict, channelCount=None) + if len(key_value_pairs) > 0: + create_map_ann(conn, image, key_value_pairs) except Exception as exc: print("Error while changing names: %s" % str(exc)) finally: @@ -83,6 +97,10 @@ def main(args): '--use_stain', action='store_true', help="""Map Ann Channels are in the form stain:label, e.g. DAPI:DNA. If use_stain, channels will be named with the stain instead of the label""") + parser.add_argument( + '--add_map_anns', action='store_true', + help="""Create new Map Anns of the form +Ch1_Stain:DAPI, Ch1_Label:DNA etc using the Channels Key-Value Pair""") parser.add_argument('--server', default="workshop.openmicroscopy.org", help="OMERO server hostname") parser.add_argument('--port', default=4064, help="OMERO server port") From 1ba26e1ec3898e0caeff529ef3ef334202b171e6 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 21 Apr 2021 18:08:02 +0100 Subject: [PATCH 05/18] Flake8 fix --- maintenance/scripts/channel_names_from_maps.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/maintenance/scripts/channel_names_from_maps.py b/maintenance/scripts/channel_names_from_maps.py index fca2b559..0ed5c70f 100644 --- a/maintenance/scripts/channel_names_from_maps.py +++ b/maintenance/scripts/channel_names_from_maps.py @@ -26,6 +26,7 @@ NAMESPACE = "openmicroscopy.org/omero/bulk_annotations" MAP_KEY = "Channels" + def create_map_ann(conn, obj, key_value_data): map_ann = MapAnnotationWrapper(conn) map_ann.setValue(key_value_data) @@ -33,8 +34,8 @@ def create_map_ann(conn, obj, key_value_data): map_ann.save() obj.linkAnnotation(map_ann) -def run(args): +def run(args): username = args.username password = args.password project_id = args.project_id @@ -71,7 +72,8 @@ def run(args): tokens = ch_name.split(":") if add_map_anns and len(tokens) > 1: key_value_pairs.extend( - [["Ch%s_Stain" % c, tokens[0]], ["Ch%s_Label" % c, tokens[1]]] + [["Ch%s_Stain" % c, tokens[0]], + ["Ch%s_Label" % c, tokens[1]]] ) if len(tokens) > token_index: label = tokens[token_index] From 46acd053a4c88fe6f9019a58ed923a0ae3200a69 Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 26 Apr 2021 09:51:57 +0100 Subject: [PATCH 06/18] calibrate_images.py target supports Project:ID --- maintenance/scripts/calibrate_images.py | 112 ++++++++++++++++++------ 1 file changed, 86 insertions(+), 26 deletions(-) diff --git a/maintenance/scripts/calibrate_images.py b/maintenance/scripts/calibrate_images.py index 5654a6c2..7a7ca574 100644 --- a/maintenance/scripts/calibrate_images.py +++ b/maintenance/scripts/calibrate_images.py @@ -33,9 +33,50 @@ from omero.model.enums import UnitsLength -def run(password, target, host, port): - - for i in range(1, 51): +def setPixelSize(conn, image, value, unit): + print('Image', image.id, value) + value_z = None + if 'x' in value: + value_xy = float(value.split('x')[0]) + value_z = float(value.split('x')[1]) + else: + value_xy = float(value) + xy = omero.model.LengthI(value_xy, getattr(UnitsLength, unit)) + p = image.getPrimaryPixels()._obj + p.setPhysicalSizeX(xy) + p.setPhysicalSizeY(xy) + if value_z is not None: + z = omero.model.LengthI(value_z, getattr(UnitsLength, unit)) + p.setPhysicalSizeZ(z) + conn.getUpdateService().saveObject(p) + + +def set_by_target_id(password, username, target, host, port, value, unit): + + conn = BlitzGateway(username, password, host=host, port=port) + conn.connect() + target_type = target.split(':')[0] + target_id = int(target.split(':')[1]) + if target_type not in ['Project', 'Dataset', 'Image']: + print("Target must be Project:ID, Dataset:ID or Image:ID") + return + images = [] + if target_type == "Project": + for dataset in conn.getObject('Project', target_id).listChildren(): + images.extend((list(dataset.listChildren()))) + elif target_type == "Dataset": + images = list(conn.getObject('Dataset', target_id).listChildren()) + elif target_type == "Image": + images = [conn.getObject("Image", target_id)] + print('images', images) + + for image in images: + setPixelSize(conn, image, value, unit) + + +def set_for_users_by_dataset_name(password, target, host, port, value, unit): + + for i in range(1, 2): username = "user-%s" % i print(username) @@ -56,42 +97,61 @@ def run(password, target, host, port): dataset_obj = dataset[0] datasetId = dataset[0].getId().getValue() - print('dataset', datasetId) - params2 = omero.sys.ParametersI() - params2.addId(dataset_obj.getId()) - query = "select l.child.id from DatasetImageLink \ - l where l.parent.id = :id" - images = service.projection(query, params2, conn.SERVICE_OPTS) - values = [] - for k in range(0, len(images)): - - image_id = images[k][0].getValue() - image = conn.getObject("Image", image_id) - - u = omero.model.LengthI(0.33, UnitsLength.MICROMETER) - p = image.getPrimaryPixels()._obj - p.setPhysicalSizeX(u) - p.setPhysicalSizeY(u) - values.append(p) - - if len(images) > 0: - conn.getUpdateService().saveArray(values) + for image in conn.getObject("Dataset", datasetId).listChildren(): + setPixelSize(conn, image, value, unit) except Exception as exc: print("Error during calibration: %s" % str(exc)) finally: conn.close() +def run(args): + password = args.password + target = args.target + value = args.value + unit = args.unit + host = args.server + port = args.port + + # Handle target is e.g. "Project:1" + if ':' in target: + try: + target_id = int(target.split(':')[1]) + set_by_target_id(password, args.user, target, host, port, value, unit) + return + except ValueError: + print("Not valid Project or Dataset ID") + + # Assume that target was a Dataset Name + set_for_users_by_dataset_name(password, target, host, port, value, unit) def main(args): + """ + The script sets Pixels Sizes in 2 use-cases. + Each needs a pixel size 'value' eg. 0.85 for X and Y or '0.85x0.2' for XY and Z + Units are optional. Default is "MICROMETER". + + 1) For many users 'user-1...user-50' etc with a NAMED Dataset + $ calibrate_images.py [password] [dataset_name] [value] --server [server] + + 2) For a single user, where the target is Project:ID or Dataset:ID + $ calibrate_images.py [password] [target] [value] --user [username] --server [server] + """ parser = argparse.ArgumentParser() parser.add_argument('password') - parser.add_argument('target') - parser.add_argument('--server', default="workshop.openmicroscopy.org", + parser.add_argument( + 'target', + help="Dataset name (for many users) or target Project/Dataset/Image:ID") + parser.add_argument('value', help="Pixel size value") + parser.add_argument('--unit', default="MICROMETER", + help="Unit from omero.") + parser.add_argument('--user', help="Username ONLY if single user") + parser.add_argument('--server', default="localhost", help="OMERO server hostname") parser.add_argument('--port', default=4064, help="OMERO server port") args = parser.parse_args(args) - run(args.password, args.target, args.server, args.port) + + run(args) if __name__ == '__main__': From c321d2e87b96996a63446b1de8c617c598bfc12d Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 11 May 2021 14:45:55 +0100 Subject: [PATCH 07/18] simple_frap_with_figure.py labels in milliseconds --- practical/python/server/simple_frap_with_figure.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/practical/python/server/simple_frap_with_figure.py b/practical/python/server/simple_frap_with_figure.py index 8d24c30a..eb295550 100644 --- a/practical/python/server/simple_frap_with_figure.py +++ b/practical/python/server/simple_frap_with_figure.py @@ -208,8 +208,8 @@ def create_omero_figure(conn, images, plots): panel_x = (col * (panel_height + spacing)) + margin j = get_panel_json(image, panel_x, panel_y, panel_width, panel_height, the_t) - # Add timestamp in 'secs' to top-left of each movie frame - j['labels'] = [{"time": "secs", + # Add timestamp in 'milliseconds' to top-left of each movie frame + j['labels'] = [{"time": "milliseconds", "size": "12", "position": "topleft", "color": "FFFFFF"}] From b90cd4bb4cd445e7977e17a4c3532cda8f49f13f Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 26 May 2021 16:26:31 +0100 Subject: [PATCH 08/18] Add figure_image_table_shape_heatmap.js --- .../figure_image_table_shape_heatmap.js | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 practical/javascript/figure_image_table_shape_heatmap.js diff --git a/practical/javascript/figure_image_table_shape_heatmap.js b/practical/javascript/figure_image_table_shape_heatmap.js new file mode 100644 index 00000000..2ef94f69 --- /dev/null +++ b/practical/javascript/figure_image_table_shape_heatmap.js @@ -0,0 +1,60 @@ + +// +// Copyright (C) 2021 University of Dundee & Open Microscopy Environment. +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Using the browser devtools to manipulate OMERO.figure is an experimental +// feature for developers. +// N.B.: Never paste untrusted code into your browser console. +// +// This script requires an OMERO.table linked to the Image +// with a 'Shape' column and another numerical named column (edit below). +// Select panel with Shapes added from OMERO (shape.id should be in the table). +// This colors each Shape according to a heatmap for the named column. + +async function shapeData(panel) { + const shapeIds = panel.get("shapes").map(s => s.id).filter(id => id > 0); + console.log('shapeIds', shapeIds); + let vals_by_shape = {}; + for (let i = 0; i < shapeIds.length; i++) { + // Load one at a time - more reliable + let url = `/webgateway/table/Image/${panel.get('imageId')}/query/?query=Shape-${shapeIds[i]}`; + let r = await fetch(url).then(rsp => rsp.json()); + let colIndex = r.data?.columns?.indexOf("Centroids_RAW_X"); + let shapeIndex = r.data?.columns?.indexOf("Shape"); + if (colIndex && shapeIndex && r.data?.rows.length > 0) { + console.log("Value", r.data.rows[0][colIndex]); + vals_by_shape[r.data.rows[0][shapeIndex]] = r.data.rows[0][colIndex]; + } + }; + // Once all loaded, we can calculate range and assign colours to shapes + const values = Object.values(vals_by_shape); + let minVal = Math.min(...values); + let valRange = Math.max(...values) - minVal; + console.log('min, range', minVal, valRange); + const new_shapes = panel.get("shapes").map(shape => { + // hide any shapes we don't have data for + if (!vals_by_shape[shape.id]) return { ...shape, strokeWidth: 0.01 }; + let value = (vals_by_shape[shape.id] - minVal) / valRange; + let red = parseInt(value * 255).toString(16); + let blue = (255 - parseInt(value * 255)).toString(16); + red = red.length == 1 ? `0` + red : red; + blue = blue.length == 1 ? `0` + blue : blue; + return { ...shape, strokeColor: `#${red}00${blue}`, strokeWidth: 5 } + }); + panel.set('shapes', new_shapes); +} +figureModel.getSelected().forEach(shapeData); From 95946fb178bd7833237ca0411671df8a63fc215c Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 26 May 2021 16:26:57 +0100 Subject: [PATCH 09/18] Add preparation/idr0079-data-prep.md --- maintenance/preparation/idr0079-data-prep.md | 103 +++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 maintenance/preparation/idr0079-data-prep.md diff --git a/maintenance/preparation/idr0079-data-prep.md b/maintenance/preparation/idr0079-data-prep.md new file mode 100644 index 00000000..2e131766 --- /dev/null +++ b/maintenance/preparation/idr0079-data-prep.md @@ -0,0 +1,103 @@ + +Workshop data preparation (idr0079) +=================================== + +This document details the steps to prepare data from idr0079 for use in an OMERO.parade +and OMERO.figure workshop. + + +IDR data import +=============== + +For OME team, steps for in-place import of IDR data can be found at +https://docs.google.com/document/d/18cLSvUKVn8jEp7KSW7e48NvuxHT_mp3tyh98loTTOYY/edit + +For other users, you will need to have Docker installed. +This container uses Aspera to download the data from EBI: + + $ docker run --rm -v /tmp:/data imagedata/download idr0021 . /data/ + +Clone https://github.com/IDR/idr0079-hartmann-lateralline and edit +```experimentA/idr0079-experimentA-filePaths.tsv``` +to point ALL paths at the location of the data downloaded above. + +If you don't want to use in-place import, comment out this line in +```experimentA/idr0079-experimentA-bulk.yml```: + + transfer: "ln_s" + + +Do the bulk import: + + $ cd experimentA/ + $ omero import --bulk idr0079-experimentA-bulk.yml + + +Add Map Annotations from IDR +============================ + +Use the script [idr_get_map_annotations.py](../scripts/idr_get_map_annotations.py) with the ID of +the 'idr0079-hartmann-lateralline/experimentA' Project created above and the corresponding +Project on IDR (1102): + + $ python idr_get_map_annotations.py username password Project:1102 Project:1301 --server localhost + +This will get map annotations from all Images in `idr0079-hartmann-lateralline/experimentA` and +create identical map annotations on the corresponding Images. + + +Rename Channels from Map Annotations +==================================== + +We can now use the map annotations to rename channels on all images. +Run the [channel_names_from_maps.py](../scripts/channel_names_from_maps.py) +script on the local data, using the local Project ID. +We are using the `stain` to name channels, e.g. `NLStdTomato`, and also adding +map annotations for inidividual channels to use for label creation in OMERO.figure: + + $ python channel_names_from_maps.py username password 1301 --server localhost --use_stain --add_map_anns + + +Manual Annotations and Rendering Settings +========================================= + +Add 5* rating to 1 image each from membranes_actin, membranes_cisgolgi, membranes_nuclei, membranes_recendo. + +Set rendering settings: Channel-1: Green, Channel-2: Red. Maybe adjust levels too. + +Set Pixel Sizes +========================================== + +See the commands used for IDR at [idr0079_voxel_sizes.sh] +(https://github.com/will-moore/idr0079-hartmann-lateralline/blob/voxel_sizes/scripts/idr0079_voxel_sizes.sh) +and run these commands on the local server, using the appropriate Dataset IDs. + +Copy Masks to Polygons +====================== + +The idr0079 images in IDR have Masks, but we want to use Polygons in OMERO.figure. +Use the [copy_masks_2_polygons.py](../scripts/copy_masks_2_polygons.py) script to +convert. NB: requires `skimage`. We want to copy to each of the 5-star rated +images above, in turn, from the ID of the corresponding Image to the local Image ID + + # login to IDR + $ omero login + # copy to local server + $ python copy_masks_2_polygons.py username password server FROM_IMAGE_ID TO_IMAGE_ID + +Create OMERO.tables +=================== + +We use the tsv files in https://github.com/IDR/idr0079-hartmann-lateralline to create +an OMERO.table on the Project, with one row per Image, summarising the stats for all the +ROIs in that Image. This uses [csv_to_table_on_project.py](https://github.com/will-moore/idr0079-hartmann-lateralline/blob/csv_to_tables_scripts/scripts/csv_to_table_on_project.py) + +Clone the github repo, then: + + $ cd idr0079-hartmann-lateralline + $ python scripts/csv_to_table_on_project.py + +We then create an OMERO.table on each Image that has ROIs added above: +Use the optional `--name NAME` to run on a single named Image: + + $ python scripts/csv_to_roi_table From ffba0da9cd0cc353a7d15a9167b9c463f149e59f Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 26 May 2021 16:34:46 +0100 Subject: [PATCH 10/18] Add copy_masks_2_polygons.py --- maintenance/scripts/copy_masks_2_polygons.py | 176 +++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 maintenance/scripts/copy_masks_2_polygons.py diff --git a/maintenance/scripts/copy_masks_2_polygons.py b/maintenance/scripts/copy_masks_2_polygons.py new file mode 100644 index 00000000..9ec67124 --- /dev/null +++ b/maintenance/scripts/copy_masks_2_polygons.py @@ -0,0 +1,176 @@ + +import omero +from omero.gateway import BlitzGateway +from omero.rtypes import rint, rstring + +from skimage import morphology +from skimage import measure + +import argparse +import sys +import numpy as np + +import omero +import omero.clients +from omero.rtypes import unwrap, rint, rstring +from omero.cli import cli_login +from omero.api import RoiOptions +from omero.gateway import BlitzGateway + + +def mask_to_binim_yx(mask): + """ + # code from omero-cli-zarr + :param mask MaskI: An OMERO mask + + :return: tuple of + - Binary mask + - (T, C, Z, Y, X, w, h) tuple of mask settings (T, C, Z may be + None) + """ + + t = unwrap(mask.theT) + c = unwrap(mask.theC) + z = unwrap(mask.theZ) + + x = int(mask.x.val) + y = int(mask.y.val) + w = int(mask.width.val) + h = int(mask.height.val) + + mask_packed = mask.getBytes() + # convert bytearray into something we can use + intarray = np.fromstring(mask_packed, dtype=np.uint8) + binarray = np.unpackbits(intarray) #.astype(self.dtype) + # truncate and reshape + binarray = np.reshape(binarray[: (w * h)], (h, w)) + + return binarray, (t, c, z, y, x, h, w) + + +def rgba_to_int(red, green, blue, alpha=255): + """ Return the color as an Integer in RGBA encoding """ + r = red << 24 + g = green << 16 + b = blue << 8 + a = alpha + rgba_int = r+g+b+a + if (rgba_int > (2**31-1)): # convert to signed 32-bit int + rgba_int = rgba_int - 2**32 + return rgba_int + + +def get_longest_contour(contours): + contour = contours[0] + for c in contours: + if len(c) > len(contour): + contour = c + return c + + +def add_polygon(roi, contour, x_offset=0, y_offset=0, z=None, t=None): + """ points is 2D list of [[x, y], [x, y]...]""" + + stride = 4 + coords = [] + # points in contour are adjacent pixels, which is too verbose + # take every nth point + for count, xy in enumerate(contour): + if count%stride == 0: + coords.append(xy) + if len(coords) < 2: + return + points = ["%s,%s" % (xy[1] + x_offset, xy[0] + y_offset) for xy in coords] + points = ", ".join(points) + + polygon = omero.model.PolygonI() + if z is not None: + polygon.theZ = rint(z) + if t is not None: + polygon.theT = rint(t) + polygon.strokeColor = rint(rgba_to_int(255, 255, 255)) + # points = "10,20, 50,150, 200,200, 250,75" + polygon.points = rstring(points) + roi.addShape(polygon) + + +def main(argv): + parser = argparse.ArgumentParser() + parser.add_argument('username2', help='Target server Username') + parser.add_argument('password2', help='Target server Password') + parser.add_argument('server2', help='Target server') + parser.add_argument('imageid', type=int, help=( + 'Copy ROIs FROM this image')) + parser.add_argument('imageid2', type=int, help=( + 'Copy ROIs TO this image')) + args = parser.parse_args(argv) + + to_image_id = args.imageid2 + + PAGE_SIZE = 50 + + with cli_login() as cli: + conn = BlitzGateway(client_obj=cli._client) + conn2 = BlitzGateway(args.username2, args.password2, + port=4064, host=args.server2) + conn2.connect() + + roi_service = conn.getRoiService() + update_service = conn2.getUpdateService() + + opts = RoiOptions() + offset = 0 + opts.offset = rint(offset) + opts.limit = rint(PAGE_SIZE) + + conn.SERVICE_OPTS.setOmeroGroup(-1) + image = conn.getObject('Image', args.imageid) + size_x = image.getSizeX() + size_y = image.getSizeY() + print(image.name) + + # NB: we repeat this query below for each 'page' of ROIs + result = roi_service.findByImage(args.imageid, opts, conn.SERVICE_OPTS) + + while len(result.rois) > 0: + print("offset", offset) + print("Found ROIs:", len(result.rois)) + for roi in result.rois: + + new_roi = omero.model.RoiI() + new_roi.setImage(omero.model.ImageI(to_image_id, False)) + shapes_added = False + + for shape in roi.copyShapes(): + + if not isinstance(shape, omero.model.MaskI): + continue + # assume shape is a Mask + np_mask, dims = mask_to_binim_yx(shape) + print('shape', np_mask.shape) + t, c, z, y, x, h, w = dims + print('dims', dims) + image = np.zeros((size_y, size_x)) + image[y:y+h, x:x+w] = np_mask + contours = measure.find_contours(image, 0.5) + print('Found contours:', len(contours)) + if len(contours) > 0: + contour = get_longest_contour(contours) + # Only add 1 Polygon per Mask Shape. + # First is usually the longest + add_polygon(new_roi, contour, 0, 0, z, t) + shapes_added = True + + if shapes_added: + update_service.saveObject(new_roi) + + offset += PAGE_SIZE + opts.offset = rint(offset) + result = roi_service.findByImage(args.imageid, opts, conn.SERVICE_OPTS) + + conn2.close() + +if __name__ == '__main__': + # First, login to source OMERO $ omero login + # Copy to target server: $ python copy_masks_2_polygons.py username password server FROM_IMAGE TO_IMAGE + main(sys.argv[1:]) From 3a851474dbc60f08101ce3ffbfe4ae91879770b7 Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 27 May 2021 15:01:58 +0100 Subject: [PATCH 11/18] flake8 fixes --- maintenance/scripts/calibrate_images.py | 21 +++++++++++--------- maintenance/scripts/copy_masks_2_polygons.py | 18 ++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/maintenance/scripts/calibrate_images.py b/maintenance/scripts/calibrate_images.py index 7a7ca574..453d2a34 100644 --- a/maintenance/scripts/calibrate_images.py +++ b/maintenance/scripts/calibrate_images.py @@ -95,7 +95,6 @@ def set_for_users_by_dataset_name(password, target, host, port, value, unit): print("No dataset with name %s found" % target) continue - dataset_obj = dataset[0] datasetId = dataset[0].getId().getValue() print('dataset', datasetId) for image in conn.getObject("Dataset", datasetId).listChildren(): @@ -105,6 +104,7 @@ def set_for_users_by_dataset_name(password, target, host, port, value, unit): finally: conn.close() + def run(args): password = args.password target = args.target @@ -116,8 +116,9 @@ def run(args): # Handle target is e.g. "Project:1" if ':' in target: try: - target_id = int(target.split(':')[1]) - set_by_target_id(password, args.user, target, host, port, value, unit) + int(target.split(':')[1]) + set_by_target_id(password, args.user, target, host, port, + value, unit) return except ValueError: print("Not valid Project or Dataset ID") @@ -125,26 +126,28 @@ def run(args): # Assume that target was a Dataset Name set_for_users_by_dataset_name(password, target, host, port, value, unit) + def main(args): """ - The script sets Pixels Sizes in 2 use-cases. - Each needs a pixel size 'value' eg. 0.85 for X and Y or '0.85x0.2' for XY and Z + The script sets Pixels Sizes in 2 use-cases. + Each needs a pixel size 'value' eg. 0.85 for X and Y or + '0.85x0.2' for XY and Z. Units are optional. Default is "MICROMETER". 1) For many users 'user-1...user-50' etc with a NAMED Dataset - $ calibrate_images.py [password] [dataset_name] [value] --server [server] + $ calibrate_images.py [password] [dataset_name] [value] --server [host] 2) For a single user, where the target is Project:ID or Dataset:ID - $ calibrate_images.py [password] [target] [value] --user [username] --server [server] + calibrate_images.py [pass] [target] [value] --user [name] --server [host] """ parser = argparse.ArgumentParser() parser.add_argument('password') parser.add_argument( 'target', - help="Dataset name (for many users) or target Project/Dataset/Image:ID") + help="Dataset name (for many users) or Project/Dataset/Image:ID") parser.add_argument('value', help="Pixel size value") parser.add_argument('--unit', default="MICROMETER", - help="Unit from omero.") + help="Unit from omero.") parser.add_argument('--user', help="Username ONLY if single user") parser.add_argument('--server', default="localhost", help="OMERO server hostname") diff --git a/maintenance/scripts/copy_masks_2_polygons.py b/maintenance/scripts/copy_masks_2_polygons.py index 9ec67124..c308d1a6 100644 --- a/maintenance/scripts/copy_masks_2_polygons.py +++ b/maintenance/scripts/copy_masks_2_polygons.py @@ -1,9 +1,4 @@ -import omero -from omero.gateway import BlitzGateway -from omero.rtypes import rint, rstring - -from skimage import morphology from skimage import measure import argparse @@ -12,10 +7,10 @@ import omero import omero.clients +from omero.gateway import BlitzGateway from omero.rtypes import unwrap, rint, rstring from omero.cli import cli_login from omero.api import RoiOptions -from omero.gateway import BlitzGateway def mask_to_binim_yx(mask): @@ -41,7 +36,7 @@ def mask_to_binim_yx(mask): mask_packed = mask.getBytes() # convert bytearray into something we can use intarray = np.fromstring(mask_packed, dtype=np.uint8) - binarray = np.unpackbits(intarray) #.astype(self.dtype) + binarray = np.unpackbits(intarray) # truncate and reshape binarray = np.reshape(binarray[: (w * h)], (h, w)) @@ -76,7 +71,7 @@ def add_polygon(roi, contour, x_offset=0, y_offset=0, z=None, t=None): # points in contour are adjacent pixels, which is too verbose # take every nth point for count, xy in enumerate(contour): - if count%stride == 0: + if count % stride == 0: coords.append(xy) if len(coords) < 2: return @@ -166,11 +161,14 @@ def main(argv): offset += PAGE_SIZE opts.offset = rint(offset) - result = roi_service.findByImage(args.imageid, opts, conn.SERVICE_OPTS) + result = roi_service.findByImage(args.imageid, opts, + conn.SERVICE_OPTS) conn2.close() + if __name__ == '__main__': # First, login to source OMERO $ omero login - # Copy to target server: $ python copy_masks_2_polygons.py username password server FROM_IMAGE TO_IMAGE + # Copy to target server, with Image IDs + # $ python copy_masks_2_polygons.py user pass server FROM_IID TO_IID main(sys.argv[1:]) From b42bbfc3153ce97e3b9eb3ac6bd9b1fe1778dbf3 Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 27 May 2021 15:05:41 +0100 Subject: [PATCH 12/18] Typo fixes --- maintenance/preparation/idr0079-data-prep.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/maintenance/preparation/idr0079-data-prep.md b/maintenance/preparation/idr0079-data-prep.md index 2e131766..35a0832a 100644 --- a/maintenance/preparation/idr0079-data-prep.md +++ b/maintenance/preparation/idr0079-data-prep.md @@ -15,7 +15,7 @@ https://docs.google.com/document/d/18cLSvUKVn8jEp7KSW7e48NvuxHT_mp3tyh98loTTOYY/ For other users, you will need to have Docker installed. This container uses Aspera to download the data from EBI: - $ docker run --rm -v /tmp:/data imagedata/download idr0021 . /data/ + $ docker run --rm -v /tmp:/data imagedata/download idr0079 . /data/ Clone https://github.com/IDR/idr0079-hartmann-lateralline and edit ```experimentA/idr0079-experimentA-filePaths.tsv``` @@ -53,7 +53,7 @@ We can now use the map annotations to rename channels on all images. Run the [channel_names_from_maps.py](../scripts/channel_names_from_maps.py) script on the local data, using the local Project ID. We are using the `stain` to name channels, e.g. `NLStdTomato`, and also adding -map annotations for inidividual channels to use for label creation in OMERO.figure: +map annotations for individual channels to use for label creation in OMERO.figure: $ python channel_names_from_maps.py username password 1301 --server localhost --use_stain --add_map_anns @@ -101,3 +101,4 @@ We then create an OMERO.table on each Image that has ROIs added above: Use the optional `--name NAME` to run on a single named Image: $ python scripts/csv_to_roi_table + From 33b8386f590c28d5d68594e77d9c85dbcc29bcc1 Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 21 Jun 2021 17:59:37 +0100 Subject: [PATCH 13/18] copy_masks_2_polygons.py handles Dataset or Image --- maintenance/scripts/copy_masks_2_polygons.py | 161 ++++++++++++------- 1 file changed, 99 insertions(+), 62 deletions(-) diff --git a/maintenance/scripts/copy_masks_2_polygons.py b/maintenance/scripts/copy_masks_2_polygons.py index c308d1a6..92c20b81 100644 --- a/maintenance/scripts/copy_masks_2_polygons.py +++ b/maintenance/scripts/copy_masks_2_polygons.py @@ -12,6 +12,7 @@ from omero.cli import cli_login from omero.api import RoiOptions +PAGE_SIZE = 10 def mask_to_binim_yx(mask): """ @@ -63,6 +64,13 @@ def get_longest_contour(contours): return c +def image_ids_by_name(dataset): + ids_by_name = {} + for image in dataset.listChildren(): + ids_by_name[image.name] = image.id + return ids_by_name + + def add_polygon(roi, contour, x_offset=0, y_offset=0, z=None, t=None): """ points is 2D list of [[x, y], [x, y]...]""" @@ -89,86 +97,115 @@ def add_polygon(roi, contour, x_offset=0, y_offset=0, z=None, t=None): roi.addShape(polygon) +def process_image(conn, conn2, image, to_image_id): + roi_service = conn.getRoiService() + update_service = conn2.getUpdateService() + + print("Processing...", image.name, image.id) + + old = conn2.getRoiService().findByImage(to_image_id, None, conn2.SERVICE_OPTS) + if len(old.rois) > 0: + print("Image", to_image_id, "already has ROIs. Ignoring...") + return + + opts = RoiOptions() + offset = 0 + opts.offset = rint(offset) + opts.limit = rint(PAGE_SIZE) + + size_x = image.getSizeX() + size_y = image.getSizeY() + + # NB: we repeat this query below for each 'page' of ROIs + result = roi_service.findByImage(image.id, opts, conn.SERVICE_OPTS) + + while len(result.rois) > 0: + print("offset", offset) + print("Found ROIs:", len(result.rois)) + for roi in result.rois: + + new_roi = omero.model.RoiI() + new_roi.setImage(omero.model.ImageI(to_image_id, False)) + shapes_added = False + + for shape in roi.copyShapes(): + + if not isinstance(shape, omero.model.MaskI): + continue + # assume shape is a Mask + np_mask, dims = mask_to_binim_yx(shape) + t, c, z, y, x, h, w = dims + plane = np.zeros((size_y, size_x)) + plane[y:y+h, x:x+w] = np_mask + contours = measure.find_contours(plane, 0.5) + print('Found contours:', len(contours)) + if len(contours) > 0: + contour = get_longest_contour(contours) + # Only add 1 Polygon per Mask Shape. + # First is usually the longest + add_polygon(new_roi, contour, 0, 0, z, t) + shapes_added = True + + if shapes_added: + update_service.saveObject(new_roi) + + offset += PAGE_SIZE + opts.offset = rint(offset) + result = roi_service.findByImage(image.id, opts, conn.SERVICE_OPTS) + + def main(argv): parser = argparse.ArgumentParser() parser.add_argument('username2', help='Target server Username') parser.add_argument('password2', help='Target server Password') parser.add_argument('server2', help='Target server') - parser.add_argument('imageid', type=int, help=( - 'Copy ROIs FROM this image')) - parser.add_argument('imageid2', type=int, help=( - 'Copy ROIs TO this image')) + parser.add_argument('source', help=( + 'Copy ROIs FROM this: Image:ID or Dataset:ID')) + parser.add_argument('target', help=( + 'Copy ROIs TO this: Image:ID or Dataset:ID')) args = parser.parse_args(argv) - to_image_id = args.imageid2 - - PAGE_SIZE = 50 with cli_login() as cli: conn = BlitzGateway(client_obj=cli._client) + conn.SERVICE_OPTS.setOmeroGroup(-1) + conn2 = BlitzGateway(args.username2, args.password2, port=4064, host=args.server2) conn2.connect() - roi_service = conn.getRoiService() - update_service = conn2.getUpdateService() - - opts = RoiOptions() - offset = 0 - opts.offset = rint(offset) - opts.limit = rint(PAGE_SIZE) - - conn.SERVICE_OPTS.setOmeroGroup(-1) - image = conn.getObject('Image', args.imageid) - size_x = image.getSizeX() - size_y = image.getSizeY() - print(image.name) - - # NB: we repeat this query below for each 'page' of ROIs - result = roi_service.findByImage(args.imageid, opts, conn.SERVICE_OPTS) - - while len(result.rois) > 0: - print("offset", offset) - print("Found ROIs:", len(result.rois)) - for roi in result.rois: - - new_roi = omero.model.RoiI() - new_roi.setImage(omero.model.ImageI(to_image_id, False)) - shapes_added = False - - for shape in roi.copyShapes(): - - if not isinstance(shape, omero.model.MaskI): - continue - # assume shape is a Mask - np_mask, dims = mask_to_binim_yx(shape) - print('shape', np_mask.shape) - t, c, z, y, x, h, w = dims - print('dims', dims) - image = np.zeros((size_y, size_x)) - image[y:y+h, x:x+w] = np_mask - contours = measure.find_contours(image, 0.5) - print('Found contours:', len(contours)) - if len(contours) > 0: - contour = get_longest_contour(contours) - # Only add 1 Polygon per Mask Shape. - # First is usually the longest - add_polygon(new_roi, contour, 0, 0, z, t) - shapes_added = True - - if shapes_added: - update_service.saveObject(new_roi) - - offset += PAGE_SIZE - opts.offset = rint(offset) - result = roi_service.findByImage(args.imageid, opts, - conn.SERVICE_OPTS) + source_images = [] + target_image_ids = [] + + source = args.source + source_id = int(source.split(":")[1]) + target = args.target + target_id = int(target.split(":")[1]) + + if source.startswith('Image:'): + source_images.append(conn.getObject('Image', source_id)) + target_image_ids.append(target_id) + elif source.startswith('Dataset:'): + dataset = conn.getObject('Dataset', source_id) + target_dataset = conn2.getObject('Dataset', target_id) + ids_by_name = image_ids_by_name(target_dataset) + for image in dataset.listChildren(): + if image.name in ids_by_name: + source_images.append(image) + target_image_ids.append(ids_by_name[image.name]) + else: + print("Source needs to be Image:ID or Dataset:ID") + + print("Processing", source_images) + print("...to target images:", target_image_ids) + for image, to_target_id in zip(source_images, target_image_ids): + process_image(conn, conn2, image, to_target_id) conn2.close() if __name__ == '__main__': # First, login to source OMERO $ omero login - # Copy to target server, with Image IDs - # $ python copy_masks_2_polygons.py user pass server FROM_IID TO_IID + # Copy to target server, with source and target. e.g. Image:123 Image:456 + # $ python copy_masks_2_polygons.py user pass server FROM_TARGET TO_TARGET main(sys.argv[1:]) From b79e0e5d75055c622bbb905ccb373ebfdc6ad4bc Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 22 Jun 2021 12:12:19 +0100 Subject: [PATCH 14/18] Update idr0079-data-prep.md --- maintenance/preparation/idr0079-data-prep.md | 19 ++- .../idr0079_csv_to_table_on_project.py | 161 ++++++++++++++++++ 2 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 maintenance/scripts/idr0079_csv_to_table_on_project.py diff --git a/maintenance/preparation/idr0079-data-prep.md b/maintenance/preparation/idr0079-data-prep.md index 35a0832a..e3b82603 100644 --- a/maintenance/preparation/idr0079-data-prep.md +++ b/maintenance/preparation/idr0079-data-prep.md @@ -77,28 +77,29 @@ Copy Masks to Polygons The idr0079 images in IDR have Masks, but we want to use Polygons in OMERO.figure. Use the [copy_masks_2_polygons.py](../scripts/copy_masks_2_polygons.py) script to -convert. NB: requires `skimage`. We want to copy to each of the 5-star rated -images above, in turn, from the ID of the corresponding Image to the local Image ID +convert. NB: requires `skimage`. We can process an Image or a Dataset at a time: # login to IDR $ omero login - # copy to local server - $ python copy_masks_2_polygons.py username password server FROM_IMAGE_ID TO_IMAGE_ID + + # copy from IDR e.g Dataset:1 to local server TARGET e.g. Dataset:2 + $ python copy_masks_2_polygons.py username password server Dataset:1 Dataset:2 Create OMERO.tables =================== We use the tsv files in https://github.com/IDR/idr0079-hartmann-lateralline to create an OMERO.table on the Project, with one row per Image, summarising the stats for all the -ROIs in that Image. This uses [csv_to_table_on_project.py](https://github.com/will-moore/idr0079-hartmann-lateralline/blob/csv_to_tables_scripts/scripts/csv_to_table_on_project.py) +ROIs in that Image. This uses [idr0079_csv_to_table_on_project.py](../scripts/idr0079_csv_to_table_on_project.py) -Clone the github repo, then: +Clone the `idr0079-hartmann-lateralline` github repo, then: $ cd idr0079-hartmann-lateralline - $ python scripts/csv_to_table_on_project.py + $ python /path/to/training-scripts/maintenance/scripts/csv_to_table_on_project.py We then create an OMERO.table on each Image that has ROIs added above: Use the optional `--name NAME` to run on a single named Image: - $ python scripts/csv_to_roi_table - + $ cd idr0079-hartmann-lateralline + # process ALL raw images (use --name NAME to process 1 image) + $ python scripts/csv_to_roi_table.py diff --git a/maintenance/scripts/idr0079_csv_to_table_on_project.py b/maintenance/scripts/idr0079_csv_to_table_on_project.py new file mode 100644 index 00000000..7c425ccd --- /dev/null +++ b/maintenance/scripts/idr0079_csv_to_table_on_project.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python + +import pandas +import csv +import mimetypes +import os + +import omero.clients +import omero.cli +import omero +from omero_metadata.populate import ParsingContext +from omero.util.metadata_utils import NSBULKANNOTATIONSRAW + + +project_name = "idr0079-hartmann-lateralline/experimentA" + +# NB: Need to checkout https://github.com/IDR/idr0079-hartmann-lateralline +# and run this from in idr0079-hartmann-lateralline directory OR update +# tables_path to point to that repo. + +tables_path = "./experimentA/idr0079_experimentA_extracted_measurements/%s/" +# Lots of tsv files to choose from... +# e.g. Tissue Frame Of Reference Primary Component Analysis measured: +# tables_path += "%s_shape_TFOR_pca_measured.tsv" +# e.g. Other Measurements: +tables_path += "%s_other_measurements.tsv" + + +def get_omero_col_type(col_name): + """Returns s for string, d for double, l for long/int""" + if "Name" in col_name: + return "s" + return "d" + + +def populate_metadata(project, dict_data): + + csv_columns = list(dict_data[0].keys()) + # Dataset Name, Image Name, PC... + csv_columns.sort() + # header s,s,d,d,d,... + col_types = [get_omero_col_type(name) for name in csv_columns] + header = f"# header {','.join(col_types)}\n" + print('header', header) + csv_file = "other_measurements_summaries.csv" + print("writing to", csv_file) + with open(csv_file, 'w') as csvfile: + # header s,s,d,l,s + csvfile.write(header) + writer = csv.DictWriter(csvfile, fieldnames=csv_columns) + writer.writeheader() + for data in dict_data: + writer.writerow(data) + + # Links the csv file to the project and parses it to create OMERO.table + mt = mimetypes.guess_type(csv_file, strict=False)[0] + fileann = conn.createFileAnnfromLocalFile( + csv_file, mimetype=mt, ns=NSBULKANNOTATIONSRAW + ) + fileid = fileann.getFile().getId() + project.linkAnnotation(fileann) + client = project._conn.c + ctx = ParsingContext( + client, project._obj, fileid=fileid, file=csv_file, allow_nan=True + ) + ctx.parse() + + +def process_image(image): + + # Read csv for each image + image_name = image.name + table_pth = tables_path % (image_name, image_name) + print('table_pth', table_pth) + df = pandas.read_csv(table_pth, delimiter="\t") + + cols = ["Source Name", + "Cell ID", + "Centroids RAW X", + "Centroids RAW Y", + "Centroids RAW Z", + "Centroids TFOR X", + "Centroids TFOR Y", + "Centroids TFOR Z", + "Longest Extension", + "Major Axis Length", + "Major/Medium Axis Eccentricity", + "Major/Minor Axis Eccentricity", + "Medium Axis Length", + "Medium/Minor Axis Eccentricity", + "Minor Axis Length", + "Orientation along X", + "Orientation along Y", + "Orientation along Z", + "Roundness (Smoothness)", + "Sphericity", + "Surface Area", + "Volume", + "X Axis Length", + "Y Axis Length", + "Y/X Aspect Ratio", + "Z Axis Length", + "Z/X Aspect Ratio", + "Z/Y Aspect Ratio"] + + summary = df.describe() + data = {'count': summary['Cell ID']['count']} + # get: RAW_Y_Range, RAW_X_Range, RAW_Z_Range + for dim in ['X', 'Y', 'Z']: + min_val = summary[f'Centroids RAW {dim}']['min'] + max_val = summary[f'Centroids RAW {dim}']['max'] + data[f'RAW_{dim}_Range'] = max_val - min_val + min_tfor = summary[f'Centroids TFOR {dim}']['min'] + max_tfor = summary[f'Centroids TFOR {dim}']['max'] + data[f'TFOR_{dim}_Range'] = max_tfor - min_tfor + + # Mean_Sphericity, Mean_Volume, Mean_Z_Axis_Length, Mean_X_Axis_Length + for col_name in ['Sphericity', 'Volume', 'X Axis Length', 'Y Axis Length', 'Z Axis Length']: + value = summary[col_name]['mean'] + data[f'Mean_{col_name}'.replace(' ', '_')] = value + + # For PC .tsf + # columns are named "PC 1", "PC 2" etc... + # for pc_id in range(1,4): + # for stat in ['count', 'mean', 'min', 'max', 'std']: + # # No spaces in OMERO.table col names! + # omero_table_colname = f"PC{pc_id}_{stat}" + # value = summary[f'PC {pc_id}'][stat] + # data[omero_table_colname] = value + + return data + + +def main(conn): + + project = conn.getObject("Project", attributes={"name": project_name}) + print("Project", project.id) + conn.SERVICE_OPTS.setOmeroGroup(project.getDetails().group.id.val) + data_rows = [] + # For each Image in Project, open the local CSV and summarise to one row + for dataset in project.listChildren(): + for image in dataset.listChildren(): + # ignore _seg images etc. + if '_' in image.name: + continue + dict_data = process_image(image) + dict_data['Dataset Name'] = dataset.name + dict_data['Image Name'] = image.name + data_rows.append(dict_data) + + populate_metadata(project, data_rows) + +# Usage: +# cd idr0079-hartmann-lateralline +# python scripts/csv_to_table_on_project.py + +if __name__ == "__main__": + with omero.cli.cli_login() as c: + conn = omero.gateway.BlitzGateway(client_obj=c.get_client()) + main(conn) + conn.close() From be45d3bc03e1970b450e1a5acba022c50361836e Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 1 Sep 2021 14:52:12 +0100 Subject: [PATCH 15/18] Revert --ignore_datasets changes to idr_get_map_annotations.py These changes weren't needed when all of idr0079 was imported on Training server --- .../scripts/idr_get_map_annotations.py | 60 ++++++------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/maintenance/scripts/idr_get_map_annotations.py b/maintenance/scripts/idr_get_map_annotations.py index a6ead8d4..0d20b8ae 100644 --- a/maintenance/scripts/idr_get_map_annotations.py +++ b/maintenance/scripts/idr_get_map_annotations.py @@ -48,21 +48,13 @@ def get_idr_datasets_as_dict(project_id): return by_name -def get_idr_images_as_dict(obj_id, dtype="Dataset"): +def get_idr_images_as_dict(dataset_id): """Get a dict of {name: {id: 1}} for Images in IDR Dataset.""" + url = webclient_api_url + "images/?id=%s" % dataset_id + images = session.get(url).json()['images'] by_name = {} - if dtype == "Dataset": - dataset_ids = [obj_id] - elif dtype == "Project": - datasets_url = webclient_api_url + "datasets/?id=%s" % obj_id - datasets = session.get(datasets_url).json()['datasets'] - dataset_ids = [d['id'] for d in datasets] - print('dataset_ids', dataset_ids) - for dataset_id in dataset_ids: - url = webclient_api_url + "images/?id=%s" % dataset_id - images = session.get(url).json()['images'] - for i in images: - by_name[i['name']] = i + for i in images: + by_name[i['name']] = i return by_name @@ -81,22 +73,19 @@ def get_idr_wells_as_grid(plate_id): return session.get(url).json()['grid'] -def annotate_project(conn, local_id, idr_id, ignore_datasets=False): +def annotate_project(conn, local_id, idr_id): project = conn.getObject("Project", local_id) - idr_images = {} - if ignore_datasets: - idr_images = get_idr_images_as_dict(idr_id, "Project") - else: - idr_datasets = get_idr_datasets_as_dict(idr_id) + idr_datasets = get_idr_datasets_as_dict(idr_id) for dataset in project.listChildren(): + print("\n\nDataset", dataset.id, dataset.name) - if not ignore_datasets: - # Get IDR Dataset with same name: - idr_dataset = idr_datasets.get(dataset.name) - if idr_dataset is None: - print(" NO IDR Dataset found!") - continue - idr_images = get_idr_images_as_dict(idr_dataset['id']) + # Get IDR Dataset with same name: + idr_dataset = idr_datasets.get(dataset.name) + if idr_dataset is None: + print(" NO IDR Dataset found!") + continue + + idr_images = get_idr_images_as_dict(idr_dataset['id']) for image in dataset.listChildren(): print("Image", image.id, image.name) @@ -149,16 +138,7 @@ def annotate_plate(conn, plate, idr_plate_id): add_new_map_anns(conn, well, url) -def run(args): - - username = args.username - password = args.password - idr_obj = args.idr_obj - local_obj = args.local_obj - host = args.server - port = args.port - ignore_datasets = args.ignore_datasets - print('ignore_datasets', ignore_datasets) +def run(username, password, idr_obj, local_obj, host, port): conn = BlitzGateway(username, password, host=host, port=port) try: @@ -175,7 +155,7 @@ def run(args): return local_id = local_obj.split(':')[1] if dtype == 'Project': - annotate_project(conn, local_id, idr_id, ignore_datasets) + annotate_project(conn, local_id, idr_id) elif dtype == 'Plate': plate = conn.getObject('Plate', local_id) annotate_plate(conn, plate, idr_id) @@ -203,14 +183,12 @@ def main(args): parser.add_argument('password') parser.add_argument('idr_obj', help=obj_help), parser.add_argument('local_obj', help=obj_help) - parser.add_argument( - '--ignore_datasets', action='store_true', - help="Can ignore Dataset names IF Images have unique names in Project") parser.add_argument('--server', default="workshop.openmicroscopy.org", help="OMERO server hostname") parser.add_argument('--port', default=4064, help="OMERO server port") args = parser.parse_args(args) - run(args) + run(args.username, args.password, args.idr_obj, args.local_obj, + args.server, args.port) if __name__ == '__main__': From 20cc22e40b33e03bb7e991021379253f04b3ed21 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 1 Sep 2021 15:06:39 +0100 Subject: [PATCH 16/18] Update idr0079-data-prep.md wrt merged PRs --- maintenance/preparation/idr0079-data-prep.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/maintenance/preparation/idr0079-data-prep.md b/maintenance/preparation/idr0079-data-prep.md index e3b82603..649ae3ad 100644 --- a/maintenance/preparation/idr0079-data-prep.md +++ b/maintenance/preparation/idr0079-data-prep.md @@ -69,8 +69,9 @@ Set Pixel Sizes ========================================== See the commands used for IDR at [idr0079_voxel_sizes.sh] -(https://github.com/will-moore/idr0079-hartmann-lateralline/blob/voxel_sizes/scripts/idr0079_voxel_sizes.sh) -and run these commands on the local server, using the appropriate Dataset IDs. +(https://github.com/IDR/idr0079-hartmann-lateralline/blob/master/scripts/idr0079_voxel_sizes.sh) +and run these commands on the local server, using the appropriate Dataset IDs, at least +for the Datasets you wish to use. Copy Masks to Polygons ====================== @@ -89,7 +90,7 @@ Create OMERO.tables =================== We use the tsv files in https://github.com/IDR/idr0079-hartmann-lateralline to create -an OMERO.table on the Project, with one row per Image, summarising the stats for all the +an OMERO.table on the Project (for use with OMERO.parade), with one row per Image, summarising the stats for all the ROIs in that Image. This uses [idr0079_csv_to_table_on_project.py](../scripts/idr0079_csv_to_table_on_project.py) Clone the `idr0079-hartmann-lateralline` github repo, then: @@ -97,9 +98,10 @@ Clone the `idr0079-hartmann-lateralline` github repo, then: $ cd idr0079-hartmann-lateralline $ python /path/to/training-scripts/maintenance/scripts/csv_to_table_on_project.py -We then create an OMERO.table on each Image that has ROIs added above: +We then create an OMERO.table on each Image that has ROIs added above, using +the `_other_measurements.tsv` table for each Image. Use the optional `--name NAME` to run on a single named Image: $ cd idr0079-hartmann-lateralline # process ALL raw images (use --name NAME to process 1 image) - $ python scripts/csv_to_roi_table.py + $ python scripts/csv_to_roi_table.py _other_measurements.tsv From df286ab55b772a95906e169a2de2299c500a7b9f Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 1 Sep 2021 15:07:55 +0100 Subject: [PATCH 17/18] Revert unused changes to calibrate_images.py --- maintenance/scripts/calibrate_images.py | 117 ++++++------------------ 1 file changed, 27 insertions(+), 90 deletions(-) diff --git a/maintenance/scripts/calibrate_images.py b/maintenance/scripts/calibrate_images.py index 453d2a34..5654a6c2 100644 --- a/maintenance/scripts/calibrate_images.py +++ b/maintenance/scripts/calibrate_images.py @@ -33,50 +33,9 @@ from omero.model.enums import UnitsLength -def setPixelSize(conn, image, value, unit): - print('Image', image.id, value) - value_z = None - if 'x' in value: - value_xy = float(value.split('x')[0]) - value_z = float(value.split('x')[1]) - else: - value_xy = float(value) - xy = omero.model.LengthI(value_xy, getattr(UnitsLength, unit)) - p = image.getPrimaryPixels()._obj - p.setPhysicalSizeX(xy) - p.setPhysicalSizeY(xy) - if value_z is not None: - z = omero.model.LengthI(value_z, getattr(UnitsLength, unit)) - p.setPhysicalSizeZ(z) - conn.getUpdateService().saveObject(p) - - -def set_by_target_id(password, username, target, host, port, value, unit): - - conn = BlitzGateway(username, password, host=host, port=port) - conn.connect() - target_type = target.split(':')[0] - target_id = int(target.split(':')[1]) - if target_type not in ['Project', 'Dataset', 'Image']: - print("Target must be Project:ID, Dataset:ID or Image:ID") - return - images = [] - if target_type == "Project": - for dataset in conn.getObject('Project', target_id).listChildren(): - images.extend((list(dataset.listChildren()))) - elif target_type == "Dataset": - images = list(conn.getObject('Dataset', target_id).listChildren()) - elif target_type == "Image": - images = [conn.getObject("Image", target_id)] - print('images', images) - - for image in images: - setPixelSize(conn, image, value, unit) - - -def set_for_users_by_dataset_name(password, target, host, port, value, unit): - - for i in range(1, 2): +def run(password, target, host, port): + + for i in range(1, 51): username = "user-%s" % i print(username) @@ -95,66 +54,44 @@ def set_for_users_by_dataset_name(password, target, host, port, value, unit): print("No dataset with name %s found" % target) continue + dataset_obj = dataset[0] datasetId = dataset[0].getId().getValue() + print('dataset', datasetId) - for image in conn.getObject("Dataset", datasetId).listChildren(): - setPixelSize(conn, image, value, unit) + params2 = omero.sys.ParametersI() + params2.addId(dataset_obj.getId()) + query = "select l.child.id from DatasetImageLink \ + l where l.parent.id = :id" + images = service.projection(query, params2, conn.SERVICE_OPTS) + values = [] + for k in range(0, len(images)): + + image_id = images[k][0].getValue() + image = conn.getObject("Image", image_id) + + u = omero.model.LengthI(0.33, UnitsLength.MICROMETER) + p = image.getPrimaryPixels()._obj + p.setPhysicalSizeX(u) + p.setPhysicalSizeY(u) + values.append(p) + + if len(images) > 0: + conn.getUpdateService().saveArray(values) except Exception as exc: print("Error during calibration: %s" % str(exc)) finally: conn.close() -def run(args): - password = args.password - target = args.target - value = args.value - unit = args.unit - host = args.server - port = args.port - - # Handle target is e.g. "Project:1" - if ':' in target: - try: - int(target.split(':')[1]) - set_by_target_id(password, args.user, target, host, port, - value, unit) - return - except ValueError: - print("Not valid Project or Dataset ID") - - # Assume that target was a Dataset Name - set_for_users_by_dataset_name(password, target, host, port, value, unit) - - def main(args): - """ - The script sets Pixels Sizes in 2 use-cases. - Each needs a pixel size 'value' eg. 0.85 for X and Y or - '0.85x0.2' for XY and Z. - Units are optional. Default is "MICROMETER". - - 1) For many users 'user-1...user-50' etc with a NAMED Dataset - $ calibrate_images.py [password] [dataset_name] [value] --server [host] - - 2) For a single user, where the target is Project:ID or Dataset:ID - calibrate_images.py [pass] [target] [value] --user [name] --server [host] - """ parser = argparse.ArgumentParser() parser.add_argument('password') - parser.add_argument( - 'target', - help="Dataset name (for many users) or Project/Dataset/Image:ID") - parser.add_argument('value', help="Pixel size value") - parser.add_argument('--unit', default="MICROMETER", - help="Unit from omero.") - parser.add_argument('--user', help="Username ONLY if single user") - parser.add_argument('--server', default="localhost", + parser.add_argument('target') + parser.add_argument('--server', default="workshop.openmicroscopy.org", help="OMERO server hostname") parser.add_argument('--port', default=4064, help="OMERO server port") args = parser.parse_args(args) - - run(args) + run(args.password, args.target, args.server, args.port) if __name__ == '__main__': From 083298950632037e33d725948ec59e5efb522cc2 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 1 Sep 2021 16:07:07 +0100 Subject: [PATCH 18/18] Remove figure_image_table_shape_heatmap.js This is now in omero-guide-figure --- .../figure_image_table_shape_heatmap.js | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 practical/javascript/figure_image_table_shape_heatmap.js diff --git a/practical/javascript/figure_image_table_shape_heatmap.js b/practical/javascript/figure_image_table_shape_heatmap.js deleted file mode 100644 index 2ef94f69..00000000 --- a/practical/javascript/figure_image_table_shape_heatmap.js +++ /dev/null @@ -1,60 +0,0 @@ - -// -// Copyright (C) 2021 University of Dundee & Open Microscopy Environment. -// All rights reserved. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -// Using the browser devtools to manipulate OMERO.figure is an experimental -// feature for developers. -// N.B.: Never paste untrusted code into your browser console. -// -// This script requires an OMERO.table linked to the Image -// with a 'Shape' column and another numerical named column (edit below). -// Select panel with Shapes added from OMERO (shape.id should be in the table). -// This colors each Shape according to a heatmap for the named column. - -async function shapeData(panel) { - const shapeIds = panel.get("shapes").map(s => s.id).filter(id => id > 0); - console.log('shapeIds', shapeIds); - let vals_by_shape = {}; - for (let i = 0; i < shapeIds.length; i++) { - // Load one at a time - more reliable - let url = `/webgateway/table/Image/${panel.get('imageId')}/query/?query=Shape-${shapeIds[i]}`; - let r = await fetch(url).then(rsp => rsp.json()); - let colIndex = r.data?.columns?.indexOf("Centroids_RAW_X"); - let shapeIndex = r.data?.columns?.indexOf("Shape"); - if (colIndex && shapeIndex && r.data?.rows.length > 0) { - console.log("Value", r.data.rows[0][colIndex]); - vals_by_shape[r.data.rows[0][shapeIndex]] = r.data.rows[0][colIndex]; - } - }; - // Once all loaded, we can calculate range and assign colours to shapes - const values = Object.values(vals_by_shape); - let minVal = Math.min(...values); - let valRange = Math.max(...values) - minVal; - console.log('min, range', minVal, valRange); - const new_shapes = panel.get("shapes").map(shape => { - // hide any shapes we don't have data for - if (!vals_by_shape[shape.id]) return { ...shape, strokeWidth: 0.01 }; - let value = (vals_by_shape[shape.id] - minVal) / valRange; - let red = parseInt(value * 255).toString(16); - let blue = (255 - parseInt(value * 255)).toString(16); - red = red.length == 1 ? `0` + red : red; - blue = blue.length == 1 ? `0` + blue : blue; - return { ...shape, strokeColor: `#${red}00${blue}`, strokeWidth: 5 } - }); - panel.set('shapes', new_shapes); -} -figureModel.getSelected().forEach(shapeData);