649 lines
16 KiB
Markdown
649 lines
16 KiB
Markdown
# Regions of Interest (ROIs)
|
|
|
|
This reference covers creating, retrieving, and analyzing ROIs in OMERO.
|
|
|
|
## ROI Overview
|
|
|
|
ROIs (Regions of Interest) in OMERO are containers for geometric shapes that mark specific regions on images. Each ROI can contain multiple shapes, and shapes can be specific to Z-sections and timepoints.
|
|
|
|
### Supported Shape Types
|
|
|
|
- **Rectangle**: Rectangular regions
|
|
- **Ellipse**: Circular and elliptical regions
|
|
- **Line**: Line segments
|
|
- **Point**: Single points
|
|
- **Polygon**: Multi-point polygons
|
|
- **Mask**: Pixel-based masks
|
|
- **Polyline**: Multi-segment lines
|
|
|
|
## Creating ROIs
|
|
|
|
### Helper Functions
|
|
|
|
```python
|
|
from omero.rtypes import rdouble, rint, rstring
|
|
import omero.model
|
|
|
|
def create_roi(conn, image, shapes):
|
|
"""
|
|
Create an ROI and link it to shapes.
|
|
|
|
Args:
|
|
conn: BlitzGateway connection
|
|
image: Image object
|
|
shapes: List of shape objects
|
|
|
|
Returns:
|
|
Saved ROI object
|
|
"""
|
|
roi = omero.model.RoiI()
|
|
roi.setImage(image._obj)
|
|
|
|
for shape in shapes:
|
|
roi.addShape(shape)
|
|
|
|
updateService = conn.getUpdateService()
|
|
return updateService.saveAndReturnObject(roi)
|
|
|
|
def rgba_to_int(red, green, blue, alpha=255):
|
|
"""
|
|
Convert RGBA values (0-255) to integer encoding for OMERO.
|
|
|
|
Args:
|
|
red, green, blue, alpha: Color values (0-255)
|
|
|
|
Returns:
|
|
Integer color value
|
|
"""
|
|
return int.from_bytes([red, green, blue, alpha],
|
|
byteorder='big', signed=True)
|
|
```
|
|
|
|
### Rectangle ROI
|
|
|
|
```python
|
|
from omero.rtypes import rdouble, rint, rstring
|
|
import omero.model
|
|
|
|
# Get image
|
|
image = conn.getObject("Image", image_id)
|
|
|
|
# Define position and size
|
|
x, y = 50, 100
|
|
width, height = 200, 150
|
|
z, t = 0, 0 # Z-section and timepoint
|
|
|
|
# Create rectangle
|
|
rect = omero.model.RectangleI()
|
|
rect.x = rdouble(x)
|
|
rect.y = rdouble(y)
|
|
rect.width = rdouble(width)
|
|
rect.height = rdouble(height)
|
|
rect.theZ = rint(z)
|
|
rect.theT = rint(t)
|
|
|
|
# Set label and colors
|
|
rect.textValue = rstring("Cell Region")
|
|
rect.fillColor = rint(rgba_to_int(255, 0, 0, 50)) # Red, semi-transparent
|
|
rect.strokeColor = rint(rgba_to_int(255, 255, 0, 255)) # Yellow border
|
|
|
|
# Create ROI
|
|
roi = create_roi(conn, image, [rect])
|
|
print(f"Created ROI ID: {roi.getId().getValue()}")
|
|
```
|
|
|
|
### Ellipse ROI
|
|
|
|
```python
|
|
# Center position and radii
|
|
center_x, center_y = 250, 250
|
|
radius_x, radius_y = 100, 75
|
|
z, t = 0, 0
|
|
|
|
# Create ellipse
|
|
ellipse = omero.model.EllipseI()
|
|
ellipse.x = rdouble(center_x)
|
|
ellipse.y = rdouble(center_y)
|
|
ellipse.radiusX = rdouble(radius_x)
|
|
ellipse.radiusY = rdouble(radius_y)
|
|
ellipse.theZ = rint(z)
|
|
ellipse.theT = rint(t)
|
|
ellipse.textValue = rstring("Nucleus")
|
|
ellipse.fillColor = rint(rgba_to_int(0, 255, 0, 50))
|
|
|
|
# Create ROI
|
|
roi = create_roi(conn, image, [ellipse])
|
|
```
|
|
|
|
### Line ROI
|
|
|
|
```python
|
|
# Line endpoints
|
|
x1, y1 = 100, 100
|
|
x2, y2 = 300, 200
|
|
z, t = 0, 0
|
|
|
|
# Create line
|
|
line = omero.model.LineI()
|
|
line.x1 = rdouble(x1)
|
|
line.y1 = rdouble(y1)
|
|
line.x2 = rdouble(x2)
|
|
line.y2 = rdouble(y2)
|
|
line.theZ = rint(z)
|
|
line.theT = rint(t)
|
|
line.textValue = rstring("Measurement Line")
|
|
line.strokeColor = rint(rgba_to_int(0, 0, 255, 255))
|
|
|
|
# Create ROI
|
|
roi = create_roi(conn, image, [line])
|
|
```
|
|
|
|
### Point ROI
|
|
|
|
```python
|
|
# Point position
|
|
x, y = 150, 150
|
|
z, t = 0, 0
|
|
|
|
# Create point
|
|
point = omero.model.PointI()
|
|
point.x = rdouble(x)
|
|
point.y = rdouble(y)
|
|
point.theZ = rint(z)
|
|
point.theT = rint(t)
|
|
point.textValue = rstring("Feature Point")
|
|
|
|
# Create ROI
|
|
roi = create_roi(conn, image, [point])
|
|
```
|
|
|
|
### Polygon ROI
|
|
|
|
```python
|
|
from omero.model.enums import UnitsLength
|
|
|
|
# Define vertices as string "x1,y1 x2,y2 x3,y3 ..."
|
|
vertices = "10,20 50,150 200,200 250,75"
|
|
z, t = 0, 0
|
|
|
|
# Create polygon
|
|
polygon = omero.model.PolygonI()
|
|
polygon.points = rstring(vertices)
|
|
polygon.theZ = rint(z)
|
|
polygon.theT = rint(t)
|
|
polygon.textValue = rstring("Cell Outline")
|
|
|
|
# Set colors and stroke width
|
|
polygon.fillColor = rint(rgba_to_int(255, 0, 255, 50))
|
|
polygon.strokeColor = rint(rgba_to_int(255, 255, 0, 255))
|
|
polygon.strokeWidth = omero.model.LengthI(2, UnitsLength.PIXEL)
|
|
|
|
# Create ROI
|
|
roi = create_roi(conn, image, [polygon])
|
|
```
|
|
|
|
### Mask ROI
|
|
|
|
```python
|
|
import numpy as np
|
|
import struct
|
|
import math
|
|
|
|
def create_mask_bytes(mask_array, bytes_per_pixel=1):
|
|
"""
|
|
Convert binary mask array to bit-packed bytes for OMERO.
|
|
|
|
Args:
|
|
mask_array: Binary numpy array (0s and 1s)
|
|
bytes_per_pixel: 1 or 2
|
|
|
|
Returns:
|
|
Byte array for OMERO mask
|
|
"""
|
|
if bytes_per_pixel == 2:
|
|
divider = 16.0
|
|
format_string = "H"
|
|
byte_factor = 0.5
|
|
elif bytes_per_pixel == 1:
|
|
divider = 8.0
|
|
format_string = "B"
|
|
byte_factor = 1
|
|
else:
|
|
raise ValueError("bytes_per_pixel must be 1 or 2")
|
|
|
|
mask_bytes = mask_array.astype(np.uint8).tobytes()
|
|
steps = math.ceil(len(mask_bytes) / divider)
|
|
packed_mask = []
|
|
|
|
for i in range(int(steps)):
|
|
binary = mask_bytes[i * int(divider):
|
|
i * int(divider) + int(divider)]
|
|
format_str = str(int(byte_factor * len(binary))) + format_string
|
|
binary = struct.unpack(format_str, binary)
|
|
s = "".join(str(bit) for bit in binary)
|
|
packed_mask.append(int(s, 2))
|
|
|
|
return bytearray(packed_mask)
|
|
|
|
# Create binary mask (1s and 0s)
|
|
mask_w, mask_h = 100, 100
|
|
mask_array = np.fromfunction(
|
|
lambda x, y: ((x - 50)**2 + (y - 50)**2) < 40**2, # Circle
|
|
(mask_w, mask_h)
|
|
)
|
|
|
|
# Pack mask
|
|
mask_packed = create_mask_bytes(mask_array, bytes_per_pixel=1)
|
|
|
|
# Mask position
|
|
mask_x, mask_y = 50, 50
|
|
z, t, c = 0, 0, 0
|
|
|
|
# Create mask
|
|
mask = omero.model.MaskI()
|
|
mask.setX(rdouble(mask_x))
|
|
mask.setY(rdouble(mask_y))
|
|
mask.setWidth(rdouble(mask_w))
|
|
mask.setHeight(rdouble(mask_h))
|
|
mask.setTheZ(rint(z))
|
|
mask.setTheT(rint(t))
|
|
mask.setTheC(rint(c))
|
|
mask.setBytes(mask_packed)
|
|
mask.textValue = rstring("Segmentation Mask")
|
|
|
|
# Set color
|
|
from omero.gateway import ColorHolder
|
|
mask_color = ColorHolder()
|
|
mask_color.setRed(255)
|
|
mask_color.setGreen(0)
|
|
mask_color.setBlue(0)
|
|
mask_color.setAlpha(100)
|
|
mask.setFillColor(rint(mask_color.getInt()))
|
|
|
|
# Create ROI
|
|
roi = create_roi(conn, image, [mask])
|
|
```
|
|
|
|
## Multiple Shapes in One ROI
|
|
|
|
```python
|
|
# Create multiple shapes for the same ROI
|
|
shapes = []
|
|
|
|
# Rectangle
|
|
rect = omero.model.RectangleI()
|
|
rect.x = rdouble(100)
|
|
rect.y = rdouble(100)
|
|
rect.width = rdouble(50)
|
|
rect.height = rdouble(50)
|
|
rect.theZ = rint(0)
|
|
rect.theT = rint(0)
|
|
shapes.append(rect)
|
|
|
|
# Ellipse
|
|
ellipse = omero.model.EllipseI()
|
|
ellipse.x = rdouble(125)
|
|
ellipse.y = rdouble(125)
|
|
ellipse.radiusX = rdouble(20)
|
|
ellipse.radiusY = rdouble(20)
|
|
ellipse.theZ = rint(0)
|
|
ellipse.theT = rint(0)
|
|
shapes.append(ellipse)
|
|
|
|
# Create single ROI with both shapes
|
|
roi = create_roi(conn, image, shapes)
|
|
```
|
|
|
|
## Retrieving ROIs
|
|
|
|
### Get All ROIs for Image
|
|
|
|
```python
|
|
# Get ROI service
|
|
roi_service = conn.getRoiService()
|
|
|
|
# Find all ROIs for image
|
|
result = roi_service.findByImage(image_id, None)
|
|
|
|
print(f"Found {len(result.rois)} ROIs")
|
|
|
|
for roi in result.rois:
|
|
print(f"ROI ID: {roi.getId().getValue()}")
|
|
print(f" Number of shapes: {len(roi.copyShapes())}")
|
|
```
|
|
|
|
### Parse ROI Shapes
|
|
|
|
```python
|
|
import omero.model
|
|
|
|
result = roi_service.findByImage(image_id, None)
|
|
|
|
for roi in result.rois:
|
|
roi_id = roi.getId().getValue()
|
|
print(f"ROI ID: {roi_id}")
|
|
|
|
for shape in roi.copyShapes():
|
|
shape_id = shape.getId().getValue()
|
|
z = shape.getTheZ().getValue() if shape.getTheZ() else None
|
|
t = shape.getTheT().getValue() if shape.getTheT() else None
|
|
|
|
# Get label
|
|
label = ""
|
|
if shape.getTextValue():
|
|
label = shape.getTextValue().getValue()
|
|
|
|
print(f" Shape ID: {shape_id}, Z: {z}, T: {t}, Label: {label}")
|
|
|
|
# Type-specific parsing
|
|
if isinstance(shape, omero.model.RectangleI):
|
|
x = shape.getX().getValue()
|
|
y = shape.getY().getValue()
|
|
width = shape.getWidth().getValue()
|
|
height = shape.getHeight().getValue()
|
|
print(f" Rectangle: ({x}, {y}) {width}x{height}")
|
|
|
|
elif isinstance(shape, omero.model.EllipseI):
|
|
x = shape.getX().getValue()
|
|
y = shape.getY().getValue()
|
|
rx = shape.getRadiusX().getValue()
|
|
ry = shape.getRadiusY().getValue()
|
|
print(f" Ellipse: center ({x}, {y}), radii ({rx}, {ry})")
|
|
|
|
elif isinstance(shape, omero.model.PointI):
|
|
x = shape.getX().getValue()
|
|
y = shape.getY().getValue()
|
|
print(f" Point: ({x}, {y})")
|
|
|
|
elif isinstance(shape, omero.model.LineI):
|
|
x1 = shape.getX1().getValue()
|
|
y1 = shape.getY1().getValue()
|
|
x2 = shape.getX2().getValue()
|
|
y2 = shape.getY2().getValue()
|
|
print(f" Line: ({x1}, {y1}) to ({x2}, {y2})")
|
|
|
|
elif isinstance(shape, omero.model.PolygonI):
|
|
points = shape.getPoints().getValue()
|
|
print(f" Polygon: {points}")
|
|
|
|
elif isinstance(shape, omero.model.MaskI):
|
|
x = shape.getX().getValue()
|
|
y = shape.getY().getValue()
|
|
width = shape.getWidth().getValue()
|
|
height = shape.getHeight().getValue()
|
|
print(f" Mask: ({x}, {y}) {width}x{height}")
|
|
```
|
|
|
|
## Analyzing ROI Intensities
|
|
|
|
### Get Statistics for ROI Shapes
|
|
|
|
```python
|
|
# Get all shapes from ROIs
|
|
roi_service = conn.getRoiService()
|
|
result = roi_service.findByImage(image_id, None)
|
|
|
|
shape_ids = []
|
|
for roi in result.rois:
|
|
for shape in roi.copyShapes():
|
|
shape_ids.append(shape.id.val)
|
|
|
|
# Define position
|
|
z, t = 0, 0
|
|
channel_index = 0
|
|
|
|
# Get statistics
|
|
stats = roi_service.getShapeStatsRestricted(
|
|
shape_ids, z, t, [channel_index]
|
|
)
|
|
|
|
# Display statistics
|
|
for i, stat in enumerate(stats):
|
|
shape_id = shape_ids[i]
|
|
print(f"Shape {shape_id} statistics:")
|
|
print(f" Points Count: {stat.pointsCount[channel_index]}")
|
|
print(f" Min: {stat.min[channel_index]}")
|
|
print(f" Mean: {stat.mean[channel_index]}")
|
|
print(f" Max: {stat.max[channel_index]}")
|
|
print(f" Sum: {stat.sum[channel_index]}")
|
|
print(f" Std Dev: {stat.stdDev[channel_index]}")
|
|
```
|
|
|
|
### Extract Pixel Values Within ROI
|
|
|
|
```python
|
|
import numpy as np
|
|
|
|
# Get image and ROI
|
|
image = conn.getObject("Image", image_id)
|
|
result = roi_service.findByImage(image_id, None)
|
|
|
|
# Get first rectangle shape
|
|
roi = result.rois[0]
|
|
rect = roi.copyShapes()[0]
|
|
|
|
# Get rectangle bounds
|
|
x = int(rect.getX().getValue())
|
|
y = int(rect.getY().getValue())
|
|
width = int(rect.getWidth().getValue())
|
|
height = int(rect.getHeight().getValue())
|
|
z = rect.getTheZ().getValue()
|
|
t = rect.getTheT().getValue()
|
|
|
|
# Get pixel data
|
|
pixels = image.getPrimaryPixels()
|
|
|
|
# Extract region for each channel
|
|
for c in range(image.getSizeC()):
|
|
# Get plane
|
|
plane = pixels.getPlane(z, c, t)
|
|
|
|
# Extract ROI region
|
|
roi_region = plane[y:y+height, x:x+width]
|
|
|
|
print(f"Channel {c}:")
|
|
print(f" Mean intensity: {np.mean(roi_region)}")
|
|
print(f" Max intensity: {np.max(roi_region)}")
|
|
```
|
|
|
|
## Modifying ROIs
|
|
|
|
### Update Shape Properties
|
|
|
|
```python
|
|
# Get ROI and shape
|
|
result = roi_service.findByImage(image_id, None)
|
|
roi = result.rois[0]
|
|
shape = roi.copyShapes()[0]
|
|
|
|
# Modify shape (example: change rectangle size)
|
|
if isinstance(shape, omero.model.RectangleI):
|
|
shape.setWidth(rdouble(150))
|
|
shape.setHeight(rdouble(100))
|
|
shape.setTextValue(rstring("Updated Rectangle"))
|
|
|
|
# Save changes
|
|
updateService = conn.getUpdateService()
|
|
updated_roi = updateService.saveAndReturnObject(roi._obj)
|
|
```
|
|
|
|
### Remove Shape from ROI
|
|
|
|
```python
|
|
result = roi_service.findByImage(image_id, None)
|
|
|
|
for roi in result.rois:
|
|
for shape in roi.copyShapes():
|
|
# Check condition (e.g., remove by label)
|
|
if (shape.getTextValue() and
|
|
shape.getTextValue().getValue() == "test-Ellipse"):
|
|
|
|
print(f"Removing shape {shape.getId().getValue()}")
|
|
roi.removeShape(shape)
|
|
|
|
# Save modified ROI
|
|
updateService = conn.getUpdateService()
|
|
roi = updateService.saveAndReturnObject(roi)
|
|
```
|
|
|
|
## Deleting ROIs
|
|
|
|
### Delete Single ROI
|
|
|
|
```python
|
|
# Delete ROI by ID
|
|
roi_id = 123
|
|
conn.deleteObjects("Roi", [roi_id], wait=True)
|
|
print(f"Deleted ROI {roi_id}")
|
|
```
|
|
|
|
### Delete All ROIs for Image
|
|
|
|
```python
|
|
# Get all ROI IDs for image
|
|
result = roi_service.findByImage(image_id, None)
|
|
roi_ids = [roi.getId().getValue() for roi in result.rois]
|
|
|
|
# Delete all
|
|
if roi_ids:
|
|
conn.deleteObjects("Roi", roi_ids, wait=True)
|
|
print(f"Deleted {len(roi_ids)} ROIs")
|
|
```
|
|
|
|
## Batch ROI Creation
|
|
|
|
### Create ROIs for Multiple Images
|
|
|
|
```python
|
|
# Get images
|
|
dataset = conn.getObject("Dataset", dataset_id)
|
|
|
|
for image in dataset.listChildren():
|
|
# Create rectangle at center of each image
|
|
x = image.getSizeX() // 2 - 50
|
|
y = image.getSizeY() // 2 - 50
|
|
|
|
rect = omero.model.RectangleI()
|
|
rect.x = rdouble(x)
|
|
rect.y = rdouble(y)
|
|
rect.width = rdouble(100)
|
|
rect.height = rdouble(100)
|
|
rect.theZ = rint(0)
|
|
rect.theT = rint(0)
|
|
rect.textValue = rstring("Auto ROI")
|
|
|
|
roi = create_roi(conn, image, [rect])
|
|
print(f"Created ROI for image {image.getName()}")
|
|
```
|
|
|
|
### Create ROIs Across Z-Stack
|
|
|
|
```python
|
|
image = conn.getObject("Image", image_id)
|
|
size_z = image.getSizeZ()
|
|
|
|
# Create rectangle on each Z-section
|
|
shapes = []
|
|
for z in range(size_z):
|
|
rect = omero.model.RectangleI()
|
|
rect.x = rdouble(100)
|
|
rect.y = rdouble(100)
|
|
rect.width = rdouble(50)
|
|
rect.height = rdouble(50)
|
|
rect.theZ = rint(z)
|
|
rect.theT = rint(0)
|
|
shapes.append(rect)
|
|
|
|
# Single ROI with shapes across Z
|
|
roi = create_roi(conn, image, shapes)
|
|
```
|
|
|
|
## Complete Example
|
|
|
|
```python
|
|
from omero.gateway import BlitzGateway
|
|
from omero.rtypes import rdouble, rint, rstring
|
|
import omero.model
|
|
|
|
HOST = 'omero.example.com'
|
|
PORT = 4064
|
|
USERNAME = 'user'
|
|
PASSWORD = 'pass'
|
|
|
|
def rgba_to_int(r, g, b, a=255):
|
|
return int.from_bytes([r, g, b, a], byteorder='big', signed=True)
|
|
|
|
with BlitzGateway(USERNAME, PASSWORD, host=HOST, port=PORT) as conn:
|
|
# Get image
|
|
image = conn.getObject("Image", image_id)
|
|
print(f"Processing: {image.getName()}")
|
|
|
|
# Create multiple ROIs
|
|
updateService = conn.getUpdateService()
|
|
|
|
# ROI 1: Rectangle
|
|
roi1 = omero.model.RoiI()
|
|
roi1.setImage(image._obj)
|
|
|
|
rect = omero.model.RectangleI()
|
|
rect.x = rdouble(50)
|
|
rect.y = rdouble(50)
|
|
rect.width = rdouble(100)
|
|
rect.height = rdouble(100)
|
|
rect.theZ = rint(0)
|
|
rect.theT = rint(0)
|
|
rect.textValue = rstring("Cell 1")
|
|
rect.strokeColor = rint(rgba_to_int(255, 0, 0, 255))
|
|
|
|
roi1.addShape(rect)
|
|
roi1 = updateService.saveAndReturnObject(roi1)
|
|
print(f"Created ROI 1: {roi1.getId().getValue()}")
|
|
|
|
# ROI 2: Ellipse
|
|
roi2 = omero.model.RoiI()
|
|
roi2.setImage(image._obj)
|
|
|
|
ellipse = omero.model.EllipseI()
|
|
ellipse.x = rdouble(200)
|
|
ellipse.y = rdouble(150)
|
|
ellipse.radiusX = rdouble(40)
|
|
ellipse.radiusY = rdouble(30)
|
|
ellipse.theZ = rint(0)
|
|
ellipse.theT = rint(0)
|
|
ellipse.textValue = rstring("Cell 2")
|
|
ellipse.strokeColor = rint(rgba_to_int(0, 255, 0, 255))
|
|
|
|
roi2.addShape(ellipse)
|
|
roi2 = updateService.saveAndReturnObject(roi2)
|
|
print(f"Created ROI 2: {roi2.getId().getValue()}")
|
|
|
|
# Retrieve and analyze
|
|
roi_service = conn.getRoiService()
|
|
result = roi_service.findByImage(image_id, None)
|
|
|
|
shape_ids = []
|
|
for roi in result.rois:
|
|
for shape in roi.copyShapes():
|
|
shape_ids.append(shape.id.val)
|
|
|
|
# Get statistics
|
|
stats = roi_service.getShapeStatsRestricted(shape_ids, 0, 0, [0])
|
|
|
|
for i, stat in enumerate(stats):
|
|
print(f"Shape {shape_ids[i]}:")
|
|
print(f" Mean intensity: {stat.mean[0]:.2f}")
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Organize Shapes**: Group related shapes in single ROIs
|
|
2. **Label Shapes**: Use textValue for identification
|
|
3. **Set Z and T**: Always specify Z-section and timepoint
|
|
4. **Color Coding**: Use consistent colors for shape types
|
|
5. **Validate Coordinates**: Ensure shapes are within image bounds
|
|
6. **Batch Creation**: Create multiple ROIs in single transaction when possible
|
|
7. **Delete Unused**: Remove temporary or test ROIs
|
|
8. **Export Data**: Store ROI statistics in tables for later analysis
|
|
9. **Version Control**: Document ROI creation methods in annotations
|
|
10. **Performance**: Use shape statistics service instead of manual pixel extraction
|