swcgeom 0.17.0__py3-none-any.whl → 0.17.2__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 (49) hide show
  1. swcgeom/_version.py +2 -2
  2. swcgeom/analysis/feature_extractor.py +25 -15
  3. swcgeom/analysis/features.py +20 -8
  4. swcgeom/analysis/lmeasure.py +33 -12
  5. swcgeom/analysis/sholl.py +10 -28
  6. swcgeom/analysis/trunk.py +12 -11
  7. swcgeom/analysis/visualization.py +9 -9
  8. swcgeom/analysis/visualization3d.py +85 -0
  9. swcgeom/analysis/volume.py +4 -4
  10. swcgeom/core/branch.py +4 -3
  11. swcgeom/core/branch_tree.py +3 -4
  12. swcgeom/core/compartment.py +3 -2
  13. swcgeom/core/node.py +17 -3
  14. swcgeom/core/path.py +6 -9
  15. swcgeom/core/population.py +43 -29
  16. swcgeom/core/swc.py +11 -10
  17. swcgeom/core/swc_utils/base.py +8 -17
  18. swcgeom/core/swc_utils/checker.py +3 -11
  19. swcgeom/core/swc_utils/io.py +7 -6
  20. swcgeom/core/swc_utils/normalizer.py +4 -3
  21. swcgeom/core/swc_utils/subtree.py +2 -2
  22. swcgeom/core/tree.py +41 -40
  23. swcgeom/core/tree_utils.py +13 -17
  24. swcgeom/core/tree_utils_impl.py +3 -3
  25. swcgeom/images/augmentation.py +3 -3
  26. swcgeom/images/folder.py +12 -26
  27. swcgeom/images/io.py +21 -35
  28. swcgeom/transforms/image_stack.py +20 -8
  29. swcgeom/transforms/images.py +3 -12
  30. swcgeom/transforms/neurolucida_asc.py +4 -6
  31. swcgeom/transforms/population.py +1 -3
  32. swcgeom/transforms/tree.py +38 -25
  33. swcgeom/transforms/tree_assembler.py +4 -3
  34. swcgeom/utils/download.py +44 -21
  35. swcgeom/utils/ellipse.py +3 -4
  36. swcgeom/utils/neuromorpho.py +17 -16
  37. swcgeom/utils/plotter_2d.py +12 -6
  38. swcgeom/utils/plotter_3d.py +31 -0
  39. swcgeom/utils/renderer.py +6 -6
  40. swcgeom/utils/sdf.py +4 -7
  41. swcgeom/utils/solid_geometry.py +1 -3
  42. swcgeom/utils/transforms.py +2 -4
  43. swcgeom/utils/volumetric_object.py +8 -10
  44. {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/METADATA +19 -19
  45. swcgeom-0.17.2.dist-info/RECORD +67 -0
  46. {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/WHEEL +1 -1
  47. swcgeom-0.17.0.dist-info/RECORD +0 -65
  48. {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/LICENSE +0 -0
  49. {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  """Analysis of volume of a SWC tree."""
2
2
 
3
- from typing import Dict, List, Literal
3
+ from typing import Literal
4
4
 
5
5
  import numpy as np
6
6
  from sdflit import ColoredMaterial, ObjectsScene, SDFObject, UniformSampler
@@ -11,7 +11,7 @@ from swcgeom.utils import VolFrustumCone, VolSphere
11
11
  __all__ = ["get_volume"]
12
12
 
13
13
  ACCURACY_LEVEL = Literal["low", "middle", "high"]
14
- ACCURACY_LEVELS: Dict[ACCURACY_LEVEL, int] = {"low": 3, "middle": 5, "high": 8}
14
+ ACCURACY_LEVELS: dict[ACCURACY_LEVEL, int] = {"low": 3, "middle": 5, "high": 8}
15
15
 
16
16
 
17
17
  def get_volume(
@@ -93,7 +93,7 @@ def _get_volume_frustum_cone(tree: Tree, *, accuracy: int) -> float:
93
93
 
94
94
  volume = 0.0
95
95
 
96
- def leave(n: Tree.Node, children: List[VolSphere]) -> VolSphere:
96
+ def leave(n: Tree.Node, children: list[VolSphere]) -> VolSphere:
97
97
  sphere = VolSphere(n.xyz(), n.r)
98
98
  cones = [VolFrustumCone(n.xyz(), n.r, c.center, c.radius) for c in children]
99
99
 
@@ -129,7 +129,7 @@ def _get_volume_frustum_cone_mc_only(tree: Tree) -> float:
129
129
  scene = ObjectsScene()
130
130
  scene.set_background((0, 0, 0))
131
131
 
132
- def leave(n: Tree.Node, children: List[VolSphere]) -> VolSphere:
132
+ def leave(n: Tree.Node, children: list[VolSphere]) -> VolSphere:
133
133
  sphere = VolSphere(n.xyz(), n.r)
134
134
  scene.add_object(SDFObject(sphere.sdf, material).into())
135
135
 
swcgeom/core/branch.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Branch is a set of node points."""
2
2
 
3
- from typing import Generic, Iterable, List
3
+ from collections.abc import Iterable
4
+ from typing import Generic
4
5
 
5
6
  import numpy as np
6
7
  import numpy.typing as npt
@@ -92,7 +93,7 @@ class Branch(Path, Generic[SWCTypeVar]):
92
93
  @classmethod
93
94
  def from_xyzr_batch(
94
95
  cls, xyzr_batch: npt.NDArray[np.float32]
95
- ) -> List["Branch[DictSWC]"]:
96
+ ) -> list["Branch[DictSWC]"]:
96
97
  r"""Create list of branch form ~numpy.ndarray.
97
98
 
98
99
  Parameters
@@ -112,7 +113,7 @@ class Branch(Path, Generic[SWCTypeVar]):
112
113
  )
113
114
  xyzr_batch = np.concatenate([xyzr_batch, ones], axis=2)
114
115
 
115
- branches: List[Branch[DictSWC]] = []
116
+ branches: list[Branch[DictSWC]] = []
116
117
  for xyzr in xyzr_batch:
117
118
  n_nodes = xyzr.shape[0]
118
119
  idx = np.arange(0, n_nodes, step=1, dtype=np.int32)
@@ -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: Dict[int, List[Branch]]
21
+ branches: dict[int, list[Branch]]
23
22
 
24
- def get_origin_branches(self) -> List[Branch]:
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) -> List[Branch]:
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
 
@@ -1,6 +1,7 @@
1
1
  """The segment is a branch with two nodes."""
2
2
 
3
- from typing import Generic, Iterable, List, TypeVar
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(List[T]):
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
@@ -1,10 +1,11 @@
1
1
  """Nueron node."""
2
2
 
3
- import warnings
4
- from typing import Any, Generic, Iterable
3
+ from collections.abc import Iterable
4
+ from typing import Any, Generic
5
5
 
6
6
  import numpy as np
7
7
  import numpy.typing as npt
8
+ from typing_extensions import deprecated
8
9
 
9
10
  from swcgeom.core.swc import DictSWC, SWCTypeVar
10
11
  from swcgeom.core.swc_utils import SWCNames
@@ -95,9 +96,22 @@ class Node(Generic[SWCTypeVar]):
95
96
  items = [self.id, self.type, x, y, z, r, self.pid]
96
97
  return " ".join(map(str, items))
97
98
 
98
- def is_bifurcation(self) -> bool:
99
+ def is_furcation(self) -> bool:
100
+ """Is furcation node."""
99
101
  return np.count_nonzero(self.attach.pid() == self.id) > 1
100
102
 
103
+ @deprecated("Use is_furcation instead")
104
+ def is_bifurcation(self) -> bool:
105
+ """Is furcation node.
106
+
107
+ Notes
108
+ -----
109
+ Deprecated due to the wrong spelling of furcation. For now, it
110
+ is just an alias of `is_furcation` and raise a warning. It will
111
+ be change to raise an error in the future.
112
+ """
113
+ return self.is_furcation()
114
+
101
115
  def is_tip(self) -> bool:
102
116
  return self.id not in self.attach.pid()
103
117
 
swcgeom/core/path.py CHANGED
@@ -1,10 +1,11 @@
1
1
  """Nueron path."""
2
2
 
3
- import warnings
4
- from typing import Generic, Iterable, Iterator, List, overload
3
+ from collections.abc import Iterable, Iterator
4
+ from typing import Generic, overload
5
5
 
6
6
  import numpy as np
7
7
  import numpy.typing as npt
8
+ from typing_extensions import deprecated
8
9
 
9
10
  from swcgeom.core.node import Node
10
11
  from swcgeom.core.swc import DictSWC, SWCLike, SWCTypeVar
@@ -15,7 +16,7 @@ __all__ = ["Path"]
15
16
  class Path(SWCLike, Generic[SWCTypeVar]):
16
17
  """Neuron path.
17
18
 
18
- A path is a linear set of points without bifurcations.
19
+ A path is a linear set of points without furcations.
19
20
  """
20
21
 
21
22
  attach: SWCTypeVar
@@ -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) -> List[Node]: ...
48
+ def __getitem__(self, key: slice) -> list[Node]: ...
48
49
  @overload
49
50
  def __getitem__(self, key: str) -> npt.NDArray: ...
50
51
  # fmt:on
@@ -74,6 +75,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
74
75
  def get_ndata(self, key: str) -> npt.NDArray:
75
76
  return self.attach.get_ndata(key)[self.idx]
76
77
 
78
+ @deprecated("Use `path.node` instead.")
77
79
  def get_node(self, idx: int | np.integer) -> Node:
78
80
  """Get the count of intersection.
79
81
 
@@ -81,11 +83,6 @@ class Path(SWCLike, Generic[SWCTypeVar]):
81
83
  Use :meth:`path.node` instead.
82
84
  """
83
85
 
84
- warnings.warn(
85
- "`Path.get_node` has been deprecated since v0.16.0 and "
86
- "will be removed in future version",
87
- DeprecationWarning,
88
- )
89
86
  return self.node(idx)
90
87
 
91
88
  def node(self, idx: int | np.integer) -> Node:
@@ -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: List[str]
48
- trees: List[Tree | None]
49
- kwargs: Dict[str, Any]
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 : Dict[str, Any]
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: List[Trees]
73
+ trees: list[Trees]
85
74
  cumsum: npt.NDArray[np.int64]
86
75
 
87
76
  def __init__(self, trees: Iterable[Trees]) -> None:
@@ -108,6 +97,19 @@ class ChainTrees:
108
97
  return (self[i] for i in range(self.__len__()))
109
98
 
110
99
 
100
+ class NestTrees:
101
+ def __init__(self, trees: Trees, idx: Iterable[int], /) -> None:
102
+ super().__init__()
103
+ self.trees = trees
104
+ self.idx = list(idx)
105
+
106
+ def __getitem__(self, key: int, /) -> Tree:
107
+ return self.trees[self.idx[key]]
108
+
109
+ def __len__(self) -> int:
110
+ return len(self.idx)
111
+
112
+
111
113
  class Population:
112
114
  """Neuron population."""
113
115
 
@@ -146,13 +148,14 @@ class Population:
146
148
 
147
149
  # fmt:off
148
150
  @overload
149
- def __getitem__(self, key: slice) -> List[Tree]: ...
151
+ def __getitem__(self, key: slice) -> Trees: ...
150
152
  @overload
151
153
  def __getitem__(self, key: int) -> Tree: ...
152
154
  # fmt:on
153
- def __getitem__(self, key):
155
+ def __getitem__(self, key: int | slice):
154
156
  if isinstance(key, slice):
155
- return [cast(Tree, self.trees[i]) for i in range(*key.indices(len(self)))]
157
+ trees = NestTrees(self.trees, range(*key.indices(len(self))))
158
+ return cast(Trees, trees)
156
159
 
157
160
  if isinstance(key, (int, np.integer)):
158
161
  return cast(Tree, self.trees[int(key)])
@@ -216,9 +219,9 @@ class Population:
216
219
  return cls.from_swc(root, ext, extra_cols=extra_cols, **kwargs)
217
220
 
218
221
  @staticmethod
219
- def find_swcs(root: str, ext: str = ".swc", relpath: bool = False) -> List[str]:
222
+ def find_swcs(root: str, ext: str = ".swc", relpath: bool = False) -> list[str]:
220
223
  """Find all swc files."""
221
- swcs: List[str] = []
224
+ swcs: list[str] = []
222
225
  for r, _, files in os.walk(root):
223
226
  rr = os.path.relpath(r, root) if relpath else r
224
227
  fs = filter(lambda f: os.path.splitext(f)[-1] == ext, files)
@@ -231,8 +234,8 @@ class Populations:
231
234
  """A set of population."""
232
235
 
233
236
  len: int
234
- populations: List[Population]
235
- labels: List[str]
237
+ populations: list[Population]
238
+ labels: list[str]
236
239
 
237
240
  def __init__(
238
241
  self, populations: Iterable[Population], labels: Optional[Iterable[str]] = None
@@ -248,9 +251,9 @@ class Populations:
248
251
 
249
252
  # fmt:off
250
253
  @overload
251
- def __getitem__(self, key: slice) -> List[List[Tree]]: ...
254
+ def __getitem__(self, key: slice) -> list[list[Tree]]: ...
252
255
  @overload
253
- def __getitem__(self, key: int) -> List[Tree]: ...
256
+ def __getitem__(self, key: int) -> list[Tree]: ...
254
257
  # fmt:on
255
258
  def __getitem__(self, key):
256
259
  return [p[key] for p in self.populations]
@@ -259,7 +262,7 @@ class Populations:
259
262
  """Miniumn length of populations."""
260
263
  return self.len
261
264
 
262
- def __iter__(self) -> Iterator[List[Tree]]:
265
+ def __iter__(self) -> Iterator[list[Tree]]:
263
266
  return (self[i] for i in range(self.len))
264
267
 
265
268
  def __repr__(self) -> str:
@@ -288,7 +291,7 @@ class Populations:
288
291
 
289
292
  Parameters
290
293
  ----------
291
- roots : list of str
294
+ roots : List of str
292
295
  intersect : bool, default `True`
293
296
  Take the intersection of these populations.
294
297
  check_same : bool, default `False`
@@ -339,3 +342,14 @@ def _get_idx(key: int, length: int) -> int:
339
342
  key += length
340
343
 
341
344
  return key
345
+
346
+
347
+ # experimental
348
+ def filter_population(
349
+ pop: Population, predicate: Callable[[Tree], bool]
350
+ ) -> "Population":
351
+ """Filter trees in the population."""
352
+
353
+ # TODO: how to avoid load trees
354
+ idx = [i for i, t in enumerate(pop) if predicate(t)]
355
+ return Population(NestTrees(pop.trees, idx), root=pop.root)
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, Dict, Iterable, List, Optional, Tuple, TypeVar, overload
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: List[Tuple[str, npt.DTypeLike]] = [
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: List[Tuple[str, npt.DTypeLike]] = [
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: List[str] = []
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: List[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> None: ...
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: List[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> str: ...
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[List[str]] = None,
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[List[str]] = None,
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: Dict[str, npt.NDArray]
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[Tuple[str, npt.NDArray[Any]]]:
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]:
@@ -1,16 +1,7 @@
1
1
  """Base SWC format utils."""
2
2
 
3
- from dataclasses import dataclass
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 = Tuple[npt.NDArray[np.int32], npt.NDArray[np.int32]] # (id, pid)
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) -> List[str]:
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, List[K]], K], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> K: ...
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, List[K]], K],
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: List[T]) => T, optional
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: List[Tuple[int, bool]] = [(root, True)] # (idx, is_enter)
149
+ stack: list[tuple[int, bool]] = [(root, True)] # (idx, is_enter)
159
150
  params = {root: None}
160
151
  vals = {}
161
152
 
@@ -1,11 +1,11 @@
1
1
  """Check common """
2
2
 
3
- import warnings
4
3
  from collections import defaultdict
5
4
  from typing import Optional
6
5
 
7
6
  import numpy as np
8
7
  import pandas as pd
8
+ from typing_extensions import deprecated
9
9
 
10
10
  from swcgeom.core.swc_utils.base import SWCNames, Topology, get_dsu, get_names, traverse
11
11
  from swcgeom.utils import DisjointSetUnion
@@ -81,6 +81,7 @@ def has_cyclic(topology: Topology) -> bool:
81
81
  return False
82
82
 
83
83
 
84
+ @deprecated("Use `is_single_root` instead")
84
85
  def check_single_root(*args, **kwargs) -> bool:
85
86
  """Check if the tree is single root.
86
87
 
@@ -88,14 +89,10 @@ def check_single_root(*args, **kwargs) -> bool:
88
89
  Use :meth:`is_single_root` instead.
89
90
  """
90
91
 
91
- warnings.warn(
92
- "`check_single_root` has been renamed to `is_single_root` since"
93
- "v0.5.0, and will be removed in next version",
94
- DeprecationWarning,
95
- )
96
92
  return is_single_root(*args, **kwargs)
97
93
 
98
94
 
95
+ @deprecated("Use `is_bifurcate` instead")
99
96
  def is_binary_tree(
100
97
  df: pd.DataFrame, exclude_root: bool = True, *, names: Optional[SWCNames] = None
101
98
  ) -> bool:
@@ -105,11 +102,6 @@ def is_binary_tree(
105
102
  Use :meth:`is_bifurcate` instead.
106
103
  """
107
104
 
108
- warnings.warn(
109
- "`is_binary_tree` has been replaced by to `is_bifurcate` since"
110
- "v0.8.0, and will be removed in next version",
111
- DeprecationWarning,
112
- )
113
105
  names = get_names(names)
114
106
  topo = (df[names.id].to_numpy(), df[names.pid].to_numpy())
115
107
  return is_bifurcate(topo, exclude_root=exclude_root)
@@ -2,7 +2,8 @@
2
2
 
3
3
  import re
4
4
  import warnings
5
- from typing import Callable, Iterable, List, Literal, Optional, Tuple
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
- ) -> Tuple[pd.DataFrame, List[str]]:
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 : list of string
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
- ) -> Tuple[pd.DataFrame, List[str]]:
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 : list of str, optional
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 : list of string
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 typing import Callable, List, Literal, Optional, Tuple
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) -> Tuple[Topology, npt.NDArray[np.int32]]:
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: List[Tuple[npt.NDArray[np.int32], int]] = [(first_root, -1)]
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 Tuple, cast
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) -> Tuple[Topology, npt.NDArray[np.int32]]:
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