swcgeom 0.15.0__py3-none-any.whl → 0.17.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/_version.py +2 -2
- swcgeom/analysis/__init__.py +1 -3
- swcgeom/analysis/feature_extractor.py +3 -3
- swcgeom/analysis/{node_features.py → features.py} +105 -3
- swcgeom/analysis/lmeasure.py +821 -0
- swcgeom/analysis/sholl.py +31 -2
- swcgeom/core/__init__.py +4 -0
- swcgeom/core/branch.py +9 -4
- swcgeom/core/{segment.py → compartment.py} +14 -9
- swcgeom/core/node.py +0 -8
- swcgeom/core/path.py +21 -6
- swcgeom/core/population.py +47 -7
- swcgeom/core/swc_utils/assembler.py +12 -1
- swcgeom/core/swc_utils/base.py +12 -5
- swcgeom/core/swc_utils/checker.py +12 -2
- swcgeom/core/tree.py +34 -37
- swcgeom/core/tree_utils.py +4 -0
- swcgeom/images/augmentation.py +6 -1
- swcgeom/images/contrast.py +107 -0
- swcgeom/images/folder.py +71 -14
- swcgeom/images/io.py +74 -88
- swcgeom/transforms/__init__.py +2 -0
- swcgeom/transforms/image_preprocess.py +100 -0
- swcgeom/transforms/image_stack.py +1 -4
- swcgeom/transforms/images.py +176 -5
- swcgeom/transforms/mst.py +5 -5
- swcgeom/transforms/neurolucida_asc.py +495 -0
- swcgeom/transforms/tree.py +5 -1
- swcgeom/utils/__init__.py +1 -0
- swcgeom/utils/neuromorpho.py +425 -300
- swcgeom/utils/numpy_helper.py +14 -4
- swcgeom/utils/plotter_2d.py +130 -0
- swcgeom/utils/renderer.py +28 -139
- swcgeom/utils/sdf.py +5 -1
- {swcgeom-0.15.0.dist-info → swcgeom-0.17.0.dist-info}/METADATA +3 -3
- swcgeom-0.17.0.dist-info/RECORD +65 -0
- {swcgeom-0.15.0.dist-info → swcgeom-0.17.0.dist-info}/WHEEL +1 -1
- swcgeom/analysis/branch_features.py +0 -67
- swcgeom/analysis/path_features.py +0 -37
- swcgeom-0.15.0.dist-info/RECORD +0 -62
- {swcgeom-0.15.0.dist-info → swcgeom-0.17.0.dist-info}/LICENSE +0 -0
- {swcgeom-0.15.0.dist-info → swcgeom-0.17.0.dist-info}/top_level.txt +0 -0
swcgeom/analysis/sholl.py
CHANGED
|
@@ -23,10 +23,15 @@ YLABLE = "Count of Intersections"
|
|
|
23
23
|
class Sholl:
|
|
24
24
|
"""Sholl analysis.
|
|
25
25
|
|
|
26
|
+
Implementation of original Sholl analysis as described in [1]_. The
|
|
27
|
+
Sholl analysis is a method to quantify the spatial distribution of
|
|
28
|
+
neuronal processes. It is based on the number of intersections of
|
|
29
|
+
concentric circles with the neuronal processes.
|
|
30
|
+
|
|
26
31
|
References
|
|
27
32
|
----------
|
|
28
|
-
[1] Dendritic organization in the neurons of the visual and
|
|
29
|
-
|
|
33
|
+
.. [1] Dendritic organization in the neurons of the visual and
|
|
34
|
+
motor cortices of the cat J. Anat., 87 (1953), pp. 387-406
|
|
30
35
|
"""
|
|
31
36
|
|
|
32
37
|
tree: Tree
|
|
@@ -156,6 +161,12 @@ class Sholl:
|
|
|
156
161
|
return self.get_rs(self.rmax, steps)
|
|
157
162
|
|
|
158
163
|
def get_count(self) -> npt.NDArray[np.int32]:
|
|
164
|
+
"""Get the count of intersection.
|
|
165
|
+
|
|
166
|
+
.. deprecated:: 0.5.0
|
|
167
|
+
Use :meth:`Sholl.get` instead.
|
|
168
|
+
"""
|
|
169
|
+
|
|
159
170
|
warnings.warn(
|
|
160
171
|
"`Sholl.get_count` has been renamed to `get` since v0.5.0, "
|
|
161
172
|
"and will be removed in next version",
|
|
@@ -164,6 +175,12 @@ class Sholl:
|
|
|
164
175
|
return self.get().astype(np.int32)
|
|
165
176
|
|
|
166
177
|
def avg(self) -> float:
|
|
178
|
+
"""Get the average of the count of intersection.
|
|
179
|
+
|
|
180
|
+
.. deprecated:: 0.6.0
|
|
181
|
+
Use :meth:`Shool(x).get().mean()` instead.
|
|
182
|
+
"""
|
|
183
|
+
|
|
167
184
|
warnings.warn(
|
|
168
185
|
"`Sholl.avg` has been deprecated since v0.6.0 and will be "
|
|
169
186
|
"removed in next version, use `Shool(x).get().mean()` "
|
|
@@ -173,6 +190,12 @@ class Sholl:
|
|
|
173
190
|
return self.get().mean()
|
|
174
191
|
|
|
175
192
|
def std(self) -> float:
|
|
193
|
+
"""Get the std of the count of intersection.
|
|
194
|
+
|
|
195
|
+
.. deprecated:: 0.6.0
|
|
196
|
+
Use :meth:`Shool(x).get().std()` instead.
|
|
197
|
+
"""
|
|
198
|
+
|
|
176
199
|
warnings.warn(
|
|
177
200
|
"`Sholl.std` has been deprecate since v0.6.0 and will be "
|
|
178
201
|
"removed in next version, use `Shool(x).get().std()` "
|
|
@@ -182,6 +205,12 @@ class Sholl:
|
|
|
182
205
|
return self.get().std()
|
|
183
206
|
|
|
184
207
|
def sum(self) -> int:
|
|
208
|
+
"""Get the sum of the count of intersection.
|
|
209
|
+
|
|
210
|
+
.. deprecated:: 0.6.0
|
|
211
|
+
Use :meth:`Shool(x).get().sum()` instead.
|
|
212
|
+
"""
|
|
213
|
+
|
|
185
214
|
warnings.warn(
|
|
186
215
|
"`Sholl.sum` has been deprecate since v0.6.0 and will be "
|
|
187
216
|
"removed in next version, use `Shool(x).get().sum()` "
|
swcgeom/core/__init__.py
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
from swcgeom.core import swc_utils
|
|
4
4
|
from swcgeom.core.branch import *
|
|
5
5
|
from swcgeom.core.branch_tree import *
|
|
6
|
+
from swcgeom.core.compartment import ( # Segment and Segments don't expose
|
|
7
|
+
Compartment,
|
|
8
|
+
Compartments,
|
|
9
|
+
)
|
|
6
10
|
from swcgeom.core.node import *
|
|
7
11
|
from swcgeom.core.path import *
|
|
8
12
|
from swcgeom.core.population import *
|
swcgeom/core/branch.py
CHANGED
|
@@ -5,8 +5,8 @@ from typing import Generic, Iterable, List
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import numpy.typing as npt
|
|
7
7
|
|
|
8
|
+
from swcgeom.core.compartment import Compartment, Compartments
|
|
8
9
|
from swcgeom.core.path import Path
|
|
9
|
-
from swcgeom.core.segment import Segment, Segments
|
|
10
10
|
from swcgeom.core.swc import DictSWC, SWCTypeVar
|
|
11
11
|
|
|
12
12
|
__all__ = ["Branch"]
|
|
@@ -24,9 +24,11 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
24
24
|
attach: SWCTypeVar
|
|
25
25
|
idx: npt.NDArray[np.int32]
|
|
26
26
|
|
|
27
|
-
class
|
|
27
|
+
class Compartment(Compartment["Branch"]):
|
|
28
28
|
"""Segment of branch."""
|
|
29
29
|
|
|
30
|
+
Segment = Compartment # Alias
|
|
31
|
+
|
|
30
32
|
def __repr__(self) -> str:
|
|
31
33
|
return f"Neuron branch with {len(self)} nodes."
|
|
32
34
|
|
|
@@ -36,8 +38,11 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
36
38
|
def get_ndata(self, key: str) -> npt.NDArray:
|
|
37
39
|
return self.attach.get_ndata(key)[self.idx]
|
|
38
40
|
|
|
39
|
-
def
|
|
40
|
-
return
|
|
41
|
+
def get_compartments(self) -> Compartments[Compartment]:
|
|
42
|
+
return Compartments(self.Compartment(self, n.pid, n.id) for n in self[1:])
|
|
43
|
+
|
|
44
|
+
def get_segments(self) -> Compartments[Compartment]:
|
|
45
|
+
return self.get_compartments() # Alias
|
|
41
46
|
|
|
42
47
|
def detach(self) -> "Branch[DictSWC]":
|
|
43
48
|
"""Detach from current attached object."""
|
|
@@ -9,11 +9,11 @@ from swcgeom.core.path import Path
|
|
|
9
9
|
from swcgeom.core.swc import DictSWC, SWCTypeVar
|
|
10
10
|
from swcgeom.core.swc_utils import SWCNames, get_names
|
|
11
11
|
|
|
12
|
-
__all__ = ["Segment", "Segments"]
|
|
12
|
+
__all__ = ["Compartment", "Compartments", "Segment", "Segments"]
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class
|
|
16
|
-
r"""
|
|
15
|
+
class Compartment(Path, Generic[SWCTypeVar]):
|
|
16
|
+
r"""Compartment attached to external object."""
|
|
17
17
|
|
|
18
18
|
attach: SWCTypeVar
|
|
19
19
|
idx: npt.NDArray[np.int32]
|
|
@@ -27,7 +27,7 @@ class Segment(Path, Generic[SWCTypeVar]):
|
|
|
27
27
|
def get_ndata(self, key: str) -> npt.NDArray:
|
|
28
28
|
return self.attach.get_ndata(key)[self.idx]
|
|
29
29
|
|
|
30
|
-
def detach(self) -> "
|
|
30
|
+
def detach(self) -> "Compartment[DictSWC]":
|
|
31
31
|
"""Detach from current attached object."""
|
|
32
32
|
# pylint: disable=consider-using-dict-items
|
|
33
33
|
attact = DictSWC(
|
|
@@ -37,18 +37,18 @@ class Segment(Path, Generic[SWCTypeVar]):
|
|
|
37
37
|
)
|
|
38
38
|
attact.ndata[self.names.id] = self.id()
|
|
39
39
|
attact.ndata[self.names.pid] = self.pid()
|
|
40
|
-
return
|
|
40
|
+
return Compartment(attact, 0, 1)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
T = TypeVar("T", bound=Compartment)
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
class
|
|
47
|
-
r"""
|
|
46
|
+
class Compartments(List[T]):
|
|
47
|
+
r"""Comparments contains a set of comparment."""
|
|
48
48
|
|
|
49
49
|
names: SWCNames
|
|
50
50
|
|
|
51
|
-
def __init__(self, segments: Iterable[
|
|
51
|
+
def __init__(self, segments: Iterable[T]) -> None:
|
|
52
52
|
super().__init__(segments)
|
|
53
53
|
self.names = self[0].names if len(self) > 0 else get_names()
|
|
54
54
|
|
|
@@ -94,3 +94,8 @@ class Segments(List[SegmentT]):
|
|
|
94
94
|
The order of axis 1 is (parent, current node).
|
|
95
95
|
"""
|
|
96
96
|
return np.array([s.get_ndata(key) for s in self])
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Aliases
|
|
100
|
+
Segment = Compartment
|
|
101
|
+
Segments = Compartments
|
swcgeom/core/node.py
CHANGED
|
@@ -95,14 +95,6 @@ class Node(Generic[SWCTypeVar]):
|
|
|
95
95
|
items = [self.id, self.type, x, y, z, r, self.pid]
|
|
96
96
|
return " ".join(map(str, items))
|
|
97
97
|
|
|
98
|
-
def child_ids(self) -> npt.NDArray[np.int32]:
|
|
99
|
-
warnings.warn(
|
|
100
|
-
"`Node.child_ids` has been deprecated since v0.3.1 and "
|
|
101
|
-
"will be removed in next version",
|
|
102
|
-
DeprecationWarning,
|
|
103
|
-
)
|
|
104
|
-
return self.attach.id()[self.attach.pid() == self.id]
|
|
105
|
-
|
|
106
98
|
def is_bifurcation(self) -> bool:
|
|
107
99
|
return np.count_nonzero(self.attach.pid() == self.id) > 1
|
|
108
100
|
|
swcgeom/core/path.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
"""Nueron
|
|
1
|
+
"""Nueron path."""
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from typing import Generic, Iterable, Iterator, List, overload
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
@@ -12,7 +13,7 @@ __all__ = ["Path"]
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class Path(SWCLike, Generic[SWCTypeVar]):
|
|
15
|
-
"""
|
|
16
|
+
"""Neuron path.
|
|
16
17
|
|
|
17
18
|
A path is a linear set of points without bifurcations.
|
|
18
19
|
"""
|
|
@@ -31,7 +32,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
31
32
|
self.source = self.attach.source
|
|
32
33
|
|
|
33
34
|
def __iter__(self) -> Iterator[Node]:
|
|
34
|
-
return (self.
|
|
35
|
+
return (self.node(i) for i in range(len(self)))
|
|
35
36
|
|
|
36
37
|
def __len__(self) -> int:
|
|
37
38
|
return self.id().shape[0]
|
|
@@ -50,7 +51,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
50
51
|
|
|
51
52
|
def __getitem__(self, key):
|
|
52
53
|
if isinstance(key, slice):
|
|
53
|
-
return [self.
|
|
54
|
+
return [self.node(i) for i in range(*key.indices(len(self)))]
|
|
54
55
|
|
|
55
56
|
if isinstance(key, (int, np.integer)):
|
|
56
57
|
length = len(self)
|
|
@@ -60,7 +61,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
60
61
|
if key < 0: # Handle negative indices
|
|
61
62
|
key += length
|
|
62
63
|
|
|
63
|
-
return self.
|
|
64
|
+
return self.node(key)
|
|
64
65
|
|
|
65
66
|
if isinstance(key, str):
|
|
66
67
|
return self.get_ndata(key)
|
|
@@ -74,6 +75,20 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
74
75
|
return self.attach.get_ndata(key)[self.idx]
|
|
75
76
|
|
|
76
77
|
def get_node(self, idx: int | np.integer) -> Node:
|
|
78
|
+
"""Get the count of intersection.
|
|
79
|
+
|
|
80
|
+
.. deprecated:: 0.16.0
|
|
81
|
+
Use :meth:`path.node` instead.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
warnings.warn(
|
|
85
|
+
"`Path.get_node` has been deprecated since v0.16.0 and "
|
|
86
|
+
"will be removed in future version",
|
|
87
|
+
DeprecationWarning,
|
|
88
|
+
)
|
|
89
|
+
return self.node(idx)
|
|
90
|
+
|
|
91
|
+
def node(self, idx: int | np.integer) -> Node:
|
|
77
92
|
return self.Node(self, idx)
|
|
78
93
|
|
|
79
94
|
def detach(self) -> "Path[DictSWC]":
|
|
@@ -129,7 +144,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
129
144
|
The end-to-end straight-line distance between start point and
|
|
130
145
|
end point.
|
|
131
146
|
"""
|
|
132
|
-
return np.linalg.norm(self.
|
|
147
|
+
return np.linalg.norm(self.node(-1).xyz() - self.node(0).xyz()).item()
|
|
133
148
|
|
|
134
149
|
def tortuosity(self) -> float:
|
|
135
150
|
"""Tortuosity of path.
|
swcgeom/core/population.py
CHANGED
|
@@ -2,21 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import warnings
|
|
5
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
5
6
|
from functools import reduce
|
|
6
7
|
from typing import (
|
|
7
8
|
Any,
|
|
9
|
+
Callable,
|
|
8
10
|
Dict,
|
|
9
11
|
Iterable,
|
|
10
12
|
Iterator,
|
|
11
13
|
List,
|
|
12
14
|
Optional,
|
|
13
15
|
Protocol,
|
|
16
|
+
TypeVar,
|
|
14
17
|
cast,
|
|
15
18
|
overload,
|
|
16
19
|
)
|
|
17
20
|
|
|
18
21
|
import numpy as np
|
|
19
22
|
import numpy.typing as npt
|
|
23
|
+
from tqdm.contrib.concurrent import process_map
|
|
24
|
+
from typing_extensions import Self
|
|
20
25
|
|
|
21
26
|
from swcgeom.core.swc import eswc_cols
|
|
22
27
|
from swcgeom.core.tree import Tree
|
|
@@ -24,6 +29,9 @@ from swcgeom.core.tree import Tree
|
|
|
24
29
|
__all__ = ["LazyLoadingTrees", "ChainTrees", "Population", "Populations"]
|
|
25
30
|
|
|
26
31
|
|
|
32
|
+
T = TypeVar("T")
|
|
33
|
+
|
|
34
|
+
|
|
27
35
|
class Trees(Protocol):
|
|
28
36
|
"""Trees protocol support index and len."""
|
|
29
37
|
|
|
@@ -48,6 +56,7 @@ class LazyLoadingTrees:
|
|
|
48
56
|
kwargs : Dict[str, Any]
|
|
49
57
|
Forwarding to `Tree.from_swc`
|
|
50
58
|
"""
|
|
59
|
+
|
|
51
60
|
super().__init__()
|
|
52
61
|
self.swcs = list(swcs)
|
|
53
62
|
self.trees = [None for _ in swcs]
|
|
@@ -61,6 +70,9 @@ class LazyLoadingTrees:
|
|
|
61
70
|
def __len__(self) -> int:
|
|
62
71
|
return len(self.swcs)
|
|
63
72
|
|
|
73
|
+
def __iter__(self) -> Iterator[Tree]:
|
|
74
|
+
return (self[i] for i in range(self.__len__()))
|
|
75
|
+
|
|
64
76
|
def load(self, key: int) -> None:
|
|
65
77
|
if self.trees[key] is None:
|
|
66
78
|
self.trees[key] = Tree.from_swc(self.swcs[key], **self.kwargs)
|
|
@@ -92,6 +104,9 @@ class ChainTrees:
|
|
|
92
104
|
def __len__(self) -> int:
|
|
93
105
|
return self.cumsum[-1].item()
|
|
94
106
|
|
|
107
|
+
def __iter__(self) -> Iterator[Tree]:
|
|
108
|
+
return (self[i] for i in range(self.__len__()))
|
|
109
|
+
|
|
95
110
|
|
|
96
111
|
class Population:
|
|
97
112
|
"""Neuron population."""
|
|
@@ -153,15 +168,40 @@ class Population:
|
|
|
153
168
|
def __repr__(self) -> str:
|
|
154
169
|
return f"Neuron population in '{self.root}'"
|
|
155
170
|
|
|
171
|
+
def map(
|
|
172
|
+
self,
|
|
173
|
+
fn: Callable[[Tree], T],
|
|
174
|
+
*,
|
|
175
|
+
max_worker: Optional[int] = None,
|
|
176
|
+
verbose: bool = False,
|
|
177
|
+
) -> Iterator[T]:
|
|
178
|
+
"""Map a function to all trees in the population.
|
|
179
|
+
|
|
180
|
+
This is a straightforward interface for parallelizing
|
|
181
|
+
computations. The parameters are intentionally kept simple and
|
|
182
|
+
user-friendly. For more advanced control, consider using
|
|
183
|
+
`concurrent.futures` directly.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
trees = (t for t in self.trees)
|
|
187
|
+
|
|
188
|
+
if verbose:
|
|
189
|
+
results = process_map(fn, trees, max_workers=max_worker)
|
|
190
|
+
else:
|
|
191
|
+
with ProcessPoolExecutor(max_worker) as p:
|
|
192
|
+
results = p.map(fn, trees)
|
|
193
|
+
|
|
194
|
+
return results
|
|
195
|
+
|
|
156
196
|
@classmethod
|
|
157
|
-
def from_swc(cls, root: str, ext: str = ".swc", **kwargs) ->
|
|
197
|
+
def from_swc(cls, root: str, ext: str = ".swc", **kwargs) -> Self:
|
|
158
198
|
if not os.path.exists(root):
|
|
159
199
|
raise FileNotFoundError(
|
|
160
200
|
f"the root does not refers to an existing directory: {root}"
|
|
161
201
|
)
|
|
162
202
|
|
|
163
203
|
swcs = cls.find_swcs(root, ext)
|
|
164
|
-
return
|
|
204
|
+
return cls(LazyLoadingTrees(swcs, **kwargs), root=root)
|
|
165
205
|
|
|
166
206
|
@classmethod
|
|
167
207
|
def from_eswc(
|
|
@@ -170,7 +210,7 @@ class Population:
|
|
|
170
210
|
ext: str = ".eswc",
|
|
171
211
|
extra_cols: Optional[Iterable[str]] = None,
|
|
172
212
|
**kwargs,
|
|
173
|
-
) ->
|
|
213
|
+
) -> Self:
|
|
174
214
|
extra_cols = list(extra_cols) if extra_cols is not None else []
|
|
175
215
|
extra_cols.extend(k for k, t in eswc_cols)
|
|
176
216
|
return cls.from_swc(root, ext, extra_cols=extra_cols, **kwargs)
|
|
@@ -235,7 +275,7 @@ class Populations:
|
|
|
235
275
|
return Population(ChainTrees(p.trees for p in self.populations))
|
|
236
276
|
|
|
237
277
|
@classmethod
|
|
238
|
-
def from_swc(
|
|
278
|
+
def from_swc(
|
|
239
279
|
cls,
|
|
240
280
|
roots: Iterable[str],
|
|
241
281
|
ext: str = ".swc",
|
|
@@ -243,7 +283,7 @@ class Populations:
|
|
|
243
283
|
check_same: bool = False,
|
|
244
284
|
labels: Optional[Iterable[str]] = None,
|
|
245
285
|
**kwargs,
|
|
246
|
-
) ->
|
|
286
|
+
) -> Self:
|
|
247
287
|
"""Get population from dirs.
|
|
248
288
|
|
|
249
289
|
Parameters
|
|
@@ -275,7 +315,7 @@ class Populations:
|
|
|
275
315
|
)
|
|
276
316
|
for i, d in enumerate(roots)
|
|
277
317
|
]
|
|
278
|
-
return
|
|
318
|
+
return cls(populations, labels=labels)
|
|
279
319
|
|
|
280
320
|
@classmethod
|
|
281
321
|
def from_eswc(
|
|
@@ -285,7 +325,7 @@ class Populations:
|
|
|
285
325
|
*,
|
|
286
326
|
ext: str = ".eswc",
|
|
287
327
|
**kwargs,
|
|
288
|
-
) ->
|
|
328
|
+
) -> Self:
|
|
289
329
|
extra_cols = list(extra_cols) if extra_cols is not None else []
|
|
290
330
|
extra_cols.extend(k for k, t in eswc_cols)
|
|
291
331
|
return cls.from_swc(roots, extra_cols=extra_cols, ext=ext, **kwargs)
|
|
@@ -6,11 +6,16 @@ This module is deprecated, please use `~.transforms.LinesToTree`
|
|
|
6
6
|
instead.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
__all__ = ["assemble_lines", "try_assemble_lines"]
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
def assemble_lines(*args, **kwargs):
|
|
13
|
+
"""Assemble lines to tree.
|
|
14
|
+
|
|
15
|
+
.. deprecated:: 0.15.0
|
|
16
|
+
Use :meth:`~.transforms.LinesToTree` instead.
|
|
17
|
+
"""
|
|
18
|
+
|
|
14
19
|
raise DeprecationWarning(
|
|
15
20
|
"`assemble_lines` has been replaced by "
|
|
16
21
|
"`~.transforms.LinesToTree` because it can be easy assemble "
|
|
@@ -19,6 +24,12 @@ def assemble_lines(*args, **kwargs):
|
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
def try_assemble_lines(*args, **kwargs):
|
|
27
|
+
"""Try assemble lines to tree.
|
|
28
|
+
|
|
29
|
+
.. deprecated:: 0.15.0
|
|
30
|
+
Use :meth:`~.transforms.LinesToTree` instead.
|
|
31
|
+
"""
|
|
32
|
+
|
|
22
33
|
raise DeprecationWarning(
|
|
23
34
|
"`try_assemble_lines` has been replaced by "
|
|
24
35
|
"`~.transforms.LinesToTree` because it can be easy assemble "
|
swcgeom/core/swc_utils/base.py
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"""Base SWC format utils."""
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import (
|
|
5
|
+
Callable,
|
|
6
|
+
List,
|
|
7
|
+
Literal,
|
|
8
|
+
NamedTuple,
|
|
9
|
+
Optional,
|
|
10
|
+
Tuple,
|
|
11
|
+
TypeVar,
|
|
12
|
+
overload,
|
|
13
|
+
)
|
|
5
14
|
|
|
6
15
|
import numpy as np
|
|
7
16
|
import numpy.typing as npt
|
|
@@ -23,8 +32,7 @@ T, K = TypeVar("T"), TypeVar("K")
|
|
|
23
32
|
Topology = Tuple[npt.NDArray[np.int32], npt.NDArray[np.int32]] # (id, pid)
|
|
24
33
|
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
class SWCNames:
|
|
35
|
+
class SWCNames(NamedTuple):
|
|
28
36
|
"""SWC format column names."""
|
|
29
37
|
|
|
30
38
|
id: str = "id"
|
|
@@ -46,8 +54,7 @@ def get_names(names: Optional[SWCNames] = None) -> SWCNames:
|
|
|
46
54
|
return names or swc_names
|
|
47
55
|
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
class SWCTypes:
|
|
57
|
+
class SWCTypes(NamedTuple):
|
|
51
58
|
"""SWC format types.
|
|
52
59
|
|
|
53
60
|
See Also
|
|
@@ -10,7 +10,6 @@ import pandas as pd
|
|
|
10
10
|
from swcgeom.core.swc_utils.base import SWCNames, Topology, get_dsu, get_names, traverse
|
|
11
11
|
from swcgeom.utils import DisjointSetUnion
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
__all__ = [
|
|
15
14
|
"is_single_root",
|
|
16
15
|
"is_bifurcate",
|
|
@@ -83,6 +82,12 @@ def has_cyclic(topology: Topology) -> bool:
|
|
|
83
82
|
|
|
84
83
|
|
|
85
84
|
def check_single_root(*args, **kwargs) -> bool:
|
|
85
|
+
"""Check if the tree is single root.
|
|
86
|
+
|
|
87
|
+
.. deprecated:: 0.5.0
|
|
88
|
+
Use :meth:`is_single_root` instead.
|
|
89
|
+
"""
|
|
90
|
+
|
|
86
91
|
warnings.warn(
|
|
87
92
|
"`check_single_root` has been renamed to `is_single_root` since"
|
|
88
93
|
"v0.5.0, and will be removed in next version",
|
|
@@ -94,7 +99,12 @@ def check_single_root(*args, **kwargs) -> bool:
|
|
|
94
99
|
def is_binary_tree(
|
|
95
100
|
df: pd.DataFrame, exclude_root: bool = True, *, names: Optional[SWCNames] = None
|
|
96
101
|
) -> bool:
|
|
97
|
-
"""Check is it a binary tree.
|
|
102
|
+
"""Check is it a binary tree.
|
|
103
|
+
|
|
104
|
+
.. deprecated:: 0.8.0
|
|
105
|
+
Use :meth:`is_bifurcate` instead.
|
|
106
|
+
"""
|
|
107
|
+
|
|
98
108
|
warnings.warn(
|
|
99
109
|
"`is_binary_tree` has been replaced by to `is_bifurcate` since"
|
|
100
110
|
"v0.8.0, and will be removed in next version",
|
swcgeom/core/tree.py
CHANGED
|
@@ -22,9 +22,9 @@ import numpy.typing as npt
|
|
|
22
22
|
import pandas as pd
|
|
23
23
|
|
|
24
24
|
from swcgeom.core.branch import Branch
|
|
25
|
+
from swcgeom.core.compartment import Compartment, Compartments
|
|
25
26
|
from swcgeom.core.node import Node
|
|
26
27
|
from swcgeom.core.path import Path
|
|
27
|
-
from swcgeom.core.segment import Segment, Segments
|
|
28
28
|
from swcgeom.core.swc import DictSWC, eswc_cols
|
|
29
29
|
from swcgeom.core.swc_utils import SWCNames, get_names, read_swc, traverse
|
|
30
30
|
from swcgeom.core.tree_utils_impl import Mapping, get_subtree_impl
|
|
@@ -48,15 +48,6 @@ class Tree(DictSWC):
|
|
|
48
48
|
children = self.attach.id()[self.attach.pid() == self.id]
|
|
49
49
|
return [Tree.Node(self.attach, idx) for idx in children]
|
|
50
50
|
|
|
51
|
-
def get_branch(self) -> "Tree.Branch":
|
|
52
|
-
warnings.warn(
|
|
53
|
-
"`Tree.Node.get_branch` has been renamed to "
|
|
54
|
-
"`Tree.Node.branch` since v0.3.1 and will be removed "
|
|
55
|
-
"in next version",
|
|
56
|
-
DeprecationWarning,
|
|
57
|
-
)
|
|
58
|
-
return self.branch()
|
|
59
|
-
|
|
60
51
|
def branch(self) -> "Tree.Branch":
|
|
61
52
|
ns: List["Tree.Node"] = [self]
|
|
62
53
|
while not ns[-1].is_bifurcation() and (p := ns[-1].parent()) is not None:
|
|
@@ -115,9 +106,11 @@ class Tree(DictSWC):
|
|
|
115
106
|
# TODO: should returns `Tree.Node`
|
|
116
107
|
"""Neural path."""
|
|
117
108
|
|
|
118
|
-
class
|
|
109
|
+
class Compartment(Compartment["Tree"]):
|
|
119
110
|
# TODO: should returns `Tree.Node`
|
|
120
|
-
"""Neural
|
|
111
|
+
"""Neural compartment."""
|
|
112
|
+
|
|
113
|
+
Segment = Compartment # Alias
|
|
121
114
|
|
|
122
115
|
class Branch(Branch["Tree"]):
|
|
123
116
|
# TODO: should returns `Tree.Node`
|
|
@@ -127,33 +120,33 @@ class Tree(DictSWC):
|
|
|
127
120
|
self,
|
|
128
121
|
n_nodes: int,
|
|
129
122
|
*,
|
|
130
|
-
# pylint: disable-next=redefined-builtin
|
|
131
|
-
id: Optional[npt.NDArray[np.int32]] = None,
|
|
132
|
-
# pylint: disable-next=redefined-builtin
|
|
133
|
-
type: Optional[npt.NDArray[np.int32]] = None,
|
|
134
|
-
x: Optional[npt.NDArray[np.float32]] = None,
|
|
135
|
-
y: Optional[npt.NDArray[np.float32]] = None,
|
|
136
|
-
z: Optional[npt.NDArray[np.float32]] = None,
|
|
137
|
-
r: Optional[npt.NDArray[np.float32]] = None,
|
|
138
|
-
pid: Optional[npt.NDArray[np.int32]] = None,
|
|
139
123
|
source: str = "",
|
|
140
124
|
comments: Optional[Iterable[str]] = None,
|
|
141
125
|
names: Optional[SWCNames] = None,
|
|
142
126
|
**kwargs: npt.NDArray,
|
|
143
127
|
) -> None:
|
|
144
128
|
names = get_names(names)
|
|
145
|
-
|
|
146
|
-
|
|
129
|
+
|
|
130
|
+
if names.id not in kwargs:
|
|
131
|
+
kwargs[names.id] = np.arange(0, n_nodes, step=1, dtype=np.int32)
|
|
132
|
+
|
|
133
|
+
if names.pid not in kwargs:
|
|
134
|
+
kwargs[names.pid] = np.arange(-1, n_nodes - 1, step=1, dtype=np.int32)
|
|
147
135
|
|
|
148
136
|
ndata = {
|
|
149
|
-
names.id: padding1d(n_nodes, id, dtype=np.int32),
|
|
150
|
-
names.type: padding1d(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
names.
|
|
154
|
-
names.
|
|
155
|
-
names.
|
|
137
|
+
names.id: padding1d(n_nodes, kwargs.pop(names.id, None), dtype=np.int32),
|
|
138
|
+
names.type: padding1d(
|
|
139
|
+
n_nodes, kwargs.pop(names.type, None), dtype=np.int32
|
|
140
|
+
),
|
|
141
|
+
names.x: padding1d(n_nodes, kwargs.pop(names.x, None), dtype=np.float32),
|
|
142
|
+
names.y: padding1d(n_nodes, kwargs.pop(names.y, None), dtype=np.float32),
|
|
143
|
+
names.z: padding1d(n_nodes, kwargs.pop(names.z, None), dtype=np.float32),
|
|
144
|
+
names.r: padding1d(
|
|
145
|
+
n_nodes, kwargs.pop(names.r, None), dtype=np.float32, padding_value=1
|
|
146
|
+
),
|
|
147
|
+
names.pid: padding1d(n_nodes, kwargs.pop(names.pid, None), dtype=np.int32),
|
|
156
148
|
}
|
|
149
|
+
# ? padding other columns
|
|
157
150
|
super().__init__(
|
|
158
151
|
**ndata, **kwargs, source=source, comments=comments, names=names
|
|
159
152
|
)
|
|
@@ -222,13 +215,16 @@ class Tree(DictSWC):
|
|
|
222
215
|
tip_ids = np.setdiff1d(self.id(), self.pid(), assume_unique=True)
|
|
223
216
|
return [self.node(i) for i in tip_ids]
|
|
224
217
|
|
|
225
|
-
def
|
|
226
|
-
return
|
|
218
|
+
def get_compartments(self) -> Compartments[Compartment]:
|
|
219
|
+
return Compartments(self.Compartment(self, n.pid, n.id) for n in self[1:])
|
|
227
220
|
|
|
228
|
-
def
|
|
229
|
-
|
|
221
|
+
def get_segments(self) -> Compartments[Compartment]: # Alias
|
|
222
|
+
return self.get_compartments()
|
|
230
223
|
|
|
231
|
-
|
|
224
|
+
def get_branches(self) -> List[Branch]:
|
|
225
|
+
def collect_branches(
|
|
226
|
+
node: "Tree.Node", pre: List[Tuple[List[Tree.Branch], List[int]]]
|
|
227
|
+
) -> Tuple[List[Tree.Branch], List[int]]:
|
|
232
228
|
if len(pre) == 1:
|
|
233
229
|
branches, child = pre[0]
|
|
234
230
|
child.append(node.id)
|
|
@@ -251,7 +247,6 @@ class Tree(DictSWC):
|
|
|
251
247
|
def get_paths(self) -> List[Path]:
|
|
252
248
|
"""Get all path from soma to tips."""
|
|
253
249
|
path_dic: Dict[int, List[int]] = {}
|
|
254
|
-
Paths = List[List[int]]
|
|
255
250
|
|
|
256
251
|
def assign_path(n: Tree.Node, pre_path: List[int] | None) -> List[int]:
|
|
257
252
|
path = [] if pre_path is None else pre_path.copy()
|
|
@@ -259,7 +254,9 @@ class Tree(DictSWC):
|
|
|
259
254
|
path_dic[n.id] = path
|
|
260
255
|
return path
|
|
261
256
|
|
|
262
|
-
def collect_path(
|
|
257
|
+
def collect_path(
|
|
258
|
+
n: Tree.Node, children: List[List[List[int]]]
|
|
259
|
+
) -> List[List[int]]:
|
|
263
260
|
if len(children) == 0:
|
|
264
261
|
return [path_dic[n.id]]
|
|
265
262
|
|
swcgeom/core/tree_utils.py
CHANGED
|
@@ -101,11 +101,15 @@ def to_sub_tree(swc_like: SWCLike, sub: Topology) -> Tuple[Tree, Dict[int, int]]
|
|
|
101
101
|
but if the node you remove is not a leaf node, you need to use
|
|
102
102
|
`propagate_remove` to remove all children.
|
|
103
103
|
|
|
104
|
+
.. deprecated:: 0.6.0
|
|
105
|
+
Use :meth:`to_subtree` instead.
|
|
106
|
+
|
|
104
107
|
Returns
|
|
105
108
|
-------
|
|
106
109
|
tree : Tree
|
|
107
110
|
id_map : Dict[int, int]
|
|
108
111
|
"""
|
|
112
|
+
|
|
109
113
|
warnings.warn(
|
|
110
114
|
"`to_sub_tree` will be removed in v0.6.0, it is replaced by "
|
|
111
115
|
"`to_subtree` beacuse it is easy to use, and this will be "
|