swcgeom 0.18.1__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/__init__.py +12 -1
- swcgeom/analysis/__init__.py +6 -6
- 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 +12 -13
- 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 +10 -12
- 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/__init__.py +2 -2
- 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/__init__.py +13 -13
- 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/__init__.py +12 -12
- 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.1.dist-info → swcgeom-0.19.0.dist-info}/METADATA +7 -6
- swcgeom-0.19.0.dist-info/RECORD +67 -0
- {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/WHEEL +1 -1
- swcgeom/_version.py +0 -16
- swcgeom-0.18.1.dist-info/RECORD +0 -68
- {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info/licenses}/LICENSE +0 -0
- {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/top_level.txt +0 -0
swcgeom/core/swc_utils/io.py
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import re
|
|
19
19
|
import warnings
|
|
20
20
|
from collections.abc import Callable, Iterable
|
|
21
|
-
from typing import Literal
|
|
21
|
+
from typing import Literal
|
|
22
22
|
|
|
23
23
|
import numpy as np
|
|
24
24
|
import numpy.typing as npt
|
|
@@ -39,41 +39,33 @@ __all__ = ["read_swc", "to_swc"]
|
|
|
39
39
|
|
|
40
40
|
def read_swc(
|
|
41
41
|
swc_file: PathOrIO,
|
|
42
|
-
extra_cols:
|
|
42
|
+
extra_cols: Iterable[str] | None = None,
|
|
43
43
|
fix_roots: Literal["somas", "nearest", False] = False,
|
|
44
44
|
sort_nodes: bool = False,
|
|
45
45
|
reset_index: bool = True,
|
|
46
46
|
*,
|
|
47
47
|
encoding: Literal["detect"] | str = "utf-8",
|
|
48
|
-
names:
|
|
48
|
+
names: SWCNames | None = None,
|
|
49
49
|
) -> tuple[pd.DataFrame, list[str]]:
|
|
50
50
|
"""Read swc file.
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
`detect`, we will try to detect the character encoding.
|
|
69
|
-
names : SWCNames, optional
|
|
70
|
-
|
|
71
|
-
Returns
|
|
72
|
-
-------
|
|
73
|
-
df : ~pandas.DataFrame
|
|
74
|
-
comments : List of string
|
|
52
|
+
NOTE: the id should be consecutively incremented.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
extra_cols: Read more cols in swc file.
|
|
56
|
+
fix_roots: Fix multiple roots.
|
|
57
|
+
sort_nodes: Sort the indices of neuron tree.
|
|
58
|
+
After sorting the nodes, the index for each parent are always less than
|
|
59
|
+
that of its children.
|
|
60
|
+
reset_index: Reset node index to start with zero.
|
|
61
|
+
DO NOT set to false if you are not sure what will happened.
|
|
62
|
+
encoding: The name of the encoding used to decode the file.
|
|
63
|
+
If is `detect`, we will try to detect the character encoding.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
df: ~pandas.DataFrame
|
|
67
|
+
comments: List of string
|
|
75
68
|
"""
|
|
76
|
-
|
|
77
69
|
names = get_names(names)
|
|
78
70
|
df, comments = parse_swc(
|
|
79
71
|
swc_file, names=names, extra_cols=extra_cols, encoding=encoding
|
|
@@ -110,10 +102,10 @@ def read_swc(
|
|
|
110
102
|
def to_swc(
|
|
111
103
|
get_ndata: Callable[[str], npt.NDArray],
|
|
112
104
|
*,
|
|
113
|
-
extra_cols:
|
|
105
|
+
extra_cols: Iterable[str] | None = None,
|
|
114
106
|
id_offset: int = 1,
|
|
115
|
-
comments:
|
|
116
|
-
names:
|
|
107
|
+
comments: Iterable[str] | None = None,
|
|
108
|
+
names: SWCNames | None = None,
|
|
117
109
|
) -> Iterable[str]:
|
|
118
110
|
"""Convert to swc format."""
|
|
119
111
|
|
|
@@ -156,21 +148,14 @@ def parse_swc(
|
|
|
156
148
|
) -> tuple[pd.DataFrame, list[str]]:
|
|
157
149
|
"""Parse swc file.
|
|
158
150
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
names : SWCNames
|
|
163
|
-
extra_cols : List of str, optional
|
|
164
|
-
encoding : str | 'detect', default `utf-8`
|
|
165
|
-
The name of the encoding used to decode the file. If is
|
|
166
|
-
`detect`, we will try to detect the character encoding.
|
|
167
|
-
|
|
168
|
-
Returns
|
|
169
|
-
-------
|
|
170
|
-
df : ~pandas.DataFrame
|
|
171
|
-
comments : List of string
|
|
172
|
-
"""
|
|
151
|
+
Args:
|
|
152
|
+
encoding: The name of the encoding used to decode the file.
|
|
153
|
+
If is `detect`, we will try to detect the character encoding.
|
|
173
154
|
|
|
155
|
+
Returns:
|
|
156
|
+
df: ~pandas.DataFrame
|
|
157
|
+
comments: List of string
|
|
158
|
+
"""
|
|
174
159
|
# pylint: disable=too-many-locals
|
|
175
160
|
extras = list(extra_cols) if extra_cols else []
|
|
176
161
|
|
|
@@ -208,7 +193,7 @@ def parse_swc(
|
|
|
208
193
|
if (match := re_swc.search(line)) is not None:
|
|
209
194
|
if flag and match.group(last_group):
|
|
210
195
|
warnings.warn(
|
|
211
|
-
f"some fields are ignored in row {i+1} of `{fname}`"
|
|
196
|
+
f"some fields are ignored in row {i + 1} of `{fname}`"
|
|
212
197
|
)
|
|
213
198
|
flag = False
|
|
214
199
|
|
|
@@ -219,10 +204,10 @@ def parse_swc(
|
|
|
219
204
|
if not comment.startswith(ignored_comment):
|
|
220
205
|
comments.append(comment)
|
|
221
206
|
elif not line.isspace():
|
|
222
|
-
raise ValueError(f"invalid row {i+1} in `{fname}`")
|
|
207
|
+
raise ValueError(f"invalid row {i + 1} in `{fname}`")
|
|
223
208
|
except UnicodeDecodeError as e:
|
|
224
209
|
raise ValueError(
|
|
225
|
-
|
|
210
|
+
"decode failed, try to enable auto detect `encoding='detect'`"
|
|
226
211
|
) from e
|
|
227
212
|
|
|
228
213
|
df = pd.DataFrame.from_dict(dict(zip(keys, vals)))
|
|
@@ -19,7 +19,7 @@ Methods ending with a underline imply an in-place transformation.
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
from collections.abc import Callable
|
|
22
|
-
from typing import Literal
|
|
22
|
+
from typing import Literal
|
|
23
23
|
|
|
24
24
|
import numpy as np
|
|
25
25
|
import numpy.typing as npt
|
|
@@ -44,7 +44,7 @@ def mark_roots_as_somas(
|
|
|
44
44
|
df: pd.DataFrame,
|
|
45
45
|
update_type: int | Literal[False] = 1,
|
|
46
46
|
*,
|
|
47
|
-
names:
|
|
47
|
+
names: SWCNames | None = None,
|
|
48
48
|
) -> pd.DataFrame:
|
|
49
49
|
return _copy_and_apply(
|
|
50
50
|
mark_roots_as_somas_, df, update_type=update_type, names=names
|
|
@@ -55,7 +55,7 @@ def mark_roots_as_somas_(
|
|
|
55
55
|
df: pd.DataFrame,
|
|
56
56
|
update_type: int | Literal[False] = 1,
|
|
57
57
|
*,
|
|
58
|
-
names:
|
|
58
|
+
names: SWCNames | None = None,
|
|
59
59
|
) -> None:
|
|
60
60
|
"""Merge multiple roots in swc.
|
|
61
61
|
|
|
@@ -72,14 +72,12 @@ def mark_roots_as_somas_(
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def link_roots_to_nearest(
|
|
75
|
-
df: pd.DataFrame, *, names:
|
|
75
|
+
df: pd.DataFrame, *, names: SWCNames | None = None
|
|
76
76
|
) -> pd.DataFrame:
|
|
77
77
|
return _copy_and_apply(link_roots_to_nearest_, df, names=names)
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def link_roots_to_nearest_(
|
|
81
|
-
df: pd.DataFrame, *, names: Optional[SWCNames] = None
|
|
82
|
-
) -> None:
|
|
80
|
+
def link_roots_to_nearest_(df: pd.DataFrame, *, names: SWCNames | None = None) -> None:
|
|
83
81
|
"""Merge multiple roots in swc.
|
|
84
82
|
|
|
85
83
|
The first root are reserved, and the others was.
|
|
@@ -94,29 +92,27 @@ def link_roots_to_nearest_(
|
|
|
94
92
|
subtree = dsu == dsu[i] # type: ignore
|
|
95
93
|
dis = np.where(subtree, np.inf, dis) # avoid link to same tree
|
|
96
94
|
dsu = np.where(subtree, dsu[dis.argmin()], dsu) # merge set
|
|
97
|
-
df.loc[i, names.pid] = df[names.id].iloc[dis.argmin()]
|
|
95
|
+
df.loc[i, names.pid] = df[names.id].iloc[dis.argmin()]
|
|
98
96
|
|
|
99
97
|
|
|
100
|
-
def sort_nodes(df: pd.DataFrame, *, names:
|
|
98
|
+
def sort_nodes(df: pd.DataFrame, *, names: SWCNames | None = None) -> pd.DataFrame:
|
|
101
99
|
"""Sort the indices of neuron tree.
|
|
102
100
|
|
|
103
101
|
The index for parent are always less than children.
|
|
104
102
|
|
|
105
|
-
See Also
|
|
106
|
-
|
|
107
|
-
~.core.swc_utils.checker.is_sorted
|
|
103
|
+
See Also:
|
|
104
|
+
~.core.swc_utils.checker.is_sorted
|
|
108
105
|
"""
|
|
109
106
|
return _copy_and_apply(sort_nodes_, df, names=names)
|
|
110
107
|
|
|
111
108
|
|
|
112
|
-
def sort_nodes_(df: pd.DataFrame, *, names:
|
|
109
|
+
def sort_nodes_(df: pd.DataFrame, *, names: SWCNames | None = None) -> None:
|
|
113
110
|
"""Sort the indices of neuron tree.
|
|
114
111
|
|
|
115
112
|
The index for parent are always less than children.
|
|
116
113
|
|
|
117
|
-
See Also
|
|
118
|
-
|
|
119
|
-
~.core.swc_utils.checker.is_sorted
|
|
114
|
+
See Also:
|
|
115
|
+
~.core.swc_utils.checker.is_sorted
|
|
120
116
|
"""
|
|
121
117
|
names = get_names(names)
|
|
122
118
|
ids, pids = df[names.id].to_numpy(), df[names.pid].to_numpy()
|
|
@@ -130,11 +126,9 @@ def sort_nodes_(df: pd.DataFrame, *, names: Optional[SWCNames] = None) -> None:
|
|
|
130
126
|
def sort_nodes_impl(topology: Topology) -> tuple[Topology, npt.NDArray[np.int32]]:
|
|
131
127
|
"""Sort the indices of neuron tree.
|
|
132
128
|
|
|
133
|
-
Returns
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
id_map : List of int
|
|
137
|
-
Map from new id to original id.
|
|
129
|
+
Returns:
|
|
130
|
+
new_topology: Topology
|
|
131
|
+
id_map: Map from new id to original id.
|
|
138
132
|
"""
|
|
139
133
|
old_ids, old_pids = topology
|
|
140
134
|
assert np.count_nonzero(old_pids == -1) == 1, "should be single root"
|
|
@@ -157,12 +151,12 @@ def sort_nodes_impl(topology: Topology) -> tuple[Topology, npt.NDArray[np.int32]
|
|
|
157
151
|
return (new_ids, new_pids), indices
|
|
158
152
|
|
|
159
153
|
|
|
160
|
-
def reset_index(df: pd.DataFrame, *, names:
|
|
154
|
+
def reset_index(df: pd.DataFrame, *, names: SWCNames | None = None) -> pd.DataFrame:
|
|
161
155
|
"""Reset node index to start with zero."""
|
|
162
156
|
return _copy_and_apply(reset_index_, df, names=names)
|
|
163
157
|
|
|
164
158
|
|
|
165
|
-
def reset_index_(df: pd.DataFrame, *, names:
|
|
159
|
+
def reset_index_(df: pd.DataFrame, *, names: SWCNames | None = None) -> None:
|
|
166
160
|
"""Reset node index to start with zero."""
|
|
167
161
|
names = get_names(names)
|
|
168
162
|
roots = df[names.pid] == -1
|
|
@@ -15,10 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
"""Cut subtree.
|
|
17
17
|
|
|
18
|
-
This module provides a series of low-level topological subtree methods,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
high-level API.
|
|
18
|
+
This module provides a series of low-level topological subtree methods, but in more
|
|
19
|
+
cases, you can use the high-level methods provided in `tree_utils`, which wrap the
|
|
20
|
+
methods in this module and provide a high-level API.
|
|
22
21
|
"""
|
|
23
22
|
|
|
24
23
|
from typing import cast
|
|
@@ -36,22 +35,17 @@ REMOVAL = -2 # A marker in utils, place in the ids to mark it removal
|
|
|
36
35
|
def to_sub_topology(sub: Topology) -> tuple[Topology, npt.NDArray[np.int32]]:
|
|
37
36
|
"""Create sub tree from origin tree.
|
|
38
37
|
|
|
39
|
-
Mark the node to be removed, then use this method to get a child
|
|
40
|
-
structure.
|
|
38
|
+
Mark the node to be removed, then use this method to get a child structure.
|
|
41
39
|
|
|
42
|
-
Returns
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
mapping : List of int
|
|
46
|
-
Map from new id to old id.
|
|
40
|
+
Returns:
|
|
41
|
+
sub_topology: Topology
|
|
42
|
+
mapping: Map from new id to old id.
|
|
47
43
|
|
|
48
|
-
See Also
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
to mark all child nodes.
|
|
44
|
+
See Also:
|
|
45
|
+
propagate_removal:
|
|
46
|
+
If the node you remove is not a leaf node, you need to use it
|
|
47
|
+
to mark all child nodes.
|
|
53
48
|
"""
|
|
54
|
-
|
|
55
49
|
sub_id = np.array(sub[0], dtype=np.int32)
|
|
56
50
|
sub_pid = np.array(sub[1], dtype=np.int32)
|
|
57
51
|
|
|
@@ -69,9 +63,8 @@ def to_sub_topology(sub: Topology) -> tuple[Topology, npt.NDArray[np.int32]]:
|
|
|
69
63
|
def propagate_removal(topology: Topology) -> Topology:
|
|
70
64
|
"""Mark all children when parent is marked as removed.
|
|
71
65
|
|
|
72
|
-
Returns
|
|
73
|
-
|
|
74
|
-
new_topology : Topology
|
|
66
|
+
Returns:
|
|
67
|
+
new_topology: Topology
|
|
75
68
|
"""
|
|
76
69
|
|
|
77
70
|
new_ids, pids = topology
|
swcgeom/core/tree.py
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import itertools
|
|
19
19
|
import os
|
|
20
20
|
from collections.abc import Callable, Iterable, Iterator
|
|
21
|
-
from typing import Literal,
|
|
21
|
+
from typing import Literal, TypeVar, Union, overload
|
|
22
22
|
|
|
23
23
|
import numpy as np
|
|
24
24
|
import numpy.typing as npt
|
|
@@ -36,7 +36,8 @@ from swcgeom.utils import PathOrIO, padding1d
|
|
|
36
36
|
|
|
37
37
|
__all__ = ["Tree"]
|
|
38
38
|
|
|
39
|
-
T
|
|
39
|
+
T = TypeVar("T")
|
|
40
|
+
K = TypeVar("K")
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
class Tree(DictSWC):
|
|
@@ -67,13 +68,11 @@ class Tree(DictSWC):
|
|
|
67
68
|
"""The end-to-end straight-line distance to soma."""
|
|
68
69
|
return self.distance(self.attach.soma())
|
|
69
70
|
|
|
70
|
-
def subtree(self, *, out_mapping:
|
|
71
|
+
def subtree(self, *, out_mapping: Mapping | None = None) -> "Tree":
|
|
71
72
|
"""Get subtree from node.
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
out_mapping : List of int or dict[int, int], optional
|
|
76
|
-
Map from new id to old id.
|
|
74
|
+
Args:
|
|
75
|
+
out_mapping: Map from new id to old id.
|
|
77
76
|
"""
|
|
78
77
|
|
|
79
78
|
n_nodes, ndata, source, names = get_subtree_impl(
|
|
@@ -87,22 +86,27 @@ class Tree(DictSWC):
|
|
|
87
86
|
def is_soma(self) -> bool: # TODO: support multi soma, e.g. 3 points
|
|
88
87
|
return self.type == self.attach.types.soma and self.is_root()
|
|
89
88
|
|
|
90
|
-
# fmt: off
|
|
91
89
|
@overload
|
|
92
|
-
def traverse(
|
|
90
|
+
def traverse(
|
|
91
|
+
self, *, enter: Callable[[Node, T | None], T], mode: Literal["dfs"] = ...
|
|
92
|
+
) -> None: ...
|
|
93
93
|
@overload
|
|
94
|
-
def traverse(
|
|
94
|
+
def traverse(
|
|
95
|
+
self, *, leave: Callable[[Node, list[K]], K], mode: Literal["dfs"] = ...
|
|
96
|
+
) -> K: ...
|
|
95
97
|
@overload
|
|
96
|
-
def traverse(
|
|
97
|
-
|
|
98
|
+
def traverse(
|
|
99
|
+
self,
|
|
100
|
+
*,
|
|
101
|
+
enter: Callable[[Node, T | None], T],
|
|
102
|
+
leave: Callable[[Node, list[K]], K],
|
|
103
|
+
mode: Literal["dfs"] = ...,
|
|
98
104
|
) -> K: ...
|
|
99
|
-
# fmt: on
|
|
100
105
|
def traverse(self, **kwargs): # type: ignore
|
|
101
106
|
"""Traverse from node.
|
|
102
107
|
|
|
103
|
-
See Also
|
|
104
|
-
|
|
105
|
-
~Tree.traverse
|
|
108
|
+
See Also:
|
|
109
|
+
~Tree.traverse
|
|
106
110
|
"""
|
|
107
111
|
return self.attach.traverse(root=self.idx, **kwargs)
|
|
108
112
|
|
|
@@ -125,8 +129,8 @@ class Tree(DictSWC):
|
|
|
125
129
|
n_nodes: int,
|
|
126
130
|
*,
|
|
127
131
|
source: str = "",
|
|
128
|
-
comments:
|
|
129
|
-
names:
|
|
132
|
+
comments: Iterable[str] | None = None,
|
|
133
|
+
names: SWCNames | None = None,
|
|
130
134
|
**kwargs: npt.NDArray,
|
|
131
135
|
) -> None:
|
|
132
136
|
names = get_names(names)
|
|
@@ -162,14 +166,12 @@ class Tree(DictSWC):
|
|
|
162
166
|
n_nodes, n_edges = self.number_of_nodes(), self.number_of_edges()
|
|
163
167
|
return f"Neuron Tree with {n_nodes} nodes and {n_edges} edges"
|
|
164
168
|
|
|
165
|
-
# fmt:off
|
|
166
169
|
@overload
|
|
167
170
|
def __getitem__(self, key: slice) -> list[Node]: ...
|
|
168
171
|
@overload
|
|
169
172
|
def __getitem__(self, key: int) -> Node: ...
|
|
170
173
|
@overload
|
|
171
174
|
def __getitem__(self, key: str) -> npt.NDArray: ...
|
|
172
|
-
# fmt:on
|
|
173
175
|
def __getitem__(self, key):
|
|
174
176
|
if isinstance(key, slice):
|
|
175
177
|
return [self.node(i) for i in range(*key.indices(len(self)))]
|
|
@@ -218,11 +220,10 @@ class Tree(DictSWC):
|
|
|
218
220
|
def get_bifurcations(self) -> list[Node]:
|
|
219
221
|
"""Get all node of furcations.
|
|
220
222
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
will be change to raise an error in the future.
|
|
223
|
+
.. deprecated:: 0.17.2
|
|
224
|
+
Deprecated due to the wrong spelling of furcation. For now, it is just an
|
|
225
|
+
alias of `get_furcations` and raise a warning. It will be change to raise
|
|
226
|
+
an error in the future.
|
|
226
227
|
"""
|
|
227
228
|
return self.get_furcations()
|
|
228
229
|
|
|
@@ -291,29 +292,40 @@ class Tree(DictSWC):
|
|
|
291
292
|
children = self.soma(type_check).children()
|
|
292
293
|
return (n.subtree() for n in children if n.type in types)
|
|
293
294
|
|
|
294
|
-
# fmt: off
|
|
295
295
|
@overload
|
|
296
|
-
def traverse(
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
def traverse(
|
|
297
|
+
self,
|
|
298
|
+
*,
|
|
299
|
+
enter: Callable[[Node, T | None], T],
|
|
300
|
+
root: int | np.integer = ...,
|
|
301
|
+
mode: Literal["dfs"] = ...,
|
|
302
|
+
) -> None: ...
|
|
299
303
|
@overload
|
|
300
|
-
def traverse(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
def traverse(
|
|
305
|
+
self,
|
|
306
|
+
*,
|
|
307
|
+
leave: Callable[[Node, list[K]], K],
|
|
308
|
+
root: int | np.integer = ...,
|
|
309
|
+
mode: Literal["dfs"] = ...,
|
|
310
|
+
) -> K: ...
|
|
311
|
+
@overload
|
|
312
|
+
def traverse(
|
|
313
|
+
self,
|
|
314
|
+
*,
|
|
315
|
+
enter: Callable[[Node, T | None], T],
|
|
316
|
+
leave: Callable[[Node, list[K]], K],
|
|
317
|
+
root: int | np.integer = ...,
|
|
318
|
+
mode: Literal["dfs"] = ...,
|
|
319
|
+
) -> K: ...
|
|
306
320
|
def traverse(self, *, enter=None, leave=None, **kwargs):
|
|
307
321
|
"""Traverse nodes.
|
|
308
322
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
leave : (n: Node, children: list[T]) => T, optional
|
|
323
|
+
Args:
|
|
324
|
+
enter: (n: Node, parent: T | None) => T
|
|
325
|
+
leave: (n: Node, children: list[T]) => T
|
|
313
326
|
|
|
314
|
-
See Also
|
|
315
|
-
|
|
316
|
-
~swc_utils.traverse
|
|
327
|
+
See Also:
|
|
328
|
+
~swc_utils.traverse
|
|
317
329
|
"""
|
|
318
330
|
|
|
319
331
|
def wrap(fn) -> Callable | None:
|
|
@@ -338,8 +350,8 @@ class Tree(DictSWC):
|
|
|
338
350
|
df: pd.DataFrame,
|
|
339
351
|
source: str = "",
|
|
340
352
|
*,
|
|
341
|
-
comments:
|
|
342
|
-
names:
|
|
353
|
+
comments: Iterable[str] | None = None,
|
|
354
|
+
names: SWCNames | None = None,
|
|
343
355
|
) -> "Tree":
|
|
344
356
|
"""Read neuron tree from data frame."""
|
|
345
357
|
names = get_names(names)
|
|
@@ -356,9 +368,8 @@ class Tree(DictSWC):
|
|
|
356
368
|
def from_swc(cls, swc_file: PathOrIO, **kwargs) -> "Tree":
|
|
357
369
|
"""Read neuron tree from swc file.
|
|
358
370
|
|
|
359
|
-
See Also
|
|
360
|
-
|
|
361
|
-
~swcgeom.core.swc_utils.read_swc
|
|
371
|
+
See Also:
|
|
372
|
+
~swcgeom.core.swc_utils.read_swc
|
|
362
373
|
"""
|
|
363
374
|
|
|
364
375
|
try:
|
|
@@ -371,13 +382,12 @@ class Tree(DictSWC):
|
|
|
371
382
|
|
|
372
383
|
@classmethod
|
|
373
384
|
def from_eswc(
|
|
374
|
-
cls, swc_file: str, extra_cols:
|
|
385
|
+
cls, swc_file: str, extra_cols: list[str] | None = None, **kwargs
|
|
375
386
|
) -> "Tree":
|
|
376
387
|
"""Read neuron tree from eswc file.
|
|
377
388
|
|
|
378
|
-
See Also
|
|
379
|
-
|
|
380
|
-
~swcgeom.Tree.from_swc
|
|
389
|
+
See Also:
|
|
390
|
+
~swcgeom.Tree.from_swc
|
|
381
391
|
"""
|
|
382
392
|
extra_cols = extra_cols or []
|
|
383
393
|
extra_cols.extend(k for k, t in eswc_cols)
|
swcgeom/core/tree_utils.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import warnings
|
|
19
19
|
from collections.abc import Callable, Iterable
|
|
20
|
-
from typing import
|
|
20
|
+
from typing import TypeVar, overload
|
|
21
21
|
|
|
22
22
|
import numpy as np
|
|
23
23
|
from typing_extensions import deprecated
|
|
@@ -46,7 +46,8 @@ __all__ = [
|
|
|
46
46
|
"cat_tree",
|
|
47
47
|
]
|
|
48
48
|
|
|
49
|
-
T
|
|
49
|
+
T = TypeVar("T")
|
|
50
|
+
K = TypeVar("K")
|
|
50
51
|
EPS = 1e-5
|
|
51
52
|
|
|
52
53
|
|
|
@@ -58,19 +59,20 @@ def is_binary_tree(tree: Tree, exclude_soma: bool = True) -> bool:
|
|
|
58
59
|
def sort_tree(tree: Tree) -> Tree:
|
|
59
60
|
"""Sort the indices of neuron tree.
|
|
60
61
|
|
|
61
|
-
See Also
|
|
62
|
-
|
|
63
|
-
~.core.swc_utils.sort_nodes
|
|
62
|
+
See Also:
|
|
63
|
+
~.core.swc_utils.sort_nodes
|
|
64
64
|
"""
|
|
65
65
|
return _sort_tree(tree.copy())
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
# fmt:off
|
|
69
68
|
@overload
|
|
70
|
-
def cut_tree(
|
|
69
|
+
def cut_tree(
|
|
70
|
+
tree: Tree, *, enter: Callable[[Tree.Node, T | None], tuple[T, bool]]
|
|
71
|
+
) -> Tree: ...
|
|
71
72
|
@overload
|
|
72
|
-
def cut_tree(
|
|
73
|
-
|
|
73
|
+
def cut_tree(
|
|
74
|
+
tree: Tree, *, leave: Callable[[Tree.Node, list[K]], tuple[K, bool]]
|
|
75
|
+
) -> Tree: ...
|
|
74
76
|
def cut_tree(tree: Tree, *, enter=None, leave=None):
|
|
75
77
|
"""Traverse and cut the tree.
|
|
76
78
|
|
|
@@ -123,9 +125,8 @@ def to_sub_tree(swc_like: SWCLike, sub: Topology) -> tuple[Tree, dict[int, int]]
|
|
|
123
125
|
Use :meth:`to_subtree` instead.
|
|
124
126
|
|
|
125
127
|
Returns
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
id_map : dict[int, int]
|
|
128
|
+
tree: Tree
|
|
129
|
+
id_map: dict[int, int]
|
|
129
130
|
"""
|
|
130
131
|
|
|
131
132
|
sub = propagate_removal(sub)
|
|
@@ -147,17 +148,14 @@ def to_subtree(
|
|
|
147
148
|
swc_like: SWCLike,
|
|
148
149
|
removals: Iterable[int],
|
|
149
150
|
*,
|
|
150
|
-
out_mapping:
|
|
151
|
+
out_mapping: Mapping | None = None,
|
|
151
152
|
) -> Tree:
|
|
152
153
|
"""Create subtree from origin tree.
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
A list of id of nodes to be removed.
|
|
159
|
-
out_mapping: List of int or dict[int, int], optional
|
|
160
|
-
Map new id to old id.
|
|
155
|
+
Args:
|
|
156
|
+
swc_like: SWCLike
|
|
157
|
+
removals: A list of id of nodes to be removed.
|
|
158
|
+
out_mapping: Map new id to old id.
|
|
161
159
|
"""
|
|
162
160
|
|
|
163
161
|
new_ids = swc_like.id().copy()
|
|
@@ -172,17 +170,14 @@ def to_subtree(
|
|
|
172
170
|
|
|
173
171
|
|
|
174
172
|
def get_subtree(
|
|
175
|
-
swc_like: SWCLike, n: int, *, out_mapping:
|
|
173
|
+
swc_like: SWCLike, n: int, *, out_mapping: Mapping | None = None
|
|
176
174
|
) -> Tree:
|
|
177
175
|
"""Get subtree rooted at n.
|
|
178
176
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
Id of the root of the subtree.
|
|
184
|
-
out_mapping: List of int or dict[int, int], optional
|
|
185
|
-
Map new id to old id.
|
|
177
|
+
Args:
|
|
178
|
+
swc_like: SWCLike
|
|
179
|
+
n: Id of the root of the subtree.
|
|
180
|
+
out_mapping: Map new id to old id.
|
|
186
181
|
"""
|
|
187
182
|
|
|
188
183
|
n_nodes, ndata, source, names = get_subtree_impl(
|
|
@@ -194,14 +189,10 @@ def get_subtree(
|
|
|
194
189
|
def redirect_tree(tree: Tree, new_root: int, sort: bool = True) -> Tree:
|
|
195
190
|
"""Set root to new point and redirect tree graph.
|
|
196
191
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
new_root : int
|
|
202
|
-
The id of new root.
|
|
203
|
-
sort : bool, default `True`
|
|
204
|
-
If true, sort indices of nodes after redirect.
|
|
192
|
+
Args:
|
|
193
|
+
tree: The tree.
|
|
194
|
+
new_root: The id of new root.
|
|
195
|
+
sort: If true, sort indices of nodes after redirect.
|
|
205
196
|
"""
|
|
206
197
|
|
|
207
198
|
tree = tree.copy()
|
|
@@ -227,22 +218,18 @@ def cat_tree( # pylint: disable=too-many-arguments
|
|
|
227
218
|
node2: int = 0,
|
|
228
219
|
*,
|
|
229
220
|
translate: bool = True,
|
|
230
|
-
names:
|
|
231
|
-
no_move:
|
|
221
|
+
names: SWCNames | None = None,
|
|
222
|
+
no_move: bool | None = None, # legacy
|
|
232
223
|
) -> Tree:
|
|
233
224
|
"""Concatenates the second tree onto the first one.
|
|
234
225
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
The node id of the connection point.
|
|
243
|
-
translate : bool, default `True`
|
|
244
|
-
Wheather to translate node_2 to node_1. If False, add link
|
|
245
|
-
between node_1 and node_2 without translate.
|
|
226
|
+
Args:
|
|
227
|
+
tree1: Tree
|
|
228
|
+
tree2: Tree
|
|
229
|
+
node1: The node id of the tree to be connected.
|
|
230
|
+
node2: The node id of the connection point.
|
|
231
|
+
translate: Weather to translate node_2 to node_1.
|
|
232
|
+
If False, add link between node_1 and node_2 without translate.
|
|
246
233
|
"""
|
|
247
234
|
if no_move is not None:
|
|
248
235
|
warnings.warn(
|