swcgeom 0.16.0__py3-none-any.whl → 0.17.1__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 +16 -15
- swcgeom/analysis/{node_features.py → features.py} +105 -3
- swcgeom/analysis/lmeasure.py +5 -5
- swcgeom/analysis/sholl.py +4 -4
- swcgeom/analysis/trunk.py +12 -11
- swcgeom/analysis/visualization.py +9 -9
- swcgeom/analysis/visualization3d.py +85 -0
- swcgeom/analysis/volume.py +4 -4
- swcgeom/core/branch.py +4 -3
- swcgeom/core/branch_tree.py +3 -4
- swcgeom/core/compartment.py +3 -2
- swcgeom/core/node.py +2 -2
- swcgeom/core/path.py +3 -2
- swcgeom/core/population.py +16 -27
- swcgeom/core/swc.py +11 -10
- swcgeom/core/swc_utils/base.py +8 -17
- swcgeom/core/swc_utils/io.py +7 -6
- swcgeom/core/swc_utils/normalizer.py +4 -3
- swcgeom/core/swc_utils/subtree.py +2 -2
- swcgeom/core/tree.py +22 -34
- swcgeom/core/tree_utils.py +11 -10
- swcgeom/core/tree_utils_impl.py +3 -3
- swcgeom/images/augmentation.py +3 -3
- swcgeom/images/folder.py +10 -16
- swcgeom/images/io.py +76 -111
- swcgeom/transforms/image_stack.py +6 -5
- swcgeom/transforms/images.py +105 -5
- swcgeom/transforms/neurolucida_asc.py +4 -6
- swcgeom/transforms/population.py +1 -3
- swcgeom/transforms/tree.py +8 -7
- swcgeom/transforms/tree_assembler.py +4 -3
- swcgeom/utils/ellipse.py +3 -4
- swcgeom/utils/neuromorpho.py +17 -16
- swcgeom/utils/plotter_2d.py +12 -6
- swcgeom/utils/plotter_3d.py +31 -0
- swcgeom/utils/renderer.py +6 -6
- swcgeom/utils/sdf.py +2 -2
- swcgeom/utils/solid_geometry.py +1 -3
- swcgeom/utils/transforms.py +1 -3
- swcgeom/utils/volumetric_object.py +8 -10
- {swcgeom-0.16.0.dist-info → swcgeom-0.17.1.dist-info}/METADATA +1 -1
- swcgeom-0.17.1.dist-info/RECORD +67 -0
- swcgeom/analysis/branch_features.py +0 -67
- swcgeom/analysis/path_features.py +0 -37
- swcgeom-0.16.0.dist-info/RECORD +0 -67
- {swcgeom-0.16.0.dist-info → swcgeom-0.17.1.dist-info}/LICENSE +0 -0
- {swcgeom-0.16.0.dist-info → swcgeom-0.17.1.dist-info}/WHEEL +0 -0
- {swcgeom-0.16.0.dist-info → swcgeom-0.17.1.dist-info}/top_level.txt +0 -0
swcgeom/core/branch_tree.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Branch tree is a simplified neuron tree."""
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
|
-
from typing import Dict, List
|
|
5
4
|
|
|
6
5
|
import numpy as np
|
|
7
6
|
import pandas as pd
|
|
@@ -19,13 +18,13 @@ class BranchTree(Tree):
|
|
|
19
18
|
A branch tree that contains only soma, branch, and tip nodes.
|
|
20
19
|
"""
|
|
21
20
|
|
|
22
|
-
branches:
|
|
21
|
+
branches: dict[int, list[Branch]]
|
|
23
22
|
|
|
24
|
-
def get_origin_branches(self) ->
|
|
23
|
+
def get_origin_branches(self) -> list[Branch]:
|
|
25
24
|
"""Get branches of original tree."""
|
|
26
25
|
return list(itertools.chain(*self.branches.values()))
|
|
27
26
|
|
|
28
|
-
def get_origin_node_branches(self, idx: int) ->
|
|
27
|
+
def get_origin_node_branches(self, idx: int) -> list[Branch]:
|
|
29
28
|
"""Get branches of node of original tree."""
|
|
30
29
|
return self.branches[idx]
|
|
31
30
|
|
swcgeom/core/compartment.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""The segment is a branch with two nodes."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import Generic, TypeVar
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
7
|
import numpy.typing as npt
|
|
@@ -43,7 +44,7 @@ class Compartment(Path, Generic[SWCTypeVar]):
|
|
|
43
44
|
T = TypeVar("T", bound=Compartment)
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
class Compartments(
|
|
47
|
+
class Compartments(list[T]):
|
|
47
48
|
r"""Comparments contains a set of comparment."""
|
|
48
49
|
|
|
49
50
|
names: SWCNames
|
swcgeom/core/node.py
CHANGED
swcgeom/core/path.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Nueron path."""
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Iterable, Iterator
|
|
5
|
+
from typing import Generic, overload
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
import numpy.typing as npt
|
|
@@ -44,7 +45,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
44
45
|
@overload
|
|
45
46
|
def __getitem__(self, key: int) -> Node: ...
|
|
46
47
|
@overload
|
|
47
|
-
def __getitem__(self, key: slice) ->
|
|
48
|
+
def __getitem__(self, key: slice) -> list[Node]: ...
|
|
48
49
|
@overload
|
|
49
50
|
def __getitem__(self, key: str) -> npt.NDArray: ...
|
|
50
51
|
# fmt:on
|
swcgeom/core/population.py
CHANGED
|
@@ -2,21 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import warnings
|
|
5
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
5
6
|
from concurrent.futures import ProcessPoolExecutor
|
|
6
7
|
from functools import reduce
|
|
7
|
-
from typing import
|
|
8
|
-
Any,
|
|
9
|
-
Callable,
|
|
10
|
-
Dict,
|
|
11
|
-
Iterable,
|
|
12
|
-
Iterator,
|
|
13
|
-
List,
|
|
14
|
-
Optional,
|
|
15
|
-
Protocol,
|
|
16
|
-
TypeVar,
|
|
17
|
-
cast,
|
|
18
|
-
overload,
|
|
19
|
-
)
|
|
8
|
+
from typing import Any, Optional, Protocol, TypeVar, cast, overload
|
|
20
9
|
|
|
21
10
|
import numpy as np
|
|
22
11
|
import numpy.typing as npt
|
|
@@ -44,16 +33,16 @@ class Trees(Protocol):
|
|
|
44
33
|
class LazyLoadingTrees:
|
|
45
34
|
"""Lazy loading trees."""
|
|
46
35
|
|
|
47
|
-
swcs:
|
|
48
|
-
trees:
|
|
49
|
-
kwargs:
|
|
36
|
+
swcs: list[str]
|
|
37
|
+
trees: list[Tree | None]
|
|
38
|
+
kwargs: dict[str, Any]
|
|
50
39
|
|
|
51
40
|
def __init__(self, swcs: Iterable[str], **kwargs) -> None:
|
|
52
41
|
"""
|
|
53
42
|
Paramters
|
|
54
43
|
---------
|
|
55
44
|
swcs : List of str
|
|
56
|
-
kwargs :
|
|
45
|
+
kwargs : dict[str, Any]
|
|
57
46
|
Forwarding to `Tree.from_swc`
|
|
58
47
|
"""
|
|
59
48
|
|
|
@@ -81,7 +70,7 @@ class LazyLoadingTrees:
|
|
|
81
70
|
class ChainTrees:
|
|
82
71
|
"""Chain trees."""
|
|
83
72
|
|
|
84
|
-
trees:
|
|
73
|
+
trees: list[Trees]
|
|
85
74
|
cumsum: npt.NDArray[np.int64]
|
|
86
75
|
|
|
87
76
|
def __init__(self, trees: Iterable[Trees]) -> None:
|
|
@@ -146,7 +135,7 @@ class Population:
|
|
|
146
135
|
|
|
147
136
|
# fmt:off
|
|
148
137
|
@overload
|
|
149
|
-
def __getitem__(self, key: slice) ->
|
|
138
|
+
def __getitem__(self, key: slice) -> list[Tree]: ...
|
|
150
139
|
@overload
|
|
151
140
|
def __getitem__(self, key: int) -> Tree: ...
|
|
152
141
|
# fmt:on
|
|
@@ -216,9 +205,9 @@ class Population:
|
|
|
216
205
|
return cls.from_swc(root, ext, extra_cols=extra_cols, **kwargs)
|
|
217
206
|
|
|
218
207
|
@staticmethod
|
|
219
|
-
def find_swcs(root: str, ext: str = ".swc", relpath: bool = False) ->
|
|
208
|
+
def find_swcs(root: str, ext: str = ".swc", relpath: bool = False) -> list[str]:
|
|
220
209
|
"""Find all swc files."""
|
|
221
|
-
swcs:
|
|
210
|
+
swcs: list[str] = []
|
|
222
211
|
for r, _, files in os.walk(root):
|
|
223
212
|
rr = os.path.relpath(r, root) if relpath else r
|
|
224
213
|
fs = filter(lambda f: os.path.splitext(f)[-1] == ext, files)
|
|
@@ -231,8 +220,8 @@ class Populations:
|
|
|
231
220
|
"""A set of population."""
|
|
232
221
|
|
|
233
222
|
len: int
|
|
234
|
-
populations:
|
|
235
|
-
labels:
|
|
223
|
+
populations: list[Population]
|
|
224
|
+
labels: list[str]
|
|
236
225
|
|
|
237
226
|
def __init__(
|
|
238
227
|
self, populations: Iterable[Population], labels: Optional[Iterable[str]] = None
|
|
@@ -248,9 +237,9 @@ class Populations:
|
|
|
248
237
|
|
|
249
238
|
# fmt:off
|
|
250
239
|
@overload
|
|
251
|
-
def __getitem__(self, key: slice) ->
|
|
240
|
+
def __getitem__(self, key: slice) -> list[list[Tree]]: ...
|
|
252
241
|
@overload
|
|
253
|
-
def __getitem__(self, key: int) ->
|
|
242
|
+
def __getitem__(self, key: int) -> list[Tree]: ...
|
|
254
243
|
# fmt:on
|
|
255
244
|
def __getitem__(self, key):
|
|
256
245
|
return [p[key] for p in self.populations]
|
|
@@ -259,7 +248,7 @@ class Populations:
|
|
|
259
248
|
"""Miniumn length of populations."""
|
|
260
249
|
return self.len
|
|
261
250
|
|
|
262
|
-
def __iter__(self) -> Iterator[
|
|
251
|
+
def __iter__(self) -> Iterator[list[Tree]]:
|
|
263
252
|
return (self[i] for i in range(self.len))
|
|
264
253
|
|
|
265
254
|
def __repr__(self) -> str:
|
|
@@ -288,7 +277,7 @@ class Populations:
|
|
|
288
277
|
|
|
289
278
|
Parameters
|
|
290
279
|
----------
|
|
291
|
-
roots :
|
|
280
|
+
roots : List of str
|
|
292
281
|
intersect : bool, default `True`
|
|
293
282
|
Take the intersection of these populations.
|
|
294
283
|
check_same : bool, default `False`
|
swcgeom/core/swc.py
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
|
+
from collections.abc import Iterable
|
|
5
6
|
from copy import deepcopy
|
|
6
|
-
from typing import Any,
|
|
7
|
+
from typing import Any, Optional, TypeVar, overload
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
10
|
import numpy.typing as npt
|
|
@@ -32,7 +33,7 @@ __all__ = [
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
swc_names_default = get_names()
|
|
35
|
-
swc_cols:
|
|
36
|
+
swc_cols: list[tuple[str, npt.DTypeLike]] = [
|
|
36
37
|
(swc_names_default.id, np.int32),
|
|
37
38
|
(swc_names_default.type, np.int32),
|
|
38
39
|
(swc_names_default.x, np.float32),
|
|
@@ -42,7 +43,7 @@ swc_cols: List[Tuple[str, npt.DTypeLike]] = [
|
|
|
42
43
|
(swc_names_default.pid, np.int32),
|
|
43
44
|
]
|
|
44
45
|
|
|
45
|
-
eswc_cols:
|
|
46
|
+
eswc_cols: list[tuple[str, npt.DTypeLike]] = [
|
|
46
47
|
("level", np.int32),
|
|
47
48
|
("mode", np.int32),
|
|
48
49
|
("timestamp", np.int32),
|
|
@@ -55,7 +56,7 @@ class SWCLike(ABC):
|
|
|
55
56
|
"""ABC of SWC."""
|
|
56
57
|
|
|
57
58
|
source: str = ""
|
|
58
|
-
comments:
|
|
59
|
+
comments: list[str] = []
|
|
59
60
|
names: SWCNames
|
|
60
61
|
types: SWCTypes
|
|
61
62
|
|
|
@@ -131,15 +132,15 @@ class SWCLike(ABC):
|
|
|
131
132
|
|
|
132
133
|
# fmt: off
|
|
133
134
|
@overload
|
|
134
|
-
def to_swc(self, fname: str, *, extra_cols:
|
|
135
|
+
def to_swc(self, fname: str, *, extra_cols: list[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> None: ...
|
|
135
136
|
@overload
|
|
136
|
-
def to_swc(self, *, extra_cols:
|
|
137
|
+
def to_swc(self, *, extra_cols: list[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> str: ...
|
|
137
138
|
# fmt: on
|
|
138
139
|
def to_swc(
|
|
139
140
|
self,
|
|
140
141
|
fname: Optional[str] = None,
|
|
141
142
|
*,
|
|
142
|
-
extra_cols: Optional[
|
|
143
|
+
extra_cols: Optional[list[str]] = None,
|
|
143
144
|
source: bool | str = True,
|
|
144
145
|
comments: bool = True,
|
|
145
146
|
id_offset: int = 1,
|
|
@@ -177,7 +178,7 @@ class SWCLike(ABC):
|
|
|
177
178
|
self,
|
|
178
179
|
fname: Optional[str] = None,
|
|
179
180
|
swc_path: Optional[str] = None,
|
|
180
|
-
extra_cols: Optional[
|
|
181
|
+
extra_cols: Optional[list[str]] = None,
|
|
181
182
|
**kwargs,
|
|
182
183
|
) -> str | None:
|
|
183
184
|
if swc_path is None:
|
|
@@ -199,7 +200,7 @@ SWCTypeVar = TypeVar("SWCTypeVar", bound=SWCLike)
|
|
|
199
200
|
class DictSWC(SWCLike):
|
|
200
201
|
"""SWC implementation on dict."""
|
|
201
202
|
|
|
202
|
-
ndata:
|
|
203
|
+
ndata: dict[str, npt.NDArray]
|
|
203
204
|
|
|
204
205
|
def __init__(
|
|
205
206
|
self,
|
|
@@ -221,7 +222,7 @@ class DictSWC(SWCLike):
|
|
|
221
222
|
def values(self) -> Iterable[npt.NDArray[Any]]:
|
|
222
223
|
return self.ndata.values()
|
|
223
224
|
|
|
224
|
-
def items(self) -> Iterable[
|
|
225
|
+
def items(self) -> Iterable[tuple[str, npt.NDArray[Any]]]:
|
|
225
226
|
return self.ndata.items()
|
|
226
227
|
|
|
227
228
|
def get_ndata(self, key: str) -> npt.NDArray[Any]:
|
swcgeom/core/swc_utils/base.py
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
"""Base SWC format utils."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from typing import
|
|
5
|
-
Callable,
|
|
6
|
-
List,
|
|
7
|
-
Literal,
|
|
8
|
-
NamedTuple,
|
|
9
|
-
Optional,
|
|
10
|
-
Tuple,
|
|
11
|
-
TypeVar,
|
|
12
|
-
overload,
|
|
13
|
-
)
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Literal, NamedTuple, Optional, TypeVar, overload
|
|
14
5
|
|
|
15
6
|
import numpy as np
|
|
16
7
|
import numpy.typing as npt
|
|
@@ -29,7 +20,7 @@ __all__ = [
|
|
|
29
20
|
]
|
|
30
21
|
|
|
31
22
|
T, K = TypeVar("T"), TypeVar("K")
|
|
32
|
-
Topology =
|
|
23
|
+
Topology = tuple[npt.NDArray[np.int32], npt.NDArray[np.int32]] # (id, pid)
|
|
33
24
|
|
|
34
25
|
|
|
35
26
|
class SWCNames(NamedTuple):
|
|
@@ -43,7 +34,7 @@ class SWCNames(NamedTuple):
|
|
|
43
34
|
r: str = "r"
|
|
44
35
|
pid: str = "pid"
|
|
45
36
|
|
|
46
|
-
def cols(self) ->
|
|
37
|
+
def cols(self) -> list[str]:
|
|
47
38
|
return [self.id, self.type, self.x, self.y, self.z, self.r, self.pid]
|
|
48
39
|
|
|
49
40
|
|
|
@@ -114,10 +105,10 @@ def get_dsu(
|
|
|
114
105
|
@overload
|
|
115
106
|
def traverse(topology: Topology, *, enter: Callable[[int, T | None], T], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> None: ...
|
|
116
107
|
@overload
|
|
117
|
-
def traverse(topology: Topology, *, leave: Callable[[int,
|
|
108
|
+
def traverse(topology: Topology, *, leave: Callable[[int, list[K]], K], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> K: ...
|
|
118
109
|
@overload
|
|
119
110
|
def traverse(
|
|
120
|
-
topology: Topology, *, enter: Callable[[int, T | None], T], leave: Callable[[int,
|
|
111
|
+
topology: Topology, *, enter: Callable[[int, T | None], T], leave: Callable[[int, list[K]], K],
|
|
121
112
|
root: int | np.integer = ..., mode: Literal["dfs"] = ...,
|
|
122
113
|
) -> K: ...
|
|
123
114
|
# fmt: on
|
|
@@ -130,7 +121,7 @@ def traverse(topology: Topology, *, mode="dfs", **kwargs):
|
|
|
130
121
|
The callback when entering node, which accepts two parameters,
|
|
131
122
|
the current node id and the return value of it parent node. In
|
|
132
123
|
particular, the root node receives an `None`.
|
|
133
|
-
leave : (id: int, children:
|
|
124
|
+
leave : (id: int, children: list[T]) => T, optional
|
|
134
125
|
The callback when leaving node. When leaving a node, subtree
|
|
135
126
|
has already been traversed. Callback accepts two parameters,
|
|
136
127
|
the current node id and list of the return value of children,
|
|
@@ -155,7 +146,7 @@ def _traverse_dfs(topology: Topology, *, enter=None, leave=None, root=0):
|
|
|
155
146
|
children_map[pid].append(idx)
|
|
156
147
|
|
|
157
148
|
# manual dfs to avoid stack overflow in long branch
|
|
158
|
-
stack:
|
|
149
|
+
stack: list[tuple[int, bool]] = [(root, True)] # (idx, is_enter)
|
|
159
150
|
params = {root: None}
|
|
160
151
|
vals = {}
|
|
161
152
|
|
swcgeom/core/swc_utils/io.py
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import warnings
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import Callable, Iterable
|
|
6
|
+
from typing import Literal, Optional
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
8
9
|
import numpy.typing as npt
|
|
@@ -30,7 +31,7 @@ def read_swc(
|
|
|
30
31
|
*,
|
|
31
32
|
encoding: Literal["detect"] | str = "utf-8",
|
|
32
33
|
names: Optional[SWCNames] = None,
|
|
33
|
-
) ->
|
|
34
|
+
) -> tuple[pd.DataFrame, list[str]]:
|
|
34
35
|
"""Read swc file.
|
|
35
36
|
|
|
36
37
|
Parameters
|
|
@@ -55,7 +56,7 @@ def read_swc(
|
|
|
55
56
|
Returns
|
|
56
57
|
-------
|
|
57
58
|
df : ~pandas.DataFrame
|
|
58
|
-
comments :
|
|
59
|
+
comments : List of string
|
|
59
60
|
"""
|
|
60
61
|
|
|
61
62
|
names = get_names(names)
|
|
@@ -137,14 +138,14 @@ def parse_swc(
|
|
|
137
138
|
names: SWCNames,
|
|
138
139
|
extra_cols: Iterable[str] | None = None,
|
|
139
140
|
encoding: Literal["detect"] | str = "utf-8",
|
|
140
|
-
) ->
|
|
141
|
+
) -> tuple[pd.DataFrame, list[str]]:
|
|
141
142
|
"""Parse swc file.
|
|
142
143
|
|
|
143
144
|
Parameters
|
|
144
145
|
----------
|
|
145
146
|
fname : PathOrIO
|
|
146
147
|
names : SWCNames
|
|
147
|
-
extra_cols :
|
|
148
|
+
extra_cols : List of str, optional
|
|
148
149
|
encoding : str | 'detect', default `utf-8`
|
|
149
150
|
The name of the encoding used to decode the file. If is
|
|
150
151
|
`detect`, we will try to detect the character encoding.
|
|
@@ -152,7 +153,7 @@ def parse_swc(
|
|
|
152
153
|
Returns
|
|
153
154
|
-------
|
|
154
155
|
df : ~pandas.DataFrame
|
|
155
|
-
comments :
|
|
156
|
+
comments : List of string
|
|
156
157
|
"""
|
|
157
158
|
|
|
158
159
|
# pylint: disable=too-many-locals
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Methods ending with a underline imply an in-place transformation.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Literal, Optional
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
10
|
import numpy.typing as npt
|
|
@@ -111,7 +112,7 @@ def sort_nodes_(df: pd.DataFrame, *, names: Optional[SWCNames] = None) -> None:
|
|
|
111
112
|
df[names.id], df[names.pid] = new_ids, new_pids
|
|
112
113
|
|
|
113
114
|
|
|
114
|
-
def sort_nodes_impl(topology: Topology) ->
|
|
115
|
+
def sort_nodes_impl(topology: Topology) -> tuple[Topology, npt.NDArray[np.int32]]:
|
|
115
116
|
"""Sort the indices of neuron tree.
|
|
116
117
|
|
|
117
118
|
Returns
|
|
@@ -127,7 +128,7 @@ def sort_nodes_impl(topology: Topology) -> Tuple[Topology, npt.NDArray[np.int32]
|
|
|
127
128
|
new_pids = np.full_like(old_ids, fill_value=-3)
|
|
128
129
|
new_id = 0
|
|
129
130
|
first_root = old_ids[(old_pids == -1).argmax()]
|
|
130
|
-
s:
|
|
131
|
+
s: list[tuple[npt.NDArray[np.int32], int]] = [(first_root, -1)]
|
|
131
132
|
while len(s) != 0:
|
|
132
133
|
old_id, new_pid = s.pop()
|
|
133
134
|
id_map[new_id] = old_id
|
|
@@ -6,7 +6,7 @@ but in more cases, you can use the high-level methods provided in
|
|
|
6
6
|
high-level API.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import cast
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
import numpy.typing as npt
|
|
@@ -18,7 +18,7 @@ __all__ = ["REMOVAL", "to_sub_topology", "propagate_removal"]
|
|
|
18
18
|
REMOVAL = -2 # A marker in utils, place in the ids to mark it removal
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def to_sub_topology(sub: Topology) ->
|
|
21
|
+
def to_sub_topology(sub: Topology) -> tuple[Topology, npt.NDArray[np.int32]]:
|
|
22
22
|
"""Create sub tree from origin tree.
|
|
23
23
|
|
|
24
24
|
Mark the node to be removed, then use this method to get a child
|
swcgeom/core/tree.py
CHANGED
|
@@ -2,20 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
4
|
import os
|
|
5
|
-
import
|
|
6
|
-
from typing import
|
|
7
|
-
Callable,
|
|
8
|
-
Dict,
|
|
9
|
-
Iterable,
|
|
10
|
-
Iterator,
|
|
11
|
-
List,
|
|
12
|
-
Literal,
|
|
13
|
-
Optional,
|
|
14
|
-
Tuple,
|
|
15
|
-
TypeVar,
|
|
16
|
-
Union,
|
|
17
|
-
overload,
|
|
18
|
-
)
|
|
5
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
6
|
+
from typing import Literal, Optional, TypeVar, Union, overload
|
|
19
7
|
|
|
20
8
|
import numpy as np
|
|
21
9
|
import numpy.typing as npt
|
|
@@ -44,12 +32,12 @@ class Tree(DictSWC):
|
|
|
44
32
|
def parent(self) -> Union["Tree.Node", None]:
|
|
45
33
|
return Tree.Node(self.attach, self.pid) if self.pid != -1 else None
|
|
46
34
|
|
|
47
|
-
def children(self) ->
|
|
35
|
+
def children(self) -> list["Tree.Node"]:
|
|
48
36
|
children = self.attach.id()[self.attach.pid() == self.id]
|
|
49
37
|
return [Tree.Node(self.attach, idx) for idx in children]
|
|
50
38
|
|
|
51
39
|
def branch(self) -> "Tree.Branch":
|
|
52
|
-
ns:
|
|
40
|
+
ns: list["Tree.Node"] = [self]
|
|
53
41
|
while not ns[-1].is_bifurcation() and (p := ns[-1].parent()) is not None:
|
|
54
42
|
ns.append(p)
|
|
55
43
|
|
|
@@ -68,7 +56,7 @@ class Tree(DictSWC):
|
|
|
68
56
|
|
|
69
57
|
Parameters
|
|
70
58
|
----------
|
|
71
|
-
out_mapping : List of int or
|
|
59
|
+
out_mapping : List of int or dict[int, int], optional
|
|
72
60
|
Map from new id to old id.
|
|
73
61
|
"""
|
|
74
62
|
|
|
@@ -160,7 +148,7 @@ class Tree(DictSWC):
|
|
|
160
148
|
|
|
161
149
|
# fmt:off
|
|
162
150
|
@overload
|
|
163
|
-
def __getitem__(self, key: slice) ->
|
|
151
|
+
def __getitem__(self, key: slice) -> list[Node]: ...
|
|
164
152
|
@overload
|
|
165
153
|
def __getitem__(self, key: int) -> Node: ...
|
|
166
154
|
@overload
|
|
@@ -199,18 +187,18 @@ class Tree(DictSWC):
|
|
|
199
187
|
raise ValueError(f"no soma found in: {self.source}")
|
|
200
188
|
return n
|
|
201
189
|
|
|
202
|
-
def get_bifurcations(self) ->
|
|
190
|
+
def get_bifurcations(self) -> list[Node]:
|
|
203
191
|
"""Get all node of bifurcations."""
|
|
204
|
-
bifurcations:
|
|
192
|
+
bifurcations: list[int] = []
|
|
205
193
|
|
|
206
|
-
def collect_bifurcations(n: Tree.Node, children:
|
|
194
|
+
def collect_bifurcations(n: Tree.Node, children: list[None]) -> None:
|
|
207
195
|
if len(children) > 1:
|
|
208
196
|
bifurcations.append(n.id)
|
|
209
197
|
|
|
210
198
|
self.traverse(leave=collect_bifurcations)
|
|
211
199
|
return [self.node(i) for i in bifurcations]
|
|
212
200
|
|
|
213
|
-
def get_tips(self) ->
|
|
201
|
+
def get_tips(self) -> list[Node]:
|
|
214
202
|
"""Get all node of tips."""
|
|
215
203
|
tip_ids = np.setdiff1d(self.id(), self.pid(), assume_unique=True)
|
|
216
204
|
return [self.node(i) for i in tip_ids]
|
|
@@ -221,16 +209,16 @@ class Tree(DictSWC):
|
|
|
221
209
|
def get_segments(self) -> Compartments[Compartment]: # Alias
|
|
222
210
|
return self.get_compartments()
|
|
223
211
|
|
|
224
|
-
def get_branches(self) ->
|
|
212
|
+
def get_branches(self) -> list[Branch]:
|
|
225
213
|
def collect_branches(
|
|
226
|
-
node: "Tree.Node", pre:
|
|
227
|
-
) ->
|
|
214
|
+
node: "Tree.Node", pre: list[tuple[list[Tree.Branch], list[int]]]
|
|
215
|
+
) -> tuple[list[Tree.Branch], list[int]]:
|
|
228
216
|
if len(pre) == 1:
|
|
229
217
|
branches, child = pre[0]
|
|
230
218
|
child.append(node.id)
|
|
231
219
|
return branches, child
|
|
232
220
|
|
|
233
|
-
branches:
|
|
221
|
+
branches: list[Tree.Branch] = []
|
|
234
222
|
|
|
235
223
|
for sub_branches, child in pre:
|
|
236
224
|
child.append(node.id)
|
|
@@ -244,19 +232,19 @@ class Tree(DictSWC):
|
|
|
244
232
|
branches, _ = self.traverse(leave=collect_branches)
|
|
245
233
|
return branches
|
|
246
234
|
|
|
247
|
-
def get_paths(self) ->
|
|
235
|
+
def get_paths(self) -> list[Path]:
|
|
248
236
|
"""Get all path from soma to tips."""
|
|
249
|
-
path_dic:
|
|
237
|
+
path_dic: dict[int, list[int]] = {}
|
|
250
238
|
|
|
251
|
-
def assign_path(n: Tree.Node, pre_path:
|
|
239
|
+
def assign_path(n: Tree.Node, pre_path: list[int] | None) -> list[int]:
|
|
252
240
|
path = [] if pre_path is None else pre_path.copy()
|
|
253
241
|
path.append(n.id)
|
|
254
242
|
path_dic[n.id] = path
|
|
255
243
|
return path
|
|
256
244
|
|
|
257
245
|
def collect_path(
|
|
258
|
-
n: Tree.Node, children:
|
|
259
|
-
) ->
|
|
246
|
+
n: Tree.Node, children: list[list[list[int]]]
|
|
247
|
+
) -> list[list[int]]:
|
|
260
248
|
if len(children) == 0:
|
|
261
249
|
return [path_dic[n.id]]
|
|
262
250
|
|
|
@@ -283,7 +271,7 @@ class Tree(DictSWC):
|
|
|
283
271
|
@overload
|
|
284
272
|
def traverse(self, *,
|
|
285
273
|
enter: Optional[Callable[[Node, T | None], T]] = ...,
|
|
286
|
-
leave: Callable[[Node,
|
|
274
|
+
leave: Callable[[Node, list[K]], K],
|
|
287
275
|
root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> K: ...
|
|
288
276
|
# fmt: on
|
|
289
277
|
|
|
@@ -293,7 +281,7 @@ class Tree(DictSWC):
|
|
|
293
281
|
Parameters
|
|
294
282
|
----------
|
|
295
283
|
enter : (n: Node, parent: T | None) => T, optional
|
|
296
|
-
leave : (n: Node, children:
|
|
284
|
+
leave : (n: Node, children: list[T]) => T, optional
|
|
297
285
|
|
|
298
286
|
See Also
|
|
299
287
|
--------
|
|
@@ -355,7 +343,7 @@ class Tree(DictSWC):
|
|
|
355
343
|
|
|
356
344
|
@classmethod
|
|
357
345
|
def from_eswc(
|
|
358
|
-
cls, swc_file: str, extra_cols: Optional[
|
|
346
|
+
cls, swc_file: str, extra_cols: Optional[list[str]] = None, **kwargs
|
|
359
347
|
) -> "Tree":
|
|
360
348
|
"""Read neuron tree from eswc file.
|
|
361
349
|
|
swcgeom/core/tree_utils.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""SWC util wrapper for tree."""
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Callable, Iterable
|
|
5
|
+
from typing import Optional, TypeVar, overload
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
|
|
@@ -50,9 +51,9 @@ def sort_tree(tree: Tree) -> Tree:
|
|
|
50
51
|
|
|
51
52
|
# fmt:off
|
|
52
53
|
@overload
|
|
53
|
-
def cut_tree(tree: Tree, *, enter: Callable[[Tree.Node, T | None],
|
|
54
|
+
def cut_tree(tree: Tree, *, enter: Callable[[Tree.Node, T | None], tuple[T, bool]]) -> Tree: ...
|
|
54
55
|
@overload
|
|
55
|
-
def cut_tree(tree: Tree, *, leave: Callable[[Tree.Node,
|
|
56
|
+
def cut_tree(tree: Tree, *, leave: Callable[[Tree.Node, list[K]], tuple[K, bool]]) -> Tree: ...
|
|
56
57
|
# fmt:on
|
|
57
58
|
def cut_tree(tree: Tree, *, enter=None, leave=None):
|
|
58
59
|
"""Traverse and cut the tree.
|
|
@@ -60,11 +61,11 @@ def cut_tree(tree: Tree, *, enter=None, leave=None):
|
|
|
60
61
|
Returning a `True` can delete the current node and its children.
|
|
61
62
|
"""
|
|
62
63
|
|
|
63
|
-
removals:
|
|
64
|
+
removals: list[int] = []
|
|
64
65
|
|
|
65
66
|
if enter:
|
|
66
67
|
|
|
67
|
-
def _enter(n: Tree.Node, parent:
|
|
68
|
+
def _enter(n: Tree.Node, parent: tuple[T, bool] | None) -> tuple[T, bool]:
|
|
68
69
|
if parent is not None and parent[1]:
|
|
69
70
|
removals.append(n.id)
|
|
70
71
|
return parent
|
|
@@ -79,7 +80,7 @@ def cut_tree(tree: Tree, *, enter=None, leave=None):
|
|
|
79
80
|
|
|
80
81
|
elif leave:
|
|
81
82
|
|
|
82
|
-
def _leave(n: Tree.Node, children:
|
|
83
|
+
def _leave(n: Tree.Node, children: list[K]) -> K:
|
|
83
84
|
res, removal = leave(n, children)
|
|
84
85
|
if removal:
|
|
85
86
|
removals.append(n.id)
|
|
@@ -94,7 +95,7 @@ def cut_tree(tree: Tree, *, enter=None, leave=None):
|
|
|
94
95
|
return to_subtree(tree, removals)
|
|
95
96
|
|
|
96
97
|
|
|
97
|
-
def to_sub_tree(swc_like: SWCLike, sub: Topology) ->
|
|
98
|
+
def to_sub_tree(swc_like: SWCLike, sub: Topology) -> tuple[Tree, dict[int, int]]:
|
|
98
99
|
"""Create subtree from origin tree.
|
|
99
100
|
|
|
100
101
|
You can directly mark the node for removal, and we will remove it,
|
|
@@ -107,7 +108,7 @@ def to_sub_tree(swc_like: SWCLike, sub: Topology) -> Tuple[Tree, Dict[int, int]]
|
|
|
107
108
|
Returns
|
|
108
109
|
-------
|
|
109
110
|
tree : Tree
|
|
110
|
-
id_map :
|
|
111
|
+
id_map : dict[int, int]
|
|
111
112
|
"""
|
|
112
113
|
|
|
113
114
|
warnings.warn(
|
|
@@ -145,7 +146,7 @@ def to_subtree(
|
|
|
145
146
|
swc_like : SWCLike
|
|
146
147
|
removals : List of int
|
|
147
148
|
A list of id of nodes to be removed.
|
|
148
|
-
out_mapping: List of int or
|
|
149
|
+
out_mapping: List of int or dict[int, int], optional
|
|
149
150
|
Map new id to old id.
|
|
150
151
|
"""
|
|
151
152
|
|
|
@@ -170,7 +171,7 @@ def get_subtree(
|
|
|
170
171
|
swc_like : SWCLike
|
|
171
172
|
n : int
|
|
172
173
|
Id of the root of the subtree.
|
|
173
|
-
out_mapping: List of int or
|
|
174
|
+
out_mapping: List of int or dict[int, int], optional
|
|
174
175
|
Map new id to old id.
|
|
175
176
|
"""
|
|
176
177
|
|