swcgeom 0.17.0__py3-none-any.whl → 0.17.2__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/_version.py +2 -2
- swcgeom/analysis/feature_extractor.py +25 -15
- swcgeom/analysis/features.py +20 -8
- swcgeom/analysis/lmeasure.py +33 -12
- swcgeom/analysis/sholl.py +10 -28
- swcgeom/analysis/trunk.py +12 -11
- swcgeom/analysis/visualization.py +9 -9
- swcgeom/analysis/visualization3d.py +85 -0
- swcgeom/analysis/volume.py +4 -4
- swcgeom/core/branch.py +4 -3
- swcgeom/core/branch_tree.py +3 -4
- swcgeom/core/compartment.py +3 -2
- swcgeom/core/node.py +17 -3
- swcgeom/core/path.py +6 -9
- swcgeom/core/population.py +43 -29
- swcgeom/core/swc.py +11 -10
- swcgeom/core/swc_utils/base.py +8 -17
- swcgeom/core/swc_utils/checker.py +3 -11
- swcgeom/core/swc_utils/io.py +7 -6
- swcgeom/core/swc_utils/normalizer.py +4 -3
- swcgeom/core/swc_utils/subtree.py +2 -2
- swcgeom/core/tree.py +41 -40
- swcgeom/core/tree_utils.py +13 -17
- swcgeom/core/tree_utils_impl.py +3 -3
- swcgeom/images/augmentation.py +3 -3
- swcgeom/images/folder.py +12 -26
- swcgeom/images/io.py +21 -35
- swcgeom/transforms/image_stack.py +20 -8
- swcgeom/transforms/images.py +3 -12
- swcgeom/transforms/neurolucida_asc.py +4 -6
- swcgeom/transforms/population.py +1 -3
- swcgeom/transforms/tree.py +38 -25
- swcgeom/transforms/tree_assembler.py +4 -3
- swcgeom/utils/download.py +44 -21
- swcgeom/utils/ellipse.py +3 -4
- swcgeom/utils/neuromorpho.py +17 -16
- swcgeom/utils/plotter_2d.py +12 -6
- swcgeom/utils/plotter_3d.py +31 -0
- swcgeom/utils/renderer.py +6 -6
- swcgeom/utils/sdf.py +4 -7
- swcgeom/utils/solid_geometry.py +1 -3
- swcgeom/utils/transforms.py +2 -4
- swcgeom/utils/volumetric_object.py +8 -10
- {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/METADATA +19 -19
- swcgeom-0.17.2.dist-info/RECORD +67 -0
- {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/WHEEL +1 -1
- swcgeom-0.17.0.dist-info/RECORD +0 -65
- {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/LICENSE +0 -0
- {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/top_level.txt +0 -0
swcgeom/analysis/volume.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Analysis of volume of a SWC tree."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Literal
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from sdflit import ColoredMaterial, ObjectsScene, SDFObject, UniformSampler
|
|
@@ -11,7 +11,7 @@ from swcgeom.utils import VolFrustumCone, VolSphere
|
|
|
11
11
|
__all__ = ["get_volume"]
|
|
12
12
|
|
|
13
13
|
ACCURACY_LEVEL = Literal["low", "middle", "high"]
|
|
14
|
-
ACCURACY_LEVELS:
|
|
14
|
+
ACCURACY_LEVELS: dict[ACCURACY_LEVEL, int] = {"low": 3, "middle": 5, "high": 8}
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def get_volume(
|
|
@@ -93,7 +93,7 @@ def _get_volume_frustum_cone(tree: Tree, *, accuracy: int) -> float:
|
|
|
93
93
|
|
|
94
94
|
volume = 0.0
|
|
95
95
|
|
|
96
|
-
def leave(n: Tree.Node, children:
|
|
96
|
+
def leave(n: Tree.Node, children: list[VolSphere]) -> VolSphere:
|
|
97
97
|
sphere = VolSphere(n.xyz(), n.r)
|
|
98
98
|
cones = [VolFrustumCone(n.xyz(), n.r, c.center, c.radius) for c in children]
|
|
99
99
|
|
|
@@ -129,7 +129,7 @@ def _get_volume_frustum_cone_mc_only(tree: Tree) -> float:
|
|
|
129
129
|
scene = ObjectsScene()
|
|
130
130
|
scene.set_background((0, 0, 0))
|
|
131
131
|
|
|
132
|
-
def leave(n: Tree.Node, children:
|
|
132
|
+
def leave(n: Tree.Node, children: list[VolSphere]) -> VolSphere:
|
|
133
133
|
sphere = VolSphere(n.xyz(), n.r)
|
|
134
134
|
scene.add_object(SDFObject(sphere.sdf, material).into())
|
|
135
135
|
|
swcgeom/core/branch.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Branch is a set of node points."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import Generic
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
7
|
import numpy.typing as npt
|
|
@@ -92,7 +93,7 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
92
93
|
@classmethod
|
|
93
94
|
def from_xyzr_batch(
|
|
94
95
|
cls, xyzr_batch: npt.NDArray[np.float32]
|
|
95
|
-
) ->
|
|
96
|
+
) -> list["Branch[DictSWC]"]:
|
|
96
97
|
r"""Create list of branch form ~numpy.ndarray.
|
|
97
98
|
|
|
98
99
|
Parameters
|
|
@@ -112,7 +113,7 @@ class Branch(Path, Generic[SWCTypeVar]):
|
|
|
112
113
|
)
|
|
113
114
|
xyzr_batch = np.concatenate([xyzr_batch, ones], axis=2)
|
|
114
115
|
|
|
115
|
-
branches:
|
|
116
|
+
branches: list[Branch[DictSWC]] = []
|
|
116
117
|
for xyzr in xyzr_batch:
|
|
117
118
|
n_nodes = xyzr.shape[0]
|
|
118
119
|
idx = np.arange(0, n_nodes, step=1, dtype=np.int32)
|
swcgeom/core/branch_tree.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Branch tree is a simplified neuron tree."""
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
|
-
from typing import Dict, List
|
|
5
4
|
|
|
6
5
|
import numpy as np
|
|
7
6
|
import pandas as pd
|
|
@@ -19,13 +18,13 @@ class BranchTree(Tree):
|
|
|
19
18
|
A branch tree that contains only soma, branch, and tip nodes.
|
|
20
19
|
"""
|
|
21
20
|
|
|
22
|
-
branches:
|
|
21
|
+
branches: dict[int, list[Branch]]
|
|
23
22
|
|
|
24
|
-
def get_origin_branches(self) ->
|
|
23
|
+
def get_origin_branches(self) -> list[Branch]:
|
|
25
24
|
"""Get branches of original tree."""
|
|
26
25
|
return list(itertools.chain(*self.branches.values()))
|
|
27
26
|
|
|
28
|
-
def get_origin_node_branches(self, idx: int) ->
|
|
27
|
+
def get_origin_node_branches(self, idx: int) -> list[Branch]:
|
|
29
28
|
"""Get branches of node of original tree."""
|
|
30
29
|
return self.branches[idx]
|
|
31
30
|
|
swcgeom/core/compartment.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""The segment is a branch with two nodes."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import Generic, TypeVar
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
7
|
import numpy.typing as npt
|
|
@@ -43,7 +44,7 @@ class Compartment(Path, Generic[SWCTypeVar]):
|
|
|
43
44
|
T = TypeVar("T", bound=Compartment)
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
class Compartments(
|
|
47
|
+
class Compartments(list[T]):
|
|
47
48
|
r"""Comparments contains a set of comparment."""
|
|
48
49
|
|
|
49
50
|
names: SWCNames
|
swcgeom/core/node.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Nueron node."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from typing import Any, Generic
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import Any, Generic
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import numpy.typing as npt
|
|
8
|
+
from typing_extensions import deprecated
|
|
8
9
|
|
|
9
10
|
from swcgeom.core.swc import DictSWC, SWCTypeVar
|
|
10
11
|
from swcgeom.core.swc_utils import SWCNames
|
|
@@ -95,9 +96,22 @@ class Node(Generic[SWCTypeVar]):
|
|
|
95
96
|
items = [self.id, self.type, x, y, z, r, self.pid]
|
|
96
97
|
return " ".join(map(str, items))
|
|
97
98
|
|
|
98
|
-
def
|
|
99
|
+
def is_furcation(self) -> bool:
|
|
100
|
+
"""Is furcation node."""
|
|
99
101
|
return np.count_nonzero(self.attach.pid() == self.id) > 1
|
|
100
102
|
|
|
103
|
+
@deprecated("Use is_furcation instead")
|
|
104
|
+
def is_bifurcation(self) -> bool:
|
|
105
|
+
"""Is furcation node.
|
|
106
|
+
|
|
107
|
+
Notes
|
|
108
|
+
-----
|
|
109
|
+
Deprecated due to the wrong spelling of furcation. For now, it
|
|
110
|
+
is just an alias of `is_furcation` and raise a warning. It will
|
|
111
|
+
be change to raise an error in the future.
|
|
112
|
+
"""
|
|
113
|
+
return self.is_furcation()
|
|
114
|
+
|
|
101
115
|
def is_tip(self) -> bool:
|
|
102
116
|
return self.id not in self.attach.pid()
|
|
103
117
|
|
swcgeom/core/path.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Nueron path."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from typing import Generic,
|
|
3
|
+
from collections.abc import Iterable, Iterator
|
|
4
|
+
from typing import Generic, overload
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import numpy.typing as npt
|
|
8
|
+
from typing_extensions import deprecated
|
|
8
9
|
|
|
9
10
|
from swcgeom.core.node import Node
|
|
10
11
|
from swcgeom.core.swc import DictSWC, SWCLike, SWCTypeVar
|
|
@@ -15,7 +16,7 @@ __all__ = ["Path"]
|
|
|
15
16
|
class Path(SWCLike, Generic[SWCTypeVar]):
|
|
16
17
|
"""Neuron path.
|
|
17
18
|
|
|
18
|
-
A path is a linear set of points without
|
|
19
|
+
A path is a linear set of points without furcations.
|
|
19
20
|
"""
|
|
20
21
|
|
|
21
22
|
attach: SWCTypeVar
|
|
@@ -44,7 +45,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
44
45
|
@overload
|
|
45
46
|
def __getitem__(self, key: int) -> Node: ...
|
|
46
47
|
@overload
|
|
47
|
-
def __getitem__(self, key: slice) ->
|
|
48
|
+
def __getitem__(self, key: slice) -> list[Node]: ...
|
|
48
49
|
@overload
|
|
49
50
|
def __getitem__(self, key: str) -> npt.NDArray: ...
|
|
50
51
|
# fmt:on
|
|
@@ -74,6 +75,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
74
75
|
def get_ndata(self, key: str) -> npt.NDArray:
|
|
75
76
|
return self.attach.get_ndata(key)[self.idx]
|
|
76
77
|
|
|
78
|
+
@deprecated("Use `path.node` instead.")
|
|
77
79
|
def get_node(self, idx: int | np.integer) -> Node:
|
|
78
80
|
"""Get the count of intersection.
|
|
79
81
|
|
|
@@ -81,11 +83,6 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
81
83
|
Use :meth:`path.node` instead.
|
|
82
84
|
"""
|
|
83
85
|
|
|
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
86
|
return self.node(idx)
|
|
90
87
|
|
|
91
88
|
def node(self, idx: int | np.integer) -> Node:
|
swcgeom/core/population.py
CHANGED
|
@@ -2,21 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import warnings
|
|
5
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
5
6
|
from concurrent.futures import ProcessPoolExecutor
|
|
6
7
|
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
|
-
)
|
|
8
|
+
from typing import Any, Optional, Protocol, TypeVar, cast, overload
|
|
20
9
|
|
|
21
10
|
import numpy as np
|
|
22
11
|
import numpy.typing as npt
|
|
@@ -44,16 +33,16 @@ class Trees(Protocol):
|
|
|
44
33
|
class LazyLoadingTrees:
|
|
45
34
|
"""Lazy loading trees."""
|
|
46
35
|
|
|
47
|
-
swcs:
|
|
48
|
-
trees:
|
|
49
|
-
kwargs:
|
|
36
|
+
swcs: list[str]
|
|
37
|
+
trees: list[Tree | None]
|
|
38
|
+
kwargs: dict[str, Any]
|
|
50
39
|
|
|
51
40
|
def __init__(self, swcs: Iterable[str], **kwargs) -> None:
|
|
52
41
|
"""
|
|
53
42
|
Paramters
|
|
54
43
|
---------
|
|
55
44
|
swcs : List of str
|
|
56
|
-
kwargs :
|
|
45
|
+
kwargs : dict[str, Any]
|
|
57
46
|
Forwarding to `Tree.from_swc`
|
|
58
47
|
"""
|
|
59
48
|
|
|
@@ -81,7 +70,7 @@ class LazyLoadingTrees:
|
|
|
81
70
|
class ChainTrees:
|
|
82
71
|
"""Chain trees."""
|
|
83
72
|
|
|
84
|
-
trees:
|
|
73
|
+
trees: list[Trees]
|
|
85
74
|
cumsum: npt.NDArray[np.int64]
|
|
86
75
|
|
|
87
76
|
def __init__(self, trees: Iterable[Trees]) -> None:
|
|
@@ -108,6 +97,19 @@ class ChainTrees:
|
|
|
108
97
|
return (self[i] for i in range(self.__len__()))
|
|
109
98
|
|
|
110
99
|
|
|
100
|
+
class NestTrees:
|
|
101
|
+
def __init__(self, trees: Trees, idx: Iterable[int], /) -> None:
|
|
102
|
+
super().__init__()
|
|
103
|
+
self.trees = trees
|
|
104
|
+
self.idx = list(idx)
|
|
105
|
+
|
|
106
|
+
def __getitem__(self, key: int, /) -> Tree:
|
|
107
|
+
return self.trees[self.idx[key]]
|
|
108
|
+
|
|
109
|
+
def __len__(self) -> int:
|
|
110
|
+
return len(self.idx)
|
|
111
|
+
|
|
112
|
+
|
|
111
113
|
class Population:
|
|
112
114
|
"""Neuron population."""
|
|
113
115
|
|
|
@@ -146,13 +148,14 @@ class Population:
|
|
|
146
148
|
|
|
147
149
|
# fmt:off
|
|
148
150
|
@overload
|
|
149
|
-
def __getitem__(self, key: slice) ->
|
|
151
|
+
def __getitem__(self, key: slice) -> Trees: ...
|
|
150
152
|
@overload
|
|
151
153
|
def __getitem__(self, key: int) -> Tree: ...
|
|
152
154
|
# fmt:on
|
|
153
|
-
def __getitem__(self, key):
|
|
155
|
+
def __getitem__(self, key: int | slice):
|
|
154
156
|
if isinstance(key, slice):
|
|
155
|
-
|
|
157
|
+
trees = NestTrees(self.trees, range(*key.indices(len(self))))
|
|
158
|
+
return cast(Trees, trees)
|
|
156
159
|
|
|
157
160
|
if isinstance(key, (int, np.integer)):
|
|
158
161
|
return cast(Tree, self.trees[int(key)])
|
|
@@ -216,9 +219,9 @@ class Population:
|
|
|
216
219
|
return cls.from_swc(root, ext, extra_cols=extra_cols, **kwargs)
|
|
217
220
|
|
|
218
221
|
@staticmethod
|
|
219
|
-
def find_swcs(root: str, ext: str = ".swc", relpath: bool = False) ->
|
|
222
|
+
def find_swcs(root: str, ext: str = ".swc", relpath: bool = False) -> list[str]:
|
|
220
223
|
"""Find all swc files."""
|
|
221
|
-
swcs:
|
|
224
|
+
swcs: list[str] = []
|
|
222
225
|
for r, _, files in os.walk(root):
|
|
223
226
|
rr = os.path.relpath(r, root) if relpath else r
|
|
224
227
|
fs = filter(lambda f: os.path.splitext(f)[-1] == ext, files)
|
|
@@ -231,8 +234,8 @@ class Populations:
|
|
|
231
234
|
"""A set of population."""
|
|
232
235
|
|
|
233
236
|
len: int
|
|
234
|
-
populations:
|
|
235
|
-
labels:
|
|
237
|
+
populations: list[Population]
|
|
238
|
+
labels: list[str]
|
|
236
239
|
|
|
237
240
|
def __init__(
|
|
238
241
|
self, populations: Iterable[Population], labels: Optional[Iterable[str]] = None
|
|
@@ -248,9 +251,9 @@ class Populations:
|
|
|
248
251
|
|
|
249
252
|
# fmt:off
|
|
250
253
|
@overload
|
|
251
|
-
def __getitem__(self, key: slice) ->
|
|
254
|
+
def __getitem__(self, key: slice) -> list[list[Tree]]: ...
|
|
252
255
|
@overload
|
|
253
|
-
def __getitem__(self, key: int) ->
|
|
256
|
+
def __getitem__(self, key: int) -> list[Tree]: ...
|
|
254
257
|
# fmt:on
|
|
255
258
|
def __getitem__(self, key):
|
|
256
259
|
return [p[key] for p in self.populations]
|
|
@@ -259,7 +262,7 @@ class Populations:
|
|
|
259
262
|
"""Miniumn length of populations."""
|
|
260
263
|
return self.len
|
|
261
264
|
|
|
262
|
-
def __iter__(self) -> Iterator[
|
|
265
|
+
def __iter__(self) -> Iterator[list[Tree]]:
|
|
263
266
|
return (self[i] for i in range(self.len))
|
|
264
267
|
|
|
265
268
|
def __repr__(self) -> str:
|
|
@@ -288,7 +291,7 @@ class Populations:
|
|
|
288
291
|
|
|
289
292
|
Parameters
|
|
290
293
|
----------
|
|
291
|
-
roots :
|
|
294
|
+
roots : List of str
|
|
292
295
|
intersect : bool, default `True`
|
|
293
296
|
Take the intersection of these populations.
|
|
294
297
|
check_same : bool, default `False`
|
|
@@ -339,3 +342,14 @@ def _get_idx(key: int, length: int) -> int:
|
|
|
339
342
|
key += length
|
|
340
343
|
|
|
341
344
|
return key
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# experimental
|
|
348
|
+
def filter_population(
|
|
349
|
+
pop: Population, predicate: Callable[[Tree], bool]
|
|
350
|
+
) -> "Population":
|
|
351
|
+
"""Filter trees in the population."""
|
|
352
|
+
|
|
353
|
+
# TODO: how to avoid load trees
|
|
354
|
+
idx = [i for i, t in enumerate(pop) if predicate(t)]
|
|
355
|
+
return Population(NestTrees(pop.trees, idx), root=pop.root)
|
swcgeom/core/swc.py
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
|
+
from collections.abc import Iterable
|
|
5
6
|
from copy import deepcopy
|
|
6
|
-
from typing import Any,
|
|
7
|
+
from typing import Any, Optional, TypeVar, overload
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
10
|
import numpy.typing as npt
|
|
@@ -32,7 +33,7 @@ __all__ = [
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
swc_names_default = get_names()
|
|
35
|
-
swc_cols:
|
|
36
|
+
swc_cols: list[tuple[str, npt.DTypeLike]] = [
|
|
36
37
|
(swc_names_default.id, np.int32),
|
|
37
38
|
(swc_names_default.type, np.int32),
|
|
38
39
|
(swc_names_default.x, np.float32),
|
|
@@ -42,7 +43,7 @@ swc_cols: List[Tuple[str, npt.DTypeLike]] = [
|
|
|
42
43
|
(swc_names_default.pid, np.int32),
|
|
43
44
|
]
|
|
44
45
|
|
|
45
|
-
eswc_cols:
|
|
46
|
+
eswc_cols: list[tuple[str, npt.DTypeLike]] = [
|
|
46
47
|
("level", np.int32),
|
|
47
48
|
("mode", np.int32),
|
|
48
49
|
("timestamp", np.int32),
|
|
@@ -55,7 +56,7 @@ class SWCLike(ABC):
|
|
|
55
56
|
"""ABC of SWC."""
|
|
56
57
|
|
|
57
58
|
source: str = ""
|
|
58
|
-
comments:
|
|
59
|
+
comments: list[str] = []
|
|
59
60
|
names: SWCNames
|
|
60
61
|
types: SWCTypes
|
|
61
62
|
|
|
@@ -131,15 +132,15 @@ class SWCLike(ABC):
|
|
|
131
132
|
|
|
132
133
|
# fmt: off
|
|
133
134
|
@overload
|
|
134
|
-
def to_swc(self, fname: str, *, extra_cols:
|
|
135
|
+
def to_swc(self, fname: str, *, extra_cols: list[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> None: ...
|
|
135
136
|
@overload
|
|
136
|
-
def to_swc(self, *, extra_cols:
|
|
137
|
+
def to_swc(self, *, extra_cols: list[str] | None = ..., source: bool | str = ..., id_offset: int = ...) -> str: ...
|
|
137
138
|
# fmt: on
|
|
138
139
|
def to_swc(
|
|
139
140
|
self,
|
|
140
141
|
fname: Optional[str] = None,
|
|
141
142
|
*,
|
|
142
|
-
extra_cols: Optional[
|
|
143
|
+
extra_cols: Optional[list[str]] = None,
|
|
143
144
|
source: bool | str = True,
|
|
144
145
|
comments: bool = True,
|
|
145
146
|
id_offset: int = 1,
|
|
@@ -177,7 +178,7 @@ class SWCLike(ABC):
|
|
|
177
178
|
self,
|
|
178
179
|
fname: Optional[str] = None,
|
|
179
180
|
swc_path: Optional[str] = None,
|
|
180
|
-
extra_cols: Optional[
|
|
181
|
+
extra_cols: Optional[list[str]] = None,
|
|
181
182
|
**kwargs,
|
|
182
183
|
) -> str | None:
|
|
183
184
|
if swc_path is None:
|
|
@@ -199,7 +200,7 @@ SWCTypeVar = TypeVar("SWCTypeVar", bound=SWCLike)
|
|
|
199
200
|
class DictSWC(SWCLike):
|
|
200
201
|
"""SWC implementation on dict."""
|
|
201
202
|
|
|
202
|
-
ndata:
|
|
203
|
+
ndata: dict[str, npt.NDArray]
|
|
203
204
|
|
|
204
205
|
def __init__(
|
|
205
206
|
self,
|
|
@@ -221,7 +222,7 @@ class DictSWC(SWCLike):
|
|
|
221
222
|
def values(self) -> Iterable[npt.NDArray[Any]]:
|
|
222
223
|
return self.ndata.values()
|
|
223
224
|
|
|
224
|
-
def items(self) -> Iterable[
|
|
225
|
+
def items(self) -> Iterable[tuple[str, npt.NDArray[Any]]]:
|
|
225
226
|
return self.ndata.items()
|
|
226
227
|
|
|
227
228
|
def get_ndata(self, key: str) -> npt.NDArray[Any]:
|
swcgeom/core/swc_utils/base.py
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
"""Base SWC format utils."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from typing import
|
|
5
|
-
Callable,
|
|
6
|
-
List,
|
|
7
|
-
Literal,
|
|
8
|
-
NamedTuple,
|
|
9
|
-
Optional,
|
|
10
|
-
Tuple,
|
|
11
|
-
TypeVar,
|
|
12
|
-
overload,
|
|
13
|
-
)
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Literal, NamedTuple, Optional, TypeVar, overload
|
|
14
5
|
|
|
15
6
|
import numpy as np
|
|
16
7
|
import numpy.typing as npt
|
|
@@ -29,7 +20,7 @@ __all__ = [
|
|
|
29
20
|
]
|
|
30
21
|
|
|
31
22
|
T, K = TypeVar("T"), TypeVar("K")
|
|
32
|
-
Topology =
|
|
23
|
+
Topology = tuple[npt.NDArray[np.int32], npt.NDArray[np.int32]] # (id, pid)
|
|
33
24
|
|
|
34
25
|
|
|
35
26
|
class SWCNames(NamedTuple):
|
|
@@ -43,7 +34,7 @@ class SWCNames(NamedTuple):
|
|
|
43
34
|
r: str = "r"
|
|
44
35
|
pid: str = "pid"
|
|
45
36
|
|
|
46
|
-
def cols(self) ->
|
|
37
|
+
def cols(self) -> list[str]:
|
|
47
38
|
return [self.id, self.type, self.x, self.y, self.z, self.r, self.pid]
|
|
48
39
|
|
|
49
40
|
|
|
@@ -114,10 +105,10 @@ def get_dsu(
|
|
|
114
105
|
@overload
|
|
115
106
|
def traverse(topology: Topology, *, enter: Callable[[int, T | None], T], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> None: ...
|
|
116
107
|
@overload
|
|
117
|
-
def traverse(topology: Topology, *, leave: Callable[[int,
|
|
108
|
+
def traverse(topology: Topology, *, leave: Callable[[int, list[K]], K], root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> K: ...
|
|
118
109
|
@overload
|
|
119
110
|
def traverse(
|
|
120
|
-
topology: Topology, *, enter: Callable[[int, T | None], T], leave: Callable[[int,
|
|
111
|
+
topology: Topology, *, enter: Callable[[int, T | None], T], leave: Callable[[int, list[K]], K],
|
|
121
112
|
root: int | np.integer = ..., mode: Literal["dfs"] = ...,
|
|
122
113
|
) -> K: ...
|
|
123
114
|
# fmt: on
|
|
@@ -130,7 +121,7 @@ def traverse(topology: Topology, *, mode="dfs", **kwargs):
|
|
|
130
121
|
The callback when entering node, which accepts two parameters,
|
|
131
122
|
the current node id and the return value of it parent node. In
|
|
132
123
|
particular, the root node receives an `None`.
|
|
133
|
-
leave : (id: int, children:
|
|
124
|
+
leave : (id: int, children: list[T]) => T, optional
|
|
134
125
|
The callback when leaving node. When leaving a node, subtree
|
|
135
126
|
has already been traversed. Callback accepts two parameters,
|
|
136
127
|
the current node id and list of the return value of children,
|
|
@@ -155,7 +146,7 @@ def _traverse_dfs(topology: Topology, *, enter=None, leave=None, root=0):
|
|
|
155
146
|
children_map[pid].append(idx)
|
|
156
147
|
|
|
157
148
|
# manual dfs to avoid stack overflow in long branch
|
|
158
|
-
stack:
|
|
149
|
+
stack: list[tuple[int, bool]] = [(root, True)] # (idx, is_enter)
|
|
159
150
|
params = {root: None}
|
|
160
151
|
vals = {}
|
|
161
152
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Check common """
|
|
2
2
|
|
|
3
|
-
import warnings
|
|
4
3
|
from collections import defaultdict
|
|
5
4
|
from typing import Optional
|
|
6
5
|
|
|
7
6
|
import numpy as np
|
|
8
7
|
import pandas as pd
|
|
8
|
+
from typing_extensions import deprecated
|
|
9
9
|
|
|
10
10
|
from swcgeom.core.swc_utils.base import SWCNames, Topology, get_dsu, get_names, traverse
|
|
11
11
|
from swcgeom.utils import DisjointSetUnion
|
|
@@ -81,6 +81,7 @@ def has_cyclic(topology: Topology) -> bool:
|
|
|
81
81
|
return False
|
|
82
82
|
|
|
83
83
|
|
|
84
|
+
@deprecated("Use `is_single_root` instead")
|
|
84
85
|
def check_single_root(*args, **kwargs) -> bool:
|
|
85
86
|
"""Check if the tree is single root.
|
|
86
87
|
|
|
@@ -88,14 +89,10 @@ def check_single_root(*args, **kwargs) -> bool:
|
|
|
88
89
|
Use :meth:`is_single_root` instead.
|
|
89
90
|
"""
|
|
90
91
|
|
|
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
92
|
return is_single_root(*args, **kwargs)
|
|
97
93
|
|
|
98
94
|
|
|
95
|
+
@deprecated("Use `is_bifurcate` instead")
|
|
99
96
|
def is_binary_tree(
|
|
100
97
|
df: pd.DataFrame, exclude_root: bool = True, *, names: Optional[SWCNames] = None
|
|
101
98
|
) -> bool:
|
|
@@ -105,11 +102,6 @@ def is_binary_tree(
|
|
|
105
102
|
Use :meth:`is_bifurcate` instead.
|
|
106
103
|
"""
|
|
107
104
|
|
|
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
105
|
names = get_names(names)
|
|
114
106
|
topo = (df[names.id].to_numpy(), df[names.pid].to_numpy())
|
|
115
107
|
return is_bifurcate(topo, exclude_root=exclude_root)
|
swcgeom/core/swc_utils/io.py
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import warnings
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import Callable, Iterable
|
|
6
|
+
from typing import Literal, Optional
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
8
9
|
import numpy.typing as npt
|
|
@@ -30,7 +31,7 @@ def read_swc(
|
|
|
30
31
|
*,
|
|
31
32
|
encoding: Literal["detect"] | str = "utf-8",
|
|
32
33
|
names: Optional[SWCNames] = None,
|
|
33
|
-
) ->
|
|
34
|
+
) -> tuple[pd.DataFrame, list[str]]:
|
|
34
35
|
"""Read swc file.
|
|
35
36
|
|
|
36
37
|
Parameters
|
|
@@ -55,7 +56,7 @@ def read_swc(
|
|
|
55
56
|
Returns
|
|
56
57
|
-------
|
|
57
58
|
df : ~pandas.DataFrame
|
|
58
|
-
comments :
|
|
59
|
+
comments : List of string
|
|
59
60
|
"""
|
|
60
61
|
|
|
61
62
|
names = get_names(names)
|
|
@@ -137,14 +138,14 @@ def parse_swc(
|
|
|
137
138
|
names: SWCNames,
|
|
138
139
|
extra_cols: Iterable[str] | None = None,
|
|
139
140
|
encoding: Literal["detect"] | str = "utf-8",
|
|
140
|
-
) ->
|
|
141
|
+
) -> tuple[pd.DataFrame, list[str]]:
|
|
141
142
|
"""Parse swc file.
|
|
142
143
|
|
|
143
144
|
Parameters
|
|
144
145
|
----------
|
|
145
146
|
fname : PathOrIO
|
|
146
147
|
names : SWCNames
|
|
147
|
-
extra_cols :
|
|
148
|
+
extra_cols : List of str, optional
|
|
148
149
|
encoding : str | 'detect', default `utf-8`
|
|
149
150
|
The name of the encoding used to decode the file. If is
|
|
150
151
|
`detect`, we will try to detect the character encoding.
|
|
@@ -152,7 +153,7 @@ def parse_swc(
|
|
|
152
153
|
Returns
|
|
153
154
|
-------
|
|
154
155
|
df : ~pandas.DataFrame
|
|
155
|
-
comments :
|
|
156
|
+
comments : List of string
|
|
156
157
|
"""
|
|
157
158
|
|
|
158
159
|
# pylint: disable=too-many-locals
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Methods ending with a underline imply an in-place transformation.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Literal, Optional
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
10
|
import numpy.typing as npt
|
|
@@ -111,7 +112,7 @@ def sort_nodes_(df: pd.DataFrame, *, names: Optional[SWCNames] = None) -> None:
|
|
|
111
112
|
df[names.id], df[names.pid] = new_ids, new_pids
|
|
112
113
|
|
|
113
114
|
|
|
114
|
-
def sort_nodes_impl(topology: Topology) ->
|
|
115
|
+
def sort_nodes_impl(topology: Topology) -> tuple[Topology, npt.NDArray[np.int32]]:
|
|
115
116
|
"""Sort the indices of neuron tree.
|
|
116
117
|
|
|
117
118
|
Returns
|
|
@@ -127,7 +128,7 @@ def sort_nodes_impl(topology: Topology) -> Tuple[Topology, npt.NDArray[np.int32]
|
|
|
127
128
|
new_pids = np.full_like(old_ids, fill_value=-3)
|
|
128
129
|
new_id = 0
|
|
129
130
|
first_root = old_ids[(old_pids == -1).argmax()]
|
|
130
|
-
s:
|
|
131
|
+
s: list[tuple[npt.NDArray[np.int32], int]] = [(first_root, -1)]
|
|
131
132
|
while len(s) != 0:
|
|
132
133
|
old_id, new_pid = s.pop()
|
|
133
134
|
id_map[new_id] = old_id
|
|
@@ -6,7 +6,7 @@ but in more cases, you can use the high-level methods provided in
|
|
|
6
6
|
high-level API.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import cast
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
import numpy.typing as npt
|
|
@@ -18,7 +18,7 @@ __all__ = ["REMOVAL", "to_sub_topology", "propagate_removal"]
|
|
|
18
18
|
REMOVAL = -2 # A marker in utils, place in the ids to mark it removal
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def to_sub_topology(sub: Topology) ->
|
|
21
|
+
def to_sub_topology(sub: Topology) -> tuple[Topology, npt.NDArray[np.int32]]:
|
|
22
22
|
"""Create sub tree from origin tree.
|
|
23
23
|
|
|
24
24
|
Mark the node to be removed, then use this method to get a child
|