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.
Files changed (104) hide show
  1. flimkit/FLIM/__init__.py +0 -0
  2. flimkit/FLIM/assemble.py +254 -0
  3. flimkit/FLIM/batch.py +681 -0
  4. flimkit/FLIM/bg_tools.py +51 -0
  5. flimkit/FLIM/fit_tools.py +244 -0
  6. flimkit/FLIM/fitters.py +1471 -0
  7. flimkit/FLIM/irf_tools.py +617 -0
  8. flimkit/FLIM/models.py +391 -0
  9. flimkit/GPU/__init__.py +85 -0
  10. flimkit/GPU/_base.py +391 -0
  11. flimkit/GPU/cuda.py +10 -0
  12. flimkit/GPU/mlx_backend.py +381 -0
  13. flimkit/GPU/mps.py +10 -0
  14. flimkit/GPU/rocm.py +10 -0
  15. flimkit/GPU/torch_backend.py +385 -0
  16. flimkit/UI/app_state.py +10 -0
  17. flimkit/UI/controller.py +139 -0
  18. flimkit/UI/expert_settings.py +248 -0
  19. flimkit/UI/fit_help.py +206 -0
  20. flimkit/UI/fov_preview.py +1085 -0
  21. flimkit/UI/gui.py +3919 -0
  22. flimkit/UI/icon.icns +0 -0
  23. flimkit/UI/icon.ico +0 -0
  24. flimkit/UI/icon.png +0 -0
  25. flimkit/UI/irf_widget.py +103 -0
  26. flimkit/UI/mode_controller.py +118 -0
  27. flimkit/UI/modes/__init__.py +0 -0
  28. flimkit/UI/modes/base.py +3 -0
  29. flimkit/UI/modes/batch_mode.py +312 -0
  30. flimkit/UI/modes/fov_mode.py +164 -0
  31. flimkit/UI/modes/irf_mode.py +80 -0
  32. flimkit/UI/modes/phasor_mode.py +131 -0
  33. flimkit/UI/modes/stitch_mode.py +254 -0
  34. flimkit/UI/phasor_panel.py +1087 -0
  35. flimkit/UI/progress_window.py +113 -0
  36. flimkit/UI/project_panel.py +262 -0
  37. flimkit/UI/results_panel.py +332 -0
  38. flimkit/UI/roi_tools.py +794 -0
  39. flimkit/UI/utils.py +217 -0
  40. flimkit/__init__.py +0 -0
  41. flimkit/_version.py +41 -0
  42. flimkit/cli.py +120 -0
  43. flimkit/configs.py +148 -0
  44. flimkit/dialogs.py +46 -0
  45. flimkit/formats/BH/__init__.py +0 -0
  46. flimkit/formats/BH/reader.py +296 -0
  47. flimkit/formats/BH/writer.py +86 -0
  48. flimkit/formats/ISS/__init__.py +0 -0
  49. flimkit/formats/ISS/fdflim.py +86 -0
  50. flimkit/formats/ISS/image.py +114 -0
  51. flimkit/formats/ISS/reader.py +223 -0
  52. flimkit/formats/PS/__init__.py +0 -0
  53. flimkit/formats/PS/reader.py +202 -0
  54. flimkit/formats/PTU/__init__.py +0 -0
  55. flimkit/formats/PTU/decode.py +27 -0
  56. flimkit/formats/PTU/phu.py +85 -0
  57. flimkit/formats/PTU/reader.py +235 -0
  58. flimkit/formats/PTU/series.py +258 -0
  59. flimkit/formats/PTU/stitch.py +1182 -0
  60. flimkit/formats/PTU/tools.py +94 -0
  61. flimkit/formats/__init__.py +2 -0
  62. flimkit/formats/flim_file.py +232 -0
  63. flimkit/formats/phasor.py +132 -0
  64. flimkit/formats/signal.py +170 -0
  65. flimkit/image/tools.py +124 -0
  66. flimkit/interactive.py +1857 -0
  67. flimkit/mpl_backend.py +22 -0
  68. flimkit/phasor/__init__.py +40 -0
  69. flimkit/phasor/filters.py +127 -0
  70. flimkit/phasor/fret.py +654 -0
  71. flimkit/phasor/interactive.py +556 -0
  72. flimkit/phasor/peaks.py +186 -0
  73. flimkit/phasor/signal.py +90 -0
  74. flimkit/phasor_launcher.py +314 -0
  75. flimkit/plugins/__init__.py +137 -0
  76. flimkit/plugins/bindings.py +116 -0
  77. flimkit/plugins/builtin/__init__.py +3 -0
  78. flimkit/plugins/builtin/core_tools.py +28 -0
  79. flimkit/plugins/loader.py +371 -0
  80. flimkit/plugins/registry.py +406 -0
  81. flimkit/project.py +197 -0
  82. flimkit/synth.py +145 -0
  83. flimkit/utils/__init__.py +0 -0
  84. flimkit/utils/batch_fit.py +301 -0
  85. flimkit/utils/config_manager.py +119 -0
  86. flimkit/utils/config_snapshot.py +30 -0
  87. flimkit/utils/crash_handler.py +183 -0
  88. flimkit/utils/display.py +197 -0
  89. flimkit/utils/enhanced_outputs.py +345 -0
  90. flimkit/utils/fancy.py +103 -0
  91. flimkit/utils/lifetime_image.py +243 -0
  92. flimkit/utils/misc.py +111 -0
  93. flimkit/utils/plotting.py +190 -0
  94. flimkit/utils/roi.py +370 -0
  95. flimkit/utils/session.py +51 -0
  96. flimkit/utils/update_check.py +198 -0
  97. flimkit/utils/xlsx_tools.py +97 -0
  98. flimkit/utils/xml_utils.py +219 -0
  99. flimkit-0.12.0.dist-info/METADATA +356 -0
  100. flimkit-0.12.0.dist-info/RECORD +104 -0
  101. flimkit-0.12.0.dist-info/WHEEL +5 -0
  102. flimkit-0.12.0.dist-info/entry_points.txt +2 -0
  103. flimkit-0.12.0.dist-info/licenses/LICENSE.md +11 -0
  104. flimkit-0.12.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,113 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk
3
+ import threading
4
+
5
+ class ProgressWindow:
6
+ def __init__(self, parent, task_name='Working...'):
7
+ self.parent = parent
8
+ self.cancelled = threading.Event()
9
+ self.top = tk.Toplevel(parent)
10
+ self.top.title(task_name)
11
+ self.top.geometry('400x120')
12
+ self.top.resizable(True, True)
13
+ self.top.transient(parent)
14
+ self.top.grab_set()
15
+ self.top.protocol('WM_DELETE_WINDOW', self.cancel)
16
+
17
+ # Make the window scalable with columns and rows
18
+ self.top.columnconfigure(0, weight=1)
19
+ self.top.rowconfigure(1, weight=1)
20
+
21
+ ttk.Label(self.top, text=task_name, font=('Arial', 12, 'bold')).grid(
22
+ row=0, column=0, pady=(16, 8), sticky='ew', padx=16)
23
+ self.progress = ttk.Progressbar(self.top, orient='horizontal', mode='determinate')
24
+ self.progress.grid(row=1, column=0, pady=6, padx=16, sticky='ew')
25
+ self.status = tk.StringVar(value='Starting...')
26
+ ttk.Label(self.top, textvariable=self.status).grid(
27
+ row=2, column=0, pady=(2, 8), padx=16, sticky='ew')
28
+ self.btn_cancel = ttk.Button(self.top, text='Cancel', command=self.cancel)
29
+ self.btn_cancel.grid(row=3, column=0, pady=(0, 8), sticky='ew', padx=16)
30
+
31
+ def set_progress(self, value, maximum=None):
32
+ if maximum is not None:
33
+ self.progress['maximum'] = maximum
34
+ self.progress['value'] = value
35
+ self.status.set(f"{int(value)}/{int(self.progress['maximum'])} completed")
36
+ self.top.update_idletasks()
37
+
38
+ def set_status(self, msg):
39
+ self.status.set(msg)
40
+ self.top.update_idletasks()
41
+
42
+ def cancel(self):
43
+ self.cancelled.set()
44
+ self.set_status('Cancelling...')
45
+ self.btn_cancel.config(state='disabled')
46
+
47
+ def close(self):
48
+ self.top.grab_release()
49
+ self.top.destroy()
50
+
51
+ def was_cancelled(self):
52
+ return self.cancelled.is_set()
53
+
54
+
55
+ class ProgressWindowManager:
56
+
57
+ def __init__(self, root):
58
+ self.root = root
59
+ self.windows = {}
60
+ self.counter = 0
61
+ self.lock = threading.Lock()
62
+
63
+ def create_progress_window(self, task_name='Processing...'):
64
+ with self.lock:
65
+ window_id = self.counter
66
+ self.counter += 1
67
+
68
+ def create_window():
69
+ window_ref = ProgressWindow(self.root, task_name=task_name)
70
+ with self.lock:
71
+ self.windows[window_id] = window_ref
72
+
73
+ self.root.after(0, create_window)
74
+ return window_id
75
+
76
+ def update_progress(self, window_id, current, total):
77
+ def update():
78
+ if window_id in self.windows:
79
+ try:
80
+ self.windows[window_id].set_progress(current, maximum=total)
81
+ except Exception:
82
+ pass
83
+
84
+ self.root.after(0, update)
85
+
86
+ def set_status(self, window_id, msg):
87
+ def update():
88
+ if window_id in self.windows:
89
+ try:
90
+ self.windows[window_id].set_status(msg)
91
+ except Exception:
92
+ pass
93
+
94
+ self.root.after(0, update)
95
+
96
+ def close_window(self, window_id):
97
+ def close():
98
+ if window_id in self.windows:
99
+ try:
100
+ self.windows[window_id].close()
101
+ except Exception:
102
+ pass
103
+ finally:
104
+ with self.lock:
105
+ self.windows.pop(window_id, None)
106
+
107
+ self.root.after(0, close)
108
+
109
+ def close_all(self):
110
+ with self.lock:
111
+ ids = list(self.windows.keys())
112
+ for wid in ids:
113
+ self.close_window(wid)
@@ -0,0 +1,262 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk, filedialog
3
+ from pathlib import Path
4
+
5
+ _ICON_FIT_ONLY = '●'
6
+ _ICON_PHASOR_ONLY = '◐'
7
+ _ICON_BOTH = '◉'
8
+ _ICON_NOSESSION = '○'
9
+ _ICON_FOV = 'F'
10
+ _ICON_XLIF = 'T'
11
+ _ICON_ZSTACK = 'Z'
12
+
13
+ class ProjectBrowserPanel:
14
+
15
+ def __init__(self, parent, app, width=170):
16
+ self._app = app
17
+ self._width = width
18
+ self._project = None
19
+ self._stems = []
20
+ self.frame = ttk.Frame(parent, width=width)
21
+ self.frame.grid_propagate(False)
22
+ self.frame.columnconfigure(0, weight=1)
23
+ self.frame.rowconfigure(1, weight=1)
24
+ hdr = ttk.Frame(self.frame)
25
+ hdr.grid(row=0, column=0, sticky='ew', padx=4, pady=(6, 2))
26
+ ttk.Label(hdr, text='Project',
27
+ font=('TkDefaultFont', 9, 'bold')).pack(side='left')
28
+ lb_outer = ttk.Frame(self.frame, relief='sunken', borderwidth=1)
29
+ lb_outer.grid(row=1, column=0, sticky='nsew', padx=4, pady=2)
30
+ lb_outer.columnconfigure(0, weight=1)
31
+ lb_outer.rowconfigure(0, weight=1)
32
+ sb = ttk.Scrollbar(lb_outer, orient='vertical')
33
+ sb.grid(row=0, column=1, sticky='ns')
34
+ self._lb = tk.Listbox(
35
+ lb_outer,
36
+ yscrollcommand = sb.set,
37
+ selectmode = 'single',
38
+ activestyle = 'none',
39
+ font = ('Courier', 9),
40
+ relief = 'flat',
41
+ borderwidth = 0,
42
+ highlightthickness = 0,
43
+ exportselection = False,
44
+ )
45
+ self._lb.grid(row=0, column=0, sticky='nsew')
46
+ sb.config(command=self._lb.yview)
47
+ self._lb.bind('<<ListboxSelect>>', self._on_select)
48
+ self._sv_status = tk.StringVar(value='No project open')
49
+ ttk.Label(
50
+ self.frame,
51
+ textvariable = self._sv_status,
52
+ foreground = 'grey',
53
+ font = ('TkDefaultFont', 7),
54
+ wraplength = width - 12,
55
+ anchor = 'w',
56
+ ).grid(row=2, column=0, sticky='ew', padx=6, pady=(2, 6))
57
+ self._setup_dnd()
58
+
59
+ def grid(self, **kw):
60
+ self.frame.grid(**kw)
61
+
62
+ def on_fit_done(
63
+ self,
64
+ stem,
65
+ out_st=None,
66
+ output_prefix=None,
67
+ ptu_dir=None,
68
+ ):
69
+ if self._project is None:
70
+ return
71
+ self._project.update_after_fit(
72
+ stem,
73
+ out_st = out_st,
74
+ output_prefix = output_prefix,
75
+ ptu_dir = ptu_dir,
76
+ )
77
+ self._project.save()
78
+ self._refresh()
79
+
80
+ def on_phasor_done(self, stem):
81
+ if self._project is None:
82
+ return
83
+ self._project.update_after_phasor(stem)
84
+ self._project.save()
85
+ self._refresh()
86
+
87
+ def _setup_dnd(self):
88
+ try:
89
+ from tkinterdnd2 import DND_FILES, DND_TEXT
90
+ except ImportError:
91
+ return
92
+ try:
93
+ self._lb.drop_target_register(DND_FILES, DND_TEXT)
94
+ self._lb.dnd_bind('<<Drop>>', self._on_drop)
95
+ except Exception:
96
+ pass
97
+
98
+ def _on_drop(self, event):
99
+ try:
100
+ paths = self._lb.tk.splitlist(event.data)
101
+ except Exception:
102
+ paths = [event.data]
103
+ for p in paths:
104
+ p = p.strip()
105
+ if not p:
106
+ continue
107
+ path = Path(p)
108
+ if path.is_dir():
109
+ self.load_folder(str(path))
110
+ if hasattr(self._app, '_add_to_recent'):
111
+ self._app._add_to_recent(str(path), 'project')
112
+ return
113
+ if path.is_file():
114
+ if hasattr(self._app, '_switch_form'):
115
+ self._app._switch_form('fov')
116
+ if hasattr(self._app, 'sv_ptu'):
117
+ self._app.sv_ptu.set(str(path))
118
+ return
119
+
120
+ def load_folder(self, folder):
121
+ if not folder:
122
+ return
123
+ from flimkit.project import ProjectFile
124
+ self._project = ProjectFile.load_or_create(Path(folder))
125
+ self._project.save()
126
+ if self._project.config:
127
+ from flimkit.utils.config_manager import cfg
128
+ cfg.load_project_overrides(self._project.config)
129
+ self._refresh()
130
+
131
+ def _open_project(self):
132
+ folder = filedialog.askdirectory(title='Open project folder')
133
+ if not folder:
134
+ return
135
+ self.load_folder(folder)
136
+ if hasattr(self._app, '_add_to_recent'):
137
+ self._app._add_to_recent(folder, 'project')
138
+
139
+ def _refresh(self):
140
+ if self._project is None:
141
+ return
142
+ self._lb.delete(0, tk.END)
143
+ self._stems.clear()
144
+ for stem, rec in self._project.sorted_scans():
145
+ has_fit = rec.has_session
146
+ has_ph = rec.has_phasor_session
147
+ if has_fit and has_ph:
148
+ session_dot = _ICON_BOTH
149
+ elif has_fit:
150
+ session_dot = _ICON_FIT_ONLY
151
+ elif has_ph:
152
+ session_dot = _ICON_PHASOR_ONLY
153
+ else:
154
+ session_dot = _ICON_NOSESSION
155
+ if rec.scan_type == 'xlif':
156
+ type_tag = _ICON_XLIF
157
+ elif rec.scan_type == 'zstack':
158
+ type_tag = _ICON_ZSTACK
159
+ else:
160
+ type_tag = _ICON_FOV
161
+ label = f'{session_dot} {type_tag} {stem}'
162
+ self._lb.insert(tk.END, label)
163
+ self._stems.append(stem)
164
+ n = len(self._stems)
165
+ n_sess = sum(1 for r in self._project.scans.values()
166
+ if r.has_session or r.has_phasor_session)
167
+ folder_name = self._project.project_dir.name
168
+ self._sv_status.set(
169
+ f"{folder_name} | {n} scan{'s' if n != 1 else ''} | {n_sess} saved"
170
+ )
171
+
172
+ def _on_select(self, _event=None):
173
+ if getattr(self, '_selecting', False):
174
+ return
175
+ sel = self._lb.curselection()
176
+ if not sel or self._project is None:
177
+ return
178
+ stem = self._stems[sel[0]]
179
+ rec = self._project.scans.get(stem)
180
+ if rec is None:
181
+ return
182
+ self._selecting = True
183
+ try:
184
+ self._load_scan(rec)
185
+ finally:
186
+ self._selecting = False
187
+
188
+ def _load_scan(self, rec):
189
+ app = self._app
190
+ if hasattr(app, '_cancel_pending_scan_loads'):
191
+ app._cancel_pending_scan_loads()
192
+ if hasattr(app, '_fov_preview'):
193
+ app._fov_preview._roi_manager.clear_all()
194
+ app._fov_preview._roi_patches.clear()
195
+ app._fov_preview._redraw_region_overlays()
196
+ for panel in (getattr(app, '_roi_analysis_panel', None),
197
+ getattr(app, '_stitch_roi_panel', None)):
198
+ if panel is not None:
199
+ panel._refresh_region_list()
200
+ if rec.scan_type == 'fov':
201
+ current_form = getattr(app, '_current_form', 'fov')
202
+ want_phasor = (current_form == 'phasor')
203
+ if want_phasor:
204
+ app._switch_form('phasor')
205
+ else:
206
+ app._switch_form('fov')
207
+ if hasattr(app, '_last_loaded_ptu'):
208
+ app._last_loaded_ptu = rec.source_path
209
+ app.sv_ptu.set(rec.source_path)
210
+ if hasattr(app, '_fov_preview'):
211
+ app._fov_preview.load_fov(rec.source_path)
212
+ if rec.xlsx_path and hasattr(app, 'sv_xlsx'):
213
+ app.sv_xlsx.set(rec.xlsx_path)
214
+ if hasattr(app, '_irf_fov'):
215
+ if rec.xlsx_path:
216
+ app._irf_fov.sv_method.set('irf_xlsx')
217
+ else:
218
+ app._irf_fov.sv_method.set('machine_irf')
219
+ if hasattr(app, 'sv_out_fov'):
220
+ app.sv_out_fov.set(rec.stem)
221
+ if want_phasor:
222
+ if hasattr(app, 'sv_ph_ptu'):
223
+ app.sv_ph_ptu.set(rec.source_path)
224
+ if rec.phasor_session_path and hasattr(app, '_restore_phasor_session'):
225
+ app._restore_phasor_session(str(rec.phasor_session_path))
226
+ else:
227
+ if rec.session_path and hasattr(app, '_load_fitted_data_from_file'):
228
+ app._load_fitted_data_from_file(str(rec.session_path), suppress_popups=True)
229
+ elif rec.scan_type == 'zstack':
230
+ app._switch_form('fov')
231
+ if hasattr(app, 'sv_fov_analysis'):
232
+ app.sv_fov_analysis.set('zstack')
233
+ if hasattr(app, '_on_fov_analysis_changed'):
234
+ app._on_fov_analysis_changed()
235
+ if hasattr(app, 'sv_zstack_dir'):
236
+ app.sv_zstack_dir.set(rec.ptu_dir or rec.source_path)
237
+ if hasattr(app, 'sv_out_fov'):
238
+ app.sv_out_fov.set(rec.stem)
239
+ if rec.session_path and hasattr(app, '_fov_preview'):
240
+ try:
241
+ from flimkit.utils.display import load_zstack_display_slices
242
+ group_dir = Path(rec.session_path).parent
243
+ slices = load_zstack_display_slices(
244
+ group_dir, ptu_dir=rec.ptu_dir, region=rec.stem)
245
+ if slices:
246
+ app._fov_preview.display_zstack(slices)
247
+ if hasattr(app, '_populate_zstack_summary_from_dir'):
248
+ app._populate_zstack_summary_from_dir(group_dir)
249
+ except Exception as exc:
250
+ print(f'[Project] Could not reload z-stack {rec.stem}: {exc}')
251
+ else:
252
+ app._switch_form('stitch')
253
+ if hasattr(app, '_last_loaded_xlif'):
254
+ app._last_loaded_xlif = rec.source_path
255
+ app.sv_xlif.set(rec.source_path)
256
+ if rec.ptu_dir and hasattr(app, 'sv_ptu_dir'):
257
+ app.sv_ptu_dir.set(rec.ptu_dir)
258
+ out_st = self._project.default_out_st(rec.stem) if self._project else ''
259
+ if hasattr(app, 'sv_out_st'):
260
+ app.sv_out_st.set(out_st)
261
+ if rec.session_path and hasattr(app, '_load_fitted_data_from_file'):
262
+ app._load_fitted_data_from_file(str(rec.session_path), suppress_popups=True)