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

Add resolution level to data access #6

Merged
merged 4 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
100 changes: 54 additions & 46 deletions bioio_ome_zarr/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ def __init__(

self._zarr = ZarrReader(parse_url(self._path, mode="r")).zarr
self._physical_pixel_sizes: Optional[types.PhysicalPixelSizes] = None
self._multiresolution_level = 0
self._channel_names: Optional[List[str]] = None

@staticmethod
Expand Down Expand Up @@ -99,14 +98,27 @@ def scenes(self) -> Tuple[str, ...]:
)
return self._scenes

@property
def resolution_levels(self) -> Tuple[int, ...]:
"""
Returns
-------
resolution_levels: Tuple[str, ...]
Return the available resolution levels for the current scene.
By default these are ordered from highest resolution to lowest
resolution.
"""
return tuple(rl for rl in range(len(self._zarr.root_attrs["multiscales"][self.current_scene_index]["datasets"])))

def _read_delayed(self) -> xr.DataArray:
return self._xarr_format(delayed=True)

def _read_immediate(self) -> xr.DataArray:
return self._xarr_format(delayed=False)

def _xarr_format(self, delayed: bool) -> xr.DataArray:
image_data = self._zarr.load(str(self.current_scene_index))
data_path = self._zarr.root_attrs["multiscales"][self.current_scene_index]["datasets"][self.current_resolution_level]["path"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: maybe check that the level is valid before indexing into datasets here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm do you think that is necessary with set_current_resolution_level() checking to see if it is valid at set time?

image_data = self._zarr.load(data_path)

axes = self._zarr.root_attrs["multiscales"][self.current_scene_index].get(
"axes"
Expand Down Expand Up @@ -139,11 +151,8 @@ def physical_pixel_sizes(self) -> types.PhysicalPixelSizes:
"""Return the physical pixel sizes of the image."""
if self._physical_pixel_sizes is None:
try:
z_size, y_size, x_size = Reader._get_pixel_size(
self._zarr,
z_size, y_size, x_size = self._get_pixel_size(
list(self.dims.order),
self._current_scene_index,
self._multiresolution_level,
)
except Exception as e:
warnings.warn(f"Could not parse zarr pixel size: {e}")
Expand All @@ -154,58 +163,23 @@ def physical_pixel_sizes(self) -> types.PhysicalPixelSizes:
)
return self._physical_pixel_sizes

@property
def channel_names(self) -> Optional[List[str]]:
if self._channel_names is None:
try:
self._channel_names = [
str(channel["label"])
for channel in self._zarr.root_attrs["omero"]["channels"]
]
except KeyError:
self._channel_names = super().channel_names
return self._channel_names

@staticmethod
def _get_coords(
dims: List[str],
shape: Tuple[int, ...],
scene: str,
channel_names: Optional[List[str]],
) -> Dict[str, Any]:
coords: Dict[str, Any] = {}

# Use dims for coord determination
if dimensions.DimensionNames.Channel in dims:
# Generate channel names if no existing channel names
if channel_names is None:
coords[dimensions.DimensionNames.Channel] = [
metadata_utils.generate_ome_channel_id(image_id=scene, channel_id=i)
for i in range(shape[dims.index(dimensions.DimensionNames.Channel)])
]
else:
coords[dimensions.DimensionNames.Channel] = channel_names

return coords

@staticmethod
def _get_pixel_size(
reader: ZarrReader, dims: List[str], series_index: int, resolution_index: int
self, dims: List[str],
) -> Tuple[Optional[float], Optional[float], Optional[float]]:
# OmeZarr file may contain an additional set of "coordinateTransformations"
# these coefficents are applied to all resolution levels.
if (
"coordinateTransformations"
in reader.root_attrs["multiscales"][series_index]
in self._zarr.root_attrs["multiscales"][self.current_scene_index]
):
universal_res_consts = reader.root_attrs["multiscales"][series_index][
universal_res_consts = self._zarr.root_attrs["multiscales"][self.current_scene_index][
"coordinateTransformations"
][0]["scale"]
else:
universal_res_consts = [1.0 for _ in range(len(dims))]

coord_transform = reader.root_attrs["multiscales"][series_index]["datasets"][
resolution_index
coord_transform = self._zarr.root_attrs["multiscales"][self.current_scene_index]["datasets"][
self.current_resolution_level
]["coordinateTransformations"]

spatial_coeffs = {}
Expand All @@ -229,3 +203,37 @@ def _get_pixel_size(
spatial_coeffs[dimensions.DimensionNames.SpatialY],
spatial_coeffs[dimensions.DimensionNames.SpatialX],
)

@property
def channel_names(self) -> Optional[List[str]]:
if self._channel_names is None:
try:
self._channel_names = [
str(channel["label"])
for channel in self._zarr.root_attrs["omero"]["channels"]
]
except KeyError:
self._channel_names = super().channel_names
return self._channel_names

@staticmethod
def _get_coords(
dims: List[str],
shape: Tuple[int, ...],
scene: str,
channel_names: Optional[List[str]],
) -> Dict[str, Any]:
coords: Dict[str, Any] = {}

# Use dims for coord determination
if dimensions.DimensionNames.Channel in dims:
# Generate channel names if no existing channel names
if channel_names is None:
coords[dimensions.DimensionNames.Channel] = [
metadata_utils.generate_ome_channel_id(image_id=scene, channel_id=i)
for i in range(shape[dims.index(dimensions.DimensionNames.Channel)])
]
else:
coords[dimensions.DimensionNames.Channel] = channel_names

return coords
35 changes: 29 additions & 6 deletions bioio_ome_zarr/tests/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"filename, "
"set_scene, "
"expected_scenes, "
"set_resolution_level, "
"expected_resolution_levels, "
"expected_shape, "
"expected_dtype, "
"expected_dims_order, "
Expand All @@ -28,35 +30,43 @@
None,
None,
None,
None,
None,
marks=pytest.mark.xfail(raises=exceptions.UnsupportedFileFormatError),
),
# General Zarr
(
"s1_t1_c1_z1_Image_0.zarr",
"s1_t1_c1_z1",
("s1_t1_c1_z1",),
2,
(0, 1, 2, 3),
(1, 1, 1, 7548, 7549),
np.uint8,
dimensions.DEFAULT_DIMENSION_ORDER,
["Channel:0:0"],
(1.0, 264.5833333333333, 264.5833333333333),
(1.0, 1058.3333333333333, 1058.3333333333333),
),
# Complex General Zarr
(
"s1_t7_c4_z3_Image_0.zarr",
"s1_t7_c4_z3_Image_0",
("s1_t7_c4_z3_Image_0",),
3,
(0, 1, 2, 3),
(7, 4, 3, 1200, 1800),
np.uint16,
dimensions.DEFAULT_DIMENSION_ORDER,
["C:0", "C:1", "C:2", "C:3"],
(1.0, 1.0, 1.0),
(1.0, 8.0, 8.0),
),
# Test Resolution Constant
(
"resolution_constant_zyx.zarr",
"resolution_constant_zyx",
("resolution_constant_zyx",),
2,
(0, 1, 2, 3),
(2, 4, 4),
np.int64,
(
Expand All @@ -65,13 +75,15 @@
+ dimensions.DimensionNames.SpatialX
),
["Channel:0"],
(0.1, 0.1, 0.1),
(0.1, 0.4, 0.4),
),
# Test TYX
(
"dimension_handling_tyx.zarr",
"dimension_handling_tyx",
("dimension_handling_tyx",),
1,
(0, 1, 2, 3),
(2, 4, 4),
np.int64,
(
Expand All @@ -80,13 +92,15 @@
+ dimensions.DimensionNames.SpatialX
),
["Channel:0"],
(None, 1.0, 1.0),
(None, 2.0, 2.0),
),
# Test ZYX
(
"dimension_handling_zyx.zarr",
"dimension_handling_zyx",
("dimension_handling_zyx",),
1,
(0, 1, 2, 3),
(2, 4, 4),
np.int64,
(
Expand All @@ -95,13 +109,15 @@
+ dimensions.DimensionNames.SpatialX
),
["Channel:0"],
(1.0, 1.0, 1.0),
(1.0, 2.0, 2.0),
),
# Test TZYX
(
"dimension_handling_tzyx.zarr",
"dimension_handling_tzyx",
("dimension_handling_tzyx",),
0,
(0, 1, 2, 3),
(2, 2, 4, 4),
np.int64,
(
Expand All @@ -117,6 +133,8 @@
"absent_metadata_dims_zyx.zarr",
"absent_metadata_dims_zyx",
("absent_metadata_dims_zyx",),
3,
(0, 1, 2, 3),
(2, 4, 4),
np.int64,
(
Expand All @@ -125,14 +143,16 @@
+ dimensions.DimensionNames.SpatialX
),
["Channel:0"],
(1.0, 1.0, 1.0),
(1.0, 8.0, 8.0),
),
],
)
def test_ome_zarr_reader(
filename: str,
set_scene: str,
set_resolution_level: int,
expected_scenes: Tuple[str, ...],
expected_resolution_levels: Tuple[int, ...],
expected_shape: Tuple[int, ...],
expected_dtype: np.dtype,
expected_dims_order: str,
Expand All @@ -147,8 +167,11 @@ def test_ome_zarr_reader(
ImageContainer=Reader,
image=uri,
set_scene=set_scene,
set_resolution_level=set_resolution_level,
expected_scenes=expected_scenes,
expected_current_scene=set_scene,
expected_resolution_levels=expected_resolution_levels,
expected_current_resolution_level=set_resolution_level,
expected_shape=expected_shape,
expected_dtype=expected_dtype,
expected_dims_order=expected_dims_order,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ classifiers = [
]
dynamic = ["version"]
dependencies = [
"bioio-base>=0.2.0",
"bioio-base>=1.0.0",
"fsspec>=2022.8.0",
"ome-zarr>=0.8.0",
"xarray>=0.16.1",
Expand Down
Loading