diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5e7f95..a253379b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,43 +2,116 @@ ## [Unreleased] +### API Breaking Changes + +Every API deprecated in `v0.5.0` (each warned "will be removed in `ngio=0.6`") is now removed — that release became `1.0.0`. + +**Renamed members** + +| Removed | Use instead | +| --- | --- | +| `OmeZarrContainer.image_meta` | `.meta` | +| `.levels_paths` on `OmeZarrContainer`, `ImagesContainer` | `.level_paths` | +| `.set_channel_percentiles(a, b)` on both | `.set_channel_windows_with_percentiles(percentiles=(a, b))` | + +**Renamed / removed arguments** + +| Removed argument | On | Use instead | +| --- | --- | --- | +| `version=` | `create_empty_plate`, `create_empty_well`, `derive_ome_zarr_plate`, `OmeZarrPlate.derive_plate` | `ngff_version=` | +| `check_type=` | `OmeZarrContainer.get_table`, `OmeZarrPlate.get_table` | `get_table_as(name, TableCls)` or `get_*_table(name)` | +| `labels=`, `pixel_size=` | all derive entry points | `channels_meta=`, `pixelsize=` | +| `xy_pixelsize=` | `create_empty_ome_zarr`, `create_ome_zarr_from_array` | `pixelsize=` | +| `xy_scaling_factor=`, `z_scaling_factor=` | same two | `scaling_factors=` | +| `channel_labels=`, `channel_wavelengths=`, `channel_colors=`, `channel_active=` | same two | `channels_meta=` | +| `labels=`, `wavelength_id=`, `start=`, `end=`, `percentiles=`, `colors=`, `active=`, `**omero_kwargs` | `set_channel_meta` on `OmeZarrContainer`, `ImagesContainer` | `channel_meta=ChannelsMeta(...)` | + +Derive entry points: `OmeZarrContainer.derive_image`/`derive_label`, `ImagesContainer.derive`, `LabelsContainer.derive`, `derive_image_container`, `derive_label`, `abstract_derive`. + +- **`pixelsize` is now required** on `create_empty_ome_zarr` and `create_ome_zarr_from_array`; omitting it raises `TypeError` instead of `NgioValueError: pixelsize must be provided.`. +- **Not affected**: the `pixel_size=` *lookup* argument on the getters (`get`, `get_image`, `get_label`, ...) stays. +- Internal: `_check_deprecated_scaling_factors` removed; `init_image_like` and `_compute_scaling_factors` lost their `yx_scaling_factor`/`z_scaling_factor` parameters. + +### Migration Guide (v0.5 → v1.0) + +```python +# Renamed arguments +create_empty_plate(store, name="plate", version="0.4") # before +create_empty_plate(store, name="plate", ngff_version="0.4") # after + +create_empty_ome_zarr(store, shape=(4, 64, 64), xy_pixelsize=0.5, z_scaling_factor=1.0) +create_empty_ome_zarr( + store, shape=(4, 64, 64), pixelsize=0.5, scaling_factors=(1.0, 2.0, 2.0) +) + +# Table type checking +table = ome_zarr.get_table("roi", check_type="roi_table") +table = ome_zarr.get_table_as("roi", RoiTable) # or ome_zarr.get_roi_table("roi") + +# Deriving +ome_zarr.derive_image(store, labels=["DAPI"], pixel_size=ps) +ome_zarr.derive_image(store, channels_meta=["DAPI"], pixelsize=(ps.y, ps.x)) + +# Iterators (the old path still works until 1.1, with a deprecation warning) +from ngio.experimental.iterators import SegmentationIterator +from ngio.iterators import SegmentationIterator # or: from ngio import ... + +# Channel metadata +ome_zarr.set_channel_meta(labels=["DAPI", "GFP"], wavelength_id=["A01", "A02"]) +ome_zarr.set_channel_meta( + channel_meta=ChannelsMeta.default_init( + labels=["DAPI", "GFP"], wavelength_id=["A01", "A02"] + ) +) + +create_empty_ome_zarr(store, shape=(2, 64, 64), axes_names=("c", "y", "x"), + pixelsize=0.5, channel_labels=["a", "b"]) +create_empty_ome_zarr(store, shape=(2, 64, 64), axes_names=("c", "y", "x"), + pixelsize=0.5, channels_meta=["a", "b"]) +``` + +`pixel_size=` only ever read `.y` and `.x`; `.z`/`.t` were ignored and z/time spacing came from the reference image. To override those too, pass `z_spacing=`/`time_spacing=` — new behaviour the old argument could not express. + +### Features +- The iterators graduated out of `ngio.experimental`: they now live in `ngio.iterators` and are re-exported from the top-level namespace, so `from ngio import SegmentationIterator` works. `ngio.experimental.iterators` still resolves the four classes but emits `NgioDeprecationWarning` on attribute access; it will be removed in `ngio=1.1`. +- Add a configurable IO retry policy: `NgioConfig.io_retry` (`RetryConfig`) with `max_retries` (default `0`), a backoff strategy (`ConstantBackoff`, `LinearBackoff`, `ExponentialBackoff`), and error matching via `retry_on` substrings or the discouraged blanket `retry_all_errors`. Ngio's own `NgioError`s are never retried. The public `ngio.utils.retry_io` decorator reads the global config at call time. +- Add `ngio.utils.NgioStore`, a picklable zarr `WrapperStore` that applies the `io_retry` policy to every store IO call and centralizes store-type dispatch (`store_type`, `full_url`, `sync_fs_and_path`, `get_mapper`, `local_root`, `memory_dict`, `list_dir_collected`, `from_any`/`ensure`). Every group ngio opens is now backed by it, so the policy covers metadata, pixel data, and lazy dask IO on workers. `ZipStore` is now explicitly supported (it previously warned). +- Apply `io_retry` to the IO paths that bypass the zarr store: the pyarrow backend's dataset load/write, the AnnData backend's direct local/fsspec writes, and the `fractal_fsspec_store` metadata probe (401s become `NgioValueError` inside the retried call, so they are never retried). + ### Fix -- Fix the deprecated `yx_scaling_factor` shim assembling scaling factors in `(z, x, y)` order instead of the canonical `(z, y, x)`: an asymmetric tuple like `yx_scaling_factor=(2, 4)` silently produced swapped per-level y/x scales. The shim also now trims leading factors for shapes with fewer than 3 axes (a 2D image previously raised a length-mismatch error). -- Fix `concatenate_image_tables` (and variants) index construction: the concatenated table's index name was never set due to an inverted `None` check, and `mode="lazy"` built the index from the extras columns only — producing duplicated index values — while `mode="eager"` also hashed the original table index. Both modes now produce the same unique per-row index. The async variant also forwards `mode` to the per-image prefetch, so lazy prefetching no longer eagerly loads dataframes. -- Fix `Roi.union`/`Roi.intersection` dropping ROI name `""` and label `0` (the joins used truthiness instead of `None` checks; label `0` is a legal label). The joined ROI's slices now also keep a deterministic axis order (the receiver's axes first, then axes only present in the other ROI) instead of an arbitrary set order. -- `Roi.from_values` now validates its inputs (it used `model_construct`, which skips pydantic validation entirely), so invalid values such as a negative `label` or fewer than 2 axes raise instead of being silently accepted. -- Fix `PixelSize.__eq__` raising `TypeError` when compared with a non-`PixelSize` object (it now returns `NotImplemented`, so `ps == "x"` is `False` and `ps in [...]` works) and make the `time_unit` comparison symmetric: previously `a == b` and `b == a` could disagree when only one side had a `time_unit`. **Behavior change**: pixel sizes with different `time_unit`s now always compare unequal. -- Fix `NgioWellMeta.add_image`/`remove_image` and `NgioPlateMeta.add_well`/`add_acquisition`/`remove_well` mutating the receiver in place: the "updated copy" they returned shared its lists with the original object, so the original was silently modified too. All five now leave the receiver untouched. -- Fix `AxesSetup.from_ordered_list` silently dropping a non-canonical axis name when a canonical name appeared to its left (e.g. `["z", "custom", "y", "x"]`): the canonical name reclaimed the slot already assigned to the custom axis. Canonical names now always claim their own slot and non-canonical names fill the remaining slots right-aligned. -- Fix `ngio.experimental.iterators` grid helpers: `grid()` gave every generated ROI the same name (they now get unique per-tile names, e.g. `t0_z0_y32_x64`, prefixed with `base_name` when given), and `by_chunks` with an overlap equal to or larger than the chunk size now raises a clear `NgioValueError` instead of crashing with `range() arg 3 must not be zero`. -- The global config singleton is now loaded lazily on first `get_config()` call instead of at import time, so setting `NGIO_CONFIG_PATH` after importing ngio (but before first use) is honored. -- Fix the v0.5 metadata decoder passing a non-string axis `unit` through unchanged: the stringification computed for non-string JSON values was discarded (`unit=v05_axis.unit` instead of the normalized value). -- Fix accessing labels on a read-only image without a `labels` group: `labels_container` (and everything routed through it) raised `NgioValueError: Cannot create a group in read only mode` instead of degrading gracefully like the tables path (`list_labels()` → `[]`, `labels_container` → `NgioValidationError`). -- Fix an empty ROI table with no backend being unusable: `RoiTable().rois()` / `.add(roi)` raised `NgioValueError` instead of treating the table as empty (the `set_table_data` path already handled this case). -- Fix duplicate ROI names not surviving a write/read roundtrip: the dedup in `RoiDictWrapper` renamed only the internal dict key, so the serialized table still contained duplicate index values. The renamed ROI now carries the deduplicated name. -- Fix `OmeZarrPlate.get_well` never returning the instance it cached (it stored one `OmeZarrWell` and returned a second, freshly built one; repeated calls now return the cached instance, matching `get_image`). -- A negative index inside a slicing sequence (e.g. `get_array(y=[-1, 0])`) now raises `NgioValueError` instead of a bare `AssertionError` (which would also vanish under `python -O`). +- `add_table` and `write_table` now preserve the input table's backend instead of rewriting with `anndata_v1` ([#207](https://github.com/BioVisionCenter/ngio/issues/207)); `backend` defaults to `None`, meaning "use the table's own". **Behavior change**: copying a `parquet`/`csv`/`json` table via `get_table` + `add_table` now keeps that backend. +- `concatenate_image_tables` (and variants) built a wrong index: the name was never set, `mode="lazy"` duplicated values and `mode="eager"` hashed the original index. Both now produce the same unique per-row index, and the async variant forwards `mode` so lazy prefetching stays lazy. +- `Roi.union`/`Roi.intersection` dropped ROI name `""` and label `0` (truthiness instead of `None` checks); joined slices now keep a deterministic axis order. +- `Roi.from_values` now validates its inputs — it used `model_construct`, which skips pydantic validation entirely. +- `PixelSize.__eq__` raised `TypeError` against non-`PixelSize` objects; it now returns `NotImplemented`. **Behavior change**: pixel sizes with different `time_unit`s always compare unequal. +- `NgioWellMeta.add_image`/`remove_image` and `NgioPlateMeta.add_well`/`add_acquisition`/`remove_well` mutated the receiver in place — the returned "copy" shared its lists with the original. +- `AxesSetup.from_ordered_list` silently dropped a non-canonical axis when a canonical name appeared to its left (e.g. `["z", "custom", "y", "x"]`). +- `ngio.iterators` grid helpers: `grid()` gave every ROI the same name (now unique per tile, e.g. `t0_z0_y32_x64`), and `by_chunks` with overlap ≥ chunk size raises `NgioValueError` instead of `range() arg 3 must not be zero`. +- The global config singleton is now loaded lazily, so `NGIO_CONFIG_PATH` set after importing ngio is honored. +- The v0.5 metadata decoder discarded the normalized value for non-string axis `unit`s. +- Accessing labels on a read-only image without a `labels` group raised `NgioValueError` instead of degrading gracefully (`list_labels()` → `[]`, `labels_container` → `NgioValidationError`). +- An empty `RoiTable` with no backend was unusable: `.rois()`/`.add(roi)` raised instead of treating the table as empty. +- Duplicate ROI names did not survive a write/read roundtrip — the dedup renamed only the internal dict key. +- `OmeZarrPlate.get_well` never returned the instance it cached; repeated calls now return the cached one, matching `get_image`. +- A negative index inside a slicing sequence (e.g. `get_array(y=[-1, 0])`) raised a bare `AssertionError` (which vanishes under `python -O`); it now raises `NgioValueError`. +- Fix the broken run link in the scheduled-CI failure issue: `{{ repo }}` is a context object, so the URL rendered as `[object Object]`. Now uses `{{ repo.owner }}/{{ repo.repo }}`. ### Tests -- Enable `test_derive_from_legacy_images` (`tests/unit/images/test_create.py`), which was never collected due to a missing `test_` prefix; it now runs for both NGFF versions with the level pairing and channel-axis handling corrected. Rename the misnamed `test_fail_derive_singleton` to `test_pyramid_clamps_singleton_dimensions`. -- Deduplicate the 18-item test-image parametrization (six verbatim copies) into a shared `zarr_name` fixture in `tests/conftest.py`. -- Register a `network` pytest marker and apply it to the two tests hitting raw.githubusercontent.com, so `-m "not network"` runs offline. The Zenodo dataset downloads moved from conftest import time into session fixtures — test collection no longer blocks on the network. -- Remove `--cov` from the default pytest `addopts` (local runs no longer pay ~40% coverage overhead); CI passes `--cov=ngio --cov-report=xml` explicitly on the codecov leg and `ci_upstream.yml` drops the now-unneeded `--no-cov`. -- Merge the four per-backend round-trip tests in `test_backends.py` into one parametrized test; drop the redundant middle-layer copy of the #207 backend-preservation test (still covered at the `write_table` and container layers). -- Anchor test-data paths to `__file__` instead of the current working directory (`tests/conftest.py`, `tests/create_test_data.py`, `test_unit_v04_utils.py`), so pytest can run from any directory. Reduce `test_multiprocessing_safety` from 1000 to 100 tasks and delete an unreferenced meta JSON fixture. -- Raise overall coverage from 91% to 95%: new unit tests for the v0.5 metadata codec (`test_unit_v05_utils.py`, mirroring the v0.4 file), container/image error paths and deprecated shims, ROI-table and plate edge cases, `NgioCache`, the `FeatureExtractorIterator` dask path, and io_pipes error branches. `hcs/_plate.py`, `images/_image.py`, `utils/_cache.py`, `experimental/iterators/_feature.py`, `io_pipes/_match_shape.py`, `_zoom_transform.py`, and `_ops_slices.py` are now at 100%. -- Speed up fixtures: the `moto_s3_server` fixture is now session-scoped (tests isolate via `random_zarr_path()`, and starting the server cost ~4s per S3 test); read-only consumers of `images_all_versions` and the Zenodo cardiomyocyte datasets now share session-scoped copies (`images_all_versions_readonly`, `cardiomyocyte_tiny_path_readonly`, `cardiomyocyte_small_mip_path_readonly`) instead of re-copying up to 126 MB per test, while mutating tests keep fresh per-test copies; `test_real_mask` copies only the single image it writes to; `test_table_ops.py` builds its two sample containers once per module. +- Raise coverage from 91% to 95%: new unit tests for the v0.5 metadata codec, container/image error paths, ROI-table and plate edge cases, `NgioCache`, the `FeatureExtractorIterator` dask path, and io_pipes error branches. +- Enable `test_derive_from_legacy_images`, never collected due to a missing `test_` prefix; rename `test_fail_derive_singleton` → `test_pyramid_clamps_singleton_dimensions`. +- Register a `network` marker so `-m "not network"` runs offline, and move the Zenodo downloads from conftest import time into session fixtures — collection no longer blocks on the network. +- Speed up fixtures: `moto_s3_server` is session-scoped, and read-only consumers of the test images and Zenodo datasets share session-scoped copies instead of re-copying up to 126 MB per test. +- Drop `--cov` from the default `addopts` (~40% local overhead); CI passes it explicitly on the codecov leg. +- Deduplicate the 18-item test-image parametrization into a shared `zarr_name` fixture; merge the four per-backend round-trip tests into one parametrized test. +- Anchor test-data paths to `__file__` so pytest can run from any directory; reduce `test_multiprocessing_safety` from 1000 to 100 tasks. ### Chores -- Speed up CI: run the test suite with `pytest-xdist` (`-n 4`, new `test` extra dependency); collect coverage and upload to codecov on the ubuntu/`test11` leg only (one report per run, unchanged content); add `--durations=10` to CI runs for runtime observability. The Zenodo download in `tests/conftest.py` is now guarded by a `FileLock` (xdist workers on a cold cache) and no longer re-extracts an already-unzipped dataset (`re_unzip=False`). -- Shrink the store-matrix test payload (`tests/stores/utils.py`) from a `(3, 5, 64, 64)`/3-level image to `(3, 2, 32, 32)`/2 levels — same monitored behaviors (multi-channel z-stack pyramid, label, all four table backends, derive across the 4×4 store matrix), far fewer object round-trips through the mocked S3/HTTP stores (~75s → considerably less on CI). -- Slim the iterator tests: the two mutating tests (`test_segmentation_iterator`, `test_masked_segmentation_iterator`) now copy only the single image they write to (new `single_image_copy` fixture, replacing per-test copies of all 18 test images; the unused `images_all_versions` fixture is removed) and run the full 9-axes matrix on v0.5 plus a v0.4 smoke subset (`yx`, `tczyx`) instead of both versions × 9 — the iterators are version-agnostic and the v0.4 write path stays monitored by the creation/store tests. The two read-only iterator tests keep the full 18-image matrix. -- Fix the CI data-cache `restore-keys` fallback never matching: the fallback key was written inside a YAML block scalar with double quotes (`"Linux-data-"`), which are literal there, so a cache-key rotation caused every PR job to re-download the ~160 MB Zenodo datasets during the test step (~80s/job). The cache key now also hashes `src/ngio/utils/_datasets.py` (the dataset registry) instead of `tests/conftest.py`, so test-suite refactors no longer rotate the key (`ci.yml`, `ci_upstream.yml`). - -### Feature -- Add a configurable IO retry policy: `NgioConfig.io_retry` (`RetryConfig`) with `max_retries` (default `0`, never retry), a backoff strategy (`ConstantBackoff`, `LinearBackoff`, or `ExponentialBackoff`, each with `delay_s`, `max_delay_s`, `jitter`), and error matching via `retry_on` substrings (matched against `"ExceptionName: message"`) or the discouraged blanket `retry_all_errors` flag, which emits an `NgioUserWarning` and is mutually exclusive with `retry_on`. Ngio's own `NgioError`s are never retried. Retry helpers live in `ngio.utils._retry`; the public `ngio.utils.retry_io` decorator reads the global config at call time. -- Add `ngio.utils.NgioStore`, a picklable zarr `WrapperStore` that applies the `io_retry` policy to every store IO call (the policy snapshot travels with the store into dask task graphs) and centralizes store-type dispatch behind uniform services (`store_type`, `full_url`, `sync_fs_and_path`, `get_mapper`, `local_root`, `memory_dict`, `list_dir_collected`, `NgioStore.from_any`/`ensure`). Every group ngio opens (`open_group_wrapper` / `ZarrGroupHandler`) is now backed by an `NgioStore`, so the `io_retry` policy applies to all zarr IO — metadata, pixel data, and lazy dask reads/writes executed on workers (the policy snapshot pickles with the store into the task graph). User-provided `zarr.Group`s are transparently reopened on a wrapped store; `ZipStore` is now an explicitly supported store type (it previously warned). -- Apply the `io_retry` policy to the IO paths that bypass the zarr store: the pyarrow table backend's dataset load/write, the AnnData backend's direct local/fsspec writes, and the `fractal_fsspec_store` metadata probe (where 401 auth failures are translated to `NgioValueError` inside the retried call, so they are never retried — even in blanket mode). +- Speed up CI: run with `pytest-xdist` (`-n 4`), collect coverage on the ubuntu/`test11` leg only, add `--durations=10`. The Zenodo download is guarded by a `FileLock` and no longer re-extracts an already-unzipped dataset. +- Fix the CI data-cache `restore-keys` fallback never matching (double quotes are literal inside a YAML block scalar), which made every PR job re-download ~160 MB. The key now hashes the dataset registry instead of `tests/conftest.py`. +- Centralize concrete-store dispatch behind `NgioStore` services, so the group handler, `copy_group`, `is_group_listable`, and the table backends no longer isinstance-check store types or reach into internals. Note: AnnData fsspec writes now use a synchronous clone of the store's filesystem. +- Shrink the store-matrix test payload from `(3, 5, 64, 64)`/3 levels to `(3, 2, 32, 32)`/2 levels — same monitored behaviors, far fewer round-trips through the mocked S3/HTTP stores. +- Slim the iterator tests: the two mutating tests copy only the image they write to, and run the full 9-axes matrix on v0.5 plus a v0.4 smoke subset instead of both versions × 9. +- `NgioDeprecationWarning` is kept in `ngio.utils`, but `import warnings` and its import are gone from `_plate.py`, `_ome_zarr_container.py`, `_image.py`, `_abstract_image.py`, and `_create_utils.py`. The private `_set_channel_meta`/`_set_channel_meta_legacy` pair collapsed into the public `set_channel_meta`. ### Documentation - Rebuild the docs on [Zensical](https://zensical.org) instead of MkDocs + Material, and move every executed code block out of the Markdown into standalone scripts under `docs/snippets/`, included via `pymdownx.snippets` and run at build time. The five tutorial notebooks become Markdown pages, and CI builds with `--strict`. @@ -47,6 +120,7 @@ - Correct what the docs claim against what ngio actually does, most substantially on the table specification pages. - Add `CODE_OF_CONDUCT.md` and `CITATION.cff`, and move `CONTRIBUTING.md` to the repository root, single-sourced into the docs so GitHub's community widgets pick them up. - Add a "Configuration" getting-started page documenting the config file location (`~/.ngio/ngio_config.json` / `NGIO_CONFIG_PATH`), the `io_retry` policy (fields, backoff strategies, marker matching, snapshot-at-open vs read-at-call semantics), and its relationship to the lower-level `s3fs.custom_retry_markers` mechanism. `NgioConfig`, `RetryConfig`, and `get_config` are now listed in the top-level API reference. +- Point the iterator pages, the three iterator tutorials and the Getting Started iterators page at `ngio.iterators`, and drop the "Experimental API" warnings now that the iterators are part of the stable API. ### Fix - `add_table` (`OmeZarrContainer`, `OmeZarrPlate`, `TablesContainer.add`) and `write_table` now preserve the input table's backend instead of always rewriting with the default `anndata_v1` backend ([#207](https://github.com/BioVisionCenter/ngio/issues/207)). The `backend` parameter now defaults to `None`, meaning "use the table's own backend" (`meta.backend`, which is `anndata_v1` for tables created in memory); pass a backend name explicitly to convert. Supporting changes: `Table.backend_name` now falls back to `meta.backend` instead of returning `None` for in-memory tables, `set_backend(backend=...)` can declare a backend preference (with early name validation and alias normalization) on a table not yet attached to a store, and passing `backend=None` no longer raises. **Behavior change**: copying a table stored with a non-default backend (e.g. `parquet`/`csv`/`json`) via `get_table` + `add_table` now keeps that backend on the destination. diff --git a/docs/api/iterators.md b/docs/api/iterators.md index 8348c91c..cca93251 100644 --- a/docs/api/iterators.md +++ b/docs/api/iterators.md @@ -4,23 +4,18 @@ description: API reference for the ngio processing iterators. # Iterators API reference -!!! warning "Experimental API" - - These classes live in `ngio.experimental.iterators` and may change or be removed in a - future release without notice. - ## ImageProcessingIterator -::: ngio.experimental.iterators.ImageProcessingIterator +::: ngio.iterators.ImageProcessingIterator ## SegmentationIterator -::: ngio.experimental.iterators.SegmentationIterator +::: ngio.iterators.SegmentationIterator ## MaskedSegmentationIterator -::: ngio.experimental.iterators.MaskedSegmentationIterator +::: ngio.iterators.MaskedSegmentationIterator ## FeatureExtractorIterator -::: ngio.experimental.iterators.FeatureExtractorIterator +::: ngio.iterators.FeatureExtractorIterator diff --git a/docs/api/ngio/iterators.md b/docs/api/ngio/iterators.md index a1fec52d..5f3badb8 100644 --- a/docs/api/ngio/iterators.md +++ b/docs/api/ngio/iterators.md @@ -1,3 +1,3 @@ -# ngio.experimental.iterators API reference +# ngio.iterators API reference -::: ngio.experimental.iterators +::: ngio.iterators diff --git a/docs/getting_started/6_iterators.md b/docs/getting_started/6_iterators.md index 2a581f41..8c086db5 100644 --- a/docs/getting_started/6_iterators.md +++ b/docs/getting_started/6_iterators.md @@ -8,13 +8,6 @@ description: The four ngio iterators for building scalable image-processing pipe When building image processing pipelines it is often useful to iterate over specific regions of the image, for example to process the image in smaller tiles or to process only specific regions of interest (ROIs). Iterators also let you set broadcasting rules for the iteration, for example to iterate over all z-planes or over all timepoints. -!!! warning "Experimental API" - - The iterators live in `ngio.experimental.iterators`, outside the stability guarantee - that covers the rest of ngio: they may change or be removed in a future release - without notice. Everything on this page works today, but pin your ngio version if you - depend on it. -
@@ -100,8 +93,8 @@ When building image processing pipelines it is often useful to iterate over spec
-ngio provides four basic `Iterator` classes, all imported from -`ngio.experimental.iterators`: +ngio provides four basic `Iterator` classes, all imported from `ngio.iterators` (or from +the top-level `ngio` namespace):
diff --git a/docs/llms.txt b/docs/llms.txt index cdc4e207..990b8dcc 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -48,7 +48,7 @@ guides and tutorials are executed when the site is built, so the outputs shown a - [Images](https://biovisioncenter.github.io/ngio/stable/api/images/): Image and Label objects and their accessors. - [HCS](https://biovisioncenter.github.io/ngio/stable/api/hcs/): open_ome_zarr_plate, OmeZarrPlate, open_ome_zarr_well, OmeZarrWell, create_empty_plate, create_empty_well. - [Tables](https://biovisioncenter.github.io/ngio/stable/api/tables/): Table classes, the tables container and backends. -- [Iterators](https://biovisioncenter.github.io/ngio/stable/api/iterators/): ImageProcessingIterator, SegmentationIterator, MaskedSegmentationIterator, FeatureExtractorIterator — experimental, from ngio.experimental.iterators. +- [Iterators](https://biovisioncenter.github.io/ngio/stable/api/iterators/): ImageProcessingIterator, SegmentationIterator, MaskedSegmentationIterator, FeatureExtractorIterator — from ngio.iterators. - [ngio top-level API](https://biovisioncenter.github.io/ngio/stable/api/ngio/ngio/): Roi, PixelSize, Dimensions, NgioConfig and other core types. ## Optional diff --git a/docs/snippets/getting_started/iterators.py b/docs/snippets/getting_started/iterators.py index 4b5a9d66..aec2dd2f 100644 --- a/docs/snippets/getting_started/iterators.py +++ b/docs/snippets/getting_started/iterators.py @@ -11,7 +11,7 @@ from pathlib import Path from ngio import open_ome_zarr_container -from ngio.experimental.iterators import ImageProcessingIterator +from ngio.iterators import ImageProcessingIterator from ngio.utils import download_ome_zarr_dataset download_dir = Path("./data").absolute() diff --git a/docs/snippets/tutorials/feature_extraction.py b/docs/snippets/tutorials/feature_extraction.py index c717ffb5..638cb508 100644 --- a/docs/snippets/tutorials/feature_extraction.py +++ b/docs/snippets/tutorials/feature_extraction.py @@ -82,7 +82,7 @@ def extract_features(image: np.ndarray, label: np.ndarray) -> pd.DataFrame: # --8<-- [end:setup_transform] # --8<-- [start:extract] -from ngio.experimental.iterators import FeatureExtractorIterator +from ngio.iterators import FeatureExtractorIterator from ngio.tables import FeatureTable iterator = FeatureExtractorIterator( diff --git a/docs/snippets/tutorials/image_processing.py b/docs/snippets/tutorials/image_processing.py index 76a44c45..94b6db52 100644 --- a/docs/snippets/tutorials/image_processing.py +++ b/docs/snippets/tutorials/image_processing.py @@ -121,7 +121,7 @@ def dask_gaussian_blur(image: da.Array, sigma: float) -> da.Array: # --8<-- [end:dask_blur] # --8<-- [start:iterators] -from ngio.experimental.iterators import ImageProcessingIterator +from ngio.iterators import ImageProcessingIterator iterator = ImageProcessingIterator( input_image=image, diff --git a/docs/snippets/tutorials/image_segmentation.py b/docs/snippets/tutorials/image_segmentation.py index 3eea6cbd..0fb65ab0 100644 --- a/docs/snippets/tutorials/image_segmentation.py +++ b/docs/snippets/tutorials/image_segmentation.py @@ -55,7 +55,7 @@ def otsu_threshold_segmentation(image: np.ndarray, max_label: int) -> np.ndarray # --8<-- [end:open_container] # --8<-- [start:segment] -from ngio.experimental.iterators import SegmentationIterator +from ngio.iterators import SegmentationIterator # Take the image to read from, and the FOV table naming the regions to walk image = ome_zarr.get_image() @@ -140,7 +140,7 @@ def otsu_threshold_segmentation(image: np.ndarray, max_label: int) -> np.ndarray # --8<-- [end:plot_mask] # --8<-- [start:masked_segment] -from ngio.experimental.iterators import MaskedSegmentationIterator +from ngio.iterators import MaskedSegmentationIterator # Take a masked image, which carries its masking ROI table with it image = ome_zarr.get_masked_image(masking_label_name="mask") diff --git a/mkdocs.yml b/mkdocs.yml index 0c3ec10a..929bee88 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -217,7 +217,7 @@ nav: - "ngio": api/ngio/ngio.md - "ngio.common": api/ngio/common.md - "ngio.io_pipes": api/ngio/io_pipes.md - - "ngio.experimental.iterators": api/ngio/iterators.md + - "ngio.iterators": api/ngio/iterators.md - "ngio.images": api/ngio/images.md - "ngio.tables": api/ngio/tables.md - "ngio.hcs": api/ngio/hcs.md diff --git a/src/ngio/__init__.py b/src/ngio/__init__.py index 96a8520d..c6ce6fd4 100644 --- a/src/ngio/__init__.py +++ b/src/ngio/__init__.py @@ -31,6 +31,12 @@ open_label, open_ome_zarr_container, ) +from ngio.iterators import ( + FeatureExtractorIterator, + ImageProcessingIterator, + MaskedSegmentationIterator, + SegmentationIterator, +) from ngio.ome_zarr_meta.ngio_specs import ( AxesSetup, DefaultNgffVersion, @@ -45,9 +51,12 @@ "ChannelSelectionModel", "DefaultNgffVersion", "Dimensions", + "FeatureExtractorIterator", "Image", "ImageInWellPath", + "ImageProcessingIterator", "Label", + "MaskedSegmentationIterator", "NgffVersions", "NgioConfig", "NgioSupportedStore", @@ -58,6 +67,7 @@ "RetryConfig", "Roi", "RoiSlice", + "SegmentationIterator", "StoreOrGroup", "create_empty_ome_zarr", "create_empty_plate", diff --git a/src/ngio/experimental/__init__.py b/src/ngio/experimental/__init__.py index 81aa4cae..a2ccb52a 100644 --- a/src/ngio/experimental/__init__.py +++ b/src/ngio/experimental/__init__.py @@ -1,5 +1,5 @@ -"""This module provides experimental features. +"""Deprecated namespace for features that have since graduated. -Use with caution as these features may change or be removed in future releases -without notice. +`ngio.experimental.iterators` is a shim for `ngio.iterators` and will be +removed in ngio 1.1. """ diff --git a/src/ngio/experimental/iterators/__init__.py b/src/ngio/experimental/iterators/__init__.py index 9c614c40..29fb5a3f 100644 --- a/src/ngio/experimental/iterators/__init__.py +++ b/src/ngio/experimental/iterators/__init__.py @@ -1,11 +1,21 @@ -"""This file is part of NGIO, a library for working with OME-Zarr data.""" +"""Deprecated alias for `ngio.iterators`. -from ngio.experimental.iterators._feature import FeatureExtractorIterator -from ngio.experimental.iterators._image_processing import ImageProcessingIterator -from ngio.experimental.iterators._segmentation import ( - MaskedSegmentationIterator, - SegmentationIterator, -) +The iterators are no longer experimental. Import them from `ngio.iterators` +(or from `ngio` directly). This module will be removed in ngio 1.1. +""" + +import warnings +from typing import TYPE_CHECKING, Any + +from ngio.utils import NgioDeprecationWarning + +if TYPE_CHECKING: + from ngio.iterators import ( + FeatureExtractorIterator, + ImageProcessingIterator, + MaskedSegmentationIterator, + SegmentationIterator, + ) __all__ = [ "FeatureExtractorIterator", @@ -13,3 +23,19 @@ "MaskedSegmentationIterator", "SegmentationIterator", ] + + +def __getattr__(name: str) -> Any: + """Forward attribute access to `ngio.iterators` with a deprecation warning.""" + if name not in __all__: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + warnings.warn( + f"'ngio.experimental.iterators.{name}' is deprecated and will be removed " + f"in ngio=1.1. Please use 'ngio.iterators.{name}' instead.", + NgioDeprecationWarning, + stacklevel=2, + ) + import ngio.iterators + + return getattr(ngio.iterators, name) diff --git a/src/ngio/hcs/_plate.py b/src/ngio/hcs/_plate.py index b1c755d8..9c00168f 100644 --- a/src/ngio/hcs/_plate.py +++ b/src/ngio/hcs/_plate.py @@ -2,7 +2,6 @@ import asyncio import logging -import warnings from collections.abc import Sequence from typing import Literal @@ -42,7 +41,6 @@ from ngio.utils import ( AccessModeLiteral, NgioCache, - NgioDeprecationWarning, NgioError, NgioValueError, StoreOrGroup, @@ -783,7 +781,6 @@ def derive_plate( self, store: StoreOrGroup, plate_name: str | None = None, - version: NgffVersions | None = None, ngff_version: NgffVersions | None = None, keep_acquisitions: bool = False, cache: bool = False, @@ -794,7 +791,6 @@ def derive_plate( Args: store (StoreOrGroup): The Zarr store or group that stores the plate. plate_name (str | None): The name of the new plate. - version (NgffVersion | None): Deprecated. Please use 'ngff_version' instead. ngff_version (NgffVersion): The NGFF version to use for the new plate. keep_acquisitions (bool): Whether to keep the acquisitions in the new plate. cache (bool): Whether to use a cache for the zarr group metadata. @@ -805,7 +801,6 @@ def derive_plate( store=store, plate_name=plate_name, ngff_version=ngff_version, - version=version, keep_acquisitions=keep_acquisitions, cache=cache, overwrite=overwrite, @@ -911,24 +906,12 @@ def get_condition_table(self, name: str) -> ConditionTable: ) return table - def get_table(self, name: str, check_type: TypedTable | None = None) -> Table: + def get_table(self, name: str) -> Table: """Get a table from the image. Args: name (str): The name of the table. - check_type (TypedTable | None): Deprecated. Please use - 'get_table_as' instead, or one of the type specific - get_*table() methods. - """ - if check_type is not None: - warnings.warn( - "The 'check_type' argument is deprecated and will be removed in " - "ngio=0.6. Please use 'get_table_as' instead or one of the " - "type specific get_*table() methods.", - NgioDeprecationWarning, - stacklevel=2, - ) return self.tables_container.get(name=name, strict=False) def get_table_as( @@ -1212,7 +1195,6 @@ def create_empty_plate( store: StoreOrGroup, name: str, images: list[ImageInWellPath] | None = None, - version: NgffVersions | None = None, ngff_version: NgffVersions = DefaultNgffVersion, cache: bool = False, overwrite: bool = False, @@ -1224,19 +1206,10 @@ def create_empty_plate( name (str): The name of the plate. images (list[ImageInWellPath] | None): A list of images to add to the plate. If None, no images are added. Defaults to None. - version (NgffVersion | None): Deprecated. Please use 'ngff_version' instead. ngff_version (NgffVersion): The NGFF version to use for the new plate. cache (bool): Whether to use a cache for the zarr group metadata. overwrite (bool): Whether to overwrite the existing plate. """ - if version is not None: - warnings.warn( - "The 'version' argument is deprecated and will be removed in ngio=0.6. " - "Please use 'ngff_version' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - ngff_version = version plate_meta = NgioPlateMeta.default_init( name=name, ngff_version=ngff_version, @@ -1268,7 +1241,6 @@ def derive_ome_zarr_plate( ome_zarr_plate: OmeZarrPlate, store: StoreOrGroup, plate_name: str | None = None, - version: NgffVersions | None = None, ngff_version: NgffVersions | None = None, keep_acquisitions: bool = False, cache: bool = False, @@ -1280,21 +1252,11 @@ def derive_ome_zarr_plate( ome_zarr_plate (OmeZarrPlate): The existing OME-Zarr plate. store (StoreOrGroup): The Zarr store or group that stores the plate. plate_name (str | None): The name of the new plate. - version (NgffVersion | None): Deprecated. Please use 'ngff_version' instead. ngff_version (NgffVersion): The NGFF version to use for the new plate. keep_acquisitions (bool): Whether to keep the acquisitions in the new plate. cache (bool): Whether to use a cache for the zarr group metadata. overwrite (bool): Whether to overwrite the existing plate. """ - if version is not None: - warnings.warn( - "The 'version' argument is deprecated and will be removed in ngio=0.6. " - "Please use 'ngff_version' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - ngff_version = version - if ngff_version is None: ngff_version = ome_zarr_plate.meta.plate.version or DefaultNgffVersion @@ -1340,7 +1302,6 @@ def open_ome_zarr_well( def create_empty_well( store: StoreOrGroup, - version: NgffVersions | None = None, ngff_version: NgffVersions = DefaultNgffVersion, cache: bool = False, overwrite: bool = False, @@ -1349,19 +1310,10 @@ def create_empty_well( Args: store (StoreOrGroup): The Zarr store or group that stores the well. - version (NgffVersion | None): Deprecated. Please use 'ngff_version' instead. ngff_version (NgffVersion): The version of the new well. cache (bool): Whether to use a cache for the zarr group metadata. overwrite (bool): Whether to overwrite the existing well. """ - if version is not None: - warnings.warn( - "The 'version' argument is deprecated and will be removed in ngio=0.6. " - "Please use 'ngff_version' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - ngff_version = version group_handler = ZarrGroupHandler( store=store, cache=True, mode="w" if overwrite else "w-" ) diff --git a/src/ngio/images/_abstract_image.py b/src/ngio/images/_abstract_image.py index e14a7764..bb4ff8ee 100644 --- a/src/ngio/images/_abstract_image.py +++ b/src/ngio/images/_abstract_image.py @@ -1,7 +1,6 @@ """Generic class to handle Image-like data in a OME-NGFF file.""" import logging -import warnings from abc import ABC, abstractmethod from collections.abc import Mapping, Sequence from typing import Any, Literal @@ -62,7 +61,6 @@ ) from ngio.tables import RoiTable from ngio.utils import ( - NgioDeprecationWarning, NgioFileExistsError, NgioValueError, StoreOrGroup, @@ -966,9 +964,6 @@ def abstract_derive( dimension_separator: Literal[".", "/"] | None = None, compressors: CompressorLike | None = None, extra_array_kwargs: Mapping[str, Any] | None = None, - # Deprecated arguments - labels: Sequence[str] | None = None, - pixel_size: PixelSize | None = None, ) -> tuple[ZarrGroupHandler, AxesSetup]: """Create an empty OME-Zarr image from an existing image. @@ -1004,35 +999,11 @@ def abstract_derive( compressors (CompressorLike | None): The compressors to use. extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to the zarr array creation. - labels (Sequence[str] | None): The labels of the new image. - This argument is DEPRECATED please use channels_meta instead. - pixel_size (PixelSize | None): The pixel size of the new image. - This argument is DEPRECATED please use pixelsize, z_spacing, - and time_spacing instead. Returns: ImagesContainer: The new derived image. """ - # TODO: remove in ngio 0.6 - if labels is not None: - warnings.warn( - "The 'labels' argument is deprecated and will be removed in " - "ngio=0.6. Please use 'channels_meta' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - channels_meta = list(labels) - if pixel_size is not None: - warnings.warn( - "The 'pixel_size' argument is deprecated and will be removed in " - "ngio=0.6. Please use 'pixelsize', 'z_spacing', and 'time_spacing'" - "instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - pixelsize = (pixel_size.y, pixel_size.x) - # End of deprecated arguments handling ref_meta = ref_image.meta shape = _normalize_shape_for_channel_policy( diff --git a/src/ngio/images/_create_utils.py b/src/ngio/images/_create_utils.py index 08fc9bbc..c7ed96af 100644 --- a/src/ngio/images/_create_utils.py +++ b/src/ngio/images/_create_utils.py @@ -1,7 +1,6 @@ """Utility functions for working with OME-Zarr images.""" import logging -import warnings from collections.abc import Mapping, Sequence from typing import Any, Literal, TypeVar @@ -30,7 +29,6 @@ ) from ngio.ome_zarr_meta.ngio_specs._axes import AxesSetup from ngio.utils import ( - NgioDeprecationWarning, NgioValueError, StoreOrGroup, ZarrGroupHandler, @@ -56,64 +54,13 @@ def _align_to_axes( return tuple(aligned_values) -def _check_deprecated_scaling_factors( - *, - yx_scaling_factor: float | tuple[float, float] | None = None, - z_scaling_factor: float | None = None, - scaling_factors: Sequence[float] | Literal["auto"] = "auto", - shape: tuple[int, ...], -) -> Sequence[float] | Literal["auto"]: - if yx_scaling_factor is not None or z_scaling_factor is not None: - warnings.warn( - "The 'yx_scaling_factor' and 'z_scaling_factor' arguments are deprecated " - "and will be removed in ngio=0.6. Please use the 'scaling_factors' " - "argument instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - if scaling_factors != "auto": - raise NgioValueError( - "Cannot use both 'scaling_factors' and deprecated " - "'yx_scaling_factor'/'z_scaling_factor' arguments." - ) - if isinstance(yx_scaling_factor, tuple): - if len(yx_scaling_factor) != 2: - raise NgioValueError( - "yx_scaling_factor tuple must have length 2 for y and x scaling." - ) - y_scale = yx_scaling_factor[0] - x_scale = yx_scaling_factor[1] - else: - y_scale = yx_scaling_factor if yx_scaling_factor is not None else 2.0 - x_scale = yx_scaling_factor if yx_scaling_factor is not None else 2.0 - z_scale = z_scaling_factor if z_scaling_factor is not None else 1.0 - scaling_factors = (z_scale, y_scale, x_scale) - if len(scaling_factors) < len(shape): - padding = (1.0,) * (len(shape) - len(scaling_factors)) - scaling_factors = padding + scaling_factors - elif len(scaling_factors) > len(shape): - scaling_factors = scaling_factors[len(scaling_factors) - len(shape) :] - - return scaling_factors - return scaling_factors - - def _compute_scaling_factors( *, scaling_factors: Sequence[float] | Literal["auto"], shape: tuple[int, ...], axes_handler: AxesHandler, - xy_scaling_factor: float | tuple[float, float] | None = None, - z_scaling_factor: float | None = None, ) -> tuple[float, ...]: """Compute scaling factors for given axes names.""" - # TODO remove with ngio 0.6 - scaling_factors = _check_deprecated_scaling_factors( - yx_scaling_factor=xy_scaling_factor, - z_scaling_factor=z_scaling_factor, - scaling_factors=scaling_factors, - shape=shape, - ) if scaling_factors == "auto": return _align_to_axes( values={ @@ -274,9 +221,6 @@ def init_image_like( axes_setup: AxesSetup | None = None, # Whether to overwrite existing image overwrite: bool = False, - # Deprecated arguments - yx_scaling_factor: float | tuple[float, float] | None = None, - z_scaling_factor: float | None = None, ) -> tuple[ZarrGroupHandler, AxesSetup]: """Create an empty OME-Zarr image with the given shape and metadata.""" shape = tuple(shape) @@ -303,8 +247,6 @@ def init_image_like( scaling_factors=scaling_factors, shape=shape, axes_handler=axes_handler, - xy_scaling_factor=yx_scaling_factor, - z_scaling_factor=z_scaling_factor, ) if isinstance(levels, int): levels_paths = tuple(str(i) for i in range(levels)) diff --git a/src/ngio/images/_image.py b/src/ngio/images/_image.py index 4e6884e4..11f32302 100644 --- a/src/ngio/images/_image.py +++ b/src/ngio/images/_image.py @@ -1,7 +1,6 @@ """Generic class to handle Image-like data in a OME-NGFF file.""" import logging -import warnings from collections.abc import Mapping, Sequence from typing import Any, Literal @@ -37,7 +36,6 @@ ) from ngio.ome_zarr_meta.ngio_specs._axes import AxesSetup from ngio.utils import ( - NgioDeprecationWarning, NgioValueError, StoreOrGroup, ZarrGroupHandler, @@ -453,17 +451,6 @@ def level_paths(self) -> list[str]: """Return the paths of the levels in the image.""" return self.meta.paths - @property - def levels_paths(self) -> list[str]: - """Deprecated: use 'level_paths' instead.""" - warnings.warn( - "'levels_paths' is deprecated and will be removed in ngio=0.6. " - "Please use 'level_paths' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - return self.level_paths - @property def levels(self) -> int: """Return the number of levels in the image.""" @@ -532,166 +519,21 @@ def get_channel_idx( channel_label=channel_label, wavelength_id=wavelength_id ) - def _set_channel_meta( - self, - channels_meta: ChannelsMeta | None = None, - ) -> None: - """Set the channels metadata.""" - if channels_meta is None: - channels_meta = ChannelsMeta.default_init(labels=self.num_channels) - meta = self.meta - meta.set_channels_meta(channels_meta) - self._meta_handler.update_meta(meta) - - def _set_channel_meta_legacy( - self, - labels: Sequence[str | None] | int | None = None, - wavelength_id: Sequence[str | None] | None = None, - start: Sequence[float | None] | None = None, - end: Sequence[float | None] | None = None, - percentiles: tuple[float, float] | None = None, - colors: Sequence[str | None] | None = None, - active: Sequence[bool | None] | None = None, - **omero_kwargs: dict, - ) -> None: - """Create a ChannelsMeta object with the default unit. - - Args: - labels(Sequence[str | None] | int): The list of channels names - in the image. If an integer is provided, the channels will - be named "channel_i". - wavelength_id(Sequence[str | None]): The wavelength ID of the channel. - If None, the wavelength ID will be the same as the channel name. - start(Sequence[float | None]): The start value for each channel. - If None, the start value will be computed from the image. - end(Sequence[float | None]): The end value for each channel. - If None, the end value will be computed from the image. - percentiles(tuple[float, float] | None): The start and end - percentiles for each channel. If None, the percentiles will - not be computed. - colors(Sequence[str | None]): The list of colors for the - channels. If None, the colors will be random. - active (Sequence[bool | None]): Whether the channel should - be shown by default. - omero_kwargs(dict): Extra fields to store in the omero attributes. - """ - low_res_dataset = self.meta.get_lowest_resolution_dataset() - ref_image = self.get(path=low_res_dataset.path) - - if start is not None and end is None: - raise NgioValueError("If start is provided, end must be provided as well.") - if end is not None and start is None: - raise NgioValueError("If end is provided, start must be provided as well.") - - if start is not None and percentiles is not None: - raise NgioValueError( - "If start and end are provided, percentiles must be None." - ) - - elif start is not None and end is not None: - if len(start) != len(end): - raise NgioValueError( - "The start and end lists must have the same length." - ) - if len(start) != self.num_channels: - raise NgioValueError( - "The start and end lists must have the same length as " - "the number of channels." - ) - - start = list(start) - end = list(end) - - else: - start, end = None, None - - if labels is None: - labels = ref_image.num_channels - - channel_meta = ChannelsMeta.default_init( - labels=labels, - wavelength_id=wavelength_id, - colors=colors, - start=start, - end=end, - active=active, - data_type=ref_image.dtype, - **omero_kwargs, - ) - self._set_channel_meta(channel_meta) - if percentiles is not None: - self.set_channel_windows_with_percentiles(percentiles=percentiles) - def set_channel_meta( self, channel_meta: ChannelsMeta | None = None, - labels: Sequence[str | None] | int | None = None, - wavelength_id: Sequence[str | None] | None = None, - start: Sequence[float | None] | None = None, - end: Sequence[float | None] | None = None, - percentiles: tuple[float, float] | None = None, - colors: Sequence[str | None] | None = None, - active: Sequence[bool | None] | None = None, - **omero_kwargs: dict, ) -> None: - """Create a ChannelsMeta object with the default unit. + """Set the channels metadata. Args: - channel_meta (ChannelsMeta | None): The channels metadata to set. - If none, it will fall back to the deprecated parameters. - labels(Sequence[str | None] | int): Deprecated. The list of channels names - in the image. If an integer is provided, the channels will - be named "channel_i". - wavelength_id(Sequence[str | None]): Deprecated. The wavelength ID of the - channel. If None, the wavelength ID will be the same as - the channel name. - start(Sequence[float | None]): Deprecated. The start value for each channel. - If None, the start value will be computed from the image. - end(Sequence[float | None]): Deprecated. The end value for each channel. - If None, the end value will be computed from the image. - percentiles(tuple[float, float] | None): Deprecated. The start and end - percentiles for each channel. If None, the percentiles will - not be computed. - colors(Sequence[str | None]): Deprecated. The list of colors for the - channels. If None, the colors will be random. - active (Sequence[bool | None]): Deprecated. Whether the channel should - be shown by default. - omero_kwargs(dict): Deprecated. Extra fields to store in the omero - attributes. + channel_meta: The channels metadata to set. If `None`, a default + metadata is created from the number of channels in the image. """ - _is_legacy = any( - param is not None - for param in [ - labels, - wavelength_id, - start, - end, - percentiles, - colors, - active, - ] - ) - if _is_legacy: - warnings.warn( - "The following parameters are deprecated and will be removed in " - "ngio=0.6: labels, wavelength_id, start, end, percentiles, " - "colors, active, omero_kwargs. Please use the " - "'channel_meta' parameter instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - self._set_channel_meta_legacy( - labels=labels, - wavelength_id=wavelength_id, - start=start, - end=end, - percentiles=percentiles, - colors=colors, - active=active, - **omero_kwargs, - ) - return None - self._set_channel_meta(channel_meta) + if channel_meta is None: + channel_meta = ChannelsMeta.default_init(labels=self.num_channels) + meta = self.meta + meta.set_channels_meta(channel_meta) + self._meta_handler.update_meta(meta) def set_channel_labels( self, @@ -712,7 +554,7 @@ def set_channel_labels( channel = ch.model_copy(update={"label": label}) new_channels.append(channel) new_meta = channels_meta.model_copy(update={"channels": new_channels}) - self._set_channel_meta(new_meta) + self.set_channel_meta(new_meta) def set_channel_colors( self, @@ -736,28 +578,7 @@ def set_channel_colors( channel = ch.model_copy(update={"channel_visualisation": ch_visualisation}) new_channels.append(channel) new_meta = channel_meta.model_copy(update={"channels": new_channels}) - self._set_channel_meta(new_meta) - - def set_channel_percentiles( - self, - start_percentile: float = 0.1, - end_percentile: float = 99.9, - ) -> None: - """Deprecated: Update the channel windows using percentiles. - - Args: - start_percentile (float): The start percentile. - end_percentile (float): The end percentile. - """ - warnings.warn( - "The 'set_channel_percentiles' method is deprecated and will be removed in " - "ngio=0.6. Please use 'set_channel_windows_with_percentiles' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - self.set_channel_windows_with_percentiles( - percentiles=(start_percentile, end_percentile) - ) + self.set_channel_meta(new_meta) def set_channel_windows( self, @@ -883,9 +704,6 @@ def derive( compressors: CompressorLike = "auto", extra_array_kwargs: Mapping[str, Any] | None = None, overwrite: bool = False, - # Deprecated arguments - labels: Sequence[str] | None = None, - pixel_size: PixelSize | None = None, ) -> "ImagesContainer": """Create an empty OME-Zarr image from an existing image. @@ -922,11 +740,6 @@ def derive( extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to the zarr array creation. overwrite (bool): Whether to overwrite an existing image. - labels (Sequence[str] | None): The labels of the new image. - This argument is deprecated please use channels_meta instead. - pixel_size (PixelSize | None): The pixel size of the new image. - This argument is deprecated please use pixelsize, z_spacing, - and time_spacing instead. Returns: ImagesContainer: The new derived image. @@ -952,8 +765,6 @@ def derive( compressors=compressors, extra_array_kwargs=extra_array_kwargs, overwrite=overwrite, - labels=labels, - pixel_size=pixel_size, ) def get( @@ -1065,9 +876,6 @@ def derive_image_container( compressors: CompressorLike | None = None, extra_array_kwargs: Mapping[str, Any] | None = None, overwrite: bool = False, - # Deprecated arguments - labels: Sequence[str] | None = None, - pixel_size: PixelSize | None = None, ) -> ImagesContainer: """Derive a new OME-Zarr image container from an existing image. @@ -1104,11 +912,6 @@ def derive_image_container( extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to the zarr array creation. overwrite (bool): Whether to overwrite an existing image. Defaults to False. - labels (Sequence[str] | None): Deprecated. This argument is deprecated, - please use channels_meta instead. - pixel_size (PixelSize | None): Deprecated. The pixel size of the new image. - This argument is deprecated, please use pixelsize, z_spacing, - and time_spacing instead. Returns: ImagesContainer: The new derived image container. @@ -1135,8 +938,6 @@ def derive_image_container( compressors=compressors, extra_array_kwargs=extra_array_kwargs, overwrite=overwrite, - labels=labels, - pixel_size=pixel_size, ) return ImagesContainer(group_handler=group_handler, axes_setup=axes_setup) diff --git a/src/ngio/images/_label.py b/src/ngio/images/_label.py index 6a3af096..a993c3e9 100644 --- a/src/ngio/images/_label.py +++ b/src/ngio/images/_label.py @@ -220,9 +220,6 @@ def derive( compressors: CompressorLike | None = None, extra_array_kwargs: Mapping[str, Any] | None = None, overwrite: bool = False, - # Deprecated arguments - labels: Sequence[str] | None = None, - pixel_size: PixelSize | None = None, ) -> "Label": """Create an empty OME-Zarr label from an existing image or label. @@ -255,11 +252,6 @@ def derive( extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to the zarr array creation. overwrite (bool): Whether to overwrite an existing label. - labels (Sequence[str] | None): Deprecated. This argument is deprecated, - please use channels_meta instead. - pixel_size (PixelSize | None): Deprecated. The pixel size of the new label. - This argument is deprecated, please use pixelsize, z_spacing, - and time_spacing instead. Returns: Label: The new derived label. @@ -291,8 +283,6 @@ def derive( compressors=compressors, extra_array_kwargs=extra_array_kwargs, overwrite=overwrite, - labels=labels, - pixel_size=pixel_size, ) if name not in existing_labels: @@ -326,9 +316,6 @@ def derive_label( compressors: CompressorLike | None = None, extra_array_kwargs: Mapping[str, Any] | None = None, overwrite: bool = False, - # Deprecated arguments - labels: Sequence[str] | None = None, - pixel_size: PixelSize | None = None, ) -> tuple[ZarrGroupHandler, AxesSetup]: """Derive a new OME-Zarr label from an existing image or label. @@ -361,11 +348,6 @@ def derive_label( extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to the zarr array creation. overwrite (bool): Whether to overwrite an existing label. Defaults to False. - labels (Sequence[str] | None): Deprecated. This argument is deprecated, - please use channels_meta instead. - pixel_size (PixelSize | None): Deprecated. The pixel size of the new label. - This argument is deprecated, please use pixelsize, z_spacing, - and time_spacing instead. Returns: tuple[ZarrGroupHandler, AxesSetup]: The group handler of the new label @@ -394,8 +376,6 @@ def derive_label( compressors=compressors, extra_array_kwargs=extra_array_kwargs, overwrite=overwrite, - labels=labels, - pixel_size=pixel_size, ) return group_handler, axes_setup diff --git a/src/ngio/images/_ome_zarr_container.py b/src/ngio/images/_ome_zarr_container.py index 1643a16c..3aaff895 100644 --- a/src/ngio/images/_ome_zarr_container.py +++ b/src/ngio/images/_ome_zarr_container.py @@ -1,7 +1,6 @@ """Abstract class for handling OME-NGFF images.""" import logging -import warnings from collections.abc import Mapping, Sequence from typing import Any, Literal @@ -43,7 +42,6 @@ ) from ngio.utils import ( AccessModeLiteral, - NgioDeprecationWarning, NgioError, NgioValidationError, NgioValueError, @@ -205,17 +203,6 @@ def meta(self) -> NgioImageMeta: """Return the image metadata.""" return self.images_container.meta - @property - def image_meta(self) -> NgioImageMeta: - """Return the image metadata.""" - warnings.warn( - "'image_meta' is deprecated and will be removed in ngio=0.6. " - "Please use 'meta' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - return self.images_container.meta - @property def axes_setup(self) -> AxesSetup: """Return the axes setup.""" @@ -231,17 +218,6 @@ def level_paths(self) -> list[str]: """Return the paths of the levels in the image.""" return self.images_container.level_paths - @property - def levels_paths(self) -> list[str]: - """Deprecated: use 'level_paths' instead.""" - warnings.warn( - "'levels_paths' is deprecated and will be removed in ngio=0.6. " - "Please use 'level_paths' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - return self.images_container.level_paths - @property def is_3d(self) -> bool: """Return True if the image is 3D.""" @@ -308,51 +284,14 @@ def get_channel_idx( def set_channel_meta( self, channel_meta: ChannelsMeta | None = None, - labels: Sequence[str | None] | int | None = None, - wavelength_id: Sequence[str | None] | None = None, - start: Sequence[float | None] | None = None, - end: Sequence[float | None] | None = None, - percentiles: tuple[float, float] | None = None, - colors: Sequence[str | None] | None = None, - active: Sequence[bool | None] | None = None, - **omero_kwargs: dict, ) -> None: - """Create a ChannelsMeta object with the default unit. + """Set the channels metadata. Args: - channel_meta (ChannelsMeta | None): The channels metadata to set. - If none, it will fall back to the deprecated parameters. - labels(Sequence[str | None] | int): Deprecated. The list of channels names - in the image. If an integer is provided, the channels will - be named "channel_i". - wavelength_id(Sequence[str | None]): Deprecated. The wavelength ID of the - channel. If None, the wavelength ID will be the same as - the channel name. - start(Sequence[float | None]): Deprecated. The start value for each channel. - If None, the start value will be computed from the image. - end(Sequence[float | None]): Deprecated. The end value for each channel. - If None, the end value will be computed from the image. - percentiles(tuple[float, float] | None): Deprecated. The start and end - percentiles for each channel. If None, the percentiles will - not be computed. - colors(Sequence[str | None]): Deprecated. The list of colors for the - channels. If None, the colors will be random. - active (Sequence[bool | None]): Deprecated. Whether the channel should - be shown by default. - omero_kwargs(dict): Deprecated. Extra fields to store in the omero - attributes. + channel_meta: The channels metadata to set. If `None`, a default + metadata is created from the number of channels in the image. """ - self._images_container.set_channel_meta( - channel_meta=channel_meta, - labels=labels, - wavelength_id=wavelength_id, - start=start, - end=end, - percentiles=percentiles, - colors=colors, - active=active, - **omero_kwargs, - ) + self._images_container.set_channel_meta(channel_meta=channel_meta) def set_channel_labels( self, @@ -376,27 +315,6 @@ def set_channel_colors( """ self._images_container.set_channel_colors(colors=colors) - def set_channel_percentiles( - self, - start_percentile: float = 0.1, - end_percentile: float = 99.9, - ) -> None: - """Deprecated: Update the channel windows using percentiles. - - Args: - start_percentile (float): The start percentile. - end_percentile (float): The end percentile. - """ - warnings.warn( - "The 'set_channel_percentiles' method is deprecated and will be removed in " - "ngio=0.6. Please use 'set_channel_windows_with_percentiles' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - self._images_container.set_channel_windows_with_percentiles( - percentiles=(start_percentile, end_percentile) - ) - def set_channel_windows( self, starts_ends: Sequence[tuple[float, float]], @@ -607,9 +525,6 @@ def derive_image( # Copy from current image copy_labels: bool = False, copy_tables: bool = False, - # Deprecated arguments - labels: Sequence[str] | None = None, - pixel_size: PixelSize | None = None, ) -> "OmeZarrContainer": """Derive a new OME-Zarr container from the current image. @@ -650,11 +565,6 @@ def derive_image( Defaults to False. copy_tables (bool): Whether to copy the tables from the current image. Defaults to False. - labels (Sequence[str] | None): Deprecated. This argument is deprecated, - please use channels_meta instead. - pixel_size (PixelSize | None): Deprecated. The pixel size of the new image. - This argument is deprecated, please use pixelsize, z_spacing, - and time_spacing instead. Returns: OmeZarrContainer: The new derived OME-Zarr container. @@ -679,8 +589,6 @@ def derive_image( compressors=compressors, extra_array_kwargs=extra_array_kwargs, overwrite=overwrite, - labels=labels, - pixel_size=pixel_size, ) new_ome_zarr = OmeZarrContainer( group_handler=new_container._group_handler, @@ -782,24 +690,12 @@ def get_condition_table(self, name: str) -> ConditionTable: ) return table - def get_table(self, name: str, check_type: TypedTable | None = None) -> Table: + def get_table(self, name: str) -> Table: """Get a table from the image. Args: name (str): The name of the table. - check_type (TypedTable | None): Deprecated. Please use - 'get_table_as' instead, or one of the type specific - get_*table() methods. - """ - if check_type is not None: - warnings.warn( - "The 'check_type' argument is deprecated and will be removed in " - "ngio=0.6. Please use 'get_table_as' instead or one of the " - "type specific get_*table() methods.", - NgioDeprecationWarning, - stacklevel=2, - ) return self.tables_container.get(name=name, strict=False) def get_table_as( @@ -968,9 +864,6 @@ def derive_label( compressors: CompressorLike | None = None, extra_array_kwargs: Mapping[str, Any] | None = None, overwrite: bool = False, - # Deprecated arguments - labels: Sequence[str] | None = None, - pixel_size: PixelSize | None = None, ) -> "Label": """Derive a new label from an existing image or label. @@ -1005,11 +898,6 @@ def derive_label( extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to the zarr array creation. overwrite (bool): Whether to overwrite an existing label. Defaults to False. - labels (Sequence[str] | None): Deprecated. This argument is deprecated, - please use channels_meta instead. - pixel_size (PixelSize | None): Deprecated. The pixel size of the new label. - This argument is deprecated, please use pixelsize, z_spacing, - and time_spacing instead. Returns: Label: The new derived label. @@ -1034,8 +922,6 @@ def derive_label( compressors=compressors, extra_array_kwargs=extra_array_kwargs, overwrite=overwrite, - labels=labels, - pixel_size=pixel_size, ) @@ -1137,7 +1023,7 @@ def open_label( def create_empty_ome_zarr( store: StoreOrGroup, shape: Sequence[int], - pixelsize: float | tuple[float, float] | None = None, + pixelsize: float | tuple[float, float], z_spacing: float = 1.0, time_spacing: float = 1.0, scaling_factors: Sequence[float] | Literal["auto"] = "auto", @@ -1157,14 +1043,6 @@ def create_empty_ome_zarr( compressors: CompressorLike = "auto", extra_array_kwargs: Mapping[str, Any] | None = None, overwrite: bool = False, - # Deprecated arguments - xy_pixelsize: float | None = None, - xy_scaling_factor: float | None = None, - z_scaling_factor: float | None = None, - channel_labels: list[str] | None = None, - channel_wavelengths: list[str] | None = None, - channel_colors: Sequence[str] | None = None, - channel_active: Sequence[bool] | None = None, ) -> OmeZarrContainer: """Create an empty OME-Zarr image with the given shape and metadata. @@ -1201,71 +1079,7 @@ def create_empty_ome_zarr( extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to the zarr array creation. Defaults to None. overwrite (bool): Whether to overwrite an existing image. Defaults to False. - xy_pixelsize (float | None): Deprecated. Use pixelsize instead. - xy_scaling_factor (float | None): Deprecated. Use scaling_factors instead. - z_scaling_factor (float | None): Deprecated. Use scaling_factors instead. - channel_labels (list[str] | None): Deprecated. Use channels_meta instead. - channel_wavelengths (list[str] | None): Deprecated. Use channels_meta instead. - channel_colors (Sequence[str] | None): Deprecated. Use channels_meta instead. - channel_active (Sequence[bool] | None): Deprecated. Use channels_meta instead. """ - if xy_pixelsize is not None: - warnings.warn( - "'xy_pixelsize' is deprecated and will be removed in ngio=0.6. " - "Please use 'pixelsize' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - pixelsize = xy_pixelsize - if xy_scaling_factor is not None or z_scaling_factor is not None: - warnings.warn( - "'xy_scaling_factor' and 'z_scaling_factor' are deprecated and will be " - "removed in ngio=0.6. Please use 'scaling_factors' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - xy_scaling_factor_ = xy_scaling_factor or 2.0 - z_scaling_factor_ = z_scaling_factor or 1.0 - if len(shape) == 2: - scaling_factors = (xy_scaling_factor_, xy_scaling_factor_) - else: - zyx_factors = (z_scaling_factor_, xy_scaling_factor_, xy_scaling_factor_) - scaling_factors = (1.0,) * (len(shape) - 3) + zyx_factors - - if channel_labels is not None: - warnings.warn( - "'channel_labels' is deprecated and will be removed in ngio=0.6. " - "Please use 'channels_meta' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - channels_meta = channel_labels - - if channel_wavelengths is not None: - warnings.warn( - "'channel_wavelengths' is deprecated and will be removed in ngio=0.6. " - "Please use 'channels_meta' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - if channel_colors is not None: - warnings.warn( - "'channel_colors' is deprecated and will be removed in ngio=0.6. " - "Please use 'channels_meta' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - if channel_active is not None: - warnings.warn( - "'channel_active' is deprecated and will be removed in ngio=0.6. " - "Please use 'channels_meta' instead.", - NgioDeprecationWarning, - stacklevel=2, - ) - - if pixelsize is None: - raise NgioValueError("pixelsize must be provided.") - handler, axes_setup = init_image_like( store=store, meta_type=NgioImageMeta, @@ -1292,29 +1106,13 @@ def create_empty_ome_zarr( overwrite=overwrite, ) - ome_zarr = OmeZarrContainer(group_handler=handler, axes_setup=axes_setup) - if ( - channel_labels is not None - or channel_wavelengths is not None - or channel_colors is not None - or channel_active is not None - ): - # Deprecated way of setting channel metadata - # we set it here for backward compatibility - ome_zarr.set_channel_meta( - labels=channel_labels, - wavelength_id=channel_wavelengths, - percentiles=None, - colors=channel_colors, - active=channel_active, - ) - return ome_zarr + return OmeZarrContainer(group_handler=handler, axes_setup=axes_setup) def create_ome_zarr_from_array( store: StoreOrGroup, array: np.ndarray, - pixelsize: float | tuple[float, float] | None = None, + pixelsize: float | tuple[float, float], z_spacing: float = 1.0, time_spacing: float = 1.0, scaling_factors: Sequence[float] | Literal["auto"] = "auto", @@ -1334,14 +1132,6 @@ def create_ome_zarr_from_array( compressors: CompressorLike = "auto", extra_array_kwargs: Mapping[str, Any] | None = None, overwrite: bool = False, - # Deprecated arguments - xy_pixelsize: float | None = None, - xy_scaling_factor: float | None = None, - z_scaling_factor: float | None = None, - channel_labels: list[str] | None = None, - channel_wavelengths: list[str] | None = None, - channel_colors: Sequence[str] | None = None, - channel_active: Sequence[bool] | None = None, ) -> OmeZarrContainer: """Create an OME-Zarr image from a numpy array. @@ -1379,13 +1169,6 @@ def create_ome_zarr_from_array( extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to the zarr array creation. Defaults to None. overwrite (bool): Whether to overwrite an existing image. Defaults to False. - xy_pixelsize (float | None): Deprecated. Use pixelsize instead. - xy_scaling_factor (float | None): Deprecated. Use scaling_factors instead. - z_scaling_factor (float | None): Deprecated. Use scaling_factors instead. - channel_labels (list[str] | None): Deprecated. Use channels_meta instead. - channel_wavelengths (list[str] | None): Deprecated. Use channels_meta instead. - channel_colors (Sequence[str] | None): Deprecated. Use channels_meta instead. - channel_active (Sequence[bool] | None): Deprecated. Use channels_meta instead. """ if len(percentiles) != 2: raise NgioValueError( @@ -1414,13 +1197,6 @@ def create_ome_zarr_from_array( compressors=compressors, extra_array_kwargs=extra_array_kwargs, overwrite=overwrite, - xy_pixelsize=xy_pixelsize, - xy_scaling_factor=xy_scaling_factor, - z_scaling_factor=z_scaling_factor, - channel_labels=channel_labels, - channel_wavelengths=channel_wavelengths, - channel_colors=channel_colors, - channel_active=channel_active, ) image = ome_zarr.get_image() image.set_array(array) diff --git a/src/ngio/iterators/__init__.py b/src/ngio/iterators/__init__.py new file mode 100644 index 00000000..11655dd4 --- /dev/null +++ b/src/ngio/iterators/__init__.py @@ -0,0 +1,15 @@ +"""Iterators to build scalable image processing pipelines.""" + +from ngio.iterators._feature import FeatureExtractorIterator +from ngio.iterators._image_processing import ImageProcessingIterator +from ngio.iterators._segmentation import ( + MaskedSegmentationIterator, + SegmentationIterator, +) + +__all__ = [ + "FeatureExtractorIterator", + "ImageProcessingIterator", + "MaskedSegmentationIterator", + "SegmentationIterator", +] diff --git a/src/ngio/experimental/iterators/_abstract_iterator.py b/src/ngio/iterators/_abstract_iterator.py similarity index 98% rename from src/ngio/experimental/iterators/_abstract_iterator.py rename to src/ngio/iterators/_abstract_iterator.py index 519fd334..4071015f 100644 --- a/src/ngio/experimental/iterators/_abstract_iterator.py +++ b/src/ngio/iterators/_abstract_iterator.py @@ -2,18 +2,18 @@ from collections.abc import Callable, Generator from typing import Generic, Literal, Self, TypeVar, overload -from ngio import Roi -from ngio.experimental.iterators._mappers import BasicMapper, MapperProtocol -from ngio.experimental.iterators._rois_utils import ( +from ngio.common import Roi +from ngio.images._abstract_image import AbstractImage +from ngio.io_pipes._io_pipes_types import DataGetterProtocol, DataSetterProtocol +from ngio.io_pipes._ops_slices_utils import check_if_regions_overlap +from ngio.iterators._mappers import BasicMapper, MapperProtocol +from ngio.iterators._rois_utils import ( by_chunks, by_yx, by_zyx, grid, rois_product, ) -from ngio.images._abstract_image import AbstractImage -from ngio.io_pipes._io_pipes_types import DataGetterProtocol, DataSetterProtocol -from ngio.io_pipes._ops_slices_utils import check_if_regions_overlap from ngio.tables import GenericRoiTable from ngio.utils import NgioValueError diff --git a/src/ngio/experimental/iterators/_feature.py b/src/ngio/iterators/_feature.py similarity index 98% rename from src/ngio/experimental/iterators/_feature.py rename to src/ngio/iterators/_feature.py index 53d891fd..6dc3a95d 100644 --- a/src/ngio/experimental/iterators/_feature.py +++ b/src/ngio/iterators/_feature.py @@ -5,7 +5,6 @@ import numpy as np from ngio.common import Roi -from ngio.experimental.iterators._abstract_iterator import AbstractIteratorBuilder from ngio.images import Image, Label from ngio.images._image import ( ChannelSlicingInputType, @@ -17,6 +16,7 @@ NumpyRoiGetter, TransformProtocol, ) +from ngio.iterators._abstract_iterator import AbstractIteratorBuilder NumpyPipeType: TypeAlias = tuple[np.ndarray, np.ndarray, Roi] DaskPipeType: TypeAlias = tuple[da.Array, da.Array, Roi] diff --git a/src/ngio/experimental/iterators/_image_processing.py b/src/ngio/iterators/_image_processing.py similarity index 98% rename from src/ngio/experimental/iterators/_image_processing.py rename to src/ngio/iterators/_image_processing.py index 4bd80d41..6f365164 100644 --- a/src/ngio/experimental/iterators/_image_processing.py +++ b/src/ngio/iterators/_image_processing.py @@ -4,7 +4,6 @@ import numpy as np from ngio.common import Roi -from ngio.experimental.iterators._abstract_iterator import AbstractIteratorBuilder from ngio.images import Image from ngio.images._image import ( ChannelSlicingInputType, @@ -18,6 +17,7 @@ TransformProtocol, ) from ngio.io_pipes._io_pipes_types import DataGetterProtocol, DataSetterProtocol +from ngio.iterators._abstract_iterator import AbstractIteratorBuilder class ImageProcessingIterator(AbstractIteratorBuilder[np.ndarray, da.Array]): diff --git a/src/ngio/experimental/iterators/_mappers.py b/src/ngio/iterators/_mappers.py similarity index 100% rename from src/ngio/experimental/iterators/_mappers.py rename to src/ngio/iterators/_mappers.py diff --git a/src/ngio/experimental/iterators/_rois_utils.py b/src/ngio/iterators/_rois_utils.py similarity index 99% rename from src/ngio/experimental/iterators/_rois_utils.py rename to src/ngio/iterators/_rois_utils.py index ccd78072..17eefda2 100644 --- a/src/ngio/experimental/iterators/_rois_utils.py +++ b/src/ngio/iterators/_rois_utils.py @@ -1,4 +1,4 @@ -from ngio import Roi +from ngio.common import Roi from ngio.images._abstract_image import AbstractImage from ngio.utils import NgioValueError diff --git a/src/ngio/experimental/iterators/_segmentation.py b/src/ngio/iterators/_segmentation.py similarity index 99% rename from src/ngio/experimental/iterators/_segmentation.py rename to src/ngio/iterators/_segmentation.py index 8ac223b1..5aa24907 100644 --- a/src/ngio/experimental/iterators/_segmentation.py +++ b/src/ngio/iterators/_segmentation.py @@ -4,7 +4,6 @@ import numpy as np from ngio.common import Roi -from ngio.experimental.iterators._abstract_iterator import AbstractIteratorBuilder from ngio.images import Image, Label from ngio.images._image import ( ChannelSlicingInputType, @@ -23,6 +22,7 @@ TransformProtocol, ) from ngio.io_pipes._io_pipes_types import DataGetterProtocol, DataSetterProtocol +from ngio.iterators._abstract_iterator import AbstractIteratorBuilder class SegmentationIterator(AbstractIteratorBuilder[np.ndarray, da.Array]): diff --git a/tests/create_test_data.py b/tests/create_test_data.py index 3426dfc4..20b33104 100644 --- a/tests/create_test_data.py +++ b/tests/create_test_data.py @@ -61,7 +61,7 @@ def create_test_images_dataset(version: NgffVersions) -> None: image_path = base_dir / spec["name"] ome_zarr = create_empty_ome_zarr( store=image_path, - xy_pixelsize=0.5, + pixelsize=0.5, shape=spec["shape"], axes_names=spec["axes"], ngff_version=version, diff --git a/tests/unit/hcs/test_plate_edge_cases.py b/tests/unit/hcs/test_plate_edge_cases.py index 30390a90..6247716c 100644 --- a/tests/unit/hcs/test_plate_edge_cases.py +++ b/tests/unit/hcs/test_plate_edge_cases.py @@ -9,7 +9,6 @@ from ngio import ( Roi, create_empty_plate, - create_empty_well, open_ome_zarr_plate, ) from ngio.tables import ( @@ -19,7 +18,7 @@ MaskingRoiTable, RoiTable, ) -from ngio.utils import NgioDeprecationWarning, NgioValueError +from ngio.utils import NgioValueError def _make_roi(name: str, label: int | None = None) -> Roi: @@ -111,14 +110,6 @@ def test_typed_table_getters(tmp_path: Path): plate.get_condition_table("roi") -def test_get_table_check_type_deprecated(tmp_path: Path): - plate = create_empty_plate(tmp_path / "plate.zarr", name="plate") - plate.add_table("roi", RoiTable(rois=[_make_roi("roi_1")])) - with pytest.warns(NgioDeprecationWarning, match="'check_type' argument"): - table = plate.get_table("roi", check_type="roi_table") - assert isinstance(table, RoiTable) - - def test_get_table_as(tmp_path: Path): plate = create_empty_plate(tmp_path / "plate.zarr", name="plate") plate.add_table("roi", RoiTable(rois=[_make_roi("roi_1")])) @@ -143,27 +134,6 @@ def test_concatenate_image_tables_as(cardiomyocyte_small_mip_path_readonly: Path assert set(table.dataframe.columns) == set(async_table.dataframe.columns) -def test_create_empty_plate_version_deprecated(tmp_path: Path): - with pytest.warns(NgioDeprecationWarning, match="'version' argument"): - plate = create_empty_plate(tmp_path / "plate.zarr", name="plate", version="0.4") - assert plate.meta.version == "0.4" - - -def test_derive_plate_version_deprecated(tmp_path: Path): - plate = create_empty_plate( - tmp_path / "plate.zarr", name="plate", ngff_version="0.5" - ) - with pytest.warns(NgioDeprecationWarning, match="'version' argument"): - derived = plate.derive_plate(tmp_path / "derived.zarr", version="0.4") - assert derived.meta.version == "0.4" - - -def test_create_empty_well_version_deprecated(tmp_path: Path): - with pytest.warns(NgioDeprecationWarning, match="'version' argument"): - well = create_empty_well(tmp_path / "well.zarr", version="0.4") - assert well.meta.version == "0.4" - - def test_get_well_returns_cached_instance(tmp_path: Path): plate = create_empty_plate(tmp_path / "plate.zarr", name="plate", cache=True) plate.add_well(row="A", column=1) diff --git a/tests/unit/images/test_container_errors.py b/tests/unit/images/test_container_errors.py index 3d116a9c..a3a5d80f 100644 --- a/tests/unit/images/test_container_errors.py +++ b/tests/unit/images/test_container_errors.py @@ -1,4 +1,4 @@ -"""Coverage tests for error paths and deprecated shims in OmeZarrContainer.""" +"""Coverage tests for error paths in OmeZarrContainer.""" from pathlib import Path @@ -17,11 +17,7 @@ from ngio.images import Label from ngio.ome_zarr_meta.ngio_specs import Channel from ngio.tables import ConditionTable, FeatureTable, MaskingRoiTable, RoiTable -from ngio.utils import ( - NgioDeprecationWarning, - NgioValidationError, - NgioValueError, -) +from ngio.utils import NgioValidationError, NgioValueError def _make_container(store=None, channels=("DAPI", "GFP")): @@ -66,31 +62,11 @@ def test_repr_many_labels_and_tables(): assert "#tables=3" in repr_str -def test_deprecated_image_meta_property(): - ome_zarr = _make_container() - with pytest.warns(NgioDeprecationWarning, match="image_meta"): - meta = ome_zarr.image_meta - assert meta.paths == ome_zarr.meta.paths - - -def test_deprecated_levels_paths_property(): - ome_zarr = _make_container() - with pytest.warns(NgioDeprecationWarning, match="levels_paths"): - paths = ome_zarr.levels_paths - assert paths == ome_zarr.level_paths - - def test_axes_setup_property(): ome_zarr = _make_container() assert ome_zarr.axes_setup == ome_zarr.images_container.axes_setup -def test_deprecated_set_channel_percentiles(): - ome_zarr = _make_container() - with pytest.warns(NgioDeprecationWarning, match="set_channel_percentiles"): - ome_zarr.set_channel_percentiles(start_percentile=1.0, end_percentile=99.0) - - def test_set_axes_units_updates_labels(): ome_zarr = _make_container() _add_label(ome_zarr, "lbl1") @@ -167,12 +143,6 @@ def test_get_condition_table(container_with_tables): container_with_tables.get_condition_table("roi") -def test_get_table_check_type_deprecated(container_with_tables): - with pytest.warns(NgioDeprecationWarning, match="check_type"): - table = container_with_tables.get_table("roi", check_type="roi_table") - assert isinstance(table, RoiTable) - - def test_get_table_as(container_with_tables): table = container_with_tables.get_table_as("roi", RoiTable) assert isinstance(table, RoiTable) @@ -203,61 +173,6 @@ def test_open_label(images_all_versions_readonly: dict[str, Path], zarr_key: str assert label_by_name.shape == label.shape -def test_create_empty_deprecated_xy_pixelsize(): - with pytest.warns(NgioDeprecationWarning, match="xy_pixelsize"): - ome_zarr = create_empty_ome_zarr( - MemoryStore(), shape=(8, 8), xy_pixelsize=0.5, levels=2 - ) - assert ome_zarr.get_image().pixel_size.x == 0.5 - - -def test_create_empty_deprecated_scaling_factors_2d(): - with pytest.warns(NgioDeprecationWarning, match="xy_scaling_factor"): - ome_zarr = create_empty_ome_zarr( - MemoryStore(), - shape=(16, 16), - pixelsize=0.5, - xy_scaling_factor=2.0, - levels=2, - ) - assert ome_zarr.levels == 2 - assert ome_zarr.get_image(path="1").shape == (8, 8) - - -def test_create_empty_deprecated_scaling_factors_3d(): - with pytest.warns(NgioDeprecationWarning, match="z_scaling_factor"): - ome_zarr = create_empty_ome_zarr( - MemoryStore(), - shape=(4, 16, 16), - pixelsize=0.5, - z_scaling_factor=1.0, - levels=2, - ) - assert ome_zarr.get_image(path="1").shape == (4, 8, 8) - - -def test_create_empty_deprecated_channel_args(): - with pytest.warns(NgioDeprecationWarning): - ome_zarr = create_empty_ome_zarr( - MemoryStore(), - shape=(2, 8, 8), - axes_names=("c", "y", "x"), - pixelsize=0.5, - levels=2, - channel_labels=["ch_a", "ch_b"], - channel_wavelengths=["w1", "w2"], - channel_colors=["FF0000", "00FF00"], - channel_active=[True, False], - ) - assert ome_zarr.channel_labels == ["ch_a", "ch_b"] - assert ome_zarr.wavelength_ids == ["w1", "w2"] - - -def test_create_empty_requires_pixelsize(): - with pytest.raises(NgioValueError, match="pixelsize must be provided"): - create_empty_ome_zarr(MemoryStore(), shape=(8, 8), levels=2) - - def test_create_from_array_bad_percentiles(): with pytest.raises(NgioValueError, match="tuple of two values"): create_ome_zarr_from_array( diff --git a/tests/unit/images/test_create.py b/tests/unit/images/test_create.py index b16ba1b4..4113858f 100644 --- a/tests/unit/images/test_create.py +++ b/tests/unit/images/test_create.py @@ -341,28 +341,3 @@ def test_derive_from_legacy_images(tmp_path: Path, ngff_version: str): img = ome_zarr.get_image(path=path_img) lbl = ome_zarr.get_label(name="my_label_level_1", path=path_lbl) assert img.shape[-2:] == lbl.shape[-2:] - - -def test_deprecated_scaling_factors_order(): - from ngio.images._create_utils import _check_deprecated_scaling_factors - from ngio.utils import NgioDeprecationWarning - - # canonical axes order is (..., z, y, x) - with pytest.warns(NgioDeprecationWarning): - factors = _check_deprecated_scaling_factors( - yx_scaling_factor=(2.0, 4.0), - z_scaling_factor=None, - scaling_factors="auto", - shape=(1, 4, 64, 64), - ) - assert factors == (1.0, 1.0, 2.0, 4.0) - - # 2D shape: factors must be trimmed to (y, x) - with pytest.warns(NgioDeprecationWarning): - factors = _check_deprecated_scaling_factors( - yx_scaling_factor=(2.0, 4.0), - z_scaling_factor=None, - scaling_factors="auto", - shape=(64, 64), - ) - assert factors == (2.0, 4.0) diff --git a/tests/unit/images/test_image_api.py b/tests/unit/images/test_image_api.py index 9425dcc0..abdb58be 100644 --- a/tests/unit/images/test_image_api.py +++ b/tests/unit/images/test_image_api.py @@ -7,8 +7,8 @@ from ngio import create_empty_ome_zarr from ngio.images import ChannelSelectionModel -from ngio.ome_zarr_meta.ngio_specs import Channel -from ngio.utils import NgioDeprecationWarning, NgioValueError +from ngio.ome_zarr_meta.ngio_specs import Channel, ChannelsMeta +from ngio.utils import NgioValueError def _make_container(): @@ -32,51 +32,19 @@ def test_channel_selection_model_index_must_be_int(): ChannelSelectionModel(mode="index", identifier="not_an_int") -def test_images_container_levels_paths_deprecated(): - container = _make_container().images_container - with pytest.warns(NgioDeprecationWarning, match="levels_paths"): - paths = container.levels_paths - assert paths == container.level_paths - - def test_set_channel_meta_default_init(): ome_zarr = _make_container() ome_zarr.set_channel_meta() assert ome_zarr.channel_labels == ["channel_0", "channel_1"] -@pytest.mark.parametrize( - "kwargs, match", - [ - ({"start": [0.0, 0.0]}, "end must be provided"), - ({"end": [1.0, 1.0]}, "start must be provided"), - ( - { - "start": [0.0, 0.0], - "end": [1.0, 1.0], - "percentiles": (0.1, 99.9), - }, - "percentiles must be None", - ), - ({"start": [0.0], "end": [1.0, 2.0]}, "same length"), - ({"start": [0.0], "end": [1.0]}, "number of channels"), - ], -) -def test_set_channel_meta_legacy_errors(kwargs, match): - ome_zarr = _make_container() - with ( - pytest.warns(NgioDeprecationWarning, match="deprecated"), - pytest.raises(NgioValueError, match=match), - ): - ome_zarr.set_channel_meta(**kwargs) - - -def test_set_channel_meta_legacy_start_end(): +def test_set_channel_meta_with_windows(): ome_zarr = _make_container() - with pytest.warns(NgioDeprecationWarning, match="deprecated"): - ome_zarr.set_channel_meta( + ome_zarr.set_channel_meta( + channel_meta=ChannelsMeta.default_init( labels=["c1", "c2"], start=[0.0, 5.0], end=[10.0, 20.0] ) + ) assert ome_zarr.channel_labels == ["c1", "c2"] channels = ome_zarr.images_container.channels_meta.channels assert channels[0].channel_visualisation.start == 0.0 @@ -85,13 +53,6 @@ def test_set_channel_meta_legacy_start_end(): assert channels[1].channel_visualisation.end == 20.0 -def test_set_channel_meta_legacy_percentiles(): - ome_zarr = _make_container() - with pytest.warns(NgioDeprecationWarning, match="deprecated"): - ome_zarr.set_channel_meta(percentiles=(0.1, 99.9)) - assert ome_zarr.num_channels == 2 - - def test_set_channel_labels_wrong_length(): ome_zarr = _make_container() with pytest.raises(NgioValueError, match="number of labels"): @@ -104,12 +65,6 @@ def test_set_channel_colors_wrong_length(): ome_zarr.set_channel_colors(["FF0000"]) -def test_images_container_set_channel_percentiles_deprecated(): - container = _make_container().images_container - with pytest.warns(NgioDeprecationWarning, match="set_channel_percentiles"): - container.set_channel_percentiles(start_percentile=1.0, end_percentile=99.0) - - def test_set_channel_windows_wrong_lengths(): ome_zarr = _make_container() with pytest.raises(NgioValueError, match="start-end pairs"): diff --git a/tests/unit/iterators/test_experimental_shim.py b/tests/unit/iterators/test_experimental_shim.py new file mode 100644 index 00000000..4e68a4d4 --- /dev/null +++ b/tests/unit/iterators/test_experimental_shim.py @@ -0,0 +1,28 @@ +"""The `ngio.experimental.iterators` shim forwards to `ngio.iterators`.""" + +import pytest + +import ngio +import ngio.experimental.iterators as experimental_iterators +from ngio import iterators +from ngio.utils import NgioDeprecationWarning + + +@pytest.mark.parametrize("name", sorted(iterators.__all__)) +def test_shim_warns_and_forwards(name: str): + with pytest.warns(NgioDeprecationWarning, match=f"ngio.iterators.{name}") as record: + obj = getattr(experimental_iterators, name) + assert obj is getattr(iterators, name) + # The promised removal version is the point of the warning; pin it. + assert "ngio=1.1" in str(record[0].message) + + +def test_shim_unknown_attribute(): + with pytest.raises(AttributeError, match="NotAnIterator"): + _ = experimental_iterators.NotAnIterator + + +@pytest.mark.parametrize("name", sorted(iterators.__all__)) +def test_reexported_at_top_level(name: str): + assert getattr(ngio, name) is getattr(iterators, name) + assert name in ngio.__all__ diff --git a/tests/unit/iterators/test_feature_iterator.py b/tests/unit/iterators/test_feature_iterator.py index dc2c99aa..19dc71cd 100644 --- a/tests/unit/iterators/test_feature_iterator.py +++ b/tests/unit/iterators/test_feature_iterator.py @@ -3,7 +3,7 @@ from zarr.storage import MemoryStore from ngio import create_ome_zarr_from_array -from ngio.experimental.iterators import FeatureExtractorIterator +from ngio.iterators import FeatureExtractorIterator def _build_iterator() -> FeatureExtractorIterator: diff --git a/tests/unit/iterators/test_iterators.py b/tests/unit/iterators/test_iterators.py index 1ea63639..3e257fbf 100644 --- a/tests/unit/iterators/test_iterators.py +++ b/tests/unit/iterators/test_iterators.py @@ -6,7 +6,7 @@ from zarr.storage import MemoryStore from ngio import open_ome_zarr_container -from ngio.experimental.iterators import ( +from ngio.iterators import ( FeatureExtractorIterator, ImageProcessingIterator, MaskedSegmentationIterator, diff --git a/tests/unit/iterators/test_rois_utils.py b/tests/unit/iterators/test_rois_utils.py index de826eea..72df3ce0 100644 --- a/tests/unit/iterators/test_rois_utils.py +++ b/tests/unit/iterators/test_rois_utils.py @@ -3,7 +3,7 @@ from zarr.storage import MemoryStore from ngio import Roi, create_ome_zarr_from_array -from ngio.experimental.iterators._rois_utils import by_chunks, grid +from ngio.iterators._rois_utils import by_chunks, grid from ngio.utils import NgioValueError