diff --git a/.github/workflows/notebook-test.yml b/.github/workflows/notebook-test.yml index c432290..e106312 100644 --- a/.github/workflows/notebook-test.yml +++ b/.github/workflows/notebook-test.yml @@ -13,4 +13,4 @@ jobs: - name: Test notebooks run: | - pixi run test-notebooks \ No newline at end of file + pixi run test-notebooks diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a08fd07..17714cd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,6 @@ name: Test -on: [push,pull_request] +on: [push, pull_request] jobs: test: @@ -9,7 +9,7 @@ jobs: max-parallel: 5 matrix: os: [ubuntu-22.04, windows-2022, macos-12, macos-14] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 @@ -30,4 +30,4 @@ jobs: if: ${{ matrix.os != 'mac-14' && matrix.package == '3.8' }} uses: mikepenz/action-junit-report@v2 with: - report_paths: 'junit/test-results*.xml' + report_paths: "junit/test-results*.xml" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..9d864d6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,52 @@ +exclude: (^.pixi/|.snap|pixi.lock) + +ci: + autoupdate_commit_msg: "ENH: update pre-commit hooks" + autofix_commit_msg: "STYLE: pre-commit fixes" + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: "v4.6.0" + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-symlinks + - id: check-json + - id: check-merge-conflict + - id: check-toml + - id: check-xml + - id: check-yaml + - id: debug-statements + - id: detect-private-key + - id: end-of-file-fixer + - id: mixed-line-ending + - id: name-tests-test + args: ["--pytest-test-first"] + - id: requirements-txt-fixer + - id: trailing-whitespace + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.0.0" + hooks: + - id: prettier + types_or: [yaml, markdown, html, css, scss, javascript, json] + args: [--prose-wrap=always] + + - repo: https://github.com/codespell-project/codespell + rev: "v2.2.5" + hooks: + - id: codespell + exclude: examples/ + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: "v0.9.0.5" + hooks: + - id: shellcheck + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.5.4 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + exclude: test/conftest.py + - id: ruff-format diff --git a/README.md b/README.md index c3f2303..0a80c44 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,11 @@ [![image](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black) [![DOI](https://zenodo.org/badge/379678181.svg)](https://zenodo.org/badge/latestdoi/379678181) -Generate a multiscale, chunked, multi-dimensional spatial image data structure that can serialized to [OME-NGFF]. - -Each scale is a scientific Python [Xarray] [spatial-image] [Dataset], organized into nodes of an Xarray [Datatree]. +Generate a multiscale, chunked, multi-dimensional spatial image data structure +that can serialized to [OME-NGFF]. +Each scale is a scientific Python [Xarray] [spatial-image] [Dataset], organized +into nodes of an Xarray [Datatree]. ## Installation @@ -32,8 +33,8 @@ image = to_spatial_image(array) print(image) ``` -An [Xarray] [spatial-image] [DataArray]. -Spatial metadata can also be passed during construction. +An [Xarray] [spatial-image] [DataArray]. Spatial metadata can also be passed +during construction. ``` @@ -82,9 +83,11 @@ DataTree('multiscales', parent=None) image (y, x) uint8 dask.array ``` -Store as an Open Microscopy Environment-Next Generation File Format ([OME-NGFF]) / [netCDF] [Zarr] store. +Store as an Open Microscopy Environment-Next Generation File Format ([OME-NGFF]) +/ [netCDF] [Zarr] store. -It is highly recommended to use `dimension_separator='/'` in the construction of the Zarr stores. +It is highly recommended to use `dimension_separator='/'` in the construction of +the Zarr stores. ```python store = zarr.storage.DirectoryStore('multiscale.zarr', dimension_separator='/') @@ -96,26 +99,51 @@ released. We mean it :-). ## Examples -- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/spatial-image/multiscale-spatial-image/main?urlpath=lab/tree/examples%2FHelloMultiscaleSpatialImageWorld.ipynb) [Hello MultiscaleSpatialImage World!](./examples/HelloMultiscaleSpatialImageWorld.ipynb) -- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/spatial-image/multiscale-spatial-image/main?urlpath=lab/tree/examples%2FConvertITKImage.ipynb) [Convert itk.Image](./examples/ConvertITKImage.ipynb) -- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/spatial-image/multiscale-spatial-image/main?urlpath=lab/tree/examples%2FConvertImageioImageResource.ipynb) [Convert imageio ImageResource](./examples/ConvertImageioImageResource.ipynb) -- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/spatial-image/multiscale-spatial-image/main?urlpath=lab/tree/examples%2FConvertPyImageJDataset.ipynb) [Convert pyimagej Dataset](./examples/ConvertPyImageJDataset.ipynb) +- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/spatial-image/multiscale-spatial-image/main?urlpath=lab/tree/examples%2FHelloMultiscaleSpatialImageWorld.ipynb) + [Hello MultiscaleSpatialImage World!](./examples/HelloMultiscaleSpatialImageWorld.ipynb) +- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/spatial-image/multiscale-spatial-image/main?urlpath=lab/tree/examples%2FConvertITKImage.ipynb) + [Convert itk.Image](./examples/ConvertITKImage.ipynb) +- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/spatial-image/multiscale-spatial-image/main?urlpath=lab/tree/examples%2FConvertImageioImageResource.ipynb) + [Convert imageio ImageResource](./examples/ConvertImageioImageResource.ipynb) +- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/spatial-image/multiscale-spatial-image/main?urlpath=lab/tree/examples%2FConvertPyImageJDataset.ipynb) + [Convert pyimagej Dataset](./examples/ConvertPyImageJDataset.ipynb) ## Development Contributions are welcome and appreciated. -To run the test suite: +### Get the source code -``` +```shell git clone https://github.com/spatial-image/multiscale-spatial-image cd multiscale-spatial-image -pip install -e ".[test]" -pytest -# Notebook tests -pytest --nbmake --nbmake-timeout=3000 examples/*ipynb ``` +### Install dependencies + +First install [pixi]. Then, install project dependencies: + +```shell +pixi install -a +pixi run pre-commit-install +``` + +### Run the test suite + +The unit tests: + +```shell +pixi run -e test test +``` + +The notebooks tests: + +```shell +pixi run test-notebooks +``` + +### Update test data + To add new or update testing data, such as a new baseline for this block: ```py @@ -150,10 +178,15 @@ gzip -9 ../data.tar python3 -c 'import pooch; print(pooch.file_hash("../data.tar.gz"))' ``` -Update the `test_data_sha256` variable in the *test/_data.py* file. -Upload the data to [web3.storage](https://web3.storage). -And update the `test_data_ipfs_cid` [Content Identifier (CID)](https://proto.school/anatomy-of-a-cid/01) variable, which is available in the web3.storage web page interface. +Update the `test_data_sha256` variable in the _test/\_data.py_ file. Upload the +data to [web3.storage](https://web3.storage). And update the +`test_data_ipfs_cid` +[Content Identifier (CID)](https://proto.school/anatomy-of-a-cid/01) variable, +which is available in the web3.storage web page interface. + +### Submit the patch +We use the standard [GitHub flow]. [spatial-image]: https://github.com/spatial-image/spatial-image [Xarray]: https://xarray.pydata.org/en/stable/ @@ -164,3 +197,5 @@ And update the `test_data_ipfs_cid` [Content Identifier (CID)](https://proto.sch [Zarr]: https://zarr.readthedocs.io/en/stable/ [Dask]: https://docs.dask.org/en/stable/array.html [netCDF]: https://www.unidata.ucar.edu/software/netcdf/ +[pixi]: https://pixi.sh +[GitHub flow]: https://docs.github.com/en/get-started/using-github/github-flow diff --git a/multiscale_spatial_image/__about__.py b/multiscale_spatial_image/__about__.py index 6beee25..52cac20 100644 --- a/multiscale_spatial_image/__about__.py +++ b/multiscale_spatial_image/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2022-present NumFOCUS # # SPDX-License-Identifier: MIT -__version__ = '1.0.0' +__version__ = "1.0.0" diff --git a/multiscale_spatial_image/__init__.py b/multiscale_spatial_image/__init__.py index be793ee..e002746 100644 --- a/multiscale_spatial_image/__init__.py +++ b/multiscale_spatial_image/__init__.py @@ -2,15 +2,14 @@ Generate a multiscale spatial image.""" - __all__ = [ - "MultiscaleSpatialImage", - "Methods", - "to_multiscale", - "itk_image_to_multiscale", - "__version__", + "MultiscaleSpatialImage", + "Methods", + "to_multiscale", + "itk_image_to_multiscale", + "__version__", ] from .__about__ import __version__ from .multiscale_spatial_image import MultiscaleSpatialImage -from .to_multiscale import Methods, to_multiscale, itk_image_to_multiscale \ No newline at end of file +from .to_multiscale import Methods, to_multiscale, itk_image_to_multiscale diff --git a/multiscale_spatial_image/multiscale_spatial_image.py b/multiscale_spatial_image/multiscale_spatial_image.py index 7c29247..1c463de 100644 --- a/multiscale_spatial_image/multiscale_spatial_image.py +++ b/multiscale_spatial_image/multiscale_spatial_image.py @@ -1,13 +1,10 @@ -from typing import Union, List +from typing import Union -import xarray as xr from datatree import DataTree -from datatree.treenode import TreeNode import numpy as np from collections.abc import MutableMapping from pathlib import Path from zarr.storage import BaseStore -import xarray as xr from datatree import register_datatree_accessor diff --git a/multiscale_spatial_image/to_multiscale/__init__.py b/multiscale_spatial_image/to_multiscale/__init__.py index 4897d27..8f47988 100644 --- a/multiscale_spatial_image/to_multiscale/__init__.py +++ b/multiscale_spatial_image/to_multiscale/__init__.py @@ -1,2 +1,4 @@ -from .to_multiscale import Methods, to_multiscale +from .to_multiscale import Methods, to_multiscale from .itk_image_to_multiscale import itk_image_to_multiscale + +__all__ = ["Methods", "to_multiscale", "itk_image_to_multiscale"] diff --git a/multiscale_spatial_image/to_multiscale/_dask_image.py b/multiscale_spatial_image/to_multiscale/_dask_image.py index dab8b64..7c30036 100644 --- a/multiscale_spatial_image/to_multiscale/_dask_image.py +++ b/multiscale_spatial_image/to_multiscale/_dask_image.py @@ -1,25 +1,28 @@ from spatial_image import to_spatial_image -from dask.array import map_blocks, map_overlap import numpy as np from ._support import _align_chunks, _dim_scale_factors, _compute_sigma + def _compute_input_spacing(input_image): - '''Helper method to manually compute image spacing. Assumes even spacing along any axis. - + """Helper method to manually compute image spacing. Assumes even spacing along any axis. + input_image: xarray.core.dataarray.DataArray The image for which voxel spacings are computed result: Dict Spacing along each enumerated image axis Example {'x': 1.0, 'y': 0.5} - ''' - return {dim: float(input_image.coords[dim][1]) - float(input_image.coords[dim][0]) - for dim in input_image.dims} + """ + return { + dim: float(input_image.coords[dim][1]) - float(input_image.coords[dim][0]) + for dim in input_image.dims + } + def _compute_output_spacing(input_image, dim_factors): - '''Helper method to manually compute output image spacing. - + """Helper method to manually compute output image spacing. + input_image: xarray.core.dataarray.DataArray The image for which voxel spacings are computed @@ -29,14 +32,15 @@ def _compute_output_spacing(input_image, dim_factors): result: Dict Spacing along each enumerated image axis Example {'x': 2.0, 'y': 1.0} - ''' + """ input_spacing = _compute_input_spacing(input_image) return {dim: input_spacing[dim] * dim_factors[dim] for dim in input_image.dims} -def _compute_output_origin(input_image, dim_factors): - '''Helper method to manually compute output image physical offset. + +def _compute_output_origin(input_image, dim_factors): + """Helper method to manually compute output image physical offset. Note that this method does not account for an image direction matrix. - + input_image: xarray.core.dataarray.DataArray The image for which voxel spacings are computed @@ -46,23 +50,32 @@ def _compute_output_origin(input_image, dim_factors): result: Dict Offset in physical space of first voxel in output image Example {'x': 0.5, 'y': 1.0} - ''' - import math - + """ + input_spacing = _compute_input_spacing(input_image) - input_origin = {dim: float(input_image.coords[dim][0]) - for dim in input_image.dims if dim in dim_factors} + input_origin = { + dim: float(input_image.coords[dim][0]) + for dim in input_image.dims + if dim in dim_factors + } # Index in input image space corresponding to offset after shrink - input_index = {dim: 0.5 * (dim_factors[dim] - 1) - for dim in input_image.dims if dim in dim_factors} + input_index = { + dim: 0.5 * (dim_factors[dim] - 1) + for dim in input_image.dims + if dim in dim_factors + } # Translate input index coordinate to offset in physical space # NOTE: This method fails to account for direction matrix - return {dim: input_index[dim] * input_spacing[dim] + input_origin[dim] - for dim in input_image.dims if dim in dim_factors} + return { + dim: input_index[dim] * input_spacing[dim] + input_origin[dim] + for dim in input_image.dims + if dim in dim_factors + } + def _get_truncate(xarray_image, sigma_values, truncate_start=4.0) -> float: - '''Discover truncate parameter yielding a viable kernel width + """Discover truncate parameter yielding a viable kernel width for dask_image.ndfilters.gaussian_filter processing. Block overlap cannot be greater than image size, so kernel radius is more limited for small images. A lower stddev truncation ceiling for kernel @@ -81,7 +94,7 @@ def _get_truncate(xarray_image, sigma_values, truncate_start=4.0) -> float: result: float Truncation value found to yield largest possible kernel width without extending beyond one chunk such that chunked smoothing would fail. - ''' + """ from dask_image.ndfilters._gaussian import _get_border @@ -89,15 +102,29 @@ def _get_truncate(xarray_image, sigma_values, truncate_start=4.0) -> float: stddev_step = 0.5 # search by stepping down by 0.5 stddev in each iteration border = _get_border(xarray_image.data, sigma_values, truncate) - while any([border_len > image_len for border_len, image_len in zip(border, xarray_image.shape)]): + while any( + [ + border_len > image_len + for border_len, image_len in zip(border, xarray_image.shape) + ] + ): truncate = truncate - stddev_step - if(truncate <= 0.0): - break + if truncate <= 0.0: + break border = _get_border(xarray_image.data, sigma_values, truncate) return truncate -def _downsample_dask_image(current_input, default_chunks, out_chunks, scale_factors, data_objects, image, label=False): + +def _downsample_dask_image( + current_input, + default_chunks, + out_chunks, + scale_factors, + data_objects, + image, + label=False, +): import dask_image.ndfilters import dask_image.ndinterp @@ -116,24 +143,28 @@ def _downsample_dask_image(current_input, default_chunks, out_chunks, scale_fact input_spacing = _compute_input_spacing(current_input) # Compute output shape and metadata - output_shape = [int(image_len / shrink_factor) - for image_len, shrink_factor in zip(current_input.shape, shrink_factors)] + output_shape = [ + int(image_len / shrink_factor) + for image_len, shrink_factor in zip(current_input.shape, shrink_factors) + ] output_spacing = _compute_output_spacing(current_input, dim_factors) output_origin = _compute_output_origin(current_input, dim_factors) - if label == 'mode': + if label == "mode": + def largest_mode(arr): values, counts = np.unique(arr, return_counts=True) m = counts.argmax() return values[m] + size = tuple(shrink_factors) blurred_array = dask_image.ndfilters.generic_filter( image=current_input.data, function=largest_mode, size=size, - mode='nearest', + mode="nearest", ) - elif label == 'nearest': + elif label == "nearest": blurred_array = current_input.data else: input_spacing_list = [input_spacing[dim] for dim in image.dims] @@ -142,16 +173,16 @@ def largest_mode(arr): blurred_array = dask_image.ndfilters.gaussian_filter( image=current_input.data, - sigma=sigma_values, # tzyx order - mode='nearest', - truncate=truncate + sigma=sigma_values, # tzyx order + mode="nearest", + truncate=truncate, ) # Construct downsample parameters image_dimension = len(dim_factors) transform = np.eye(image_dimension) for dim, shrink_factor in enumerate(shrink_factors): - transform[dim,dim] = shrink_factor + transform[dim, dim] = shrink_factor if label: order = 0 else: @@ -161,9 +192,9 @@ def largest_mode(arr): blurred_array, matrix=transform, order=order, - output_shape=output_shape # tzyx order + output_shape=output_shape, # tzyx order ).compute() - + downscaled = to_spatial_image( downscaled_array, dims=image.dims, @@ -173,9 +204,7 @@ def largest_mode(arr): axis_names={ d: image.coords[d].attrs.get("long_name", d) for d in image.dims }, - axis_units={ - d: image.coords[d].attrs.get("units", "") for d in image.dims - }, + axis_units={d: image.coords[d].attrs.get("units", "") for d in image.dims}, t_coords=image.coords.get("t", None), c_coords=image.coords.get("c", None), ) diff --git a/multiscale_spatial_image/to_multiscale/_itk.py b/multiscale_spatial_image/to_multiscale/_itk.py index 3cf5b4c..508a5c2 100644 --- a/multiscale_spatial_image/to_multiscale/_itk.py +++ b/multiscale_spatial_image/to_multiscale/_itk.py @@ -1,20 +1,25 @@ from spatial_image import to_spatial_image from dask.array import map_blocks, map_overlap import numpy as np +from typing import Tuple from ._support import _align_chunks, _dim_scale_factors, _compute_sigma -def _get_block(current_input, block_index:int): - '''Helper method for accessing an enumerated chunk from xarray input''' + +def _get_block(current_input, block_index: int): + """Helper method for accessing an enumerated chunk from xarray input""" block_shape = [c[block_index] for c in current_input.chunks] block = current_input[tuple([slice(0, s) for s in block_shape])] # For consistency for now, do not utilize direction until there is standardized support for # direction cosines / orientation in OME-NGFF block.attrs.pop("direction", None) return block - -def _compute_itk_gaussian_kernel_radius(input_size, sigma_values, shrink_factors) -> list: - '''Get kernel radius in xyzt directions''' + + +def _compute_itk_gaussian_kernel_radius( + input_size, sigma_values, shrink_factors +) -> list: + """Get kernel radius in xyzt directions""" DEFAULT_MAX_KERNEL_WIDTH = 32 MAX_KERNEL_ERROR = 0.01 image_dimension = len(input_size) @@ -23,10 +28,10 @@ def _compute_itk_gaussian_kernel_radius(input_size, sigma_values, shrink_factors # Constrain kernel width to be at most the size of one chunk max_kernel_width = min(DEFAULT_MAX_KERNEL_WIDTH, *input_size) - variance = [sigma ** 2 for sigma in sigma_values] + variance = [sigma**2 for sigma in sigma_values] - def generate_radius(direction:int) -> int: - '''Follow itk.DiscreteGaussianImageFilter procedure to generate directional kernels''' + def generate_radius(direction: int) -> int: + """Follow itk.DiscreteGaussianImageFilter procedure to generate directional kernels""" oper = itk.GaussianOperator[itk.F, image_dimension]() oper.SetDirection(direction) oper.SetMaximumError(MAX_KERNEL_ERROR) @@ -38,44 +43,61 @@ def generate_radius(direction:int) -> int: return [generate_radius(dim) for dim in range(image_dimension)] -def _itk_blur_and_downsample(xarray_data, gaussian_filter_name, interpolator_name, shrink_factors, sigma_values, kernel_radius): - '''Blur and then downsample a given image chunk''' +def _itk_blur_and_downsample( + xarray_data, + gaussian_filter_name, + interpolator_name, + shrink_factors, + sigma_values, + kernel_radius, +): + """Blur and then downsample a given image chunk""" import itk - + # xarray chunk does not have metadata attached, values are ITK defaults image = itk.image_view_from_array(xarray_data) input_origin = itk.origin(image) # Skip this image block if it has 0 voxels block_size = itk.size(image) - if(any([block_len == 0 for block_len in block_size])): + if any([block_len == 0 for block_len in block_size]): return None - + # Output values are relative to input itk_shrink_factors = shrink_factors # xyzt itk_kernel_radius = kernel_radius - output_origin = [val + radius for val, radius in zip(input_origin, itk_kernel_radius)] + output_origin = [ + val + radius for val, radius in zip(input_origin, itk_kernel_radius) + ] output_spacing = [s * f for s, f in zip(itk.spacing(image), itk_shrink_factors)] - output_size = [max(0,int((image_len - 2 * radius) / shrink_factor)) - for image_len, radius, shrink_factor in zip(itk.size(image), itk_kernel_radius, itk_shrink_factors)] + output_size = [ + max(0, int((image_len - 2 * radius) / shrink_factor)) + for image_len, radius, shrink_factor in zip( + itk.size(image), itk_kernel_radius, itk_shrink_factors + ) + ] # Optionally run accelerated smoothing with itk-vkfft - if gaussian_filter_name == 'VkDiscreteGaussianImageFilter': + if gaussian_filter_name == "VkDiscreteGaussianImageFilter": smoothing_filter_template = itk.VkDiscreteGaussianImageFilter - elif gaussian_filter_name == 'DiscreteGaussianImageFilter': + elif gaussian_filter_name == "DiscreteGaussianImageFilter": smoothing_filter_template = itk.DiscreteGaussianImageFilter else: - raise ValueError(f'Unsupported gaussian_filter {gaussian_filter_name}') + raise ValueError(f"Unsupported gaussian_filter {gaussian_filter_name}") # Construct pipeline - smoothing_filter = smoothing_filter_template.New(image, - sigma_array=sigma_values, - use_image_spacing=False) - - if interpolator_name == 'LinearInterpolateImageFunction': - interpolator_instance = itk.LinearInterpolateImageFunction.New(smoothing_filter.GetOutput()) - elif interpolator_name == 'LabelImageGaussianInterpolateImageFunction': - interpolator_instance = itk.LabelImageGaussianInterpolateImageFunction.New(smoothing_filter.GetOutput()) + smoothing_filter = smoothing_filter_template.New( + image, sigma_array=sigma_values, use_image_spacing=False + ) + + if interpolator_name == "LinearInterpolateImageFunction": + interpolator_instance = itk.LinearInterpolateImageFunction.New( + smoothing_filter.GetOutput() + ) + elif interpolator_name == "LabelImageGaussianInterpolateImageFunction": + interpolator_instance = itk.LabelImageGaussianInterpolateImageFunction.New( + smoothing_filter.GetOutput() + ) # Similar approach as compute_sigma # Ref: https://link.springer.com/content/pdf/10.1007/978-3-319-24571-3_81.pdf sigma = [s * 0.7355 for s in output_spacing] @@ -83,19 +105,23 @@ def _itk_blur_and_downsample(xarray_data, gaussian_filter_name, interpolator_nam interpolator_instance.SetSigma(sigma) interpolator_instance.SetAlpha(sigma_max * 2.5) else: - raise ValueError(f'Unsupported interpolator_name {interpolator_name}') - - shrink_filter = itk.ResampleImageFilter.New(smoothing_filter.GetOutput(), + raise ValueError(f"Unsupported interpolator_name {interpolator_name}") + + shrink_filter = itk.ResampleImageFilter.New( + smoothing_filter.GetOutput(), interpolator=interpolator_instance, size=output_size, output_spacing=output_spacing, - output_origin=output_origin) + output_origin=output_origin, + ) shrink_filter.Update() - + return shrink_filter.GetOutput() -def _downsample_itk_bin_shrink(current_input, default_chunks, out_chunks, scale_factors, data_objects, image): +def _downsample_itk_bin_shrink( + current_input, default_chunks, out_chunks, scale_factors, data_objects, image +): import itk for factor_index, scale_factor in enumerate(scale_factors): @@ -111,14 +137,10 @@ def _downsample_itk_bin_shrink(current_input, default_chunks, out_chunks, scale_ # direction cosines / orientation in OME-NGFF block_0.attrs.pop("direction", None) block_input = itk.image_from_xarray(block_0) - filt = itk.BinShrinkImageFilter.New( - block_input, shrink_factors=shrink_factors - ) + 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()) - } + 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()) } @@ -133,9 +155,7 @@ def _downsample_itk_bin_shrink(current_input, default_chunks, out_chunks, scale_ block_neg1 = current_input[tuple([slice(0, s) for s in block_neg1_shape])] block_neg1.attrs.pop("direction", None) block_input = itk.image_from_xarray(block_neg1) - filt = itk.BinShrinkImageFilter.New( - block_input, shrink_factors=shrink_factors - ) + filt = itk.BinShrinkImageFilter.New(block_input, shrink_factors=shrink_factors) filt.UpdateOutputInformation() block_output = filt.GetOutput() for i, c in enumerate(output_chunks): @@ -159,9 +179,7 @@ def _downsample_itk_bin_shrink(current_input, default_chunks, out_chunks, scale_ axis_names={ d: image.coords[d].attrs.get("long_name", d) for d in image.dims }, - axis_units={ - d: image.coords[d].attrs.get("units", "") for d in image.dims - }, + axis_units={d: image.coords[d].attrs.get("units", "") for d in image.dims}, t_coords=image.coords.get("t", None), c_coords=image.coords.get("c", None), ) @@ -174,16 +192,18 @@ def _downsample_itk_bin_shrink(current_input, default_chunks, out_chunks, scale_ return data_objects -def _downsample_itk_gaussian(current_input, default_chunks, out_chunks, scale_factors, data_objects, image): - import itk +def _downsample_itk_gaussian( + current_input, default_chunks, out_chunks, scale_factors, data_objects, image +): + import itk # Optionally run accelerated smoothing with itk-vkfft - if 'VkFFTBackend' in dir(itk): - gaussian_filter_name = 'VkDiscreteGaussianImageFilter' + if "VkFFTBackend" in dir(itk): + gaussian_filter_name = "VkDiscreteGaussianImageFilter" else: - gaussian_filter_name = 'DiscreteGaussianImageFilter' + gaussian_filter_name = "DiscreteGaussianImageFilter" - interpolator_name = 'LinearInterpolateImageFunction' + interpolator_name = "LinearInterpolateImageFunction" for factor_index, scale_factor in enumerate(scale_factors): dim_factors = _dim_scale_factors(image.dims, scale_factor) @@ -195,15 +215,17 @@ def _downsample_itk_gaussian(current_input, default_chunks, out_chunks, scale_fa # Compute metadata for region splitting # Blocks 0, ..., N-2 have the same shape - block_0_input = _get_block(current_input,0) + block_0_input = _get_block(current_input, 0) # Block N-1 may be smaller than preceding blocks - block_neg1_input = _get_block(current_input,-1) + block_neg1_input = _get_block(current_input, -1) # Compute overlap for Gaussian blurring for all blocks block_0_image = itk.image_from_xarray(block_0_input) input_spacing = itk.spacing(block_0_image) sigma_values = _compute_sigma(input_spacing, shrink_factors) - kernel_radius = _compute_itk_gaussian_kernel_radius(itk.size(block_0_image), sigma_values, shrink_factors) + kernel_radius = _compute_itk_gaussian_kernel_radius( + itk.size(block_0_image), sigma_values, shrink_factors + ) # Compute output size and spatial metadata for blocks 0, .., N-2 filt = itk.BinShrinkImageFilter.New( @@ -213,7 +235,7 @@ def _downsample_itk_gaussian(current_input, default_chunks, out_chunks, scale_fa block_output = filt.GetOutput() block_0_output_spacing = block_output.GetSpacing() block_0_output_origin = block_output.GetOrigin() - + block_0_scale = { image_dims[i]: s for (i, s) in enumerate(block_0_output_spacing) } @@ -221,11 +243,17 @@ def _downsample_itk_gaussian(current_input, default_chunks, out_chunks, scale_fa image_dims[i]: s for (i, s) in enumerate(block_0_output_origin) } dtype = block_output.dtype - - computed_size = [int(block_len / shrink_factor) - for block_len, shrink_factor in zip(itk.size(block_0_image), shrink_factors)] - assert all([itk.size(block_output)[dim] == computed_size[dim] - for dim in range(block_output.ndim)]) + + computed_size = [ + int(block_len / shrink_factor) + for block_len, shrink_factor in zip(itk.size(block_0_image), shrink_factors) + ] + assert all( + [ + itk.size(block_output)[dim] == computed_size[dim] + for dim in range(block_output.ndim) + ] + ) output_chunks = list(current_input.chunks) for i, c in enumerate(output_chunks): output_chunks[i] = [ @@ -237,29 +265,39 @@ def _downsample_itk_gaussian(current_input, default_chunks, out_chunks, scale_fa filt.SetInput(block_neg1_image) filt.UpdateOutputInformation() block_output = filt.GetOutput() - computed_size = [int(block_len / shrink_factor) - for block_len, shrink_factor in zip(itk.size(block_neg1_image), shrink_factors)] - assert all([itk.size(block_output)[dim] == computed_size[dim] - for dim in range(block_output.ndim)]) + computed_size = [ + int(block_len / shrink_factor) + for block_len, shrink_factor in zip( + itk.size(block_neg1_image), shrink_factors + ) + ] + assert all( + [ + itk.size(block_output)[dim] == computed_size[dim] + for dim in range(block_output.ndim) + ] + ) for i, c in enumerate(output_chunks): output_chunks[i][-1] = block_output.shape[i] output_chunks[i] = tuple(output_chunks[i]) output_chunks = tuple(output_chunks) downscaled_array = map_overlap( - _itk_blur_and_downsample, - current_input.data, - gaussian_filter_name=gaussian_filter_name, - interpolator_name=interpolator_name, - shrink_factors=shrink_factors, - sigma_values=sigma_values, - kernel_radius=kernel_radius, - dtype=dtype, - depth={dim: radius for dim, radius in enumerate(np.flip(kernel_radius))}, # overlap is in tzyx - boundary='nearest', - trim=False # Overlapped region is trimmed in blur_and_downsample to output size + _itk_blur_and_downsample, + current_input.data, + gaussian_filter_name=gaussian_filter_name, + interpolator_name=interpolator_name, + shrink_factors=shrink_factors, + sigma_values=sigma_values, + kernel_radius=kernel_radius, + dtype=dtype, + depth={ + dim: radius for dim, radius in enumerate(np.flip(kernel_radius)) + }, # overlap is in tzyx + boundary="nearest", + trim=False, # Overlapped region is trimmed in blur_and_downsample to output size ).compute() - + downscaled = to_spatial_image( downscaled_array, dims=image.dims, @@ -269,9 +307,7 @@ def _downsample_itk_gaussian(current_input, default_chunks, out_chunks, scale_fa axis_names={ d: image.coords[d].attrs.get("long_name", d) for d in image.dims }, - axis_units={ - d: image.coords[d].attrs.get("units", "") for d in image.dims - }, + axis_units={d: image.coords[d].attrs.get("units", "") for d in image.dims}, t_coords=image.coords.get("t", None), c_coords=image.coords.get("c", None), ) @@ -282,12 +318,15 @@ def _downsample_itk_gaussian(current_input, default_chunks, out_chunks, scale_fa current_input = downscaled return data_objects -def _downsample_itk_label(current_input, default_chunks, out_chunks, scale_factors, data_objects, image): + +def _downsample_itk_label( + current_input, default_chunks, out_chunks, scale_factors, data_objects, image +): # Uses the LabelImageGaussianInterpolateImageFunction. More appropriate for integer label images. - import itk + import itk - gaussian_filter_name = 'DiscreteGaussianImageFilter' - interpolator_name = 'LabelImageGaussianInterpolateImageFunction' + gaussian_filter_name = "DiscreteGaussianImageFilter" + interpolator_name = "LabelImageGaussianInterpolateImageFunction" for factor_index, scale_factor in enumerate(scale_factors): dim_factors = _dim_scale_factors(image.dims, scale_factor) @@ -299,15 +338,17 @@ def _downsample_itk_label(current_input, default_chunks, out_chunks, scale_facto # Compute metadata for region splitting # Blocks 0, ..., N-2 have the same shape - block_0_input = _get_block(current_input,0) + block_0_input = _get_block(current_input, 0) # Block N-1 may be smaller than preceding blocks - block_neg1_input = _get_block(current_input,-1) + block_neg1_input = _get_block(current_input, -1) # Compute overlap for Gaussian blurring for all blocks block_0_image = itk.image_from_xarray(block_0_input) input_spacing = itk.spacing(block_0_image) sigma_values = _compute_sigma(input_spacing, shrink_factors) - kernel_radius = _compute_itk_gaussian_kernel_radius(itk.size(block_0_image), sigma_values, shrink_factors) + kernel_radius = _compute_itk_gaussian_kernel_radius( + itk.size(block_0_image), sigma_values, shrink_factors + ) # Compute output size and spatial metadata for blocks 0, .., N-2 filt = itk.BinShrinkImageFilter.New( @@ -317,7 +358,7 @@ def _downsample_itk_label(current_input, default_chunks, out_chunks, scale_facto block_output = filt.GetOutput() block_0_output_spacing = block_output.GetSpacing() block_0_output_origin = block_output.GetOrigin() - + block_0_scale = { image_dims[i]: s for (i, s) in enumerate(block_0_output_spacing) } @@ -325,11 +366,17 @@ def _downsample_itk_label(current_input, default_chunks, out_chunks, scale_facto image_dims[i]: s for (i, s) in enumerate(block_0_output_origin) } dtype = block_output.dtype - - computed_size = [int(block_len / shrink_factor) - for block_len, shrink_factor in zip(itk.size(block_0_image), shrink_factors)] - assert all([itk.size(block_output)[dim] == computed_size[dim] - for dim in range(block_output.ndim)]) + + computed_size = [ + int(block_len / shrink_factor) + for block_len, shrink_factor in zip(itk.size(block_0_image), shrink_factors) + ] + assert all( + [ + itk.size(block_output)[dim] == computed_size[dim] + for dim in range(block_output.ndim) + ] + ) output_chunks = list(current_input.chunks) for i, c in enumerate(output_chunks): output_chunks[i] = [ @@ -341,29 +388,39 @@ def _downsample_itk_label(current_input, default_chunks, out_chunks, scale_facto filt.SetInput(block_neg1_image) filt.UpdateOutputInformation() block_output = filt.GetOutput() - computed_size = [int(block_len / shrink_factor) - for block_len, shrink_factor in zip(itk.size(block_neg1_image), shrink_factors)] - assert all([itk.size(block_output)[dim] == computed_size[dim] - for dim in range(block_output.ndim)]) + computed_size = [ + int(block_len / shrink_factor) + for block_len, shrink_factor in zip( + itk.size(block_neg1_image), shrink_factors + ) + ] + assert all( + [ + itk.size(block_output)[dim] == computed_size[dim] + for dim in range(block_output.ndim) + ] + ) for i, c in enumerate(output_chunks): output_chunks[i][-1] = block_output.shape[i] output_chunks[i] = tuple(output_chunks[i]) output_chunks = tuple(output_chunks) downscaled_array = map_overlap( - _itk_blur_and_downsample, - current_input.data, - gaussian_filter_name=gaussian_filter_name, - interpolator_name=interpolator_name, - shrink_factors=shrink_factors, - sigma_values=sigma_values, - kernel_radius=kernel_radius, - dtype=dtype, - depth={dim: radius for dim, radius in enumerate(np.flip(kernel_radius))}, # overlap is in tzyx - boundary='nearest', - trim=False # Overlapped region is trimmed in blur_and_downsample to output size + _itk_blur_and_downsample, + current_input.data, + gaussian_filter_name=gaussian_filter_name, + interpolator_name=interpolator_name, + shrink_factors=shrink_factors, + sigma_values=sigma_values, + kernel_radius=kernel_radius, + dtype=dtype, + depth={ + dim: radius for dim, radius in enumerate(np.flip(kernel_radius)) + }, # overlap is in tzyx + boundary="nearest", + trim=False, # Overlapped region is trimmed in blur_and_downsample to output size ).compute() - + downscaled = to_spatial_image( downscaled_array, dims=image.dims, @@ -373,9 +430,7 @@ def _downsample_itk_label(current_input, default_chunks, out_chunks, scale_facto axis_names={ d: image.coords[d].attrs.get("long_name", d) for d in image.dims }, - axis_units={ - d: image.coords[d].attrs.get("units", "") for d in image.dims - }, + axis_units={d: image.coords[d].attrs.get("units", "") for d in image.dims}, t_coords=image.coords.get("t", None), c_coords=image.coords.get("c", None), ) @@ -385,4 +440,4 @@ def _downsample_itk_label(current_input, default_chunks, out_chunks, scale_facto ) current_input = downscaled - return data_objects \ No newline at end of file + return data_objects diff --git a/multiscale_spatial_image/to_multiscale/_support.py b/multiscale_spatial_image/to_multiscale/_support.py index b4b6afb..bbf4cfd 100644 --- a/multiscale_spatial_image/to_multiscale/_support.py +++ b/multiscale_spatial_image/to_multiscale/_support.py @@ -1,8 +1,11 @@ _spatial_dims = {"x", "y", "z"} + def _dim_scale_factors(dims, scale_factor): if isinstance(scale_factor, int): - result_scale_factors = {dim: scale_factor for dim in _spatial_dims.intersection(dims)} + result_scale_factors = { + dim: scale_factor for dim in _spatial_dims.intersection(dims) + } else: result_scale_factors = scale_factor return result_scale_factors @@ -25,8 +28,9 @@ def _align_chunks(current_input, default_chunks, dim_factors): return current_input + def _compute_sigma(input_spacings, shrink_factors) -> list: - '''Compute Gaussian kernel sigma values for resampling to isotropic spacing. + """Compute Gaussian kernel sigma values for resampling to isotropic spacing. sigma = sqrt((isoSpacing^2 - inputSpacing[0]^2)/(2*sqrt(2*ln(2)))^2) Ref https://discourse.itk.org/t/resampling-to-isotropic-signal-processing-theory/1403/16 @@ -38,10 +42,16 @@ def _compute_sigma(input_spacings, shrink_factors) -> list: result: List Standard deviation of Gaussian kernel along each axis in xyzt order - ''' + """ assert len(input_spacings) == len(shrink_factors) import math - output_spacings = [input_spacing * shrink for input_spacing, shrink in zip(input_spacings, shrink_factors)] + + output_spacings = [ + input_spacing * shrink + for input_spacing, shrink in zip(input_spacings, shrink_factors) + ] denominator = (2 * ((2 * math.log(2)) ** 0.5)) ** 2 - return [((output_spacing ** 2 - input_spacing ** 2) / denominator) ** 0.5 - for input_spacing, output_spacing in zip(input_spacings, output_spacings)] + return [ + ((output_spacing**2 - input_spacing**2) / denominator) ** 0.5 + for input_spacing, output_spacing in zip(input_spacings, output_spacings) + ] diff --git a/multiscale_spatial_image/to_multiscale/_xarray.py b/multiscale_spatial_image/to_multiscale/_xarray.py index 65b7085..1353a83 100644 --- a/multiscale_spatial_image/to_multiscale/_xarray.py +++ b/multiscale_spatial_image/to_multiscale/_xarray.py @@ -1,6 +1,9 @@ from ._support import _align_chunks, _dim_scale_factors -def _downsample_xarray_coarsen(current_input, default_chunks, out_chunks, scale_factors, data_objects, name): + +def _downsample_xarray_coarsen( + current_input, default_chunks, out_chunks, scale_factors, data_objects, name +): for factor_index, scale_factor in enumerate(scale_factors): dim_factors = _dim_scale_factors(current_input.dims, scale_factor) current_input = _align_chunks(current_input, default_chunks, dim_factors) @@ -18,4 +21,4 @@ def _downsample_xarray_coarsen(current_input, default_chunks, out_chunks, scale_ ) current_input = downscaled - return data_objects \ No newline at end of file + return data_objects diff --git a/multiscale_spatial_image/to_multiscale/itk_image_to_multiscale.py b/multiscale_spatial_image/to_multiscale/itk_image_to_multiscale.py index d763331..b144084 100644 --- a/multiscale_spatial_image/to_multiscale/itk_image_to_multiscale.py +++ b/multiscale_spatial_image/to_multiscale/itk_image_to_multiscale.py @@ -5,6 +5,7 @@ from .to_multiscale import to_multiscale, Methods from datatree import DataTree + def itk_image_to_multiscale( image, scale_factors: Sequence[Union[Dict[str, int], int]], @@ -20,8 +21,8 @@ def itk_image_to_multiscale( Tuple[Tuple[int, ...], ...], Mapping[Any, Union[None, int, Tuple[int, ...]]], ] - ] = None) -> DataTree: - + ] = None, +) -> DataTree: import itk import numpy as np @@ -30,39 +31,57 @@ def itk_image_to_multiscale( if object_name and not object_name.isspace(): name = object_name else: - name = 'image' + name = "image" # Handle anatomical axes if anatomical_axes and (axis_names is None): - axis_names = {"x": "right-left", "y": "anterior-posterior", "z": "inferior-superior"} - + axis_names = { + "x": "right-left", + "y": "anterior-posterior", + "z": "inferior-superior", + } + # Orient 3D image so that direction is identity wrt RAI coordinates image_dimension = image.GetImageDimension() input_direction = np.array(image.GetDirection()) oriented_image = image - if anatomical_axes and image_dimension == 3 and not (np.eye(image_dimension) == input_direction).all(): + if ( + anatomical_axes + and image_dimension == 3 + and not (np.eye(image_dimension) == input_direction).all() + ): desired_orientation = itk.SpatialOrientationEnums.ValidCoordinateOrientations_ITK_COORDINATE_ORIENTATION_RAI - oriented_image = itk.orient_image_filter(image, use_image_direction=True, desired_coordinate_orientation=desired_orientation) + oriented_image = itk.orient_image_filter( + image, + use_image_direction=True, + desired_coordinate_orientation=desired_orientation, + ) elif anatomical_axes and image_dimension != 3: - raise ValueError(f'Cannot use anatomical axes for input image of size {image_dimension}') - + raise ValueError( + f"Cannot use anatomical axes for input image of size {image_dimension}" + ) + image_da = itk.xarray_from_image(oriented_image) image_da.name = name - image_dims: Tuple[str, str, str, str] = ("x", "y", "z", "t") # ITK dims are in xyzt order + image_dims: Tuple[str, str, str, str] = ( + "x", + "y", + "z", + "t", + ) # ITK dims are in xyzt order scale = {image_dims[i]: s for (i, s) in enumerate(image.GetSpacing())} translation = {image_dims[i]: s for (i, s) in enumerate(image.GetOrigin())} - spatial_image = to_spatial_image(image_da.data, - dims=image_da.dims, - scale=scale, - translation=translation, - name=name, - axis_names=axis_names, - axis_units=axis_units) + spatial_image = to_spatial_image( + image_da.data, + dims=image_da.dims, + scale=scale, + translation=translation, + name=name, + axis_names=axis_names, + axis_units=axis_units, + ) - return to_multiscale(spatial_image, - scale_factors, - method=method, - chunks=chunks) + return to_multiscale(spatial_image, scale_factors, method=method, chunks=chunks) diff --git a/multiscale_spatial_image/to_multiscale/to_multiscale.py b/multiscale_spatial_image/to_multiscale/to_multiscale.py index 828d3a3..23a8c1b 100644 --- a/multiscale_spatial_image/to_multiscale/to_multiscale.py +++ b/multiscale_spatial_image/to_multiscale/to_multiscale.py @@ -1,18 +1,21 @@ -from typing import Union, Sequence, List, Optional, Dict, Mapping, Any, Tuple +from typing import Union, Sequence, Optional, Dict, Mapping, Any, Tuple from enum import Enum -from spatial_image import to_spatial_image, SpatialImage # type: ignore +from spatial_image import SpatialImage # type: ignore -from dask.array import map_blocks, map_overlap -import numpy as np from datatree import DataTree from ._xarray import _downsample_xarray_coarsen -from ._itk import _downsample_itk_bin_shrink, _downsample_itk_gaussian, _downsample_itk_label +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 + class Methods(Enum): XARRAY_COARSEN = "xarray_coarsen" ITK_BIN_SHRINK = "itk_bin_shrink" @@ -22,6 +25,7 @@ class Methods(Enum): DASK_IMAGE_MODE = "dask_image_mode" DASK_IMAGE_NEAREST = "dask_image_nearest" + @inject_docs(m=Methods) def to_multiscale( image: SpatialImage, @@ -83,45 +87,103 @@ def to_multiscale( out_chunks = chunks if out_chunks is None: out_chunks = default_chunks - + # check for valid scale factors - current_shape = {d:s for (d, s) in zip(image.dims, image.shape) if d not in {"t", "c"}} + current_shape = { + d: s for (d, s) in zip(image.dims, image.shape) if d not in {"t", "c"} + } for scale_factor in scale_factors: if isinstance(scale_factor, dict): - current_shape = {k: (current_shape[k] / s) for (k, s) in scale_factor.items()} + current_shape = { + k: (current_shape[k] / s) for (k, s) in scale_factor.items() + } elif isinstance(scale_factor, int): current_shape = {k: (s / scale_factor) for (k, s) in current_shape.items()} - for k,v in current_shape.items(): + for k, v in current_shape.items(): if v < 1: - raise ValueError(f"Scale factor {scale_factor} is incompatible with image shape {image.shape} along dimension `{k}`.") + raise ValueError( + f"Scale factor {scale_factor} is incompatible with image shape {image.shape} along dimension `{k}`." + ) current_input = image.chunk(out_chunks) # https://github.com/pydata/xarray/issues/5219 if "chunks" in current_input.encoding: del current_input.encoding["chunks"] - data_objects = {f"scale0": current_input.to_dataset(name=image.name, promote_attrs=True)} + data_objects = { + "scale0": current_input.to_dataset(name=image.name, promote_attrs=True) + } 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) + 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) + 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) + 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) + 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) + 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') + 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') - - multiscale = DataTree.from_dict( - d=data_objects - ) + data_objects = _downsample_dask_image( + current_input, + default_chunks, + out_chunks, + scale_factors, + data_objects, + image, + label="mode", + ) + + multiscale = DataTree.from_dict(d=data_objects) return multiscale diff --git a/pixi.lock b/pixi.lock index 30e6d7c..1a4ac9a 100644 --- a/pixi.lock +++ b/pixi.lock @@ -154,7 +154,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.7.4-hbcca054_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.0.0-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.0.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h77fa898_0.conda @@ -203,7 +202,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.7.4-h8857fd0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.0.0-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.0.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.0-h1b8f9f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda @@ -246,7 +244,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.7.4-hf0a4a13_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.0.0-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.0.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.46.0-hfb93653_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-hfb2fe0b_1.conda @@ -290,7 +287,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.7.4-h56e8100_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.0.0-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.0.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.46.0-h2466b09_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_1.conda @@ -331,6 +327,135 @@ environments: - pypi: https://files.pythonhosted.org/packages/6e/a3/5e92dc7e35c08574472bbd9201aabdad03e38d54cc47c421922d219502c6/xarray_datatree-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/59/f2f8fa894e79699ff290f0ed37b60749220694c397cf784d1f45eb2b5151/zarr-2.17.2-py3-none-any.whl - pypi: . + lint: + channels: + - url: https://conda.anaconda.org/conda-forge/ + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.7.4-hbcca054_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.16.0-py39h7a31438_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h77fa898_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.0-hde9e2c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.1.0-hc0a3c3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.1-h4bc722e_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-3.7.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.9.19-h0755675_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-4_cp39.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.1-py39hd1e30aa_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-71.0.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39h7633fee_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.26.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 + osx-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.7.4-h8857fd0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.16.0-py39h18ef598_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-18.1.8-hef8daea_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.2-h0d85af4_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.46.0-h1b8f9f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h5846eda_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.1-h87427d6_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-3.7.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.9.19-h7a9c478_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python_abi-3.9-4_cp39.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.1-py39hdc70f33_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-71.0.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ukkonen-1.0.1-py39h8ee36c8_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.26.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h0d85af4_2.tar.bz2 + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.7.4-hf0a4a13_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.16.0-py39he153c15_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-18.1.8-h167917d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.46.0-hfb93653_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-hfb2fe0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-hb89a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.3.1-hfb2fe0b_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-3.7.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.9.19-hd7ebdb9_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python_abi-3.9-4_cp39.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.1-py39h0f82c59_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-71.0.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.0.1-py39hbd775c9_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.26.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 + win-64: + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h2466b09_7.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.7.4-h56e8100_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.16.0-py39ha55989b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.46.0-h2466b09_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-3.7.1-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.9.19-h4de0772_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python_abi-3.9-4_cp39.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.1-py39ha55989b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-71.0.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/ukkonen-1.0.1-py39h1f6ef14_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h8a93ad2_20.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-ha82c5b3_20.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.26.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_20.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h8ffe710_2.tar.bz2 notebooks: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -365,6 +490,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 @@ -493,7 +619,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/8c/8e/d424659bfaa8ad706740986e7b3e0455a800333c9014af45ea03fc31728d/dask_expr-1.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/70/e34e5090865c3f840256c462e4671937270317fdf260871cd72546449d54/dask_image-2024.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/30/ad4483dfc5a1999f26b7bc5edc311576f433a3e00dd8aea01f2099c3a29f/fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/70/b0/6f1ebdabfb604e39a0f84428986b89ab55f246b64cddaa495f2c953e1f6b/frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl @@ -573,6 +698,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 @@ -696,7 +822,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/8c/8e/d424659bfaa8ad706740986e7b3e0455a800333c9014af45ea03fc31728d/dask_expr-1.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/70/e34e5090865c3f840256c462e4671937270317fdf260871cd72546449d54/dask_image-2024.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/88/86aba816dc6cc4a296df93fb00f6b1dc1ba495c235ccb4241f14cc1a5872/fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/4d/23/7f01123d0e5adcc65cbbde5731378237dea7db467abd19e391f1ddd4130d/frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl @@ -776,6 +901,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 @@ -899,7 +1025,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/8c/8e/d424659bfaa8ad706740986e7b3e0455a800333c9014af45ea03fc31728d/dask_expr-1.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/70/e34e5090865c3f840256c462e4671937270317fdf260871cd72546449d54/dask_image-2024.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/d5/bff14bc918cb2f407e336de41f4dc85aa79888c5954a0d9e4ff4c29aebd9/fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8b/c9/a81e9af48291954a883d35686f32308238dc968043143133b8ac9e2772af/frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl @@ -979,6 +1104,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 @@ -1103,7 +1229,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/8c/8e/d424659bfaa8ad706740986e7b3e0455a800333c9014af45ea03fc31728d/dask_expr-1.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/70/e34e5090865c3f840256c462e4671937270317fdf260871cd72546449d54/dask_image-2024.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/d5/f7d2122140848fb7a7bd1d59881b822dd514c19b7648984b7565d9f39d56/fonttools-4.53.1-cp39-cp39-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/29/eb/2110c4be2f622e87864e433efd7c4ee6e4f8a59ff2a93c1aa426ee50a8b8/frozenlist-1.4.1-cp39-cp39-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl @@ -1178,6 +1303,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2 @@ -1263,7 +1389,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/8c/8e/d424659bfaa8ad706740986e7b3e0455a800333c9014af45ea03fc31728d/dask_expr-1.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/70/e34e5090865c3f840256c462e4671937270317fdf260871cd72546449d54/dask_image-2024.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/b0/6f1ebdabfb604e39a0f84428986b89ab55f246b64cddaa495f2c953e1f6b/frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/84/f1647217231f6cc46883e5d26e870cc3e1520d458ecd52d6df750810d53c/imageio-2.34.2-py3-none-any.whl @@ -1317,6 +1442,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2 @@ -1395,7 +1521,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/8c/8e/d424659bfaa8ad706740986e7b3e0455a800333c9014af45ea03fc31728d/dask_expr-1.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/70/e34e5090865c3f840256c462e4671937270317fdf260871cd72546449d54/dask_image-2024.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4d/23/7f01123d0e5adcc65cbbde5731378237dea7db467abd19e391f1ddd4130d/frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/84/f1647217231f6cc46883e5d26e870cc3e1520d458ecd52d6df750810d53c/imageio-2.34.2-py3-none-any.whl @@ -1449,6 +1574,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2 @@ -1527,7 +1653,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/8c/8e/d424659bfaa8ad706740986e7b3e0455a800333c9014af45ea03fc31728d/dask_expr-1.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/70/e34e5090865c3f840256c462e4671937270317fdf260871cd72546449d54/dask_image-2024.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8b/c9/a81e9af48291954a883d35686f32308238dc968043143133b8ac9e2772af/frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/84/f1647217231f6cc46883e5d26e870cc3e1520d458ecd52d6df750810d53c/imageio-2.34.2-py3-none-any.whl @@ -1581,6 +1706,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2 @@ -1660,7 +1786,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/8c/8e/d424659bfaa8ad706740986e7b3e0455a800333c9014af45ea03fc31728d/dask_expr-1.1.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/70/e34e5090865c3f840256c462e4671937270317fdf260871cd72546449d54/dask_image-2024.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/eb/2110c4be2f622e87864e433efd7c4ee6e4f8a59ff2a93c1aa426ee50a8b8/frozenlist-1.4.1-cp39-cp39-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/84/f1647217231f6cc46883e5d26e870cc3e1520d458ecd52d6df750810d53c/imageio-2.34.2-py3-none-any.whl @@ -2430,6 +2555,23 @@ packages: - pkg:pypi/cffi?source=conda-forge-mapping size: 231790 timestamp: 1696002104149 +- kind: conda + name: cfgv + version: 3.3.1 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2 + sha256: fbc03537a27ef756162c49b1d0608bf7ab12fa5e38ceb8563d6f4859e835ac5c + md5: ebb5f5f7dc4f1a3780ef7ea7738db08c + depends: + - python >=3.6.1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cfgv?source=conda-forge-mapping + size: 10788 + timestamp: 1629909423398 - kind: conda name: charset-normalizer version: 3.3.2 @@ -2787,6 +2929,23 @@ packages: - pkg:pypi/defusedxml?source=conda-forge-mapping size: 24062 timestamp: 1615232388757 +- kind: conda + name: distlib + version: 0.3.8 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.8-pyhd8ed1ab_0.conda + sha256: 3ff11acdd5cc2f80227682966916e878e45ced94f59c402efb94911a5774e84e + md5: db16c66b759a64dc5183d69cc3745a52 + depends: + - python 2.7|>=3.6 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/distlib?source=conda-forge-mapping + size: 274915 + timestamp: 1702383349284 - kind: conda name: entrypoints version: '0.4' @@ -2843,26 +3002,22 @@ packages: url: https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl sha256: 758819cb5d94cdedf4e836988b74de396ceacb8e2794d21f82d131fd9ee77237 requires_python: '>=3.6' -- kind: pypi +- kind: conda name: filelock version: 3.15.4 - url: https://files.pythonhosted.org/packages/ae/f0/48285f0262fe47103a4a45972ed2f9b93e4c80b8fd609fa98da78b2a5706/filelock-3.15.4-py3-none-any.whl - sha256: 6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 - requires_dist: - - furo>=2023.9.10 ; extra == 'docs' - - sphinx-autodoc-typehints!=1.23.4,>=1.25.2 ; extra == 'docs' - - sphinx>=7.2.6 ; extra == 'docs' - - covdefaults>=2.3 ; extra == 'testing' - - coverage>=7.3.2 ; extra == 'testing' - - diff-cover>=8.0.1 ; extra == 'testing' - - pytest-asyncio>=0.21 ; extra == 'testing' - - pytest-cov>=4.1 ; extra == 'testing' - - pytest-mock>=3.12 ; extra == 'testing' - - pytest-timeout>=2.2 ; extra == 'testing' - - pytest>=7.4.3 ; extra == 'testing' - - virtualenv>=20.26.2 ; extra == 'testing' - - typing-extensions>=4.8 ; python_version < '3.11' and extra == 'typing' - requires_python: '>=3.8' + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda + sha256: f78d9c0be189a77cb0c67d02f33005f71b89037a85531996583fb79ff3fe1a0a + md5: 0e7e4388e9d5283e22b35a9443bdbcc9 + depends: + - python >=3.7 + license: Unlicense + purls: + - pkg:pypi/filelock?source=conda-forge-mapping + size: 17592 + timestamp: 1719088395353 - kind: pypi name: fonttools version: 4.53.1 @@ -3277,6 +3432,24 @@ packages: - pkg:pypi/hyperframe?source=conda-forge-mapping size: 14646 timestamp: 1619110249723 +- kind: conda + name: identify + version: 2.6.0 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.0-pyhd8ed1ab_0.conda + sha256: 4a2889027df94d51be283536ac235feba77eaa42a0d051f65cd07ba824b324a6 + md5: f80cc5989f445f23b1622d6c455896d9 + depends: + - python >=3.6 + - ukkonen + license: MIT + license_family: MIT + purls: + - pkg:pypi/identify?source=conda-forge-mapping + size: 78197 + timestamp: 1720413864262 - kind: conda name: idna version: '3.7' @@ -5122,7 +5295,7 @@ packages: name: multiscale-spatial-image version: 1.0.0 path: . - sha256: efacfdaf8fc2f03d5f46c027961c9bbbe96d70359ff8f9b95537b0811eb97076 + sha256: 268c56b7a15dccc1806743654756c37cb1ef925215ad1fb6c100650657c854cc requires_dist: - dask - numpy @@ -5358,6 +5531,24 @@ packages: - pkg:pypi/nest-asyncio?source=conda-forge-mapping size: 11638 timestamp: 1705850780510 +- kind: conda + name: nodeenv + version: 1.9.1 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_0.conda + sha256: 85ee07342ab055dc081f3de8292c5e7195e43e046db9c5750f242f928f6bb8f2 + md5: dfe0528d0f1c16c1f7c528ea5536ab30 + depends: + - python 2.7|>=3.7 + - setuptools + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nodeenv?source=conda-forge-mapping + size: 34489 + timestamp: 1717585382642 - kind: conda name: notebook-shim version: 0.2.4 @@ -6308,6 +6499,28 @@ packages: - pkg:pypi/pooch?source=conda-forge-mapping size: 54375 timestamp: 1717777969967 +- kind: conda + name: pre-commit + version: 3.7.1 + build: pyha770c72_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/pre-commit-3.7.1-pyha770c72_0.conda + sha256: 689c169ce6ed5d516d8524cc1e6ef2687dff19747c1ed1ee9b347a71f47ff12d + md5: 724bc4489c1174fc8e3233b0624fa51f + depends: + - cfgv >=2.0.0 + - identify >=1.0.0 + - nodeenv >=0.11.1 + - python >=3.9 + - pyyaml >=5.1 + - virtualenv >=20.10.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pre-commit?source=conda-forge-mapping + size: 179748 + timestamp: 1715432871404 - kind: conda name: prometheus_client version: 0.20.0 @@ -8197,6 +8410,90 @@ packages: purls: [] size: 1283972 timestamp: 1666630199266 +- kind: conda + name: ukkonen + version: 1.0.1 + build: py39h1f6ef14_4 + build_number: 4 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/ukkonen-1.0.1-py39h1f6ef14_4.conda + sha256: 3358d70dacdace2e634a7d166cd4cb3df46d71715d2e245e6495cf7e50250ac6 + md5: 9c2b113471940e17cd83a437a862cf5e + depends: + - cffi + - python >=3.9,<3.10.0a0 + - python_abi 3.9.* *_cp39 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=conda-forge-mapping + size: 17124 + timestamp: 1695549938973 +- kind: conda + name: ukkonen + version: 1.0.1 + build: py39h7633fee_4 + build_number: 4 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39h7633fee_4.conda + sha256: 6ca31e79eeee63ea33e5b18dd81c1bc202c43741b5f0de3bcd4409f9ffd93a95 + md5: b66595fbda99771266f042f42c7457be + depends: + - cffi + - libgcc-ng >=12 + - libstdcxx-ng >=12 + - python >=3.9,<3.10.0a0 + - python_abi 3.9.* *_cp39 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=conda-forge-mapping + size: 13827 + timestamp: 1695549555453 +- kind: conda + name: ukkonen + version: 1.0.1 + build: py39h8ee36c8_4 + build_number: 4 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/ukkonen-1.0.1-py39h8ee36c8_4.conda + sha256: 87b17e86e8540aa4a598fd024fd884427557138dceae2ba48d3e99a51dad623a + md5: 234e1d8799d93d5d51b3d778019a6db9 + depends: + - cffi + - libcxx >=15.0.7 + - python >=3.9,<3.10.0a0 + - python_abi 3.9.* *_cp39 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=conda-forge-mapping + size: 13089 + timestamp: 1695549678243 +- kind: conda + name: ukkonen + version: 1.0.1 + build: py39hbd775c9_4 + build_number: 4 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.0.1-py39hbd775c9_4.conda + sha256: 2e497466d784c9d2ae94a9e72da05f5b125654ca95d0c0e6c4c697fc5f8be640 + md5: 1c2db28b464d0331fc4e79796b25c389 + depends: + - cffi + - libcxx >=15.0.7 + - python >=3.9,<3.10.0a0 + - python >=3.9,<3.10.0a0 *_cpython + - python_abi 3.9.* *_cp39 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=conda-forge-mapping + size: 13864 + timestamp: 1695550024662 - kind: conda name: uri-template version: 1.3.0 @@ -8272,6 +8569,26 @@ packages: purls: [] size: 751934 timestamp: 1717709031266 +- kind: conda + name: virtualenv + version: 20.26.3 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.26.3-pyhd8ed1ab_0.conda + sha256: f78961b194e33eed5fdccb668774651ec9423a043069fa7a4e3e2f853b08aa0c + md5: 284008712816c64c85bf2b7fa9f3b264 + depends: + - distlib <1,>=0.3.7 + - filelock <4,>=3.12.2 + - platformdirs <5,>=3.9.1 + - python >=3.8 + license: MIT + license_family: MIT + purls: + - pkg:pypi/virtualenv?source=conda-forge-mapping + size: 4363507 + timestamp: 1719150878323 - kind: conda name: vs2015_runtime version: 14.40.33810 diff --git a/pyproject.toml b/pyproject.toml index 65a8277..6356037 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,6 +93,7 @@ default = { solve-group = "default" } test = { features = ["test", "dask-image", "itk"], solve-group = "default" } notebooks = { features = ["test", "dask-image", "itk", "imagej", "notebooks"], solve-group = "default" } data = { features = ["data"], no-default-feature = true, solve-group = "default" } +lint = { features = ["lint"], no-default-feature = true, solve-group = "default" } [tool.pixi.feature.test.tasks] test = { cmd = "pytest", description = "Run the test suite" } @@ -117,3 +118,11 @@ pooch = ">=1.8.2,<2" [tool.pixi.feature.data.tasks] hash-data = { cmd = "tar cvf ../data.tar * && gzip -9 -f ../data.tar && echo 'New SHA256:' && python3 -c 'import pooch; print(pooch.file_hash(\"../data.tar.gz\"))'", cwd = "test/data", description = "Update the testing data tarball and get its sha256 hash" } + +[tool.pixi.feature.lint.dependencies] +pre-commit = "*" + +[tool.pixi.feature.lint.tasks] +pre-commit-install = { cmd = "pre-commit install", description = "Install pre-commit hooks" } +pre-commit-run = { cmd = "pre-commit run --all", description = "Run pre-commit hooks on all repository files" } +lint = { depends-on = ["pre-commit-run"], description = "Run linters" } diff --git a/test/_data.py b/test/_data.py index 90f51e7..97ed434 100644 --- a/test/_data.py +++ b/test/_data.py @@ -6,20 +6,22 @@ import xarray as xr from datatree import open_datatree -test_data_ipfs_cid = 'bafybeiaskr5fxg6rbcwlxl6ibzqhubdleacenrpbnymc6oblwoi7ceqzta' -test_data_sha256 = '507dd779cba007c46ea68a5fe8865cabd5d8a7e00816470faae9195d1f1c3cd1' +test_data_ipfs_cid = "bafybeiaskr5fxg6rbcwlxl6ibzqhubdleacenrpbnymc6oblwoi7ceqzta" +test_data_sha256 = "507dd779cba007c46ea68a5fe8865cabd5d8a7e00816470faae9195d1f1c3cd1" test_dir = Path(__file__).resolve().parent extract_dir = "data" test_data_dir = test_dir / extract_dir -test_data = pooch.create(path=test_dir, - base_url=f"https://{test_data_ipfs_cid}.ipfs.w3s.link/ipfs/{test_data_ipfs_cid}/", - registry= { +test_data = pooch.create( + path=test_dir, + base_url=f"https://{test_data_ipfs_cid}.ipfs.w3s.link/ipfs/{test_data_ipfs_cid}/", + registry={ "data.tar.gz": f"sha256:{test_data_sha256}", }, - retry_if_failed=5 - ) + retry_if_failed=5, +) + @pytest.fixture def input_images(): @@ -45,25 +47,29 @@ def input_images(): test_data_dir / "input" / "2th_cthead1.zarr", ) image_ds = xr.open_zarr(store) - image_da = image_ds['2th_cthead1'] + image_da = image_ds["2th_cthead1"] result["2th_cthead1"] = image_da return result + def verify_against_baseline(dataset_name, baseline_name, multiscale): store = DirectoryStore( - test_data_dir / f"baseline/{dataset_name}/{baseline_name}", dimension_separator="/" + test_data_dir / f"baseline/{dataset_name}/{baseline_name}", + dimension_separator="/", ) dt = open_datatree(store, engine="zarr", mode="r") xr.testing.assert_equal(dt.ds, multiscale.ds) for scale in multiscale.children: xr.testing.assert_equal(dt[scale].ds, multiscale[scale].ds) + def store_new_image(dataset_name, baseline_name, multiscale_image): - '''Helper method for writing output results to disk - for later upload as test baseline''' + """Helper method for writing output results to disk + for later upload as test baseline""" path = test_data_dir / f"baseline/{dataset_name}/{baseline_name}" store = DirectoryStore( - str(path), dimension_separator="/", + str(path), + dimension_separator="/", ) multiscale_image.to_zarr(store, mode="w") diff --git a/test/test_ngff_validation.py b/test/test_ngff_validation.py index 27df06b..98b56d7 100644 --- a/test/test_ngff_validation.py +++ b/test/test_ngff_validation.py @@ -3,7 +3,6 @@ import urllib3 from referencing import Registry, Resource -from referencing.jsonschema import DRAFT202012 from jsonschema import Draft202012Validator from datatree import DataTree @@ -17,14 +16,18 @@ ngff_uri = "https://ngff.openmicroscopy.org" + def load_schema(version: str = "0.4", strict: bool = False) -> Dict: strict_str = "" if strict: strict_str = "strict_" - response = http.request("GET", f"{ngff_uri}/{version}/schemas/{strict_str}image.schema") + response = http.request( + "GET", f"{ngff_uri}/{version}/schemas/{strict_str}image.schema" + ) schema = json.loads(response.data.decode()) return schema + def check_valid_ngff(multiscale: DataTree): store = zarr.storage.MemoryStore(dimension_separator="/") assert isinstance(multiscale.msi, MultiscaleSpatialImage) @@ -34,8 +37,10 @@ def check_valid_ngff(multiscale: DataTree): ngff = metadata[".zattrs"] image_schema = load_schema(version="0.4", strict=False) - strict_image_schema = load_schema(version="0.4", strict=True) - registry = Registry().with_resource(ngff_uri, resource=Resource.from_contents(image_schema)) + # strict_image_schema = load_schema(version="0.4", strict=True) + registry = Registry().with_resource( + ngff_uri, resource=Resource.from_contents(image_schema) + ) validator = Draft202012Validator(image_schema, registry=registry) # registry_strict = Registry().with_resource(ngff_uri, resource=Resource.from_contents(strict_image_schema)) # strict_validator = Draft202012Validator(strict_schema, registry=registry_strict) diff --git a/test/test_to_multiscale.py b/test/test_to_multiscale.py index 4bfb5fd..8077463 100644 --- a/test/test_to_multiscale.py +++ b/test/test_to_multiscale.py @@ -2,10 +2,10 @@ import pytest from multiscale_spatial_image import to_multiscale -from ._data import input_images +from ._data import input_images # noqa: F401 -def test_base_scale(input_images): +def test_base_scale(input_images): # noqa: F811 image = input_images["cthead1"] multiscale = to_multiscale(image, []) diff --git a/test/test_to_multiscale_dask_image.py b/test/test_to_multiscale_dask_image.py index a0746dc..1b9148c 100644 --- a/test/test_to_multiscale_dask_image.py +++ b/test/test_to_multiscale_dask_image.py @@ -1,8 +1,9 @@ -from multiscale_spatial_image import Methods, to_multiscale +from multiscale_spatial_image import Methods, to_multiscale -from ._data import input_images, verify_against_baseline +from ._data import verify_against_baseline, input_images # noqa: F401 -def test_gaussian_isotropic_scale_factors(input_images): + +def test_gaussian_isotropic_scale_factors(input_images): # noqa: F811 dataset_name = "cthead1" image = input_images[dataset_name] baseline_name = "2_4/DASK_IMAGE_GAUSSIAN" @@ -22,7 +23,7 @@ def test_gaussian_isotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_gaussian_anisotropic_scale_factors(input_images): +def test_gaussian_anisotropic_scale_factors(input_images): # noqa: F811 dataset_name = "cthead1" image = input_images[dataset_name] scale_factors = [{"x": 2, "y": 4}, {"x": 1, "y": 2}] @@ -42,7 +43,7 @@ def test_gaussian_anisotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_label_nearest_isotropic_scale_factors(input_images): +def test_label_nearest_isotropic_scale_factors(input_images): # noqa: F811 dataset_name = "2th_cthead1" image = input_images[dataset_name] baseline_name = "2_4/DASK_IMAGE_NEAREST" @@ -56,7 +57,7 @@ def test_label_nearest_isotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_label_nearest_anisotropic_scale_factors(input_images): +def test_label_nearest_anisotropic_scale_factors(input_images): # noqa: F811 dataset_name = "2th_cthead1" image = input_images[dataset_name] scale_factors = [{"x": 2, "y": 4}, {"x": 1, "y": 2}] @@ -65,7 +66,7 @@ def test_label_nearest_anisotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_label_mode_isotropic_scale_factors(input_images): +def test_label_mode_isotropic_scale_factors(input_images): # noqa: F811 dataset_name = "2th_cthead1" image = input_images[dataset_name] baseline_name = "2_4/DASK_IMAGE_MODE" @@ -79,7 +80,7 @@ def test_label_mode_isotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_label_mode_anisotropic_scale_factors(input_images): +def test_label_mode_anisotropic_scale_factors(input_images): # noqa: F811 dataset_name = "2th_cthead1" image = input_images[dataset_name] scale_factors = [{"x": 2, "y": 4}, {"x": 1, "y": 2}] diff --git a/test/test_to_multiscale_itk.py b/test/test_to_multiscale_itk.py index 7faab3c..12dc1dd 100644 --- a/test/test_to_multiscale_itk.py +++ b/test/test_to_multiscale_itk.py @@ -1,8 +1,9 @@ from multiscale_spatial_image import Methods, to_multiscale, itk_image_to_multiscale -from ._data import input_images, verify_against_baseline, store_new_image +from ._data import verify_against_baseline, input_images # noqa: F401 -def test_isotropic_scale_factors(input_images): + +def test_isotropic_scale_factors(input_images): # noqa: F811 dataset_name = "cthead1" image = input_images[dataset_name] multiscale = to_multiscale(image, [2, 4], method=Methods.ITK_BIN_SHRINK) @@ -22,7 +23,7 @@ def test_isotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_gaussian_isotropic_scale_factors(input_images): +def test_gaussian_isotropic_scale_factors(input_images): # noqa: F811 dataset_name = "cthead1" image = input_images[dataset_name] baseline_name = "2_4/ITK_GAUSSIAN" @@ -42,7 +43,7 @@ def test_gaussian_isotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_label_gaussian_isotropic_scale_factors(input_images): +def test_label_gaussian_isotropic_scale_factors(input_images): # noqa: F811 dataset_name = "2th_cthead1" image = input_images[dataset_name] baseline_name = "2_4/ITK_LABEL_GAUSSIAN" @@ -56,7 +57,7 @@ def test_label_gaussian_isotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_anisotropic_scale_factors(input_images): +def test_anisotropic_scale_factors(input_images): # noqa: F811 dataset_name = "cthead1" image = input_images[dataset_name] scale_factors = [{"x": 2, "y": 4}, {"x": 1, "y": 2}] @@ -76,7 +77,7 @@ def test_anisotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_gaussian_anisotropic_scale_factors(input_images): +def test_gaussian_anisotropic_scale_factors(input_images): # noqa: F811 dataset_name = "cthead1" image = input_images[dataset_name] scale_factors = [{"x": 2, "y": 4}, {"x": 1, "y": 2}] @@ -96,7 +97,7 @@ def test_gaussian_anisotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_label_gaussian_anisotropic_scale_factors(input_images): +def test_label_gaussian_anisotropic_scale_factors(input_images): # noqa: F811 dataset_name = "2th_cthead1" image = input_images[dataset_name] scale_factors = [{"x": 2, "y": 4}, {"x": 1, "y": 2}] @@ -105,14 +106,14 @@ def test_label_gaussian_anisotropic_scale_factors(input_images): verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_from_itk(input_images): +def test_from_itk(input_images): # noqa: F811 import itk import numpy as np # Test 2D with ITK default metadata dataset_name = "cthead1" image = itk.image_from_xarray(input_images[dataset_name]) - scale_factors=[4,2] + scale_factors = [4, 2] multiscale = itk_image_to_multiscale(image, scale_factors) baseline_name = "4_2/from_itk" verify_against_baseline(dataset_name, baseline_name, multiscale) @@ -120,29 +121,39 @@ def test_from_itk(input_images): # Test 2D with nonunit metadata dataset_name = "cthead1" image = itk.image_from_xarray(input_images[dataset_name]) - image.SetDirection(np.array([[-1,0],[0,1]])) - image.SetSpacing([0.5,2.0]) - image.SetOrigin([3.0,5.0]) - - name='cthead1_nonunit_metadata' - axis_units={dim: 'millimeters' for dim in ('x','y','z')} - - scale_factors=[4,2] - multiscale = itk_image_to_multiscale(image, scale_factors=scale_factors, anatomical_axes=False, axis_units=axis_units, name=name) + image.SetDirection(np.array([[-1, 0], [0, 1]])) + image.SetSpacing([0.5, 2.0]) + image.SetOrigin([3.0, 5.0]) + + name = "cthead1_nonunit_metadata" + axis_units = {dim: "millimeters" for dim in ("x", "y", "z")} + + scale_factors = [4, 2] + multiscale = itk_image_to_multiscale( + image, + scale_factors=scale_factors, + anatomical_axes=False, + axis_units=axis_units, + name=name, + ) baseline_name = "4_2/from_itk_nonunit_metadata" verify_against_baseline(dataset_name, baseline_name, multiscale) # Expect error for 2D image with anatomical axes try: - itk_image_to_multiscale(image, scale_factors=scale_factors, anatomical_axes=True) - raise Exception('Failed to catch expected exception for 2D image requesting anatomical axes') + itk_image_to_multiscale( + image, scale_factors=scale_factors, anatomical_axes=True + ) + raise Exception( + "Failed to catch expected exception for 2D image requesting anatomical axes" + ) except ValueError: - pass # caught expected exception + pass # caught expected exception # Test 3D with ITK default metadata dataset_name = "small_head" image = itk.image_from_xarray(input_images[dataset_name]) - scale_factors=[4,2] + scale_factors = [4, 2] multiscale = itk_image_to_multiscale(image, scale_factors) baseline_name = "4_2/from_itk" verify_against_baseline(dataset_name, baseline_name, multiscale) @@ -150,12 +161,20 @@ def test_from_itk(input_images): # Test 3D with additional metadata dataset_name = "small_head" image = itk.image_from_xarray(input_images[dataset_name]) - image.SetObjectName(str(input_images[dataset_name].name)) # implicit in image_from_xarray in itk>v5.3rc04 - - name='small_head_anatomical' - axis_units={dim: 'millimeters' for dim in input_images[dataset_name].dims} - - scale_factors=[4,2] - multiscale = itk_image_to_multiscale(image, scale_factors=scale_factors, anatomical_axes=True, axis_units=axis_units, name=name) + image.SetObjectName( + str(input_images[dataset_name].name) + ) # implicit in image_from_xarray in itk>v5.3rc04 + + name = "small_head_anatomical" + axis_units = {dim: "millimeters" for dim in input_images[dataset_name].dims} + + scale_factors = [4, 2] + multiscale = itk_image_to_multiscale( + image, + scale_factors=scale_factors, + anatomical_axes=True, + axis_units=axis_units, + name=name, + ) baseline_name = "4_2/from_itk_anatomical" verify_against_baseline(dataset_name, baseline_name, multiscale) diff --git a/test/test_to_multiscale_xarray.py b/test/test_to_multiscale_xarray.py index 7eda462..da286f7 100644 --- a/test/test_to_multiscale_xarray.py +++ b/test/test_to_multiscale_xarray.py @@ -1,8 +1,9 @@ from multiscale_spatial_image import Methods, to_multiscale -from ._data import input_images, verify_against_baseline, store_new_image +from ._data import verify_against_baseline, input_images # noqa: F401 -def test_isotropic_scale_factors(input_images): + +def test_isotropic_scale_factors(input_images): # noqa: F811 dataset_name = "cthead1" image = input_images[dataset_name] baseline_name = "2_4/XARRAY_COARSEN" @@ -21,7 +22,8 @@ def test_isotropic_scale_factors(input_images): multiscale = to_multiscale(image, [2, 3, 4], method=Methods.XARRAY_COARSEN) verify_against_baseline(dataset_name, baseline_name, multiscale) -def test_anisotropic_scale_factors(input_images): + +def test_anisotropic_scale_factors(input_images): # noqa: F811 dataset_name = "cthead1" image = input_images[dataset_name] scale_factors = [{"x": 2, "y": 4}, {"x": 1, "y": 2}] @@ -41,4 +43,4 @@ def test_anisotropic_scale_factors(input_images): ] multiscale = to_multiscale(image, scale_factors, method=Methods.XARRAY_COARSEN) baseline_name = "x3y2z4_x2y2z2_x1y2z1/XARRAY_COARSEN" - verify_against_baseline(dataset_name, baseline_name, multiscale) \ No newline at end of file + verify_against_baseline(dataset_name, baseline_name, multiscale)