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.
- bioimage_py-0.1.0/LICENSE +21 -0
- bioimage_py-0.1.0/PKG-INFO +89 -0
- bioimage_py-0.1.0/README.md +11 -0
- bioimage_py-0.1.0/bioimage_py/__init__.py +29 -0
- bioimage_py-0.1.0/bioimage_py/__version__.py +2 -0
- bioimage_py-0.1.0/bioimage_py/_indexing.py +115 -0
- bioimage_py-0.1.0/bioimage_py/copy.py +149 -0
- bioimage_py-0.1.0/bioimage_py/downsample.py +96 -0
- bioimage_py-0.1.0/bioimage_py/filters/__init__.py +14 -0
- bioimage_py-0.1.0/bioimage_py/filters/filters.py +265 -0
- bioimage_py-0.1.0/bioimage_py/io/__init__.py +26 -0
- bioimage_py-0.1.0/bioimage_py/io/_util.py +75 -0
- bioimage_py-0.1.0/bioimage_py/io/files.py +79 -0
- bioimage_py-0.1.0/bioimage_py/io/image_stack_wrapper.py +291 -0
- bioimage_py-0.1.0/bioimage_py/io/knossos_wrapper.py +168 -0
- bioimage_py-0.1.0/bioimage_py/io/mrc_wrapper.py +119 -0
- bioimage_py-0.1.0/bioimage_py/io/msr_wrapper.py +206 -0
- bioimage_py-0.1.0/bioimage_py/io/nifti_wrapper.py +110 -0
- bioimage_py-0.1.0/bioimage_py/io/registry.py +124 -0
- bioimage_py-0.1.0/bioimage_py/morphology/__init__.py +5 -0
- bioimage_py-0.1.0/bioimage_py/morphology/morphology.py +221 -0
- bioimage_py-0.1.0/bioimage_py/morphology/regionprops.py +333 -0
- bioimage_py-0.1.0/bioimage_py/runner/__init__.py +17 -0
- bioimage_py-0.1.0/bioimage_py/runner/_harness.py +142 -0
- bioimage_py-0.1.0/bioimage_py/runner/base.py +427 -0
- bioimage_py-0.1.0/bioimage_py/runner/config.py +80 -0
- bioimage_py-0.1.0/bioimage_py/runner/distributed.py +891 -0
- bioimage_py-0.1.0/bioimage_py/runner/factory.py +34 -0
- bioimage_py-0.1.0/bioimage_py/segmentation/__init__.py +5 -0
- bioimage_py-0.1.0/bioimage_py/segmentation/label.py +249 -0
- bioimage_py-0.1.0/bioimage_py/segmentation/watershed.py +169 -0
- bioimage_py-0.1.0/bioimage_py/sources/__init__.py +23 -0
- bioimage_py-0.1.0/bioimage_py/sources/array_source.py +109 -0
- bioimage_py-0.1.0/bioimage_py/sources/base.py +117 -0
- bioimage_py-0.1.0/bioimage_py/sources/cloudvolume_source.py +170 -0
- bioimage_py-0.1.0/bioimage_py/sources/dispatch.py +132 -0
- bioimage_py-0.1.0/bioimage_py/sources/file_source.py +144 -0
- bioimage_py-0.1.0/bioimage_py/sources/webknossos_source.py +214 -0
- bioimage_py-0.1.0/bioimage_py/stats/__init__.py +4 -0
- bioimage_py-0.1.0/bioimage_py/stats/stats.py +320 -0
- bioimage_py-0.1.0/bioimage_py/transformation.py +231 -0
- bioimage_py-0.1.0/bioimage_py/util.py +260 -0
- bioimage_py-0.1.0/bioimage_py/wrapper/__init__.py +29 -0
- bioimage_py-0.1.0/bioimage_py/wrapper/affine.py +122 -0
- bioimage_py-0.1.0/bioimage_py/wrapper/base.py +309 -0
- bioimage_py-0.1.0/bioimage_py/wrapper/generic.py +239 -0
- bioimage_py-0.1.0/bioimage_py/wrapper/resize.py +143 -0
- bioimage_py-0.1.0/bioimage_py.egg-info/PKG-INFO +89 -0
- bioimage_py-0.1.0/bioimage_py.egg-info/SOURCES.txt +80 -0
- bioimage_py-0.1.0/bioimage_py.egg-info/dependency_links.txt +1 -0
- bioimage_py-0.1.0/bioimage_py.egg-info/requires.txt +58 -0
- bioimage_py-0.1.0/bioimage_py.egg-info/top_level.txt +1 -0
- bioimage_py-0.1.0/pyproject.toml +44 -0
- bioimage_py-0.1.0/setup.cfg +15 -0
- bioimage_py-0.1.0/tests/test_affine_source.py +97 -0
- bioimage_py-0.1.0/tests/test_copy.py +84 -0
- bioimage_py-0.1.0/tests/test_downsample.py +76 -0
- bioimage_py-0.1.0/tests/test_filters.py +111 -0
- bioimage_py-0.1.0/tests/test_io_registry.py +60 -0
- bioimage_py-0.1.0/tests/test_io_sources.py +350 -0
- bioimage_py-0.1.0/tests/test_io_util.py +107 -0
- bioimage_py-0.1.0/tests/test_morphology.py +80 -0
- bioimage_py-0.1.0/tests/test_morphology_features.py +181 -0
- bioimage_py-0.1.0/tests/test_n5_io.py +32 -0
- bioimage_py-0.1.0/tests/test_op_rerun.py +88 -0
- bioimage_py-0.1.0/tests/test_resized_source.py +79 -0
- bioimage_py-0.1.0/tests/test_runner_failures.py +118 -0
- bioimage_py-0.1.0/tests/test_runner_map.py +58 -0
- bioimage_py-0.1.0/tests/test_runner_parity.py +124 -0
- bioimage_py-0.1.0/tests/test_runner_run.py +30 -0
- bioimage_py-0.1.0/tests/test_slurm_runner.py +170 -0
- bioimage_py-0.1.0/tests/test_slurm_submit_failure.py +39 -0
- bioimage_py-0.1.0/tests/test_source_indexing.py +100 -0
- bioimage_py-0.1.0/tests/test_sources.py +69 -0
- bioimage_py-0.1.0/tests/test_stats.py +73 -0
- bioimage_py-0.1.0/tests/test_transformation.py +65 -0
- bioimage_py-0.1.0/tests/test_transformation_wrappers.py +138 -0
- bioimage_py-0.1.0/tests/test_util.py +50 -0
- bioimage_py-0.1.0/tests/test_webknossos_local.py +48 -0
- bioimage_py-0.1.0/tests/test_wrapper_generic.py +131 -0
- 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,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
|
+
]
|