orbit-gui 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.
- orbit_gui-0.1.0/LICENSE +21 -0
- orbit_gui-0.1.0/MANIFEST.in +1 -0
- orbit_gui-0.1.0/PKG-INFO +124 -0
- orbit_gui-0.1.0/README.md +79 -0
- orbit_gui-0.1.0/pyproject.toml +81 -0
- orbit_gui-0.1.0/setup.cfg +4 -0
- orbit_gui-0.1.0/setup.py +115 -0
- orbit_gui-0.1.0/src/orbit/__init__.py +86 -0
- orbit_gui-0.1.0/src/orbit/_blocks.py +124 -0
- orbit_gui-0.1.0/src/orbit/_concurrency.py +74 -0
- orbit_gui-0.1.0/src/orbit/_masks.py +57 -0
- orbit_gui-0.1.0/src/orbit/_merge.py +43 -0
- orbit_gui-0.1.0/src/orbit/_native/__init__.py +28 -0
- orbit_gui-0.1.0/src/orbit/_native/orbit_native.cpp +320 -0
- orbit_gui-0.1.0/src/orbit/_patches.py +96 -0
- orbit_gui-0.1.0/src/orbit/_peak_picking.py +30 -0
- orbit_gui-0.1.0/src/orbit/_volumetric.py +27 -0
- orbit_gui-0.1.0/src/orbit/cnmf.py +422 -0
- orbit_gui-0.1.0/src/orbit/cnmf_deconvolution.py +163 -0
- orbit_gui-0.1.0/src/orbit/cnmf_e.py +179 -0
- orbit_gui-0.1.0/src/orbit/cnmf_e_background.py +272 -0
- orbit_gui-0.1.0/src/orbit/cnmf_e_init.py +128 -0
- orbit_gui-0.1.0/src/orbit/cnmf_init.py +169 -0
- orbit_gui-0.1.0/src/orbit/denoising.py +149 -0
- orbit_gui-0.1.0/src/orbit/denoising_3d.py +31 -0
- orbit_gui-0.1.0/src/orbit/detrending.py +340 -0
- orbit_gui-0.1.0/src/orbit/ljung_box.py +79 -0
- orbit_gui-0.1.0/src/orbit/masking.py +87 -0
- orbit_gui-0.1.0/src/orbit/motion_correction.py +567 -0
- orbit_gui-0.1.0/src/orbit/motion_correction_3d.py +157 -0
- orbit_gui-0.1.0/src/orbit/motion_metrics.py +125 -0
- orbit_gui-0.1.0/src/orbit/neuropil.py +75 -0
- orbit_gui-0.1.0/src/orbit/normalization.py +181 -0
- orbit_gui-0.1.0/src/orbit/patchwarp.py +205 -0
- orbit_gui-0.1.0/src/orbit/pca_denoise.py +78 -0
- orbit_gui-0.1.0/src/orbit/projections.py +184 -0
- orbit_gui-0.1.0/src/orbit/qc_traces.py +39 -0
- orbit_gui-0.1.0/src/orbit/roi_extraction_corr.py +300 -0
- orbit_gui-0.1.0/src/orbit/roi_extraction_graft.py +152 -0
- orbit_gui-0.1.0/src/orbit/roi_extraction_graft_3d.py +196 -0
- orbit_gui-0.1.0/src/orbit/roi_extraction_pca_ica.py +104 -0
- orbit_gui-0.1.0/src/orbit/roi_extraction_realseudo.py +97 -0
- orbit_gui-0.1.0/src/orbit/seudo/__init__.py +38 -0
- orbit_gui-0.1.0/src/orbit/seudo/_native/__init__.py +38 -0
- orbit_gui-0.1.0/src/orbit/seudo/_native/fista_native.cpp +346 -0
- orbit_gui-0.1.0/src/orbit/seudo/auto_classify.py +240 -0
- orbit_gui-0.1.0/src/orbit/seudo/blob.py +39 -0
- orbit_gui-0.1.0/src/orbit/seudo/classification_io.py +31 -0
- orbit_gui-0.1.0/src/orbit/seudo/constants.py +55 -0
- orbit_gui-0.1.0/src/orbit/seudo/core.py +100 -0
- orbit_gui-0.1.0/src/orbit/seudo/estimate.py +431 -0
- orbit_gui-0.1.0/src/orbit/seudo/geometry.py +45 -0
- orbit_gui-0.1.0/src/orbit/seudo/run_on_transients.py +72 -0
- orbit_gui-0.1.0/src/orbit/seudo/seudo_residual.py +65 -0
- orbit_gui-0.1.0/src/orbit/seudo/solver.py +77 -0
- orbit_gui-0.1.0/src/orbit/seudo/stats.py +35 -0
- orbit_gui-0.1.0/src/orbit/seudo/streaming.py +1609 -0
- orbit_gui-0.1.0/src/orbit/seudo/transients.py +234 -0
- orbit_gui-0.1.0/src/orbit/volume_render.py +93 -0
- orbit_gui-0.1.0/src/orbit_gui.egg-info/PKG-INFO +124 -0
- orbit_gui-0.1.0/src/orbit_gui.egg-info/SOURCES.txt +178 -0
- orbit_gui-0.1.0/src/orbit_gui.egg-info/dependency_links.txt +1 -0
- orbit_gui-0.1.0/src/orbit_gui.egg-info/entry_points.txt +2 -0
- orbit_gui-0.1.0/src/orbit_gui.egg-info/requires.txt +21 -0
- orbit_gui-0.1.0/src/orbit_gui.egg-info/top_level.txt +2 -0
- orbit_gui-0.1.0/src/orbitapp/__init__.py +9 -0
- orbit_gui-0.1.0/src/orbitapp/__main__.py +9 -0
- orbit_gui-0.1.0/src/orbitapp/app.py +287 -0
- orbit_gui-0.1.0/src/orbitapp/assets.py +39 -0
- orbit_gui-0.1.0/src/orbitapp/fits_io.py +96 -0
- orbit_gui-0.1.0/src/orbitapp/format.py +39 -0
- orbit_gui-0.1.0/src/orbitapp/io.py +203 -0
- orbit_gui-0.1.0/src/orbitapp/report.py +445 -0
- orbit_gui-0.1.0/src/orbitapp/session_io.py +167 -0
- orbit_gui-0.1.0/src/orbitapp/state.py +134 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/__init__.py +23 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/denoising_tab.py +518 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/detrending_tab.py +335 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/load_tab.py +253 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/mask_tab.py +280 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/motion_correction_tab.py +364 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/normalization_tab.py +249 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/projections_tab.py +193 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/roi_validation_tab.py +1174 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/save_tab.py +155 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/source_extraction_tab.py +1199 -0
- orbit_gui-0.1.0/src/orbitapp/tabs/stage_tab.py +437 -0
- orbit_gui-0.1.0/src/orbitapp/theme.py +305 -0
- orbit_gui-0.1.0/src/orbitapp/volumetric_io.py +166 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/__init__.py +43 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/busy_bar.py +83 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/commit_controls.py +41 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/confirm.py +21 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/header_bar.py +305 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/image_coords.py +43 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/image_slideshow.py +79 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/movie_popout.py +22 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/options_dialog.py +91 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/parameters_dialog.py +46 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/qc_panel.py +94 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/resource_monitor.py +156 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/roi_overlay.py +56 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/roi_review_panel.py +349 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/spinbox.py +27 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/stage_panel.py +145 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/step_params_dialog.py +51 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/volume_view.py +209 -0
- orbit_gui-0.1.0/src/orbitapp/widgets/volumetric_load_dialog.py +60 -0
- orbit_gui-0.1.0/src/orbitapp/workers.py +108 -0
- orbit_gui-0.1.0/tests/test_app.py +254 -0
- orbit_gui-0.1.0/tests/test_blocks.py +128 -0
- orbit_gui-0.1.0/tests/test_cnmf.py +323 -0
- orbit_gui-0.1.0/tests/test_cnmf_deconvolution.py +135 -0
- orbit_gui-0.1.0/tests/test_cnmf_e.py +191 -0
- orbit_gui-0.1.0/tests/test_cnmf_e_background.py +182 -0
- orbit_gui-0.1.0/tests/test_cnmf_e_init.py +105 -0
- orbit_gui-0.1.0/tests/test_cnmf_init.py +98 -0
- orbit_gui-0.1.0/tests/test_concurrency.py +97 -0
- orbit_gui-0.1.0/tests/test_denoising.py +201 -0
- orbit_gui-0.1.0/tests/test_denoising_3d.py +86 -0
- orbit_gui-0.1.0/tests/test_denoising_tab.py +437 -0
- orbit_gui-0.1.0/tests/test_detrending.py +441 -0
- orbit_gui-0.1.0/tests/test_detrending_tab.py +515 -0
- orbit_gui-0.1.0/tests/test_fits_io.py +60 -0
- orbit_gui-0.1.0/tests/test_format.py +47 -0
- orbit_gui-0.1.0/tests/test_header_bar.py +251 -0
- orbit_gui-0.1.0/tests/test_image_axis_order.py +37 -0
- orbit_gui-0.1.0/tests/test_image_coords.py +79 -0
- orbit_gui-0.1.0/tests/test_io.py +191 -0
- orbit_gui-0.1.0/tests/test_ljung_box.py +78 -0
- orbit_gui-0.1.0/tests/test_load_tab.py +314 -0
- orbit_gui-0.1.0/tests/test_mask_tab.py +371 -0
- orbit_gui-0.1.0/tests/test_masking.py +80 -0
- orbit_gui-0.1.0/tests/test_masking_3d.py +45 -0
- orbit_gui-0.1.0/tests/test_memmap_pipeline_e2e.py +122 -0
- orbit_gui-0.1.0/tests/test_memory_usage.py +265 -0
- orbit_gui-0.1.0/tests/test_merge.py +65 -0
- orbit_gui-0.1.0/tests/test_motion_correction.py +328 -0
- orbit_gui-0.1.0/tests/test_motion_correction_3d.py +140 -0
- orbit_gui-0.1.0/tests/test_motion_correction_tab.py +287 -0
- orbit_gui-0.1.0/tests/test_motion_metrics.py +127 -0
- orbit_gui-0.1.0/tests/test_neuropil.py +90 -0
- orbit_gui-0.1.0/tests/test_normalization.py +155 -0
- orbit_gui-0.1.0/tests/test_normalization_tab.py +185 -0
- orbit_gui-0.1.0/tests/test_normalization_tab_memmap.py +112 -0
- orbit_gui-0.1.0/tests/test_options_dialog.py +109 -0
- orbit_gui-0.1.0/tests/test_patches.py +65 -0
- orbit_gui-0.1.0/tests/test_patchwarp.py +70 -0
- orbit_gui-0.1.0/tests/test_pca_denoise.py +79 -0
- orbit_gui-0.1.0/tests/test_phase_correlate.py +117 -0
- orbit_gui-0.1.0/tests/test_projections.py +324 -0
- orbit_gui-0.1.0/tests/test_projections_tab.py +111 -0
- orbit_gui-0.1.0/tests/test_qc_traces.py +47 -0
- orbit_gui-0.1.0/tests/test_report.py +227 -0
- orbit_gui-0.1.0/tests/test_resource_monitor.py +151 -0
- orbit_gui-0.1.0/tests/test_roi_extraction_corr.py +78 -0
- orbit_gui-0.1.0/tests/test_roi_extraction_graft.py +99 -0
- orbit_gui-0.1.0/tests/test_roi_extraction_graft_3d.py +172 -0
- orbit_gui-0.1.0/tests/test_roi_extraction_pca_ica.py +71 -0
- orbit_gui-0.1.0/tests/test_roi_extraction_realseudo.py +76 -0
- orbit_gui-0.1.0/tests/test_roi_overlay.py +49 -0
- orbit_gui-0.1.0/tests/test_roi_validation_tab.py +184 -0
- orbit_gui-0.1.0/tests/test_save_tab.py +146 -0
- orbit_gui-0.1.0/tests/test_session_io.py +102 -0
- orbit_gui-0.1.0/tests/test_seudo.py +211 -0
- orbit_gui-0.1.0/tests/test_seudo_native.py +142 -0
- orbit_gui-0.1.0/tests/test_seudo_streaming.py +122 -0
- orbit_gui-0.1.0/tests/test_source_extraction_tab.py +1520 -0
- orbit_gui-0.1.0/tests/test_source_extraction_tab_memmap.py +305 -0
- orbit_gui-0.1.0/tests/test_stage_panel.py +64 -0
- orbit_gui-0.1.0/tests/test_stage_tab.py +236 -0
- orbit_gui-0.1.0/tests/test_state.py +163 -0
- orbit_gui-0.1.0/tests/test_step_params_dialog.py +55 -0
- orbit_gui-0.1.0/tests/test_theme.py +281 -0
- orbit_gui-0.1.0/tests/test_volume_render.py +75 -0
- orbit_gui-0.1.0/tests/test_volume_view.py +96 -0
- orbit_gui-0.1.0/tests/test_volumetric_io.py +196 -0
- orbit_gui-0.1.0/tests/test_volumetric_load_dialog.py +41 -0
- orbit_gui-0.1.0/tests/test_widgets.py +600 -0
- orbit_gui-0.1.0/tests/test_workers.py +66 -0
orbit_gui-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NeuralCoDy
|
|
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 @@
|
|
|
1
|
+
include src/orbit/_native/orbit_native.cpp
|
orbit_gui-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orbit-gui
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Assessment-forward analysis pipeline and GUI for functional neural optical imaging
|
|
5
|
+
Author: Adam Charles
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/NeuralCoDy/orbit-gui
|
|
8
|
+
Project-URL: Repository, https://github.com/NeuralCoDy/orbit-gui
|
|
9
|
+
Project-URL: Issues, https://github.com/NeuralCoDy/orbit-gui/issues
|
|
10
|
+
Keywords: calcium-imaging,neuroscience,microscopy,source-extraction,roi,image-processing
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy>=1.24
|
|
26
|
+
Requires-Dist: scipy>=1.10
|
|
27
|
+
Requires-Dist: scikit-image>=0.21
|
|
28
|
+
Requires-Dist: scikit-learn>=1.3
|
|
29
|
+
Requires-Dist: opencv-python-headless>=4.8
|
|
30
|
+
Requires-Dist: PyWavelets>=1.4
|
|
31
|
+
Requires-Dist: threadpoolctl>=3.1
|
|
32
|
+
Requires-Dist: pygraft-gui>=0.3.0
|
|
33
|
+
Provides-Extra: test
|
|
34
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
35
|
+
Provides-Extra: gui
|
|
36
|
+
Requires-Dist: PySide6>=6.5; extra == "gui"
|
|
37
|
+
Requires-Dist: pyqtgraph>=0.13; extra == "gui"
|
|
38
|
+
Requires-Dist: tifffile>=2023.7; extra == "gui"
|
|
39
|
+
Requires-Dist: h5py>=3.9; extra == "gui"
|
|
40
|
+
Requires-Dist: roiapp>=0.1.0; extra == "gui"
|
|
41
|
+
Requires-Dist: matplotlib>=3.7; extra == "gui"
|
|
42
|
+
Requires-Dist: astropy>=5.3; extra == "gui"
|
|
43
|
+
Requires-Dist: psutil>=5.9; extra == "gui"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# orbit-gui
|
|
47
|
+
|
|
48
|
+
An assessment-forward GUI for the analysis of functional neural optical imaging data:
|
|
49
|
+
validation metrics are computed and surfaced at every pipeline stage (motion correction,
|
|
50
|
+
source identification, demixing, ...), not bolted on at the end.
|
|
51
|
+
|
|
52
|
+
## Layout
|
|
53
|
+
|
|
54
|
+
- `src/orbit/` -- pure-Python algorithm and validation-metric core, no Qt. Every stage
|
|
55
|
+
and every metric is a standalone function (e.g. `orbit.projections.mean_projection`),
|
|
56
|
+
independently unit-tested, and called by the GUI rather than implemented inside it.
|
|
57
|
+
- `src/orbitapp/` -- the PySide6 GUI shell. One tab per pipeline stage; tabs only call
|
|
58
|
+
`orbit` functions and display results. Mirrors the architecture of the sibling
|
|
59
|
+
[pyGraFT](https://github.com/adamshch/GraFT-analysis) project (`AppState` +
|
|
60
|
+
per-stage tabs), and reuses the `MovieSliderWidget` bundled with the
|
|
61
|
+
[roiapp](https://pypi.org/project/roiapp/) distribution for movie playback.
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install orbit-gui[gui]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
or, for a development checkout:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install -e ".[test,gui]"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`orbit`'s native (C++) accelerator for the slower per-pixel/per-trace operations
|
|
76
|
+
(local correlation and mode projections, OASIS deconvolution, the per-pixel
|
|
77
|
+
Ljung-Box test) builds automatically whenever a C++ compiler is available (via
|
|
78
|
+
`setup.py`'s `ext_modules` -- see PyPI's published wheels, which ship it
|
|
79
|
+
prebuilt) -- falls back to pure numpy/Python if no compiler is found, so this
|
|
80
|
+
never blocks `pip install`. To rebuild it in place after editing the `.cpp`
|
|
81
|
+
source, without reinstalling:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
src/orbit/_native/build_native.sh
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Real-SEUDO's own per-cell FISTA solve has a separate optional accelerator (also
|
|
88
|
+
falls back to pure Python if skipped) -- needs FFTW3 in addition to a C++14
|
|
89
|
+
compiler and pybind11 (Debian/Ubuntu: `apt-get install libfftw3-dev`). It builds
|
|
90
|
+
automatically alongside the accelerator above wherever FFTW3 is found (including
|
|
91
|
+
in PyPI's published wheels, on the platforms where FFTW3 could be provisioned in
|
|
92
|
+
CI). To rebuild it in place after editing the `.cpp` source, without reinstalling:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
src/orbit/seudo/_native/build_native.sh
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Run
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python3 -m orbitapp
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Develop
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pytest
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Status
|
|
111
|
+
|
|
112
|
+
- **Load**: a movie (TIFF/folder-of-TIFFs/NPY/H5/MAT), previewed via an embedded,
|
|
113
|
+
scrubbable movie player.
|
|
114
|
+
- **Data Projections**: mean/median/mode/variance/Fano-factor/local-correlation
|
|
115
|
+
projections, computed lazily and cached.
|
|
116
|
+
- **Motion Correction**: rigid, piecewise-rigid, or PatchWarp-style piecewise-affine
|
|
117
|
+
registration, with quality metrics (mMD/mCM/ECC, singular-value-spectrum tightening,
|
|
118
|
+
spatial PC maps) shown alongside the before/after images. Runs in the background;
|
|
119
|
+
results are explicit candidates previewed in the tab until committed to the active
|
|
120
|
+
dataset, which is then recorded in the header's pipeline breadcrumb.
|
|
121
|
+
|
|
122
|
+
Later phases add source identification and demixing/contamination assessment -- see
|
|
123
|
+
project memory for the full roadmap.
|
|
124
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# orbit-gui
|
|
2
|
+
|
|
3
|
+
An assessment-forward GUI for the analysis of functional neural optical imaging data:
|
|
4
|
+
validation metrics are computed and surfaced at every pipeline stage (motion correction,
|
|
5
|
+
source identification, demixing, ...), not bolted on at the end.
|
|
6
|
+
|
|
7
|
+
## Layout
|
|
8
|
+
|
|
9
|
+
- `src/orbit/` -- pure-Python algorithm and validation-metric core, no Qt. Every stage
|
|
10
|
+
and every metric is a standalone function (e.g. `orbit.projections.mean_projection`),
|
|
11
|
+
independently unit-tested, and called by the GUI rather than implemented inside it.
|
|
12
|
+
- `src/orbitapp/` -- the PySide6 GUI shell. One tab per pipeline stage; tabs only call
|
|
13
|
+
`orbit` functions and display results. Mirrors the architecture of the sibling
|
|
14
|
+
[pyGraFT](https://github.com/adamshch/GraFT-analysis) project (`AppState` +
|
|
15
|
+
per-stage tabs), and reuses the `MovieSliderWidget` bundled with the
|
|
16
|
+
[roiapp](https://pypi.org/project/roiapp/) distribution for movie playback.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install orbit-gui[gui]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
or, for a development checkout:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install -e ".[test,gui]"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`orbit`'s native (C++) accelerator for the slower per-pixel/per-trace operations
|
|
31
|
+
(local correlation and mode projections, OASIS deconvolution, the per-pixel
|
|
32
|
+
Ljung-Box test) builds automatically whenever a C++ compiler is available (via
|
|
33
|
+
`setup.py`'s `ext_modules` -- see PyPI's published wheels, which ship it
|
|
34
|
+
prebuilt) -- falls back to pure numpy/Python if no compiler is found, so this
|
|
35
|
+
never blocks `pip install`. To rebuild it in place after editing the `.cpp`
|
|
36
|
+
source, without reinstalling:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
src/orbit/_native/build_native.sh
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Real-SEUDO's own per-cell FISTA solve has a separate optional accelerator (also
|
|
43
|
+
falls back to pure Python if skipped) -- needs FFTW3 in addition to a C++14
|
|
44
|
+
compiler and pybind11 (Debian/Ubuntu: `apt-get install libfftw3-dev`). It builds
|
|
45
|
+
automatically alongside the accelerator above wherever FFTW3 is found (including
|
|
46
|
+
in PyPI's published wheels, on the platforms where FFTW3 could be provisioned in
|
|
47
|
+
CI). To rebuild it in place after editing the `.cpp` source, without reinstalling:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
src/orbit/seudo/_native/build_native.sh
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Run
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python3 -m orbitapp
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Develop
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pytest
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Status
|
|
66
|
+
|
|
67
|
+
- **Load**: a movie (TIFF/folder-of-TIFFs/NPY/H5/MAT), previewed via an embedded,
|
|
68
|
+
scrubbable movie player.
|
|
69
|
+
- **Data Projections**: mean/median/mode/variance/Fano-factor/local-correlation
|
|
70
|
+
projections, computed lazily and cached.
|
|
71
|
+
- **Motion Correction**: rigid, piecewise-rigid, or PatchWarp-style piecewise-affine
|
|
72
|
+
registration, with quality metrics (mMD/mCM/ECC, singular-value-spectrum tightening,
|
|
73
|
+
spatial PC maps) shown alongside the before/after images. Runs in the background;
|
|
74
|
+
results are explicit candidates previewed in the tab until committed to the active
|
|
75
|
+
dataset, which is then recorded in the header's pipeline breadcrumb.
|
|
76
|
+
|
|
77
|
+
Later phases add source identification and demixing/contamination assessment -- see
|
|
78
|
+
project memory for the full roadmap.
|
|
79
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel", "pybind11>=2.11"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# PyPI project/distribution name -- distinct from the importable package
|
|
7
|
+
# name, which stays "orbit" (== `import orbit`), same split as this
|
|
8
|
+
# project's own pygraft-gui dependency (PyPI name) / graft (import name).
|
|
9
|
+
# "orbit" itself is already taken on PyPI by an unrelated package.
|
|
10
|
+
name = "orbit-gui"
|
|
11
|
+
version = "0.1.0"
|
|
12
|
+
description = "Assessment-forward analysis pipeline and GUI for functional neural optical imaging"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
license = { text = "MIT" }
|
|
16
|
+
authors = [{ name = "Adam Charles" }]
|
|
17
|
+
keywords = [
|
|
18
|
+
"calcium-imaging",
|
|
19
|
+
"neuroscience",
|
|
20
|
+
"microscopy",
|
|
21
|
+
"source-extraction",
|
|
22
|
+
"roi",
|
|
23
|
+
"image-processing",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Intended Audience :: Science/Research",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Programming Language :: Python :: 3.13",
|
|
35
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
36
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
37
|
+
]
|
|
38
|
+
dependencies = [
|
|
39
|
+
"numpy>=1.24",
|
|
40
|
+
"scipy>=1.10",
|
|
41
|
+
"scikit-image>=0.21",
|
|
42
|
+
"scikit-learn>=1.3",
|
|
43
|
+
"opencv-python-headless>=4.8",
|
|
44
|
+
"PyWavelets>=1.4",
|
|
45
|
+
# scikit-learn's own dependency already, so effectively always present --
|
|
46
|
+
# listed explicitly since orbit._concurrency.limited_native_threads calls
|
|
47
|
+
# its API directly, not just benefiting from scikit-learn's internal use.
|
|
48
|
+
"threadpoolctl>=3.1",
|
|
49
|
+
# orbit.cnmf's spatial update and orbit.roi_extraction_graft both import
|
|
50
|
+
# graft (this package's algorithm module, not its GUI) unconditionally --
|
|
51
|
+
# a base dependency, not gui-only, even though most other GraFT-adjacent
|
|
52
|
+
# code here is GUI-facing.
|
|
53
|
+
"pygraft-gui>=0.3.0",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[project.urls]
|
|
57
|
+
Homepage = "https://github.com/NeuralCoDy/orbit-gui"
|
|
58
|
+
Repository = "https://github.com/NeuralCoDy/orbit-gui"
|
|
59
|
+
Issues = "https://github.com/NeuralCoDy/orbit-gui/issues"
|
|
60
|
+
|
|
61
|
+
[project.optional-dependencies]
|
|
62
|
+
test = ["pytest>=7.0"]
|
|
63
|
+
gui = [
|
|
64
|
+
"PySide6>=6.5",
|
|
65
|
+
"pyqtgraph>=0.13",
|
|
66
|
+
"tifffile>=2023.7",
|
|
67
|
+
"h5py>=3.9",
|
|
68
|
+
"roiapp>=0.1.0",
|
|
69
|
+
"matplotlib>=3.7",
|
|
70
|
+
"astropy>=5.3",
|
|
71
|
+
"psutil>=5.9",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[project.scripts]
|
|
75
|
+
orbitapp = "orbitapp.__main__:main"
|
|
76
|
+
|
|
77
|
+
[tool.setuptools.packages.find]
|
|
78
|
+
where = ["src"]
|
|
79
|
+
|
|
80
|
+
[tool.pytest.ini_options]
|
|
81
|
+
testpaths = ["tests"]
|
orbit_gui-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Builds orbit's two optional C++ extensions. This is additive to
|
|
2
|
+
pyproject.toml's [project] metadata -- setuptools reads both.
|
|
3
|
+
|
|
4
|
+
Both extensions are marked optional: if a compiler (or, for the second
|
|
5
|
+
one, FFTW3) isn't available, setuptools skips that extension and warns
|
|
6
|
+
instead of aborting `pip install`. Everything that can use them falls
|
|
7
|
+
back to an equivalent pure-Python/numpy implementation transparently
|
|
8
|
+
either way -- see orbit._native's and orbit.seudo._native's own
|
|
9
|
+
docstrings.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from setuptools import Extension, setup
|
|
16
|
+
|
|
17
|
+
if sys.platform == "win32":
|
|
18
|
+
# MSVC's flag syntax differs from GCC/Clang's ("/" not "-", /std:c++14
|
|
19
|
+
# not -std=c++14) -- std::thread needs no extra link flag on Windows
|
|
20
|
+
# (unlike -pthread on Unix), the runtime provides it directly.
|
|
21
|
+
EXTRA_COMPILE_ARGS = ["/O2", "/std:c++14"]
|
|
22
|
+
EXTRA_LINK_ARGS: list[str] = []
|
|
23
|
+
else:
|
|
24
|
+
EXTRA_COMPILE_ARGS = ["-O3", "-std=c++14", "-pthread"]
|
|
25
|
+
EXTRA_LINK_ARGS = ["-pthread"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _native_extension() -> list[Extension]:
|
|
29
|
+
"""orbit._native._orbit_native: local correlation, half-sample mode,
|
|
30
|
+
OASIS deconvolution, the Ljung-Box Q statistic -- pybind11 + the
|
|
31
|
+
standard library only (std::thread for parallelism), no system
|
|
32
|
+
dependency beyond a C++ compiler."""
|
|
33
|
+
try:
|
|
34
|
+
import pybind11
|
|
35
|
+
except ImportError:
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
return [
|
|
39
|
+
Extension(
|
|
40
|
+
"orbit._native._orbit_native",
|
|
41
|
+
sources=["src/orbit/_native/orbit_native.cpp"],
|
|
42
|
+
include_dirs=[pybind11.get_include()],
|
|
43
|
+
language="c++",
|
|
44
|
+
extra_compile_args=EXTRA_COMPILE_ARGS,
|
|
45
|
+
extra_link_args=EXTRA_LINK_ARGS,
|
|
46
|
+
optional=True,
|
|
47
|
+
)
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# (include_dir, lib_dir) pairs worth trying even with no env override --
|
|
52
|
+
# covers the common package-manager default prefixes. Harmless if a pair
|
|
53
|
+
# doesn't exist: an -I/-L flag pointing at a missing directory is a
|
|
54
|
+
# silent no-op for gcc/clang/MSVC, so listing all of these unconditionally
|
|
55
|
+
# is safe. This matters most on macOS: cibuildwheel cross-compiles an
|
|
56
|
+
# x86_64 wheel from an arm64 (macos-14) runner, and Homebrew installs
|
|
57
|
+
# each architecture's own FFTW3 to a DIFFERENT prefix (/opt/homebrew for
|
|
58
|
+
# native arm64, /usr/local for an x86_64 build under Rosetta) -- a single
|
|
59
|
+
# fixed path can't cover both, but trying both candidates and letting the
|
|
60
|
+
# linker pick whichever actually has the right-architecture library does.
|
|
61
|
+
_FFTW_CANDIDATE_DIRS = [
|
|
62
|
+
("/opt/homebrew/include", "/opt/homebrew/lib"), # Homebrew, Apple Silicon
|
|
63
|
+
("/usr/local/include", "/usr/local/lib"), # Homebrew (Intel/Rosetta) / common Linux local prefix
|
|
64
|
+
("/usr/include", "/usr/lib/x86_64-linux-gnu"), # Debian/Ubuntu system package
|
|
65
|
+
("/usr/include", "/usr/lib64"), # RHEL/AlmaLinux/manylinux system package
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _seudo_native_extension() -> list[Extension]:
|
|
70
|
+
"""orbit.seudo._native._fista_native: Real-SEUDO's per-cell FISTA
|
|
71
|
+
solve, compiled -- needs FFTW3 in addition to a compiler, unlike the
|
|
72
|
+
extension above. Tries FFTW_INCLUDE_DIR/FFTW_LIB_DIR from the
|
|
73
|
+
environment first (same override src/orbit/seudo/_native/
|
|
74
|
+
build_native.sh already supports for a manual build -- set instead
|
|
75
|
+
by .github/workflows/wheels.yml on Windows, where there's no
|
|
76
|
+
standard install prefix to guess), then falls back to
|
|
77
|
+
_FFTW_CANDIDATE_DIRS' common package-manager prefixes."""
|
|
78
|
+
try:
|
|
79
|
+
import pybind11
|
|
80
|
+
except ImportError:
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
include_dirs = [pybind11.get_include()]
|
|
84
|
+
library_dirs = []
|
|
85
|
+
|
|
86
|
+
fftw_include_dir = os.environ.get("FFTW_INCLUDE_DIR")
|
|
87
|
+
fftw_lib_dir = os.environ.get("FFTW_LIB_DIR")
|
|
88
|
+
if fftw_include_dir or fftw_lib_dir:
|
|
89
|
+
if fftw_include_dir:
|
|
90
|
+
include_dirs.append(fftw_include_dir)
|
|
91
|
+
if fftw_lib_dir:
|
|
92
|
+
library_dirs.append(fftw_lib_dir)
|
|
93
|
+
else:
|
|
94
|
+
for inc, lib in _FFTW_CANDIDATE_DIRS:
|
|
95
|
+
if os.path.isdir(inc):
|
|
96
|
+
include_dirs.append(inc)
|
|
97
|
+
if os.path.isdir(lib):
|
|
98
|
+
library_dirs.append(lib)
|
|
99
|
+
|
|
100
|
+
return [
|
|
101
|
+
Extension(
|
|
102
|
+
"orbit.seudo._native._fista_native",
|
|
103
|
+
sources=["src/orbit/seudo/_native/fista_native.cpp"],
|
|
104
|
+
include_dirs=include_dirs,
|
|
105
|
+
library_dirs=library_dirs,
|
|
106
|
+
libraries=["fftw3"],
|
|
107
|
+
language="c++",
|
|
108
|
+
extra_compile_args=EXTRA_COMPILE_ARGS,
|
|
109
|
+
extra_link_args=EXTRA_LINK_ARGS,
|
|
110
|
+
optional=True,
|
|
111
|
+
)
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
setup(ext_modules=_native_extension() + _seudo_native_extension())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""orbit: pure-Python algorithm and validation-metric core for the
|
|
2
|
+
assessment-forward functional optical imaging pipeline.
|
|
3
|
+
|
|
4
|
+
No Qt/GUI dependencies live here (see orbitapp for the PySide6 shell) --
|
|
5
|
+
every stage and every metric is a standalone, independently testable
|
|
6
|
+
function, called by the GUI rather than implemented inside it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .cnmf import CNMFResult, cnmf_source_extraction
|
|
10
|
+
from .cnmf_e import cnmf_e_source_extraction
|
|
11
|
+
from .denoising import (
|
|
12
|
+
denoise_gaussian,
|
|
13
|
+
denoise_median,
|
|
14
|
+
denoise_wavelet_space,
|
|
15
|
+
denoise_wavelet_time,
|
|
16
|
+
residual_energy_fraction,
|
|
17
|
+
)
|
|
18
|
+
from .motion_correction import motion_correct, patch_motion_correct, rigid_motion_correct
|
|
19
|
+
from .motion_metrics import (
|
|
20
|
+
enhanced_correlation_coefficient,
|
|
21
|
+
mean_correlation_to_reference,
|
|
22
|
+
mean_max_intensity_difference,
|
|
23
|
+
spatiotemporal_svd,
|
|
24
|
+
)
|
|
25
|
+
from .neuropil import compute_neuropil_traces, neuropil_ring_mask
|
|
26
|
+
from .normalization import describe_normalization, normalize_movie, pixel_value_histogram, robust_std, summary_stats
|
|
27
|
+
from .patchwarp import patchwarp_motion_correct
|
|
28
|
+
from .projections import (
|
|
29
|
+
fano_factor_projection,
|
|
30
|
+
fano_factor_projection_volumetric,
|
|
31
|
+
local_correlation_projection,
|
|
32
|
+
mean_projection,
|
|
33
|
+
mean_projection_volumetric,
|
|
34
|
+
median_projection,
|
|
35
|
+
median_projection_volumetric,
|
|
36
|
+
mode_projection,
|
|
37
|
+
mode_projection_volumetric,
|
|
38
|
+
variance_projection,
|
|
39
|
+
variance_projection_volumetric,
|
|
40
|
+
)
|
|
41
|
+
from .qc_traces import qc_trace_samples
|
|
42
|
+
from .roi_extraction_corr import CorrMaskResult, find_seed_candidates, roi_from_seed
|
|
43
|
+
from .roi_extraction_pca_ica import PCAICAResult, pca_ica_source_extraction
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"mean_projection",
|
|
47
|
+
"median_projection",
|
|
48
|
+
"variance_projection",
|
|
49
|
+
"fano_factor_projection",
|
|
50
|
+
"local_correlation_projection",
|
|
51
|
+
"mode_projection",
|
|
52
|
+
"mean_projection_volumetric",
|
|
53
|
+
"median_projection_volumetric",
|
|
54
|
+
"variance_projection_volumetric",
|
|
55
|
+
"fano_factor_projection_volumetric",
|
|
56
|
+
"mode_projection_volumetric",
|
|
57
|
+
"rigid_motion_correct",
|
|
58
|
+
"patch_motion_correct",
|
|
59
|
+
"patchwarp_motion_correct",
|
|
60
|
+
"motion_correct",
|
|
61
|
+
"mean_max_intensity_difference",
|
|
62
|
+
"enhanced_correlation_coefficient",
|
|
63
|
+
"mean_correlation_to_reference",
|
|
64
|
+
"spatiotemporal_svd",
|
|
65
|
+
"denoise_wavelet_time",
|
|
66
|
+
"denoise_wavelet_space",
|
|
67
|
+
"denoise_gaussian",
|
|
68
|
+
"denoise_median",
|
|
69
|
+
"qc_trace_samples",
|
|
70
|
+
"residual_energy_fraction",
|
|
71
|
+
"normalize_movie",
|
|
72
|
+
"describe_normalization",
|
|
73
|
+
"pixel_value_histogram",
|
|
74
|
+
"robust_std",
|
|
75
|
+
"summary_stats",
|
|
76
|
+
"CorrMaskResult",
|
|
77
|
+
"roi_from_seed",
|
|
78
|
+
"find_seed_candidates",
|
|
79
|
+
"compute_neuropil_traces",
|
|
80
|
+
"neuropil_ring_mask",
|
|
81
|
+
"PCAICAResult",
|
|
82
|
+
"pca_ica_source_extraction",
|
|
83
|
+
"CNMFResult",
|
|
84
|
+
"cnmf_source_extraction",
|
|
85
|
+
"cnmf_e_source_extraction",
|
|
86
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Tiny helper for streaming a large array through a reduction one slab
|
|
2
|
+
at a time -- so a whole-movie float64 working copy is never held, only
|
|
3
|
+
one block, while the reduction itself still accumulates at full width.
|
|
4
|
+
|
|
5
|
+
Used by the assessment metrics that are pure reductions over an axis
|
|
6
|
+
(``orbit.denoising.residual_energy_fraction`` over frames-as-rows,
|
|
7
|
+
``orbit.motion_metrics.mean_correlation_to_reference`` over frames) and
|
|
8
|
+
by the depth-projected residual the Denoising tab shows for a volumetric
|
|
9
|
+
movie -- all cases where the input can be a multi-GB (T, L, W, D) volume
|
|
10
|
+
but the result is a scalar or a single 2D-per-frame projection.
|
|
11
|
+
|
|
12
|
+
``chunked_median``/``chunked_variance`` are the one place ``np.median``
|
|
13
|
+
and ``.var()`` should be called on a potentially movie-sized array
|
|
14
|
+
anywhere in orbit: both build a second, often-larger copy of their whole
|
|
15
|
+
input internally (a partition copy; a promoted-dtype ``arr - mean``
|
|
16
|
+
array). Every such caller routes through here so a memory-bound fix
|
|
17
|
+
lands in one place."""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from collections.abc import Callable, Iterator
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
|
|
25
|
+
_DEFAULT_TARGET_BYTES = 64 * 1024 * 1024 # ~64 MiB float64 working slab per block
|
|
26
|
+
_MEDIAN_SLAB_BYTES = 256 * 1024 * 1024 # working-slab budget for chunked_median/chunked_variance
|
|
27
|
+
_BLOCK_PIXELS = 150 # default hard cap on a block's span of the chunked axis (on top of the byte budget)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def iter_axis_slices(
|
|
31
|
+
shape: tuple[int, ...],
|
|
32
|
+
axis: int,
|
|
33
|
+
target_bytes: int = _DEFAULT_TARGET_BYTES,
|
|
34
|
+
itemsize: int = 8,
|
|
35
|
+
max_block: int | None = None,
|
|
36
|
+
) -> Iterator[slice]:
|
|
37
|
+
"""Yield ``slice`` objects that partition ``axis`` of an array of
|
|
38
|
+
``shape`` into consecutive runs, each about ``target_bytes`` once
|
|
39
|
+
widened to ``itemsize`` bytes per element (default 8 -> float64).
|
|
40
|
+
|
|
41
|
+
``max_block``, if given, further caps the step regardless of what
|
|
42
|
+
the byte budget alone would allow -- the byte budget still applies
|
|
43
|
+
underneath it, so a chunk is never larger than either limit allows.
|
|
44
|
+
|
|
45
|
+
Index the array as ``arr[s]`` for ``axis == 0`` or, in general,
|
|
46
|
+
``arr[(slice(None),) * axis + (s,)]`` / ``arr[..., s]`` for the last
|
|
47
|
+
axis. A small array yields a single full-extent slice, so callers
|
|
48
|
+
don't need a separate "small input" path."""
|
|
49
|
+
axis_len = shape[axis]
|
|
50
|
+
other_elems = 1
|
|
51
|
+
for i, n in enumerate(shape):
|
|
52
|
+
if i != axis:
|
|
53
|
+
other_elems *= n
|
|
54
|
+
step = max(1, target_bytes // max(1, other_elems * itemsize))
|
|
55
|
+
if max_block is not None:
|
|
56
|
+
step = min(step, max_block)
|
|
57
|
+
for start in range(0, axis_len, step):
|
|
58
|
+
yield slice(start, min(start + step, axis_len))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _promoted_dtype(dtype: np.dtype) -> np.dtype:
|
|
62
|
+
"""np.median's/.var()'s own output-dtype promotion rule: int or a
|
|
63
|
+
float narrower than float64 -> float64; float64 (or wider) stays."""
|
|
64
|
+
return dtype if np.issubdtype(dtype, np.floating) else np.dtype(np.float64)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _chunked_reduce(
|
|
68
|
+
arr: np.ndarray,
|
|
69
|
+
axis: int,
|
|
70
|
+
reduce_fn: Callable[[np.ndarray], np.ndarray],
|
|
71
|
+
keepdims: bool = False,
|
|
72
|
+
out_dtype: np.dtype | None = None,
|
|
73
|
+
max_block: int | None = _BLOCK_PIXELS,
|
|
74
|
+
) -> np.ndarray:
|
|
75
|
+
"""Shared block-at-a-time driver behind chunked_median/
|
|
76
|
+
chunked_variance: moves ``axis`` to the front, walks the first
|
|
77
|
+
remaining axis in blocks (bounded by ``_MEDIAN_SLAB_BYTES`` and
|
|
78
|
+
``max_block`` together), and writes ``reduce_fn(block)`` (block's
|
|
79
|
+
reduction axis at 0) into the matching output slice. Only one block
|
|
80
|
+
is ever copied to ``out_dtype`` at a time -- ``arr`` is never
|
|
81
|
+
mutated or fully materialized at that dtype."""
|
|
82
|
+
moved = np.moveaxis(arr, axis, 0) # reduction axis -> 0 (a view)
|
|
83
|
+
if out_dtype is None:
|
|
84
|
+
out_dtype = _promoted_dtype(moved.dtype)
|
|
85
|
+
out = np.empty(moved.shape[1:], dtype=out_dtype)
|
|
86
|
+
itemsize = np.dtype(out_dtype).itemsize
|
|
87
|
+
for sl in iter_axis_slices(moved.shape, 1, target_bytes=_MEDIAN_SLAB_BYTES, itemsize=itemsize, max_block=max_block):
|
|
88
|
+
block = np.array(moved[:, sl], dtype=out_dtype) # own copy -- safe to overwrite
|
|
89
|
+
out[sl] = reduce_fn(block)
|
|
90
|
+
return np.expand_dims(out, axis) if keepdims else out
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def chunked_median(
|
|
94
|
+
arr: np.ndarray,
|
|
95
|
+
axis: int | None = None,
|
|
96
|
+
keepdims: bool = False,
|
|
97
|
+
out_dtype: np.dtype | None = None,
|
|
98
|
+
max_block: int | None = _BLOCK_PIXELS,
|
|
99
|
+
) -> np.ndarray:
|
|
100
|
+
"""``np.median(arr, axis=axis, keepdims=keepdims)`` computed one
|
|
101
|
+
block of the first non-reduction axis at a time -- see this module's
|
|
102
|
+
docstring. ``axis=None`` (a single global scalar, which genuinely
|
|
103
|
+
needs every value at once) falls straight through to ``np.median``,
|
|
104
|
+
so this is a drop-in wherever ``np.median`` was called.
|
|
105
|
+
|
|
106
|
+
``out_dtype`` defaults to np.median's own promotion rule; pass one
|
|
107
|
+
(e.g. float32) for a caller that wants a narrower working dtype
|
|
108
|
+
regardless of the input's. Element-wise identical to ``np.median``
|
|
109
|
+
for any block partitioning -- each block's per-pixel median only
|
|
110
|
+
needs that pixel's own full time series."""
|
|
111
|
+
if axis is None:
|
|
112
|
+
return np.median(arr, keepdims=keepdims)
|
|
113
|
+
return _chunked_reduce(
|
|
114
|
+
arr, axis, lambda block: np.median(block, axis=0, overwrite_input=True), keepdims, out_dtype, max_block,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def chunked_variance(arr: np.ndarray, axis: int, max_block: int | None = _BLOCK_PIXELS) -> np.ndarray:
|
|
119
|
+
"""``arr.var(axis=axis)`` (ddof=0) computed one block at a time --
|
|
120
|
+
ndarray.var() builds a full promoted-dtype ``arr - mean`` array
|
|
121
|
+
before squaring/summing it, and each block instead only needs its
|
|
122
|
+
own. Element-wise identical to ``arr.var(axis=axis)``, for the same
|
|
123
|
+
per-pixel-independence reason as chunked_median."""
|
|
124
|
+
return _chunked_reduce(arr, axis, lambda block: block.var(axis=0), max_block=max_block)
|