swcgeom 0.15.0__tar.gz → 0.18.3__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.
- {swcgeom-0.15.0 → swcgeom-0.18.3}/LICENSE +1 -1
- {swcgeom-0.15.0/swcgeom.egg-info → swcgeom-0.18.3}/PKG-INFO +14 -10
- {swcgeom-0.15.0 → swcgeom-0.18.3}/README.md +7 -3
- {swcgeom-0.15.0 → swcgeom-0.18.3}/pyproject.toml +6 -13
- swcgeom-0.18.3/swcgeom/__init__.py +31 -0
- swcgeom-0.18.3/swcgeom/analysis/__init__.py +23 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/analysis/feature_extractor.py +43 -18
- swcgeom-0.18.3/swcgeom/analysis/features.py +250 -0
- swcgeom-0.18.3/swcgeom/analysis/lmeasure.py +857 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/analysis/sholl.py +55 -29
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/analysis/trunk.py +27 -11
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/analysis/visualization.py +24 -9
- swcgeom-0.18.3/swcgeom/analysis/visualization3d.py +100 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/analysis/volume.py +19 -4
- swcgeom-0.18.3/swcgeom/core/__init__.py +34 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/branch.py +28 -7
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/branch_tree.py +18 -4
- swcgeom-0.15.0/swcgeom/core/segment.py → swcgeom-0.18.3/swcgeom/core/compartment.py +31 -10
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/node.py +31 -10
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/path.py +37 -10
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/population.py +103 -34
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/swc.py +26 -10
- swcgeom-0.18.3/swcgeom/core/swc_utils/__init__.py +31 -0
- swcgeom-0.18.3/swcgeom/core/swc_utils/assembler.py +52 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/swc_utils/base.py +25 -12
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/swc_utils/checker.py +31 -14
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/swc_utils/io.py +24 -7
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/swc_utils/normalizer.py +20 -4
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/swc_utils/subtree.py +17 -2
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/tree.py +85 -72
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/tree_utils.py +31 -16
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/core/tree_utils_impl.py +18 -3
- swcgeom-0.18.3/swcgeom/images/__init__.py +19 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/images/augmentation.py +24 -4
- swcgeom-0.18.3/swcgeom/images/contrast.py +122 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/images/folder.py +97 -39
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/images/io.py +108 -121
- swcgeom-0.18.3/swcgeom/transforms/__init__.py +30 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/transforms/base.py +17 -2
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/transforms/branch.py +74 -8
- swcgeom-0.18.3/swcgeom/transforms/branch_tree.py +82 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/transforms/geometry.py +22 -7
- swcgeom-0.18.3/swcgeom/transforms/image_preprocess.py +115 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/transforms/image_stack.py +37 -13
- swcgeom-0.18.3/swcgeom/transforms/images.py +209 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/transforms/mst.py +20 -5
- swcgeom-0.18.3/swcgeom/transforms/neurolucida_asc.py +508 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/transforms/path.py +15 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/transforms/population.py +16 -3
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/transforms/tree.py +89 -31
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/transforms/tree_assembler.py +23 -7
- swcgeom-0.18.3/swcgeom/utils/__init__.py +29 -0
- swcgeom-0.18.3/swcgeom/utils/debug.py +34 -0
- swcgeom-0.18.3/swcgeom/utils/download.py +137 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/utils/dsu.py +15 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/utils/ellipse.py +18 -4
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/utils/file.py +15 -0
- swcgeom-0.18.3/swcgeom/utils/neuromorpho.py +606 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/utils/numpy_helper.py +29 -4
- swcgeom-0.18.3/swcgeom/utils/plotter_2d.py +151 -0
- swcgeom-0.18.3/swcgeom/utils/plotter_3d.py +48 -0
- swcgeom-0.18.3/swcgeom/utils/renderer.py +148 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/utils/sdf.py +24 -8
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/utils/solid_geometry.py +16 -3
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/utils/transforms.py +17 -4
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom/utils/volumetric_object.py +23 -10
- {swcgeom-0.15.0 → swcgeom-0.18.3/swcgeom.egg-info}/PKG-INFO +14 -10
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom.egg-info/SOURCES.txt +11 -41
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom.egg-info/requires.txt +4 -4
- swcgeom-0.15.0/.github/workflows/build.yml +0 -38
- swcgeom-0.15.0/.github/workflows/github-publish.yml +0 -35
- swcgeom-0.15.0/.github/workflows/pypi-publish.yml +0 -31
- swcgeom-0.15.0/.github/workflows/test.yml +0 -28
- swcgeom-0.15.0/.gitignore +0 -164
- swcgeom-0.15.0/.pylintrc +0 -636
- swcgeom-0.15.0/.vscode/settings.json +0 -20
- swcgeom-0.15.0/CHANGELOG.md +0 -704
- swcgeom-0.15.0/examples/Branch.ipynb +0 -224
- swcgeom-0.15.0/examples/BranchTree.ipynb +0 -94
- swcgeom-0.15.0/examples/CollectTips.ipynb +0 -101
- swcgeom-0.15.0/examples/CutTree.ipynb +0 -197
- swcgeom-0.15.0/examples/Features.ipynb +0 -261
- swcgeom-0.15.0/examples/GeometryTransform.ipynb +0 -81
- swcgeom-0.15.0/examples/ImageStack.ipynb +0 -89
- swcgeom-0.15.0/examples/MST.ipynb +0 -173
- swcgeom-0.15.0/examples/SpectralClustering.ipynb +0 -267
- swcgeom-0.15.0/examples/Tree.ipynb +0 -248
- swcgeom-0.15.0/examples/data/101711-10_4p5-of-16_initial.CNG.swc +0 -161
- swcgeom-0.15.0/examples/data/101711-11_16-of-16_initial.CNG.swc +0 -318
- swcgeom-0.15.0/examples/data/1059283677_15257_2226-X16029-Y23953.swc +0 -5517
- swcgeom-0.15.0/examples/data/toydata.swc +0 -7
- swcgeom-0.15.0/examples/dgl/graph.py +0 -59
- swcgeom-0.15.0/examples/pytorch/branch.py +0 -53
- swcgeom-0.15.0/examples/pytorch/branch_dataset.py +0 -98
- swcgeom-0.15.0/examples/pytorch/tree_folder_dataset.py +0 -53
- swcgeom-0.15.0/git-conventional-commits.yaml +0 -44
- swcgeom-0.15.0/swcgeom/__init__.py +0 -6
- swcgeom-0.15.0/swcgeom/_version.py +0 -16
- swcgeom-0.15.0/swcgeom/analysis/__init__.py +0 -10
- swcgeom-0.15.0/swcgeom/analysis/branch_features.py +0 -67
- swcgeom-0.15.0/swcgeom/analysis/node_features.py +0 -121
- swcgeom-0.15.0/swcgeom/analysis/path_features.py +0 -37
- swcgeom-0.15.0/swcgeom/core/__init__.py +0 -11
- swcgeom-0.15.0/swcgeom/core/swc_utils/__init__.py +0 -17
- swcgeom-0.15.0/swcgeom/core/swc_utils/assembler.py +0 -26
- swcgeom-0.15.0/swcgeom/images/__init__.py +0 -4
- swcgeom-0.15.0/swcgeom/transforms/__init__.py +0 -12
- swcgeom-0.15.0/swcgeom/transforms/images.py +0 -32
- swcgeom-0.15.0/swcgeom/utils/__init__.py +0 -13
- swcgeom-0.15.0/swcgeom/utils/debug.py +0 -19
- swcgeom-0.15.0/swcgeom/utils/download.py +0 -99
- swcgeom-0.15.0/swcgeom/utils/neuromorpho.py +0 -469
- swcgeom-0.15.0/swcgeom/utils/renderer.py +0 -244
- swcgeom-0.15.0/tests/__init__.py +0 -0
- swcgeom-0.15.0/tests/analysis/test_volume.py +0 -57
- swcgeom-0.15.0/tests/utils/__init__.py +0 -0
- swcgeom-0.15.0/tests/utils/test_dsu.py +0 -32
- swcgeom-0.15.0/tests/utils/test_sdf.py +0 -69
- swcgeom-0.15.0/tests/utils/test_solid_geometry.py +0 -148
- swcgeom-0.15.0/tests/utils/test_transforms.py +0 -33
- swcgeom-0.15.0/tests/utils/test_volumetric_object.py +0 -138
- {swcgeom-0.15.0 → swcgeom-0.18.3}/setup.cfg +0 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom.egg-info/dependency_links.txt +0 -0
- {swcgeom-0.15.0 → swcgeom-0.18.3}/swcgeom.egg-info/top_level.txt +0 -0
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright [
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: swcgeom
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.3
|
|
4
4
|
Summary: Neuron geometry library for swc format
|
|
5
|
-
Author-email: yzx9 <
|
|
5
|
+
Author-email: yzx9 <pypi@yzx9.xyz>
|
|
6
6
|
License: Apache-2.0
|
|
7
7
|
Project-URL: repository, https://github.com/yzx9/swcgeom
|
|
8
8
|
Keywords: neuronscience,neuron,neuroanatomy,neuron-morphology
|
|
@@ -13,20 +13,20 @@ Requires-Dist: imagecodecs>=2023.3.16
|
|
|
13
13
|
Requires-Dist: matplotlib>=3.5.2
|
|
14
14
|
Requires-Dist: numpy>=1.22.3
|
|
15
15
|
Requires-Dist: pandas>=1.4.2
|
|
16
|
-
Requires-Dist: pynrrd>=1.
|
|
16
|
+
Requires-Dist: pynrrd>=1.1.0
|
|
17
17
|
Requires-Dist: scipy>=1.9.1
|
|
18
18
|
Requires-Dist: sdflit>=0.2.1
|
|
19
19
|
Requires-Dist: seaborn>=0.12.0
|
|
20
20
|
Requires-Dist: tifffile>=2022.8.12
|
|
21
21
|
Requires-Dist: typing_extensions>=4.4.0
|
|
22
|
-
Requires-Dist:
|
|
22
|
+
Requires-Dist: tqdm>=4.46.1
|
|
23
|
+
Requires-Dist: v3d-py-helper>=0.4.1
|
|
23
24
|
Provides-Extra: all
|
|
24
25
|
Requires-Dist: beautifulsoup4>=4.11.1; extra == "all"
|
|
25
26
|
Requires-Dist: certifi>=2023.5.7; extra == "all"
|
|
26
27
|
Requires-Dist: chardet>=5.2.0; extra == "all"
|
|
27
28
|
Requires-Dist: lmdb>=1.4.1; extra == "all"
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist: tqdm>=4.46.1; extra == "all"
|
|
29
|
+
Requires-Dist: requests>=2.0.0; extra == "all"
|
|
30
30
|
Requires-Dist: urllib3>=1.26.0; extra == "all"
|
|
31
31
|
|
|
32
32
|
# SWCGEOM
|
|
@@ -56,16 +56,20 @@ pip install build
|
|
|
56
56
|
pip install --editable .
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Static analysis don't support import hook used in editable install for
|
|
59
|
+
Static analysis don't support import hook used in editable install for
|
|
60
|
+
[PEP660](https://peps.python.org/pep-0660/) since upgrade to setuptools v64+,
|
|
61
|
+
detail information at [setuptools#3518](https://github.com/pypa/setuptools/issues/3518),
|
|
62
|
+
a workaround for vscode with pylance:
|
|
60
63
|
|
|
61
64
|
```json
|
|
62
65
|
{
|
|
63
|
-
|
|
66
|
+
"python.analysis.extraPaths": ["/path/to/this/project"]
|
|
64
67
|
}
|
|
65
68
|
```
|
|
66
69
|
|
|
67
70
|
## LICENSE
|
|
68
71
|
|
|
69
|
-
This work is licensed under a
|
|
72
|
+
This work is licensed under a
|
|
73
|
+
<a rel="license" href="https://www.apache.org/licenses/">Apache-2.0</a>.
|
|
70
74
|
|
|
71
75
|
Copyright (c) 2022-present, Zexin Yuan
|
|
@@ -25,16 +25,20 @@ pip install build
|
|
|
25
25
|
pip install --editable .
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Static analysis don't support import hook used in editable install for
|
|
28
|
+
Static analysis don't support import hook used in editable install for
|
|
29
|
+
[PEP660](https://peps.python.org/pep-0660/) since upgrade to setuptools v64+,
|
|
30
|
+
detail information at [setuptools#3518](https://github.com/pypa/setuptools/issues/3518),
|
|
31
|
+
a workaround for vscode with pylance:
|
|
29
32
|
|
|
30
33
|
```json
|
|
31
34
|
{
|
|
32
|
-
|
|
35
|
+
"python.analysis.extraPaths": ["/path/to/this/project"]
|
|
33
36
|
}
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
## LICENSE
|
|
37
40
|
|
|
38
|
-
This work is licensed under a
|
|
41
|
+
This work is licensed under a
|
|
42
|
+
<a rel="license" href="https://www.apache.org/licenses/">Apache-2.0</a>.
|
|
39
43
|
|
|
40
44
|
Copyright (c) 2022-present, Zexin Yuan
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=64", "setuptools_scm[toml]>=6.2"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
1
|
[project]
|
|
6
2
|
name = "swcgeom"
|
|
7
|
-
|
|
3
|
+
version = "0.18.3"
|
|
8
4
|
description = "Neuron geometry library for swc format"
|
|
9
5
|
readme = "README.md"
|
|
10
6
|
requires-python = ">=3.10"
|
|
11
|
-
authors = [{ name = "yzx9", email = "
|
|
7
|
+
authors = [{ name = "yzx9", email = "pypi@yzx9.xyz" }]
|
|
12
8
|
keywords = ["neuronscience", "neuron", "neuroanatomy", "neuron-morphology"]
|
|
13
9
|
license = { text = "Apache-2.0" }
|
|
14
10
|
dependencies = [
|
|
@@ -16,13 +12,14 @@ dependencies = [
|
|
|
16
12
|
"matplotlib>=3.5.2",
|
|
17
13
|
"numpy>=1.22.3",
|
|
18
14
|
"pandas>=1.4.2",
|
|
19
|
-
"pynrrd>=1.
|
|
15
|
+
"pynrrd>=1.1.0",
|
|
20
16
|
"scipy>=1.9.1",
|
|
21
17
|
"sdflit>=0.2.1",
|
|
22
18
|
"seaborn>=0.12.0",
|
|
23
19
|
"tifffile>=2022.8.12",
|
|
24
20
|
"typing_extensions>=4.4.0",
|
|
25
|
-
"
|
|
21
|
+
"tqdm>=4.46.1",
|
|
22
|
+
"v3d-py-helper>=0.4.1",
|
|
26
23
|
]
|
|
27
24
|
|
|
28
25
|
[project.optional-dependencies]
|
|
@@ -31,13 +28,9 @@ all = [
|
|
|
31
28
|
"certifi>=2023.5.7",
|
|
32
29
|
"chardet>=5.2.0",
|
|
33
30
|
"lmdb>=1.4.1",
|
|
34
|
-
"
|
|
35
|
-
"tqdm>=4.46.1",
|
|
31
|
+
"requests>=2.0.0",
|
|
36
32
|
"urllib3>=1.26.0",
|
|
37
33
|
]
|
|
38
34
|
|
|
39
35
|
[project.urls]
|
|
40
36
|
repository = "https://github.com/yzx9/swcgeom"
|
|
41
|
-
|
|
42
|
-
[tool.setuptools_scm]
|
|
43
|
-
write_to = "swcgeom/_version.py"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""A neuron geometry library for swc format."""
|
|
16
|
+
|
|
17
|
+
from swcgeom import analysis, core, images, transforms
|
|
18
|
+
from swcgeom.analysis import draw
|
|
19
|
+
from swcgeom.core import BranchTree, Population, Populations, Tree
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"analysis",
|
|
23
|
+
"core",
|
|
24
|
+
"images",
|
|
25
|
+
"transforms",
|
|
26
|
+
"draw",
|
|
27
|
+
"BranchTree",
|
|
28
|
+
"Population",
|
|
29
|
+
"Populations",
|
|
30
|
+
"Tree",
|
|
31
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
"""Analysis for neuron trees."""
|
|
17
|
+
|
|
18
|
+
from swcgeom.analysis.feature_extractor import * # noqa: F403
|
|
19
|
+
from swcgeom.analysis.features import * # noqa: F403
|
|
20
|
+
from swcgeom.analysis.sholl import * # noqa: F403
|
|
21
|
+
from swcgeom.analysis.trunk import * # noqa: F403
|
|
22
|
+
from swcgeom.analysis.visualization import * # noqa: F403
|
|
23
|
+
from swcgeom.analysis.volume import * # noqa: F403
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
"""Easy way to compute and visualize common features for feature.
|
|
2
17
|
|
|
3
18
|
Notes
|
|
@@ -7,23 +22,24 @@ naming specification.
|
|
|
7
22
|
"""
|
|
8
23
|
|
|
9
24
|
from abc import ABC, abstractmethod
|
|
25
|
+
from collections.abc import Callable
|
|
10
26
|
from functools import cached_property
|
|
11
27
|
from itertools import chain
|
|
12
28
|
from os.path import basename
|
|
13
|
-
from typing import Any,
|
|
29
|
+
from typing import Any, Literal, overload
|
|
14
30
|
|
|
15
31
|
import numpy as np
|
|
16
32
|
import numpy.typing as npt
|
|
17
33
|
import seaborn as sns
|
|
18
34
|
from matplotlib.axes import Axes
|
|
19
35
|
|
|
20
|
-
from swcgeom.analysis.
|
|
21
|
-
|
|
22
|
-
|
|
36
|
+
from swcgeom.analysis.features import (
|
|
37
|
+
BranchFeatures,
|
|
38
|
+
FurcationFeatures,
|
|
23
39
|
NodeFeatures,
|
|
40
|
+
PathFeatures,
|
|
24
41
|
TipFeatures,
|
|
25
42
|
)
|
|
26
|
-
from swcgeom.analysis.path_features import PathFeatures
|
|
27
43
|
from swcgeom.analysis.sholl import Sholl
|
|
28
44
|
from swcgeom.analysis.volume import get_volume
|
|
29
45
|
from swcgeom.core import Population, Populations, Tree
|
|
@@ -39,6 +55,9 @@ Feature = Literal[
|
|
|
39
55
|
"node_count",
|
|
40
56
|
"node_radial_distance",
|
|
41
57
|
"node_branch_order",
|
|
58
|
+
# furcation nodes
|
|
59
|
+
"furcation_count",
|
|
60
|
+
"furcation_radial_distance",
|
|
42
61
|
# bifurcation nodes
|
|
43
62
|
"bifurcation_count",
|
|
44
63
|
"bifurcation_radial_distance",
|
|
@@ -54,9 +73,9 @@ Feature = Literal[
|
|
|
54
73
|
]
|
|
55
74
|
|
|
56
75
|
NDArrayf32 = npt.NDArray[np.float32]
|
|
57
|
-
FeatAndKwargs = Feature |
|
|
76
|
+
FeatAndKwargs = Feature | tuple[Feature, dict[str, Any]]
|
|
58
77
|
|
|
59
|
-
Feature1D = set(["length", "volume", "node_count", "
|
|
78
|
+
Feature1D = set(["length", "volume", "node_count", "furcation_count", "tip_count"])
|
|
60
79
|
|
|
61
80
|
|
|
62
81
|
class Features:
|
|
@@ -69,7 +88,7 @@ class Features:
|
|
|
69
88
|
@cached_property
|
|
70
89
|
def node_features(self) -> NodeFeatures: return NodeFeatures(self.tree)
|
|
71
90
|
@cached_property
|
|
72
|
-
def
|
|
91
|
+
def furcation_features(self) -> FurcationFeatures: return FurcationFeatures(self.node_features)
|
|
73
92
|
@cached_property
|
|
74
93
|
def tip_features(self) -> TipFeatures: return TipFeatures(self.node_features)
|
|
75
94
|
@cached_property
|
|
@@ -121,9 +140,9 @@ class FeatureExtractor(ABC):
|
|
|
121
140
|
@overload
|
|
122
141
|
def get(self, feature: Feature, **kwargs) -> NDArrayf32: ...
|
|
123
142
|
@overload
|
|
124
|
-
def get(self, feature:
|
|
143
|
+
def get(self, feature: list[FeatAndKwargs]) -> list[NDArrayf32]: ...
|
|
125
144
|
@overload
|
|
126
|
-
def get(self, feature:
|
|
145
|
+
def get(self, feature: dict[Feature, dict[str, Any]]) -> dict[str, NDArrayf32]: ...
|
|
127
146
|
# fmt:on
|
|
128
147
|
def get(self, feature, **kwargs):
|
|
129
148
|
"""Get feature.
|
|
@@ -168,7 +187,7 @@ class FeatureExtractor(ABC):
|
|
|
168
187
|
|
|
169
188
|
# Custom Plots
|
|
170
189
|
|
|
171
|
-
def plot_node_branch_order(self, feature_kwargs:
|
|
190
|
+
def plot_node_branch_order(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
|
|
172
191
|
vals = self._get("node_branch_order", **feature_kwargs)
|
|
173
192
|
bin_edges = np.arange(int(np.ceil(vals.max() + 1))) + 0.5
|
|
174
193
|
return self._plot_histogram_impl(vals, bin_edges, **kwargs)
|
|
@@ -213,6 +232,12 @@ class FeatureExtractor(ABC):
|
|
|
213
232
|
) -> Axes:
|
|
214
233
|
raise NotImplementedError()
|
|
215
234
|
|
|
235
|
+
def get_bifurcation_count(self, **kwargs):
|
|
236
|
+
raise DeprecationWarning("Use `furcation_count` instead.")
|
|
237
|
+
|
|
238
|
+
def get_bifurcation_radial_distance(self, **kwargs):
|
|
239
|
+
raise DeprecationWarning("Use `furcation_radial_distance` instead.")
|
|
240
|
+
|
|
216
241
|
|
|
217
242
|
class TreeFeatureExtractor(FeatureExtractor):
|
|
218
243
|
"""Extract feature from tree."""
|
|
@@ -234,7 +259,7 @@ class TreeFeatureExtractor(FeatureExtractor):
|
|
|
234
259
|
|
|
235
260
|
def plot_sholl(
|
|
236
261
|
self,
|
|
237
|
-
feature_kwargs:
|
|
262
|
+
feature_kwargs: dict[str, Any], # pylint: disable=unused-argument
|
|
238
263
|
**kwargs,
|
|
239
264
|
) -> Axes:
|
|
240
265
|
_, ax = self._features.sholl.plot(**kwargs)
|
|
@@ -264,7 +289,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
|
|
|
264
289
|
"""Extract features from population."""
|
|
265
290
|
|
|
266
291
|
_population: Population
|
|
267
|
-
_features:
|
|
292
|
+
_features: list[Features]
|
|
268
293
|
|
|
269
294
|
def __init__(self, population: Population) -> None:
|
|
270
295
|
super().__init__()
|
|
@@ -279,7 +304,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
|
|
|
279
304
|
|
|
280
305
|
# Custom Plots
|
|
281
306
|
|
|
282
|
-
def plot_sholl(self, feature_kwargs:
|
|
307
|
+
def plot_sholl(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
|
|
283
308
|
vals, rs = self._get_sholl_impl(**feature_kwargs)
|
|
284
309
|
ax = self._lineplot(xs=rs, ys=vals.flatten(), **kwargs)
|
|
285
310
|
ax.set_ylabel("Count of Intersections")
|
|
@@ -295,7 +320,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
|
|
|
295
320
|
|
|
296
321
|
def _get_sholl_impl(
|
|
297
322
|
self, steps: int = 20, **kwargs
|
|
298
|
-
) ->
|
|
323
|
+
) -> tuple[NDArrayf32, NDArrayf32]:
|
|
299
324
|
rmax = max(t.sholl.rmax for t in self._features)
|
|
300
325
|
rs = Sholl.get_rs(rmax=rmax, steps=steps)
|
|
301
326
|
vals = self._get_impl("sholl", steps=rs, **kwargs)
|
|
@@ -333,7 +358,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
|
|
|
333
358
|
"""Extract feature from population."""
|
|
334
359
|
|
|
335
360
|
_populations: Populations
|
|
336
|
-
_features:
|
|
361
|
+
_features: list[list[Features]]
|
|
337
362
|
|
|
338
363
|
def __init__(self, populations: Populations) -> None:
|
|
339
364
|
super().__init__()
|
|
@@ -348,7 +373,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
|
|
|
348
373
|
|
|
349
374
|
# Custom Plots
|
|
350
375
|
|
|
351
|
-
def plot_sholl(self, feature_kwargs:
|
|
376
|
+
def plot_sholl(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
|
|
352
377
|
vals, rs = self._get_sholl_impl(**feature_kwargs)
|
|
353
378
|
ax = self._lineplot(xs=rs, ys=vals, **kwargs)
|
|
354
379
|
ax.set_ylabel("Count of Intersections")
|
|
@@ -369,7 +394,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
|
|
|
369
394
|
|
|
370
395
|
def _get_sholl_impl(
|
|
371
396
|
self, steps: int = 20, **kwargs
|
|
372
|
-
) ->
|
|
397
|
+
) -> tuple[NDArrayf32, NDArrayf32]:
|
|
373
398
|
rmaxs = chain.from_iterable((t.sholl.rmax for t in p) for p in self._features)
|
|
374
399
|
rmax = max(rmaxs)
|
|
375
400
|
rs = Sholl.get_rs(rmax=rmax, steps=steps)
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Copyright 2022-2025 Zexin Yuan
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
"""Feature anlysis of tree."""
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from functools import cached_property
|
|
20
|
+
from typing import TypeVar
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
import numpy.typing as npt
|
|
24
|
+
from typing_extensions import Self, deprecated
|
|
25
|
+
|
|
26
|
+
from swcgeom.core import Branch, BranchTree, Tree
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"NodeFeatures",
|
|
30
|
+
"BifurcationFeatures",
|
|
31
|
+
"TipFeatures",
|
|
32
|
+
"PathFeatures",
|
|
33
|
+
"BranchFeatures",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
T = TypeVar("T", bound=Branch)
|
|
37
|
+
|
|
38
|
+
# Node Level
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class NodeFeatures:
|
|
42
|
+
"""Evaluate node feature of tree."""
|
|
43
|
+
|
|
44
|
+
tree: Tree
|
|
45
|
+
|
|
46
|
+
@cached_property
|
|
47
|
+
def _branch_tree(self) -> BranchTree:
|
|
48
|
+
return BranchTree.from_tree(self.tree)
|
|
49
|
+
|
|
50
|
+
def __init__(self, tree: Tree) -> None:
|
|
51
|
+
self.tree = tree
|
|
52
|
+
|
|
53
|
+
def get_count(self) -> npt.NDArray[np.float32]:
|
|
54
|
+
"""Get number of nodes.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
count : array of shape (1,)
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
return np.array([self.tree.number_of_nodes()], dtype=np.float32)
|
|
62
|
+
|
|
63
|
+
def get_radial_distance(self) -> npt.NDArray[np.float32]:
|
|
64
|
+
"""Get the end-to-end straight-line distance to soma.
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
radial_distance : npt.NDArray[np.float32]
|
|
69
|
+
Array of shape (N,).
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
xyz = self.tree.xyz() - self.tree.soma().xyz()
|
|
73
|
+
radial_distance = np.linalg.norm(xyz, axis=1)
|
|
74
|
+
return radial_distance
|
|
75
|
+
|
|
76
|
+
def get_branch_order(self) -> npt.NDArray[np.int32]:
|
|
77
|
+
"""Get branch order of criticle nodes of tree.
|
|
78
|
+
|
|
79
|
+
Branch order is the number of bifurcations between current
|
|
80
|
+
position and the root.
|
|
81
|
+
|
|
82
|
+
Criticle node means that soma, bifucation nodes, tips.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
order : npt.NDArray[np.int32]
|
|
87
|
+
Array of shape (N,), which k is the number of branchs.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
order = np.zeros_like(self._branch_tree.id(), dtype=np.int32)
|
|
91
|
+
|
|
92
|
+
def assign_depth(n: Tree.Node, pre_depth: int | None) -> int:
|
|
93
|
+
cur_order = pre_depth + 1 if pre_depth is not None else 0
|
|
94
|
+
order[n.id] = cur_order
|
|
95
|
+
return cur_order
|
|
96
|
+
|
|
97
|
+
self._branch_tree.traverse(enter=assign_depth)
|
|
98
|
+
return order
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class _SubsetNodesFeatures(ABC):
|
|
102
|
+
_features: NodeFeatures
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def nodes(self) -> npt.NDArray[np.bool_]:
|
|
107
|
+
raise NotImplementedError()
|
|
108
|
+
|
|
109
|
+
def __init__(self, features: NodeFeatures) -> None:
|
|
110
|
+
self._features = features
|
|
111
|
+
|
|
112
|
+
def get_count(self) -> npt.NDArray[np.float32]:
|
|
113
|
+
"""Get number of nodes.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
count : npt.NDArray[np.float32]
|
|
118
|
+
Array of shape (1,).
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
return np.array([np.count_nonzero(self.nodes)], dtype=np.float32)
|
|
122
|
+
|
|
123
|
+
def get_radial_distance(self) -> npt.NDArray[np.float32]:
|
|
124
|
+
"""Get the end-to-end straight-line distance to soma.
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
radial_distance : npt.NDArray[np.float32]
|
|
129
|
+
Array of shape (N,).
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
return self._features.get_radial_distance()[self.nodes]
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def from_tree(cls, tree: Tree) -> Self:
|
|
136
|
+
return cls(NodeFeatures(tree))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class FurcationFeatures(_SubsetNodesFeatures):
|
|
140
|
+
"""Evaluate furcation node feature of tree."""
|
|
141
|
+
|
|
142
|
+
@cached_property
|
|
143
|
+
def nodes(self) -> npt.NDArray[np.bool_]:
|
|
144
|
+
return np.array([n.is_furcation() for n in self._features.tree])
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@deprecated("Use FurcationFeatures instead")
|
|
148
|
+
class BifurcationFeatures(FurcationFeatures):
|
|
149
|
+
"""Evaluate bifurcation node feature of tree.
|
|
150
|
+
|
|
151
|
+
Notes
|
|
152
|
+
-----
|
|
153
|
+
Deprecated due to the wrong spelling of furcation. For now, it
|
|
154
|
+
is just an alias of `FurcationFeatures` and raise a warning. It
|
|
155
|
+
will be change to raise an error in the future.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class TipFeatures(_SubsetNodesFeatures):
|
|
160
|
+
"""Evaluate tip node feature of tree."""
|
|
161
|
+
|
|
162
|
+
@cached_property
|
|
163
|
+
def nodes(self) -> npt.NDArray[np.bool_]:
|
|
164
|
+
return np.array([n.is_tip() for n in self._features.tree])
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Path Level
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class PathFeatures:
|
|
171
|
+
"""Path analysis 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._paths)
|
|
180
|
+
|
|
181
|
+
def get_length(self) -> npt.NDArray[np.float32]:
|
|
182
|
+
"""Get length of paths."""
|
|
183
|
+
|
|
184
|
+
length = [path.length() for path in self._paths]
|
|
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([path.tortuosity() for path in self._paths], dtype=np.float32)
|
|
191
|
+
|
|
192
|
+
@cached_property
|
|
193
|
+
def _paths(self) -> list[Tree.Path]:
|
|
194
|
+
return self.tree.get_paths()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class BranchFeatures:
|
|
198
|
+
"""Analysis bransh of tree."""
|
|
199
|
+
|
|
200
|
+
tree: Tree
|
|
201
|
+
|
|
202
|
+
def __init__(self, tree: Tree) -> None:
|
|
203
|
+
self.tree = tree
|
|
204
|
+
|
|
205
|
+
def get_count(self) -> int:
|
|
206
|
+
return len(self._branches)
|
|
207
|
+
|
|
208
|
+
def get_length(self) -> npt.NDArray[np.float32]:
|
|
209
|
+
"""Get length of branches."""
|
|
210
|
+
|
|
211
|
+
length = [br.length() for br in self._branches]
|
|
212
|
+
return np.array(length, dtype=np.float32)
|
|
213
|
+
|
|
214
|
+
def get_tortuosity(self) -> npt.NDArray[np.float32]:
|
|
215
|
+
"""Get tortuosity of path."""
|
|
216
|
+
|
|
217
|
+
return np.array([br.tortuosity() for br in self._branches], dtype=np.float32)
|
|
218
|
+
|
|
219
|
+
def get_angle(self, eps: float = 1e-7) -> npt.NDArray[np.float32]:
|
|
220
|
+
"""Get agnle between branches.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
angle : npt.NDArray[np.float32]
|
|
225
|
+
An array of shape (N, N), which N is length of branches.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
return self.calc_angle(self._branches, eps=eps)
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def calc_angle(branches: list[T], eps: float = 1e-7) -> npt.NDArray[np.float32]:
|
|
232
|
+
"""Calc agnle between branches.
|
|
233
|
+
|
|
234
|
+
Returns
|
|
235
|
+
-------
|
|
236
|
+
angle : npt.NDArray[np.float32]
|
|
237
|
+
An array of shape (N, N), which N is length of branches.
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
vector = np.array([br[-1].xyz() - br[0].xyz() for br in branches])
|
|
241
|
+
vector_dot = np.matmul(vector, vector.T)
|
|
242
|
+
vector_norm = np.linalg.norm(vector, ord=2, axis=1, keepdims=True)
|
|
243
|
+
vector_norm_dot = np.matmul(vector_norm, vector_norm.T) + eps
|
|
244
|
+
arccos = np.clip(vector_dot / vector_norm_dot, -1, 1)
|
|
245
|
+
angle = np.arccos(arccos)
|
|
246
|
+
return angle
|
|
247
|
+
|
|
248
|
+
@cached_property
|
|
249
|
+
def _branches(self) -> list[Tree.Branch]:
|
|
250
|
+
return self.tree.get_branches()
|