Skip to content

Commit 5f3f675

Browse files
committed
Add support for plates
1 parent b8ae691 commit 5f3f675

File tree

2 files changed

+118
-23
lines changed

2 files changed

+118
-23
lines changed

src/omero_zarr/cli.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
from .extinfo import (
4545
get_images,
4646
set_external_info,
47-
get_path,
4847
)
4948

5049
HELP = """Export data in zarr format.
@@ -348,17 +347,14 @@ def export(self, args: argparse.Namespace) -> None:
348347

349348
@gateway_required
350349
def extinfo(self, args: argparse.Namespace) -> None:
351-
for img in get_images(self.gateway, args.object):
352-
path = get_path(self.gateway, img.getId())
350+
for img, well, idx in get_images(self.gateway, args.object):
353351
img = img._obj
354-
if path.endswith("OME/METADATA.ome.xml"):
355-
path = path.replace("OME/METADATA.ome.xml", "0")
356-
path = f"/{path}"
357-
img = set_external_info(img, path)
352+
try:
353+
img = set_external_info(self.gateway, img, well, idx)
358354
img = self.gateway.getUpdateService().saveAndReturnObject(img)
359-
self.ctx.out(f"Set path to '{path}' for image {img.id._val}")
360-
else:
361-
self.ctx.out(f"'{path}' for image {img.id._val} doesn't seem to be an ome.zarr, skipping.")
355+
self.ctx.out(f"Set path to '{img.details.externalInfo.lsid._val}' for image {img.id._val}")
356+
except Exception as e:
357+
self.ctx.err(f"Failed to set external info for image {img.id._val}: {e}")
362358

363359

364360
def _lookup(

src/omero_zarr/extinfo.py

Lines changed: 112 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,27 @@
88
from omero.model import Screen
99
from omero.model import Dataset
1010
from omero.model import Project
11+
import re
1112

1213

13-
def get_path(conn: BlitzGateway, image_id: int) -> str:
14+
# Regex to match well positions (eg. A1)
15+
WELL_POS_RE = re.compile(r"(?P<row>\D+)(?P<col>\d+)")
16+
17+
18+
def _get_path(conn: BlitzGateway, image_id: int) -> str:
19+
"""
20+
Retrieve the (first) original file path for a given OMERO image.
21+
22+
Args:
23+
conn (BlitzGateway): Active OMERO gateway connection
24+
image_id (int): ID of the OMERO image
25+
26+
Returns:
27+
str: path of the image file
28+
29+
Raises:
30+
Exception: If the query fails or the path cannot be retrieved
31+
"""
1432
params = ParametersI()
1533
params.addId(image_id)
1634
query = """
@@ -25,24 +43,49 @@ def get_path(conn: BlitzGateway, image_id: int) -> str:
2543
return res
2644

2745

28-
def set_external_info(img: ImageI, path: str) -> ImageI:
29-
info = ExternalInfoI()
30-
info.entityType = rstring("com.glencoesoftware.ngff:multiscales")
31-
info.entityId = rlong(3)
32-
info.lsid = rstring(path)
33-
img.details.externalInfo = info
34-
return img
46+
def _lookup(conn: BlitzGateway, type: str, oid: int) -> BlitzObjectWrapper:
47+
"""
48+
Look up an OMERO object by its type and ID.
3549
50+
Args:
51+
conn (BlitzGateway): Active OMERO gateway connection
52+
type (str): Type of OMERO object (e.g., "Screen", "Plate", "Image")
53+
oid (int): Object ID to look up
3654
37-
def _lookup(conn: BlitzGateway, type: str, oid: int) -> BlitzObjectWrapper:
55+
Returns:
56+
BlitzObjectWrapper: Wrapped OMERO object
57+
58+
Raises:
59+
ValueError: If the object doesn't exist
60+
"""
3861
conn.SERVICE_OPTS.setOmeroGroup("-1")
3962
obj = conn.getObject(type, oid)
4063
if not obj:
4164
raise ValueError(f"No such {type}: {oid}")
4265
return obj
4366

4467

45-
def get_images(conn: BlitzGateway, object):
68+
def get_images(conn: BlitzGateway, object) -> tuple[BlitzObjectWrapper, str | None, int | None]:
69+
"""
70+
Generator that yields images from any OMERO container object.
71+
72+
Recursively traverses OMERO container hierarchies (Screen/Plate/Project/Dataset)
73+
to find all contained images.
74+
75+
Args:
76+
conn (BlitzGateway): Active OMERO gateway connection
77+
object: OMERO container object (Screen, Plate, Project, Dataset, Image)
78+
or a list of such objects
79+
80+
Yields:
81+
tuple: Contains:
82+
- BlitzObjectWrapper: Image object
83+
- str | None: Well position (eg. A1) if from plate, None otherwise
84+
- int | None: Well sample index if from plate, None otherwise
85+
86+
Raises:
87+
ValueError: If given an unsupported object type
88+
"""
4689
if isinstance(object, list):
4790
for x in object:
4891
yield from get_images(conn, x)
@@ -55,17 +98,73 @@ def get_images(conn: BlitzGateway, object):
5598
for well in plt.listChildren():
5699
for idx in range(0, well.countWellSample()):
57100
img = well.getImage(idx)
58-
yield img
101+
yield img, well.getWellPos(), idx
59102
elif isinstance(object, Project):
60103
prj = _lookup(conn, "Project", object.id)
61104
for ds in prj.listChildren():
62105
yield from get_images(conn, ds._obj)
63106
elif isinstance(object, Dataset):
64107
ds = _lookup(conn, "Dataset", object.id)
65108
for img in ds.listChildren():
66-
yield img
109+
yield img, None, None
67110
elif isinstance(object, Image):
68111
img = _lookup(conn, "Image", object.id)
69-
yield img
112+
yield img, None, None
70113
else:
71114
raise ValueError(f"Unsupported type: {object.__class__.__name__}")
115+
116+
117+
def set_external_info(conn: BlitzGateway, img: ImageI, well: str, idx: int) -> ImageI:
118+
"""
119+
Set external info for an image to enable omero zarr pixel buffer handling.
120+
121+
This function configures an image for use with the omero-zarr-pixel-buffer by:
122+
1. Retrieving the original file path
123+
2. Converting it to point to the 5d multiscale image directory
124+
3. Setting up the external info with the path and metadata
125+
126+
For plate-based images (with wells), the path structure follows:
127+
/<base_path>/<row>/<column>/<field index>
128+
129+
For regular images:
130+
/<base_path>/0
131+
132+
Args:
133+
conn (BlitzGateway): Active OMERO gateway connection
134+
img (ImageI): OMERO image object to modify
135+
well (str): Well position (e.g., 'A1', 'B2') for plate-based images, or None
136+
idx (int): Well sample / field index for plate-based images, or None
137+
138+
Returns:
139+
ImageI: Modified image object with updated external info
140+
141+
Raises:
142+
ValueError: If the image path is not an OME-Zarr format or if well position is invalid
143+
144+
Note:
145+
The external info is configured with entity type 'com.glencoesoftware.ngff:multiscales'
146+
and entity ID 3, which are required by the omero-zarr-pixel-buffer.
147+
"""
148+
path = _get_path(conn, img.id)
149+
if path.endswith("OME/METADATA.ome.xml"):
150+
if well:
151+
match = WELL_POS_RE.match(well)
152+
if match:
153+
col = match.group("col")
154+
row = match.group("row")
155+
path = path.replace("OME/METADATA.ome.xml", f"{row}")
156+
path = f"/{path}/{col}/{idx}"
157+
else:
158+
raise ValueError(f"Couldn't parse well position: {well}")
159+
else:
160+
path = path.replace("OME/METADATA.ome.xml", "0")
161+
path = f"/{path}"
162+
else:
163+
raise ValueError(f"Doesn't seem to be an ome.zarr: {path}")
164+
165+
info = ExternalInfoI()
166+
info.entityType = rstring("com.glencoesoftware.ngff:multiscales")
167+
info.entityId = rlong(3)
168+
info.lsid = rstring(path)
169+
img.details.externalInfo = info
170+
return img

0 commit comments

Comments
 (0)