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/UI/icon.icns
ADDED
|
Binary file
|
flimkit/UI/icon.ico
ADDED
|
Binary file
|
flimkit/UI/icon.png
ADDED
|
Binary file
|
flimkit/UI/irf_widget.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import tkinter as tk
|
|
3
|
+
from tkinter import ttk
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from flimkit.UI.utils import _browse_file, _section
|
|
6
|
+
class IRFWidget:
|
|
7
|
+
_AUTO_FILL = object()
|
|
8
|
+
CHOICES = [
|
|
9
|
+
('Analytical model (LAS X export)', 'irf_xlsx'),
|
|
10
|
+
('Machine IRF (.npy pre-built)', 'machine_irf'),
|
|
11
|
+
('Machine IRF + full σ broadening', 'machine_irf_sigma_full'),
|
|
12
|
+
('Machine IRF + half σ broadening (σ≤0.5)', 'machine_irf_sigma_half'),
|
|
13
|
+
('Measured IRF file (scatter PTU or .pck)', 'file'),
|
|
14
|
+
('Estimate from decay - raw', 'raw'),
|
|
15
|
+
('Estimate from decay - parametric', 'parametric'),
|
|
16
|
+
('Gaussian (fallback)', 'gaussian'),
|
|
17
|
+
]
|
|
18
|
+
def __init__(self, parent, default='irf_xlsx', xlsx_var=None, machine_irf_default: str = ''):
|
|
19
|
+
self.xlsx_var = xlsx_var
|
|
20
|
+
self._machine_irf_default = machine_irf_default
|
|
21
|
+
self.sv_method = tk.StringVar(value=default)
|
|
22
|
+
self.sv_path = tk.StringVar()
|
|
23
|
+
self.frame = _section(parent, 'Instrument Response Function (IRF)',
|
|
24
|
+
help_topic='irf')
|
|
25
|
+
self.frame.columnconfigure(1, weight=1)
|
|
26
|
+
for i, (lbl, val) in enumerate(self.CHOICES):
|
|
27
|
+
ttk.Radiobutton(self.frame, text=lbl, variable=self.sv_method,
|
|
28
|
+
value=val, command=self._update).grid(
|
|
29
|
+
row=i, column=0, columnspan=3, sticky='w', padx=4, pady=1)
|
|
30
|
+
r = len(self.CHOICES)
|
|
31
|
+
self._path_lbl = ttk.Label(self.frame, text='IRF file')
|
|
32
|
+
self._path_lbl.grid(row=r, column=0, sticky='e', padx=6, pady=3)
|
|
33
|
+
self._path_e = ttk.Entry(self.frame, textvariable=self.sv_path, width=45)
|
|
34
|
+
self._path_e.grid(row=r, column=1, sticky='ew', padx=4, pady=3)
|
|
35
|
+
self._path_btn = ttk.Button(
|
|
36
|
+
self.frame, text='Browse...',
|
|
37
|
+
command=self._browse_irf_path)
|
|
38
|
+
self._path_btn.grid(row=r, column=2, padx=4, pady=3)
|
|
39
|
+
self._note = ttk.Label(
|
|
40
|
+
self.frame,
|
|
41
|
+
text='Uses the LAS X export entered in Input Files above',
|
|
42
|
+
foreground='grey')
|
|
43
|
+
self._note.grid(row=r + 1, column=0, columnspan=3, sticky='w', padx=8, pady=3)
|
|
44
|
+
self._update()
|
|
45
|
+
def _browse_irf_path(self):
|
|
46
|
+
if self.sv_method.get().startswith('machine_irf'):
|
|
47
|
+
_browse_file(self.sv_path, 'Select machine IRF',
|
|
48
|
+
[('NumPy array', '*.npy'), ('All', '*.*')])
|
|
49
|
+
else:
|
|
50
|
+
from flimkit.formats import supported_extensions
|
|
51
|
+
_flim = ' '.join('*' + e for e in supported_extensions())
|
|
52
|
+
_browse_file(self.sv_path, 'Select IRF file',
|
|
53
|
+
[('FLIM / PCK / LAS X export',
|
|
54
|
+
_flim + ' *.pck *.xlsx *.csv *.tsv *.txt *.dat *.ascii *.asc'),
|
|
55
|
+
('All', '*.*')])
|
|
56
|
+
def _show_browse(self):
|
|
57
|
+
method = self.sv_method.get()
|
|
58
|
+
self._path_lbl.config(
|
|
59
|
+
text='Machine IRF (.npy) path' if method.startswith('machine_irf') else 'IRF file')
|
|
60
|
+
if method.startswith('machine_irf') and not self.sv_path.get().endswith('.npy'):
|
|
61
|
+
self.sv_path.set(self._machine_irf_default)
|
|
62
|
+
self._path_lbl.grid()
|
|
63
|
+
self._path_e.grid()
|
|
64
|
+
self._path_btn.grid()
|
|
65
|
+
self._note.grid_remove()
|
|
66
|
+
def _show_note(self):
|
|
67
|
+
self._path_lbl.grid_remove()
|
|
68
|
+
self._path_e.grid_remove()
|
|
69
|
+
self._path_btn.grid_remove()
|
|
70
|
+
self._note.grid()
|
|
71
|
+
def _hide_all(self):
|
|
72
|
+
self._path_lbl.grid_remove()
|
|
73
|
+
self._path_e.grid_remove()
|
|
74
|
+
self._path_btn.grid_remove()
|
|
75
|
+
self._note.grid_remove()
|
|
76
|
+
def _update(self):
|
|
77
|
+
method = self.sv_method.get()
|
|
78
|
+
if method == 'irf_xlsx':
|
|
79
|
+
self._show_note() if self.xlsx_var is not None else self._show_browse()
|
|
80
|
+
elif method in ('file',) or method.startswith('machine_irf'):
|
|
81
|
+
self._show_browse()
|
|
82
|
+
else:
|
|
83
|
+
self._hide_all()
|
|
84
|
+
def grid(self, **kw):
|
|
85
|
+
self.frame.grid(**kw)
|
|
86
|
+
|
|
87
|
+
def grid_remove(self):
|
|
88
|
+
self.frame.grid_remove()
|
|
89
|
+
def get_args(self, xlsx_fallback: Optional[str] = None) -> dict:
|
|
90
|
+
method = self.sv_method.get()
|
|
91
|
+
path = self.sv_path.get().strip() or None
|
|
92
|
+
if method == 'irf_xlsx':
|
|
93
|
+
xlsx = (self.xlsx_var.get().strip() if self.xlsx_var else None) \
|
|
94
|
+
or xlsx_fallback or path
|
|
95
|
+
return dict(irf=None, irf_xlsx=xlsx, estimate_irf='none', no_xlsx_irf=False, machine_irf=None)
|
|
96
|
+
elif method.startswith('machine_irf'):
|
|
97
|
+
return dict(irf=None, irf_xlsx=None, estimate_irf=method, no_xlsx_irf=True, machine_irf=path)
|
|
98
|
+
elif method == 'file':
|
|
99
|
+
return dict(irf=path, irf_xlsx=None, estimate_irf='none', no_xlsx_irf=True, machine_irf=None)
|
|
100
|
+
elif method in ('raw', 'parametric'):
|
|
101
|
+
return dict(irf=None, irf_xlsx=None, estimate_irf=method, no_xlsx_irf=True, machine_irf=None)
|
|
102
|
+
else:
|
|
103
|
+
return dict(irf=None, irf_xlsx=None, estimate_irf='none', no_xlsx_irf=True, machine_irf=None)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from flimkit.UI.roi_tools import RoiAnalysisPanel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ModeController:
|
|
5
|
+
def __init__(self, builder):
|
|
6
|
+
self.b = builder
|
|
7
|
+
|
|
8
|
+
def switch(self, form_id: str):
|
|
9
|
+
for btn in self.b._form_buttons.values():
|
|
10
|
+
btn.state(['!pressed'])
|
|
11
|
+
|
|
12
|
+
if form_id in self.b._form_inner_frames:
|
|
13
|
+
# batch and irf are menu-only - no sidebar button to highlight
|
|
14
|
+
|
|
15
|
+
self.b._current_form = form_id
|
|
16
|
+
|
|
17
|
+
if form_id == 'fov':
|
|
18
|
+
self.b._stitch_tabs.grid_remove()
|
|
19
|
+
if 'phasor' in self.b._form_inner_frames:
|
|
20
|
+
self.b._form_inner_frames['phasor'][0].grid_remove()
|
|
21
|
+
if hasattr(self.b, '_roi_analysis_panel'):
|
|
22
|
+
self.b._fov_preview._roi_analysis_panel = self.b._roi_analysis_panel
|
|
23
|
+
|
|
24
|
+
fov_frame = self.b._form_inner_frames['fov'][0]
|
|
25
|
+
fov_frame.grid(row=0, column=0, sticky='nsew')
|
|
26
|
+
fov_frame.lift()
|
|
27
|
+
fov_frame.tkraise()
|
|
28
|
+
|
|
29
|
+
self.b._analysis_tabs.grid(row=0, column=0, sticky='nsew')
|
|
30
|
+
self.b._analysis_tabs.lift()
|
|
31
|
+
self.b._analysis_tabs.tkraise()
|
|
32
|
+
self.b._analysis_tabs.select(0)
|
|
33
|
+
|
|
34
|
+
# update_idletasks avoids re-entrant event processing
|
|
35
|
+
self.b._fit_settings_tab.update_idletasks()
|
|
36
|
+
self.b._refresh_scrollable_frame(form_id)
|
|
37
|
+
|
|
38
|
+
elif form_id == 'stitch':
|
|
39
|
+
self.b._analysis_tabs.grid_remove()
|
|
40
|
+
if 'phasor' in self.b._form_inner_frames:
|
|
41
|
+
self.b._form_inner_frames['phasor'][0].grid_remove()
|
|
42
|
+
|
|
43
|
+
if 'fov' in self.b._form_inner_frames:
|
|
44
|
+
self.b._form_inner_frames['fov'][0].grid_remove()
|
|
45
|
+
|
|
46
|
+
stitch_frame = self.b._form_inner_frames['stitch'][0]
|
|
47
|
+
stitch_frame.grid(row=0, column=0, sticky='nsew')
|
|
48
|
+
stitch_frame.lift()
|
|
49
|
+
stitch_frame.tkraise()
|
|
50
|
+
|
|
51
|
+
self.b._stitch_tabs.grid(row=0, column=0, sticky='nsew')
|
|
52
|
+
self.b._stitch_tabs.lift()
|
|
53
|
+
self.b._stitch_tabs.tkraise()
|
|
54
|
+
|
|
55
|
+
if not hasattr(self.b, '_stitch_roi_panel'):
|
|
56
|
+
self.b._stitch_roi_panel = RoiAnalysisPanel(self.b._stitch_roi_analysis_frame)
|
|
57
|
+
self.b._stitch_roi_panel.app = self.b
|
|
58
|
+
self.b._stitch_roi_panel.grid(row=0, column=0, sticky='nsew')
|
|
59
|
+
self.b._stitch_roi_panel.fov_preview = self.b._fov_preview
|
|
60
|
+
self.b._fov_preview._roi_analysis_panel = self.b._stitch_roi_panel
|
|
61
|
+
|
|
62
|
+
self.b._stitch_tabs.select(0)
|
|
63
|
+
|
|
64
|
+
# update_idletasks avoids re-entrant event processing
|
|
65
|
+
self.b._stitch_settings_tab.update_idletasks()
|
|
66
|
+
self.b._refresh_scrollable_frame(form_id)
|
|
67
|
+
|
|
68
|
+
else:
|
|
69
|
+
self.b._analysis_tabs.grid_remove()
|
|
70
|
+
self.b._stitch_tabs.grid_remove()
|
|
71
|
+
|
|
72
|
+
for fid in ('phasor', 'fov', 'stitch', 'batch', 'irf'):
|
|
73
|
+
if fid != form_id and fid in self.b._form_inner_frames:
|
|
74
|
+
self.b._form_inner_frames[fid][0].grid_remove()
|
|
75
|
+
|
|
76
|
+
if form_id in self.b._form_inner_frames:
|
|
77
|
+
selected_frame = self.b._form_inner_frames[form_id][0]
|
|
78
|
+
selected_frame.grid(row=0, column=0, sticky='nsew')
|
|
79
|
+
selected_frame.lift()
|
|
80
|
+
selected_frame.tkraise()
|
|
81
|
+
self.b._refresh_scrollable_frame(form_id)
|
|
82
|
+
|
|
83
|
+
if form_id == 'phasor':
|
|
84
|
+
self.b._fov_preview.frame.grid_remove()
|
|
85
|
+
self.b._phasor_panel.frame.grid()
|
|
86
|
+
self.b._preview_frame_label.configure(text=' Phasor Analysis ')
|
|
87
|
+
if (hasattr(self.b, 'sv_ph_ptu') and hasattr(self.b, 'sv_ptu')
|
|
88
|
+
and not self.b.sv_ph_ptu.get().strip()):
|
|
89
|
+
fov_ptu = self.b.sv_ptu.get().strip()
|
|
90
|
+
if fov_ptu:
|
|
91
|
+
self.b.sv_ph_ptu.set(fov_ptu)
|
|
92
|
+
if hasattr(self.b, '_irf_fov'):
|
|
93
|
+
method = self.b._irf_fov.sv_method.get()
|
|
94
|
+
if method == 'irf_xlsx' and hasattr(self.b, 'sv_xlsx'):
|
|
95
|
+
xlsx = self.b.sv_xlsx.get().strip()
|
|
96
|
+
if xlsx and not self.b.sv_ph_irf.get().strip():
|
|
97
|
+
self.b.sv_ph_irf.set(xlsx)
|
|
98
|
+
elif method == 'machine_irf':
|
|
99
|
+
mirf = self.b._irf_fov.sv_path.get().strip()
|
|
100
|
+
if mirf and not self.b.sv_ph_mirf.get().strip():
|
|
101
|
+
self.b.sv_ph_mirf.set(mirf)
|
|
102
|
+
elif form_id in ('batch', 'irf'):
|
|
103
|
+
self.b._phasor_panel.frame.grid_remove()
|
|
104
|
+
self.b._fov_preview.frame.grid_remove()
|
|
105
|
+
label = ' Machine IRF Builder ' if form_id == 'irf' else ' Batch Processing '
|
|
106
|
+
self.b._preview_frame_label.configure(text=label)
|
|
107
|
+
# Show the IRF plot canvas if it exists (created after first build)
|
|
108
|
+
if form_id == 'irf' and hasattr(self.b, '_irf_plot_frame'):
|
|
109
|
+
self.b._irf_plot_frame.grid()
|
|
110
|
+
else:
|
|
111
|
+
self.b._phasor_panel.frame.grid_remove()
|
|
112
|
+
if hasattr(self.b, '_irf_plot_frame'):
|
|
113
|
+
self.b._irf_plot_frame.grid_remove()
|
|
114
|
+
self.b._fov_preview.frame.grid()
|
|
115
|
+
|
|
116
|
+
if hasattr(self.b, 'mode_status'):
|
|
117
|
+
self.b.mode_status.set(f"Current: {self.b._form_labels.get(form_id, form_id)}")
|
|
118
|
+
self.b._preview_frame_label.configure(text=' FOV Preview ')
|
|
File without changes
|
flimkit/UI/modes/base.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter import ttk
|
|
3
|
+
|
|
4
|
+
from flimkit.UI.modes.base import BaseMode
|
|
5
|
+
from flimkit.UI.utils import PAD, _C, _section, _row, _browse_file, _browse_dir, _tog, FLIM_FILETYPES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BatchMode(BaseMode):
|
|
9
|
+
def build(self):
|
|
10
|
+
outer, tab = self.b._form_inner_frames['batch']
|
|
11
|
+
tab.columnconfigure(0, weight=1)
|
|
12
|
+
|
|
13
|
+
self.b.state.sv_batch_mode = tk.StringVar(value='tiled')
|
|
14
|
+
|
|
15
|
+
self.b._batch_mode_label = ttk.Label(
|
|
16
|
+
tab, text='Mode: Multi-Tile ROI Fit',
|
|
17
|
+
font=('TkDefaultFont', 9, 'bold'), foreground='#555')
|
|
18
|
+
self.b._batch_mode_label.grid(row=0, column=0, sticky='w', padx=8, pady=(6, 2))
|
|
19
|
+
|
|
20
|
+
ff = _section(tab, 'Input / Output')
|
|
21
|
+
ff.grid(row=1, column=0, sticky='ew', pady=(0, 6))
|
|
22
|
+
ff.columnconfigure(1, weight=1)
|
|
23
|
+
self.b.state.sv_batch_xlif_dir = tk.StringVar()
|
|
24
|
+
self.b.state.sv_batch_ptu_dir = tk.StringVar()
|
|
25
|
+
self.b.state.sv_batch_out_dir = tk.StringVar()
|
|
26
|
+
|
|
27
|
+
self.b._batch_xlif_fr = ttk.Frame(ff)
|
|
28
|
+
self.b._batch_xlif_fr.grid(row=0, column=0, columnspan=3, sticky='ew')
|
|
29
|
+
self.b._batch_xlif_fr.columnconfigure(1, weight=1)
|
|
30
|
+
ttk.Label(self.b._batch_xlif_fr, text='XLIF folder *').grid(
|
|
31
|
+
row=0, column=0, sticky='e', padx=6, pady=3)
|
|
32
|
+
ttk.Entry(self.b._batch_xlif_fr, textvariable=self.b.sv_batch_xlif_dir, width=45).grid(
|
|
33
|
+
row=0, column=1, sticky='ew', padx=4, pady=3)
|
|
34
|
+
ttk.Button(self.b._batch_xlif_fr, text='Browse...',
|
|
35
|
+
command=lambda: _browse_dir(self.b.sv_batch_xlif_dir,
|
|
36
|
+
'Folder of XLIF files')).grid(
|
|
37
|
+
row=0, column=2, padx=4, pady=3)
|
|
38
|
+
|
|
39
|
+
_row(ff, 'PTU folder *', self.b.sv_batch_ptu_dir, 1,
|
|
40
|
+
lambda: _browse_dir(self.b.sv_batch_ptu_dir, 'PTU tile directory'))
|
|
41
|
+
|
|
42
|
+
self.b._batch_out_fr = ttk.Frame(ff)
|
|
43
|
+
self.b._batch_out_fr.grid(row=2, column=0, columnspan=3, sticky='ew')
|
|
44
|
+
self.b._batch_out_fr.columnconfigure(1, weight=1)
|
|
45
|
+
ttk.Label(self.b._batch_out_fr, text='Output base dir *').grid(
|
|
46
|
+
row=0, column=0, sticky='e', padx=6, pady=3)
|
|
47
|
+
ttk.Entry(self.b._batch_out_fr, textvariable=self.b.sv_batch_out_dir, width=45).grid(
|
|
48
|
+
row=0, column=1, sticky='ew', padx=4, pady=3)
|
|
49
|
+
ttk.Button(self.b._batch_out_fr, text='Browse...',
|
|
50
|
+
command=lambda: _browse_dir(self.b.sv_batch_out_dir,
|
|
51
|
+
'Base output directory')).grid(
|
|
52
|
+
row=0, column=2, padx=4, pady=3)
|
|
53
|
+
|
|
54
|
+
self.b.state.bv_batch_save_beside = tk.BooleanVar(value=False)
|
|
55
|
+
self.b.state.sv_batch_save_beside_preview = tk.StringVar(value='')
|
|
56
|
+
|
|
57
|
+
def _update_beside_preview(*_):
|
|
58
|
+
ptu = self.b.sv_batch_ptu_dir.get().strip()
|
|
59
|
+
preview = (ptu.rstrip('/\\') + '/save/') if ptu else '<ptu-dir>/save/'
|
|
60
|
+
self.b.sv_batch_save_beside_preview.set(preview)
|
|
61
|
+
|
|
62
|
+
def _on_save_beside(*_):
|
|
63
|
+
if self.b.bv_batch_save_beside.get():
|
|
64
|
+
self.b._batch_out_fr.grid_remove()
|
|
65
|
+
self.b._batch_beside_preview_lbl.pack(side='left', padx=2)
|
|
66
|
+
_update_beside_preview()
|
|
67
|
+
else:
|
|
68
|
+
self.b._batch_out_fr.grid()
|
|
69
|
+
self.b._batch_beside_preview_lbl.pack_forget()
|
|
70
|
+
|
|
71
|
+
self.b.sv_batch_ptu_dir.trace_add('write', _update_beside_preview)
|
|
72
|
+
|
|
73
|
+
beside_fr = ttk.Frame(ff)
|
|
74
|
+
beside_fr.grid(row=3, column=0, columnspan=3, sticky='w')
|
|
75
|
+
ttk.Checkbutton(beside_fr, text='Save beside input folder →',
|
|
76
|
+
variable=self.b.bv_batch_save_beside,
|
|
77
|
+
command=_on_save_beside).pack(side='left', padx=(4, 0))
|
|
78
|
+
self.b._batch_beside_preview_lbl = ttk.Label(
|
|
79
|
+
beside_fr, textvariable=self.b.sv_batch_save_beside_preview,
|
|
80
|
+
foreground='#5c9bd6')
|
|
81
|
+
self.b._batch_beside_preview_lbl.pack(side='left', padx=2)
|
|
82
|
+
self.b._batch_beside_preview_lbl.pack_forget()
|
|
83
|
+
|
|
84
|
+
self.b._batch_io_help = ttk.Label(
|
|
85
|
+
ff, text='One sub-folder per ROI created inside the output base dir.',
|
|
86
|
+
foreground='grey')
|
|
87
|
+
self.b._batch_io_help.grid(row=4, column=1, columnspan=2, sticky='w', padx=4)
|
|
88
|
+
|
|
89
|
+
fi = _section(tab, 'IRF')
|
|
90
|
+
fi.grid(row=2, column=0, sticky='ew', pady=(0, 6))
|
|
91
|
+
fi.columnconfigure(1, weight=1)
|
|
92
|
+
self.b.state.sv_batch_mirf = tk.StringVar(value=str(_C()['MACHINE_IRF_DEFAULT_PATH']))
|
|
93
|
+
_row(fi, 'Machine IRF (.npy) *', self.b.sv_batch_mirf, 0,
|
|
94
|
+
lambda: _browse_file(self.b.sv_batch_mirf, 'Machine IRF',
|
|
95
|
+
[('NumPy', '*.npy'), ('All', '*.*')]))
|
|
96
|
+
|
|
97
|
+
fp = _section(tab, 'Fitting Parameters', help_topic='fit_model')
|
|
98
|
+
fp.grid(row=3, column=0, sticky='ew', pady=(0, 6))
|
|
99
|
+
ttk.Label(fp, text='Fit model:').grid(row=0, column=0, sticky='w', **PAD)
|
|
100
|
+
self.b.state.sv_fit_model_batch = tk.StringVar(value='discrete')
|
|
101
|
+
ttk.Radiobutton(fp, text='n-exp', variable=self.b.sv_fit_model_batch,
|
|
102
|
+
value='discrete').grid(row=0, column=1, sticky='w', padx=1)
|
|
103
|
+
ttk.Radiobutton(fp, text='Gaussian dist.', variable=self.b.sv_fit_model_batch,
|
|
104
|
+
value='gaussian').grid(row=0, column=2, sticky='w', padx=1)
|
|
105
|
+
ttk.Radiobutton(fp, text='Lorentzian dist.', variable=self.b.sv_fit_model_batch,
|
|
106
|
+
value='lorentzian').grid(row=0, column=3, sticky='w', padx=1)
|
|
107
|
+
ttk.Radiobutton(fp, text='n-exp tail', variable=self.b.sv_fit_model_batch,
|
|
108
|
+
value='tail').grid(row=0, column=4, sticky='w', padx=1)
|
|
109
|
+
|
|
110
|
+
self.b.state.iv_nexp_batch = tk.IntVar(value=2)
|
|
111
|
+
self.b.state.iv_ncomp_dist_batch = tk.IntVar(value=1)
|
|
112
|
+
|
|
113
|
+
nexp_frame_batch = ttk.Frame(fp)
|
|
114
|
+
nexp_frame_batch.grid(row=1, column=0, columnspan=5, sticky='w')
|
|
115
|
+
ttk.Label(nexp_frame_batch, text='Components:').pack(side='left', padx=(4, 8))
|
|
116
|
+
for n in (1, 2, 3):
|
|
117
|
+
ttk.Radiobutton(nexp_frame_batch, text=str(n), variable=self.b.iv_nexp_batch,
|
|
118
|
+
value=n).pack(side='left', padx=4)
|
|
119
|
+
|
|
120
|
+
dist_frame_batch = ttk.Frame(fp)
|
|
121
|
+
ttk.Label(dist_frame_batch, text='Components:').pack(side='left', padx=(4, 8))
|
|
122
|
+
ttk.Radiobutton(dist_frame_batch, text='1 (unimodal)', variable=self.b.iv_ncomp_dist_batch,
|
|
123
|
+
value=1).pack(side='left', padx=4)
|
|
124
|
+
ttk.Radiobutton(dist_frame_batch, text='2 (bimodal)', variable=self.b.iv_ncomp_dist_batch,
|
|
125
|
+
value=2).pack(side='left', padx=4)
|
|
126
|
+
|
|
127
|
+
def _on_batch_model_change(*_):
|
|
128
|
+
if self.b.sv_fit_model_batch.get() in ('discrete', 'tail'):
|
|
129
|
+
dist_frame_batch.grid_remove()
|
|
130
|
+
nexp_frame_batch.grid(row=1, column=0, columnspan=5, sticky='w')
|
|
131
|
+
else:
|
|
132
|
+
nexp_frame_batch.grid_remove()
|
|
133
|
+
dist_frame_batch.grid(row=1, column=0, columnspan=5, sticky='w')
|
|
134
|
+
self.b.sv_fit_model_batch.trace_add('write', _on_batch_model_change)
|
|
135
|
+
|
|
136
|
+
ttk.Label(fp, text='Fit window (ns):').grid(row=2, column=0, sticky='w', **PAD)
|
|
137
|
+
self.b.state.sv_batch_tau_min = tk.StringVar(value=str(_C()['Tau_min']))
|
|
138
|
+
self.b.state.sv_batch_tau_max = tk.StringVar(value=str(_C()['Tau_max']))
|
|
139
|
+
ttk.Entry(fp, textvariable=self.b.sv_batch_tau_min, width=7).grid(row=2, column=1, padx=4)
|
|
140
|
+
ttk.Label(fp, text='to').grid(row=2, column=2)
|
|
141
|
+
ttk.Entry(fp, textvariable=self.b.sv_batch_tau_max, width=7).grid(row=2, column=3, padx=4)
|
|
142
|
+
ttk.Label(fp, text='ns', foreground='grey').grid(row=2, column=4, padx=4)
|
|
143
|
+
|
|
144
|
+
ttk.Label(fp, text='Colour scale (ns):').grid(row=3, column=0, sticky='w', **PAD)
|
|
145
|
+
self.b.state.sv_batch_tau_lo = tk.StringVar(
|
|
146
|
+
value='' if _C()['TAU_DISPLAY_MIN'] is None else str(_C()['TAU_DISPLAY_MIN']))
|
|
147
|
+
self.b.state.sv_batch_tau_hi = tk.StringVar(
|
|
148
|
+
value='' if _C()['TAU_DISPLAY_MAX'] is None else str(_C()['TAU_DISPLAY_MAX']))
|
|
149
|
+
ttk.Entry(fp, textvariable=self.b.sv_batch_tau_lo, width=7).grid(row=3, column=1, padx=4)
|
|
150
|
+
ttk.Label(fp, text='to').grid(row=3, column=2)
|
|
151
|
+
ttk.Entry(fp, textvariable=self.b.sv_batch_tau_hi, width=7).grid(row=3, column=3, padx=4)
|
|
152
|
+
ttk.Label(fp, text='ns (display only)', foreground='grey').grid(row=3, column=4, padx=4)
|
|
153
|
+
|
|
154
|
+
freg = _section(tab, 'Tile Registration')
|
|
155
|
+
freg.grid(row=4, column=0, sticky='ew', pady=(0, 6))
|
|
156
|
+
self.b._batch_freg = freg
|
|
157
|
+
self.b.state.bv_batch_register = tk.BooleanVar(value=True)
|
|
158
|
+
ttk.Checkbutton(freg, text='Phase-correlation registration (fixes stage Y/X drift)',
|
|
159
|
+
variable=self.b.bv_batch_register).grid(
|
|
160
|
+
row=0, column=0, columnspan=3, sticky='w', **PAD)
|
|
161
|
+
ttk.Label(freg, text='Max shift (px):').grid(row=1, column=0, sticky='w', **PAD)
|
|
162
|
+
self.b.state.sv_batch_reg_shift = tk.StringVar(value='120')
|
|
163
|
+
ttk.Entry(freg, textvariable=self.b.sv_batch_reg_shift, width=6).grid(
|
|
164
|
+
row=1, column=1, sticky='w', padx=4)
|
|
165
|
+
ttk.Label(freg, text='(increase if drift > 120px)',
|
|
166
|
+
foreground='grey').grid(row=1, column=2, sticky='w')
|
|
167
|
+
|
|
168
|
+
fm = _section(tab, 'Masking', help_topic='masking')
|
|
169
|
+
fm.grid(row=5, column=0, sticky='ew', pady=(0, 6))
|
|
170
|
+
self.b.state.bv_batch_thr = tk.BooleanVar(value=False)
|
|
171
|
+
self.b.state.sv_batch_thr = tk.StringVar()
|
|
172
|
+
ttk.Checkbutton(fm, text='Intensity threshold (min photons/px):',
|
|
173
|
+
variable=self.b.bv_batch_thr,
|
|
174
|
+
command=lambda: _tog(self.b.bv_batch_thr, self.b._batch_thr_e)).grid(
|
|
175
|
+
row=0, column=0, sticky='w', **PAD)
|
|
176
|
+
self.b._batch_thr_e = ttk.Entry(fm, textvariable=self.b.sv_batch_thr,
|
|
177
|
+
width=8, state='disabled')
|
|
178
|
+
self.b._batch_thr_e.grid(row=0, column=1, sticky='w', padx=4)
|
|
179
|
+
self.b.state.bv_batch_correct_pileup = tk.BooleanVar(value=False)
|
|
180
|
+
ttk.Checkbutton(fm, text='Apply Coates pile-up correction (recommended if pile-up > 5%)',
|
|
181
|
+
variable=self.b.bv_batch_correct_pileup).grid(
|
|
182
|
+
row=1, column=0, columnspan=3, sticky='w', **PAD)
|
|
183
|
+
|
|
184
|
+
ttk.Label(fm, text='Time-varying background PTU:').grid(row=2, column=0, sticky='w', **PAD)
|
|
185
|
+
self.b.state.sv_batch_tvb_ptu = tk.StringVar()
|
|
186
|
+
ttk.Entry(fm, textvariable=self.b.sv_batch_tvb_ptu, width=24).grid(
|
|
187
|
+
row=2, column=1, sticky='ew', padx=4)
|
|
188
|
+
ttk.Button(fm, text='Browse...',
|
|
189
|
+
command=lambda: _browse_file(self.b.sv_batch_tvb_ptu,
|
|
190
|
+
'Background reference PTU',
|
|
191
|
+
FLIM_FILETYPES)).grid(
|
|
192
|
+
row=2, column=2, sticky='w', padx=4)
|
|
193
|
+
ttk.Label(fm, text='(optional: applied to every FOV in the batch)',
|
|
194
|
+
foreground='grey').grid(row=3, column=0, columnspan=3, sticky='w', padx=8)
|
|
195
|
+
|
|
196
|
+
fexp = _section(tab, 'Image Export')
|
|
197
|
+
fexp.grid(row=6, column=0, sticky='ew', pady=(0, 6))
|
|
198
|
+
self.b.state.bv_batch_save_lifetime = tk.BooleanVar(value=True)
|
|
199
|
+
self.b.state.bv_batch_save_rgb = tk.BooleanVar(value=True)
|
|
200
|
+
self.b.state.bv_batch_save_intensity = tk.BooleanVar(value=True)
|
|
201
|
+
self.b.state.bv_batch_save_npy = tk.BooleanVar(value=True)
|
|
202
|
+
self.b.state.bv_batch_save_ind = tk.BooleanVar(value=False)
|
|
203
|
+
ttk.Checkbutton(fexp, text='Lifetime image (uint16 TIFF)',
|
|
204
|
+
variable=self.b.bv_batch_save_lifetime).grid(row=0, column=0, sticky='w', **PAD)
|
|
205
|
+
ttk.Checkbutton(fexp, text='Component RGB TIFF',
|
|
206
|
+
variable=self.b.bv_batch_save_rgb).grid(row=0, column=1, sticky='w', **PAD)
|
|
207
|
+
ttk.Checkbutton(fexp, text='Intensity TIFF',
|
|
208
|
+
variable=self.b.bv_batch_save_intensity).grid(row=0, column=2, sticky='w', **PAD)
|
|
209
|
+
ttk.Checkbutton(fexp, text='Raw maps (.npy)',
|
|
210
|
+
variable=self.b.bv_batch_save_npy).grid(row=1, column=0, sticky='w', **PAD)
|
|
211
|
+
ttk.Checkbutton(fexp, text='Individual component maps (τ₁, a₁, τ₂...)',
|
|
212
|
+
variable=self.b.bv_batch_save_ind).grid(row=1, column=1, columnspan=2, sticky='w', **PAD)
|
|
213
|
+
self.b.state.bv_batch_save_stack = tk.BooleanVar(value=True)
|
|
214
|
+
ttk.Checkbutton(fexp, text='4D stacks - (T, H, W) .npy per map (timelapse only)',
|
|
215
|
+
variable=self.b.bv_batch_save_stack).grid(row=2, column=0, columnspan=3, sticky='w', **PAD)
|
|
216
|
+
ttk.Label(fexp, text='Lifetime colour scale (ns):').grid(row=3, column=0, sticky='w', **PAD)
|
|
217
|
+
ttk.Entry(fexp, textvariable=self.b.sv_batch_tau_lo, width=7).grid(row=3, column=1, sticky='w', padx=4)
|
|
218
|
+
ttk.Label(fexp, text='to').grid(row=3, column=2)
|
|
219
|
+
ttk.Entry(fexp, textvariable=self.b.sv_batch_tau_hi, width=7).grid(row=3, column=3, sticky='w', padx=4)
|
|
220
|
+
ttk.Label(fexp, text='ns (blank = auto)', foreground='grey').grid(row=3, column=4, sticky='w', padx=4)
|
|
221
|
+
ttk.Label(fexp, text='Gamma (lifetime image):').grid(row=4, column=0, sticky='w', **PAD)
|
|
222
|
+
self.b.state.sv_batch_gamma = tk.StringVar(value='0.4')
|
|
223
|
+
ttk.Entry(fexp, textvariable=self.b.sv_batch_gamma, width=5).grid(row=4, column=1, sticky='w', padx=4)
|
|
224
|
+
ttk.Label(fexp, text='(0.4 = boost dim tissue; 1.0 = linear)',
|
|
225
|
+
foreground='grey').grid(row=4, column=2, columnspan=3, sticky='w')
|
|
226
|
+
ttk.Label(fexp, text='Intensity display max:').grid(row=5, column=0, sticky='w', **PAD)
|
|
227
|
+
self.b.state.sv_batch_int_max = tk.StringVar()
|
|
228
|
+
ttk.Entry(fexp, textvariable=self.b.sv_batch_int_max, width=8).grid(row=5, column=1, sticky='w', padx=4)
|
|
229
|
+
ttk.Label(fexp, text='(blank = auto 99th percentile)',
|
|
230
|
+
foreground='grey').grid(row=5, column=2, columnspan=3, sticky='w')
|
|
231
|
+
|
|
232
|
+
ttk.Label(fexp, text='Lifetime weighting:').grid(row=6, column=0, sticky='w', **PAD)
|
|
233
|
+
self.b.state.sv_batch_tau_weighting = tk.StringVar(value='amplitude')
|
|
234
|
+
ttk.Combobox(fexp, textvariable=self.b.sv_batch_tau_weighting, state='readonly',
|
|
235
|
+
values=['amplitude', 'intensity'], width=11).grid(row=6, column=1, sticky='w', padx=4)
|
|
236
|
+
ttk.Label(fexp, text='(τ map / lifetime image; both raw .npy saved either way)',
|
|
237
|
+
foreground='grey').grid(row=6, column=2, columnspan=3, sticky='w')
|
|
238
|
+
|
|
239
|
+
ftl = _section(tab, 'Timelapse - Reference Lifetimes')
|
|
240
|
+
ftl.grid(row=7, column=0, sticky='ew', pady=(0, 6))
|
|
241
|
+
self.b._batch_tl_fr = ftl
|
|
242
|
+
|
|
243
|
+
self.b.state.bv_tl_fix_tau = tk.BooleanVar(value=False)
|
|
244
|
+
self.b.state.sv_tl_tau1 = tk.StringVar(value='')
|
|
245
|
+
self.b.state.sv_tl_tau2 = tk.StringVar(value='')
|
|
246
|
+
self.b.state.sv_tl_tau3 = tk.StringVar(value='')
|
|
247
|
+
|
|
248
|
+
ttk.Checkbutton(ftl,
|
|
249
|
+
text='Fix reference τ values (skip pooled-decay fit)',
|
|
250
|
+
variable=self.b.bv_tl_fix_tau,
|
|
251
|
+
command=lambda: _tog_tl_entries()).grid(
|
|
252
|
+
row=0, column=0, columnspan=6, sticky='w', **PAD)
|
|
253
|
+
|
|
254
|
+
self.b._tl_tau_fr = ttk.Frame(ftl)
|
|
255
|
+
self.b._tl_tau_fr.grid(row=1, column=0, columnspan=6, sticky='w')
|
|
256
|
+
|
|
257
|
+
def _make_tau_box(col, sym, var):
|
|
258
|
+
box = ttk.Frame(self.b._tl_tau_fr)
|
|
259
|
+
box.grid(row=0, column=col, sticky='w')
|
|
260
|
+
ttk.Label(box, text=sym).grid(row=0, column=0, sticky='e', padx=(8, 2))
|
|
261
|
+
e = ttk.Entry(box, textvariable=var, width=8, state='disabled')
|
|
262
|
+
e.grid(row=0, column=1, padx=4)
|
|
263
|
+
return box, e
|
|
264
|
+
|
|
265
|
+
self.b._tl_tau1_box, self.b._tl_tau1_e = _make_tau_box(0, 'τ₁ (ns):', self.b.sv_tl_tau1)
|
|
266
|
+
self.b._tl_tau2_box, self.b._tl_tau2_e = _make_tau_box(1, 'τ₂ (ns):', self.b.sv_tl_tau2)
|
|
267
|
+
self.b._tl_tau3_box, self.b._tl_tau3_e = _make_tau_box(2, 'τ₃ (ns):', self.b.sv_tl_tau3)
|
|
268
|
+
ttk.Label(self.b._tl_tau_fr, text='(leave blank to fit from pooled data)',
|
|
269
|
+
foreground='grey').grid(row=0, column=3, padx=6)
|
|
270
|
+
|
|
271
|
+
def _tog_tl_entries():
|
|
272
|
+
state = 'normal' if self.b.bv_tl_fix_tau.get() else 'disabled'
|
|
273
|
+
self.b._tl_tau1_e.config(state=state)
|
|
274
|
+
self.b._tl_tau2_e.config(state=state)
|
|
275
|
+
self.b._tl_tau3_e.config(state=state)
|
|
276
|
+
|
|
277
|
+
def _update_tl_tau_count(*_):
|
|
278
|
+
n = self.b.iv_nexp_batch.get()
|
|
279
|
+
(self.b._tl_tau2_box.grid() if n >= 2 else self.b._tl_tau2_box.grid_remove())
|
|
280
|
+
(self.b._tl_tau3_box.grid() if n >= 3 else self.b._tl_tau3_box.grid_remove())
|
|
281
|
+
|
|
282
|
+
self.b.iv_nexp_batch.trace_add('write', _update_tl_tau_count)
|
|
283
|
+
_update_tl_tau_count()
|
|
284
|
+
self.b.state.bv_tl_pool_positions = tk.BooleanVar(value=False)
|
|
285
|
+
ttk.Checkbutton(ftl,
|
|
286
|
+
text='Pool all positions for a shared reference τ '
|
|
287
|
+
'(default: each position gets its own τ)',
|
|
288
|
+
variable=self.b.bv_tl_pool_positions).grid(
|
|
289
|
+
row=2, column=0, columnspan=5, sticky='w', **PAD)
|
|
290
|
+
|
|
291
|
+
self.b.state.bv_tl_bound_fraction = tk.BooleanVar(value=False)
|
|
292
|
+
ttk.Checkbutton(ftl,
|
|
293
|
+
text='Compute bound fraction α₂/(α₁+α₂) '
|
|
294
|
+
'(off by default - verify component ordering)',
|
|
295
|
+
variable=self.b.bv_tl_bound_fraction).grid(
|
|
296
|
+
row=3, column=0, columnspan=5, sticky='w', **PAD)
|
|
297
|
+
|
|
298
|
+
ftl.grid_remove()
|
|
299
|
+
|
|
300
|
+
self.b._expert_banner_batch = ttk.Label(
|
|
301
|
+
tab, text='⚙ Custom expert settings active',
|
|
302
|
+
foreground='#e8a838', font=('TkDefaultFont', 9, 'bold'))
|
|
303
|
+
self.b._expert_banner_batch.grid(row=8, column=0, sticky='w', padx=8)
|
|
304
|
+
self.b._expert_banner_batch.grid_remove()
|
|
305
|
+
|
|
306
|
+
btn_row_batch = ttk.Frame(tab)
|
|
307
|
+
btn_row_batch.grid(row=9, column=0, pady=8)
|
|
308
|
+
ttk.Button(btn_row_batch, text='⚙ Expert Settings',
|
|
309
|
+
command=self.b._open_expert_settings).pack(side='left', padx=4)
|
|
310
|
+
self.b._btn_batch = ttk.Button(btn_row_batch, text='▶ Run Batch ROI Fit',
|
|
311
|
+
command=self.b._dispatch_batch)
|
|
312
|
+
self.b._btn_batch.pack(side='left', padx=4, ipadx=20, ipady=4)
|