flimkit 0.12.0__py3-none-any.whl
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/FLIM/__init__.py +0 -0
- flimkit/FLIM/assemble.py +254 -0
- flimkit/FLIM/batch.py +681 -0
- flimkit/FLIM/bg_tools.py +51 -0
- flimkit/FLIM/fit_tools.py +244 -0
- flimkit/FLIM/fitters.py +1471 -0
- flimkit/FLIM/irf_tools.py +617 -0
- flimkit/FLIM/models.py +391 -0
- flimkit/GPU/__init__.py +85 -0
- flimkit/GPU/_base.py +391 -0
- flimkit/GPU/cuda.py +10 -0
- flimkit/GPU/mlx_backend.py +381 -0
- flimkit/GPU/mps.py +10 -0
- flimkit/GPU/rocm.py +10 -0
- flimkit/GPU/torch_backend.py +385 -0
- flimkit/UI/app_state.py +10 -0
- flimkit/UI/controller.py +139 -0
- flimkit/UI/expert_settings.py +248 -0
- flimkit/UI/fit_help.py +206 -0
- flimkit/UI/fov_preview.py +1085 -0
- flimkit/UI/gui.py +3919 -0
- flimkit/UI/icon.icns +0 -0
- flimkit/UI/icon.ico +0 -0
- flimkit/UI/icon.png +0 -0
- flimkit/UI/irf_widget.py +103 -0
- flimkit/UI/mode_controller.py +118 -0
- flimkit/UI/modes/__init__.py +0 -0
- flimkit/UI/modes/base.py +3 -0
- flimkit/UI/modes/batch_mode.py +312 -0
- flimkit/UI/modes/fov_mode.py +164 -0
- flimkit/UI/modes/irf_mode.py +80 -0
- flimkit/UI/modes/phasor_mode.py +131 -0
- flimkit/UI/modes/stitch_mode.py +254 -0
- flimkit/UI/phasor_panel.py +1087 -0
- flimkit/UI/progress_window.py +113 -0
- flimkit/UI/project_panel.py +262 -0
- flimkit/UI/results_panel.py +332 -0
- flimkit/UI/roi_tools.py +794 -0
- flimkit/UI/utils.py +217 -0
- flimkit/__init__.py +0 -0
- flimkit/_version.py +41 -0
- flimkit/cli.py +120 -0
- flimkit/configs.py +148 -0
- flimkit/dialogs.py +46 -0
- flimkit/formats/BH/__init__.py +0 -0
- flimkit/formats/BH/reader.py +296 -0
- flimkit/formats/BH/writer.py +86 -0
- flimkit/formats/ISS/__init__.py +0 -0
- flimkit/formats/ISS/fdflim.py +86 -0
- flimkit/formats/ISS/image.py +114 -0
- flimkit/formats/ISS/reader.py +223 -0
- flimkit/formats/PS/__init__.py +0 -0
- flimkit/formats/PS/reader.py +202 -0
- flimkit/formats/PTU/__init__.py +0 -0
- flimkit/formats/PTU/decode.py +27 -0
- flimkit/formats/PTU/phu.py +85 -0
- flimkit/formats/PTU/reader.py +235 -0
- flimkit/formats/PTU/series.py +258 -0
- flimkit/formats/PTU/stitch.py +1182 -0
- flimkit/formats/PTU/tools.py +94 -0
- flimkit/formats/__init__.py +2 -0
- flimkit/formats/flim_file.py +232 -0
- flimkit/formats/phasor.py +132 -0
- flimkit/formats/signal.py +170 -0
- flimkit/image/tools.py +124 -0
- flimkit/interactive.py +1857 -0
- flimkit/mpl_backend.py +22 -0
- flimkit/phasor/__init__.py +40 -0
- flimkit/phasor/filters.py +127 -0
- flimkit/phasor/fret.py +654 -0
- flimkit/phasor/interactive.py +556 -0
- flimkit/phasor/peaks.py +186 -0
- flimkit/phasor/signal.py +90 -0
- flimkit/phasor_launcher.py +314 -0
- flimkit/plugins/__init__.py +137 -0
- flimkit/plugins/bindings.py +116 -0
- flimkit/plugins/builtin/__init__.py +3 -0
- flimkit/plugins/builtin/core_tools.py +28 -0
- flimkit/plugins/loader.py +371 -0
- flimkit/plugins/registry.py +406 -0
- flimkit/project.py +197 -0
- flimkit/synth.py +145 -0
- flimkit/utils/__init__.py +0 -0
- flimkit/utils/batch_fit.py +301 -0
- flimkit/utils/config_manager.py +119 -0
- flimkit/utils/config_snapshot.py +30 -0
- flimkit/utils/crash_handler.py +183 -0
- flimkit/utils/display.py +197 -0
- flimkit/utils/enhanced_outputs.py +345 -0
- flimkit/utils/fancy.py +103 -0
- flimkit/utils/lifetime_image.py +243 -0
- flimkit/utils/misc.py +111 -0
- flimkit/utils/plotting.py +190 -0
- flimkit/utils/roi.py +370 -0
- flimkit/utils/session.py +51 -0
- flimkit/utils/update_check.py +198 -0
- flimkit/utils/xlsx_tools.py +97 -0
- flimkit/utils/xml_utils.py +219 -0
- flimkit-0.12.0.dist-info/METADATA +356 -0
- flimkit-0.12.0.dist-info/RECORD +104 -0
- flimkit-0.12.0.dist-info/WHEEL +5 -0
- flimkit-0.12.0.dist-info/entry_points.txt +2 -0
- flimkit-0.12.0.dist-info/licenses/LICENSE.md +11 -0
- flimkit-0.12.0.dist-info/top_level.txt +1 -0
flimkit/FLIM/models.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from ..FLIM.irf_tools import build_full_irf
|
|
3
|
+
|
|
4
|
+
_N_QUAD = 200
|
|
5
|
+
|
|
6
|
+
def _exponential_kernel(tcspc_res, n_bins, taus, amps, bg):
|
|
7
|
+
t = np.arange(n_bins, dtype=float) * tcspc_res
|
|
8
|
+
return sum(a * np.exp(-t / max(tau, 1e-15))
|
|
9
|
+
for a, tau in zip(amps, taus)) + bg
|
|
10
|
+
|
|
11
|
+
def apply_pileup(model, n_sync):
|
|
12
|
+
# Forward first-photon pile-up: P(first photon of a pulse lands in bin i).
|
|
13
|
+
# Exact inverse of coates_pileup_correction, so the fit sees distorted model
|
|
14
|
+
# against raw (Poisson) data instead of corrected (non-Poisson) data.
|
|
15
|
+
# Needs the FULL-length model: bin i depends on the cumulative rate in every
|
|
16
|
+
# preceding bin, including bins outside the fit window.
|
|
17
|
+
n_s = float(n_sync)
|
|
18
|
+
lam = np.maximum(np.asarray(model, dtype=float), 0.0) / n_s
|
|
19
|
+
cum = np.concatenate([[0.0], np.cumsum(lam[:-1])])
|
|
20
|
+
return n_s * np.exp(-cum) * (1.0 - np.exp(-lam))
|
|
21
|
+
|
|
22
|
+
class _DECost:
|
|
23
|
+
def __init__(self, tcspc_res, n_bins, irf_prompt, n_exp, bg_fixed,
|
|
24
|
+
has_tail, fit_bg, fit_sigma,
|
|
25
|
+
fit_idx, decay, weights,
|
|
26
|
+
tvb_profile=None, fit_tvb=False, n_sync=None):
|
|
27
|
+
self.tcspc_res = tcspc_res
|
|
28
|
+
self.n_bins = n_bins
|
|
29
|
+
self.irf_prompt = irf_prompt
|
|
30
|
+
self.n_exp = n_exp
|
|
31
|
+
self.bg_fixed = bg_fixed
|
|
32
|
+
self.has_tail = has_tail
|
|
33
|
+
self.fit_bg = fit_bg
|
|
34
|
+
self.fit_sigma = fit_sigma
|
|
35
|
+
self.fit_idx = fit_idx
|
|
36
|
+
self.decay = decay
|
|
37
|
+
self.weights = weights
|
|
38
|
+
self.tvb_profile = tvb_profile
|
|
39
|
+
self.fit_tvb = fit_tvb
|
|
40
|
+
self.n_sync = n_sync
|
|
41
|
+
|
|
42
|
+
def __call__(self, params):
|
|
43
|
+
model = reconvolution_model(
|
|
44
|
+
params, self.tcspc_res, self.n_bins, self.irf_prompt,
|
|
45
|
+
self.n_exp, self.bg_fixed, self.has_tail,
|
|
46
|
+
self.fit_bg, self.fit_sigma,
|
|
47
|
+
tvb_profile=self.tvb_profile, fit_tvb=self.fit_tvb,
|
|
48
|
+
n_sync=self.n_sync)
|
|
49
|
+
res = ((model[self.fit_idx]
|
|
50
|
+
- self.decay[self.fit_idx])
|
|
51
|
+
/ self.weights)
|
|
52
|
+
return np.sum(res**2)
|
|
53
|
+
|
|
54
|
+
class _DECostLogTau(_DECost):
|
|
55
|
+
def __call__(self, params):
|
|
56
|
+
params_lin = np.array(params, dtype=float)
|
|
57
|
+
params_lin[:self.n_exp] = 10.0 ** params_lin[:self.n_exp]
|
|
58
|
+
return super().__call__(params_lin)
|
|
59
|
+
|
|
60
|
+
class _DECostPoisson:
|
|
61
|
+
def __init__(self, tcspc_res, n_bins, irf_prompt, n_exp, bg_fixed,
|
|
62
|
+
has_tail, fit_bg, fit_sigma,
|
|
63
|
+
fit_idx, decay,
|
|
64
|
+
tvb_profile=None, fit_tvb=False, n_sync=None):
|
|
65
|
+
self.tcspc_res = tcspc_res
|
|
66
|
+
self.n_bins = n_bins
|
|
67
|
+
self.irf_prompt = irf_prompt
|
|
68
|
+
self.n_exp = n_exp
|
|
69
|
+
self.bg_fixed = bg_fixed
|
|
70
|
+
self.has_tail = has_tail
|
|
71
|
+
self.fit_bg = fit_bg
|
|
72
|
+
self.fit_sigma = fit_sigma
|
|
73
|
+
self.fit_idx = fit_idx
|
|
74
|
+
self.decay = decay
|
|
75
|
+
self.tvb_profile = tvb_profile
|
|
76
|
+
self.fit_tvb = fit_tvb
|
|
77
|
+
self.n_sync = n_sync
|
|
78
|
+
|
|
79
|
+
def __call__(self, params):
|
|
80
|
+
model = reconvolution_model(
|
|
81
|
+
params, self.tcspc_res, self.n_bins, self.irf_prompt,
|
|
82
|
+
self.n_exp, self.bg_fixed, self.has_tail,
|
|
83
|
+
self.fit_bg, self.fit_sigma,
|
|
84
|
+
tvb_profile=self.tvb_profile, fit_tvb=self.fit_tvb,
|
|
85
|
+
n_sync=self.n_sync)
|
|
86
|
+
n = self.decay[self.fit_idx]
|
|
87
|
+
m = np.maximum(model[self.fit_idx], 1e-10)
|
|
88
|
+
# Poisson deviance (C-statistic)
|
|
89
|
+
dev = m - n
|
|
90
|
+
pos = n > 0
|
|
91
|
+
dev[pos] += n[pos] * np.log(n[pos] / m[pos])
|
|
92
|
+
return 2.0 * np.sum(dev)
|
|
93
|
+
|
|
94
|
+
class _DECostPoissonLogTau(_DECostPoisson):
|
|
95
|
+
|
|
96
|
+
def __call__(self, params):
|
|
97
|
+
params_lin = np.array(params, dtype=float)
|
|
98
|
+
params_lin[:self.n_exp] = 10.0 ** params_lin[:self.n_exp]
|
|
99
|
+
return super().__call__(params_lin)
|
|
100
|
+
|
|
101
|
+
def unpack_tail_params(params, n_exp, fit_t0, fit_bg, fit_tvb,
|
|
102
|
+
t0_fixed=0.0, bg_fixed=0.0, tvb_fixed=0.0):
|
|
103
|
+
taus = np.clip(np.asarray(params[:n_exp], dtype=float), 1e-14, None)
|
|
104
|
+
amps = np.asarray(params[n_exp:2 * n_exp], dtype=float)
|
|
105
|
+
idx = 2 * n_exp
|
|
106
|
+
if fit_t0:
|
|
107
|
+
t0 = float(params[idx]); idx += 1
|
|
108
|
+
else:
|
|
109
|
+
t0 = float(t0_fixed)
|
|
110
|
+
if fit_bg:
|
|
111
|
+
bg = float(params[idx]); idx += 1
|
|
112
|
+
else:
|
|
113
|
+
bg = float(bg_fixed)
|
|
114
|
+
if fit_tvb:
|
|
115
|
+
tvb_scale = float(params[idx]); idx += 1
|
|
116
|
+
else:
|
|
117
|
+
tvb_scale = float(tvb_fixed)
|
|
118
|
+
return taus, amps, t0, bg, tvb_scale
|
|
119
|
+
|
|
120
|
+
def tail_basis(tcspc_res, n_bins, taus, t0):
|
|
121
|
+
t = np.arange(n_bins, dtype=float) * tcspc_res - t0
|
|
122
|
+
live = t >= 0.0
|
|
123
|
+
t_pos = np.maximum(t, 0.0)
|
|
124
|
+
return np.array([np.where(live, np.exp(-t_pos / max(tau, 1e-15)), 0.0)
|
|
125
|
+
for tau in taus])
|
|
126
|
+
|
|
127
|
+
def tail_model(params, tcspc_res, n_bins, n_exp, bg_fixed, fit_bg,
|
|
128
|
+
fit_t0=False, t0_fixed=0.0,
|
|
129
|
+
tvb_profile=None, fit_tvb=False, tvb_fixed=0.0,
|
|
130
|
+
n_sync=None):
|
|
131
|
+
taus, amps, t0, bg, tvb_scale = unpack_tail_params(
|
|
132
|
+
params, n_exp, fit_t0, fit_bg, fit_tvb,
|
|
133
|
+
t0_fixed=t0_fixed, bg_fixed=bg_fixed, tvb_fixed=tvb_fixed)
|
|
134
|
+
order = np.argsort(-taus)
|
|
135
|
+
taus = taus[order]
|
|
136
|
+
amps = amps[order]
|
|
137
|
+
model = amps @ tail_basis(tcspc_res, n_bins, taus, t0) + bg
|
|
138
|
+
if tvb_profile is not None:
|
|
139
|
+
model = model + tvb_scale * tvb_profile
|
|
140
|
+
if n_sync:
|
|
141
|
+
model = apply_pileup(model, n_sync)
|
|
142
|
+
return model
|
|
143
|
+
|
|
144
|
+
class _DECostTail:
|
|
145
|
+
def __init__(self, tcspc_res, n_bins, n_exp, bg_fixed, fit_bg,
|
|
146
|
+
fit_idx, decay, weights,
|
|
147
|
+
fit_t0=False, t0_fixed=0.0,
|
|
148
|
+
tvb_profile=None, fit_tvb=False, n_sync=None):
|
|
149
|
+
self.tcspc_res = tcspc_res
|
|
150
|
+
self.n_bins = n_bins
|
|
151
|
+
self.n_exp = n_exp
|
|
152
|
+
self.bg_fixed = bg_fixed
|
|
153
|
+
self.fit_bg = fit_bg
|
|
154
|
+
self.fit_idx = fit_idx
|
|
155
|
+
self.decay = decay
|
|
156
|
+
self.weights = weights
|
|
157
|
+
self.fit_t0 = fit_t0
|
|
158
|
+
self.t0_fixed = t0_fixed
|
|
159
|
+
self.tvb_profile = tvb_profile
|
|
160
|
+
self.fit_tvb = fit_tvb
|
|
161
|
+
self.n_sync = n_sync
|
|
162
|
+
|
|
163
|
+
def _model(self, params):
|
|
164
|
+
return tail_model(
|
|
165
|
+
params, self.tcspc_res, self.n_bins, self.n_exp,
|
|
166
|
+
self.bg_fixed, self.fit_bg,
|
|
167
|
+
fit_t0=self.fit_t0, t0_fixed=self.t0_fixed,
|
|
168
|
+
tvb_profile=self.tvb_profile, fit_tvb=self.fit_tvb,
|
|
169
|
+
n_sync=self.n_sync)
|
|
170
|
+
|
|
171
|
+
def __call__(self, params):
|
|
172
|
+
model = self._model(params)
|
|
173
|
+
res = (model[self.fit_idx] - self.decay[self.fit_idx]) / self.weights
|
|
174
|
+
return np.sum(res ** 2)
|
|
175
|
+
|
|
176
|
+
class _DECostTailLogTau(_DECostTail):
|
|
177
|
+
def __call__(self, params):
|
|
178
|
+
params_lin = np.array(params, dtype=float)
|
|
179
|
+
params_lin[:self.n_exp] = 10.0 ** params_lin[:self.n_exp]
|
|
180
|
+
return super().__call__(params_lin)
|
|
181
|
+
|
|
182
|
+
class _DECostTailPoisson(_DECostTail):
|
|
183
|
+
def __init__(self, tcspc_res, n_bins, n_exp, bg_fixed, fit_bg,
|
|
184
|
+
fit_idx, decay,
|
|
185
|
+
fit_t0=False, t0_fixed=0.0,
|
|
186
|
+
tvb_profile=None, fit_tvb=False, n_sync=None):
|
|
187
|
+
super().__init__(tcspc_res, n_bins, n_exp, bg_fixed, fit_bg,
|
|
188
|
+
fit_idx, decay, None,
|
|
189
|
+
fit_t0=fit_t0, t0_fixed=t0_fixed,
|
|
190
|
+
tvb_profile=tvb_profile, fit_tvb=fit_tvb, n_sync=n_sync)
|
|
191
|
+
|
|
192
|
+
def __call__(self, params):
|
|
193
|
+
model = self._model(params)
|
|
194
|
+
n = self.decay[self.fit_idx]
|
|
195
|
+
m = np.maximum(model[self.fit_idx], 1e-10)
|
|
196
|
+
dev = m - n
|
|
197
|
+
pos = n > 0
|
|
198
|
+
dev[pos] += n[pos] * np.log(n[pos] / m[pos])
|
|
199
|
+
return 2.0 * np.sum(dev)
|
|
200
|
+
|
|
201
|
+
class _DECostTailPoissonLogTau(_DECostTailPoisson):
|
|
202
|
+
def __call__(self, params):
|
|
203
|
+
params_lin = np.array(params, dtype=float)
|
|
204
|
+
params_lin[:self.n_exp] = 10.0 ** params_lin[:self.n_exp]
|
|
205
|
+
return super().__call__(params_lin)
|
|
206
|
+
|
|
207
|
+
def _alpha_gaussian(tau_grid, tau_center, sigma_tau):
|
|
208
|
+
return np.exp(-0.5 * ((tau_grid - tau_center) / max(sigma_tau, 1e-15)) ** 2)
|
|
209
|
+
|
|
210
|
+
def _alpha_lorentzian(tau_grid, tau_center, gamma):
|
|
211
|
+
half_g = max(gamma / 2.0, 1e-15)
|
|
212
|
+
return half_g ** 2 / ((tau_grid - tau_center) ** 2 + half_g ** 2)
|
|
213
|
+
|
|
214
|
+
def _dist_kernel(tcspc_res, n_bins, tau_center, width, amp, dist_type, n_quad=_N_QUAD):
|
|
215
|
+
spread = 4.0 * width if dist_type == 'gaussian' else 8.0 * max(width / 2.0, 1e-15)
|
|
216
|
+
tau_lo = max(tau_center - spread, 1e-12)
|
|
217
|
+
tau_hi = max(tau_center + spread, tau_lo + 1e-12)
|
|
218
|
+
tau_grid = np.linspace(tau_lo, tau_hi, n_quad)
|
|
219
|
+
alpha = (_alpha_gaussian(tau_grid, tau_center, width) if dist_type == 'gaussian'
|
|
220
|
+
else _alpha_lorentzian(tau_grid, tau_center, width))
|
|
221
|
+
alpha_sum = alpha.sum()
|
|
222
|
+
if alpha_sum > 0:
|
|
223
|
+
alpha = alpha / alpha_sum
|
|
224
|
+
t = np.arange(n_bins, dtype=float) * tcspc_res
|
|
225
|
+
exp_mat = np.exp(-t[None, :] / np.maximum(tau_grid[:, None], 1e-15))
|
|
226
|
+
return amp * (alpha @ exp_mat)
|
|
227
|
+
|
|
228
|
+
def dist_reconvolution_model(params, tcspc_res, n_bins, irf_prompt,
|
|
229
|
+
n_components, dist_type, bg_fixed, fit_bg, fit_sigma,
|
|
230
|
+
tvb_profile=None, fit_tvb=False, tvb_fixed=0.0,
|
|
231
|
+
n_sync=None):
|
|
232
|
+
# params layout: [tau_c×N, width×N, amp×N, shift, (sigma), (bg), (tvb_scale)]
|
|
233
|
+
tau_centers = np.clip(params[:n_components], 1e-14, None)
|
|
234
|
+
widths = np.clip(params[n_components:2 * n_components], 1e-14, None)
|
|
235
|
+
amps = params[2 * n_components:3 * n_components]
|
|
236
|
+
idx = 3 * n_components
|
|
237
|
+
shift = params[idx]; idx += 1
|
|
238
|
+
sigma = params[idx] if fit_sigma else 0.0
|
|
239
|
+
if fit_sigma:
|
|
240
|
+
idx += 1
|
|
241
|
+
if fit_bg:
|
|
242
|
+
bg = params[idx]; idx += 1
|
|
243
|
+
else:
|
|
244
|
+
bg = bg_fixed
|
|
245
|
+
if fit_tvb:
|
|
246
|
+
tvb_scale = params[idx]; idx += 1
|
|
247
|
+
else:
|
|
248
|
+
tvb_scale = tvb_fixed
|
|
249
|
+
kernel = np.zeros(n_bins, dtype=float)
|
|
250
|
+
for i in range(n_components):
|
|
251
|
+
kernel += _dist_kernel(tcspc_res, n_bins, tau_centers[i], widths[i], amps[i], dist_type)
|
|
252
|
+
kernel += bg
|
|
253
|
+
irf_full = build_full_irf(irf_prompt, shift, sigma, 0.0, 1.0, n_bins)
|
|
254
|
+
model = np.real(np.fft.ifft(np.fft.fft(kernel) * np.fft.fft(irf_full)))
|
|
255
|
+
if tvb_profile is not None:
|
|
256
|
+
model = model + tvb_scale * tvb_profile
|
|
257
|
+
if n_sync:
|
|
258
|
+
model = apply_pileup(model, n_sync)
|
|
259
|
+
return model
|
|
260
|
+
|
|
261
|
+
def build_dist_basis_grid(tcspc_res, n_bins, irf_fixed,
|
|
262
|
+
tau_grid, width_grid, dist_type, n_quad=_N_QUAD):
|
|
263
|
+
irf_fft = np.fft.fft(irf_fixed)
|
|
264
|
+
n_total = len(tau_grid) * len(width_grid)
|
|
265
|
+
basis = np.empty((n_total, n_bins), dtype=np.float32)
|
|
266
|
+
param_pairs = np.empty((n_total, 2), dtype=np.float32)
|
|
267
|
+
idx = 0
|
|
268
|
+
for tau_c in tau_grid:
|
|
269
|
+
for w in width_grid:
|
|
270
|
+
kernel = _dist_kernel(tcspc_res, n_bins, tau_c, w, 1.0, dist_type, n_quad)
|
|
271
|
+
basis[idx] = np.real(np.fft.ifft(np.fft.fft(kernel) * irf_fft)).astype(np.float32)
|
|
272
|
+
param_pairs[idx] = [tau_c, w]
|
|
273
|
+
idx += 1
|
|
274
|
+
return basis, param_pairs
|
|
275
|
+
|
|
276
|
+
class _DECostDist:
|
|
277
|
+
def __init__(self, tcspc_res, n_bins, irf_prompt, n_components, dist_type,
|
|
278
|
+
bg_fixed, fit_bg, fit_sigma, fit_idx, decay, weights,
|
|
279
|
+
tvb_profile=None, fit_tvb=False, n_sync=None):
|
|
280
|
+
self.tcspc_res = tcspc_res
|
|
281
|
+
self.n_bins = n_bins
|
|
282
|
+
self.irf_prompt = irf_prompt
|
|
283
|
+
self.n_components = n_components
|
|
284
|
+
self.dist_type = dist_type
|
|
285
|
+
self.bg_fixed = bg_fixed
|
|
286
|
+
self.fit_bg = fit_bg
|
|
287
|
+
self.fit_sigma = fit_sigma
|
|
288
|
+
self.fit_idx = fit_idx
|
|
289
|
+
self.decay = decay
|
|
290
|
+
self.weights = weights
|
|
291
|
+
self.tvb_profile = tvb_profile
|
|
292
|
+
self.fit_tvb = fit_tvb
|
|
293
|
+
self.n_sync = n_sync
|
|
294
|
+
|
|
295
|
+
def __call__(self, params):
|
|
296
|
+
model = dist_reconvolution_model(
|
|
297
|
+
params, self.tcspc_res, self.n_bins, self.irf_prompt,
|
|
298
|
+
self.n_components, self.dist_type,
|
|
299
|
+
self.bg_fixed, self.fit_bg, self.fit_sigma,
|
|
300
|
+
tvb_profile=self.tvb_profile, fit_tvb=self.fit_tvb,
|
|
301
|
+
n_sync=self.n_sync)
|
|
302
|
+
res = ((model[self.fit_idx]
|
|
303
|
+
- self.decay[self.fit_idx])
|
|
304
|
+
/ self.weights)
|
|
305
|
+
return np.sum(res ** 2)
|
|
306
|
+
|
|
307
|
+
class _DECostDistLogParam(_DECostDist):
|
|
308
|
+
def __call__(self, params):
|
|
309
|
+
p = np.array(params, dtype=float)
|
|
310
|
+
n = self.n_components
|
|
311
|
+
p[:n] = 10.0 ** p[:n]
|
|
312
|
+
p[n:2*n] = 10.0 ** p[n:2*n]
|
|
313
|
+
return super().__call__(p)
|
|
314
|
+
|
|
315
|
+
class _DECostDistPoisson:
|
|
316
|
+
def __init__(self, tcspc_res, n_bins, irf_prompt, n_components, dist_type,
|
|
317
|
+
bg_fixed, fit_bg, fit_sigma, fit_idx, decay,
|
|
318
|
+
tvb_profile=None, fit_tvb=False, n_sync=None):
|
|
319
|
+
self.tcspc_res = tcspc_res
|
|
320
|
+
self.n_bins = n_bins
|
|
321
|
+
self.irf_prompt = irf_prompt
|
|
322
|
+
self.n_components = n_components
|
|
323
|
+
self.dist_type = dist_type
|
|
324
|
+
self.bg_fixed = bg_fixed
|
|
325
|
+
self.fit_bg = fit_bg
|
|
326
|
+
self.fit_sigma = fit_sigma
|
|
327
|
+
self.fit_idx = fit_idx
|
|
328
|
+
self.decay = decay
|
|
329
|
+
self.tvb_profile = tvb_profile
|
|
330
|
+
self.fit_tvb = fit_tvb
|
|
331
|
+
self.n_sync = n_sync
|
|
332
|
+
|
|
333
|
+
def __call__(self, params):
|
|
334
|
+
model = dist_reconvolution_model(
|
|
335
|
+
params, self.tcspc_res, self.n_bins, self.irf_prompt,
|
|
336
|
+
self.n_components, self.dist_type,
|
|
337
|
+
self.bg_fixed, self.fit_bg, self.fit_sigma,
|
|
338
|
+
tvb_profile=self.tvb_profile, fit_tvb=self.fit_tvb,
|
|
339
|
+
n_sync=self.n_sync)
|
|
340
|
+
n = self.decay[self.fit_idx]
|
|
341
|
+
m = np.maximum(model[self.fit_idx], 1e-10)
|
|
342
|
+
dev = m - n
|
|
343
|
+
pos = n > 0
|
|
344
|
+
dev[pos] += n[pos] * np.log(n[pos] / m[pos])
|
|
345
|
+
return 2.0 * np.sum(dev)
|
|
346
|
+
|
|
347
|
+
class _DECostDistPoissonLogParam(_DECostDistPoisson):
|
|
348
|
+
def __call__(self, params):
|
|
349
|
+
p = np.array(params, dtype=float)
|
|
350
|
+
n = self.n_components
|
|
351
|
+
p[:n] = 10.0 ** p[:n]
|
|
352
|
+
p[n:2*n] = 10.0 ** p[n:2*n]
|
|
353
|
+
return super().__call__(p)
|
|
354
|
+
|
|
355
|
+
def reconvolution_model(params, tcspc_res, n_bins, irf_prompt,
|
|
356
|
+
n_exp, bg_fixed, has_tail, fit_bg, fit_sigma,
|
|
357
|
+
tvb_profile=None, fit_tvb=False, tvb_fixed=0.0,
|
|
358
|
+
n_sync=None):
|
|
359
|
+
taus = np.clip(params[:n_exp], 1e-14, None)
|
|
360
|
+
amps = params[n_exp:2*n_exp]
|
|
361
|
+
order = np.argsort(-taus)
|
|
362
|
+
taus = taus[order]
|
|
363
|
+
amps = amps[order]
|
|
364
|
+
idx = 2 * n_exp
|
|
365
|
+
shift = params[idx]; idx += 1
|
|
366
|
+
if fit_sigma:
|
|
367
|
+
sigma = params[idx]; idx += 1
|
|
368
|
+
else:
|
|
369
|
+
sigma = 0.0
|
|
370
|
+
if fit_bg:
|
|
371
|
+
bg = params[idx]; idx += 1
|
|
372
|
+
else:
|
|
373
|
+
bg = bg_fixed
|
|
374
|
+
if fit_tvb:
|
|
375
|
+
tvb_scale = params[idx]; idx += 1
|
|
376
|
+
else:
|
|
377
|
+
tvb_scale = tvb_fixed
|
|
378
|
+
if has_tail:
|
|
379
|
+
tail_amp = params[idx]
|
|
380
|
+
tail_tau = params[idx + 1]
|
|
381
|
+
else:
|
|
382
|
+
tail_amp, tail_tau = 0.0, 1.0
|
|
383
|
+
irf_full = build_full_irf(irf_prompt, shift, sigma, tail_amp, tail_tau, n_bins)
|
|
384
|
+
kernel = _exponential_kernel(tcspc_res, n_bins, taus, amps, bg)
|
|
385
|
+
model = np.real(np.fft.ifft(np.fft.fft(kernel) * np.fft.fft(irf_full)))
|
|
386
|
+
if tvb_profile is not None:
|
|
387
|
+
model = model + tvb_scale * tvb_profile
|
|
388
|
+
# after bg and TVB: the detector piles up every photon reaching it, not just signal
|
|
389
|
+
if n_sync:
|
|
390
|
+
model = apply_pileup(model, n_sync)
|
|
391
|
+
return model
|
flimkit/GPU/__init__.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
def get_backend(prefer='auto'):
|
|
5
|
+
if prefer == 'auto':
|
|
6
|
+
for name in ('mlx', 'cuda', 'mps', 'rocm'):
|
|
7
|
+
b = _try_backend(name)
|
|
8
|
+
if b is not None:
|
|
9
|
+
return b
|
|
10
|
+
return None
|
|
11
|
+
return _try_backend(prefer)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _try_backend(name):
|
|
15
|
+
if name == 'mlx':
|
|
16
|
+
return _try_mlx()
|
|
17
|
+
if name in ('cuda', 'mps', 'rocm'):
|
|
18
|
+
return _try_torch(name)
|
|
19
|
+
raise ValueError(
|
|
20
|
+
f"Unknown backend {name!r}. "
|
|
21
|
+
"Choose from: 'auto', 'mlx', 'cuda', 'mps', 'rocm'."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _try_mlx():
|
|
26
|
+
if sys.platform != 'darwin':
|
|
27
|
+
return None
|
|
28
|
+
try:
|
|
29
|
+
import mlx.core as mx # noqa: F401
|
|
30
|
+
gpu = mx.Device(mx.gpu)
|
|
31
|
+
with mx.stream(gpu):
|
|
32
|
+
mx.eval(mx.array([1.0]) + 1)
|
|
33
|
+
except Exception:
|
|
34
|
+
return None
|
|
35
|
+
from flimkit.GPU.mlx_backend import MLXBackend
|
|
36
|
+
return MLXBackend()
|
|
37
|
+
|
|
38
|
+
def _cuda_available():
|
|
39
|
+
try:
|
|
40
|
+
import torch
|
|
41
|
+
with warnings.catch_warnings():
|
|
42
|
+
warnings.simplefilter('ignore')
|
|
43
|
+
return torch.cuda.is_available()
|
|
44
|
+
except Exception:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _cuda_device_name():
|
|
49
|
+
try:
|
|
50
|
+
import torch
|
|
51
|
+
with warnings.catch_warnings():
|
|
52
|
+
warnings.simplefilter('ignore')
|
|
53
|
+
return torch.cuda.get_device_name(0).lower()
|
|
54
|
+
except Exception:
|
|
55
|
+
return ''
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _try_torch(name):
|
|
59
|
+
try:
|
|
60
|
+
import torch
|
|
61
|
+
except ImportError:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
if name == 'cuda':
|
|
65
|
+
if not _cuda_available():
|
|
66
|
+
return None
|
|
67
|
+
device = 'cuda'
|
|
68
|
+
elif name == 'mps':
|
|
69
|
+
if not (torch.backends.mps.is_available() and
|
|
70
|
+
torch.backends.mps.is_built()):
|
|
71
|
+
return None
|
|
72
|
+
device = 'mps'
|
|
73
|
+
elif name == 'rocm':
|
|
74
|
+
if not _cuda_available():
|
|
75
|
+
return None
|
|
76
|
+
name_str = _cuda_device_name()
|
|
77
|
+
if not any(k in name_str for k in ('amd', 'radeon', 'vega', 'navi', 'gfx')):
|
|
78
|
+
return None
|
|
79
|
+
device = 'cuda'
|
|
80
|
+
else:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
from flimkit.GPU.torch_backend import TorchBackend
|
|
84
|
+
return TorchBackend(device=device)
|
|
85
|
+
|