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/core/tree.py
CHANGED
|
@@ -2,24 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
4
|
import os
|
|
5
|
-
import
|
|
6
|
-
from typing import
|
|
7
|
-
Callable,
|
|
8
|
-
Dict,
|
|
9
|
-
Iterable,
|
|
10
|
-
Iterator,
|
|
11
|
-
List,
|
|
12
|
-
Literal,
|
|
13
|
-
Optional,
|
|
14
|
-
Tuple,
|
|
15
|
-
TypeVar,
|
|
16
|
-
Union,
|
|
17
|
-
overload,
|
|
18
|
-
)
|
|
5
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
6
|
+
from typing import Literal, Optional, TypeVar, Union, overload
|
|
19
7
|
|
|
20
8
|
import numpy as np
|
|
21
9
|
import numpy.typing as npt
|
|
22
10
|
import pandas as pd
|
|
11
|
+
from typing_extensions import deprecated
|
|
23
12
|
|
|
24
13
|
from swcgeom.core.branch import Branch
|
|
25
14
|
from swcgeom.core.compartment import Compartment, Compartments
|
|
@@ -44,17 +33,17 @@ class Tree(DictSWC):
|
|
|
44
33
|
def parent(self) -> Union["Tree.Node", None]:
|
|
45
34
|
return Tree.Node(self.attach, self.pid) if self.pid != -1 else None
|
|
46
35
|
|
|
47
|
-
def children(self) ->
|
|
36
|
+
def children(self) -> list["Tree.Node"]:
|
|
48
37
|
children = self.attach.id()[self.attach.pid() == self.id]
|
|
49
38
|
return [Tree.Node(self.attach, idx) for idx in children]
|
|
50
39
|
|
|
51
40
|
def branch(self) -> "Tree.Branch":
|
|
52
|
-
ns:
|
|
53
|
-
while not ns[-1].
|
|
41
|
+
ns: list["Tree.Node"] = [self]
|
|
42
|
+
while not ns[-1].is_furcation() and (p := ns[-1].parent()) is not None:
|
|
54
43
|
ns.append(p)
|
|
55
44
|
|
|
56
45
|
ns.reverse()
|
|
57
|
-
while not (ns[-1].
|
|
46
|
+
while not (ns[-1].is_furcation() or ns[-1].is_tip()):
|
|
58
47
|
ns.append(ns[-1].children()[0])
|
|
59
48
|
|
|
60
49
|
return Tree.Branch(self.attach, [n.id for n in ns])
|
|
@@ -68,7 +57,7 @@ class Tree(DictSWC):
|
|
|
68
57
|
|
|
69
58
|
Parameters
|
|
70
59
|
----------
|
|
71
|
-
out_mapping : List of int or
|
|
60
|
+
out_mapping : List of int or dict[int, int], optional
|
|
72
61
|
Map from new id to old id.
|
|
73
62
|
"""
|
|
74
63
|
|
|
@@ -160,7 +149,7 @@ class Tree(DictSWC):
|
|
|
160
149
|
|
|
161
150
|
# fmt:off
|
|
162
151
|
@overload
|
|
163
|
-
def __getitem__(self, key: slice) ->
|
|
152
|
+
def __getitem__(self, key: slice) -> list[Node]: ...
|
|
164
153
|
@overload
|
|
165
154
|
def __getitem__(self, key: int) -> Node: ...
|
|
166
155
|
@overload
|
|
@@ -199,18 +188,30 @@ class Tree(DictSWC):
|
|
|
199
188
|
raise ValueError(f"no soma found in: {self.source}")
|
|
200
189
|
return n
|
|
201
190
|
|
|
202
|
-
def
|
|
203
|
-
"""Get all node of
|
|
204
|
-
|
|
191
|
+
def get_furcations(self) -> list[Node]:
|
|
192
|
+
"""Get all node of furcations."""
|
|
193
|
+
furcations: list[int] = []
|
|
205
194
|
|
|
206
|
-
def
|
|
195
|
+
def collect_furcations(n: Tree.Node, children: list[None]) -> None:
|
|
207
196
|
if len(children) > 1:
|
|
208
|
-
|
|
197
|
+
furcations.append(n.id)
|
|
209
198
|
|
|
210
|
-
self.traverse(leave=
|
|
211
|
-
return [self.node(i) for i in
|
|
199
|
+
self.traverse(leave=collect_furcations)
|
|
200
|
+
return [self.node(i) for i in furcations]
|
|
212
201
|
|
|
213
|
-
|
|
202
|
+
@deprecated("Use `get_furcations` instead")
|
|
203
|
+
def get_bifurcations(self) -> list[Node]:
|
|
204
|
+
"""Get all node of furcations.
|
|
205
|
+
|
|
206
|
+
Notes
|
|
207
|
+
-----
|
|
208
|
+
Deprecated due to the wrong spelling of furcation. For now, it
|
|
209
|
+
is just an alias of `get_furcations` and raise a warning. It
|
|
210
|
+
will be change to raise an error in the future.
|
|
211
|
+
"""
|
|
212
|
+
return self.get_furcations()
|
|
213
|
+
|
|
214
|
+
def get_tips(self) -> list[Node]:
|
|
214
215
|
"""Get all node of tips."""
|
|
215
216
|
tip_ids = np.setdiff1d(self.id(), self.pid(), assume_unique=True)
|
|
216
217
|
return [self.node(i) for i in tip_ids]
|
|
@@ -221,16 +222,16 @@ class Tree(DictSWC):
|
|
|
221
222
|
def get_segments(self) -> Compartments[Compartment]: # Alias
|
|
222
223
|
return self.get_compartments()
|
|
223
224
|
|
|
224
|
-
def get_branches(self) ->
|
|
225
|
+
def get_branches(self) -> list[Branch]:
|
|
225
226
|
def collect_branches(
|
|
226
|
-
node: "Tree.Node", pre:
|
|
227
|
-
) ->
|
|
227
|
+
node: "Tree.Node", pre: list[tuple[list[Tree.Branch], list[int]]]
|
|
228
|
+
) -> tuple[list[Tree.Branch], list[int]]:
|
|
228
229
|
if len(pre) == 1:
|
|
229
230
|
branches, child = pre[0]
|
|
230
231
|
child.append(node.id)
|
|
231
232
|
return branches, child
|
|
232
233
|
|
|
233
|
-
branches:
|
|
234
|
+
branches: list[Tree.Branch] = []
|
|
234
235
|
|
|
235
236
|
for sub_branches, child in pre:
|
|
236
237
|
child.append(node.id)
|
|
@@ -244,19 +245,19 @@ class Tree(DictSWC):
|
|
|
244
245
|
branches, _ = self.traverse(leave=collect_branches)
|
|
245
246
|
return branches
|
|
246
247
|
|
|
247
|
-
def get_paths(self) ->
|
|
248
|
+
def get_paths(self) -> list[Path]:
|
|
248
249
|
"""Get all path from soma to tips."""
|
|
249
|
-
path_dic:
|
|
250
|
+
path_dic: dict[int, list[int]] = {}
|
|
250
251
|
|
|
251
|
-
def assign_path(n: Tree.Node, pre_path:
|
|
252
|
+
def assign_path(n: Tree.Node, pre_path: list[int] | None) -> list[int]:
|
|
252
253
|
path = [] if pre_path is None else pre_path.copy()
|
|
253
254
|
path.append(n.id)
|
|
254
255
|
path_dic[n.id] = path
|
|
255
256
|
return path
|
|
256
257
|
|
|
257
258
|
def collect_path(
|
|
258
|
-
n: Tree.Node, children:
|
|
259
|
-
) ->
|
|
259
|
+
n: Tree.Node, children: list[list[list[int]]]
|
|
260
|
+
) -> list[list[int]]:
|
|
260
261
|
if len(children) == 0:
|
|
261
262
|
return [path_dic[n.id]]
|
|
262
263
|
|
|
@@ -283,7 +284,7 @@ class Tree(DictSWC):
|
|
|
283
284
|
@overload
|
|
284
285
|
def traverse(self, *,
|
|
285
286
|
enter: Optional[Callable[[Node, T | None], T]] = ...,
|
|
286
|
-
leave: Callable[[Node,
|
|
287
|
+
leave: Callable[[Node, list[K]], K],
|
|
287
288
|
root: int | np.integer = ..., mode: Literal["dfs"] = ...) -> K: ...
|
|
288
289
|
# fmt: on
|
|
289
290
|
|
|
@@ -293,7 +294,7 @@ class Tree(DictSWC):
|
|
|
293
294
|
Parameters
|
|
294
295
|
----------
|
|
295
296
|
enter : (n: Node, parent: T | None) => T, optional
|
|
296
|
-
leave : (n: Node, children:
|
|
297
|
+
leave : (n: Node, children: list[T]) => T, optional
|
|
297
298
|
|
|
298
299
|
See Also
|
|
299
300
|
--------
|
|
@@ -355,7 +356,7 @@ class Tree(DictSWC):
|
|
|
355
356
|
|
|
356
357
|
@classmethod
|
|
357
358
|
def from_eswc(
|
|
358
|
-
cls, swc_file: str, extra_cols: Optional[
|
|
359
|
+
cls, swc_file: str, extra_cols: Optional[list[str]] = None, **kwargs
|
|
359
360
|
) -> "Tree":
|
|
360
361
|
"""Read neuron tree from eswc file.
|
|
361
362
|
|
swcgeom/core/tree_utils.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""SWC util wrapper for tree."""
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Callable, Iterable
|
|
5
|
+
from typing import Optional, TypeVar, overload
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
8
|
+
from typing_extensions import deprecated
|
|
7
9
|
|
|
8
10
|
from swcgeom.core.swc import SWCLike
|
|
9
11
|
from swcgeom.core.swc_utils import (
|
|
@@ -50,9 +52,9 @@ def sort_tree(tree: Tree) -> Tree:
|
|
|
50
52
|
|
|
51
53
|
# fmt:off
|
|
52
54
|
@overload
|
|
53
|
-
def cut_tree(tree: Tree, *, enter: Callable[[Tree.Node, T | None],
|
|
55
|
+
def cut_tree(tree: Tree, *, enter: Callable[[Tree.Node, T | None], tuple[T, bool]]) -> Tree: ...
|
|
54
56
|
@overload
|
|
55
|
-
def cut_tree(tree: Tree, *, leave: Callable[[Tree.Node,
|
|
57
|
+
def cut_tree(tree: Tree, *, leave: Callable[[Tree.Node, list[K]], tuple[K, bool]]) -> Tree: ...
|
|
56
58
|
# fmt:on
|
|
57
59
|
def cut_tree(tree: Tree, *, enter=None, leave=None):
|
|
58
60
|
"""Traverse and cut the tree.
|
|
@@ -60,11 +62,11 @@ def cut_tree(tree: Tree, *, enter=None, leave=None):
|
|
|
60
62
|
Returning a `True` can delete the current node and its children.
|
|
61
63
|
"""
|
|
62
64
|
|
|
63
|
-
removals:
|
|
65
|
+
removals: list[int] = []
|
|
64
66
|
|
|
65
67
|
if enter:
|
|
66
68
|
|
|
67
|
-
def _enter(n: Tree.Node, parent:
|
|
69
|
+
def _enter(n: Tree.Node, parent: tuple[T, bool] | None) -> tuple[T, bool]:
|
|
68
70
|
if parent is not None and parent[1]:
|
|
69
71
|
removals.append(n.id)
|
|
70
72
|
return parent
|
|
@@ -79,7 +81,7 @@ def cut_tree(tree: Tree, *, enter=None, leave=None):
|
|
|
79
81
|
|
|
80
82
|
elif leave:
|
|
81
83
|
|
|
82
|
-
def _leave(n: Tree.Node, children:
|
|
84
|
+
def _leave(n: Tree.Node, children: list[K]) -> K:
|
|
83
85
|
res, removal = leave(n, children)
|
|
84
86
|
if removal:
|
|
85
87
|
removals.append(n.id)
|
|
@@ -94,7 +96,8 @@ def cut_tree(tree: Tree, *, enter=None, leave=None):
|
|
|
94
96
|
return to_subtree(tree, removals)
|
|
95
97
|
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
@deprecated("Use `to_subtree` instead")
|
|
100
|
+
def to_sub_tree(swc_like: SWCLike, sub: Topology) -> tuple[Tree, dict[int, int]]:
|
|
98
101
|
"""Create subtree from origin tree.
|
|
99
102
|
|
|
100
103
|
You can directly mark the node for removal, and we will remove it,
|
|
@@ -107,16 +110,9 @@ def to_sub_tree(swc_like: SWCLike, sub: Topology) -> Tuple[Tree, Dict[int, int]]
|
|
|
107
110
|
Returns
|
|
108
111
|
-------
|
|
109
112
|
tree : Tree
|
|
110
|
-
id_map :
|
|
113
|
+
id_map : dict[int, int]
|
|
111
114
|
"""
|
|
112
115
|
|
|
113
|
-
warnings.warn(
|
|
114
|
-
"`to_sub_tree` will be removed in v0.6.0, it is replaced by "
|
|
115
|
-
"`to_subtree` beacuse it is easy to use, and this will be "
|
|
116
|
-
"removed in next version",
|
|
117
|
-
DeprecationWarning,
|
|
118
|
-
)
|
|
119
|
-
|
|
120
116
|
sub = propagate_removal(sub)
|
|
121
117
|
(new_id, new_pid), id_map_arr = to_sub_topology(sub)
|
|
122
118
|
|
|
@@ -145,7 +141,7 @@ def to_subtree(
|
|
|
145
141
|
swc_like : SWCLike
|
|
146
142
|
removals : List of int
|
|
147
143
|
A list of id of nodes to be removed.
|
|
148
|
-
out_mapping: List of int or
|
|
144
|
+
out_mapping: List of int or dict[int, int], optional
|
|
149
145
|
Map new id to old id.
|
|
150
146
|
"""
|
|
151
147
|
|
|
@@ -170,7 +166,7 @@ def get_subtree(
|
|
|
170
166
|
swc_like : SWCLike
|
|
171
167
|
n : int
|
|
172
168
|
Id of the root of the subtree.
|
|
173
|
-
out_mapping: List of int or
|
|
169
|
+
out_mapping: List of int or dict[int, int], optional
|
|
174
170
|
Map new id to old id.
|
|
175
171
|
"""
|
|
176
172
|
|
swcgeom/core/tree_utils_impl.py
CHANGED
|
@@ -5,7 +5,7 @@ Notes
|
|
|
5
5
|
Do not import `Tree` and keep this file minimized.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, Optional
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
import numpy.typing as npt
|
|
@@ -15,8 +15,8 @@ from swcgeom.core.swc_utils import Topology, to_sub_topology, traverse
|
|
|
15
15
|
|
|
16
16
|
__all__ = ["get_subtree_impl", "to_subtree_impl"]
|
|
17
17
|
|
|
18
|
-
Mapping =
|
|
19
|
-
TreeArgs =
|
|
18
|
+
Mapping = dict[int, int] | list[int]
|
|
19
|
+
TreeArgs = tuple[int, dict[str, npt.NDArray[Any]], str, SWCNames]
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def get_subtree_impl(
|
swcgeom/images/augmentation.py
CHANGED
|
@@ -6,7 +6,7 @@ This is expremental code, and the API is subject to change.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import random
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Literal, Optional
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
import numpy.typing as npt
|
|
@@ -54,7 +54,7 @@ class Augmentation:
|
|
|
54
54
|
|
|
55
55
|
def swapaxes(self, x, mode: Optional[Literal["xy", "xz", "yz"]] = None) -> NDArrf32:
|
|
56
56
|
if mode is None:
|
|
57
|
-
modes:
|
|
57
|
+
modes: list[Literal["xy", "xz", "yz"]] = ["xy", "xz", "yz"]
|
|
58
58
|
mode = modes[self.rand.randint(0, 2)]
|
|
59
59
|
|
|
60
60
|
match mode:
|
|
@@ -69,7 +69,7 @@ class Augmentation:
|
|
|
69
69
|
|
|
70
70
|
def flip(self, x, mode: Optional[Literal["xy", "xz", "yz"]] = None) -> NDArrf32:
|
|
71
71
|
if mode is None:
|
|
72
|
-
modes:
|
|
72
|
+
modes: list[Literal["xy", "xz", "yz"]] = ["xy", "xz", "yz"]
|
|
73
73
|
mode = modes[random.randint(0, 2)]
|
|
74
74
|
|
|
75
75
|
match mode:
|
swcgeom/images/folder.py
CHANGED
|
@@ -3,24 +3,14 @@
|
|
|
3
3
|
import math
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
|
-
import
|
|
6
|
+
from collections.abc import Callable, Iterable
|
|
7
7
|
from dataclasses import dataclass
|
|
8
|
-
from typing import
|
|
9
|
-
Callable,
|
|
10
|
-
Generic,
|
|
11
|
-
Iterable,
|
|
12
|
-
List,
|
|
13
|
-
Literal,
|
|
14
|
-
Optional,
|
|
15
|
-
Tuple,
|
|
16
|
-
TypeVar,
|
|
17
|
-
overload,
|
|
18
|
-
)
|
|
8
|
+
from typing import Generic, Literal, Optional, TypeVar, overload
|
|
19
9
|
|
|
20
10
|
import numpy as np
|
|
21
11
|
import numpy.typing as npt
|
|
22
12
|
from tqdm import tqdm
|
|
23
|
-
from typing_extensions import Self
|
|
13
|
+
from typing_extensions import Self, deprecated
|
|
24
14
|
|
|
25
15
|
from swcgeom.images.io import ScalarType, read_imgs
|
|
26
16
|
from swcgeom.transforms import Identity, Transform
|
|
@@ -33,7 +23,7 @@ T = TypeVar("T")
|
|
|
33
23
|
class ImageStackFolderBase(Generic[ScalarType, T]):
|
|
34
24
|
"""Image stack folder base."""
|
|
35
25
|
|
|
36
|
-
files:
|
|
26
|
+
files: list[str]
|
|
37
27
|
transform: Transform[npt.NDArray[ScalarType], T]
|
|
38
28
|
|
|
39
29
|
# fmt: off
|
|
@@ -61,7 +51,10 @@ class ImageStackFolderBase(Generic[ScalarType, T]):
|
|
|
61
51
|
return read_imgs(fname, dtype=self.dtype).get_full() # type: ignore
|
|
62
52
|
|
|
63
53
|
@staticmethod
|
|
64
|
-
def scan(root: str, *, pattern: Optional[str] = None) ->
|
|
54
|
+
def scan(root: str, *, pattern: Optional[str] = None) -> list[str]:
|
|
55
|
+
if not os.path.isdir(root):
|
|
56
|
+
raise NotADirectoryError(f"not a directory: {root}")
|
|
57
|
+
|
|
65
58
|
is_valid = re.compile(pattern).match if pattern is not None else truthly
|
|
66
59
|
|
|
67
60
|
fs = []
|
|
@@ -71,6 +64,7 @@ class ImageStackFolderBase(Generic[ScalarType, T]):
|
|
|
71
64
|
return fs
|
|
72
65
|
|
|
73
66
|
@staticmethod
|
|
67
|
+
@deprecated("Use `~swcgeom.images.io.read_imgs(fname).get_full()` instead")
|
|
74
68
|
def read_imgs(fname: str) -> npt.NDArray[np.float32]:
|
|
75
69
|
"""Read images.
|
|
76
70
|
|
|
@@ -78,14 +72,6 @@ class ImageStackFolderBase(Generic[ScalarType, T]):
|
|
|
78
72
|
Use :meth:`~swcgeom.images.io.read_imgs(fname).get_full()` instead.
|
|
79
73
|
"""
|
|
80
74
|
|
|
81
|
-
warnings.warn(
|
|
82
|
-
"`ImageStackFolderBase.read_imgs` serves as a "
|
|
83
|
-
"straightforward wrapper for `~swcgeom.images.io.read_imgs(fname).get_full()`. "
|
|
84
|
-
"However, as it is not utilized within our internal "
|
|
85
|
-
"processes, it is scheduled for removal in the "
|
|
86
|
-
"forthcoming version.",
|
|
87
|
-
DeprecationWarning,
|
|
88
|
-
)
|
|
89
75
|
return read_imgs(fname).get_full()
|
|
90
76
|
|
|
91
77
|
|
|
@@ -168,13 +154,13 @@ class ImageStackFolder(ImageStackFolderBase[ScalarType, T]):
|
|
|
168
154
|
class LabeledImageStackFolder(ImageStackFolderBase[ScalarType, T]):
|
|
169
155
|
"""Image stack folder with label."""
|
|
170
156
|
|
|
171
|
-
labels:
|
|
157
|
+
labels: list[int]
|
|
172
158
|
|
|
173
159
|
def __init__(self, files: Iterable[str], labels: Iterable[int], **kwargs):
|
|
174
160
|
super().__init__(files, **kwargs)
|
|
175
161
|
self.labels = list(labels)
|
|
176
162
|
|
|
177
|
-
def __getitem__(self, idx: int) ->
|
|
163
|
+
def __getitem__(self, idx: int) -> tuple[T, int]:
|
|
178
164
|
return self._get(self.files[idx]), self.labels[idx]
|
|
179
165
|
|
|
180
166
|
@classmethod
|
|
@@ -205,7 +191,7 @@ class PathImageStackFolder(ImageStackFolderBase[ScalarType, T]):
|
|
|
205
191
|
super().__init__(files, **kwargs)
|
|
206
192
|
self.root = root
|
|
207
193
|
|
|
208
|
-
def __getitem__(self, idx: int) ->
|
|
194
|
+
def __getitem__(self, idx: int) -> tuple[T, str]:
|
|
209
195
|
relpath = os.path.relpath(self.files[idx], self.root)
|
|
210
196
|
return self._get(self.files[idx]), relpath
|
|
211
197
|
|
swcgeom/images/io.py
CHANGED
|
@@ -4,30 +4,20 @@ import os
|
|
|
4
4
|
import re
|
|
5
5
|
import warnings
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
|
+
from collections.abc import Callable, Iterable
|
|
7
8
|
from functools import cache, lru_cache
|
|
8
|
-
from typing import
|
|
9
|
-
Any,
|
|
10
|
-
Callable,
|
|
11
|
-
Generic,
|
|
12
|
-
Iterable,
|
|
13
|
-
List,
|
|
14
|
-
Literal,
|
|
15
|
-
Optional,
|
|
16
|
-
Tuple,
|
|
17
|
-
TypeVar,
|
|
18
|
-
cast,
|
|
19
|
-
overload,
|
|
20
|
-
)
|
|
9
|
+
from typing import Any, Generic, Literal, Optional, TypeVar, cast, overload
|
|
21
10
|
|
|
22
11
|
import nrrd
|
|
23
12
|
import numpy as np
|
|
24
13
|
import numpy.typing as npt
|
|
25
14
|
import tifffile
|
|
15
|
+
from typing_extensions import deprecated
|
|
26
16
|
from v3dpy.loaders import PBD, Raw
|
|
27
17
|
|
|
28
18
|
__all__ = ["read_imgs", "save_tiff", "read_images"]
|
|
29
19
|
|
|
30
|
-
Vec3i =
|
|
20
|
+
Vec3i = tuple[int, int, int]
|
|
31
21
|
ScalarType = TypeVar("ScalarType", bound=np.generic, covariant=True)
|
|
32
22
|
|
|
33
23
|
RE_TERAFLY_ROOT = re.compile(r"^RES\((\d+)x(\d+)x(\d+)\)$")
|
|
@@ -58,17 +48,17 @@ class ImageStack(ABC, Generic[ScalarType]):
|
|
|
58
48
|
def __getitem__(self, key: int) -> npt.NDArray[ScalarType]: ... # array of shape (Y, Z, C)
|
|
59
49
|
@overload
|
|
60
50
|
@abstractmethod
|
|
61
|
-
def __getitem__(self, key:
|
|
51
|
+
def __getitem__(self, key: tuple[int, int]) -> npt.NDArray[ScalarType]: ... # array of shape (Z, C)
|
|
62
52
|
@overload
|
|
63
53
|
@abstractmethod
|
|
64
|
-
def __getitem__(self, key:
|
|
54
|
+
def __getitem__(self, key: tuple[int, int, int]) -> npt.NDArray[ScalarType]: ... # array of shape (C,)
|
|
65
55
|
@overload
|
|
66
56
|
@abstractmethod
|
|
67
|
-
def __getitem__(self, key:
|
|
57
|
+
def __getitem__(self, key: tuple[int, int, int, int]) -> ScalarType: ... # value
|
|
68
58
|
@overload
|
|
69
59
|
@abstractmethod
|
|
70
60
|
def __getitem__(
|
|
71
|
-
self, key: slice |
|
|
61
|
+
self, key: slice | tuple[slice, slice] | tuple[slice, slice, slice] | tuple[slice, slice, slice, slice],
|
|
72
62
|
) -> npt.NDArray[ScalarType]: ... # array of shape (X, Y, Z, C)
|
|
73
63
|
@overload
|
|
74
64
|
@abstractmethod
|
|
@@ -95,7 +85,7 @@ class ImageStack(ABC, Generic[ScalarType]):
|
|
|
95
85
|
return self[:, :, :, :]
|
|
96
86
|
|
|
97
87
|
@property
|
|
98
|
-
def shape(self) ->
|
|
88
|
+
def shape(self) -> tuple[int, int, int, int]:
|
|
99
89
|
raise NotImplementedError()
|
|
100
90
|
|
|
101
91
|
|
|
@@ -118,7 +108,7 @@ def read_imgs(fname: str, **kwargs): # type: ignore
|
|
|
118
108
|
Casting data to specified dtype. If integer and float
|
|
119
109
|
conversions occur, they will be scaled (assuming floats are
|
|
120
110
|
between 0 and 1).
|
|
121
|
-
**kwargs :
|
|
111
|
+
**kwargs : dict[str, Any]
|
|
122
112
|
Forwarding to the corresponding reader.
|
|
123
113
|
"""
|
|
124
114
|
|
|
@@ -169,7 +159,7 @@ def save_tiff(
|
|
|
169
159
|
Compression algorithm, forwarding to `tifffile.imwrite`. If no
|
|
170
160
|
algorithnm is specify specified, we will use the zlib algorithm
|
|
171
161
|
with compression level 6 by default.
|
|
172
|
-
**kwargs :
|
|
162
|
+
**kwargs : dict[str, Any]
|
|
173
163
|
Forwarding to `tifffile.imwrite`
|
|
174
164
|
"""
|
|
175
165
|
if isinstance(data, ImageStack):
|
|
@@ -245,8 +235,8 @@ class NDArrayImageStack(ImageStack[ScalarType]):
|
|
|
245
235
|
return self.imgs
|
|
246
236
|
|
|
247
237
|
@property
|
|
248
|
-
def shape(self) ->
|
|
249
|
-
return cast(
|
|
238
|
+
def shape(self) -> tuple[int, int, int, int]:
|
|
239
|
+
return cast(tuple[int, int, int, int], self.imgs.shape)
|
|
250
240
|
|
|
251
241
|
|
|
252
242
|
class TiffImageStack(NDArrayImageStack[ScalarType]):
|
|
@@ -324,7 +314,7 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
324
314
|
use its coordinate system, remember to FLIP Y-AXIS BACK.
|
|
325
315
|
"""
|
|
326
316
|
|
|
327
|
-
_listdir: Callable[[str],
|
|
317
|
+
_listdir: Callable[[str], list[str]]
|
|
328
318
|
_read_patch: Callable[[str], npt.NDArray]
|
|
329
319
|
|
|
330
320
|
def __init__(
|
|
@@ -350,7 +340,7 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
350
340
|
self.res, self.res_dirs, self.res_patch_sizes = self.get_resolutions(root)
|
|
351
341
|
|
|
352
342
|
@cache
|
|
353
|
-
def listdir(path: str) ->
|
|
343
|
+
def listdir(path: str) -> list[str]:
|
|
354
344
|
return os.listdir(path)
|
|
355
345
|
|
|
356
346
|
@lru_cache(maxsize=lru_maxsize)
|
|
@@ -429,19 +419,19 @@ class TeraflyImageStack(ImageStack[ScalarType]):
|
|
|
429
419
|
raise NotImplementedError() # TODO
|
|
430
420
|
|
|
431
421
|
@property
|
|
432
|
-
def shape(self) ->
|
|
422
|
+
def shape(self) -> tuple[int, int, int, int]:
|
|
433
423
|
res_max = self.res[-1]
|
|
434
424
|
return res_max[0], res_max[1], res_max[2], 1
|
|
435
425
|
|
|
436
426
|
@classmethod
|
|
437
|
-
def get_resolutions(cls, root: str) ->
|
|
427
|
+
def get_resolutions(cls, root: str) -> tuple[list[Vec3i], list[str], list[Vec3i]]:
|
|
438
428
|
"""Get all resolutions.
|
|
439
429
|
|
|
440
430
|
Returns
|
|
441
431
|
-------
|
|
442
432
|
resolutions : List of (int, int, int)
|
|
443
433
|
Sequence of sorted resolutions (from small to large).
|
|
444
|
-
roots :
|
|
434
|
+
roots : list[str]
|
|
445
435
|
Sequence of root of resolutions respectively.
|
|
446
436
|
patch_sizes : List of (int, int, int)
|
|
447
437
|
Sequence of patch size of resolutions respectively.
|
|
@@ -581,7 +571,7 @@ class GrayImageStack:
|
|
|
581
571
|
@overload
|
|
582
572
|
def __getitem__(self, key: npt.NDArray[np.integer[Any]]) -> np.float32: ...
|
|
583
573
|
@overload
|
|
584
|
-
def __getitem__(self, key: slice |
|
|
574
|
+
def __getitem__(self, key: slice | tuple[slice, slice] | tuple[slice, slice, slice]) -> npt.NDArray[np.float32]: ...
|
|
585
575
|
# fmt: on
|
|
586
576
|
def __getitem__(self, key):
|
|
587
577
|
"""Get pixel/patch of image stack."""
|
|
@@ -608,10 +598,11 @@ class GrayImageStack:
|
|
|
608
598
|
return self.imgs.get_full()[:, :, :, 0]
|
|
609
599
|
|
|
610
600
|
@property
|
|
611
|
-
def shape(self) ->
|
|
601
|
+
def shape(self) -> tuple[int, int, int]:
|
|
612
602
|
return self.imgs.shape[:-1]
|
|
613
603
|
|
|
614
604
|
|
|
605
|
+
@deprecated("Use `read_imgs` instead")
|
|
615
606
|
def read_images(*args, **kwargs) -> GrayImageStack:
|
|
616
607
|
"""Read images.
|
|
617
608
|
|
|
@@ -619,9 +610,4 @@ def read_images(*args, **kwargs) -> GrayImageStack:
|
|
|
619
610
|
Use :meth:`read_imgs` instead.
|
|
620
611
|
"""
|
|
621
612
|
|
|
622
|
-
warnings.warn(
|
|
623
|
-
"`read_images` has been replaced by `read_imgs` because it"
|
|
624
|
-
"provide rgb support, and this will be removed in next version",
|
|
625
|
-
DeprecationWarning,
|
|
626
|
-
)
|
|
627
613
|
return GrayImageStack(read_imgs(*args, **kwargs))
|
|
@@ -12,7 +12,8 @@ pip install swcgeom[all]
|
|
|
12
12
|
import os
|
|
13
13
|
import re
|
|
14
14
|
import time
|
|
15
|
-
from
|
|
15
|
+
from collections.abc import Iterable
|
|
16
|
+
from typing import Optional
|
|
16
17
|
|
|
17
18
|
import numpy as np
|
|
18
19
|
import numpy.typing as npt
|
|
@@ -26,6 +27,7 @@ from sdflit import (
|
|
|
26
27
|
SDFObject,
|
|
27
28
|
)
|
|
28
29
|
from tqdm import tqdm
|
|
30
|
+
from typing_extensions import deprecated
|
|
29
31
|
|
|
30
32
|
from swcgeom.core import Population, Tree
|
|
31
33
|
from swcgeom.transforms.base import Transform
|
|
@@ -62,14 +64,14 @@ class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
|
|
|
62
64
|
ONLY works for small image stacks, use :meth`transform_and_save`
|
|
63
65
|
for big image stack.
|
|
64
66
|
"""
|
|
65
|
-
return np.stack(list(self.
|
|
67
|
+
return np.stack(list(self.transform(x, verbose=False)), axis=0)
|
|
66
68
|
|
|
67
|
-
def
|
|
69
|
+
def transform(
|
|
68
70
|
self,
|
|
69
71
|
x: Tree,
|
|
70
72
|
verbose: bool = True,
|
|
71
73
|
*,
|
|
72
|
-
ranges: Optional[
|
|
74
|
+
ranges: Optional[tuple[npt.ArrayLike, npt.ArrayLike]] = None,
|
|
73
75
|
) -> Iterable[npt.NDArray[np.uint8]]:
|
|
74
76
|
if verbose:
|
|
75
77
|
print("To image stack: " + x.source)
|
|
@@ -101,10 +103,20 @@ class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
|
|
|
101
103
|
frame = (255 * voxel[..., 0, 0]).astype(np.uint8)
|
|
102
104
|
yield frame
|
|
103
105
|
|
|
106
|
+
@deprecated("Use transform instead")
|
|
107
|
+
def transfrom(
|
|
108
|
+
self,
|
|
109
|
+
x: Tree,
|
|
110
|
+
verbose: bool = True,
|
|
111
|
+
*,
|
|
112
|
+
ranges: Optional[tuple[npt.ArrayLike, npt.ArrayLike]] = None,
|
|
113
|
+
) -> Iterable[npt.NDArray[np.uint8]]:
|
|
114
|
+
return self.transform(x, verbose, ranges=ranges)
|
|
115
|
+
|
|
104
116
|
def transform_and_save(
|
|
105
117
|
self, fname: str, x: Tree, verbose: bool = True, **kwargs
|
|
106
118
|
) -> None:
|
|
107
|
-
self.save_tif(fname, self.
|
|
119
|
+
self.save_tif(fname, self.transform(x, verbose=verbose, **kwargs))
|
|
108
120
|
|
|
109
121
|
def transform_population(
|
|
110
122
|
self, population: Population | str, verbose: bool = True
|
|
@@ -133,7 +145,7 @@ class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
|
|
|
133
145
|
scene = ObjectsScene()
|
|
134
146
|
scene.set_background((0, 0, 0))
|
|
135
147
|
|
|
136
|
-
def leave(n: Tree.Node, children:
|
|
148
|
+
def leave(n: Tree.Node, children: list[Tree.Node]) -> Tree.Node:
|
|
137
149
|
for c in children:
|
|
138
150
|
sdf = RoundCone(_tp3f(n.xyz()), _tp3f(c.xyz()), n.r, c.r).into()
|
|
139
151
|
scene.add_object(SDFObject(sdf, material).into())
|
|
@@ -175,7 +187,7 @@ class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
|
|
|
175
187
|
def save_tif(
|
|
176
188
|
fname: str,
|
|
177
189
|
frames: Iterable[npt.NDArray[np.uint8]],
|
|
178
|
-
resolution:
|
|
190
|
+
resolution: tuple[float, float] = (1, 1),
|
|
179
191
|
) -> None:
|
|
180
192
|
with tifffile.TiffWriter(fname) as tif:
|
|
181
193
|
for frame in frames:
|
|
@@ -191,7 +203,7 @@ class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
|
|
|
191
203
|
)
|
|
192
204
|
|
|
193
205
|
|
|
194
|
-
def _tp3f(x: npt.NDArray) ->
|
|
206
|
+
def _tp3f(x: npt.NDArray) -> tuple[float, float, float]:
|
|
195
207
|
"""Convert to tuple of 3 floats."""
|
|
196
208
|
assert len(x) == 3
|
|
197
209
|
return (float(x[0]), float(x[1]), float(x[2]))
|