bioimage-py 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. bioimage_py-0.1.0/LICENSE +21 -0
  2. bioimage_py-0.1.0/PKG-INFO +89 -0
  3. bioimage_py-0.1.0/README.md +11 -0
  4. bioimage_py-0.1.0/bioimage_py/__init__.py +29 -0
  5. bioimage_py-0.1.0/bioimage_py/__version__.py +2 -0
  6. bioimage_py-0.1.0/bioimage_py/_indexing.py +115 -0
  7. bioimage_py-0.1.0/bioimage_py/copy.py +149 -0
  8. bioimage_py-0.1.0/bioimage_py/downsample.py +96 -0
  9. bioimage_py-0.1.0/bioimage_py/filters/__init__.py +14 -0
  10. bioimage_py-0.1.0/bioimage_py/filters/filters.py +265 -0
  11. bioimage_py-0.1.0/bioimage_py/io/__init__.py +26 -0
  12. bioimage_py-0.1.0/bioimage_py/io/_util.py +75 -0
  13. bioimage_py-0.1.0/bioimage_py/io/files.py +79 -0
  14. bioimage_py-0.1.0/bioimage_py/io/image_stack_wrapper.py +291 -0
  15. bioimage_py-0.1.0/bioimage_py/io/knossos_wrapper.py +168 -0
  16. bioimage_py-0.1.0/bioimage_py/io/mrc_wrapper.py +119 -0
  17. bioimage_py-0.1.0/bioimage_py/io/msr_wrapper.py +206 -0
  18. bioimage_py-0.1.0/bioimage_py/io/nifti_wrapper.py +110 -0
  19. bioimage_py-0.1.0/bioimage_py/io/registry.py +124 -0
  20. bioimage_py-0.1.0/bioimage_py/morphology/__init__.py +5 -0
  21. bioimage_py-0.1.0/bioimage_py/morphology/morphology.py +221 -0
  22. bioimage_py-0.1.0/bioimage_py/morphology/regionprops.py +333 -0
  23. bioimage_py-0.1.0/bioimage_py/runner/__init__.py +17 -0
  24. bioimage_py-0.1.0/bioimage_py/runner/_harness.py +142 -0
  25. bioimage_py-0.1.0/bioimage_py/runner/base.py +427 -0
  26. bioimage_py-0.1.0/bioimage_py/runner/config.py +80 -0
  27. bioimage_py-0.1.0/bioimage_py/runner/distributed.py +891 -0
  28. bioimage_py-0.1.0/bioimage_py/runner/factory.py +34 -0
  29. bioimage_py-0.1.0/bioimage_py/segmentation/__init__.py +5 -0
  30. bioimage_py-0.1.0/bioimage_py/segmentation/label.py +249 -0
  31. bioimage_py-0.1.0/bioimage_py/segmentation/watershed.py +169 -0
  32. bioimage_py-0.1.0/bioimage_py/sources/__init__.py +23 -0
  33. bioimage_py-0.1.0/bioimage_py/sources/array_source.py +109 -0
  34. bioimage_py-0.1.0/bioimage_py/sources/base.py +117 -0
  35. bioimage_py-0.1.0/bioimage_py/sources/cloudvolume_source.py +170 -0
  36. bioimage_py-0.1.0/bioimage_py/sources/dispatch.py +132 -0
  37. bioimage_py-0.1.0/bioimage_py/sources/file_source.py +144 -0
  38. bioimage_py-0.1.0/bioimage_py/sources/webknossos_source.py +214 -0
  39. bioimage_py-0.1.0/bioimage_py/stats/__init__.py +4 -0
  40. bioimage_py-0.1.0/bioimage_py/stats/stats.py +320 -0
  41. bioimage_py-0.1.0/bioimage_py/transformation.py +231 -0
  42. bioimage_py-0.1.0/bioimage_py/util.py +260 -0
  43. bioimage_py-0.1.0/bioimage_py/wrapper/__init__.py +29 -0
  44. bioimage_py-0.1.0/bioimage_py/wrapper/affine.py +122 -0
  45. bioimage_py-0.1.0/bioimage_py/wrapper/base.py +309 -0
  46. bioimage_py-0.1.0/bioimage_py/wrapper/generic.py +239 -0
  47. bioimage_py-0.1.0/bioimage_py/wrapper/resize.py +143 -0
  48. bioimage_py-0.1.0/bioimage_py.egg-info/PKG-INFO +89 -0
  49. bioimage_py-0.1.0/bioimage_py.egg-info/SOURCES.txt +80 -0
  50. bioimage_py-0.1.0/bioimage_py.egg-info/dependency_links.txt +1 -0
  51. bioimage_py-0.1.0/bioimage_py.egg-info/requires.txt +58 -0
  52. bioimage_py-0.1.0/bioimage_py.egg-info/top_level.txt +1 -0
  53. bioimage_py-0.1.0/pyproject.toml +44 -0
  54. bioimage_py-0.1.0/setup.cfg +15 -0
  55. bioimage_py-0.1.0/tests/test_affine_source.py +97 -0
  56. bioimage_py-0.1.0/tests/test_copy.py +84 -0
  57. bioimage_py-0.1.0/tests/test_downsample.py +76 -0
  58. bioimage_py-0.1.0/tests/test_filters.py +111 -0
  59. bioimage_py-0.1.0/tests/test_io_registry.py +60 -0
  60. bioimage_py-0.1.0/tests/test_io_sources.py +350 -0
  61. bioimage_py-0.1.0/tests/test_io_util.py +107 -0
  62. bioimage_py-0.1.0/tests/test_morphology.py +80 -0
  63. bioimage_py-0.1.0/tests/test_morphology_features.py +181 -0
  64. bioimage_py-0.1.0/tests/test_n5_io.py +32 -0
  65. bioimage_py-0.1.0/tests/test_op_rerun.py +88 -0
  66. bioimage_py-0.1.0/tests/test_resized_source.py +79 -0
  67. bioimage_py-0.1.0/tests/test_runner_failures.py +118 -0
  68. bioimage_py-0.1.0/tests/test_runner_map.py +58 -0
  69. bioimage_py-0.1.0/tests/test_runner_parity.py +124 -0
  70. bioimage_py-0.1.0/tests/test_runner_run.py +30 -0
  71. bioimage_py-0.1.0/tests/test_slurm_runner.py +170 -0
  72. bioimage_py-0.1.0/tests/test_slurm_submit_failure.py +39 -0
  73. bioimage_py-0.1.0/tests/test_source_indexing.py +100 -0
  74. bioimage_py-0.1.0/tests/test_sources.py +69 -0
  75. bioimage_py-0.1.0/tests/test_stats.py +73 -0
  76. bioimage_py-0.1.0/tests/test_transformation.py +65 -0
  77. bioimage_py-0.1.0/tests/test_transformation_wrappers.py +138 -0
  78. bioimage_py-0.1.0/tests/test_util.py +50 -0
  79. bioimage_py-0.1.0/tests/test_webknossos_local.py +48 -0
  80. bioimage_py-0.1.0/tests/test_wrapper_generic.py +131 -0
  81. bioimage_py-0.1.0/tests/test_write_safety.py +104 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 computational-cell-analytics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: bioimage_py
3
+ Version: 0.1.0
4
+ Summary: Efficient, parallel, and distributed implementation of image analysis and segmentation functionality.
5
+ Author-email: Constantin Pape <constantin.pape@cs.uni-goettingen.de>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 computational-cell-analytics
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Requires-Python: >=3.11
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: numpy
32
+ Requires-Dist: pandas
33
+ Requires-Dist: scikit-image>=0.20
34
+ Requires-Dist: cloudpickle
35
+ Requires-Dist: tqdm
36
+ Requires-Dist: threadpoolctl
37
+ Requires-Dist: bioimage-cpp
38
+ Provides-Extra: io
39
+ Requires-Dist: zarr>=3; extra == "io"
40
+ Requires-Dist: z5py; extra == "io"
41
+ Provides-Extra: hdf5
42
+ Requires-Dist: h5py; extra == "hdf5"
43
+ Provides-Extra: mrc
44
+ Requires-Dist: mrcfile; extra == "mrc"
45
+ Provides-Extra: nifti
46
+ Requires-Dist: nibabel; extra == "nifti"
47
+ Provides-Extra: imagestack
48
+ Requires-Dist: imageio; extra == "imagestack"
49
+ Requires-Dist: tifffile; extra == "imagestack"
50
+ Provides-Extra: msr
51
+ Requires-Dist: msr-reader; extra == "msr"
52
+ Provides-Extra: cloudvolume
53
+ Requires-Dist: cloud-volume; extra == "cloudvolume"
54
+ Provides-Extra: webknossos
55
+ Requires-Dist: webknossos; extra == "webknossos"
56
+ Provides-Extra: io-all
57
+ Requires-Dist: zarr>=3; extra == "io-all"
58
+ Requires-Dist: z5py; extra == "io-all"
59
+ Requires-Dist: h5py; extra == "io-all"
60
+ Requires-Dist: mrcfile; extra == "io-all"
61
+ Requires-Dist: nibabel; extra == "io-all"
62
+ Requires-Dist: imageio; extra == "io-all"
63
+ Requires-Dist: tifffile; extra == "io-all"
64
+ Requires-Dist: cloud-volume; extra == "io-all"
65
+ Requires-Dist: webknossos; extra == "io-all"
66
+ Provides-Extra: test
67
+ Requires-Dist: pytest; extra == "test"
68
+ Requires-Dist: zarr>=3; extra == "test"
69
+ Requires-Dist: scikit-image; extra == "test"
70
+ Requires-Dist: scipy; extra == "test"
71
+ Requires-Dist: openpyxl; extra == "test"
72
+ Provides-Extra: dev
73
+ Requires-Dist: flake8; extra == "dev"
74
+ Requires-Dist: pyflakes; extra == "dev"
75
+ Provides-Extra: docs
76
+ Requires-Dist: pdoc; extra == "docs"
77
+ Dynamic: license-file
78
+
79
+ # bioimage-py
80
+
81
+ Efficient, parallel, and distributed implementation of image analysis and segmentation functionality.
82
+
83
+ ```bash
84
+ python -m pip install -e .
85
+ ```
86
+
87
+ See the [documentation](https://computational-cell-analytics.github.io/bioimage-py/)
88
+ for [installation](https://computational-cell-analytics.github.io/bioimage-py/bioimage_py.html#installation)
89
+ and [usage examples](https://computational-cell-analytics.github.io/bioimage-py/bioimage_py.html#usage).
@@ -0,0 +1,11 @@
1
+ # bioimage-py
2
+
3
+ Efficient, parallel, and distributed implementation of image analysis and segmentation functionality.
4
+
5
+ ```bash
6
+ python -m pip install -e .
7
+ ```
8
+
9
+ See the [documentation](https://computational-cell-analytics.github.io/bioimage-py/)
10
+ for [installation](https://computational-cell-analytics.github.io/bioimage-py/bioimage_py.html#installation)
11
+ and [usage examples](https://computational-cell-analytics.github.io/bioimage-py/bioimage_py.html#usage).
@@ -0,0 +1,29 @@
1
+ """bioimage_py: efficient, parallel, and distributed image analysis and segmentation.
2
+
3
+ .. include:: ../docs/installation.md
4
+ .. include:: ../docs/usage.md
5
+ """
6
+ from . import filters, io, morphology, segmentation, stats # noqa: F401
7
+ from .copy import copy
8
+ from .downsample import downsample
9
+ from .runner import get_runner
10
+ from .sources import as_source, open_cloudvolume, open_source, open_webknossos
11
+ from .util import to_roi
12
+ from .__version__ import __version__
13
+
14
+ __all__ = [
15
+ "__version__",
16
+ "stats",
17
+ "filters",
18
+ "segmentation",
19
+ "morphology",
20
+ "io",
21
+ "copy",
22
+ "downsample",
23
+ "get_runner",
24
+ "as_source",
25
+ "open_source",
26
+ "open_cloudvolume",
27
+ "open_webknossos",
28
+ "to_roi",
29
+ ]
@@ -0,0 +1,2 @@
1
+ """Single source of truth for the package version."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,115 @@
1
+ """Index-normalization helpers shared across the library (ported from ``elf.util``).
2
+
3
+ This is a leaf module: it imports only the standard library and numpy, so it can be used by the
4
+ ``sources`` layer, the ``io`` wrappers and elsewhere without risking an import cycle. It turns
5
+ numpy-style basic indices (int / slice / ellipsis / tuple) into a full tuple of in-bounds slices
6
+ plus the axes that should be squeezed afterwards.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import numbers
11
+ from typing import Any, Tuple
12
+
13
+ import numpy as np
14
+
15
+
16
+ def slice_to_start_stop(s: slice, size: int) -> slice:
17
+ """Normalize a slice so its start/stop are positive, in-bounds coordinates."""
18
+ if s.step not in (None, 1):
19
+ raise ValueError("Nontrivial steps are not supported")
20
+
21
+ if s.start is None:
22
+ start = 0
23
+ elif -size <= s.start < 0:
24
+ start = size + s.start
25
+ elif s.start < -size or s.start >= size:
26
+ return slice(None, 0)
27
+ else:
28
+ start = s.start
29
+
30
+ if s.stop is None or s.stop > size:
31
+ stop = size
32
+ elif s.stop < 0:
33
+ stop = size + s.stop
34
+ else:
35
+ stop = s.stop
36
+
37
+ if stop < 1:
38
+ return slice(None, 0)
39
+
40
+ # Clamp so a reversed slice (start > stop) yields an empty region rather than a negative
41
+ # extent (which crashes the assemble-from-pieces wrappers downstream).
42
+ return slice(start, max(start, stop))
43
+
44
+
45
+ def int_to_start_stop(i: int, size: int) -> slice:
46
+ """Return the unit slice corresponding to an integer coordinate."""
47
+ if -size < i < 0:
48
+ start = i + size
49
+ elif i >= size or i < -size:
50
+ raise ValueError("Index ({}) out of range (0-{})".format(i, size - 1))
51
+ else:
52
+ start = i
53
+ return slice(start, start + 1)
54
+
55
+
56
+ def normalize_index(index: Any, shape: Tuple[int, ...]) -> Tuple[Tuple[slice, ...], Tuple[int, ...]]:
57
+ """Normalize an index into a full tuple of in-bounds slices plus the axes to squeeze.
58
+
59
+ The index may be an integer, a slice, an ellipsis, or a tuple thereof. It is expanded to one
60
+ entry per axis with positive start/stop coordinates; integer entries record the axis to squeeze.
61
+
62
+ Args:
63
+ index: The index to normalize.
64
+ shape: The shape of the array-like object being indexed.
65
+
66
+ Returns:
67
+ A tuple of the normalized slices and the axes that should be squeezed after indexing.
68
+ """
69
+ type_msg = (
70
+ "Advanced selection inappropriate. "
71
+ "Only numbers, slices (`:`), and ellipsis (`...`) are valid indices (or tuples thereof)"
72
+ )
73
+
74
+ if isinstance(index, tuple):
75
+ slices_lst = list(index)
76
+ elif isinstance(index, (numbers.Number, slice, type(Ellipsis))):
77
+ slices_lst = [index]
78
+ else:
79
+ raise TypeError(type_msg)
80
+
81
+ ndim = len(shape)
82
+ if len([item for item in slices_lst if item != Ellipsis]) > ndim:
83
+ raise TypeError("Argument sequence too long")
84
+ elif len(slices_lst) < ndim and Ellipsis not in slices_lst:
85
+ slices_lst.append(Ellipsis)
86
+
87
+ normalized = []
88
+ found_ellipsis = False
89
+ squeeze = []
90
+ for item in slices_lst:
91
+ d = len(normalized)
92
+ if isinstance(item, slice):
93
+ normalized.append(slice_to_start_stop(item, shape[d]))
94
+ elif isinstance(item, numbers.Number):
95
+ squeeze.append(d)
96
+ normalized.append(int_to_start_stop(int(item), shape[d]))
97
+ elif isinstance(item, type(Ellipsis)):
98
+ if found_ellipsis:
99
+ raise ValueError("Only one ellipsis may be used")
100
+ found_ellipsis = True
101
+ while len(normalized) + (len(slices_lst) - d - 1) < ndim:
102
+ normalized.append(slice(0, shape[len(normalized)]))
103
+ else:
104
+ raise TypeError(type_msg)
105
+ return tuple(normalized), tuple(squeeze)
106
+
107
+
108
+ def squeeze_singletons(item: np.ndarray, to_squeeze: Tuple[int, ...]) -> np.ndarray:
109
+ """Squeeze the axes recorded by :func:`normalize_index` from a read result."""
110
+ if len(to_squeeze) == len(item.shape):
111
+ return item.flatten()[0]
112
+ elif to_squeeze:
113
+ return item.squeeze(to_squeeze)
114
+ else:
115
+ return item
@@ -0,0 +1,149 @@
1
+ """Block-wise copy of one source into another.
2
+
3
+ Copies an input source into a writable output source block-wise, reusing the runner machinery.
4
+ This is the minimal array-output operation: there is no halo and no per-block computation, just a
5
+ read of each block from the input and a write to the output. Typical uses are converting between
6
+ storage formats (e.g. a tiff stack to zarr) and persisting an on-the-fly transformation (a
7
+ ``wrapper`` source) to file so the result is stored rather than recomputed on every read.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import Optional, Sequence, Tuple
12
+
13
+ import numpy as np
14
+
15
+ from .runner import get_runner
16
+ from .runner.config import RunnerConfig
17
+ from .sources import Source, SourceLike, as_source
18
+ from .util import BlockDescriptor, ComputeFn, check_rerun_args, to_roi
19
+
20
+ __all__ = ["copy"]
21
+
22
+
23
+ def _full_roi(ndim: int) -> Tuple[slice, ...]:
24
+ """Return a slicing that selects the whole array."""
25
+ return tuple(slice(None) for _ in range(ndim))
26
+
27
+
28
+ def _same_array(a: Source, b: Source) -> bool:
29
+ """Return whether two sources wrap the same underlying array object."""
30
+ return getattr(a, "array", None) is getattr(b, "array", object())
31
+
32
+
33
+ def _make_compute() -> ComputeFn:
34
+ """Build the per-block copy function (no captured state, so trivially cloudpickle-safe)."""
35
+
36
+ def _compute(block: BlockDescriptor, inputs: Sequence[Source], outputs: Sequence[Source],
37
+ mask: Optional[Source]) -> None:
38
+ input_, output_ = inputs[0], outputs[0]
39
+ roi = to_roi(block)
40
+ if mask is not None:
41
+ m = mask[roi].astype(bool)
42
+ if not m.any():
43
+ return None
44
+ # Keep out-of-mask voxels of the output unchanged.
45
+ output_[roi] = np.where(m, input_[roi], output_[roi])
46
+ return None
47
+ output_[roi] = input_[roi]
48
+ return None
49
+
50
+ return _compute
51
+
52
+
53
+ def _copy_source(
54
+ input: SourceLike,
55
+ output: Optional[SourceLike],
56
+ *,
57
+ block_shape: Optional[Tuple[int, ...]],
58
+ job_type: str,
59
+ job_config: Optional[RunnerConfig],
60
+ num_workers: int,
61
+ mask: Optional[SourceLike],
62
+ name: str,
63
+ block_ids: Optional[Sequence[int]] = None,
64
+ resume_from: Optional[str] = None,
65
+ ) -> SourceLike:
66
+ """Materialize ``input`` into ``output`` block-wise (shared by :func:`copy` and downsample).
67
+
68
+ Handles output allocation (a numpy array for local execution, a required file-backed array for
69
+ distributed execution), the direct (whole-array) fast path, and the runner dispatch. The output
70
+ shape and dtype are taken from ``input`` (so a shape-changing wrapper input is handled too).
71
+ """
72
+ check_rerun_args(job_type, resume_from, block_ids)
73
+ src = as_source(input)
74
+ ndim = src.ndim
75
+ # A subset/resume rerun is block-wise, so it cannot use the direct (whole-array) path.
76
+ direct = (job_type == "local" and num_workers == 1 and block_shape is None and mask is None
77
+ and block_ids is None and resume_from is None)
78
+
79
+ if output is None:
80
+ if job_type != "local":
81
+ raise ValueError(
82
+ f"'output' is required for distributed execution (job_type={job_type!r}); "
83
+ "pass a file-backed (zarr/n5) output array."
84
+ )
85
+ out_array: SourceLike = np.zeros(tuple(src.shape), dtype=src.dtype)
86
+ else:
87
+ out_array = output
88
+
89
+ out = as_source(out_array)
90
+ if not direct and _same_array(out, src):
91
+ raise ValueError(f"Block-wise {name} needs 'output' to differ from 'input'.")
92
+
93
+ if direct:
94
+ out[_full_roi(out.ndim)] = src[_full_roi(ndim)]
95
+ return out_array
96
+
97
+ compute = _make_compute()
98
+ runner = get_runner(job_type, job_config)
99
+ runner.run(compute, [src], outputs=[out], block_shape=block_shape,
100
+ mask=mask, num_workers=num_workers, block_ids=block_ids, resume_from=resume_from,
101
+ name=name)
102
+ return out_array
103
+
104
+
105
+ def copy(
106
+ input: SourceLike,
107
+ output: Optional[SourceLike] = None,
108
+ *,
109
+ block_shape: Optional[Tuple[int, ...]] = None,
110
+ job_type: str = "local",
111
+ job_config: Optional[RunnerConfig] = None,
112
+ num_workers: int = 1,
113
+ mask: Optional[SourceLike] = None,
114
+ block_ids: Optional[Sequence[int]] = None,
115
+ resume_from: Optional[str] = None,
116
+ ) -> SourceLike:
117
+ """Copy a source into an output, block-wise.
118
+
119
+ The data is read from ``input`` and written into ``output`` one block at a time. The input may
120
+ be any source, including a read-only ``FileSource`` (e.g. a tiff stack) or a ``wrapper`` source
121
+ whose transformation is computed on read; copying it materializes the transformed data to the
122
+ output. The data is written into the output as-is, so the output array's dtype governs and a
123
+ cast is applied on assignment when it differs from the input dtype.
124
+
125
+ Args:
126
+ input: The input data to copy (a numpy/zarr/n5 array or a `Source`).
127
+ output: The output array to write into. Optional for local execution — a numpy array
128
+ matching the input shape and dtype is allocated and returned if omitted; **required**
129
+ for distributed execution, where it must be a writable, file-backed (zarr/n5) array.
130
+ block_shape: Shape of the processing blocks. Defaults to the input chunk shape; required
131
+ for unchunked data.
132
+ job_type: Execution backend: one of ``"local"``, ``"subprocess"`` or ``"slurm"``.
133
+ job_config: Backend configuration (a `RunnerConfig` / `SlurmConfig`).
134
+ num_workers: Number of parallel workers (threads for ``local``, tasks for distributed
135
+ backends).
136
+ mask: Optional binary mask; only voxels within the mask are copied (out-of-mask output
137
+ voxels are left unchanged).
138
+ block_ids: Restrict processing to these block ids (e.g. to re-run previously failed blocks
139
+ into the existing ``output``). Mutually exclusive with ``resume_from``.
140
+ resume_from: Distributed only; the preserved temp folder of a failed run to resume (see
141
+ ``runner.run``); the missing blocks are written into ``output``. Mutually exclusive
142
+ with ``block_ids``.
143
+
144
+ Returns:
145
+ The output array (the provided ``output``, or a newly allocated numpy array).
146
+ """
147
+ return _copy_source(input, output, block_shape=block_shape, job_type=job_type,
148
+ job_config=job_config, num_workers=num_workers, mask=mask, name="copy",
149
+ block_ids=block_ids, resume_from=resume_from)
@@ -0,0 +1,96 @@
1
+ """Block-wise downsampling of a source.
2
+
3
+ Downsampling wraps the input in a :class:`~bioimage_py.wrapper.ResizedSource` at the downscaled
4
+ target shape and copies it block-wise into the output, reusing the copy machinery. Because the
5
+ wrapper resamples on read, this is exactly a block-wise copy of the resized source.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from numbers import Integral
10
+ from typing import Optional, Sequence, Tuple, Union
11
+
12
+ from .copy import _copy_source
13
+ from .runner.config import RunnerConfig
14
+ from .sources import SourceLike, as_source
15
+ from .util import downscale_shape
16
+ from .wrapper import ResizedSource
17
+
18
+ __all__ = ["downsample"]
19
+
20
+ # A downscaling factor: a single int (isotropic) or a per-axis sequence of ints.
21
+ ScaleFactor = Union[int, Sequence[int]]
22
+
23
+
24
+ def downsample(
25
+ input: SourceLike,
26
+ scale_factor: ScaleFactor,
27
+ output: Optional[SourceLike] = None,
28
+ *,
29
+ order: int = 0,
30
+ anti_aliasing: bool = False,
31
+ block_shape: Optional[Tuple[int, ...]] = None,
32
+ job_type: str = "local",
33
+ job_config: Optional[RunnerConfig] = None,
34
+ num_workers: int = 1,
35
+ mask: Optional[SourceLike] = None,
36
+ block_ids: Optional[Sequence[int]] = None,
37
+ resume_from: Optional[str] = None,
38
+ ) -> SourceLike:
39
+ """Downsample a source by an integer factor, block-wise.
40
+
41
+ The input is wrapped in a :class:`~bioimage_py.wrapper.ResizedSource` at the downscaled shape
42
+ (computed with :func:`~bioimage_py.util.downscale_shape`, ceil mode) and copied into the output.
43
+
44
+ The defaults (``order=0`` nearest, ``anti_aliasing=False``) are label-safe — they preserve the
45
+ input values and are appropriate for segmentations. For intensity / image data pass ``order=1``
46
+ (or higher) and ``anti_aliasing=True`` for a smooth, alias-free downsample.
47
+
48
+ Args:
49
+ input: The input data to downsample (a numpy/zarr/n5 array or a `Source`). 2D or 3D.
50
+ scale_factor: The downscaling factor: a single int (isotropic) or a per-axis sequence of
51
+ ints. Each factor must be ``>= 1`` (1 leaves that axis unchanged).
52
+ output: The output array to write into. Optional for local execution — a numpy array of the
53
+ downscaled shape and the input dtype is allocated and returned if omitted; **required**
54
+ for distributed execution, where it (and the input) must be file-backed (zarr/n5).
55
+ order: The interpolation order (0 to 5). Use ``0`` (nearest) for label data.
56
+ anti_aliasing: Whether to Gaussian pre-smooth before sampling to avoid aliasing.
57
+ Recommended for image data; leave ``False`` for labels.
58
+ block_shape: Shape of the processing blocks (in the downscaled output space). Defaults to
59
+ the resized source's chunk shape; required for unchunked data.
60
+ job_type: Execution backend: one of ``"local"``, ``"subprocess"`` or ``"slurm"``.
61
+ job_config: Backend configuration (a `RunnerConfig` / `SlurmConfig`).
62
+ num_workers: Number of parallel workers (threads for ``local``, tasks for distributed
63
+ backends).
64
+ mask: Optional binary mask in the downscaled output space; only voxels within the mask are
65
+ written (out-of-mask output voxels are left unchanged).
66
+ block_ids: Restrict processing to these block ids (in the downscaled output space), e.g. to
67
+ re-run previously failed blocks into the existing ``output``. Mutually exclusive with
68
+ ``resume_from``.
69
+ resume_from: Distributed only; the preserved temp folder of a failed run to resume (see
70
+ ``runner.run``); the missing blocks are written into ``output``. Mutually exclusive
71
+ with ``block_ids``.
72
+
73
+ Returns:
74
+ The output array (the provided ``output``, or a newly allocated numpy array).
75
+ """
76
+ src = as_source(input)
77
+ ndim = src.ndim
78
+ if isinstance(scale_factor, Integral):
79
+ factors: Tuple[int, ...] = (int(scale_factor),) * ndim
80
+ else:
81
+ factors = tuple(int(f) for f in scale_factor)
82
+ if len(factors) != ndim:
83
+ raise ValueError(
84
+ f"scale_factor {scale_factor} does not match the input dimensionality {ndim}."
85
+ )
86
+ if any(f < 1 for f in factors):
87
+ raise ValueError(
88
+ f"downsample requires scale factors >= 1, got {factors}; "
89
+ "use a ResizedSource directly to upsample."
90
+ )
91
+
92
+ target_shape = downscale_shape(src.shape, factors)
93
+ wrapped = ResizedSource(src, target_shape, order=order, anti_aliasing=anti_aliasing)
94
+ return _copy_source(wrapped, output, block_shape=block_shape, job_type=job_type,
95
+ job_config=job_config, num_workers=num_workers, mask=mask, name="downsample",
96
+ block_ids=block_ids, resume_from=resume_from)
@@ -0,0 +1,14 @@
1
+ """Block-wise image filters."""
2
+ from .filters import (
3
+ apply_filter,
4
+ gaussian_gradient_magnitude,
5
+ gaussian_smoothing,
6
+ laplacian_of_gaussian,
7
+ )
8
+
9
+ __all__ = [
10
+ "apply_filter",
11
+ "gaussian_smoothing",
12
+ "gaussian_gradient_magnitude",
13
+ "laplacian_of_gaussian",
14
+ ]