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.

Files changed (68) hide show
  1. swcgeom/__init__.py +12 -1
  2. swcgeom/analysis/__init__.py +6 -6
  3. swcgeom/analysis/feature_extractor.py +22 -24
  4. swcgeom/analysis/features.py +18 -40
  5. swcgeom/analysis/lmeasure.py +227 -323
  6. swcgeom/analysis/sholl.py +17 -23
  7. swcgeom/analysis/trunk.py +23 -28
  8. swcgeom/analysis/visualization.py +37 -44
  9. swcgeom/analysis/visualization3d.py +16 -25
  10. swcgeom/analysis/volume.py +33 -47
  11. swcgeom/core/__init__.py +12 -13
  12. swcgeom/core/branch.py +10 -17
  13. swcgeom/core/branch_tree.py +3 -2
  14. swcgeom/core/compartment.py +1 -1
  15. swcgeom/core/node.py +3 -6
  16. swcgeom/core/path.py +11 -16
  17. swcgeom/core/population.py +32 -51
  18. swcgeom/core/swc.py +25 -16
  19. swcgeom/core/swc_utils/__init__.py +10 -12
  20. swcgeom/core/swc_utils/assembler.py +5 -12
  21. swcgeom/core/swc_utils/base.py +40 -31
  22. swcgeom/core/swc_utils/checker.py +3 -8
  23. swcgeom/core/swc_utils/io.py +32 -47
  24. swcgeom/core/swc_utils/normalizer.py +17 -23
  25. swcgeom/core/swc_utils/subtree.py +13 -20
  26. swcgeom/core/tree.py +61 -51
  27. swcgeom/core/tree_utils.py +36 -49
  28. swcgeom/core/tree_utils_impl.py +4 -6
  29. swcgeom/images/__init__.py +2 -2
  30. swcgeom/images/augmentation.py +23 -39
  31. swcgeom/images/contrast.py +22 -46
  32. swcgeom/images/folder.py +32 -34
  33. swcgeom/images/io.py +80 -121
  34. swcgeom/transforms/__init__.py +13 -13
  35. swcgeom/transforms/base.py +28 -19
  36. swcgeom/transforms/branch.py +31 -41
  37. swcgeom/transforms/branch_tree.py +3 -1
  38. swcgeom/transforms/geometry.py +13 -4
  39. swcgeom/transforms/image_preprocess.py +2 -0
  40. swcgeom/transforms/image_stack.py +40 -35
  41. swcgeom/transforms/images.py +31 -24
  42. swcgeom/transforms/mst.py +27 -40
  43. swcgeom/transforms/neurolucida_asc.py +13 -13
  44. swcgeom/transforms/path.py +4 -0
  45. swcgeom/transforms/population.py +4 -0
  46. swcgeom/transforms/tree.py +16 -11
  47. swcgeom/transforms/tree_assembler.py +37 -54
  48. swcgeom/utils/__init__.py +12 -12
  49. swcgeom/utils/download.py +7 -14
  50. swcgeom/utils/dsu.py +12 -0
  51. swcgeom/utils/ellipse.py +26 -14
  52. swcgeom/utils/file.py +8 -13
  53. swcgeom/utils/neuromorpho.py +78 -92
  54. swcgeom/utils/numpy_helper.py +15 -12
  55. swcgeom/utils/plotter_2d.py +10 -16
  56. swcgeom/utils/plotter_3d.py +7 -9
  57. swcgeom/utils/renderer.py +16 -8
  58. swcgeom/utils/sdf.py +12 -23
  59. swcgeom/utils/solid_geometry.py +58 -2
  60. swcgeom/utils/transforms.py +164 -100
  61. swcgeom/utils/volumetric_object.py +29 -53
  62. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/METADATA +7 -6
  63. swcgeom-0.19.0.dist-info/RECORD +67 -0
  64. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/WHEEL +1 -1
  65. swcgeom/_version.py +0 -16
  66. swcgeom-0.18.1.dist-info/RECORD +0 -68
  67. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info/licenses}/LICENSE +0 -0
  68. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/top_level.txt +0 -0
@@ -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, Optional
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: Optional[Iterable[str]] = None,
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: Optional[SWCNames] = None,
48
+ names: SWCNames | None = None,
49
49
  ) -> tuple[pd.DataFrame, list[str]]:
50
50
  """Read swc file.
51
51
 
52
- Parameters
53
- ----------
54
- swc_file : PathOrIO
55
- Path of swc file, the id should be consecutively incremented.
56
- extra_cols : Iterable[str], optional
57
- Read more cols in swc file.
58
- fix_roots : `somas`|`nearest`|False, default `False`
59
- Fix multiple roots.
60
- sort_nodes : bool, default `False`
61
- Sort the indices of neuron tree, the index for parent are
62
- always less than children.
63
- reset_index : bool, default `True`
64
- Reset node index to start with zero, DO NOT set to false if
65
- you are not sure what will happend.
66
- encoding : str | 'detect', default `utf-8`
67
- The name of the encoding used to decode the file. If is
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: Optional[Iterable[str]] = None,
105
+ extra_cols: Iterable[str] | None = None,
114
106
  id_offset: int = 1,
115
- comments: Optional[Iterable[str]] = None,
116
- names: Optional[SWCNames] = None,
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
- Parameters
160
- ----------
161
- fname : PathOrIO
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
- f"decode failed, try to enable auto detect `encoding='detect'`"
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, Optional
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: Optional[SWCNames] = None,
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: Optional[SWCNames] = None,
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: Optional[SWCNames] = None
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()] # type: ignore
95
+ df.loc[i, names.pid] = df[names.id].iloc[dis.argmin()]
98
96
 
99
97
 
100
- def sort_nodes(df: pd.DataFrame, *, names: Optional[SWCNames] = None) -> pd.DataFrame:
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: Optional[SWCNames] = None) -> None:
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
- new_topology : Topology
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: Optional[SWCNames] = None) -> pd.DataFrame:
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: Optional[SWCNames] = None) -> None:
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
- but in more cases, you can use the high-level methods provided in
20
- `tree_utils`, which wrap the methods in this module and provide a
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
- sub_topology : Topology
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
- propagate_removal :
51
- If the node you remove is not a leaf node, you need to use it
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, Optional, TypeVar, Union, overload
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, K = TypeVar("T"), TypeVar("K")
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: Optional[Mapping] = None) -> "Tree":
71
+ def subtree(self, *, out_mapping: Mapping | None = None) -> "Tree":
71
72
  """Get subtree from node.
72
73
 
73
- Parameters
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(self, *, enter: Callable[[Node, T | None], T], mode: Literal["dfs"] = ...) -> None: ...
90
+ def traverse(
91
+ self, *, enter: Callable[[Node, T | None], T], mode: Literal["dfs"] = ...
92
+ ) -> None: ...
93
93
  @overload
94
- def traverse(self, *, leave: Callable[[Node, list[K]], K], mode: Literal["dfs"] = ...) -> K: ...
94
+ def traverse(
95
+ self, *, leave: Callable[[Node, list[K]], K], mode: Literal["dfs"] = ...
96
+ ) -> K: ...
95
97
  @overload
96
- def traverse(self, *,
97
- enter: Callable[[Node, T | None], T], leave: Callable[[Node, list[K]], K], mode: Literal["dfs"] = ...,
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: Optional[Iterable[str]] = None,
129
- names: Optional[SWCNames] = None,
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
- 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.
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(self, *,
297
- enter: Callable[[Node, T | None], T],
298
- root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> None: ...
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(self, *,
301
- enter: Optional[Callable[[Node, T | None], T]] = ...,
302
- leave: Callable[[Node, list[K]], K],
303
- root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> K: ...
304
- # fmt: on
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
- Parameters
310
- ----------
311
- enter : (n: Node, parent: T | None) => T, optional
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: Optional[Iterable[str]] = None,
342
- names: Optional[SWCNames] = None,
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: Optional[list[str]] = None, **kwargs
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)
@@ -17,7 +17,7 @@
17
17
 
18
18
  import warnings
19
19
  from collections.abc import Callable, Iterable
20
- from typing import Optional, TypeVar, overload
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, K = TypeVar("T"), TypeVar("K")
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(tree: Tree, *, enter: Callable[[Tree.Node, T | None], tuple[T, bool]]) -> 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(tree: Tree, *, leave: Callable[[Tree.Node, list[K]], tuple[K, bool]]) -> Tree: ...
73
- # fmt:on
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
- tree : Tree
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: Optional[Mapping] = None,
151
+ out_mapping: Mapping | None = None,
151
152
  ) -> Tree:
152
153
  """Create subtree from origin tree.
153
154
 
154
- Parameters
155
- ----------
156
- swc_like : SWCLike
157
- removals : List of int
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: Optional[Mapping] = None
173
+ swc_like: SWCLike, n: int, *, out_mapping: Mapping | None = None
176
174
  ) -> Tree:
177
175
  """Get subtree rooted at n.
178
176
 
179
- Parameters
180
- ----------
181
- swc_like : SWCLike
182
- n : int
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
- Parameter
198
- ---------
199
- tree : Tree
200
- The tree.
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: Optional[SWCNames] = None,
231
- no_move: Optional[bool] = None, # legacy
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
- Paramters
236
- ---------
237
- tree1 : Tree
238
- tree2 : Tree
239
- node1 : int, default `0`
240
- The node id of the tree to be connected.
241
- node2 : int, default `0`
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(