Skip to content

Commit dd63922

Browse files
committed
Support Plates
1 parent e4cda75 commit dd63922

File tree

2 files changed

+118
-0
lines changed

2 files changed

+118
-0
lines changed

napari_ome_zarr/ome_zarr_reader.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from xml.etree import ElementTree as ET
1414

1515
from typing import Any, Dict, List, Tuple, Union
16+
from .plate import get_pyramid_lazy, get_first_well, get_first_field_path
1617

1718
LayerData = Union[Tuple[Any], Tuple[Any, Dict], Tuple[Any, Dict, str]]
1819

@@ -166,6 +167,15 @@ class Plate(Spec):
166167
def matches(group: Group) -> bool:
167168
return "plate" in Spec.get_attrs(group)
168169

170+
def data(self):
171+
# we want to return a dask pyramid...
172+
return get_pyramid_lazy(self.group)
173+
174+
def metadata(self):
175+
well_group = get_first_well(self.group)
176+
first_field_path = get_first_field_path(well_group)
177+
image_group = well_group[first_field_path]
178+
return Multiscales(image_group).metadata()
169179

170180
class Label(Multiscales):
171181

napari_ome_zarr/plate.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
2+
from zarr import Group
3+
import dask.array as da
4+
import numpy as np
5+
6+
7+
def get_attrs(group: Group):
8+
if "ome" in group.attrs:
9+
return group.attrs["ome"]
10+
return group.attrs
11+
12+
13+
def get_pyramid_lazy(plate_group) -> None:
14+
"""
15+
Return a pyramid of dask data, where the highest resolution is the
16+
stitched full-resolution images.
17+
"""
18+
# plate_data = plate_group.attrs["plate"]
19+
# well_paths = [well["path"] for well in plate_data.get("wells")]
20+
# well_paths.sort()
21+
22+
# Get the first well...
23+
well_group = get_first_well(plate_group)
24+
first_field_path = get_first_field_path(well_group)
25+
image_group = well_group[first_field_path]
26+
27+
# We assume all images are same shape & dtype as the first one
28+
paths = [ds["path"] for ds in get_attrs(image_group)["multiscales"][0]["datasets"]]
29+
img_pyramid = [da.from_zarr(image_group[path]) for path in paths]
30+
img_pyramid_shapes = [d.shape for d in img_pyramid]
31+
numpy_type = img_pyramid[0].dtype
32+
33+
# Create a dask pyramid for the plate
34+
pyramid = []
35+
for level, tile_shape in enumerate(img_pyramid_shapes):
36+
lazy_plate = get_stitched_grid(plate_group, level, tile_shape, numpy_type, first_field_path)
37+
pyramid.append(lazy_plate)
38+
39+
# Use the first image's metadata for viewing the whole Plate
40+
# node.metadata = well_spec.img_metadata
41+
42+
# "metadata" dict gets added to each 'plate' layer in napari
43+
# node.metadata.update({"metadata": {"plate": self.plate_data}})
44+
return pyramid
45+
46+
47+
def get_stitched_grid(plate_group, level: int, tile_shape: tuple, numpy_type, first_field_path) -> da.core.Array:
48+
49+
plate_data = get_attrs(plate_group)["plate"]
50+
rows = plate_data.get("rows")
51+
columns = plate_data.get("columns")
52+
row_names = [row["name"] for row in rows]
53+
col_names = [col["name"] for col in columns]
54+
55+
well_paths = [well["path"] for well in plate_data.get("wells")]
56+
well_paths.sort()
57+
58+
row_count = len(rows)
59+
column_count = len(columns)
60+
61+
def get_tile(row: int, col: int) -> da.core.Array:
62+
"""tile_name is 'level,z,c,t,row,col'"""
63+
64+
# check whether the Well exists at this row/column
65+
well_path = f"{row_names[row]}/{col_names[col]}"
66+
if well_path not in well_paths:
67+
return np.zeros(tile_shape, dtype=numpy_type)
68+
69+
img_path = f"{well_path}/{first_field_path}/{level}"
70+
71+
try:
72+
# this is a dask array - data not loaded from source yet
73+
data = da.from_zarr(plate_group[img_path])
74+
except ValueError:
75+
# FIXME: check the Well to get the actual first field path
76+
data = da.zeros(tile_shape, dtype=numpy_type)
77+
return data
78+
79+
lazy_rows = []
80+
# For level 0, return whole image for each tile
81+
for row in range(row_count):
82+
lazy_row: list[da.Array] = [
83+
get_tile(row, col) for col in range(column_count)
84+
]
85+
lazy_rows.append(da.concatenate(lazy_row, axis=len(lazy_row[0].shape) - 1))
86+
return da.concatenate(lazy_rows, axis=len(lazy_rows[0].shape) - 2)
87+
88+
89+
def get_first_well(plate_group):
90+
plate_data = get_attrs(plate_group)["plate"]
91+
well_paths = [well["path"] for well in plate_data.get("wells")]
92+
well_paths.sort()
93+
94+
# Get the first well...
95+
well_group = plate_group[well_paths[0]]
96+
if well_group is None:
97+
raise Exception("Could not find first well")
98+
return well_group
99+
100+
101+
def get_first_field_path(well_group):
102+
well_data = get_attrs(well_group)["well"]
103+
if well_data is None:
104+
raise Exception("Could not find well data")
105+
106+
first_field_path = well_data["images"][0]["path"]
107+
return first_field_path
108+

0 commit comments

Comments
 (0)