From 21e4424c9ad8b0bd740a12fe625bb2528f0fe197 Mon Sep 17 00:00:00 2001 From: Michele Bortolomeazzi <38500587+MicheleBortol@users.noreply.github.com> Date: Mon, 4 Nov 2024 09:43:12 +0100 Subject: [PATCH 1/4] Bump to version 1.1.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index da3b0cd..39085f1 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="omero-vitessce", - version="1.1.2", + version="1.1.3", description="OMERO Vitessce multimodal data viewer plugin for OMERO.web", long_description=open('README.md').read(), long_description_content_type='text/markdown', From 8c213ad1cb1c8c8f4495f79673bcf1c20b468d10 Mon Sep 17 00:00:00 2001 From: Michele Bortolomeazzi <38500587+MicheleBortol@users.noreply.github.com> Date: Mon, 4 Nov 2024 09:44:09 +0100 Subject: [PATCH 2/4] Check ROIs and add support for Rects. --- omero_vitessce/utils.py | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/omero_vitessce/utils.py b/omero_vitessce/utils.py index 0846dd9..904b1c0 100644 --- a/omero_vitessce/utils.py +++ b/omero_vitessce/utils.py @@ -17,6 +17,10 @@ # Get the address of omeroweb from the config SERVER = omero_vitessce_settings.SERVER_ADDRESS[1:-1] +# Valid ROI shapes for representing cells +VALID_SHAPES = ["ome.model.roi.Polygon_roi", + "ome.model.roi.Rectangle_roi"] + def get_files_images(obj_type, obj_id, conn): """ Gets all the non config files attached to an object, @@ -168,28 +172,41 @@ def add_cell_identities(config_args, vc_dataset): class VitessceShape(): """ - Converts an OMERO ROI shape to a vitessce compatibel represetnation + Converts an OMERO ROI shape to a vitessce compatible representation https://github.com/will-moore/omero-vitessce/blob/master/omero_vitessce/views.py """ def __init__(self, shape): - self.shape = self.to_shapely(shape) + self.type = shape.ROI + self.polygon = self.to_shapely(shape) self.name = shape.getTextValue().getValue() def to_shapely(self, omero_shape): encoder = get_encoder(omero_shape.__class__) shape_json = encoder.encode(omero_shape) - if "Points" in shape_json: + coords = [] + + if self.type == "ome.model.roi.Polygon_roi": xy = shape_json["Points"].split(" ") - coords = [] for coord in xy: c = coord.split(",") coords.append((float(c[0]), float(c[1]))) - return Polygon(coords) + + elif self.type == "ome.model.roi.Rectangle_roi": + # X and Y are the top-left corner + x = omero_shape.getX().getValue() + y = omero_shape.getY().getValue() + w = omero_shape.getWidth().getValue() + h = omero_shape.getHeight().getValue() + coords.append((x, y)) + coords.append((x + w, y)) + coords.append((x + w, y + h)) + coords.append((x, y + h)) + return Polygon(coords) def poly(self): # Use 2 to get e.g. 8 points from 56. - return list(self.shape.simplify(2).exterior.coords) + return list(self.polygon.simplify(2).exterior.coords) def process_rois(img_ids, conn): @@ -203,7 +220,12 @@ def process_rois(img_ids, conn): if not img: continue rois += img.getROIs() - shapes = [VitessceShape(r.getShape(0)) for r in rois] + shapes = [r.getShape(0) for r in rois] + # Exclude non closed shapes + shapes = filter(lambda x: x.ROI in VALID_SHAPES, shapes) + # Exclude shapes whithout a text value = No Cell ID + shapes = filter(lambda x: x.getTextValue() is not None, shapes) + shapes = [VitessceShape(s) for s in shapes] return shapes From f8b2da8104989b7605cc4920628f3780de579e57 Mon Sep 17 00:00:00 2001 From: Michele Bortolomeazzi <38500587+MicheleBortol@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:18:11 +0100 Subject: [PATCH 3/4] Add rectangles to test. --- test/integration/test_rois.py | 40 ++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/test/integration/test_rois.py b/test/integration/test_rois.py index 1319acf..1750484 100644 --- a/test/integration/test_rois.py +++ b/test/integration/test_rois.py @@ -2,14 +2,14 @@ import pytest import omero.model -from omero.rtypes import rstring +from omero.rtypes import rstring, rdouble from omeroweb.testlib import IWebTest from omero.gateway import BlitzGateway from omero_vitessce import utils -def add_roi(img, points, name, conn): +def add_roi_polygon(img, points, name, conn): polygon = omero.model.PolygonI() polygon.points = rstring(points) polygon.setTextValue(rstring(name)) @@ -20,6 +20,20 @@ def add_roi(img, points, name, conn): return roi.getId().getValue() +def add_roi_rectangle(img, x, y, w, h, name, conn): + rect = omero.model.RectangleI() + rect.setX(rdouble(x)) + rect.setY(rdouble(y)) + rect.setWidth(rdouble(w)) + rect.setHeight(rdouble(h)) + rect.setTextValue(rstring(name)) + roi = omero.model.RoiI() + roi.addShape(rect) + roi.setImage(img._obj) + roi = conn.getUpdateService().saveAndReturnObject(roi) + return roi.getId().getValue() + + class TestConfig(IWebTest): """Tests loading the index page.""" USER_NAME = "test_user" @@ -42,17 +56,27 @@ def rois(self, conn): """make two mock ROIs and link them to Image:1 and Image:2""" rois = [] img = conn.getObject("Image", 1) - rois.append(add_roi(img, "10,20, 50,150, 200,200, 250,75", "A", conn)) + rois.append( + add_roi_polygon(img, "10,20, 200,200, 250,75 300,250, 75,400", + "A", conn)) + rois.append(add_roi_polygon(img, "10,20, 50,150, 250,75", "B", conn)) img = conn.getObject("Image", 2) - rois.append(add_roi(img, "10,20, 50,150, 250,75", "B", conn)) + rois.append(add_roi_rectangle(img, 0, 0, 10, 5, "C", conn)) + rois.append(add_roi_rectangle(img, 3333, 6666, 1000, 2000, "D", conn)) yield rois conn.deleteObjects("Roi", rois) def test_rois(self, conn, rois): shapes = utils.process_rois([1, 2], conn) - obs_dict = utils.make_cell_json(shapes) # We get floats from OMERO - exp_dict = {"A": [(10.0, 20.0), (50.0, 150.0), - (200.0, 200.0), (250.0, 75.0), (10.0, 20.0)], + obs_dict = utils.make_cell_json(shapes) # OMERO uses floats + exp_dict = {"A": [(10.0, 20.0), (200.0, 200.0), + (250.0, 75.0), (300.0, 250.0), + (75.0, 400.0), (10.0, 20.0)], "B": [(10.0, 20.0), (50.0, 150.0), - (250.0, 75.0), (10.0, 20.0)]} + (250.0, 75.0), (10.0, 20.0)], + "C": [(0.0, 0.0), (10.0, 0.0), + (10.0, 5.0), (0.0, 5.0), (0.0, 0.0)], + "D": [(3333.0, 6666.0), (4333.0, 6666.0), + (4333.0, 8666.0), (3333.0, 8666.0), + (3333.0, 6666.0)]} assert obs_dict == exp_dict From aca25c014ca7fa9775a003e3d565ad535ea94d66 Mon Sep 17 00:00:00 2001 From: Michele Bortolomeazzi Date: Mon, 4 Nov 2024 10:58:41 +0100 Subject: [PATCH 4/4] Add more dtails on ROIS --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 36cdaf8..a1aeef7 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ The following fields are available: - `Config file name` (optional, "VitessceConfig-YYYY.MM.DD_HHMMSS.json"): Name of the config file to attach, a ".json" extension is added if missing. - `Images` (required): OMERO Image(s) to view, assumes the same pixel size for all images. - `Segmentation` (optional, `None`): Label image to overlay on the image, pixel values correspond to cell identities. -- `ROIs` (optional, `False`): Use the ROIs from OMERO as a cell segmentation. Assumes 1 polygon shape per ROI, whose text value is the cell identity. +- `ROIs` (optional, `False`): Use the ROIs from OMERO as a cell segmentation. Assumes 1 polygon/rectangle shape per ROI, whose text value is the cell identity. - `Cell identities` (optional, `None`): `.csv` file with at least 2 columns: `Cell id column` and `Label column` defined in the 2 fields below. - `Cell id column` (optional, "cell_id"): Name of the `Cell id column` used in `Cell identities`, `Expression`, `Embeddings`. - `Label column` (optional, "label"): Name of the `Label` used in `Cell identities`. @@ -121,6 +121,9 @@ The `Embeddings` file is necessary to show the cells in a scatterplot. The `Molecules` file is used to overlay molecules on the image. All molecules are displayed and selecting by gene is not yet possible. The `Status` panel will be empty unless a `Segmentation` or `Embeddings` are provided. +The `ROIs` are used as cells. Only the first shape is considered, and only polygons and rectangles are kept (TO DO: add support for ellipses). Other shapes are skipped. +The text value of the shape is used as a cell identity to link with expression/labels/embeddings, if there is no text value the ROI is skipped. + #### Attaching preexisting config files Custom config files should have a `.json` extension and added as attachements to a dataset or an image. The configuration files does not need to refer to the dataset / image it is attached to and can refer to other images.