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.
Files changed (180) hide show
  1. orbit_gui-0.1.0/LICENSE +21 -0
  2. orbit_gui-0.1.0/MANIFEST.in +1 -0
  3. orbit_gui-0.1.0/PKG-INFO +124 -0
  4. orbit_gui-0.1.0/README.md +79 -0
  5. orbit_gui-0.1.0/pyproject.toml +81 -0
  6. orbit_gui-0.1.0/setup.cfg +4 -0
  7. orbit_gui-0.1.0/setup.py +115 -0
  8. orbit_gui-0.1.0/src/orbit/__init__.py +86 -0
  9. orbit_gui-0.1.0/src/orbit/_blocks.py +124 -0
  10. orbit_gui-0.1.0/src/orbit/_concurrency.py +74 -0
  11. orbit_gui-0.1.0/src/orbit/_masks.py +57 -0
  12. orbit_gui-0.1.0/src/orbit/_merge.py +43 -0
  13. orbit_gui-0.1.0/src/orbit/_native/__init__.py +28 -0
  14. orbit_gui-0.1.0/src/orbit/_native/orbit_native.cpp +320 -0
  15. orbit_gui-0.1.0/src/orbit/_patches.py +96 -0
  16. orbit_gui-0.1.0/src/orbit/_peak_picking.py +30 -0
  17. orbit_gui-0.1.0/src/orbit/_volumetric.py +27 -0
  18. orbit_gui-0.1.0/src/orbit/cnmf.py +422 -0
  19. orbit_gui-0.1.0/src/orbit/cnmf_deconvolution.py +163 -0
  20. orbit_gui-0.1.0/src/orbit/cnmf_e.py +179 -0
  21. orbit_gui-0.1.0/src/orbit/cnmf_e_background.py +272 -0
  22. orbit_gui-0.1.0/src/orbit/cnmf_e_init.py +128 -0
  23. orbit_gui-0.1.0/src/orbit/cnmf_init.py +169 -0
  24. orbit_gui-0.1.0/src/orbit/denoising.py +149 -0
  25. orbit_gui-0.1.0/src/orbit/denoising_3d.py +31 -0
  26. orbit_gui-0.1.0/src/orbit/detrending.py +340 -0
  27. orbit_gui-0.1.0/src/orbit/ljung_box.py +79 -0
  28. orbit_gui-0.1.0/src/orbit/masking.py +87 -0
  29. orbit_gui-0.1.0/src/orbit/motion_correction.py +567 -0
  30. orbit_gui-0.1.0/src/orbit/motion_correction_3d.py +157 -0
  31. orbit_gui-0.1.0/src/orbit/motion_metrics.py +125 -0
  32. orbit_gui-0.1.0/src/orbit/neuropil.py +75 -0
  33. orbit_gui-0.1.0/src/orbit/normalization.py +181 -0
  34. orbit_gui-0.1.0/src/orbit/patchwarp.py +205 -0
  35. orbit_gui-0.1.0/src/orbit/pca_denoise.py +78 -0
  36. orbit_gui-0.1.0/src/orbit/projections.py +184 -0
  37. orbit_gui-0.1.0/src/orbit/qc_traces.py +39 -0
  38. orbit_gui-0.1.0/src/orbit/roi_extraction_corr.py +300 -0
  39. orbit_gui-0.1.0/src/orbit/roi_extraction_graft.py +152 -0
  40. orbit_gui-0.1.0/src/orbit/roi_extraction_graft_3d.py +196 -0
  41. orbit_gui-0.1.0/src/orbit/roi_extraction_pca_ica.py +104 -0
  42. orbit_gui-0.1.0/src/orbit/roi_extraction_realseudo.py +97 -0
  43. orbit_gui-0.1.0/src/orbit/seudo/__init__.py +38 -0
  44. orbit_gui-0.1.0/src/orbit/seudo/_native/__init__.py +38 -0
  45. orbit_gui-0.1.0/src/orbit/seudo/_native/fista_native.cpp +346 -0
  46. orbit_gui-0.1.0/src/orbit/seudo/auto_classify.py +240 -0
  47. orbit_gui-0.1.0/src/orbit/seudo/blob.py +39 -0
  48. orbit_gui-0.1.0/src/orbit/seudo/classification_io.py +31 -0
  49. orbit_gui-0.1.0/src/orbit/seudo/constants.py +55 -0
  50. orbit_gui-0.1.0/src/orbit/seudo/core.py +100 -0
  51. orbit_gui-0.1.0/src/orbit/seudo/estimate.py +431 -0
  52. orbit_gui-0.1.0/src/orbit/seudo/geometry.py +45 -0
  53. orbit_gui-0.1.0/src/orbit/seudo/run_on_transients.py +72 -0
  54. orbit_gui-0.1.0/src/orbit/seudo/seudo_residual.py +65 -0
  55. orbit_gui-0.1.0/src/orbit/seudo/solver.py +77 -0
  56. orbit_gui-0.1.0/src/orbit/seudo/stats.py +35 -0
  57. orbit_gui-0.1.0/src/orbit/seudo/streaming.py +1609 -0
  58. orbit_gui-0.1.0/src/orbit/seudo/transients.py +234 -0
  59. orbit_gui-0.1.0/src/orbit/volume_render.py +93 -0
  60. orbit_gui-0.1.0/src/orbit_gui.egg-info/PKG-INFO +124 -0
  61. orbit_gui-0.1.0/src/orbit_gui.egg-info/SOURCES.txt +178 -0
  62. orbit_gui-0.1.0/src/orbit_gui.egg-info/dependency_links.txt +1 -0
  63. orbit_gui-0.1.0/src/orbit_gui.egg-info/entry_points.txt +2 -0
  64. orbit_gui-0.1.0/src/orbit_gui.egg-info/requires.txt +21 -0
  65. orbit_gui-0.1.0/src/orbit_gui.egg-info/top_level.txt +2 -0
  66. orbit_gui-0.1.0/src/orbitapp/__init__.py +9 -0
  67. orbit_gui-0.1.0/src/orbitapp/__main__.py +9 -0
  68. orbit_gui-0.1.0/src/orbitapp/app.py +287 -0
  69. orbit_gui-0.1.0/src/orbitapp/assets.py +39 -0
  70. orbit_gui-0.1.0/src/orbitapp/fits_io.py +96 -0
  71. orbit_gui-0.1.0/src/orbitapp/format.py +39 -0
  72. orbit_gui-0.1.0/src/orbitapp/io.py +203 -0
  73. orbit_gui-0.1.0/src/orbitapp/report.py +445 -0
  74. orbit_gui-0.1.0/src/orbitapp/session_io.py +167 -0
  75. orbit_gui-0.1.0/src/orbitapp/state.py +134 -0
  76. orbit_gui-0.1.0/src/orbitapp/tabs/__init__.py +23 -0
  77. orbit_gui-0.1.0/src/orbitapp/tabs/denoising_tab.py +518 -0
  78. orbit_gui-0.1.0/src/orbitapp/tabs/detrending_tab.py +335 -0
  79. orbit_gui-0.1.0/src/orbitapp/tabs/load_tab.py +253 -0
  80. orbit_gui-0.1.0/src/orbitapp/tabs/mask_tab.py +280 -0
  81. orbit_gui-0.1.0/src/orbitapp/tabs/motion_correction_tab.py +364 -0
  82. orbit_gui-0.1.0/src/orbitapp/tabs/normalization_tab.py +249 -0
  83. orbit_gui-0.1.0/src/orbitapp/tabs/projections_tab.py +193 -0
  84. orbit_gui-0.1.0/src/orbitapp/tabs/roi_validation_tab.py +1174 -0
  85. orbit_gui-0.1.0/src/orbitapp/tabs/save_tab.py +155 -0
  86. orbit_gui-0.1.0/src/orbitapp/tabs/source_extraction_tab.py +1199 -0
  87. orbit_gui-0.1.0/src/orbitapp/tabs/stage_tab.py +437 -0
  88. orbit_gui-0.1.0/src/orbitapp/theme.py +305 -0
  89. orbit_gui-0.1.0/src/orbitapp/volumetric_io.py +166 -0
  90. orbit_gui-0.1.0/src/orbitapp/widgets/__init__.py +43 -0
  91. orbit_gui-0.1.0/src/orbitapp/widgets/busy_bar.py +83 -0
  92. orbit_gui-0.1.0/src/orbitapp/widgets/commit_controls.py +41 -0
  93. orbit_gui-0.1.0/src/orbitapp/widgets/confirm.py +21 -0
  94. orbit_gui-0.1.0/src/orbitapp/widgets/header_bar.py +305 -0
  95. orbit_gui-0.1.0/src/orbitapp/widgets/image_coords.py +43 -0
  96. orbit_gui-0.1.0/src/orbitapp/widgets/image_slideshow.py +79 -0
  97. orbit_gui-0.1.0/src/orbitapp/widgets/movie_popout.py +22 -0
  98. orbit_gui-0.1.0/src/orbitapp/widgets/options_dialog.py +91 -0
  99. orbit_gui-0.1.0/src/orbitapp/widgets/parameters_dialog.py +46 -0
  100. orbit_gui-0.1.0/src/orbitapp/widgets/qc_panel.py +94 -0
  101. orbit_gui-0.1.0/src/orbitapp/widgets/resource_monitor.py +156 -0
  102. orbit_gui-0.1.0/src/orbitapp/widgets/roi_overlay.py +56 -0
  103. orbit_gui-0.1.0/src/orbitapp/widgets/roi_review_panel.py +349 -0
  104. orbit_gui-0.1.0/src/orbitapp/widgets/spinbox.py +27 -0
  105. orbit_gui-0.1.0/src/orbitapp/widgets/stage_panel.py +145 -0
  106. orbit_gui-0.1.0/src/orbitapp/widgets/step_params_dialog.py +51 -0
  107. orbit_gui-0.1.0/src/orbitapp/widgets/volume_view.py +209 -0
  108. orbit_gui-0.1.0/src/orbitapp/widgets/volumetric_load_dialog.py +60 -0
  109. orbit_gui-0.1.0/src/orbitapp/workers.py +108 -0
  110. orbit_gui-0.1.0/tests/test_app.py +254 -0
  111. orbit_gui-0.1.0/tests/test_blocks.py +128 -0
  112. orbit_gui-0.1.0/tests/test_cnmf.py +323 -0
  113. orbit_gui-0.1.0/tests/test_cnmf_deconvolution.py +135 -0
  114. orbit_gui-0.1.0/tests/test_cnmf_e.py +191 -0
  115. orbit_gui-0.1.0/tests/test_cnmf_e_background.py +182 -0
  116. orbit_gui-0.1.0/tests/test_cnmf_e_init.py +105 -0
  117. orbit_gui-0.1.0/tests/test_cnmf_init.py +98 -0
  118. orbit_gui-0.1.0/tests/test_concurrency.py +97 -0
  119. orbit_gui-0.1.0/tests/test_denoising.py +201 -0
  120. orbit_gui-0.1.0/tests/test_denoising_3d.py +86 -0
  121. orbit_gui-0.1.0/tests/test_denoising_tab.py +437 -0
  122. orbit_gui-0.1.0/tests/test_detrending.py +441 -0
  123. orbit_gui-0.1.0/tests/test_detrending_tab.py +515 -0
  124. orbit_gui-0.1.0/tests/test_fits_io.py +60 -0
  125. orbit_gui-0.1.0/tests/test_format.py +47 -0
  126. orbit_gui-0.1.0/tests/test_header_bar.py +251 -0
  127. orbit_gui-0.1.0/tests/test_image_axis_order.py +37 -0
  128. orbit_gui-0.1.0/tests/test_image_coords.py +79 -0
  129. orbit_gui-0.1.0/tests/test_io.py +191 -0
  130. orbit_gui-0.1.0/tests/test_ljung_box.py +78 -0
  131. orbit_gui-0.1.0/tests/test_load_tab.py +314 -0
  132. orbit_gui-0.1.0/tests/test_mask_tab.py +371 -0
  133. orbit_gui-0.1.0/tests/test_masking.py +80 -0
  134. orbit_gui-0.1.0/tests/test_masking_3d.py +45 -0
  135. orbit_gui-0.1.0/tests/test_memmap_pipeline_e2e.py +122 -0
  136. orbit_gui-0.1.0/tests/test_memory_usage.py +265 -0
  137. orbit_gui-0.1.0/tests/test_merge.py +65 -0
  138. orbit_gui-0.1.0/tests/test_motion_correction.py +328 -0
  139. orbit_gui-0.1.0/tests/test_motion_correction_3d.py +140 -0
  140. orbit_gui-0.1.0/tests/test_motion_correction_tab.py +287 -0
  141. orbit_gui-0.1.0/tests/test_motion_metrics.py +127 -0
  142. orbit_gui-0.1.0/tests/test_neuropil.py +90 -0
  143. orbit_gui-0.1.0/tests/test_normalization.py +155 -0
  144. orbit_gui-0.1.0/tests/test_normalization_tab.py +185 -0
  145. orbit_gui-0.1.0/tests/test_normalization_tab_memmap.py +112 -0
  146. orbit_gui-0.1.0/tests/test_options_dialog.py +109 -0
  147. orbit_gui-0.1.0/tests/test_patches.py +65 -0
  148. orbit_gui-0.1.0/tests/test_patchwarp.py +70 -0
  149. orbit_gui-0.1.0/tests/test_pca_denoise.py +79 -0
  150. orbit_gui-0.1.0/tests/test_phase_correlate.py +117 -0
  151. orbit_gui-0.1.0/tests/test_projections.py +324 -0
  152. orbit_gui-0.1.0/tests/test_projections_tab.py +111 -0
  153. orbit_gui-0.1.0/tests/test_qc_traces.py +47 -0
  154. orbit_gui-0.1.0/tests/test_report.py +227 -0
  155. orbit_gui-0.1.0/tests/test_resource_monitor.py +151 -0
  156. orbit_gui-0.1.0/tests/test_roi_extraction_corr.py +78 -0
  157. orbit_gui-0.1.0/tests/test_roi_extraction_graft.py +99 -0
  158. orbit_gui-0.1.0/tests/test_roi_extraction_graft_3d.py +172 -0
  159. orbit_gui-0.1.0/tests/test_roi_extraction_pca_ica.py +71 -0
  160. orbit_gui-0.1.0/tests/test_roi_extraction_realseudo.py +76 -0
  161. orbit_gui-0.1.0/tests/test_roi_overlay.py +49 -0
  162. orbit_gui-0.1.0/tests/test_roi_validation_tab.py +184 -0
  163. orbit_gui-0.1.0/tests/test_save_tab.py +146 -0
  164. orbit_gui-0.1.0/tests/test_session_io.py +102 -0
  165. orbit_gui-0.1.0/tests/test_seudo.py +211 -0
  166. orbit_gui-0.1.0/tests/test_seudo_native.py +142 -0
  167. orbit_gui-0.1.0/tests/test_seudo_streaming.py +122 -0
  168. orbit_gui-0.1.0/tests/test_source_extraction_tab.py +1520 -0
  169. orbit_gui-0.1.0/tests/test_source_extraction_tab_memmap.py +305 -0
  170. orbit_gui-0.1.0/tests/test_stage_panel.py +64 -0
  171. orbit_gui-0.1.0/tests/test_stage_tab.py +236 -0
  172. orbit_gui-0.1.0/tests/test_state.py +163 -0
  173. orbit_gui-0.1.0/tests/test_step_params_dialog.py +55 -0
  174. orbit_gui-0.1.0/tests/test_theme.py +281 -0
  175. orbit_gui-0.1.0/tests/test_volume_render.py +75 -0
  176. orbit_gui-0.1.0/tests/test_volume_view.py +96 -0
  177. orbit_gui-0.1.0/tests/test_volumetric_io.py +196 -0
  178. orbit_gui-0.1.0/tests/test_volumetric_load_dialog.py +41 -0
  179. orbit_gui-0.1.0/tests/test_widgets.py +600 -0
  180. orbit_gui-0.1.0/tests/test_workers.py +66 -0
@@ -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
@@ -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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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)