flimkit 0.13.7__tar.gz → 0.13.8__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.7 → flimkit-0.13.8}/PKG-INFO +1 -1
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/FLIM/assemble.py +44 -98
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/FLIM/batch.py +15 -4
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/FLIM/irf_tools.py +78 -65
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/fov_preview.py +97 -99
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/gui.py +59 -47
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/_version.py +3 -2
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/PTU/stitch.py +70 -55
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/interactive.py +15 -9
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/project.py +17 -1
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/display.py +58 -3
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/enhanced_outputs.py +55 -122
- flimkit-0.13.8/flimkit/utils/export_png.py +83 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/lifetime_image.py +37 -85
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit.egg-info/PKG-INFO +1 -1
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit.egg-info/SOURCES.txt +1 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/LICENSE.md +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/README.md +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/FLIM/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/FLIM/bg_tools.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/FLIM/fit_tools.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/FLIM/fitters.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/FLIM/models.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/GPU/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/GPU/_base.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/GPU/cuda.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/GPU/mlx_backend.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/GPU/mps.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/GPU/rocm.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/GPU/torch_backend.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/app_state.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/controller.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/expert_settings.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/fit_help.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/icon.icns +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/icon.ico +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/icon.png +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/irf_widget.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/mode_controller.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/modes/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/modes/base.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/modes/batch_mode.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/modes/fov_mode.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/modes/irf_mode.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/modes/phasor_mode.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/modes/stitch_mode.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/phasor_panel.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/progress_window.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/project_panel.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/results_panel.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/roi_tools.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/UI/utils.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/cli.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/configs.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/dialogs.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/BH/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/BH/reader.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/BH/writer.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/ISS/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/ISS/fdflim.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/ISS/image.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/ISS/reader.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/PS/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/PS/reader.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/PTU/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/PTU/decode.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/PTU/phu.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/PTU/reader.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/PTU/series.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/PTU/tools.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/flim_file.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/phasor.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/formats/signal.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/image/tools.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/mpl_backend.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/phasor/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/phasor/filters.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/phasor/fret.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/phasor/interactive.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/phasor/peaks.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/phasor/signal.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/phasor_launcher.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/plugins/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/plugins/bindings.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/plugins/builtin/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/plugins/builtin/core_tools.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/plugins/loader.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/plugins/registry.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/synth.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/__init__.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/apply_settings.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/batch_fit.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/config_manager.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/config_snapshot.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/crash_handler.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/fancy.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/misc.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/ome_zarr.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/plotting.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/roi.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/session.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/update_check.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/xlsx_tools.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit/utils/xml_utils.py +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit.egg-info/dependency_links.txt +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit.egg-info/entry_points.txt +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit.egg-info/requires.txt +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/flimkit.egg-info/top_level.txt +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/pyproject.toml +0 -0
- {flimkit-0.13.7 → flimkit-0.13.8}/setup.cfg +0 -0
|
@@ -2,7 +2,7 @@ import numpy as np
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import List, Dict, Any, Optional
|
|
4
4
|
import tifffile
|
|
5
|
-
|
|
5
|
+
from flimkit.utils.export_png import tiff_resolution
|
|
6
6
|
|
|
7
7
|
class Cancelled(Exception):
|
|
8
8
|
pass
|
|
@@ -15,48 +15,35 @@ def assemble_tile_maps(
|
|
|
15
15
|
cancel_event=None,
|
|
16
16
|
) -> Dict[str, np.ndarray]:
|
|
17
17
|
H, W = canvas_height, canvas_width
|
|
18
|
-
|
|
19
18
|
keys_scalar = ['tau_mean_amp', 'tau_mean_int', 'chi2', 'calibrated_chi2_r']
|
|
20
|
-
keys_tau
|
|
21
|
-
keys_amp
|
|
22
|
-
keys_all
|
|
23
|
-
|
|
24
|
-
# Build nearest-centre ownership map
|
|
25
|
-
# For every canvas pixel, record the index of the tile whose centre is
|
|
26
|
-
# closest. Ties broken by later tiles (last write wins, doesn't matter).
|
|
27
|
-
# Use squared Euclidean distance - no sqrt needed for comparison.
|
|
19
|
+
keys_tau = [f'tau{k}' for k in range(1, n_exp + 1)]
|
|
20
|
+
keys_amp = [f'a{k}' for k in range(1, n_exp + 1)]
|
|
21
|
+
keys_all = keys_scalar + keys_tau + keys_amp
|
|
28
22
|
min_dist2 = np.full((H, W), np.inf, dtype=np.float64)
|
|
29
|
-
owner
|
|
30
|
-
|
|
23
|
+
owner = np.full((H, W), -1, dtype=np.int32)
|
|
31
24
|
for ti, tr in enumerate(tile_results):
|
|
32
25
|
if cancel_event is not None and cancel_event.is_set():
|
|
33
26
|
raise Cancelled('cancelled while mapping tile ownership')
|
|
34
27
|
if tr.get('pixel_maps') is None:
|
|
35
28
|
continue
|
|
36
29
|
y0, x0 = tr['pixel_y'], tr['pixel_x']
|
|
37
|
-
th, tw
|
|
38
|
-
y1
|
|
39
|
-
x1
|
|
40
|
-
|
|
30
|
+
th, tw = tr['tile_h'], tr['tile_w']
|
|
31
|
+
y1 = min(y0 + th, H)
|
|
32
|
+
x1 = min(x0 + tw, W)
|
|
41
33
|
cy = y0 + th / 2.0
|
|
42
34
|
cx = x0 + tw / 2.0
|
|
43
|
-
|
|
44
35
|
rows = np.arange(y0, y1, dtype=np.float64)
|
|
45
36
|
cols = np.arange(x0, x1, dtype=np.float64)
|
|
46
|
-
dy2
|
|
47
|
-
dx2
|
|
37
|
+
dy2 = (rows - cy) ** 2
|
|
38
|
+
dx2 = (cols - cx) ** 2
|
|
48
39
|
dist2 = dy2[:, np.newaxis] + dx2
|
|
49
|
-
|
|
50
|
-
# Only take ownership where this tile is strictly closer
|
|
51
40
|
region = min_dist2[y0:y1, x0:x1]
|
|
52
41
|
closer = dist2 < region
|
|
53
42
|
min_dist2[y0:y1, x0:x1] = np.where(closer, dist2, region)
|
|
54
|
-
owner[y0:y1, x0:x1]
|
|
55
|
-
|
|
43
|
+
owner[y0:y1, x0:x1] = np.where(closer, ti, owner[y0:y1, x0:x1])
|
|
56
44
|
canvas = {k: np.full((H, W), np.nan, dtype=np.float32) for k in keys_all}
|
|
57
45
|
intensity_canvas = np.zeros((H, W), dtype=np.float32)
|
|
58
|
-
coverage
|
|
59
|
-
|
|
46
|
+
coverage = np.zeros((H, W), dtype=np.uint16)
|
|
60
47
|
for ti, tr in enumerate(tile_results):
|
|
61
48
|
if cancel_event is not None and cancel_event.is_set():
|
|
62
49
|
raise Cancelled('cancelled while assembling the canvas')
|
|
@@ -64,89 +51,72 @@ def assemble_tile_maps(
|
|
|
64
51
|
if pm is None:
|
|
65
52
|
continue
|
|
66
53
|
y0, x0 = tr['pixel_y'], tr['pixel_x']
|
|
67
|
-
th, tw
|
|
54
|
+
th, tw = tr['tile_h'], tr['tile_w']
|
|
68
55
|
y1 = min(y0 + th, H)
|
|
69
56
|
x1 = min(x0 + tw, W)
|
|
70
57
|
dy, dx = y1 - y0, x1 - x0
|
|
71
|
-
|
|
72
58
|
owned = (owner[y0:y1, x0:x1] == ti)
|
|
73
|
-
|
|
74
59
|
tile_int = np.asarray(
|
|
75
60
|
pm.get('intensity', np.zeros((th, tw), dtype=np.float32)),
|
|
76
61
|
dtype=np.float32)[:dy, :dx]
|
|
77
|
-
|
|
78
62
|
intensity_canvas[y0:y1, x0:x1] = np.where(
|
|
79
63
|
owned, tile_int, intensity_canvas[y0:y1, x0:x1])
|
|
80
64
|
coverage[y0:y1, x0:x1] = np.where(
|
|
81
65
|
owned, coverage[y0:y1, x0:x1] + 1, coverage[y0:y1, x0:x1])
|
|
82
|
-
|
|
83
66
|
for key in keys_all:
|
|
84
67
|
raw = pm.get(key)
|
|
85
68
|
if raw is None:
|
|
86
69
|
continue
|
|
87
70
|
raw = np.asarray(raw, dtype=np.float32)[:dy, :dx]
|
|
88
|
-
# Only place fitted (finite) pixels that this tile owns
|
|
89
71
|
place = owned & np.isfinite(raw)
|
|
90
72
|
canvas[key][y0:y1, x0:x1] = np.where(
|
|
91
73
|
place, raw, canvas[key][y0:y1, x0:x1])
|
|
92
|
-
|
|
93
74
|
canvas['intensity'] = intensity_canvas
|
|
94
|
-
canvas['coverage']
|
|
95
|
-
|
|
75
|
+
canvas['coverage'] = coverage.astype(np.float32)
|
|
96
76
|
n_covered = int((owner >= 0).sum())
|
|
97
77
|
print(
|
|
98
|
-
f
|
|
99
|
-
f
|
|
100
|
-
f
|
|
78
|
+
f' Canvas {H}×{W} | tiles={len(tile_results)} '
|
|
79
|
+
f'| covered={n_covered:,} px '
|
|
80
|
+
f'| ownership map built (nearest-centre, no blending)'
|
|
101
81
|
)
|
|
102
|
-
|
|
103
82
|
return canvas
|
|
104
83
|
|
|
105
|
-
|
|
106
84
|
def derive_global_tau(
|
|
107
85
|
canvas: Dict[str, np.ndarray],
|
|
108
86
|
n_exp: int,
|
|
109
87
|
tissue_mask: Optional[np.ndarray] = None,
|
|
110
88
|
) -> Dict[str, Any]:
|
|
111
89
|
tau_arrays = [canvas[f'tau{k}'] for k in range(1, n_exp + 1)]
|
|
112
|
-
a_arrays
|
|
113
|
-
|
|
90
|
+
a_arrays = [canvas[f'a{k}'] for k in range(1, n_exp + 1)]
|
|
114
91
|
total_amp = np.zeros_like(tau_arrays[0])
|
|
115
|
-
tau_mean
|
|
92
|
+
tau_mean = np.zeros_like(tau_arrays[0])
|
|
116
93
|
for tau_k, a_k in zip(tau_arrays, a_arrays):
|
|
117
94
|
valid = np.isfinite(tau_k) & np.isfinite(a_k)
|
|
118
|
-
tau_mean
|
|
95
|
+
tau_mean = np.where(valid, tau_mean + tau_k * a_k, tau_mean)
|
|
119
96
|
total_amp = np.where(valid, total_amp + a_k, total_amp)
|
|
120
|
-
|
|
121
97
|
with np.errstate(invalid='ignore', divide='ignore'):
|
|
122
98
|
tau_mean_amp = np.where(total_amp > 0, tau_mean / total_amp, np.nan)
|
|
123
|
-
|
|
124
99
|
valid_px = np.isfinite(tau_mean_amp)
|
|
125
100
|
if tissue_mask is not None:
|
|
126
101
|
valid_px = valid_px & tissue_mask
|
|
127
|
-
|
|
128
102
|
if not valid_px.any():
|
|
129
103
|
return {'error': 'No valid fitted pixels found'}
|
|
130
|
-
|
|
131
104
|
summary = {
|
|
132
105
|
'n_pixels_fitted': int(valid_px.sum()),
|
|
133
106
|
'tau_mean_amp_global_ns': float(np.nanmean(tau_mean_amp[valid_px])),
|
|
134
107
|
'tau_std_amp_global_ns': float(np.nanstd(tau_mean_amp[valid_px])),
|
|
135
108
|
'tau_median_amp_global_ns': float(np.nanmedian(tau_mean_amp[valid_px])),
|
|
136
109
|
}
|
|
137
|
-
|
|
138
110
|
for k, (tau_k, a_k) in enumerate(zip(tau_arrays, a_arrays), start=1):
|
|
139
111
|
valid_k = np.isfinite(tau_k) & np.isfinite(a_k) & valid_px
|
|
140
112
|
if valid_k.any():
|
|
141
113
|
summary[f'tau{k}_mean_ns'] = float(
|
|
142
114
|
np.average(tau_k[valid_k], weights=a_k[valid_k]))
|
|
143
|
-
summary[f'tau{k}_std_ns']
|
|
115
|
+
summary[f'tau{k}_std_ns'] = float(np.nanstd(tau_k[valid_k]))
|
|
144
116
|
summary[f'a{k}_mean_frac'] = float(
|
|
145
117
|
np.nanmean(a_k[valid_k] / total_amp[valid_k]))
|
|
146
|
-
|
|
147
118
|
return summary
|
|
148
119
|
|
|
149
|
-
|
|
150
120
|
def save_assembled_maps(
|
|
151
121
|
canvas: Dict[str, np.ndarray],
|
|
152
122
|
global_summary: Dict[str, Any],
|
|
@@ -158,28 +128,18 @@ def save_assembled_maps(
|
|
|
158
128
|
intensity_display_min: Optional[float] = None,
|
|
159
129
|
intensity_display_max: Optional[float] = None,
|
|
160
130
|
tau_weighting: str = 'amp',
|
|
131
|
+
field_area_um2: Optional[float] = None,
|
|
161
132
|
):
|
|
162
133
|
output_dir = Path(output_dir)
|
|
163
134
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
164
|
-
|
|
165
|
-
# Raw .npy maps are saved for every canvas key (both tau_mean_amp and
|
|
166
|
-
# tau_mean_int), unclipped. tau_weighting only selects which one becomes
|
|
167
|
-
# the display tau TIFF below.
|
|
168
135
|
for key, arr in canvas.items():
|
|
169
|
-
np.save(str(output_dir / f
|
|
170
|
-
|
|
136
|
+
np.save(str(output_dir / f'{roi_name}_{key}.npy'), arr)
|
|
171
137
|
tau_key = 'tau_mean_int' if tau_weighting == 'int' else 'tau_mean_amp'
|
|
172
138
|
if tau_key not in canvas:
|
|
173
139
|
tau_key = 'tau_mean_amp'
|
|
174
|
-
|
|
175
|
-
# Use percentile-based clipping so a handful of very bright pixels (cell
|
|
176
|
-
# clusters, tile-overlap edges) do not compress the bulk of tissue to
|
|
177
|
-
# near-zero. Display range overrides take priority when provided.
|
|
178
140
|
intensity = canvas['intensity']
|
|
179
|
-
pos
|
|
141
|
+
pos = intensity[intensity > 0]
|
|
180
142
|
if pos.size > 0:
|
|
181
|
-
# lo=0 so dim tissue is never clipped to black.
|
|
182
|
-
# hi=99.5th percentile clips only the top 0.5% of bright outliers.
|
|
183
143
|
i_min = float(intensity_display_min) if intensity_display_min is not None else 0.0
|
|
184
144
|
i_max = float(intensity_display_max) if intensity_display_max is not None else float(np.percentile(pos, 99.0))
|
|
185
145
|
i_max = max(i_max, i_min + 1e-6)
|
|
@@ -190,65 +150,51 @@ def save_assembled_maps(
|
|
|
190
150
|
else:
|
|
191
151
|
intensity_scaled = np.zeros_like(intensity, dtype=np.uint16)
|
|
192
152
|
i_min, i_max = 0.0, 0.0
|
|
193
|
-
tifffile.imwrite(str(output_dir / f
|
|
194
|
-
print(f
|
|
195
|
-
|
|
196
|
-
# tau_mean_amp TIFF (uint16)
|
|
197
|
-
# 0 → tau_min (or 0 if auto)
|
|
198
|
-
# 65535 → tau_max (or nanmax if auto)
|
|
199
|
-
# 0 → unfitted pixels (NaN → 0 before cast)
|
|
200
|
-
# This matches the FLIM microscope export convention.
|
|
153
|
+
tifffile.imwrite(str(output_dir / f'{roi_name}_intensity.tif'), intensity_scaled, **tiff_resolution(intensity_scaled, field_area_um2))
|
|
154
|
+
print(f' intensity display range: {i_min:.1f} - {i_max:.1f} counts')
|
|
201
155
|
tau_map = canvas[tau_key]
|
|
202
|
-
finite
|
|
203
|
-
|
|
156
|
+
finite = np.isfinite(tau_map)
|
|
204
157
|
t_min = float(tau_display_min) if tau_display_min is not None \
|
|
205
158
|
else float(np.nanpercentile(tau_map[finite], 0.5) if finite.any() else 0.0)
|
|
206
159
|
t_max = float(tau_display_max) if tau_display_max is not None \
|
|
207
160
|
else float(np.nanpercentile(tau_map[finite], 99.5) if finite.any() else 1.0)
|
|
208
161
|
t_max = max(t_max, t_min + 1e-6)
|
|
209
|
-
|
|
210
162
|
tau_u16 = np.where(
|
|
211
163
|
finite,
|
|
212
164
|
np.clip((tau_map - t_min) / (t_max - t_min) * 65535, 0, 65535),
|
|
213
165
|
0
|
|
214
166
|
).astype(np.uint16)
|
|
215
|
-
tifffile.imwrite(str(output_dir / f
|
|
216
|
-
print(f
|
|
217
|
-
f
|
|
218
|
-
|
|
219
|
-
# Global summary text
|
|
220
|
-
summary_path = output_dir / f"{roi_name}_global_summary.txt"
|
|
167
|
+
tifffile.imwrite(str(output_dir / f'{roi_name}_{tau_key}.tif'), tau_u16, **tiff_resolution(tau_u16, field_area_um2))
|
|
168
|
+
print(f' {tau_key} display range: {t_min:.3f} - {t_max:.3f} ns '
|
|
169
|
+
f'→ 0 - 65535')
|
|
170
|
+
summary_path = output_dir / f'{roi_name}_global_summary.txt'
|
|
221
171
|
with open(summary_path, 'w') as f:
|
|
222
172
|
f.write('=' * 60 + '\n')
|
|
223
173
|
f.write('PER-TILE FIT - GLOBAL TAU SUMMARY\n')
|
|
224
174
|
f.write('=' * 60 + '\n\n')
|
|
225
|
-
f.write(f
|
|
175
|
+
f.write(f'ROI: {roi_name}\n')
|
|
226
176
|
n_pixels_fitted = global_summary.get('n_pixels_fitted', 'N/A')
|
|
227
177
|
if isinstance(n_pixels_fitted, int):
|
|
228
|
-
f.write(f
|
|
178
|
+
f.write(f'Pixels fitted: {n_pixels_fitted:,}\n\n')
|
|
229
179
|
else:
|
|
230
|
-
f.write(f
|
|
180
|
+
f.write(f'Pixels fitted: {n_pixels_fitted}\n\n')
|
|
231
181
|
f.write('Amplitude-weighted mean lifetime (global):\n')
|
|
232
|
-
f.write(f" tau_mean
|
|
233
|
-
f.write(f" tau_std
|
|
182
|
+
f.write(f" tau_mean = {global_summary.get('tau_mean_amp_global_ns', float('nan')):.4f} ns\n")
|
|
183
|
+
f.write(f" tau_std = {global_summary.get('tau_std_amp_global_ns', float('nan')):.4f} ns\n")
|
|
234
184
|
f.write(f" tau_median= {global_summary.get('tau_median_amp_global_ns', float('nan')):.4f} ns\n\n")
|
|
235
185
|
f.write('Per-component (amplitude-weighted across ROI):\n')
|
|
236
186
|
for k in range(1, n_exp + 1):
|
|
237
187
|
tau_k = global_summary.get(f'tau{k}_mean_ns', None)
|
|
238
|
-
a_k
|
|
188
|
+
a_k = global_summary.get(f'a{k}_mean_frac', None)
|
|
239
189
|
if tau_k is not None:
|
|
240
|
-
f.write(f
|
|
241
|
-
f
|
|
190
|
+
f.write(f' tau{k} = {tau_k:.4f} ns '
|
|
191
|
+
f'a{k} = {a_k:.3f} (mean amplitude fraction)\n')
|
|
242
192
|
f.write('\n' + '=' * 60 + '\n')
|
|
243
|
-
|
|
244
|
-
# Component RGB TIFF
|
|
245
193
|
try:
|
|
246
194
|
from ..utils.lifetime_image import make_component_rgb_tiff
|
|
247
|
-
make_component_rgb_tiff(canvas, output_dir, roi_name, n_exp, verbose=True)
|
|
195
|
+
make_component_rgb_tiff(canvas, output_dir, roi_name, n_exp, verbose=True, field_area_um2=field_area_um2)
|
|
248
196
|
except Exception as _e:
|
|
249
|
-
print(f
|
|
250
|
-
|
|
251
|
-
print(f
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return summary_path
|
|
197
|
+
print(f' component RGB skipped: {_e}')
|
|
198
|
+
print(f' Assembled maps saved to {output_dir}')
|
|
199
|
+
print(f' Global summary: {summary_path.name}')
|
|
200
|
+
return summary_path
|
|
@@ -9,6 +9,7 @@ from flimkit.formats import FLIMFile
|
|
|
9
9
|
from ..FLIM.fitters import fit_summed, fit_per_pixel
|
|
10
10
|
from ..FLIM.fit_tools import find_irf_peak_bin
|
|
11
11
|
from ..utils.lifetime_image import make_lifetime_image, make_component_rgb_tiff
|
|
12
|
+
from ..utils.export_png import flim_field_area_um2, tiff_resolution
|
|
12
13
|
from ..utils.plotting import plot_summed
|
|
13
14
|
from ..configs import (
|
|
14
15
|
MIN_PHOTONS_PERPIX, lm_restarts, de_population, de_maxiter, n_workers,
|
|
@@ -200,6 +201,7 @@ def fit_timelapse(ptu_dir, output_dir, args,
|
|
|
200
201
|
)
|
|
201
202
|
redox = compute_redox_metrics(pixel_maps, args.nexp,
|
|
202
203
|
compute_bound_fraction=compute_bound_fraction)
|
|
204
|
+
area_um2 = flim_field_area_um2(ptu)
|
|
203
205
|
pos_dir = frame_dir / f's{s}'
|
|
204
206
|
pos_dir.mkdir(exist_ok=True)
|
|
205
207
|
intensity = pixel_maps.get('intensity', pixel_stack.sum(axis=2))
|
|
@@ -218,6 +220,7 @@ def fit_timelapse(ptu_dir, output_dir, args,
|
|
|
218
220
|
canvas=pixel_maps, output_dir=pos_dir, roi_name=roi_name,
|
|
219
221
|
tau_min_ns=tau_disp_min, tau_max_ns=tau_disp_max,
|
|
220
222
|
intensity_percentile_hi=95, tau_key='tau_mean_int', verbose=False,
|
|
223
|
+
field_area_um2=area_um2,
|
|
221
224
|
)
|
|
222
225
|
except Exception as exc:
|
|
223
226
|
print(f' Warning: lifetime image export failed for {roi_name}: {exc}')
|
|
@@ -258,6 +261,7 @@ def fit_timelapse(ptu_dir, output_dir, args,
|
|
|
258
261
|
make_component_rgb_tiff(
|
|
259
262
|
canvas=pixel_maps, output_dir=pos_dir, roi_name=roi_name,
|
|
260
263
|
n_exp=args.nexp, intensity_percentile_hi=95, verbose=False,
|
|
264
|
+
field_area_um2=area_um2,
|
|
261
265
|
)
|
|
262
266
|
except Exception as exc:
|
|
263
267
|
print(f' Warning: component RGB TIFF export failed for {roi_name}: {exc}')
|
|
@@ -272,14 +276,16 @@ def fit_timelapse(ptu_dir, output_dir, args,
|
|
|
272
276
|
intensity_u16 = np.clip(
|
|
273
277
|
intensity.astype(np.float64) / i_max * 65535, 0, 65535
|
|
274
278
|
).astype(np.uint16)
|
|
275
|
-
_tifffile.imwrite(str(pos_dir / f'{roi_name}_intensity.tif'), intensity_u16
|
|
279
|
+
_tifffile.imwrite(str(pos_dir / f'{roi_name}_intensity.tif'), intensity_u16,
|
|
280
|
+
**tiff_resolution(intensity_u16, area_um2))
|
|
276
281
|
except Exception as exc:
|
|
277
282
|
print(f' Warning: intensity TIFF export failed for {roi_name}: {exc}')
|
|
278
283
|
if getattr(args, 'save_ind', False):
|
|
279
284
|
try:
|
|
280
285
|
from ..utils.enhanced_outputs import save_individual_tau_maps
|
|
281
286
|
save_individual_tau_maps(
|
|
282
|
-
pixel_maps, pos_dir, roi_name=roi_name, n_exp=args.nexp
|
|
287
|
+
pixel_maps, pos_dir, roi_name=roi_name, n_exp=args.nexp,
|
|
288
|
+
field_area_um2=area_um2)
|
|
283
289
|
except Exception as exc:
|
|
284
290
|
print(f' Warning: individual component map export failed for {roi_name}: {exc}')
|
|
285
291
|
stats = {'t': t, 's': s, 'path': str(ptu_path)}
|
|
@@ -528,6 +534,7 @@ def fit_zstack(ptu_dir, output_dir, args,
|
|
|
528
534
|
)
|
|
529
535
|
redox = compute_redox_metrics(pixel_maps, args.nexp,
|
|
530
536
|
compute_bound_fraction=compute_bound_fraction)
|
|
537
|
+
area_um2 = flim_field_area_um2(ptu)
|
|
531
538
|
intensity = pixel_maps.get('intensity', pixel_stack.sum(axis=2))
|
|
532
539
|
np.save(str(slice_dir / 'intensity.npy'), intensity.astype(np.float32))
|
|
533
540
|
for map_name in ('alpha_1', 'alpha_2', 'alpha_3', 'tau_mean_amp',
|
|
@@ -544,6 +551,7 @@ def fit_zstack(ptu_dir, output_dir, args,
|
|
|
544
551
|
canvas=pixel_maps, output_dir=slice_dir, roi_name=roi_name,
|
|
545
552
|
tau_min_ns=tau_disp_min, tau_max_ns=tau_disp_max,
|
|
546
553
|
intensity_percentile_hi=95, tau_key='tau_mean_int', verbose=False,
|
|
554
|
+
field_area_um2=area_um2,
|
|
547
555
|
)
|
|
548
556
|
if png_path is not None and Path(png_path).exists():
|
|
549
557
|
shutil.copyfile(str(png_path), str(preview_dir / f'z{z:04d}.png'))
|
|
@@ -586,6 +594,7 @@ def fit_zstack(ptu_dir, output_dir, args,
|
|
|
586
594
|
make_component_rgb_tiff(
|
|
587
595
|
canvas=pixel_maps, output_dir=slice_dir, roi_name=roi_name,
|
|
588
596
|
n_exp=args.nexp, intensity_percentile_hi=95, verbose=False,
|
|
597
|
+
field_area_um2=area_um2,
|
|
589
598
|
)
|
|
590
599
|
except Exception as exc:
|
|
591
600
|
print(f' Warning: component RGB TIFF export failed for {roi_name}: {exc}')
|
|
@@ -600,14 +609,16 @@ def fit_zstack(ptu_dir, output_dir, args,
|
|
|
600
609
|
intensity_u16 = np.clip(
|
|
601
610
|
intensity.astype(np.float64) / i_max * 65535, 0, 65535
|
|
602
611
|
).astype(np.uint16)
|
|
603
|
-
_tifffile.imwrite(str(slice_dir / f'{roi_name}_intensity.tif'), intensity_u16
|
|
612
|
+
_tifffile.imwrite(str(slice_dir / f'{roi_name}_intensity.tif'), intensity_u16,
|
|
613
|
+
**tiff_resolution(intensity_u16, area_um2))
|
|
604
614
|
except Exception as exc:
|
|
605
615
|
print(f' Warning: intensity TIFF export failed for {roi_name}: {exc}')
|
|
606
616
|
if getattr(args, 'save_ind', False):
|
|
607
617
|
try:
|
|
608
618
|
from ..utils.enhanced_outputs import save_individual_tau_maps
|
|
609
619
|
save_individual_tau_maps(
|
|
610
|
-
pixel_maps, slice_dir, roi_name=roi_name, n_exp=args.nexp
|
|
620
|
+
pixel_maps, slice_dir, roi_name=roi_name, n_exp=args.nexp,
|
|
621
|
+
field_area_um2=area_um2)
|
|
611
622
|
except Exception as exc:
|
|
612
623
|
print(f' Warning: individual component map export failed for {roi_name}: {exc}')
|
|
613
624
|
stats = {'z': z, 'path': str(ptu_path)}
|