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.

Files changed (49) hide show
  1. swcgeom/_version.py +2 -2
  2. swcgeom/analysis/feature_extractor.py +25 -15
  3. swcgeom/analysis/features.py +20 -8
  4. swcgeom/analysis/lmeasure.py +33 -12
  5. swcgeom/analysis/sholl.py +10 -28
  6. swcgeom/analysis/trunk.py +12 -11
  7. swcgeom/analysis/visualization.py +9 -9
  8. swcgeom/analysis/visualization3d.py +85 -0
  9. swcgeom/analysis/volume.py +4 -4
  10. swcgeom/core/branch.py +4 -3
  11. swcgeom/core/branch_tree.py +3 -4
  12. swcgeom/core/compartment.py +3 -2
  13. swcgeom/core/node.py +17 -3
  14. swcgeom/core/path.py +6 -9
  15. swcgeom/core/population.py +43 -29
  16. swcgeom/core/swc.py +11 -10
  17. swcgeom/core/swc_utils/base.py +8 -17
  18. swcgeom/core/swc_utils/checker.py +3 -11
  19. swcgeom/core/swc_utils/io.py +7 -6
  20. swcgeom/core/swc_utils/normalizer.py +4 -3
  21. swcgeom/core/swc_utils/subtree.py +2 -2
  22. swcgeom/core/tree.py +41 -40
  23. swcgeom/core/tree_utils.py +13 -17
  24. swcgeom/core/tree_utils_impl.py +3 -3
  25. swcgeom/images/augmentation.py +3 -3
  26. swcgeom/images/folder.py +12 -26
  27. swcgeom/images/io.py +21 -35
  28. swcgeom/transforms/image_stack.py +20 -8
  29. swcgeom/transforms/images.py +3 -12
  30. swcgeom/transforms/neurolucida_asc.py +4 -6
  31. swcgeom/transforms/population.py +1 -3
  32. swcgeom/transforms/tree.py +38 -25
  33. swcgeom/transforms/tree_assembler.py +4 -3
  34. swcgeom/utils/download.py +44 -21
  35. swcgeom/utils/ellipse.py +3 -4
  36. swcgeom/utils/neuromorpho.py +17 -16
  37. swcgeom/utils/plotter_2d.py +12 -6
  38. swcgeom/utils/plotter_3d.py +31 -0
  39. swcgeom/utils/renderer.py +6 -6
  40. swcgeom/utils/sdf.py +4 -7
  41. swcgeom/utils/solid_geometry.py +1 -3
  42. swcgeom/utils/transforms.py +2 -4
  43. swcgeom/utils/volumetric_object.py +8 -10
  44. {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/METADATA +19 -19
  45. swcgeom-0.17.2.dist-info/RECORD +67 -0
  46. {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/WHEEL +1 -1
  47. swcgeom-0.17.0.dist-info/RECORD +0 -65
  48. {swcgeom-0.17.0.dist-info → swcgeom-0.17.2.dist-info}/LICENSE +0 -0
  49. {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 warnings
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) -> List["Tree.Node"]:
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: List["Tree.Node"] = [self]
53
- while not ns[-1].is_bifurcation() and (p := ns[-1].parent()) is not None:
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].is_bifurcation() or ns[-1].is_tip()):
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 Dict[int, int], optional
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) -> List[Node]: ...
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 get_bifurcations(self) -> List[Node]:
203
- """Get all node of bifurcations."""
204
- bifurcations: List[int] = []
191
+ def get_furcations(self) -> list[Node]:
192
+ """Get all node of furcations."""
193
+ furcations: list[int] = []
205
194
 
206
- def collect_bifurcations(n: Tree.Node, children: List[None]) -> None:
195
+ def collect_furcations(n: Tree.Node, children: list[None]) -> None:
207
196
  if len(children) > 1:
208
- bifurcations.append(n.id)
197
+ furcations.append(n.id)
209
198
 
210
- self.traverse(leave=collect_bifurcations)
211
- return [self.node(i) for i in bifurcations]
199
+ self.traverse(leave=collect_furcations)
200
+ return [self.node(i) for i in furcations]
212
201
 
213
- def get_tips(self) -> List[Node]:
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) -> List[Branch]:
225
+ def get_branches(self) -> list[Branch]:
225
226
  def collect_branches(
226
- node: "Tree.Node", pre: List[Tuple[List[Tree.Branch], List[int]]]
227
- ) -> Tuple[List[Tree.Branch], List[int]]:
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: List[Tree.Branch] = []
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) -> List[Path]:
248
+ def get_paths(self) -> list[Path]:
248
249
  """Get all path from soma to tips."""
249
- path_dic: Dict[int, List[int]] = {}
250
+ path_dic: dict[int, list[int]] = {}
250
251
 
251
- def assign_path(n: Tree.Node, pre_path: List[int] | None) -> List[int]:
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: List[List[List[int]]]
259
- ) -> List[List[int]]:
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, List[K]], K],
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: List[T]) => T, optional
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[List[str]] = None, **kwargs
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
 
@@ -1,9 +1,11 @@
1
1
  """SWC util wrapper for tree."""
2
2
 
3
3
  import warnings
4
- from typing import Callable, Dict, Iterable, List, Optional, Tuple, TypeVar, overload
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], Tuple[T, bool]]) -> Tree: ...
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, List[K]], Tuple[K, bool]]) -> Tree: ...
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: List[int] = []
65
+ removals: list[int] = []
64
66
 
65
67
  if enter:
66
68
 
67
- def _enter(n: Tree.Node, parent: Tuple[T, bool] | None) -> Tuple[T, bool]:
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: List[K]) -> K:
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
- def to_sub_tree(swc_like: SWCLike, sub: Topology) -> Tuple[Tree, Dict[int, int]]:
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 : Dict[int, int]
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 Dict[int, int], optional
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 Dict[int, int], optional
169
+ out_mapping: List of int or dict[int, int], optional
174
170
  Map new id to old id.
175
171
  """
176
172
 
@@ -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, Dict, List, Optional, Tuple
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 = Dict[int, int] | List[int]
19
- TreeArgs = Tuple[int, Dict[str, npt.NDArray[Any]], str, SWCNames]
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(
@@ -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 List, Literal, Optional
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: List[Literal["xy", "xz", "yz"]] = ["xy", "xz", "yz"]
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: List[Literal["xy", "xz", "yz"]] = ["xy", "xz", "yz"]
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 warnings
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: List[str]
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) -> List[str]:
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: List[int]
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) -> Tuple[T, 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) -> Tuple[T, str]:
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 = Tuple[int, int, int]
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: Tuple[int, int]) -> npt.NDArray[ScalarType]: ... # array of shape (Z, C)
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: Tuple[int, int, int]) -> npt.NDArray[ScalarType]: ... # array of shape (C,)
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: Tuple[int, int, int, int]) -> ScalarType: ... # value
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 | Tuple[slice, slice] | Tuple[slice, slice, slice] | Tuple[slice, slice, slice, 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) -> Tuple[int, int, int, int]:
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 : Dict[str, Any]
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 : Dict[str, Any]
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) -> Tuple[int, int, int, int]:
249
- return cast(Tuple[int, int, int, int], self.imgs.shape)
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], List[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) -> List[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) -> Tuple[int, int, int, int]:
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) -> Tuple[List[Vec3i], List[str], List[Vec3i]]:
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 : List[str]
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 | Tuple[slice, slice] | Tuple[slice, slice, slice]) -> npt.NDArray[np.float32]: ...
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) -> Tuple[int, int, int]:
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 typing import Iterable, List, Optional, Tuple
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.transfrom(x, verbose=False)), axis=0)
67
+ return np.stack(list(self.transform(x, verbose=False)), axis=0)
66
68
 
67
- def transfrom(
69
+ def transform(
68
70
  self,
69
71
  x: Tree,
70
72
  verbose: bool = True,
71
73
  *,
72
- ranges: Optional[Tuple[npt.ArrayLike, npt.ArrayLike]] = None,
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.transfrom(x, verbose=verbose, **kwargs))
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: List[Tree.Node]) -> Tree.Node:
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: Tuple[float, float] = (1, 1),
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) -> Tuple[float, float, float]:
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]))