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.
- puprisa-1.0.0/LICENSE +21 -0
- puprisa-1.0.0/PKG-INFO +233 -0
- puprisa-1.0.0/README.md +210 -0
- puprisa-1.0.0/pyproject.toml +52 -0
- puprisa-1.0.0/setup.cfg +4 -0
- puprisa-1.0.0/src/puprisa/__init__.py +0 -0
- puprisa-1.0.0/src/puprisa/__main__.py +5 -0
- puprisa-1.0.0/src/puprisa/app_context.py +43 -0
- puprisa-1.0.0/src/puprisa/controllers/__init__.py +0 -0
- puprisa-1.0.0/src/puprisa/controllers/curve_controller.py +150 -0
- puprisa-1.0.0/src/puprisa/controllers/mask_controller.py +243 -0
- puprisa-1.0.0/src/puprisa/controllers/phasor_freq_controller.py +71 -0
- puprisa-1.0.0/src/puprisa/controllers/plot_controller.py +96 -0
- puprisa-1.0.0/src/puprisa/controllers/pps_slice_controller.py +105 -0
- puprisa-1.0.0/src/puprisa/controllers/processing_controller.py +237 -0
- puprisa-1.0.0/src/puprisa/controllers/roi_controller.py +231 -0
- puprisa-1.0.0/src/puprisa/controllers/stack_controller.py +104 -0
- puprisa-1.0.0/src/puprisa/core/__init__.py +0 -0
- puprisa-1.0.0/src/puprisa/core/fit.py +238 -0
- puprisa-1.0.0/src/puprisa/core/io.py +656 -0
- puprisa-1.0.0/src/puprisa/core/mask.py +128 -0
- puprisa-1.0.0/src/puprisa/core/phasor.py +271 -0
- puprisa-1.0.0/src/puprisa/core/pps.py +585 -0
- puprisa-1.0.0/src/puprisa/core/process.py +200 -0
- puprisa-1.0.0/src/puprisa/core/visualize.py +574 -0
- puprisa-1.0.0/src/puprisa/gui.py +13 -0
- puprisa-1.0.0/src/puprisa/model/__init__.py +0 -0
- puprisa-1.0.0/src/puprisa/model/curve_manager.py +122 -0
- puprisa-1.0.0/src/puprisa/model/entities.py +148 -0
- puprisa-1.0.0/src/puprisa/model/mask_manager.py +372 -0
- puprisa-1.0.0/src/puprisa/model/plot_manager.py +188 -0
- puprisa-1.0.0/src/puprisa/model/processing_manager.py +216 -0
- puprisa-1.0.0/src/puprisa/model/roi_manager.py +344 -0
- puprisa-1.0.0/src/puprisa/model/stack_manager.py +171 -0
- puprisa-1.0.0/src/puprisa/ui/__init__.py +0 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/__init__.py +0 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/about.py +23 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/background_subtraction.py +62 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/colorbar_custom_range.py +60 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/curve_fit.py +220 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/edit_roi.py +226 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/mask_creation.py +83 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/mask_math.py +77 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/phasor_alpha.py +16 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/spectrum.py +342 -0
- puprisa-1.0.0/src/puprisa/ui/dialogs/stack_math.py +99 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_about.py +90 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_bgsub_first_last.py +125 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_bgsub_fixed_value.py +74 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_bgsub_neg_delay.py +77 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_colorbar_customrange.py +97 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_curve_fit.py +890 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_edit_roi.py +456 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_mask_from_intensity_threshold.py +116 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_mask_from_zero_pixels.py +68 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_mask_math.py +113 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_phasor_alpha.py +84 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_spectrum.py +119 -0
- puprisa-1.0.0/src/puprisa/ui/generated/dialog_stack_math.py +144 -0
- puprisa-1.0.0/src/puprisa/ui/generated/ui_main_window.py +478 -0
- puprisa-1.0.0/src/puprisa/ui/generated/ui_phasor_window.py +348 -0
- puprisa-1.0.0/src/puprisa/ui/main_window.py +222 -0
- puprisa-1.0.0/src/puprisa/ui/phasor_window.py +189 -0
- puprisa-1.0.0/src/puprisa/ui/resources/__init__.py +0 -0
- puprisa-1.0.0/src/puprisa/ui/resources/exp_decay.svg +265 -0
- puprisa-1.0.0/src/puprisa/ui/resources/exp_decay_inf.svg +148 -0
- puprisa-1.0.0/src/puprisa/ui/resources/icon.svg +837 -0
- puprisa-1.0.0/src/puprisa/ui/resources/instantaneous.svg +138 -0
- puprisa-1.0.0/src/puprisa/ui/resources/qt.svg +5 -0
- puprisa-1.0.0/src/puprisa/ui/widgets/__init__.py +0 -0
- puprisa-1.0.0/src/puprisa/ui/widgets/draggable_roi.py +324 -0
- puprisa-1.0.0/src/puprisa/ui/widgets/mpl_canvas.py +19 -0
- puprisa-1.0.0/src/puprisa/ui/widgets/scrollable_graphics_view.py +26 -0
- puprisa-1.0.0/src/puprisa/utils/__init__.py +0 -0
- puprisa-1.0.0/src/puprisa/utils/color_utils.py +145 -0
- puprisa-1.0.0/src/puprisa/utils/curve_plot_utils.py +54 -0
- puprisa-1.0.0/src/puprisa/utils/geometry_utils.py +170 -0
- puprisa-1.0.0/src/puprisa/utils/range_parser_utils.py +34 -0
- puprisa-1.0.0/src/puprisa/viewmodels/__init__.py +0 -0
- puprisa-1.0.0/src/puprisa/viewmodels/curve_view_model.py +113 -0
- puprisa-1.0.0/src/puprisa/viewmodels/mask_view_model.py +97 -0
- puprisa-1.0.0/src/puprisa/viewmodels/phasor_plot_view_model.py +479 -0
- puprisa-1.0.0/src/puprisa/viewmodels/pps_plot_view_model.py +306 -0
- puprisa-1.0.0/src/puprisa/viewmodels/roi_scene_bridge.py +345 -0
- puprisa-1.0.0/src/puprisa/viewmodels/roi_view_model.py +210 -0
- puprisa-1.0.0/src/puprisa/viewmodels/stack_view_model.py +139 -0
- puprisa-1.0.0/src/puprisa.egg-info/PKG-INFO +233 -0
- puprisa-1.0.0/src/puprisa.egg-info/SOURCES.txt +91 -0
- puprisa-1.0.0/src/puprisa.egg-info/dependency_links.txt +1 -0
- puprisa-1.0.0/src/puprisa.egg-info/entry_points.txt +2 -0
- puprisa-1.0.0/src/puprisa.egg-info/requires.txt +7 -0
- puprisa-1.0.0/src/puprisa.egg-info/top_level.txt +1 -0
- 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)
|
puprisa-1.0.0/README.md
ADDED
|
@@ -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"]
|
puprisa-1.0.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -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
|