flimkit 0.13.4__tar.gz → 0.13.6__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.
- {flimkit-0.13.4 → flimkit-0.13.6}/PKG-INFO +15 -4
- {flimkit-0.13.4 → flimkit-0.13.6}/README.md +14 -3
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/FLIM/batch.py +18 -2
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/fov_preview.py +98 -10
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/gui.py +340 -26
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/irf_widget.py +15 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/modes/batch_mode.py +1 -34
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/modes/fov_mode.py +2 -3
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/modes/stitch_mode.py +2 -30
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/phasor_panel.py +135 -148
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/project_panel.py +7 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/roi_tools.py +34 -34
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/_version.py +4 -2
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/configs.py +21 -1
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/PTU/reader.py +11 -10
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/interactive.py +19 -3
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/phasor_launcher.py +45 -9
- flimkit-0.13.6/flimkit/utils/apply_settings.py +89 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/display.py +27 -3
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit.egg-info/PKG-INFO +15 -4
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit.egg-info/SOURCES.txt +1 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/LICENSE.md +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/FLIM/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/FLIM/assemble.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/FLIM/bg_tools.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/FLIM/fit_tools.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/FLIM/fitters.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/FLIM/irf_tools.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/FLIM/models.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/GPU/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/GPU/_base.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/GPU/cuda.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/GPU/mlx_backend.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/GPU/mps.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/GPU/rocm.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/GPU/torch_backend.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/app_state.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/controller.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/expert_settings.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/fit_help.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/icon.icns +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/icon.ico +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/icon.png +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/mode_controller.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/modes/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/modes/base.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/modes/irf_mode.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/modes/phasor_mode.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/progress_window.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/results_panel.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/UI/utils.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/cli.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/dialogs.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/BH/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/BH/reader.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/BH/writer.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/ISS/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/ISS/fdflim.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/ISS/image.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/ISS/reader.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/PS/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/PS/reader.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/PTU/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/PTU/decode.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/PTU/phu.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/PTU/series.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/PTU/stitch.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/PTU/tools.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/flim_file.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/phasor.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/formats/signal.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/image/tools.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/mpl_backend.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/phasor/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/phasor/filters.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/phasor/fret.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/phasor/interactive.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/phasor/peaks.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/phasor/signal.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/plugins/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/plugins/bindings.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/plugins/builtin/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/plugins/builtin/core_tools.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/plugins/loader.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/plugins/registry.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/project.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/synth.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/__init__.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/batch_fit.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/config_manager.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/config_snapshot.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/crash_handler.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/enhanced_outputs.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/fancy.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/lifetime_image.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/misc.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/ome_zarr.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/plotting.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/roi.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/session.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/update_check.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/xlsx_tools.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit/utils/xml_utils.py +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit.egg-info/dependency_links.txt +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit.egg-info/entry_points.txt +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit.egg-info/requires.txt +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/flimkit.egg-info/top_level.txt +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/pyproject.toml +0 -0
- {flimkit-0.13.4 → flimkit-0.13.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flimkit
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.6
|
|
4
4
|
Summary: Fluorescence lifetime imaging analysis: reading, fitting, phasors and stitching
|
|
5
5
|
Author-email: Alex Hunt <alexander.hunt@ed.ac.uk>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -133,25 +133,36 @@ python install.py --dev
|
|
|
133
133
|
|
|
134
134
|
## Docker / TrueNAS SCALE
|
|
135
135
|
|
|
136
|
-
A pre-built image is available on Docker Hub. It runs the
|
|
136
|
+
A pre-built image is available on Docker Hub. It runs FLIMKit with the [web UI](https://github.com/FLIMKit/flimkit-web-ui), so the whole app is used from a browser with nothing installed on the client.
|
|
137
137
|
|
|
138
138
|
**Pull and run:**
|
|
139
139
|
|
|
140
140
|
```bash
|
|
141
141
|
docker run -d \
|
|
142
142
|
-p 14500:14500 \
|
|
143
|
+
-e FLIMKIT_PASSWORD=choose-a-password \
|
|
143
144
|
-v /path/to/your/data:/data \
|
|
145
|
+
-v /path/to/flimkit-config:/root/.flimkit \
|
|
144
146
|
--name flimkit \
|
|
145
147
|
alex1075/flimkit:latest
|
|
146
148
|
```
|
|
147
149
|
|
|
148
|
-
Then open **http://localhost:14500** in
|
|
150
|
+
Then open **http://localhost:14500** and log in as `flimkit` with that password. Put PTU files and data in the folder you mount to `/data` and use the Browse buttons in the page to navigate there. The config mount keeps expert settings, preferences and recent files when the container is recreated.
|
|
151
|
+
|
|
152
|
+
| Variable | Default | Effect |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `FLIMKIT_PASSWORD` | not set | Password for the web UI (user `flimkit`) and for the desktop view. Without it, anyone who can reach the port can use FLIMKit and browse `/data`. |
|
|
155
|
+
| `FLIMKIT_DESKTOP` | `0` | Set to `1` to also serve the full desktop through noVNC on port 14501, for the few windows the web UI does not cover. Publish that port too. |
|
|
156
|
+
| `FLIMKIT_GEOMETRY` | `1440x900` | Size of the virtual screen FLIMKit draws on |
|
|
157
|
+
| `TZ` | `Etc/UTC` | Time zone for log timestamps |
|
|
158
|
+
|
|
159
|
+
The container reports healthy once the web UI answers on `/healthz`.
|
|
149
160
|
|
|
150
161
|
**TrueNAS SCALE (Custom App):**
|
|
151
162
|
|
|
152
163
|
1. Apps → Discover Apps → Custom App
|
|
153
164
|
2. Paste the contents of `docker/docker-compose.yaml` from this repo
|
|
154
|
-
3. Edit the volume paths to match your pool (e.g. `/mnt/tank/microscopy:/data`)
|
|
165
|
+
3. Edit the volume paths to match your pool (e.g. `/mnt/tank/microscopy:/data`) and uncomment `FLIMKIT_PASSWORD` with a password of your own
|
|
155
166
|
4. Deploy - TrueNAS will pull the image automatically
|
|
156
167
|
|
|
157
168
|
**Build from source** (required if you want to push your own changes):
|
|
@@ -71,25 +71,36 @@ python install.py --dev
|
|
|
71
71
|
|
|
72
72
|
## Docker / TrueNAS SCALE
|
|
73
73
|
|
|
74
|
-
A pre-built image is available on Docker Hub. It runs the
|
|
74
|
+
A pre-built image is available on Docker Hub. It runs FLIMKit with the [web UI](https://github.com/FLIMKit/flimkit-web-ui), so the whole app is used from a browser with nothing installed on the client.
|
|
75
75
|
|
|
76
76
|
**Pull and run:**
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
79
|
docker run -d \
|
|
80
80
|
-p 14500:14500 \
|
|
81
|
+
-e FLIMKIT_PASSWORD=choose-a-password \
|
|
81
82
|
-v /path/to/your/data:/data \
|
|
83
|
+
-v /path/to/flimkit-config:/root/.flimkit \
|
|
82
84
|
--name flimkit \
|
|
83
85
|
alex1075/flimkit:latest
|
|
84
86
|
```
|
|
85
87
|
|
|
86
|
-
Then open **http://localhost:14500** in
|
|
88
|
+
Then open **http://localhost:14500** and log in as `flimkit` with that password. Put PTU files and data in the folder you mount to `/data` and use the Browse buttons in the page to navigate there. The config mount keeps expert settings, preferences and recent files when the container is recreated.
|
|
89
|
+
|
|
90
|
+
| Variable | Default | Effect |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `FLIMKIT_PASSWORD` | not set | Password for the web UI (user `flimkit`) and for the desktop view. Without it, anyone who can reach the port can use FLIMKit and browse `/data`. |
|
|
93
|
+
| `FLIMKIT_DESKTOP` | `0` | Set to `1` to also serve the full desktop through noVNC on port 14501, for the few windows the web UI does not cover. Publish that port too. |
|
|
94
|
+
| `FLIMKIT_GEOMETRY` | `1440x900` | Size of the virtual screen FLIMKit draws on |
|
|
95
|
+
| `TZ` | `Etc/UTC` | Time zone for log timestamps |
|
|
96
|
+
|
|
97
|
+
The container reports healthy once the web UI answers on `/healthz`.
|
|
87
98
|
|
|
88
99
|
**TrueNAS SCALE (Custom App):**
|
|
89
100
|
|
|
90
101
|
1. Apps → Discover Apps → Custom App
|
|
91
102
|
2. Paste the contents of `docker/docker-compose.yaml` from this repo
|
|
92
|
-
3. Edit the volume paths to match your pool (e.g. `/mnt/tank/microscopy:/data`)
|
|
103
|
+
3. Edit the volume paths to match your pool (e.g. `/mnt/tank/microscopy:/data`) and uncomment `FLIMKIT_PASSWORD` with a password of your own
|
|
93
104
|
4. Deploy - TrueNAS will pull the image automatically
|
|
94
105
|
|
|
95
106
|
**Build from source** (required if you want to push your own changes):
|
|
@@ -20,6 +20,18 @@ from ..utils.batch_fit import (
|
|
|
20
20
|
save_tile_lifetime_txt, save_map_stacks, plot_metric_summary,
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
+
def _window_kwargs(args):
|
|
24
|
+
from ..interactive import parse_exclude_ns
|
|
25
|
+
return dict(
|
|
26
|
+
irf_shift_bins=getattr(args, 'irf_shift_bins', 2),
|
|
27
|
+
fit_start_ns=getattr(args, 'fit_start_ns', None),
|
|
28
|
+
fit_end_ns=getattr(args, 'fit_end_ns', None),
|
|
29
|
+
exclude_ns=parse_exclude_ns(getattr(args, 'exclude_ns', None)),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def _binning(args):
|
|
33
|
+
return max(1, int(getattr(args, 'binning', 1) or 1))
|
|
34
|
+
|
|
23
35
|
def _save_4d_stacks(frame_positions, group_dir, group_label):
|
|
24
36
|
sorted_t = sorted(frame_positions.keys())
|
|
25
37
|
all_s = sorted({s for pos in frame_positions.values() for s in pos})
|
|
@@ -122,6 +134,7 @@ def fit_timelapse(ptu_dir, output_dir, args,
|
|
|
122
134
|
polish=not getattr(args, 'no_polish', False),
|
|
123
135
|
cost_function=getattr(args, 'cost_function', 'poisson'),
|
|
124
136
|
sigma_max=sigma_max,
|
|
137
|
+
**_window_kwargs(args),
|
|
125
138
|
)
|
|
126
139
|
print(f' Reference fit: {time.time() - t0:.1f} s')
|
|
127
140
|
taus_ns = global_summary.get('taus_ns', global_popt[:args.nexp] * 1e9)
|
|
@@ -163,7 +176,7 @@ def fit_timelapse(ptu_dir, output_dir, args,
|
|
|
163
176
|
print(f'\n t={t} s={s}: {ptu_path.name}')
|
|
164
177
|
t_start = time.time()
|
|
165
178
|
ptu = FLIMFile(str(ptu_path), verbose=False)
|
|
166
|
-
pixel_stack = ptu.raw_pixel_stack(channel=channel)
|
|
179
|
+
pixel_stack = ptu.raw_pixel_stack(channel=channel, binning=_binning(args))
|
|
167
180
|
if pixel_stack.shape[2] != n_bins:
|
|
168
181
|
nb = pixel_stack.shape[2]
|
|
169
182
|
if nb > n_bins:
|
|
@@ -229,6 +242,7 @@ def fit_timelapse(ptu_dir, output_dir, args,
|
|
|
229
242
|
polish=not getattr(args, 'no_polish', False),
|
|
230
243
|
cost_function=getattr(args, 'cost_function', 'poisson'),
|
|
231
244
|
sigma_max=sigma_max,
|
|
245
|
+
**_window_kwargs(args),
|
|
232
246
|
)
|
|
233
247
|
plot_summed(
|
|
234
248
|
tile_decay, tile_summary, ptu, None,
|
|
@@ -430,6 +444,7 @@ def fit_zstack(ptu_dir, output_dir, args,
|
|
|
430
444
|
polish=not getattr(args, 'no_polish', False),
|
|
431
445
|
cost_function=getattr(args, 'cost_function', 'poisson'),
|
|
432
446
|
sigma_max=sigma_max,
|
|
447
|
+
**_window_kwargs(args),
|
|
433
448
|
)
|
|
434
449
|
print(f' Reference fit: {time.time() - t0:.1f} s')
|
|
435
450
|
taus_ns = global_summary.get('taus_ns', global_popt[:args.nexp] * 1e9)
|
|
@@ -489,7 +504,7 @@ def fit_zstack(ptu_dir, output_dir, args,
|
|
|
489
504
|
slice_dir = group_dir / f'z{z:04d}'
|
|
490
505
|
slice_dir.mkdir(exist_ok=True)
|
|
491
506
|
ptu = FLIMFile(str(ptu_path), verbose=False)
|
|
492
|
-
pixel_stack = ptu.raw_pixel_stack(channel=channel)
|
|
507
|
+
pixel_stack = ptu.raw_pixel_stack(channel=channel, binning=_binning(args))
|
|
493
508
|
if pixel_stack.shape[2] != n_bins:
|
|
494
509
|
nb = pixel_stack.shape[2]
|
|
495
510
|
if nb > n_bins:
|
|
@@ -555,6 +570,7 @@ def fit_zstack(ptu_dir, output_dir, args,
|
|
|
555
570
|
polish=not getattr(args, 'no_polish', False),
|
|
556
571
|
cost_function=getattr(args, 'cost_function', 'poisson'),
|
|
557
572
|
sigma_max=sigma_max,
|
|
573
|
+
**_window_kwargs(args),
|
|
558
574
|
)
|
|
559
575
|
plot_summed(
|
|
560
576
|
tile_decay, tile_summary, ptu, None,
|
|
@@ -86,7 +86,7 @@ class FOVPreviewPanel:
|
|
|
86
86
|
state='readonly', width=10)
|
|
87
87
|
self._cmap_combo.grid(row=1, column=4, sticky='w', padx=2)
|
|
88
88
|
self._cmap_combo['values'] = list(display.COLORMAPS.keys())
|
|
89
|
-
ttk.Button(ctrl_frame, text='Update', width=8, command=self.
|
|
89
|
+
ttk.Button(ctrl_frame, text='Update', width=8, command=self._on_update_display).grid(row=1, column=5, sticky='w', padx=2)
|
|
90
90
|
self._bv_show_decay = tk.BooleanVar(value=True)
|
|
91
91
|
ttk.Checkbutton(ctrl_frame, text='Show Decay Plot',
|
|
92
92
|
variable=self._bv_show_decay,
|
|
@@ -108,6 +108,22 @@ class FOVPreviewPanel:
|
|
|
108
108
|
value='amplitude', command=self._on_weighting_changed).pack(side='left')
|
|
109
109
|
ttk.Radiobutton(wt_frame, text='Intensity', variable=self._sv_tau_weighting,
|
|
110
110
|
value='intensity', command=self._on_weighting_changed).pack(side='left')
|
|
111
|
+
int_frame = ttk.Frame(ctrl_frame)
|
|
112
|
+
int_frame.grid(row=4, column=0, columnspan=6, sticky='w', pady=(4, 0))
|
|
113
|
+
ttk.Label(int_frame, text='Intensity:').pack(side='left', padx=(0, 4))
|
|
114
|
+
ttk.Label(int_frame, text='Min').pack(side='left')
|
|
115
|
+
self._sv_int_min = tk.StringVar()
|
|
116
|
+
ttk.Entry(int_frame, textvariable=self._sv_int_min, width=7).pack(side='left', padx=2)
|
|
117
|
+
ttk.Label(int_frame, text='Max').pack(side='left', padx=(6, 0))
|
|
118
|
+
self._sv_int_max = tk.StringVar()
|
|
119
|
+
ttk.Entry(int_frame, textvariable=self._sv_int_max, width=7).pack(side='left', padx=2)
|
|
120
|
+
ttk.Button(int_frame, text='Auto', width=6,
|
|
121
|
+
command=self._auto_intensity_scale).pack(side='left', padx=2)
|
|
122
|
+
self._sv_int_cmap = tk.StringVar(value='inferno')
|
|
123
|
+
int_combo = ttk.Combobox(int_frame, textvariable=self._sv_int_cmap, state='readonly',
|
|
124
|
+
width=9, values=display.INTENSITY_COLORMAPS)
|
|
125
|
+
int_combo.pack(side='left', padx=(6, 2))
|
|
126
|
+
int_combo.bind('<<ComboboxSelected>>', lambda _e: self._on_update_display())
|
|
111
127
|
self._ptu_path = None
|
|
112
128
|
self._lifetime_map = None
|
|
113
129
|
self._pixel_maps = None
|
|
@@ -119,6 +135,7 @@ class FOVPreviewPanel:
|
|
|
119
135
|
'gamma': 1.0,
|
|
120
136
|
'cmap': 'viridis',
|
|
121
137
|
}
|
|
138
|
+
self._int_display = {'vmin': None, 'vmax': None, 'cmap': 'inferno'}
|
|
122
139
|
self._n_exp = 1
|
|
123
140
|
self._irf_prompt = None
|
|
124
141
|
self._roi_manager = RoiManager()
|
|
@@ -140,6 +157,7 @@ class FOVPreviewPanel:
|
|
|
140
157
|
if not _keep_zstack:
|
|
141
158
|
self._hide_zstack()
|
|
142
159
|
self._lifetime_map = None
|
|
160
|
+
self._intensity_map = None
|
|
143
161
|
self._pixel_maps = None
|
|
144
162
|
self._cached_resid_data = None
|
|
145
163
|
if not ptu_path or not Path(ptu_path).exists():
|
|
@@ -158,8 +176,8 @@ class FOVPreviewPanel:
|
|
|
158
176
|
if is_image:
|
|
159
177
|
stack = ptu.pixel_stack(channel=None, binning=1)
|
|
160
178
|
intensity = stack.sum(axis=2)
|
|
161
|
-
|
|
162
|
-
self._ax_img
|
|
179
|
+
self._intensity_map = intensity
|
|
180
|
+
self._draw_intensity(self._ax_img, intensity)
|
|
163
181
|
else:
|
|
164
182
|
intensity = None
|
|
165
183
|
self._ax_img.text(0.5, 0.5, 'Point measurement\nno image to reconstruct',
|
|
@@ -187,6 +205,9 @@ class FOVPreviewPanel:
|
|
|
187
205
|
self._ax_resid.grid(True, alpha=0.3)
|
|
188
206
|
self._cached_resid_data = None
|
|
189
207
|
self._redraw_region_overlays()
|
|
208
|
+
if is_image:
|
|
209
|
+
self._sync_intensity_controls()
|
|
210
|
+
self._ctrl_frame.grid()
|
|
190
211
|
self._canvas_mpl.draw_idle()
|
|
191
212
|
n_photons = int(decay.sum())
|
|
192
213
|
if is_image:
|
|
@@ -275,8 +296,7 @@ class FOVPreviewPanel:
|
|
|
275
296
|
taus_fit = global_summary.get('taus_ns', [])
|
|
276
297
|
model = global_summary.get('model')
|
|
277
298
|
self._ax_img.clear()
|
|
278
|
-
|
|
279
|
-
self._ax_img.imshow(intensity_clipped, cmap='inferno', origin='upper')
|
|
299
|
+
self._draw_intensity(self._ax_img, intensity)
|
|
280
300
|
self._ax_img.set_title('Intensity', fontsize=9, fontweight='bold', color='white')
|
|
281
301
|
self._strip_image_axes(self._ax_img)
|
|
282
302
|
self._ax_flim.clear()
|
|
@@ -492,8 +512,7 @@ class FOVPreviewPanel:
|
|
|
492
512
|
return
|
|
493
513
|
intensity = tifffile.imread(str(intensity_files[0]))
|
|
494
514
|
self._ax_img.clear()
|
|
495
|
-
|
|
496
|
-
self._ax_img.imshow(intensity_clipped, cmap='inferno', origin='upper')
|
|
515
|
+
self._draw_intensity(self._ax_img, intensity)
|
|
497
516
|
self._ax_img.set_title('Stitched ROI', fontsize=9, fontweight='bold', color='white')
|
|
498
517
|
self._strip_image_axes(self._ax_img)
|
|
499
518
|
lifetime_data = None
|
|
@@ -595,6 +614,75 @@ class FOVPreviewPanel:
|
|
|
595
614
|
pass
|
|
596
615
|
self._lifetime_map = lifetime_map
|
|
597
616
|
self._update_flim_display()
|
|
617
|
+
def _intensity_limits(self, intensity):
|
|
618
|
+
import numpy as np
|
|
619
|
+
lo = self._int_display.get('vmin')
|
|
620
|
+
hi = self._int_display.get('vmax')
|
|
621
|
+
if lo is None:
|
|
622
|
+
lo = 0.0
|
|
623
|
+
if hi is None:
|
|
624
|
+
finite = np.asarray(intensity)[np.isfinite(intensity)]
|
|
625
|
+
hi = float(np.percentile(finite, 99)) if finite.size > 0 else 1.0
|
|
626
|
+
if hi <= lo:
|
|
627
|
+
hi = lo + 1.0
|
|
628
|
+
return lo, hi
|
|
629
|
+
|
|
630
|
+
def _draw_intensity(self, ax, intensity):
|
|
631
|
+
lo, hi = self._intensity_limits(intensity)
|
|
632
|
+
cmap = display.get_colormap(self._int_display.get('cmap', 'inferno'))
|
|
633
|
+
return ax.imshow(intensity, cmap=cmap, origin='upper', vmin=lo, vmax=hi)
|
|
634
|
+
|
|
635
|
+
def _read_intensity_controls(self):
|
|
636
|
+
def num(var):
|
|
637
|
+
text = var.get().strip()
|
|
638
|
+
if text == '':
|
|
639
|
+
return None
|
|
640
|
+
try:
|
|
641
|
+
return float(text)
|
|
642
|
+
except ValueError:
|
|
643
|
+
return None
|
|
644
|
+
self._int_display = {
|
|
645
|
+
'vmin': num(self._sv_int_min),
|
|
646
|
+
'vmax': num(self._sv_int_max),
|
|
647
|
+
'cmap': self._sv_int_cmap.get() or 'inferno',
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
def _sync_intensity_controls(self):
|
|
651
|
+
cs = self._int_display
|
|
652
|
+
self._sv_int_min.set('' if cs.get('vmin') is None else f"{cs['vmin']:g}")
|
|
653
|
+
self._sv_int_max.set('' if cs.get('vmax') is None else f"{cs['vmax']:g}")
|
|
654
|
+
self._sv_int_cmap.set(cs.get('cmap', 'inferno'))
|
|
655
|
+
|
|
656
|
+
def _sync_flim_controls(self):
|
|
657
|
+
cs = self._flim_color_scale
|
|
658
|
+
self._sv_tau_min.set('' if cs.get('vmin') is None else f"{cs['vmin']:.2f}")
|
|
659
|
+
self._sv_tau_max.set('' if cs.get('vmax') is None else f"{cs['vmax']:.2f}")
|
|
660
|
+
self._sv_gamma.set(str(cs.get('gamma', 1.0)))
|
|
661
|
+
self._sv_cmap.set(cs.get('cmap', 'viridis'))
|
|
662
|
+
|
|
663
|
+
def _update_intensity_display(self):
|
|
664
|
+
if self._intensity_map is None or not self._ax_img.get_visible():
|
|
665
|
+
return
|
|
666
|
+
title = self._ax_img.get_title() or 'Intensity'
|
|
667
|
+
self._ax_img.clear()
|
|
668
|
+
self._draw_intensity(self._ax_img, self._intensity_map)
|
|
669
|
+
self._ax_img.set_title(title, fontsize=9, fontweight='bold', color='white')
|
|
670
|
+
self._strip_image_axes(self._ax_img)
|
|
671
|
+
self._redraw_region_overlays()
|
|
672
|
+
self._canvas_mpl.draw_idle()
|
|
673
|
+
|
|
674
|
+
def _auto_intensity_scale(self):
|
|
675
|
+
self._sv_int_min.set('')
|
|
676
|
+
self._sv_int_max.set('')
|
|
677
|
+
self._on_update_display()
|
|
678
|
+
|
|
679
|
+
def _on_update_display(self):
|
|
680
|
+
self._read_intensity_controls()
|
|
681
|
+
self._update_intensity_display()
|
|
682
|
+
self._update_flim_display()
|
|
683
|
+
if self._lifetime_map is None:
|
|
684
|
+
self._save_color_scale_update()
|
|
685
|
+
|
|
598
686
|
def _auto_detect_scale(self):
|
|
599
687
|
import numpy as np
|
|
600
688
|
if self._lifetime_map is None:
|
|
@@ -677,6 +765,7 @@ class FOVPreviewPanel:
|
|
|
677
765
|
session_data = {key: existing_data[key].item() if existing_data[key].ndim == 0 else existing_data[key]
|
|
678
766
|
for key in existing_data.files}
|
|
679
767
|
session_data['fov_color_scale'] = json.dumps(self._flim_color_scale)
|
|
768
|
+
session_data['fov_intensity_scale'] = json.dumps(self._int_display)
|
|
680
769
|
np.savez_compressed(session_file, **session_data)
|
|
681
770
|
print(f"[Color Scale] ✓ Saved to {session_file.name}")
|
|
682
771
|
except Exception as e:
|
|
@@ -706,6 +795,7 @@ class FOVPreviewPanel:
|
|
|
706
795
|
if self._intensity_map is not None:
|
|
707
796
|
session_data['fov_intensity_map'] = self._intensity_map
|
|
708
797
|
session_data['fov_color_scale'] = json.dumps(self._flim_color_scale)
|
|
798
|
+
session_data['fov_intensity_scale'] = json.dumps(self._int_display)
|
|
709
799
|
session_data['fov_n_exp'] = self._n_exp
|
|
710
800
|
if self._ptu_path:
|
|
711
801
|
session_data['fov_ptu_path'] = self._ptu_path
|
|
@@ -927,9 +1017,7 @@ class FOVPreviewPanel:
|
|
|
927
1017
|
_ax.set_facecolor('black')
|
|
928
1018
|
if self._ax_img.get_visible() and self._intensity_map is not None:
|
|
929
1019
|
import numpy as np
|
|
930
|
-
|
|
931
|
-
np.percentile(self._intensity_map, 99))
|
|
932
|
-
self._ax_img.imshow(intensity_clipped, cmap='inferno', origin='upper')
|
|
1020
|
+
self._draw_intensity(self._ax_img, self._intensity_map)
|
|
933
1021
|
self._ax_img.set_title(img_title, fontsize=9, fontweight='bold', color='white')
|
|
934
1022
|
self._strip_image_axes(self._ax_img)
|
|
935
1023
|
elif self._ax_img.get_visible():
|