Skip to content

Commit

Permalink
Merge pull request #13 from fractal-analytics-platform/nice_repr
Browse files Browse the repository at this point in the history
Nice repr for ngio objects
  • Loading branch information
lorenzocerrone authored Nov 13, 2024
2 parents 7cf8db8 + 75b9eec commit ebb8c1b
Show file tree
Hide file tree
Showing 20 changed files with 379 additions and 59 deletions.
7 changes: 7 additions & 0 deletions docs/notebooks/basic_usage.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@
"print(f\"{new_ngff_image.levels_paths=}\")\n",
"print(f\"{new_ngff_image.num_levels=}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand Down
2 changes: 1 addition & 1 deletion docs/notebooks/processing.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"\n",
"mip_ngff = ngff_image.derive_new_image(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr/B/03/0_mip\",\n",
" name=\"MIP\",\n",
" shape=(1, 1, 2160, 5120))"
" on_disk_shape=(1, 1, 2160, 5120))"
]
},
{
Expand Down
12 changes: 12 additions & 0 deletions src/ngio/core/image_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ def __init__(
_label_group=label_group,
)

def __repr__(self) -> str:
"""Return the string representation of the class."""
name = "Image("
len_name = len(name)
return (
f"{name}"
f"path={self.path},\n"
f"{' ':>{len_name}}{self.pixel_size},\n"
f"{' ':>{len_name}}{self.dimensions},\n"
")"
)

@property
def metadata(self) -> ImageMeta:
"""Return the metadata of the image."""
Expand Down
21 changes: 21 additions & 0 deletions src/ngio/core/image_like_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,27 @@ def group(self) -> zarr.Group:
"""Return the Zarr group containing the image data."""
return self._group

@property
def root_path(self) -> str:
"""Return the path to the root group."""
return str(self._group.store.path)

@property
def group_path(self) -> str:
"""Return the path to the group."""
root = self.root_path
if root.endswith("/"):
root = root[:-1]
return f"{root}/{self._group.path}"

@property
def array_path(self) -> str:
"""Return the path to the array."""
group = self.group_path
if group.endswith("/"):
group = group[:-1]
return f"{group}/{self.path}"

@property
def metadata(self) -> ImageLabelMeta:
"""Return the metadata of the image."""
Expand Down
23 changes: 23 additions & 0 deletions src/ngio/core/label_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def __init__(
self,
store: StoreOrGroup,
*,
name: str,
path: str | None = None,
idx: int | None = None,
pixel_size: PixelSize | None = None,
Expand All @@ -41,6 +42,7 @@ def __init__(
Args:
store (StoreOrGroup): The Zarr store or group containing the image data.
name (str): The name of the label.
path (str | None): The path to the level.
idx (int | None): The index of the level.
pixel_size (PixelSize | None): The pixel size of the level.
Expand All @@ -64,6 +66,26 @@ def __init__(
_label_group=label_group,
)

self._name = name

def __repr__(self) -> str:
"""Return the string representation of the label."""
name = "Label("
len_name = len(name)
return (
f"{name}"
f"path={self.path},\n"
f"{' ':>{len_name}}name={self.name},\n"
f"{' ':>{len_name}}{self.pixel_size},\n"
f"{' ':>{len_name}}{self.dimensions},\n"
")"
)

@property
def name(self) -> str:
"""Return the name of the label."""
return self._name

@property
def metadata(self) -> LabelMeta:
"""Return the metadata of the image."""
Expand Down Expand Up @@ -277,6 +299,7 @@ def get_label(

return Label(
store=self._label_group[name],
name=name,
path=path,
pixel_size=pixel_size,
highest_resolution=highest_resolution,
Expand Down
107 changes: 77 additions & 30 deletions src/ngio/core/ngff_image.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Abstract class for handling OME-NGFF images."""

from typing import Any

import dask.array as da
import numpy as np

Expand Down Expand Up @@ -37,6 +39,19 @@ def __init__(
ngio_logger.info(f"Opened image located in store: {store}")
ngio_logger.info(f"- Image number of levels: {self.num_levels}")

def __repr__(self) -> str:
"""Get the string representation of the image."""
name = "NGFFImage("
len_name = len(name)
return (
f"{name}"
f"store={self.store}, \n"
f"{' ':>{len_name}}paths={self.levels_paths}, \n"
f"{' ':>{len_name}}labels={self.label.list()}, \n"
f"{' ':>{len_name}}tables={self.table.list()}, \n"
")"
)

@property
def image_meta(self) -> ImageMeta:
"""Get the image metadata."""
Expand Down Expand Up @@ -126,60 +141,76 @@ def _compute_percentiles(
starts, ends = [], []
for c in range(num_c):
data = lowest_res_image.get_array(c=c, mode="dask").ravel()
_start_percentile = da.percentile(
data, start_percentile, method="nearest"
).compute()
_end_percentile = da.percentile(
data, end_percentile, method="nearest"
_start_percentile, _end_percentile = da.percentile(
data, [start_percentile, end_percentile], method="nearest"
).compute()

starts.append(_start_percentile)
ends.append(_end_percentile)

return starts, ends

def set_omero(
def lazy_init_omero(
self,
labels: list[str],
wavelengths: list[str] | None = None,
labels: list[str] | int | None = None,
wavelength_ids: list[str] | None = None,
colors: list[str] | None = None,
adjust_window: bool = True,
start_percentile: int = 5,
end_percentile: int = 95,
active: list[bool] | None = None,
write: bool = True,
start_percentile: float | None = 1,
end_percentile: float | None = 99,
data_type: Any = np.uint16,
consolidate: bool = True,
) -> None:
"""Set the OMERO metadata for the image.
Args:
labels (list[str]): The labels of the channels.
wavelengths (list[str], optional): The wavelengths of the channels.
colors (list[str], optional): The colors of the channels.
adjust_window (bool, optional): Whether to adjust the window.
start_percentile (int, optional): The start percentile.
end_percentile (int, optional): The end percentile.
active (list[bool], optional): Whether the channel is active.
write (bool, optional): Whether to write the metadata to the image.
labels (list[str] | int | None): The labels of the channels.
wavelength_ids (list[str] | None): The wavelengths of the channels.
colors (list[str] | None): The colors of the channels.
active (list[bool] | None): Whether the channels are active.
start_percentile (float | None): The start percentile for computing the data
range. If None, the start is the same as the min value of the data type.
end_percentile (float | None): The end percentile for for computing the data
range. If None, the start is the same as the max value of the data type.
data_type (Any): The data type of the image.
consolidate (bool): Whether to consolidate the metadata.
"""
image_ref = self.get_image()
if labels is None:
ref = self.get_image()
labels = ref.num_channels

if adjust_window:
if start_percentile is not None and end_percentile is not None:
start, end = self._compute_percentiles(
start_percentile=start_percentile, end_percentile=end_percentile
)
elif start_percentile is None and end_percentile is None:
raise ValueError("Both start and end percentiles cannot be None.")
elif end_percentile is None and start_percentile is not None:
raise ValueError(
"End percentile cannot be None if start percentile is not."
)
else:
start, end = None, None

self.image_meta.omero_lazy_init(
self.image_meta.lazy_init_omero(
labels=labels,
wavelength_ids=wavelengths,
wavelength_ids=wavelength_ids,
colors=colors,
active=active,
start=start,
end=end,
data_type=image_ref.on_disk_array.dtype,
active=active,
data_type=data_type,
)

if consolidate:
self._image_meta.write_meta(self.image_meta)

def update_omero_window(
self, start_percentile: int = 5, end_percentile: int = 95
self,
start_percentile: int = 1,
end_percentile: int = 99,
min_value: int | float | None = None,
max_value: int | float | None = None,
) -> None:
"""Update the OMERO window.
Expand All @@ -188,6 +219,8 @@ def update_omero_window(
Args:
start_percentile (int): The start percentile.
end_percentile (int): The end percentile
min_value (int | float | None): The minimum value of the window.
max_value (int | float | None): The maximum value of the window.
"""
start, ends = self._compute_percentiles(
Expand All @@ -196,7 +229,21 @@ def update_omero_window(
meta = self.image_meta
ref_image = self.get_image()

max_dtype = np.iinfo(ref_image.on_disk_array.dtype).max
for func in [np.iinfo, np.finfo]:
try:
type_max = func(ref_image.on_disk_array.dtype).max
type_min = func(ref_image.on_disk_array.dtype).min
break
except ValueError:
continue
else:
raise ValueError("Data type not recognized.")

if min_value is None:
min_value = type_min
if max_value is None:
max_value = type_max

num_c = ref_image.dimensions.get("c", 1)

if meta.omero is None:
Expand All @@ -216,8 +263,8 @@ def update_omero_window(
):
channel.channel_visualisation.start = s
channel.channel_visualisation.end = e
channel.channel_visualisation.min = 0
channel.channel_visualisation.max = max_dtype
channel.channel_visualisation.min = min_value
channel.channel_visualisation.max = max_value

ngio_logger.info(
f"Updated window for channel {channel.label}. "
Expand Down
22 changes: 11 additions & 11 deletions src/ngio/core/roi.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from ngio.ngff_meta.fractal_image_meta import PixelSize, SpaceUnits


def _to_raster(value: float, pixel_size: PixelSize, max_shape: int) -> int:
def _to_raster(value: float, pixel_size: float, max_shape: int) -> int:
"""Convert to raster coordinates."""
round_value = int(np.round(value / pixel_size))
return min(round_value, max_shape)
Expand All @@ -40,13 +40,17 @@ def to_raster_coo(
self, pixel_size: PixelSize, dimensions: Dimensions
) -> "RasterCooROI":
"""Convert to raster coordinates."""
dim_x = dimensions.get("x")
dim_y = dimensions.get("y")
dim_z = dimensions.get("z", 1)

return RasterCooROI(
x=_to_raster(self.x, pixel_size.x, dimensions.x),
y=_to_raster(self.y, pixel_size.y, dimensions.y),
z=_to_raster(self.z, pixel_size.z, dimensions.z),
x_length=_to_raster(self.x_length, pixel_size.x, dimensions.x),
y_length=_to_raster(self.y_length, pixel_size.y, dimensions.y),
z_length=_to_raster(self.z_length, pixel_size.z, dimensions.z),
x=_to_raster(self.x, pixel_size.x, dim_x),
y=_to_raster(self.y, pixel_size.y, dim_y),
z=_to_raster(self.z, pixel_size.z, dim_z),
x_length=_to_raster(self.x_length, pixel_size.x, dim_x),
y_length=_to_raster(self.y_length, pixel_size.y, dim_y),
z_length=_to_raster(self.z_length, pixel_size.z, dim_z),
original_roi=self,
)

Expand All @@ -64,10 +68,6 @@ class RasterCooROI(BaseModel):

def to_world_coo_roi(self, pixel_size: PixelSize) -> WorldCooROI:
"""Convert to world coordinates."""
if self.field_index is None:
raise ValueError(
"Field index must be provided to convert to world coordinates roi."
)
return WorldCooROI(
x=_to_world(self.x, pixel_size.x),
y=_to_world(self.y, pixel_size.y),
Expand Down
22 changes: 17 additions & 5 deletions src/ngio/ngff_meta/fractal_image_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,22 @@ def lazy_init(
data_type(Any): The data type of the channel.
active(bool): Whether the channel should be shown by default.
"""
start = start if start is not None else np.iinfo(data_type).min
end = end if end is not None else np.iinfo(data_type).max
for func in [np.iinfo, np.finfo]:
try:
min_value = func(data_type).min
max_value = func(data_type).max
break
except ValueError:
continue
else:
raise ValueError(f"Invalid data type {data_type}.")

start = start if start is not None else min_value
end = end if end is not None else max_value
return ChannelVisualisation(
color=color,
min=np.iinfo(data_type).min,
max=np.iinfo(data_type).max,
min=min_value,
max=max_value,
start=start,
end=end,
active=active,
Expand Down Expand Up @@ -246,7 +256,9 @@ def _check_elements(elements: Collection[T], expected_type: Any) -> Collection[T

for element in elements:
if not isinstance(element, expected_type):
raise ValueError(f"All elements must be of the same type {expected_type}.")
raise ValueError(
f"All elements must be of the same type {expected_type}. Got {element}."
)

return elements

Expand Down
Loading

0 comments on commit ebb8c1b

Please sign in to comment.