swcgeom 0.14.0__py3-none-any.whl → 0.16.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/lmeasure.py +821 -0
- swcgeom/analysis/sholl.py +31 -2
- swcgeom/core/__init__.py +4 -0
- swcgeom/core/branch.py +9 -4
- swcgeom/core/branch_tree.py +2 -3
- swcgeom/core/{segment.py → compartment.py} +14 -9
- swcgeom/core/node.py +0 -8
- swcgeom/core/path.py +21 -6
- swcgeom/core/population.py +42 -3
- swcgeom/core/swc_utils/assembler.py +20 -138
- swcgeom/core/swc_utils/base.py +12 -5
- swcgeom/core/swc_utils/checker.py +12 -2
- swcgeom/core/swc_utils/subtree.py +2 -2
- swcgeom/core/tree.py +53 -49
- swcgeom/core/tree_utils.py +27 -5
- swcgeom/core/tree_utils_impl.py +22 -6
- swcgeom/images/augmentation.py +6 -1
- swcgeom/images/contrast.py +107 -0
- swcgeom/images/folder.py +111 -29
- swcgeom/images/io.py +79 -40
- swcgeom/transforms/__init__.py +2 -0
- swcgeom/transforms/base.py +41 -21
- swcgeom/transforms/branch.py +5 -5
- swcgeom/transforms/geometry.py +42 -18
- swcgeom/transforms/image_preprocess.py +100 -0
- swcgeom/transforms/image_stack.py +46 -28
- swcgeom/transforms/images.py +76 -6
- swcgeom/transforms/mst.py +10 -18
- swcgeom/transforms/neurolucida_asc.py +495 -0
- swcgeom/transforms/population.py +2 -2
- swcgeom/transforms/tree.py +12 -14
- swcgeom/transforms/tree_assembler.py +85 -19
- 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.14.0.dist-info → swcgeom-0.16.0.dist-info}/METADATA +3 -3
- swcgeom-0.16.0.dist-info/RECORD +67 -0
- {swcgeom-0.14.0.dist-info → swcgeom-0.16.0.dist-info}/WHEEL +1 -1
- swcgeom-0.14.0.dist-info/RECORD +0 -62
- {swcgeom-0.14.0.dist-info → swcgeom-0.16.0.dist-info}/LICENSE +0 -0
- {swcgeom-0.14.0.dist-info → swcgeom-0.16.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."""
|
swcgeom/core/branch_tree.py
CHANGED
|
@@ -5,7 +5,6 @@ from typing import Dict, List
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pandas as pd
|
|
8
|
-
from typing_extensions import Self
|
|
9
8
|
|
|
10
9
|
from swcgeom.core.branch import Branch
|
|
11
10
|
from swcgeom.core.swc_utils import to_sub_topology
|
|
@@ -31,7 +30,7 @@ class BranchTree(Tree):
|
|
|
31
30
|
return self.branches[idx]
|
|
32
31
|
|
|
33
32
|
@classmethod
|
|
34
|
-
def from_tree(cls, tree: Tree) ->
|
|
33
|
+
def from_tree(cls, tree: Tree) -> "BranchTree":
|
|
35
34
|
"""Generating a branch tree from tree."""
|
|
36
35
|
|
|
37
36
|
branches = tree.get_branches()
|
|
@@ -56,6 +55,6 @@ class BranchTree(Tree):
|
|
|
56
55
|
return branch_tree
|
|
57
56
|
|
|
58
57
|
@classmethod
|
|
59
|
-
def from_data_frame(cls, df: pd.DataFrame, *args, **kwargs) ->
|
|
58
|
+
def from_data_frame(cls, df: pd.DataFrame, *args, **kwargs) -> "BranchTree":
|
|
60
59
|
tree = super().from_data_frame(df, *args, **kwargs)
|
|
61
60
|
return cls.from_tree(tree)
|
|
@@ -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,25 @@
|
|
|
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
|
|
20
24
|
from typing_extensions import Self
|
|
21
25
|
|
|
22
26
|
from swcgeom.core.swc import eswc_cols
|
|
@@ -25,6 +29,9 @@ from swcgeom.core.tree import Tree
|
|
|
25
29
|
__all__ = ["LazyLoadingTrees", "ChainTrees", "Population", "Populations"]
|
|
26
30
|
|
|
27
31
|
|
|
32
|
+
T = TypeVar("T")
|
|
33
|
+
|
|
34
|
+
|
|
28
35
|
class Trees(Protocol):
|
|
29
36
|
"""Trees protocol support index and len."""
|
|
30
37
|
|
|
@@ -49,6 +56,7 @@ class LazyLoadingTrees:
|
|
|
49
56
|
kwargs : Dict[str, Any]
|
|
50
57
|
Forwarding to `Tree.from_swc`
|
|
51
58
|
"""
|
|
59
|
+
|
|
52
60
|
super().__init__()
|
|
53
61
|
self.swcs = list(swcs)
|
|
54
62
|
self.trees = [None for _ in swcs]
|
|
@@ -62,6 +70,9 @@ class LazyLoadingTrees:
|
|
|
62
70
|
def __len__(self) -> int:
|
|
63
71
|
return len(self.swcs)
|
|
64
72
|
|
|
73
|
+
def __iter__(self) -> Iterator[Tree]:
|
|
74
|
+
return (self[i] for i in range(self.__len__()))
|
|
75
|
+
|
|
65
76
|
def load(self, key: int) -> None:
|
|
66
77
|
if self.trees[key] is None:
|
|
67
78
|
self.trees[key] = Tree.from_swc(self.swcs[key], **self.kwargs)
|
|
@@ -93,6 +104,9 @@ class ChainTrees:
|
|
|
93
104
|
def __len__(self) -> int:
|
|
94
105
|
return self.cumsum[-1].item()
|
|
95
106
|
|
|
107
|
+
def __iter__(self) -> Iterator[Tree]:
|
|
108
|
+
return (self[i] for i in range(self.__len__()))
|
|
109
|
+
|
|
96
110
|
|
|
97
111
|
class Population:
|
|
98
112
|
"""Neuron population."""
|
|
@@ -154,6 +168,31 @@ class Population:
|
|
|
154
168
|
def __repr__(self) -> str:
|
|
155
169
|
return f"Neuron population in '{self.root}'"
|
|
156
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
|
+
|
|
157
196
|
@classmethod
|
|
158
197
|
def from_swc(cls, root: str, ext: str = ".swc", **kwargs) -> Self:
|
|
159
198
|
if not os.path.exists(root):
|
|
@@ -162,7 +201,7 @@ class Population:
|
|
|
162
201
|
)
|
|
163
202
|
|
|
164
203
|
swcs = cls.find_swcs(root, ext)
|
|
165
|
-
return
|
|
204
|
+
return cls(LazyLoadingTrees(swcs, **kwargs), root=root)
|
|
166
205
|
|
|
167
206
|
@classmethod
|
|
168
207
|
def from_eswc(
|
|
@@ -236,7 +275,7 @@ class Populations:
|
|
|
236
275
|
return Population(ChainTrees(p.trees for p in self.populations))
|
|
237
276
|
|
|
238
277
|
@classmethod
|
|
239
|
-
def from_swc(
|
|
278
|
+
def from_swc(
|
|
240
279
|
cls,
|
|
241
280
|
roots: Iterable[str],
|
|
242
281
|
ext: str = ".swc",
|
|
@@ -268,7 +307,7 @@ class Populations:
|
|
|
268
307
|
|
|
269
308
|
fs = [inter for _ in roots]
|
|
270
309
|
elif check_same:
|
|
271
|
-
assert
|
|
310
|
+
assert [fs[0] == a for a in fs[1:]], "not the same among populations"
|
|
272
311
|
|
|
273
312
|
populations = [
|
|
274
313
|
Population(
|
|
@@ -1,155 +1,37 @@
|
|
|
1
|
-
"""Assemble lines to swc.
|
|
1
|
+
"""Assemble lines to swc.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import pandas as pd
|
|
9
|
-
|
|
10
|
-
from swcgeom.core.swc_utils.base import SWCNames, get_names
|
|
11
|
-
from swcgeom.core.swc_utils.normalizer import link_roots_to_nearest_, sort_nodes_
|
|
3
|
+
Notes
|
|
4
|
+
-----
|
|
5
|
+
This module is deprecated, please use `~.transforms.LinesToTree`
|
|
6
|
+
instead.
|
|
7
|
+
"""
|
|
12
8
|
|
|
13
9
|
__all__ = ["assemble_lines", "try_assemble_lines"]
|
|
14
10
|
|
|
15
11
|
|
|
16
|
-
def assemble_lines(*args, **kwargs)
|
|
17
|
-
"""Assemble lines to
|
|
18
|
-
|
|
19
|
-
Assemble all the lines into a set of subtrees, and then connect
|
|
20
|
-
them.
|
|
12
|
+
def assemble_lines(*args, **kwargs):
|
|
13
|
+
"""Assemble lines to tree.
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
lines : List of ~pd.DataFrame
|
|
25
|
-
An array of tables containing a line, columns should follwing
|
|
26
|
-
the swc.
|
|
27
|
-
**kwargs
|
|
28
|
-
Forwarding to `try_assemble_lines`
|
|
29
|
-
|
|
30
|
-
Returns
|
|
31
|
-
-------
|
|
32
|
-
tree : ~pd.DataFrame
|
|
33
|
-
|
|
34
|
-
See Also
|
|
35
|
-
--------
|
|
36
|
-
~swcgeom.core.swc_utils.try_assemble_lines
|
|
15
|
+
.. deprecated:: 0.15.0
|
|
16
|
+
Use :meth:`~.transforms.LinesToTree` instead.
|
|
37
17
|
"""
|
|
38
|
-
|
|
18
|
+
|
|
19
|
+
raise DeprecationWarning(
|
|
39
20
|
"`assemble_lines` has been replaced by "
|
|
40
21
|
"`~.transforms.LinesToTree` because it can be easy assemble "
|
|
41
|
-
"with other tansformations,
|
|
42
|
-
"version.",
|
|
43
|
-
DeprecationWarning,
|
|
22
|
+
"with other tansformations.",
|
|
44
23
|
)
|
|
45
|
-
return assemble_lines_impl(*args, **kwargs)
|
|
46
24
|
|
|
47
25
|
|
|
48
|
-
def try_assemble_lines(*args, **kwargs)
|
|
49
|
-
"""
|
|
26
|
+
def try_assemble_lines(*args, **kwargs):
|
|
27
|
+
"""Try assemble lines to tree.
|
|
50
28
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
the tree, repeat until there are no line to merge, return tree and
|
|
54
|
-
the remaining lines.
|
|
55
|
-
|
|
56
|
-
Parameters
|
|
57
|
-
----------
|
|
58
|
-
lines : List of ~pd.DataFrame
|
|
59
|
-
An array of tables containing a line, columns should follwing
|
|
60
|
-
the swc.
|
|
61
|
-
undirected : bool, default `True`
|
|
62
|
-
Both ends of a line can be considered connection point. If
|
|
63
|
-
`False`, only the starting point.
|
|
64
|
-
thre : float, default `0.2`
|
|
65
|
-
Connection threshold.
|
|
66
|
-
id_offset : int, default `0`
|
|
67
|
-
The offset of the line node id.
|
|
68
|
-
sort_nodes : bool, default `True`
|
|
69
|
-
sort nodes of subtree
|
|
70
|
-
names : SWCNames, optional
|
|
71
|
-
|
|
72
|
-
Returns
|
|
73
|
-
-------
|
|
74
|
-
tree : ~pd.DataFrame
|
|
75
|
-
remaining_lines : List of ~pd.DataFrame
|
|
29
|
+
.. deprecated:: 0.15.0
|
|
30
|
+
Use :meth:`~.transforms.LinesToTree` instead.
|
|
76
31
|
"""
|
|
77
|
-
|
|
32
|
+
|
|
33
|
+
raise DeprecationWarning(
|
|
78
34
|
"`try_assemble_lines` has been replaced by "
|
|
79
35
|
"`~.transforms.LinesToTree` because it can be easy assemble "
|
|
80
|
-
"with other tansformations,
|
|
81
|
-
"version.",
|
|
82
|
-
DeprecationWarning,
|
|
36
|
+
"with other tansformations.",
|
|
83
37
|
)
|
|
84
|
-
return try_assemble_lines_impl(*args, **kwargs)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# TODO: move the following codes to `transforms` module
|
|
88
|
-
|
|
89
|
-
EPS = 1e-5
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def assemble_lines_impl(lines: Iterable[pd.DataFrame], **kwargs) -> pd.DataFrame:
|
|
93
|
-
tree, lines = try_assemble_lines_impl(lines, sort_nodes=False, **kwargs)
|
|
94
|
-
while len(lines) > 0:
|
|
95
|
-
t, lines = try_assemble_lines_impl(
|
|
96
|
-
lines, id_offset=len(tree), sort_nodes=False, **kwargs
|
|
97
|
-
)
|
|
98
|
-
tree = pd.concat([tree, t])
|
|
99
|
-
|
|
100
|
-
tree = tree.reset_index()
|
|
101
|
-
link_roots_to_nearest_(tree)
|
|
102
|
-
sort_nodes_(tree)
|
|
103
|
-
return tree
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def try_assemble_lines_impl( # pylint: disable=too-many-arguments
|
|
107
|
-
lines: Iterable[pd.DataFrame],
|
|
108
|
-
undirected: bool = True,
|
|
109
|
-
thre: float = 0.2,
|
|
110
|
-
id_offset: int = 0,
|
|
111
|
-
sort_nodes: bool = True,
|
|
112
|
-
*,
|
|
113
|
-
names: Optional[SWCNames] = None,
|
|
114
|
-
) -> Tuple[pd.DataFrame, List[pd.DataFrame]]:
|
|
115
|
-
names = get_names(names)
|
|
116
|
-
lines = copy(list(lines))
|
|
117
|
-
|
|
118
|
-
tree = lines[0]
|
|
119
|
-
tree[names.id] = id_offset + np.arange(len(tree))
|
|
120
|
-
tree[names.pid] = tree[names.id] - 1
|
|
121
|
-
tree.at[0, names.pid] = -1
|
|
122
|
-
del lines[0]
|
|
123
|
-
|
|
124
|
-
while True:
|
|
125
|
-
for i, line in enumerate(lines):
|
|
126
|
-
for p in [0, -1] if undirected else [0]:
|
|
127
|
-
xyz = [names.x, names.y, names.z]
|
|
128
|
-
vs = tree[xyz] - line.iloc[p][xyz]
|
|
129
|
-
dis = np.linalg.norm(vs, axis=1)
|
|
130
|
-
ind = np.argmin(dis)
|
|
131
|
-
if dis[ind] > thre:
|
|
132
|
-
continue
|
|
133
|
-
|
|
134
|
-
if dis[ind] < EPS:
|
|
135
|
-
line = line.drop((p + len(line)) % len(line)).reset_index(drop=True)
|
|
136
|
-
|
|
137
|
-
line[names.id] = id_offset + len(tree) + np.arange(len(line))
|
|
138
|
-
line[names.pid] = line[names.id] + (-1 if p == 0 else 1)
|
|
139
|
-
line.at[(p + len(line)) % len(line), names.pid] = tree.iloc[ind][
|
|
140
|
-
names.id
|
|
141
|
-
]
|
|
142
|
-
tree = pd.concat([tree, line])
|
|
143
|
-
del lines[i]
|
|
144
|
-
break
|
|
145
|
-
else:
|
|
146
|
-
continue
|
|
147
|
-
|
|
148
|
-
break
|
|
149
|
-
else:
|
|
150
|
-
break
|
|
151
|
-
|
|
152
|
-
if sort_nodes:
|
|
153
|
-
sort_nodes_(tree)
|
|
154
|
-
|
|
155
|
-
return tree, lines
|
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",
|
|
@@ -27,8 +27,8 @@ def to_sub_topology(sub: Topology) -> Tuple[Topology, npt.NDArray[np.int32]]:
|
|
|
27
27
|
Returns
|
|
28
28
|
-------
|
|
29
29
|
sub_topology : Topology
|
|
30
|
-
|
|
31
|
-
Map from new id to
|
|
30
|
+
mapping : List of int
|
|
31
|
+
Map from new id to old id.
|
|
32
32
|
|
|
33
33
|
See Also
|
|
34
34
|
--------
|