Skip to content

Commit

Permalink
add roi first implementation, and dimensio model
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzocerrone committed Sep 20, 2024
1 parent f2d8da4 commit 97fb66a
Show file tree
Hide file tree
Showing 15 changed files with 419 additions and 86 deletions.
5 changes: 5 additions & 0 deletions src/ngio/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
"""Core classes for the ngio library."""

from ngio.core.image_handler import Image
from ngio.core.label_handler import Label

__all__ = ["Image", "Label"]
102 changes: 102 additions & 0 deletions src/ngio/core/dimensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""Dimension metadata.
This is not related to the NGFF metadata,
but it is based on the actual metadata of the image data.
"""

from zarr import Array


class Dimensions:
"""Dimension metadata."""

def __init__(
self, array: Array, axes_names: list[str], axes_order: list[int]
) -> None:
"""Create a Dimension object from a Zarr array.
Args:
array (Array): The Zarr array.
axes_names (list[str]): The names of the axes.
axes_order (list[int]): The order of the axes.
"""
# We init with the shape only but in the ZarrV3
# we will have to validate the axes names too.
self._on_disk_shape = array.shape

if len(self._on_disk_shape) != len(axes_names):
raise ValueError(
"The number of axes names must match the number of dimensions."
)

self._axes_names = axes_names
self._axes_order = axes_order
self._shape = [self._on_disk_shape[i] for i in axes_order]
self._shape_dict = dict(zip(axes_names, self._shape, strict=True))

@property
def shape(self) -> tuple[int, ...]:
"""Return the shape as a tuple."""
return tuple(self._shape)

@property
def on_disk_shape(self) -> tuple[int, ...]:
"""Return the shape as a tuple."""
return tuple(self._on_disk_shape)

def ad_dict(self) -> dict[str, int]:
"""Return the shape as a dictionary."""
return self._shape_dict

@property
def t(self) -> int:
"""Return the time dimension."""
return self._shape_dict.get("t", None)

@property
def c(self) -> int:
"""Return the channel dimension."""
return self._shape_dict.get("c", None)

@property
def z(self) -> int:
"""Return the z dimension."""
return self._shape_dict.get("z", None)

@property
def y(self) -> int:
"""Return the y dimension."""
return self._shape_dict.get("y", None)

@property
def x(self) -> int:
"""Return the x dimension."""
return self._shape_dict.get("x", None)

@property
def on_disk_ndim(self) -> int:
"""Return the number of dimensions on disk."""
return len(self._on_disk_shape)

@property
def ndim(self) -> int:
"""Return the number of dimensions."""
return len(self._shape)

def is_3D(self) -> bool:
"""Return whether the data is 3D."""
if (self.z is None) or (self.z == 1):
return False
return True

def is_time_series(self) -> bool:
"""Return whether the data is a time series."""
if (self.t is None) or (self.t == 1):
return False
return True

def has_multiple_channels(self) -> bool:
"""Return whether the data has multiple channels."""
if (self.c is None) or (self.c == 1):
return False
return True
69 changes: 65 additions & 4 deletions src/ngio/core/image_like_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

from typing import Literal

import numpy as np
import zarr

from ngio.io import StoreOrGroup, open_group
from ngio.core.dimensions import Dimensions
from ngio.io import StoreOrGroup, open_group_wrapper
from ngio.ngff_meta import (
Dataset,
ImageLabelMeta,
Expand Down Expand Up @@ -47,23 +49,57 @@ def __init__(
cache (bool): Whether to cache the metadata.
"""
if not isinstance(store, zarr.Group):
store = open_group(store=store, mode="r")
store = open_group_wrapper(store=store, mode="r+")

self._group = store

self._metadata_handler = get_ngff_image_meta_handler(
store=store, meta_mode=meta_mode, cache=cache
)

# Find the level / resolution index
metadata = self._metadata_handler.load_meta()
self._dataset = metadata.get_dataset(
dataset = metadata.get_dataset(
path=path,
idx=idx,
pixel_size=pixel_size,
highest_resolution=highest_resolution,
strict=strict,
)
self._group = store
self._init_dataset(dataset)

def _init_dataset(self, dataset: Dataset):
"""Set the dataset of the image.
This method is for internal use only.
"""
self._dataset = dataset

if self._dataset.path not in self._group.array_keys():
raise ValueError(f"Dataset {self._dataset.path} not found in the group.")

self._array = self._group[self.dataset.path]
self._diminesions = Dimensions(
array=self._array,
axes_names=self._dataset.axes_names,
axes_order=self._dataset.axes_order,
)

def _debug_set_new_dataset(
self,
new_dataset: Dataset,
):
"""Debug method to change the the dataset metadata.
This methods allow to change dataset after initialization.
This allow to skip the OME-NGFF metadata validation.
This method is for testing/debug purposes only.
DO NOT USE THIS METHOD IN PRODUCTION CODE.
"""
self._init_dataset(new_dataset)

# Method to get the metadata of the image
@property
def group(self) -> zarr.Group:
"""Return the Zarr group containing the image data."""
Expand Down Expand Up @@ -108,3 +144,28 @@ def space_axes_unit(self) -> SpaceUnits:
def pixel_size(self) -> PixelSize:
"""Return the pixel resolution of the image."""
return self.dataset.pixel_size

# Method to get the data of the image
@property
def array(self) -> zarr.Array:
"""Return the image data as a Zarr array."""
return self._array

@property
def dimensions(self) -> Dimensions:
"""Return the dimensions of the image."""
return self._diminesions

@property
def shape(self) -> tuple[int, ...]:
"""Return the shape of the image."""
return self.dimensions.shape

@property
def on_disk_shape(self) -> tuple[int, ...]:
"""Return the shape of the image."""
return self.dimensions.on_disk_shape

def get_data(self) -> np.ndarray:
"""Return the image data as a Zarr array."""
return self.array[...]
4 changes: 2 additions & 2 deletions src/ngio/core/ngff_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from zarr.store.common import StoreLike

from ngio.core.image_handler import Image
from ngio.io import open_group
from ngio.io import open_group_wrapper
from ngio.ngff_meta import FractalImageLabelMeta, get_ngff_image_meta_handler

T = TypeVar("T")
Expand Down Expand Up @@ -67,7 +67,7 @@ class NgffImage:
def __init__(self, store: StoreLike) -> None:
"""Initialize the NGFFImage in read mode."""
self.store = store
self.group = open_group(store=store, mode="r+")
self.group = open_group_wrapper(store=store, mode="r+")
self._image_meta = get_ngff_image_meta_handler(
self.group, meta_mode="image", cache=False
)
Expand Down
40 changes: 36 additions & 4 deletions src/ngio/core/roi.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pydantic import BaseModel

from ngio.ngff_meta.fractal_image_meta import SpaceUnits, PixelSize
import numpy as np


class Point(BaseModel):
Expand All @@ -15,13 +16,31 @@ class WorldCooROI(BaseModel):
"""Region of interest (ROI) metadata."""

field_index: str
p1: Point
p2: Point
x: float
y: float
z: float
x_length: float
y_length: float
z_length: float
unit: SpaceUnits

def to_raster_coo(self, pixel_size: float) -> "RasterCooROI":
def _to_raster(self, value: float, pixel_size: PixelSize, max_shape: int) -> int:
"""Convert to raster coordinates."""
raise NotImplementedError
round_value = int(np.round(value / pixel_size))
return min(round_value, max_shape)

def to_raster_coo(self, pixel_size: PixelSize, max_shape) -> "RasterCooROI":
"""Convert to raster coordinates."""
RasterCooROI(
field_index=self.field_index,
x=self._to_raster(value=self.x, pixel_size=pixel_size.x, max_shape=2**32),
y=int(self.y / pixel_size.y),
z=int(self.z / pixel_size.z),
x_length=int(self.x_length / pixel_size.x),
y_length=int(self.y_length / pixel_size.y),
z_length=int(self.z_length / pixel_size.z),
original_roi=self,
)


class RasterCooROI(BaseModel):
Expand All @@ -34,7 +53,20 @@ class RasterCooROI(BaseModel):
x_length: int
y_length: int
z_length: int
original_roi: WorldCooROI

def to_world_coo(self, pixel_size: float) -> "WorldCooROI":
"""Convert to world coordinates."""
raise NotImplementedError

def x_slice(self) -> slice:
"""Return the slice for the x-axis."""
return slice(self.x, self.x + self.x_length)

def y_slice(self) -> slice:
"""Return the slice for the y-axis."""
return slice(self.y, self.y + self.y_length)

def z_slice(self) -> slice:
"""Return the slice for the z-axis."""
return slice(self.z, self.z + self.z_length)
6 changes: 3 additions & 3 deletions src/ngio/ngff_meta/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
)


def _create_image_metadata(
def _create_multiscale_meta(
on_disk_axis: list[str] = ("t", "c", "z", "y", "x"),
pixel_sizes: PixelSize | None = None,
xy_scaling_factor: float = 2.0,
Expand Down Expand Up @@ -110,7 +110,7 @@ def create_image_metadata(
version: The version of NGFF metadata.
"""
datasets = _create_image_metadata(
datasets = _create_multiscale_meta(
on_disk_axis=on_disk_axis,
pixel_sizes=pixel_sizes,
xy_scaling_factor=xy_scaling_factor,
Expand Down Expand Up @@ -192,7 +192,7 @@ def create_label_metadata(
name: The name of the metadata.
version: The version of NGFF metadata.
"""
datasets, _ = _create_image_metadata(
datasets = _create_multiscale_meta(
on_disk_axis=on_disk_axis,
pixel_sizes=pixel_sizes,
xy_scaling_factor=xy_scaling_factor,
Expand Down
7 changes: 7 additions & 0 deletions src/ngio/pipes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""A module to handle data transforms for image data."""

from ngio.pipes.common import ArrayLike
from ngio.pipes._slicer_transforms import NaiveSlicer
from ngio.pipes.data_transform_pipe import DataTransformPipe

__all__ = ["ArrayLike", "DataTransformPipe", "NaiveSlicer"]
Loading

0 comments on commit 97fb66a

Please sign in to comment.