puprisa 1.0.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 (93) hide show
  1. puprisa-1.0.0/LICENSE +21 -0
  2. puprisa-1.0.0/PKG-INFO +233 -0
  3. puprisa-1.0.0/README.md +210 -0
  4. puprisa-1.0.0/pyproject.toml +52 -0
  5. puprisa-1.0.0/setup.cfg +4 -0
  6. puprisa-1.0.0/src/puprisa/__init__.py +0 -0
  7. puprisa-1.0.0/src/puprisa/__main__.py +5 -0
  8. puprisa-1.0.0/src/puprisa/app_context.py +43 -0
  9. puprisa-1.0.0/src/puprisa/controllers/__init__.py +0 -0
  10. puprisa-1.0.0/src/puprisa/controllers/curve_controller.py +150 -0
  11. puprisa-1.0.0/src/puprisa/controllers/mask_controller.py +243 -0
  12. puprisa-1.0.0/src/puprisa/controllers/phasor_freq_controller.py +71 -0
  13. puprisa-1.0.0/src/puprisa/controllers/plot_controller.py +96 -0
  14. puprisa-1.0.0/src/puprisa/controllers/pps_slice_controller.py +105 -0
  15. puprisa-1.0.0/src/puprisa/controllers/processing_controller.py +237 -0
  16. puprisa-1.0.0/src/puprisa/controllers/roi_controller.py +231 -0
  17. puprisa-1.0.0/src/puprisa/controllers/stack_controller.py +104 -0
  18. puprisa-1.0.0/src/puprisa/core/__init__.py +0 -0
  19. puprisa-1.0.0/src/puprisa/core/fit.py +238 -0
  20. puprisa-1.0.0/src/puprisa/core/io.py +656 -0
  21. puprisa-1.0.0/src/puprisa/core/mask.py +128 -0
  22. puprisa-1.0.0/src/puprisa/core/phasor.py +271 -0
  23. puprisa-1.0.0/src/puprisa/core/pps.py +585 -0
  24. puprisa-1.0.0/src/puprisa/core/process.py +200 -0
  25. puprisa-1.0.0/src/puprisa/core/visualize.py +574 -0
  26. puprisa-1.0.0/src/puprisa/gui.py +13 -0
  27. puprisa-1.0.0/src/puprisa/model/__init__.py +0 -0
  28. puprisa-1.0.0/src/puprisa/model/curve_manager.py +122 -0
  29. puprisa-1.0.0/src/puprisa/model/entities.py +148 -0
  30. puprisa-1.0.0/src/puprisa/model/mask_manager.py +372 -0
  31. puprisa-1.0.0/src/puprisa/model/plot_manager.py +188 -0
  32. puprisa-1.0.0/src/puprisa/model/processing_manager.py +216 -0
  33. puprisa-1.0.0/src/puprisa/model/roi_manager.py +344 -0
  34. puprisa-1.0.0/src/puprisa/model/stack_manager.py +171 -0
  35. puprisa-1.0.0/src/puprisa/ui/__init__.py +0 -0
  36. puprisa-1.0.0/src/puprisa/ui/dialogs/__init__.py +0 -0
  37. puprisa-1.0.0/src/puprisa/ui/dialogs/about.py +23 -0
  38. puprisa-1.0.0/src/puprisa/ui/dialogs/background_subtraction.py +62 -0
  39. puprisa-1.0.0/src/puprisa/ui/dialogs/colorbar_custom_range.py +60 -0
  40. puprisa-1.0.0/src/puprisa/ui/dialogs/curve_fit.py +220 -0
  41. puprisa-1.0.0/src/puprisa/ui/dialogs/edit_roi.py +226 -0
  42. puprisa-1.0.0/src/puprisa/ui/dialogs/mask_creation.py +83 -0
  43. puprisa-1.0.0/src/puprisa/ui/dialogs/mask_math.py +77 -0
  44. puprisa-1.0.0/src/puprisa/ui/dialogs/phasor_alpha.py +16 -0
  45. puprisa-1.0.0/src/puprisa/ui/dialogs/spectrum.py +342 -0
  46. puprisa-1.0.0/src/puprisa/ui/dialogs/stack_math.py +99 -0
  47. puprisa-1.0.0/src/puprisa/ui/generated/dialog_about.py +90 -0
  48. puprisa-1.0.0/src/puprisa/ui/generated/dialog_bgsub_first_last.py +125 -0
  49. puprisa-1.0.0/src/puprisa/ui/generated/dialog_bgsub_fixed_value.py +74 -0
  50. puprisa-1.0.0/src/puprisa/ui/generated/dialog_bgsub_neg_delay.py +77 -0
  51. puprisa-1.0.0/src/puprisa/ui/generated/dialog_colorbar_customrange.py +97 -0
  52. puprisa-1.0.0/src/puprisa/ui/generated/dialog_curve_fit.py +890 -0
  53. puprisa-1.0.0/src/puprisa/ui/generated/dialog_edit_roi.py +456 -0
  54. puprisa-1.0.0/src/puprisa/ui/generated/dialog_mask_from_intensity_threshold.py +116 -0
  55. puprisa-1.0.0/src/puprisa/ui/generated/dialog_mask_from_zero_pixels.py +68 -0
  56. puprisa-1.0.0/src/puprisa/ui/generated/dialog_mask_math.py +113 -0
  57. puprisa-1.0.0/src/puprisa/ui/generated/dialog_phasor_alpha.py +84 -0
  58. puprisa-1.0.0/src/puprisa/ui/generated/dialog_spectrum.py +119 -0
  59. puprisa-1.0.0/src/puprisa/ui/generated/dialog_stack_math.py +144 -0
  60. puprisa-1.0.0/src/puprisa/ui/generated/ui_main_window.py +478 -0
  61. puprisa-1.0.0/src/puprisa/ui/generated/ui_phasor_window.py +348 -0
  62. puprisa-1.0.0/src/puprisa/ui/main_window.py +222 -0
  63. puprisa-1.0.0/src/puprisa/ui/phasor_window.py +189 -0
  64. puprisa-1.0.0/src/puprisa/ui/resources/__init__.py +0 -0
  65. puprisa-1.0.0/src/puprisa/ui/resources/exp_decay.svg +265 -0
  66. puprisa-1.0.0/src/puprisa/ui/resources/exp_decay_inf.svg +148 -0
  67. puprisa-1.0.0/src/puprisa/ui/resources/icon.svg +837 -0
  68. puprisa-1.0.0/src/puprisa/ui/resources/instantaneous.svg +138 -0
  69. puprisa-1.0.0/src/puprisa/ui/resources/qt.svg +5 -0
  70. puprisa-1.0.0/src/puprisa/ui/widgets/__init__.py +0 -0
  71. puprisa-1.0.0/src/puprisa/ui/widgets/draggable_roi.py +324 -0
  72. puprisa-1.0.0/src/puprisa/ui/widgets/mpl_canvas.py +19 -0
  73. puprisa-1.0.0/src/puprisa/ui/widgets/scrollable_graphics_view.py +26 -0
  74. puprisa-1.0.0/src/puprisa/utils/__init__.py +0 -0
  75. puprisa-1.0.0/src/puprisa/utils/color_utils.py +145 -0
  76. puprisa-1.0.0/src/puprisa/utils/curve_plot_utils.py +54 -0
  77. puprisa-1.0.0/src/puprisa/utils/geometry_utils.py +170 -0
  78. puprisa-1.0.0/src/puprisa/utils/range_parser_utils.py +34 -0
  79. puprisa-1.0.0/src/puprisa/viewmodels/__init__.py +0 -0
  80. puprisa-1.0.0/src/puprisa/viewmodels/curve_view_model.py +113 -0
  81. puprisa-1.0.0/src/puprisa/viewmodels/mask_view_model.py +97 -0
  82. puprisa-1.0.0/src/puprisa/viewmodels/phasor_plot_view_model.py +479 -0
  83. puprisa-1.0.0/src/puprisa/viewmodels/pps_plot_view_model.py +306 -0
  84. puprisa-1.0.0/src/puprisa/viewmodels/roi_scene_bridge.py +345 -0
  85. puprisa-1.0.0/src/puprisa/viewmodels/roi_view_model.py +210 -0
  86. puprisa-1.0.0/src/puprisa/viewmodels/stack_view_model.py +139 -0
  87. puprisa-1.0.0/src/puprisa.egg-info/PKG-INFO +233 -0
  88. puprisa-1.0.0/src/puprisa.egg-info/SOURCES.txt +91 -0
  89. puprisa-1.0.0/src/puprisa.egg-info/dependency_links.txt +1 -0
  90. puprisa-1.0.0/src/puprisa.egg-info/entry_points.txt +2 -0
  91. puprisa-1.0.0/src/puprisa.egg-info/requires.txt +7 -0
  92. puprisa-1.0.0/src/puprisa.egg-info/top_level.txt +1 -0
  93. puprisa-1.0.0/tests/test_visualize.py +34 -0
puprisa-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Warren Lab
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.
puprisa-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.4
2
+ Name: puprisa
3
+ Version: 1.0.0
4
+ Summary: Pump-Probe Image Stack Analysis
5
+ Author-email: Ryan Su <ryan.su@duke.edu>, Xiaotian Feng <xf58@duke.edu>, David Grass <david.grass@duke.edu>, Martin Fischer <martin.fischer@duke.edu>
6
+ License-Expression: MIT
7
+ Keywords: pump-probe,microscopy,image-analysis,transient-absorption,phasor-analysis
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: numpy
16
+ Requires-Dist: pandas
17
+ Requires-Dist: matplotlib
18
+ Requires-Dist: scikit-image
19
+ Requires-Dist: scipy
20
+ Requires-Dist: tifffile
21
+ Requires-Dist: PySide6
22
+ Dynamic: license-file
23
+
24
+ # Puprisa
25
+
26
+ **Puprisa** is a desktop and Python toolkit for analysing pump-probe microscopy image stacks. It combines stack loading, preprocessing, spatial and phasor-region analysis, and curve export in one PySide6 application, while keeping the numerical `PPS` API available for notebooks and scripts.
27
+
28
+ The package is designed for time-resolved transient-absorption data and also supports Z stacks. Time-axis stacks can be analysed in phasor space; Z stacks retain the same loading, masking, plotting, and basic-processing workflow.
29
+
30
+ ## What it provides
31
+
32
+ - Load DukeScan TIFF stacks, saved Puprisa pickle stacks, and Mathematica binary stacks.
33
+ - Recover time delays or Z positions from DukeScan metadata when possible.
34
+ - Work with several open stacks and derive new ones through downsampling or stack arithmetic.
35
+ - Subtract backgrounds, normalize signals, run truncated-SVD denoising, and control image display ranges.
36
+ - Create layered exclusion masks from intensity thresholds or ROI selections.
37
+ - Draw rectangular, circular, elliptical, or polygonal ROIs in pixel space.
38
+ - Compute phasor coordinates for time-axis stacks, select phasor-space ROIs, and inspect their spatial locations.
39
+ - Plot, normalize, and export ROI Average curves as CSV.
40
+ - Save processed stacks as TIFF or pickle; save mask layers as JSON.
41
+
42
+ ## Quick start
43
+
44
+ Puprisa requires Python 3.10 or later. From the repository root:
45
+
46
+ ```bash
47
+ python -m venv .venv
48
+ ```
49
+
50
+ Activate the environment:
51
+
52
+ ```powershell
53
+ # Windows PowerShell
54
+ .venv\Scripts\Activate.ps1
55
+ ```
56
+
57
+ ```bash
58
+ # macOS or Linux
59
+ source .venv/bin/activate
60
+ ```
61
+
62
+ Install the project
63
+
64
+ ```python
65
+ python -m pip install .
66
+ ```
67
+
68
+ Or, install the project in editable mode:
69
+
70
+ ```bash
71
+ python -m pip install -e .
72
+ ```
73
+
74
+ Launch the desktop application:
75
+
76
+ ```bash
77
+ puprisa
78
+ ```
79
+
80
+ The checked-in example data can be opened directly from `data/example_stack_DS_CH1.tif`. Its companion log file supplies the time-delay axis.
81
+
82
+ ## Typical workflow
83
+
84
+ 1. Open one or more stacks with **File / Open Stack**.
85
+ 2. Select a stack, browse frames with the slice control, and choose an appropriate color scale.
86
+ 3. Apply background subtraction, normalization, or SVD denoising if required. These operations modify the selected stack; downsampling and stack math instead create a new derived stack.
87
+ 4. Build an exclusion mask with **Mask / Mask from threshold**, or draw a pixel ROI and convert it to a mask.
88
+ 5. Add pixel-space ROIs to calculate spatially resolved average curves. Use **Curve / Export Curve** to write CSV output.
89
+ 6. For a time-axis stack, open **Phasor / Phasor Analysis**. Choose a modulation frequency, draw phasor-space ROIs, and inspect their spatial projections and average curves.
90
+
91
+ For detailed operating instructions, see the [user guide](docs/docs/user_guide.md). The complete MkDocs site lives under `docs/`.
92
+
93
+ ## Using Puprisa from Python
94
+
95
+ ```python
96
+ from puprisa.core.pps import PPS
97
+
98
+ stack = PPS.load("data/example_stack_DS_CH1.tif")
99
+ stack.apply_background_subtraction(indices=range(3), pixelwise=True)
100
+ stack.create_mask_from_threshold()
101
+ stack.plot_slice(slice_index=1)
102
+
103
+ projection = stack.project()
104
+ phasor_coordinates = stack.phasor(freq=0.25)
105
+ stack.save("processed_stack.pkl", format="pickle")
106
+ ```
107
+
108
+ `images` use the shape `(n_frames, height, width)`. Time values are expressed in picoseconds and Z positions in micrometres.
109
+
110
+ ## Data formats
111
+
112
+ | Format | Extensions | Notes |
113
+ | --- | --- | --- |
114
+ | DukeScan TIFF | `.tif`, `.tiff` | Time (`t = … ps`) or Z (`z = …`) axis is inferred from TIFF tag 285 when available. Time delay loading also falls back to a companion `_xaxis.txt` or `.log` file. |
115
+ | Puprisa pickle | `.pkl`, `.pickle` | Preserves images, axis data, masks, background-subtraction state, results, and filename metadata. |
116
+ | Mathematica binary | `.m`, `.mathematica` | Supported by the Python loader when the axis type is supplied programmatically. The current desktop file dialog exposes TIFF and pickle files. |
117
+
118
+ ## Documentation site
119
+
120
+ The documentation is written for MkDocs Material and uses `mkdocstrings` to render API reference pages from `src/`.
121
+
122
+ ```bash
123
+ python -m pip install mkdocs-material mkdocstrings[python]
124
+ mkdocs serve -f docs/mkdocs.yml
125
+ ```
126
+
127
+ Build a static site with:
128
+
129
+ ```bash
130
+ mkdocs build -f docs/mkdocs.yml
131
+ ```
132
+
133
+ ## Architecture
134
+
135
+ The desktop application follows a **Model-View-ViewModel (MVVM)** design, with small controller objects for command handling:
136
+
137
+ ```text
138
+ src/puprisa/
139
+ core/ Qt-free numerical domain: PPS stack, I/O, processing, masks, phasor, fitting
140
+ model/ Qt-free session state: entities and event-emitting managers
141
+ viewmodels/ Qt view models that bind each manager to a widget/graphics scene
142
+ controllers/ Qt command handlers (open dialogs, then call a manager)
143
+ ui/ Thin windows, dialogs, custom widgets, and Designer forms
144
+ utils/ Shared helpers (geometry, parsing, colours, plotting)
145
+ ```
146
+
147
+ The `core` and `model` layers never import Qt, so the numerical `PPS` API **and** the session-state managers (`StackManager`, `MaskManager`, `RoiManager`, `CurveManager`, `ProcessingManager`, `PlotManager`) are fully usable from notebooks and scripts. View models subscribe to manager callback events and redraw their views; controllers translate menu and button actions into manager calls. Both windows share one `ApplicationContext`, so they see the same session data. See the [architecture guide](docs/docs/architecture.md) and the [data-flow notes](docs/docs/dev/data_flow.md) for the full picture.
148
+
149
+ ## Project layout
150
+
151
+ ```text
152
+ src/puprisa/ Package source
153
+ core/ Qt-free numerical data, I/O, processing, masking, phasor, and fitting
154
+ model/ Qt-free entities and event-driven state managers (the MVVM Model)
155
+ controllers/ Qt command handlers that own dialogs and call managers
156
+ viewmodels/ Qt view models that keep widgets and graphics scenes current
157
+ ui/ Thin windows, dialogs, custom widgets, and Designer forms
158
+ utils/ Shared helpers used by several layers
159
+ docs/ MkDocs configuration and source pages
160
+ data/ Example DukeScan stack and associated metadata
161
+ examples/ Notebook example
162
+ ```
163
+
164
+ ## Important analysis semantics
165
+
166
+ - A mask layer uses `True` for pixels to **exclude**. The effective `PPS.mask` uses `True` for pixels that remain in the analysis.
167
+ - A processing operation acts on the selected stack and invalidates its cached phasor coordinates. Background reset restores the stack's baseline image copy; it does not undo every later destructive operation.
168
+ - ROI curves include both the selected ROI and the stack's effective analysis mask.
169
+ - Phasor analysis is available only for stacks whose axis type is `time`.
170
+
171
+ ## Development
172
+
173
+ `ApplicationContext` is the composition root: it builds the Qt-free model managers in dependency order, wires cross-manager listeners, and lazily constructs `MainWindow` and `PhasorWindow`. The windows are thin MVVM shells that instantiate view models and controllers and connect them to the shared managers.
174
+
175
+ - Model managers are Qt-free and emit callback events that presentation code observes.
176
+ - View models subscribe to those events and keep a specific widget, list, or graphics scene current.
177
+ - Controllers own dialogs and call exactly one manager method per user action.
178
+
179
+ Keep new analysis in `core`, expose state changes through a validated `model` manager method, and only then add a controller action or a view-model response. See the [architecture guide](docs/docs/architecture.md) and [developer documentation](docs/docs/dev/data_flow.md).
180
+
181
+ There is no automated test suite in the current repository. The validation guidance in the documentation describes focused checks for contributors.
182
+
183
+ ## Dependencies
184
+
185
+ Core dependencies:
186
+
187
+ - **numpy**: Numerical array operations and linear algebra
188
+ - **matplotlib**: Plotting and visualization
189
+ - **scikit-image**: Image processing, filtering, and thresholding algorithms
190
+ - **scipy**: Scientific computing, optimization, and special functions
191
+ - **pandas**: Data import and manipulation
192
+ - **pillow**: Image file I/O (TIFF support)
193
+ - **scikit-learn**: Machine learning (optional, for classification features)
194
+ - **pyside6**: GUI
195
+
196
+ See [pyproject.toml](pyproject.toml) for complete list with pinned versions.
197
+
198
+ ## Contributing
199
+
200
+ Contributions are welcome! To contribute:
201
+
202
+ 1. Fork the repository
203
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
204
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
205
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
206
+ 5. Open a Pull Request
207
+
208
+ Please ensure your code follows Python best practices and includes appropriate documentation.
209
+
210
+ ## License
211
+
212
+ MIT
213
+
214
+ ## Authors
215
+
216
+ Created by Ryan Su
217
+
218
+ Contributed by Xiaotian Feng (丰啸天), David Grass, Martin C. Fischer
219
+
220
+ ## Citation
221
+
222
+ If you use this software in your research, please cite:
223
+
224
+ ```
225
+ [Add citation information here]
226
+ ```
227
+
228
+ ## Support
229
+
230
+ For questions, issues, or feature requests:
231
+
232
+ - Open an issue on the repository
233
+ - Contact: [xiaotian.feng@duke.edu](mailto: xiaotian.feng@duke.edu)
@@ -0,0 +1,210 @@
1
+ # Puprisa
2
+
3
+ **Puprisa** is a desktop and Python toolkit for analysing pump-probe microscopy image stacks. It combines stack loading, preprocessing, spatial and phasor-region analysis, and curve export in one PySide6 application, while keeping the numerical `PPS` API available for notebooks and scripts.
4
+
5
+ The package is designed for time-resolved transient-absorption data and also supports Z stacks. Time-axis stacks can be analysed in phasor space; Z stacks retain the same loading, masking, plotting, and basic-processing workflow.
6
+
7
+ ## What it provides
8
+
9
+ - Load DukeScan TIFF stacks, saved Puprisa pickle stacks, and Mathematica binary stacks.
10
+ - Recover time delays or Z positions from DukeScan metadata when possible.
11
+ - Work with several open stacks and derive new ones through downsampling or stack arithmetic.
12
+ - Subtract backgrounds, normalize signals, run truncated-SVD denoising, and control image display ranges.
13
+ - Create layered exclusion masks from intensity thresholds or ROI selections.
14
+ - Draw rectangular, circular, elliptical, or polygonal ROIs in pixel space.
15
+ - Compute phasor coordinates for time-axis stacks, select phasor-space ROIs, and inspect their spatial locations.
16
+ - Plot, normalize, and export ROI Average curves as CSV.
17
+ - Save processed stacks as TIFF or pickle; save mask layers as JSON.
18
+
19
+ ## Quick start
20
+
21
+ Puprisa requires Python 3.10 or later. From the repository root:
22
+
23
+ ```bash
24
+ python -m venv .venv
25
+ ```
26
+
27
+ Activate the environment:
28
+
29
+ ```powershell
30
+ # Windows PowerShell
31
+ .venv\Scripts\Activate.ps1
32
+ ```
33
+
34
+ ```bash
35
+ # macOS or Linux
36
+ source .venv/bin/activate
37
+ ```
38
+
39
+ Install the project
40
+
41
+ ```python
42
+ python -m pip install .
43
+ ```
44
+
45
+ Or, install the project in editable mode:
46
+
47
+ ```bash
48
+ python -m pip install -e .
49
+ ```
50
+
51
+ Launch the desktop application:
52
+
53
+ ```bash
54
+ puprisa
55
+ ```
56
+
57
+ The checked-in example data can be opened directly from `data/example_stack_DS_CH1.tif`. Its companion log file supplies the time-delay axis.
58
+
59
+ ## Typical workflow
60
+
61
+ 1. Open one or more stacks with **File / Open Stack**.
62
+ 2. Select a stack, browse frames with the slice control, and choose an appropriate color scale.
63
+ 3. Apply background subtraction, normalization, or SVD denoising if required. These operations modify the selected stack; downsampling and stack math instead create a new derived stack.
64
+ 4. Build an exclusion mask with **Mask / Mask from threshold**, or draw a pixel ROI and convert it to a mask.
65
+ 5. Add pixel-space ROIs to calculate spatially resolved average curves. Use **Curve / Export Curve** to write CSV output.
66
+ 6. For a time-axis stack, open **Phasor / Phasor Analysis**. Choose a modulation frequency, draw phasor-space ROIs, and inspect their spatial projections and average curves.
67
+
68
+ For detailed operating instructions, see the [user guide](docs/docs/user_guide.md). The complete MkDocs site lives under `docs/`.
69
+
70
+ ## Using Puprisa from Python
71
+
72
+ ```python
73
+ from puprisa.core.pps import PPS
74
+
75
+ stack = PPS.load("data/example_stack_DS_CH1.tif")
76
+ stack.apply_background_subtraction(indices=range(3), pixelwise=True)
77
+ stack.create_mask_from_threshold()
78
+ stack.plot_slice(slice_index=1)
79
+
80
+ projection = stack.project()
81
+ phasor_coordinates = stack.phasor(freq=0.25)
82
+ stack.save("processed_stack.pkl", format="pickle")
83
+ ```
84
+
85
+ `images` use the shape `(n_frames, height, width)`. Time values are expressed in picoseconds and Z positions in micrometres.
86
+
87
+ ## Data formats
88
+
89
+ | Format | Extensions | Notes |
90
+ | --- | --- | --- |
91
+ | DukeScan TIFF | `.tif`, `.tiff` | Time (`t = … ps`) or Z (`z = …`) axis is inferred from TIFF tag 285 when available. Time delay loading also falls back to a companion `_xaxis.txt` or `.log` file. |
92
+ | Puprisa pickle | `.pkl`, `.pickle` | Preserves images, axis data, masks, background-subtraction state, results, and filename metadata. |
93
+ | Mathematica binary | `.m`, `.mathematica` | Supported by the Python loader when the axis type is supplied programmatically. The current desktop file dialog exposes TIFF and pickle files. |
94
+
95
+ ## Documentation site
96
+
97
+ The documentation is written for MkDocs Material and uses `mkdocstrings` to render API reference pages from `src/`.
98
+
99
+ ```bash
100
+ python -m pip install mkdocs-material mkdocstrings[python]
101
+ mkdocs serve -f docs/mkdocs.yml
102
+ ```
103
+
104
+ Build a static site with:
105
+
106
+ ```bash
107
+ mkdocs build -f docs/mkdocs.yml
108
+ ```
109
+
110
+ ## Architecture
111
+
112
+ The desktop application follows a **Model-View-ViewModel (MVVM)** design, with small controller objects for command handling:
113
+
114
+ ```text
115
+ src/puprisa/
116
+ core/ Qt-free numerical domain: PPS stack, I/O, processing, masks, phasor, fitting
117
+ model/ Qt-free session state: entities and event-emitting managers
118
+ viewmodels/ Qt view models that bind each manager to a widget/graphics scene
119
+ controllers/ Qt command handlers (open dialogs, then call a manager)
120
+ ui/ Thin windows, dialogs, custom widgets, and Designer forms
121
+ utils/ Shared helpers (geometry, parsing, colours, plotting)
122
+ ```
123
+
124
+ The `core` and `model` layers never import Qt, so the numerical `PPS` API **and** the session-state managers (`StackManager`, `MaskManager`, `RoiManager`, `CurveManager`, `ProcessingManager`, `PlotManager`) are fully usable from notebooks and scripts. View models subscribe to manager callback events and redraw their views; controllers translate menu and button actions into manager calls. Both windows share one `ApplicationContext`, so they see the same session data. See the [architecture guide](docs/docs/architecture.md) and the [data-flow notes](docs/docs/dev/data_flow.md) for the full picture.
125
+
126
+ ## Project layout
127
+
128
+ ```text
129
+ src/puprisa/ Package source
130
+ core/ Qt-free numerical data, I/O, processing, masking, phasor, and fitting
131
+ model/ Qt-free entities and event-driven state managers (the MVVM Model)
132
+ controllers/ Qt command handlers that own dialogs and call managers
133
+ viewmodels/ Qt view models that keep widgets and graphics scenes current
134
+ ui/ Thin windows, dialogs, custom widgets, and Designer forms
135
+ utils/ Shared helpers used by several layers
136
+ docs/ MkDocs configuration and source pages
137
+ data/ Example DukeScan stack and associated metadata
138
+ examples/ Notebook example
139
+ ```
140
+
141
+ ## Important analysis semantics
142
+
143
+ - A mask layer uses `True` for pixels to **exclude**. The effective `PPS.mask` uses `True` for pixels that remain in the analysis.
144
+ - A processing operation acts on the selected stack and invalidates its cached phasor coordinates. Background reset restores the stack's baseline image copy; it does not undo every later destructive operation.
145
+ - ROI curves include both the selected ROI and the stack's effective analysis mask.
146
+ - Phasor analysis is available only for stacks whose axis type is `time`.
147
+
148
+ ## Development
149
+
150
+ `ApplicationContext` is the composition root: it builds the Qt-free model managers in dependency order, wires cross-manager listeners, and lazily constructs `MainWindow` and `PhasorWindow`. The windows are thin MVVM shells that instantiate view models and controllers and connect them to the shared managers.
151
+
152
+ - Model managers are Qt-free and emit callback events that presentation code observes.
153
+ - View models subscribe to those events and keep a specific widget, list, or graphics scene current.
154
+ - Controllers own dialogs and call exactly one manager method per user action.
155
+
156
+ Keep new analysis in `core`, expose state changes through a validated `model` manager method, and only then add a controller action or a view-model response. See the [architecture guide](docs/docs/architecture.md) and [developer documentation](docs/docs/dev/data_flow.md).
157
+
158
+ There is no automated test suite in the current repository. The validation guidance in the documentation describes focused checks for contributors.
159
+
160
+ ## Dependencies
161
+
162
+ Core dependencies:
163
+
164
+ - **numpy**: Numerical array operations and linear algebra
165
+ - **matplotlib**: Plotting and visualization
166
+ - **scikit-image**: Image processing, filtering, and thresholding algorithms
167
+ - **scipy**: Scientific computing, optimization, and special functions
168
+ - **pandas**: Data import and manipulation
169
+ - **pillow**: Image file I/O (TIFF support)
170
+ - **scikit-learn**: Machine learning (optional, for classification features)
171
+ - **pyside6**: GUI
172
+
173
+ See [pyproject.toml](pyproject.toml) for complete list with pinned versions.
174
+
175
+ ## Contributing
176
+
177
+ Contributions are welcome! To contribute:
178
+
179
+ 1. Fork the repository
180
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
181
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
182
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
183
+ 5. Open a Pull Request
184
+
185
+ Please ensure your code follows Python best practices and includes appropriate documentation.
186
+
187
+ ## License
188
+
189
+ MIT
190
+
191
+ ## Authors
192
+
193
+ Created by Ryan Su
194
+
195
+ Contributed by Xiaotian Feng (丰啸天), David Grass, Martin C. Fischer
196
+
197
+ ## Citation
198
+
199
+ If you use this software in your research, please cite:
200
+
201
+ ```
202
+ [Add citation information here]
203
+ ```
204
+
205
+ ## Support
206
+
207
+ For questions, issues, or feature requests:
208
+
209
+ - Open an issue on the repository
210
+ - Contact: [xiaotian.feng@duke.edu](mailto: xiaotian.feng@duke.edu)
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "puprisa"
7
+ version = "1.0.0"
8
+ description = "Pump-Probe Image Stack Analysis"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "numpy",
13
+ "pandas",
14
+ "matplotlib",
15
+ "scikit-image",
16
+ "scipy",
17
+ "tifffile",
18
+ "PySide6"
19
+ ]
20
+
21
+ authors = [
22
+ { name = "Ryan Su", email = "ryan.su@duke.edu" },
23
+ { name = "Xiaotian Feng", email = "xf58@duke.edu" },
24
+ { name = "David Grass", email = "david.grass@duke.edu" },
25
+ { name = "Martin Fischer", email = "martin.fischer@duke.edu" }
26
+ ]
27
+
28
+ license = "MIT"
29
+
30
+ keywords = [
31
+ "pump-probe",
32
+ "microscopy",
33
+ "image-analysis",
34
+ "transient-absorption",
35
+ "phasor-analysis",
36
+ ]
37
+
38
+ classifiers = [
39
+ "Development Status :: 5 - Production/Stable",
40
+ "Intended Audience :: Science/Research",
41
+ "Programming Language :: Python :: 3",
42
+ "Topic :: Scientific/Engineering",
43
+ ]
44
+
45
+ [project.scripts]
46
+ puprisa = "puprisa.gui:main"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.setuptools.package-data]
52
+ puprisa = ["ui/resources/*.svg"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,5 @@
1
+ import sys
2
+ from .gui import main
3
+
4
+ if __name__ == "__main__":
5
+ sys.exit(main())
@@ -0,0 +1,43 @@
1
+ # puprisa/app_context.py
2
+ """Application composition root."""
3
+
4
+ from puprisa.model.curve_manager import CurveManager
5
+ from puprisa.model.mask_manager import MaskManager
6
+ from puprisa.model.plot_manager import PlotManager
7
+ from puprisa.model.processing_manager import ProcessingManager
8
+ from puprisa.model.roi_manager import RoiManager
9
+ from puprisa.model.stack_manager import StackEvent, StackManager
10
+
11
+ class ApplicationContext:
12
+ def __init__(self):
13
+ # 1. Create all managers in dependency order.
14
+ self.stack_manager = StackManager()
15
+ self.mask_manager = MaskManager(stack_manager=self.stack_manager)
16
+ self.roi_manager = RoiManager(
17
+ stack_manager=self.stack_manager,
18
+ mask_manager=self.mask_manager,
19
+ )
20
+ self.curve_manager = CurveManager(
21
+ stack_manager=self.stack_manager,
22
+ roi_manager=self.roi_manager,
23
+ )
24
+ self.processing_manager = ProcessingManager(
25
+ stack_manager=self.stack_manager,
26
+ )
27
+ self.plot_manager = PlotManager(
28
+ stack_manager=self.stack_manager,
29
+ processing_manager=self.processing_manager,
30
+ )
31
+
32
+ self.stack_manager.add_listener(self.roi_manager.handle_stack_event)
33
+ self.stack_manager.add_listener(self.plot_manager.handle_stack_event)
34
+ self.stack_manager.add_listener(self.mask_manager.handle_stack_event)
35
+ self.processing_manager.add_listener(self.plot_manager.handle_processing_event)
36
+
37
+ def create_main_window(self):
38
+ from puprisa.ui.main_window import MainWindow
39
+ return MainWindow(self)
40
+
41
+ def create_phasor_window(self):
42
+ from puprisa.ui.phasor_window import PhasorWindow
43
+ return PhasorWindow(self)
File without changes