swcgeom 0.16.0__tar.gz → 0.17.0__tar.gz

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 (110) hide show
  1. {swcgeom-0.16.0 → swcgeom-0.17.0}/CHANGELOG.md +20 -0
  2. {swcgeom-0.16.0/swcgeom.egg-info → swcgeom-0.17.0}/PKG-INFO +1 -1
  3. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/_version.py +2 -2
  4. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/analysis/__init__.py +1 -3
  5. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/analysis/feature_extractor.py +3 -3
  6. swcgeom-0.16.0/swcgeom/analysis/node_features.py → swcgeom-0.17.0/swcgeom/analysis/features.py +105 -3
  7. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/images/io.py +60 -84
  8. swcgeom-0.17.0/swcgeom/transforms/images.py +203 -0
  9. {swcgeom-0.16.0 → swcgeom-0.17.0/swcgeom.egg-info}/PKG-INFO +1 -1
  10. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom.egg-info/SOURCES.txt +1 -3
  11. swcgeom-0.16.0/swcgeom/analysis/branch_features.py +0 -67
  12. swcgeom-0.16.0/swcgeom/analysis/path_features.py +0 -37
  13. swcgeom-0.16.0/swcgeom/transforms/images.py +0 -102
  14. {swcgeom-0.16.0 → swcgeom-0.17.0}/.github/workflows/build.yml +0 -0
  15. {swcgeom-0.16.0 → swcgeom-0.17.0}/.github/workflows/github-publish.yml +0 -0
  16. {swcgeom-0.16.0 → swcgeom-0.17.0}/.github/workflows/pypi-publish.yml +0 -0
  17. {swcgeom-0.16.0 → swcgeom-0.17.0}/.github/workflows/test.yml +0 -0
  18. {swcgeom-0.16.0 → swcgeom-0.17.0}/.gitignore +0 -0
  19. {swcgeom-0.16.0 → swcgeom-0.17.0}/.pylintrc +0 -0
  20. {swcgeom-0.16.0 → swcgeom-0.17.0}/.vscode/settings.json +0 -0
  21. {swcgeom-0.16.0 → swcgeom-0.17.0}/LICENSE +0 -0
  22. {swcgeom-0.16.0 → swcgeom-0.17.0}/README.md +0 -0
  23. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/Branch.ipynb +0 -0
  24. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/BranchTree.ipynb +0 -0
  25. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/CollectTips.ipynb +0 -0
  26. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/CutTree.ipynb +0 -0
  27. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/Features.ipynb +0 -0
  28. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/GeometryTransform.ipynb +0 -0
  29. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/ImageStack.ipynb +0 -0
  30. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/MST.ipynb +0 -0
  31. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/SpectralClustering.ipynb +0 -0
  32. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/Tree.ipynb +0 -0
  33. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/data/101711-10_4p5-of-16_initial.CNG.swc +0 -0
  34. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/data/101711-11_16-of-16_initial.CNG.swc +0 -0
  35. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/data/1059283677_15257_2226-X16029-Y23953.swc +0 -0
  36. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/data/toydata.swc +0 -0
  37. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/dgl/graph.py +0 -0
  38. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/pytorch/branch.py +0 -0
  39. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/pytorch/branch_dataset.py +0 -0
  40. {swcgeom-0.16.0 → swcgeom-0.17.0}/examples/pytorch/tree_folder_dataset.py +0 -0
  41. {swcgeom-0.16.0 → swcgeom-0.17.0}/git-conventional-commits.yaml +0 -0
  42. {swcgeom-0.16.0 → swcgeom-0.17.0}/pyproject.toml +0 -0
  43. {swcgeom-0.16.0 → swcgeom-0.17.0}/setup.cfg +0 -0
  44. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/__init__.py +0 -0
  45. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/analysis/lmeasure.py +0 -0
  46. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/analysis/sholl.py +0 -0
  47. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/analysis/trunk.py +0 -0
  48. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/analysis/visualization.py +0 -0
  49. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/analysis/volume.py +0 -0
  50. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/__init__.py +0 -0
  51. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/branch.py +0 -0
  52. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/branch_tree.py +0 -0
  53. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/compartment.py +0 -0
  54. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/node.py +0 -0
  55. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/path.py +0 -0
  56. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/population.py +0 -0
  57. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/swc.py +0 -0
  58. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/swc_utils/__init__.py +0 -0
  59. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/swc_utils/assembler.py +0 -0
  60. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/swc_utils/base.py +0 -0
  61. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/swc_utils/checker.py +0 -0
  62. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/swc_utils/io.py +0 -0
  63. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/swc_utils/normalizer.py +0 -0
  64. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/swc_utils/subtree.py +0 -0
  65. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/tree.py +0 -0
  66. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/tree_utils.py +0 -0
  67. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/core/tree_utils_impl.py +0 -0
  68. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/images/__init__.py +0 -0
  69. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/images/augmentation.py +0 -0
  70. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/images/contrast.py +0 -0
  71. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/images/folder.py +0 -0
  72. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/__init__.py +0 -0
  73. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/base.py +0 -0
  74. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/branch.py +0 -0
  75. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/geometry.py +0 -0
  76. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/image_preprocess.py +0 -0
  77. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/image_stack.py +0 -0
  78. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/mst.py +0 -0
  79. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/neurolucida_asc.py +0 -0
  80. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/path.py +0 -0
  81. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/population.py +0 -0
  82. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/tree.py +0 -0
  83. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/transforms/tree_assembler.py +0 -0
  84. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/__init__.py +0 -0
  85. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/debug.py +0 -0
  86. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/download.py +0 -0
  87. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/dsu.py +0 -0
  88. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/ellipse.py +0 -0
  89. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/file.py +0 -0
  90. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/neuromorpho.py +0 -0
  91. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/numpy_helper.py +0 -0
  92. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/plotter_2d.py +0 -0
  93. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/renderer.py +0 -0
  94. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/sdf.py +0 -0
  95. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/solid_geometry.py +0 -0
  96. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/transforms.py +0 -0
  97. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom/utils/volumetric_object.py +0 -0
  98. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom.egg-info/dependency_links.txt +0 -0
  99. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom.egg-info/requires.txt +0 -0
  100. {swcgeom-0.16.0 → swcgeom-0.17.0}/swcgeom.egg-info/top_level.txt +0 -0
  101. {swcgeom-0.16.0 → swcgeom-0.17.0}/tests/__init__.py +0 -0
  102. {swcgeom-0.16.0 → swcgeom-0.17.0}/tests/analysis/test_volume.py +0 -0
  103. {swcgeom-0.16.0 → swcgeom-0.17.0}/tests/transforms/test_neurolucida_asc.py +0 -0
  104. {swcgeom-0.16.0 → swcgeom-0.17.0}/tests/utils/__init__.py +0 -0
  105. {swcgeom-0.16.0 → swcgeom-0.17.0}/tests/utils/test_dsu.py +0 -0
  106. {swcgeom-0.16.0 → swcgeom-0.17.0}/tests/utils/test_numpy_helper.py +0 -0
  107. {swcgeom-0.16.0 → swcgeom-0.17.0}/tests/utils/test_sdf.py +0 -0
  108. {swcgeom-0.16.0 → swcgeom-0.17.0}/tests/utils/test_solid_geometry.py +0 -0
  109. {swcgeom-0.16.0 → swcgeom-0.17.0}/tests/utils/test_transforms.py +0 -0
  110. {swcgeom-0.16.0 → swcgeom-0.17.0}/tests/utils/test_volumetric_object.py +0 -0
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## **0.17.0**&emsp;<sub><sup>2024-04-04 ([59257cb...2072139](https://github.com/yzx9/swcgeom/compare/59257cbfb6264afd3ca31fcba002f692bbb69ed0...20721393086e5a890b097b3ae32aaa1e4ad6b898?diff=split))</sup></sub>
4
+
5
+ ### Features
6
+
7
+ ##### &ensp;`images`
8
+
9
+ - handle raw file in terafly ([67ecd63](https://github.com/yzx9/swcgeom/commit/67ecd63bf4124d49b00e779c2702a7b939ef710b))
10
+ - add image stack dtype support ([7313ead](https://github.com/yzx9/swcgeom/commit/7313ead09ec61970f221b9e0c3691d31c269b591))
11
+
12
+ ##### &ensp;`transforms`
13
+
14
+ - add new image transformation classes ([7b5d805](https://github.com/yzx9/swcgeom/commit/7b5d805a1aa0deac3f9d7576a9aecab36b59774b))
15
+ - add image flip and notes ([5f2a4d1](https://github.com/yzx9/swcgeom/commit/5f2a4d1a9c2b5fb68e8221c7b024a411b247ab6d))
16
+
17
+
18
+ ### BREAKING CHANGES
19
+ - `images` remove deprecated \`swap\_xy\` and \`filp\_xy\` flag ([2072139](https://github.com/yzx9/swcgeom/commit/20721393086e5a890b097b3ae32aaa1e4ad6b898))
20
+ <br>
21
+
22
+
3
23
  ## **0.16.0**&emsp;<sub><sup>2024-03-16 ([bf2bf95...7029483](https://github.com/yzx9/swcgeom/compare/bf2bf95ee9fbfb7eba871db7303292f1dfcc7b8f...70294836643e51ca43d039887bbd71de3a9b561b?diff=split))</sup></sub>
4
24
 
5
25
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: swcgeom
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Summary: Neuron geometry library for swc format
5
5
  Author-email: yzx9 <yuan.zx@outlook.com>
6
6
  License: Apache-2.0
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.16.0'
16
- __version_tuple__ = version_tuple = (0, 16, 0)
15
+ __version__ = version = '0.17.0'
16
+ __version_tuple__ = version_tuple = (0, 17, 0)
@@ -1,9 +1,7 @@
1
1
  """Analysis for neuron trees."""
2
2
 
3
- from swcgeom.analysis.branch_features import *
4
3
  from swcgeom.analysis.feature_extractor import *
5
- from swcgeom.analysis.node_features import *
6
- from swcgeom.analysis.path_features import *
4
+ from swcgeom.analysis.features import *
7
5
  from swcgeom.analysis.sholl import *
8
6
  from swcgeom.analysis.trunk import *
9
7
  from swcgeom.analysis.visualization import *
@@ -17,13 +17,13 @@ import numpy.typing as npt
17
17
  import seaborn as sns
18
18
  from matplotlib.axes import Axes
19
19
 
20
- from swcgeom.analysis.branch_features import BranchFeatures
21
- from swcgeom.analysis.node_features import (
20
+ from swcgeom.analysis.features import (
22
21
  BifurcationFeatures,
22
+ BranchFeatures,
23
23
  NodeFeatures,
24
+ PathFeatures,
24
25
  TipFeatures,
25
26
  )
26
- from swcgeom.analysis.path_features import PathFeatures
27
27
  from swcgeom.analysis.sholl import Sholl
28
28
  from swcgeom.analysis.volume import get_volume
29
29
  from swcgeom.core import Population, Populations, Tree
@@ -1,15 +1,26 @@
1
- """Depth distribution of tree."""
1
+ """Feature anlysis of tree."""
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from functools import cached_property
5
+ from typing import List, TypeVar
5
6
 
6
7
  import numpy as np
7
8
  import numpy.typing as npt
8
9
  from typing_extensions import Self
9
10
 
10
- from swcgeom.core import BranchTree, Tree
11
+ from swcgeom.core import Branch, BranchTree, Tree
11
12
 
12
- __all__ = ["NodeFeatures", "BifurcationFeatures", "TipFeatures"]
13
+ __all__ = [
14
+ "NodeFeatures",
15
+ "BifurcationFeatures",
16
+ "TipFeatures",
17
+ "PathFeatures",
18
+ "BranchFeatures",
19
+ ]
20
+
21
+ T = TypeVar("T", bound=Branch)
22
+
23
+ # Node Level
13
24
 
14
25
 
15
26
  class NodeFeatures:
@@ -31,6 +42,7 @@ class NodeFeatures:
31
42
  -------
32
43
  count : array of shape (1,)
33
44
  """
45
+
34
46
  return np.array([self.tree.number_of_nodes()], dtype=np.float32)
35
47
 
36
48
  def get_radial_distance(self) -> npt.NDArray[np.float32]:
@@ -41,6 +53,7 @@ class NodeFeatures:
41
53
  radial_distance : npt.NDArray[np.float32]
42
54
  Array of shape (N,).
43
55
  """
56
+
44
57
  xyz = self.tree.xyz() - self.tree.soma().xyz()
45
58
  radial_distance = np.linalg.norm(xyz, axis=1)
46
59
  return radial_distance
@@ -58,6 +71,7 @@ class NodeFeatures:
58
71
  order : npt.NDArray[np.int32]
59
72
  Array of shape (N,), which k is the number of branchs.
60
73
  """
74
+
61
75
  order = np.zeros_like(self._branch_tree.id(), dtype=np.int32)
62
76
 
63
77
  def assign_depth(n: Tree.Node, pre_depth: int | None) -> int:
@@ -88,6 +102,7 @@ class _SubsetNodesFeatures(ABC):
88
102
  count : npt.NDArray[np.float32]
89
103
  Array of shape (1,).
90
104
  """
105
+
91
106
  return np.array([np.count_nonzero(self.nodes)], dtype=np.float32)
92
107
 
93
108
  def get_radial_distance(self) -> npt.NDArray[np.float32]:
@@ -98,6 +113,7 @@ class _SubsetNodesFeatures(ABC):
98
113
  radial_distance : npt.NDArray[np.float32]
99
114
  Array of shape (N,).
100
115
  """
116
+
101
117
  return self._features.get_radial_distance()[self.nodes]
102
118
 
103
119
  @classmethod
@@ -119,3 +135,89 @@ class TipFeatures(_SubsetNodesFeatures):
119
135
  @cached_property
120
136
  def nodes(self) -> npt.NDArray[np.bool_]:
121
137
  return np.array([n.is_tip() for n in self._features.tree])
138
+
139
+
140
+ # Path Level
141
+
142
+
143
+ class PathFeatures:
144
+ """Path analysis of tree."""
145
+
146
+ tree: Tree
147
+
148
+ def __init__(self, tree: Tree) -> None:
149
+ self.tree = tree
150
+
151
+ def get_count(self) -> int:
152
+ return len(self._paths)
153
+
154
+ def get_length(self) -> npt.NDArray[np.float32]:
155
+ """Get length of paths."""
156
+
157
+ length = [path.length() for path in self._paths]
158
+ return np.array(length, dtype=np.float32)
159
+
160
+ def get_tortuosity(self) -> npt.NDArray[np.float32]:
161
+ """Get tortuosity of path."""
162
+
163
+ return np.array([path.tortuosity() for path in self._paths], dtype=np.float32)
164
+
165
+ @cached_property
166
+ def _paths(self) -> List[Tree.Path]:
167
+ return self.tree.get_paths()
168
+
169
+
170
+ class BranchFeatures:
171
+ """Analysis bransh of tree."""
172
+
173
+ tree: Tree
174
+
175
+ def __init__(self, tree: Tree) -> None:
176
+ self.tree = tree
177
+
178
+ def get_count(self) -> int:
179
+ return len(self._branches)
180
+
181
+ def get_length(self) -> npt.NDArray[np.float32]:
182
+ """Get length of branches."""
183
+
184
+ length = [br.length() for br in self._branches]
185
+ return np.array(length, dtype=np.float32)
186
+
187
+ def get_tortuosity(self) -> npt.NDArray[np.float32]:
188
+ """Get tortuosity of path."""
189
+
190
+ return np.array([br.tortuosity() for br in self._branches], dtype=np.float32)
191
+
192
+ def get_angle(self, eps: float = 1e-7) -> npt.NDArray[np.float32]:
193
+ """Get agnle between branches.
194
+
195
+ Returns
196
+ -------
197
+ angle : npt.NDArray[np.float32]
198
+ An array of shape (N, N), which N is length of branches.
199
+ """
200
+
201
+ return self.calc_angle(self._branches, eps=eps)
202
+
203
+ @staticmethod
204
+ def calc_angle(branches: List[T], eps: float = 1e-7) -> npt.NDArray[np.float32]:
205
+ """Calc agnle between branches.
206
+
207
+ Returns
208
+ -------
209
+ angle : npt.NDArray[np.float32]
210
+ An array of shape (N, N), which N is length of branches.
211
+ """
212
+
213
+ vector = np.array([br[-1].xyz() - br[0].xyz() for br in branches])
214
+ vector_dot = np.matmul(vector, vector.T)
215
+ vector_norm = np.linalg.norm(vector, ord=2, axis=1, keepdims=True)
216
+ vector_norm_dot = np.matmul(vector_norm, vector_norm.T) + eps
217
+ arccos = np.clip(vector_dot / vector_norm_dot, -1, 1)
218
+ angle = np.arccos(arccos)
219
+ return angle
220
+
221
+ @cached_property
222
+ def _branches(self) -> List[Tree.Branch]:
223
+ return self.tree.get_branches()
@@ -107,26 +107,42 @@ def read_imgs(fname: str, *, dtype: None =..., **kwargs) -> ImageStack[np.float3
107
107
  # fmt:on
108
108
 
109
109
 
110
- def read_imgs(fname: str, *, dtype=None, **kwargs): # type: ignore
111
- """Read image stack."""
112
-
113
- kwargs["dtype"] = dtype or np.float32
114
-
115
- ext = os.path.splitext(fname)[-1]
116
- if ext in [".tif", ".tiff"]:
117
- return TiffImageStack(fname, **kwargs)
118
- if ext in [".nrrd"]:
119
- return NrrdImageStack(fname, **kwargs)
120
- if ext in [".v3dpbd"]:
121
- return V3dpbdImageStack(fname, **kwargs)
122
- if ext in [".v3draw"]:
123
- return V3drawImageStack(fname, **kwargs)
124
- if ext in [".npy", ".npz"]:
125
- return NDArrayImageStack(np.load(fname), **kwargs)
110
+ def read_imgs(fname: str, **kwargs): # type: ignore
111
+ """Read image stack.
112
+
113
+ Parameters
114
+ ----------
115
+ fname : str
116
+ The path of image stack.
117
+ dtype : np.dtype, default to `np.float32`
118
+ Casting data to specified dtype. If integer and float
119
+ conversions occur, they will be scaled (assuming floats are
120
+ between 0 and 1).
121
+ **kwargs : Dict[str, Any]
122
+ Forwarding to the corresponding reader.
123
+ """
124
+
125
+ kwargs.setdefault("dtype", np.float32)
126
+ if not os.path.exists(fname):
127
+ raise ValueError(f"image stack not exists: {fname}")
128
+
129
+ # match file extension
130
+ match os.path.splitext(fname)[-1]:
131
+ case ".tif" | ".tiff":
132
+ return TiffImageStack(fname, **kwargs)
133
+ case ".nrrd":
134
+ return NrrdImageStack(fname, **kwargs)
135
+ case ".v3dpbd":
136
+ return V3dpbdImageStack(fname, **kwargs)
137
+ case ".v3draw":
138
+ return V3drawImageStack(fname, **kwargs)
139
+ case ".npy":
140
+ return NDArrayImageStack(np.load(fname), **kwargs)
141
+
142
+ # try to read as terafly
126
143
  if TeraflyImageStack.is_root(fname):
127
144
  return TeraflyImageStack(fname, **kwargs)
128
- if not os.path.exists(fname):
129
- raise ValueError("image stack not exists")
145
+
130
146
  raise ValueError("unsupported image stack")
131
147
 
132
148
 
@@ -135,7 +151,6 @@ def save_tiff(
135
151
  fname: str,
136
152
  *,
137
153
  dtype: Optional[np.unsignedinteger | np.floating] = None,
138
- swap_xy: Optional[bool] = None,
139
154
  compression: str | Literal[False] = "zlib",
140
155
  **kwargs,
141
156
  ) -> None:
@@ -164,17 +179,6 @@ def save_tiff(
164
179
  data = np.expand_dims(data, -1) # (_, _, _) -> (_, _, _, C), C === 1
165
180
 
166
181
  axes = "ZXYC"
167
- if swap_xy is not None:
168
- warnings.warn(
169
- "flag `swap_xy` is easy to implement in user space and "
170
- "is more flexiable. Since this flag is rarely used, we "
171
- "decided to remove it in the next version",
172
- DeprecationWarning,
173
- )
174
- if swap_xy is True:
175
- axes = "ZYXC"
176
- data = data.swapaxes(0, 1) # (X, Y, _, _) -> (Y, X, _, _)
177
-
178
182
  assert data.ndim == 4, "should be an array of shape (X, Y, Z, C)"
179
183
  assert data.shape[-1] in [1, 3], "support 'miniblack' or 'rgb'"
180
184
 
@@ -209,12 +213,7 @@ class NDArrayImageStack(ImageStack[ScalarType]):
209
213
  """NDArray image stack."""
210
214
 
211
215
  def __init__(
212
- self,
213
- imgs: npt.NDArray[Any],
214
- swap_xy: Optional[bool] = None,
215
- filp_xy: Optional[bool] = None,
216
- *,
217
- dtype: ScalarType,
216
+ self, imgs: npt.NDArray[Any], *, dtype: Optional[ScalarType] = None
218
217
  ) -> None:
219
218
  super().__init__()
220
219
 
@@ -222,34 +221,22 @@ class NDArrayImageStack(ImageStack[ScalarType]):
222
221
  imgs = np.expand_dims(imgs, -1)
223
222
  assert imgs.ndim == 4, "Should be shape of (X, Y, Z, C)"
224
223
 
225
- if swap_xy is not None:
226
- warnings.warn(
227
- "flag `swap_xy` now is unnecessary, tifffile will "
228
- "automatically adjust dimensions according to "
229
- "`tags.axes`, so this flag will be removed in the next "
230
- " version",
231
- DeprecationWarning,
232
- )
233
- if swap_xy is True:
234
- imgs = imgs.swapaxes(0, 1) # (Y, X, _, _) -> (X, Y, _, _)
235
-
236
- if filp_xy is not None:
237
- warnings.warn(
238
- "flag `filp_xy` is easy to implement in user space and "
239
- "is more flexiable. Since this flag is rarely used, we "
240
- "decided to remove it in the next version",
241
- DeprecationWarning,
242
- )
243
- if filp_xy is True:
244
- imgs = np.flip(imgs, (0, 1)) # (X, Y, Z, C)
224
+ if dtype is not None:
225
+ dtype_raw = imgs.dtype
226
+ if np.issubdtype(dtype, np.floating) and np.issubdtype(
227
+ dtype_raw, np.unsignedinteger
228
+ ):
229
+ sclar_factor = 1.0 / UINT_MAX[dtype_raw]
230
+ imgs = sclar_factor * imgs.astype(dtype)
231
+ elif np.issubdtype(dtype, np.unsignedinteger) and np.issubdtype(
232
+ dtype_raw, np.floating
233
+ ):
234
+ sclar_factor = UINT_MAX[dtype] # type: ignore
235
+ imgs *= (sclar_factor * imgs).astype(dtype)
236
+ else:
237
+ imgs = imgs.astype(dtype)
245
238
 
246
- dtype_raw = imgs.dtype
247
- self.imgs = imgs.astype(dtype)
248
- if np.issubdtype(dtype, np.floating) and np.issubdtype(
249
- dtype_raw, np.unsignedinteger
250
- ): # TODO: add a option to disable this
251
- sclar_factor = 1.0 / UINT_MAX[imgs.dtype]
252
- self.imgs *= sclar_factor
239
+ self.imgs = imgs
253
240
 
254
241
  def __getitem__(self, key):
255
242
  return self.imgs.__getitem__(key)
@@ -265,15 +252,7 @@ class NDArrayImageStack(ImageStack[ScalarType]):
265
252
  class TiffImageStack(NDArrayImageStack[ScalarType]):
266
253
  """Tiff image stack."""
267
254
 
268
- def __init__(
269
- self,
270
- fname: str,
271
- swap_xy: Optional[bool] = None,
272
- filp_xy: Optional[bool] = None,
273
- *,
274
- dtype: ScalarType,
275
- **kwargs,
276
- ) -> None:
255
+ def __init__(self, fname: str, *, dtype: ScalarType, **kwargs) -> None:
277
256
  with tifffile.TiffFile(fname, **kwargs) as f:
278
257
  s = f.series[0]
279
258
  imgs, axes = s.asarray(), s.axes
@@ -285,23 +264,15 @@ class TiffImageStack(NDArrayImageStack[ScalarType]):
285
264
 
286
265
  orders = [AXES_ORDER[c] for c in axes]
287
266
  imgs = imgs.transpose(np.argsort(orders))
288
- super().__init__(imgs, swap_xy=swap_xy, filp_xy=filp_xy, dtype=dtype)
267
+ super().__init__(imgs, dtype=dtype)
289
268
 
290
269
 
291
270
  class NrrdImageStack(NDArrayImageStack[ScalarType]):
292
271
  """Nrrd image stack."""
293
272
 
294
- def __init__(
295
- self,
296
- fname: str,
297
- swap_xy: Optional[bool] = None,
298
- filp_xy: Optional[bool] = None,
299
- *,
300
- dtype: ScalarType,
301
- **kwargs,
302
- ) -> None:
273
+ def __init__(self, fname: str, *, dtype: ScalarType, **kwargs) -> None:
303
274
  imgs, header = nrrd.read(fname, **kwargs)
304
- super().__init__(imgs, swap_xy=swap_xy, filp_xy=filp_xy, dtype=dtype)
275
+ super().__init__(imgs, dtype=dtype)
305
276
  self.header = header
306
277
 
307
278
 
@@ -384,7 +355,12 @@ class TeraflyImageStack(ImageStack[ScalarType]):
384
355
 
385
356
  @lru_cache(maxsize=lru_maxsize)
386
357
  def read_patch(path: str) -> npt.NDArray[ScalarType]:
387
- return read_imgs(path, dtype=dtype).get_full()
358
+ match os.path.splitext(path)[-1]:
359
+ case "raw":
360
+ # Treat it as a v3draw file
361
+ return V3drawImageStack(path, dtype=dtype).get_full()
362
+ case _:
363
+ return read_imgs(path, dtype=dtype).get_full()
388
364
 
389
365
  self._listdir, self._read_patch = listdir, read_patch
390
366
 
@@ -0,0 +1,203 @@
1
+ """Image stack related transform."""
2
+
3
+ import warnings
4
+ from typing import Tuple
5
+
6
+ import numpy as np
7
+ import numpy.typing as npt
8
+
9
+ from swcgeom.transforms.base import Identity, Transform
10
+
11
+ __all__ = [
12
+ "ImagesCenterCrop",
13
+ "ImagesScale",
14
+ "ImagesClip",
15
+ "ImagesFlip",
16
+ "ImagesFlipY",
17
+ "ImagesNormalizer",
18
+ "ImagesMeanVarianceAdjustment",
19
+ "ImagesScaleToUnitRange",
20
+ "ImagesHistogramEqualization",
21
+ "Center", # legacy
22
+ ]
23
+
24
+
25
+ NDArrayf32 = npt.NDArray[np.float32]
26
+
27
+
28
+ class ImagesCenterCrop(Transform[NDArrayf32, NDArrayf32]):
29
+ """Get image stack center."""
30
+
31
+ def __init__(self, shape_out: int | Tuple[int, int, int]):
32
+ super().__init__()
33
+ self.shape_out = (
34
+ shape_out
35
+ if isinstance(shape_out, tuple)
36
+ else (shape_out, shape_out, shape_out)
37
+ )
38
+
39
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
40
+ diff = np.subtract(x.shape[:3], self.shape_out)
41
+ s = diff // 2
42
+ e = np.add(s, self.shape_out)
43
+ return x[s[0] : e[0], s[1] : e[1], s[2] : e[2], :]
44
+
45
+ def extra_repr(self) -> str:
46
+ return f"shape_out=({','.join(str(a) for a in self.shape_out)})"
47
+
48
+
49
+ class Center(ImagesCenterCrop):
50
+ """Get image stack center.
51
+
52
+ .. deprecated:: 0.16.0
53
+ Use :class:`ImagesCenterCrop` instead.
54
+ """
55
+
56
+ def __init__(self, shape_out: int | Tuple[int, int, int]):
57
+ warnings.warn(
58
+ "`Center` is deprecated, use `ImagesCenterCrop` instead",
59
+ DeprecationWarning,
60
+ stacklevel=2,
61
+ )
62
+ super().__init__(shape_out)
63
+
64
+
65
+ class ImagesScale(Transform[NDArrayf32, NDArrayf32]):
66
+ def __init__(self, scaler: float) -> None:
67
+ super().__init__()
68
+ self.scaler = scaler
69
+
70
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
71
+ return self.scaler * x
72
+
73
+ def extra_repr(self) -> str:
74
+ return f"scaler={self.scaler}"
75
+
76
+
77
+ class ImagesClip(Transform[NDArrayf32, NDArrayf32]):
78
+ def __init__(self, vmin: float = 0, vmax: float = 1, /) -> None:
79
+ super().__init__()
80
+ self.vmin, self.vmax = vmin, vmax
81
+
82
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
83
+ return np.clip(x, self.vmin, self.vmax)
84
+
85
+ def extra_repr(self) -> str:
86
+ return f"vmin={self.vmin}, vmax={self.vmax}"
87
+
88
+
89
+ class ImagesFlip(Transform[NDArrayf32, NDArrayf32]):
90
+ """Flip image stack along axis."""
91
+
92
+ def __init__(self, axis: int, /) -> None:
93
+ super().__init__()
94
+ self.axis = axis
95
+
96
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
97
+ return np.flip(x, axis=self.axis)
98
+
99
+ def extra_repr(self) -> str:
100
+ return f"axis={self.axis}"
101
+
102
+
103
+ class ImagesFlipY(ImagesFlip):
104
+ """Flip image stack along Y-axis.
105
+
106
+ See Also
107
+ --------
108
+ ~.images.io.TeraflyImageStack:
109
+ Terafly and Vaa3d use a especial right-handed coordinate system
110
+ (with origin point in the left-top and z-axis points front),
111
+ but we flip y-axis to makes it a left-handed coordinate system
112
+ (with orgin point in the left-bottom and z-axis points front).
113
+ If you need to use its coordinate system, remember to FLIP
114
+ Y-AXIS BACK.
115
+ """
116
+
117
+ def __init__(self, axis: int = 1, /) -> None:
118
+ super().__init__(axis) # (X, Y, Z, C)
119
+
120
+
121
+ class ImagesNormalizer(Transform[NDArrayf32, NDArrayf32]):
122
+ """Normalize image stack."""
123
+
124
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
125
+ mean = np.mean(x)
126
+ variance = np.var(x)
127
+ return (x - mean) / variance
128
+
129
+
130
+ class ImagesMeanVarianceAdjustment(Transform[NDArrayf32, NDArrayf32]):
131
+ """Adjust image stack mean and variance.
132
+
133
+ See Also
134
+ --------
135
+ ~swcgeom.images.ImageStackFolder.stat
136
+ """
137
+
138
+ def __init__(self, mean: float, variance: float) -> None:
139
+ super().__init__()
140
+ self.mean = mean
141
+ self.variance = variance
142
+
143
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
144
+ return (x - self.mean) / self.variance
145
+
146
+ def extra_repr(self) -> str:
147
+ return f"mean={self.mean}, variance={self.variance}"
148
+
149
+
150
+ class ImagesScaleToUnitRange(Transform[NDArrayf32, NDArrayf32]):
151
+ """Scale image stack to unit range."""
152
+
153
+ def __init__(self, vmin: float, vmax: float, *, clip: bool = True) -> None:
154
+ """Scale image stack to unit range.
155
+
156
+ Parameters
157
+ ----------
158
+ vmin : float
159
+ Minimum value.
160
+ vmax : float
161
+ Maximum value.
162
+ clip : bool, default True
163
+ Clip values to [0, 1] to avoid numerical issues.
164
+ """
165
+
166
+ super().__init__()
167
+ self.vmin = vmin
168
+ self.vmax = vmax
169
+ self.diff = vmax - vmin
170
+ self.clip = clip
171
+ self.post = ImagesClip(0, 1) if self.clip else Identity()
172
+
173
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
174
+ return self.post((x - self.vmin) / self.diff)
175
+
176
+ def extra_repr(self) -> str:
177
+ return f"vmin={self.vmin}, vmax={self.vmax}, clip={self.clip}"
178
+
179
+
180
+ class ImagesHistogramEqualization(Transform[NDArrayf32, NDArrayf32]):
181
+ """Image histogram equalization.
182
+
183
+ References
184
+ ----------
185
+ http://www.janeriksolem.net/histogram-equalization-with-python-and.html
186
+ """
187
+
188
+ def __init__(self, bins: int = 256) -> None:
189
+ super().__init__()
190
+ self.bins = bins
191
+
192
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
193
+ # get image histogram
194
+ hist, bin_edges = np.histogram(x.flatten(), self.bins, density=True)
195
+ cdf = hist.cumsum() # cumulative distribution function
196
+ cdf = cdf / cdf[-1] # normalize
197
+
198
+ # use linear interpolation of cdf to find new pixel values
199
+ equalized = np.interp(x.flatten(), bin_edges[:-1], cdf)
200
+ return equalized.reshape(x.shape).astype(np.float32)
201
+
202
+ def extra_repr(self) -> str:
203
+ return f"bins={self.bins}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: swcgeom
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Summary: Neuron geometry library for swc format
5
5
  Author-email: yzx9 <yuan.zx@outlook.com>
6
6
  License: Apache-2.0
@@ -36,11 +36,9 @@ swcgeom.egg-info/dependency_links.txt
36
36
  swcgeom.egg-info/requires.txt
37
37
  swcgeom.egg-info/top_level.txt
38
38
  swcgeom/analysis/__init__.py
39
- swcgeom/analysis/branch_features.py
40
39
  swcgeom/analysis/feature_extractor.py
40
+ swcgeom/analysis/features.py
41
41
  swcgeom/analysis/lmeasure.py
42
- swcgeom/analysis/node_features.py
43
- swcgeom/analysis/path_features.py
44
42
  swcgeom/analysis/sholl.py
45
43
  swcgeom/analysis/trunk.py
46
44
  swcgeom/analysis/visualization.py
@@ -1,67 +0,0 @@
1
- """Branch anlysis of tree."""
2
-
3
- from functools import cached_property
4
- from typing import List, TypeVar
5
-
6
- import numpy as np
7
- import numpy.typing as npt
8
-
9
- from swcgeom.core import Branch, Tree
10
-
11
- __all__ = ["BranchFeatures"]
12
-
13
- T = TypeVar("T", bound=Branch)
14
-
15
-
16
- class BranchFeatures:
17
- """Analysis bransh of tree."""
18
-
19
- tree: Tree
20
-
21
- def __init__(self, tree: Tree) -> None:
22
- self.tree = tree
23
-
24
- def get_count(self) -> int:
25
- return len(self._branches)
26
-
27
- def get_length(self) -> npt.NDArray[np.float32]:
28
- """Get length of branches."""
29
- length = [br.length() for br in self._branches]
30
- return np.array(length, dtype=np.float32)
31
-
32
- def get_tortuosity(self) -> npt.NDArray[np.float32]:
33
- """Get tortuosity of path."""
34
- return np.array([br.tortuosity() for br in self._branches], dtype=np.float32)
35
-
36
- def get_angle(self, eps: float = 1e-7) -> npt.NDArray[np.float32]:
37
- """Get agnle between branches.
38
-
39
- Returns
40
- -------
41
- angle : npt.NDArray[np.float32]
42
- An array of shape (N, N), which N is length of branches.
43
- """
44
-
45
- return self.calc_angle(self._branches, eps=eps)
46
-
47
- @staticmethod
48
- def calc_angle(branches: List[T], eps: float = 1e-7) -> npt.NDArray[np.float32]:
49
- """Calc agnle between branches.
50
-
51
- Returns
52
- -------
53
- angle : npt.NDArray[np.float32]
54
- An array of shape (N, N), which N is length of branches.
55
- """
56
-
57
- vector = np.array([br[-1].xyz() - br[0].xyz() for br in branches])
58
- vector_dot = np.matmul(vector, vector.T)
59
- vector_norm = np.linalg.norm(vector, ord=2, axis=1, keepdims=True)
60
- vector_norm_dot = np.matmul(vector_norm, vector_norm.T) + eps
61
- arccos = np.clip(vector_dot / vector_norm_dot, -1, 1)
62
- angle = np.arccos(arccos)
63
- return angle
64
-
65
- @cached_property
66
- def _branches(self) -> List[Tree.Branch]:
67
- return self.tree.get_branches()
@@ -1,37 +0,0 @@
1
- """Depth distribution of tree."""
2
-
3
-
4
- from functools import cached_property
5
- from typing import List
6
-
7
- import numpy as np
8
- import numpy.typing as npt
9
-
10
- from swcgeom.core import Tree
11
-
12
- __all__ = ["PathFeatures"]
13
-
14
-
15
- class PathFeatures:
16
- """Path analysis of tree."""
17
-
18
- tree: Tree
19
-
20
- def __init__(self, tree: Tree) -> None:
21
- self.tree = tree
22
-
23
- def get_count(self) -> int:
24
- return len(self._paths)
25
-
26
- def get_length(self) -> npt.NDArray[np.float32]:
27
- """Get length of paths."""
28
- length = [path.length() for path in self._paths]
29
- return np.array(length, dtype=np.float32)
30
-
31
- def get_tortuosity(self) -> npt.NDArray[np.float32]:
32
- """Get tortuosity of path."""
33
- return np.array([path.tortuosity() for path in self._paths], dtype=np.float32)
34
-
35
- @cached_property
36
- def _paths(self) -> List[Tree.Path]:
37
- return self.tree.get_paths()
@@ -1,102 +0,0 @@
1
- """Image stack related transform."""
2
-
3
- import warnings
4
- from typing import Tuple
5
-
6
- import numpy as np
7
- import numpy.typing as npt
8
-
9
- from swcgeom.transforms.base import Transform
10
-
11
- __all__ = [
12
- "ImagesCenterCrop",
13
- "ImagesScale",
14
- "ImagesClip",
15
- "ImagesNormalizer",
16
- "ImagesMeanVarianceAdjustment",
17
- "Center", # legacy
18
- ]
19
-
20
-
21
- NDArrayf32 = npt.NDArray[np.float32]
22
-
23
-
24
- class ImagesCenterCrop(Transform[NDArrayf32, NDArrayf32]):
25
- """Get image stack center."""
26
-
27
- def __init__(self, shape_out: int | Tuple[int, int, int]):
28
- super().__init__()
29
- self.shape_out = (
30
- shape_out
31
- if isinstance(shape_out, tuple)
32
- else (shape_out, shape_out, shape_out)
33
- )
34
-
35
- def __call__(self, x: NDArrayf32) -> NDArrayf32:
36
- diff = np.subtract(x.shape[:3], self.shape_out)
37
- s = diff // 2
38
- e = np.add(s, self.shape_out)
39
- return x[s[0] : e[0], s[1] : e[1], s[2] : e[2], :]
40
-
41
- def extra_repr(self) -> str:
42
- return f"shape_out=({','.join(str(a) for a in self.shape_out)})"
43
-
44
-
45
- class Center(ImagesCenterCrop):
46
- """Get image stack center.
47
-
48
- .. deprecated:: 0.5.0
49
- Use :class:`ImagesCenterCrop` instead.
50
- """
51
-
52
- def __init__(self, shape_out: int | Tuple[int, int, int]):
53
- warnings.warn(
54
- "`Center` is deprecated, use `ImagesCenterCrop` instead",
55
- DeprecationWarning,
56
- stacklevel=2,
57
- )
58
- super().__init__(shape_out)
59
-
60
-
61
- class ImagesScale(Transform[NDArrayf32, NDArrayf32]):
62
- def __init__(self, scaler: float) -> None:
63
- super().__init__()
64
- self.scaler = scaler
65
-
66
- def __call__(self, x: NDArrayf32) -> NDArrayf32:
67
- return self.scaler * x
68
-
69
-
70
- class ImagesClip(Transform[NDArrayf32, NDArrayf32]):
71
- def __init__(self, vmin: float = 0, vmax: float = 1, /) -> None:
72
- super().__init__()
73
- self.vmin, self.vmax = vmin, vmax
74
-
75
- def __call__(self, x: NDArrayf32) -> NDArrayf32:
76
- return np.clip(x, self.vmin, self.vmax)
77
-
78
-
79
- class ImagesNormalizer(Transform[NDArrayf32, NDArrayf32]):
80
- """Normalize image stack."""
81
-
82
- def __call__(self, x: NDArrayf32) -> NDArrayf32:
83
- mean = np.mean(x)
84
- variance = np.var(x)
85
- return (x - mean) / variance
86
-
87
-
88
- class ImagesMeanVarianceAdjustment(Transform[NDArrayf32, NDArrayf32]):
89
- """Adjust image stack mean and variance.
90
-
91
- See Also
92
- --------
93
- ~swcgeom.images.ImageStackFolder.stat
94
- """
95
-
96
- def __init__(self, mean: float, variance: float) -> None:
97
- super().__init__()
98
- self.mean = mean
99
- self.variance = variance
100
-
101
- def __call__(self, x: NDArrayf32) -> NDArrayf32:
102
- return (x - self.mean) / self.variance
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes