Skip to content

Commit

Permalink
multiple bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzocerrone committed Sep 26, 2024
1 parent 95dd94d commit 53840b8
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 32 deletions.
54 changes: 54 additions & 0 deletions src/ngio/core/image_like_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Generic class to handle Image-like data in a OME-NGFF file."""

from functools import partial
from pathlib import Path
from typing import Literal
from warnings import warn
Expand All @@ -9,6 +10,7 @@
import zarr
from dask.delayed import Delayed
from dask.distributed import Lock
from scipy.ndimage import zoom

from ngio._common_types import ArrayLike
from ngio.core.dimensions import Dimensions
Expand Down Expand Up @@ -382,3 +384,55 @@ def set_data(
x=x, y=y, z=z, t=t, c=c, preserve_dimensions=preserve_dimensions
)
self._set_pipe(data_pipe=data_pipe, patch=patch)

def consolidate(self, order: int = 1) -> None:
"""Consolidate the Zarr array."""
processed_paths = [self]

todo_image = [
ImageLike(store=self.group, path=_path)
for _path in self.metadata.levels_paths
if _path != self.path
]

while todo_image:
dist_matrix = np.zeros((len(processed_paths), len(todo_image)))
for i, image in enumerate(todo_image):
for j, processed_image in enumerate(processed_paths):
dist_matrix[j, i] = np.sqrt(
np.sum(
[
(s1 - s2) ** 2
for s1, s2 in zip(
image.shape, processed_image.shape, strict=False
)
]
)
)

source, target = np.unravel_index(dist_matrix.argmin(), dist_matrix.shape)

source_image = processed_paths[source]
target_image = todo_image.pop(target)

zoom_factors = tuple(
[
s1 / s2
for s1, s2 in zip(
target_image.shape, source_image.shape, strict=False
)
]
)

partial_zoom = partial(zoom, zoom=zoom_factors, order=order)
out_image = da.map_blocks(
partial_zoom,
source_image.dask_array,
# dtype = source_image.dask_array,
chunks=target_image.array.chunks,
)
out_image = out_image.astype(target_image.array.dtype)
target_image.array[...] = out_image.compute()
# compute the transformation

processed_paths.append(target_image)
141 changes: 128 additions & 13 deletions src/ngio/core/label_handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""A module to handle OME-NGFF images stored in Zarr format."""

import zarr

from ngio.core.image_handler import Image
from ngio.core.image_like_handler import ImageLike
from ngio.core.utils import create_empty_ome_zarr_label
from ngio.io import StoreLike, StoreOrGroup
from ngio.ngff_meta.fractal_image_meta import LabelMeta, PixelSize

Expand All @@ -13,6 +17,7 @@ class Label(ImageLike):
"""

def __init__(
self,
store: StoreOrGroup,
*,
path: str | None = None,
Expand Down Expand Up @@ -47,6 +52,7 @@ def __init__(
cache=cache,
)

@property
def metadata(self) -> LabelMeta:
"""Return the metadata of the image."""
return super().metadata
Expand All @@ -55,23 +61,132 @@ def metadata(self) -> LabelMeta:
class LabelGroup:
"""A class to handle the /labels group in an OME-NGFF file."""

def __init__(self, group: StoreLike) -> None:
def __init__(
self,
group: StoreLike | zarr.Group,
image_ref: Image | None = None,
) -> None:
"""Initialize the LabelGroupHandler."""
self._group = group
if not isinstance(group, zarr.Group):
group = zarr.open_group(group, mode="a")

@property
def group_name(self) -> str:
"""Return the name of the group."""
return "labels"
if "labels" not in group:
self._group = group.create_group("labels")
self._group.attrs["labels"] = [] # initialize the labels attribute
else:
self._group: zarr.Group = group["labels"]

self._imgage_ref = image_ref

def list(self) -> list[str]:
"""List all labels in the group."""
return list(self._group.array_keys())
return self._group.attrs.get("labels", [])

def get(
self,
name: str,
path: str | None = None,
pixel_size: PixelSize | None = None,
highest_resolution: bool = True,
) -> Label:
"""Geta a Label from the group.
Args:
name (str): The name of the label.
path (str | None, optional): The path to the level.
pixel_size (tuple[float, ...] | list[float] | None, optional): The pixel
size of the level.
highest_resolution (bool, optional): Whether to get the highest
resolution level
"""
if name not in self.list():
raise ValueError(f"Label {name} not found in the group.")

def get(self, name: str) -> Label:
"""Get a label from the group."""
raise NotImplementedError("Not yet implemented.")
if path is not None or pixel_size is not None:
highest_resolution = False

return Label(
store=self._group[name],
path=path,
pixel_size=pixel_size,
highest_resolution=highest_resolution,
)

def derive(
self,
name: str,
overwrite: bool = False,
**kwargs,
) -> Label:
"""Derive a new label from an existing label.
Args:
name (str): The name of the new label.
overwrite (bool): If True, the label will be overwritten if it exists.
Default is False.
**kwargs: Additional keyword arguments to pass to the new label.
"""
list_of_labels = self.list()

if overwrite and name in list_of_labels:
self._group.attrs["label"] = [
label for label in list_of_labels if label != name
]
elif not overwrite and name in list_of_labels:
raise ValueError(f"Label {name} already exists in the group.")

# create the new label
new_label_group = self._group.create_group(name, overwrite=overwrite)

if self._imgage_ref is None:
label_0 = self.get(list_of_labels[0])
metadata = label_0.metadata
on_disk_shape = label_0.on_disk_shape
chunks = label_0.array.chunks
dataset = label_0.dataset
else:
label_0 = self._imgage_ref
metadata = label_0.metadata
channel_index = metadata.index_mapping.get("c", None)
if channel_index is not None:
on_disk_shape = (
label_0.on_disk_shape[:channel_index]
+ label_0.on_disk_shape[channel_index + 1 :]
)
chunks = (
label_0.array.chunks[:channel_index]
+ label_0.array.chunks[channel_index + 1 :]
)
else:
on_disk_shape = label_0.on_disk_shape
chunks = label_0.array.chunks

metadata = metadata.remove_axis("c")
dataset = metadata.get_highest_resolution_dataset()

default_kwargs = {
"store": new_label_group,
"shape": on_disk_shape,
"chunks": chunks,
"dtype": label_0.array.dtype,
"on_disk_axis": dataset.on_disk_axes_names,
"pixel_sizes": dataset.pixel_size,
"xy_scaling_factor": metadata.xy_scaling_factor,
"z_scaling_factor": metadata.z_scaling_factor,
"time_spacing": dataset.time_spacing,
"time_units": dataset.time_axis_unit,
"num_levels": metadata.num_levels,
"name": name,
"overwrite": overwrite,
"version": metadata.version,
}

default_kwargs.update(kwargs)

create_empty_ome_zarr_label(
**default_kwargs,
)

def write(self, name: str, data: Label) -> None:
"""Create a label in the group."""
raise NotImplementedError("Not yet implemented.")
if name not in self.list():
self._group.attrs["labels"] = [*list_of_labels, name]
return self.get(name)
85 changes: 77 additions & 8 deletions src/ngio/core/ngff_image.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
"""Abstract class for handling OME-NGFF images."""

from typing import Protocol, TypeVar
from typing import Any, Protocol, TypeVar

import numpy as np

from ngio.core.image_handler import Image
from ngio.core.label_handler import LabelGroup
from ngio.core.utils import create_empty_ome_zarr_image
from ngio.io import StoreLike, open_group_wrapper
from ngio.ngff_meta import FractalImageLabelMeta, get_ngff_image_meta_handler
from ngio.ngff_meta import get_ngff_image_meta_handler
from ngio.ngff_meta.fractal_image_meta import ImageMeta, PixelSize
from ngio.tables.tables_group import TableGroup

T = TypeVar("T")

Expand Down Expand Up @@ -70,8 +76,11 @@ def __init__(self, store: StoreLike) -> None:
self.group, meta_mode="image", cache=False
)

self.table = TableGroup(self.group)
self.label = LabelGroup(self.group, image_ref=self.get_image())

@property
def image_meta(self) -> FractalImageLabelMeta:
def image_meta(self) -> ImageMeta:
"""Get the image metadata."""
return self._image_meta.load_meta()

Expand All @@ -88,14 +97,14 @@ def levels_paths(self) -> list[str]:
def get_image(
self,
*,
level_path: str | int | None = None,
pixel_size: tuple[float, ...] | list[float] | None = None,
path: str | None = None,
pixel_size: PixelSize | None = None,
highest_resolution: bool = True,
) -> Image:
"""Get an image handler for the given level.
Args:
level_path (str | int | None, optional): The path to the level.
path (str | None, optional): The path to the level.
pixel_size (tuple[float, ...] | list[float] | None, optional): The pixel
size of the level.
highest_resolution (bool, optional): Whether to get the highest
Expand All @@ -104,23 +113,83 @@ def get_image(
Returns:
ImageHandler: The image handler.
"""
if path is not None or pixel_size is not None:
highest_resolution = False

return Image(
store=self.group,
level_path=level_path,
path=path,
pixel_size=pixel_size,
highest_resolution=highest_resolution,
)

def _update_omero_window(self) -> None:
"""Update the OMERO window."""
meta = self.image_meta
image = self.get_image(highest_resolution=True)
max_dtype = np.iinfo(image.array.dtype).max
start, end = image.dask_array.min().compute(), image.dask_array.max().compute()

channel_list = meta.omero.channels

new_channel_list = []
for channel in channel_list:
channel.extra_fields["window"] = {
"start": start,
"end": end,
"min": 0,
"max": max_dtype,
}
new_channel_list.append(channel)

meta.omero.channels = new_channel_list
self._image_meta.write_meta(meta)

def derive_new_image(
self,
store: StoreLike,
name: str,
overwrite: bool = True,
**kwargs,
) -> "NgffImage":
"""Derive a new image from the current image.
Args:
store (StoreLike): The store to create the new image in.
name (str): The name of the new image.
overwrite (bool): Whether to overwrite the image if it exists
**kwargs: Additional keyword arguments.
Follow the same signature as `create_empty_ome_zarr_image`.
Returns:
NgffImage: The new image.
"""
raise NotImplementedError("Deriving new images is not yet implemented.")
image_0 = self.get_image(highest_resolution=True)

default_kwargs = {
"store": store,
"shape": image_0.on_disk_shape,
"chunks": image_0.array.chunks,
"dtype": image_0.array.dtype,
"on_disk_axis": image_0.dataset.on_disk_axes_names,
"pixel_sizes": image_0.pixel_size,
"xy_scaling_factor": self.image_meta.xy_scaling_factor,
"z_scaling_factor": self.image_meta.z_scaling_factor,
"time_spacing": image_0.dataset.time_spacing,
"time_units": image_0.dataset.time_axis_unit,
"num_levels": self.num_levels,
"name": name,
"channel_labels": image_0.channel_labels,
"channel_wavelengths": None,
"channel_kwargs": None,
"omero_kwargs": None,
"overwrite": overwrite,
"version": self.image_meta.version,
}

default_kwargs.update(kwargs)

create_empty_ome_zarr_image(
**default_kwargs,
)
return NgffImage(store=store)
Loading

0 comments on commit 53840b8

Please sign in to comment.