swcgeom 0.16.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 +48 -12
- swcgeom/analysis/sholl.py +25 -28
- 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 +31 -12
- swcgeom/core/branch.py +19 -3
- swcgeom/core/branch_tree.py +18 -4
- swcgeom/core/compartment.py +18 -2
- swcgeom/core/node.py +32 -3
- swcgeom/core/path.py +21 -9
- swcgeom/core/population.py +58 -29
- swcgeom/core/swc.py +26 -10
- swcgeom/core/swc_utils/__init__.py +21 -7
- swcgeom/core/swc_utils/assembler.py +15 -0
- swcgeom/core/swc_utils/base.py +23 -17
- swcgeom/core/swc_utils/checker.py +19 -12
- 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 +56 -40
- swcgeom/core/tree_utils.py +28 -17
- swcgeom/core/tree_utils_impl.py +18 -3
- swcgeom/images/__init__.py +17 -2
- swcgeom/images/augmentation.py +18 -3
- swcgeom/images/contrast.py +15 -0
- swcgeom/images/folder.py +27 -26
- swcgeom/images/io.py +94 -117
- swcgeom/transforms/__init__.py +28 -12
- 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 +15 -0
- swcgeom/transforms/image_stack.py +36 -9
- swcgeom/transforms/images.py +121 -14
- swcgeom/transforms/mst.py +15 -0
- swcgeom/transforms/neurolucida_asc.py +20 -7
- swcgeom/transforms/path.py +15 -0
- swcgeom/transforms/population.py +16 -3
- swcgeom/transforms/tree.py +84 -30
- swcgeom/transforms/tree_assembler.py +23 -7
- swcgeom/utils/__init__.py +27 -12
- 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 +35 -23
- swcgeom/utils/numpy_helper.py +15 -0
- swcgeom/utils/plotter_2d.py +27 -6
- swcgeom/utils/plotter_3d.py +48 -0
- swcgeom/utils/renderer.py +21 -6
- swcgeom/utils/sdf.py +19 -7
- swcgeom/utils/solid_geometry.py +16 -3
- swcgeom/utils/transforms.py +17 -4
- swcgeom/utils/volumetric_object.py +23 -10
- {swcgeom-0.16.0.dist-info → swcgeom-0.18.3.dist-info}/LICENSE +1 -1
- {swcgeom-0.16.0.dist-info → swcgeom-0.18.3.dist-info}/METADATA +28 -24
- swcgeom-0.18.3.dist-info/RECORD +67 -0
- {swcgeom-0.16.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.16.0.dist-info/RECORD +0 -67
- {swcgeom-0.16.0.dist-info → swcgeom-0.18.3.dist-info}/top_level.txt +0 -0
swcgeom/core/path.py
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
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
|
"""Nueron path."""
|
|
2
17
|
|
|
3
|
-
import
|
|
4
|
-
from typing import Generic,
|
|
18
|
+
from collections.abc import Iterable, Iterator
|
|
19
|
+
from typing import Generic, overload
|
|
5
20
|
|
|
6
21
|
import numpy as np
|
|
7
22
|
import numpy.typing as npt
|
|
23
|
+
from typing_extensions import deprecated
|
|
8
24
|
|
|
9
25
|
from swcgeom.core.node import Node
|
|
10
26
|
from swcgeom.core.swc import DictSWC, SWCLike, SWCTypeVar
|
|
@@ -15,7 +31,7 @@ __all__ = ["Path"]
|
|
|
15
31
|
class Path(SWCLike, Generic[SWCTypeVar]):
|
|
16
32
|
"""Neuron path.
|
|
17
33
|
|
|
18
|
-
A path is a linear set of points without
|
|
34
|
+
A path is a linear set of points without furcations.
|
|
19
35
|
"""
|
|
20
36
|
|
|
21
37
|
attach: SWCTypeVar
|
|
@@ -44,7 +60,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
44
60
|
@overload
|
|
45
61
|
def __getitem__(self, key: int) -> Node: ...
|
|
46
62
|
@overload
|
|
47
|
-
def __getitem__(self, key: slice) ->
|
|
63
|
+
def __getitem__(self, key: slice) -> list[Node]: ...
|
|
48
64
|
@overload
|
|
49
65
|
def __getitem__(self, key: str) -> npt.NDArray: ...
|
|
50
66
|
# fmt:on
|
|
@@ -74,6 +90,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
74
90
|
def get_ndata(self, key: str) -> npt.NDArray:
|
|
75
91
|
return self.attach.get_ndata(key)[self.idx]
|
|
76
92
|
|
|
93
|
+
@deprecated("Use `path.node` instead.")
|
|
77
94
|
def get_node(self, idx: int | np.integer) -> Node:
|
|
78
95
|
"""Get the count of intersection.
|
|
79
96
|
|
|
@@ -81,11 +98,6 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
81
98
|
Use :meth:`path.node` instead.
|
|
82
99
|
"""
|
|
83
100
|
|
|
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
101
|
return self.node(idx)
|
|
90
102
|
|
|
91
103
|
def node(self, idx: int | np.integer) -> Node:
|
swcgeom/core/population.py
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
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
|
"""Neuron population is a set of tree."""
|
|
2
17
|
|
|
3
18
|
import os
|
|
4
19
|
import warnings
|
|
20
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
5
21
|
from concurrent.futures import ProcessPoolExecutor
|
|
6
22
|
from functools import reduce
|
|
7
|
-
from typing import
|
|
8
|
-
Any,
|
|
9
|
-
Callable,
|
|
10
|
-
Dict,
|
|
11
|
-
Iterable,
|
|
12
|
-
Iterator,
|
|
13
|
-
List,
|
|
14
|
-
Optional,
|
|
15
|
-
Protocol,
|
|
16
|
-
TypeVar,
|
|
17
|
-
cast,
|
|
18
|
-
overload,
|
|
19
|
-
)
|
|
23
|
+
from typing import Any, Optional, Protocol, TypeVar, cast, overload
|
|
20
24
|
|
|
21
25
|
import numpy as np
|
|
22
26
|
import numpy.typing as npt
|
|
@@ -44,16 +48,16 @@ class Trees(Protocol):
|
|
|
44
48
|
class LazyLoadingTrees:
|
|
45
49
|
"""Lazy loading trees."""
|
|
46
50
|
|
|
47
|
-
swcs:
|
|
48
|
-
trees:
|
|
49
|
-
kwargs:
|
|
51
|
+
swcs: list[str]
|
|
52
|
+
trees: list[Tree | None]
|
|
53
|
+
kwargs: dict[str, Any]
|
|
50
54
|
|
|
51
55
|
def __init__(self, swcs: Iterable[str], **kwargs) -> None:
|
|
52
56
|
"""
|
|
53
57
|
Paramters
|
|
54
58
|
---------
|
|
55
59
|
swcs : List of str
|
|
56
|
-
kwargs :
|
|
60
|
+
kwargs : dict[str, Any]
|
|
57
61
|
Forwarding to `Tree.from_swc`
|
|
58
62
|
"""
|
|
59
63
|
|
|
@@ -81,7 +85,7 @@ class LazyLoadingTrees:
|
|
|
81
85
|
class ChainTrees:
|
|
82
86
|
"""Chain trees."""
|
|
83
87
|
|
|
84
|
-
trees:
|
|
88
|
+
trees: list[Trees]
|
|
85
89
|
cumsum: npt.NDArray[np.int64]
|
|
86
90
|
|
|
87
91
|
def __init__(self, trees: Iterable[Trees]) -> None:
|
|
@@ -108,6 +112,19 @@ class ChainTrees:
|
|
|
108
112
|
return (self[i] for i in range(self.__len__()))
|
|
109
113
|
|
|
110
114
|
|
|
115
|
+
class NestTrees:
|
|
116
|
+
def __init__(self, trees: Trees, idx: Iterable[int], /) -> None:
|
|
117
|
+
super().__init__()
|
|
118
|
+
self.trees = trees
|
|
119
|
+
self.idx = list(idx)
|
|
120
|
+
|
|
121
|
+
def __getitem__(self, key: int, /) -> Tree:
|
|
122
|
+
return self.trees[self.idx[key]]
|
|
123
|
+
|
|
124
|
+
def __len__(self) -> int:
|
|
125
|
+
return len(self.idx)
|
|
126
|
+
|
|
127
|
+
|
|
111
128
|
class Population:
|
|
112
129
|
"""Neuron population."""
|
|
113
130
|
|
|
@@ -146,13 +163,14 @@ class Population:
|
|
|
146
163
|
|
|
147
164
|
# fmt:off
|
|
148
165
|
@overload
|
|
149
|
-
def __getitem__(self, key: slice) ->
|
|
166
|
+
def __getitem__(self, key: slice) -> Trees: ...
|
|
150
167
|
@overload
|
|
151
168
|
def __getitem__(self, key: int) -> Tree: ...
|
|
152
169
|
# fmt:on
|
|
153
|
-
def __getitem__(self, key):
|
|
170
|
+
def __getitem__(self, key: int | slice):
|
|
154
171
|
if isinstance(key, slice):
|
|
155
|
-
|
|
172
|
+
trees = NestTrees(self.trees, range(*key.indices(len(self))))
|
|
173
|
+
return cast(Trees, trees)
|
|
156
174
|
|
|
157
175
|
if isinstance(key, (int, np.integer)):
|
|
158
176
|
return cast(Tree, self.trees[int(key)])
|
|
@@ -216,9 +234,9 @@ class Population:
|
|
|
216
234
|
return cls.from_swc(root, ext, extra_cols=extra_cols, **kwargs)
|
|
217
235
|
|
|
218
236
|
@staticmethod
|
|
219
|
-
def find_swcs(root: str, ext: str = ".swc", relpath: bool = False) ->
|
|
237
|
+
def find_swcs(root: str, ext: str = ".swc", relpath: bool = False) -> list[str]:
|
|
220
238
|
"""Find all swc files."""
|
|
221
|
-
swcs:
|
|
239
|
+
swcs: list[str] = []
|
|
222
240
|
for r, _, files in os.walk(root):
|
|
223
241
|
rr = os.path.relpath(r, root) if relpath else r
|
|
224
242
|
fs = filter(lambda f: os.path.splitext(f)[-1] == ext, files)
|
|
@@ -231,8 +249,8 @@ class Populations:
|
|
|
231
249
|
"""A set of population."""
|
|
232
250
|
|
|
233
251
|
len: int
|
|
234
|
-
populations:
|
|
235
|
-
labels:
|
|
252
|
+
populations: list[Population]
|
|
253
|
+
labels: list[str]
|
|
236
254
|
|
|
237
255
|
def __init__(
|
|
238
256
|
self, populations: Iterable[Population], labels: Optional[Iterable[str]] = None
|
|
@@ -248,9 +266,9 @@ class Populations:
|
|
|
248
266
|
|
|
249
267
|
# fmt:off
|
|
250
268
|
@overload
|
|
251
|
-
def __getitem__(self, key: slice) ->
|
|
269
|
+
def __getitem__(self, key: slice) -> list[list[Tree]]: ...
|
|
252
270
|
@overload
|
|
253
|
-
def __getitem__(self, key: int) ->
|
|
271
|
+
def __getitem__(self, key: int) -> list[Tree]: ...
|
|
254
272
|
# fmt:on
|
|
255
273
|
def __getitem__(self, key):
|
|
256
274
|
return [p[key] for p in self.populations]
|
|
@@ -259,7 +277,7 @@ class Populations:
|
|
|
259
277
|
"""Miniumn length of populations."""
|
|
260
278
|
return self.len
|
|
261
279
|
|
|
262
|
-
def __iter__(self) -> Iterator[
|
|
280
|
+
def __iter__(self) -> Iterator[list[Tree]]:
|
|
263
281
|
return (self[i] for i in range(self.len))
|
|
264
282
|
|
|
265
283
|
def __repr__(self) -> str:
|
|
@@ -288,7 +306,7 @@ class Populations:
|
|
|
288
306
|
|
|
289
307
|
Parameters
|
|
290
308
|
----------
|
|
291
|
-
roots :
|
|
309
|
+
roots : List of str
|
|
292
310
|
intersect : bool, default `True`
|
|
293
311
|
Take the intersection of these populations.
|
|
294
312
|
check_same : bool, default `False`
|
|
@@ -339,3 +357,14 @@ def _get_idx(key: int, length: int) -> int:
|
|
|
339
357
|
key += length
|
|
340
358
|
|
|
341
359
|
return key
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# experimental
|
|
363
|
+
def filter_population(
|
|
364
|
+
pop: Population, predicate: Callable[[Tree], bool]
|
|
365
|
+
) -> "Population":
|
|
366
|
+
"""Filter trees in the population."""
|
|
367
|
+
|
|
368
|
+
# TODO: how to avoid load trees
|
|
369
|
+
idx = [i for i, t in enumerate(pop) if predicate(t)]
|
|
370
|
+
return Population(NestTrees(pop.trees, idx), root=pop.root)
|
swcgeom/core/swc.py
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
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
|
"""SWC format."""
|
|
2
17
|
|
|
3
18
|
import warnings
|
|
4
19
|
from abc import ABC, abstractmethod
|
|
20
|
+
from collections.abc import Iterable
|
|
5
21
|
from copy import deepcopy
|
|
6
|
-
from typing import Any,
|
|
22
|
+
from typing import Any, Optional, TypeVar, overload
|
|
7
23
|
|
|
8
24
|
import numpy as np
|
|
9
25
|
import numpy.typing as npt
|
|
@@ -32,7 +48,7 @@ __all__ = [
|
|
|
32
48
|
|
|
33
49
|
|
|
34
50
|
swc_names_default = get_names()
|
|
35
|
-
swc_cols:
|
|
51
|
+
swc_cols: list[tuple[str, npt.DTypeLike]] = [
|
|
36
52
|
(swc_names_default.id, np.int32),
|
|
37
53
|
(swc_names_default.type, np.int32),
|
|
38
54
|
(swc_names_default.x, np.float32),
|
|
@@ -42,7 +58,7 @@ swc_cols: List[Tuple[str, npt.DTypeLike]] = [
|
|
|
42
58
|
(swc_names_default.pid, np.int32),
|
|
43
59
|
]
|
|
44
60
|
|
|
45
|
-
eswc_cols:
|
|
61
|
+
eswc_cols: list[tuple[str, npt.DTypeLike]] = [
|
|
46
62
|
("level", np.int32),
|
|
47
63
|
("mode", np.int32),
|
|
48
64
|
("timestamp", np.int32),
|
|
@@ -55,7 +71,7 @@ class SWCLike(ABC):
|
|
|
55
71
|
"""ABC of SWC."""
|
|
56
72
|
|
|
57
73
|
source: str = ""
|
|
58
|
-
comments:
|
|
74
|
+
comments: list[str] = []
|
|
59
75
|
names: SWCNames
|
|
60
76
|
types: SWCTypes
|
|
61
77
|
|
|
@@ -131,15 +147,15 @@ class SWCLike(ABC):
|
|
|
131
147
|
|
|
132
148
|
# fmt: off
|
|
133
149
|
@overload
|
|
134
|
-
def to_swc(self, fname: str, *, extra_cols:
|
|
150
|
+
def to_swc(self, fname: str, *, extra_cols: list[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> None: ...
|
|
135
151
|
@overload
|
|
136
|
-
def to_swc(self, *, extra_cols:
|
|
152
|
+
def to_swc(self, *, extra_cols: list[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> str: ...
|
|
137
153
|
# fmt: on
|
|
138
154
|
def to_swc(
|
|
139
155
|
self,
|
|
140
156
|
fname: Optional[str] = None,
|
|
141
157
|
*,
|
|
142
|
-
extra_cols: Optional[
|
|
158
|
+
extra_cols: Optional[list[str]] = None,
|
|
143
159
|
source: bool | str = True,
|
|
144
160
|
comments: bool = True,
|
|
145
161
|
id_offset: int = 1,
|
|
@@ -177,7 +193,7 @@ class SWCLike(ABC):
|
|
|
177
193
|
self,
|
|
178
194
|
fname: Optional[str] = None,
|
|
179
195
|
swc_path: Optional[str] = None,
|
|
180
|
-
extra_cols: Optional[
|
|
196
|
+
extra_cols: Optional[list[str]] = None,
|
|
181
197
|
**kwargs,
|
|
182
198
|
) -> str | None:
|
|
183
199
|
if swc_path is None:
|
|
@@ -199,7 +215,7 @@ SWCTypeVar = TypeVar("SWCTypeVar", bound=SWCLike)
|
|
|
199
215
|
class DictSWC(SWCLike):
|
|
200
216
|
"""SWC implementation on dict."""
|
|
201
217
|
|
|
202
|
-
ndata:
|
|
218
|
+
ndata: dict[str, npt.NDArray]
|
|
203
219
|
|
|
204
220
|
def __init__(
|
|
205
221
|
self,
|
|
@@ -221,7 +237,7 @@ class DictSWC(SWCLike):
|
|
|
221
237
|
def values(self) -> Iterable[npt.NDArray[Any]]:
|
|
222
238
|
return self.ndata.values()
|
|
223
239
|
|
|
224
|
-
def items(self) -> Iterable[
|
|
240
|
+
def items(self) -> Iterable[tuple[str, npt.NDArray[Any]]]:
|
|
225
241
|
return self.ndata.items()
|
|
226
242
|
|
|
227
243
|
def get_ndata(self, key: str) -> npt.NDArray[Any]:
|
|
@@ -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
|
"""SWC format utils.
|
|
2
17
|
|
|
3
18
|
Notes
|
|
@@ -8,10 +23,9 @@ If you use the method here, please review the code more frequently, we
|
|
|
8
23
|
will try to flag all breaking changes but NO promises.
|
|
9
24
|
"""
|
|
10
25
|
|
|
11
|
-
|
|
12
|
-
from swcgeom.core.swc_utils.
|
|
13
|
-
from swcgeom.core.swc_utils.
|
|
14
|
-
from swcgeom.core.swc_utils.
|
|
15
|
-
from swcgeom.core.swc_utils.
|
|
16
|
-
from swcgeom.core.swc_utils.
|
|
17
|
-
from swcgeom.core.swc_utils.subtree import *
|
|
26
|
+
from swcgeom.core.swc_utils.assembler import * # noqa: F403
|
|
27
|
+
from swcgeom.core.swc_utils.base import * # noqa: F403
|
|
28
|
+
from swcgeom.core.swc_utils.checker import * # noqa: F403
|
|
29
|
+
from swcgeom.core.swc_utils.io import * # noqa: F403
|
|
30
|
+
from swcgeom.core.swc_utils.normalizer import * # noqa: F403
|
|
31
|
+
from swcgeom.core.swc_utils.subtree import * # noqa: F403
|
|
@@ -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
|
"""Assemble lines to swc.
|
|
2
17
|
|
|
3
18
|
Notes
|
swcgeom/core/swc_utils/base.py
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
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
|
"""Base SWC format utils."""
|
|
2
17
|
|
|
3
|
-
from
|
|
4
|
-
from typing import
|
|
5
|
-
Callable,
|
|
6
|
-
List,
|
|
7
|
-
Literal,
|
|
8
|
-
NamedTuple,
|
|
9
|
-
Optional,
|
|
10
|
-
Tuple,
|
|
11
|
-
TypeVar,
|
|
12
|
-
overload,
|
|
13
|
-
)
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from typing import Literal, NamedTuple, Optional, TypeVar, overload
|
|
14
20
|
|
|
15
21
|
import numpy as np
|
|
16
22
|
import numpy.typing as npt
|
|
@@ -29,7 +35,7 @@ __all__ = [
|
|
|
29
35
|
]
|
|
30
36
|
|
|
31
37
|
T, K = TypeVar("T"), TypeVar("K")
|
|
32
|
-
Topology =
|
|
38
|
+
Topology = tuple[npt.NDArray[np.int32], npt.NDArray[np.int32]] # (id, pid)
|
|
33
39
|
|
|
34
40
|
|
|
35
41
|
class SWCNames(NamedTuple):
|
|
@@ -43,7 +49,7 @@ class SWCNames(NamedTuple):
|
|
|
43
49
|
r: str = "r"
|
|
44
50
|
pid: str = "pid"
|
|
45
51
|
|
|
46
|
-
def cols(self) ->
|
|
52
|
+
def cols(self) -> list[str]:
|
|
47
53
|
return [self.id, self.type, self.x, self.y, self.z, self.r, self.pid]
|
|
48
54
|
|
|
49
55
|
|
|
@@ -114,10 +120,10 @@ def get_dsu(
|
|
|
114
120
|
@overload
|
|
115
121
|
def traverse(topology: Topology, *, enter: Callable[[int, T | None], T], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> None: ...
|
|
116
122
|
@overload
|
|
117
|
-
def traverse(topology: Topology, *, leave: Callable[[int,
|
|
123
|
+
def traverse(topology: Topology, *, leave: Callable[[int, list[K]], K], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> K: ...
|
|
118
124
|
@overload
|
|
119
125
|
def traverse(
|
|
120
|
-
topology: Topology, *, enter: Callable[[int, T | None], T], leave: Callable[[int,
|
|
126
|
+
topology: Topology, *, enter: Callable[[int, T | None], T], leave: Callable[[int, list[K]], K],
|
|
121
127
|
root: int | np.integer = ..., mode: Literal["dfs"] = ...,
|
|
122
128
|
) -> K: ...
|
|
123
129
|
# fmt: on
|
|
@@ -130,7 +136,7 @@ def traverse(topology: Topology, *, mode="dfs", **kwargs):
|
|
|
130
136
|
The callback when entering node, which accepts two parameters,
|
|
131
137
|
the current node id and the return value of it parent node. In
|
|
132
138
|
particular, the root node receives an `None`.
|
|
133
|
-
leave : (id: int, children:
|
|
139
|
+
leave : (id: int, children: list[T]) => T, optional
|
|
134
140
|
The callback when leaving node. When leaving a node, subtree
|
|
135
141
|
has already been traversed. Callback accepts two parameters,
|
|
136
142
|
the current node id and list of the return value of children,
|
|
@@ -155,7 +161,7 @@ def _traverse_dfs(topology: Topology, *, enter=None, leave=None, root=0):
|
|
|
155
161
|
children_map[pid].append(idx)
|
|
156
162
|
|
|
157
163
|
# manual dfs to avoid stack overflow in long branch
|
|
158
|
-
stack:
|
|
164
|
+
stack: list[tuple[int, bool]] = [(root, True)] # (idx, is_enter)
|
|
159
165
|
params = {root: None}
|
|
160
166
|
vals = {}
|
|
161
167
|
|
|
@@ -1,11 +1,26 @@
|
|
|
1
|
-
|
|
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
|
+
|
|
16
|
+
"""Check common"""
|
|
2
17
|
|
|
3
|
-
import warnings
|
|
4
18
|
from collections import defaultdict
|
|
5
19
|
from typing import Optional
|
|
6
20
|
|
|
7
21
|
import numpy as np
|
|
8
22
|
import pandas as pd
|
|
23
|
+
from typing_extensions import deprecated
|
|
9
24
|
|
|
10
25
|
from swcgeom.core.swc_utils.base import SWCNames, Topology, get_dsu, get_names, traverse
|
|
11
26
|
from swcgeom.utils import DisjointSetUnion
|
|
@@ -81,6 +96,7 @@ def has_cyclic(topology: Topology) -> bool:
|
|
|
81
96
|
return False
|
|
82
97
|
|
|
83
98
|
|
|
99
|
+
@deprecated("Use `is_single_root` instead")
|
|
84
100
|
def check_single_root(*args, **kwargs) -> bool:
|
|
85
101
|
"""Check if the tree is single root.
|
|
86
102
|
|
|
@@ -88,14 +104,10 @@ def check_single_root(*args, **kwargs) -> bool:
|
|
|
88
104
|
Use :meth:`is_single_root` instead.
|
|
89
105
|
"""
|
|
90
106
|
|
|
91
|
-
warnings.warn(
|
|
92
|
-
"`check_single_root` has been renamed to `is_single_root` since"
|
|
93
|
-
"v0.5.0, and will be removed in next version",
|
|
94
|
-
DeprecationWarning,
|
|
95
|
-
)
|
|
96
107
|
return is_single_root(*args, **kwargs)
|
|
97
108
|
|
|
98
109
|
|
|
110
|
+
@deprecated("Use `is_bifurcate` instead")
|
|
99
111
|
def is_binary_tree(
|
|
100
112
|
df: pd.DataFrame, exclude_root: bool = True, *, names: Optional[SWCNames] = None
|
|
101
113
|
) -> bool:
|
|
@@ -105,11 +117,6 @@ def is_binary_tree(
|
|
|
105
117
|
Use :meth:`is_bifurcate` instead.
|
|
106
118
|
"""
|
|
107
119
|
|
|
108
|
-
warnings.warn(
|
|
109
|
-
"`is_binary_tree` has been replaced by to `is_bifurcate` since"
|
|
110
|
-
"v0.8.0, and will be removed in next version",
|
|
111
|
-
DeprecationWarning,
|
|
112
|
-
)
|
|
113
120
|
names = get_names(names)
|
|
114
121
|
topo = (df[names.id].to_numpy(), df[names.pid].to_numpy())
|
|
115
122
|
return is_bifurcate(topo, exclude_root=exclude_root)
|
swcgeom/core/swc_utils/io.py
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
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
|
"""Read and write swc format."""
|
|
2
17
|
|
|
3
18
|
import re
|
|
4
19
|
import warnings
|
|
5
|
-
from
|
|
20
|
+
from collections.abc import Callable, Iterable
|
|
21
|
+
from typing import Literal, Optional
|
|
6
22
|
|
|
7
23
|
import numpy as np
|
|
8
24
|
import numpy.typing as npt
|
|
@@ -30,7 +46,7 @@ def read_swc(
|
|
|
30
46
|
*,
|
|
31
47
|
encoding: Literal["detect"] | str = "utf-8",
|
|
32
48
|
names: Optional[SWCNames] = None,
|
|
33
|
-
) ->
|
|
49
|
+
) -> tuple[pd.DataFrame, list[str]]:
|
|
34
50
|
"""Read swc file.
|
|
35
51
|
|
|
36
52
|
Parameters
|
|
@@ -55,7 +71,7 @@ def read_swc(
|
|
|
55
71
|
Returns
|
|
56
72
|
-------
|
|
57
73
|
df : ~pandas.DataFrame
|
|
58
|
-
comments :
|
|
74
|
+
comments : List of string
|
|
59
75
|
"""
|
|
60
76
|
|
|
61
77
|
names = get_names(names)
|
|
@@ -137,14 +153,14 @@ def parse_swc(
|
|
|
137
153
|
names: SWCNames,
|
|
138
154
|
extra_cols: Iterable[str] | None = None,
|
|
139
155
|
encoding: Literal["detect"] | str = "utf-8",
|
|
140
|
-
) ->
|
|
156
|
+
) -> tuple[pd.DataFrame, list[str]]:
|
|
141
157
|
"""Parse swc file.
|
|
142
158
|
|
|
143
159
|
Parameters
|
|
144
160
|
----------
|
|
145
161
|
fname : PathOrIO
|
|
146
162
|
names : SWCNames
|
|
147
|
-
extra_cols :
|
|
163
|
+
extra_cols : List of str, optional
|
|
148
164
|
encoding : str | 'detect', default `utf-8`
|
|
149
165
|
The name of the encoding used to decode the file. If is
|
|
150
166
|
`detect`, we will try to detect the character encoding.
|
|
@@ -152,7 +168,7 @@ def parse_swc(
|
|
|
152
168
|
Returns
|
|
153
169
|
-------
|
|
154
170
|
df : ~pandas.DataFrame
|
|
155
|
-
comments :
|
|
171
|
+
comments : List of string
|
|
156
172
|
"""
|
|
157
173
|
|
|
158
174
|
# pylint: disable=too-many-locals
|
|
@@ -171,7 +187,8 @@ def parse_swc(
|
|
|
171
187
|
RE_FLOAT, # r
|
|
172
188
|
r"(-?[0-9]+)", # pid
|
|
173
189
|
] + [
|
|
174
|
-
RE_FLOAT
|
|
190
|
+
RE_FLOAT
|
|
191
|
+
for _ in extras # assert float
|
|
175
192
|
]
|
|
176
193
|
|
|
177
194
|
re_swc_cols_str = r"\s+".join(re_swc_cols)
|
|
@@ -1,9 +1,25 @@
|
|
|
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
|
"""SWC format utils.
|
|
2
17
|
|
|
3
18
|
Methods ending with a underline imply an in-place transformation.
|
|
4
19
|
"""
|
|
5
20
|
|
|
6
|
-
from
|
|
21
|
+
from collections.abc import Callable
|
|
22
|
+
from typing import Literal, Optional
|
|
7
23
|
|
|
8
24
|
import numpy as np
|
|
9
25
|
import numpy.typing as npt
|
|
@@ -76,7 +92,7 @@ def link_roots_to_nearest_(
|
|
|
76
92
|
vs = df[[names.x, names.y, names.z]] - row[[names.x, names.y, names.z]]
|
|
77
93
|
dis = np.linalg.norm(vs.to_numpy(), axis=1)
|
|
78
94
|
subtree = dsu == dsu[i] # type: ignore
|
|
79
|
-
dis = np.where(subtree, np.
|
|
95
|
+
dis = np.where(subtree, np.inf, dis) # avoid link to same tree
|
|
80
96
|
dsu = np.where(subtree, dsu[dis.argmin()], dsu) # merge set
|
|
81
97
|
df.loc[i, names.pid] = df[names.id].iloc[dis.argmin()] # type: ignore
|
|
82
98
|
|
|
@@ -111,7 +127,7 @@ def sort_nodes_(df: pd.DataFrame, *, names: Optional[SWCNames] = None) -> None:
|
|
|
111
127
|
df[names.id], df[names.pid] = new_ids, new_pids
|
|
112
128
|
|
|
113
129
|
|
|
114
|
-
def sort_nodes_impl(topology: Topology) ->
|
|
130
|
+
def sort_nodes_impl(topology: Topology) -> tuple[Topology, npt.NDArray[np.int32]]:
|
|
115
131
|
"""Sort the indices of neuron tree.
|
|
116
132
|
|
|
117
133
|
Returns
|
|
@@ -127,7 +143,7 @@ def sort_nodes_impl(topology: Topology) -> Tuple[Topology, npt.NDArray[np.int32]
|
|
|
127
143
|
new_pids = np.full_like(old_ids, fill_value=-3)
|
|
128
144
|
new_id = 0
|
|
129
145
|
first_root = old_ids[(old_pids == -1).argmax()]
|
|
130
|
-
s:
|
|
146
|
+
s: list[tuple[npt.NDArray[np.int32], int]] = [(first_root, -1)]
|
|
131
147
|
while len(s) != 0:
|
|
132
148
|
old_id, new_pid = s.pop()
|
|
133
149
|
id_map[new_id] = old_id
|