swcgeom 0.17.0__tar.gz → 0.17.2__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 (109) hide show
  1. {swcgeom-0.17.0 → swcgeom-0.17.2}/CHANGELOG.md +31 -0
  2. {swcgeom-0.17.0/swcgeom.egg-info → swcgeom-0.17.2}/PKG-INFO +2 -2
  3. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/CollectTips.ipynb +2 -3
  4. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/CutTree.ipynb +1 -2
  5. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/Features.ipynb +2 -2
  6. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/dgl/graph.py +4 -4
  7. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/pytorch/branch.py +2 -1
  8. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/pytorch/branch_dataset.py +3 -2
  9. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/pytorch/tree_folder_dataset.py +1 -1
  10. {swcgeom-0.17.0 → swcgeom-0.17.2}/pyproject.toml +1 -1
  11. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/_version.py +2 -2
  12. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/feature_extractor.py +25 -15
  13. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/features.py +20 -8
  14. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/lmeasure.py +33 -12
  15. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/sholl.py +10 -28
  16. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/trunk.py +12 -11
  17. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/visualization.py +9 -9
  18. swcgeom-0.17.2/swcgeom/analysis/visualization3d.py +85 -0
  19. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/volume.py +4 -4
  20. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/branch.py +4 -3
  21. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/branch_tree.py +3 -4
  22. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/compartment.py +3 -2
  23. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/node.py +17 -3
  24. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/path.py +6 -9
  25. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/population.py +43 -29
  26. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc.py +11 -10
  27. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/base.py +8 -17
  28. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/checker.py +3 -11
  29. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/io.py +7 -6
  30. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/normalizer.py +4 -3
  31. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/subtree.py +2 -2
  32. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/tree.py +41 -40
  33. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/tree_utils.py +13 -17
  34. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/tree_utils_impl.py +3 -3
  35. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/images/augmentation.py +3 -3
  36. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/images/folder.py +12 -26
  37. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/images/io.py +21 -35
  38. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/image_stack.py +20 -8
  39. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/images.py +3 -12
  40. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/neurolucida_asc.py +4 -6
  41. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/population.py +1 -3
  42. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/tree.py +38 -25
  43. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/tree_assembler.py +4 -3
  44. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/download.py +44 -21
  45. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/ellipse.py +3 -4
  46. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/neuromorpho.py +17 -16
  47. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/plotter_2d.py +12 -6
  48. swcgeom-0.17.2/swcgeom/utils/plotter_3d.py +31 -0
  49. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/renderer.py +6 -6
  50. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/sdf.py +4 -7
  51. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/solid_geometry.py +1 -3
  52. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/transforms.py +2 -4
  53. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/volumetric_object.py +8 -10
  54. {swcgeom-0.17.0 → swcgeom-0.17.2/swcgeom.egg-info}/PKG-INFO +2 -2
  55. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom.egg-info/SOURCES.txt +2 -0
  56. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom.egg-info/requires.txt +1 -1
  57. {swcgeom-0.17.0 → swcgeom-0.17.2}/.github/workflows/build.yml +0 -0
  58. {swcgeom-0.17.0 → swcgeom-0.17.2}/.github/workflows/github-publish.yml +0 -0
  59. {swcgeom-0.17.0 → swcgeom-0.17.2}/.github/workflows/pypi-publish.yml +0 -0
  60. {swcgeom-0.17.0 → swcgeom-0.17.2}/.github/workflows/test.yml +0 -0
  61. {swcgeom-0.17.0 → swcgeom-0.17.2}/.gitignore +0 -0
  62. {swcgeom-0.17.0 → swcgeom-0.17.2}/.pylintrc +0 -0
  63. {swcgeom-0.17.0 → swcgeom-0.17.2}/.vscode/settings.json +0 -0
  64. {swcgeom-0.17.0 → swcgeom-0.17.2}/LICENSE +0 -0
  65. {swcgeom-0.17.0 → swcgeom-0.17.2}/README.md +0 -0
  66. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/Branch.ipynb +0 -0
  67. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/BranchTree.ipynb +0 -0
  68. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/GeometryTransform.ipynb +0 -0
  69. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/ImageStack.ipynb +0 -0
  70. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/MST.ipynb +0 -0
  71. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/SpectralClustering.ipynb +0 -0
  72. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/Tree.ipynb +0 -0
  73. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/data/101711-10_4p5-of-16_initial.CNG.swc +0 -0
  74. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/data/101711-11_16-of-16_initial.CNG.swc +0 -0
  75. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/data/1059283677_15257_2226-X16029-Y23953.swc +0 -0
  76. {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/data/toydata.swc +0 -0
  77. {swcgeom-0.17.0 → swcgeom-0.17.2}/git-conventional-commits.yaml +0 -0
  78. {swcgeom-0.17.0 → swcgeom-0.17.2}/setup.cfg +0 -0
  79. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/__init__.py +0 -0
  80. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/__init__.py +0 -0
  81. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/__init__.py +0 -0
  82. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/__init__.py +0 -0
  83. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/assembler.py +0 -0
  84. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/images/__init__.py +0 -0
  85. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/images/contrast.py +0 -0
  86. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/__init__.py +0 -0
  87. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/base.py +0 -0
  88. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/branch.py +0 -0
  89. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/geometry.py +0 -0
  90. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/image_preprocess.py +0 -0
  91. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/mst.py +0 -0
  92. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/path.py +0 -0
  93. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/__init__.py +0 -0
  94. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/debug.py +0 -0
  95. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/dsu.py +0 -0
  96. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/file.py +0 -0
  97. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/numpy_helper.py +0 -0
  98. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom.egg-info/dependency_links.txt +0 -0
  99. {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom.egg-info/top_level.txt +0 -0
  100. {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/__init__.py +0 -0
  101. {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/analysis/test_volume.py +0 -0
  102. {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/transforms/test_neurolucida_asc.py +0 -0
  103. {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/__init__.py +0 -0
  104. {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_dsu.py +0 -0
  105. {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_numpy_helper.py +0 -0
  106. {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_sdf.py +0 -0
  107. {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_solid_geometry.py +0 -0
  108. {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_transforms.py +0 -0
  109. {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_volumetric_object.py +0 -0
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## **0.17.2**&emsp;<sub><sup>2024-08-07 ([9d5347d...59a5cbf](https://github.com/yzx9/swcgeom/compare/9d5347d0a427f460b92fd86c596e40a23812e48d...59a5cbfbe78f18a95ff8f35e72a4a0f0cdbc5009?diff=split))</sup></sub>
4
+
5
+ ### Bug Fixes
6
+
7
+ ##### &ensp;`analysis`
8
+
9
+ - handle zero partition asymmetry in lmeasure ([1f332f2](https://github.com/yzx9/swcgeom/commit/1f332f22f465ea1ec7ff0b5e4013ddb6d64ffa5d))
10
+ - workaround with angle ([05d2ff7](https://github.com/yzx9/swcgeom/commit/05d2ff77ba5f4b4331daca0bade89e10a56f564d))
11
+
12
+ ##### &ensp;`utils`
13
+
14
+ - download with multiprocessing should avoid locals func ([6aff3e0](https://github.com/yzx9/swcgeom/commit/6aff3e0ee06fb527f759e6ec9ac09ab1ca745dad))
15
+
16
+
17
+ ### BREAKING CHANGES
18
+ - rename bifurcation to furcation ([26e9dec](https://github.com/yzx9/swcgeom/commit/26e9dec2db5b7d457dfe52d9a8e0177d26bc5b07))
19
+ - `core` lazy loading in range indexing ([7d38ea8](https://github.com/yzx9/swcgeom/commit/7d38ea80fefc907c3f55663c052db9b81f144d68))
20
+ <br>
21
+
22
+
23
+ ## **0.17.1**&emsp;<sub><sup>2024-04-05 ([a8007ac...0ef3c2b](https://github.com/yzx9/swcgeom/compare/a8007ac6c5eb7de03298dbdcc6be59d3c282e125...0ef3c2b370a570366a79ef456c1125d12f006409?diff=split))</sup></sub>
24
+
25
+ ### Features
26
+
27
+ ##### &ensp;`utils`
28
+
29
+ - set round style ([90030e3](https://github.com/yzx9/swcgeom/commit/90030e3af1dc7ba763e04c313cb93f154e2f052c))
30
+
31
+ <br>
32
+
33
+
3
34
  ## **0.17.0**&emsp;<sub><sup>2024-04-04 ([59257cb...2072139](https://github.com/yzx9/swcgeom/compare/59257cbfb6264afd3ca31fcba002f692bbb69ed0...20721393086e5a890b097b3ae32aaa1e4ad6b898?diff=split))</sup></sub>
4
35
 
5
36
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: swcgeom
3
- Version: 0.17.0
3
+ Version: 0.17.2
4
4
  Summary: Neuron geometry library for swc format
5
5
  Author-email: yzx9 <yuan.zx@outlook.com>
6
6
  License: Apache-2.0
@@ -20,7 +20,7 @@ Requires-Dist: seaborn>=0.12.0
20
20
  Requires-Dist: tifffile>=2022.8.12
21
21
  Requires-Dist: typing_extensions>=4.4.0
22
22
  Requires-Dist: tqdm>=4.46.1
23
- Requires-Dist: v3d-py-helper>=0.1.0
23
+ Requires-Dist: v3d-py-helper==0.1.0
24
24
  Provides-Extra: all
25
25
  Requires-Dist: beautifulsoup4>=4.11.1; extra == "all"
26
26
  Requires-Dist: certifi>=2023.5.7; extra == "all"
@@ -23,7 +23,6 @@
23
23
  "import os\n",
24
24
  "from functools import reduce\n",
25
25
  "from operator import add\n",
26
- "from typing import List\n",
27
26
  "\n",
28
27
  "import numpy as np\n",
29
28
  "\n",
@@ -37,11 +36,11 @@
37
36
  "metadata": {},
38
37
  "outputs": [],
39
38
  "source": [
40
- "def collect_tips(cur: swcgeom.Tree.Node, children: List[int]) -> int:\n",
39
+ "def collect_tips(cur: swcgeom.Tree.Node, children: list[int]) -> int:\n",
41
40
  " cur[\"tips\"] = max(1, reduce(add, children, 0))\n",
42
41
  " return cur[\"tips\"]\n",
43
42
  "\n",
44
- "def collect_length(cur: swcgeom.Tree.Node, children: List[swcgeom.Tree.Node]) -> swcgeom.Tree.Node:\n",
43
+ "def collect_length(cur: swcgeom.Tree.Node, children: list[swcgeom.Tree.Node]) -> swcgeom.Tree.Node:\n",
45
44
  " def length(acc: float, child: swcgeom.Tree.Node) -> float:\n",
46
45
  " return acc + cur.distance(child) + child[\"length\"]\n",
47
46
  "\n",
@@ -30,7 +30,6 @@
30
30
  "outputs": [],
31
31
  "source": [
32
32
  "import math\n",
33
- "from typing import List\n",
34
33
  "\n",
35
34
  "import numpy as np\n",
36
35
  "import matplotlib.pyplot as plt\n",
@@ -138,7 +137,7 @@
138
137
  "\n",
139
138
  " removals = []\n",
140
139
  "\n",
141
- " def collect_cut(n: Tree.Node, children: List[int]) -> int:\n",
140
+ " def collect_cut(n: Tree.Node, children: list[int]) -> int:\n",
142
141
  " if len(children) <= 1:\n",
143
142
  " return n.id\n",
144
143
  "\n",
@@ -20,7 +20,7 @@
20
20
  "metadata": {},
21
21
  "outputs": [],
22
22
  "source": [
23
- "from typing import Any, cast, Dict\n",
23
+ "from typing import Any, cast\n",
24
24
  "\n",
25
25
  "import numpy as np\n",
26
26
  "import matplotlib.pyplot as plt\n",
@@ -88,7 +88,7 @@
88
88
  ],
89
89
  "source": [
90
90
  "features = extract_feature(tree3)\n",
91
- "all_features: Dict[Feature, Dict] = {\n",
91
+ "all_features: dict[Feature, dict] = {\n",
92
92
  " \"length\": {},\n",
93
93
  " \"node_radial_distance\": {},\n",
94
94
  " \"node_branch_order\": {},\n",
@@ -1,6 +1,6 @@
1
1
  """Dgl grapth transforms."""
2
2
 
3
- from typing import List, cast
3
+ from typing import cast
4
4
 
5
5
  import dgl
6
6
 
@@ -19,13 +19,13 @@ class ToDGLGraph(Transform[Tree, dgl.DGLGraph]):
19
19
  this class is more of a toy and template.
20
20
  """
21
21
 
22
- keys: List[str] | None
22
+ keys: list[str] | None
23
23
  to_bidirected: bool
24
24
 
25
25
  def __init__(
26
26
  self,
27
27
  to_bidirected: bool = False,
28
- keys: List[str] | None = None,
28
+ keys: list[str] | None = None,
29
29
  ) -> None:
30
30
  """Transofrm tree to dgl graph.
31
31
 
@@ -33,7 +33,7 @@ class ToDGLGraph(Transform[Tree, dgl.DGLGraph]):
33
33
  ----------
34
34
  to_bidirected : bool, default to `False`
35
35
  If True, return bidirected graph.
36
- keys : List[str], optional
36
+ keys : list[str], optional
37
37
  Copy these keys as ndata of graph.
38
38
  """
39
39
 
@@ -1,6 +1,7 @@
1
1
  """Transformations in branch requires pytorch."""
2
2
 
3
- from typing import Callable, Literal
3
+ from collections.abc import Callable
4
+ from typing import Literal
4
5
 
5
6
  import numpy as np
6
7
  import numpy.typing as npt
@@ -2,7 +2,8 @@
2
2
 
3
3
  import os
4
4
  import warnings
5
- from typing import Generic, Iterable, TypeVar, cast
5
+ from collections.abc import Iterable
6
+ from typing import Generic, TypeVar, cast
6
7
 
7
8
  import torch
8
9
  import torch.utils.data
@@ -40,7 +41,7 @@ class BranchDataset(torch.utils.data.Dataset, Generic[T]):
40
41
  save : Union[str, bool], default `True`
41
42
  Save branch data to file if not False. If `True`, automatically
42
43
  generate file name.
43
- transfroms : Transfroms[Branch, T], optional
44
+ transforms : Transforms[Branch, T], optional
44
45
  Branch transformations.
45
46
 
46
47
  See Also
@@ -30,7 +30,7 @@ class TreeFolderDataset(torch.utils.data.Dataset, Generic[T]):
30
30
  ----------
31
31
  swc_dir : str
32
32
  Path of SWC file directory.
33
- transfroms : Transfroms[Tree, T], optional
33
+ transforms : Transforms[Tree, T], optional
34
34
  Branch transformations.
35
35
 
36
36
  See Also
@@ -23,7 +23,7 @@ dependencies = [
23
23
  "tifffile>=2022.8.12",
24
24
  "typing_extensions>=4.4.0",
25
25
  "tqdm>=4.46.1",
26
- "v3d-py-helper>=0.1.0",
26
+ "v3d-py-helper==0.1.0",
27
27
  ]
28
28
 
29
29
  [project.optional-dependencies]
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.17.0'
16
- __version_tuple__ = version_tuple = (0, 17, 0)
15
+ __version__ = version = '0.17.2'
16
+ __version_tuple__ = version_tuple = (0, 17, 2)
@@ -7,10 +7,11 @@ naming specification.
7
7
  """
8
8
 
9
9
  from abc import ABC, abstractmethod
10
+ from collections.abc import Callable
10
11
  from functools import cached_property
11
12
  from itertools import chain
12
13
  from os.path import basename
13
- from typing import Any, Callable, Dict, List, Literal, Tuple, overload
14
+ from typing import Any, Literal, overload
14
15
 
15
16
  import numpy as np
16
17
  import numpy.typing as npt
@@ -18,8 +19,8 @@ import seaborn as sns
18
19
  from matplotlib.axes import Axes
19
20
 
20
21
  from swcgeom.analysis.features import (
21
- BifurcationFeatures,
22
22
  BranchFeatures,
23
+ FurcationFeatures,
23
24
  NodeFeatures,
24
25
  PathFeatures,
25
26
  TipFeatures,
@@ -39,6 +40,9 @@ Feature = Literal[
39
40
  "node_count",
40
41
  "node_radial_distance",
41
42
  "node_branch_order",
43
+ # furcation nodes
44
+ "furcation_count",
45
+ "furcation_radial_distance",
42
46
  # bifurcation nodes
43
47
  "bifurcation_count",
44
48
  "bifurcation_radial_distance",
@@ -54,9 +58,9 @@ Feature = Literal[
54
58
  ]
55
59
 
56
60
  NDArrayf32 = npt.NDArray[np.float32]
57
- FeatAndKwargs = Feature | Tuple[Feature, Dict[str, Any]]
61
+ FeatAndKwargs = Feature | tuple[Feature, dict[str, Any]]
58
62
 
59
- Feature1D = set(["length", "volume", "node_count", "bifurcation_count", "tip_count"])
63
+ Feature1D = set(["length", "volume", "node_count", "furcation_count", "tip_count"])
60
64
 
61
65
 
62
66
  class Features:
@@ -69,7 +73,7 @@ class Features:
69
73
  @cached_property
70
74
  def node_features(self) -> NodeFeatures: return NodeFeatures(self.tree)
71
75
  @cached_property
72
- def bifurcation_features(self) -> BifurcationFeatures: return BifurcationFeatures(self.node_features)
76
+ def furcation_features(self) -> FurcationFeatures: return FurcationFeatures(self.node_features)
73
77
  @cached_property
74
78
  def tip_features(self) -> TipFeatures: return TipFeatures(self.node_features)
75
79
  @cached_property
@@ -121,9 +125,9 @@ class FeatureExtractor(ABC):
121
125
  @overload
122
126
  def get(self, feature: Feature, **kwargs) -> NDArrayf32: ...
123
127
  @overload
124
- def get(self, feature: List[FeatAndKwargs]) -> List[NDArrayf32]: ...
128
+ def get(self, feature: list[FeatAndKwargs]) -> list[NDArrayf32]: ...
125
129
  @overload
126
- def get(self, feature: Dict[Feature, Dict[str, Any]]) -> Dict[str, NDArrayf32]: ...
130
+ def get(self, feature: dict[Feature, dict[str, Any]]) -> dict[str, NDArrayf32]: ...
127
131
  # fmt:on
128
132
  def get(self, feature, **kwargs):
129
133
  """Get feature.
@@ -168,7 +172,7 @@ class FeatureExtractor(ABC):
168
172
 
169
173
  # Custom Plots
170
174
 
171
- def plot_node_branch_order(self, feature_kwargs: Dict[str, Any], **kwargs) -> Axes:
175
+ def plot_node_branch_order(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
172
176
  vals = self._get("node_branch_order", **feature_kwargs)
173
177
  bin_edges = np.arange(int(np.ceil(vals.max() + 1))) + 0.5
174
178
  return self._plot_histogram_impl(vals, bin_edges, **kwargs)
@@ -213,6 +217,12 @@ class FeatureExtractor(ABC):
213
217
  ) -> Axes:
214
218
  raise NotImplementedError()
215
219
 
220
+ def get_bifurcation_count(self, **kwargs):
221
+ raise DeprecationWarning("Use `furcation_count` instead.")
222
+
223
+ def get_bifurcation_radial_distance(self, **kwargs):
224
+ raise DeprecationWarning("Use `furcation_radial_distance` instead.")
225
+
216
226
 
217
227
  class TreeFeatureExtractor(FeatureExtractor):
218
228
  """Extract feature from tree."""
@@ -234,7 +244,7 @@ class TreeFeatureExtractor(FeatureExtractor):
234
244
 
235
245
  def plot_sholl(
236
246
  self,
237
- feature_kwargs: Dict[str, Any], # pylint: disable=unused-argument
247
+ feature_kwargs: dict[str, Any], # pylint: disable=unused-argument
238
248
  **kwargs,
239
249
  ) -> Axes:
240
250
  _, ax = self._features.sholl.plot(**kwargs)
@@ -264,7 +274,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
264
274
  """Extract features from population."""
265
275
 
266
276
  _population: Population
267
- _features: List[Features]
277
+ _features: list[Features]
268
278
 
269
279
  def __init__(self, population: Population) -> None:
270
280
  super().__init__()
@@ -279,7 +289,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
279
289
 
280
290
  # Custom Plots
281
291
 
282
- def plot_sholl(self, feature_kwargs: Dict[str, Any], **kwargs) -> Axes:
292
+ def plot_sholl(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
283
293
  vals, rs = self._get_sholl_impl(**feature_kwargs)
284
294
  ax = self._lineplot(xs=rs, ys=vals.flatten(), **kwargs)
285
295
  ax.set_ylabel("Count of Intersections")
@@ -295,7 +305,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
295
305
 
296
306
  def _get_sholl_impl(
297
307
  self, steps: int = 20, **kwargs
298
- ) -> Tuple[NDArrayf32, NDArrayf32]:
308
+ ) -> tuple[NDArrayf32, NDArrayf32]:
299
309
  rmax = max(t.sholl.rmax for t in self._features)
300
310
  rs = Sholl.get_rs(rmax=rmax, steps=steps)
301
311
  vals = self._get_impl("sholl", steps=rs, **kwargs)
@@ -333,7 +343,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
333
343
  """Extract feature from population."""
334
344
 
335
345
  _populations: Populations
336
- _features: List[List[Features]]
346
+ _features: list[list[Features]]
337
347
 
338
348
  def __init__(self, populations: Populations) -> None:
339
349
  super().__init__()
@@ -348,7 +358,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
348
358
 
349
359
  # Custom Plots
350
360
 
351
- def plot_sholl(self, feature_kwargs: Dict[str, Any], **kwargs) -> Axes:
361
+ def plot_sholl(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
352
362
  vals, rs = self._get_sholl_impl(**feature_kwargs)
353
363
  ax = self._lineplot(xs=rs, ys=vals, **kwargs)
354
364
  ax.set_ylabel("Count of Intersections")
@@ -369,7 +379,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
369
379
 
370
380
  def _get_sholl_impl(
371
381
  self, steps: int = 20, **kwargs
372
- ) -> Tuple[NDArrayf32, NDArrayf32]:
382
+ ) -> tuple[NDArrayf32, NDArrayf32]:
373
383
  rmaxs = chain.from_iterable((t.sholl.rmax for t in p) for p in self._features)
374
384
  rmax = max(rmaxs)
375
385
  rs = Sholl.get_rs(rmax=rmax, steps=steps)
@@ -2,11 +2,11 @@
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from functools import cached_property
5
- from typing import List, TypeVar
5
+ from typing import TypeVar
6
6
 
7
7
  import numpy as np
8
8
  import numpy.typing as npt
9
- from typing_extensions import Self
9
+ from typing_extensions import Self, deprecated
10
10
 
11
11
  from swcgeom.core import Branch, BranchTree, Tree
12
12
 
@@ -121,12 +121,24 @@ class _SubsetNodesFeatures(ABC):
121
121
  return cls(NodeFeatures(tree))
122
122
 
123
123
 
124
- class BifurcationFeatures(_SubsetNodesFeatures):
125
- """Evaluate bifurcation node feature of tree."""
124
+ class FurcationFeatures(_SubsetNodesFeatures):
125
+ """Evaluate furcation node feature of tree."""
126
126
 
127
127
  @cached_property
128
128
  def nodes(self) -> npt.NDArray[np.bool_]:
129
- return np.array([n.is_bifurcation() for n in self._features.tree])
129
+ return np.array([n.is_furcation() for n in self._features.tree])
130
+
131
+
132
+ @deprecated("Use FurcationFeatures instead")
133
+ class BifurcationFeatures(FurcationFeatures):
134
+ """Evaluate bifurcation node feature of tree.
135
+
136
+ Notes
137
+ -----
138
+ Deprecated due to the wrong spelling of furcation. For now, it
139
+ is just an alias of `FurcationFeatures` and raise a warning. It
140
+ will be change to raise an error in the future.
141
+ """
130
142
 
131
143
 
132
144
  class TipFeatures(_SubsetNodesFeatures):
@@ -163,7 +175,7 @@ class PathFeatures:
163
175
  return np.array([path.tortuosity() for path in self._paths], dtype=np.float32)
164
176
 
165
177
  @cached_property
166
- def _paths(self) -> List[Tree.Path]:
178
+ def _paths(self) -> list[Tree.Path]:
167
179
  return self.tree.get_paths()
168
180
 
169
181
 
@@ -201,7 +213,7 @@ class BranchFeatures:
201
213
  return self.calc_angle(self._branches, eps=eps)
202
214
 
203
215
  @staticmethod
204
- def calc_angle(branches: List[T], eps: float = 1e-7) -> npt.NDArray[np.float32]:
216
+ def calc_angle(branches: list[T], eps: float = 1e-7) -> npt.NDArray[np.float32]:
205
217
  """Calc agnle between branches.
206
218
 
207
219
  Returns
@@ -219,5 +231,5 @@ class BranchFeatures:
219
231
  return angle
220
232
 
221
233
  @cached_property
222
- def _branches(self) -> List[Tree.Branch]:
234
+ def _branches(self) -> list[Tree.Branch]:
223
235
  return self.tree.get_branches()
@@ -1,13 +1,11 @@
1
1
  """L-Measure analysis."""
2
2
 
3
3
  import math
4
- from typing import Literal, Tuple
4
+ from typing import Literal
5
5
 
6
6
  import numpy as np
7
7
  import numpy.typing as npt
8
-
9
8
  from swcgeom.core import Branch, Compartment, Node, Tree
10
- from swcgeom.utils import angle
11
9
 
12
10
  __all__ = ["LMeasure"]
13
11
 
@@ -69,7 +67,7 @@ class LMeasure:
69
67
  --------
70
68
  L-Measure: http://cng.gmu.edu:8080/Lm/help/N_bifs.htm
71
69
  """
72
- return len(tree.get_bifurcations())
70
+ return len(tree.get_furcations())
73
71
 
74
72
  def n_branch(self, tree: Tree) -> int:
75
73
  """Number of branches.
@@ -163,12 +161,15 @@ class LMeasure:
163
161
  --------
164
162
  L-Measure: http://cng.gmu.edu:8080/Lm/help/Partition_asymmetry.htm
165
163
  """
164
+
166
165
  children = n.children()
167
166
  assert (
168
167
  len(children) == 2
169
168
  ), "Partition asymmetry is only defined for bifurcations"
170
169
  n1 = len(children[0].subtree().get_tips())
171
170
  n2 = len(children[1].subtree().get_tips())
171
+ if n1 == n2:
172
+ return 0
172
173
  return abs(n1 - n2) / (n1 + n2 - 2)
173
174
 
174
175
  def fractal_dim(self):
@@ -274,7 +275,7 @@ class LMeasure:
274
275
  rall_power, _, _, _ = self._rall_power(bif)
275
276
  return rall_power
276
277
 
277
- def _rall_power_d(self, bif: Tree.Node) -> Tuple[float, float, float]:
278
+ def _rall_power_d(self, bif: Tree.Node) -> tuple[float, float, float]:
278
279
  children = bif.children()
279
280
  assert len(children) == 2, "Rall Power is only defined for bifurcations"
280
281
  parent = bif.parent()
@@ -284,7 +285,7 @@ class LMeasure:
284
285
  da, db = 2 * children[0].r, 2 * children[1].r
285
286
  return dp, da, db
286
287
 
287
- def _rall_power(self, bif: Tree.Node) -> Tuple[float, float, float, float]:
288
+ def _rall_power(self, bif: Tree.Node) -> tuple[float, float, float, float]:
288
289
  dp, da, db = self._rall_power_d(bif)
289
290
  start, stop, step = 0, 5, 5 / 1000
290
291
  xs = np.arange(start, stop, step)
@@ -336,7 +337,7 @@ class LMeasure:
336
337
  return (da**rall_power + db**rall_power) / dp**rall_power
337
338
 
338
339
  def bif_ampl_local(self, bif: Tree.Node) -> float:
339
- """Bifuraction angle.
340
+ """Bifurcation angle.
340
341
 
341
342
  Given a bifurcation, this function returns the angle between
342
343
  the first two compartments (in degree).
@@ -350,7 +351,7 @@ class LMeasure:
350
351
  return np.degrees(angle(v1, v2))
351
352
 
352
353
  def bif_ampl_remote(self, bif: Tree.Node) -> float:
353
- """Bifuraction angle.
354
+ """Bifurcation angle.
354
355
 
355
356
  This function returns the angle between two bifurcation points
356
357
  or between bifurcation point and terminal point or between two
@@ -361,7 +362,7 @@ class LMeasure:
361
362
  L-Measure: http://cng.gmu.edu:8080/Lm/help/Bif_ampl_remote.htm
362
363
  """
363
364
 
364
- v1, v2 = self._bif_vector_local(bif)
365
+ v1, v2 = self._bif_vector_remote(bif)
365
366
  return np.degrees(angle(v1, v2))
366
367
 
367
368
  def bif_tilt_local(self, bif: Tree.Node) -> float:
@@ -501,7 +502,7 @@ class LMeasure:
501
502
 
502
503
  def _bif_vector_local(
503
504
  self, bif: Tree.Node
504
- ) -> Tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]:
505
+ ) -> tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]:
505
506
  children = bif.children()
506
507
  assert len(children) == 2, "Only defined for bifurcations"
507
508
 
@@ -511,7 +512,7 @@ class LMeasure:
511
512
 
512
513
  def _bif_vector_remote(
513
514
  self, bif: Tree.Node
514
- ) -> Tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]:
515
+ ) -> tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]:
515
516
  children = bif.children()
516
517
  assert len(children) == 2, "Only defined for bifurcations"
517
518
 
@@ -665,7 +666,7 @@ class LMeasure:
665
666
  n = node
666
667
  order = 0
667
668
  while n is not None:
668
- if n.is_bifurcation():
669
+ if n.is_furcation():
669
670
  order += 1
670
671
  n = n.parent()
671
672
  return order
@@ -819,3 +820,23 @@ def pill_surface_area(ra: float, rb: float, h: float) -> float:
819
820
  bottom_hemisphere_area = 2 * math.pi * rb**2
820
821
  total_area = lateral_area + top_hemisphere_area + bottom_hemisphere_area
821
822
  return total_area
823
+
824
+
825
+ # TODO: move to `utils`
826
+ def angle(a: npt.ArrayLike, b: npt.ArrayLike) -> float:
827
+ """Get the angle of vectors.
828
+
829
+ Returns
830
+ -------
831
+ angle : float
832
+ Angle [0, PI) in radians.
833
+ """
834
+
835
+ a, b = np.array(a), np.array(b)
836
+ if np.linalg.norm(a) == 0 or np.linalg.norm(b) == 0:
837
+ raise ValueError("Input vectors must not be zero vectors.")
838
+
839
+ costheta = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
840
+ costheta = np.clip(costheta, -1, 1) # avoid numerical errors
841
+ theta = np.arccos(costheta)
842
+ return theta
@@ -1,13 +1,14 @@
1
1
  """Sholl analysis."""
2
2
 
3
3
  import warnings
4
- from typing import List, Literal, Optional, Tuple
4
+ from typing import Literal, Optional
5
5
 
6
6
  import numpy as np
7
7
  import numpy.typing as npt
8
8
  import seaborn as sns
9
9
  from matplotlib.axes import Axes
10
10
  from matplotlib.figure import Figure
11
+ from typing_extensions import deprecated
11
12
 
12
13
  from swcgeom.analysis.visualization import draw
13
14
  from swcgeom.core import Tree
@@ -84,18 +85,18 @@ class Sholl:
84
85
 
85
86
  def plot( # pylint: disable=too-many-arguments
86
87
  self,
87
- steps: List[float] | int = 20,
88
+ steps: list[float] | int = 20,
88
89
  plot_type: str | None = None,
89
90
  kind: Literal["bar", "linechart", "circles"] = "circles",
90
91
  fig: Figure | None = None,
91
92
  ax: Axes | None = None,
92
93
  **kwargs,
93
- ) -> Tuple[Figure, Axes]:
94
+ ) -> tuple[Figure, Axes]:
94
95
  """Plot Sholl analysis.
95
96
 
96
97
  Parameters
97
98
  ----------
98
- steps : int or List[float], default to 20
99
+ steps : int or list[float], default to 20
99
100
  Steps of raius of circle. If steps is int, then it will be
100
101
  evenly divided into n radii.
101
102
  kind : "bar" | "linechart" | "circles", default `circles`
@@ -160,20 +161,17 @@ class Sholl:
160
161
 
161
162
  return self.get_rs(self.rmax, steps)
162
163
 
164
+ @deprecated("Use `Sholl.get(x)` instead")
163
165
  def get_count(self) -> npt.NDArray[np.int32]:
164
166
  """Get the count of intersection.
165
167
 
166
168
  .. deprecated:: 0.5.0
167
- Use :meth:`Sholl.get` instead.
169
+ Use :meth:`Sholl(x).get()` instead.
168
170
  """
169
171
 
170
- warnings.warn(
171
- "`Sholl.get_count` has been renamed to `get` since v0.5.0, "
172
- "and will be removed in next version",
173
- DeprecationWarning,
174
- )
175
172
  return self.get().astype(np.int32)
176
173
 
174
+ @deprecated("Use `Shool(x).get().mean()` instead")
177
175
  def avg(self) -> float:
178
176
  """Get the average of the count of intersection.
179
177
 
@@ -181,14 +179,9 @@ class Sholl:
181
179
  Use :meth:`Shool(x).get().mean()` instead.
182
180
  """
183
181
 
184
- warnings.warn(
185
- "`Sholl.avg` has been deprecated since v0.6.0 and will be "
186
- "removed in next version, use `Shool(x).get().mean()` "
187
- "instead",
188
- DeprecationWarning,
189
- )
190
182
  return self.get().mean()
191
183
 
184
+ @deprecated("Use `Shool(x).get().std()` instead")
192
185
  def std(self) -> float:
193
186
  """Get the std of the count of intersection.
194
187
 
@@ -196,14 +189,9 @@ class Sholl:
196
189
  Use :meth:`Shool(x).get().std()` instead.
197
190
  """
198
191
 
199
- warnings.warn(
200
- "`Sholl.std` has been deprecate since v0.6.0 and will be "
201
- "removed in next version, use `Shool(x).get().std()` "
202
- "instead",
203
- DeprecationWarning,
204
- )
205
192
  return self.get().std()
206
193
 
194
+ @deprecated("Use `Shool(x).get().sum()` instead")
207
195
  def sum(self) -> int:
208
196
  """Get the sum of the count of intersection.
209
197
 
@@ -211,10 +199,4 @@ class Sholl:
211
199
  Use :meth:`Shool(x).get().sum()` instead.
212
200
  """
213
201
 
214
- warnings.warn(
215
- "`Sholl.sum` has been deprecate since v0.6.0 and will be "
216
- "removed in next version, use `Shool(x).get().sum()` "
217
- "instead",
218
- DeprecationWarning,
219
- )
220
202
  return self.get().sum()