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.
- swcgeom/__init__.py +12 -1
- swcgeom/analysis/__init__.py +6 -6
- swcgeom/analysis/feature_extractor.py +22 -24
- swcgeom/analysis/features.py +18 -40
- swcgeom/analysis/lmeasure.py +227 -323
- swcgeom/analysis/sholl.py +17 -23
- swcgeom/analysis/trunk.py +23 -28
- swcgeom/analysis/visualization.py +37 -44
- swcgeom/analysis/visualization3d.py +16 -25
- swcgeom/analysis/volume.py +33 -47
- swcgeom/core/__init__.py +12 -13
- swcgeom/core/branch.py +10 -17
- swcgeom/core/branch_tree.py +3 -2
- swcgeom/core/compartment.py +1 -1
- swcgeom/core/node.py +3 -6
- swcgeom/core/path.py +11 -16
- swcgeom/core/population.py +32 -51
- swcgeom/core/swc.py +25 -16
- swcgeom/core/swc_utils/__init__.py +10 -12
- swcgeom/core/swc_utils/assembler.py +5 -12
- swcgeom/core/swc_utils/base.py +40 -31
- swcgeom/core/swc_utils/checker.py +3 -8
- swcgeom/core/swc_utils/io.py +32 -47
- swcgeom/core/swc_utils/normalizer.py +17 -23
- swcgeom/core/swc_utils/subtree.py +13 -20
- swcgeom/core/tree.py +61 -51
- swcgeom/core/tree_utils.py +36 -49
- swcgeom/core/tree_utils_impl.py +4 -6
- swcgeom/images/__init__.py +2 -2
- swcgeom/images/augmentation.py +23 -39
- swcgeom/images/contrast.py +22 -46
- swcgeom/images/folder.py +32 -34
- swcgeom/images/io.py +80 -121
- swcgeom/transforms/__init__.py +13 -13
- swcgeom/transforms/base.py +28 -19
- swcgeom/transforms/branch.py +31 -41
- swcgeom/transforms/branch_tree.py +3 -1
- swcgeom/transforms/geometry.py +13 -4
- swcgeom/transforms/image_preprocess.py +2 -0
- swcgeom/transforms/image_stack.py +40 -35
- swcgeom/transforms/images.py +31 -24
- swcgeom/transforms/mst.py +27 -40
- swcgeom/transforms/neurolucida_asc.py +13 -13
- swcgeom/transforms/path.py +4 -0
- swcgeom/transforms/population.py +4 -0
- swcgeom/transforms/tree.py +16 -11
- swcgeom/transforms/tree_assembler.py +37 -54
- swcgeom/utils/__init__.py +12 -12
- swcgeom/utils/download.py +7 -14
- swcgeom/utils/dsu.py +12 -0
- swcgeom/utils/ellipse.py +26 -14
- swcgeom/utils/file.py +8 -13
- swcgeom/utils/neuromorpho.py +78 -92
- swcgeom/utils/numpy_helper.py +15 -12
- swcgeom/utils/plotter_2d.py +10 -16
- swcgeom/utils/plotter_3d.py +7 -9
- swcgeom/utils/renderer.py +16 -8
- swcgeom/utils/sdf.py +12 -23
- swcgeom/utils/solid_geometry.py +58 -2
- swcgeom/utils/transforms.py +164 -100
- swcgeom/utils/volumetric_object.py +29 -53
- {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/METADATA +7 -6
- swcgeom-0.19.0.dist-info/RECORD +67 -0
- {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/WHEEL +1 -1
- swcgeom/_version.py +0 -16
- swcgeom-0.18.1.dist-info/RECORD +0 -68
- {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info/licenses}/LICENSE +0 -0
- {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
|
|
22
|
-
|
|
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:
|
|
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:
|
|
56
|
-
types:
|
|
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:
|
|
133
|
-
children:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
429
|
+
def __init__(self, r: IO[str]):
|
|
430
430
|
self.r = r
|
|
431
431
|
self.lineno = 1
|
|
432
432
|
self.column = 1
|
swcgeom/transforms/path.py
CHANGED
|
@@ -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)
|
swcgeom/transforms/population.py
CHANGED
|
@@ -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}"
|
swcgeom/transforms/tree.py
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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:
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
The
|
|
53
|
-
|
|
54
|
-
|
|
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
|