swcgeom 0.14.0__py3-none-any.whl → 0.16.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 (45) hide show
  1. swcgeom/_version.py +2 -2
  2. swcgeom/analysis/lmeasure.py +821 -0
  3. swcgeom/analysis/sholl.py +31 -2
  4. swcgeom/core/__init__.py +4 -0
  5. swcgeom/core/branch.py +9 -4
  6. swcgeom/core/branch_tree.py +2 -3
  7. swcgeom/core/{segment.py → compartment.py} +14 -9
  8. swcgeom/core/node.py +0 -8
  9. swcgeom/core/path.py +21 -6
  10. swcgeom/core/population.py +42 -3
  11. swcgeom/core/swc_utils/assembler.py +20 -138
  12. swcgeom/core/swc_utils/base.py +12 -5
  13. swcgeom/core/swc_utils/checker.py +12 -2
  14. swcgeom/core/swc_utils/subtree.py +2 -2
  15. swcgeom/core/tree.py +53 -49
  16. swcgeom/core/tree_utils.py +27 -5
  17. swcgeom/core/tree_utils_impl.py +22 -6
  18. swcgeom/images/augmentation.py +6 -1
  19. swcgeom/images/contrast.py +107 -0
  20. swcgeom/images/folder.py +111 -29
  21. swcgeom/images/io.py +79 -40
  22. swcgeom/transforms/__init__.py +2 -0
  23. swcgeom/transforms/base.py +41 -21
  24. swcgeom/transforms/branch.py +5 -5
  25. swcgeom/transforms/geometry.py +42 -18
  26. swcgeom/transforms/image_preprocess.py +100 -0
  27. swcgeom/transforms/image_stack.py +46 -28
  28. swcgeom/transforms/images.py +76 -6
  29. swcgeom/transforms/mst.py +10 -18
  30. swcgeom/transforms/neurolucida_asc.py +495 -0
  31. swcgeom/transforms/population.py +2 -2
  32. swcgeom/transforms/tree.py +12 -14
  33. swcgeom/transforms/tree_assembler.py +85 -19
  34. swcgeom/utils/__init__.py +1 -0
  35. swcgeom/utils/neuromorpho.py +425 -300
  36. swcgeom/utils/numpy_helper.py +14 -4
  37. swcgeom/utils/plotter_2d.py +130 -0
  38. swcgeom/utils/renderer.py +28 -139
  39. swcgeom/utils/sdf.py +5 -1
  40. {swcgeom-0.14.0.dist-info → swcgeom-0.16.0.dist-info}/METADATA +3 -3
  41. swcgeom-0.16.0.dist-info/RECORD +67 -0
  42. {swcgeom-0.14.0.dist-info → swcgeom-0.16.0.dist-info}/WHEEL +1 -1
  43. swcgeom-0.14.0.dist-info/RECORD +0 -62
  44. {swcgeom-0.14.0.dist-info → swcgeom-0.16.0.dist-info}/LICENSE +0 -0
  45. {swcgeom-0.14.0.dist-info → swcgeom-0.16.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."""
@@ -5,7 +5,6 @@ from typing import Dict, List
5
5
 
6
6
  import numpy as np
7
7
  import pandas as pd
8
- from typing_extensions import Self
9
8
 
10
9
  from swcgeom.core.branch import Branch
11
10
  from swcgeom.core.swc_utils import to_sub_topology
@@ -31,7 +30,7 @@ class BranchTree(Tree):
31
30
  return self.branches[idx]
32
31
 
33
32
  @classmethod
34
- def from_tree(cls, tree: Tree) -> Self:
33
+ def from_tree(cls, tree: Tree) -> "BranchTree":
35
34
  """Generating a branch tree from tree."""
36
35
 
37
36
  branches = tree.get_branches()
@@ -56,6 +55,6 @@ class BranchTree(Tree):
56
55
  return branch_tree
57
56
 
58
57
  @classmethod
59
- def from_data_frame(cls, df: pd.DataFrame, *args, **kwargs) -> Self:
58
+ def from_data_frame(cls, df: pd.DataFrame, *args, **kwargs) -> "BranchTree":
60
59
  tree = super().from_data_frame(df, *args, **kwargs)
61
60
  return cls.from_tree(tree)
@@ -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,25 @@
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
20
24
  from typing_extensions import Self
21
25
 
22
26
  from swcgeom.core.swc import eswc_cols
@@ -25,6 +29,9 @@ from swcgeom.core.tree import Tree
25
29
  __all__ = ["LazyLoadingTrees", "ChainTrees", "Population", "Populations"]
26
30
 
27
31
 
32
+ T = TypeVar("T")
33
+
34
+
28
35
  class Trees(Protocol):
29
36
  """Trees protocol support index and len."""
30
37
 
@@ -49,6 +56,7 @@ class LazyLoadingTrees:
49
56
  kwargs : Dict[str, Any]
50
57
  Forwarding to `Tree.from_swc`
51
58
  """
59
+
52
60
  super().__init__()
53
61
  self.swcs = list(swcs)
54
62
  self.trees = [None for _ in swcs]
@@ -62,6 +70,9 @@ class LazyLoadingTrees:
62
70
  def __len__(self) -> int:
63
71
  return len(self.swcs)
64
72
 
73
+ def __iter__(self) -> Iterator[Tree]:
74
+ return (self[i] for i in range(self.__len__()))
75
+
65
76
  def load(self, key: int) -> None:
66
77
  if self.trees[key] is None:
67
78
  self.trees[key] = Tree.from_swc(self.swcs[key], **self.kwargs)
@@ -93,6 +104,9 @@ class ChainTrees:
93
104
  def __len__(self) -> int:
94
105
  return self.cumsum[-1].item()
95
106
 
107
+ def __iter__(self) -> Iterator[Tree]:
108
+ return (self[i] for i in range(self.__len__()))
109
+
96
110
 
97
111
  class Population:
98
112
  """Neuron population."""
@@ -154,6 +168,31 @@ class Population:
154
168
  def __repr__(self) -> str:
155
169
  return f"Neuron population in '{self.root}'"
156
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
+
157
196
  @classmethod
158
197
  def from_swc(cls, root: str, ext: str = ".swc", **kwargs) -> Self:
159
198
  if not os.path.exists(root):
@@ -162,7 +201,7 @@ class Population:
162
201
  )
163
202
 
164
203
  swcs = cls.find_swcs(root, ext)
165
- return Population(LazyLoadingTrees(swcs, **kwargs), root=root)
204
+ return cls(LazyLoadingTrees(swcs, **kwargs), root=root)
166
205
 
167
206
  @classmethod
168
207
  def from_eswc(
@@ -236,7 +275,7 @@ class Populations:
236
275
  return Population(ChainTrees(p.trees for p in self.populations))
237
276
 
238
277
  @classmethod
239
- def from_swc( # pylint: disable=too-many-arguments
278
+ def from_swc(
240
279
  cls,
241
280
  roots: Iterable[str],
242
281
  ext: str = ".swc",
@@ -268,7 +307,7 @@ class Populations:
268
307
 
269
308
  fs = [inter for _ in roots]
270
309
  elif check_same:
271
- assert reduce(lambda a, b: a == b, fs), "not the same among populations"
310
+ assert [fs[0] == a for a in fs[1:]], "not the same among populations"
272
311
 
273
312
  populations = [
274
313
  Population(
@@ -1,155 +1,37 @@
1
- """Assemble lines to swc."""
1
+ """Assemble lines to swc.
2
2
 
3
- import warnings
4
- from copy import copy
5
- from typing import Iterable, List, Optional, Tuple
6
-
7
- import numpy as np
8
- import pandas as pd
9
-
10
- from swcgeom.core.swc_utils.base import SWCNames, get_names
11
- from swcgeom.core.swc_utils.normalizer import link_roots_to_nearest_, sort_nodes_
3
+ Notes
4
+ -----
5
+ This module is deprecated, please use `~.transforms.LinesToTree`
6
+ instead.
7
+ """
12
8
 
13
9
  __all__ = ["assemble_lines", "try_assemble_lines"]
14
10
 
15
11
 
16
- def assemble_lines(*args, **kwargs) -> pd.DataFrame:
17
- """Assemble lines to a tree.
18
-
19
- Assemble all the lines into a set of subtrees, and then connect
20
- them.
12
+ def assemble_lines(*args, **kwargs):
13
+ """Assemble lines to tree.
21
14
 
22
- Parameters
23
- ----------
24
- lines : List of ~pd.DataFrame
25
- An array of tables containing a line, columns should follwing
26
- the swc.
27
- **kwargs
28
- Forwarding to `try_assemble_lines`
29
-
30
- Returns
31
- -------
32
- tree : ~pd.DataFrame
33
-
34
- See Also
35
- --------
36
- ~swcgeom.core.swc_utils.try_assemble_lines
15
+ .. deprecated:: 0.15.0
16
+ Use :meth:`~.transforms.LinesToTree` instead.
37
17
  """
38
- warnings.warn(
18
+
19
+ raise DeprecationWarning(
39
20
  "`assemble_lines` has been replaced by "
40
21
  "`~.transforms.LinesToTree` because it can be easy assemble "
41
- "with other tansformations, and this will be removed in next "
42
- "version.",
43
- DeprecationWarning,
22
+ "with other tansformations.",
44
23
  )
45
- return assemble_lines_impl(*args, **kwargs)
46
24
 
47
25
 
48
- def try_assemble_lines(*args, **kwargs) -> Tuple[pd.DataFrame, List[pd.DataFrame]]:
49
- """Trying assemble lines to a tree.
26
+ def try_assemble_lines(*args, **kwargs):
27
+ """Try assemble lines to tree.
50
28
 
51
- Treat the first line as a tree, find a line whose shortest distance
52
- between the tree and the line is less than threshold, merge it into
53
- the tree, repeat until there are no line to merge, return tree and
54
- the remaining lines.
55
-
56
- Parameters
57
- ----------
58
- lines : List of ~pd.DataFrame
59
- An array of tables containing a line, columns should follwing
60
- the swc.
61
- undirected : bool, default `True`
62
- Both ends of a line can be considered connection point. If
63
- `False`, only the starting point.
64
- thre : float, default `0.2`
65
- Connection threshold.
66
- id_offset : int, default `0`
67
- The offset of the line node id.
68
- sort_nodes : bool, default `True`
69
- sort nodes of subtree
70
- names : SWCNames, optional
71
-
72
- Returns
73
- -------
74
- tree : ~pd.DataFrame
75
- remaining_lines : List of ~pd.DataFrame
29
+ .. deprecated:: 0.15.0
30
+ Use :meth:`~.transforms.LinesToTree` instead.
76
31
  """
77
- warnings.warn(
32
+
33
+ raise DeprecationWarning(
78
34
  "`try_assemble_lines` has been replaced by "
79
35
  "`~.transforms.LinesToTree` because it can be easy assemble "
80
- "with other tansformations, and this will be removed in next "
81
- "version.",
82
- DeprecationWarning,
36
+ "with other tansformations.",
83
37
  )
84
- return try_assemble_lines_impl(*args, **kwargs)
85
-
86
-
87
- # TODO: move the following codes to `transforms` module
88
-
89
- EPS = 1e-5
90
-
91
-
92
- def assemble_lines_impl(lines: Iterable[pd.DataFrame], **kwargs) -> pd.DataFrame:
93
- tree, lines = try_assemble_lines_impl(lines, sort_nodes=False, **kwargs)
94
- while len(lines) > 0:
95
- t, lines = try_assemble_lines_impl(
96
- lines, id_offset=len(tree), sort_nodes=False, **kwargs
97
- )
98
- tree = pd.concat([tree, t])
99
-
100
- tree = tree.reset_index()
101
- link_roots_to_nearest_(tree)
102
- sort_nodes_(tree)
103
- return tree
104
-
105
-
106
- def try_assemble_lines_impl( # pylint: disable=too-many-arguments
107
- lines: Iterable[pd.DataFrame],
108
- undirected: bool = True,
109
- thre: float = 0.2,
110
- id_offset: int = 0,
111
- sort_nodes: bool = True,
112
- *,
113
- names: Optional[SWCNames] = None,
114
- ) -> Tuple[pd.DataFrame, List[pd.DataFrame]]:
115
- names = get_names(names)
116
- lines = copy(list(lines))
117
-
118
- tree = lines[0]
119
- tree[names.id] = id_offset + np.arange(len(tree))
120
- tree[names.pid] = tree[names.id] - 1
121
- tree.at[0, names.pid] = -1
122
- del lines[0]
123
-
124
- while True:
125
- for i, line in enumerate(lines):
126
- for p in [0, -1] if undirected else [0]:
127
- xyz = [names.x, names.y, names.z]
128
- vs = tree[xyz] - line.iloc[p][xyz]
129
- dis = np.linalg.norm(vs, axis=1)
130
- ind = np.argmin(dis)
131
- if dis[ind] > thre:
132
- continue
133
-
134
- if dis[ind] < EPS:
135
- line = line.drop((p + len(line)) % len(line)).reset_index(drop=True)
136
-
137
- line[names.id] = id_offset + len(tree) + np.arange(len(line))
138
- line[names.pid] = line[names.id] + (-1 if p == 0 else 1)
139
- line.at[(p + len(line)) % len(line), names.pid] = tree.iloc[ind][
140
- names.id
141
- ]
142
- tree = pd.concat([tree, line])
143
- del lines[i]
144
- break
145
- else:
146
- continue
147
-
148
- break
149
- else:
150
- break
151
-
152
- if sort_nodes:
153
- sort_nodes_(tree)
154
-
155
- return tree, lines
@@ -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",
@@ -27,8 +27,8 @@ def to_sub_topology(sub: Topology) -> Tuple[Topology, npt.NDArray[np.int32]]:
27
27
  Returns
28
28
  -------
29
29
  sub_topology : Topology
30
- id_map : List of int
31
- Map from new id to original id.
30
+ mapping : List of int
31
+ Map from new id to old id.
32
32
 
33
33
  See Also
34
34
  --------