swcgeom 0.16.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 +48 -12
  6. swcgeom/analysis/sholl.py +25 -28
  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 +31 -12
  12. swcgeom/core/branch.py +19 -3
  13. swcgeom/core/branch_tree.py +18 -4
  14. swcgeom/core/compartment.py +18 -2
  15. swcgeom/core/node.py +32 -3
  16. swcgeom/core/path.py +21 -9
  17. swcgeom/core/population.py +58 -29
  18. swcgeom/core/swc.py +26 -10
  19. swcgeom/core/swc_utils/__init__.py +21 -7
  20. swcgeom/core/swc_utils/assembler.py +15 -0
  21. swcgeom/core/swc_utils/base.py +23 -17
  22. swcgeom/core/swc_utils/checker.py +19 -12
  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 +56 -40
  27. swcgeom/core/tree_utils.py +28 -17
  28. swcgeom/core/tree_utils_impl.py +18 -3
  29. swcgeom/images/__init__.py +17 -2
  30. swcgeom/images/augmentation.py +18 -3
  31. swcgeom/images/contrast.py +15 -0
  32. swcgeom/images/folder.py +27 -26
  33. swcgeom/images/io.py +94 -117
  34. swcgeom/transforms/__init__.py +28 -12
  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 +15 -0
  40. swcgeom/transforms/image_stack.py +36 -9
  41. swcgeom/transforms/images.py +121 -14
  42. swcgeom/transforms/mst.py +15 -0
  43. swcgeom/transforms/neurolucida_asc.py +20 -7
  44. swcgeom/transforms/path.py +15 -0
  45. swcgeom/transforms/population.py +16 -3
  46. swcgeom/transforms/tree.py +84 -30
  47. swcgeom/transforms/tree_assembler.py +23 -7
  48. swcgeom/utils/__init__.py +27 -12
  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 +35 -23
  55. swcgeom/utils/numpy_helper.py +15 -0
  56. swcgeom/utils/plotter_2d.py +27 -6
  57. swcgeom/utils/plotter_3d.py +48 -0
  58. swcgeom/utils/renderer.py +21 -6
  59. swcgeom/utils/sdf.py +19 -7
  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.16.0.dist-info → swcgeom-0.18.3.dist-info}/LICENSE +1 -1
  64. {swcgeom-0.16.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.16.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.16.0.dist-info/RECORD +0 -67
  72. {swcgeom-0.16.0.dist-info → swcgeom-0.18.3.dist-info}/top_level.txt +0 -0
swcgeom/core/path.py CHANGED
@@ -1,10 +1,26 @@
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
  """Nueron path."""
2
17
 
3
- import warnings
4
- from typing import Generic, Iterable, Iterator, List, overload
18
+ from collections.abc import Iterable, Iterator
19
+ from typing import Generic, overload
5
20
 
6
21
  import numpy as np
7
22
  import numpy.typing as npt
23
+ from typing_extensions import deprecated
8
24
 
9
25
  from swcgeom.core.node import Node
10
26
  from swcgeom.core.swc import DictSWC, SWCLike, SWCTypeVar
@@ -15,7 +31,7 @@ __all__ = ["Path"]
15
31
  class Path(SWCLike, Generic[SWCTypeVar]):
16
32
  """Neuron path.
17
33
 
18
- A path is a linear set of points without bifurcations.
34
+ A path is a linear set of points without furcations.
19
35
  """
20
36
 
21
37
  attach: SWCTypeVar
@@ -44,7 +60,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
44
60
  @overload
45
61
  def __getitem__(self, key: int) -> Node: ...
46
62
  @overload
47
- def __getitem__(self, key: slice) -> List[Node]: ...
63
+ def __getitem__(self, key: slice) -> list[Node]: ...
48
64
  @overload
49
65
  def __getitem__(self, key: str) -> npt.NDArray: ...
50
66
  # fmt:on
@@ -74,6 +90,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
74
90
  def get_ndata(self, key: str) -> npt.NDArray:
75
91
  return self.attach.get_ndata(key)[self.idx]
76
92
 
93
+ @deprecated("Use `path.node` instead.")
77
94
  def get_node(self, idx: int | np.integer) -> Node:
78
95
  """Get the count of intersection.
79
96
 
@@ -81,11 +98,6 @@ class Path(SWCLike, Generic[SWCTypeVar]):
81
98
  Use :meth:`path.node` instead.
82
99
  """
83
100
 
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
101
  return self.node(idx)
90
102
 
91
103
  def node(self, idx: int | np.integer) -> Node:
@@ -1,22 +1,26 @@
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 population is a set of tree."""
2
17
 
3
18
  import os
4
19
  import warnings
20
+ from collections.abc import Callable, Iterable, Iterator
5
21
  from concurrent.futures import ProcessPoolExecutor
6
22
  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
- )
23
+ from typing import Any, Optional, Protocol, TypeVar, cast, overload
20
24
 
21
25
  import numpy as np
22
26
  import numpy.typing as npt
@@ -44,16 +48,16 @@ class Trees(Protocol):
44
48
  class LazyLoadingTrees:
45
49
  """Lazy loading trees."""
46
50
 
47
- swcs: List[str]
48
- trees: List[Tree | None]
49
- kwargs: Dict[str, Any]
51
+ swcs: list[str]
52
+ trees: list[Tree | None]
53
+ kwargs: dict[str, Any]
50
54
 
51
55
  def __init__(self, swcs: Iterable[str], **kwargs) -> None:
52
56
  """
53
57
  Paramters
54
58
  ---------
55
59
  swcs : List of str
56
- kwargs : Dict[str, Any]
60
+ kwargs : dict[str, Any]
57
61
  Forwarding to `Tree.from_swc`
58
62
  """
59
63
 
@@ -81,7 +85,7 @@ class LazyLoadingTrees:
81
85
  class ChainTrees:
82
86
  """Chain trees."""
83
87
 
84
- trees: List[Trees]
88
+ trees: list[Trees]
85
89
  cumsum: npt.NDArray[np.int64]
86
90
 
87
91
  def __init__(self, trees: Iterable[Trees]) -> None:
@@ -108,6 +112,19 @@ class ChainTrees:
108
112
  return (self[i] for i in range(self.__len__()))
109
113
 
110
114
 
115
+ class NestTrees:
116
+ def __init__(self, trees: Trees, idx: Iterable[int], /) -> None:
117
+ super().__init__()
118
+ self.trees = trees
119
+ self.idx = list(idx)
120
+
121
+ def __getitem__(self, key: int, /) -> Tree:
122
+ return self.trees[self.idx[key]]
123
+
124
+ def __len__(self) -> int:
125
+ return len(self.idx)
126
+
127
+
111
128
  class Population:
112
129
  """Neuron population."""
113
130
 
@@ -146,13 +163,14 @@ class Population:
146
163
 
147
164
  # fmt:off
148
165
  @overload
149
- def __getitem__(self, key: slice) -> List[Tree]: ...
166
+ def __getitem__(self, key: slice) -> Trees: ...
150
167
  @overload
151
168
  def __getitem__(self, key: int) -> Tree: ...
152
169
  # fmt:on
153
- def __getitem__(self, key):
170
+ def __getitem__(self, key: int | slice):
154
171
  if isinstance(key, slice):
155
- return [cast(Tree, self.trees[i]) for i in range(*key.indices(len(self)))]
172
+ trees = NestTrees(self.trees, range(*key.indices(len(self))))
173
+ return cast(Trees, trees)
156
174
 
157
175
  if isinstance(key, (int, np.integer)):
158
176
  return cast(Tree, self.trees[int(key)])
@@ -216,9 +234,9 @@ class Population:
216
234
  return cls.from_swc(root, ext, extra_cols=extra_cols, **kwargs)
217
235
 
218
236
  @staticmethod
219
- def find_swcs(root: str, ext: str = ".swc", relpath: bool = False) -> List[str]:
237
+ def find_swcs(root: str, ext: str = ".swc", relpath: bool = False) -> list[str]:
220
238
  """Find all swc files."""
221
- swcs: List[str] = []
239
+ swcs: list[str] = []
222
240
  for r, _, files in os.walk(root):
223
241
  rr = os.path.relpath(r, root) if relpath else r
224
242
  fs = filter(lambda f: os.path.splitext(f)[-1] == ext, files)
@@ -231,8 +249,8 @@ class Populations:
231
249
  """A set of population."""
232
250
 
233
251
  len: int
234
- populations: List[Population]
235
- labels: List[str]
252
+ populations: list[Population]
253
+ labels: list[str]
236
254
 
237
255
  def __init__(
238
256
  self, populations: Iterable[Population], labels: Optional[Iterable[str]] = None
@@ -248,9 +266,9 @@ class Populations:
248
266
 
249
267
  # fmt:off
250
268
  @overload
251
- def __getitem__(self, key: slice) -> List[List[Tree]]: ...
269
+ def __getitem__(self, key: slice) -> list[list[Tree]]: ...
252
270
  @overload
253
- def __getitem__(self, key: int) -> List[Tree]: ...
271
+ def __getitem__(self, key: int) -> list[Tree]: ...
254
272
  # fmt:on
255
273
  def __getitem__(self, key):
256
274
  return [p[key] for p in self.populations]
@@ -259,7 +277,7 @@ class Populations:
259
277
  """Miniumn length of populations."""
260
278
  return self.len
261
279
 
262
- def __iter__(self) -> Iterator[List[Tree]]:
280
+ def __iter__(self) -> Iterator[list[Tree]]:
263
281
  return (self[i] for i in range(self.len))
264
282
 
265
283
  def __repr__(self) -> str:
@@ -288,7 +306,7 @@ class Populations:
288
306
 
289
307
  Parameters
290
308
  ----------
291
- roots : list of str
309
+ roots : List of str
292
310
  intersect : bool, default `True`
293
311
  Take the intersection of these populations.
294
312
  check_same : bool, default `False`
@@ -339,3 +357,14 @@ def _get_idx(key: int, length: int) -> int:
339
357
  key += length
340
358
 
341
359
  return key
360
+
361
+
362
+ # experimental
363
+ def filter_population(
364
+ pop: Population, predicate: Callable[[Tree], bool]
365
+ ) -> "Population":
366
+ """Filter trees in the population."""
367
+
368
+ # TODO: how to avoid load trees
369
+ idx = [i for i, t in enumerate(pop) if predicate(t)]
370
+ return Population(NestTrees(pop.trees, idx), root=pop.root)
swcgeom/core/swc.py CHANGED
@@ -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."""
2
17
 
3
18
  import warnings
4
19
  from abc import ABC, abstractmethod
20
+ from collections.abc import Iterable
5
21
  from copy import deepcopy
6
- from typing import Any, Dict, Iterable, List, Optional, Tuple, TypeVar, overload
22
+ from typing import Any, Optional, TypeVar, overload
7
23
 
8
24
  import numpy as np
9
25
  import numpy.typing as npt
@@ -32,7 +48,7 @@ __all__ = [
32
48
 
33
49
 
34
50
  swc_names_default = get_names()
35
- swc_cols: List[Tuple[str, npt.DTypeLike]] = [
51
+ swc_cols: list[tuple[str, npt.DTypeLike]] = [
36
52
  (swc_names_default.id, np.int32),
37
53
  (swc_names_default.type, np.int32),
38
54
  (swc_names_default.x, np.float32),
@@ -42,7 +58,7 @@ swc_cols: List[Tuple[str, npt.DTypeLike]] = [
42
58
  (swc_names_default.pid, np.int32),
43
59
  ]
44
60
 
45
- eswc_cols: List[Tuple[str, npt.DTypeLike]] = [
61
+ eswc_cols: list[tuple[str, npt.DTypeLike]] = [
46
62
  ("level", np.int32),
47
63
  ("mode", np.int32),
48
64
  ("timestamp", np.int32),
@@ -55,7 +71,7 @@ class SWCLike(ABC):
55
71
  """ABC of SWC."""
56
72
 
57
73
  source: str = ""
58
- comments: List[str] = []
74
+ comments: list[str] = []
59
75
  names: SWCNames
60
76
  types: SWCTypes
61
77
 
@@ -131,15 +147,15 @@ class SWCLike(ABC):
131
147
 
132
148
  # fmt: off
133
149
  @overload
134
- def to_swc(self, fname: str, *, extra_cols: List[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> None: ...
150
+ def to_swc(self, fname: str, *, extra_cols: list[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> None: ...
135
151
  @overload
136
- def to_swc(self, *, extra_cols: List[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> str: ...
152
+ def to_swc(self, *, extra_cols: list[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> str: ...
137
153
  # fmt: on
138
154
  def to_swc(
139
155
  self,
140
156
  fname: Optional[str] = None,
141
157
  *,
142
- extra_cols: Optional[List[str]] = None,
158
+ extra_cols: Optional[list[str]] = None,
143
159
  source: bool | str = True,
144
160
  comments: bool = True,
145
161
  id_offset: int = 1,
@@ -177,7 +193,7 @@ class SWCLike(ABC):
177
193
  self,
178
194
  fname: Optional[str] = None,
179
195
  swc_path: Optional[str] = None,
180
- extra_cols: Optional[List[str]] = None,
196
+ extra_cols: Optional[list[str]] = None,
181
197
  **kwargs,
182
198
  ) -> str | None:
183
199
  if swc_path is None:
@@ -199,7 +215,7 @@ SWCTypeVar = TypeVar("SWCTypeVar", bound=SWCLike)
199
215
  class DictSWC(SWCLike):
200
216
  """SWC implementation on dict."""
201
217
 
202
- ndata: Dict[str, npt.NDArray]
218
+ ndata: dict[str, npt.NDArray]
203
219
 
204
220
  def __init__(
205
221
  self,
@@ -221,7 +237,7 @@ class DictSWC(SWCLike):
221
237
  def values(self) -> Iterable[npt.NDArray[Any]]:
222
238
  return self.ndata.values()
223
239
 
224
- def items(self) -> Iterable[Tuple[str, npt.NDArray[Any]]]:
240
+ def items(self) -> Iterable[tuple[str, npt.NDArray[Any]]]:
225
241
  return self.ndata.items()
226
242
 
227
243
  def get_ndata(self, key: str) -> npt.NDArray[Any]:
@@ -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
  """SWC format utils.
2
17
 
3
18
  Notes
@@ -8,10 +23,9 @@ If you use the method here, please review the code more frequently, we
8
23
  will try to flag all breaking changes but NO promises.
9
24
  """
10
25
 
11
-
12
- from swcgeom.core.swc_utils.assembler import *
13
- from swcgeom.core.swc_utils.base import *
14
- from swcgeom.core.swc_utils.checker import *
15
- from swcgeom.core.swc_utils.io import *
16
- from swcgeom.core.swc_utils.normalizer import *
17
- from swcgeom.core.swc_utils.subtree import *
26
+ from swcgeom.core.swc_utils.assembler import * # noqa: F403
27
+ from swcgeom.core.swc_utils.base import * # noqa: F403
28
+ from swcgeom.core.swc_utils.checker import * # noqa: F403
29
+ from swcgeom.core.swc_utils.io import * # noqa: F403
30
+ from swcgeom.core.swc_utils.normalizer import * # noqa: F403
31
+ from swcgeom.core.swc_utils.subtree import * # noqa: F403
@@ -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
@@ -1,16 +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 (
5
- Callable,
6
- List,
7
- Literal,
8
- NamedTuple,
9
- Optional,
10
- Tuple,
11
- TypeVar,
12
- overload,
13
- )
18
+ from collections.abc import Callable
19
+ from typing import Literal, NamedTuple, Optional, TypeVar, overload
14
20
 
15
21
  import numpy as np
16
22
  import numpy.typing as npt
@@ -29,7 +35,7 @@ __all__ = [
29
35
  ]
30
36
 
31
37
  T, K = TypeVar("T"), TypeVar("K")
32
- 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)
33
39
 
34
40
 
35
41
  class SWCNames(NamedTuple):
@@ -43,7 +49,7 @@ class SWCNames(NamedTuple):
43
49
  r: str = "r"
44
50
  pid: str = "pid"
45
51
 
46
- def cols(self) -> List[str]:
52
+ def cols(self) -> list[str]:
47
53
  return [self.id, self.type, self.x, self.y, self.z, self.r, self.pid]
48
54
 
49
55
 
@@ -114,10 +120,10 @@ def get_dsu(
114
120
  @overload
115
121
  def traverse(topology: Topology, *, enter: Callable[[int, T | None], T], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> None: ...
116
122
  @overload
117
- 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: ...
118
124
  @overload
119
125
  def traverse(
120
- 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],
121
127
  root: int | np.integer = ..., mode: Literal["dfs"] = ...,
122
128
  ) -> K: ...
123
129
  # fmt: on
@@ -130,7 +136,7 @@ def traverse(topology: Topology, *, mode="dfs", **kwargs):
130
136
  The callback when entering node, which accepts two parameters,
131
137
  the current node id and the return value of it parent node. In
132
138
  particular, the root node receives an `None`.
133
- leave : (id: int, children: List[T]) => T, optional
139
+ leave : (id: int, children: list[T]) => T, optional
134
140
  The callback when leaving node. When leaving a node, subtree
135
141
  has already been traversed. Callback accepts two parameters,
136
142
  the current node id and list of the return value of children,
@@ -155,7 +161,7 @@ def _traverse_dfs(topology: Topology, *, enter=None, leave=None, root=0):
155
161
  children_map[pid].append(idx)
156
162
 
157
163
  # manual dfs to avoid stack overflow in long branch
158
- stack: List[Tuple[int, bool]] = [(root, True)] # (idx, is_enter)
164
+ stack: list[tuple[int, bool]] = [(root, True)] # (idx, is_enter)
159
165
  params = {root: None}
160
166
  vals = {}
161
167
 
@@ -1,11 +1,26 @@
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
@@ -81,6 +96,7 @@ def has_cyclic(topology: Topology) -> bool:
81
96
  return False
82
97
 
83
98
 
99
+ @deprecated("Use `is_single_root` instead")
84
100
  def check_single_root(*args, **kwargs) -> bool:
85
101
  """Check if the tree is single root.
86
102
 
@@ -88,14 +104,10 @@ def check_single_root(*args, **kwargs) -> bool:
88
104
  Use :meth:`is_single_root` instead.
89
105
  """
90
106
 
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
107
  return is_single_root(*args, **kwargs)
97
108
 
98
109
 
110
+ @deprecated("Use `is_bifurcate` instead")
99
111
  def is_binary_tree(
100
112
  df: pd.DataFrame, exclude_root: bool = True, *, names: Optional[SWCNames] = None
101
113
  ) -> bool:
@@ -105,11 +117,6 @@ def is_binary_tree(
105
117
  Use :meth:`is_bifurcate` instead.
106
118
  """
107
119
 
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
120
  names = get_names(names)
114
121
  topo = (df[names.id].to_numpy(), df[names.pid].to_numpy())
115
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