swcgeom 0.18.1__py3-none-any.whl → 0.19.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of swcgeom might be problematic. Click here for more details.

Files changed (68) hide show
  1. swcgeom/__init__.py +12 -1
  2. swcgeom/analysis/__init__.py +6 -6
  3. swcgeom/analysis/feature_extractor.py +22 -24
  4. swcgeom/analysis/features.py +18 -40
  5. swcgeom/analysis/lmeasure.py +227 -323
  6. swcgeom/analysis/sholl.py +17 -23
  7. swcgeom/analysis/trunk.py +23 -28
  8. swcgeom/analysis/visualization.py +37 -44
  9. swcgeom/analysis/visualization3d.py +16 -25
  10. swcgeom/analysis/volume.py +33 -47
  11. swcgeom/core/__init__.py +12 -13
  12. swcgeom/core/branch.py +10 -17
  13. swcgeom/core/branch_tree.py +3 -2
  14. swcgeom/core/compartment.py +1 -1
  15. swcgeom/core/node.py +3 -6
  16. swcgeom/core/path.py +11 -16
  17. swcgeom/core/population.py +32 -51
  18. swcgeom/core/swc.py +25 -16
  19. swcgeom/core/swc_utils/__init__.py +10 -12
  20. swcgeom/core/swc_utils/assembler.py +5 -12
  21. swcgeom/core/swc_utils/base.py +40 -31
  22. swcgeom/core/swc_utils/checker.py +3 -8
  23. swcgeom/core/swc_utils/io.py +32 -47
  24. swcgeom/core/swc_utils/normalizer.py +17 -23
  25. swcgeom/core/swc_utils/subtree.py +13 -20
  26. swcgeom/core/tree.py +61 -51
  27. swcgeom/core/tree_utils.py +36 -49
  28. swcgeom/core/tree_utils_impl.py +4 -6
  29. swcgeom/images/__init__.py +2 -2
  30. swcgeom/images/augmentation.py +23 -39
  31. swcgeom/images/contrast.py +22 -46
  32. swcgeom/images/folder.py +32 -34
  33. swcgeom/images/io.py +80 -121
  34. swcgeom/transforms/__init__.py +13 -13
  35. swcgeom/transforms/base.py +28 -19
  36. swcgeom/transforms/branch.py +31 -41
  37. swcgeom/transforms/branch_tree.py +3 -1
  38. swcgeom/transforms/geometry.py +13 -4
  39. swcgeom/transforms/image_preprocess.py +2 -0
  40. swcgeom/transforms/image_stack.py +40 -35
  41. swcgeom/transforms/images.py +31 -24
  42. swcgeom/transforms/mst.py +27 -40
  43. swcgeom/transforms/neurolucida_asc.py +13 -13
  44. swcgeom/transforms/path.py +4 -0
  45. swcgeom/transforms/population.py +4 -0
  46. swcgeom/transforms/tree.py +16 -11
  47. swcgeom/transforms/tree_assembler.py +37 -54
  48. swcgeom/utils/__init__.py +12 -12
  49. swcgeom/utils/download.py +7 -14
  50. swcgeom/utils/dsu.py +12 -0
  51. swcgeom/utils/ellipse.py +26 -14
  52. swcgeom/utils/file.py +8 -13
  53. swcgeom/utils/neuromorpho.py +78 -92
  54. swcgeom/utils/numpy_helper.py +15 -12
  55. swcgeom/utils/plotter_2d.py +10 -16
  56. swcgeom/utils/plotter_3d.py +7 -9
  57. swcgeom/utils/renderer.py +16 -8
  58. swcgeom/utils/sdf.py +12 -23
  59. swcgeom/utils/solid_geometry.py +58 -2
  60. swcgeom/utils/transforms.py +164 -100
  61. swcgeom/utils/volumetric_object.py +29 -53
  62. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/METADATA +7 -6
  63. swcgeom-0.19.0.dist-info/RECORD +67 -0
  64. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/WHEEL +1 -1
  65. swcgeom/_version.py +0 -16
  66. swcgeom-0.18.1.dist-info/RECORD +0 -68
  67. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info/licenses}/LICENSE +0 -0
  68. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/top_level.txt +0 -0
@@ -18,8 +18,9 @@
18
18
  import os
19
19
  import re
20
20
  from enum import Enum, auto
21
- from io import TextIOBase
22
- from typing import Any, NamedTuple, Optional, cast
21
+ from typing import IO, Any, NamedTuple, cast
22
+
23
+ from typing_extensions import override
23
24
 
24
25
  from swcgeom.core import Tree
25
26
  from swcgeom.core.swc_utils import SWCNames, SWCTypes, get_names, get_types
@@ -31,6 +32,7 @@ __all__ = ["NeurolucidaAscToSwc"]
31
32
  class NeurolucidaAscToSwc(Transform[str, Tree]):
32
33
  """Convert neurolucida asc format to swc format."""
33
34
 
35
+ @override
34
36
  def __call__(self, x: str) -> Tree:
35
37
  return self.convert(x)
36
38
 
@@ -42,7 +44,7 @@ class NeurolucidaAscToSwc(Transform[str, Tree]):
42
44
  return tree
43
45
 
44
46
  @classmethod
45
- def from_stream(cls, x: TextIOBase, *, source: str = "") -> Tree:
47
+ def from_stream(cls, x: IO[str], *, source: str = "") -> Tree:
46
48
  parser = Parser(x, source=source)
47
49
  ast = parser.parse()
48
50
  tree = cls.from_ast(ast)
@@ -52,8 +54,8 @@ class NeurolucidaAscToSwc(Transform[str, Tree]):
52
54
  def from_ast(
53
55
  ast: "AST",
54
56
  *,
55
- names: Optional[SWCNames] = None,
56
- types: Optional[SWCTypes] = None,
57
+ names: SWCNames | None = None,
58
+ types: SWCTypes | None = None,
57
59
  ) -> Tree:
58
60
  names = get_names(names)
59
61
  types = get_types(types)
@@ -129,8 +131,8 @@ class ASTNode:
129
131
  self,
130
132
  type: ASTType,
131
133
  value: Any = None,
132
- tokens: Optional[list["Token"]] = None,
133
- children: Optional[list["ASTNode"]] = None,
134
+ tokens: list["Token"] | None = None,
135
+ children: list["ASTNode"] | None = None,
134
136
  ):
135
137
  self.type = type
136
138
  self.value = value
@@ -149,9 +151,7 @@ class ASTNode:
149
151
  """
150
152
  Compare two ASTNode objects.
151
153
 
152
- Notes
153
- -----
154
- The `parent`, `tokens` attribute is not compared.
154
+ NOTE: The `parent`, `tokens` attribute is not compared.
155
155
  """
156
156
  return (
157
157
  isinstance(__value, ASTNode)
@@ -162,7 +162,7 @@ class ASTNode:
162
162
 
163
163
 
164
164
  class AST(ASTNode):
165
- def __init__(self, children: Optional[list[ASTNode]] = None, source: str = ""):
165
+ def __init__(self, children: list[ASTNode] | None = None, source: str = ""):
166
166
  super().__init__(ASTType.ROOT, children=children)
167
167
  self.source = source
168
168
 
@@ -216,7 +216,7 @@ class AssertionTokenTypeError(Exception):
216
216
 
217
217
 
218
218
  class Parser:
219
- def __init__(self, r: TextIOBase, *, source: str = ""):
219
+ def __init__(self, r: IO[str], *, source: str = ""):
220
220
  self.lexer = Lexer(r)
221
221
  self.next_token = None
222
222
  self.source = source
@@ -426,7 +426,7 @@ RE_FLOAT = re.compile(r"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?")
426
426
 
427
427
 
428
428
  class Lexer:
429
- def __init__(self, r: TextIOBase):
429
+ def __init__(self, r: IO[str]):
430
430
  self.r = r
431
431
  self.lineno = 1
432
432
  self.column = 1
@@ -15,6 +15,8 @@
15
15
 
16
16
  """Transformation in path."""
17
17
 
18
+ from typing_extensions import override
19
+
18
20
  from swcgeom.core import Path, Tree, redirect_tree
19
21
  from swcgeom.transforms.base import Transform
20
22
 
@@ -24,6 +26,7 @@ __all__ = ["PathToTree", "PathReverser"]
24
26
  class PathToTree(Transform[Path, Tree]):
25
27
  """Transform path to tree."""
26
28
 
29
+ @override
27
30
  def __call__(self, x: Path) -> Tree:
28
31
  t = Tree(
29
32
  x.number_of_nodes(),
@@ -55,6 +58,7 @@ class PathReverser(Transform[Path, Path]):
55
58
  super().__init__()
56
59
  self.to_tree = PathToTree()
57
60
 
61
+ @override
58
62
  def __call__(self, x: Path) -> Path:
59
63
  x[0].type, x[-1].type = x[-1].type, x[0].type
60
64
  t = self.to_tree(x)
@@ -15,6 +15,8 @@
15
15
 
16
16
  """Transformation in population."""
17
17
 
18
+ from typing_extensions import override
19
+
18
20
  from swcgeom.core import Population, Tree
19
21
  from swcgeom.transforms.base import Transform
20
22
 
@@ -28,6 +30,7 @@ class PopulationTransform(Transform[Population, Population]):
28
30
  super().__init__()
29
31
  self.transform = transform
30
32
 
33
+ @override
31
34
  def __call__(self, population: Population) -> Population:
32
35
  trees: list[Tree] = []
33
36
  for t in population:
@@ -38,5 +41,6 @@ class PopulationTransform(Transform[Population, Population]):
38
41
 
39
42
  return Population(trees, root=population.root)
40
43
 
44
+ @override
41
45
  def extra_repr(self) -> str:
42
46
  return f"transform={self.transform}"
@@ -16,10 +16,9 @@
16
16
  """Transformation in tree."""
17
17
 
18
18
  from collections.abc import Callable
19
- from typing import Optional
20
19
 
21
20
  import numpy as np
22
- from typing_extensions import deprecated
21
+ from typing_extensions import deprecated, override
23
22
 
24
23
  from swcgeom.core import Branch, BranchTree, DictSWC, Path, Tree, cut_tree, to_subtree
25
24
  from swcgeom.core.swc_utils import SWCTypes, get_types
@@ -46,6 +45,7 @@ __all__ = [
46
45
  class ToBranchTree(Transform[Tree, BranchTree]):
47
46
  """Transform tree to branch tree."""
48
47
 
48
+ @override
49
49
  def __call__(self, x: Tree) -> BranchTree:
50
50
  return BranchTree.from_tree(x)
51
51
 
@@ -56,6 +56,7 @@ class ToLongestPath(Transform[Tree, Path[DictSWC]]):
56
56
  def __init__(self, *, detach: bool = True) -> None:
57
57
  self.detach = detach
58
58
 
59
+ @override
59
60
  def __call__(self, x: Tree) -> Path[DictSWC]:
60
61
  paths = x.get_paths()
61
62
  idx = np.argmax([p.length() for p in paths])
@@ -71,6 +72,7 @@ class TreeSmoother(Transform[Tree, Tree]): # pylint: disable=missing-class-docs
71
72
  self.n_nodes = n_nodes
72
73
  self.trans = BranchConvSmoother(n_nodes=n_nodes)
73
74
 
75
+ @override
74
76
  def __call__(self, x: Tree) -> Tree:
75
77
  x = x.copy()
76
78
  for br in x.get_branches():
@@ -82,6 +84,7 @@ class TreeSmoother(Transform[Tree, Tree]): # pylint: disable=missing-class-docs
82
84
 
83
85
  return x
84
86
 
87
+ @override
85
88
  def extra_repr(self) -> str:
86
89
  return f"n_nodes={self.n_nodes}"
87
90
 
@@ -100,15 +103,14 @@ class CutByType(Transform[Tree, Tree]):
100
103
 
101
104
  In order to preserve the tree structure, all ancestor nodes of the node to be preserved will be preserved.
102
105
 
103
- Notes
104
- -----
105
- Not all reserved nodes are of the specified type.
106
+ NOTE: Not all reserved nodes are of the specified type.
106
107
  """
107
108
 
108
109
  def __init__(self, type: int) -> None: # pylint: disable=redefined-builtin
109
110
  super().__init__()
110
111
  self.type = type
111
112
 
113
+ @override
112
114
  def __call__(self, x: Tree) -> Tree:
113
115
  removals = set(x.id()[x.type() != self.type])
114
116
 
@@ -121,6 +123,7 @@ class CutByType(Transform[Tree, Tree]):
121
123
  y = to_subtree(x, removals)
122
124
  return y
123
125
 
126
+ @override
124
127
  def extra_repr(self) -> str:
125
128
  return f"type={self.type}"
126
129
 
@@ -128,7 +131,7 @@ class CutByType(Transform[Tree, Tree]):
128
131
  class CutAxonTree(CutByType):
129
132
  """Cut axon tree."""
130
133
 
131
- def __init__(self, types: Optional[SWCTypes] = None) -> None:
134
+ def __init__(self, types: SWCTypes | None = None) -> None:
132
135
  types = get_types(types)
133
136
  super().__init__(type=types.axon)
134
137
 
@@ -136,7 +139,7 @@ class CutAxonTree(CutByType):
136
139
  class CutDendriteTree(CutByType):
137
140
  """Cut dendrite tree."""
138
141
 
139
- def __init__(self, types: Optional[SWCTypes] = None) -> None:
142
+ def __init__(self, types: SWCTypes | None = None) -> None:
140
143
  types = get_types(types)
141
144
  super().__init__(type=types.basal_dendrite) # TODO: apical dendrite
142
145
 
@@ -149,6 +152,7 @@ class CutByFurcationOrder(Transform[Tree, Tree]):
149
152
  def __init__(self, max_bifurcation_order: int) -> None:
150
153
  self.max_furcation_order = max_bifurcation_order
151
154
 
155
+ @override
152
156
  def __call__(self, x: Tree) -> Tree:
153
157
  return cut_tree(x, enter=self._enter)
154
158
 
@@ -169,9 +173,7 @@ class CutByFurcationOrder(Transform[Tree, Tree]):
169
173
  class CutByBifurcationOrder(CutByFurcationOrder):
170
174
  """Cut tree by bifurcation order.
171
175
 
172
- Notes
173
- -----
174
- Deprecated due to the wrong spelling of furcation. For now, it
176
+ NOTE: Deprecated due to the wrong spelling of furcation. For now, it
175
177
  is just an alias of `CutByFurcationOrder` and raise a warning. It
176
178
  will be change to raise an error in the future.
177
179
  """
@@ -197,7 +199,7 @@ class CutShortTipBranch(Transform[Tree, Tree]):
197
199
  callbacks: list[Callable[[Tree.Branch], None]]
198
200
 
199
201
  def __init__(
200
- self, thre: float = 5, callback: Optional[Callable[[Tree.Branch], None]] = None
202
+ self, thre: float = 5, callback: Callable[[Tree.Branch], None] | None = None
201
203
  ) -> None:
202
204
  self.thre = thre
203
205
  self.callbacks = []
@@ -205,6 +207,7 @@ class CutShortTipBranch(Transform[Tree, Tree]):
205
207
  if callback is not None:
206
208
  self.callbacks.append(callback)
207
209
 
210
+ @override
208
211
  def __call__(self, x: Tree) -> Tree:
209
212
  removals: list[int] = []
210
213
  self.callbacks.append(lambda br: removals.append(br[1].id))
@@ -212,6 +215,7 @@ class CutShortTipBranch(Transform[Tree, Tree]):
212
215
  self.callbacks.pop()
213
216
  return to_subtree(x, removals)
214
217
 
218
+ @override
215
219
  def extra_repr(self) -> str:
216
220
  return f"threshold={self.thre}"
217
221
 
@@ -252,6 +256,7 @@ class Resampler(Transform[Tree, Tree]):
252
256
  self.resampler = branch_resampler
253
257
  self.assembler = BranchTreeAssembler()
254
258
 
259
+ @override
255
260
  def __call__(self, x: Tree) -> Tree:
256
261
  t = BranchTree.from_tree(x)
257
262
  t.branches = {
@@ -17,10 +17,10 @@
17
17
 
18
18
  from collections.abc import Iterable
19
19
  from copy import copy
20
- from typing import Optional
21
20
 
22
21
  import numpy as np
23
22
  import pandas as pd
23
+ from typing_extensions import override
24
24
 
25
25
  from swcgeom.core import Tree
26
26
  from swcgeom.core.swc_utils import (
@@ -39,20 +39,18 @@ class LinesToTree(Transform[list[pd.DataFrame], Tree]):
39
39
 
40
40
  def __init__(self, *, thre: float = 0.2, undirected: bool = True):
41
41
  """
42
- Parameters
43
- ----------
44
- thre : float, default `0.2`
45
- Connection threshold.
46
- undirected : bool, default `True`
47
- Both ends of a line can be considered connection point. If
48
- `False`, only the starting point.
42
+ Args:
43
+ thre: Connection threshold.
44
+ undirected: Both ends of a line can be considered connection point.
45
+ If `False`, only the starting point.
49
46
  """
50
47
  super().__init__()
51
48
  self.thre = thre
52
49
  self.undirected = undirected
53
50
 
51
+ @override
54
52
  def __call__(
55
- self, lines: Iterable[pd.DataFrame], *, names: Optional[SWCNames] = None
53
+ self, lines: Iterable[pd.DataFrame], *, names: SWCNames | None = None
56
54
  ): # TODO check this
57
55
  return self.assemble(lines, names=names)
58
56
 
@@ -61,30 +59,22 @@ class LinesToTree(Transform[list[pd.DataFrame], Tree]):
61
59
  lines: Iterable[pd.DataFrame],
62
60
  *,
63
61
  undirected: bool = True,
64
- names: Optional[SWCNames] = None,
62
+ names: SWCNames | None = None,
65
63
  ) -> pd.DataFrame:
66
64
  """Assemble lines to a tree.
67
65
 
68
- Assemble all the lines into a set of subtrees, and then connect
69
- them.
70
-
71
- Parameters
72
- ----------
73
- lines : List of ~pd.DataFrame
74
- An array of tables containing a line, columns should
75
- following the swc.
76
- undirected : bool, default `True`
77
- Forwarding to `self.try_assemble`.
78
- names : SWCNames, optional
79
- Forwarding to `self.try_assemble`.
80
-
81
- Returns
82
- -------
83
- tree : ~pd.DataFrame
84
-
85
- See Also
86
- --------
87
- self.try_assemble
66
+ Assemble all the lines into a set of subtrees, and then connect them.
67
+
68
+ Args:
69
+ lines: An array of tables containing a line, columns should following the swc.
70
+ undirected: Forwarding to `self.try_assemble`.
71
+ names: Forwarding to `self.try_assemble`.
72
+
73
+ Returns:
74
+ tree: ~pd.DataFrame
75
+
76
+ See Also:
77
+ self.try_assemble
88
78
  """
89
79
 
90
80
  tree, lines = self.try_assemble(
@@ -112,33 +102,25 @@ class LinesToTree(Transform[list[pd.DataFrame], Tree]):
112
102
  id_offset: int = 0,
113
103
  undirected: bool = True,
114
104
  sort_nodes: bool = True,
115
- names: Optional[SWCNames] = None,
105
+ names: SWCNames | None = None,
116
106
  ) -> tuple[pd.DataFrame, list[pd.DataFrame]]:
117
107
  """Trying assemble lines to a tree.
118
108
 
119
- Treat the first line as a tree, find a line whose shortest distance
120
- between the tree and the line is less than threshold, merge it into
121
- the tree, repeat until there are no line to merge, return tree and
122
- the remaining lines.
123
-
124
- Parameters
125
- ----------
126
- lines : List of ~pd.DataFrame
127
- An array of tables containing a line, columns should follwing
128
- the swc.
129
- id_offset : int, default `0`
130
- The offset of the line node id.
131
- undirected : bool, default `True`
132
- Both ends of a line can be considered connection point. If
133
- `False`, only the starting point.
134
- sort_nodes : bool, default `True`
135
- sort nodes of subtree.
136
- names : SWCNames, optional
137
-
138
- Returns
139
- -------
140
- tree : ~pandas.DataFrame
141
- remaining_lines : List of ~pandas.DataFrame
109
+ Treat the first line as a tree, find a line whose shortest distance between
110
+ the tree and the line is less than threshold, merge it into the tree, repeat
111
+ until there are no line to merge, return tree and the remaining lines.
112
+
113
+ Args:
114
+ lines: An array of tables containing a line, columns should following the swc.
115
+ id_offset: The offset of the line node id.
116
+ undirected: Both ends of a line can be considered connection point.
117
+ If `False`, only the starting point.
118
+ sort_nodes: sort nodes of subtree.
119
+ names: SWCNames, optional
120
+
121
+ Returns:
122
+ tree: ~pandas.DataFrame
123
+ remaining_lines: List of ~pandas.DataFrame
142
124
  """
143
125
 
144
126
  names = get_names(names)
@@ -185,5 +167,6 @@ class LinesToTree(Transform[list[pd.DataFrame], Tree]):
185
167
 
186
168
  return tree, lines
187
169
 
170
+ @override
188
171
  def extra_repr(self) -> str:
189
172
  return f"thre={self.thre}, undirected={self.undirected}"
swcgeom/utils/__init__.py CHANGED
@@ -15,15 +15,15 @@
15
15
 
16
16
  """Utils."""
17
17
 
18
- from swcgeom.utils.debug import *
19
- from swcgeom.utils.dsu import *
20
- from swcgeom.utils.ellipse import *
21
- from swcgeom.utils.file import *
22
- from swcgeom.utils.neuromorpho import *
23
- from swcgeom.utils.numpy_helper import *
24
- from swcgeom.utils.plotter_2d import *
25
- from swcgeom.utils.renderer import *
26
- from swcgeom.utils.sdf import *
27
- from swcgeom.utils.solid_geometry import *
28
- from swcgeom.utils.transforms import *
29
- from swcgeom.utils.volumetric_object import *
18
+ from swcgeom.utils.debug import * # noqa: F403
19
+ from swcgeom.utils.dsu import * # noqa: F403
20
+ from swcgeom.utils.ellipse import * # noqa: F403
21
+ from swcgeom.utils.file import * # noqa: F403
22
+ from swcgeom.utils.neuromorpho import * # noqa: F403
23
+ from swcgeom.utils.numpy_helper import * # noqa: F403
24
+ from swcgeom.utils.plotter_2d import * # noqa: F403
25
+ from swcgeom.utils.renderer import * # noqa: F403
26
+ from swcgeom.utils.sdf import * # noqa: F403
27
+ from swcgeom.utils.solid_geometry import * # noqa: F403
28
+ from swcgeom.utils.transforms import * # noqa: F403
29
+ from swcgeom.utils.volumetric_object import * # noqa: F403
swcgeom/utils/download.py CHANGED
@@ -15,9 +15,7 @@
15
15
 
16
16
  """Download helpers.
17
17
 
18
- Notes
19
- -----
20
- All denpendencies need to be installed, try:
18
+ NOTE: All denpendencies need to be installed, try:
21
19
 
22
20
  ```sh
23
21
  pip install swcgeom[all]
@@ -67,16 +65,11 @@ def clone_index_page(
67
65
 
68
66
  E.g: `https://download.brainimagelibrary.org/biccn/zeng/luo/fMOST/cells/`
69
67
 
70
- Parameters
71
- ----------
72
- index_url : str
73
- URL of index page.
74
- dist_dir : str
75
- Directory of dist.
76
- override : bool, default `False`
77
- Override existing file, skip file if `False`.
78
- multiprocess : int, default `4`
79
- How many process are available for download.
68
+ Args:
69
+ index_url: URL of index page.
70
+ dist_dir: Directory of dist.
71
+ override: Override existing file, skip file if `False`.
72
+ multiprocess: How many process are available for download.
80
73
  """
81
74
  files = get_urls_in_index_page(index_url)
82
75
  logging.info("downloader: search `%s`, found %s files.", index_url, len(files))
@@ -95,7 +88,7 @@ def _clone_index_page(url: str, index_url: str, dist_dir: str, override: bool) -
95
88
  dist = os.path.join(dist_dir, filepath)
96
89
  if os.path.exists(dist):
97
90
  if not override:
98
- logging.info("downloader: file `%s` exits, skiped.", dist)
91
+ logging.info("downloader: file `%s` exits, skipped.", dist)
99
92
  return
100
93
 
101
94
  logging.info("downloader: file `%s` exits, deleted.", dist)
swcgeom/utils/dsu.py CHANGED
@@ -22,6 +22,18 @@ class DisjointSetUnion:
22
22
  """Disjoint Set Union.
23
23
 
24
24
  DSU with path compression and union by rank.
25
+
26
+ >>> dsu = DisjointSetUnion(3)
27
+ >>> dsu.is_same_set(0, 1)
28
+ False
29
+ >>> dsu.union_sets(0, 1)
30
+ >>> dsu.is_same_set(0, 1)
31
+ True
32
+ >>> dsu.is_same_set(0, 2)
33
+ False
34
+ >>> dsu.union_sets(1, 2)
35
+ >>> dsu.is_same_set(0, 2)
36
+ True
25
37
  """
26
38
 
27
39
  def __init__(self, node_number: int):
swcgeom/utils/ellipse.py CHANGED
@@ -85,29 +85,41 @@ class Ellipse:
85
85
 
86
86
 
87
87
  def mvee(points: npt.NDArray[np.floating], tol: float = 1e-3) -> Ellipse:
88
- A, centroid = _mvee(points, tol=tol)
89
- return Ellipse(A, centroid)
88
+ """Finds the Minimum Volume Enclosing Ellipsoid.
90
89
 
90
+ >>> # Create a set of 2D points
91
+ >>> points = np.array([[0, 0], [1, 0], [0, 1], [1, 1]], dtype=np.float64)
92
+ >>> ellipse = mvee(points)
91
93
 
92
- def _mvee(
93
- points: npt.NDArray[np.floating], tol: float = 1e-3
94
- ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
95
- """Finds the Minimum Volume Enclosing Ellipsoid.
94
+ >>> # Check centroid is at center of points
95
+ >>> np.allclose(ellipse.centroid, [0.5, 0.5])
96
+ True
96
97
 
97
- Returns
98
- -------
99
- A : matrix of shape (d, d)
100
- The ellipse equation in the 'center form': (x-c)' * A * (x-c) = 1
101
- centroid : array of shape (d,)
102
- The center coordinates of the ellipse.
98
+ >>> # Check ellipse properties
99
+ >>> rx, ry = ellipse.radii
100
+ >>> np.allclose([rx, ry], [np.sqrt(2) / 2, np.sqrt(2) / 2], rtol=1e-5)
101
+ True
103
102
 
104
- Reference
105
- ---------
103
+ Reference:
106
104
  1. http://stackoverflow.com/questions/14016898/port-matlab-bounding-ellipsoid-code-to-python
107
105
  2. http://stackoverflow.com/questions/1768197/bounding-ellipse/1768440#1768440
108
106
  3. https://minillinim.github.io/GroopM/dev_docs/groopm.ellipsoid-pysrc.html
107
+
108
+ Args:
109
+ points: Array of shape (N, d) where N is number of points and d is dimension
110
+ tol: Tolerance for convergence
111
+
112
+ Returns:
113
+ Ellipse: An Ellipse object containing the minimum volume enclosing ellipse
109
114
  """
110
115
 
116
+ A, centroid = _mvee(points, tol=tol)
117
+ return Ellipse(A, centroid)
118
+
119
+
120
+ def _mvee(
121
+ points: npt.NDArray[np.floating], tol: float = 1e-3
122
+ ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
111
123
  N, d = points.shape
112
124
  Q = np.column_stack((points, np.ones(N))).T
113
125
  err = tol + 1.0
swcgeom/utils/file.py CHANGED
@@ -15,10 +15,7 @@
15
15
 
16
16
  """File related utils.
17
17
 
18
- Notes
19
- -----
20
- If character coding is enabled, all denpendencies need to be installed,
21
- try:
18
+ NOTE: If character coding is enabled, all denpendencies need to be installed, try:
22
19
 
23
20
  ```sh
24
21
  pip install swcgeom[all]
@@ -45,15 +42,13 @@ class FileReader:
45
42
  ) -> None:
46
43
  """Read file.
47
44
 
48
- Parameters
49
- ----------
50
- fname : PathOrIO
51
- encoding : str | 'detect', default `utf-8`
52
- The name of the encoding used to decode the file. If is
53
- `detect`, we will try to detect the character encoding.
54
- low_confidence : float, default to 0.9
55
- Used for detect character endocing, raising warning when
56
- parsing with low confidence.
45
+ Args:
46
+ fname: PathOrIO
47
+ encoding: The name of the encoding used to decode the file.
48
+ If is `detect`, we will try to detect the character encoding.
49
+ low_confidence: The confidence threshold for character encoding detection.
50
+ Used for detect character endocing, raising warning when parsing with
51
+ low confidence.
57
52
  """
58
53
  # TODO: support StringIO
59
54
  self.fname, self.fb, self.f = "", None, None