From 1ef265d3d0155709c92b3d64de2f90b0352a126b Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Thu, 25 Jan 2024 15:03:50 -0500 Subject: [PATCH] ENH: Implement ITKWASM_BIN_SHRINK --- ngff_zarr/methods/_itkwasm.py | 61 ++++++++++++++++++++----------- ngff_zarr/to_multiscales.py | 5 +++ pyproject.toml | 2 +- test/test_to_ngff_zarr_itkwasm.py | 24 ++++++------ 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/ngff_zarr/methods/_itkwasm.py b/ngff_zarr/methods/_itkwasm.py index ae2b44d..e8038a8 100644 --- a/ngff_zarr/methods/_itkwasm.py +++ b/ngff_zarr/methods/_itkwasm.py @@ -38,10 +38,31 @@ def _itkwasm_blur_and_downsample( return downsampled.data +def _itkwasm_chunk_bin_shrink( + image_data, + shrink_factors, +): + """Compute the local mean and downsample on a given image chunk""" + import itkwasm + from itkwasm_downsample import downsample_bin_shrink + + # chunk does not have metadata attached, values are ITK defaults + image = itkwasm.image_from_array(image_data) + + # Skip this image block if it has 0 voxels + block_size = image.size + if any(block_len == 0 for block_len in block_size): + return None + + downsampled = downsample_bin_shrink(image, shrink_factors=shrink_factors) + return downsampled.data + + def _downsample_itkwasm_bin_shrink( ngff_image: NgffImage, default_chunks, out_chunks, scale_factors ): - import itk + import itkwasm + from itkwasm_downsample import downsample_bin_shrink multiscales = [ ngff_image, @@ -62,40 +83,38 @@ def _downsample_itkwasm_bin_shrink( # For consistency for now, do not utilize direction until there is standardized support for # direction cosines / orientation in OME-NGFF # block_0.attrs.pop("direction", None) - block_input = itk.image_from_array(np.ones_like(block_0)) + block_input = itkwasm.image_from_array(np.ones_like(block_0)) spacing = [previous_image.scale[d] for d in spatial_dims] - block_input.SetSpacing(spacing) + block_input.spacing = spacing origin = [previous_image.translation[d] for d in spatial_dims] - block_input.SetOrigin(origin) - filt = itk.BinShrinkImageFilter.New(block_input, shrink_factors=shrink_factors) - filt.UpdateOutputInformation() - block_output = filt.GetOutput() - scale = {_image_dims[i]: s for (i, s) in enumerate(block_output.GetSpacing())} - translation = { - _image_dims[i]: s for (i, s) in enumerate(block_output.GetOrigin()) - } - dtype = block_output.dtype + block_input.origin = origin + block_output = downsample_bin_shrink( + block_input, shrink_factors, information_only=False + ) + scale = {_image_dims[i]: s for (i, s) in enumerate(block_output.spacing)} + translation = {_image_dims[i]: s for (i, s) in enumerate(block_output.origin)} + dtype = block_output.data.dtype output_chunks = list(previous_image.data.chunks) for i, c in enumerate(output_chunks): output_chunks[i] = [ - block_output.shape[i], + block_output.data.shape[i], ] * len(c) block_neg1 = _get_block(previous_image, -1) # block_neg1.attrs.pop("direction", None) - block_input = itk.image_from_array(np.ones_like(block_neg1)) - block_input.SetSpacing(spacing) - block_input.SetOrigin(origin) - filt = itk.BinShrinkImageFilter.New(block_input, shrink_factors=shrink_factors) - filt.UpdateOutputInformation() - block_output = filt.GetOutput() + block_input = itkwasm.image_from_array(np.ones_like(block_neg1)) + block_input.spacing = spacing + block_input.origin = origin + block_output = downsample_bin_shrink( + block_input, shrink_factors, information_only=False + ) for i in range(len(output_chunks)): - output_chunks[i][-1] = block_output.shape[i] + output_chunks[i][-1] = block_output.data.shape[i] output_chunks[i] = tuple(output_chunks[i]) output_chunks = tuple(output_chunks) downscaled_array = map_blocks( - itk.bin_shrink_image_filter, + _itkwasm_chunk_bin_shrink, previous_image.data, shrink_factors=shrink_factors, dtype=dtype, diff --git a/ngff_zarr/to_multiscales.py b/ngff_zarr/to_multiscales.py index 4f8af87..49f6ecf 100644 --- a/ngff_zarr/to_multiscales.py +++ b/ngff_zarr/to_multiscales.py @@ -22,6 +22,7 @@ _downsample_itk_gaussian, ) from .methods._itkwasm import ( + _downsample_itkwasm_bin_shrink, _downsample_itkwasm_gaussian, ) from .methods._support import _spatial_dims @@ -313,6 +314,10 @@ def to_multiscales( images = _downsample_itkwasm_gaussian( ngff_image, default_chunks, out_chunks, scale_factors ) + elif method is Methods.ITKWASM_BIN_SHRINK: + images = _downsample_itkwasm_bin_shrink( + ngff_image, default_chunks, out_chunks, scale_factors + ) elif method is Methods.ITK_GAUSSIAN: images = _downsample_itk_gaussian( ngff_image, default_chunks, out_chunks, scale_factors diff --git a/pyproject.toml b/pyproject.toml index 53ed9e6..97c6231 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ classifiers = [ ] dependencies = [ "dask[array]", - "itkwasm >= 1.0b165", + "itkwasm >= 1.0b166", "itkwasm-downsample >= 1.1.0", "numpy", "platformdirs", diff --git a/test/test_to_ngff_zarr_itkwasm.py b/test/test_to_ngff_zarr_itkwasm.py index 700c3eb..586f2f4 100644 --- a/test/test_to_ngff_zarr_itkwasm.py +++ b/test/test_to_ngff_zarr_itkwasm.py @@ -2,17 +2,19 @@ from ._data import store_new_multiscales, verify_against_baseline -# def test_bin_shrink_isotropic_scale_factors(input_images): -# dataset_name = "cthead1" -# image = input_images[dataset_name] -# baseline_name = "2_4/ITKWASM_BIN_SHRINK.zarr" -# multiscales = to_multiscales(image, [2, 4], method=Methods.ITK_BIN_SHRINK) -# # store_new_multiscales(dataset_name, baseline_name, multiscales) -# verify_against_baseline(dataset_name, baseline_name, multiscales) - -# baseline_name = "auto/ITK_BIN_SHRINK.zarr" -# multiscales = to_multiscales(image, method=Methods.ITK_BIN_SHRINK) -# verify_against_baseline(dataset_name, baseline_name, multiscales) + +def test_bin_shrink_isotropic_scale_factors(input_images): + dataset_name = "cthead1" + image = input_images[dataset_name] + baseline_name = "2_4/ITKWASM_BIN_SHRINK.zarr" + multiscales = to_multiscales(image, [2, 4], method=Methods.ITKWASM_BIN_SHRINK) + store_new_multiscales(dataset_name, baseline_name, multiscales) + verify_against_baseline(dataset_name, baseline_name, multiscales) + + baseline_name = "auto/ITKWASM_BIN_SHRINK.zarr" + multiscales = to_multiscales(image, method=Methods.ITKWASM_BIN_SHRINK) + store_new_multiscales(dataset_name, baseline_name, multiscales) + verify_against_baseline(dataset_name, baseline_name, multiscales) def test_gaussian_isotropic_scale_factors(input_images):