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
|
@@ -1,6 +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
|
"""The segment is a branch with two nodes."""
|
|
2
17
|
|
|
3
|
-
from
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
from typing import Generic, TypeVar
|
|
4
20
|
|
|
5
21
|
import numpy as np
|
|
6
22
|
import numpy.typing as npt
|
|
@@ -9,11 +25,11 @@ from swcgeom.core.path import Path
|
|
|
9
25
|
from swcgeom.core.swc import DictSWC, SWCTypeVar
|
|
10
26
|
from swcgeom.core.swc_utils import SWCNames, get_names
|
|
11
27
|
|
|
12
|
-
__all__ = ["Segment", "Segments"]
|
|
28
|
+
__all__ = ["Compartment", "Compartments", "Segment", "Segments"]
|
|
13
29
|
|
|
14
30
|
|
|
15
|
-
class
|
|
16
|
-
r"""
|
|
31
|
+
class Compartment(Path, Generic[SWCTypeVar]):
|
|
32
|
+
r"""Compartment attached to external object."""
|
|
17
33
|
|
|
18
34
|
attach: SWCTypeVar
|
|
19
35
|
idx: npt.NDArray[np.int32]
|
|
@@ -27,7 +43,7 @@ class Segment(Path, Generic[SWCTypeVar]):
|
|
|
27
43
|
def get_ndata(self, key: str) -> npt.NDArray:
|
|
28
44
|
return self.attach.get_ndata(key)[self.idx]
|
|
29
45
|
|
|
30
|
-
def detach(self) -> "
|
|
46
|
+
def detach(self) -> "Compartment[DictSWC]":
|
|
31
47
|
"""Detach from current attached object."""
|
|
32
48
|
# pylint: disable=consider-using-dict-items
|
|
33
49
|
attact = DictSWC(
|
|
@@ -37,18 +53,18 @@ class Segment(Path, Generic[SWCTypeVar]):
|
|
|
37
53
|
)
|
|
38
54
|
attact.ndata[self.names.id] = self.id()
|
|
39
55
|
attact.ndata[self.names.pid] = self.pid()
|
|
40
|
-
return
|
|
56
|
+
return Compartment(attact, 0, 1)
|
|
41
57
|
|
|
42
58
|
|
|
43
|
-
|
|
59
|
+
T = TypeVar("T", bound=Compartment)
|
|
44
60
|
|
|
45
61
|
|
|
46
|
-
class
|
|
47
|
-
r"""
|
|
62
|
+
class Compartments(list[T]):
|
|
63
|
+
r"""Comparments contains a set of comparment."""
|
|
48
64
|
|
|
49
65
|
names: SWCNames
|
|
50
66
|
|
|
51
|
-
def __init__(self, segments: Iterable[
|
|
67
|
+
def __init__(self, segments: Iterable[T]) -> None:
|
|
52
68
|
super().__init__(segments)
|
|
53
69
|
self.names = self[0].names if len(self) > 0 else get_names()
|
|
54
70
|
|
|
@@ -94,3 +110,8 @@ class Segments(List[SegmentT]):
|
|
|
94
110
|
The order of axis 1 is (parent, current node).
|
|
95
111
|
"""
|
|
96
112
|
return np.array([s.get_ndata(key) for s in self])
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Aliases
|
|
116
|
+
Segment = Compartment
|
|
117
|
+
Segments = Compartments
|
swcgeom/core/node.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 node."""
|
|
2
17
|
|
|
3
|
-
import
|
|
4
|
-
from typing import Any, Generic
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
from typing import Any, Generic
|
|
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.swc import DictSWC, SWCTypeVar
|
|
10
26
|
from swcgeom.core.swc_utils import SWCNames
|
|
@@ -95,16 +111,21 @@ class Node(Generic[SWCTypeVar]):
|
|
|
95
111
|
items = [self.id, self.type, x, y, z, r, self.pid]
|
|
96
112
|
return " ".join(map(str, items))
|
|
97
113
|
|
|
98
|
-
def
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"will be removed in next version",
|
|
102
|
-
DeprecationWarning,
|
|
103
|
-
)
|
|
104
|
-
return self.attach.id()[self.attach.pid() == self.id]
|
|
114
|
+
def is_furcation(self) -> bool:
|
|
115
|
+
"""Is furcation node."""
|
|
116
|
+
return np.count_nonzero(self.attach.pid() == self.id) > 1
|
|
105
117
|
|
|
118
|
+
@deprecated("Use is_furcation instead")
|
|
106
119
|
def is_bifurcation(self) -> bool:
|
|
107
|
-
|
|
120
|
+
"""Is furcation node.
|
|
121
|
+
|
|
122
|
+
Notes
|
|
123
|
+
-----
|
|
124
|
+
Deprecated due to the wrong spelling of furcation. For now, it
|
|
125
|
+
is just an alias of `is_furcation` and raise a warning. It will
|
|
126
|
+
be change to raise an error in the future.
|
|
127
|
+
"""
|
|
128
|
+
return self.is_furcation()
|
|
108
129
|
|
|
109
130
|
def is_tip(self) -> bool:
|
|
110
131
|
return self.id not in self.attach.pid()
|
swcgeom/core/path.py
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
+
"""Nueron path."""
|
|
17
|
+
|
|
18
|
+
from collections.abc import Iterable, Iterator
|
|
19
|
+
from typing import Generic, overload
|
|
4
20
|
|
|
5
21
|
import numpy as np
|
|
6
22
|
import numpy.typing as npt
|
|
23
|
+
from typing_extensions import deprecated
|
|
7
24
|
|
|
8
25
|
from swcgeom.core.node import Node
|
|
9
26
|
from swcgeom.core.swc import DictSWC, SWCLike, SWCTypeVar
|
|
@@ -12,9 +29,9 @@ __all__ = ["Path"]
|
|
|
12
29
|
|
|
13
30
|
|
|
14
31
|
class Path(SWCLike, Generic[SWCTypeVar]):
|
|
15
|
-
"""
|
|
32
|
+
"""Neuron path.
|
|
16
33
|
|
|
17
|
-
A path is a linear set of points without
|
|
34
|
+
A path is a linear set of points without furcations.
|
|
18
35
|
"""
|
|
19
36
|
|
|
20
37
|
attach: SWCTypeVar
|
|
@@ -31,7 +48,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
31
48
|
self.source = self.attach.source
|
|
32
49
|
|
|
33
50
|
def __iter__(self) -> Iterator[Node]:
|
|
34
|
-
return (self.
|
|
51
|
+
return (self.node(i) for i in range(len(self)))
|
|
35
52
|
|
|
36
53
|
def __len__(self) -> int:
|
|
37
54
|
return self.id().shape[0]
|
|
@@ -43,14 +60,14 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
43
60
|
@overload
|
|
44
61
|
def __getitem__(self, key: int) -> Node: ...
|
|
45
62
|
@overload
|
|
46
|
-
def __getitem__(self, key: slice) ->
|
|
63
|
+
def __getitem__(self, key: slice) -> list[Node]: ...
|
|
47
64
|
@overload
|
|
48
65
|
def __getitem__(self, key: str) -> npt.NDArray: ...
|
|
49
66
|
# fmt:on
|
|
50
67
|
|
|
51
68
|
def __getitem__(self, key):
|
|
52
69
|
if isinstance(key, slice):
|
|
53
|
-
return [self.
|
|
70
|
+
return [self.node(i) for i in range(*key.indices(len(self)))]
|
|
54
71
|
|
|
55
72
|
if isinstance(key, (int, np.integer)):
|
|
56
73
|
length = len(self)
|
|
@@ -60,7 +77,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
60
77
|
if key < 0: # Handle negative indices
|
|
61
78
|
key += length
|
|
62
79
|
|
|
63
|
-
return self.
|
|
80
|
+
return self.node(key)
|
|
64
81
|
|
|
65
82
|
if isinstance(key, str):
|
|
66
83
|
return self.get_ndata(key)
|
|
@@ -73,7 +90,17 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
73
90
|
def get_ndata(self, key: str) -> npt.NDArray:
|
|
74
91
|
return self.attach.get_ndata(key)[self.idx]
|
|
75
92
|
|
|
93
|
+
@deprecated("Use `path.node` instead.")
|
|
76
94
|
def get_node(self, idx: int | np.integer) -> Node:
|
|
95
|
+
"""Get the count of intersection.
|
|
96
|
+
|
|
97
|
+
.. deprecated:: 0.16.0
|
|
98
|
+
Use :meth:`path.node` instead.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
return self.node(idx)
|
|
102
|
+
|
|
103
|
+
def node(self, idx: int | np.integer) -> Node:
|
|
77
104
|
return self.Node(self, idx)
|
|
78
105
|
|
|
79
106
|
def detach(self) -> "Path[DictSWC]":
|
|
@@ -129,7 +156,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
|
|
|
129
156
|
The end-to-end straight-line distance between start point and
|
|
130
157
|
end point.
|
|
131
158
|
"""
|
|
132
|
-
return np.linalg.norm(self.
|
|
159
|
+
return np.linalg.norm(self.node(-1).xyz() - self.node(0).xyz()).item()
|
|
133
160
|
|
|
134
161
|
def tortuosity(self) -> float:
|
|
135
162
|
"""Tortuosity of path.
|
swcgeom/core/population.py
CHANGED
|
@@ -1,22 +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
|
"""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
|
|
21
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
5
22
|
from functools import reduce
|
|
6
|
-
from typing import
|
|
7
|
-
Any,
|
|
8
|
-
Dict,
|
|
9
|
-
Iterable,
|
|
10
|
-
Iterator,
|
|
11
|
-
List,
|
|
12
|
-
Optional,
|
|
13
|
-
Protocol,
|
|
14
|
-
cast,
|
|
15
|
-
overload,
|
|
16
|
-
)
|
|
23
|
+
from typing import Any, Optional, Protocol, TypeVar, cast, overload
|
|
17
24
|
|
|
18
25
|
import numpy as np
|
|
19
26
|
import numpy.typing as npt
|
|
27
|
+
from tqdm.contrib.concurrent import process_map
|
|
28
|
+
from typing_extensions import Self
|
|
20
29
|
|
|
21
30
|
from swcgeom.core.swc import eswc_cols
|
|
22
31
|
from swcgeom.core.tree import Tree
|
|
@@ -24,6 +33,9 @@ from swcgeom.core.tree import Tree
|
|
|
24
33
|
__all__ = ["LazyLoadingTrees", "ChainTrees", "Population", "Populations"]
|
|
25
34
|
|
|
26
35
|
|
|
36
|
+
T = TypeVar("T")
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
class Trees(Protocol):
|
|
28
40
|
"""Trees protocol support index and len."""
|
|
29
41
|
|
|
@@ -36,18 +48,19 @@ class Trees(Protocol):
|
|
|
36
48
|
class LazyLoadingTrees:
|
|
37
49
|
"""Lazy loading trees."""
|
|
38
50
|
|
|
39
|
-
swcs:
|
|
40
|
-
trees:
|
|
41
|
-
kwargs:
|
|
51
|
+
swcs: list[str]
|
|
52
|
+
trees: list[Tree | None]
|
|
53
|
+
kwargs: dict[str, Any]
|
|
42
54
|
|
|
43
55
|
def __init__(self, swcs: Iterable[str], **kwargs) -> None:
|
|
44
56
|
"""
|
|
45
57
|
Paramters
|
|
46
58
|
---------
|
|
47
59
|
swcs : List of str
|
|
48
|
-
kwargs :
|
|
60
|
+
kwargs : dict[str, Any]
|
|
49
61
|
Forwarding to `Tree.from_swc`
|
|
50
62
|
"""
|
|
63
|
+
|
|
51
64
|
super().__init__()
|
|
52
65
|
self.swcs = list(swcs)
|
|
53
66
|
self.trees = [None for _ in swcs]
|
|
@@ -61,6 +74,9 @@ class LazyLoadingTrees:
|
|
|
61
74
|
def __len__(self) -> int:
|
|
62
75
|
return len(self.swcs)
|
|
63
76
|
|
|
77
|
+
def __iter__(self) -> Iterator[Tree]:
|
|
78
|
+
return (self[i] for i in range(self.__len__()))
|
|
79
|
+
|
|
64
80
|
def load(self, key: int) -> None:
|
|
65
81
|
if self.trees[key] is None:
|
|
66
82
|
self.trees[key] = Tree.from_swc(self.swcs[key], **self.kwargs)
|
|
@@ -69,7 +85,7 @@ class LazyLoadingTrees:
|
|
|
69
85
|
class ChainTrees:
|
|
70
86
|
"""Chain trees."""
|
|
71
87
|
|
|
72
|
-
trees:
|
|
88
|
+
trees: list[Trees]
|
|
73
89
|
cumsum: npt.NDArray[np.int64]
|
|
74
90
|
|
|
75
91
|
def __init__(self, trees: Iterable[Trees]) -> None:
|
|
@@ -92,6 +108,22 @@ class ChainTrees:
|
|
|
92
108
|
def __len__(self) -> int:
|
|
93
109
|
return self.cumsum[-1].item()
|
|
94
110
|
|
|
111
|
+
def __iter__(self) -> Iterator[Tree]:
|
|
112
|
+
return (self[i] for i in range(self.__len__()))
|
|
113
|
+
|
|
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
|
+
|
|
95
127
|
|
|
96
128
|
class Population:
|
|
97
129
|
"""Neuron population."""
|
|
@@ -131,13 +163,14 @@ class Population:
|
|
|
131
163
|
|
|
132
164
|
# fmt:off
|
|
133
165
|
@overload
|
|
134
|
-
def __getitem__(self, key: slice) ->
|
|
166
|
+
def __getitem__(self, key: slice) -> Trees: ...
|
|
135
167
|
@overload
|
|
136
168
|
def __getitem__(self, key: int) -> Tree: ...
|
|
137
169
|
# fmt:on
|
|
138
|
-
def __getitem__(self, key):
|
|
170
|
+
def __getitem__(self, key: int | slice):
|
|
139
171
|
if isinstance(key, slice):
|
|
140
|
-
|
|
172
|
+
trees = NestTrees(self.trees, range(*key.indices(len(self))))
|
|
173
|
+
return cast(Trees, trees)
|
|
141
174
|
|
|
142
175
|
if isinstance(key, (int, np.integer)):
|
|
143
176
|
return cast(Tree, self.trees[int(key)])
|
|
@@ -153,15 +186,40 @@ class Population:
|
|
|
153
186
|
def __repr__(self) -> str:
|
|
154
187
|
return f"Neuron population in '{self.root}'"
|
|
155
188
|
|
|
189
|
+
def map(
|
|
190
|
+
self,
|
|
191
|
+
fn: Callable[[Tree], T],
|
|
192
|
+
*,
|
|
193
|
+
max_worker: Optional[int] = None,
|
|
194
|
+
verbose: bool = False,
|
|
195
|
+
) -> Iterator[T]:
|
|
196
|
+
"""Map a function to all trees in the population.
|
|
197
|
+
|
|
198
|
+
This is a straightforward interface for parallelizing
|
|
199
|
+
computations. The parameters are intentionally kept simple and
|
|
200
|
+
user-friendly. For more advanced control, consider using
|
|
201
|
+
`concurrent.futures` directly.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
trees = (t for t in self.trees)
|
|
205
|
+
|
|
206
|
+
if verbose:
|
|
207
|
+
results = process_map(fn, trees, max_workers=max_worker)
|
|
208
|
+
else:
|
|
209
|
+
with ProcessPoolExecutor(max_worker) as p:
|
|
210
|
+
results = p.map(fn, trees)
|
|
211
|
+
|
|
212
|
+
return results
|
|
213
|
+
|
|
156
214
|
@classmethod
|
|
157
|
-
def from_swc(cls, root: str, ext: str = ".swc", **kwargs) ->
|
|
215
|
+
def from_swc(cls, root: str, ext: str = ".swc", **kwargs) -> Self:
|
|
158
216
|
if not os.path.exists(root):
|
|
159
217
|
raise FileNotFoundError(
|
|
160
218
|
f"the root does not refers to an existing directory: {root}"
|
|
161
219
|
)
|
|
162
220
|
|
|
163
221
|
swcs = cls.find_swcs(root, ext)
|
|
164
|
-
return
|
|
222
|
+
return cls(LazyLoadingTrees(swcs, **kwargs), root=root)
|
|
165
223
|
|
|
166
224
|
@classmethod
|
|
167
225
|
def from_eswc(
|
|
@@ -170,15 +228,15 @@ class Population:
|
|
|
170
228
|
ext: str = ".eswc",
|
|
171
229
|
extra_cols: Optional[Iterable[str]] = None,
|
|
172
230
|
**kwargs,
|
|
173
|
-
) ->
|
|
231
|
+
) -> Self:
|
|
174
232
|
extra_cols = list(extra_cols) if extra_cols is not None else []
|
|
175
233
|
extra_cols.extend(k for k, t in eswc_cols)
|
|
176
234
|
return cls.from_swc(root, ext, extra_cols=extra_cols, **kwargs)
|
|
177
235
|
|
|
178
236
|
@staticmethod
|
|
179
|
-
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]:
|
|
180
238
|
"""Find all swc files."""
|
|
181
|
-
swcs:
|
|
239
|
+
swcs: list[str] = []
|
|
182
240
|
for r, _, files in os.walk(root):
|
|
183
241
|
rr = os.path.relpath(r, root) if relpath else r
|
|
184
242
|
fs = filter(lambda f: os.path.splitext(f)[-1] == ext, files)
|
|
@@ -191,8 +249,8 @@ class Populations:
|
|
|
191
249
|
"""A set of population."""
|
|
192
250
|
|
|
193
251
|
len: int
|
|
194
|
-
populations:
|
|
195
|
-
labels:
|
|
252
|
+
populations: list[Population]
|
|
253
|
+
labels: list[str]
|
|
196
254
|
|
|
197
255
|
def __init__(
|
|
198
256
|
self, populations: Iterable[Population], labels: Optional[Iterable[str]] = None
|
|
@@ -208,9 +266,9 @@ class Populations:
|
|
|
208
266
|
|
|
209
267
|
# fmt:off
|
|
210
268
|
@overload
|
|
211
|
-
def __getitem__(self, key: slice) ->
|
|
269
|
+
def __getitem__(self, key: slice) -> list[list[Tree]]: ...
|
|
212
270
|
@overload
|
|
213
|
-
def __getitem__(self, key: int) ->
|
|
271
|
+
def __getitem__(self, key: int) -> list[Tree]: ...
|
|
214
272
|
# fmt:on
|
|
215
273
|
def __getitem__(self, key):
|
|
216
274
|
return [p[key] for p in self.populations]
|
|
@@ -219,7 +277,7 @@ class Populations:
|
|
|
219
277
|
"""Miniumn length of populations."""
|
|
220
278
|
return self.len
|
|
221
279
|
|
|
222
|
-
def __iter__(self) -> Iterator[
|
|
280
|
+
def __iter__(self) -> Iterator[list[Tree]]:
|
|
223
281
|
return (self[i] for i in range(self.len))
|
|
224
282
|
|
|
225
283
|
def __repr__(self) -> str:
|
|
@@ -235,7 +293,7 @@ class Populations:
|
|
|
235
293
|
return Population(ChainTrees(p.trees for p in self.populations))
|
|
236
294
|
|
|
237
295
|
@classmethod
|
|
238
|
-
def from_swc(
|
|
296
|
+
def from_swc(
|
|
239
297
|
cls,
|
|
240
298
|
roots: Iterable[str],
|
|
241
299
|
ext: str = ".swc",
|
|
@@ -243,12 +301,12 @@ class Populations:
|
|
|
243
301
|
check_same: bool = False,
|
|
244
302
|
labels: Optional[Iterable[str]] = None,
|
|
245
303
|
**kwargs,
|
|
246
|
-
) ->
|
|
304
|
+
) -> Self:
|
|
247
305
|
"""Get population from dirs.
|
|
248
306
|
|
|
249
307
|
Parameters
|
|
250
308
|
----------
|
|
251
|
-
roots :
|
|
309
|
+
roots : List of str
|
|
252
310
|
intersect : bool, default `True`
|
|
253
311
|
Take the intersection of these populations.
|
|
254
312
|
check_same : bool, default `False`
|
|
@@ -275,7 +333,7 @@ class Populations:
|
|
|
275
333
|
)
|
|
276
334
|
for i, d in enumerate(roots)
|
|
277
335
|
]
|
|
278
|
-
return
|
|
336
|
+
return cls(populations, labels=labels)
|
|
279
337
|
|
|
280
338
|
@classmethod
|
|
281
339
|
def from_eswc(
|
|
@@ -285,7 +343,7 @@ class Populations:
|
|
|
285
343
|
*,
|
|
286
344
|
ext: str = ".eswc",
|
|
287
345
|
**kwargs,
|
|
288
|
-
) ->
|
|
346
|
+
) -> Self:
|
|
289
347
|
extra_cols = list(extra_cols) if extra_cols is not None else []
|
|
290
348
|
extra_cols.extend(k for k, t in eswc_cols)
|
|
291
349
|
return cls.from_swc(roots, extra_cols=extra_cols, ext=ext, **kwargs)
|
|
@@ -299,3 +357,14 @@ def _get_idx(key: int, length: int) -> int:
|
|
|
299
357
|
key += length
|
|
300
358
|
|
|
301
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
|