swcgeom 0.15.0__py3-none-any.whl → 0.18.3__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.

Files changed (72) hide show
  1. swcgeom/__init__.py +26 -1
  2. swcgeom/analysis/__init__.py +21 -8
  3. swcgeom/analysis/feature_extractor.py +43 -18
  4. swcgeom/analysis/features.py +250 -0
  5. swcgeom/analysis/lmeasure.py +857 -0
  6. swcgeom/analysis/sholl.py +55 -29
  7. swcgeom/analysis/trunk.py +27 -11
  8. swcgeom/analysis/visualization.py +24 -9
  9. swcgeom/analysis/visualization3d.py +100 -0
  10. swcgeom/analysis/volume.py +19 -4
  11. swcgeom/core/__init__.py +32 -9
  12. swcgeom/core/branch.py +28 -7
  13. swcgeom/core/branch_tree.py +18 -4
  14. swcgeom/core/{segment.py → compartment.py} +31 -10
  15. swcgeom/core/node.py +31 -10
  16. swcgeom/core/path.py +37 -10
  17. swcgeom/core/population.py +103 -34
  18. swcgeom/core/swc.py +26 -10
  19. swcgeom/core/swc_utils/__init__.py +21 -7
  20. swcgeom/core/swc_utils/assembler.py +27 -1
  21. swcgeom/core/swc_utils/base.py +25 -12
  22. swcgeom/core/swc_utils/checker.py +31 -14
  23. swcgeom/core/swc_utils/io.py +24 -7
  24. swcgeom/core/swc_utils/normalizer.py +20 -4
  25. swcgeom/core/swc_utils/subtree.py +17 -2
  26. swcgeom/core/tree.py +85 -72
  27. swcgeom/core/tree_utils.py +31 -16
  28. swcgeom/core/tree_utils_impl.py +18 -3
  29. swcgeom/images/__init__.py +17 -2
  30. swcgeom/images/augmentation.py +24 -4
  31. swcgeom/images/contrast.py +122 -0
  32. swcgeom/images/folder.py +97 -39
  33. swcgeom/images/io.py +108 -121
  34. swcgeom/transforms/__init__.py +28 -10
  35. swcgeom/transforms/base.py +17 -2
  36. swcgeom/transforms/branch.py +74 -8
  37. swcgeom/transforms/branch_tree.py +82 -0
  38. swcgeom/transforms/geometry.py +22 -7
  39. swcgeom/transforms/image_preprocess.py +115 -0
  40. swcgeom/transforms/image_stack.py +37 -13
  41. swcgeom/transforms/images.py +184 -7
  42. swcgeom/transforms/mst.py +20 -5
  43. swcgeom/transforms/neurolucida_asc.py +508 -0
  44. swcgeom/transforms/path.py +15 -0
  45. swcgeom/transforms/population.py +16 -3
  46. swcgeom/transforms/tree.py +89 -31
  47. swcgeom/transforms/tree_assembler.py +23 -7
  48. swcgeom/utils/__init__.py +27 -11
  49. swcgeom/utils/debug.py +15 -0
  50. swcgeom/utils/download.py +59 -21
  51. swcgeom/utils/dsu.py +15 -0
  52. swcgeom/utils/ellipse.py +18 -4
  53. swcgeom/utils/file.py +15 -0
  54. swcgeom/utils/neuromorpho.py +439 -302
  55. swcgeom/utils/numpy_helper.py +29 -4
  56. swcgeom/utils/plotter_2d.py +151 -0
  57. swcgeom/utils/plotter_3d.py +48 -0
  58. swcgeom/utils/renderer.py +49 -145
  59. swcgeom/utils/sdf.py +24 -8
  60. swcgeom/utils/solid_geometry.py +16 -3
  61. swcgeom/utils/transforms.py +17 -4
  62. swcgeom/utils/volumetric_object.py +23 -10
  63. {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/LICENSE +1 -1
  64. {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/METADATA +28 -24
  65. swcgeom-0.18.3.dist-info/RECORD +67 -0
  66. {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/WHEEL +1 -1
  67. swcgeom/_version.py +0 -16
  68. swcgeom/analysis/branch_features.py +0 -67
  69. swcgeom/analysis/node_features.py +0 -121
  70. swcgeom/analysis/path_features.py +0 -37
  71. swcgeom-0.15.0.dist-info/RECORD +0 -62
  72. {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,18 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  """Assemble lines to swc.
2
17
 
3
18
  Notes
@@ -6,11 +21,16 @@ This module is deprecated, please use `~.transforms.LinesToTree`
6
21
  instead.
7
22
  """
8
23
 
9
-
10
24
  __all__ = ["assemble_lines", "try_assemble_lines"]
11
25
 
12
26
 
13
27
  def assemble_lines(*args, **kwargs):
28
+ """Assemble lines to tree.
29
+
30
+ .. deprecated:: 0.15.0
31
+ Use :meth:`~.transforms.LinesToTree` instead.
32
+ """
33
+
14
34
  raise DeprecationWarning(
15
35
  "`assemble_lines` has been replaced by "
16
36
  "`~.transforms.LinesToTree` because it can be easy assemble "
@@ -19,6 +39,12 @@ def assemble_lines(*args, **kwargs):
19
39
 
20
40
 
21
41
  def try_assemble_lines(*args, **kwargs):
42
+ """Try assemble lines to tree.
43
+
44
+ .. deprecated:: 0.15.0
45
+ Use :meth:`~.transforms.LinesToTree` instead.
46
+ """
47
+
22
48
  raise DeprecationWarning(
23
49
  "`try_assemble_lines` has been replaced by "
24
50
  "`~.transforms.LinesToTree` because it can be easy assemble "
@@ -1,7 +1,22 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  """Base SWC format utils."""
2
17
 
3
- from dataclasses import dataclass
4
- from typing import Callable, List, Literal, Optional, Tuple, TypeVar, overload
18
+ from collections.abc import Callable
19
+ from typing import Literal, NamedTuple, Optional, TypeVar, overload
5
20
 
6
21
  import numpy as np
7
22
  import numpy.typing as npt
@@ -20,11 +35,10 @@ __all__ = [
20
35
  ]
21
36
 
22
37
  T, K = TypeVar("T"), TypeVar("K")
23
- Topology = Tuple[npt.NDArray[np.int32], npt.NDArray[np.int32]] # (id, pid)
38
+ Topology = tuple[npt.NDArray[np.int32], npt.NDArray[np.int32]] # (id, pid)
24
39
 
25
40
 
26
- @dataclass
27
- class SWCNames:
41
+ class SWCNames(NamedTuple):
28
42
  """SWC format column names."""
29
43
 
30
44
  id: str = "id"
@@ -35,7 +49,7 @@ class SWCNames:
35
49
  r: str = "r"
36
50
  pid: str = "pid"
37
51
 
38
- def cols(self) -> List[str]:
52
+ def cols(self) -> list[str]:
39
53
  return [self.id, self.type, self.x, self.y, self.z, self.r, self.pid]
40
54
 
41
55
 
@@ -46,8 +60,7 @@ def get_names(names: Optional[SWCNames] = None) -> SWCNames:
46
60
  return names or swc_names
47
61
 
48
62
 
49
- @dataclass
50
- class SWCTypes:
63
+ class SWCTypes(NamedTuple):
51
64
  """SWC format types.
52
65
 
53
66
  See Also
@@ -107,10 +120,10 @@ def get_dsu(
107
120
  @overload
108
121
  def traverse(topology: Topology, *, enter: Callable[[int, T | None], T], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> None: ...
109
122
  @overload
110
- def traverse(topology: Topology, *, leave: Callable[[int, List[K]], K], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> K: ...
123
+ def traverse(topology: Topology, *, leave: Callable[[int, list[K]], K], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> K: ...
111
124
  @overload
112
125
  def traverse(
113
- topology: Topology, *, enter: Callable[[int, T | None], T], leave: Callable[[int, List[K]], K],
126
+ topology: Topology, *, enter: Callable[[int, T | None], T], leave: Callable[[int, list[K]], K],
114
127
  root: int | np.integer = ..., mode: Literal["dfs"] = ...,
115
128
  ) -> K: ...
116
129
  # fmt: on
@@ -123,7 +136,7 @@ def traverse(topology: Topology, *, mode="dfs", **kwargs):
123
136
  The callback when entering node, which accepts two parameters,
124
137
  the current node id and the return value of it parent node. In
125
138
  particular, the root node receives an `None`.
126
- leave : (id: int, children: List[T]) => T, optional
139
+ leave : (id: int, children: list[T]) => T, optional
127
140
  The callback when leaving node. When leaving a node, subtree
128
141
  has already been traversed. Callback accepts two parameters,
129
142
  the current node id and list of the return value of children,
@@ -148,7 +161,7 @@ def _traverse_dfs(topology: Topology, *, enter=None, leave=None, root=0):
148
161
  children_map[pid].append(idx)
149
162
 
150
163
  # manual dfs to avoid stack overflow in long branch
151
- stack: List[Tuple[int, bool]] = [(root, True)] # (idx, is_enter)
164
+ stack: list[tuple[int, bool]] = [(root, True)] # (idx, is_enter)
152
165
  params = {root: None}
153
166
  vals = {}
154
167
 
@@ -1,16 +1,30 @@
1
- """Check common """
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ """Check common"""
2
17
 
3
- import warnings
4
18
  from collections import defaultdict
5
19
  from typing import Optional
6
20
 
7
21
  import numpy as np
8
22
  import pandas as pd
23
+ from typing_extensions import deprecated
9
24
 
10
25
  from swcgeom.core.swc_utils.base import SWCNames, Topology, get_dsu, get_names, traverse
11
26
  from swcgeom.utils import DisjointSetUnion
12
27
 
13
-
14
28
  __all__ = [
15
29
  "is_single_root",
16
30
  "is_bifurcate",
@@ -82,24 +96,27 @@ def has_cyclic(topology: Topology) -> bool:
82
96
  return False
83
97
 
84
98
 
99
+ @deprecated("Use `is_single_root` instead")
85
100
  def check_single_root(*args, **kwargs) -> bool:
86
- warnings.warn(
87
- "`check_single_root` has been renamed to `is_single_root` since"
88
- "v0.5.0, and will be removed in next version",
89
- DeprecationWarning,
90
- )
101
+ """Check if the tree is single root.
102
+
103
+ .. deprecated:: 0.5.0
104
+ Use :meth:`is_single_root` instead.
105
+ """
106
+
91
107
  return is_single_root(*args, **kwargs)
92
108
 
93
109
 
110
+ @deprecated("Use `is_bifurcate` instead")
94
111
  def is_binary_tree(
95
112
  df: pd.DataFrame, exclude_root: bool = True, *, names: Optional[SWCNames] = None
96
113
  ) -> bool:
97
- """Check is it a binary tree."""
98
- warnings.warn(
99
- "`is_binary_tree` has been replaced by to `is_bifurcate` since"
100
- "v0.8.0, and will be removed in next version",
101
- DeprecationWarning,
102
- )
114
+ """Check is it a binary tree.
115
+
116
+ .. deprecated:: 0.8.0
117
+ Use :meth:`is_bifurcate` instead.
118
+ """
119
+
103
120
  names = get_names(names)
104
121
  topo = (df[names.id].to_numpy(), df[names.pid].to_numpy())
105
122
  return is_bifurcate(topo, exclude_root=exclude_root)
@@ -1,8 +1,24 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  """Read and write swc format."""
2
17
 
3
18
  import re
4
19
  import warnings
5
- from typing import Callable, Iterable, List, Literal, Optional, Tuple
20
+ from collections.abc import Callable, Iterable
21
+ from typing import Literal, Optional
6
22
 
7
23
  import numpy as np
8
24
  import numpy.typing as npt
@@ -30,7 +46,7 @@ def read_swc(
30
46
  *,
31
47
  encoding: Literal["detect"] | str = "utf-8",
32
48
  names: Optional[SWCNames] = None,
33
- ) -> Tuple[pd.DataFrame, List[str]]:
49
+ ) -> tuple[pd.DataFrame, list[str]]:
34
50
  """Read swc file.
35
51
 
36
52
  Parameters
@@ -55,7 +71,7 @@ def read_swc(
55
71
  Returns
56
72
  -------
57
73
  df : ~pandas.DataFrame
58
- comments : list of string
74
+ comments : List of string
59
75
  """
60
76
 
61
77
  names = get_names(names)
@@ -137,14 +153,14 @@ def parse_swc(
137
153
  names: SWCNames,
138
154
  extra_cols: Iterable[str] | None = None,
139
155
  encoding: Literal["detect"] | str = "utf-8",
140
- ) -> Tuple[pd.DataFrame, List[str]]:
156
+ ) -> tuple[pd.DataFrame, list[str]]:
141
157
  """Parse swc file.
142
158
 
143
159
  Parameters
144
160
  ----------
145
161
  fname : PathOrIO
146
162
  names : SWCNames
147
- extra_cols : list of str, optional
163
+ extra_cols : List of str, optional
148
164
  encoding : str | 'detect', default `utf-8`
149
165
  The name of the encoding used to decode the file. If is
150
166
  `detect`, we will try to detect the character encoding.
@@ -152,7 +168,7 @@ def parse_swc(
152
168
  Returns
153
169
  -------
154
170
  df : ~pandas.DataFrame
155
- comments : list of string
171
+ comments : List of string
156
172
  """
157
173
 
158
174
  # pylint: disable=too-many-locals
@@ -171,7 +187,8 @@ def parse_swc(
171
187
  RE_FLOAT, # r
172
188
  r"(-?[0-9]+)", # pid
173
189
  ] + [
174
- RE_FLOAT for _ in extras # assert float
190
+ RE_FLOAT
191
+ for _ in extras # assert float
175
192
  ]
176
193
 
177
194
  re_swc_cols_str = r"\s+".join(re_swc_cols)
@@ -1,9 +1,25 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  """SWC format utils.
2
17
 
3
18
  Methods ending with a underline imply an in-place transformation.
4
19
  """
5
20
 
6
- from typing import Callable, List, Literal, Optional, Tuple
21
+ from collections.abc import Callable
22
+ from typing import Literal, Optional
7
23
 
8
24
  import numpy as np
9
25
  import numpy.typing as npt
@@ -76,7 +92,7 @@ def link_roots_to_nearest_(
76
92
  vs = df[[names.x, names.y, names.z]] - row[[names.x, names.y, names.z]]
77
93
  dis = np.linalg.norm(vs.to_numpy(), axis=1)
78
94
  subtree = dsu == dsu[i] # type: ignore
79
- dis = np.where(subtree, np.Infinity, dis) # avoid link to same tree
95
+ dis = np.where(subtree, np.inf, dis) # avoid link to same tree
80
96
  dsu = np.where(subtree, dsu[dis.argmin()], dsu) # merge set
81
97
  df.loc[i, names.pid] = df[names.id].iloc[dis.argmin()] # type: ignore
82
98
 
@@ -111,7 +127,7 @@ def sort_nodes_(df: pd.DataFrame, *, names: Optional[SWCNames] = None) -> None:
111
127
  df[names.id], df[names.pid] = new_ids, new_pids
112
128
 
113
129
 
114
- def sort_nodes_impl(topology: Topology) -> Tuple[Topology, npt.NDArray[np.int32]]:
130
+ def sort_nodes_impl(topology: Topology) -> tuple[Topology, npt.NDArray[np.int32]]:
115
131
  """Sort the indices of neuron tree.
116
132
 
117
133
  Returns
@@ -127,7 +143,7 @@ def sort_nodes_impl(topology: Topology) -> Tuple[Topology, npt.NDArray[np.int32]
127
143
  new_pids = np.full_like(old_ids, fill_value=-3)
128
144
  new_id = 0
129
145
  first_root = old_ids[(old_pids == -1).argmax()]
130
- s: List[Tuple[npt.NDArray[np.int32], int]] = [(first_root, -1)]
146
+ s: list[tuple[npt.NDArray[np.int32], int]] = [(first_root, -1)]
131
147
  while len(s) != 0:
132
148
  old_id, new_pid = s.pop()
133
149
  id_map[new_id] = old_id
@@ -1,3 +1,18 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  """Cut subtree.
2
17
 
3
18
  This module provides a series of low-level topological subtree methods,
@@ -6,7 +21,7 @@ but in more cases, you can use the high-level methods provided in
6
21
  high-level API.
7
22
  """
8
23
 
9
- from typing import Tuple, cast
24
+ from typing import cast
10
25
 
11
26
  import numpy as np
12
27
  import numpy.typing as npt
@@ -18,7 +33,7 @@ __all__ = ["REMOVAL", "to_sub_topology", "propagate_removal"]
18
33
  REMOVAL = -2 # A marker in utils, place in the ids to mark it removal
19
34
 
20
35
 
21
- def to_sub_topology(sub: Topology) -> Tuple[Topology, npt.NDArray[np.int32]]:
36
+ def to_sub_topology(sub: Topology) -> tuple[Topology, npt.NDArray[np.int32]]:
22
37
  """Create sub tree from origin tree.
23
38
 
24
39
  Mark the node to be removed, then use this method to get a child
swcgeom/core/tree.py CHANGED
@@ -1,30 +1,34 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  """Neuron tree."""
2
17
 
3
18
  import itertools
4
19
  import os
5
- import warnings
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
- )
20
+ from collections.abc import Callable, Iterable, Iterator
21
+ from typing import Literal, Optional, TypeVar, Union, overload
19
22
 
20
23
  import numpy as np
21
24
  import numpy.typing as npt
22
25
  import pandas as pd
26
+ from typing_extensions import deprecated
23
27
 
24
28
  from swcgeom.core.branch import Branch
29
+ from swcgeom.core.compartment import Compartment, Compartments
25
30
  from swcgeom.core.node import Node
26
31
  from swcgeom.core.path import Path
27
- from swcgeom.core.segment import Segment, Segments
28
32
  from swcgeom.core.swc import DictSWC, eswc_cols
29
33
  from swcgeom.core.swc_utils import SWCNames, get_names, read_swc, traverse
30
34
  from swcgeom.core.tree_utils_impl import Mapping, get_subtree_impl
@@ -44,26 +48,17 @@ class Tree(DictSWC):
44
48
  def parent(self) -> Union["Tree.Node", None]:
45
49
  return Tree.Node(self.attach, self.pid) if self.pid != -1 else None
46
50
 
47
- def children(self) -> List["Tree.Node"]:
51
+ def children(self) -> list["Tree.Node"]:
48
52
  children = self.attach.id()[self.attach.pid() == self.id]
49
53
  return [Tree.Node(self.attach, idx) for idx in children]
50
54
 
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
55
  def branch(self) -> "Tree.Branch":
61
- ns: List["Tree.Node"] = [self]
62
- while not ns[-1].is_bifurcation() and (p := ns[-1].parent()) is not None:
56
+ ns: list["Tree.Node"] = [self]
57
+ while not ns[-1].is_furcation() and (p := ns[-1].parent()) is not None:
63
58
  ns.append(p)
64
59
 
65
60
  ns.reverse()
66
- while not (ns[-1].is_bifurcation() or ns[-1].is_tip()):
61
+ while not (ns[-1].is_furcation() or ns[-1].is_tip()):
67
62
  ns.append(ns[-1].children()[0])
68
63
 
69
64
  return Tree.Branch(self.attach, [n.id for n in ns])
@@ -77,7 +72,7 @@ class Tree(DictSWC):
77
72
 
78
73
  Parameters
79
74
  ----------
80
- out_mapping : List of int or Dict[int, int], optional
75
+ out_mapping : List of int or dict[int, int], optional
81
76
  Map from new id to old id.
82
77
  """
83
78
 
@@ -115,9 +110,11 @@ class Tree(DictSWC):
115
110
  # TODO: should returns `Tree.Node`
116
111
  """Neural path."""
117
112
 
118
- class Segment(Segment["Tree"]):
113
+ class Compartment(Compartment["Tree"]):
119
114
  # TODO: should returns `Tree.Node`
120
- """Neural segment."""
115
+ """Neural compartment."""
116
+
117
+ Segment = Compartment # Alias
121
118
 
122
119
  class Branch(Branch["Tree"]):
123
120
  # TODO: should returns `Tree.Node`
@@ -127,33 +124,33 @@ class Tree(DictSWC):
127
124
  self,
128
125
  n_nodes: int,
129
126
  *,
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
127
  source: str = "",
140
128
  comments: Optional[Iterable[str]] = None,
141
129
  names: Optional[SWCNames] = None,
142
130
  **kwargs: npt.NDArray,
143
131
  ) -> None:
144
132
  names = get_names(names)
145
- id = np.arange(0, n_nodes, step=1, dtype=np.int32) if id is None else id
146
- pid = np.arange(-1, n_nodes - 1, step=1, dtype=np.int32) if pid is None else pid
133
+
134
+ if names.id not in kwargs:
135
+ kwargs[names.id] = np.arange(0, n_nodes, step=1, dtype=np.int32)
136
+
137
+ if names.pid not in kwargs:
138
+ kwargs[names.pid] = np.arange(-1, n_nodes - 1, step=1, dtype=np.int32)
147
139
 
148
140
  ndata = {
149
- names.id: padding1d(n_nodes, id, dtype=np.int32),
150
- names.type: padding1d(n_nodes, type, dtype=np.int32),
151
- names.x: padding1d(n_nodes, x),
152
- names.y: padding1d(n_nodes, y),
153
- names.z: padding1d(n_nodes, z),
154
- names.r: padding1d(n_nodes, r, padding_value=1),
155
- names.pid: padding1d(n_nodes, pid, dtype=np.int32),
141
+ names.id: padding1d(n_nodes, kwargs.pop(names.id, None), dtype=np.int32),
142
+ names.type: padding1d(
143
+ n_nodes, kwargs.pop(names.type, None), dtype=np.int32
144
+ ),
145
+ names.x: padding1d(n_nodes, kwargs.pop(names.x, None), dtype=np.float32),
146
+ names.y: padding1d(n_nodes, kwargs.pop(names.y, None), dtype=np.float32),
147
+ names.z: padding1d(n_nodes, kwargs.pop(names.z, None), dtype=np.float32),
148
+ names.r: padding1d(
149
+ n_nodes, kwargs.pop(names.r, None), dtype=np.float32, padding_value=1
150
+ ),
151
+ names.pid: padding1d(n_nodes, kwargs.pop(names.pid, None), dtype=np.int32),
156
152
  }
153
+ # ? padding other columns
157
154
  super().__init__(
158
155
  **ndata, **kwargs, source=source, comments=comments, names=names
159
156
  )
@@ -167,7 +164,7 @@ class Tree(DictSWC):
167
164
 
168
165
  # fmt:off
169
166
  @overload
170
- def __getitem__(self, key: slice) -> List[Node]: ...
167
+ def __getitem__(self, key: slice) -> list[Node]: ...
171
168
  @overload
172
169
  def __getitem__(self, key: int) -> Node: ...
173
170
  @overload
@@ -206,35 +203,50 @@ class Tree(DictSWC):
206
203
  raise ValueError(f"no soma found in: {self.source}")
207
204
  return n
208
205
 
209
- def get_bifurcations(self) -> List[Node]:
210
- """Get all node of bifurcations."""
211
- bifurcations: List[int] = []
206
+ def get_furcations(self) -> list[Node]:
207
+ """Get all node of furcations."""
208
+ furcations: list[int] = []
212
209
 
213
- def collect_bifurcations(n: Tree.Node, children: List[None]) -> None:
210
+ def collect_furcations(n: Tree.Node, children: list[None]) -> None:
214
211
  if len(children) > 1:
215
- bifurcations.append(n.id)
212
+ furcations.append(n.id)
213
+
214
+ self.traverse(leave=collect_furcations)
215
+ return [self.node(i) for i in furcations]
216
216
 
217
- self.traverse(leave=collect_bifurcations)
218
- return [self.node(i) for i in bifurcations]
217
+ @deprecated("Use `get_furcations` instead")
218
+ def get_bifurcations(self) -> list[Node]:
219
+ """Get all node of furcations.
220
+
221
+ Notes
222
+ -----
223
+ Deprecated due to the wrong spelling of furcation. For now, it
224
+ is just an alias of `get_furcations` and raise a warning. It
225
+ will be change to raise an error in the future.
226
+ """
227
+ return self.get_furcations()
219
228
 
220
- def get_tips(self) -> List[Node]:
229
+ def get_tips(self) -> list[Node]:
221
230
  """Get all node of tips."""
222
231
  tip_ids = np.setdiff1d(self.id(), self.pid(), assume_unique=True)
223
232
  return [self.node(i) for i in tip_ids]
224
233
 
225
- def get_segments(self) -> Segments[Segment]:
226
- return Segments(self.Segment(self, n.pid, n.id) for n in self[1:])
234
+ def get_compartments(self) -> Compartments[Compartment]:
235
+ return Compartments(self.Compartment(self, n.pid, n.id) for n in self[1:])
227
236
 
228
- def get_branches(self) -> List[Branch]:
229
- Info = Tuple[List[Tree.Branch], List[int]]
237
+ def get_segments(self) -> Compartments[Compartment]: # Alias
238
+ return self.get_compartments()
230
239
 
231
- def collect_branches(node: "Tree.Node", pre: List[Info]) -> Info:
240
+ def get_branches(self) -> list[Branch]:
241
+ def collect_branches(
242
+ node: "Tree.Node", pre: list[tuple[list[Tree.Branch], list[int]]]
243
+ ) -> tuple[list[Tree.Branch], list[int]]:
232
244
  if len(pre) == 1:
233
245
  branches, child = pre[0]
234
246
  child.append(node.id)
235
247
  return branches, child
236
248
 
237
- branches: List[Tree.Branch] = []
249
+ branches: list[Tree.Branch] = []
238
250
 
239
251
  for sub_branches, child in pre:
240
252
  child.append(node.id)
@@ -248,18 +260,19 @@ class Tree(DictSWC):
248
260
  branches, _ = self.traverse(leave=collect_branches)
249
261
  return branches
250
262
 
251
- def get_paths(self) -> List[Path]:
263
+ def get_paths(self) -> list[Path]:
252
264
  """Get all path from soma to tips."""
253
- path_dic: Dict[int, List[int]] = {}
254
- Paths = List[List[int]]
265
+ path_dic: dict[int, list[int]] = {}
255
266
 
256
- def assign_path(n: Tree.Node, pre_path: List[int] | None) -> List[int]:
267
+ def assign_path(n: Tree.Node, pre_path: list[int] | None) -> list[int]:
257
268
  path = [] if pre_path is None else pre_path.copy()
258
269
  path.append(n.id)
259
270
  path_dic[n.id] = path
260
271
  return path
261
272
 
262
- def collect_path(n: Tree.Node, children: List[Paths]) -> Paths:
273
+ def collect_path(
274
+ n: Tree.Node, children: list[list[list[int]]]
275
+ ) -> list[list[int]]:
263
276
  if len(children) == 0:
264
277
  return [path_dic[n.id]]
265
278
 
@@ -286,7 +299,7 @@ class Tree(DictSWC):
286
299
  @overload
287
300
  def traverse(self, *,
288
301
  enter: Optional[Callable[[Node, T | None], T]] = ...,
289
- leave: Callable[[Node, List[K]], K],
302
+ leave: Callable[[Node, list[K]], K],
290
303
  root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> K: ...
291
304
  # fmt: on
292
305
 
@@ -296,7 +309,7 @@ class Tree(DictSWC):
296
309
  Parameters
297
310
  ----------
298
311
  enter : (n: Node, parent: T | None) => T, optional
299
- leave : (n: Node, children: List[T]) => T, optional
312
+ leave : (n: Node, children: list[T]) => T, optional
300
313
 
301
314
  See Also
302
315
  --------
@@ -358,7 +371,7 @@ class Tree(DictSWC):
358
371
 
359
372
  @classmethod
360
373
  def from_eswc(
361
- cls, swc_file: str, extra_cols: Optional[List[str]] = None, **kwargs
374
+ cls, swc_file: str, extra_cols: Optional[list[str]] = None, **kwargs
362
375
  ) -> "Tree":
363
376
  """Read neuron tree from eswc file.
364
377