swcgeom 0.18.3__py3-none-any.whl → 0.19.1__py3-none-any.whl
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.
Potentially problematic release.
This version of swcgeom might be problematic. Click here for more details.
- swcgeom/analysis/feature_extractor.py +22 -24
- swcgeom/analysis/features.py +18 -40
- swcgeom/analysis/lmeasure.py +227 -323
- swcgeom/analysis/sholl.py +17 -23
- swcgeom/analysis/trunk.py +23 -28
- swcgeom/analysis/visualization.py +37 -44
- swcgeom/analysis/visualization3d.py +16 -25
- swcgeom/analysis/volume.py +33 -47
- swcgeom/core/__init__.py +1 -6
- swcgeom/core/branch.py +10 -17
- swcgeom/core/branch_tree.py +3 -2
- swcgeom/core/compartment.py +1 -1
- swcgeom/core/node.py +3 -6
- swcgeom/core/path.py +11 -16
- swcgeom/core/population.py +32 -51
- swcgeom/core/swc.py +25 -16
- swcgeom/core/swc_utils/__init__.py +4 -6
- swcgeom/core/swc_utils/assembler.py +5 -12
- swcgeom/core/swc_utils/base.py +40 -31
- swcgeom/core/swc_utils/checker.py +3 -8
- swcgeom/core/swc_utils/io.py +32 -47
- swcgeom/core/swc_utils/normalizer.py +17 -23
- swcgeom/core/swc_utils/subtree.py +13 -20
- swcgeom/core/tree.py +61 -51
- swcgeom/core/tree_utils.py +36 -49
- swcgeom/core/tree_utils_impl.py +4 -6
- swcgeom/images/augmentation.py +23 -39
- swcgeom/images/contrast.py +22 -46
- swcgeom/images/folder.py +32 -34
- swcgeom/images/io.py +108 -126
- swcgeom/transforms/base.py +28 -19
- swcgeom/transforms/branch.py +31 -41
- swcgeom/transforms/branch_tree.py +3 -1
- swcgeom/transforms/geometry.py +13 -4
- swcgeom/transforms/image_preprocess.py +2 -0
- swcgeom/transforms/image_stack.py +40 -35
- swcgeom/transforms/images.py +31 -24
- swcgeom/transforms/mst.py +27 -40
- swcgeom/transforms/neurolucida_asc.py +13 -13
- swcgeom/transforms/path.py +4 -0
- swcgeom/transforms/population.py +4 -0
- swcgeom/transforms/tree.py +16 -11
- swcgeom/transforms/tree_assembler.py +37 -54
- swcgeom/utils/download.py +7 -14
- swcgeom/utils/dsu.py +12 -0
- swcgeom/utils/ellipse.py +26 -14
- swcgeom/utils/file.py +8 -13
- swcgeom/utils/neuromorpho.py +78 -92
- swcgeom/utils/numpy_helper.py +15 -12
- swcgeom/utils/plotter_2d.py +10 -16
- swcgeom/utils/plotter_3d.py +7 -9
- swcgeom/utils/renderer.py +16 -8
- swcgeom/utils/sdf.py +12 -23
- swcgeom/utils/solid_geometry.py +58 -2
- swcgeom/utils/transforms.py +164 -100
- swcgeom/utils/volumetric_object.py +29 -53
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.1.dist-info}/METADATA +6 -5
- swcgeom-0.19.1.dist-info/RECORD +67 -0
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.1.dist-info}/WHEEL +1 -1
- swcgeom-0.18.3.dist-info/RECORD +0 -67
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.1.dist-info/licenses}/LICENSE +0 -0
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.1.dist-info}/top_level.txt +0 -0
swcgeom/images/contrast.py
CHANGED
|
@@ -15,12 +15,10 @@
|
|
|
15
15
|
|
|
16
16
|
"""The contrast of an image.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
-----
|
|
20
|
-
This is expremental code, and the API is subject to change.
|
|
18
|
+
NOTE: This is expremental code, and the API is subject to change.
|
|
21
19
|
"""
|
|
22
20
|
|
|
23
|
-
from typing import
|
|
21
|
+
from typing import overload
|
|
24
22
|
|
|
25
23
|
import numpy as np
|
|
26
24
|
import numpy.typing as npt
|
|
@@ -34,13 +32,11 @@ Array3D = npt.NDArray[np.float32]
|
|
|
34
32
|
def contrast_std(image: Array3D) -> float:
|
|
35
33
|
"""Get the std contrast of an image stack.
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
imgs : ndarray
|
|
35
|
+
Args:
|
|
36
|
+
imgs: ndarray
|
|
40
37
|
|
|
41
|
-
Returns
|
|
42
|
-
|
|
43
|
-
contrast : float
|
|
38
|
+
Returns:
|
|
39
|
+
contrast
|
|
44
40
|
"""
|
|
45
41
|
...
|
|
46
42
|
|
|
@@ -49,21 +45,17 @@ def contrast_std(image: Array3D) -> float:
|
|
|
49
45
|
def contrast_std(image: Array3D, contrast: float) -> Array3D:
|
|
50
46
|
"""Adjust the contrast of an image stack.
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
constrast : float
|
|
56
|
-
The contrast adjustment factor. 1.0 leaves the image unchanged.
|
|
48
|
+
Args:
|
|
49
|
+
imgs: ndarray
|
|
50
|
+
contrast: The contrast adjustment factor. 1.0 leaves the image unchanged.
|
|
57
51
|
|
|
58
|
-
Returns
|
|
59
|
-
|
|
60
|
-
imgs : ndarray
|
|
61
|
-
The adjusted image.
|
|
52
|
+
Returns:
|
|
53
|
+
imgs: The adjusted image.
|
|
62
54
|
"""
|
|
63
55
|
...
|
|
64
56
|
|
|
65
57
|
|
|
66
|
-
def contrast_std(image: Array3D, contrast:
|
|
58
|
+
def contrast_std(image: Array3D, contrast: float | None = None):
|
|
67
59
|
if contrast is None:
|
|
68
60
|
return np.std(image).item()
|
|
69
61
|
else:
|
|
@@ -73,15 +65,9 @@ def contrast_std(image: Array3D, contrast: Optional[float] = None):
|
|
|
73
65
|
def contrast_michelson(image: Array3D) -> float:
|
|
74
66
|
"""Get the Michelson contrast of an image stack.
|
|
75
67
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
imgs : ndarray
|
|
79
|
-
|
|
80
|
-
Returns
|
|
81
|
-
-------
|
|
82
|
-
contrast : float
|
|
68
|
+
Returns:
|
|
69
|
+
contrast: float
|
|
83
70
|
"""
|
|
84
|
-
|
|
85
71
|
vmax = np.max(image)
|
|
86
72
|
vmin = np.min(image)
|
|
87
73
|
return ((vmax - vmin) / (vmax + vmin)).item()
|
|
@@ -90,33 +76,23 @@ def contrast_michelson(image: Array3D) -> float:
|
|
|
90
76
|
def contrast_rms(imgs: npt.NDArray[np.float32]) -> float:
|
|
91
77
|
"""Get the RMS contrast of an image stack.
|
|
92
78
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
imgs : ndarray
|
|
96
|
-
|
|
97
|
-
Returns
|
|
98
|
-
-------
|
|
99
|
-
contrast : float
|
|
79
|
+
Returns:
|
|
80
|
+
contrast
|
|
100
81
|
"""
|
|
101
|
-
|
|
102
82
|
return np.sqrt(np.mean(imgs**2)).item()
|
|
103
83
|
|
|
104
84
|
|
|
105
85
|
def contrast_weber(imgs: Array3D, mask: npt.NDArray[np.bool_]) -> float:
|
|
106
86
|
"""Get the Weber contrast of an image stack.
|
|
107
87
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
The mask to segment the foreground and background. 1 for
|
|
113
|
-
foreground, 0 for background.
|
|
88
|
+
Args:
|
|
89
|
+
imgs: ndarray
|
|
90
|
+
mask: The mask to segment the foreground and background.
|
|
91
|
+
1 for foreground, 0 for background.
|
|
114
92
|
|
|
115
|
-
Returns
|
|
116
|
-
|
|
117
|
-
contrast : float
|
|
93
|
+
Returns:
|
|
94
|
+
contrast
|
|
118
95
|
"""
|
|
119
|
-
|
|
120
96
|
l_foreground = np.mean(imgs, where=mask)
|
|
121
97
|
l_background = np.mean(imgs, where=np.logical_not(mask))
|
|
122
98
|
return ((l_foreground - l_background) / l_background).item()
|
swcgeom/images/folder.py
CHANGED
|
@@ -20,7 +20,7 @@ import os
|
|
|
20
20
|
import re
|
|
21
21
|
from collections.abc import Callable, Iterable
|
|
22
22
|
from dataclasses import dataclass
|
|
23
|
-
from typing import Generic, Literal,
|
|
23
|
+
from typing import Generic, Literal, TypeVar, overload
|
|
24
24
|
|
|
25
25
|
import numpy as np
|
|
26
26
|
import numpy.typing as npt
|
|
@@ -41,13 +41,21 @@ class ImageStackFolderBase(Generic[ScalarType, T]):
|
|
|
41
41
|
files: list[str]
|
|
42
42
|
transform: Transform[npt.NDArray[ScalarType], T]
|
|
43
43
|
|
|
44
|
-
# fmt: off
|
|
45
44
|
@overload
|
|
46
|
-
def __init__(
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
files: Iterable[str],
|
|
48
|
+
*,
|
|
49
|
+
transform: Transform[npt.NDArray[np.float32], T] | None = ...,
|
|
50
|
+
) -> None: ...
|
|
47
51
|
@overload
|
|
48
|
-
def __init__(
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
files: Iterable[str],
|
|
55
|
+
*,
|
|
56
|
+
dtype: ScalarType,
|
|
57
|
+
transform: Transform[npt.NDArray[ScalarType], T] | None = ...,
|
|
58
|
+
) -> None: ...
|
|
51
59
|
def __init__(self, files: Iterable[str], *, dtype=None, transform=None) -> None:
|
|
52
60
|
super().__init__()
|
|
53
61
|
self.files = list(files)
|
|
@@ -66,7 +74,7 @@ class ImageStackFolderBase(Generic[ScalarType, T]):
|
|
|
66
74
|
return read_imgs(fname, dtype=self.dtype).get_full() # type: ignore
|
|
67
75
|
|
|
68
76
|
@staticmethod
|
|
69
|
-
def scan(root: str, *, pattern:
|
|
77
|
+
def scan(root: str, *, pattern: str | None = None) -> list[str]:
|
|
70
78
|
if not os.path.isdir(root):
|
|
71
79
|
raise NotADirectoryError(f"not a directory: {root}")
|
|
72
80
|
|
|
@@ -108,16 +116,12 @@ class ImageStackFolder(ImageStackFolderBase[ScalarType, T]):
|
|
|
108
116
|
def stat(self, *, transform: bool = False, verbose: bool = False) -> Statistics:
|
|
109
117
|
"""Statistics of folder.
|
|
110
118
|
|
|
111
|
-
|
|
112
|
-
----------
|
|
113
|
-
transform : bool, default to False
|
|
114
|
-
Apply transform to the images. If True, you need to make
|
|
115
|
-
sure the transformed data is a ndarray.
|
|
116
|
-
verbose : bool, optional
|
|
119
|
+
NOTE: We are asserting that the images are of the same shape.
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
Args:
|
|
122
|
+
transform: Apply transform to the images.
|
|
123
|
+
If True, you need to make sure the transformed data is a ndarray.
|
|
124
|
+
verbose: Show progress bar.
|
|
121
125
|
"""
|
|
122
126
|
|
|
123
127
|
vmin, vmax = math.inf, -math.inf
|
|
@@ -134,7 +138,7 @@ class ImageStackFolder(ImageStackFolderBase[ScalarType, T]):
|
|
|
134
138
|
M2 = np.zeros_like(imgs)
|
|
135
139
|
|
|
136
140
|
n += 1
|
|
137
|
-
delta = imgs - mean
|
|
141
|
+
delta = imgs - mean
|
|
138
142
|
mean += delta / n
|
|
139
143
|
delta2 = imgs - mean
|
|
140
144
|
M2 += delta * delta2
|
|
@@ -152,15 +156,12 @@ class ImageStackFolder(ImageStackFolderBase[ScalarType, T]):
|
|
|
152
156
|
)
|
|
153
157
|
|
|
154
158
|
@classmethod
|
|
155
|
-
def from_dir(cls, root: str, *, pattern:
|
|
159
|
+
def from_dir(cls, root: str, *, pattern: str | None = None, **kwargs) -> Self:
|
|
156
160
|
"""
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
Filter files by pattern.
|
|
162
|
-
**kwargs
|
|
163
|
-
Pass to `cls.__init__`
|
|
161
|
+
Args:
|
|
162
|
+
root: str
|
|
163
|
+
pattern: Filter files by pattern.
|
|
164
|
+
**kwargs: Pass to `cls.__init__`
|
|
164
165
|
"""
|
|
165
166
|
|
|
166
167
|
return cls(cls.scan(root, pattern=pattern), **kwargs)
|
|
@@ -184,7 +185,7 @@ class LabeledImageStackFolder(ImageStackFolderBase[ScalarType, T]):
|
|
|
184
185
|
root: str,
|
|
185
186
|
label: int | Callable[[str], int],
|
|
186
187
|
*,
|
|
187
|
-
pattern:
|
|
188
|
+
pattern: str | None = None,
|
|
188
189
|
**kwargs,
|
|
189
190
|
) -> Self:
|
|
190
191
|
files = cls.scan(root, pattern=pattern)
|
|
@@ -211,15 +212,12 @@ class PathImageStackFolder(ImageStackFolderBase[ScalarType, T]):
|
|
|
211
212
|
return self._get(self.files[idx]), relpath
|
|
212
213
|
|
|
213
214
|
@classmethod
|
|
214
|
-
def from_dir(cls, root: str, *, pattern:
|
|
215
|
+
def from_dir(cls, root: str, *, pattern: str | None = None, **kwargs) -> Self:
|
|
215
216
|
"""
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
Filter files by pattern.
|
|
221
|
-
**kwargs
|
|
222
|
-
Pass to `cls.__init__`
|
|
217
|
+
Args:
|
|
218
|
+
root: str
|
|
219
|
+
pattern: Filter files by pattern.
|
|
220
|
+
**kwargs: Pass to `cls.__init__`
|
|
223
221
|
"""
|
|
224
222
|
|
|
225
223
|
return cls(cls.scan(root, pattern=pattern), root=root, **kwargs)
|
swcgeom/images/io.py
CHANGED
|
@@ -17,18 +17,18 @@
|
|
|
17
17
|
|
|
18
18
|
import os
|
|
19
19
|
import re
|
|
20
|
+
import sys
|
|
20
21
|
import warnings
|
|
21
22
|
from abc import ABC, abstractmethod
|
|
22
23
|
from collections.abc import Callable, Iterable
|
|
23
24
|
from functools import cache, lru_cache
|
|
24
|
-
from typing import Any, Generic, Literal,
|
|
25
|
+
from typing import Any, Generic, Literal, TypeVar, cast, overload
|
|
25
26
|
|
|
26
27
|
import nrrd
|
|
27
28
|
import numpy as np
|
|
28
29
|
import numpy.typing as npt
|
|
29
30
|
import tifffile
|
|
30
31
|
from typing_extensions import deprecated
|
|
31
|
-
from v3dpy.loaders import PBD, Raw
|
|
32
32
|
|
|
33
33
|
__all__ = ["read_imgs", "save_tiff", "read_images"]
|
|
34
34
|
|
|
@@ -39,10 +39,10 @@ RE_TERAFLY_ROOT = re.compile(r"^RES\((\d+)x(\d+)x(\d+)\)$")
|
|
|
39
39
|
RE_TERAFLY_NAME = re.compile(r"^\d+(_\d+)?(_\d+)?")
|
|
40
40
|
|
|
41
41
|
UINT_MAX = {
|
|
42
|
-
np.dtype(np.uint8): (2**8) - 1,
|
|
43
|
-
np.dtype(np.uint16): (2**16) - 1,
|
|
44
|
-
np.dtype(np.uint32): (2**32) - 1,
|
|
45
|
-
np.dtype(np.uint64): (2**64) - 1,
|
|
42
|
+
np.dtype(np.uint8): (2**8) - 1,
|
|
43
|
+
np.dtype(np.uint16): (2**16) - 1,
|
|
44
|
+
np.dtype(np.uint32): (2**32) - 1,
|
|
45
|
+
np.dtype(np.uint64): (2**64) - 1,
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
AXES_ORDER = {
|
|
@@ -83,19 +83,15 @@ class ImageStack(ABC, Generic[ScalarType]):
|
|
|
83
83
|
def __getitem__(self, key):
|
|
84
84
|
"""Get pixel/patch of image stack.
|
|
85
85
|
|
|
86
|
-
Returns
|
|
87
|
-
|
|
88
|
-
value : ndarray of f32
|
|
89
|
-
NDArray which shape depends on key. If key is tuple of ints,
|
|
86
|
+
Returns:
|
|
87
|
+
value: NDArray which shape depends on key. If key is tuple of ints,
|
|
90
88
|
"""
|
|
91
89
|
raise NotImplementedError()
|
|
92
90
|
|
|
93
91
|
def get_full(self) -> npt.NDArray[ScalarType]:
|
|
94
92
|
"""Get full image stack.
|
|
95
93
|
|
|
96
|
-
|
|
97
|
-
-----
|
|
98
|
-
this will load the full image stack into memory.
|
|
94
|
+
NOTE: this will load the full image stack into memory.
|
|
99
95
|
"""
|
|
100
96
|
return self[:, :, :, :]
|
|
101
97
|
|
|
@@ -104,29 +100,20 @@ class ImageStack(ABC, Generic[ScalarType]):
|
|
|
104
100
|
raise NotImplementedError()
|
|
105
101
|
|
|
106
102
|
|
|
107
|
-
# fmt:off
|
|
108
103
|
@overload
|
|
109
104
|
def read_imgs(fname: str, *, dtype: ScalarType, **kwargs) -> ImageStack[ScalarType]: ...
|
|
110
105
|
@overload
|
|
111
|
-
def read_imgs(fname: str, *, dtype: None
|
|
112
|
-
# fmt:on
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
def read_imgs(fname: str, *, dtype: None = ..., **kwargs) -> ImageStack[np.float32]: ...
|
|
115
107
|
def read_imgs(fname: str, **kwargs): # type: ignore
|
|
116
108
|
"""Read image stack.
|
|
117
109
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
conversions occur, they will be scaled (assuming floats are
|
|
125
|
-
between 0 and 1).
|
|
126
|
-
**kwargs : dict[str, Any]
|
|
127
|
-
Forwarding to the corresponding reader.
|
|
110
|
+
Args:
|
|
111
|
+
fname: The path of image stack.
|
|
112
|
+
dtype: Casting data to specified dtype.
|
|
113
|
+
If integer and float conversions occur, they will be scaled (assuming floats
|
|
114
|
+
are between 0 and 1). Default to `np.float32`.
|
|
115
|
+
**kwargs: Forwarding to the corresponding reader.
|
|
128
116
|
"""
|
|
129
|
-
|
|
130
117
|
kwargs.setdefault("dtype", np.float32)
|
|
131
118
|
if not os.path.exists(fname):
|
|
132
119
|
raise ValueError(f"image stack not exists: {fname}")
|
|
@@ -155,27 +142,22 @@ def save_tiff(
|
|
|
155
142
|
data: npt.NDArray | ImageStack,
|
|
156
143
|
fname: str,
|
|
157
144
|
*,
|
|
158
|
-
dtype:
|
|
145
|
+
dtype: np.unsignedinteger | np.floating | None = None,
|
|
159
146
|
compression: str | Literal[False] = "zlib",
|
|
160
147
|
**kwargs,
|
|
161
148
|
) -> None:
|
|
162
149
|
"""Save image stack as tiff.
|
|
163
150
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
Compression algorithm, forwarding to `tifffile.imwrite`. If no
|
|
175
|
-
algorithnm is specify specified, we will use the zlib algorithm
|
|
176
|
-
with compression level 6 by default.
|
|
177
|
-
**kwargs : dict[str, Any]
|
|
178
|
-
Forwarding to `tifffile.imwrite`
|
|
151
|
+
Args:
|
|
152
|
+
data: The image stack.
|
|
153
|
+
fname: str
|
|
154
|
+
dtype: Casting data to specified dtype.
|
|
155
|
+
If integer and float conversions occur, they will be scaled (assuming
|
|
156
|
+
floats are between 0 and 1).
|
|
157
|
+
compression: Compression algorithm, forwarding to `tifffile.imwrite`.
|
|
158
|
+
If no algorithnm is specify specified, we will use the zlib algorithm with
|
|
159
|
+
compression level 6 by default.
|
|
160
|
+
**kwargs: Forwarding to `tifffile.imwrite`
|
|
179
161
|
"""
|
|
180
162
|
if isinstance(data, ImageStack):
|
|
181
163
|
data = data.get_full() # TODO: avoid load full imgs to memory
|
|
@@ -191,11 +173,11 @@ def save_tiff(
|
|
|
191
173
|
if np.issubdtype(data.dtype, np.floating) and np.issubdtype(
|
|
192
174
|
dtype, np.unsignedinteger
|
|
193
175
|
):
|
|
194
|
-
scaler_factor = UINT_MAX[np.dtype(dtype)]
|
|
176
|
+
scaler_factor = UINT_MAX[np.dtype(dtype)]
|
|
195
177
|
elif np.issubdtype(data.dtype, np.unsignedinteger) and np.issubdtype(
|
|
196
178
|
dtype, np.floating
|
|
197
179
|
):
|
|
198
|
-
scaler_factor = 1 / UINT_MAX[np.dtype(data.dtype)]
|
|
180
|
+
scaler_factor = 1 / UINT_MAX[np.dtype(data.dtype)]
|
|
199
181
|
else:
|
|
200
182
|
scaler_factor = 1
|
|
201
183
|
|
|
@@ -218,7 +200,7 @@ class NDArrayImageStack(ImageStack[ScalarType]):
|
|
|
218
200
|
"""NDArray image stack."""
|
|
219
201
|
|
|
220
202
|
def __init__(
|
|
221
|
-
self, imgs: npt.NDArray[Any], *, dtype:
|
|
203
|
+
self, imgs: npt.NDArray[Any], *, dtype: ScalarType | None = None
|
|
222
204
|
) -> None:
|
|
223
205
|
super().__init__()
|
|
224
206
|
|
|
@@ -231,13 +213,13 @@ class NDArrayImageStack(ImageStack[ScalarType]):
|
|
|
231
213
|
if np.issubdtype(dtype, np.floating) and np.issubdtype(
|
|
232
214
|
dtype_raw, np.unsignedinteger
|
|
233
215
|
):
|
|
234
|
-
|
|
235
|
-
imgs =
|
|
216
|
+
scalar_factor = 1.0 / UINT_MAX[dtype_raw]
|
|
217
|
+
imgs = scalar_factor * imgs.astype(dtype)
|
|
236
218
|
elif np.issubdtype(dtype, np.unsignedinteger) and np.issubdtype(
|
|
237
219
|
dtype_raw, np.floating
|
|
238
220
|
):
|
|
239
|
-
|
|
240
|
-
imgs *= (
|
|
221
|
+
scalar_factor = UINT_MAX[dtype]
|
|
222
|
+
imgs *= (scalar_factor * imgs).astype(dtype)
|
|
241
223
|
else:
|
|
242
224
|
imgs = imgs.astype(dtype)
|
|
243
225
|
|
|
@@ -281,29 +263,48 @@ class NrrdImageStack(NDArrayImageStack[ScalarType]):
|
|
|
281
263
|
self.header = header
|
|
282
264
|
|
|
283
265
|
|
|
284
|
-
|
|
285
|
-
|
|
266
|
+
if sys.version_info < (3, 13):
|
|
267
|
+
from v3dpy.loaders import PBD, Raw
|
|
286
268
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
) -> None:
|
|
290
|
-
r = loader()
|
|
291
|
-
imgs = r.load(fname)
|
|
292
|
-
super().__init__(imgs, dtype=dtype, **kwargs)
|
|
269
|
+
class V3dImageStack(NDArrayImageStack[ScalarType]):
|
|
270
|
+
"""v3d image stack."""
|
|
293
271
|
|
|
272
|
+
def __init_subclass__(cls, loader: Raw | PBD) -> None:
|
|
273
|
+
super().__init_subclass__()
|
|
274
|
+
cls._loader = loader
|
|
294
275
|
|
|
295
|
-
|
|
296
|
-
|
|
276
|
+
def __init__(self, fname: str, *, dtype: ScalarType, **kwargs) -> None:
|
|
277
|
+
r = self._loader()
|
|
278
|
+
imgs = r.load(fname)
|
|
279
|
+
super().__init__(imgs, dtype=dtype, **kwargs)
|
|
297
280
|
|
|
298
|
-
|
|
299
|
-
|
|
281
|
+
class V3drawImageStack(V3dImageStack[ScalarType], loader=Raw):
|
|
282
|
+
"""v3draw image stack."""
|
|
300
283
|
|
|
284
|
+
class V3dpbdImageStack(V3dImageStack[ScalarType], loader=PBD):
|
|
285
|
+
"""v3dpbd image stack."""
|
|
301
286
|
|
|
302
|
-
|
|
303
|
-
"""v3dpbd image stack."""
|
|
287
|
+
else:
|
|
304
288
|
|
|
305
|
-
def
|
|
306
|
-
|
|
289
|
+
def _v3d_not_supported(*args, **kwargs):
|
|
290
|
+
raise RuntimeError(
|
|
291
|
+
"v3d-py-helper is not supported in Python 3.13 or newer. See #33"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
class V3dImageStack:
|
|
295
|
+
"""v3d image stack (disabled due to unsupported Python version)."""
|
|
296
|
+
|
|
297
|
+
__init__ = _v3d_not_supported
|
|
298
|
+
|
|
299
|
+
class V3drawImageStack:
|
|
300
|
+
"""v3draw image stack (disabled due to unsupported Python version)."""
|
|
301
|
+
|
|
302
|
+
__init__ = _v3d_not_supported
|
|
303
|
+
|
|
304
|
+
class V3dpbdImageStack:
|
|
305
|
+
"""v3dpbd image stack (disabled due to unsupported Python version)."""
|
|
306
|
+
|
|
307
|
+
__init__ = _v3d_not_supported
|
|
307
308
|
|
|
308
309
|
|
|
309
310
|
class TeraflyImageStack(ImageStack[ScalarType]):
|
|
@@ -312,21 +313,17 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
312
313
|
TeraFly is a terabytes of multidimensional volumetric images file
|
|
313
314
|
format as described in [1]_.
|
|
314
315
|
|
|
315
|
-
|
|
316
|
-
----------
|
|
317
|
-
.. [1] Bria, Alessandro, Giulio Iannello, Leonardo Onofri, and
|
|
318
|
-
Hanchuan Peng. “TeraFly: Real-Time Three-Dimensional
|
|
319
|
-
Visualization and Annotation of Terabytes of Multidimensional
|
|
320
|
-
Volumetric Images.” Nature Methods 13,
|
|
321
|
-
no. 3 (March 2016): 192-94. https://doi.org/10.1038/nmeth.3767.
|
|
322
|
-
|
|
323
|
-
Notes
|
|
324
|
-
-----
|
|
325
|
-
Terafly and Vaa3d use a especial right-handed coordinate system
|
|
316
|
+
NOTE: Terafly and Vaa3d use a especial right-handed coordinate system
|
|
326
317
|
(with origin point in the left-top and z-axis points front), but we
|
|
327
|
-
flip y-axis to makes it a left-handed coordinate system (with
|
|
318
|
+
flip y-axis to makes it a left-handed coordinate system (with origin
|
|
328
319
|
point in the left-bottom and z-axis points front). If you need to
|
|
329
320
|
use its coordinate system, remember to FLIP Y-AXIS BACK.
|
|
321
|
+
|
|
322
|
+
References:
|
|
323
|
+
.. [1] Bria, Alessandro, Giulio Iannello, Leonardo Onofri, and Hanchuan Peng.
|
|
324
|
+
“TeraFly: Real-Time Three-Dimensional Visualization and Annotation of Terabytes
|
|
325
|
+
of Multidimensional Volumetric Images.” Nature Methods 13, no. 3 (March 2016):
|
|
326
|
+
192-94. https://doi.org/10.1038/nmeth.3767.
|
|
330
327
|
"""
|
|
331
328
|
|
|
332
329
|
_listdir: Callable[[str], list[str]]
|
|
@@ -336,17 +333,13 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
336
333
|
self, root: str, *, dtype: ScalarType, lru_maxsize: int | None = 128
|
|
337
334
|
) -> None:
|
|
338
335
|
r"""
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
Forwarding to `functools.lru_cache`. A decompressed array
|
|
347
|
-
size of (256, 256, 256, 1), which is the typical size of
|
|
348
|
-
terafly image stack, takes about 256 * 256 * 256 * 1 *
|
|
349
|
-
4B = 64MB. A cache size of 128 requires about 8GB memeory.
|
|
336
|
+
Args:
|
|
337
|
+
root: The root of terafly which contains directories named as `RES(YxXxZ)`.
|
|
338
|
+
dtype: np.dtype
|
|
339
|
+
lru_maxsize: Forwarding to `functools.lru_cache`.
|
|
340
|
+
A decompressed array size of (256, 256, 256, 1), which is the typical
|
|
341
|
+
size of terafly image stack, takes about 256 * 256 * 256 * 1 * 4B = 64MB.
|
|
342
|
+
A cache size of 128 requires about 8GB memory.
|
|
350
343
|
"""
|
|
351
344
|
|
|
352
345
|
super().__init__()
|
|
@@ -372,11 +365,8 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
372
365
|
def __getitem__(self, key):
|
|
373
366
|
"""Get images in max resolution.
|
|
374
367
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
```python
|
|
378
|
-
imgs[0, 0, 0, 0] # get value
|
|
379
|
-
imgs[0:64, 0:64, 0:64, :] # get patch
|
|
368
|
+
>>> imgs[0, 0, 0, 0] # get value # doctest: +SKIP
|
|
369
|
+
>>> imgs[0:64, 0:64, 0:64, :] # get patch # doctest: +SKIP
|
|
380
370
|
```
|
|
381
371
|
"""
|
|
382
372
|
if not isinstance(key, tuple):
|
|
@@ -399,9 +389,8 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
399
389
|
) -> npt.NDArray[ScalarType]:
|
|
400
390
|
"""Get patch of image stack.
|
|
401
391
|
|
|
402
|
-
Returns
|
|
403
|
-
|
|
404
|
-
patch : array of shape (X, Y, Z, C)
|
|
392
|
+
Returns:
|
|
393
|
+
patch: array of shape (X, Y, Z, C)
|
|
405
394
|
"""
|
|
406
395
|
if isinstance(strides, int):
|
|
407
396
|
strides = (strides, strides, strides)
|
|
@@ -421,10 +410,9 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
421
410
|
def find_correspond_imgs(self, p, res_level=-1):
|
|
422
411
|
"""Find the image which contain this point.
|
|
423
412
|
|
|
424
|
-
Returns
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
patch_offset : (int, int, int)
|
|
413
|
+
Returns:
|
|
414
|
+
patch: array of shape (X, Y, Z, C)
|
|
415
|
+
patch_offset: (int, int, int)
|
|
428
416
|
"""
|
|
429
417
|
p = np.array(p)
|
|
430
418
|
self._check_params(res_level, p)
|
|
@@ -442,14 +430,10 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
442
430
|
def get_resolutions(cls, root: str) -> tuple[list[Vec3i], list[str], list[Vec3i]]:
|
|
443
431
|
"""Get all resolutions.
|
|
444
432
|
|
|
445
|
-
Returns
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
Sequence of
|
|
449
|
-
roots : list[str]
|
|
450
|
-
Sequence of root of resolutions respectively.
|
|
451
|
-
patch_sizes : List of (int, int, int)
|
|
452
|
-
Sequence of patch size of resolutions respectively.
|
|
433
|
+
Returns:
|
|
434
|
+
resolutions: Sequence of sorted resolutions (from small to large).
|
|
435
|
+
roots: Sequence of root of resolutions respectively.
|
|
436
|
+
patch_sizes: Sequence of patch size of resolutions respectively.
|
|
453
437
|
"""
|
|
454
438
|
|
|
455
439
|
roots = list(cls.get_resolution_dirs(root))
|
|
@@ -497,13 +481,13 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
497
481
|
|
|
498
482
|
res = self.res[res_level]
|
|
499
483
|
for p in coords:
|
|
500
|
-
assert np.less(
|
|
501
|
-
[0, 0, 0
|
|
502
|
-
)
|
|
484
|
+
assert np.less([0, 0, 0], p).all(), (
|
|
485
|
+
f"indices ({p[0]}, {p[1]}, {p[2]}) out of range (0, 0, 0)"
|
|
486
|
+
)
|
|
503
487
|
|
|
504
|
-
assert np.greater(
|
|
505
|
-
res,
|
|
506
|
-
)
|
|
488
|
+
assert np.greater(res, p).all(), (
|
|
489
|
+
f"indices ({p[0]}, {p[1]}, {p[2]}) out of range ({res[0]}, {res[1]}, {res[2]})"
|
|
490
|
+
)
|
|
507
491
|
|
|
508
492
|
def _get_range(self, starts, ends, res_level, out):
|
|
509
493
|
# pylint: disable=too-many-locals
|
|
@@ -529,13 +513,13 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
529
513
|
if shape[1] > lens[1]:
|
|
530
514
|
starts_y = starts + [0, lens[1], 0]
|
|
531
515
|
ends_y = np.array([starts[0], ends[1], ends[2]])
|
|
532
|
-
ends_y += [min(shape[0], lens[0]), 0, 0]
|
|
516
|
+
ends_y += [min(shape[0], lens[0]), 0, 0]
|
|
533
517
|
self._get_range(starts_y, ends_y, res_level, out[:, lens[1] :, :])
|
|
534
518
|
|
|
535
519
|
if shape[2] > lens[2]:
|
|
536
520
|
starts_z = starts + [0, 0, lens[2]]
|
|
537
521
|
ends_z = np.array([starts[0], starts[1], ends[2]])
|
|
538
|
-
ends_z += [min(shape[0], lens[0]), min(shape[1], lens[1]), 0]
|
|
522
|
+
ends_z += [min(shape[0], lens[0]), min(shape[1], lens[1]), 0]
|
|
539
523
|
self._get_range(starts_z, ends_z, res_level, out[:, :, lens[2] :])
|
|
540
524
|
|
|
541
525
|
def _find_correspond_imgs(self, p, res_level):
|
|
@@ -580,14 +564,14 @@ class GrayImageStack:
|
|
|
580
564
|
def __init__(self, imgs: ImageStack) -> None:
|
|
581
565
|
self.imgs = imgs
|
|
582
566
|
|
|
583
|
-
# fmt: off
|
|
584
567
|
@overload
|
|
585
568
|
def __getitem__(self, key: Vec3i) -> np.float32: ...
|
|
586
569
|
@overload
|
|
587
570
|
def __getitem__(self, key: npt.NDArray[np.integer[Any]]) -> np.float32: ...
|
|
588
571
|
@overload
|
|
589
|
-
def __getitem__(
|
|
590
|
-
|
|
572
|
+
def __getitem__(
|
|
573
|
+
self, key: slice | tuple[slice, slice] | tuple[slice, slice, slice]
|
|
574
|
+
) -> npt.NDArray[np.float32]: ...
|
|
591
575
|
def __getitem__(self, key):
|
|
592
576
|
"""Get pixel/patch of image stack."""
|
|
593
577
|
v = self[key]
|
|
@@ -601,14 +585,12 @@ class GrayImageStack:
|
|
|
601
585
|
return v[:, 0]
|
|
602
586
|
if v.ndim == 1:
|
|
603
587
|
return v[0]
|
|
604
|
-
raise ValueError("
|
|
588
|
+
raise ValueError("unsupported key")
|
|
605
589
|
|
|
606
590
|
def get_full(self) -> npt.NDArray[np.float32]:
|
|
607
591
|
"""Get full image stack.
|
|
608
592
|
|
|
609
|
-
|
|
610
|
-
-----
|
|
611
|
-
this will load the full image stack into memory.
|
|
593
|
+
NOTE: this will load the full image stack into memory.
|
|
612
594
|
"""
|
|
613
595
|
return self.imgs.get_full()[:, :, :, 0]
|
|
614
596
|
|