Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Rectangle ROIs #14

Merged
merged 4 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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.
Expand Down
36 changes: 29 additions & 7 deletions omero_vitessce/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand All @@ -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


Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
40 changes: 32 additions & 8 deletions test/integration/test_rois.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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"
Expand All @@ -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