swcgeom 0.15.0__py3-none-any.whl → 0.17.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 (42) hide show
  1. swcgeom/_version.py +2 -2
  2. swcgeom/analysis/__init__.py +1 -3
  3. swcgeom/analysis/feature_extractor.py +3 -3
  4. swcgeom/analysis/{node_features.py → features.py} +105 -3
  5. swcgeom/analysis/lmeasure.py +821 -0
  6. swcgeom/analysis/sholl.py +31 -2
  7. swcgeom/core/__init__.py +4 -0
  8. swcgeom/core/branch.py +9 -4
  9. swcgeom/core/{segment.py → compartment.py} +14 -9
  10. swcgeom/core/node.py +0 -8
  11. swcgeom/core/path.py +21 -6
  12. swcgeom/core/population.py +47 -7
  13. swcgeom/core/swc_utils/assembler.py +12 -1
  14. swcgeom/core/swc_utils/base.py +12 -5
  15. swcgeom/core/swc_utils/checker.py +12 -2
  16. swcgeom/core/tree.py +34 -37
  17. swcgeom/core/tree_utils.py +4 -0
  18. swcgeom/images/augmentation.py +6 -1
  19. swcgeom/images/contrast.py +107 -0
  20. swcgeom/images/folder.py +71 -14
  21. swcgeom/images/io.py +74 -88
  22. swcgeom/transforms/__init__.py +2 -0
  23. swcgeom/transforms/image_preprocess.py +100 -0
  24. swcgeom/transforms/image_stack.py +1 -4
  25. swcgeom/transforms/images.py +176 -5
  26. swcgeom/transforms/mst.py +5 -5
  27. swcgeom/transforms/neurolucida_asc.py +495 -0
  28. swcgeom/transforms/tree.py +5 -1
  29. swcgeom/utils/__init__.py +1 -0
  30. swcgeom/utils/neuromorpho.py +425 -300
  31. swcgeom/utils/numpy_helper.py +14 -4
  32. swcgeom/utils/plotter_2d.py +130 -0
  33. swcgeom/utils/renderer.py +28 -139
  34. swcgeom/utils/sdf.py +5 -1
  35. {swcgeom-0.15.0.dist-info → swcgeom-0.17.0.dist-info}/METADATA +3 -3
  36. swcgeom-0.17.0.dist-info/RECORD +65 -0
  37. {swcgeom-0.15.0.dist-info → swcgeom-0.17.0.dist-info}/WHEEL +1 -1
  38. swcgeom/analysis/branch_features.py +0 -67
  39. swcgeom/analysis/path_features.py +0 -37
  40. swcgeom-0.15.0.dist-info/RECORD +0 -62
  41. {swcgeom-0.15.0.dist-info → swcgeom-0.17.0.dist-info}/LICENSE +0 -0
  42. {swcgeom-0.15.0.dist-info → swcgeom-0.17.0.dist-info}/top_level.txt +0 -0
swcgeom/analysis/sholl.py CHANGED
@@ -23,10 +23,15 @@ YLABLE = "Count of Intersections"
23
23
  class Sholl:
24
24
  """Sholl analysis.
25
25
 
26
+ Implementation of original Sholl analysis as described in [1]_. The
27
+ Sholl analysis is a method to quantify the spatial distribution of
28
+ neuronal processes. It is based on the number of intersections of
29
+ concentric circles with the neuronal processes.
30
+
26
31
  References
27
32
  ----------
28
- [1] Dendritic organization in the neurons of the visual and motor
29
- cortices of the cat J. Anat., 87 (1953), pp. 387-406
33
+ .. [1] Dendritic organization in the neurons of the visual and
34
+ motor cortices of the cat J. Anat., 87 (1953), pp. 387-406
30
35
  """
31
36
 
32
37
  tree: Tree
@@ -156,6 +161,12 @@ class Sholl:
156
161
  return self.get_rs(self.rmax, steps)
157
162
 
158
163
  def get_count(self) -> npt.NDArray[np.int32]:
164
+ """Get the count of intersection.
165
+
166
+ .. deprecated:: 0.5.0
167
+ Use :meth:`Sholl.get` instead.
168
+ """
169
+
159
170
  warnings.warn(
160
171
  "`Sholl.get_count` has been renamed to `get` since v0.5.0, "
161
172
  "and will be removed in next version",
@@ -164,6 +175,12 @@ class Sholl:
164
175
  return self.get().astype(np.int32)
165
176
 
166
177
  def avg(self) -> float:
178
+ """Get the average of the count of intersection.
179
+
180
+ .. deprecated:: 0.6.0
181
+ Use :meth:`Shool(x).get().mean()` instead.
182
+ """
183
+
167
184
  warnings.warn(
168
185
  "`Sholl.avg` has been deprecated since v0.6.0 and will be "
169
186
  "removed in next version, use `Shool(x).get().mean()` "
@@ -173,6 +190,12 @@ class Sholl:
173
190
  return self.get().mean()
174
191
 
175
192
  def std(self) -> float:
193
+ """Get the std of the count of intersection.
194
+
195
+ .. deprecated:: 0.6.0
196
+ Use :meth:`Shool(x).get().std()` instead.
197
+ """
198
+
176
199
  warnings.warn(
177
200
  "`Sholl.std` has been deprecate since v0.6.0 and will be "
178
201
  "removed in next version, use `Shool(x).get().std()` "
@@ -182,6 +205,12 @@ class Sholl:
182
205
  return self.get().std()
183
206
 
184
207
  def sum(self) -> int:
208
+ """Get the sum of the count of intersection.
209
+
210
+ .. deprecated:: 0.6.0
211
+ Use :meth:`Shool(x).get().sum()` instead.
212
+ """
213
+
185
214
  warnings.warn(
186
215
  "`Sholl.sum` has been deprecate since v0.6.0 and will be "
187
216
  "removed in next version, use `Shool(x).get().sum()` "
swcgeom/core/__init__.py CHANGED
@@ -3,6 +3,10 @@
3
3
  from swcgeom.core import swc_utils
4
4
  from swcgeom.core.branch import *
5
5
  from swcgeom.core.branch_tree import *
6
+ from swcgeom.core.compartment import ( # Segment and Segments don't expose
7
+ Compartment,
8
+ Compartments,
9
+ )
6
10
  from swcgeom.core.node import *
7
11
  from swcgeom.core.path import *
8
12
  from swcgeom.core.population import *
swcgeom/core/branch.py CHANGED
@@ -5,8 +5,8 @@ from typing import Generic, Iterable, List
5
5
  import numpy as np
6
6
  import numpy.typing as npt
7
7
 
8
+ from swcgeom.core.compartment import Compartment, Compartments
8
9
  from swcgeom.core.path import Path
9
- from swcgeom.core.segment import Segment, Segments
10
10
  from swcgeom.core.swc import DictSWC, SWCTypeVar
11
11
 
12
12
  __all__ = ["Branch"]
@@ -24,9 +24,11 @@ class Branch(Path, Generic[SWCTypeVar]):
24
24
  attach: SWCTypeVar
25
25
  idx: npt.NDArray[np.int32]
26
26
 
27
- class Segment(Segment["Branch"]):
27
+ class Compartment(Compartment["Branch"]):
28
28
  """Segment of branch."""
29
29
 
30
+ Segment = Compartment # Alias
31
+
30
32
  def __repr__(self) -> str:
31
33
  return f"Neuron branch with {len(self)} nodes."
32
34
 
@@ -36,8 +38,11 @@ class Branch(Path, Generic[SWCTypeVar]):
36
38
  def get_ndata(self, key: str) -> npt.NDArray:
37
39
  return self.attach.get_ndata(key)[self.idx]
38
40
 
39
- def get_segments(self) -> Segments[Segment]:
40
- return Segments(self.Segment(self, n.pid, n.id) for n in self[1:])
41
+ def get_compartments(self) -> Compartments[Compartment]:
42
+ return Compartments(self.Compartment(self, n.pid, n.id) for n in self[1:])
43
+
44
+ def get_segments(self) -> Compartments[Compartment]:
45
+ return self.get_compartments() # Alias
41
46
 
42
47
  def detach(self) -> "Branch[DictSWC]":
43
48
  """Detach from current attached object."""
@@ -9,11 +9,11 @@ from swcgeom.core.path import Path
9
9
  from swcgeom.core.swc import DictSWC, SWCTypeVar
10
10
  from swcgeom.core.swc_utils import SWCNames, get_names
11
11
 
12
- __all__ = ["Segment", "Segments"]
12
+ __all__ = ["Compartment", "Compartments", "Segment", "Segments"]
13
13
 
14
14
 
15
- class Segment(Path, Generic[SWCTypeVar]):
16
- r"""Segment attached to external object."""
15
+ class Compartment(Path, Generic[SWCTypeVar]):
16
+ r"""Compartment attached to external object."""
17
17
 
18
18
  attach: SWCTypeVar
19
19
  idx: npt.NDArray[np.int32]
@@ -27,7 +27,7 @@ class Segment(Path, Generic[SWCTypeVar]):
27
27
  def get_ndata(self, key: str) -> npt.NDArray:
28
28
  return self.attach.get_ndata(key)[self.idx]
29
29
 
30
- def detach(self) -> "Segment[DictSWC]":
30
+ def detach(self) -> "Compartment[DictSWC]":
31
31
  """Detach from current attached object."""
32
32
  # pylint: disable=consider-using-dict-items
33
33
  attact = DictSWC(
@@ -37,18 +37,18 @@ class Segment(Path, Generic[SWCTypeVar]):
37
37
  )
38
38
  attact.ndata[self.names.id] = self.id()
39
39
  attact.ndata[self.names.pid] = self.pid()
40
- return Segment(attact, 0, 1)
40
+ return Compartment(attact, 0, 1)
41
41
 
42
42
 
43
- SegmentT = TypeVar("SegmentT", bound=Segment)
43
+ T = TypeVar("T", bound=Compartment)
44
44
 
45
45
 
46
- class Segments(List[SegmentT]):
47
- r"""Segments contains a set of segments."""
46
+ class Compartments(List[T]):
47
+ r"""Comparments contains a set of comparment."""
48
48
 
49
49
  names: SWCNames
50
50
 
51
- def __init__(self, segments: Iterable[SegmentT]) -> None:
51
+ def __init__(self, segments: Iterable[T]) -> None:
52
52
  super().__init__(segments)
53
53
  self.names = self[0].names if len(self) > 0 else get_names()
54
54
 
@@ -94,3 +94,8 @@ class Segments(List[SegmentT]):
94
94
  The order of axis 1 is (parent, current node).
95
95
  """
96
96
  return np.array([s.get_ndata(key) for s in self])
97
+
98
+
99
+ # Aliases
100
+ Segment = Compartment
101
+ Segments = Compartments
swcgeom/core/node.py CHANGED
@@ -95,14 +95,6 @@ class Node(Generic[SWCTypeVar]):
95
95
  items = [self.id, self.type, x, y, z, r, self.pid]
96
96
  return " ".join(map(str, items))
97
97
 
98
- def child_ids(self) -> npt.NDArray[np.int32]:
99
- warnings.warn(
100
- "`Node.child_ids` has been deprecated since v0.3.1 and "
101
- "will be removed in next version",
102
- DeprecationWarning,
103
- )
104
- return self.attach.id()[self.attach.pid() == self.id]
105
-
106
98
  def is_bifurcation(self) -> bool:
107
99
  return np.count_nonzero(self.attach.pid() == self.id) > 1
108
100
 
swcgeom/core/path.py CHANGED
@@ -1,5 +1,6 @@
1
- """Nueron node."""
1
+ """Nueron path."""
2
2
 
3
+ import warnings
3
4
  from typing import Generic, Iterable, Iterator, List, overload
4
5
 
5
6
  import numpy as np
@@ -12,7 +13,7 @@ __all__ = ["Path"]
12
13
 
13
14
 
14
15
  class Path(SWCLike, Generic[SWCTypeVar]):
15
- """Neural path.
16
+ """Neuron path.
16
17
 
17
18
  A path is a linear set of points without bifurcations.
18
19
  """
@@ -31,7 +32,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
31
32
  self.source = self.attach.source
32
33
 
33
34
  def __iter__(self) -> Iterator[Node]:
34
- return (self.get_node(i) for i in range(len(self)))
35
+ return (self.node(i) for i in range(len(self)))
35
36
 
36
37
  def __len__(self) -> int:
37
38
  return self.id().shape[0]
@@ -50,7 +51,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
50
51
 
51
52
  def __getitem__(self, key):
52
53
  if isinstance(key, slice):
53
- return [self.get_node(i) for i in range(*key.indices(len(self)))]
54
+ return [self.node(i) for i in range(*key.indices(len(self)))]
54
55
 
55
56
  if isinstance(key, (int, np.integer)):
56
57
  length = len(self)
@@ -60,7 +61,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
60
61
  if key < 0: # Handle negative indices
61
62
  key += length
62
63
 
63
- return self.get_node(key)
64
+ return self.node(key)
64
65
 
65
66
  if isinstance(key, str):
66
67
  return self.get_ndata(key)
@@ -74,6 +75,20 @@ class Path(SWCLike, Generic[SWCTypeVar]):
74
75
  return self.attach.get_ndata(key)[self.idx]
75
76
 
76
77
  def get_node(self, idx: int | np.integer) -> Node:
78
+ """Get the count of intersection.
79
+
80
+ .. deprecated:: 0.16.0
81
+ Use :meth:`path.node` instead.
82
+ """
83
+
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
+ return self.node(idx)
90
+
91
+ def node(self, idx: int | np.integer) -> Node:
77
92
  return self.Node(self, idx)
78
93
 
79
94
  def detach(self) -> "Path[DictSWC]":
@@ -129,7 +144,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
129
144
  The end-to-end straight-line distance between start point and
130
145
  end point.
131
146
  """
132
- return np.linalg.norm(self.get_node(-1).xyz() - self.get_node(0).xyz()).item()
147
+ return np.linalg.norm(self.node(-1).xyz() - self.node(0).xyz()).item()
133
148
 
134
149
  def tortuosity(self) -> float:
135
150
  """Tortuosity of path.
@@ -2,21 +2,26 @@
2
2
 
3
3
  import os
4
4
  import warnings
5
+ from concurrent.futures import ProcessPoolExecutor
5
6
  from functools import reduce
6
7
  from typing import (
7
8
  Any,
9
+ Callable,
8
10
  Dict,
9
11
  Iterable,
10
12
  Iterator,
11
13
  List,
12
14
  Optional,
13
15
  Protocol,
16
+ TypeVar,
14
17
  cast,
15
18
  overload,
16
19
  )
17
20
 
18
21
  import numpy as np
19
22
  import numpy.typing as npt
23
+ from tqdm.contrib.concurrent import process_map
24
+ from typing_extensions import Self
20
25
 
21
26
  from swcgeom.core.swc import eswc_cols
22
27
  from swcgeom.core.tree import Tree
@@ -24,6 +29,9 @@ from swcgeom.core.tree import Tree
24
29
  __all__ = ["LazyLoadingTrees", "ChainTrees", "Population", "Populations"]
25
30
 
26
31
 
32
+ T = TypeVar("T")
33
+
34
+
27
35
  class Trees(Protocol):
28
36
  """Trees protocol support index and len."""
29
37
 
@@ -48,6 +56,7 @@ class LazyLoadingTrees:
48
56
  kwargs : Dict[str, Any]
49
57
  Forwarding to `Tree.from_swc`
50
58
  """
59
+
51
60
  super().__init__()
52
61
  self.swcs = list(swcs)
53
62
  self.trees = [None for _ in swcs]
@@ -61,6 +70,9 @@ class LazyLoadingTrees:
61
70
  def __len__(self) -> int:
62
71
  return len(self.swcs)
63
72
 
73
+ def __iter__(self) -> Iterator[Tree]:
74
+ return (self[i] for i in range(self.__len__()))
75
+
64
76
  def load(self, key: int) -> None:
65
77
  if self.trees[key] is None:
66
78
  self.trees[key] = Tree.from_swc(self.swcs[key], **self.kwargs)
@@ -92,6 +104,9 @@ class ChainTrees:
92
104
  def __len__(self) -> int:
93
105
  return self.cumsum[-1].item()
94
106
 
107
+ def __iter__(self) -> Iterator[Tree]:
108
+ return (self[i] for i in range(self.__len__()))
109
+
95
110
 
96
111
  class Population:
97
112
  """Neuron population."""
@@ -153,15 +168,40 @@ class Population:
153
168
  def __repr__(self) -> str:
154
169
  return f"Neuron population in '{self.root}'"
155
170
 
171
+ def map(
172
+ self,
173
+ fn: Callable[[Tree], T],
174
+ *,
175
+ max_worker: Optional[int] = None,
176
+ verbose: bool = False,
177
+ ) -> Iterator[T]:
178
+ """Map a function to all trees in the population.
179
+
180
+ This is a straightforward interface for parallelizing
181
+ computations. The parameters are intentionally kept simple and
182
+ user-friendly. For more advanced control, consider using
183
+ `concurrent.futures` directly.
184
+ """
185
+
186
+ trees = (t for t in self.trees)
187
+
188
+ if verbose:
189
+ results = process_map(fn, trees, max_workers=max_worker)
190
+ else:
191
+ with ProcessPoolExecutor(max_worker) as p:
192
+ results = p.map(fn, trees)
193
+
194
+ return results
195
+
156
196
  @classmethod
157
- def from_swc(cls, root: str, ext: str = ".swc", **kwargs) -> "Population":
197
+ def from_swc(cls, root: str, ext: str = ".swc", **kwargs) -> Self:
158
198
  if not os.path.exists(root):
159
199
  raise FileNotFoundError(
160
200
  f"the root does not refers to an existing directory: {root}"
161
201
  )
162
202
 
163
203
  swcs = cls.find_swcs(root, ext)
164
- return Population(LazyLoadingTrees(swcs, **kwargs), root=root)
204
+ return cls(LazyLoadingTrees(swcs, **kwargs), root=root)
165
205
 
166
206
  @classmethod
167
207
  def from_eswc(
@@ -170,7 +210,7 @@ class Population:
170
210
  ext: str = ".eswc",
171
211
  extra_cols: Optional[Iterable[str]] = None,
172
212
  **kwargs,
173
- ) -> "Population":
213
+ ) -> Self:
174
214
  extra_cols = list(extra_cols) if extra_cols is not None else []
175
215
  extra_cols.extend(k for k, t in eswc_cols)
176
216
  return cls.from_swc(root, ext, extra_cols=extra_cols, **kwargs)
@@ -235,7 +275,7 @@ class Populations:
235
275
  return Population(ChainTrees(p.trees for p in self.populations))
236
276
 
237
277
  @classmethod
238
- def from_swc( # pylint: disable=too-many-arguments
278
+ def from_swc(
239
279
  cls,
240
280
  roots: Iterable[str],
241
281
  ext: str = ".swc",
@@ -243,7 +283,7 @@ class Populations:
243
283
  check_same: bool = False,
244
284
  labels: Optional[Iterable[str]] = None,
245
285
  **kwargs,
246
- ) -> "Populations":
286
+ ) -> Self:
247
287
  """Get population from dirs.
248
288
 
249
289
  Parameters
@@ -275,7 +315,7 @@ class Populations:
275
315
  )
276
316
  for i, d in enumerate(roots)
277
317
  ]
278
- return Populations(populations, labels=labels)
318
+ return cls(populations, labels=labels)
279
319
 
280
320
  @classmethod
281
321
  def from_eswc(
@@ -285,7 +325,7 @@ class Populations:
285
325
  *,
286
326
  ext: str = ".eswc",
287
327
  **kwargs,
288
- ) -> "Populations":
328
+ ) -> Self:
289
329
  extra_cols = list(extra_cols) if extra_cols is not None else []
290
330
  extra_cols.extend(k for k, t in eswc_cols)
291
331
  return cls.from_swc(roots, extra_cols=extra_cols, ext=ext, **kwargs)
@@ -6,11 +6,16 @@ This module is deprecated, please use `~.transforms.LinesToTree`
6
6
  instead.
7
7
  """
8
8
 
9
-
10
9
  __all__ = ["assemble_lines", "try_assemble_lines"]
11
10
 
12
11
 
13
12
  def assemble_lines(*args, **kwargs):
13
+ """Assemble lines to tree.
14
+
15
+ .. deprecated:: 0.15.0
16
+ Use :meth:`~.transforms.LinesToTree` instead.
17
+ """
18
+
14
19
  raise DeprecationWarning(
15
20
  "`assemble_lines` has been replaced by "
16
21
  "`~.transforms.LinesToTree` because it can be easy assemble "
@@ -19,6 +24,12 @@ def assemble_lines(*args, **kwargs):
19
24
 
20
25
 
21
26
  def try_assemble_lines(*args, **kwargs):
27
+ """Try assemble lines to tree.
28
+
29
+ .. deprecated:: 0.15.0
30
+ Use :meth:`~.transforms.LinesToTree` instead.
31
+ """
32
+
22
33
  raise DeprecationWarning(
23
34
  "`try_assemble_lines` has been replaced by "
24
35
  "`~.transforms.LinesToTree` because it can be easy assemble "
@@ -1,7 +1,16 @@
1
1
  """Base SWC format utils."""
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import Callable, List, Literal, Optional, Tuple, TypeVar, overload
4
+ from typing import (
5
+ Callable,
6
+ List,
7
+ Literal,
8
+ NamedTuple,
9
+ Optional,
10
+ Tuple,
11
+ TypeVar,
12
+ overload,
13
+ )
5
14
 
6
15
  import numpy as np
7
16
  import numpy.typing as npt
@@ -23,8 +32,7 @@ T, K = TypeVar("T"), TypeVar("K")
23
32
  Topology = Tuple[npt.NDArray[np.int32], npt.NDArray[np.int32]] # (id, pid)
24
33
 
25
34
 
26
- @dataclass
27
- class SWCNames:
35
+ class SWCNames(NamedTuple):
28
36
  """SWC format column names."""
29
37
 
30
38
  id: str = "id"
@@ -46,8 +54,7 @@ def get_names(names: Optional[SWCNames] = None) -> SWCNames:
46
54
  return names or swc_names
47
55
 
48
56
 
49
- @dataclass
50
- class SWCTypes:
57
+ class SWCTypes(NamedTuple):
51
58
  """SWC format types.
52
59
 
53
60
  See Also
@@ -10,7 +10,6 @@ import pandas as pd
10
10
  from swcgeom.core.swc_utils.base import SWCNames, Topology, get_dsu, get_names, traverse
11
11
  from swcgeom.utils import DisjointSetUnion
12
12
 
13
-
14
13
  __all__ = [
15
14
  "is_single_root",
16
15
  "is_bifurcate",
@@ -83,6 +82,12 @@ def has_cyclic(topology: Topology) -> bool:
83
82
 
84
83
 
85
84
  def check_single_root(*args, **kwargs) -> bool:
85
+ """Check if the tree is single root.
86
+
87
+ .. deprecated:: 0.5.0
88
+ Use :meth:`is_single_root` instead.
89
+ """
90
+
86
91
  warnings.warn(
87
92
  "`check_single_root` has been renamed to `is_single_root` since"
88
93
  "v0.5.0, and will be removed in next version",
@@ -94,7 +99,12 @@ def check_single_root(*args, **kwargs) -> bool:
94
99
  def is_binary_tree(
95
100
  df: pd.DataFrame, exclude_root: bool = True, *, names: Optional[SWCNames] = None
96
101
  ) -> bool:
97
- """Check is it a binary tree."""
102
+ """Check is it a binary tree.
103
+
104
+ .. deprecated:: 0.8.0
105
+ Use :meth:`is_bifurcate` instead.
106
+ """
107
+
98
108
  warnings.warn(
99
109
  "`is_binary_tree` has been replaced by to `is_bifurcate` since"
100
110
  "v0.8.0, and will be removed in next version",
swcgeom/core/tree.py CHANGED
@@ -22,9 +22,9 @@ import numpy.typing as npt
22
22
  import pandas as pd
23
23
 
24
24
  from swcgeom.core.branch import Branch
25
+ from swcgeom.core.compartment import Compartment, Compartments
25
26
  from swcgeom.core.node import Node
26
27
  from swcgeom.core.path import Path
27
- from swcgeom.core.segment import Segment, Segments
28
28
  from swcgeom.core.swc import DictSWC, eswc_cols
29
29
  from swcgeom.core.swc_utils import SWCNames, get_names, read_swc, traverse
30
30
  from swcgeom.core.tree_utils_impl import Mapping, get_subtree_impl
@@ -48,15 +48,6 @@ class Tree(DictSWC):
48
48
  children = self.attach.id()[self.attach.pid() == self.id]
49
49
  return [Tree.Node(self.attach, idx) for idx in children]
50
50
 
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
51
  def branch(self) -> "Tree.Branch":
61
52
  ns: List["Tree.Node"] = [self]
62
53
  while not ns[-1].is_bifurcation() and (p := ns[-1].parent()) is not None:
@@ -115,9 +106,11 @@ class Tree(DictSWC):
115
106
  # TODO: should returns `Tree.Node`
116
107
  """Neural path."""
117
108
 
118
- class Segment(Segment["Tree"]):
109
+ class Compartment(Compartment["Tree"]):
119
110
  # TODO: should returns `Tree.Node`
120
- """Neural segment."""
111
+ """Neural compartment."""
112
+
113
+ Segment = Compartment # Alias
121
114
 
122
115
  class Branch(Branch["Tree"]):
123
116
  # TODO: should returns `Tree.Node`
@@ -127,33 +120,33 @@ class Tree(DictSWC):
127
120
  self,
128
121
  n_nodes: int,
129
122
  *,
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
123
  source: str = "",
140
124
  comments: Optional[Iterable[str]] = None,
141
125
  names: Optional[SWCNames] = None,
142
126
  **kwargs: npt.NDArray,
143
127
  ) -> None:
144
128
  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
129
+
130
+ if names.id not in kwargs:
131
+ kwargs[names.id] = np.arange(0, n_nodes, step=1, dtype=np.int32)
132
+
133
+ if names.pid not in kwargs:
134
+ kwargs[names.pid] = np.arange(-1, n_nodes - 1, step=1, dtype=np.int32)
147
135
 
148
136
  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),
137
+ names.id: padding1d(n_nodes, kwargs.pop(names.id, None), dtype=np.int32),
138
+ names.type: padding1d(
139
+ n_nodes, kwargs.pop(names.type, None), dtype=np.int32
140
+ ),
141
+ names.x: padding1d(n_nodes, kwargs.pop(names.x, None), dtype=np.float32),
142
+ names.y: padding1d(n_nodes, kwargs.pop(names.y, None), dtype=np.float32),
143
+ names.z: padding1d(n_nodes, kwargs.pop(names.z, None), dtype=np.float32),
144
+ names.r: padding1d(
145
+ n_nodes, kwargs.pop(names.r, None), dtype=np.float32, padding_value=1
146
+ ),
147
+ names.pid: padding1d(n_nodes, kwargs.pop(names.pid, None), dtype=np.int32),
156
148
  }
149
+ # ? padding other columns
157
150
  super().__init__(
158
151
  **ndata, **kwargs, source=source, comments=comments, names=names
159
152
  )
@@ -222,13 +215,16 @@ class Tree(DictSWC):
222
215
  tip_ids = np.setdiff1d(self.id(), self.pid(), assume_unique=True)
223
216
  return [self.node(i) for i in tip_ids]
224
217
 
225
- def get_segments(self) -> Segments[Segment]:
226
- return Segments(self.Segment(self, n.pid, n.id) for n in self[1:])
218
+ def get_compartments(self) -> Compartments[Compartment]:
219
+ return Compartments(self.Compartment(self, n.pid, n.id) for n in self[1:])
227
220
 
228
- def get_branches(self) -> List[Branch]:
229
- Info = Tuple[List[Tree.Branch], List[int]]
221
+ def get_segments(self) -> Compartments[Compartment]: # Alias
222
+ return self.get_compartments()
230
223
 
231
- def collect_branches(node: "Tree.Node", pre: List[Info]) -> Info:
224
+ def get_branches(self) -> List[Branch]:
225
+ def collect_branches(
226
+ node: "Tree.Node", pre: List[Tuple[List[Tree.Branch], List[int]]]
227
+ ) -> Tuple[List[Tree.Branch], List[int]]:
232
228
  if len(pre) == 1:
233
229
  branches, child = pre[0]
234
230
  child.append(node.id)
@@ -251,7 +247,6 @@ class Tree(DictSWC):
251
247
  def get_paths(self) -> List[Path]:
252
248
  """Get all path from soma to tips."""
253
249
  path_dic: Dict[int, List[int]] = {}
254
- Paths = List[List[int]]
255
250
 
256
251
  def assign_path(n: Tree.Node, pre_path: List[int] | None) -> List[int]:
257
252
  path = [] if pre_path is None else pre_path.copy()
@@ -259,7 +254,9 @@ class Tree(DictSWC):
259
254
  path_dic[n.id] = path
260
255
  return path
261
256
 
262
- def collect_path(n: Tree.Node, children: List[Paths]) -> Paths:
257
+ def collect_path(
258
+ n: Tree.Node, children: List[List[List[int]]]
259
+ ) -> List[List[int]]:
263
260
  if len(children) == 0:
264
261
  return [path_dic[n.id]]
265
262
 
@@ -101,11 +101,15 @@ def to_sub_tree(swc_like: SWCLike, sub: Topology) -> Tuple[Tree, Dict[int, int]]
101
101
  but if the node you remove is not a leaf node, you need to use
102
102
  `propagate_remove` to remove all children.
103
103
 
104
+ .. deprecated:: 0.6.0
105
+ Use :meth:`to_subtree` instead.
106
+
104
107
  Returns
105
108
  -------
106
109
  tree : Tree
107
110
  id_map : Dict[int, int]
108
111
  """
112
+
109
113
  warnings.warn(
110
114
  "`to_sub_tree` will be removed in v0.6.0, it is replaced by "
111
115
  "`to_subtree` beacuse it is easy to use, and this will be "
@@ -1,4 +1,9 @@
1
- """Play augment in image stack."""
1
+ """Play augment in image stack.
2
+
3
+ Notes
4
+ -----
5
+ This is expremental code, and the API is subject to change.
6
+ """
2
7
 
3
8
  import random
4
9
  from typing import List, Literal, Optional