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,164 @@
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
+ from flimkit.UI.irf_widget import IRFWidget
7
+
8
+ class FovMode(BaseMode):
9
+ def build(self):
10
+ outer, tab = self.b._form_inner_frames['fov']
11
+ tab.columnconfigure(0, weight=1)
12
+ ff = _section(tab, 'Input Files')
13
+ ff.grid(row=0, column=0, sticky='ew', pady=(0, 6))
14
+ ff.columnconfigure(0, weight=1)
15
+ atr = ttk.Frame(ff)
16
+ atr.grid(row=0, column=0, sticky='ew', pady=(0, 2))
17
+ ttk.Label(atr, text='Analysis:').pack(side='left', padx=(2, 8))
18
+ self.b.state.sv_fov_analysis = tk.StringVar(value='single')
19
+ ttk.Radiobutton(atr, text='Single FOV', variable=self.b.sv_fov_analysis,
20
+ value='single', command=self.b._on_fov_analysis_changed).pack(side='left', padx=2)
21
+ ttk.Radiobutton(atr, text='Z-stack', variable=self.b.sv_fov_analysis,
22
+ value='zstack', command=self.b._on_fov_analysis_changed).pack(side='left', padx=2)
23
+ ttk.Label(atr, text='(z-stack: a folder of region_zX.ptu slices, fitted as one FOV)',
24
+ foreground='grey').pack(side='left', padx=(8, 0))
25
+ self.b.state.sv_ptu = tk.StringVar()
26
+ self.b.state.sv_xlsx = tk.StringVar()
27
+ self.b.state.sv_zstack_dir = tk.StringVar()
28
+ self.b.sv_ptu.trace_add('write', self.b._on_fov_ptu_changed)
29
+ self.b.sv_zstack_dir.trace_add('write', self.b._on_zstack_dir_changed)
30
+ single_fr = ttk.Frame(ff)
31
+ single_fr.grid(row=1, column=0, sticky='ew')
32
+ single_fr.columnconfigure(1, weight=1)
33
+ _row(single_fr, 'Data file *', self.b.sv_ptu, 0,
34
+ lambda: _browse_file(self.b.sv_ptu, 'FLIM file', FLIM_FILETYPES))
35
+ self.b._fov_single_fr = single_fr
36
+ zstack_fr = ttk.Frame(ff)
37
+ zstack_fr.grid(row=1, column=0, sticky='ew')
38
+ zstack_fr.columnconfigure(1, weight=1)
39
+ _row(zstack_fr, 'Z-stack folder *', self.b.sv_zstack_dir, 0,
40
+ lambda: _browse_dir(self.b.sv_zstack_dir,
41
+ 'Folder of region_zX.ptu z-slices'))
42
+ self.b._fov_zstack_fr = zstack_fr
43
+ zstack_fr.grid_remove()
44
+ xlsx_fr = ttk.Frame(ff)
45
+ xlsx_fr.grid(row=2, column=0, sticky='ew')
46
+ xlsx_fr.columnconfigure(1, weight=1)
47
+ _row(xlsx_fr, 'LAS X export (optional)', self.b.sv_xlsx, 0,
48
+ lambda: _browse_file(self.b.sv_xlsx, 'LAS X export',
49
+ [('LAS X export', '*.xlsx *.csv *.tsv *.txt *.dat *.ascii *.asc'),
50
+ ('All', '*.*')]))
51
+ self.b._irf_fov = IRFWidget(tab, default='irf_xlsx', xlsx_var=self.b.sv_xlsx,
52
+ machine_irf_default=str(_C()['MACHINE_IRF_DEFAULT_PATH']))
53
+ self.b._irf_fov.grid(row=1, column=0, sticky='ew', pady=(0, 6))
54
+ fp = _section(tab, 'Fitting Parameters', help_topic='fit_model')
55
+ fp.grid(row=2, column=0, sticky='ew', pady=(0, 6))
56
+ ttk.Label(fp, text='Fit model:').grid(row=0, column=0, sticky='w', **PAD)
57
+ self.b.state.sv_fit_model_fov = tk.StringVar(value='discrete')
58
+ ttk.Radiobutton(fp, text='n-exp', variable=self.b.sv_fit_model_fov,
59
+ value='discrete').grid(row=0, column=1, sticky='w', padx=1)
60
+ ttk.Radiobutton(fp, text='Gaussian dist.', variable=self.b.sv_fit_model_fov,
61
+ value='gaussian').grid(row=0, column=2, sticky='w', padx=1)
62
+ ttk.Radiobutton(fp, text='Lorentzian dist.', variable=self.b.sv_fit_model_fov,
63
+ value='lorentzian').grid(row=0, column=3, sticky='w', padx=1)
64
+ ttk.Radiobutton(fp, text='n-exp tail', variable=self.b.sv_fit_model_fov,
65
+ value='tail').grid(row=0, column=4, sticky='w', padx=1)
66
+ self.b.state.iv_nexp_fov = tk.IntVar(value=2)
67
+ self.b.state.iv_ncomp_dist_fov = tk.IntVar(value=1)
68
+ nexp_frame = ttk.Frame(fp)
69
+ nexp_frame.grid(row=1, column=0, columnspan=5, sticky='w')
70
+ ttk.Label(nexp_frame, text='Components:').pack(side='left', padx=(4, 8))
71
+ for n in (1, 2, 3):
72
+ ttk.Radiobutton(nexp_frame, text=str(n), variable=self.b.iv_nexp_fov,
73
+ value=n).pack(side='left', padx=1)
74
+ dist_frame = ttk.Frame(fp)
75
+ ttk.Label(dist_frame, text='Components:').pack(side='left', padx=(4, 8))
76
+ ttk.Radiobutton(dist_frame, text='1 (unimodal)', variable=self.b.iv_ncomp_dist_fov,
77
+ value=1).pack(side='left', padx=1)
78
+ ttk.Radiobutton(dist_frame, text='2 (bimodal)', variable=self.b.iv_ncomp_dist_fov,
79
+ value=2).pack(side='left', padx=1)
80
+
81
+ tail_note = ttk.Label(fp, text='Tail fit: no IRF used, fitted past the decay peak',
82
+ foreground='#888')
83
+ def _on_fov_model_change(*_):
84
+ _m = self.b.sv_fit_model_fov.get()
85
+ if _m in ('discrete', 'tail'):
86
+ dist_frame.grid_remove()
87
+ nexp_frame.grid(row=1, column=0, columnspan=5, sticky='w')
88
+ else:
89
+ nexp_frame.grid_remove()
90
+ dist_frame.grid(row=1, column=0, columnspan=5, sticky='w')
91
+ if _m == 'tail':
92
+ tail_note.grid(row=5, column=0, columnspan=5, sticky='w', padx=4)
93
+ self.b._irf_fov.grid_remove()
94
+ else:
95
+ tail_note.grid_remove()
96
+ self.b._irf_fov.grid(row=1, column=0, sticky='ew', pady=(0, 6))
97
+ self.b.sv_fit_model_fov.trace_add('write', _on_fov_model_change)
98
+ mode_row = ttk.Frame(fp)
99
+ mode_row.grid(row=2, column=0, columnspan=5, sticky='w', pady=(2, 0))
100
+ ttk.Label(mode_row, text='Fitting mode:').pack(side='left', padx=(0, 10))
101
+ self.b.state.sv_mode_fov = tk.StringVar(value='both')
102
+ radio_frame = ttk.Frame(mode_row)
103
+ radio_frame.pack(side='left')
104
+ ttk.Radiobutton(radio_frame, text='Full', variable=self.b.sv_mode_fov,
105
+ value='both').pack(side='left', padx=2)
106
+ ttk.Radiobutton(radio_frame, text='Fast', variable=self.b.sv_mode_fov,
107
+ value='summed').pack(side='left', padx=2)
108
+ ttk.Label(mode_row, text='(fast = no FLIM image)',
109
+ foreground='grey').pack(side='left', padx=(10, 0))
110
+ ttk.Label(fp, text='Fit window (ns):').grid(row=3, column=0, sticky='w', **PAD)
111
+ self.b.state.sv_tau_min_fov = tk.StringVar(value=str(_C()['Tau_min']))
112
+ self.b.state.sv_tau_max_fov = tk.StringVar(value=str(_C()['Tau_max']))
113
+ ttk.Entry(fp, textvariable=self.b.sv_tau_min_fov, width=7).grid(row=3, column=1, sticky='w', padx=4)
114
+ ttk.Label(fp, text='to').grid(row=3, column=2)
115
+ ttk.Entry(fp, textvariable=self.b.sv_tau_max_fov, width=7).grid(row=3, column=3, sticky='w', padx=4)
116
+ ttk.Label(fp, text='ns (fitting range)', foreground='grey').grid(row=3, column=4, sticky='w')
117
+ ttk.Label(fp, text='Output prefix:').grid(row=4, column=0, sticky='w', **PAD)
118
+ self.b.state.sv_out_fov = tk.StringVar(value='flim_out')
119
+ ttk.Entry(fp, textvariable=self.b.sv_out_fov, width=35).grid(
120
+ row=4, column=1, columnspan=3, sticky='ew', padx=4)
121
+ fm = _section(tab, 'Masking & Thresholding', help_topic='masking')
122
+ fm.grid(row=3, column=0, sticky='ew', pady=(0, 6))
123
+ self.b.state.bv_cell = tk.BooleanVar(value=False)
124
+ ttk.Checkbutton(fm, text='Apply cell mask (Cellpose-SAM)',
125
+ variable=self.b.bv_cell).grid(
126
+ row=0, column=0, columnspan=3, sticky='w', **PAD)
127
+ self.b.state.bv_thr_fov = tk.BooleanVar(value=False)
128
+ self.b.state.sv_thr_fov = tk.StringVar()
129
+ ttk.Checkbutton(fm, text='Intensity threshold (min photons/px):',
130
+ variable=self.b.bv_thr_fov,
131
+ command=lambda: _tog(self.b.bv_thr_fov, self.b._thr_fov_e)).grid(
132
+ row=1, column=0, sticky='w', **PAD)
133
+ self.b._thr_fov_e = ttk.Entry(fm, textvariable=self.b.sv_thr_fov,
134
+ width=8, state='disabled')
135
+ self.b._thr_fov_e.grid(row=1, column=1, sticky='w', padx=4)
136
+ ttk.Label(fm, text='(leave blank for no threshold)',
137
+ foreground='grey').grid(row=1, column=2, sticky='w')
138
+ self.b.state.bv_correct_pileup = tk.BooleanVar(value=False)
139
+ ttk.Checkbutton(fm, text='Apply Coates pile-up correction (recommended if pile-up > 5%)',
140
+ variable=self.b.bv_correct_pileup).grid(
141
+ row=2, column=0, columnspan=3, sticky='w', **PAD)
142
+ ttk.Label(fm, text='Time-varying background PTU:').grid(row=3, column=0, sticky='w', **PAD)
143
+ self.b.state.sv_tvb_ptu_fov = tk.StringVar()
144
+ ttk.Entry(fm, textvariable=self.b.sv_tvb_ptu_fov, width=24).grid(
145
+ row=3, column=1, sticky='ew', padx=4)
146
+ ttk.Button(fm, text='Browse...',
147
+ command=lambda: _browse_file(self.b.sv_tvb_ptu_fov,
148
+ 'Background reference PTU',
149
+ FLIM_FILETYPES)).grid(
150
+ row=3, column=2, sticky='w', padx=4)
151
+ ttk.Label(fm, text='(optional: fits a measured fluorophore-free background decay, FLIMfit-style)',
152
+ foreground='grey').grid(row=4, column=0, columnspan=3, sticky='w', padx=8)
153
+ self.b._expert_banner_fov = ttk.Label(
154
+ tab, text='⚙ Custom expert settings active',
155
+ foreground='#e8a838', font=('TkDefaultFont', 9, 'bold'))
156
+ self.b._expert_banner_fov.grid(row=4, column=0, sticky='w', padx=8)
157
+ self.b._expert_banner_fov.grid_remove()
158
+ btn_row_fov = ttk.Frame(tab)
159
+ btn_row_fov.grid(row=5, column=0, pady=8)
160
+ ttk.Button(btn_row_fov, text='⚙ Expert Settings',
161
+ command=self.b._open_expert_settings).pack(side='left', padx=4)
162
+ self.b._btn_fov = ttk.Button(btn_row_fov, text='▶ Run Single-FOV Fit',
163
+ command=self.b._run_fov)
164
+ self.b._btn_fov.pack(side='left', padx=4, ipadx=20, ipady=4)
@@ -0,0 +1,80 @@
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_dir
6
+
7
+
8
+ class IrfMode(BaseMode):
9
+ def build(self):
10
+ outer, tab = self.b._form_inner_frames['irf']
11
+ tab.columnconfigure(0, weight=1)
12
+
13
+ cfg = _C()
14
+
15
+ ff = _section(tab, 'Source Data')
16
+ ff.grid(row=0, column=0, sticky='ew', pady=(0, 6))
17
+ ff.columnconfigure(1, weight=1)
18
+ self.b.state.sv_mirf_src = tk.StringVar()
19
+ _row(
20
+ ff,
21
+ 'PTU/XLSX folder *',
22
+ self.b.sv_mirf_src,
23
+ 0,
24
+ lambda: _browse_dir(self.b.sv_mirf_src, 'Folder with paired .ptu and .xlsx'),
25
+ )
26
+ ttk.Label(
27
+ ff,
28
+ text='Builder uses matching <name>.ptu + <name>.xlsx pairs.',
29
+ foreground='grey',
30
+ ).grid(row=1, column=1, columnspan=2, sticky='w', padx=4)
31
+
32
+ fp = _section(tab, 'Build Settings')
33
+ fp.grid(row=1, column=0, sticky='ew', pady=(0, 6))
34
+ fp.columnconfigure(1, weight=1)
35
+
36
+ self.b.state.sv_mirf_anchor = tk.StringVar(value=cfg['MACHINE_IRF_ALIGN_ANCHOR'])
37
+ self.b.state.sv_mirf_reducer = tk.StringVar(value=cfg['MACHINE_IRF_REDUCER'])
38
+
39
+ ttk.Label(fp, text='Align anchor:').grid(row=0, column=0, sticky='w', **PAD)
40
+ ttk.Combobox(
41
+ fp,
42
+ textvariable=self.b.sv_mirf_anchor,
43
+ values=['peak', 'halfmax', 'onset10', 'slope'],
44
+ state='readonly',
45
+ width=12,
46
+ ).grid(row=0, column=1, sticky='w', padx=4)
47
+
48
+ ttk.Label(fp, text='Reducer:').grid(row=1, column=0, sticky='w', **PAD)
49
+ ttk.Combobox(
50
+ fp,
51
+ textvariable=self.b.sv_mirf_reducer,
52
+ values=['median', 'mean'],
53
+ state='readonly',
54
+ width=12,
55
+ ).grid(row=1, column=1, sticky='w', padx=4)
56
+
57
+ fo = _section(tab, 'Output')
58
+ fo.grid(row=2, column=0, sticky='ew', pady=(0, 6))
59
+ fo.columnconfigure(1, weight=1)
60
+ self.b.state.sv_mirf_out_dir = tk.StringVar(value=str(cfg['MACHINE_IRF_DIR']))
61
+ self.b.state.sv_mirf_name = tk.StringVar(value='machine_irf_default')
62
+
63
+ _row(
64
+ fo,
65
+ 'Output directory *',
66
+ self.b.sv_mirf_out_dir,
67
+ 0,
68
+ lambda: _browse_dir(self.b.sv_mirf_out_dir, 'Machine IRF output directory'),
69
+ )
70
+ ttk.Label(fo, text='Base filename:').grid(row=1, column=0, sticky='w', **PAD)
71
+ ttk.Entry(fo, textvariable=self.b.sv_mirf_name, width=35).grid(
72
+ row=1, column=1, columnspan=2, sticky='ew', padx=4
73
+ )
74
+
75
+ self.b._btn_mirf = ttk.Button(
76
+ tab,
77
+ text='▶ Build Machine IRF',
78
+ command=self.b._run_build_machine_irf,
79
+ )
80
+ self.b._btn_mirf.grid(row=3, column=0, pady=8, ipadx=20, ipady=4)
@@ -0,0 +1,131 @@
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, FLIM_FILETYPES
6
+ from flimkit.UI.phasor_panel import PhasorViewPanel
7
+
8
+
9
+ class PhasorMode(BaseMode):
10
+ def build(self):
11
+ outer, inner = self.b._form_inner_frames['phasor']
12
+ inner.columnconfigure(0, weight=1)
13
+ ctrl = ttk.Frame(inner, padding=(6, 4))
14
+ ctrl.grid(row=0, column=0, sticky='ew')
15
+ ctrl.columnconfigure(0, weight=1)
16
+
17
+ mode_fr = _section(ctrl, 'Input Mode')
18
+ mode_fr.grid(row=0, column=0, sticky='ew', pady=(0, 4))
19
+ mode_fr.columnconfigure(1, weight=1)
20
+
21
+ self.b.state.sv_ph_mode = tk.StringVar(value='new')
22
+ ttk.Radiobutton(mode_fr, text='New PTU file',
23
+ variable=self.b.sv_ph_mode, value='new',
24
+ command=self.b._ph_mode_changed).grid(
25
+ row=0, column=0, sticky='w', padx=4, pady=1)
26
+ ttk.Radiobutton(mode_fr, text='Resume session (.npz)',
27
+ variable=self.b.sv_ph_mode, value='session',
28
+ command=self.b._ph_mode_changed).grid(
29
+ row=0, column=1, sticky='w', padx=4, pady=1)
30
+
31
+ self.b._ph_new = ttk.Frame(ctrl)
32
+ self.b._ph_new.columnconfigure(0, weight=1)
33
+ self.b._ph_new.grid(row=1, column=0, sticky='ew')
34
+ fn = _section(self.b._ph_new, 'New Analysis')
35
+ fn.grid(row=0, column=0, sticky='ew')
36
+ fn.columnconfigure(1, weight=1)
37
+ self.b.state.sv_ph_ptu = tk.StringVar()
38
+ self.b.state.sv_ph_irf = tk.StringVar()
39
+ self.b.state.sv_ph_mirf = tk.StringVar(
40
+ value=str(_C()['MACHINE_IRF_DEFAULT_PATH']))
41
+ _row(fn, 'PTU file *', self.b.sv_ph_ptu, 0,
42
+ lambda: _browse_file(self.b.sv_ph_ptu, 'PTU file',
43
+ FLIM_FILETYPES))
44
+ _row(fn, 'IRF XLSX (optional)', self.b.sv_ph_irf, 1,
45
+ lambda: _browse_file(self.b.sv_ph_irf, 'IRF XLSX',
46
+ [('Excel', '*.xlsx'), ('All', '*.*')]))
47
+ _row(fn, 'Machine IRF (optional)', self.b.sv_ph_mirf, 2,
48
+ lambda: _browse_file(self.b.sv_ph_mirf, 'Machine IRF',
49
+ [('NumPy', '*.npy'), ('All', '*.*')]))
50
+ ttk.Label(fn, text='XLSX takes priority if both supplied',
51
+ foreground='grey').grid(
52
+ row=3, column=1, columnspan=2, sticky='w', padx=4)
53
+
54
+ self.b._ph_sess = ttk.Frame(ctrl)
55
+ self.b._ph_sess.columnconfigure(0, weight=1)
56
+ self.b._ph_sess.grid(row=2, column=0, sticky='ew')
57
+ fs = _section(self.b._ph_sess, 'Resume Session')
58
+ fs.grid(row=0, column=0, sticky='ew')
59
+ fs.columnconfigure(1, weight=1)
60
+ self.b.state.sv_ph_session = tk.StringVar()
61
+ _row(fs, 'Session (.npz) *', self.b.sv_ph_session, 0,
62
+ lambda: _browse_file(self.b.sv_ph_session, 'Session file',
63
+ [('NPZ', '*.npz'), ('All', '*.*')]))
64
+ self.b._ph_sess.grid_remove()
65
+
66
+ opt_fr = _section(ctrl, 'Display Options')
67
+ opt_fr.grid(row=3, column=0, sticky='ew', pady=(4, 0))
68
+ ttk.Label(opt_fr, text='Min photons (fraction):').grid(
69
+ row=0, column=0, sticky='w', **PAD)
70
+ self.b.state.sv_ph_minph = tk.StringVar(value='0.01')
71
+ ttk.Entry(opt_fr, textvariable=self.b.sv_ph_minph, width=8).grid(
72
+ row=0, column=1, sticky='w', padx=4)
73
+ ttk.Label(opt_fr, text='Max cursors:').grid(
74
+ row=0, column=2, sticky='w', padx=8)
75
+ self.b.state.sv_ph_maxc = tk.StringVar(value='6')
76
+ ttk.Entry(opt_fr, textvariable=self.b.sv_ph_maxc, width=4).grid(
77
+ row=0, column=3, sticky='w', padx=4)
78
+
79
+ self.b._btn_ph = ttk.Button(ctrl, text='▶ Load & Analyse',
80
+ command=self.b._run_phasor)
81
+ self.b._btn_ph.grid(row=4, column=0, pady=(6, 2), ipadx=16, ipady=3,
82
+ sticky='w')
83
+
84
+ peaks_fr = _section(ctrl, 'Find Peaks')
85
+ peaks_fr.grid(row=5, column=0, sticky='ew', pady=(6, 0))
86
+ peaks_fr.columnconfigure(1, weight=1)
87
+ ttk.Label(peaks_fr, text='Smooth σ:').grid(
88
+ row=0, column=0, sticky='w', **PAD)
89
+ self.b.state.sv_ph_pk_sigma = tk.StringVar(value='3.0')
90
+ ttk.Entry(peaks_fr, textvariable=self.b.sv_ph_pk_sigma, width=6).grid(
91
+ row=0, column=1, sticky='w', padx=4)
92
+ ttk.Label(peaks_fr, text='Threshold:').grid(
93
+ row=1, column=0, sticky='w', **PAD)
94
+ self.b.state.sv_ph_pk_thresh = tk.StringVar(value='0.10')
95
+ ttk.Entry(peaks_fr, textvariable=self.b.sv_ph_pk_thresh, width=6).grid(
96
+ row=1, column=1, sticky='w', padx=4)
97
+ ttk.Button(peaks_fr, text='🔍 Find Peaks',
98
+ command=self.b._run_ph_find_peaks).grid(
99
+ row=2, column=0, columnspan=2, sticky='w', pady=(4, 0), padx=8)
100
+
101
+ fret_fr = _section(ctrl, 'FRET Analysis')
102
+ fret_fr.grid(row=6, column=0, sticky='ew', pady=(6, 0))
103
+ fret_fr.columnconfigure(1, weight=1)
104
+ ttk.Label(fret_fr, text='Donor τ (ns):').grid(
105
+ row=0, column=0, sticky='w', **PAD)
106
+ self.b.state.sv_ph_fret_taud = tk.StringVar(value='4.0')
107
+ ttk.Entry(fret_fr, textvariable=self.b.sv_ph_fret_taud, width=8).grid(
108
+ row=0, column=1, sticky='w', padx=4)
109
+ ttk.Label(fret_fr, text='Acceptor τ (ns):').grid(
110
+ row=1, column=0, sticky='w', **PAD)
111
+ self.b.state.sv_ph_fret_taua = tk.StringVar(value='')
112
+ ttk.Entry(fret_fr, textvariable=self.b.sv_ph_fret_taua, width=8).grid(
113
+ row=1, column=1, sticky='w', padx=4)
114
+ ttk.Label(fret_fr, text='(blank = donor-only)',
115
+ foreground='grey').grid(
116
+ row=1, column=2, sticky='w', padx=2)
117
+ ttk.Label(fret_fr, text='Donor fretting:').grid(
118
+ row=2, column=0, sticky='w', **PAD)
119
+ self.b.state.sv_ph_fret_fretting = tk.StringVar(value='1.0')
120
+ ttk.Entry(fret_fr, textvariable=self.b.sv_ph_fret_fretting, width=8).grid(
121
+ row=2, column=1, sticky='w', padx=4)
122
+ _fret_btn_fr = ttk.Frame(fret_fr)
123
+ _fret_btn_fr.grid(row=3, column=0, columnspan=3,
124
+ sticky='w', pady=(4, 0), padx=8)
125
+ ttk.Button(_fret_btn_fr, text='↔ Overlay Trajectory',
126
+ command=self.b._run_ph_fret_overlay).pack(side='left', padx=(0, 4))
127
+ ttk.Button(_fret_btn_fr, text='Fit Donor FRET',
128
+ command=self.b._run_ph_fit_fret).pack(side='left', padx=(0, 4))
129
+ ttk.Button(_fret_btn_fr, text='✕ Clear Overlay',
130
+ command=lambda: self.b._phasor_panel.clear_fret_overlay()).pack(
131
+ side='left')
@@ -0,0 +1,254 @@
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
+ from flimkit.UI.irf_widget import IRFWidget
7
+
8
+
9
+ class StitchMode(BaseMode):
10
+ def build(self):
11
+ outer, tab = self.b._form_inner_frames['stitch']
12
+ tab.columnconfigure(0, weight=1)
13
+
14
+ ff = _section(tab, 'Input Files')
15
+ ff.grid(row=0, column=0, sticky='ew', pady=(0, 6))
16
+ ff.columnconfigure(1, weight=1)
17
+ self.b.state.sv_xlif = tk.StringVar()
18
+ self.b.sv_xlif.trace_add('write', self.b._on_xlif_changed)
19
+ self.b.state.sv_ptu_dir = tk.StringVar()
20
+ self.b.state.sv_out_st = tk.StringVar()
21
+
22
+ _row(ff, 'XLIF metadata *', self.b.sv_xlif, 0,
23
+ lambda: _browse_file(self.b.sv_xlif, 'XLIF file',
24
+ [('XLIF', '*.xlif'), ('All', '*.*')]))
25
+ _row(ff, 'PTU tile directory *', self.b.sv_ptu_dir, 1,
26
+ lambda: _browse_dir(self.b.sv_ptu_dir, 'PTU tile directory'))
27
+ _row(ff, 'Base output dir *', self.b.sv_out_st, 2,
28
+ lambda: _browse_dir(self.b.sv_out_st, 'Output directory'))
29
+
30
+ ttk.Label(ff, text='(A sub-folder named after the ROI will be created inside)',
31
+ foreground='grey').grid(row=3, column=1, columnspan=2,
32
+ sticky='w', padx=4)
33
+ self.b.state.bv_rotate = tk.BooleanVar(value=True)
34
+ ttk.Checkbutton(ff, text='Rotate tiles 90° CW (recommended for FLIM microscope)',
35
+ variable=self.b.bv_rotate).grid(
36
+ row=4, column=0, columnspan=3, sticky='w', padx=4, pady=(4, 0))
37
+
38
+ fp = _section(tab, 'Pipeline')
39
+ fp.grid(row=1, column=0, sticky='ew', pady=(4, 2))
40
+ self.b.state.sv_pipeline = tk.StringVar(value='stitch_only')
41
+ for r, (val, lbl) in enumerate([
42
+ ('stitch_only', 'Stitch tiles only'),
43
+ ('stitch_fit', 'Stitch then fit full ROI'),
44
+ ('tile_fit', 'Per-tile fit [recommended - fits each tile independently]'),
45
+ ('series_fit', 'Multidimensional series [z-stack and/or timelapse tiles]'),
46
+ ]):
47
+ ttk.Radiobutton(fp, text=lbl, variable=self.b.sv_pipeline,
48
+ value=val, command=self.b._pipeline_changed).grid(
49
+ row=r, column=0, sticky='w', padx=4, pady=1)
50
+
51
+ self.b._fit_frame = ttk.Frame(tab)
52
+ self.b._fit_frame.columnconfigure(0, weight=1)
53
+ self.b._fit_frame.grid(row=2, column=0, sticky='ew')
54
+ self.build_fit(self.b._fit_frame)
55
+ self.b._fit_frame.grid_remove()
56
+
57
+ self.b._expert_banner_st = ttk.Label(
58
+ tab, text='⚙ Custom expert settings active',
59
+ foreground='#e8a838', font=('TkDefaultFont', 9, 'bold'))
60
+ self.b._expert_banner_st.grid(row=3, column=0, sticky='w', padx=8)
61
+ self.b._expert_banner_st.grid_remove()
62
+
63
+ btn_row_st = ttk.Frame(tab)
64
+ btn_row_st.grid(row=4, column=0, pady=8)
65
+ self.b._btn_expert_st = ttk.Button(btn_row_st, text='⚙ Expert Settings',
66
+ command=self.b._open_expert_settings)
67
+ self.b._btn_expert_st.pack(side='left', padx=4)
68
+ self.b._btn_st = ttk.Button(btn_row_st, text='▶ Run Tile Stitch',
69
+ command=self.b._run_stitch)
70
+ self.b._btn_st.pack(side='left', padx=4, ipadx=20, ipady=4)
71
+
72
+ def build_fit(self, parent):
73
+ self.b._irf_st = IRFWidget(parent, default='machine_irf',
74
+ machine_irf_default=str(_C()['MACHINE_IRF_DEFAULT_PATH']))
75
+ self.b._irf_st.grid(row=0, column=0, sticky='ew', pady=(0, 6))
76
+ self.b._irf_st.frame.columnconfigure(1, weight=1)
77
+
78
+ fp = _section(parent, 'Fitting Parameters', help_topic='fit_model')
79
+ fp.grid(row=1, column=0, sticky='ew', pady=(0, 6))
80
+
81
+ ttk.Label(fp, text='Fit model:').grid(row=0, column=0, sticky='w', **PAD)
82
+ self.b.state.sv_fit_model_st = tk.StringVar(value='discrete')
83
+ ttk.Radiobutton(fp, text='n-exp', variable=self.b.sv_fit_model_st,
84
+ value='discrete').grid(row=0, column=1, sticky='w', padx=1)
85
+ ttk.Radiobutton(fp, text='Gaussian dist.', variable=self.b.sv_fit_model_st,
86
+ value='gaussian').grid(row=0, column=2, sticky='w', padx=1)
87
+ ttk.Radiobutton(fp, text='Lorentzian dist.', variable=self.b.sv_fit_model_st,
88
+ value='lorentzian').grid(row=0, column=3, sticky='w', padx=1)
89
+ ttk.Radiobutton(fp, text='n-exp tail', variable=self.b.sv_fit_model_st,
90
+ value='tail').grid(row=0, column=4, sticky='w', padx=1)
91
+
92
+ self.b.state.iv_nexp_st = tk.IntVar(value=2)
93
+ self.b.state.iv_ncomp_dist_st = tk.IntVar(value=1)
94
+
95
+ nexp_frame_st = ttk.Frame(fp)
96
+ nexp_frame_st.grid(row=1, column=0, columnspan=5, sticky='w')
97
+ ttk.Label(nexp_frame_st, text='Components:').pack(side='left', padx=(4, 8))
98
+ for n in (1, 2, 3):
99
+ ttk.Radiobutton(nexp_frame_st, text=str(n), variable=self.b.iv_nexp_st,
100
+ value=n).pack(side='left', padx=4)
101
+
102
+ dist_frame_st = ttk.Frame(fp)
103
+ ttk.Label(dist_frame_st, text='Components:').pack(side='left', padx=(4, 8))
104
+ ttk.Radiobutton(dist_frame_st, text='1 (unimodal)', variable=self.b.iv_ncomp_dist_st,
105
+ value=1).pack(side='left', padx=4)
106
+ ttk.Radiobutton(dist_frame_st, text='2 (bimodal)', variable=self.b.iv_ncomp_dist_st,
107
+ value=2).pack(side='left', padx=4)
108
+
109
+ tail_note_st = ttk.Label(fp, text='Tail fit: no IRF used, fitted past the decay peak',
110
+ foreground='#888')
111
+ def _on_st_model_change(*_):
112
+ _m = self.b.sv_fit_model_st.get()
113
+ if _m in ('discrete', 'tail'):
114
+ dist_frame_st.grid_remove()
115
+ nexp_frame_st.grid(row=1, column=0, columnspan=5, sticky='w')
116
+ else:
117
+ nexp_frame_st.grid_remove()
118
+ dist_frame_st.grid(row=1, column=0, columnspan=5, sticky='w')
119
+ if _m == 'tail':
120
+ tail_note_st.grid(row=6, column=0, columnspan=5, sticky='w', padx=4)
121
+ self.b._irf_st.grid_remove()
122
+ else:
123
+ tail_note_st.grid_remove()
124
+ self.b._irf_st.grid(row=0, column=0, sticky='ew', pady=(0, 6))
125
+ self.b.sv_fit_model_st.trace_add('write', _on_st_model_change)
126
+
127
+ self.b.state.bv_perpix = tk.BooleanVar(value=False)
128
+ ttk.Checkbutton(fp, text='Per-pixel fitting [REQUIRED FOR ROI ANALYSIS]',
129
+ variable=self.b.bv_perpix,
130
+ command=self.b._perpix_toggled).grid(
131
+ row=2, column=0, columnspan=4, sticky='w', **PAD)
132
+
133
+ self.b._pxf = ttk.Frame(fp)
134
+ self.b._pxf.grid(row=3, column=0, columnspan=4, sticky='ew', padx=20)
135
+
136
+ self.b.state.bv_save_tau_weighted = tk.BooleanVar(value=True)
137
+ self.b.state.bv_save_int_weighted = tk.BooleanVar(value=True)
138
+ self.b.state.bv_save_amp_weighted = tk.BooleanVar(value=False)
139
+ self.b.state.bv_save_ind = tk.BooleanVar(value=False)
140
+
141
+ ttk.Checkbutton(self.b._pxf, text='Export τ-weighted map',
142
+ variable=self.b.bv_save_tau_weighted).grid(row=0, column=0, sticky='w', padx=(0, 8))
143
+ ttk.Checkbutton(self.b._pxf, text='Export intensity-weighted map',
144
+ variable=self.b.bv_save_int_weighted).grid(row=0, column=1, sticky='w', padx=(0, 8))
145
+ ttk.Checkbutton(self.b._pxf, text='Export amplitude-weighted map',
146
+ variable=self.b.bv_save_amp_weighted).grid(row=1, column=0, sticky='w', padx=(0, 8))
147
+ ttk.Checkbutton(self.b._pxf, text='Save individual component maps',
148
+ variable=self.b.bv_save_ind).grid(row=1, column=1, sticky='w')
149
+
150
+ self.b.state.sv_tau_lo = tk.StringVar()
151
+ self.b.state.sv_tau_hi = tk.StringVar()
152
+ self.b.state.sv_int_lo = tk.StringVar()
153
+ self.b.state.sv_int_hi = tk.StringVar()
154
+
155
+ ttk.Label(self.b._pxf, text='Lifetime display (ns):').grid(row=2, column=0, sticky='w', pady=2)
156
+ ttk.Entry(self.b._pxf, textvariable=self.b.sv_tau_lo, width=7).grid(row=2, column=1, padx=4)
157
+ ttk.Label(self.b._pxf, text='to').grid(row=2, column=2)
158
+ ttk.Entry(self.b._pxf, textvariable=self.b.sv_tau_hi, width=7).grid(row=2, column=3, padx=4)
159
+ ttk.Label(self.b._pxf, text='(blank = auto)', foreground='grey').grid(row=2, column=4, padx=4)
160
+
161
+ ttk.Label(self.b._pxf, text='Intensity display:').grid(row=3, column=0, sticky='w', pady=2)
162
+ ttk.Entry(self.b._pxf, textvariable=self.b.sv_int_lo, width=7).grid(row=3, column=1, padx=4)
163
+ ttk.Label(self.b._pxf, text='to').grid(row=3, column=2)
164
+ ttk.Entry(self.b._pxf, textvariable=self.b.sv_int_hi, width=7).grid(row=3, column=3, padx=4)
165
+ ttk.Label(self.b._pxf, text='(blank = auto)', foreground='grey').grid(row=3, column=4, padx=4)
166
+
167
+ self.b._pxf.grid_remove()
168
+
169
+ self.b.state.sv_tau_fit_lo = tk.StringVar(value=str(_C()['Tau_min']))
170
+ self.b.state.sv_tau_fit_hi = tk.StringVar(value=str(_C()['Tau_max']))
171
+ ttk.Label(fp, text='Fit window (ns):').grid(row=3, column=0, sticky='w', pady=2)
172
+ ttk.Entry(fp, textvariable=self.b.sv_tau_fit_lo, width=7).grid(row=3, column=1, padx=4)
173
+ ttk.Label(fp, text='to').grid(row=3, column=2)
174
+ ttk.Entry(fp, textvariable=self.b.sv_tau_fit_hi, width=7).grid(row=3, column=3, padx=4)
175
+ ttk.Label(fp, text='ns (fitting range)', foreground='grey').grid(row=3, column=4, padx=4)
176
+
177
+ fm = _section(parent, 'Masking & Thresholding', help_topic='masking')
178
+ fm.grid(row=2, column=0, sticky='ew', pady=(0, 6))
179
+
180
+ self.b.state.bv_thr_st = tk.BooleanVar(value=False)
181
+ self.b.state.sv_thr_st = tk.StringVar()
182
+ ttk.Checkbutton(fm, text='Intensity threshold (min photons/px):',
183
+ variable=self.b.bv_thr_st,
184
+ command=lambda: _tog(self.b.bv_thr_st, self.b._thr_st_e)).grid(
185
+ row=0, column=0, sticky='w', **PAD)
186
+ self.b._thr_st_e = ttk.Entry(fm, textvariable=self.b.sv_thr_st,
187
+ width=8, state='disabled')
188
+ self.b._thr_st_e.grid(row=0, column=1, sticky='w', padx=4)
189
+ ttk.Label(fm, text='(leave blank for no threshold)',
190
+ foreground='grey').grid(row=0, column=2, sticky='w')
191
+
192
+ self.b.state.bv_correct_pileup_st = tk.BooleanVar(value=False)
193
+ ttk.Checkbutton(fm, text='Apply Coates pile-up correction (recommended if pile-up > 5%)',
194
+ variable=self.b.bv_correct_pileup_st).grid(
195
+ row=1, column=0, columnspan=3, sticky='w', **PAD)
196
+ ttk.Label(fm, text='Time-varying background PTU:').grid(row=2, column=0, sticky='w', **PAD)
197
+ self.b.state.sv_tvb_ptu_st = tk.StringVar()
198
+ ttk.Entry(fm, textvariable=self.b.sv_tvb_ptu_st, width=24).grid(
199
+ row=2, column=1, sticky='ew', padx=4)
200
+ ttk.Button(fm, text='Browse...',
201
+ command=lambda: _browse_file(self.b.sv_tvb_ptu_st,
202
+ 'Background reference PTU',
203
+ FLIM_FILETYPES)).grid(
204
+ row=2, column=2, sticky='w', padx=4)
205
+ ttk.Label(fm, text='(optional: measured background, aligned per tile)',
206
+ foreground='grey').grid(row=3, column=0, columnspan=3, sticky='w', padx=8)
207
+ freg = _section(parent, 'Tile Registration')
208
+ freg.grid(row=3, column=0, sticky='ew', pady=(0, 6))
209
+ self.b.state.bv_register = tk.BooleanVar(value=True)
210
+ ttk.Checkbutton(freg, text='Phase-correlation registration (fixes stage Y/X drift)',
211
+ variable=self.b.bv_register).grid(
212
+ row=0, column=0, columnspan=3, sticky='w', **PAD)
213
+ ttk.Label(freg, text='Max shift (px):').grid(row=1, column=0, sticky='w', **PAD)
214
+ self.b.state.sv_reg_max_shift = tk.StringVar(value='120')
215
+ ttk.Entry(freg, textvariable=self.b.sv_reg_max_shift, width=6).grid(
216
+ row=1, column=1, sticky='w', padx=4)
217
+ ttk.Label(freg, text='(increase if drift > 120px)',
218
+ foreground='grey').grid(row=1, column=2, sticky='w')
219
+
220
+ self.b._series_frame = ttk.Frame(parent)
221
+ self.b._series_frame.columnconfigure(0, weight=1)
222
+ self.b._series_frame.grid(row=5, column=0, sticky='ew', pady=(0, 4))
223
+ fsr = _section(self.b._series_frame, 'Multidimensional Series')
224
+ fsr.grid(row=0, column=0, sticky='ew')
225
+ ttk.Label(fsr, text='Pool decay every N timepoints:').grid(
226
+ row=0, column=0, sticky='w', **PAD)
227
+ self.b.state.sv_pool_stride = tk.StringVar(value='10')
228
+ ttk.Entry(fsr, textvariable=self.b.sv_pool_stride, width=6).grid(
229
+ row=0, column=1, sticky='w', padx=4)
230
+ ttk.Label(fsr, text='(1 = every timepoint; higher is faster, same consensus)',
231
+ foreground='grey').grid(row=0, column=2, sticky='w')
232
+ ttk.Label(fsr, text='One consensus lifetime is fitted across the whole series, '
233
+ 'so amplitudes stay comparable between timepoints.',
234
+ foreground='grey', wraplength=520, justify='left').grid(
235
+ row=1, column=0, columnspan=3, sticky='w', padx=8, pady=(2, 0))
236
+ ttk.Label(fsr, text='An XLIF or LIF is used as the starting layout and then refined '
237
+ 'against the tile overlap. Without one the positions come from '
238
+ 'the overlap alone, which is less reliable on sparse samples.',
239
+ foreground='grey', wraplength=520, justify='left').grid(
240
+ row=2, column=0, columnspan=3, sticky='w', padx=8)
241
+ self.b._series_frame.grid_remove()
242
+
243
+ self.b._tile_extras_frame = ttk.Frame(parent)
244
+ self.b._tile_extras_frame.columnconfigure(0, weight=1)
245
+ self.b._tile_extras_frame.grid(row=4, column=0, sticky='ew', pady=(0, 4))
246
+ fte = _section(self.b._tile_extras_frame, 'Per-Tile IRF Directory (optional)')
247
+ fte.grid(row=0, column=0, sticky='ew')
248
+ fte.columnconfigure(1, weight=1)
249
+ self.b.state.sv_tile_irf_dir = tk.StringVar()
250
+ _row(fte, 'IRF XLSX dir', self.b.sv_tile_irf_dir, 0,
251
+ lambda: _browse_dir(self.b.sv_tile_irf_dir, 'Directory of per-tile IRF xlsx files'))
252
+ ttk.Label(fte, text='One <tile_name>.xlsx per tile; leave blank to use IRF method above',
253
+ foreground='grey').grid(row=1, column=1, columnspan=2, sticky='w', padx=4)
254
+ self.b._tile_extras_frame.grid_remove()