swcgeom 0.15.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 +857 -0
- swcgeom/analysis/sholl.py +55 -29
- 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 +32 -9
- swcgeom/core/branch.py +28 -7
- swcgeom/core/branch_tree.py +18 -4
- swcgeom/core/{segment.py → compartment.py} +31 -10
- swcgeom/core/node.py +31 -10
- swcgeom/core/path.py +37 -10
- swcgeom/core/population.py +103 -34
- swcgeom/core/swc.py +26 -10
- swcgeom/core/swc_utils/__init__.py +21 -7
- swcgeom/core/swc_utils/assembler.py +27 -1
- swcgeom/core/swc_utils/base.py +25 -12
- swcgeom/core/swc_utils/checker.py +31 -14
- 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 +85 -72
- swcgeom/core/tree_utils.py +31 -16
- swcgeom/core/tree_utils_impl.py +18 -3
- swcgeom/images/__init__.py +17 -2
- swcgeom/images/augmentation.py +24 -4
- swcgeom/images/contrast.py +122 -0
- swcgeom/images/folder.py +97 -39
- swcgeom/images/io.py +108 -121
- swcgeom/transforms/__init__.py +28 -10
- 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 +115 -0
- swcgeom/transforms/image_stack.py +37 -13
- swcgeom/transforms/images.py +184 -7
- swcgeom/transforms/mst.py +20 -5
- swcgeom/transforms/neurolucida_asc.py +508 -0
- swcgeom/transforms/path.py +15 -0
- swcgeom/transforms/population.py +16 -3
- swcgeom/transforms/tree.py +89 -31
- swcgeom/transforms/tree_assembler.py +23 -7
- swcgeom/utils/__init__.py +27 -11
- 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 +439 -302
- swcgeom/utils/numpy_helper.py +29 -4
- swcgeom/utils/plotter_2d.py +151 -0
- swcgeom/utils/plotter_3d.py +48 -0
- swcgeom/utils/renderer.py +49 -145
- swcgeom/utils/sdf.py +24 -8
- swcgeom/utils/solid_geometry.py +16 -3
- swcgeom/utils/transforms.py +17 -4
- swcgeom/utils/volumetric_object.py +23 -10
- {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/LICENSE +1 -1
- {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/METADATA +28 -24
- swcgeom-0.18.3.dist-info/RECORD +67 -0
- {swcgeom-0.15.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.15.0.dist-info/RECORD +0 -62
- {swcgeom-0.15.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
|
|
@@ -23,10 +39,15 @@ YLABLE = "Count of Intersections"
|
|
|
23
39
|
class Sholl:
|
|
24
40
|
"""Sholl analysis.
|
|
25
41
|
|
|
42
|
+
Implementation of original Sholl analysis as described in [1]_. The
|
|
43
|
+
Sholl analysis is a method to quantify the spatial distribution of
|
|
44
|
+
neuronal processes. It is based on the number of intersections of
|
|
45
|
+
concentric circles with the neuronal processes.
|
|
46
|
+
|
|
26
47
|
References
|
|
27
48
|
----------
|
|
28
|
-
[1] Dendritic organization in the neurons of the visual and
|
|
29
|
-
|
|
49
|
+
.. [1] Dendritic organization in the neurons of the visual and
|
|
50
|
+
motor cortices of the cat J. Anat., 87 (1953), pp. 387-406
|
|
30
51
|
"""
|
|
31
52
|
|
|
32
53
|
tree: Tree
|
|
@@ -79,18 +100,18 @@ class Sholl:
|
|
|
79
100
|
|
|
80
101
|
def plot( # pylint: disable=too-many-arguments
|
|
81
102
|
self,
|
|
82
|
-
steps:
|
|
103
|
+
steps: list[float] | int = 20,
|
|
83
104
|
plot_type: str | None = None,
|
|
84
105
|
kind: Literal["bar", "linechart", "circles"] = "circles",
|
|
85
106
|
fig: Figure | None = None,
|
|
86
107
|
ax: Axes | None = None,
|
|
87
108
|
**kwargs,
|
|
88
|
-
) ->
|
|
109
|
+
) -> tuple[Figure, Axes]:
|
|
89
110
|
"""Plot Sholl analysis.
|
|
90
111
|
|
|
91
112
|
Parameters
|
|
92
113
|
----------
|
|
93
|
-
steps : int or
|
|
114
|
+
steps : int or list[float], default to 20
|
|
94
115
|
Steps of raius of circle. If steps is int, then it will be
|
|
95
116
|
evenly divided into n radii.
|
|
96
117
|
kind : "bar" | "linechart" | "circles", default `circles`
|
|
@@ -155,37 +176,42 @@ class Sholl:
|
|
|
155
176
|
|
|
156
177
|
return self.get_rs(self.rmax, steps)
|
|
157
178
|
|
|
179
|
+
@deprecated("Use `Sholl.get(x)` instead")
|
|
158
180
|
def get_count(self) -> npt.NDArray[np.int32]:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
181
|
+
"""Get the count of intersection.
|
|
182
|
+
|
|
183
|
+
.. deprecated:: 0.5.0
|
|
184
|
+
Use :meth:`Sholl(x).get()` instead.
|
|
185
|
+
"""
|
|
186
|
+
|
|
164
187
|
return self.get().astype(np.int32)
|
|
165
188
|
|
|
189
|
+
@deprecated("Use `Shool(x).get().mean()` instead")
|
|
166
190
|
def avg(self) -> float:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
"""Get the average of the count of intersection.
|
|
192
|
+
|
|
193
|
+
.. deprecated:: 0.6.0
|
|
194
|
+
Use :meth:`Shool(x).get().mean()` instead.
|
|
195
|
+
"""
|
|
196
|
+
|
|
173
197
|
return self.get().mean()
|
|
174
198
|
|
|
199
|
+
@deprecated("Use `Shool(x).get().std()` instead")
|
|
175
200
|
def std(self) -> float:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
"""Get the std of the count of intersection.
|
|
202
|
+
|
|
203
|
+
.. deprecated:: 0.6.0
|
|
204
|
+
Use :meth:`Shool(x).get().std()` instead.
|
|
205
|
+
"""
|
|
206
|
+
|
|
182
207
|
return self.get().std()
|
|
183
208
|
|
|
209
|
+
@deprecated("Use `Shool(x).get().sum()` instead")
|
|
184
210
|
def sum(self) -> int:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
211
|
+
"""Get the sum of the count of intersection.
|
|
212
|
+
|
|
213
|
+
.. deprecated:: 0.6.0
|
|
214
|
+
Use :meth:`Shool(x).get().sum()` instead.
|
|
215
|
+
"""
|
|
216
|
+
|
|
191
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,11 +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
|
-
from swcgeom.core.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from swcgeom.core.
|
|
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,
|
|
28
|
+
)
|
|
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,12 +1,28 @@
|
|
|
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
|
|
7
23
|
|
|
24
|
+
from swcgeom.core.compartment import Compartment, Compartments
|
|
8
25
|
from swcgeom.core.path import Path
|
|
9
|
-
from swcgeom.core.segment import Segment, Segments
|
|
10
26
|
from swcgeom.core.swc import DictSWC, SWCTypeVar
|
|
11
27
|
|
|
12
28
|
__all__ = ["Branch"]
|
|
@@ -24,9 +40,11 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
24
40
|
attach: SWCTypeVar
|
|
25
41
|
idx: npt.NDArray[np.int32]
|
|
26
42
|
|
|
27
|
-
class
|
|
43
|
+
class Compartment(Compartment["Branch"]):
|
|
28
44
|
"""Segment of branch."""
|
|
29
45
|
|
|
46
|
+
Segment = Compartment # Alias
|
|
47
|
+
|
|
30
48
|
def __repr__(self) -> str:
|
|
31
49
|
return f"Neuron branch with {len(self)} nodes."
|
|
32
50
|
|
|
@@ -36,8 +54,11 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
36
54
|
def get_ndata(self, key: str) -> npt.NDArray:
|
|
37
55
|
return self.attach.get_ndata(key)[self.idx]
|
|
38
56
|
|
|
39
|
-
def
|
|
40
|
-
return
|
|
57
|
+
def get_compartments(self) -> Compartments[Compartment]:
|
|
58
|
+
return Compartments(self.Compartment(self, n.pid, n.id) for n in self[1:])
|
|
59
|
+
|
|
60
|
+
def get_segments(self) -> Compartments[Compartment]:
|
|
61
|
+
return self.get_compartments() # Alias
|
|
41
62
|
|
|
42
63
|
def detach(self) -> "Branch[DictSWC]":
|
|
43
64
|
"""Detach from current attached object."""
|
|
@@ -87,7 +108,7 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
87
108
|
@classmethod
|
|
88
109
|
def from_xyzr_batch(
|
|
89
110
|
cls, xyzr_batch: npt.NDArray[np.float32]
|
|
90
|
-
) ->
|
|
111
|
+
) -> list["Branch[DictSWC]"]:
|
|
91
112
|
r"""Create list of branch form ~numpy.ndarray.
|
|
92
113
|
|
|
93
114
|
Parameters
|
|
@@ -107,7 +128,7 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
107
128
|
)
|
|
108
129
|
xyzr_batch = np.concatenate([xyzr_batch, ones], axis=2)
|
|
109
130
|
|
|
110
|
-
branches:
|
|
131
|
+
branches: list[Branch[DictSWC]] = []
|
|
111
132
|
for xyzr in xyzr_batch:
|
|
112
133
|
n_nodes = xyzr.shape[0]
|
|
113
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
|
|