Skill Tracer
Skills › Scientific Agent Skills › omero-integration › rois.md

rois.md

298 lines · skills/omero-integration/references/rois.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Regions of Interest (ROIs)
2
3ROIs and shape labels may encode phenotypes, diagnoses, sample identifiers, or
4analysis decisions. Export geometry and labels only within an explicit scope.
5

Regions of Interest (ROIs)

ROIs and shape labels may encode phenotypes, diagnoses, sample identifiers, or analysis decisions. Export geometry and labels only within an explicit scope.

Raw lines 1 to 5
6## Model Semantics
7
8The OMERO 5.6 line uses the June 2016 OME schema. The OME ROI model defines
9eight concrete 2D shape types:
11- Ellipse
12- Label
13- Line
14- Mask
15- Point
16- Polygon
17- Polyline
18- Rectangle
20Plane selectors are optional:
22- `TheZ`: z-section; absent means all z-sections
23- `TheT`: timepoint; absent means all timepoints
24- `TheC`: channel; absent means all channels
26Do not coerce absent values to zero. A 3D ROI is represented as a union of 2D
27shapes across planes; it is not a native volumetric mesh.
29Display fields include RGBA fill/stroke colors, stroke width, fill rule, dash
30array, text label, and font properties. Geometry and display metadata are
31distinct. A shape can also have an affine `Transform`; preserve its six
32coefficients (`A00`, `A01`, `A02`, `A10`, `A11`, `A12`) when exporting local
33coordinates. Ignoring it can move or distort a reconstructed ROI.

Model Semantics

The OMERO 5.6 line uses the June 2016 OME schema. The OME ROI model defines eight concrete 2D shape types:

  • Ellipse
  • Label
  • Line
  • Mask
  • Point
  • Polygon
  • Polyline
  • Rectangle

Plane selectors are optional:

  • TheZ: z-section; absent means all z-sections
  • TheT: timepoint; absent means all timepoints
  • TheC: channel; absent means all channels

Do not coerce absent values to zero. A 3D ROI is represented as a union of 2D shapes across planes; it is not a native volumetric mesh.

Display fields include RGBA fill/stroke colors, stroke width, fill rule, dash array, text label, and font properties. Geometry and display metadata are distinct. A shape can also have an affine Transform; preserve its six coefficients (A00, A01, A02, A10, A11, A12) when exporting local coordinates. Ignoring it can move or distort a reconstructed ROI.

Raw lines 6 to 34
35## Current Service Status
37The current Python examples still retrieve ROIs with:
39```python
40roi_service = conn.getRoiService()
41result = roi_service.findByImage(image_id, None, conn.SERVICE_OPTS)
42```
44However, the current OMERO Blitz API marks the `IRoi` interface deprecated.
45That means “still available but at removal risk,” not “already removed.” OME
46does not document a general replacement for every `IRoi` method in the pages
47reviewed for this snapshot.
49Therefore:
51- isolate `IRoi` use behind a small function;
52- do not build new broad workflows around legacy measurement-table helpers;
53- verify the target server's generated API before relying on it;
54- record this dependency in long-lived integrations.
56The official OMERO.web JSON API also documents paginated ROI listing by image:
57`/api/v0/m/rois/?image=<id>&limit=<n>&offset=<n>`. Its page cap is on
58ROIs, and each returned ROI still includes its child shapes. Use it only when the site
59exposes the documented `api` app over HTTPS and its authentication model is
60appropriate.

Current Service Status

The current Python examples still retrieve ROIs with:

roi_service = conn.getRoiService()
result = roi_service.findByImage(image_id, None, conn.SERVICE_OPTS)

However, the current OMERO Blitz API marks the IRoi interface deprecated. That means “still available but at removal risk,” not “already removed.” OME does not document a general replacement for every IRoi method in the pages reviewed for this snapshot.

Therefore:

  • isolate IRoi use behind a small function;
  • do not build new broad workflows around legacy measurement-table helpers;
  • verify the target server's generated API before relying on it;
  • record this dependency in long-lived integrations.

The official OMERO.web JSON API also documents paginated ROI listing by image: /api/v0/m/rois/?image=<id>&limit=<n>&offset=<n>. Its page cap is on ROIs, and each returned ROI still includes its child shapes. Use it only when the site exposes the documented api app over HTTPS and its authentication model is appropriate.

Raw lines 35 to 61
62## Bounded ROI Read
64`findByImage()` does not expose page arguments. Apply explicit image, ROI, and
65shape caps and report truncation:
67```python
68image_id = 123
69max_rois = 100
70max_shapes_per_roi = 500
72roi_service = conn.getRoiService()
73result = roi_service.findByImage(image_id, None, conn.SERVICE_OPTS)
74rois = list(result.rois)
76roi_truncated = len(rois) > max_rois
77for roi in rois[:max_rois]:
78 shapes = list(roi.copyShapes())
79 print(
80 {
81 "roi_id": roi.getId().getValue(),
82 "shape_count_returned": min(len(shapes), max_shapes_per_roi),
83 "shape_truncated": len(shapes) > max_shapes_per_roi,
84 }
85 )
86```
88The cap limits client processing/output, not necessarily server work:
89`findByImage()` has already loaded all matching ROIs and their shapes,
90including mask payloads. Masks are omitted from the JSON, not from that
91service response. For a known very
92large image, do not call it casually; consider the documented paginated JSON
93API or a site-reviewed query.
95The bundled exporter requires explicit image IDs and uses redaction defaults:
97```bash
98python -B scripts/export_image_metadata.py \
99 --image-id 123 \
100 --max-rois-per-image 100 \
101 --max-shapes-per-roi 500 \
102 --output ./image-123-metadata.json
103```
105Review the dry run, then add `--execute`. ROI labels remain redacted unless
106`--include-roi-labels` is specified. The exporter preserves the affine
107transform and flags `geometry_truncated` when a Points string exceeds the
108length cap. Such a record is an inventory, not reconstructable geometry.

Bounded ROI Read

findByImage() does not expose page arguments. Apply explicit image, ROI, and shape caps and report truncation:

image_id = 123
max_rois = 100
max_shapes_per_roi = 500

roi_service = conn.getRoiService()
result = roi_service.findByImage(image_id, None, conn.SERVICE_OPTS)
rois = list(result.rois)

roi_truncated = len(rois) > max_rois
for roi in rois[:max_rois]:
    shapes = list(roi.copyShapes())
    print(
        {
            "roi_id": roi.getId().getValue(),
            "shape_count_returned": min(len(shapes), max_shapes_per_roi),
            "shape_truncated": len(shapes) > max_shapes_per_roi,
        }
    )

The cap limits client processing/output, not necessarily server work: findByImage() has already loaded all matching ROIs and their shapes, including mask payloads. Masks are omitted from the JSON, not from that service response. For a known very large image, do not call it casually; consider the documented paginated JSON API or a site-reviewed query.

The bundled exporter requires explicit image IDs and uses redaction defaults:

python -B scripts/export_image_metadata.py \
  --image-id 123 \
  --max-rois-per-image 100 \
  --max-shapes-per-roi 500 \
  --output ./image-123-metadata.json

Review the dry run, then add --execute. ROI labels remain redacted unless --include-roi-labels is specified. The exporter preserves the affine transform and flags geometry_truncated when a Points string exceeds the length cap. Such a record is an inventory, not reconstructable geometry.

Raw lines 62 to 109
Runs code scripts/export_image_metadata.py :98
110## Reading Shape Fields
112Generated model values are usually wrapped in OMERO rtypes. Preserve `None`:
114```python
115def unwrap(value):
116 if value is None:
117 return None
118 getter = getattr(value, "getValue", None)
119 return getter() if callable(getter) else value
122for roi in result.rois[:max_rois]:
123 for shape in list(roi.copyShapes())[:max_shapes_per_roi]:
124 record = {
125 "id": unwrap(shape.getId()),
126 "the_z": unwrap(shape.getTheZ()),
127 "the_t": unwrap(shape.getTheT()),
128 "the_c": unwrap(shape.getTheC()),
129 "label_redacted": True,
130 }
131 print(record)
132```
134Use type checks before geometry access:
136```python
137import omero.model
139if isinstance(shape, omero.model.RectangleI):
140 geometry = {
141 "x": unwrap(shape.getX()),
142 "y": unwrap(shape.getY()),
143 "width": unwrap(shape.getWidth()),
144 "height": unwrap(shape.getHeight()),
145 }
146elif isinstance(shape, omero.model.EllipseI):
147 geometry = {
148 "x": unwrap(shape.getX()),
149 "y": unwrap(shape.getY()),
150 "radius_x": unwrap(shape.getRadiusX()),
151 "radius_y": unwrap(shape.getRadiusY()),
152 }
153elif isinstance(shape, omero.model.LineI):
154 geometry = {
155 "x1": unwrap(shape.getX1()),
156 "y1": unwrap(shape.getY1()),
157 "x2": unwrap(shape.getX2()),
158 "y2": unwrap(shape.getY2()),
159 }
160elif isinstance(shape, (omero.model.PolygonI, omero.model.PolylineI)):
161 geometry = {"points": unwrap(shape.getPoints())}
162```
164Validate coordinates against image dimensions. Preserve floating-point
165coordinates; do not truncate them to integers merely for JSON.
167For masks:
169- export position, dimensions, plane selectors, and byte count only by default;
170- do not serialize mask bytes into broad JSON;
171- treat decoded masks as pixel-derived data;
172- validate width/height and bit packing against the current model before
173 reconstructing.

Reading Shape Fields

Generated model values are usually wrapped in OMERO rtypes. Preserve None:

def unwrap(value):
    if value is None:
        return None
    getter = getattr(value, "getValue", None)
    return getter() if callable(getter) else value


for roi in result.rois[:max_rois]:
    for shape in list(roi.copyShapes())[:max_shapes_per_roi]:
        record = {
            "id": unwrap(shape.getId()),
            "the_z": unwrap(shape.getTheZ()),
            "the_t": unwrap(shape.getTheT()),
            "the_c": unwrap(shape.getTheC()),
            "label_redacted": True,
        }
        print(record)

Use type checks before geometry access:

import omero.model

if isinstance(shape, omero.model.RectangleI):
    geometry = {
        "x": unwrap(shape.getX()),
        "y": unwrap(shape.getY()),
        "width": unwrap(shape.getWidth()),
        "height": unwrap(shape.getHeight()),
    }
elif isinstance(shape, omero.model.EllipseI):
    geometry = {
        "x": unwrap(shape.getX()),
        "y": unwrap(shape.getY()),
        "radius_x": unwrap(shape.getRadiusX()),
        "radius_y": unwrap(shape.getRadiusY()),
    }
elif isinstance(shape, omero.model.LineI):
    geometry = {
        "x1": unwrap(shape.getX1()),
        "y1": unwrap(shape.getY1()),
        "x2": unwrap(shape.getX2()),
        "y2": unwrap(shape.getY2()),
    }
elif isinstance(shape, (omero.model.PolygonI, omero.model.PolylineI)):
    geometry = {"points": unwrap(shape.getPoints())}

Validate coordinates against image dimensions. Preserve floating-point coordinates; do not truncate them to integers merely for JSON.

For masks:

  • export position, dimensions, plane selectors, and byte count only by default;
  • do not serialize mask bytes into broad JSON;
  • treat decoded masks as pixel-derived data;
  • validate width/height and bit packing against the current model before reconstructing.
Raw lines 110 to 174
175## Creating an ROI Is a Write
177Current core-model pattern:
179```python
180import omero.model
181from omero.rtypes import rdouble, rint, rstring
183image = conn.getObject("Image", image_id)
184if image is None:
185 raise LookupError("Image unavailable")
187x = 50.0
188y = 100.0
189width = 200.0
190height = 150.0
191z = 0
192t = 0
194if x < 0 or y < 0 or x + width > image.getSizeX() or y + height > image.getSizeY():
195 raise ValueError("Rectangle is outside image bounds")
196if not 0 <= z < image.getSizeZ() or not 0 <= t < image.getSizeT():
197 raise ValueError("Plane index is outside image bounds")
199rectangle = omero.model.RectangleI()
200rectangle.setX(rdouble(x))
201rectangle.setY(rdouble(y))
202rectangle.setWidth(rdouble(width))
203rectangle.setHeight(rdouble(height))
204rectangle.setTheZ(rint(z))
205rectangle.setTheT(rint(t))
206rectangle.setTextValue(rstring("reviewed-region"))
208roi = omero.model.RoiI()
209roi.setImage(image._obj)
210roi.addShape(rectangle)
212saved = conn.getUpdateService().saveAndReturnObject(roi, conn.SERVICE_OPTS)
213print(saved.getId().getValue())
214```
216Before execution, confirm:
218- image ID, group, and dimensions;
219- shape count and coordinate system;
220- Z/T/C semantics;
221- label sensitivity;
222- write permission;
223- whether an existing ROI should be updated rather than duplicated.

Creating an ROI Is a Write

Current core-model pattern:

import omero.model
from omero.rtypes import rdouble, rint, rstring

image = conn.getObject("Image", image_id)
if image is None:
    raise LookupError("Image unavailable")

x = 50.0
y = 100.0
width = 200.0
height = 150.0
z = 0
t = 0

if x < 0 or y < 0 or x + width > image.getSizeX() or y + height > image.getSizeY():
    raise ValueError("Rectangle is outside image bounds")
if not 0 <= z < image.getSizeZ() or not 0 <= t < image.getSizeT():
    raise ValueError("Plane index is outside image bounds")

rectangle = omero.model.RectangleI()
rectangle.setX(rdouble(x))
rectangle.setY(rdouble(y))
rectangle.setWidth(rdouble(width))
rectangle.setHeight(rdouble(height))
rectangle.setTheZ(rint(z))
rectangle.setTheT(rint(t))
rectangle.setTextValue(rstring("reviewed-region"))

roi = omero.model.RoiI()
roi.setImage(image._obj)
roi.addShape(rectangle)

saved = conn.getUpdateService().saveAndReturnObject(roi, conn.SERVICE_OPTS)
print(saved.getId().getValue())

Before execution, confirm:

  • image ID, group, and dimensions;
  • shape count and coordinate system;
  • Z/T/C semantics;
  • label sensitivity;
  • write permission;
  • whether an existing ROI should be updated rather than duplicated.
Raw lines 175 to 224
225## RGBA Encoding
227The official Python example encodes color bytes as a signed 32-bit integer:
229```python
230def rgba_to_int(red, green, blue, alpha=255):
231 channels = (red, green, blue, alpha)
232 if any(not 0 <= value <= 255 for value in channels):
233 raise ValueError("RGBA channels must be in 0..255")
234 return int.from_bytes(channels, byteorder="big", signed=True)
235```
237Use `rint(rgba_to_int(...))` for generated shape color fields. Do not swap RGBA
238order or assume an unsigned representation.

RGBA Encoding

The official Python example encodes color bytes as a signed 32-bit integer:

def rgba_to_int(red, green, blue, alpha=255):
    channels = (red, green, blue, alpha)
    if any(not 0 <= value <= 255 for value in channels):
        raise ValueError("RGBA channels must be in 0..255")
    return int.from_bytes(channels, byteorder="big", signed=True)

Use rint(rgba_to_int(...)) for generated shape color fields. Do not swap RGBA order or assume an unsigned representation.

Raw lines 225 to 239
240## Intensity Measurements
242The historical Python example uses
243`IRoi.getShapeStatsRestricted(shape_ids, z, t, channel_indices)`. Since
244`IRoi` is deprecated:
2461. verify the method in the target server's generated API;
2472. cap shape and channel counts;
2483. preserve channel indexing (pixel channels are zero-based);
2494. record the exact server/client versions;
2505. do not describe the result as a replacement for a validated analysis
251 pipeline.
253For simple rectangular reads, a bounded pixel tile may be clearer:
255```python
256pixels = image.getPrimaryPixels()
257z = 0
258c = 0
259t = 0
260x = 10
261y = 20
262width = 100
263height = 80
265tile = next(pixels.getTiles([(z, c, t, (x, y, width, height))]))
266```
268Confirm the current API's tile generator behavior and cap tile area. Polygon,
269polyline, ellipse, and mask measurements require a correctly defined raster
270mask; a bounding box alone is not the ROI.

Intensity Measurements

The historical Python example uses IRoi.getShapeStatsRestricted(shape_ids, z, t, channel_indices). Since IRoi is deprecated:

  1. verify the method in the target server's generated API;
  2. cap shape and channel counts;
  3. preserve channel indexing (pixel channels are zero-based);
  4. record the exact server/client versions;
  5. do not describe the result as a replacement for a validated analysis pipeline.

For simple rectangular reads, a bounded pixel tile may be clearer:

pixels = image.getPrimaryPixels()
z = 0
c = 0
t = 0
x = 10
y = 20
width = 100
height = 80

tile = next(pixels.getTiles([(z, c, t, (x, y, width, height))]))

Confirm the current API's tile generator behavior and cap tile area. Polygon, polyline, ellipse, and mask measurements require a correctly defined raster mask; a bounding box alone is not the ROI.

Raw lines 240 to 271
272## Updates and Deletion
274Updating a shape and saving an ROI is a write that can affect downstream
275measurements. Deleting an ROI removes its shapes. Never add delete support to a
276read-only export script.
278Before update/delete:
280- show image, ROI, and shape IDs;
281- retrieve current values;
282- check permissions and group context;
283- obtain approval for the exact IDs;
284- avoid namespace/label-based bulk selection;
285- wait for command completion and verify the result.

Updates and Deletion

Updating a shape and saving an ROI is a write that can affect downstream measurements. Deleting an ROI removes its shapes. Never add delete support to a read-only export script.

Before update/delete:

  • show image, ROI, and shape IDs;
  • retrieve current values;
  • check permissions and group context;
  • obtain approval for the exact IDs;
  • avoid namespace/label-based bulk selection;
  • wait for command completion and verify the result.
Raw lines 272 to 286
287## ROI Export Checklist
289- Explicit image IDs only
290- One group context
291- Maximum images, ROIs per image, and shapes per ROI
292- Labels redacted by default
293- Mask bytes omitted
294- No pixel values unless separately requested
295- Plane selectors preserve `None`
296- Geometry strings have a length cap
297- `IRoi` deprecation recorded
298- Connection closed in `finally`

ROI Export Checklist

  • Explicit image IDs only
  • One group context
  • Maximum images, ROIs per image, and shapes per ROI
  • Labels redacted by default
  • Mask bytes omitted
  • No pixel values unless separately requested
  • Plane selectors preserve None
  • Geometry strings have a length cap
  • IRoi deprecation recorded
  • Connection closed in finally
Raw lines 287 to 298