spacr 0.1.16__py3-none-any.whl → 0.1.55__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.
spacr/gui_utils.py CHANGED
@@ -1,204 +1,135 @@
1
- import os, spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib, re, csv, requests
1
+ import os, spacr, traceback, io, sys, ast, ctypes, matplotlib, re, csv, requests, ast
2
2
  import matplotlib.pyplot as plt
3
3
  matplotlib.use('Agg')
4
- import numpy as np
5
4
  import tkinter as tk
6
- from tkinter import ttk, messagebox
5
+ from tkinter import ttk
7
6
  import tkinter.font as tkFont
8
- from torchvision import models
9
- #from ttf_opensans import opensans
10
-
7
+ from tkinter import filedialog
8
+ from tkinter import Checkbutton
11
9
  from tkinter import font as tkFont
10
+ from multiprocessing import Process, Value, Queue, Manager, set_start_method
11
+ from tkinter import ttk, scrolledtext
12
+ from matplotlib.figure import Figure
13
+ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
14
+ import time
15
+ import requests
16
+ from huggingface_hub import list_repo_files, hf_hub_download
12
17
 
13
18
  from .logger import log_function_call
19
+ from .settings import set_default_train_test_model, get_measure_crop_settings, set_default_settings_preprocess_generate_masks, get_analyze_reads_default_settings, set_default_umap_image_settings
14
20
 
15
21
  try:
16
22
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
17
23
  except AttributeError:
18
24
  pass
19
25
 
20
- class ToolTip:
21
- def __init__(self, widget, text):
22
- self.widget = widget
23
- self.text = text
24
- self.tooltip_window = None
25
- widget.bind("<Enter>", self.show_tooltip)
26
- widget.bind("<Leave>", self.hide_tooltip)
27
-
28
- def show_tooltip(self, event):
29
- x = event.x_root + 20
30
- y = event.y_root + 10
31
- self.tooltip_window = tk.Toplevel(self.widget)
32
- self.tooltip_window.wm_overrideredirect(True)
33
- self.tooltip_window.wm_geometry(f"+{x}+{y}")
34
- label = tk.Label(self.tooltip_window, text=self.text, background="yellow", relief='solid', borderwidth=1)
35
- label.pack()
36
-
37
- def hide_tooltip(self, event):
38
- if self.tooltip_window:
39
- self.tooltip_window.destroy()
40
- self.tooltip_window = None
41
-
42
- def load_app(root, app_name, app_func):
43
- # Cancel all scheduled after tasks
44
- if hasattr(root, 'after_tasks'):
45
- for task in root.after_tasks:
46
- root.after_cancel(task)
47
- root.after_tasks = []
48
-
49
- # Exit functionality only for the annotation app
50
- if app_name == "Annotate" and hasattr(root, 'current_app_exit_func'):
51
- root.current_app_exit_func()
52
-
53
- # Clear the current content frame
54
- if hasattr(root, 'content_frame'):
55
- for widget in root.content_frame.winfo_children():
56
- widget.destroy()
57
- else:
58
- root.content_frame = tk.Frame(root)
59
- root.content_frame.grid(row=1, column=0, sticky="nsew")
60
- root.grid_rowconfigure(1, weight=1)
61
- root.grid_columnconfigure(0, weight=1)
62
-
63
- # Initialize the new app in the content frame
64
- app_func(root.content_frame)
65
-
66
- def create_menu_bar(root):
67
- from .app_mask import initiate_mask_root
68
- from .app_measure import initiate_measure_root
69
- from .app_annotate import initiate_annotation_app_root
70
- from .app_make_masks import initiate_mask_app_root
71
- from .app_classify import initiate_classify_root
72
-
73
- gui_apps = {
74
- "Mask": initiate_mask_root,
75
- "Measure": initiate_measure_root,
76
- "Annotate": initiate_annotation_app_root,
77
- "Make Masks": initiate_mask_app_root,
78
- "Classify": initiate_classify_root
79
- }
80
-
81
- def load_app_wrapper(app_name, app_func):
82
- load_app(root, app_name, app_func)
83
-
84
- # Create the menu bar
85
- menu_bar = tk.Menu(root, bg="#008080", fg="white")
86
- # Create a "SpaCr Applications" menu
87
- app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
88
- menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
89
- # Add options to the "SpaCr Applications" menu
90
- for app_name, app_func in gui_apps.items():
91
- app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app_wrapper(app_name, app_func))
92
- # Add a separator and an exit option
93
- app_menu.add_separator()
94
- app_menu.add_command(label="Exit", command=root.quit)
95
- # Configure the menu for the root window
96
- root.config(menu=menu_bar)
97
-
98
- def proceed_with_app(root, app_name, app_func):
99
-
100
- from .app_mask import gui_mask
101
- from .app_measure import gui_measure
102
- from .app_annotate import gui_annotate
103
- from .app_make_masks import gui_make_masks
104
- from .app_classify import gui_classify
105
- from .gui import gui_app
106
-
107
- # Clear the current content frame
108
- if hasattr(root, 'content_frame'):
109
- for widget in root.content_frame.winfo_children():
110
- widget.destroy()
111
- else:
112
- root.content_frame = tk.Frame(root)
113
- root.content_frame.grid(row=1, column=0, sticky="nsew")
114
- root.grid_rowconfigure(1, weight=1)
115
- root.grid_columnconfigure(0, weight=1)
116
-
117
- # Initialize the new app in the content frame
118
- if app_name == "Main App":
119
- root.destroy() # Close the current window
120
- gui_app() # Open the main app window
121
- elif app_name == "Mask":
122
- gui_mask()
123
- elif app_name == "Measure":
124
- gui_measure()
125
- elif app_name == "Annotate":
126
- gui_annotate()
127
- elif app_name == "Make Masks":
128
- gui_make_masks()
129
- elif app_name == "Classify":
130
- gui_classify()
131
- else:
132
- raise ValueError(f"Invalid app name: {app_name}")
26
+ # Define global variables
27
+ q = None
28
+ console_output = None
29
+ parent_frame = None
30
+ vars_dict = None
31
+ canvas = None
32
+ canvas_widget = None
33
+ scrollable_frame = None
34
+ progress_label = None
35
+ fig_queue = None
133
36
 
134
- def load_app(root, app_name, app_func):
135
- # Cancel all scheduled after tasks
136
- if hasattr(root, 'after_tasks'):
137
- for task in root.after_tasks:
138
- root.after_cancel(task)
139
- root.after_tasks = []
37
+ thread_control = {"run_thread": None, "stop_requested": False}
140
38
 
141
- # Exit functionality only for the annotation app
142
- if app_name != "Annotate" and hasattr(root, 'current_app_exit_func'):
143
- root.next_app_func = proceed_with_app
144
- root.next_app_args = (app_name, app_func) # Ensure correct arguments
145
- root.current_app_exit_func()
146
- else:
147
- proceed_with_app(root, app_name, app_func)
39
+ class spacrCheckbutton(ttk.Checkbutton):
40
+ def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
41
+ super().__init__(parent, *args, **kwargs)
42
+ self.text = text
43
+ self.variable = variable if variable else tk.BooleanVar()
44
+ self.command = command
45
+ self.configure(text=self.text, variable=self.variable, command=self.command, style='Spacr.TCheckbutton')
148
46
 
149
- def create_menu_bar(root):
150
- from .app_mask import initiate_mask_root
151
- from .app_measure import initiate_measure_root
152
- from .app_annotate import initiate_annotation_app_root
153
- from .app_make_masks import initiate_mask_app_root
154
- from .app_classify import initiate_classify_root
155
- from .gui import gui_app
47
+ class spacrFrame(ttk.Frame):
48
+ def __init__(self, container, width=None, *args, bg='black', **kwargs):
49
+ super().__init__(container, *args, **kwargs)
50
+ self.configure(style='TFrame')
51
+ if width is None:
52
+ screen_width = self.winfo_screenwidth()
53
+ width = screen_width // 4
54
+ canvas = tk.Canvas(self, bg=bg, width=width)
55
+ scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
56
+
57
+ self.scrollable_frame = ttk.Frame(canvas, style='TFrame')
58
+ self.scrollable_frame.bind(
59
+ "<Configure>",
60
+ lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
61
+ )
62
+ canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
63
+ canvas.configure(yscrollcommand=scrollbar.set)
64
+
65
+ canvas.grid(row=0, column=0, sticky="nsew")
66
+ scrollbar.grid(row=0, column=1, sticky="ns")
156
67
 
157
- gui_apps = {
158
- "Main App": gui_app,
159
- "Mask": initiate_mask_root,
160
- "Measure": initiate_measure_root,
161
- "Annotate": initiate_annotation_app_root,
162
- "Make Masks": initiate_mask_app_root,
163
- "Classify": initiate_classify_root
164
- }
68
+ self.grid_rowconfigure(0, weight=1)
69
+ self.grid_columnconfigure(0, weight=1)
70
+ self.grid_columnconfigure(1, weight=0)
71
+
72
+ for child in self.scrollable_frame.winfo_children():
73
+ child.configure(bg='black')
165
74
 
166
- def load_app_wrapper(app_name, app_func):
167
- load_app(root, app_name, app_func)
75
+ class spacrLabel(tk.Frame):
76
+ def __init__(self, parent, text="", font=None, style=None, align="right", **kwargs):
77
+ label_kwargs = {k: v for k, v in kwargs.items() if k in ['foreground', 'background', 'font', 'anchor', 'justify', 'wraplength']}
78
+ for key in label_kwargs.keys():
79
+ kwargs.pop(key)
80
+ super().__init__(parent, **kwargs)
81
+ self.text = text
82
+ self.kwargs = label_kwargs
83
+ self.align = align
84
+ screen_height = self.winfo_screenheight()
85
+ label_height = screen_height // 50
86
+ label_width = label_height * 10
87
+ self.canvas = tk.Canvas(self, width=label_width, height=label_height, highlightthickness=0, bg=self.kwargs.get("background", "black"))
88
+ self.canvas.grid(row=0, column=0, sticky="ew")
89
+
90
+ self.font_style = font if font else tkFont.Font(family=self.kwargs.get("font_family", "Helvetica"), size=self.kwargs.get("font_size", 12), weight=tkFont.NORMAL)
91
+ self.style = style
92
+
93
+ if self.align == "center":
94
+ anchor_value = tk.CENTER
95
+ text_anchor = 'center'
96
+ else: # default to right alignment
97
+ anchor_value = tk.E
98
+ text_anchor = 'e'
99
+
100
+ if self.style:
101
+ ttk_style = ttk.Style()
102
+ ttk_style.configure(self.style, **label_kwargs)
103
+ self.label_text = ttk.Label(self.canvas, text=self.text, style=self.style, anchor=text_anchor, justify=text_anchor)
104
+ self.label_text.pack(fill=tk.BOTH, expand=True)
105
+ else:
106
+ self.label_text = self.canvas.create_text(label_width // 2 if self.align == "center" else label_width - 5,
107
+ label_height // 2, text=self.text, fill=self.kwargs.get("foreground", "white"),
108
+ font=self.font_style, anchor=anchor_value, justify=tk.RIGHT)
168
109
 
169
- # Create the menu bar
170
- menu_bar = tk.Menu(root, bg="#008080", fg="white")
171
- # Create a "SpaCr Applications" menu
172
- app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
173
- menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
174
- # Add options to the "SpaCr Applications" menu
175
- for app_name, app_func in gui_apps.items():
176
- app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app_wrapper(app_name, app_func))
177
- # Add a separator and an exit option
178
- app_menu.add_separator()
179
- app_menu.add_command(label="Exit", command=root.destroy) # Use root.destroy instead of root.quit
180
- # Configure the menu for the root window
181
- root.config(menu=menu_bar)
110
+ def set_text(self, text):
111
+ if self.style:
112
+ self.label_text.config(text=text)
113
+ else:
114
+ self.canvas.itemconfig(self.label_text, text=text)
182
115
 
183
- class CustomButton(tk.Frame):
116
+ class spacrButton(tk.Frame):
184
117
  def __init__(self, parent, text="", command=None, font=None, *args, **kwargs):
185
118
  super().__init__(parent, *args, **kwargs)
186
119
  self.text = text
187
120
  self.command = command
188
-
189
- # Detect screen height and calculate button dimensions
190
121
  screen_height = self.winfo_screenheight()
191
122
  button_height = screen_height // 50
192
123
  button_width = button_height * 3
193
124
 
194
- self.canvas = tk.Canvas(self, width=button_width, height=button_height, highlightthickness=0, bg="black")
125
+ # Increase the canvas size to accommodate the button and the rim
126
+ self.canvas = tk.Canvas(self, width=button_width + 4, height=button_height + 4, highlightthickness=0, bg="black")
195
127
  self.canvas.grid(row=0, column=0)
196
128
 
197
- self.button_bg = self.create_rounded_rectangle(0, 0, button_width, button_height, radius=20, fill="#800080")
129
+ self.button_bg = self.create_rounded_rectangle(2, 2, button_width + 2, button_height + 2, radius=20, fill="#000000", outline="#ffffff")
198
130
 
199
- # Use the passed font or default to Helvetica if not provided
200
131
  self.font_style = font if font else tkFont.Font(family="Helvetica", size=12, weight=tkFont.NORMAL)
201
- self.button_text = self.canvas.create_text(button_width // 2, button_height // 2, text=self.text, fill="white", font=self.font_style)
132
+ self.button_text = self.canvas.create_text((button_width + 4) // 2, (button_height + 4) // 2, text=self.text, fill="white", font=self.font_style)
202
133
 
203
134
  self.bind("<Enter>", self.on_enter)
204
135
  self.bind("<Leave>", self.on_leave)
@@ -208,62 +139,56 @@ class CustomButton(tk.Frame):
208
139
  self.canvas.bind("<Button-1>", self.on_click)
209
140
 
210
141
  def on_enter(self, event=None):
211
- self.canvas.itemconfig(self.button_bg, fill="#993399")
142
+ self.canvas.itemconfig(self.button_bg, fill="#008080") # Teal color
212
143
 
213
144
  def on_leave(self, event=None):
214
- self.canvas.itemconfig(self.button_bg, fill="#800080")
145
+ self.canvas.itemconfig(self.button_bg, fill="#000000") # Black color
215
146
 
216
147
  def on_click(self, event=None):
217
148
  if self.command:
218
149
  self.command()
219
150
 
220
151
  def create_rounded_rectangle(self, x1, y1, x2, y2, radius=20, **kwargs):
221
- points = [x1 + radius, y1,
222
- x1 + radius, y1,
223
- x2 - radius, y1,
224
- x2 - radius, y1,
225
- x2, y1,
226
- x2, y1 + radius,
227
- x2, y1 + radius,
228
- x2, y2 - radius,
229
- x2, y2 - radius,
230
- x2, y2,
231
- x2 - radius, y2,
232
- x2 - radius, y2,
233
- x1 + radius, y2,
234
- x1 + radius, y2,
235
- x1, y2,
236
- x1, y2 - radius,
237
- x1, y2 - radius,
238
- x1, y1 + radius,
239
- x1, y1 + radius,
240
- x1, y1]
241
-
152
+ points = [
153
+ x1 + radius, y1,
154
+ x1 + radius, y1,
155
+ x2 - radius, y1,
156
+ x2 - radius, y1,
157
+ x2, y1,
158
+ x2, y1 + radius,
159
+ x2, y1 + radius,
160
+ x2, y2 - radius,
161
+ x2, y2 - radius,
162
+ x2, y2,
163
+ x2 - radius, y2,
164
+ x2 - radius, y2,
165
+ x1 + radius, y2,
166
+ x1 + radius, y2,
167
+ x1, y2,
168
+ x1, y2 - radius,
169
+ x1, y2 - radius,
170
+ x1, y1 + radius,
171
+ x1, y1 + radius,
172
+ x1, y1
173
+ ]
242
174
  return self.canvas.create_polygon(points, **kwargs, smooth=True)
243
175
 
244
- class ToggleSwitch(ttk.Frame):
176
+
177
+ class spacrSwitch(ttk.Frame):
245
178
  def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
246
179
  super().__init__(parent, *args, **kwargs)
247
180
  self.text = text
248
181
  self.variable = variable if variable else tk.BooleanVar()
249
182
  self.command = command
250
-
251
183
  self.canvas = tk.Canvas(self, width=40, height=20, highlightthickness=0, bd=0, bg="black")
252
184
  self.canvas.grid(row=0, column=1, padx=(10, 0))
253
-
254
- # Background rounded rectangle with smaller dimensions and no outline
255
185
  self.switch_bg = self.create_rounded_rectangle(2, 2, 38, 18, radius=9, outline="", fill="#fff")
256
-
257
- # Switch ball with no outline
258
186
  self.switch = self.canvas.create_oval(4, 4, 16, 16, outline="", fill="#800080") # Purple initially
259
-
260
- self.label = ttk.Label(self, text=self.text, background="black", foreground="white")
187
+ self.label = spacrLabel(self, text=self.text, background="black", foreground="white")
261
188
  self.label.grid(row=0, column=0, padx=(0, 10))
262
-
263
189
  self.bind("<Button-1>", self.toggle)
264
190
  self.canvas.bind("<Button-1>", self.toggle)
265
191
  self.label.bind("<Button-1>", self.toggle)
266
-
267
192
  self.update_switch()
268
193
 
269
194
  def toggle(self, event=None):
@@ -328,699 +253,283 @@ class ToggleSwitch(ttk.Frame):
328
253
  x1, y1]
329
254
 
330
255
  return self.canvas.create_polygon(points, **kwargs, smooth=True)
331
-
332
- def set_default_font(root, font_name="Helvetica", size=12):
333
- default_font = (font_name, size)
334
- root.option_add("*Font", default_font)
335
- root.option_add("*TButton.Font", default_font)
336
- root.option_add("*TLabel.Font", default_font)
337
- root.option_add("*TEntry.Font", default_font)
338
256
 
339
- def check_and_download_font():
340
- font_name = "Helvetica"
341
- font_dir = "fonts"
342
- font_path = os.path.join(font_dir, "OpenSans-Regular.ttf")
343
-
344
- # Check if the font is already available
345
- available_fonts = list(tkFont.families())
346
- if font_name not in available_fonts:
347
- print(f"Font '{font_name}' not found. Downloading...")
348
- if not os.path.exists(font_dir):
349
- os.makedirs(font_dir)
350
-
351
- if not os.path.exists(font_path):
352
- url = "https://github.com/google/fonts/blob/main/apache/opensans/OpenSans-Regular.ttf?raw=true"
353
- response = requests.get(url)
354
- with open(font_path, "wb") as f:
355
- f.write(response.content)
356
- try:
357
- tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
358
- tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
359
- tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
360
- except tk.TclError:
361
- tkFont.nametofont("TkDefaultFont").configure(family="Helvetica", size=10)
362
- tkFont.nametofont("TkTextFont").configure(family="Helvetica", size=10)
363
- tkFont.nametofont("TkHeadingFont").configure(family="Helvetica", size=12)
257
+ class spacrToolTip:
258
+ def __init__(self, widget, text):
259
+ self.widget = widget
260
+ self.text = text
261
+ self.tooltip_window = None
262
+ widget.bind("<Enter>", self.show_tooltip)
263
+ widget.bind("<Leave>", self.hide_tooltip)
264
+
265
+ def show_tooltip(self, event):
266
+ x = event.x_root + 20
267
+ y = event.y_root + 10
268
+ self.tooltip_window = tk.Toplevel(self.widget)
269
+ self.tooltip_window.wm_overrideredirect(True)
270
+ self.tooltip_window.wm_geometry(f"+{x}+{y}")
271
+ self.tooltip_window.configure(bg='black')
272
+ label = tk.Label(self.tooltip_window, text=self.text, background="#333333", foreground="white", relief='flat', borderwidth=0)
273
+ label.grid(row=0, column=0, padx=5, pady=5)
274
+
275
+ def hide_tooltip(self, event):
276
+ if self.tooltip_window:
277
+ self.tooltip_window.destroy()
278
+ self.tooltip_window = None
279
+
280
+ def initiate_abort():
281
+ global thread_control
282
+ if thread_control.get("stop_requested") is not None:
283
+ thread_control["stop_requested"].value = 1
284
+
285
+ if thread_control.get("run_thread") is not None:
286
+ thread_control["run_thread"].join(timeout=5)
287
+ if thread_control["run_thread"].is_alive():
288
+ thread_control["run_thread"].terminate()
289
+ thread_control["run_thread"] = None
290
+
291
+ def start_process(q, fig_queue, settings_type='mask'):
292
+ global thread_control, vars_dict
293
+ from .settings import check_settings
294
+
295
+ settings = check_settings(vars_dict)
296
+ if thread_control.get("run_thread") is not None:
297
+ initiate_abort()
298
+ stop_requested = Value('i', 0) # multiprocessing shared value for inter-process communication
299
+ thread_control["stop_requested"] = stop_requested
300
+ if settings_type == 'mask':
301
+ thread_control["run_thread"] = Process(target=run_mask_gui, args=(settings, q, fig_queue, stop_requested))
302
+ elif settings_type == 'measure':
303
+ thread_control["run_thread"] = Process(target=run_measure_gui, args=(settings, q, fig_queue, stop_requested))
304
+ elif settings_type == 'classify':
305
+ thread_control["run_thread"] = Process(target=run_classify_gui, args=(settings, q, fig_queue, stop_requested))
306
+ elif settings_type == 'sequencing':
307
+ thread_control["run_thread"] = Process(target=run_sequencing_gui, args=(settings, q, fig_queue, stop_requested))
308
+ elif settings_type == 'umap':
309
+ thread_control["run_thread"] = Process(target=run_umap_gui, args=(settings, q, fig_queue, stop_requested))
310
+ thread_control["run_thread"].start()
311
+
312
+ def import_settings(settings_type='mask'):
313
+ global vars_dict, scrollable_frame
314
+ from .settings import generate_fields
315
+ csv_file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
316
+ csv_settings = read_settings_from_csv(csv_file_path)
317
+ if settings_type == 'mask':
318
+ settings = set_default_settings_preprocess_generate_masks(src='path', settings={})
319
+ elif settings_type == 'measure':
320
+ settings = get_measure_crop_settings(settings={})
321
+ elif settings_type == 'classify':
322
+ settings = set_default_train_test_model(settings={})
323
+ elif settings_type == 'sequencing':
324
+ settings = get_analyze_reads_default_settings(settings={})
325
+ elif settings_type == 'umap':
326
+ settings = set_default_umap_image_settings(settings={})
364
327
  else:
365
- tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
366
- tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
367
- tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
328
+ raise ValueError(f"Invalid settings type: {settings_type}")
329
+
330
+ variables = convert_settings_dict_for_gui(settings)
331
+ new_settings = update_settings_from_csv(variables, csv_settings)
332
+ vars_dict = generate_fields(new_settings, scrollable_frame)
368
333
 
369
- def set_dark_style_v1(style):
370
- font_style = tkFont.Font(family="Helvetica", size=10)
334
+ def set_dark_style(style):
335
+ font_style = tkFont.Font(family="Helvetica", size=24)
336
+
337
+ # Entry
371
338
  style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='black', foreground='#ffffff', font=font_style)
339
+
340
+ # Combobox
372
341
  style.configure('TCombobox', fieldbackground='black', background='black', foreground='#ffffff', font=font_style)
373
- style.configure('Custom.TButton', padding='10 10 10 10', borderwidth=1, relief='solid', background='#008080', foreground='#ffffff', font=font_style)
374
- style.map('Custom.TButton',
375
- background=[('active', '#66b2b2'), ('disabled', '#004d4d'), ('!disabled', '#008080')],
376
- foreground=[('active', '#ffffff'), ('disabled', '#888888')])
342
+ style.map('TCombobox', fieldbackground=[('readonly', 'black')], foreground=[('readonly', '#ffffff')])
343
+
344
+ # Custom Button
345
+ style.configure('Custom.TButton', background='black', foreground='white', bordercolor='white', focusthickness=3, focuscolor='white', font=('Helvetica', 12))
346
+ style.map('Custom.TButton', background=[('active', 'teal'), ('!active', 'black')], foreground=[('active', 'white'), ('!active', 'white')], bordercolor=[('active', 'white'), ('!active', 'white')])
347
+
348
+ # Custom Label
377
349
  style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='black', foreground='#ffffff', font=font_style)
378
- style.configure('TCheckbutton', background='black', foreground='#ffffff', indicatoron=False, relief='flat', font=font_style)
379
- style.map('TCheckbutton', background=[('selected', '#555555'), ('active', '#555555')])
350
+
351
+ # Checkbutton
352
+ style.configure('Spacr.TCheckbutton', background='black', foreground='#ffffff', indicatoron=False, relief='flat', font="15")
353
+ style.map('Spacr.TCheckbutton', background=[('selected', 'black'), ('active', 'black')], foreground=[('selected', '#ffffff'), ('active', '#ffffff')])
380
354
 
381
- def set_dark_style(style):
382
- font_style = tkFont.Font(family="Helvetica", size=10)
383
- style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='black', foreground='#ffffff', font=font_style) # Entry
384
- style.configure('TCombobox', fieldbackground='black', background='black', foreground='#ffffff', font=font_style) # Combobox
385
- style.configure('Custom.TButton', padding='10 10 10 10', borderwidth=1, relief='solid', background='#008080', foreground='#ffffff', font=font_style) # Custom Button
386
- style.map('Custom.TButton',
387
- background=[('active', '#66b2b2'), ('disabled', '#004d4d'), ('!disabled', '#008080')],
388
- foreground=[('active', '#ffffff'), ('disabled', '#888888')])
389
- style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='black', foreground='#ffffff', font=font_style) # Custom Label
390
- style.configure('TCheckbutton', background='black', foreground='#ffffff', indicatoron=False, relief='flat', font=font_style) # Checkbutton
391
- style.map('TCheckbutton', background=[('selected', '#555555'), ('active', '#555555')])
392
- style.configure('TLabel', background='black', foreground='#ffffff', font=font_style) # Label
393
- style.configure('TFrame', background='black') # Frame
394
- style.configure('TPanedwindow', background='black') # PanedWindow
395
- style.configure('TNotebook', background='black', tabmargins=[2, 5, 2, 0]) # Notebook
355
+ # General Label
356
+ style.configure('TLabel', background='black', foreground='#ffffff', font=font_style)
357
+
358
+ # Frame
359
+ style.configure('TFrame', background='black')
360
+
361
+ # PanedWindow
362
+ style.configure('TPanedwindow', background='black')
363
+
364
+ # Notebook
365
+ style.configure('TNotebook', background='black', tabmargins=[2, 5, 2, 0])
396
366
  style.configure('TNotebook.Tab', background='black', foreground='#ffffff', padding=[5, 5], font=font_style)
397
- style.map('TNotebook.Tab', background=[('selected', '#555555'), ('active', '#555555')])
398
- style.configure('TButton', background='black', foreground='#ffffff', padding='5 5 5 5', font=font_style) # Button (regular)
367
+ style.map('TNotebook.Tab', background=[('selected', '#555555'), ('active', '#555555')], foreground=[('selected', '#ffffff'), ('active', '#ffffff')])
368
+
369
+ # Button (regular)
370
+ style.configure('TButton', background='black', foreground='#ffffff', padding='5 5 5 5', font=font_style)
399
371
  style.map('TButton', background=[('active', '#555555'), ('disabled', '#333333')])
400
- style.configure('Vertical.TScrollbar', background='black', troughcolor='black', bordercolor='black') # Scrollbar
372
+
373
+ # Scrollbar
374
+ style.configure('Vertical.TScrollbar', background='black', troughcolor='black', bordercolor='black')
401
375
  style.configure('Horizontal.TScrollbar', background='black', troughcolor='black', bordercolor='black')
376
+
377
+ # LabelFrame
378
+ style.configure('Custom.TLabelFrame', font=('Helvetica', 10, 'bold'), background='black', foreground='white', relief='solid', borderwidth=1)
379
+ style.configure('Custom.TLabelFrame.Label', background='black', foreground='white', font=('Helvetica', 10, 'bold'))
402
380
 
403
- def read_settings_from_csv(csv_file_path):
404
- settings = {}
405
- with open(csv_file_path, newline='') as csvfile:
406
- reader = csv.DictReader(csvfile)
407
- for row in reader:
408
- key = row['Key']
409
- value = row['Value']
410
- settings[key] = value
411
- return settings
412
-
413
- def update_settings_from_csv(variables, csv_settings):
414
- new_settings = variables.copy() # Start with a copy of the original settings
415
- for key, value in csv_settings.items():
416
- if key in new_settings:
417
- # Get the variable type and options from the original settings
418
- var_type, options, _ = new_settings[key]
419
- # Update the default value with the CSV value, keeping the type and options unchanged
420
- new_settings[key] = (var_type, options, value)
421
- return new_settings
422
-
423
- def safe_literal_eval(value):
424
- try:
425
- # First, try to evaluate as a literal
426
- return ast.literal_eval(value)
427
- except (ValueError, SyntaxError):
428
- # If it fails, return the value as it is (a string)
429
- return value
430
-
431
- def disable_interactivity(fig):
432
- if hasattr(fig.canvas, 'toolbar'):
433
- fig.canvas.toolbar.pack_forget()
434
-
435
- event_handlers = fig.canvas.callbacks.callbacks
436
- for event, handlers in list(event_handlers.items()):
437
- for handler_id in list(handlers.keys()):
438
- fig.canvas.mpl_disconnect(handler_id)
381
+ def set_default_font(root, font_name="Helvetica", size=12):
382
+ default_font = (font_name, size)
383
+ root.option_add("*Font", default_font)
384
+ root.option_add("*TButton.Font", default_font)
385
+ root.option_add("*TLabel.Font", default_font)
386
+ root.option_add("*TEntry.Font", default_font)
439
387
 
440
- class ScrollableFrame_v1(ttk.Frame):
441
- def __init__(self, container, *args, bg='black', **kwargs):
442
- super().__init__(container, *args, **kwargs)
443
- self.configure(style='TFrame')
444
- screen_width = self.winfo_screenwidth()
445
- frame_width = screen_width // 4 # Set the frame width to 1/4th of the screen width
446
- canvas = tk.Canvas(self, bg=bg, width=frame_width) # Set canvas background to match dark mode
447
- scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
448
- self.scrollable_frame = ttk.Frame(canvas, style='TFrame', padding=5) # Ensure it uses the styled frame
449
- self.scrollable_frame.bind(
450
- "<Configure>",
451
- lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
452
- )
453
- canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
454
- canvas.configure(yscrollcommand=scrollbar.set)
455
- canvas.pack(side="left", fill="both", expand=True)
456
- scrollbar.pack(side="right", fill="y")
388
+ def create_menu_bar(root):
389
+ from .app_annotate import initiate_annotation_app_root
390
+ from .app_make_masks import initiate_mask_app_root
457
391
 
458
- class ScrollableFrame(ttk.Frame):
459
- def __init__(self, container, width=None, *args, bg='black', **kwargs):
460
- super().__init__(container, *args, **kwargs)
461
- self.configure(style='TFrame')
462
- if width is None:
463
- screen_width = self.winfo_screenwidth()
464
- width = screen_width // 4
465
- canvas = tk.Canvas(self, bg=bg, width=width)
466
- scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
467
-
468
- self.scrollable_frame = ttk.Frame(canvas, style='TFrame')
469
- self.scrollable_frame.bind(
470
- "<Configure>",
471
- lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
472
- )
473
- canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
474
- canvas.configure(yscrollcommand=scrollbar.set)
475
- canvas.pack(side="left", fill="both", expand=True)
476
- scrollbar.pack(side="right", fill="y")
477
- for child in self.scrollable_frame.winfo_children():
478
- child.configure(bg='black')
392
+ gui_apps = {
393
+ "Mask": 'mask',
394
+ "Measure": 'measure',
395
+ "Annotate": initiate_annotation_app_root,
396
+ "Make Masks": initiate_mask_app_root,
397
+ "Classify": 'classify',
398
+ "Sequencing": 'sequencing',
399
+ "Umap": 'umap'
400
+ }
479
401
 
480
- class StdoutRedirector:
481
- def __init__(self, text_widget):
482
- self.text_widget = text_widget
402
+ def load_app_wrapper(app_name, app_func):
403
+ load_app(root, app_name, app_func)
483
404
 
484
- def write(self, string):
485
- try:
486
- if self.text_widget.winfo_exists():
487
- self.text_widget.insert(tk.END, string)
488
- self.text_widget.see(tk.END)
489
- except tk.TclError:
490
- pass # Handle or log the error as needed
405
+ # Create the menu bar
406
+ menu_bar = tk.Menu(root, bg="#008080", fg="white")
407
+ # Create a "SpaCr Applications" menu
408
+ app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
409
+ menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
410
+ # Add options to the "SpaCr Applications" menu
411
+ for app_name, app_func in gui_apps.items():
412
+ app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app_wrapper(app_name, app_func))
413
+ # Add a separator and an exit option
414
+ app_menu.add_separator()
415
+ app_menu.add_command(label="Exit", command=root.quit)
416
+ # Configure the menu for the root window
417
+ root.config(menu=menu_bar)
491
418
 
492
- def flush(self):
493
- pass
419
+ def proceed_with_app(root, app_name, app_func):
420
+ from .app_annotate import gui_annotate
421
+ from .app_make_masks import gui_make_masks
422
+ from .gui import gui_app
494
423
 
495
- def check_mask_gui_settings(vars_dict):
496
- settings = {}
497
- for key, var in vars_dict.items():
498
- value = var.get()
499
-
500
- # Handle conversion for specific types if necessary
501
- if key in ['channels', 'timelapse_frame_limits']: # Assuming these should be lists
502
- try:
503
- # Convert string representation of a list into an actual list
504
- settings[key] = eval(value)
505
- except:
506
- messagebox.showerror("Error", f"Invalid format for {key}. Please enter a valid list.")
507
- return
508
- elif key in ['nucleus_channel', 'cell_channel', 'pathogen_channel', 'examples_to_plot', 'batch_size', 'timelapse_memory', 'workers', 'fps', 'magnification']: # Assuming these should be integers
509
- try:
510
- settings[key] = int(value) if value else None
511
- except ValueError:
512
- messagebox.showerror("Error", f"Invalid number for {key}.")
513
- return
514
- elif key in ['nucleus_background', 'cell_background', 'pathogen_background', 'nucleus_Signal_to_noise', 'cell_Signal_to_noise', 'pathogen_Signal_to_noise', 'nucleus_CP_prob', 'cell_CP_prob', 'pathogen_CP_prob', 'lower_quantile']: # Assuming these should be floats
424
+ # Clear the current content frame
425
+ if hasattr(root, 'content_frame'):
426
+ for widget in root.content_frame.winfo_children():
515
427
  try:
516
- settings[key] = float(value) if value else None
517
- except ValueError:
518
- messagebox.showerror("Error", f"Invalid number for {key}.")
519
- return
520
- else:
521
- settings[key] = value
522
- return settings
428
+ widget.destroy()
429
+ except tk.TclError as e:
430
+ print(f"Error destroying widget: {e}")
431
+ else:
432
+ root.content_frame = tk.Frame(root)
433
+ root.content_frame.grid(row=1, column=0, sticky="nsew")
434
+ root.grid_rowconfigure(1, weight=1)
435
+ root.grid_columnconfigure(0, weight=1)
523
436
 
524
- def check_measure_gui_settings(vars_dict):
525
- settings = {}
526
- for key, var in vars_dict.items():
527
- value = var.get() # Retrieves the string representation for entries or the actual value for checkboxes and combos.
437
+ # Initialize the new app in the content frame
438
+ if app_name == "Mask":
439
+ initiate_root(root.content_frame, 'mask')
440
+ elif app_name == "Measure":
441
+ initiate_root(root.content_frame, 'measure')
442
+ elif app_name == "Classify":
443
+ initiate_root(root.content_frame, 'classify')
444
+ elif app_name == "Sequencing":
445
+ initiate_root(root.content_frame, 'sequencing')
446
+ elif app_name == "Umap":
447
+ initiate_root(root.content_frame, 'umap')
448
+ elif app_name == "Annotate":
449
+ gui_annotate()
450
+ elif app_name == "Make Masks":
451
+ gui_make_masks()
452
+ else:
453
+ raise ValueError(f"Invalid app name: {app_name}")
528
454
 
529
- try:
530
- if key in ['channels', 'png_dims']:
531
- settings[key] = [int(chan) for chan in ast.literal_eval(value)] if value else []
532
-
533
- elif key in ['cell_loc', 'pathogen_loc', 'treatment_loc']:
534
- # Convert to a list of lists of strings, ensuring all structures are lists.
535
- settings[key] = [list(map(str, sublist)) for sublist in ast.literal_eval(value)] if value else []
536
-
537
- elif key == 'dialate_png_ratios':
538
- settings[key] = [float(num) for num in ast.literal_eval(value)] if value else []
539
-
540
- elif key == 'normalize':
541
- settings[key] = [int(num) for num in ast.literal_eval(value)] if value else []
542
-
543
- # Directly assign string values for these specific keys
544
- elif key in ['normalize_by', 'experiment', 'measurement', 'input_folder']:
545
- settings[key] = value
546
-
547
- elif key == 'png_size':
548
- settings[key] = [list(map(int, dim)) for dim in ast.literal_eval(value)] if value else []
549
-
550
- # Ensure these are lists of strings, converting from tuples if necessary
551
- elif key in ['timelapse_objects', 'crop_mode', 'cells', 'pathogens', 'treatments']:
552
- eval_value = ast.literal_eval(value) if value else []
553
- settings[key] = list(map(str, eval_value)) if isinstance(eval_value, (list, tuple)) else [str(eval_value)]
554
-
555
- # Handling for single non-string values (int, float, bool)
556
- elif key in ['cell_mask_dim', 'cell_min_size', 'nucleus_mask_dim', 'nucleus_min_size', 'pathogen_mask_dim', 'pathogen_min_size', 'cytoplasm_min_size', 'max_workers', 'channel_of_interest', 'nr_imgs']:
557
- settings[key] = int(value) if value else None
558
-
559
- elif key == 'um_per_pixel':
560
- settings[key] = float(value) if value else None
561
-
562
- # Handling boolean values based on checkboxes
563
- elif key in ['save_png', 'use_bounding_box', 'save_measurements', 'plot', 'plot_filtration', 'include_uninfected', 'dialate_pngs', 'timelapse', 'representative_images']:
564
- settings[key] = var.get()
565
-
566
- except SyntaxError as e:
567
- print(f"Syntax error processing {key}: {str(e)}")
568
- #messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
569
- return None
570
- except Exception as e:
571
- print(f"Error processing {key}: {str(e)}")
572
- #messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
573
- return None
455
+ def load_app(root, app_name, app_func):
456
+ # Cancel all scheduled after tasks
457
+ if hasattr(root, 'after_tasks'):
458
+ for task in root.after_tasks:
459
+ root.after_cancel(task)
460
+ root.after_tasks = []
574
461
 
575
- return settings
462
+ # Exit functionality only for the annotation app
463
+ if app_name != "Annotate" and hasattr(root, 'current_app_exit_func'):
464
+ root.next_app_func = proceed_with_app
465
+ root.next_app_args = (app_name, app_func) # Ensure correct arguments
466
+ root.current_app_exit_func()
467
+ else:
468
+ proceed_with_app(root, app_name, app_func)
576
469
 
577
- def check_classify_gui_settings(vars_dict):
470
+ def read_settings_from_csv(csv_file_path):
578
471
  settings = {}
579
- for key, var in vars_dict.items():
580
- value = var.get() # This retrieves the string representation for entries or the actual value for checkboxes and combos
581
-
582
- try:
583
- if key in ['src', 'measurement']:
584
- # Directly assign string values
585
- settings[key] = str(value)
586
- elif key in ['cell_mask_dim', 'image_size', 'batch_size', 'epochs', 'gradient_accumulation_steps', 'num_workers']:
587
- # Convert to integer
588
- settings[key] = int(value)
589
- elif key in ['val_split', 'learning_rate', 'weight_decay', 'dropout_rate']:
590
- # Convert to float
591
- settings[key] = float(value)
592
- elif key == 'classes':
593
- # Evaluate as list
594
- settings[key] = ast.literal_eval(value)
595
-
596
- elif key in ['model_type','optimizer_type','schedule','loss_type','train_mode']:
597
- settings[key] = value
598
-
599
- elif key in ['gradient_accumulation','normalize','save','plot', 'init_weights','amsgrad','use_checkpoint','intermedeate_save','pin_memory', 'num_workers','verbose']:
600
- settings[key] = bool(value)
601
-
602
- except SyntaxError as e:
603
- messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
604
- return None
605
- except Exception as e:
606
- messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
607
- return None
608
-
472
+ with open(csv_file_path, newline='') as csvfile:
473
+ reader = csv.DictReader(csvfile)
474
+ for row in reader:
475
+ key = row['Key']
476
+ value = row['Value']
477
+ settings[key] = value
609
478
  return settings
610
479
 
611
- def check_sim_gui_settings(vars_dict):
612
- settings = {}
613
- for key, var in vars_dict.items():
614
- value = var.get() # This retrieves the string representation for entries or the actual value for checkboxes and combos
615
-
616
- try:
617
- if key in ['src', 'name', 'variable']:
618
- # Directly assign string values
619
- settings[key] = str(value)
620
-
621
- elif key in ['nr_plates', 'number_of_genes','number_of_active_genes','avg_genes_per_well','avg_cells_per_well','avg_reads_per_gene']:
622
- #generate list of integers from list
623
- ls = [int(num) for num in ast.literal_eval(value)]
624
- if len(ls) == 3 and ls[2] > 0:
625
- list_of_integers = list(range(ls[0], ls[1], ls[2]))
626
- list_of_integers = [num + 1 if num == 0 else num for num in list_of_integers]
627
- else:
628
- list_of_integers = [ls[0]]
629
- settings[key] = list_of_integers
630
-
631
- elif key in ['sequencing_error','well_ineq_coeff','gene_ineq_coeff', 'positive_mean']:
632
- #generate list of floats from list
633
- ls = [float(num) for num in ast.literal_eval(value)]
634
- if len(ls) == 3 and ls[2] > 0:
635
- list_of_floats = np.linspace(ls[0], ls[1], ls[2])
636
- list_of_floats.tolist()
637
- list_of_floats = [x if x != 0.0 else x + 0.01 for x in list_of_floats]
638
- else:
639
- list_of_floats = [ls[0]]
640
- settings[key] = list_of_floats
641
-
642
- elif key in ['plot', 'random_seed']:
643
- # Evaluate as bool
644
- settings[key] = bool(value)
645
-
646
- elif key in ['number_of_control_genes', 'replicates', 'max_workers']:
647
- # Convert to integer
648
- settings[key] = int(value)
649
-
650
- except SyntaxError as e:
651
- messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
652
- return None
653
- except Exception as e:
654
- messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
655
- return None
656
-
657
- return settings
480
+ def update_settings_from_csv(variables, csv_settings):
481
+ new_settings = variables.copy() # Start with a copy of the original settings
482
+ for key, value in csv_settings.items():
483
+ if key in new_settings:
484
+ # Get the variable type and options from the original settings
485
+ var_type, options, _ = new_settings[key]
486
+ # Update the default value with the CSV value, keeping the type and options unchanged
487
+ new_settings[key] = (var_type, options, value)
488
+ return new_settings
658
489
 
659
- def sim_variables():
660
- variables = {
661
- 'name':('entry', None, 'plates_2_4_8'),
662
- 'variable':('entry', None, 'all'),
663
- 'src':('entry', None, '/home/olafsson/Desktop/simulations'),
664
- 'number_of_control_genes':('entry', None, 30),
665
- 'replicates':('entry', None, 4),
666
- 'max_workers':('entry', None, 1),
667
- 'plot':('check', None, True),
668
- 'random_seed':('check', None, True),
669
- 'nr_plates': ('entry', None, '[8,8,0]'),# '[2,2,8]'
670
- 'number_of_genes': ('entry', None, '[100, 100, 0]'), #[1384, 1384, 0]
671
- 'number_of_active_genes': ('entry', None, '[10,10,0]'),
672
- 'avg_genes_per_well': ('entry', None, '[2, 10, 2]'),
673
- 'avg_cells_per_well': ('entry', None, '[100, 100, 0]'),
674
- 'positive_mean': ('entry', None, '[0.8, 0.8, 0]'),
675
- 'avg_reads_per_gene': ('entry', None, '[1000,1000, 0]'),
676
- 'sequencing_error': ('entry', None, '[0.01, 0.01, 0]'),
677
- 'well_ineq_coeff': ('entry', None, '[0.3,0.3,0]'),
678
- 'gene_ineq_coeff': ('entry', None, '[0.8,0.8,0]'),
679
- }
680
- return variables
490
+ def parse_list(value):
491
+ try:
492
+ parsed_value = ast.literal_eval(value)
493
+ if isinstance(parsed_value, list):
494
+ return parsed_value
495
+ else:
496
+ raise ValueError
497
+ except (ValueError, SyntaxError):
498
+ raise ValueError("Invalid format for list")
499
+
500
+ def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
501
+ label_column = 0
502
+ widget_column = 1
681
503
 
682
- def add_measure_gui_defaults(settings):
683
- settings['compartments'] = ['pathogen', 'cytoplasm']
684
- return settings
504
+ # Configure the column widths
505
+ frame.grid_columnconfigure(label_column, weight=0) # Allow the label column to expand
506
+ frame.grid_columnconfigure(widget_column, weight=1) # Allow the widget column to expand
685
507
 
686
- def measure_variables():
687
- variables = {
688
- 'input_folder':('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui/merged'),
689
- 'channels': ('combo', ['[0,1,2,3]','[0,1,2]','[0,1]','[0]'], '[0,1,2,3]'),
690
- 'cell_mask_dim':('entry', None, 4),
691
- 'cell_min_size':('entry', None, 0),
692
- 'cytoplasm_min_size':('entry', None, 0),
693
- 'nucleus_mask_dim':('entry', None, 5),
694
- 'nucleus_min_size':('entry', None, 0),
695
- 'pathogen_mask_dim':('entry', None, 6),
696
- 'pathogen_min_size':('entry', None, 0),
697
- 'save_png':('check', None, True),
698
- 'crop_mode':('entry', None, '["cell"]'),
699
- 'use_bounding_box':('check', None, True),
700
- 'png_size': ('entry', None, '[[224,224]]'),
701
- 'normalize':('entry', None, '[2,98]'),
702
- 'png_dims':('entry', None, '[1,2,3]'),
703
- 'normalize_by':('combo', ['fov', 'png'], 'png'),
704
- 'save_measurements':('check', None, True),
705
- 'representative_images':('check', None, True),
706
- 'plot':('check', None, True),
707
- 'plot_filtration':('check', None, True),
708
- 'include_uninfected':('check', None, True),
709
- 'dialate_pngs':('check', None, False),
710
- 'dialate_png_ratios':('entry', None, '[0.2]'),
711
- 'timelapse':('check', None, False),
712
- 'timelapse_objects':('combo', ['["cell"]', '["nucleus"]', '["pathogen"]', '["cytoplasm"]'], '["cell"]'),
713
- 'max_workers':('entry', None, 30),
714
- 'experiment':('entry', None, 'experiment name'),
715
- 'cells':('entry', None, ['HeLa']),
716
- 'cell_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
717
- 'pathogens':('entry', None, '["wt","mutant"]'),
718
- 'pathogen_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
719
- 'treatments': ('entry', None, '["cm","lovastatin_20uM"]'),
720
- 'treatment_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
721
- 'channel_of_interest':('entry', None, 3),
722
- 'compartments':('entry', None, '["pathogen","cytoplasm"]'),
723
- 'measurement':('entry', None, 'mean_intensity'),
724
- 'nr_imgs':('entry', None, 32),
725
- 'um_per_pixel':('entry', None, 0.1)
726
- }
727
- return variables
508
+ # Right-align the label text and the label itself
509
+ label = spacrLabel(frame, text=label_text, background="black", foreground="white", anchor='e', justify='right')
510
+ label.grid(column=label_column, row=row, sticky=tk.E, padx=(5, 2), pady=5) # Align label to the right
728
511
 
729
- def classify_variables():
730
-
731
- def get_torchvision_models():
732
- # Fetch all public callable attributes from torchvision.models that are functions
733
- model_names = [name for name, obj in inspect.getmembers(models)
734
- if inspect.isfunction(obj) and not name.startswith("__")]
735
- return model_names
736
-
737
- model_names = get_torchvision_models()
738
- variables = {
739
- 'src':('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui/merged'),
740
- 'cell_mask_dim':('entry', None, 4),
741
- 'classes':('entry', None, '["nc","pc"]'),
742
- 'measurement':('entry', None, 'mean_intensity'),
743
- 'model_type': ('combo', model_names, 'resnet50'),
744
- 'optimizer_type': ('combo', ['adamw','adam'], 'adamw'),
745
- 'schedule': ('combo', ['reduce_lr_on_plateau','step_lr'], 'reduce_lr_on_plateau'),
746
- 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
747
- 'image_size': ('entry', None, 224),
748
- 'batch_size': ('entry', None, 12),
749
- 'epochs': ('entry', None, 2),
750
- 'val_split': ('entry', None, 0.1),
751
- 'train_mode': ('combo', ['erm', 'irm'], 'erm'),
752
- 'learning_rate': ('entry', None, 0.0001),
753
- 'weight_decay': ('entry', None, 0.00001),
754
- 'dropout_rate': ('entry', None, 0.1),
755
- 'gradient_accumulation': ('check', None, True),
756
- 'gradient_accumulation_steps': ('entry', None, 4),
757
- 'normalize': ('check', None, True),
758
- 'save': ('check', None, True),
759
- 'plot': ('check', None, True),
760
- 'init_weights': ('check', None, True),
761
- 'amsgrad': ('check', None, True),
762
- 'use_checkpoint': ('check', None, True),
763
- 'intermedeate_save': ('check', None, True),
764
- 'pin_memory': ('check', None, True),
765
- 'num_workers': ('entry', None, 30),
766
- 'verbose': ('check', None, True),
767
- }
768
- return variables
769
-
770
- def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
771
- label = ttk.Label(frame, text=label_text, style='Custom.TLabel') # Apply Custom.TLabel style for labels
772
- label.grid(column=0, row=row, sticky=tk.W, padx=5, pady=5)
773
-
774
512
  if var_type == 'entry':
775
513
  var = tk.StringVar(value=default_value) # Set default value
776
514
  entry = ttk.Entry(frame, textvariable=var, style='TEntry') # Apply TEntry style for entries
777
- entry.grid(column=1, row=row, sticky=tk.EW, padx=5)
515
+ entry.grid(column=widget_column, row=row, sticky=tk.W, padx=(2, 5), pady=5) # Align widget to the left
778
516
  return (label, entry, var) # Return both the label and the entry, and the variable
779
517
  elif var_type == 'check':
780
518
  var = tk.BooleanVar(value=default_value) # Set default value (True/False)
781
- check = ToggleSwitch(frame, text="", variable=var) # Use ToggleSwitch class
782
- check.grid(column=1, row=row, sticky=tk.W, padx=5)
519
+ check = spacrCheckbutton(frame, text="", variable=var, style='TCheckbutton')
520
+ check.grid(column=widget_column, row=row, sticky=tk.W, padx=(2, 5), pady=5) # Align widget to the left
783
521
  return (label, check, var) # Return both the label and the checkbutton, and the variable
784
522
  elif var_type == 'combo':
785
523
  var = tk.StringVar(value=default_value) # Set default value
786
524
  combo = ttk.Combobox(frame, textvariable=var, values=options, style='TCombobox') # Apply TCombobox style
787
- combo.grid(column=1, row=row, sticky=tk.EW, padx=5)
525
+ combo.grid(column=widget_column, row=row, sticky=tk.W, padx=(2, 5), pady=5) # Align widget to the left
788
526
  if default_value:
789
527
  combo.set(default_value)
790
528
  return (label, combo, var) # Return both the label and the combobox, and the variable
791
529
  else:
792
530
  var = None # Placeholder in case of an undefined var_type
793
531
  return (label, None, var)
794
-
795
- def convert_settings_dict_for_gui(settings):
796
- variables = {}
797
- special_cases = {
798
- 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
799
- 'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
800
- 'magnification': ('combo', [20, 40, 60], 20),
801
- 'nucleus_channel': ('combo', [0, 1, 2, 3, None], None),
802
- 'cell_channel': ('combo', [0, 1, 2, 3, None], None),
803
- 'pathogen_channel': ('combo', [0, 1, 2, 3, None], None),
804
- 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
805
- 'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
806
- 'model_type': ('combo', ['resnet50', 'other_model'], 'resnet50'),
807
- 'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
808
- 'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
809
- 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
810
- 'normalize_by': ('combo', ['fov', 'png'], 'png'),
811
- }
812
- for key, value in settings.items():
813
- if key in special_cases:
814
- variables[key] = special_cases[key]
815
- elif isinstance(value, bool):
816
- variables[key] = ('check', None, value)
817
- elif isinstance(value, int) or isinstance(value, float):
818
- variables[key] = ('entry', None, value)
819
- elif isinstance(value, str):
820
- variables[key] = ('entry', None, value)
821
- elif value is None:
822
- variables[key] = ('entry', None, value)
823
- elif isinstance(value, list):
824
- variables[key] = ('entry', None, str(value))
825
- return variables
826
-
827
- def mask_variables():
828
- variables = {
829
- 'src': ('entry', None, 'path/to/images'),
830
- 'pathogen_model': ('entry', None, 'path/to/model'),
831
- 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
832
- 'custom_regex': ('entry', None, None),
833
- 'experiment': ('entry', None, 'exp'),
834
- 'channels': ('combo', ['[0,1,2,3]','[0,1,2]','[0,1]','[0]'], '[0,1,2,3]'),
835
- 'magnification': ('combo', [20, 40, 60,], 40),
836
- 'nucleus_channel': ('combo', [0,1,2,3, None], 0),
837
- 'nucleus_background': ('entry', None, 100),
838
- 'nucleus_Signal_to_noise': ('entry', None, 5),
839
- 'nucleus_CP_prob': ('entry', None, 0),
840
- 'remove_background_nucleus': ('check', None, False),
841
- 'cell_channel': ('combo', [0,1,2,3, None], 3),
842
- 'cell_background': ('entry', None, 100),
843
- 'cell_Signal_to_noise': ('entry', None, 5),
844
- 'cell_CP_prob': ('entry', None, 0),
845
- 'remove_background_cell': ('check', None, False),
846
- 'pathogen_channel': ('combo', [0,1,2,3, None], 2),
847
- 'pathogen_background': ('entry', None, 100),
848
- 'pathogen_Signal_to_noise': ('entry', None, 3),
849
- 'pathogen_CP_prob': ('entry', None, 0),
850
- 'remove_background_pathogen': ('check', None, False),
851
- 'preprocess': ('check', None, True),
852
- 'masks': ('check', None, True),
853
- 'examples_to_plot': ('entry', None, 1),
854
- 'randomize': ('check', None, True),
855
- 'batch_size': ('entry', None, 50),
856
- 'timelapse': ('check', None, False),
857
- 'timelapse_displacement': ('entry', None, None),
858
- 'timelapse_memory': ('entry', None, 0),
859
- 'timelapse_frame_limits': ('entry', None, '[0,1000]'),
860
- 'timelapse_remove_transient': ('check', None, True),
861
- 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
862
- 'timelapse_objects': ('combo', ['cell','nucleus','pathogen','cytoplasm', None], None),
863
- 'fps': ('entry', None, 2),
864
- 'remove_background': ('check', None, True),
865
- 'lower_quantile': ('entry', None, 0.01),
866
- #'merge': ('check', None, False),
867
- 'normalize_plots': ('check', None, True),
868
- 'all_to_mip': ('check', None, False),
869
- 'pick_slice': ('check', None, False),
870
- 'skip_mode': ('entry', None, None),
871
- 'save': ('check', None, True),
872
- 'plot': ('check', None, True),
873
- 'workers': ('entry', None, 30),
874
- 'verbose': ('check', None, True),
875
- 'filter': ('check', None, True),
876
- 'merge_pathogens': ('check', None, True),
877
- 'adjust_cells': ('check', None, True),
878
- 'test_images': ('entry', None, 10),
879
- 'random_test': ('check', None, True),
880
- }
881
- return variables
882
-
883
- def add_mask_gui_defaults(settings):
884
- settings['remove_background'] = True
885
- settings['fps'] = 2
886
- settings['all_to_mip'] = False
887
- settings['pick_slice'] = False
888
- settings['merge'] = False
889
- settings['skip_mode'] = ''
890
- settings['verbose'] = False
891
- settings['normalize_plots'] = True
892
- settings['randomize'] = True
893
- settings['preprocess'] = True
894
- settings['masks'] = True
895
- settings['examples_to_plot'] = 1
896
- return settings
897
-
898
- def generate_fields(variables, scrollable_frame):
899
- vars_dict = {}
900
- row = 5
901
- tooltips = {
902
- "src": "Path to the folder containing the images.",
903
- "metadata_type": "Type of metadata to expect in the images. This will determine how the images are processed. If 'custom' is selected, you can provide a custom regex pattern to extract metadata from the image names",
904
- "custom_regex": "Custom regex pattern to extract metadata from the image names. This will only be used if 'custom' is selected for 'metadata_type'.",
905
- "experiment": "Name of the experiment. This will be used to name the output files.",
906
- "channels": "List of channels to use for the analysis. The first channel is 0, the second is 1, and so on. For example, [0,1,2] will use channels 0, 1, and 2.",
907
- "magnification": "At what magnification the images were taken. This will be used to determine the size of the objects in the images.",
908
- "nucleus_channel": "The channel to use for the nucleus. If None, the nucleus will not be segmented.",
909
- "nucleus_background": "The background intensity for the nucleus channel. This will be used to remove background noise.",
910
- "nucleus_Signal_to_noise": "The signal-to-noise ratio for the nucleus channel. This will be used to determine the range of intensities to normalize images to for nucleus segmentation.",
911
- "nucleus_CP_prob": "The cellpose probability threshold for the nucleus channel. This will be used to segment the nucleus.",
912
- "cell_channel": "The channel to use for the cell. If None, the cell will not be segmented.",
913
- "cell_background": "The background intensity for the cell channel. This will be used to remove background noise.",
914
- "cell_Signal_to_noise": "The signal-to-noise ratio for the cell channel. This will be used to determine the range of intensities to normalize images to for cell segmentation.",
915
- "cell_CP_prob": "The cellpose probability threshold for the cell channel. This will be used to segment the cell.",
916
- "pathogen_channel": "The channel to use for the pathogen. If None, the pathogen will not be segmented.",
917
- "pathogen_background": "The background intensity for the pathogen channel. This will be used to remove background noise.",
918
- "pathogen_Signal_to_noise": "The signal-to-noise ratio for the pathogen channel. This will be used to determine the range of intensities to normalize images to for pathogen segmentation.",
919
- "pathogen_CP_prob": "The cellpose probability threshold for the pathogen channel. This will be used to segment the pathogen.",
920
- "preprocess": "Whether to preprocess the images before segmentation. This includes background removal and normalization. Set to False only if this step has already been done.",
921
- "masks": "Whether to generate masks for the segmented objects. If True, masks will be generated for the nucleus, cell, and pathogen.",
922
- "examples_to_plot": "The number of images to plot for each segmented object. This will be used to visually inspect the segmentation results and normalization .",
923
- "randomize": "Whether to randomize the order of the images before processing. Recommended to avoid bias in the segmentation.",
924
- "batch_size": "The batch size to use for processing the images. This will determine how many images are processed at once. Images are normalized and segmented in batches. Lower if application runs out of RAM or VRAM.",
925
- "timelapse": "Whether to process the images as a timelapse.",
926
- "timelapse_displacement": "The displacement between frames in the timelapse. This will be used to align the frames before processing.",
927
- "timelapse_memory": "The number of frames to in tandem objects must be present in to be considered the same object in the timelapse.",
928
- "timelapse_frame_limits": "The frame limits to use for the timelapse. This will determine which frames are processed. For example, [5,20] will process frames 5 to 20.",
929
- "timelapse_remove_transient": "Whether to remove transient objects in the timelapse. Transient objects are present in fewer than all frames.",
930
- "timelapse_mode": "The mode to use for processing the timelapse. 'trackpy' uses the trackpy library for tracking objects, while 'btrack' uses the btrack library.",
931
- "timelapse_objects": "The objects to track in the timelapse (cell, nucleus or pathogen). This will determine which objects are tracked over time. If None, all objects will be tracked.",
932
- "fps": "Frames per second of the automatically generated timelapse movies.",
933
- "remove_background": "Whether to remove background noise from the images. This will help improve the quality of the segmentation.",
934
- "lower_quantile": "The lower quantile to use for normalizing the images. This will be used to determine the range of intensities to normalize images to.",
935
- "merge_pathogens": "Whether to merge pathogen objects that share more than 75% of their perimiter.",
936
- "normalize_plots": "Whether to normalize the plots.",
937
- "all_to_mip": "Whether to convert all images to maximum intensity projections before processing.",
938
- "pick_slice": "Whether to pick a single slice from the z-stack images. If False, the maximum intensity projection will be used.",
939
- "skip_mode": "The mode to use for skipping images. This will determine how to handle images that cannot be processed.",
940
- "save": "Whether to save the results to disk.",
941
- "plot": "Whether to plot the results.",
942
- "workers": "The number of workers to use for processing the images. This will determine how many images are processed in parallel. Increase to speed up processing.",
943
- "verbose": "Whether to print verbose output during processing.",
944
- "input_folder": "Path to the folder containing the images.",
945
- "cell_mask_dim": "The dimension of the array the cell mask is saved in.",
946
- "cell_min_size": "The minimum size of cell objects in pixels2.",
947
- "cytoplasm_min_size": "The minimum size of cytoplasm objects in pixels2.",
948
- "nucleus_mask_dim": "The dimension of the array the nucleus mask is saved in.",
949
- "nucleus_min_size": "The minimum size of nucleus objects in pixels2.",
950
- "pathogen_mask_dim": "The dimension of the array the pathogen mask is saved in.",
951
- "pathogen_min_size": "The minimum size of pathogen objects in pixels2.",
952
- "save_png": "Whether to save the segmented objects as PNG images.",
953
- "crop_mode": "The mode to use for cropping the images. This will determine which objects are cropped from the images (cell, nucleus, pathogen, cytoplasm).",
954
- "use_bounding_box": "Whether to use the bounding box of the objects for cropping. If False, only the object itself will be cropped.",
955
- "png_size": "The size of the PNG images to save. This will determine the size of the saved images.",
956
- "normalize": "The percentiles to use for normalizing the images. This will be used to determine the range of intensities to normalize images to., if None, no normalization is done.",
957
- "png_dims": "The dimensions of the PNG images to save. This will determine the dimensions of the saved images. Maximum of 3 dimensions e.g. [1,2,3].",
958
- "normalize_by": "Whether to normalize the images by field of view (fov) or by PNG image (png).",
959
- "save_measurements": "Whether to save the measurements to disk.",
960
- "representative_images": "Whether to save representative images of the segmented objects (Not working yet).",
961
- "plot": "Whether to plot results.",
962
- "plot_filtration": "Whether to plot the filtration steps.",
963
- "include_uninfected": "Whether to include uninfected cells in the analysis.",
964
- "dialate_pngs": "Whether to dialate the PNG images before saving.",
965
- "dialate_png_ratios": "The ratios to use for dialating the PNG images. This will determine the amount of dialation applied to the images before cropping.",
966
- "timelapse_objects": "The objects to track in the timelapse (cell, nucleus or pathogen). This will determine which objects are tracked over time. If None, all objects will be tracked.",
967
- "max_workers": "The number of workers to use for processing the images. This will determine how many images are processed in parallel. Increase to speed up processing.",
968
- "cells: ": "The cell types to include in the analysis.",
969
- "cell_loc": "The locations of the cell types in the images.",
970
- "pathogens": "The pathogen types to include in the analysis.",
971
- "pathogen_loc": "The locations of the pathogen types in the images.",
972
- "treatments": "The treatments to include in the analysis.",
973
- "treatment_loc": "The locations of the treatments in the images.",
974
- "channel_of_interest": "The channel of interest to use for the analysis.",
975
- "compartments": "The compartments to measure in the images.",
976
- "measurement": "The measurement to use for the analysis.",
977
- "nr_imgs": "The number of images to plot.",
978
- "um_per_pixel": "The micrometers per pixel for the images.",
979
- }
980
-
981
- for key, (var_type, options, default_value) in variables.items():
982
- label, widget, var = create_input_field(scrollable_frame.scrollable_frame, key, row, var_type, options, default_value)
983
- vars_dict[key] = (label, widget, var) # Store the label, widget, and variable
984
-
985
- # Add tooltip to the label if it exists in the tooltips dictionary
986
- if key in tooltips:
987
- ToolTip(label, tooltips[key])
988
-
989
- row += 1
990
- return vars_dict
991
-
992
- class TextRedirector(object):
993
- def __init__(self, widget, queue):
994
- self.widget = widget
995
- self.queue = queue
996
-
997
- def write(self, str):
998
- self.queue.put(str)
999
-
1000
- def flush(self):
1001
- pass
1002
-
1003
- def create_dark_mode(root, style, console_output):
1004
- dark_bg = 'black'
1005
- light_text = 'white'
1006
- dark_text = 'black'
1007
- input_bg = '#555555' # Slightly lighter background for input fields
1008
-
1009
- # Configure ttkcompartments('TFrame', background=dark_bg)
1010
- style.configure('TLabel', background=dark_bg, foreground=light_text)
1011
- style.configure('TEntry', fieldbackground=input_bg, foreground=dark_text, background=dark_bg)
1012
- style.configure('TButton', background=dark_bg, foreground=dark_text)
1013
- style.map('TButton', background=[('active', dark_bg)], foreground=[('active', dark_text)])
1014
- style.configure('Dark.TCheckbutton', background=dark_bg, foreground=dark_text)
1015
- style.map('Dark.TCheckbutton', background=[('active', dark_bg)], foreground=[('active', dark_text)])
1016
- style.configure('TCombobox', fieldbackground=input_bg, foreground=dark_text, background=dark_bg, selectbackground=input_bg, selectforeground=dark_text)
1017
- style.map('TCombobox', fieldbackground=[('readonly', input_bg)], selectbackground=[('readonly', input_bg)], foreground=[('readonly', dark_text)])
1018
-
1019
- if console_output != None:
1020
- console_output.config(bg=dark_bg, fg=light_text, insertbackground=light_text) #, font=("Helvetica", 12)
1021
- root.configure(bg=dark_bg)
1022
532
 
1023
- ##@log_function_call
1024
533
  def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
1025
534
  try:
1026
535
  ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
@@ -1076,6 +585,14 @@ def clear_canvas(canvas):
1076
585
 
1077
586
  # Redraw the now empty canvas without changing its size
1078
587
  canvas.draw_idle()
588
+
589
+ def my_show():
590
+ """
591
+ Replacement for plt.show() that queues figures instead of displaying them.
592
+ """
593
+ fig = plt.gcf()
594
+ fig_queue.put(fig)
595
+ plt.close(fig)
1079
596
 
1080
597
  def measure_crop_wrapper(settings, q, fig_queue):
1081
598
  """
@@ -1087,14 +604,6 @@ def measure_crop_wrapper(settings, q, fig_queue):
1087
604
  - fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
1088
605
  """
1089
606
 
1090
- def my_show():
1091
- """
1092
- Replacement for plt.show() that queues figures instead of displaying them.
1093
- """
1094
- fig = plt.gcf()
1095
- fig_queue.put(fig) # Queue the figure for GUI display
1096
- plt.close(fig) # Prevent the figure from being shown by plt.show()
1097
-
1098
607
  # Temporarily override plt.show
1099
608
  original_show = plt.show
1100
609
  plt.show = my_show
@@ -1104,12 +613,11 @@ def measure_crop_wrapper(settings, q, fig_queue):
1104
613
  spacr.measure.measure_crop(settings=settings)
1105
614
  except Exception as e:
1106
615
  errorMessage = f"Error during processing: {e}"
1107
- q.put(errorMessage) # Send the error message to the GUI via the queue
616
+ q.put(errorMessage)
1108
617
  traceback.print_exc()
1109
618
  finally:
1110
- plt.show = original_show # Restore the original plt.show function
619
+ plt.show = original_show
1111
620
 
1112
- #@log_function_call
1113
621
  def preprocess_generate_masks_wrapper(settings, q, fig_queue):
1114
622
  """
1115
623
  Wraps the measure_crop function to integrate with GUI processes.
@@ -1120,26 +628,48 @@ def preprocess_generate_masks_wrapper(settings, q, fig_queue):
1120
628
  - fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
1121
629
  """
1122
630
 
1123
- def my_show():
1124
- """
1125
- Replacement for plt.show() that queues figures instead of displaying them.
1126
- """
1127
- fig = plt.gcf()
1128
- fig_queue.put(fig) # Queue the figure for GUI display
1129
- plt.close(fig) # Prevent the figure from being shown by plt.show()
631
+ # Temporarily override plt.show
632
+ original_show = plt.show
633
+ plt.show = my_show
634
+
635
+ try:
636
+ spacr.core.preprocess_generate_masks(src=settings['src'], settings=settings)
637
+ except Exception as e:
638
+ errorMessage = f"Error during processing: {e}"
639
+ q.put(errorMessage)
640
+ traceback.print_exc()
641
+ finally:
642
+ plt.show = original_show
643
+
644
+ def sequencing_wrapper(settings, q, fig_queue):
1130
645
 
1131
646
  # Temporarily override plt.show
1132
647
  original_show = plt.show
1133
648
  plt.show = my_show
1134
649
 
1135
650
  try:
1136
- spacr.core.preprocess_generate_masks(settings['src'], settings=settings)
651
+ spacr.sequencing.analyze_reads(settings=settings)
1137
652
  except Exception as e:
1138
653
  errorMessage = f"Error during processing: {e}"
1139
- q.put(errorMessage) # Send the error message to the GUI via the queue
654
+ q.put(errorMessage)
1140
655
  traceback.print_exc()
1141
656
  finally:
1142
- plt.show = original_show # Restore the original plt.show function
657
+ plt.show = original_show
658
+
659
+ def umap_wrapper(settings, q, fig_queue):
660
+
661
+ # Temporarily override plt.show
662
+ original_show = plt.show
663
+ plt.show = my_show
664
+
665
+ try:
666
+ spacr.core.generate_image_umap(settings=settings)
667
+ except Exception as e:
668
+ errorMessage = f"Error during processing: {e}"
669
+ q.put(errorMessage)
670
+ traceback.print_exc()
671
+ finally:
672
+ plt.show = original_show
1143
673
 
1144
674
  def train_test_model_wrapper(settings, q, fig_queue):
1145
675
  """
@@ -1202,4 +732,481 @@ def run_multiple_simulations_wrapper(settings, q, fig_queue):
1202
732
  q.put(errorMessage) # Send the error message to the GUI via the queue
1203
733
  traceback.print_exc()
1204
734
  finally:
1205
- plt.show = original_show # Restore the original plt.show function
735
+ plt.show = original_show # Restore the original plt.show function
736
+
737
+ def convert_settings_dict_for_gui(settings):
738
+ variables = {}
739
+ special_cases = {
740
+ 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
741
+ 'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
742
+ 'cell_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
743
+ 'nucleus_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
744
+ 'pathogen_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
745
+ #'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
746
+ 'magnification': ('combo', [20, 40, 60], 20),
747
+ 'nucleus_channel': ('combo', [0, 1, 2, 3, None], None),
748
+ 'cell_channel': ('combo', [0, 1, 2, 3, None], None),
749
+ 'pathogen_channel': ('combo', [0, 1, 2, 3, None], None),
750
+ 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
751
+ 'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
752
+ 'model_type': ('combo', ['resnet50', 'other_model'], 'resnet50'),
753
+ 'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
754
+ 'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
755
+ 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
756
+ 'normalize_by': ('combo', ['fov', 'png'], 'png'),
757
+ }
758
+
759
+ for key, value in settings.items():
760
+ if key in special_cases:
761
+ variables[key] = special_cases[key]
762
+ elif isinstance(value, bool):
763
+ variables[key] = ('check', None, value)
764
+ elif isinstance(value, int) or isinstance(value, float):
765
+ variables[key] = ('entry', None, value)
766
+ elif isinstance(value, str):
767
+ variables[key] = ('entry', None, value)
768
+ elif value is None:
769
+ variables[key] = ('entry', None, value)
770
+ elif isinstance(value, list):
771
+ variables[key] = ('entry', None, str(value))
772
+ else:
773
+ variables[key] = ('entry', None, str(value))
774
+ return variables
775
+
776
+ def setup_frame(parent_frame):
777
+ style = ttk.Style(parent_frame)
778
+ set_dark_style(style)
779
+ set_default_font(parent_frame, font_name="Helvetica", size=8)
780
+ parent_frame.configure(bg='black')
781
+ parent_frame.grid_rowconfigure(0, weight=1)
782
+ parent_frame.grid_columnconfigure(0, weight=1)
783
+ vertical_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL, bg='black')
784
+ vertical_container.grid(row=0, column=0, sticky=tk.NSEW)
785
+ horizontal_container = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL, bg='black')
786
+ vertical_container.add(horizontal_container, stretch="always")
787
+ horizontal_container.grid_columnconfigure(0, weight=1)
788
+ horizontal_container.grid_columnconfigure(1, weight=1)
789
+ settings_frame = tk.Frame(horizontal_container, bg='black')
790
+ settings_frame.grid_rowconfigure(0, weight=0)
791
+ settings_frame.grid_rowconfigure(1, weight=1)
792
+ settings_frame.grid_columnconfigure(0, weight=1)
793
+ horizontal_container.add(settings_frame, stretch="always", sticky="nsew")
794
+ return parent_frame, vertical_container, horizontal_container
795
+
796
+ def setup_settings_panel(vertical_container, settings_type='mask', frame_height=500, frame_width=1000):
797
+ global vars_dict, scrollable_frame
798
+ from .settings import set_default_settings_preprocess_generate_masks, get_measure_crop_settings, set_default_train_test_model, get_analyze_reads_default_settings, set_default_umap_image_settings, generate_fields
799
+
800
+ print("Setting up settings panel")
801
+
802
+ # Create settings frame
803
+ settings_frame = tk.Frame(vertical_container, bg='black', height=frame_height, width=frame_width)
804
+ vertical_container.add(settings_frame, stretch="always")
805
+
806
+ # Add settings label
807
+ settings_label = spacrLabel(settings_frame, text="Settings", background="black", foreground="white", anchor='center', justify='center', align="center")
808
+ settings_label.grid(row=0, column=0, pady=10, padx=10)
809
+
810
+ # Create a spacrFrame inside the settings_frame
811
+ scrollable_frame = spacrFrame(settings_frame, bg='black', width=frame_width)
812
+ scrollable_frame.grid(row=1, column=0, sticky="nsew")
813
+
814
+ # Configure the weights for resizing
815
+ settings_frame.grid_rowconfigure(1, weight=1)
816
+ settings_frame.grid_columnconfigure(0, weight=1)
817
+
818
+ # Load settings based on type
819
+ if settings_type == 'mask':
820
+ settings = set_default_settings_preprocess_generate_masks(src='path', settings={})
821
+ elif settings_type == 'measure':
822
+ settings = get_measure_crop_settings(settings={})
823
+ elif settings_type == 'classify':
824
+ settings = set_default_train_test_model(settings={})
825
+ elif settings_type == 'sequencing':
826
+ settings = get_analyze_reads_default_settings(settings={})
827
+ elif settings_type == 'umap':
828
+ settings = set_default_umap_image_settings(settings={})
829
+ else:
830
+ raise ValueError(f"Invalid settings type: {settings_type}")
831
+
832
+ # Generate fields for settings
833
+ variables = convert_settings_dict_for_gui(settings)
834
+ vars_dict = generate_fields(variables, scrollable_frame)
835
+ print("Settings panel setup complete")
836
+ return scrollable_frame, vars_dict
837
+
838
+ def setup_plot_section(vertical_container):
839
+ global canvas, canvas_widget
840
+ plot_frame = tk.PanedWindow(vertical_container, orient=tk.VERTICAL)
841
+ vertical_container.add(plot_frame, stretch="always")
842
+ figure = Figure(figsize=(30, 4), dpi=100, facecolor='black')
843
+ plot = figure.add_subplot(111)
844
+ plot.plot([], []) # This creates an empty plot.
845
+ plot.axis('off')
846
+ canvas = FigureCanvasTkAgg(figure, master=plot_frame)
847
+ canvas.get_tk_widget().configure(cursor='arrow', background='black', highlightthickness=0)
848
+ canvas_widget = canvas.get_tk_widget()
849
+ plot_frame.add(canvas_widget, stretch="always")
850
+ canvas.draw()
851
+ canvas.figure = figure
852
+ return canvas, canvas_widget
853
+
854
+ def setup_console(vertical_container):
855
+ global console_output
856
+ print("Setting up console frame")
857
+ console_frame = tk.Frame(vertical_container, bg='black')
858
+ vertical_container.add(console_frame, stretch="always")
859
+ console_label = spacrLabel(console_frame, text="Console", background="black", foreground="white", anchor='center', justify='center', align="center")
860
+ console_label.grid(row=0, column=0, pady=10, padx=10)
861
+ console_output = scrolledtext.ScrolledText(console_frame, height=10, bg='black', fg='white', insertbackground='white')
862
+ console_output.grid(row=1, column=0, sticky="nsew")
863
+ console_frame.grid_rowconfigure(1, weight=1)
864
+ console_frame.grid_columnconfigure(0, weight=1)
865
+ print("Console setup complete")
866
+ return console_output
867
+
868
+ def setup_progress_frame(vertical_container):
869
+ global progress_output
870
+ progress_frame = tk.Frame(vertical_container, bg='black')
871
+ vertical_container.add(progress_frame, stretch="always")
872
+ label_frame = tk.Frame(progress_frame, bg='black')
873
+ label_frame.grid(row=0, column=0, sticky="ew", pady=(5, 0), padx=10)
874
+ progress_label = spacrLabel(label_frame, text="Processing: 0%", background="black", foreground="white", font=('Helvetica', 12), anchor='w', justify='left', align="left")
875
+ progress_label.grid(row=0, column=0, sticky="w")
876
+ progress_output = scrolledtext.ScrolledText(progress_frame, height=10, bg='black', fg='white', insertbackground='white')
877
+ progress_output.grid(row=1, column=0, sticky="nsew")
878
+ progress_frame.grid_rowconfigure(1, weight=1)
879
+ progress_frame.grid_columnconfigure(0, weight=1)
880
+ print("Progress frame setup complete")
881
+ return progress_output
882
+
883
+ def download_hug_dataset():
884
+ global vars_dict, q
885
+ repo_id = "einarolafsson/toxo_mito"
886
+ subfolder = "plate1"
887
+ local_dir = os.path.join(os.path.expanduser("~"), "datasets") # Set to the home directory
888
+ try:
889
+ local_path = download_dataset(repo_id, subfolder, local_dir)
890
+ if 'src' in vars_dict:
891
+ vars_dict['src'][2].set(local_path) # Assuming vars_dict['src'] is a tuple and the 3rd element is a StringVar
892
+ q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
893
+ q.put(f"Dataset downloaded to: {local_path}\n")
894
+ except Exception as e:
895
+ q.put(f"Failed to download dataset: {e}\n")
896
+
897
+ def download_dataset(repo_id, subfolder, local_dir=None, retries=5, delay=5):
898
+ global q
899
+ """
900
+ Downloads a dataset from Hugging Face and returns the local path.
901
+
902
+ Args:
903
+ repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito').
904
+ subfolder (str): The subfolder path within the repository (e.g., 'plate1').
905
+ local_dir (str): The local directory where the dataset will be saved. Defaults to the user's home directory.
906
+ retries (int): Number of retry attempts in case of failure.
907
+ delay (int): Delay in seconds between retries.
908
+
909
+ Returns:
910
+ str: The local path to the downloaded dataset.
911
+ """
912
+ if local_dir is None:
913
+ local_dir = os.path.join(os.path.expanduser("~"), "datasets")
914
+
915
+ local_subfolder_dir = os.path.join(local_dir, subfolder)
916
+ if not os.path.exists(local_subfolder_dir):
917
+ os.makedirs(local_subfolder_dir)
918
+ elif len(os.listdir(local_subfolder_dir)) == 40:
919
+ q.put(f"Dataset already downloaded to: {local_subfolder_dir}")
920
+ return local_subfolder_dir
921
+
922
+ attempt = 0
923
+ while attempt < retries:
924
+ try:
925
+ files = list_repo_files(repo_id, repo_type="dataset")
926
+ subfolder_files = [file for file in files if file.startswith(subfolder)]
927
+
928
+ for file_name in subfolder_files:
929
+ for attempt in range(retries):
930
+ try:
931
+ url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
932
+ response = requests.get(url, stream=True)
933
+ response.raise_for_status()
934
+
935
+ local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
936
+ with open(local_file_path, 'wb') as file:
937
+ for chunk in response.iter_content(chunk_size=8192):
938
+ file.write(chunk)
939
+ q.put(f"Downloaded file: {file_name}")
940
+ break
941
+ except (requests.HTTPError, requests.Timeout) as e:
942
+ q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
943
+ time.sleep(delay)
944
+ else:
945
+ raise Exception(f"Failed to download {file_name} after multiple attempts.")
946
+
947
+ return local_subfolder_dir
948
+
949
+ except (requests.HTTPError, requests.Timeout) as e:
950
+ q.put(f"Error downloading dataset: {e}. Retrying in {delay} seconds...")
951
+ attempt += 1
952
+ time.sleep(delay)
953
+
954
+ raise Exception("Failed to download dataset after multiple attempts.")
955
+
956
+ def setup_button_section(horizontal_container, settings_type='mask', settings_row=5, run=True, abort=True, download=True, import_btn=True):
957
+ global button_frame, run_button, abort_button, download_dataset_button, import_button, q, fig_queue, vars_dict
958
+
959
+ button_frame = tk.Frame(horizontal_container, bg='black')
960
+ horizontal_container.add(button_frame, stretch="always", sticky="nsew")
961
+ button_frame.grid_rowconfigure(0, weight=0)
962
+ button_frame.grid_rowconfigure(1, weight=1)
963
+ button_frame.grid_columnconfigure(0, weight=1)
964
+
965
+ categories_label = spacrLabel(button_frame, text="Categories", background="black", foreground="white", font=('Helvetica', 12), anchor='center', justify='center', align="center") # Increase font size
966
+ categories_label.grid(row=0, column=0, pady=10, padx=10)
967
+
968
+ button_scrollable_frame = spacrFrame(button_frame, bg='black')
969
+ button_scrollable_frame.grid(row=1, column=0, sticky="nsew")
970
+
971
+ btn_col = 0
972
+ btn_row = 1
973
+
974
+ if run:
975
+ run_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Run", command=lambda: start_process(q, fig_queue, settings_type), font=('Helvetica', 12))
976
+ run_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
977
+ btn_row += 1
978
+
979
+ if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap']:
980
+ abort_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort, font=('Helvetica', 12))
981
+ abort_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
982
+ btn_row += 1
983
+
984
+ if download and settings_type in ['mask']:
985
+ download_dataset_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Download", command=download_hug_dataset, font=('Helvetica', 12))
986
+ download_dataset_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
987
+ btn_row += 1
988
+
989
+ if import_btn:
990
+ import_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Import", command=lambda: import_settings(settings_row, settings_type), font=('Helvetica', 12))
991
+ import_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
992
+
993
+ # Call toggle_settings after vars_dict is initialized
994
+ if vars_dict is not None:
995
+ toggle_settings(button_scrollable_frame)
996
+ return button_scrollable_frame
997
+
998
+ def toggle_test_mode():
999
+ global vars_dict, test_mode_button
1000
+ current_state = vars_dict['test_mode'][2].get()
1001
+ new_state = not current_state
1002
+ vars_dict['test_mode'][2].set(new_state)
1003
+ if new_state:
1004
+ test_mode_button.config(bg="blue")
1005
+ else:
1006
+ test_mode_button.config(bg="gray")
1007
+
1008
+ def toggle_settings(button_scrollable_frame):
1009
+ global vars_dict
1010
+ from .settings import categories
1011
+
1012
+ if vars_dict is None:
1013
+ raise ValueError("vars_dict is not initialized.")
1014
+
1015
+ def toggle_category(settings, var):
1016
+ for setting in settings:
1017
+ if setting in vars_dict:
1018
+ label, widget, _ = vars_dict[setting]
1019
+ if var.get() == 0:
1020
+ label.grid_remove()
1021
+ widget.grid_remove()
1022
+ else:
1023
+ label.grid()
1024
+ widget.grid()
1025
+
1026
+ row = 1
1027
+ col = 2
1028
+ category_idx = 0
1029
+
1030
+ for category, settings in categories.items():
1031
+ if any(setting in vars_dict for setting in settings):
1032
+ category_var = tk.IntVar(value=0)
1033
+ vars_dict[category] = (None, None, category_var)
1034
+ toggle = spacrCheckbutton(
1035
+ button_scrollable_frame.scrollable_frame,
1036
+ text=category,
1037
+ variable=category_var,
1038
+ command=lambda cat=settings, var=category_var: toggle_category(cat, var)
1039
+ )
1040
+ # Directly set the style
1041
+ style = ttk.Style()
1042
+ font_style = tkFont.Font(family="Helvetica", size=12, weight="bold")
1043
+ style.configure('Spacr.TCheckbutton', font=font_style, background='black', foreground='#ffffff', indicatoron=False, relief='flat')
1044
+ style.map('Spacr.TCheckbutton', background=[('selected', 'black'), ('active', 'black')], foreground=[('selected', '#ffffff'), ('active', '#ffffff')])
1045
+ toggle.configure(style='Spacr.TCheckbutton')
1046
+ toggle.grid(row=row, column=col, sticky="w", pady=2, padx=2)
1047
+ col += 1
1048
+ category_idx += 1
1049
+
1050
+ if category_idx % 4 == 0:
1051
+ row += 1
1052
+ col = 2
1053
+
1054
+ for settings in categories.values():
1055
+ for setting in settings:
1056
+ if setting in vars_dict:
1057
+ label, widget, _ = vars_dict[setting]
1058
+ label.grid_remove()
1059
+ widget.grid_remove()
1060
+
1061
+ def process_fig_queue():
1062
+ global canvas, fig_queue, canvas_widget, parent_frame
1063
+ try:
1064
+ while not fig_queue.empty():
1065
+ clear_canvas(canvas)
1066
+ fig = fig_queue.get_nowait()
1067
+ for ax in fig.get_axes():
1068
+ ax.set_xticks([]) # Remove x-axis ticks
1069
+ ax.set_yticks([]) # Remove y-axis ticks
1070
+ ax.xaxis.set_visible(False) # Hide the x-axis
1071
+ ax.yaxis.set_visible(False) # Hide the y-axis
1072
+ fig.tight_layout()
1073
+ fig.set_facecolor('black')
1074
+ canvas.figure = fig
1075
+ fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
1076
+ fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
1077
+ canvas.draw_idle()
1078
+ except Exception as e:
1079
+ traceback.print_exc()
1080
+ finally:
1081
+ after_id = canvas_widget.after(100, process_fig_queue)
1082
+ parent_frame.after_tasks.append(after_id)
1083
+
1084
+ def process_console_queue():
1085
+ global q, console_output, parent_frame
1086
+ while not q.empty():
1087
+ message = q.get_nowait()
1088
+ console_output.insert(tk.END, message)
1089
+ console_output.see(tk.END)
1090
+ after_id = console_output.after(100, process_console_queue)
1091
+ parent_frame.after_tasks.append(after_id)
1092
+
1093
+ def run_mask_gui(settings, q, fig_queue, stop_requested):
1094
+ process_stdout_stderr(q)
1095
+ try:
1096
+ preprocess_generate_masks_wrapper(settings, q, fig_queue)
1097
+ except Exception as e:
1098
+ q.put(f"Error during processing: {e}")
1099
+ traceback.print_exc()
1100
+ finally:
1101
+ stop_requested.value = 1
1102
+
1103
+ def run_sequencing_gui(settings, q, fig_queue, stop_requested):
1104
+ process_stdout_stderr(q)
1105
+ try:
1106
+ sequencing_wrapper(settings, q, fig_queue)
1107
+ except Exception as e:
1108
+ q.put(f"Error during processing: {e}")
1109
+ traceback.print_exc()
1110
+ finally:
1111
+ stop_requested.value = 1
1112
+
1113
+ def run_umap_gui(settings, q, fig_queue, stop_requested):
1114
+ process_stdout_stderr(q)
1115
+ try:
1116
+ umap_wrapper(settings, q, fig_queue)
1117
+ except Exception as e:
1118
+ q.put(f"Error during processing: {e}")
1119
+ traceback.print_exc()
1120
+ finally:
1121
+ stop_requested.value = 1
1122
+
1123
+ def run_measure_gui(settings, q, fig_queue, stop_requested):
1124
+ process_stdout_stderr(q)
1125
+ try:
1126
+ settings['input_folder'] = settings['src']
1127
+ measure_crop_wrapper(settings=settings, q=q, fig_queue=fig_queue)
1128
+ except Exception as e:
1129
+ q.put(f"Error during processing: {e}")
1130
+ traceback.print_exc()
1131
+ finally:
1132
+ stop_requested.value = 1
1133
+
1134
+ def run_classify_gui(settings, q, fig_queue, stop_requested):
1135
+ process_stdout_stderr(q)
1136
+ try:
1137
+ train_test_model_wrapper(settings['src'], settings)
1138
+ except Exception as e:
1139
+ q.put(f"Error during processing: {e}")
1140
+ traceback.print_exc()
1141
+ finally:
1142
+ stop_requested.value = 1
1143
+
1144
+ def set_globals(q_var, console_output_var, parent_frame_var, vars_dict_var, canvas_var, canvas_widget_var, scrollable_frame_var, progress_label_var, fig_queue_var):
1145
+ global q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, progress_label, fig_queue
1146
+ q = q_var
1147
+ console_output = console_output_var
1148
+ parent_frame = parent_frame_var
1149
+ vars_dict = vars_dict_var
1150
+ canvas = canvas_var
1151
+ canvas_widget = canvas_widget_var
1152
+ scrollable_frame = scrollable_frame_var
1153
+ progress_label = progress_label_var
1154
+ fig_queue = fig_queue_var
1155
+
1156
+ def initiate_root(parent, settings_type='mask'):
1157
+ global q, fig_queue, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, progress_label, button_scrollable_frame
1158
+ print("Initializing root with settings_type:", settings_type)
1159
+ parent_frame = parent
1160
+
1161
+ if not hasattr(parent_frame, 'after_tasks'):
1162
+ parent_frame.after_tasks = []
1163
+
1164
+ for widget in parent_frame.winfo_children():
1165
+ if widget.winfo_exists():
1166
+ try:
1167
+ widget.destroy()
1168
+ except tk.TclError as e:
1169
+ print(f"Error destroying widget: {e}")
1170
+
1171
+ q = Queue()
1172
+ fig_queue = Queue()
1173
+ parent_frame, vertical_container, horizontal_container = setup_frame(parent_frame)
1174
+ scrollable_frame, vars_dict = setup_settings_panel(horizontal_container, settings_type) # Adjust height and width as needed
1175
+ button_scrollable_frame = setup_button_section(horizontal_container, settings_type)
1176
+ canvas, canvas_widget = setup_plot_section(vertical_container)
1177
+ console_output = setup_console(vertical_container)
1178
+
1179
+ if settings_type in ['mask', 'measure', 'classify', 'sequencing']:
1180
+ progress_output = setup_progress_frame(vertical_container)
1181
+ else:
1182
+ progress_output = None
1183
+
1184
+ set_globals(q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, progress_label, fig_queue)
1185
+ process_console_queue()
1186
+ process_fig_queue()
1187
+ after_id = parent_frame.after(100, lambda: main_thread_update_function(parent_frame, q, fig_queue, canvas_widget, progress_label))
1188
+ parent_frame.after_tasks.append(after_id)
1189
+ print("Root initialization complete")
1190
+ return parent_frame, vars_dict
1191
+
1192
+ def cancel_after_tasks(frame):
1193
+ if hasattr(frame, 'after_tasks'):
1194
+ for task in frame.after_tasks:
1195
+ frame.after_cancel(task)
1196
+ frame.after_tasks.clear()
1197
+
1198
+ def start_gui_app(settings_type='mask'):
1199
+ global q, fig_queue, parent_frame, scrollable_frame, vars_dict, canvas, canvas_widget, progress_label
1200
+ root = tk.Tk()
1201
+ width = root.winfo_screenwidth()
1202
+ height = root.winfo_screenheight()
1203
+ root.geometry(f"{width}x{height}")
1204
+ root.title(f"SpaCr: {settings_type.capitalize()}")
1205
+ root.content_frame = tk.Frame(root)
1206
+ print("Starting GUI app with settings_type:", settings_type)
1207
+ initiate_root(root.content_frame, settings_type)
1208
+ create_menu_bar(root)
1209
+ root.mainloop()
1210
+
1211
+
1212
+