swcgeom 0.18.1__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/__init__.py +12 -1
- swcgeom/analysis/__init__.py +6 -6
- 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 +12 -13
- 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 +10 -12
- 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/__init__.py +2 -2
- 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/__init__.py +13 -13
- 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/__init__.py +12 -12
- 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.1.dist-info → swcgeom-0.19.0.dist-info}/METADATA +7 -6
- swcgeom-0.19.0.dist-info/RECORD +67 -0
- {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/WHEEL +1 -1
- swcgeom/_version.py +0 -16
- swcgeom-0.18.1.dist-info/RECORD +0 -68
- {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info/licenses}/LICENSE +0 -0
- {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/top_level.txt +0 -0
swcgeom/core/tree_utils_impl.py
CHANGED
|
@@ -15,12 +15,10 @@
|
|
|
15
15
|
|
|
16
16
|
"""SWC util wrapper for tree, split to avoid circle imports.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
-----
|
|
20
|
-
Do not import `Tree` and keep this file minimized.
|
|
18
|
+
NOTE: Do not import `Tree` and keep this file minimized.
|
|
21
19
|
"""
|
|
22
20
|
|
|
23
|
-
from typing import Any
|
|
21
|
+
from typing import Any
|
|
24
22
|
|
|
25
23
|
import numpy as np
|
|
26
24
|
import numpy.typing as npt
|
|
@@ -35,7 +33,7 @@ TreeArgs = tuple[int, dict[str, npt.NDArray[Any]], str, SWCNames]
|
|
|
35
33
|
|
|
36
34
|
|
|
37
35
|
def get_subtree_impl(
|
|
38
|
-
swc_like: SWCLike, n: int, *, out_mapping:
|
|
36
|
+
swc_like: SWCLike, n: int, *, out_mapping: Mapping | None = None
|
|
39
37
|
) -> TreeArgs:
|
|
40
38
|
ids = []
|
|
41
39
|
topo = (swc_like.id(), swc_like.pid())
|
|
@@ -51,7 +49,7 @@ def to_subtree_impl(
|
|
|
51
49
|
swc_like: SWCLike,
|
|
52
50
|
sub: Topology,
|
|
53
51
|
*,
|
|
54
|
-
out_mapping:
|
|
52
|
+
out_mapping: Mapping | None = None,
|
|
55
53
|
) -> TreeArgs:
|
|
56
54
|
(new_id, new_pid), mapping = to_sub_topology(sub)
|
|
57
55
|
|
swcgeom/images/__init__.py
CHANGED
swcgeom/images/augmentation.py
CHANGED
|
@@ -15,13 +15,11 @@
|
|
|
15
15
|
|
|
16
16
|
"""Play augment in image stack.
|
|
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
21
|
import random
|
|
24
|
-
from typing import Literal
|
|
22
|
+
from typing import Literal
|
|
25
23
|
|
|
26
24
|
import numpy as np
|
|
27
25
|
import numpy.typing as npt
|
|
@@ -67,7 +65,7 @@ class Augmentation:
|
|
|
67
65
|
self.seed = seed
|
|
68
66
|
self.rand = random.Random(seed)
|
|
69
67
|
|
|
70
|
-
def swapaxes(self, x, mode:
|
|
68
|
+
def swapaxes(self, x, mode: Literal["xy", "xz", "yz"] | None = None) -> NDArrf32:
|
|
71
69
|
if mode is None:
|
|
72
70
|
modes: list[Literal["xy", "xz", "yz"]] = ["xy", "xz", "yz"]
|
|
73
71
|
mode = modes[self.rand.randint(0, 2)]
|
|
@@ -82,7 +80,7 @@ class Augmentation:
|
|
|
82
80
|
case _:
|
|
83
81
|
raise ValueError(f"invalid mode: {mode}")
|
|
84
82
|
|
|
85
|
-
def flip(self, x, mode:
|
|
83
|
+
def flip(self, x, mode: Literal["xy", "xz", "yz"] | None = None) -> NDArrf32:
|
|
86
84
|
if mode is None:
|
|
87
85
|
modes: list[Literal["xy", "xz", "yz"]] = ["xy", "xz", "yz"]
|
|
88
86
|
mode = modes[random.randint(0, 2)]
|
|
@@ -101,16 +99,13 @@ class Augmentation:
|
|
|
101
99
|
fns = list(augs.keys())
|
|
102
100
|
|
|
103
101
|
|
|
104
|
-
def play_augment(x: NDArrf32, method:
|
|
102
|
+
def play_augment(x: NDArrf32, method: Augmentation | int | None = None) -> NDArrf32:
|
|
105
103
|
"""Play augment in x.
|
|
106
104
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
method : int or str, optional
|
|
112
|
-
Augmentation method index / name. if not provided, a random
|
|
113
|
-
augment will be apply.
|
|
105
|
+
Args
|
|
106
|
+
x: Array of shape (X, Y, Z, C)
|
|
107
|
+
method: Augmentation method index / name.
|
|
108
|
+
If not provided, a random augment will be apply.
|
|
114
109
|
"""
|
|
115
110
|
|
|
116
111
|
if isinstance(method, str):
|
|
@@ -132,31 +127,20 @@ def random_augmentations(
|
|
|
132
127
|
) -> npt.NDArray[np.int64]:
|
|
133
128
|
"""Generate a sequence of augmentations.
|
|
134
129
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Include identity transform.
|
|
145
|
-
|
|
146
|
-
Returns
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
Sequence of length N * K, contains image index and augmentation
|
|
150
|
-
method index.
|
|
151
|
-
|
|
152
|
-
Examples
|
|
153
|
-
--------
|
|
154
|
-
```python
|
|
155
|
-
xs = os.listdir("path_to_imgs")
|
|
156
|
-
augs = generate_random_augmentations(len(xs), 5)
|
|
157
|
-
for i, j in range(augs):
|
|
158
|
-
x = play_augment(read_imgs(os.path.join("path_to_imgs", xs[i])), j)
|
|
159
|
-
```
|
|
130
|
+
>>> xs = os.listdir("path_to_imgs") # doctest: +SKIP
|
|
131
|
+
>>> augs = generate_random_augmentations(len(xs), 5) # doctest: +SKIP
|
|
132
|
+
>>> for i, j in range(augs): # doctest: +SKIP
|
|
133
|
+
... x = play_augment(read_imgs(os.path.join("path_to_imgs", xs[i])), j)
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
n: Size of image stacks.
|
|
137
|
+
k: Each image stack augmented to K image stack.
|
|
138
|
+
seed: Random seed, forwarding to `random.Random`
|
|
139
|
+
include_identity: Include identity transform.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
augmentations: List of (int, int)
|
|
143
|
+
Sequence of length N * K, contains index of image and augmentation method.
|
|
160
144
|
"""
|
|
161
145
|
|
|
162
146
|
rand = random.Random(seed)
|
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)
|