swcgeom 0.19.4__cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.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.

Files changed (72) hide show
  1. swcgeom/__init__.py +21 -0
  2. swcgeom/analysis/__init__.py +13 -0
  3. swcgeom/analysis/feature_extractor.py +454 -0
  4. swcgeom/analysis/features.py +218 -0
  5. swcgeom/analysis/lmeasure.py +750 -0
  6. swcgeom/analysis/sholl.py +201 -0
  7. swcgeom/analysis/trunk.py +183 -0
  8. swcgeom/analysis/visualization.py +191 -0
  9. swcgeom/analysis/visualization3d.py +81 -0
  10. swcgeom/analysis/volume.py +143 -0
  11. swcgeom/core/__init__.py +19 -0
  12. swcgeom/core/branch.py +129 -0
  13. swcgeom/core/branch_tree.py +65 -0
  14. swcgeom/core/compartment.py +107 -0
  15. swcgeom/core/node.py +130 -0
  16. swcgeom/core/path.py +155 -0
  17. swcgeom/core/population.py +341 -0
  18. swcgeom/core/swc.py +247 -0
  19. swcgeom/core/swc_utils/__init__.py +19 -0
  20. swcgeom/core/swc_utils/assembler.py +35 -0
  21. swcgeom/core/swc_utils/base.py +180 -0
  22. swcgeom/core/swc_utils/checker.py +107 -0
  23. swcgeom/core/swc_utils/io.py +204 -0
  24. swcgeom/core/swc_utils/normalizer.py +163 -0
  25. swcgeom/core/swc_utils/subtree.py +70 -0
  26. swcgeom/core/tree.py +384 -0
  27. swcgeom/core/tree_utils.py +277 -0
  28. swcgeom/core/tree_utils_impl.py +58 -0
  29. swcgeom/images/__init__.py +9 -0
  30. swcgeom/images/augmentation.py +149 -0
  31. swcgeom/images/contrast.py +87 -0
  32. swcgeom/images/folder.py +217 -0
  33. swcgeom/images/io.py +578 -0
  34. swcgeom/images/loaders/__init__.py +8 -0
  35. swcgeom/images/loaders/pbd.cpython-311-x86_64-linux-gnu.so +0 -0
  36. swcgeom/images/loaders/pbd.pyx +523 -0
  37. swcgeom/images/loaders/raw.cpython-311-x86_64-linux-gnu.so +0 -0
  38. swcgeom/images/loaders/raw.pyx +183 -0
  39. swcgeom/transforms/__init__.py +20 -0
  40. swcgeom/transforms/base.py +136 -0
  41. swcgeom/transforms/branch.py +223 -0
  42. swcgeom/transforms/branch_tree.py +74 -0
  43. swcgeom/transforms/geometry.py +270 -0
  44. swcgeom/transforms/image_preprocess.py +107 -0
  45. swcgeom/transforms/image_stack.py +219 -0
  46. swcgeom/transforms/images.py +206 -0
  47. swcgeom/transforms/mst.py +183 -0
  48. swcgeom/transforms/neurolucida_asc.py +498 -0
  49. swcgeom/transforms/path.py +56 -0
  50. swcgeom/transforms/population.py +36 -0
  51. swcgeom/transforms/tree.py +265 -0
  52. swcgeom/transforms/tree_assembler.py +161 -0
  53. swcgeom/utils/__init__.py +18 -0
  54. swcgeom/utils/debug.py +23 -0
  55. swcgeom/utils/download.py +119 -0
  56. swcgeom/utils/dsu.py +58 -0
  57. swcgeom/utils/ellipse.py +131 -0
  58. swcgeom/utils/file.py +90 -0
  59. swcgeom/utils/neuromorpho.py +581 -0
  60. swcgeom/utils/numpy_helper.py +70 -0
  61. swcgeom/utils/plotter_2d.py +134 -0
  62. swcgeom/utils/plotter_3d.py +35 -0
  63. swcgeom/utils/renderer.py +145 -0
  64. swcgeom/utils/sdf.py +324 -0
  65. swcgeom/utils/solid_geometry.py +154 -0
  66. swcgeom/utils/transforms.py +367 -0
  67. swcgeom/utils/volumetric_object.py +483 -0
  68. swcgeom-0.19.4.dist-info/METADATA +86 -0
  69. swcgeom-0.19.4.dist-info/RECORD +72 -0
  70. swcgeom-0.19.4.dist-info/WHEEL +6 -0
  71. swcgeom-0.19.4.dist-info/licenses/LICENSE +201 -0
  72. swcgeom-0.19.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,206 @@
1
+
2
+ # SPDX-FileCopyrightText: 2022 - 2025 Zexin Yuan <pypi@yzx9.xyz>
3
+ #
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ """Image stack related transform."""
7
+
8
+ import numpy as np
9
+ import numpy.typing as npt
10
+ from typing_extensions import deprecated, override
11
+
12
+ from swcgeom.transforms.base import Identity, Transform
13
+
14
+ __all__ = [
15
+ "ImagesCenterCrop",
16
+ "ImagesScale",
17
+ "ImagesClip",
18
+ "ImagesFlip",
19
+ "ImagesFlipY",
20
+ "ImagesNormalizer",
21
+ "ImagesMeanVarianceAdjustment",
22
+ "ImagesScaleToUnitRange",
23
+ "ImagesHistogramEqualization",
24
+ "Center", # legacy
25
+ ]
26
+
27
+
28
+ NDArrayf32 = npt.NDArray[np.float32]
29
+
30
+
31
+ class ImagesCenterCrop(Transform[NDArrayf32, NDArrayf32]):
32
+ """Get image stack center."""
33
+
34
+ def __init__(self, shape_out: int | tuple[int, int, int]):
35
+ super().__init__()
36
+ self.shape_out = (
37
+ shape_out
38
+ if isinstance(shape_out, tuple)
39
+ else (shape_out, shape_out, shape_out)
40
+ )
41
+
42
+ @override
43
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
44
+ diff = np.subtract(x.shape[:3], self.shape_out)
45
+ s = diff // 2
46
+ e = np.add(s, self.shape_out)
47
+ return x[s[0] : e[0], s[1] : e[1], s[2] : e[2], :]
48
+
49
+ @override
50
+ def extra_repr(self) -> str:
51
+ return f"shape_out=({','.join(str(a) for a in self.shape_out)})"
52
+
53
+
54
+ @deprecated("use `ImagesCenterCrop` instead", stacklevel=2)
55
+ class Center(ImagesCenterCrop):
56
+ """Get image stack center.
57
+
58
+ .. deprecated:: 0.16.0
59
+ Use :class:`ImagesCenterCrop` instead.
60
+ """
61
+
62
+
63
+ class ImagesScale(Transform[NDArrayf32, NDArrayf32]):
64
+ def __init__(self, scaler: float) -> None:
65
+ super().__init__()
66
+ self.scaler = scaler
67
+
68
+ @override
69
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
70
+ return self.scaler * x
71
+
72
+ @override
73
+ def extra_repr(self) -> str:
74
+ return f"scaler={self.scaler}"
75
+
76
+
77
+ class ImagesClip(Transform[NDArrayf32, NDArrayf32]):
78
+ def __init__(self, vmin: float = 0, vmax: float = 1, /) -> None:
79
+ super().__init__()
80
+ self.vmin, self.vmax = vmin, vmax
81
+
82
+ @override
83
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
84
+ return np.clip(x, self.vmin, self.vmax)
85
+
86
+ @override
87
+ def extra_repr(self) -> str:
88
+ return f"vmin={self.vmin}, vmax={self.vmax}"
89
+
90
+
91
+ class ImagesFlip(Transform[NDArrayf32, NDArrayf32]):
92
+ """Flip image stack along axis."""
93
+
94
+ def __init__(self, axis: int, /) -> None:
95
+ super().__init__()
96
+ self.axis = axis
97
+
98
+ @override
99
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
100
+ return np.flip(x, axis=self.axis)
101
+
102
+ @override
103
+ def extra_repr(self) -> str:
104
+ return f"axis={self.axis}"
105
+
106
+
107
+ class ImagesFlipY(ImagesFlip):
108
+ """Flip image stack along Y-axis.
109
+
110
+ See Also:
111
+ ~.images.io.TeraflyImageStack:
112
+ Terafly and Vaa3d use a especial right-handed coordinate system (with
113
+ origin point in the left-top and z-axis points front), but we flip y-axis
114
+ to makes it a left-handed coordinate system (with origin point in the
115
+ left-bottom and z-axis points front). If you need to use its coordinate
116
+ system, remember to FLIP Y-AXIS BACK.
117
+ """
118
+
119
+ def __init__(self, axis: int = 1, /) -> None:
120
+ super().__init__(axis) # (X, Y, Z, C)
121
+
122
+
123
+ class ImagesNormalizer(Transform[NDArrayf32, NDArrayf32]):
124
+ """Normalize image stack."""
125
+
126
+ @override
127
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
128
+ mean = np.mean(x)
129
+ variance = np.var(x)
130
+ return (x - mean) / variance
131
+
132
+
133
+ class ImagesMeanVarianceAdjustment(Transform[NDArrayf32, NDArrayf32]):
134
+ """Adjust image stack mean and variance.
135
+
136
+ See Also:
137
+ ~swcgeom.images.ImageStackFolder.stat
138
+ """
139
+
140
+ def __init__(self, mean: float, variance: float) -> None:
141
+ super().__init__()
142
+ self.mean = mean
143
+ self.variance = variance
144
+
145
+ @override
146
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
147
+ return (x - self.mean) / self.variance
148
+
149
+ @override
150
+ def extra_repr(self) -> str:
151
+ return f"mean={self.mean}, variance={self.variance}"
152
+
153
+
154
+ class ImagesScaleToUnitRange(Transform[NDArrayf32, NDArrayf32]):
155
+ """Scale image stack to unit range."""
156
+
157
+ def __init__(self, vmin: float, vmax: float, *, clip: bool = True) -> None:
158
+ """Scale image stack to unit range.
159
+
160
+ Args:
161
+ vmin: Minimum value.
162
+ vmax: Maximum value.
163
+ clip: Clip values to [0, 1] to avoid numerical issues.
164
+ """
165
+
166
+ super().__init__()
167
+ self.vmin = vmin
168
+ self.vmax = vmax
169
+ self.diff = vmax - vmin
170
+ self.clip = clip
171
+ self.post = ImagesClip(0, 1) if self.clip else Identity()
172
+
173
+ @override
174
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
175
+ return self.post((x - self.vmin) / self.diff)
176
+
177
+ @override
178
+ def extra_repr(self) -> str:
179
+ return f"vmin={self.vmin}, vmax={self.vmax}, clip={self.clip}"
180
+
181
+
182
+ class ImagesHistogramEqualization(Transform[NDArrayf32, NDArrayf32]):
183
+ """Image histogram equalization.
184
+
185
+ References:
186
+ http://www.janeriksolem.net/histogram-equalization-with-python-and.html
187
+ """
188
+
189
+ def __init__(self, bins: int = 256) -> None:
190
+ super().__init__()
191
+ self.bins = bins
192
+
193
+ @override
194
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
195
+ # get image histogram
196
+ hist, bin_edges = np.histogram(x.flatten(), self.bins, density=True)
197
+ cdf = hist.cumsum() # cumulative distribution function
198
+ cdf = cdf / cdf[-1] # normalize
199
+
200
+ # use linear interpolation of cdf to find new pixel values
201
+ equalized = np.interp(x.flatten(), bin_edges[:-1], cdf)
202
+ return equalized.reshape(x.shape).astype(np.float32)
203
+
204
+ @override
205
+ def extra_repr(self) -> str:
206
+ return f"bins={self.bins}"
@@ -0,0 +1,183 @@
1
+
2
+ # SPDX-FileCopyrightText: 2022 - 2025 Zexin Yuan <pypi@yzx9.xyz>
3
+ #
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ """Minimum spanning tree."""
7
+
8
+ import warnings
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+ from numpy import ma
13
+ from numpy import typing as npt
14
+ from typing_extensions import override
15
+
16
+ from swcgeom.core import Tree, sort_tree
17
+ from swcgeom.core.swc_utils import SWCNames, SWCTypes, get_names, get_types
18
+ from swcgeom.transforms.base import Transform
19
+
20
+ __all__ = ["PointsToCuntzMST", "PointsToMST"]
21
+
22
+
23
+ class PointsToCuntzMST(Transform[npt.NDArray[np.float32], Tree]):
24
+ """Create tree from points.
25
+
26
+ Creates trees corresponding to the minimum spanning tree keeping
27
+ the path length to the root small (with balancing factor bf).
28
+
29
+ References:
30
+ .. [1] Cuntz, H., Forstner, F., Borst, A. & Häusser, M. One Rule to
31
+ Grow Them Al: A General Theory of Neuronal Branching and Its
32
+ Practical Application. PLOS Comput Biol 6, e1000877 (2010).
33
+ .. [2] Cuntz, H., Borst, A. & Segev, I. Optimization principles of
34
+ dendritic structure. Theor Biol Med Model 4, 21 (2007).
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ *,
40
+ bf: float = 0.4,
41
+ furcations: int = 2,
42
+ exclude_soma: bool = True,
43
+ sort: bool = True,
44
+ names: SWCNames | None = None,
45
+ types: SWCTypes | None = None,
46
+ ) -> None:
47
+ """
48
+ Args:
49
+ bf: Balancing factor between 0~1.
50
+ furcations: Suppress multi-furcations which more than k.
51
+ If set to -1, no suppression.
52
+ exclude_soma: Suppress multi-furcations exclude soma.
53
+ """
54
+ self.bf = np.clip(bf, 0, 1)
55
+ self.furcations = furcations
56
+ self.exclude_soma = exclude_soma
57
+ self.sort = sort
58
+ self.names = get_names(names)
59
+ self.types = get_types(types)
60
+
61
+ @override
62
+ def __call__( # pylint: disable=too-many-locals
63
+ self,
64
+ points: npt.NDArray[np.floating],
65
+ soma: npt.ArrayLike | None = None,
66
+ *,
67
+ names: SWCNames | None = None,
68
+ ) -> Tree:
69
+ """
70
+ Args:
71
+ points: Positions of points cloud.
72
+ soma: Position of soma. If none, use the first point as soma.
73
+ """
74
+ if names is None:
75
+ names = self.names
76
+ else:
77
+ warnings.warn(
78
+ "`PointsToCuntzMST(...)(names=...)` has been replaced by "
79
+ "`PointsToCuntzMST(...,names=...)` since v0.12.0, and will be removed "
80
+ "in next version",
81
+ DeprecationWarning,
82
+ )
83
+ names = get_names(names) # TODO: remove it
84
+
85
+ if soma is not None:
86
+ soma = np.array(soma)
87
+ assert soma.shape == (3,)
88
+ points = np.concatenate([[soma], points])
89
+
90
+ n = points.shape[0]
91
+ dis = np.linalg.norm(
92
+ points.reshape((-1, 1, 3)) - points.reshape((1, -1, 3)), axis=2
93
+ )
94
+
95
+ pid = np.full(n, fill_value=-1)
96
+ acc = np.zeros(n)
97
+ furcations = np.zeros(n, dtype=np.int32)
98
+ conn = np.zeros(n, dtype=np.bool_) # connect points
99
+ conn[0] = True
100
+ mask = np.ones((n, n), dtype=np.bool_)
101
+ mask[0, :] = False
102
+ mask[0, 0] = True
103
+ for _ in range(n - 1): # for tree: e = n-1
104
+ cost = ma.array(dis + self.bf * acc, mask=mask)
105
+ (i, j) = np.unravel_index(cost.argmin(), cost.shape)
106
+
107
+ furcations[i] += 1
108
+ if (
109
+ self.furcations != -1
110
+ and furcations[i] >= self.furcations
111
+ and (not self.exclude_soma or i != 0)
112
+ ):
113
+ mask[i, :] = True # avoid it link to any point
114
+ mask[:, i] = True # avoid any point lint to it
115
+
116
+ pid[j] = i
117
+ acc[j] = acc[i] + dis[i, j]
118
+ conn[j] = True
119
+ mask[j, :] = conn # enable link to other points
120
+ mask[:, j] = True # avoid any point lint to it
121
+
122
+ dic = {
123
+ names.id: np.arange(n),
124
+ names.type: np.full(n, fill_value=self.types.glia_processes),
125
+ names.x: points[:, 0],
126
+ names.y: points[:, 1],
127
+ names.z: points[:, 2],
128
+ names.r: 1,
129
+ names.pid: pid,
130
+ }
131
+ dic[names.type][0] = self.types.soma
132
+ df = pd.DataFrame.from_dict(dic)
133
+ t = Tree.from_data_frame(df, names=names)
134
+ if self.sort:
135
+ t = sort_tree(t)
136
+ return t
137
+
138
+ @override
139
+ def extra_repr(self) -> str: # TODO: names, types
140
+ return f"bf={self.bf:.4f}, furcations={self.furcations}, exclude_soma={self.exclude_soma}, sort={self.sort}"
141
+
142
+
143
+ class PointsToMST(PointsToCuntzMST): # pylint: disable=too-few-public-methods
144
+ """Create minimum spanning tree from points."""
145
+
146
+ def __init__(
147
+ self,
148
+ furcations: int = 2,
149
+ *,
150
+ k_furcations: int | None = None,
151
+ exclude_soma: bool = True,
152
+ names: SWCNames | None = None,
153
+ types: SWCTypes | None = None,
154
+ **kwargs,
155
+ ) -> None:
156
+ """
157
+ Args:
158
+ furcations: Suppress multifurcations which more than k.
159
+ If set to -1, no suppression.
160
+ exclude_soma: Suppress multi-furcations exclude soma.
161
+ """
162
+
163
+ if k_furcations is not None:
164
+ warnings.warn(
165
+ "`PointsToMST(k_furcations=...)` has been renamed to "
166
+ "`PointsToMST(furcations=...)` since v0.12.0, and will "
167
+ "be removed in next version",
168
+ DeprecationWarning,
169
+ )
170
+ furcations = k_furcations
171
+
172
+ super().__init__(
173
+ bf=0,
174
+ furcations=furcations,
175
+ exclude_soma=exclude_soma,
176
+ names=names,
177
+ types=types,
178
+ **kwargs,
179
+ )
180
+
181
+ @override
182
+ def extra_repr(self) -> str:
183
+ return f"furcations-{self.furcations}, exclude-soma={self.exclude_soma}"