Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c47e43b
add `ngff_zarr` dependency
jo-mueller Aug 13, 2025
5be3696
Refactor to_multiscale to use ngff_zarr for multiscale generation
jo-mueller Aug 13, 2025
3d27179
set default downsample to avoid unreachable code exception
jo-mueller Aug 14, 2025
3f44ff0
typo
jo-mueller Aug 14, 2025
b25b851
use ngff_zarr methods as options for downsampling
jo-mueller Aug 14, 2025
43eb4a0
infer axis units and names from spatial_image
jo-mueller Aug 14, 2025
7581071
Used `ITK_BIN_SHRINK` as default for compliance with previous behavior
jo-mueller Aug 15, 2025
9390571
added escape for zarr v2/v3 store creation
jo-mueller Aug 15, 2025
d4537f5
reran notebook with new multiscales function
jo-mueller Aug 15, 2025
3110b11
infer scales from `spatial_image`
jo-mueller Aug 15, 2025
c305cf1
infer translation from `si`
jo-mueller Aug 15, 2025
a086bc9
refactor variable name in multiscale generation loop
jo-mueller Aug 15, 2025
e7c7326
set metadata to None properly rather than self-referential values
jo-mueller Aug 15, 2025
de22f35
convert scale value to floats
jo-mueller Aug 15, 2025
a9222a5
import methods from ngff_zarr under distinguishable name
jo-mueller Aug 15, 2025
895f577
syntax error
jo-mueller Aug 15, 2025
86e8169
fix indendation (code stle)
jo-mueller Aug 18, 2025
8aed1c1
added hint for ascending scale factors to readme
jo-mueller Aug 19, 2025
63ebbef
fixed escape for axes units being unset
jo-mueller Aug 19, 2025
cb4f726
fixed ascending level order in tests
jo-mueller Aug 19, 2025
286478d
specify method to generate multiscale in test
jo-mueller Aug 19, 2025
803095f
use dask downsampling in test
jo-mueller Aug 19, 2025
9504d34
update test data IDs
jo-mueller Aug 19, 2025
9320bce
store image with correct baseline name
jo-mueller Aug 20, 2025
bac128b
updated data links
jo-mueller Aug 20, 2025
e78a7f7
Update multiscale_spatial_image/to_multiscale/to_multiscale.py
jo-mueller Aug 29, 2025
94f5433
Update multiscale_spatial_image/to_multiscale/to_multiscale.py
jo-mueller Aug 29, 2025
4608c58
Update multiscale_spatial_image/to_multiscale/to_multiscale.py
jo-mueller Sep 15, 2025
5f0c28e
Update multiscale_spatial_image/to_multiscale/to_multiscale.py
jo-mueller Sep 15, 2025
60c6897
remove unfit test condition for xarrays
jo-mueller Sep 16, 2025
098fffa
replace hashes and CID
jo-mueller Sep 16, 2025
08ac6fd
fix baseline_name
jo-mueller Sep 16, 2025
35d6970
replace data storage with Zenodo
jo-mueller Sep 17, 2025
5b6315d
Revert "replace data storage with Zenodo"
jo-mueller Sep 22, 2025
4139b6c
Use pinata.cloud for testing
jo-mueller Sep 22, 2025
003e777
added gateway variable for s3 storage
jo-mueller Sep 22, 2025
624e354
add https to gateway
jo-mueller Sep 24, 2025
22db6a1
ENH: Update Python minimum required version to 3.11
thewtex Nov 13, 2025
e6ef8ec
BUG: Upgrade zarr-python dependency to 3.1.3
thewtex Nov 13, 2025
6ef4e7c
COMP: Rename tool.pixi.project to tool.pixi.workspace
thewtex Nov 13, 2025
c6cf8fa
Merge pull request #1 from thewtex/use-ngff_zarr-in-`to_multiscale`
jo-mueller Nov 13, 2025
f0a6b8b
remove python 3.10 from test matrix
jo-mueller Nov 13, 2025
e47efb9
Update pixi.lock
jo-mueller Nov 13, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
max-parallel: 5
matrix:
os: [ubuntu-24.04, windows-2022, macos-13]
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand Down
226 changes: 132 additions & 94 deletions examples/ConvertImageioImageResource.ipynb

Large diffs are not rendered by default.

740 changes: 422 additions & 318 deletions examples/ConvertTiffFile.ipynb

Large diffs are not rendered by default.

164 changes: 75 additions & 89 deletions multiscale_spatial_image/to_multiscale/to_multiscale.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
from typing import Union, Sequence, Optional, Dict, Mapping, Any, Tuple
from enum import Enum
import warnings

from spatial_image import SpatialImage # type: ignore


from spatial_image import SpatialImage, to_spatial_image # type: ignore
import ngff_zarr as nz
from xarray import DataTree

from ._xarray import _downsample_xarray_coarsen
from ._itk import (
_downsample_itk_bin_shrink,
_downsample_itk_gaussian,
_downsample_itk_label,
)
from ._dask_image import _downsample_dask_image
from .._docs import inject_docs

from ngff_zarr import Methods as NZMethods

class Methods(Enum):
XARRAY_COARSEN = "xarray_coarsen"
Expand All @@ -25,12 +18,21 @@ class Methods(Enum):
DASK_IMAGE_MODE = "dask_image_mode"
DASK_IMAGE_NEAREST = "dask_image_nearest"
Copy link
Contributor

Choose a reason for hiding this comment

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

We can also add ITKWASM_LABEL_IMAGE



@inject_docs(m=Methods)
MSI_METHOD_MAPPING = {
'xarray_coarsen': NZMethods.ITK_BIN_SHRINK,
'itk_bin_shrink': NZMethods.ITK_BIN_SHRINK,
'itk_gaussian': NZMethods.ITK_GAUSSIAN,
'itk_label_gaussian': NZMethods.ITKWASM_LABEL_IMAGE,
'dask_image_gaussian': NZMethods.DASK_IMAGE_GAUSSIAN,
'dask_image_mode': NZMethods.DASK_IMAGE_MODE,
'dask_image_nearest': NZMethods.DASK_IMAGE_NEAREST,
}

@inject_docs(m=NZMethods)
def to_multiscale(
image: SpatialImage,
scale_factors: Sequence[Union[Dict[str, int], int]],
method: Optional[Methods] = None,
method: Optional[NZMethods] = None,
chunks: Optional[
Union[
int,
Expand All @@ -51,16 +53,19 @@ def to_multiscale(

scale_factors : int per scale or dict of spatial dimension int's per scale
Integer scale factors to apply uniformly across all spatial dimension or
along individual spatial dimensions.
Examples: [2, 2] or [{{'x': 2, 'y': 4 }}, {{'x': 5, 'y': 10}}]
along individual spatial dimensions. The scale factors need to be passed
in **ascending order**.
This will work: [2, 4] or [{{'x': 2, 'y': 4}}, {{'x': 5, 'y': 10}}].
This will not work: [4, 2] or [{{'x': 5, 'y': 10}}, {{'x': 2, 'y': 4}}].

method : multiscale_spatial_image.Methods, optional
Method to reduce the input image. Available methods are the following:

- `{m.XARRAY_COARSEN.value!r}` - Use xarray coarsen to downsample the image.
- `{m.ITK_BIN_SHRINK.value!r}` - Use ITK BinShrinkImageFilter to downsample the image.
- `{m.ITK_BIN_SHRINK.value!r}` - Use ITK ShrinkImageFilter to downsample the image (DEFAULT).
- `{m.ITKWASM_BIN_SHRINK.value!r}` - Use ITKWASM BinShrinkImageFilter to downsample the image.
- `{m.ITK_GAUSSIAN.value!r}` - Use ITK GaussianImageFilter to downsample the image.
- `{m.ITK_LABEL_GAUSSIAN.value!r}` - Use ITK LabelGaussianImageFilter to downsample the image.
- `{m.ITKWASM_GAUSSIAN.value!r}` - Use ITK ShrinkImageFilter to downsample the image.
- `{m.ITKWASM_LABEL_IMAGE.value!r}` - Use ITK LabelGaussianImageFilter to downsample the image.
- `{m.DASK_IMAGE_GAUSSIAN.value!r}` - Use dask-image gaussian_filter to downsample the image.
- `{m.DASK_IMAGE_MODE.value!r}` - Use dask-image mode_filter to downsample the image.
- `{m.DASK_IMAGE_NEAREST.value!r}` - Use dask-image zoom to downsample the image.
Expand All @@ -84,6 +89,8 @@ def to_multiscale(
default_chunks = {d: default_chunks for d in image.dims}
if "t" in image.dims:
default_chunks["t"] = 1
if "c" in image.dims:
default_chunks["c"] = 1
out_chunks = chunks
if out_chunks is None:
out_chunks = default_chunks
Expand All @@ -110,79 +117,58 @@ def to_multiscale(
# https://github.com/pydata/xarray/issues/5219
if "chunks" in current_input.encoding:
del current_input.encoding["chunks"]
data_objects = {
"scale0": current_input.to_dataset(name=image.name, promote_attrs=True)

# get metadata from the image
axes_names = {d: image[d].attrs.get('long_name', None) for d in image.dims}
axes_names = None if all(v is None for v in axes_names.values()) else axes_names

axes_units = {d: image[d].attrs.get('units', None) for d in image.dims}
axes_units = None if all(v == '' or v is None for v in axes_units.values()) else axes_units

spatial_dims = [d for d in image.dims if d in {"z", "y", "x"}]
scale = {
d: float(image[d][1] - image[d][0]) if len(image[d]) > 1 else 1.0 for d in spatial_dims
}
translation = {
d: float(image[d][0]) if len(image[d]) > 0 else 0.0 for d in spatial_dims
}

ngff_image = nz.to_ngff_image(
current_input,
dims=image.dims,
name=image.name,
scale=scale,
translation=translation,
)

if method is None:
method = Methods.XARRAY_COARSEN

if method is Methods.XARRAY_COARSEN:
data_objects = _downsample_xarray_coarsen(
current_input,
default_chunks,
out_chunks,
scale_factors,
data_objects,
image.name,
)
elif method is Methods.ITK_BIN_SHRINK:
data_objects = _downsample_itk_bin_shrink(
current_input,
default_chunks,
out_chunks,
scale_factors,
data_objects,
image,
)
elif method is Methods.ITK_GAUSSIAN:
data_objects = _downsample_itk_gaussian(
current_input,
default_chunks,
out_chunks,
scale_factors,
data_objects,
image,
)
elif method is Methods.ITK_LABEL_GAUSSIAN:
data_objects = _downsample_itk_label(
current_input,
default_chunks,
out_chunks,
scale_factors,
data_objects,
image,
)
elif method is Methods.DASK_IMAGE_GAUSSIAN:
data_objects = _downsample_dask_image(
current_input,
default_chunks,
out_chunks,
scale_factors,
data_objects,
image,
label=False,
)
elif method is Methods.DASK_IMAGE_NEAREST:
data_objects = _downsample_dask_image(
current_input,
default_chunks,
out_chunks,
scale_factors,
data_objects,
image,
label="nearest",
)
elif method is Methods.DASK_IMAGE_MODE:
data_objects = _downsample_dask_image(
current_input,
default_chunks,
out_chunks,
scale_factors,
data_objects,
image,
label="mode",
)
method = nz.Methods.ITK_BIN_SHRINK
else:
if isinstance(method, Enum) and method.value in MSI_METHOD_MAPPING:
warnings.warn(
f"Method {method.name} is deprecated, use {MSI_METHOD_MAPPING[method.value]} instead.",
DeprecationWarning,
)
method = MSI_METHOD_MAPPING[method.value]

multiscales = nz.to_multiscales(
ngff_image,
scale_factors=scale_factors,
method=method,
chunks=out_chunks,
)

data_objects = {}
for factor, img in enumerate(multiscales.images):
si = to_spatial_image(
img.data,
dims=img.dims,
scale=img.scale,
axis_names=axes_names,
axis_units=axes_units,
translation=img.translation,
)
data_objects[f"scale{factor}"] = si.to_dataset(name=image.name, promote_attrs=True)

multiscale = DataTree.from_dict(data_objects)

Expand Down
Loading
Loading