morphoview 0.1.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.
- morphoview-0.1.0/LICENSE +21 -0
- morphoview-0.1.0/PKG-INFO +209 -0
- morphoview-0.1.0/README.md +175 -0
- morphoview-0.1.0/pyproject.toml +48 -0
- morphoview-0.1.0/setup.cfg +4 -0
- morphoview-0.1.0/src/morphoview/__init__.py +93 -0
- morphoview-0.1.0/src/morphoview/backends/__init__.py +9 -0
- morphoview-0.1.0/src/morphoview/backends/movie.py +101 -0
- morphoview-0.1.0/src/morphoview/backends/mpl.py +121 -0
- morphoview-0.1.0/src/morphoview/backends/vispy.py +41 -0
- morphoview-0.1.0/src/morphoview/backends/vtk.py +282 -0
- morphoview-0.1.0/src/morphoview/cli.py +289 -0
- morphoview-0.1.0/src/morphoview/colors.py +249 -0
- morphoview-0.1.0/src/morphoview/graph.py +187 -0
- morphoview-0.1.0/src/morphoview/py.typed +0 -0
- morphoview-0.1.0/src/morphoview/swc.py +56 -0
- morphoview-0.1.0/src/morphoview/transform.py +93 -0
- morphoview-0.1.0/src/morphoview.egg-info/PKG-INFO +209 -0
- morphoview-0.1.0/src/morphoview.egg-info/SOURCES.txt +25 -0
- morphoview-0.1.0/src/morphoview.egg-info/dependency_links.txt +1 -0
- morphoview-0.1.0/src/morphoview.egg-info/entry_points.txt +2 -0
- morphoview-0.1.0/src/morphoview.egg-info/requires.txt +21 -0
- morphoview-0.1.0/src/morphoview.egg-info/top_level.txt +1 -0
- morphoview-0.1.0/tests/test_colors.py +41 -0
- morphoview-0.1.0/tests/test_graph.py +84 -0
- morphoview-0.1.0/tests/test_mpl.py +32 -0
- morphoview-0.1.0/tests/test_swc.py +37 -0
morphoview-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Subhasis Ray
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: morphoview
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Read, analyze and visualize neuronal morphologies in SWC format.
|
|
5
|
+
Author: Subhasis Ray
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/subhacom/morphoview
|
|
8
|
+
Keywords: neuroscience,morphology,SWC,visualization,neuron
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy>=1.20
|
|
18
|
+
Requires-Dist: networkx>=3.0
|
|
19
|
+
Provides-Extra: mpl
|
|
20
|
+
Requires-Dist: matplotlib>=3.4; extra == "mpl"
|
|
21
|
+
Provides-Extra: vtk
|
|
22
|
+
Requires-Dist: vtk>=9.0; extra == "vtk"
|
|
23
|
+
Provides-Extra: vispy
|
|
24
|
+
Requires-Dist: vispy>=0.11; extra == "vispy"
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: matplotlib>=3.4; extra == "all"
|
|
27
|
+
Requires-Dist: vtk>=9.0; extra == "all"
|
|
28
|
+
Requires-Dist: vispy>=0.11; extra == "all"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
31
|
+
Requires-Dist: matplotlib>=3.4; extra == "dev"
|
|
32
|
+
Requires-Dist: vtk>=9.0; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# morphoview
|
|
36
|
+
|
|
37
|
+
Read, analyze and visualize neuronal morphologies stored in
|
|
38
|
+
[SWC](http://www.neuronland.org/NLMorphologyConverter/MorphologyFormats/SWC/Spec.html)
|
|
39
|
+
format.
|
|
40
|
+
|
|
41
|
+
`morphoview` loads an SWC trace into a `networkx` graph, computes
|
|
42
|
+
morphology statistics (branch points, leaves, path lengths, total
|
|
43
|
+
neurite length), and renders the cell in 2-D or 3-D using any of three
|
|
44
|
+
pluggable backends:
|
|
45
|
+
|
|
46
|
+
| Backend | Strengths | Extra dependency |
|
|
47
|
+
|--------------|----------------------------------------------|------------------|
|
|
48
|
+
| `mpl` | Portable, figure-quality 2-D/3-D line plots | `matplotlib` |
|
|
49
|
+
| `vtk` | Tapered tubes, interactive, PNG/movie export | `vtk` |
|
|
50
|
+
| `vispy` | GPU-accelerated interactive tube view | `vispy` |
|
|
51
|
+
|
|
52
|
+
The core I/O and analysis need only `numpy` and `networkx`; each
|
|
53
|
+
rendering backend is optional and imported lazily.
|
|
54
|
+
|
|
55
|
+
This is a refactored, Python-3-native successor to the `morphutils`
|
|
56
|
+
scripts (`neurograph.py`, `displaycell.py`, `morph3d*.py`, `cellmovie.py`).
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install -e . # core only (numpy + networkx)
|
|
62
|
+
pip install -e ".[mpl]" # + matplotlib backend
|
|
63
|
+
pip install -e ".[vtk]" # + VTK backend
|
|
64
|
+
pip install -e ".[all]" # everything
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Command line
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Print morphology statistics
|
|
71
|
+
morphoview info examples/sample.swc
|
|
72
|
+
|
|
73
|
+
# Interactive 3-D display (VTK, tapered tubes, coloured by structure)
|
|
74
|
+
morphoview display examples/sample.swc
|
|
75
|
+
|
|
76
|
+
# Matplotlib 2-D projection onto the XY plane
|
|
77
|
+
morphoview display examples/sample.swc -b mpl --proj xy -c 4cp
|
|
78
|
+
|
|
79
|
+
# Several cells at once, positioned independently
|
|
80
|
+
morphoview display cellA.swc cellB.swc -t "0 0 0" -t "200 0 0" -r "z 90"
|
|
81
|
+
|
|
82
|
+
# Save a snapshot without opening a window (headless)
|
|
83
|
+
morphoview display examples/sample.swc --save cell.png --offscreen
|
|
84
|
+
|
|
85
|
+
# Render a rotating movie sweeping 360 degrees about Y
|
|
86
|
+
morphoview movie -i examples/sample.swc -o cell.avi -y 360
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Useful `display` options:
|
|
90
|
+
|
|
91
|
+
- `-b/--backend {vtk,mpl,vispy}` — pick the renderer (default `vtk`).
|
|
92
|
+
- `-l/--lines W` — draw fixed-width lines of width `W` instead of tubes.
|
|
93
|
+
- `-c/--colormap NAME` — structure colour palette (see below).
|
|
94
|
+
- `-t/--translate "X Y Z"`, `-r/--rotate "x 90 y 45"`, `-m/--mirror x` —
|
|
95
|
+
per-file rigid transforms (repeat the flag once per input file).
|
|
96
|
+
- `--branches`, `--leaves`, `-s/--struct-id ID...` — label nodes.
|
|
97
|
+
- `-a/--scalebar`, `-F/--fullscreen`, `--save FILE.png`, `--offscreen`.
|
|
98
|
+
|
|
99
|
+
## Python API
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
import morphoview as nv
|
|
103
|
+
|
|
104
|
+
graph = nv.swc_to_graph("examples/sample.swc")
|
|
105
|
+
print(nv.summary(graph))
|
|
106
|
+
# {'nodes': 19, 'edges': 18, 'branch_points': 3, 'branches': 8,
|
|
107
|
+
# 'leaves': 5, 'total_length': 57.76...}
|
|
108
|
+
|
|
109
|
+
# Per-structure node lists (soma / axon / dendrites / custom regions)
|
|
110
|
+
by_type = nv.structure_node_map(graph)
|
|
111
|
+
|
|
112
|
+
# Path length from the soma to every node
|
|
113
|
+
distances = nv.soma_distance(graph)
|
|
114
|
+
|
|
115
|
+
# Render with matplotlib
|
|
116
|
+
from morphoview.backends import mpl
|
|
117
|
+
ax = mpl.plot_3d(graph, color=nv.get_colormap("3cd2"))
|
|
118
|
+
mpl.show()
|
|
119
|
+
|
|
120
|
+
# Render with VTK (tapered tubes)
|
|
121
|
+
from morphoview.backends import vtk
|
|
122
|
+
vtk.show(graph, colormap=nv.get_colormap("3cd2"), axes=True)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Transformations
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from morphoview import transform as tf
|
|
129
|
+
|
|
130
|
+
tf.rotate(graph, thetaz=90) # degrees, about the origin
|
|
131
|
+
tf.translate(graph, 100, 0, 0) # micrometres
|
|
132
|
+
tf.mirror(graph, "x") # reflect across the YZ plane
|
|
133
|
+
|
|
134
|
+
combined = nv.combine(graph_a, graph_b) # merge, renumbering node ids
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Structure ids and colour palettes
|
|
138
|
+
|
|
139
|
+
SWC tags each point with a structure id. The standard ids are
|
|
140
|
+
`1=soma`, `2=axon`, `3=basal dendrite`, `4=apical dendrite`; ids `>= 5`
|
|
141
|
+
are custom (`morphoview` also names the GGN regions `5=LCA`, `6=MCA`,
|
|
142
|
+
`7=LH`, `8=alphaL`).
|
|
143
|
+
|
|
144
|
+
Palettes are keyed by short names combining a class count and a scheme,
|
|
145
|
+
mostly from [colorbrewer2.org](https://colorbrewer2.org) and the
|
|
146
|
+
[SRON](https://personal.sron.nl/~pault/) colour-blind-safe sets:
|
|
147
|
+
`3cd2` (default), `3cs2`, `3cp`, `4cp`, `5cd2`, `5cs3`, `7q`, `7cp`,
|
|
148
|
+
`7ca`, `7cd2`, `9q`, `10cp`, `10cs3`, `15cb`. Structure ids beyond a
|
|
149
|
+
palette's length wrap around so every morphology still renders.
|
|
150
|
+
|
|
151
|
+
## Project layout
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
src/morphoview/
|
|
155
|
+
swc.py SWC file I/O (numpy structured arrays)
|
|
156
|
+
graph.py networkx graph construction + morphology analysis
|
|
157
|
+
colors.py structure-id names and colour palettes
|
|
158
|
+
transform.py rigid transforms (translate/rotate/mirror)
|
|
159
|
+
cli.py the `morphoview` command-line entry point
|
|
160
|
+
backends/
|
|
161
|
+
mpl.py matplotlib 2-D/3-D
|
|
162
|
+
vtk.py VTK tubes/lines, labels, PNG export
|
|
163
|
+
vispy.py vispy tube view
|
|
164
|
+
movie.py VTK rotating-movie export
|
|
165
|
+
tests/ pytest suite (core + matplotlib backend)
|
|
166
|
+
examples/sample.swc small synthetic neuron for demos/tests
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
pip install -e ".[dev]"
|
|
173
|
+
pytest
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Releasing to PyPI
|
|
177
|
+
|
|
178
|
+
Publishing is automated by
|
|
179
|
+
[`.github/workflows/publish.yml`](.github/workflows/publish.yml). It builds
|
|
180
|
+
the sdist and wheel on every push/PR as a smoke test, and uploads them to
|
|
181
|
+
PyPI when a GitHub Release is published, using
|
|
182
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC) — so
|
|
183
|
+
there are no API tokens to store as secrets.
|
|
184
|
+
|
|
185
|
+
One-time setup on PyPI: at
|
|
186
|
+
<https://pypi.org/manage/account/publishing/> add a *pending publisher* for
|
|
187
|
+
this project with
|
|
188
|
+
|
|
189
|
+
- PyPI project name: `morphoview`
|
|
190
|
+
- Owner: `subhacom`
|
|
191
|
+
- Repository: `morphoview`
|
|
192
|
+
- Workflow: `publish.yml`
|
|
193
|
+
- Environment: `pypi`
|
|
194
|
+
|
|
195
|
+
To cut a release:
|
|
196
|
+
|
|
197
|
+
1. Bump `version` in `pyproject.toml` (and commit).
|
|
198
|
+
2. Create a GitHub Release with a tag matching that version, e.g. `v0.1.0`
|
|
199
|
+
(the leading `v` is optional). The workflow verifies the tag matches
|
|
200
|
+
`pyproject.toml` and fails fast on a mismatch.
|
|
201
|
+
3. Publishing runs automatically once the release is published.
|
|
202
|
+
|
|
203
|
+
## History
|
|
204
|
+
It was refactored and updated by Claude Opus 4.8 from the morphology utilities developed for the GGN model published in Ray S, Aldworth ZN, Stopfer MA. Feedback inhibition and its control in an insect olfactory circuit. Scott K, editor. eLife. 2020 Mar 12;9:e53281. doi:10.7554/eLife.53281.
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# morphoview
|
|
2
|
+
|
|
3
|
+
Read, analyze and visualize neuronal morphologies stored in
|
|
4
|
+
[SWC](http://www.neuronland.org/NLMorphologyConverter/MorphologyFormats/SWC/Spec.html)
|
|
5
|
+
format.
|
|
6
|
+
|
|
7
|
+
`morphoview` loads an SWC trace into a `networkx` graph, computes
|
|
8
|
+
morphology statistics (branch points, leaves, path lengths, total
|
|
9
|
+
neurite length), and renders the cell in 2-D or 3-D using any of three
|
|
10
|
+
pluggable backends:
|
|
11
|
+
|
|
12
|
+
| Backend | Strengths | Extra dependency |
|
|
13
|
+
|--------------|----------------------------------------------|------------------|
|
|
14
|
+
| `mpl` | Portable, figure-quality 2-D/3-D line plots | `matplotlib` |
|
|
15
|
+
| `vtk` | Tapered tubes, interactive, PNG/movie export | `vtk` |
|
|
16
|
+
| `vispy` | GPU-accelerated interactive tube view | `vispy` |
|
|
17
|
+
|
|
18
|
+
The core I/O and analysis need only `numpy` and `networkx`; each
|
|
19
|
+
rendering backend is optional and imported lazily.
|
|
20
|
+
|
|
21
|
+
This is a refactored, Python-3-native successor to the `morphutils`
|
|
22
|
+
scripts (`neurograph.py`, `displaycell.py`, `morph3d*.py`, `cellmovie.py`).
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install -e . # core only (numpy + networkx)
|
|
28
|
+
pip install -e ".[mpl]" # + matplotlib backend
|
|
29
|
+
pip install -e ".[vtk]" # + VTK backend
|
|
30
|
+
pip install -e ".[all]" # everything
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Command line
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Print morphology statistics
|
|
37
|
+
morphoview info examples/sample.swc
|
|
38
|
+
|
|
39
|
+
# Interactive 3-D display (VTK, tapered tubes, coloured by structure)
|
|
40
|
+
morphoview display examples/sample.swc
|
|
41
|
+
|
|
42
|
+
# Matplotlib 2-D projection onto the XY plane
|
|
43
|
+
morphoview display examples/sample.swc -b mpl --proj xy -c 4cp
|
|
44
|
+
|
|
45
|
+
# Several cells at once, positioned independently
|
|
46
|
+
morphoview display cellA.swc cellB.swc -t "0 0 0" -t "200 0 0" -r "z 90"
|
|
47
|
+
|
|
48
|
+
# Save a snapshot without opening a window (headless)
|
|
49
|
+
morphoview display examples/sample.swc --save cell.png --offscreen
|
|
50
|
+
|
|
51
|
+
# Render a rotating movie sweeping 360 degrees about Y
|
|
52
|
+
morphoview movie -i examples/sample.swc -o cell.avi -y 360
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Useful `display` options:
|
|
56
|
+
|
|
57
|
+
- `-b/--backend {vtk,mpl,vispy}` — pick the renderer (default `vtk`).
|
|
58
|
+
- `-l/--lines W` — draw fixed-width lines of width `W` instead of tubes.
|
|
59
|
+
- `-c/--colormap NAME` — structure colour palette (see below).
|
|
60
|
+
- `-t/--translate "X Y Z"`, `-r/--rotate "x 90 y 45"`, `-m/--mirror x` —
|
|
61
|
+
per-file rigid transforms (repeat the flag once per input file).
|
|
62
|
+
- `--branches`, `--leaves`, `-s/--struct-id ID...` — label nodes.
|
|
63
|
+
- `-a/--scalebar`, `-F/--fullscreen`, `--save FILE.png`, `--offscreen`.
|
|
64
|
+
|
|
65
|
+
## Python API
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import morphoview as nv
|
|
69
|
+
|
|
70
|
+
graph = nv.swc_to_graph("examples/sample.swc")
|
|
71
|
+
print(nv.summary(graph))
|
|
72
|
+
# {'nodes': 19, 'edges': 18, 'branch_points': 3, 'branches': 8,
|
|
73
|
+
# 'leaves': 5, 'total_length': 57.76...}
|
|
74
|
+
|
|
75
|
+
# Per-structure node lists (soma / axon / dendrites / custom regions)
|
|
76
|
+
by_type = nv.structure_node_map(graph)
|
|
77
|
+
|
|
78
|
+
# Path length from the soma to every node
|
|
79
|
+
distances = nv.soma_distance(graph)
|
|
80
|
+
|
|
81
|
+
# Render with matplotlib
|
|
82
|
+
from morphoview.backends import mpl
|
|
83
|
+
ax = mpl.plot_3d(graph, color=nv.get_colormap("3cd2"))
|
|
84
|
+
mpl.show()
|
|
85
|
+
|
|
86
|
+
# Render with VTK (tapered tubes)
|
|
87
|
+
from morphoview.backends import vtk
|
|
88
|
+
vtk.show(graph, colormap=nv.get_colormap("3cd2"), axes=True)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Transformations
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from morphoview import transform as tf
|
|
95
|
+
|
|
96
|
+
tf.rotate(graph, thetaz=90) # degrees, about the origin
|
|
97
|
+
tf.translate(graph, 100, 0, 0) # micrometres
|
|
98
|
+
tf.mirror(graph, "x") # reflect across the YZ plane
|
|
99
|
+
|
|
100
|
+
combined = nv.combine(graph_a, graph_b) # merge, renumbering node ids
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Structure ids and colour palettes
|
|
104
|
+
|
|
105
|
+
SWC tags each point with a structure id. The standard ids are
|
|
106
|
+
`1=soma`, `2=axon`, `3=basal dendrite`, `4=apical dendrite`; ids `>= 5`
|
|
107
|
+
are custom (`morphoview` also names the GGN regions `5=LCA`, `6=MCA`,
|
|
108
|
+
`7=LH`, `8=alphaL`).
|
|
109
|
+
|
|
110
|
+
Palettes are keyed by short names combining a class count and a scheme,
|
|
111
|
+
mostly from [colorbrewer2.org](https://colorbrewer2.org) and the
|
|
112
|
+
[SRON](https://personal.sron.nl/~pault/) colour-blind-safe sets:
|
|
113
|
+
`3cd2` (default), `3cs2`, `3cp`, `4cp`, `5cd2`, `5cs3`, `7q`, `7cp`,
|
|
114
|
+
`7ca`, `7cd2`, `9q`, `10cp`, `10cs3`, `15cb`. Structure ids beyond a
|
|
115
|
+
palette's length wrap around so every morphology still renders.
|
|
116
|
+
|
|
117
|
+
## Project layout
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
src/morphoview/
|
|
121
|
+
swc.py SWC file I/O (numpy structured arrays)
|
|
122
|
+
graph.py networkx graph construction + morphology analysis
|
|
123
|
+
colors.py structure-id names and colour palettes
|
|
124
|
+
transform.py rigid transforms (translate/rotate/mirror)
|
|
125
|
+
cli.py the `morphoview` command-line entry point
|
|
126
|
+
backends/
|
|
127
|
+
mpl.py matplotlib 2-D/3-D
|
|
128
|
+
vtk.py VTK tubes/lines, labels, PNG export
|
|
129
|
+
vispy.py vispy tube view
|
|
130
|
+
movie.py VTK rotating-movie export
|
|
131
|
+
tests/ pytest suite (core + matplotlib backend)
|
|
132
|
+
examples/sample.swc small synthetic neuron for demos/tests
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pip install -e ".[dev]"
|
|
139
|
+
pytest
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Releasing to PyPI
|
|
143
|
+
|
|
144
|
+
Publishing is automated by
|
|
145
|
+
[`.github/workflows/publish.yml`](.github/workflows/publish.yml). It builds
|
|
146
|
+
the sdist and wheel on every push/PR as a smoke test, and uploads them to
|
|
147
|
+
PyPI when a GitHub Release is published, using
|
|
148
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC) — so
|
|
149
|
+
there are no API tokens to store as secrets.
|
|
150
|
+
|
|
151
|
+
One-time setup on PyPI: at
|
|
152
|
+
<https://pypi.org/manage/account/publishing/> add a *pending publisher* for
|
|
153
|
+
this project with
|
|
154
|
+
|
|
155
|
+
- PyPI project name: `morphoview`
|
|
156
|
+
- Owner: `subhacom`
|
|
157
|
+
- Repository: `morphoview`
|
|
158
|
+
- Workflow: `publish.yml`
|
|
159
|
+
- Environment: `pypi`
|
|
160
|
+
|
|
161
|
+
To cut a release:
|
|
162
|
+
|
|
163
|
+
1. Bump `version` in `pyproject.toml` (and commit).
|
|
164
|
+
2. Create a GitHub Release with a tag matching that version, e.g. `v0.1.0`
|
|
165
|
+
(the leading `v` is optional). The workflow verifies the tag matches
|
|
166
|
+
`pyproject.toml` and fails fast on a mismatch.
|
|
167
|
+
3. Publishing runs automatically once the release is published.
|
|
168
|
+
|
|
169
|
+
## History
|
|
170
|
+
It was refactored and updated by Claude Opus 4.8 from the morphology utilities developed for the GGN model published in Ray S, Aldworth ZN, Stopfer MA. Feedback inhibition and its control in an insect olfactory circuit. Scott K, editor. eLife. 2020 Mar 12;9:e53281. doi:10.7554/eLife.53281.
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "morphoview"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Read, analyze and visualize neuronal morphologies in SWC format."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Subhasis Ray" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["neuroscience", "morphology", "SWC", "visualization", "neuron"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"numpy>=1.20",
|
|
25
|
+
"networkx>=3.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
mpl = ["matplotlib>=3.4"]
|
|
30
|
+
vtk = ["vtk>=9.0"]
|
|
31
|
+
vispy = ["vispy>=0.11"]
|
|
32
|
+
all = ["matplotlib>=3.4", "vtk>=9.0", "vispy>=0.11"]
|
|
33
|
+
dev = ["pytest>=7.0", "matplotlib>=3.4", "vtk>=9.0"]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
morphoview = "morphoview.cli:main"
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/subhacom/morphoview"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
morphoview = ["py.typed"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""morphoview -- read, analyze and visualize neuronal morphologies.
|
|
2
|
+
|
|
3
|
+
Quick start::
|
|
4
|
+
|
|
5
|
+
import morphoview as nv
|
|
6
|
+
|
|
7
|
+
graph = nv.swc_to_graph("cell.swc")
|
|
8
|
+
print(nv.summary(graph))
|
|
9
|
+
|
|
10
|
+
# Matplotlib (portable, always available if matplotlib is installed)
|
|
11
|
+
from morphoview.backends import mpl
|
|
12
|
+
mpl.plot_3d(graph, color=nv.get_colormap("3cd2"))
|
|
13
|
+
mpl.show()
|
|
14
|
+
|
|
15
|
+
# VTK (tapered tubes, interactive)
|
|
16
|
+
from morphoview.backends import vtk
|
|
17
|
+
vtk.show(graph, colormap=nv.get_colormap("3cd2"), axes=True)
|
|
18
|
+
|
|
19
|
+
The rendering backends (matplotlib, VTK, vispy) are optional and imported
|
|
20
|
+
lazily, so the core I/O and analysis work with just numpy + networkx.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from . import colors, graph, swc, transform
|
|
26
|
+
from .colors import (
|
|
27
|
+
COLORMAPS,
|
|
28
|
+
DEFAULT_COLORMAP,
|
|
29
|
+
STRUCTURE_IDS,
|
|
30
|
+
STRUCTURE_NAMES,
|
|
31
|
+
color_for_structure,
|
|
32
|
+
get_colormap,
|
|
33
|
+
normalize,
|
|
34
|
+
structure_name,
|
|
35
|
+
)
|
|
36
|
+
from .graph import (
|
|
37
|
+
branch_points,
|
|
38
|
+
combine,
|
|
39
|
+
graph_to_swc,
|
|
40
|
+
leaf_nodes,
|
|
41
|
+
n_branches,
|
|
42
|
+
n_leaves,
|
|
43
|
+
soma_distance,
|
|
44
|
+
soma_pathlen,
|
|
45
|
+
structure_node_map,
|
|
46
|
+
summary,
|
|
47
|
+
swc_to_graph,
|
|
48
|
+
total_length,
|
|
49
|
+
)
|
|
50
|
+
from .swc import SWC_DTYPE, load_swc, save_swc
|
|
51
|
+
from .transform import apply_transform, mirror, rotate, translate
|
|
52
|
+
|
|
53
|
+
__version__ = "0.1.0"
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"__version__",
|
|
57
|
+
# submodules
|
|
58
|
+
"colors",
|
|
59
|
+
"graph",
|
|
60
|
+
"swc",
|
|
61
|
+
"transform",
|
|
62
|
+
# colors
|
|
63
|
+
"COLORMAPS",
|
|
64
|
+
"DEFAULT_COLORMAP",
|
|
65
|
+
"STRUCTURE_IDS",
|
|
66
|
+
"STRUCTURE_NAMES",
|
|
67
|
+
"color_for_structure",
|
|
68
|
+
"get_colormap",
|
|
69
|
+
"normalize",
|
|
70
|
+
"structure_name",
|
|
71
|
+
# swc I/O
|
|
72
|
+
"SWC_DTYPE",
|
|
73
|
+
"load_swc",
|
|
74
|
+
"save_swc",
|
|
75
|
+
# graph
|
|
76
|
+
"swc_to_graph",
|
|
77
|
+
"graph_to_swc",
|
|
78
|
+
"combine",
|
|
79
|
+
"branch_points",
|
|
80
|
+
"n_branches",
|
|
81
|
+
"leaf_nodes",
|
|
82
|
+
"n_leaves",
|
|
83
|
+
"total_length",
|
|
84
|
+
"soma_distance",
|
|
85
|
+
"soma_pathlen",
|
|
86
|
+
"structure_node_map",
|
|
87
|
+
"summary",
|
|
88
|
+
# transforms
|
|
89
|
+
"apply_transform",
|
|
90
|
+
"translate",
|
|
91
|
+
"rotate",
|
|
92
|
+
"mirror",
|
|
93
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Rendering backends for neuronal morphologies.
|
|
2
|
+
|
|
3
|
+
Each backend is imported lazily so that the heavy, optional dependencies
|
|
4
|
+
(matplotlib, VTK, vispy) are only required if you actually use them.
|
|
5
|
+
Import the one you need directly, e.g.::
|
|
6
|
+
|
|
7
|
+
from morphoview.backends import mpl
|
|
8
|
+
from morphoview.backends import vtk
|
|
9
|
+
"""
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Render a rotating movie of a morphology with VTK.
|
|
2
|
+
|
|
3
|
+
Rotates the neuron about one or more axes and writes each frame, producing
|
|
4
|
+
an animation. Requires the ``vtk`` package.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Dict, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
import networkx as nx
|
|
12
|
+
|
|
13
|
+
from .. import colors, transform
|
|
14
|
+
from . import vtk as vtk_backend
|
|
15
|
+
|
|
16
|
+
vtk = vtk_backend.vtk
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _bounding_center(graph: nx.DiGraph) -> Tuple[float, float, float]:
|
|
20
|
+
xs = [graph.nodes[n]["x"] for n in graph]
|
|
21
|
+
ys = [graph.nodes[n]["y"] for n in graph]
|
|
22
|
+
zs = [graph.nodes[n]["z"] for n in graph]
|
|
23
|
+
return (
|
|
24
|
+
(min(xs) + max(xs)) * 0.5,
|
|
25
|
+
(min(ys) + max(ys)) * 0.5,
|
|
26
|
+
(min(zs) + max(zs)) * 0.5,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def dump_movie(
|
|
31
|
+
filename: str,
|
|
32
|
+
graph: nx.DiGraph,
|
|
33
|
+
colormap: Optional[Dict[int, colors.RGB]] = None,
|
|
34
|
+
background: Tuple[float, float, float] = (0, 0, 0),
|
|
35
|
+
lines: float = 0.0,
|
|
36
|
+
xrot: float = 0.0,
|
|
37
|
+
yrot: float = 0.0,
|
|
38
|
+
zrot: float = 0.0,
|
|
39
|
+
xangle: float = 0.0,
|
|
40
|
+
yangle: float = 0.0,
|
|
41
|
+
zangle: float = 0.0,
|
|
42
|
+
frames_per_degree: int = 10,
|
|
43
|
+
framerate: int = 25,
|
|
44
|
+
size: Tuple[int, int] = (800, 600),
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Write a rotating animation of ``graph`` to ``filename`` (AVI).
|
|
47
|
+
|
|
48
|
+
``xrot/yrot/zrot`` orient the neuron before recording; ``xangle`` etc.
|
|
49
|
+
are the total sweep in degrees about each axis during the movie.
|
|
50
|
+
"""
|
|
51
|
+
if colormap is None:
|
|
52
|
+
colormap = colors.get_colormap(colors.DEFAULT_COLORMAP)
|
|
53
|
+
|
|
54
|
+
# Centre the morphology at the origin, then apply the initial orientation.
|
|
55
|
+
cx, cy, cz = _bounding_center(graph)
|
|
56
|
+
transform.apply_transform(
|
|
57
|
+
graph,
|
|
58
|
+
transform.rotation_translation_matrix(-cx, -cy, -cz, xrot, yrot, zrot),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
renderer = vtk_backend.make_renderer(
|
|
62
|
+
graph, colormap=colormap, background=background, lines=lines
|
|
63
|
+
)
|
|
64
|
+
actor = renderer.GetActors().GetLastActor()
|
|
65
|
+
cx, cy, cz = _bounding_center(graph)
|
|
66
|
+
actor.SetOrigin(cx, cy, cz)
|
|
67
|
+
|
|
68
|
+
window = vtk.vtkRenderWindow()
|
|
69
|
+
window.SetSize(*size)
|
|
70
|
+
window.AddRenderer(renderer)
|
|
71
|
+
interactor = vtk.vtkRenderWindowInteractor()
|
|
72
|
+
interactor.SetRenderWindow(window)
|
|
73
|
+
window.Render()
|
|
74
|
+
interactor.Initialize()
|
|
75
|
+
|
|
76
|
+
def _step(angle):
|
|
77
|
+
return (0.0 if angle == 0 else 1.0 / frames_per_degree,
|
|
78
|
+
int(angle * frames_per_degree))
|
|
79
|
+
|
|
80
|
+
dx, xframes = _step(xangle)
|
|
81
|
+
dy, yframes = _step(yangle)
|
|
82
|
+
dz, zframes = _step(zangle)
|
|
83
|
+
frames = max(xframes, yframes, zframes)
|
|
84
|
+
|
|
85
|
+
w2if = vtk.vtkWindowToImageFilter()
|
|
86
|
+
w2if.SetInput(window)
|
|
87
|
+
w2if.ReadFrontBufferOff()
|
|
88
|
+
w2if.Update()
|
|
89
|
+
writer = vtk.vtkAVIWriter()
|
|
90
|
+
writer.SetRate(framerate)
|
|
91
|
+
writer.SetInputConnection(w2if.GetOutputPort())
|
|
92
|
+
writer.SetFileName(filename)
|
|
93
|
+
writer.Start()
|
|
94
|
+
for _ in range(frames):
|
|
95
|
+
actor.RotateX(dx)
|
|
96
|
+
actor.RotateY(dy)
|
|
97
|
+
actor.RotateZ(dz)
|
|
98
|
+
window.Render()
|
|
99
|
+
w2if.Modified() # crucial: force the filter to grab the new frame
|
|
100
|
+
writer.Write()
|
|
101
|
+
writer.End()
|