swcgeom 0.16.0__py3-none-any.whl → 0.18.3__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 +26 -1
- swcgeom/analysis/__init__.py +21 -8
- swcgeom/analysis/feature_extractor.py +43 -18
- swcgeom/analysis/features.py +250 -0
- swcgeom/analysis/lmeasure.py +48 -12
- swcgeom/analysis/sholl.py +25 -28
- swcgeom/analysis/trunk.py +27 -11
- swcgeom/analysis/visualization.py +24 -9
- swcgeom/analysis/visualization3d.py +100 -0
- swcgeom/analysis/volume.py +19 -4
- swcgeom/core/__init__.py +31 -12
- swcgeom/core/branch.py +19 -3
- swcgeom/core/branch_tree.py +18 -4
- swcgeom/core/compartment.py +18 -2
- swcgeom/core/node.py +32 -3
- swcgeom/core/path.py +21 -9
- swcgeom/core/population.py +58 -29
- swcgeom/core/swc.py +26 -10
- swcgeom/core/swc_utils/__init__.py +21 -7
- swcgeom/core/swc_utils/assembler.py +15 -0
- swcgeom/core/swc_utils/base.py +23 -17
- swcgeom/core/swc_utils/checker.py +19 -12
- swcgeom/core/swc_utils/io.py +24 -7
- swcgeom/core/swc_utils/normalizer.py +20 -4
- swcgeom/core/swc_utils/subtree.py +17 -2
- swcgeom/core/tree.py +56 -40
- swcgeom/core/tree_utils.py +28 -17
- swcgeom/core/tree_utils_impl.py +18 -3
- swcgeom/images/__init__.py +17 -2
- swcgeom/images/augmentation.py +18 -3
- swcgeom/images/contrast.py +15 -0
- swcgeom/images/folder.py +27 -26
- swcgeom/images/io.py +94 -117
- swcgeom/transforms/__init__.py +28 -12
- swcgeom/transforms/base.py +17 -2
- swcgeom/transforms/branch.py +74 -8
- swcgeom/transforms/branch_tree.py +82 -0
- swcgeom/transforms/geometry.py +22 -7
- swcgeom/transforms/image_preprocess.py +15 -0
- swcgeom/transforms/image_stack.py +36 -9
- swcgeom/transforms/images.py +121 -14
- swcgeom/transforms/mst.py +15 -0
- swcgeom/transforms/neurolucida_asc.py +20 -7
- swcgeom/transforms/path.py +15 -0
- swcgeom/transforms/population.py +16 -3
- swcgeom/transforms/tree.py +84 -30
- swcgeom/transforms/tree_assembler.py +23 -7
- swcgeom/utils/__init__.py +27 -12
- swcgeom/utils/debug.py +15 -0
- swcgeom/utils/download.py +59 -21
- swcgeom/utils/dsu.py +15 -0
- swcgeom/utils/ellipse.py +18 -4
- swcgeom/utils/file.py +15 -0
- swcgeom/utils/neuromorpho.py +35 -23
- swcgeom/utils/numpy_helper.py +15 -0
- swcgeom/utils/plotter_2d.py +27 -6
- swcgeom/utils/plotter_3d.py +48 -0
- swcgeom/utils/renderer.py +21 -6
- swcgeom/utils/sdf.py +19 -7
- swcgeom/utils/solid_geometry.py +16 -3
- swcgeom/utils/transforms.py +17 -4
- swcgeom/utils/volumetric_object.py +23 -10
- {swcgeom-0.16.0.dist-info → swcgeom-0.18.3.dist-info}/LICENSE +1 -1
- {swcgeom-0.16.0.dist-info → swcgeom-0.18.3.dist-info}/METADATA +28 -24
- swcgeom-0.18.3.dist-info/RECORD +67 -0
- {swcgeom-0.16.0.dist-info → swcgeom-0.18.3.dist-info}/WHEEL +1 -1
- swcgeom/_version.py +0 -16
- swcgeom/analysis/branch_features.py +0 -67
- swcgeom/analysis/node_features.py +0 -121
- swcgeom/analysis/path_features.py +0 -37
- swcgeom-0.16.0.dist-info/RECORD +0 -67
- {swcgeom-0.16.0.dist-info → swcgeom-0.18.3.dist-info}/top_level.txt +0 -0
swcgeom/analysis/sholl.py
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Sholl analysis."""
|
|
2
17
|
|
|
3
18
|
import warnings
|
|
4
|
-
from typing import
|
|
19
|
+
from typing import Literal, Optional
|
|
5
20
|
|
|
6
21
|
import numpy as np
|
|
7
22
|
import numpy.typing as npt
|
|
8
23
|
import seaborn as sns
|
|
9
24
|
from matplotlib.axes import Axes
|
|
10
25
|
from matplotlib.figure import Figure
|
|
26
|
+
from typing_extensions import deprecated
|
|
11
27
|
|
|
12
28
|
from swcgeom.analysis.visualization import draw
|
|
13
29
|
from swcgeom.core import Tree
|
|
@@ -84,18 +100,18 @@ class Sholl:
|
|
|
84
100
|
|
|
85
101
|
def plot( # pylint: disable=too-many-arguments
|
|
86
102
|
self,
|
|
87
|
-
steps:
|
|
103
|
+
steps: list[float] | int = 20,
|
|
88
104
|
plot_type: str | None = None,
|
|
89
105
|
kind: Literal["bar", "linechart", "circles"] = "circles",
|
|
90
106
|
fig: Figure | None = None,
|
|
91
107
|
ax: Axes | None = None,
|
|
92
108
|
**kwargs,
|
|
93
|
-
) ->
|
|
109
|
+
) -> tuple[Figure, Axes]:
|
|
94
110
|
"""Plot Sholl analysis.
|
|
95
111
|
|
|
96
112
|
Parameters
|
|
97
113
|
----------
|
|
98
|
-
steps : int or
|
|
114
|
+
steps : int or list[float], default to 20
|
|
99
115
|
Steps of raius of circle. If steps is int, then it will be
|
|
100
116
|
evenly divided into n radii.
|
|
101
117
|
kind : "bar" | "linechart" | "circles", default `circles`
|
|
@@ -160,20 +176,17 @@ class Sholl:
|
|
|
160
176
|
|
|
161
177
|
return self.get_rs(self.rmax, steps)
|
|
162
178
|
|
|
179
|
+
@deprecated("Use `Sholl.get(x)` instead")
|
|
163
180
|
def get_count(self) -> npt.NDArray[np.int32]:
|
|
164
181
|
"""Get the count of intersection.
|
|
165
182
|
|
|
166
183
|
.. deprecated:: 0.5.0
|
|
167
|
-
Use :meth:`Sholl.get` instead.
|
|
184
|
+
Use :meth:`Sholl(x).get()` instead.
|
|
168
185
|
"""
|
|
169
186
|
|
|
170
|
-
warnings.warn(
|
|
171
|
-
"`Sholl.get_count` has been renamed to `get` since v0.5.0, "
|
|
172
|
-
"and will be removed in next version",
|
|
173
|
-
DeprecationWarning,
|
|
174
|
-
)
|
|
175
187
|
return self.get().astype(np.int32)
|
|
176
188
|
|
|
189
|
+
@deprecated("Use `Shool(x).get().mean()` instead")
|
|
177
190
|
def avg(self) -> float:
|
|
178
191
|
"""Get the average of the count of intersection.
|
|
179
192
|
|
|
@@ -181,14 +194,9 @@ class Sholl:
|
|
|
181
194
|
Use :meth:`Shool(x).get().mean()` instead.
|
|
182
195
|
"""
|
|
183
196
|
|
|
184
|
-
warnings.warn(
|
|
185
|
-
"`Sholl.avg` has been deprecated since v0.6.0 and will be "
|
|
186
|
-
"removed in next version, use `Shool(x).get().mean()` "
|
|
187
|
-
"instead",
|
|
188
|
-
DeprecationWarning,
|
|
189
|
-
)
|
|
190
197
|
return self.get().mean()
|
|
191
198
|
|
|
199
|
+
@deprecated("Use `Shool(x).get().std()` instead")
|
|
192
200
|
def std(self) -> float:
|
|
193
201
|
"""Get the std of the count of intersection.
|
|
194
202
|
|
|
@@ -196,14 +204,9 @@ class Sholl:
|
|
|
196
204
|
Use :meth:`Shool(x).get().std()` instead.
|
|
197
205
|
"""
|
|
198
206
|
|
|
199
|
-
warnings.warn(
|
|
200
|
-
"`Sholl.std` has been deprecate since v0.6.0 and will be "
|
|
201
|
-
"removed in next version, use `Shool(x).get().std()` "
|
|
202
|
-
"instead",
|
|
203
|
-
DeprecationWarning,
|
|
204
|
-
)
|
|
205
207
|
return self.get().std()
|
|
206
208
|
|
|
209
|
+
@deprecated("Use `Shool(x).get().sum()` instead")
|
|
207
210
|
def sum(self) -> int:
|
|
208
211
|
"""Get the sum of the count of intersection.
|
|
209
212
|
|
|
@@ -211,10 +214,4 @@ class Sholl:
|
|
|
211
214
|
Use :meth:`Shool(x).get().sum()` instead.
|
|
212
215
|
"""
|
|
213
216
|
|
|
214
|
-
warnings.warn(
|
|
215
|
-
"`Sholl.sum` has been deprecate since v0.6.0 and will be "
|
|
216
|
-
"removed in next version, use `Shool(x).get().sum()` "
|
|
217
|
-
"instead",
|
|
218
|
-
DeprecationWarning,
|
|
219
|
-
)
|
|
220
217
|
return self.get().sum()
|
swcgeom/analysis/trunk.py
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Plot trunk and florets."""
|
|
2
17
|
|
|
3
18
|
# pylint: disable=invalid-name
|
|
4
19
|
|
|
20
|
+
from collections.abc import Iterable
|
|
5
21
|
from itertools import chain
|
|
6
|
-
from typing import Any,
|
|
22
|
+
from typing import Any, Literal, Optional, cast
|
|
7
23
|
|
|
8
24
|
import numpy as np
|
|
9
25
|
import numpy.typing as npt
|
|
@@ -28,28 +44,28 @@ def draw_trunk(
|
|
|
28
44
|
*,
|
|
29
45
|
fig: Optional[Figure] = None,
|
|
30
46
|
ax: Optional[Axes] = None,
|
|
31
|
-
bound: Bounds |
|
|
32
|
-
point: bool |
|
|
47
|
+
bound: Bounds | tuple[Bounds, dict[str, Any]] | None = "ellipse",
|
|
48
|
+
point: bool | dict[str, Any] = True,
|
|
33
49
|
projection: Projection = "2d",
|
|
34
50
|
cmap: Any = "viridis",
|
|
35
51
|
**kwargs,
|
|
36
|
-
) ->
|
|
52
|
+
) -> tuple[Figure, Axes]:
|
|
37
53
|
"""Draw trunk tree.
|
|
38
54
|
|
|
39
55
|
Parameters
|
|
40
56
|
----------
|
|
41
57
|
t : Tree
|
|
42
|
-
florets : List of (int |
|
|
58
|
+
florets : List of (int | list of int)
|
|
43
59
|
The florets that needs to be removed, each floret can be a
|
|
44
60
|
subtree or multiple subtrees (e.g., dendrites are a bunch of
|
|
45
61
|
subtrees), each number is the id of a tree node.
|
|
46
62
|
fig : ~matplotlib.figure.Figure, optional
|
|
47
63
|
ax : ~matplotlib.axes.Axes, optional
|
|
48
|
-
bound : Bounds | (Bounds,
|
|
64
|
+
bound : Bounds | (Bounds, dict[str, Any]) | None, default 'ellipse'
|
|
49
65
|
Kind of bound, support 'aabb', 'ellipse'. If bound is None, no
|
|
50
66
|
bound will be drawn. If bound is a tuple, the second item will
|
|
51
67
|
used as kwargs and forward to draw function.
|
|
52
|
-
point : bool |
|
|
68
|
+
point : bool | dict[str, Any], default True
|
|
53
69
|
Draw point at the start of a subtree. If point is False, no
|
|
54
70
|
point will be drawn. If point is a dict, this will used a
|
|
55
71
|
kwargs and forward to draw function.
|
|
@@ -57,7 +73,7 @@ def draw_trunk(
|
|
|
57
73
|
Colormap, any value supported by ~matplotlib.cm.Colormap. We
|
|
58
74
|
will use the ratio of the length of the subtree to the total
|
|
59
75
|
length of the tree to determine the color.
|
|
60
|
-
**kwargs :
|
|
76
|
+
**kwargs : dict[str, Any]
|
|
61
77
|
Forward to ~swcgeom.analysis.draw.
|
|
62
78
|
"""
|
|
63
79
|
# pylint: disable=too-many-locals
|
|
@@ -83,14 +99,14 @@ def draw_trunk(
|
|
|
83
99
|
|
|
84
100
|
def split_florets(
|
|
85
101
|
t: Tree, florets: Iterable[int | Iterable[int]]
|
|
86
|
-
) ->
|
|
102
|
+
) -> tuple[Tree, list[list[Tree]]]:
|
|
87
103
|
florets = [[i] if isinstance(i, (int, np.integer)) else i for i in florets]
|
|
88
104
|
subtrees = [[get_subtree(t, ff) for ff in f] for f in florets]
|
|
89
105
|
trunk = to_subtree(t, chain(*florets))
|
|
90
106
|
return trunk, subtrees
|
|
91
107
|
|
|
92
108
|
|
|
93
|
-
def get_length_ratio(t: Tree, tss:
|
|
109
|
+
def get_length_ratio(t: Tree, tss: list[list[Tree]]) -> Any:
|
|
94
110
|
lens = np.array([sum(t.length() for t in ts) for ts in tss])
|
|
95
111
|
return lens / t.length()
|
|
96
112
|
|
|
@@ -101,7 +117,7 @@ def get_length_ratio(t: Tree, tss: List[List[Tree]]) -> Any:
|
|
|
101
117
|
def draw_bound(
|
|
102
118
|
ts: Iterable[Tree],
|
|
103
119
|
ax: Axes,
|
|
104
|
-
bound: Bounds |
|
|
120
|
+
bound: Bounds | tuple[Bounds, dict[str, Any]],
|
|
105
121
|
projection: Projection,
|
|
106
122
|
**kwargs,
|
|
107
123
|
) -> None:
|
|
@@ -1,8 +1,23 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Painter utils."""
|
|
2
17
|
|
|
3
18
|
import os
|
|
4
19
|
import weakref
|
|
5
|
-
from typing import Any,
|
|
20
|
+
from typing import Any, Literal, Optional
|
|
6
21
|
|
|
7
22
|
import numpy as np
|
|
8
23
|
from matplotlib.axes import Axes
|
|
@@ -21,15 +36,15 @@ from swcgeom.utils import (
|
|
|
21
36
|
|
|
22
37
|
__all__ = ["draw"]
|
|
23
38
|
|
|
24
|
-
Positions = Literal["lt", "lb", "rt", "rb"] |
|
|
25
|
-
locations:
|
|
39
|
+
Positions = Literal["lt", "lb", "rt", "rb"] | tuple[float, float]
|
|
40
|
+
locations: dict[Literal["lt", "lb", "rt", "rb"], tuple[float, float]] = {
|
|
26
41
|
"lt": (0.10, 0.90),
|
|
27
42
|
"lb": (0.10, 0.10),
|
|
28
43
|
"rt": (0.90, 0.90),
|
|
29
44
|
"rb": (0.90, 0.10),
|
|
30
45
|
}
|
|
31
46
|
|
|
32
|
-
ax_weak_memo = weakref.WeakKeyDictionary[Axes,
|
|
47
|
+
ax_weak_memo = weakref.WeakKeyDictionary[Axes, dict[str, Any]]({})
|
|
33
48
|
|
|
34
49
|
|
|
35
50
|
def draw(
|
|
@@ -39,7 +54,7 @@ def draw(
|
|
|
39
54
|
ax: Optional[Axes] = None,
|
|
40
55
|
show: bool | None = None,
|
|
41
56
|
camera: CameraOptions = "xy",
|
|
42
|
-
color: Optional[
|
|
57
|
+
color: Optional[dict[int, str] | str] = None,
|
|
43
58
|
label: str | bool = True,
|
|
44
59
|
direction_indicator: Positions | Literal[False] = "rb",
|
|
45
60
|
unit: Optional[str] = None,
|
|
@@ -64,7 +79,7 @@ def draw(
|
|
|
64
79
|
vector, then then threat it as (look-at, up), so camera is
|
|
65
80
|
((0, 0, 0), look-at, up). An easy way is to use the presets
|
|
66
81
|
"xy", "yz" and "zx".
|
|
67
|
-
color :
|
|
82
|
+
color : dict[int, str] | "vaa3d" | str, optional
|
|
68
83
|
Color map. If is dict, segments will be colored by the type of
|
|
69
84
|
parent node.If is string, the value will be use for any type.
|
|
70
85
|
label : str | bool, default True
|
|
@@ -120,14 +135,14 @@ def draw(
|
|
|
120
135
|
return fig, ax
|
|
121
136
|
|
|
122
137
|
|
|
123
|
-
def get_ax_swc(ax: Axes) ->
|
|
138
|
+
def get_ax_swc(ax: Axes) -> list[SWCLike]:
|
|
124
139
|
ax_weak_memo.setdefault(ax, {})
|
|
125
140
|
return ax_weak_memo[ax]["swc"]
|
|
126
141
|
|
|
127
142
|
|
|
128
143
|
def get_ax_color(
|
|
129
|
-
ax: Axes, swc: SWCLike, color: Optional[
|
|
130
|
-
) -> str |
|
|
144
|
+
ax: Axes, swc: SWCLike, color: Optional[dict[int, str] | str] = None
|
|
145
|
+
) -> str | list[str]:
|
|
131
146
|
if color == "vaa3d":
|
|
132
147
|
color = palette.vaa3d
|
|
133
148
|
elif isinstance(color, str):
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
"""Painter utils.
|
|
17
|
+
|
|
18
|
+
Notes
|
|
19
|
+
-----
|
|
20
|
+
This is a experimental function, it may be changed in the future.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
from matplotlib.axes import Axes
|
|
27
|
+
from matplotlib.figure import Figure
|
|
28
|
+
from mpl_toolkits.mplot3d import Axes3D
|
|
29
|
+
|
|
30
|
+
from swcgeom.analysis.visualization import (
|
|
31
|
+
_set_ax_memo,
|
|
32
|
+
get_ax_color,
|
|
33
|
+
get_ax_swc,
|
|
34
|
+
set_ax_legend,
|
|
35
|
+
)
|
|
36
|
+
from swcgeom.core import SWCLike, Tree
|
|
37
|
+
from swcgeom.utils.plotter_3d import draw_lines_3d
|
|
38
|
+
|
|
39
|
+
__all__ = ["draw3d"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# TODO: support Camera
|
|
43
|
+
def draw3d(
|
|
44
|
+
swc: SWCLike | str,
|
|
45
|
+
*,
|
|
46
|
+
ax: Axes,
|
|
47
|
+
show: bool | None = None,
|
|
48
|
+
color: Optional[dict[int, str] | str] = None,
|
|
49
|
+
label: str | bool = True,
|
|
50
|
+
**kwargs,
|
|
51
|
+
) -> tuple[Figure, Axes]:
|
|
52
|
+
r"""Draw neuron tree.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
swc : SWCLike | str
|
|
57
|
+
If it is str, then it is treated as the path of swc file.
|
|
58
|
+
fig : ~matplotlib.axes.Figure, optional
|
|
59
|
+
ax : ~matplotlib.axes.Axes, optional
|
|
60
|
+
show : bool | None, default `None`
|
|
61
|
+
Wheather to call `plt.show()`. If not specified, it will depend
|
|
62
|
+
on if ax is passed in, it will not be called, otherwise it will
|
|
63
|
+
be called by default.
|
|
64
|
+
color : dict[int, str] | "vaa3d" | str, optional
|
|
65
|
+
Color map. If is dict, segments will be colored by the type of
|
|
66
|
+
parent node.If is string, the value will be use for any type.
|
|
67
|
+
label : str | bool, default True
|
|
68
|
+
Label of legend, disable if False.
|
|
69
|
+
**kwargs : dict[str, Unknown]
|
|
70
|
+
Forwarded to `~mpl_toolkits.mplot3d.art3d.Line3DCollection`.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
assert isinstance(ax, Axes3D), "only support 3D axes."
|
|
74
|
+
|
|
75
|
+
swc = Tree.from_swc(swc) if isinstance(swc, str) else swc
|
|
76
|
+
|
|
77
|
+
show = (show is True) or (show is None and ax is None)
|
|
78
|
+
my_color = get_ax_color(ax, swc, color) # type: ignore
|
|
79
|
+
|
|
80
|
+
xyz = swc.xyz()
|
|
81
|
+
starts, ends = swc.id()[1:], swc.pid()[1:]
|
|
82
|
+
lines = np.stack([xyz[starts], xyz[ends]], axis=1)
|
|
83
|
+
collection = draw_lines_3d(ax, lines, color=my_color, **kwargs)
|
|
84
|
+
|
|
85
|
+
min_vals = lines.reshape(-1, 3).min(axis=0)
|
|
86
|
+
max_vals = lines.reshape(-1, 3).max(axis=0)
|
|
87
|
+
ax.set_xlim(min_vals[0], max_vals[0])
|
|
88
|
+
ax.set_ylim(min_vals[1], max_vals[1])
|
|
89
|
+
ax.set_zlim(min_vals[2], max_vals[2])
|
|
90
|
+
|
|
91
|
+
_set_ax_memo(ax, swc, label=label, handle=collection)
|
|
92
|
+
|
|
93
|
+
if len(get_ax_swc(ax)) == 1:
|
|
94
|
+
# ax.set_aspect(1)
|
|
95
|
+
ax.spines[["top", "right"]].set_visible(False)
|
|
96
|
+
else:
|
|
97
|
+
set_ax_legend(ax, loc="upper right") # enable legend
|
|
98
|
+
|
|
99
|
+
fig = ax.figure
|
|
100
|
+
return fig, ax # type: ignore
|
swcgeom/analysis/volume.py
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Analysis of volume of a SWC tree."""
|
|
2
17
|
|
|
3
|
-
from typing import
|
|
18
|
+
from typing import Literal
|
|
4
19
|
|
|
5
20
|
import numpy as np
|
|
6
21
|
from sdflit import ColoredMaterial, ObjectsScene, SDFObject, UniformSampler
|
|
@@ -11,7 +26,7 @@ from swcgeom.utils import VolFrustumCone, VolSphere
|
|
|
11
26
|
__all__ = ["get_volume"]
|
|
12
27
|
|
|
13
28
|
ACCURACY_LEVEL = Literal["low", "middle", "high"]
|
|
14
|
-
ACCURACY_LEVELS:
|
|
29
|
+
ACCURACY_LEVELS: dict[ACCURACY_LEVEL, int] = {"low": 3, "middle": 5, "high": 8}
|
|
15
30
|
|
|
16
31
|
|
|
17
32
|
def get_volume(
|
|
@@ -93,7 +108,7 @@ def _get_volume_frustum_cone(tree: Tree, *, accuracy: int) -> float:
|
|
|
93
108
|
|
|
94
109
|
volume = 0.0
|
|
95
110
|
|
|
96
|
-
def leave(n: Tree.Node, children:
|
|
111
|
+
def leave(n: Tree.Node, children: list[VolSphere]) -> VolSphere:
|
|
97
112
|
sphere = VolSphere(n.xyz(), n.r)
|
|
98
113
|
cones = [VolFrustumCone(n.xyz(), n.r, c.center, c.radius) for c in children]
|
|
99
114
|
|
|
@@ -129,7 +144,7 @@ def _get_volume_frustum_cone_mc_only(tree: Tree) -> float:
|
|
|
129
144
|
scene = ObjectsScene()
|
|
130
145
|
scene.set_background((0, 0, 0))
|
|
131
146
|
|
|
132
|
-
def leave(n: Tree.Node, children:
|
|
147
|
+
def leave(n: Tree.Node, children: list[VolSphere]) -> VolSphere:
|
|
133
148
|
sphere = VolSphere(n.xyz(), n.r)
|
|
134
149
|
scene.add_object(SDFObject(sphere.sdf, material).into())
|
|
135
150
|
|
swcgeom/core/__init__.py
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Neuron trees."""
|
|
2
17
|
|
|
3
|
-
from swcgeom.core import swc_utils
|
|
4
|
-
from swcgeom.core.branch import *
|
|
5
|
-
from swcgeom.core.branch_tree import *
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
18
|
+
from swcgeom.core import swc_utils as swc_utils
|
|
19
|
+
from swcgeom.core.branch import * # noqa: F403
|
|
20
|
+
from swcgeom.core.branch_tree import * # noqa: F403
|
|
21
|
+
|
|
22
|
+
# Segment and Segments don't expose
|
|
23
|
+
from swcgeom.core.compartment import (
|
|
24
|
+
Compartment as Compartment,
|
|
25
|
+
)
|
|
26
|
+
from swcgeom.core.compartment import (
|
|
27
|
+
Compartments as Compartments,
|
|
9
28
|
)
|
|
10
|
-
from swcgeom.core.node import *
|
|
11
|
-
from swcgeom.core.path import *
|
|
12
|
-
from swcgeom.core.population import *
|
|
13
|
-
from swcgeom.core.swc import *
|
|
14
|
-
from swcgeom.core.tree import *
|
|
15
|
-
from swcgeom.core.tree_utils import *
|
|
29
|
+
from swcgeom.core.node import * # noqa: F403
|
|
30
|
+
from swcgeom.core.path import * # noqa: F403
|
|
31
|
+
from swcgeom.core.population import * # noqa: F403
|
|
32
|
+
from swcgeom.core.swc import * # noqa: F403
|
|
33
|
+
from swcgeom.core.tree import * # noqa: F403
|
|
34
|
+
from swcgeom.core.tree_utils import * # noqa: F403
|
swcgeom/core/branch.py
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Branch is a set of node points."""
|
|
2
17
|
|
|
3
|
-
from
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
from typing import Generic
|
|
4
20
|
|
|
5
21
|
import numpy as np
|
|
6
22
|
import numpy.typing as npt
|
|
@@ -92,7 +108,7 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
92
108
|
@classmethod
|
|
93
109
|
def from_xyzr_batch(
|
|
94
110
|
cls, xyzr_batch: npt.NDArray[np.float32]
|
|
95
|
-
) ->
|
|
111
|
+
) -> list["Branch[DictSWC]"]:
|
|
96
112
|
r"""Create list of branch form ~numpy.ndarray.
|
|
97
113
|
|
|
98
114
|
Parameters
|
|
@@ -112,7 +128,7 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
112
128
|
)
|
|
113
129
|
xyzr_batch = np.concatenate([xyzr_batch, ones], axis=2)
|
|
114
130
|
|
|
115
|
-
branches:
|
|
131
|
+
branches: list[Branch[DictSWC]] = []
|
|
116
132
|
for xyzr in xyzr_batch:
|
|
117
133
|
n_nodes = xyzr.shape[0]
|
|
118
134
|
idx = np.arange(0, n_nodes, step=1, dtype=np.int32)
|
swcgeom/core/branch_tree.py
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Branch tree is a simplified neuron tree."""
|
|
2
17
|
|
|
3
18
|
import itertools
|
|
4
|
-
from typing import Dict, List
|
|
5
19
|
|
|
6
20
|
import numpy as np
|
|
7
21
|
import pandas as pd
|
|
@@ -19,13 +33,13 @@ class BranchTree(Tree):
|
|
|
19
33
|
A branch tree that contains only soma, branch, and tip nodes.
|
|
20
34
|
"""
|
|
21
35
|
|
|
22
|
-
branches:
|
|
36
|
+
branches: dict[int, list[Branch]]
|
|
23
37
|
|
|
24
|
-
def get_origin_branches(self) ->
|
|
38
|
+
def get_origin_branches(self) -> list[Branch]:
|
|
25
39
|
"""Get branches of original tree."""
|
|
26
40
|
return list(itertools.chain(*self.branches.values()))
|
|
27
41
|
|
|
28
|
-
def get_origin_node_branches(self, idx: int) ->
|
|
42
|
+
def get_origin_node_branches(self, idx: int) -> list[Branch]:
|
|
29
43
|
"""Get branches of node of original tree."""
|
|
30
44
|
return self.branches[idx]
|
|
31
45
|
|
swcgeom/core/compartment.py
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""The segment is a branch with two nodes."""
|
|
2
17
|
|
|
3
|
-
from
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
from typing import Generic, TypeVar
|
|
4
20
|
|
|
5
21
|
import numpy as np
|
|
6
22
|
import numpy.typing as npt
|
|
@@ -43,7 +59,7 @@ class Compartment(Path, Generic[SWCTypeVar]):
|
|
|
43
59
|
T = TypeVar("T", bound=Compartment)
|
|
44
60
|
|
|
45
61
|
|
|
46
|
-
class Compartments(
|
|
62
|
+
class Compartments(list[T]):
|
|
47
63
|
r"""Comparments contains a set of comparment."""
|
|
48
64
|
|
|
49
65
|
names: SWCNames
|
swcgeom/core/node.py
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Nueron node."""
|
|
2
17
|
|
|
3
|
-
import
|
|
4
|
-
from typing import Any, Generic
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
from typing import Any, Generic
|
|
5
20
|
|
|
6
21
|
import numpy as np
|
|
7
22
|
import numpy.typing as npt
|
|
23
|
+
from typing_extensions import deprecated
|
|
8
24
|
|
|
9
25
|
from swcgeom.core.swc import DictSWC, SWCTypeVar
|
|
10
26
|
from swcgeom.core.swc_utils import SWCNames
|
|
@@ -95,9 +111,22 @@ class Node(Generic[SWCTypeVar]):
|
|
|
95
111
|
items = [self.id, self.type, x, y, z, r, self.pid]
|
|
96
112
|
return " ".join(map(str, items))
|
|
97
113
|
|
|
98
|
-
def
|
|
114
|
+
def is_furcation(self) -> bool:
|
|
115
|
+
"""Is furcation node."""
|
|
99
116
|
return np.count_nonzero(self.attach.pid() == self.id) > 1
|
|
100
117
|
|
|
118
|
+
@deprecated("Use is_furcation instead")
|
|
119
|
+
def is_bifurcation(self) -> bool:
|
|
120
|
+
"""Is furcation node.
|
|
121
|
+
|
|
122
|
+
Notes
|
|
123
|
+
-----
|
|
124
|
+
Deprecated due to the wrong spelling of furcation. For now, it
|
|
125
|
+
is just an alias of `is_furcation` and raise a warning. It will
|
|
126
|
+
be change to raise an error in the future.
|
|
127
|
+
"""
|
|
128
|
+
return self.is_furcation()
|
|
129
|
+
|
|
101
130
|
def is_tip(self) -> bool:
|
|
102
131
|
return self.id not in self.attach.pid()
|
|
103
132
|
|