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/analysis/sholl.py
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"""Sholl analysis."""
|
|
17
17
|
|
|
18
18
|
import warnings
|
|
19
|
-
from typing import Literal
|
|
19
|
+
from typing import Literal
|
|
20
20
|
|
|
21
21
|
import numpy as np
|
|
22
22
|
import numpy.typing as npt
|
|
@@ -39,13 +39,12 @@ YLABLE = "Count of Intersections"
|
|
|
39
39
|
class Sholl:
|
|
40
40
|
"""Sholl analysis.
|
|
41
41
|
|
|
42
|
-
Implementation of original Sholl analysis as described in [1]_. The
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
Implementation of original Sholl analysis as described in [1]_. The Sholl analysis
|
|
43
|
+
is a method to quantify the spatial distribution of neuronal processes. It is
|
|
44
|
+
based on the number of intersections of concentric circles with the neuronal
|
|
45
|
+
processes.
|
|
46
46
|
|
|
47
|
-
References
|
|
48
|
-
----------
|
|
47
|
+
References:
|
|
49
48
|
.. [1] Dendritic organization in the neurons of the visual and
|
|
50
49
|
motor cortices of the cat J. Anat., 87 (1953), pp. 387-406
|
|
51
50
|
"""
|
|
@@ -57,11 +56,7 @@ class Sholl:
|
|
|
57
56
|
# compat
|
|
58
57
|
step: float | None = None
|
|
59
58
|
|
|
60
|
-
def __init__(
|
|
61
|
-
self,
|
|
62
|
-
tree: Tree | str,
|
|
63
|
-
step: Optional[float] = None,
|
|
64
|
-
) -> None:
|
|
59
|
+
def __init__(self, tree: Tree | str, step: float | None = None) -> None:
|
|
65
60
|
tree = Tree.from_swc(tree) if isinstance(tree, str) else tree
|
|
66
61
|
try:
|
|
67
62
|
self.tree = TranslateOrigin.transform(tree) # shift
|
|
@@ -109,18 +104,14 @@ class Sholl:
|
|
|
109
104
|
) -> tuple[Figure, Axes]:
|
|
110
105
|
"""Plot Sholl analysis.
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
ax : ~matplotlib.axes.Axes
|
|
120
|
-
**kwargs :
|
|
121
|
-
Forwarding to plot method.
|
|
107
|
+
Args:
|
|
108
|
+
steps: Steps of raius of circle.
|
|
109
|
+
If steps is int, then it will be evenly divided into n radii.
|
|
110
|
+
kind: Kind of plot.
|
|
111
|
+
fig: The figure to plot on.
|
|
112
|
+
ax: The axes to plot on.
|
|
113
|
+
**kwargs: Forwarding to plot method.
|
|
122
114
|
"""
|
|
123
|
-
|
|
124
115
|
if plot_type is not None:
|
|
125
116
|
warnings.warn(
|
|
126
117
|
"`plot_type` has been renamed to `kind` since v0.5.0, "
|
|
@@ -136,9 +127,11 @@ class Sholl:
|
|
|
136
127
|
case "bar":
|
|
137
128
|
sns.barplot(x=xs, y=ys, ax=ax, **kwargs)
|
|
138
129
|
ax.set_ylabel(YLABLE)
|
|
130
|
+
|
|
139
131
|
case "linechart":
|
|
140
132
|
sns.lineplot(x=xs, y=ys, ax=ax, **kwargs)
|
|
141
133
|
ax.set_ylabel(YLABLE)
|
|
134
|
+
|
|
142
135
|
case "circles":
|
|
143
136
|
kwargs.setdefault("y_min", 0)
|
|
144
137
|
drawtree = kwargs.pop("drawtree", True)
|
|
@@ -155,6 +148,7 @@ class Sholl:
|
|
|
155
148
|
fig.colorbar(patches, ax=ax, label=YLABLE)
|
|
156
149
|
elif isinstance(colorbar, (Axes, np.ndarray, list)):
|
|
157
150
|
fig.colorbar(patches, ax=colorbar, label=YLABLE)
|
|
151
|
+
|
|
158
152
|
case _:
|
|
159
153
|
raise ValueError(f"unsupported kind: {kind}")
|
|
160
154
|
|
swcgeom/analysis/trunk.py
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
from collections.abc import Iterable
|
|
21
21
|
from itertools import chain
|
|
22
|
-
from typing import Any, Literal,
|
|
22
|
+
from typing import Any, Literal, cast
|
|
23
23
|
|
|
24
24
|
import numpy as np
|
|
25
25
|
import numpy.typing as npt
|
|
@@ -42,8 +42,8 @@ def draw_trunk(
|
|
|
42
42
|
t: Tree,
|
|
43
43
|
florets: Iterable[int | Iterable[int]],
|
|
44
44
|
*,
|
|
45
|
-
fig:
|
|
46
|
-
ax:
|
|
45
|
+
fig: Figure | None = None,
|
|
46
|
+
ax: Axes | None = None,
|
|
47
47
|
bound: Bounds | tuple[Bounds, dict[str, Any]] | None = "ellipse",
|
|
48
48
|
point: bool | dict[str, Any] = True,
|
|
49
49
|
projection: Projection = "2d",
|
|
@@ -52,29 +52,24 @@ def draw_trunk(
|
|
|
52
52
|
) -> tuple[Figure, Axes]:
|
|
53
53
|
"""Draw trunk tree.
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Colormap, any value supported by ~matplotlib.cm.Colormap. We
|
|
74
|
-
will use the ratio of the length of the subtree to the total
|
|
75
|
-
length of the tree to determine the color.
|
|
76
|
-
**kwargs : dict[str, Any]
|
|
77
|
-
Forward to ~swcgeom.analysis.draw.
|
|
55
|
+
Args:
|
|
56
|
+
t: Tree
|
|
57
|
+
florets: The florets that needs to be removed.
|
|
58
|
+
Each floret can be a subtree or multiple subtrees (e.g., dendrites are a
|
|
59
|
+
bunch of subtrees), each number is the id of a tree node.
|
|
60
|
+
fig: Figure to plot on.
|
|
61
|
+
ax: Axes to plot on.
|
|
62
|
+
bound: Kind of bound, support 'aabb', 'ellipse'.
|
|
63
|
+
If bound is None, no bound will be drawn. If bound is a tuple, the second
|
|
64
|
+
item will used as kwargs and forward to draw function.
|
|
65
|
+
point: Draw point at the start of a subtree.
|
|
66
|
+
If point is False, no point will be drawn. If point is a dict, this will
|
|
67
|
+
used a kwargs and forward to draw function.
|
|
68
|
+
cmap: Colormap.
|
|
69
|
+
Any value supported by ~matplotlib.cm.Colormap. We will use the ratio of
|
|
70
|
+
the length of the subtree to the total length of the tree to determine the
|
|
71
|
+
color.
|
|
72
|
+
**kwargs: Forward to ~swcgeom.analysis.draw.
|
|
78
73
|
"""
|
|
79
74
|
# pylint: disable=too-many-locals
|
|
80
75
|
trunk, tss = split_florets(t, florets)
|
|
@@ -138,7 +133,7 @@ def create_bound_2d(ts: Iterable[Tree], bound: Bounds, **kwargs) -> Patch:
|
|
|
138
133
|
return create_aabb_2d(xy, **kwargs)
|
|
139
134
|
if bound == "ellipse":
|
|
140
135
|
return create_ellipse_2d(xy, **kwargs)
|
|
141
|
-
raise ValueError(f"
|
|
136
|
+
raise ValueError(f"unsupported bound `{bound}` in 2d projection")
|
|
142
137
|
|
|
143
138
|
|
|
144
139
|
def create_aabb_2d(xy: npt.NDArray, fill: bool = False, **kwargs) -> Rectangle:
|
|
@@ -177,7 +172,7 @@ def draw_point(ts: Iterable[Tree], ax: Axes, projection: Projection, **kwargs) -
|
|
|
177
172
|
|
|
178
173
|
|
|
179
174
|
def create_point_2d(
|
|
180
|
-
ts: Iterable[Tree], radius:
|
|
175
|
+
ts: Iterable[Tree], radius: float | None = None, **kwargs
|
|
181
176
|
) -> Circle:
|
|
182
177
|
if radius is None:
|
|
183
178
|
xyz = np.concatenate([t.xyz() for t in ts]) # TODO: cache
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
|
|
2
3
|
#
|
|
3
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
5
|
# you may not use this file except in compliance with the License.
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
|
|
18
19
|
import os
|
|
19
20
|
import weakref
|
|
20
|
-
from typing import Any, Literal
|
|
21
|
+
from typing import Any, Literal
|
|
21
22
|
|
|
22
23
|
import numpy as np
|
|
23
24
|
from matplotlib.axes import Axes
|
|
@@ -50,52 +51,45 @@ ax_weak_memo = weakref.WeakKeyDictionary[Axes, dict[str, Any]]({})
|
|
|
50
51
|
def draw(
|
|
51
52
|
swc: SWCLike | str,
|
|
52
53
|
*,
|
|
53
|
-
fig:
|
|
54
|
-
ax:
|
|
54
|
+
fig: Figure | None = None,
|
|
55
|
+
ax: Axes | None = None,
|
|
55
56
|
show: bool | None = None,
|
|
56
57
|
camera: CameraOptions = "xy",
|
|
57
|
-
color:
|
|
58
|
+
color: dict[int, str] | str | None = None,
|
|
58
59
|
label: str | bool = True,
|
|
59
60
|
direction_indicator: Positions | Literal[False] = "rb",
|
|
60
|
-
unit:
|
|
61
|
+
unit: str | None = None,
|
|
61
62
|
**kwargs,
|
|
62
63
|
) -> tuple[Figure, Axes]:
|
|
63
64
|
r"""Draw neuron tree.
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
**kwargs : dict[str, Unknown]
|
|
93
|
-
Forwarded to `~matplotlib.collections.LineCollection`.
|
|
94
|
-
|
|
95
|
-
Returns
|
|
96
|
-
-------
|
|
97
|
-
fig : ~matplotlib.axes.Figure
|
|
98
|
-
ax : ~matplotlib.axes.Axes
|
|
66
|
+
Args:
|
|
67
|
+
swc: The swc tree to draw.
|
|
68
|
+
If it is str, then it is treated as the path of swc file.
|
|
69
|
+
fig: The figure to plot on.
|
|
70
|
+
ax: The axes to plot on.
|
|
71
|
+
show: Weather to call `plt.show()`.
|
|
72
|
+
If not specified, it will depend on if ax is passed in, it will not be
|
|
73
|
+
called, otherwise it will be called by default.
|
|
74
|
+
camera: Camera options (position, look-at, up).
|
|
75
|
+
One, two, or three vectors are supported, if only one vector, then threat
|
|
76
|
+
it as look-at, so camera is ((0, 0, 0), look-at, (0, 1, 0)); if two vector,
|
|
77
|
+
then then threat it as (look-at, up), so camera is ((0, 0, 0), look-at, up).
|
|
78
|
+
An easy way is to use the presets "xy", "yz" and "zx".
|
|
79
|
+
color: Color map.
|
|
80
|
+
If is dict, segments will be colored by the type of parent node.If is
|
|
81
|
+
string, the value will be use for any type.
|
|
82
|
+
label: Label of legend, disable if False.
|
|
83
|
+
direction_indicator: Draw a xyz direction indicator.
|
|
84
|
+
Can be place on 'lt', 'lb', 'rt', 'rb', or custom position.
|
|
85
|
+
unit: str, optional
|
|
86
|
+
Add unit text, e.g.: r"$\mu m$".
|
|
87
|
+
**kwargs: dict[str, Unknown]
|
|
88
|
+
Forwarded to `~matplotlib.collections.LineCollection`.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
fig: The figure to plot on.
|
|
92
|
+
ax: The axes to plot on.
|
|
99
93
|
"""
|
|
100
94
|
# pylint: disable=too-many-locals
|
|
101
95
|
swc = Tree.from_swc(swc) if isinstance(swc, str) else swc
|
|
@@ -141,7 +135,9 @@ def get_ax_swc(ax: Axes) -> list[SWCLike]:
|
|
|
141
135
|
|
|
142
136
|
|
|
143
137
|
def get_ax_color(
|
|
144
|
-
ax: Axes,
|
|
138
|
+
ax: Axes,
|
|
139
|
+
swc: SWCLike,
|
|
140
|
+
color: dict[int, str] | str | None = None, # TODO: improve typing
|
|
145
141
|
) -> str | list[str]:
|
|
146
142
|
if color == "vaa3d":
|
|
147
143
|
color = palette.vaa3d
|
|
@@ -176,10 +172,7 @@ def set_ax_legend(ax: Axes, *args, **kwargs) -> Legend | None:
|
|
|
176
172
|
|
|
177
173
|
|
|
178
174
|
def _set_ax_memo(
|
|
179
|
-
ax: Axes,
|
|
180
|
-
swc: SWCLike,
|
|
181
|
-
label: Optional[str | bool] = None,
|
|
182
|
-
handle: Optional[Any] = None,
|
|
175
|
+
ax: Axes, swc: SWCLike, label: str | bool | None = None, handle: Any | None = None
|
|
183
176
|
):
|
|
184
177
|
ax_weak_memo.setdefault(ax, {})
|
|
185
178
|
ax_weak_memo[ax].setdefault("swc", [])
|
|
@@ -15,13 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
"""Painter utils.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
-----
|
|
20
|
-
This is a experimental function, it may be changed in the future.
|
|
18
|
+
NOTE: This is a experimental function, it may be changed in the future.
|
|
21
19
|
"""
|
|
22
20
|
|
|
23
|
-
from typing import Optional
|
|
24
|
-
|
|
25
21
|
import numpy as np
|
|
26
22
|
from matplotlib.axes import Axes
|
|
27
23
|
from matplotlib.figure import Figure
|
|
@@ -45,37 +41,32 @@ def draw3d(
|
|
|
45
41
|
*,
|
|
46
42
|
ax: Axes,
|
|
47
43
|
show: bool | None = None,
|
|
48
|
-
color:
|
|
44
|
+
color: dict[int, str] | str | None = None, # TODO: improve typing
|
|
49
45
|
label: str | bool = True,
|
|
50
46
|
**kwargs,
|
|
51
47
|
) -> tuple[Figure, Axes]:
|
|
52
48
|
r"""Draw neuron tree.
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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`.
|
|
50
|
+
Args:
|
|
51
|
+
swc: The swc tree to draw.
|
|
52
|
+
If it is str, then it is treated as the path of swc file.
|
|
53
|
+
fig: The figure to plot on.
|
|
54
|
+
ax: The axes to plot on.
|
|
55
|
+
show: Weather to call `plt.show()`.
|
|
56
|
+
If not specified, it will depend on if ax is passed in, it will not be
|
|
57
|
+
called, otherwise it will be called by default.
|
|
58
|
+
color: Color map.
|
|
59
|
+
If is dict, segments will be colored by the type of parent node.If is
|
|
60
|
+
string, the value will be use for any type.
|
|
61
|
+
label: Label of legend, disable if False.
|
|
62
|
+
**kwargs: Forwarded to `~mpl_toolkits.mplot3d.art3d.Line3DCollection`.
|
|
71
63
|
"""
|
|
72
|
-
|
|
73
64
|
assert isinstance(ax, Axes3D), "only support 3D axes."
|
|
74
65
|
|
|
75
66
|
swc = Tree.from_swc(swc) if isinstance(swc, str) else swc
|
|
76
67
|
|
|
77
68
|
show = (show is True) or (show is None and ax is None)
|
|
78
|
-
my_color = get_ax_color(ax, swc, color)
|
|
69
|
+
my_color = get_ax_color(ax, swc, color)
|
|
79
70
|
|
|
80
71
|
xyz = swc.xyz()
|
|
81
72
|
starts, ends = swc.id()[1:], swc.pid()[1:]
|
swcgeom/analysis/volume.py
CHANGED
|
@@ -37,45 +37,34 @@ def get_volume(
|
|
|
37
37
|
) -> float:
|
|
38
38
|
"""Get the volume of the tree.
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
The SWC format is a method for representing neurons, which includes
|
|
66
|
-
both the radius of individual points and their interconnectivity.
|
|
67
|
-
Consequently, there are multiple distinct approaches to
|
|
68
|
-
representation within this framework.
|
|
69
|
-
|
|
70
|
-
Currently, we support a standard approach to volume calculation.
|
|
71
|
-
This method involves treating each node as a sphere and
|
|
72
|
-
representing the connections between them as truncated cone-like
|
|
73
|
-
structures, or frustums, with varying radii at their top and bottom
|
|
74
|
-
surfaces.
|
|
40
|
+
Args:
|
|
41
|
+
tree: Neuronal tree.
|
|
42
|
+
method: Method for volume calculation.
|
|
43
|
+
accuracy: Accuracy level for volume calculation. The higher the accuracy,
|
|
44
|
+
the more accurate the volume calculation, but the slower the
|
|
45
|
+
calculation. The accuracy level can be specified either as an
|
|
46
|
+
integer or as a string.
|
|
47
|
+
|
|
48
|
+
The string values correspond to the following accuracy levels:
|
|
49
|
+
|
|
50
|
+
- "low": 3
|
|
51
|
+
- "middle": 5
|
|
52
|
+
- "high": 8
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
volume: Volume of the tree.
|
|
56
|
+
|
|
57
|
+
NOTE: The SWC format is a method for representing neurons, which includes both the
|
|
58
|
+
radius of individual points and their interconnectivity. Consequently, there are
|
|
59
|
+
multiple distinct approaches to representation within this framework.
|
|
60
|
+
|
|
61
|
+
Currently, we support a standard approach to volume calculation. This method
|
|
62
|
+
involves treating each node as a sphere and representing the connections between
|
|
63
|
+
them as truncated cone-like structures, or frustums, with varying radii at their
|
|
64
|
+
top and bottom surfaces.
|
|
75
65
|
|
|
76
66
|
We welcome additional representation methods through pull requests.
|
|
77
67
|
"""
|
|
78
|
-
|
|
79
68
|
if isinstance(accuracy, str):
|
|
80
69
|
accuracy = ACCURACY_LEVELS[accuracy]
|
|
81
70
|
|
|
@@ -91,18 +80,15 @@ def get_volume(
|
|
|
91
80
|
def _get_volume_frustum_cone(tree: Tree, *, accuracy: int) -> float:
|
|
92
81
|
"""Get the volume of the tree using the frustum cone method.
|
|
93
82
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
5 : Above and Sphere-Frustum Cone intersection in multi-branch
|
|
103
|
-
10 : Fully calculated by Monte Carlo method
|
|
83
|
+
Args:
|
|
84
|
+
tree: Neuronal tree.
|
|
85
|
+
accuracy: Accuracy level.
|
|
86
|
+
1 : Sphere only
|
|
87
|
+
2 : Sphere and Frustum Cone
|
|
88
|
+
3 : Sphere, Frustum Cone, and intersection in single-branch
|
|
89
|
+
5 : Above and Sphere-Frustum Cone intersection in multi-branch
|
|
90
|
+
10 : Fully calculated by Monte Carlo method
|
|
104
91
|
"""
|
|
105
|
-
|
|
106
92
|
if accuracy == 10:
|
|
107
93
|
return _get_volume_frustum_cone_mc_only(tree)
|
|
108
94
|
|
swcgeom/core/__init__.py
CHANGED
|
@@ -20,12 +20,7 @@ from swcgeom.core.branch import * # noqa: F403
|
|
|
20
20
|
from swcgeom.core.branch_tree import * # noqa: F403
|
|
21
21
|
|
|
22
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
|
-
)
|
|
23
|
+
from swcgeom.core.compartment import Compartment, Compartments # noqa: F401
|
|
29
24
|
from swcgeom.core.node import * # noqa: F403
|
|
30
25
|
from swcgeom.core.path import * # noqa: F403
|
|
31
26
|
from swcgeom.core.population import * # noqa: F403
|
swcgeom/core/branch.py
CHANGED
|
@@ -31,10 +31,8 @@ __all__ = ["Branch"]
|
|
|
31
31
|
class Branch(Path, Generic[SWCTypeVar]):
|
|
32
32
|
r"""Neural branch.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Only a part of data of branch nodes is valid, such as `x`, `y`, `z` and
|
|
37
|
-
`r`, but the `id` and `pid` is usually invalid.
|
|
34
|
+
NOTE: Only a part of data of branch nodes is valid, such as `x`, `y`, `z` and `r`,
|
|
35
|
+
but the `id` and `pid` is usually invalid.
|
|
38
36
|
"""
|
|
39
37
|
|
|
40
38
|
attach: SWCTypeVar
|
|
@@ -76,12 +74,10 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
76
74
|
def from_xyzr(cls, xyzr: npt.NDArray[np.float32]) -> "Branch[DictSWC]":
|
|
77
75
|
r"""Create a branch from ~numpy.ndarray.
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
nodes is enabled. If shape (n, 3), only `x`, `y`, `z` is enabled
|
|
84
|
-
and `r` will fill by 1.
|
|
77
|
+
Args:
|
|
78
|
+
xyzr: Collection of nodes.
|
|
79
|
+
If shape (n, 4), both `x`, `y`, `z`, `r` of nodes is enabled. If shape
|
|
80
|
+
(n, 3), only `x`, `y`, `z` is enabled and `r` will fill by 1.
|
|
85
81
|
"""
|
|
86
82
|
assert xyzr.ndim == 2 and xyzr.shape[1] in (
|
|
87
83
|
3,
|
|
@@ -111,14 +107,11 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
111
107
|
) -> list["Branch[DictSWC]"]:
|
|
112
108
|
r"""Create list of branch form ~numpy.ndarray.
|
|
113
109
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`z`, `r` of nodes is enabled. If shape (bs, n, 3), only `x`, `y`,
|
|
119
|
-
`z` is enabled and `r` will fill by 1.
|
|
110
|
+
Args:
|
|
111
|
+
xyzr: Batch of collection of nodes.
|
|
112
|
+
If shape (bs, n, 4), both `x`, `y`, `z`, `r` of nodes is enabled. If
|
|
113
|
+
shape (bs, n, 3), only `x`, `y`, `z` is enabled and `r` will fill by 1.
|
|
120
114
|
"""
|
|
121
|
-
|
|
122
115
|
assert xyzr_batch.ndim == 3
|
|
123
116
|
assert xyzr_batch.shape[1] >= 3
|
|
124
117
|
|
swcgeom/core/branch_tree.py
CHANGED
|
@@ -19,6 +19,7 @@ import itertools
|
|
|
19
19
|
|
|
20
20
|
import numpy as np
|
|
21
21
|
import pandas as pd
|
|
22
|
+
from typing_extensions import Self
|
|
22
23
|
|
|
23
24
|
from swcgeom.core.branch import Branch
|
|
24
25
|
from swcgeom.core.swc_utils import to_sub_topology
|
|
@@ -44,7 +45,7 @@ class BranchTree(Tree):
|
|
|
44
45
|
return self.branches[idx]
|
|
45
46
|
|
|
46
47
|
@classmethod
|
|
47
|
-
def from_tree(cls, tree: Tree) ->
|
|
48
|
+
def from_tree(cls, tree: Tree) -> Self:
|
|
48
49
|
"""Generating a branch tree from tree."""
|
|
49
50
|
|
|
50
51
|
branches = tree.get_branches()
|
|
@@ -69,6 +70,6 @@ class BranchTree(Tree):
|
|
|
69
70
|
return branch_tree
|
|
70
71
|
|
|
71
72
|
@classmethod
|
|
72
|
-
def from_data_frame(cls, df: pd.DataFrame, *args, **kwargs) ->
|
|
73
|
+
def from_data_frame(cls, df: pd.DataFrame, *args, **kwargs) -> Self:
|
|
73
74
|
tree = super().from_data_frame(df, *args, **kwargs)
|
|
74
75
|
return cls.from_tree(tree)
|
swcgeom/core/compartment.py
CHANGED
swcgeom/core/node.py
CHANGED
|
@@ -119,11 +119,9 @@ class Node(Generic[SWCTypeVar]):
|
|
|
119
119
|
def is_bifurcation(self) -> bool:
|
|
120
120
|
"""Is furcation node.
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
is just an alias of `is_furcation` and raise a warning. It will
|
|
126
|
-
be change to raise an error in the future.
|
|
122
|
+
NOTE: Deprecated due to the wrong spelling of furcation. For now, it is just an
|
|
123
|
+
alias of `is_furcation` and raise a warning. It will be change to raise an
|
|
124
|
+
error in the future.
|
|
127
125
|
"""
|
|
128
126
|
return self.is_furcation()
|
|
129
127
|
|
|
@@ -132,7 +130,6 @@ class Node(Generic[SWCTypeVar]):
|
|
|
132
130
|
|
|
133
131
|
def detach(self) -> "Node[DictSWC]":
|
|
134
132
|
"""Detach from current attached object."""
|
|
135
|
-
# pylint: disable=consider-using-dict-items
|
|
136
133
|
attact = DictSWC(
|
|
137
134
|
**{k: np.array([self[k]]) for k in self.keys()},
|
|
138
135
|
source=self.attach.source,
|
swcgeom/core/path.py
CHANGED
|
@@ -56,15 +56,12 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
56
56
|
def __repr__(self) -> str:
|
|
57
57
|
return f"Neuron path with {len(self)} nodes."
|
|
58
58
|
|
|
59
|
-
# fmt:off
|
|
60
59
|
@overload
|
|
61
60
|
def __getitem__(self, key: int) -> Node: ...
|
|
62
61
|
@overload
|
|
63
62
|
def __getitem__(self, key: slice) -> list[Node]: ...
|
|
64
63
|
@overload
|
|
65
64
|
def __getitem__(self, key: str) -> npt.NDArray: ...
|
|
66
|
-
# fmt:on
|
|
67
|
-
|
|
68
65
|
def __getitem__(self, key):
|
|
69
66
|
if isinstance(key, slice):
|
|
70
67
|
return [self.node(i) for i in range(*key.indices(len(self)))]
|
|
@@ -118,22 +115,22 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
118
115
|
def id(self) -> npt.NDArray[np.int32]: # pylint: disable=invalid-name
|
|
119
116
|
"""Get the ids of shape (n_sample,).
|
|
120
117
|
|
|
121
|
-
Returns
|
|
118
|
+
Returns:
|
|
119
|
+
a consecutively incremented id.
|
|
122
120
|
|
|
123
|
-
See Also
|
|
124
|
-
|
|
125
|
-
self.origin_id
|
|
121
|
+
See Also:
|
|
122
|
+
self.origin_id
|
|
126
123
|
"""
|
|
127
124
|
return np.arange(len(self.origin_id()), dtype=np.int32)
|
|
128
125
|
|
|
129
126
|
def pid(self) -> npt.NDArray[np.int32]:
|
|
130
127
|
"""Get the ids of shape (n_sample,).
|
|
131
128
|
|
|
132
|
-
Returns
|
|
129
|
+
Returns:
|
|
130
|
+
a consecutively incremented pid.
|
|
133
131
|
|
|
134
|
-
See Also
|
|
135
|
-
|
|
136
|
-
self.origin_pid
|
|
132
|
+
See Also:
|
|
133
|
+
self.origin_pid
|
|
137
134
|
"""
|
|
138
135
|
return np.arange(-1, len(self.origin_id()) - 1, dtype=np.int32)
|
|
139
136
|
|
|
@@ -153,17 +150,15 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
153
150
|
def straight_line_distance(self) -> float:
|
|
154
151
|
"""Straight-line distance of path.
|
|
155
152
|
|
|
156
|
-
The end-to-end straight-line distance between start point and
|
|
157
|
-
end point.
|
|
153
|
+
The end-to-end straight-line distance between start point and end point.
|
|
158
154
|
"""
|
|
159
155
|
return np.linalg.norm(self.node(-1).xyz() - self.node(0).xyz()).item()
|
|
160
156
|
|
|
161
157
|
def tortuosity(self) -> float:
|
|
162
158
|
"""Tortuosity of path.
|
|
163
159
|
|
|
164
|
-
The straight-line distance between two consecutive branch
|
|
165
|
-
|
|
166
|
-
those points.
|
|
160
|
+
The straight-line distance between two consecutive branch points divided by the
|
|
161
|
+
length of the neuronal path between those points.
|
|
167
162
|
"""
|
|
168
163
|
if (length := self.length()) == 0:
|
|
169
164
|
return 1
|