diff --git a/pyproject.toml b/pyproject.toml index 684b373..ab4e244 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,9 +22,13 @@ dependencies = [ "bioio-tifffile>=1.3", "fire>=0.7.1", "jinja2>=3.1.6", + "numpy>=2.2.6", "pandas>=3.0.2", + "scipy>=1.17.1", + "tqdm>=4.67.3", "pandera>=0.31.1", "scikit-image>=0.26", + ] scripts.ZedProfiler = "ZedProfiler.cli:trigger" diff --git a/src/zedprofiler/featurization/granularity.py b/src/zedprofiler/featurization/granularity.py index 958ff14..9d79181 100644 --- a/src/zedprofiler/featurization/granularity.py +++ b/src/zedprofiler/featurization/granularity.py @@ -1,10 +1,412 @@ -"""Granularity featurization module scaffold.""" +""" +Calculate the granularity spectrum of a 3D image. +""" from __future__ import annotations -from zedprofiler.exceptions import ZedProfilerError +from typing import Dict, Optional +import numpy +import scipy.ndimage +import skimage.morphology +import tqdm -def compute() -> dict[str, list[float]]: - """Placeholder for granularity computation implementation.""" - raise ZedProfilerError("granularity.compute is not implemented yet") +from zedprofiler.IO.loading_classes import ObjectLoader + + +def _fix_scipy_ndimage_result(result: float | list | numpy.ndarray) -> numpy.ndarray: + """Convert scipy.ndimage aggregation results to a consistent array. + + Equivalent to centrosome.cpmorphology.fixup_scipy_ndimage_result. + scipy.ndimage.mean/sum can return a scalar when there's one label, + or a list otherwise. This ensures we always get a numpy array. + + Parameters + ---------- + result : scalar, list, or numpy.ndarray + Output from scipy.ndimage.mean or similar. + + Returns + ------- + numpy.ndarray + 1-D array of results. + """ + if numpy.isscalar(result): + return numpy.array([result]) + return numpy.asarray(result) + + +def _subsample_3d( + data: numpy.ndarray, + new_shape: numpy.ndarray, + subsample_factor: float, + order: int = 1, +) -> numpy.ndarray: + """Subsample a 3D array using map_coordinates, matching CellProfiler. + + CellProfiler generates coordinates for the new shape and divides by + subsample_factor to map back into the original coordinate space. + The same scalar factor is used for all three axes. + + Parameters + ---------- + data : numpy.ndarray + 3D array to subsample. + new_shape : numpy.ndarray + Target shape as a float array (coordinate grid extent). + subsample_factor : float + The factor used to divide coordinates (same for all axes). + order : int + Interpolation order (1 for linear, 0 for nearest-neighbor). + + Returns + ------- + numpy.ndarray + Subsampled array. + """ + if subsample_factor >= 1.0: + return data.copy() + + k, i, j = ( + numpy.mgrid[0 : new_shape[0], 0 : new_shape[1], 0 : new_shape[2]].astype(float) + / subsample_factor + ) + return scipy.ndimage.map_coordinates(data, (k, i, j), order=order) + + +def _upsample_3d( + data: numpy.ndarray, + subsampled_shape: numpy.ndarray, + original_shape: tuple, +) -> numpy.ndarray: + """Upsample a 3D array back to original shape using map_coordinates. + + Matches CellProfiler's approach for restoring reconstructed images + to the original label resolution. + + Parameters + ---------- + data : numpy.ndarray + Subsampled 3D array to upsample. + subsampled_shape : numpy.ndarray + Shape of the subsampled space (float array, preserves CellProfiler + precision). + original_shape : tuple + Target shape to upsample to. + + Returns + ------- + numpy.ndarray + Upsampled array at original_shape resolution. + """ + k, i, j = numpy.mgrid[ + 0 : original_shape[0], 0 : original_shape[1], 0 : original_shape[2] + ].astype(float) + k *= float(subsampled_shape[0] - 1) / float(original_shape[0] - 1) + i *= float(subsampled_shape[1] - 1) / float(original_shape[1] - 1) + j *= float(subsampled_shape[2] - 1) / float(original_shape[2] - 1) + return scipy.ndimage.map_coordinates(data, (k, i, j), order=1) + + +def compute_granularity( # noqa: C901, PLR0912, PLR0913, PLR0915 + object_loader: ObjectLoader, + radius: int = 10, + granular_spectrum_length: int = 16, + subsample_size: float = 0.25, + image_sample_size: float = 0.25, + mask_threshold: float = 0.9, + verbose: bool = False, + image_mask: Optional[numpy.ndarray] = None, +) -> Dict[str, list]: + """Calculate the granularity spectrum of a 3D image. + + Follows the CellProfiler MeasureGranularity algorithm exactly for 3D: + 1. Subsample the image uniformly (same factor for Z, Y, X). + 2. Further subsample for background tophat removal. + 3. Iteratively erode with ball(1) and reconstruct, measuring + signal lost at each scale as image-level and per-object values. + + Parameters + ---------- + object_loader : ObjectLoader + Loader containing the image and label arrays. + radius : int + Radius of the structuring element for background removal. + Should correspond to texture radius *after* subsampling. + granular_spectrum_length : int + Number of granularity scales to measure. + subsample_size : float + Subsampling factor for the image (0, 1]. Applied uniformly to Z/Y/X. + image_sample_size : float + Subsampling factor for background reduction (0, 1]. + Applied relative to the already-subsampled image. + mask_threshold : float + Threshold for converting interpolated masks back to boolean. + verbose : bool + Print diagnostic information. + image_mask : numpy.ndarray or None + Boolean mask matching the image shape. Corresponds to CellProfiler's + ``im.mask``. If None (default), all pixels are considered valid + (all-True mask), matching the typical CellProfiler behavior for + unmasked images. + + Returns + ------- + Dict[str, list] + Dictionary with keys 'object_id', 'feature', 'value'. + Image-level measurements use object_id=0. + """ + # Validate inputs + if subsample_size <= 0 or subsample_size > 1: + raise ValueError(f"subsample_size must be in (0, 1], got {subsample_size}") + if image_sample_size <= 0 or image_sample_size > 1: + raise ValueError( + f"image_sample_size must be in (0, 1], got {image_sample_size}" + ) + if radius <= 0: + raise ValueError(f"radius must be positive, got {radius}") + if granular_spectrum_length <= 0: + raise ValueError( + f"granular_spectrum_length must be positive, got {granular_spectrum_length}" + ) + + # Get original data + original_pixels = object_loader.image + original_labels = object_loader.label_image + original_shape = original_pixels.shape + + # Mask: CellProfiler uses im.mask (typically all-True for unmasked images) + if image_mask is None: + original_mask = numpy.ones(original_shape, dtype=bool) + else: + original_mask = image_mask.astype(bool) + + # ------------------------------------------------------------------ + # Step 1: Subsample image and mask (uniform factor for all axes) + # CellProfiler: new_shape = shape * subsample_size + # coordinates = mgrid[0:new_shape] / subsample_size + # ------------------------------------------------------------------ + new_shape = numpy.array(original_shape, dtype=float) + + if subsample_size < 1.0: + new_shape = new_shape * subsample_size + + pixels = _subsample_3d( + original_pixels, + new_shape, + subsample_factor=subsample_size, + order=1, + ) + mask = ( + _subsample_3d( + original_mask.astype(float), + new_shape, + subsample_factor=subsample_size, + order=1, + ) + > mask_threshold + ) + + if verbose: + print( + f"Subsampled image: {original_shape} -> {pixels.shape} " + f"(factor={subsample_size})" + ) + else: + pixels = original_pixels.copy() + mask = original_mask.copy() + + # ------------------------------------------------------------------ + # Step 2: Background removal via tophat filter + # + # CellProfiler 3D BUG (replicated for compatibility): + # The 3D branch uses new_shape for grid bounds and subsample_size + # for coordinate division, instead of back_shape and + # image_sample_size as the 2D branch does. This means: + # - back_pixels has the SAME shape as pixels (not smaller) + # - Many coordinates are out of bounds → map_coordinates returns 0 + # We replicate this exactly to match CellProfiler output. + # ------------------------------------------------------------------ + if image_sample_size < 1.0: + back_shape = new_shape * image_sample_size + + # CellProfiler 3D: mgrid[0:new_shape] / subsample_size + # (NOT mgrid[0:back_shape] / image_sample_size as 2D does) + k, i, j = ( + numpy.mgrid[0 : new_shape[0], 0 : new_shape[1], 0 : new_shape[2]].astype( + float + ) + / subsample_size + ) + back_pixels = scipy.ndimage.map_coordinates(pixels, (k, i, j), order=1) + back_mask = ( + scipy.ndimage.map_coordinates(mask.astype(float), (k, i, j)) + > mask_threshold + ) + + if verbose: + print( + f"Background subsampled: pixels {pixels.shape} -> " + f"back_pixels {back_pixels.shape} " + f"(image_sample_size={image_sample_size})" + ) + else: + back_pixels = pixels + back_mask = mask + back_shape = new_shape + + # Tophat filter: masked erosion + masked dilation + footprint_bg = skimage.morphology.ball(radius, dtype=bool) + + back_pixels_masked = numpy.zeros_like(back_pixels) + back_pixels_masked[back_mask] = back_pixels[back_mask] + back_pixels = skimage.morphology.erosion(back_pixels_masked, footprint=footprint_bg) + + back_pixels_masked = numpy.zeros_like(back_pixels) + back_pixels_masked[back_mask] = back_pixels[back_mask] + back_pixels = skimage.morphology.dilation( + back_pixels_masked, footprint=footprint_bg + ) + + # Upsample background back to subsampled image size + if image_sample_size < 1.0: + # CellProfiler 3D: mgrid[0:new_shape] with coords scaled by + # (back_shape - 1) / (new_shape - 1) + k, i, j = numpy.mgrid[ + 0 : new_shape[0], 0 : new_shape[1], 0 : new_shape[2] + ].astype(float) + k *= float(back_shape[0] - 1) / float(new_shape[0] - 1) + i *= float(back_shape[1] - 1) / float(new_shape[1] - 1) + j *= float(back_shape[2] - 1) / float(new_shape[2] - 1) + back_pixels = scipy.ndimage.map_coordinates(back_pixels, (k, i, j), order=1) + + # Subtract background + pixels = pixels - back_pixels + pixels[pixels < 0] = 0 + + if verbose: + print("Background removed via tophat filter.") + + # ------------------------------------------------------------------ + # Step 3: Object initialization + # CellProfiler computes per-object start_mean from the ORIGINAL image + # (im.pixel_data) using the full-resolution label image, with labels + # masked by im.mask: labels[~im.mask] = 0. + # ------------------------------------------------------------------ + object_measurements = { + "object_id": [], + "feature": [], + "value": [], + } + + label_range = numpy.unique(original_labels[original_labels > 0]) + nobjects = len(label_range) + + if nobjects > 0: + label_range = numpy.arange(1, nobjects + 1) + + # CellProfiler: self.labels[~im.mask] = 0 + masked_labels = original_labels.copy() + masked_labels[~original_mask] = 0 + + per_object_current_mean = _fix_scipy_ndimage_result( + scipy.ndimage.mean(original_pixels, masked_labels, label_range) + ) + per_object_start_mean = numpy.maximum( + per_object_current_mean, numpy.finfo(float).eps + ) + else: + label_range = numpy.array([], dtype=int) + masked_labels = original_labels + per_object_current_mean = numpy.array([]) + per_object_start_mean = numpy.array([]) + + # ------------------------------------------------------------------ + # Step 4: Granular spectrum loop + # CellProfiler computes startmean AFTER background subtraction but + # BEFORE zeroing pixels outside mask (zeroing is implicit via indexing). + # ------------------------------------------------------------------ + startmean = numpy.mean(pixels[mask]) + ero = pixels.copy() + # Mask the test image so masked pixels have no effect during reconstruction + ero[~mask] = 0 + currentmean = startmean + startmean = max(startmean, numpy.finfo(float).eps) + + # CellProfiler uses ball(1) for the iterative erosion/reconstruction loop + footprint = skimage.morphology.ball(1, dtype=bool) + + if verbose: + print( + f"Image startmean: {startmean:.6f}, " + f"Processing {nobjects} objects, " + f"Spectrum length: {granular_spectrum_length}" + ) + + for scale in tqdm.tqdm( + range(1, granular_spectrum_length + 1), + desc="Granularity measurement", + position=1, + leave=False, + ): + prevmean = currentmean + + # Masked erosion + ero_masked = numpy.zeros_like(ero) + ero_masked[mask] = ero[mask] + ero = skimage.morphology.erosion(ero_masked, footprint=footprint) + + # Reconstruction + rec = skimage.morphology.reconstruction(ero, pixels, footprint=footprint) + + # Image-level granularity + currentmean = numpy.mean(rec[mask]) + gs = (prevmean - currentmean) * 100 / startmean + + if verbose and scale == 1: + print(f"Scale 1 - gs: {gs:.4f}, currentmean: {currentmean:.6f}") + + # ---------------------------------------------------------- + # Per-object granularity: upsample rec to original shape, + # then compute per-label means using masked_labels. + # ---------------------------------------------------------- + if nobjects > 0: + if subsample_size < 1.0: + rec_full = _upsample_3d( + rec, + subsampled_shape=new_shape, + original_shape=original_shape, + ) + else: + rec_full = rec + + # Single-pass per-object mean via scipy.ndimage.mean + new_object_means = _fix_scipy_ndimage_result( + scipy.ndimage.mean(rec_full, masked_labels, label_range) + ) + + # Granular spectrum: (prev - new) * 100 / start, per object + gss = ( + (per_object_current_mean - new_object_means) + * 100 + / per_object_start_mean + ) + + per_object_current_mean = new_object_means + + # Record measurements for each object + for idx in range(len(label_range)): + object_measurements["object_id"].append(int(label_range[idx])) + object_measurements["feature"].append(scale) + object_measurements["value"].append(float(gss[idx])) + + if verbose: + n_total = len(object_measurements["object_id"]) + non_zero = sum(1 for v in object_measurements["value"] if v > 0) + print(f"Total measurements: {n_total}") + print(f"Non-zero measurements: {non_zero}") + if non_zero > 0: + vals = [v for v in object_measurements["value"] if v > 0] + print(f"Mean granularity: {numpy.mean(vals):.2f}") + + return object_measurements diff --git a/tests/featurization/test_granularity.py b/tests/featurization/test_granularity.py new file mode 100644 index 0000000..a1d944e --- /dev/null +++ b/tests/featurization/test_granularity.py @@ -0,0 +1,151 @@ +import re +from types import SimpleNamespace + +import numpy as np +import pytest +from _pytest.capture import CaptureFixture + +from zedprofiler.featurization.granularity import ( + _fix_scipy_ndimage_result, + _subsample_3d, + _upsample_3d, + compute_granularity, +) + +EXPECTED_THREE_SCALES = 3 +EXPECTED_TWO_SCALES = 2 + + +def _make_loader(image: np.ndarray, labels: np.ndarray) -> SimpleNamespace: + return SimpleNamespace(image=image, label_image=labels) + + +def test_fix_scipy_ndimage_result_handles_scalar_and_sequence() -> None: + scalar_result = _fix_scipy_ndimage_result(3.14) + sequence_result = _fix_scipy_ndimage_result([1.0, 2.0]) + + np.testing.assert_array_equal(scalar_result, np.array([3.14])) + np.testing.assert_array_equal(sequence_result, np.array([1.0, 2.0])) + + +def test_subsample_3d_returns_copy_when_factor_is_one() -> None: + data = np.arange(4 * 4 * 4, dtype=float).reshape(4, 4, 4) + out = _subsample_3d(data, np.array(data.shape, dtype=float), subsample_factor=1.0) + + np.testing.assert_array_equal(out, data) + assert out is not data + + +def test_subsample_3d_reduces_shape_for_fractional_factor() -> None: + data = np.arange(4 * 4 * 4, dtype=float).reshape(4, 4, 4) + new_shape = np.array(data.shape, dtype=float) * 0.5 + + out = _subsample_3d(data, new_shape, subsample_factor=0.5) + + assert out.shape == (2, 2, 2) + + +def test_upsample_3d_restores_requested_shape() -> None: + data = np.arange(2 * 2 * 2, dtype=float).reshape(2, 2, 2) + + out = _upsample_3d( + data=data, + subsampled_shape=np.array([2.0, 2.0, 2.0]), + original_shape=(4, 4, 4), + ) + + assert out.shape == (4, 4, 4) + + +@pytest.mark.parametrize( + ("kwargs", "error_text"), + [ + ({"subsample_size": 0.0}, "subsample_size must be in (0, 1]"), + ({"image_sample_size": 0.0}, "image_sample_size must be in (0, 1]"), + ({"radius": 0}, "radius must be positive"), + ({"granular_spectrum_length": 0}, "granular_spectrum_length must be positive"), + ], +) +def test_compute_granularity_validates_inputs( + kwargs: dict[str, float | int], + error_text: str, +) -> None: + image = np.ones((4, 4, 4), dtype=float) + labels = np.ones((4, 4, 4), dtype=int) + loader = _make_loader(image, labels) + + with pytest.raises(ValueError, match=re.escape(error_text)): + compute_granularity(loader, **kwargs) + + +def test_compute_granularity_returns_empty_measurements_when_no_objects() -> None: + image = np.ones((6, 6, 6), dtype=float) + labels = np.zeros((6, 6, 6), dtype=int) + loader = _make_loader(image, labels) + + result = compute_granularity( + loader, + radius=1, + granular_spectrum_length=2, + subsample_size=1.0, + image_sample_size=1.0, + verbose=False, + ) + + assert result == {"object_id": [], "feature": [], "value": []} + + +def test_compute_granularity_generates_measurements_for_objects() -> None: + image = np.zeros((7, 7, 7), dtype=float) + image[2:5, 2:5, 2:5] = 20.0 + labels = np.zeros((7, 7, 7), dtype=int) + labels[2:5, 2:5, 2:5] = 1 + loader = _make_loader(image, labels) + + result = compute_granularity( + loader, + radius=1, + granular_spectrum_length=3, + subsample_size=1.0, + image_sample_size=1.0, + verbose=False, + ) + + assert result["object_id"] == [1, 1, 1] + assert result["feature"] == [1, 2, 3] + # Expected per-scale granularity percentages for the single object + assert result["value"] == [100.0, 0.0, 0.0] + + +def test_compute_granularity_with_subsampling_mask_and_verbose( + capsys: CaptureFixture[str], +) -> None: + image = np.zeros((8, 8, 8), dtype=float) + image[2:6, 2:6, 2:6] = 30.0 + labels = np.zeros((8, 8, 8), dtype=int) + labels[2:6, 2:6, 2:6] = 1 + mask = np.ones((8, 8, 8), dtype=bool) + mask[0, :, :] = False + loader = _make_loader(image, labels) + + result = compute_granularity( + loader, + radius=1, + granular_spectrum_length=2, + subsample_size=0.75, + image_sample_size=0.5, + mask_threshold=0.5, + verbose=True, + image_mask=mask, + ) + + captured = capsys.readouterr() + + assert "Subsampled image" in captured.out + assert "Background removed via tophat filter." in captured.out + assert result["object_id"] == [1, 1] + assert result["feature"] == [1, 2] + # Expected per-scale granularity percentages (floating values) + assert result["value"] == pytest.approx( + [57.91469603714501, 42.085303962855], rel=1e-12 + ) diff --git a/uv.lock b/uv.lock index 64da325..5075075 100644 --- a/uv.lock +++ b/uv.lock @@ -693,32 +693,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] -[[package]] -name = "imagecodecs" -version = "2026.3.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/8d/dc18623e5e926ad53c626e128c8baaf4ec42e41029cf0a07381cfef79289/imagecodecs-2026.3.6.tar.gz", hash = "sha256:471b8a4d1b3843cbf7179b45f7d7261f0c0b28809efc1ca6c47822477b143b85", size = 9565259 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/db/873d063c99a726d772bf6f076288da59bb12e9f2af3518c2e4de5fde234d/imagecodecs-2026.3.6-cp311-abi3-macosx_10_15_x86_64.whl", hash = "sha256:44cfb3b609d941014f8ac7cf8611b15ccfd7119443bbb6b5e53916b242d31f9e", size = 13953250 }, - { url = "https://files.pythonhosted.org/packages/42/84/36c38a82f033ffbc9e706dad32be7148f130fc00e7bb417ab60e063897a0/imagecodecs-2026.3.6-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:e64037f22980a211b17bf6bdf03f14ff459a7432eec24f7a58c342f6992132fa", size = 11697496 }, - { url = "https://files.pythonhosted.org/packages/45/fa/f67c4e644fdf06503e120f9d1c8d8654b99066dea7093a674b67704fa4a4/imagecodecs-2026.3.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:30fa140bb1a112a889926af36977214ed52a22e4557356043259b5e2f79cfba5", size = 25604431 }, - { url = "https://files.pythonhosted.org/packages/8f/29/93ea9cbab7f57b4e60480c51fc51d8e138e399d11797c981d5f6e79f9832/imagecodecs-2026.3.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e30a14aa2e1c6c90e00375292726486c1d90bf003b1414d608ea4d1f62fd8a79", size = 26468592 }, - { url = "https://files.pythonhosted.org/packages/e8/a7/e3a89b2c516eaca7446e8f1335daeec90764b50888af5e073a2b6a987fcf/imagecodecs-2026.3.6-cp311-abi3-win32.whl", hash = "sha256:c972a45dfee1befbac048ba3492607003e9a185811e8febdc1ed531d48c07e75", size = 15597722 }, - { url = "https://files.pythonhosted.org/packages/22/c7/2b37a7fe9a2eb21011e50f046d62e68ac4e0f8d6ad94d7a10e9f8e8d685f/imagecodecs-2026.3.6-cp311-abi3-win_amd64.whl", hash = "sha256:e8fba5b9ac7be109ed35070208bc1683fa17cc381ed9535a4eae200c6d883bd8", size = 19177403 }, - { url = "https://files.pythonhosted.org/packages/c0/c7/94e930cef9e0a29a2df5e3ba3bacd2c2f1e34ca373fe48624b64af8ae91c/imagecodecs-2026.3.6-cp311-abi3-win_arm64.whl", hash = "sha256:fc4856913be6c8b3861223158920d934a0ae203149a435f585622dbbff8ed696", size = 15193605 }, - { url = "https://files.pythonhosted.org/packages/a3/0b/ba7ca5a14cf2ff744c47898ab9e98b6b96b364bce1459afcab5f4e5ea2bb/imagecodecs-2026.3.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ea2cd774854a3bfe1dc9508f98907edc877c02a78642ab371adbdec65a8865cc", size = 14335958 }, - { url = "https://files.pythonhosted.org/packages/18/d9/4750fb9739d474e399fa566b3a5c7033f2c9c0078bc869473b23d3e0d4b7/imagecodecs-2026.3.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6efa104ce600f61fef03a43daab34fe2953c8adb4e4ee6eb9544825f5361e5ab", size = 12021474 }, - { url = "https://files.pythonhosted.org/packages/29/d3/f0a71c797f836021241500d4a459b0f93317f2b5c9bcc39fb1eaa50e01ea/imagecodecs-2026.3.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:16fd83dd25d9c888317bc8cbbc5d7490b78c84c58275d80fb71ea7b28645276a", size = 29606531 }, - { url = "https://files.pythonhosted.org/packages/0c/8c/a1b433418306636fc22a94b669d7ebbbde8e3b26aef7d693c08f9f8a65af/imagecodecs-2026.3.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:39fcf671537cc92dd24ba4dcba257d8aa47e6f58018e839a5216c626f03d4126", size = 30039276 }, - { url = "https://files.pythonhosted.org/packages/ac/96/1474c0e242fb11d68304f858298971370fdfcecc404dbd9993a3ea449b4f/imagecodecs-2026.3.6-cp314-cp314t-win32.whl", hash = "sha256:65110ff30723a6e0b79c6c74ff52909a34b131366c4f76b1b68084cca5fcd982", size = 16345843 }, - { url = "https://files.pythonhosted.org/packages/d5/13/d0e09d8aaafe6fac1aa1bd8c0dcc0f024998ea4a23477128db14a085b4cf/imagecodecs-2026.3.6-cp314-cp314t-win_amd64.whl", hash = "sha256:1b810efeeb06bdcca07ae410a81bdb5eb9fb38f91689baf0f0b75783f5119a40", size = 20230890 }, - { url = "https://files.pythonhosted.org/packages/9c/06/3a5fe853e4df9a3655f8be81c1eb48013aab282c1557341b8634dd718e32/imagecodecs-2026.3.6-cp314-cp314t-win_arm64.whl", hash = "sha256:a2363c64e346cee4136f02a207b1b645729142f5260d122ddc3d5ff9e32ac6b1", size = 15911854 }, -] - [[package]] name = "imageio" version = "2.37.3" @@ -1125,23 +1099,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl", hash = "sha256:d8975035155d034bdfde5c0c37891425314b7ea8d3a6c4b5d18c294348714cd9", size = 170478 }, ] -[[package]] -name = "kerchunk" -version = "0.2.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fsspec" }, - { name = "numcodecs" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, - { name = "ujson" }, - { name = "zarr" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/a0/ebeca522912e68f360117404a6b2f740d4b0a343d98e9586377a2fd21567/kerchunk-0.2.10.tar.gz", hash = "sha256:aae63c0fe4ca2e97025f026578a0577545011fe6679751392c815e0d1d6bf954", size = 716526 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/e4/3c356a9ea448a48caa5e44cd51293f7e896cd606f2ef86da96f5d61cc427/kerchunk-0.2.10-py3-none-any.whl", hash = "sha256:7fdaa77dae25c75d3ec9402c49208f37ae51d346ab082724e3e32608438f8c66", size = 68490 }, -] - [[package]] name = "lazy-loader" version = "0.5" @@ -1154,77 +1111,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044 }, ] -[[package]] -name = "locket" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398 }, -] - -[[package]] -name = "lxml" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689 }, - { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892 }, - { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489 }, - { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162 }, - { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247 }, - { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042 }, - { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304 }, - { url = "https://files.pythonhosted.org/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578 }, - { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209 }, - { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365 }, - { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654 }, - { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326 }, - { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879 }, - { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048 }, - { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241 }, - { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938 }, - { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728 }, - { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372 }, - { url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713 }, - { url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874 }, - { url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535 }, - { url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881 }, - { url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305 }, - { url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522 }, - { url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310 }, - { url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799 }, - { url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693 }, - { url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708 }, - { url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737 }, - { url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817 }, - { url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753 }, - { url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071 }, - { url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319 }, - { url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139 }, - { url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195 }, - { url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870 }, - { url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548 }, - { url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866 }, - { url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476 }, - { url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719 }, - { url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890 }, - { url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008 }, - { url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451 }, - { url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135 }, - { url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126 }, - { url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579 }, - { url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206 }, - { url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906 }, - { url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553 }, - { url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458 }, - { url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861 }, - { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377 }, - { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701 }, - { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120 }, -] - [[package]] name = "markdown-it-py" version = "4.0.0" @@ -1806,21 +1692,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579 }, ] -[[package]] -name = "pint" -version = "0.25.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "flexcache" }, - { name = "flexparser" }, - { name = "platformdirs" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/52/9d/b1379cdbd33a49d17d627bc24e2b63cca06a1c5343b38072d2889499e82e/pint-0.25.3.tar.gz", hash = "sha256:f8f5df6cf65314d74da1ade1bf96f8e3e4d0c41b51577ac53c49e7d44ca5acee", size = 255106 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl", hash = "sha256:27eb25143bd5de9fcc4d5a4b484f16faf6b4615aa93ece6b3373a8c1a3c1b97d", size = 307488 }, -] - [[package]] name = "platformdirs" version = "4.3.8" @@ -2408,15 +2279,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165 }, ] -[[package]] -name = "semver" -version = "3.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912 }, -] - [[package]] name = "send2trash" version = "1.8.3" @@ -2651,13 +2513,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/9f/74f110b4271ded519c7add4341cbabc824de26817ff1c345b3109df9e99c/tifffile-2026.4.11-py3-none-any.whl", hash = "sha256:9b94ffeddb39e97601af646345e8808f885773de01b299e480ed6d3a41509ec9", size = 248227 }, ] -[package.optional-dependencies] -zarr = [ - { name = "fsspec" }, - { name = "kerchunk" }, - { name = "zarr" }, -] - [[package]] name = "tinycss2" version = "1.4.0" @@ -2698,6 +2553,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/a7/535c44c7bea4578e48281d83c615219f3ab19e6abc67625ef637c73987be/tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7", size = 443596 }, ] +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374 }, +] + [[package]] name = "traitlets" version = "5.14.3" @@ -2921,9 +2788,12 @@ dependencies = [ { name = "bioio-tifffile" }, { name = "fire" }, { name = "jinja2" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, { name = "pandas" }, - { name = "pandera" }, { name = "scikit-image" }, + { name = "scipy" }, + { name = "tqdm" }, ] [package.dev-dependencies] @@ -2953,9 +2823,11 @@ requires-dist = [ { name = "bioio-tifffile", specifier = ">=1.3.0" }, { name = "fire", specifier = ">=0.7.1" }, { name = "jinja2", specifier = ">=3.1.6" }, + { name = "numpy", specifier = ">=2.2.6" }, { name = "pandas", specifier = ">=3.0.2" }, - { name = "pandera", specifier = ">=0.31.1" }, { name = "scikit-image", specifier = ">=0.26.0" }, + { name = "scipy", specifier = ">=1.17.1" }, + { name = "tqdm", specifier = ">=4.67.3" }, ] [package.metadata.requires-dev]