swcgeom 0.18.3__py3-none-any.whl → 0.19.0__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 +80 -121
- 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.0.dist-info}/METADATA +5 -4
- swcgeom-0.19.0.dist-info/RECORD +67 -0
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.0.dist-info}/WHEEL +1 -1
- swcgeom-0.18.3.dist-info/RECORD +0 -67
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.0.dist-info/licenses}/LICENSE +0 -0
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.0.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
|
@@ -21,7 +21,7 @@ import warnings
|
|
|
21
21
|
from abc import ABC, abstractmethod
|
|
22
22
|
from collections.abc import Callable, Iterable
|
|
23
23
|
from functools import cache, lru_cache
|
|
24
|
-
from typing import Any, Generic, Literal,
|
|
24
|
+
from typing import Any, Generic, Literal, TypeVar, cast, overload
|
|
25
25
|
|
|
26
26
|
import nrrd
|
|
27
27
|
import numpy as np
|
|
@@ -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
|
|
|
@@ -284,27 +266,23 @@ class NrrdImageStack(NDArrayImageStack[ScalarType]):
|
|
|
284
266
|
class V3dImageStack(NDArrayImageStack[ScalarType]):
|
|
285
267
|
"""v3d image stack."""
|
|
286
268
|
|
|
287
|
-
def
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
269
|
+
def __init_subclass__(cls, loader: Raw | PBD) -> None:
|
|
270
|
+
super().__init_subclass__()
|
|
271
|
+
cls._loader = loader
|
|
272
|
+
|
|
273
|
+
def __init__(self, fname: str, *, dtype: ScalarType, **kwargs) -> None:
|
|
274
|
+
r = self._loader()
|
|
291
275
|
imgs = r.load(fname)
|
|
292
276
|
super().__init__(imgs, dtype=dtype, **kwargs)
|
|
293
277
|
|
|
294
278
|
|
|
295
|
-
class V3drawImageStack(V3dImageStack[ScalarType]):
|
|
279
|
+
class V3drawImageStack(V3dImageStack[ScalarType], loader=Raw):
|
|
296
280
|
"""v3draw image stack."""
|
|
297
281
|
|
|
298
|
-
def __init__(self, fname: str, *, dtype: ScalarType, **kwargs) -> None:
|
|
299
|
-
super().__init__(fname, loader=Raw, dtype=dtype, **kwargs)
|
|
300
|
-
|
|
301
282
|
|
|
302
|
-
class V3dpbdImageStack(V3dImageStack[ScalarType]):
|
|
283
|
+
class V3dpbdImageStack(V3dImageStack[ScalarType], loader=PBD):
|
|
303
284
|
"""v3dpbd image stack."""
|
|
304
285
|
|
|
305
|
-
def __init__(self, fname: str, *, dtype: ScalarType, **kwargs) -> None:
|
|
306
|
-
super().__init__(fname, loader=PBD, dtype=dtype, **kwargs)
|
|
307
|
-
|
|
308
286
|
|
|
309
287
|
class TeraflyImageStack(ImageStack[ScalarType]):
|
|
310
288
|
"""TeraFly image stack.
|
|
@@ -312,21 +290,17 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
312
290
|
TeraFly is a terabytes of multidimensional volumetric images file
|
|
313
291
|
format as described in [1]_.
|
|
314
292
|
|
|
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
|
|
293
|
+
NOTE: Terafly and Vaa3d use a especial right-handed coordinate system
|
|
326
294
|
(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
|
|
295
|
+
flip y-axis to makes it a left-handed coordinate system (with origin
|
|
328
296
|
point in the left-bottom and z-axis points front). If you need to
|
|
329
297
|
use its coordinate system, remember to FLIP Y-AXIS BACK.
|
|
298
|
+
|
|
299
|
+
References:
|
|
300
|
+
.. [1] Bria, Alessandro, Giulio Iannello, Leonardo Onofri, and Hanchuan Peng.
|
|
301
|
+
“TeraFly: Real-Time Three-Dimensional Visualization and Annotation of Terabytes
|
|
302
|
+
of Multidimensional Volumetric Images.” Nature Methods 13, no. 3 (March 2016):
|
|
303
|
+
192-94. https://doi.org/10.1038/nmeth.3767.
|
|
330
304
|
"""
|
|
331
305
|
|
|
332
306
|
_listdir: Callable[[str], list[str]]
|
|
@@ -336,17 +310,13 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
336
310
|
self, root: str, *, dtype: ScalarType, lru_maxsize: int | None = 128
|
|
337
311
|
) -> None:
|
|
338
312
|
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.
|
|
313
|
+
Args:
|
|
314
|
+
root: The root of terafly which contains directories named as `RES(YxXxZ)`.
|
|
315
|
+
dtype: np.dtype
|
|
316
|
+
lru_maxsize: Forwarding to `functools.lru_cache`.
|
|
317
|
+
A decompressed array size of (256, 256, 256, 1), which is the typical
|
|
318
|
+
size of terafly image stack, takes about 256 * 256 * 256 * 1 * 4B = 64MB.
|
|
319
|
+
A cache size of 128 requires about 8GB memory.
|
|
350
320
|
"""
|
|
351
321
|
|
|
352
322
|
super().__init__()
|
|
@@ -372,11 +342,8 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
372
342
|
def __getitem__(self, key):
|
|
373
343
|
"""Get images in max resolution.
|
|
374
344
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
```python
|
|
378
|
-
imgs[0, 0, 0, 0] # get value
|
|
379
|
-
imgs[0:64, 0:64, 0:64, :] # get patch
|
|
345
|
+
>>> imgs[0, 0, 0, 0] # get value # doctest: +SKIP
|
|
346
|
+
>>> imgs[0:64, 0:64, 0:64, :] # get patch # doctest: +SKIP
|
|
380
347
|
```
|
|
381
348
|
"""
|
|
382
349
|
if not isinstance(key, tuple):
|
|
@@ -399,9 +366,8 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
399
366
|
) -> npt.NDArray[ScalarType]:
|
|
400
367
|
"""Get patch of image stack.
|
|
401
368
|
|
|
402
|
-
Returns
|
|
403
|
-
|
|
404
|
-
patch : array of shape (X, Y, Z, C)
|
|
369
|
+
Returns:
|
|
370
|
+
patch: array of shape (X, Y, Z, C)
|
|
405
371
|
"""
|
|
406
372
|
if isinstance(strides, int):
|
|
407
373
|
strides = (strides, strides, strides)
|
|
@@ -421,10 +387,9 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
421
387
|
def find_correspond_imgs(self, p, res_level=-1):
|
|
422
388
|
"""Find the image which contain this point.
|
|
423
389
|
|
|
424
|
-
Returns
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
patch_offset : (int, int, int)
|
|
390
|
+
Returns:
|
|
391
|
+
patch: array of shape (X, Y, Z, C)
|
|
392
|
+
patch_offset: (int, int, int)
|
|
428
393
|
"""
|
|
429
394
|
p = np.array(p)
|
|
430
395
|
self._check_params(res_level, p)
|
|
@@ -442,14 +407,10 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
442
407
|
def get_resolutions(cls, root: str) -> tuple[list[Vec3i], list[str], list[Vec3i]]:
|
|
443
408
|
"""Get all resolutions.
|
|
444
409
|
|
|
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.
|
|
410
|
+
Returns:
|
|
411
|
+
resolutions: Sequence of sorted resolutions (from small to large).
|
|
412
|
+
roots: Sequence of root of resolutions respectively.
|
|
413
|
+
patch_sizes: Sequence of patch size of resolutions respectively.
|
|
453
414
|
"""
|
|
454
415
|
|
|
455
416
|
roots = list(cls.get_resolution_dirs(root))
|
|
@@ -497,13 +458,13 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
497
458
|
|
|
498
459
|
res = self.res[res_level]
|
|
499
460
|
for p in coords:
|
|
500
|
-
assert np.less(
|
|
501
|
-
[0, 0, 0
|
|
502
|
-
)
|
|
461
|
+
assert np.less([0, 0, 0], p).all(), (
|
|
462
|
+
f"indices ({p[0]}, {p[1]}, {p[2]}) out of range (0, 0, 0)"
|
|
463
|
+
)
|
|
503
464
|
|
|
504
|
-
assert np.greater(
|
|
505
|
-
res,
|
|
506
|
-
)
|
|
465
|
+
assert np.greater(res, p).all(), (
|
|
466
|
+
f"indices ({p[0]}, {p[1]}, {p[2]}) out of range ({res[0]}, {res[1]}, {res[2]})"
|
|
467
|
+
)
|
|
507
468
|
|
|
508
469
|
def _get_range(self, starts, ends, res_level, out):
|
|
509
470
|
# pylint: disable=too-many-locals
|
|
@@ -529,13 +490,13 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
529
490
|
if shape[1] > lens[1]:
|
|
530
491
|
starts_y = starts + [0, lens[1], 0]
|
|
531
492
|
ends_y = np.array([starts[0], ends[1], ends[2]])
|
|
532
|
-
ends_y += [min(shape[0], lens[0]), 0, 0]
|
|
493
|
+
ends_y += [min(shape[0], lens[0]), 0, 0]
|
|
533
494
|
self._get_range(starts_y, ends_y, res_level, out[:, lens[1] :, :])
|
|
534
495
|
|
|
535
496
|
if shape[2] > lens[2]:
|
|
536
497
|
starts_z = starts + [0, 0, lens[2]]
|
|
537
498
|
ends_z = np.array([starts[0], starts[1], ends[2]])
|
|
538
|
-
ends_z += [min(shape[0], lens[0]), min(shape[1], lens[1]), 0]
|
|
499
|
+
ends_z += [min(shape[0], lens[0]), min(shape[1], lens[1]), 0]
|
|
539
500
|
self._get_range(starts_z, ends_z, res_level, out[:, :, lens[2] :])
|
|
540
501
|
|
|
541
502
|
def _find_correspond_imgs(self, p, res_level):
|
|
@@ -580,14 +541,14 @@ class GrayImageStack:
|
|
|
580
541
|
def __init__(self, imgs: ImageStack) -> None:
|
|
581
542
|
self.imgs = imgs
|
|
582
543
|
|
|
583
|
-
# fmt: off
|
|
584
544
|
@overload
|
|
585
545
|
def __getitem__(self, key: Vec3i) -> np.float32: ...
|
|
586
546
|
@overload
|
|
587
547
|
def __getitem__(self, key: npt.NDArray[np.integer[Any]]) -> np.float32: ...
|
|
588
548
|
@overload
|
|
589
|
-
def __getitem__(
|
|
590
|
-
|
|
549
|
+
def __getitem__(
|
|
550
|
+
self, key: slice | tuple[slice, slice] | tuple[slice, slice, slice]
|
|
551
|
+
) -> npt.NDArray[np.float32]: ...
|
|
591
552
|
def __getitem__(self, key):
|
|
592
553
|
"""Get pixel/patch of image stack."""
|
|
593
554
|
v = self[key]
|
|
@@ -601,14 +562,12 @@ class GrayImageStack:
|
|
|
601
562
|
return v[:, 0]
|
|
602
563
|
if v.ndim == 1:
|
|
603
564
|
return v[0]
|
|
604
|
-
raise ValueError("
|
|
565
|
+
raise ValueError("unsupported key")
|
|
605
566
|
|
|
606
567
|
def get_full(self) -> npt.NDArray[np.float32]:
|
|
607
568
|
"""Get full image stack.
|
|
608
569
|
|
|
609
|
-
|
|
610
|
-
-----
|
|
611
|
-
this will load the full image stack into memory.
|
|
570
|
+
NOTE: this will load the full image stack into memory.
|
|
612
571
|
"""
|
|
613
572
|
return self.imgs.get_full()[:, :, :, 0]
|
|
614
573
|
|