swcgeom 0.15.0__py3-none-any.whl → 0.18.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of swcgeom might be problematic. Click here for more details.
- swcgeom/__init__.py +26 -1
- swcgeom/analysis/__init__.py +21 -8
- swcgeom/analysis/feature_extractor.py +43 -18
- swcgeom/analysis/features.py +250 -0
- swcgeom/analysis/lmeasure.py +857 -0
- swcgeom/analysis/sholl.py +55 -29
- swcgeom/analysis/trunk.py +27 -11
- swcgeom/analysis/visualization.py +24 -9
- swcgeom/analysis/visualization3d.py +100 -0
- swcgeom/analysis/volume.py +19 -4
- swcgeom/core/__init__.py +32 -9
- swcgeom/core/branch.py +28 -7
- swcgeom/core/branch_tree.py +18 -4
- swcgeom/core/{segment.py → compartment.py} +31 -10
- swcgeom/core/node.py +31 -10
- swcgeom/core/path.py +37 -10
- swcgeom/core/population.py +103 -34
- swcgeom/core/swc.py +26 -10
- swcgeom/core/swc_utils/__init__.py +21 -7
- swcgeom/core/swc_utils/assembler.py +27 -1
- swcgeom/core/swc_utils/base.py +25 -12
- swcgeom/core/swc_utils/checker.py +31 -14
- swcgeom/core/swc_utils/io.py +24 -7
- swcgeom/core/swc_utils/normalizer.py +20 -4
- swcgeom/core/swc_utils/subtree.py +17 -2
- swcgeom/core/tree.py +85 -72
- swcgeom/core/tree_utils.py +31 -16
- swcgeom/core/tree_utils_impl.py +18 -3
- swcgeom/images/__init__.py +17 -2
- swcgeom/images/augmentation.py +24 -4
- swcgeom/images/contrast.py +122 -0
- swcgeom/images/folder.py +97 -39
- swcgeom/images/io.py +108 -121
- swcgeom/transforms/__init__.py +28 -10
- swcgeom/transforms/base.py +17 -2
- swcgeom/transforms/branch.py +74 -8
- swcgeom/transforms/branch_tree.py +82 -0
- swcgeom/transforms/geometry.py +22 -7
- swcgeom/transforms/image_preprocess.py +115 -0
- swcgeom/transforms/image_stack.py +37 -13
- swcgeom/transforms/images.py +184 -7
- swcgeom/transforms/mst.py +20 -5
- swcgeom/transforms/neurolucida_asc.py +508 -0
- swcgeom/transforms/path.py +15 -0
- swcgeom/transforms/population.py +16 -3
- swcgeom/transforms/tree.py +89 -31
- swcgeom/transforms/tree_assembler.py +23 -7
- swcgeom/utils/__init__.py +27 -11
- swcgeom/utils/debug.py +15 -0
- swcgeom/utils/download.py +59 -21
- swcgeom/utils/dsu.py +15 -0
- swcgeom/utils/ellipse.py +18 -4
- swcgeom/utils/file.py +15 -0
- swcgeom/utils/neuromorpho.py +439 -302
- swcgeom/utils/numpy_helper.py +29 -4
- swcgeom/utils/plotter_2d.py +151 -0
- swcgeom/utils/plotter_3d.py +48 -0
- swcgeom/utils/renderer.py +49 -145
- swcgeom/utils/sdf.py +24 -8
- swcgeom/utils/solid_geometry.py +16 -3
- swcgeom/utils/transforms.py +17 -4
- swcgeom/utils/volumetric_object.py +23 -10
- {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/LICENSE +1 -1
- {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/METADATA +28 -24
- swcgeom-0.18.3.dist-info/RECORD +67 -0
- {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/WHEEL +1 -1
- swcgeom/_version.py +0 -16
- swcgeom/analysis/branch_features.py +0 -67
- swcgeom/analysis/node_features.py +0 -121
- swcgeom/analysis/path_features.py +0 -37
- swcgeom-0.15.0.dist-info/RECORD +0 -62
- {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/top_level.txt +0 -0
swcgeom/transforms/tree.py
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Transformation in tree."""
|
|
2
17
|
|
|
3
|
-
import
|
|
4
|
-
from typing import
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from typing import Optional
|
|
5
20
|
|
|
6
21
|
import numpy as np
|
|
22
|
+
from typing_extensions import deprecated
|
|
7
23
|
|
|
8
|
-
from swcgeom.core import BranchTree, DictSWC, Path, Tree, cut_tree, to_subtree
|
|
24
|
+
from swcgeom.core import Branch, BranchTree, DictSWC, Path, Tree, cut_tree, to_subtree
|
|
9
25
|
from swcgeom.core.swc_utils import SWCTypes, get_types
|
|
10
26
|
from swcgeom.transforms.base import Transform
|
|
11
|
-
from swcgeom.transforms.branch import BranchConvSmoother
|
|
27
|
+
from swcgeom.transforms.branch import BranchConvSmoother, BranchIsometricResampler
|
|
28
|
+
from swcgeom.transforms.branch_tree import BranchTreeAssembler
|
|
12
29
|
from swcgeom.transforms.geometry import Normalizer
|
|
13
30
|
|
|
14
31
|
__all__ = [
|
|
@@ -19,8 +36,9 @@ __all__ = [
|
|
|
19
36
|
"CutByType",
|
|
20
37
|
"CutAxonTree",
|
|
21
38
|
"CutDendriteTree",
|
|
22
|
-
"
|
|
39
|
+
"CutByFurcationOrder",
|
|
23
40
|
"CutShortTipBranch",
|
|
41
|
+
"IsometricResampler",
|
|
24
42
|
]
|
|
25
43
|
|
|
26
44
|
|
|
@@ -64,21 +82,17 @@ class TreeSmoother(Transform[Tree, Tree]): # pylint: disable=missing-class-docs
|
|
|
64
82
|
|
|
65
83
|
return x
|
|
66
84
|
|
|
67
|
-
def extra_repr(self):
|
|
85
|
+
def extra_repr(self) -> str:
|
|
68
86
|
return f"n_nodes={self.n_nodes}"
|
|
69
87
|
|
|
70
88
|
|
|
89
|
+
@deprecated("Use `Normalizer` instead")
|
|
71
90
|
class TreeNormalizer(Normalizer[Tree]):
|
|
72
|
-
"""Noramlize coordinates and radius to 0-1.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"v0.6.0 beacuse it applies more widely, and this will be "
|
|
78
|
-
"removed in next version",
|
|
79
|
-
DeprecationWarning,
|
|
80
|
-
)
|
|
81
|
-
super().__init__(*args, **kwargs)
|
|
91
|
+
"""Noramlize coordinates and radius to 0-1.
|
|
92
|
+
|
|
93
|
+
.. deprecated:: 0.6.0
|
|
94
|
+
Use :cls:`Normalizer` instead.
|
|
95
|
+
"""
|
|
82
96
|
|
|
83
97
|
|
|
84
98
|
class CutByType(Transform[Tree, Tree]):
|
|
@@ -98,7 +112,7 @@ class CutByType(Transform[Tree, Tree]):
|
|
|
98
112
|
def __call__(self, x: Tree) -> Tree:
|
|
99
113
|
removals = set(x.id()[x.type() != self.type])
|
|
100
114
|
|
|
101
|
-
def leave(n: Tree.Node, keep_children:
|
|
115
|
+
def leave(n: Tree.Node, keep_children: list[bool]) -> bool:
|
|
102
116
|
if n.id in removals and any(keep_children):
|
|
103
117
|
removals.remove(n.id)
|
|
104
118
|
return n.id not in removals
|
|
@@ -107,7 +121,7 @@ class CutByType(Transform[Tree, Tree]):
|
|
|
107
121
|
y = to_subtree(x, removals)
|
|
108
122
|
return y
|
|
109
123
|
|
|
110
|
-
def extra_repr(self):
|
|
124
|
+
def extra_repr(self) -> str:
|
|
111
125
|
return f"type={self.type}"
|
|
112
126
|
|
|
113
127
|
|
|
@@ -127,28 +141,48 @@ class CutDendriteTree(CutByType):
|
|
|
127
141
|
super().__init__(type=types.basal_dendrite) # TODO: apical dendrite
|
|
128
142
|
|
|
129
143
|
|
|
130
|
-
class
|
|
131
|
-
"""Cut tree by
|
|
144
|
+
class CutByFurcationOrder(Transform[Tree, Tree]):
|
|
145
|
+
"""Cut tree by furcation order."""
|
|
132
146
|
|
|
133
|
-
|
|
147
|
+
max_furcation_order: int
|
|
134
148
|
|
|
135
149
|
def __init__(self, max_bifurcation_order: int) -> None:
|
|
136
|
-
self.
|
|
150
|
+
self.max_furcation_order = max_bifurcation_order
|
|
137
151
|
|
|
138
152
|
def __call__(self, x: Tree) -> Tree:
|
|
139
153
|
return cut_tree(x, enter=self._enter)
|
|
140
154
|
|
|
141
155
|
def __repr__(self) -> str:
|
|
142
|
-
return f"CutByBifurcationOrder-{self.
|
|
156
|
+
return f"CutByBifurcationOrder-{self.max_furcation_order}"
|
|
143
157
|
|
|
144
|
-
def _enter(self, n: Tree.Node, parent_level: int | None) ->
|
|
158
|
+
def _enter(self, n: Tree.Node, parent_level: int | None) -> tuple[int, bool]:
|
|
145
159
|
if parent_level is None:
|
|
146
160
|
level = 0
|
|
147
|
-
elif n.
|
|
161
|
+
elif n.is_furcation():
|
|
148
162
|
level = parent_level + 1
|
|
149
163
|
else:
|
|
150
164
|
level = parent_level
|
|
151
|
-
return (level, level >= self.
|
|
165
|
+
return (level, level >= self.max_furcation_order)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@deprecated("Use CutByFurcationOrder instead")
|
|
169
|
+
class CutByBifurcationOrder(CutByFurcationOrder):
|
|
170
|
+
"""Cut tree by bifurcation order.
|
|
171
|
+
|
|
172
|
+
Notes
|
|
173
|
+
-----
|
|
174
|
+
Deprecated due to the wrong spelling of furcation. For now, it
|
|
175
|
+
is just an alias of `CutByFurcationOrder` and raise a warning. It
|
|
176
|
+
will be change to raise an error in the future.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
max_furcation_order: int
|
|
180
|
+
|
|
181
|
+
def __init__(self, max_bifurcation_order: int) -> None:
|
|
182
|
+
super().__init__(max_bifurcation_order)
|
|
183
|
+
|
|
184
|
+
def __repr__(self) -> str:
|
|
185
|
+
return f"CutByBifurcationOrder-{self.max_furcation_order}"
|
|
152
186
|
|
|
153
187
|
|
|
154
188
|
class CutShortTipBranch(Transform[Tree, Tree]):
|
|
@@ -160,7 +194,7 @@ class CutShortTipBranch(Transform[Tree, Tree]):
|
|
|
160
194
|
"""
|
|
161
195
|
|
|
162
196
|
thre: float
|
|
163
|
-
callbacks:
|
|
197
|
+
callbacks: list[Callable[[Tree.Branch], None]]
|
|
164
198
|
|
|
165
199
|
def __init__(
|
|
166
200
|
self, thre: float = 5, callback: Optional[Callable[[Tree.Branch], None]] = None
|
|
@@ -172,18 +206,18 @@ class CutShortTipBranch(Transform[Tree, Tree]):
|
|
|
172
206
|
self.callbacks.append(callback)
|
|
173
207
|
|
|
174
208
|
def __call__(self, x: Tree) -> Tree:
|
|
175
|
-
removals:
|
|
209
|
+
removals: list[int] = []
|
|
176
210
|
self.callbacks.append(lambda br: removals.append(br[1].id))
|
|
177
211
|
x.traverse(leave=self._leave)
|
|
178
212
|
self.callbacks.pop()
|
|
179
213
|
return to_subtree(x, removals)
|
|
180
214
|
|
|
181
|
-
def extra_repr(self):
|
|
215
|
+
def extra_repr(self) -> str:
|
|
182
216
|
return f"threshold={self.thre}"
|
|
183
217
|
|
|
184
218
|
def _leave(
|
|
185
|
-
self, n: Tree.Node, children:
|
|
186
|
-
) ->
|
|
219
|
+
self, n: Tree.Node, children: list[tuple[float, Tree.Node] | None]
|
|
220
|
+
) -> tuple[float, Tree.Node] | None:
|
|
187
221
|
if len(children) == 0: # tip
|
|
188
222
|
return 0, n
|
|
189
223
|
|
|
@@ -210,3 +244,27 @@ class CutShortTipBranch(Transform[Tree, Tree]):
|
|
|
210
244
|
cb(br)
|
|
211
245
|
|
|
212
246
|
return None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class Resampler(Transform[Tree, Tree]):
|
|
250
|
+
def __init__(self, branch_resampler: Transform[Branch, Branch]) -> None:
|
|
251
|
+
super().__init__()
|
|
252
|
+
self.resampler = branch_resampler
|
|
253
|
+
self.assembler = BranchTreeAssembler()
|
|
254
|
+
|
|
255
|
+
def __call__(self, x: Tree) -> Tree:
|
|
256
|
+
t = BranchTree.from_tree(x)
|
|
257
|
+
t.branches = {
|
|
258
|
+
k: [self.resampler(br) for br in brs] for k, brs in t.branches.items()
|
|
259
|
+
}
|
|
260
|
+
return self.assembler(t)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class IsometricResampler(Resampler):
|
|
264
|
+
def __init__(
|
|
265
|
+
self, distance: float, *, adjust_last_gap: bool = True, **kwargs
|
|
266
|
+
) -> None:
|
|
267
|
+
branch_resampler = BranchIsometricResampler(
|
|
268
|
+
distance, adjust_last_gap=adjust_last_gap, **kwargs
|
|
269
|
+
)
|
|
270
|
+
super().__init__(branch_resampler)
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Assemble a tree."""
|
|
2
17
|
|
|
18
|
+
from collections.abc import Iterable
|
|
3
19
|
from copy import copy
|
|
4
|
-
from typing import
|
|
20
|
+
from typing import Optional
|
|
5
21
|
|
|
6
22
|
import numpy as np
|
|
7
23
|
import pandas as pd
|
|
@@ -18,7 +34,7 @@ from swcgeom.transforms.base import Transform
|
|
|
18
34
|
EPS = 1e-5
|
|
19
35
|
|
|
20
36
|
|
|
21
|
-
class LinesToTree(Transform[
|
|
37
|
+
class LinesToTree(Transform[list[pd.DataFrame], Tree]):
|
|
22
38
|
"""Assemble lines to swc."""
|
|
23
39
|
|
|
24
40
|
def __init__(self, *, thre: float = 0.2, undirected: bool = True):
|
|
@@ -37,7 +53,7 @@ class LinesToTree(Transform[List[pd.DataFrame], Tree]):
|
|
|
37
53
|
|
|
38
54
|
def __call__(
|
|
39
55
|
self, lines: Iterable[pd.DataFrame], *, names: Optional[SWCNames] = None
|
|
40
|
-
):
|
|
56
|
+
): # TODO check this
|
|
41
57
|
return self.assemble(lines, names=names)
|
|
42
58
|
|
|
43
59
|
def assemble(
|
|
@@ -55,8 +71,8 @@ class LinesToTree(Transform[List[pd.DataFrame], Tree]):
|
|
|
55
71
|
Parameters
|
|
56
72
|
----------
|
|
57
73
|
lines : List of ~pd.DataFrame
|
|
58
|
-
An array of tables containing a line, columns should
|
|
59
|
-
the swc.
|
|
74
|
+
An array of tables containing a line, columns should
|
|
75
|
+
following the swc.
|
|
60
76
|
undirected : bool, default `True`
|
|
61
77
|
Forwarding to `self.try_assemble`.
|
|
62
78
|
names : SWCNames, optional
|
|
@@ -97,7 +113,7 @@ class LinesToTree(Transform[List[pd.DataFrame], Tree]):
|
|
|
97
113
|
undirected: bool = True,
|
|
98
114
|
sort_nodes: bool = True,
|
|
99
115
|
names: Optional[SWCNames] = None,
|
|
100
|
-
) ->
|
|
116
|
+
) -> tuple[pd.DataFrame, list[pd.DataFrame]]:
|
|
101
117
|
"""Trying assemble lines to a tree.
|
|
102
118
|
|
|
103
119
|
Treat the first line as a tree, find a line whose shortest distance
|
|
@@ -169,5 +185,5 @@ class LinesToTree(Transform[List[pd.DataFrame], Tree]):
|
|
|
169
185
|
|
|
170
186
|
return tree, lines
|
|
171
187
|
|
|
172
|
-
def extra_repr(self):
|
|
188
|
+
def extra_repr(self) -> str:
|
|
173
189
|
return f"thre={self.thre}, undirected={self.undirected}"
|
swcgeom/utils/__init__.py
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Utils."""
|
|
2
17
|
|
|
3
|
-
from swcgeom.utils.debug import *
|
|
4
|
-
from swcgeom.utils.dsu import *
|
|
5
|
-
from swcgeom.utils.ellipse import *
|
|
6
|
-
from swcgeom.utils.file import *
|
|
7
|
-
from swcgeom.utils.neuromorpho import *
|
|
8
|
-
from swcgeom.utils.numpy_helper import *
|
|
9
|
-
from swcgeom.utils.
|
|
10
|
-
from swcgeom.utils.
|
|
11
|
-
from swcgeom.utils.
|
|
12
|
-
from swcgeom.utils.
|
|
13
|
-
from swcgeom.utils.
|
|
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/debug.py
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Debug helpers"""
|
|
2
17
|
|
|
3
18
|
import time
|
swcgeom/utils/download.py
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Download helpers.
|
|
2
17
|
|
|
3
18
|
Notes
|
|
@@ -13,6 +28,7 @@ import itertools
|
|
|
13
28
|
import logging
|
|
14
29
|
import multiprocessing
|
|
15
30
|
import os
|
|
31
|
+
from functools import partial
|
|
16
32
|
from urllib.parse import urljoin
|
|
17
33
|
|
|
18
34
|
__all__ = ["download", "fetch_page", "clone_index_page"]
|
|
@@ -26,7 +42,7 @@ def download(dst: str, url: str) -> None:
|
|
|
26
42
|
r = conn.request("GET", url)
|
|
27
43
|
|
|
28
44
|
dirname = os.path.dirname(dst)
|
|
29
|
-
if not os.path.exists(dirname):
|
|
45
|
+
if dirname != "" and not os.path.exists(dirname):
|
|
30
46
|
os.makedirs(dirname)
|
|
31
47
|
|
|
32
48
|
with open(dst, "wb") as file:
|
|
@@ -41,7 +57,7 @@ def fetch_page(url: str):
|
|
|
41
57
|
conn = connection_from_url(url)
|
|
42
58
|
r = conn.request("GET", url)
|
|
43
59
|
data = r.data.decode("utf-8")
|
|
44
|
-
return BeautifulSoup(data)
|
|
60
|
+
return BeautifulSoup(data, features="html.parser")
|
|
45
61
|
|
|
46
62
|
|
|
47
63
|
def clone_index_page(
|
|
@@ -62,31 +78,35 @@ def clone_index_page(
|
|
|
62
78
|
multiprocess : int, default `4`
|
|
63
79
|
How many process are available for download.
|
|
64
80
|
"""
|
|
65
|
-
from urllib3.exceptions import HTTPError
|
|
66
|
-
|
|
67
81
|
files = get_urls_in_index_page(index_url)
|
|
68
82
|
logging.info("downloader: search `%s`, found %s files.", index_url, len(files))
|
|
69
83
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
logging.info("downloader: file `%s` exits, skiped.", dist)
|
|
76
|
-
return
|
|
84
|
+
task = partial(
|
|
85
|
+
_clone_index_page, index_url=index_url, dist_dir=dist_dir, override=override
|
|
86
|
+
)
|
|
87
|
+
with multiprocessing.Pool(multiprocess) as p:
|
|
88
|
+
p.map(task, files)
|
|
77
89
|
|
|
78
|
-
logging.info("downloader: file `%s` exits, deleted.", dist)
|
|
79
|
-
os.remove(filepath)
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
download(filepath, url)
|
|
84
|
-
logging.info("downloader: download `%s` to `%s`", url, dist)
|
|
85
|
-
except HTTPError as ex:
|
|
86
|
-
logging.info("downloader: fails to download `%s`, except `%s`", url, ex)
|
|
91
|
+
def _clone_index_page(url: str, index_url: str, dist_dir: str, override: bool) -> None:
|
|
92
|
+
from urllib3.exceptions import HTTPError
|
|
87
93
|
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
filepath = url.removeprefix(index_url)
|
|
95
|
+
dist = os.path.join(dist_dir, filepath)
|
|
96
|
+
if os.path.exists(dist):
|
|
97
|
+
if not override:
|
|
98
|
+
logging.info("downloader: file `%s` exits, skiped.", dist)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
logging.info("downloader: file `%s` exits, deleted.", dist)
|
|
102
|
+
os.remove(dist)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
logging.info("downloader: downloading `%s` to `%s`", url, dist)
|
|
106
|
+
download(dist, url)
|
|
107
|
+
logging.info("downloader: download `%s` to `%s`", url, dist)
|
|
108
|
+
except HTTPError as ex:
|
|
109
|
+
logging.info("downloader: fails to download `%s`, except `%s`", url, ex)
|
|
90
110
|
|
|
91
111
|
|
|
92
112
|
def get_urls_in_index_page(url: str) -> list[str]:
|
|
@@ -97,3 +117,21 @@ def get_urls_in_index_page(url: str) -> list[str]:
|
|
|
97
117
|
dirs = [urljoin(url, a) for a in links if a != "../" and a.endswith("/")]
|
|
98
118
|
files.extend(itertools.chain(*[get_urls_in_index_page(dir) for dir in dirs]))
|
|
99
119
|
return files
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
import argparse
|
|
124
|
+
|
|
125
|
+
parser = argparse.ArgumentParser(description="Download files from index page.")
|
|
126
|
+
parser.add_argument("url", type=str, help="URL of index page.")
|
|
127
|
+
parser.add_argument("dist", type=str, help="Directory of dist.")
|
|
128
|
+
parser.add_argument(
|
|
129
|
+
"--override", type=bool, default=False, help="Override existing file."
|
|
130
|
+
)
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
"--multiprocess", type=int, default=4, help="How many process are available."
|
|
133
|
+
)
|
|
134
|
+
args = parser.parse_args()
|
|
135
|
+
|
|
136
|
+
logging.basicConfig(level=logging.INFO)
|
|
137
|
+
clone_index_page(args.url, args.dist, args.override, args.multiprocess)
|
swcgeom/utils/dsu.py
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Disjoint Set Union Impl."""
|
|
2
17
|
|
|
3
18
|
__all__ = ["DisjointSetUnion"]
|
swcgeom/utils/ellipse.py
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Finds the Minimum Volume Enclosing Ellipsoid."""
|
|
2
17
|
|
|
3
18
|
# pylint: disable=invalid-name
|
|
4
19
|
|
|
5
20
|
from functools import cached_property
|
|
6
|
-
from typing import Tuple
|
|
7
21
|
|
|
8
22
|
import numpy as np
|
|
9
23
|
import numpy.linalg as la
|
|
@@ -22,7 +36,7 @@ class Ellipse:
|
|
|
22
36
|
self.centroid = centroid
|
|
23
37
|
|
|
24
38
|
@property
|
|
25
|
-
def radii(self) ->
|
|
39
|
+
def radii(self) -> tuple[float, float]:
|
|
26
40
|
# x, y radii.
|
|
27
41
|
_U, D, _V = self.svd
|
|
28
42
|
rx, ry = 1.0 / np.sqrt(D)
|
|
@@ -39,7 +53,7 @@ class Ellipse:
|
|
|
39
53
|
return b
|
|
40
54
|
|
|
41
55
|
@property
|
|
42
|
-
def axes(self) ->
|
|
56
|
+
def axes(self) -> tuple[float, float]:
|
|
43
57
|
# Major and minor semi-axis of the ellipse.
|
|
44
58
|
rx, ry = self.radii
|
|
45
59
|
dx, dy = 2 * rx, 2 * ry
|
|
@@ -77,7 +91,7 @@ def mvee(points: npt.NDArray[np.floating], tol: float = 1e-3) -> Ellipse:
|
|
|
77
91
|
|
|
78
92
|
def _mvee(
|
|
79
93
|
points: npt.NDArray[np.floating], tol: float = 1e-3
|
|
80
|
-
) ->
|
|
94
|
+
) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
|
|
81
95
|
"""Finds the Minimum Volume Enclosing Ellipsoid.
|
|
82
96
|
|
|
83
97
|
Returns
|
swcgeom/utils/file.py
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""File related utils.
|
|
2
17
|
|
|
3
18
|
Notes
|