spacr 0.1.1__py3-none-any.whl → 0.1.6__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,56 +1,34 @@
1
- import os, spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib, re, csv, requests
2
- import matplotlib.pyplot as plt
3
- matplotlib.use('Agg')
4
- import numpy as np
1
+ import io, sys, ast, ctypes, re, csv, ast
5
2
  import tkinter as tk
6
- from tkinter import ttk, messagebox
7
- import tkinter.font as tkFont
8
- from torchvision import models
9
- #from ttf_opensans import opensans
3
+ from tkinter import ttk
10
4
 
11
- from tkinter import font as tkFont
12
-
13
-
14
- from .logger import log_function_call
5
+ from . gui_core import initiate_root
6
+ from .gui_elements import spacrLabel, spacrCheckbutton, set_dark_style
15
7
 
16
8
  try:
17
9
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
18
10
  except AttributeError:
19
11
  pass
20
12
 
21
- class ToolTip:
22
- def __init__(self, widget, text):
23
- self.widget = widget
24
- self.text = text
25
- self.tooltip_window = None
26
- widget.bind("<Enter>", self.show_tooltip)
27
- widget.bind("<Leave>", self.hide_tooltip)
28
-
29
- def show_tooltip(self, event):
30
- x = event.x_root + 20
31
- y = event.y_root + 10
32
- self.tooltip_window = tk.Toplevel(self.widget)
33
- self.tooltip_window.wm_overrideredirect(True)
34
- self.tooltip_window.wm_geometry(f"+{x}+{y}")
35
- label = tk.Label(self.tooltip_window, text=self.text, background="yellow", relief='solid', borderwidth=1)
36
- label.pack()
13
+ def set_default_font(root, font_name="Helvetica", size=12):
14
+ default_font = (font_name, size)
15
+ root.option_add("*Font", default_font)
16
+ root.option_add("*TButton.Font", default_font)
17
+ root.option_add("*TLabel.Font", default_font)
18
+ root.option_add("*TEntry.Font", default_font)
37
19
 
38
- def hide_tooltip(self, event):
39
- if self.tooltip_window:
40
- self.tooltip_window.destroy()
41
- self.tooltip_window = None
42
-
43
- def load_app(root, app_name, app_func):
44
- # Cancel all scheduled after tasks
45
- if hasattr(root, 'after_tasks'):
46
- for task in root.after_tasks:
47
- root.after_cancel(task)
48
- root.after_tasks = []
20
+ def proceed_with_app(root, app_name, app_func):
21
+ from .app_annotate import gui_annotate
22
+ from .app_make_masks import gui_make_masks
23
+ from .gui import gui_app
49
24
 
50
25
  # Clear the current content frame
51
26
  if hasattr(root, 'content_frame'):
52
27
  for widget in root.content_frame.winfo_children():
53
- widget.destroy()
28
+ try:
29
+ widget.destroy()
30
+ except tk.TclError as e:
31
+ print(f"Error destroying widget: {e}")
54
32
  else:
55
33
  root.content_frame = tk.Frame(root)
56
34
  root.content_frame.grid(row=1, column=0, sticky="nsew")
@@ -58,658 +36,74 @@ def load_app(root, app_name, app_func):
58
36
  root.grid_columnconfigure(0, weight=1)
59
37
 
60
38
  # Initialize the new app in the content frame
61
- app_func(root.content_frame)
62
-
63
- def create_menu_bar(root):
64
- from .gui_mask_app import initiate_mask_root
65
- from .gui_measure_app import initiate_measure_root
66
- from .annotate_app import initiate_annotation_app_root
67
- from .mask_app import initiate_mask_app_root
68
- from .gui_classify_app import initiate_classify_root
69
-
70
- gui_apps = {
71
- "Mask": initiate_mask_root,
72
- "Measure": initiate_measure_root,
73
- "Annotate": initiate_annotation_app_root,
74
- "Make Masks": initiate_mask_app_root,
75
- "Classify": initiate_classify_root
76
- }
77
-
78
- def load_app_wrapper(app_name, app_func):
79
- load_app(root, app_name, app_func)
80
-
81
- # Create the menu bar
82
- menu_bar = tk.Menu(root, bg="#008080", fg="white")
83
- # Create a "SpaCr Applications" menu
84
- app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
85
- menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
86
- # Add options to the "SpaCr Applications" menu
87
- for app_name, app_func in gui_apps.items():
88
- app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app_wrapper(app_name, app_func))
89
- # Add a separator and an exit option
90
- app_menu.add_separator()
91
- app_menu.add_command(label="Exit", command=root.quit)
92
- # Configure the menu for the root window
93
- root.config(menu=menu_bar)
94
-
95
- class CustomButton(tk.Frame):
96
- def __init__(self, parent, text="", command=None, font=None, *args, **kwargs):
97
- super().__init__(parent, *args, **kwargs)
98
- self.text = text
99
- self.command = command
100
-
101
- self.canvas = tk.Canvas(self, width=150, height=50, highlightthickness=0, bg="black")
102
- self.canvas.grid(row=0, column=0)
103
-
104
- self.button_bg = self.create_rounded_rectangle(0, 0, 150, 50, radius=20, fill="#800080")
105
-
106
- # Use the passed font or default to Helvetica if not provided
107
- self.font_style = font if font else tkFont.Font(family="Helvetica", size=12, weight=tkFont.NORMAL)
108
- self.button_text = self.canvas.create_text(75, 25, text=self.text, fill="white", font=self.font_style)
109
-
110
- self.bind("<Enter>", self.on_enter)
111
- self.bind("<Leave>", self.on_leave)
112
- self.bind("<Button-1>", self.on_click)
113
- self.canvas.bind("<Enter>", self.on_enter)
114
- self.canvas.bind("<Leave>", self.on_leave)
115
- self.canvas.bind("<Button-1>", self.on_click)
116
-
117
- def on_enter(self, event=None):
118
- self.canvas.itemconfig(self.button_bg, fill="#993399")
119
-
120
- def on_leave(self, event=None):
121
- self.canvas.itemconfig(self.button_bg, fill="#800080")
122
-
123
- def on_click(self, event=None):
124
- if self.command:
125
- self.command()
126
-
127
- def create_rounded_rectangle(self, x1, y1, x2, y2, radius=20, **kwargs):
128
- points = [x1 + radius, y1,
129
- x1 + radius, y1,
130
- x2 - radius, y1,
131
- x2 - radius, y1,
132
- x2, y1,
133
- x2, y1 + radius,
134
- x2, y1 + radius,
135
- x2, y2 - radius,
136
- x2, y2 - radius,
137
- x2, y2,
138
- x2 - radius, y2,
139
- x2 - radius, y2,
140
- x1 + radius, y2,
141
- x1 + radius, y2,
142
- x1, y2,
143
- x1, y2 - radius,
144
- x1, y2 - radius,
145
- x1, y1 + radius,
146
- x1, y1 + radius,
147
- x1, y1]
148
-
149
- return self.canvas.create_polygon(points, **kwargs, smooth=True)
150
-
151
- class ToggleSwitch(ttk.Frame):
152
- def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
153
- super().__init__(parent, *args, **kwargs)
154
- self.text = text
155
- self.variable = variable if variable else tk.BooleanVar()
156
- self.command = command
157
-
158
- self.canvas = tk.Canvas(self, width=40, height=20, highlightthickness=0, bd=0, bg="black")
159
- self.canvas.grid(row=0, column=1, padx=(10, 0))
160
-
161
- # Background rounded rectangle with smaller dimensions and no outline
162
- self.switch_bg = self.create_rounded_rectangle(2, 2, 38, 18, radius=9, outline="", fill="#fff")
163
-
164
- # Switch ball with no outline
165
- self.switch = self.canvas.create_oval(4, 4, 16, 16, outline="", fill="#800080") # Purple initially
166
-
167
- self.label = ttk.Label(self, text=self.text, background="black", foreground="white")
168
- self.label.grid(row=0, column=0, padx=(0, 10))
169
-
170
- self.bind("<Button-1>", self.toggle)
171
- self.canvas.bind("<Button-1>", self.toggle)
172
- self.label.bind("<Button-1>", self.toggle)
173
-
174
- self.update_switch()
175
-
176
- def toggle(self, event=None):
177
- self.variable.set(not self.variable.get())
178
- self.animate_switch()
179
- if self.command:
180
- self.command()
181
-
182
- def update_switch(self):
183
- if self.variable.get():
184
- self.canvas.itemconfig(self.switch, fill="#008080") # Teal
185
- self.canvas.coords(self.switch, 24, 4, 36, 16) # Move switch to the right
186
- else:
187
- self.canvas.itemconfig(self.switch, fill="#800080") # Purple
188
- self.canvas.coords(self.switch, 4, 4, 16, 16) # Move switch to the left
189
-
190
- def animate_switch(self):
191
- if self.variable.get():
192
- start_x, end_x = 4, 24
193
- final_color = "#008080" # Teal
194
- else:
195
- start_x, end_x = 24, 4
196
- final_color = "#800080" # Purple
197
-
198
- self.animate_movement(start_x, end_x, final_color)
199
-
200
- def animate_movement(self, start_x, end_x, final_color):
201
- step = 1 if start_x < end_x else -1
202
- for i in range(start_x, end_x, step):
203
- self.canvas.coords(self.switch, i, 4, i + 12, 16)
204
- self.canvas.update()
205
- self.after(10) # Small delay for smooth animation
206
- self.canvas.itemconfig(self.switch, fill=final_color)
207
-
208
- def get(self):
209
- return self.variable.get()
210
-
211
- def set(self, value):
212
- self.variable.set(value)
213
- self.update_switch()
214
-
215
- def create_rounded_rectangle(self, x1, y1, x2, y2, radius=9, **kwargs): # Smaller radius for smaller switch
216
- points = [x1 + radius, y1,
217
- x1 + radius, y1,
218
- x2 - radius, y1,
219
- x2 - radius, y1,
220
- x2, y1,
221
- x2, y1 + radius,
222
- x2, y1 + radius,
223
- x2, y2 - radius,
224
- x2, y2 - radius,
225
- x2, y2,
226
- x2 - radius, y2,
227
- x2 - radius, y2,
228
- x1 + radius, y2,
229
- x1 + radius, y2,
230
- x1, y2,
231
- x1, y2 - radius,
232
- x1, y2 - radius,
233
- x1, y1 + radius,
234
- x1, y1 + radius,
235
- x1, y1]
236
-
237
- return self.canvas.create_polygon(points, **kwargs, smooth=True)
238
-
239
- def set_default_font(root, font_name="Helvetica", size=12):
240
- default_font = (font_name, size)
241
- root.option_add("*Font", default_font)
242
- root.option_add("*TButton.Font", default_font)
243
- root.option_add("*TLabel.Font", default_font)
244
- root.option_add("*TEntry.Font", default_font)
245
-
246
- def check_and_download_font_v1():
247
- font_name = "Helvetica"
248
- font_dir = "fonts"
249
- font_path = os.path.join(font_dir, "OpenSans-Regular.ttf")
250
-
251
- # Check if the font is already available
252
- available_fonts = list(tkFont.families())
253
- if font_name not in available_fonts:
254
- print(f"Font '{font_name}' not found. Downloading...")
255
- if not os.path.exists(font_dir):
256
- os.makedirs(font_dir)
257
-
258
- if not os.path.exists(font_path):
259
- url = "https://github.com/google/fonts/blob/main/apache/opensans/OpenSans-Regular.ttf?raw=true"
260
- response = requests.get(url)
261
- with open(font_path, "wb") as f:
262
- f.write(response.content)
263
-
264
- # Load the font
265
- try:
266
- tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
267
- tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
268
- tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
269
- except tk.TclError:
270
- tkFont.nametofont("TkDefaultFont").configure(family="Helvetica", size=10)
271
- tkFont.nametofont("TkTextFont").configure(family="Helvetica", size=10)
272
- tkFont.nametofont("TkHeadingFont").configure(family="Helvetica", size=12)
39
+ if app_name == "Mask":
40
+ initiate_root(root.content_frame, 'mask')
41
+ elif app_name == "Measure":
42
+ initiate_root(root.content_frame, 'measure')
43
+ elif app_name == "Classify":
44
+ initiate_root(root.content_frame, 'classify')
45
+ elif app_name == "Sequencing":
46
+ initiate_root(root.content_frame, 'sequencing')
47
+ elif app_name == "Umap":
48
+ initiate_root(root.content_frame, 'umap')
49
+ elif app_name == "Annotate":
50
+ gui_annotate()
51
+ elif app_name == "Make Masks":
52
+ gui_make_masks()
273
53
  else:
274
- tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
275
- tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
276
- tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
277
-
278
- def style_text_boxes_v1(style):
279
- check_and_download_font()
280
- font_style = tkFont.Font(family="Helvetica", size=10) # Define the Helvetica font
281
- style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='#000000', foreground='#ffffff', font=font_style)
282
- style.configure('TCombobox', fieldbackground='#000000', background='#000000', foreground='#ffffff', font=font_style)
283
- style.configure('Custom.TButton', padding='10 10 10 10', borderwidth=1, relief='solid', background='#008080', foreground='#ffffff', font=font_style)
284
- style.map('Custom.TButton',
285
- background=[('active', '#66b2b2'), ('disabled', '#004d4d'), ('!disabled', '#008080')],
286
- foreground=[('active', '#ffffff'), ('disabled', '#888888')])
287
- style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='#000000', foreground='#ffffff', font=font_style)
288
- style.configure('TCheckbutton', background='#333333', foreground='#ffffff', indicatoron=False, relief='flat', font=font_style)
289
- style.map('TCheckbutton', background=[('selected', '#555555'), ('active', '#555555')])
54
+ raise ValueError(f"Invalid app name: {app_name}")
290
55
 
291
- def check_and_download_font():
292
- font_name = "Helvetica"
293
- font_dir = "fonts"
294
- font_path = os.path.join(font_dir, "OpenSans-Regular.ttf")
295
-
296
- # Check if the font is already available
297
- available_fonts = list(tkFont.families())
298
- if font_name not in available_fonts:
299
- print(f"Font '{font_name}' not found. Downloading...")
300
- if not os.path.exists(font_dir):
301
- os.makedirs(font_dir)
302
-
303
- if not os.path.exists(font_path):
304
- url = "https://github.com/google/fonts/blob/main/apache/opensans/OpenSans-Regular.ttf?raw=true"
305
- response = requests.get(url)
306
- with open(font_path, "wb") as f:
307
- f.write(response.content)
56
+ def load_app(root, app_name, app_func):
57
+ # Cancel all scheduled after tasks
58
+ if hasattr(root, 'after_tasks'):
59
+ for task in root.after_tasks:
60
+ root.after_cancel(task)
61
+ root.after_tasks = []
308
62
 
309
- # Load the font
310
- try:
311
- tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
312
- tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
313
- tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
314
- except tk.TclError:
315
- tkFont.nametofont("TkDefaultFont").configure(family="Helvetica", size=10)
316
- tkFont.nametofont("TkTextFont").configure(family="Helvetica", size=10)
317
- tkFont.nametofont("TkHeadingFont").configure(family="Helvetica", size=12)
63
+ # Exit functionality only for the annotation app
64
+ if app_name != "Annotate" and hasattr(root, 'current_app_exit_func'):
65
+ root.next_app_func = proceed_with_app
66
+ root.next_app_args = (app_name, app_func) # Ensure correct arguments
67
+ root.current_app_exit_func()
318
68
  else:
319
- tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
320
- tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
321
- tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
322
-
323
- def style_text_boxes(style):
324
- check_and_download_font()
325
- font_style = tkFont.Font(family="Helvetica", size=10) # Define the Helvetica font
326
- style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='#000000', foreground='#ffffff', font=font_style)
327
- style.configure('TCombobox', fieldbackground='#000000', background='#000000', foreground='#ffffff', font=font_style)
328
- style.configure('Custom.TButton', padding='10 10 10 10', borderwidth=1, relief='solid', background='#008080', foreground='#ffffff', font=font_style)
329
- style.map('Custom.TButton',
330
- background=[('active', '#66b2b2'), ('disabled', '#004d4d'), ('!disabled', '#008080')],
331
- foreground=[('active', '#ffffff'), ('disabled', '#888888')])
332
- style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='#000000', foreground='#ffffff', font=font_style)
333
- style.configure('TCheckbutton', background='#333333', foreground='#ffffff', indicatoron=False, relief='flat', font=font_style)
334
- style.map('TCheckbutton', background=[('selected', '#555555'), ('active', '#555555')])
335
-
69
+ proceed_with_app(root, app_name, app_func)
336
70
 
337
- def read_settings_from_csv(csv_file_path):
338
- settings = {}
339
- with open(csv_file_path, newline='') as csvfile:
340
- reader = csv.DictReader(csvfile)
341
- for row in reader:
342
- key = row['Key']
343
- value = row['Value']
344
- settings[key] = value
345
- return settings
346
-
347
- def update_settings_from_csv(variables, csv_settings):
348
- new_settings = variables.copy() # Start with a copy of the original settings
349
- for key, value in csv_settings.items():
350
- if key in new_settings:
351
- # Get the variable type and options from the original settings
352
- var_type, options, _ = new_settings[key]
353
- # Update the default value with the CSV value, keeping the type and options unchanged
354
- new_settings[key] = (var_type, options, value)
355
- return new_settings
356
-
357
- def safe_literal_eval(value):
71
+ def parse_list(value):
358
72
  try:
359
- # First, try to evaluate as a literal
360
- return ast.literal_eval(value)
361
- except (ValueError, SyntaxError):
362
- # If it fails, return the value as it is (a string)
363
- return value
364
-
365
- def disable_interactivity(fig):
366
- if hasattr(fig.canvas, 'toolbar'):
367
- fig.canvas.toolbar.pack_forget()
368
-
369
- event_handlers = fig.canvas.callbacks.callbacks
370
- for event, handlers in list(event_handlers.items()):
371
- for handler_id in list(handlers.keys()):
372
- fig.canvas.mpl_disconnect(handler_id)
373
-
374
- class ScrollableFrame(ttk.Frame):
375
- def __init__(self, container, *args, bg='black', **kwargs):
376
- super().__init__(container, *args, **kwargs)
377
- self.configure(style='TFrame') # Ensure this uses the styled frame from dark mode
378
-
379
- canvas = tk.Canvas(self, bg=bg) # Set canvas background to match dark mode
380
- scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
381
-
382
- self.scrollable_frame = ttk.Frame(canvas, style='TFrame') # Ensure it uses the styled frame
383
- self.scrollable_frame.bind(
384
- "<Configure>",
385
- lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
386
- )
387
-
388
- canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
389
- canvas.configure(yscrollcommand=scrollbar.set)
390
-
391
- canvas.pack(side="left", fill="both", expand=True)
392
- scrollbar.pack(side="right", fill="y")
393
-
394
- class StdoutRedirector_v1(object):
395
- def __init__(self, text_widget):
396
- self.text_widget = text_widget
397
-
398
- def write(self, string):
399
- self.text_widget.insert(tk.END, string)
400
- self.text_widget.see(tk.END)
401
-
402
- def flush(self):
403
- pass
404
-
405
- class StdoutRedirector:
406
- def __init__(self, text_widget):
407
- self.text_widget = text_widget
408
-
409
- def write(self, string):
410
- try:
411
- if self.text_widget.winfo_exists():
412
- self.text_widget.insert(tk.END, string)
413
- self.text_widget.see(tk.END)
414
- except tk.TclError:
415
- pass # Handle or log the error as needed
416
-
417
- def flush(self):
418
- pass
419
-
420
- def check_mask_gui_settings(vars_dict):
421
- settings = {}
422
- for key, var in vars_dict.items():
423
- value = var.get()
424
-
425
- # Handle conversion for specific types if necessary
426
- if key in ['channels', 'timelapse_frame_limits']: # Assuming these should be lists
427
- try:
428
- # Convert string representation of a list into an actual list
429
- settings[key] = eval(value)
430
- except:
431
- messagebox.showerror("Error", f"Invalid format for {key}. Please enter a valid list.")
432
- return
433
- elif key in ['nucleus_channel', 'cell_channel', 'pathogen_channel', 'examples_to_plot', 'batch_size', 'timelapse_memory', 'workers', 'fps', 'magnification']: # Assuming these should be integers
434
- try:
435
- settings[key] = int(value) if value else None
436
- except ValueError:
437
- messagebox.showerror("Error", f"Invalid number for {key}.")
438
- return
439
- 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
440
- try:
441
- settings[key] = float(value) if value else None
442
- except ValueError:
443
- messagebox.showerror("Error", f"Invalid number for {key}.")
444
- return
73
+ parsed_value = ast.literal_eval(value)
74
+ if isinstance(parsed_value, list):
75
+ return parsed_value
445
76
  else:
446
- settings[key] = value
447
- return settings
448
-
449
- def check_measure_gui_settings(vars_dict):
450
- settings = {}
451
- for key, var in vars_dict.items():
452
- value = var.get() # Retrieves the string representation for entries or the actual value for checkboxes and combos.
453
-
454
- try:
455
- if key in ['channels', 'png_dims']:
456
- settings[key] = [int(chan) for chan in ast.literal_eval(value)] if value else []
457
-
458
- elif key in ['cell_loc', 'pathogen_loc', 'treatment_loc']:
459
- # Convert to a list of lists of strings, ensuring all structures are lists.
460
- settings[key] = [list(map(str, sublist)) for sublist in ast.literal_eval(value)] if value else []
461
-
462
- elif key == 'dialate_png_ratios':
463
- settings[key] = [float(num) for num in ast.literal_eval(value)] if value else []
464
-
465
- elif key == 'normalize':
466
- settings[key] = [int(num) for num in ast.literal_eval(value)] if value else []
467
-
468
- # Directly assign string values for these specific keys
469
- elif key in ['normalize_by', 'experiment', 'measurement', 'input_folder']:
470
- settings[key] = value
471
-
472
- elif key == 'png_size':
473
- settings[key] = [list(map(int, dim)) for dim in ast.literal_eval(value)] if value else []
474
-
475
- # Ensure these are lists of strings, converting from tuples if necessary
476
- elif key in ['timelapse_objects', 'crop_mode', 'cells', 'pathogens', 'treatments']:
477
- eval_value = ast.literal_eval(value) if value else []
478
- settings[key] = list(map(str, eval_value)) if isinstance(eval_value, (list, tuple)) else [str(eval_value)]
479
-
480
- # Handling for single non-string values (int, float, bool)
481
- 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']:
482
- settings[key] = int(value) if value else None
483
-
484
- elif key == 'um_per_pixel':
485
- settings[key] = float(value) if value else None
486
-
487
- # Handling boolean values based on checkboxes
488
- elif key in ['save_png', 'use_bounding_box', 'save_measurements', 'plot', 'plot_filtration', 'include_uninfected', 'dialate_pngs', 'timelapse', 'representative_images']:
489
- settings[key] = var.get()
490
-
491
- except SyntaxError as e:
492
- print(f"Syntax error processing {key}: {str(e)}")
493
- #messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
494
- return None
495
- except Exception as e:
496
- print(f"Error processing {key}: {str(e)}")
497
- #messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
498
- return None
499
-
500
- return settings
501
-
502
- def check_classify_gui_settings(vars_dict):
503
- settings = {}
504
- for key, var in vars_dict.items():
505
- value = var.get() # This retrieves the string representation for entries or the actual value for checkboxes and combos
506
-
507
- try:
508
- if key in ['src', 'measurement']:
509
- # Directly assign string values
510
- settings[key] = str(value)
511
- elif key in ['cell_mask_dim', 'image_size', 'batch_size', 'epochs', 'gradient_accumulation_steps', 'num_workers']:
512
- # Convert to integer
513
- settings[key] = int(value)
514
- elif key in ['val_split', 'learning_rate', 'weight_decay', 'dropout_rate']:
515
- # Convert to float
516
- settings[key] = float(value)
517
- elif key == 'classes':
518
- # Evaluate as list
519
- settings[key] = ast.literal_eval(value)
520
-
521
- elif key in ['model_type','optimizer_type','schedule','loss_type','train_mode']:
522
- settings[key] = value
523
-
524
- elif key in ['gradient_accumulation','normalize','save','plot', 'init_weights','amsgrad','use_checkpoint','intermedeate_save','pin_memory', 'num_workers','verbose']:
525
- settings[key] = bool(value)
526
-
527
- except SyntaxError as e:
528
- messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
529
- return None
530
- except Exception as e:
531
- messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
532
- return None
533
-
534
- return settings
535
-
536
- def check_sim_gui_settings(vars_dict):
537
- settings = {}
538
- for key, var in vars_dict.items():
539
- value = var.get() # This retrieves the string representation for entries or the actual value for checkboxes and combos
540
-
541
- try:
542
- if key in ['src', 'name', 'variable']:
543
- # Directly assign string values
544
- settings[key] = str(value)
545
-
546
- elif key in ['nr_plates', 'number_of_genes','number_of_active_genes','avg_genes_per_well','avg_cells_per_well','avg_reads_per_gene']:
547
- #generate list of integers from list
548
- ls = [int(num) for num in ast.literal_eval(value)]
549
- if len(ls) == 3 and ls[2] > 0:
550
- list_of_integers = list(range(ls[0], ls[1], ls[2]))
551
- list_of_integers = [num + 1 if num == 0 else num for num in list_of_integers]
552
- else:
553
- list_of_integers = [ls[0]]
554
- settings[key] = list_of_integers
555
-
556
- elif key in ['sequencing_error','well_ineq_coeff','gene_ineq_coeff', 'positive_mean']:
557
- #generate list of floats from list
558
- ls = [float(num) for num in ast.literal_eval(value)]
559
- if len(ls) == 3 and ls[2] > 0:
560
- list_of_floats = np.linspace(ls[0], ls[1], ls[2])
561
- list_of_floats.tolist()
562
- list_of_floats = [x if x != 0.0 else x + 0.01 for x in list_of_floats]
563
- else:
564
- list_of_floats = [ls[0]]
565
- settings[key] = list_of_floats
566
-
567
- elif key in ['plot', 'random_seed']:
568
- # Evaluate as bool
569
- settings[key] = bool(value)
570
-
571
- elif key in ['number_of_control_genes', 'replicates', 'max_workers']:
572
- # Convert to integer
573
- settings[key] = int(value)
574
-
575
- except SyntaxError as e:
576
- messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
577
- return None
578
- except Exception as e:
579
- messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
580
- return None
581
-
582
- return settings
583
-
584
- def sim_variables():
585
- variables = {
586
- 'name':('entry', None, 'plates_2_4_8'),
587
- 'variable':('entry', None, 'all'),
588
- 'src':('entry', None, '/home/olafsson/Desktop/simulations'),
589
- 'number_of_control_genes':('entry', None, 30),
590
- 'replicates':('entry', None, 4),
591
- 'max_workers':('entry', None, 1),
592
- 'plot':('check', None, True),
593
- 'random_seed':('check', None, True),
594
- 'nr_plates': ('entry', None, '[8,8,0]'),# '[2,2,8]'
595
- 'number_of_genes': ('entry', None, '[100, 100, 0]'), #[1384, 1384, 0]
596
- 'number_of_active_genes': ('entry', None, '[10,10,0]'),
597
- 'avg_genes_per_well': ('entry', None, '[2, 10, 2]'),
598
- 'avg_cells_per_well': ('entry', None, '[100, 100, 0]'),
599
- 'positive_mean': ('entry', None, '[0.8, 0.8, 0]'),
600
- 'avg_reads_per_gene': ('entry', None, '[1000,1000, 0]'),
601
- 'sequencing_error': ('entry', None, '[0.01, 0.01, 0]'),
602
- 'well_ineq_coeff': ('entry', None, '[0.3,0.3,0]'),
603
- 'gene_ineq_coeff': ('entry', None, '[0.8,0.8,0]'),
604
- }
605
- return variables
77
+ raise ValueError
78
+ except (ValueError, SyntaxError):
79
+ raise ValueError("Invalid format for list")
80
+
81
+ def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
82
+ label_column = 0
83
+ widget_column = 1
606
84
 
607
- def add_measure_gui_defaults(settings):
608
- settings['compartments'] = ['pathogen', 'cytoplasm']
609
- return settings
85
+ # Configure the column widths
86
+ frame.grid_columnconfigure(label_column, weight=0) # Allow the label column to expand
87
+ frame.grid_columnconfigure(widget_column, weight=1) # Allow the widget column to expand
610
88
 
611
- def measure_variables():
612
- variables = {
613
- 'input_folder':('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui/merged'),
614
- 'channels': ('combo', ['[0,1,2,3]','[0,1,2]','[0,1]','[0]'], '[0,1,2,3]'),
615
- 'cell_mask_dim':('entry', None, 4),
616
- 'cell_min_size':('entry', None, 0),
617
- 'cytoplasm_min_size':('entry', None, 0),
618
- 'nucleus_mask_dim':('entry', None, 5),
619
- 'nucleus_min_size':('entry', None, 0),
620
- 'pathogen_mask_dim':('entry', None, 6),
621
- 'pathogen_min_size':('entry', None, 0),
622
- 'save_png':('check', None, True),
623
- 'crop_mode':('entry', None, '["cell"]'),
624
- 'use_bounding_box':('check', None, True),
625
- 'png_size': ('entry', None, '[[224,224]]'),
626
- 'normalize':('entry', None, '[2,98]'),
627
- 'png_dims':('entry', None, '[1,2,3]'),
628
- 'normalize_by':('combo', ['fov', 'png'], 'png'),
629
- 'save_measurements':('check', None, True),
630
- 'representative_images':('check', None, True),
631
- 'plot':('check', None, True),
632
- 'plot_filtration':('check', None, True),
633
- 'include_uninfected':('check', None, True),
634
- 'dialate_pngs':('check', None, False),
635
- 'dialate_png_ratios':('entry', None, '[0.2]'),
636
- 'timelapse':('check', None, False),
637
- 'timelapse_objects':('combo', ['["cell"]', '["nucleus"]', '["pathogen"]', '["cytoplasm"]'], '["cell"]'),
638
- 'max_workers':('entry', None, 30),
639
- 'experiment':('entry', None, 'experiment name'),
640
- 'cells':('entry', None, ['HeLa']),
641
- 'cell_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
642
- 'pathogens':('entry', None, '["wt","mutant"]'),
643
- 'pathogen_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
644
- 'treatments': ('entry', None, '["cm","lovastatin_20uM"]'),
645
- 'treatment_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
646
- 'channel_of_interest':('entry', None, 3),
647
- 'compartments':('entry', None, '["pathogen","cytoplasm"]'),
648
- 'measurement':('entry', None, 'mean_intensity'),
649
- 'nr_imgs':('entry', None, 32),
650
- 'um_per_pixel':('entry', None, 0.1)
651
- }
652
- return variables
89
+ # Right-align the label text and the label itself
90
+ label = spacrLabel(frame, text=label_text, background="black", foreground="white", anchor='e', justify='right')
91
+ label.grid(column=label_column, row=row, sticky=tk.E, padx=(5, 2), pady=5) # Align label to the right
653
92
 
654
- def classify_variables():
655
-
656
- def get_torchvision_models():
657
- # Fetch all public callable attributes from torchvision.models that are functions
658
- model_names = [name for name, obj in inspect.getmembers(models)
659
- if inspect.isfunction(obj) and not name.startswith("__")]
660
- return model_names
661
-
662
- model_names = get_torchvision_models()
663
- variables = {
664
- 'src':('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui/merged'),
665
- 'cell_mask_dim':('entry', None, 4),
666
- 'classes':('entry', None, '["nc","pc"]'),
667
- 'measurement':('entry', None, 'mean_intensity'),
668
- 'model_type': ('combo', model_names, 'resnet50'),
669
- 'optimizer_type': ('combo', ['adamw','adam'], 'adamw'),
670
- 'schedule': ('combo', ['reduce_lr_on_plateau','step_lr'], 'reduce_lr_on_plateau'),
671
- 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
672
- 'image_size': ('entry', None, 224),
673
- 'batch_size': ('entry', None, 12),
674
- 'epochs': ('entry', None, 2),
675
- 'val_split': ('entry', None, 0.1),
676
- 'train_mode': ('combo', ['erm', 'irm'], 'erm'),
677
- 'learning_rate': ('entry', None, 0.0001),
678
- 'weight_decay': ('entry', None, 0.00001),
679
- 'dropout_rate': ('entry', None, 0.1),
680
- 'gradient_accumulation': ('check', None, True),
681
- 'gradient_accumulation_steps': ('entry', None, 4),
682
- 'normalize': ('check', None, True),
683
- 'save': ('check', None, True),
684
- 'plot': ('check', None, True),
685
- 'init_weights': ('check', None, True),
686
- 'amsgrad': ('check', None, True),
687
- 'use_checkpoint': ('check', None, True),
688
- 'intermedeate_save': ('check', None, True),
689
- 'pin_memory': ('check', None, True),
690
- 'num_workers': ('entry', None, 30),
691
- 'verbose': ('check', None, True),
692
- }
693
- return variables
694
-
695
- def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
696
- label = ttk.Label(frame, text=label_text, style='Custom.TLabel') # Apply Custom.TLabel style for labels
697
- label.grid(column=0, row=row, sticky=tk.W, padx=5, pady=5)
698
-
699
93
  if var_type == 'entry':
700
94
  var = tk.StringVar(value=default_value) # Set default value
701
95
  entry = ttk.Entry(frame, textvariable=var, style='TEntry') # Apply TEntry style for entries
702
- entry.grid(column=1, row=row, sticky=tk.EW, padx=5)
96
+ entry.grid(column=widget_column, row=row, sticky=tk.W, padx=(2, 5), pady=5) # Align widget to the left
703
97
  return (label, entry, var) # Return both the label and the entry, and the variable
704
98
  elif var_type == 'check':
705
99
  var = tk.BooleanVar(value=default_value) # Set default value (True/False)
706
- check = ToggleSwitch(frame, text=label_text, variable=var) # Use ToggleSwitch class
707
- check.grid(column=1, row=row, sticky=tk.W, padx=5)
100
+ check = spacrCheckbutton(frame, text="", variable=var, style='TCheckbutton')
101
+ check.grid(column=widget_column, row=row, sticky=tk.W, padx=(2, 5), pady=5) # Align widget to the left
708
102
  return (label, check, var) # Return both the label and the checkbutton, and the variable
709
103
  elif var_type == 'combo':
710
104
  var = tk.StringVar(value=default_value) # Set default value
711
105
  combo = ttk.Combobox(frame, textvariable=var, values=options, style='TCombobox') # Apply TCombobox style
712
- combo.grid(column=1, row=row, sticky=tk.EW, padx=5)
106
+ combo.grid(column=widget_column, row=row, sticky=tk.W, padx=(2, 5), pady=5) # Align widget to the left
713
107
  if default_value:
714
108
  combo.set(default_value)
715
109
  return (label, combo, var) # Return both the label and the combobox, and the variable
@@ -717,227 +111,6 @@ def create_input_field(frame, label_text, row, var_type='entry', options=None, d
717
111
  var = None # Placeholder in case of an undefined var_type
718
112
  return (label, None, var)
719
113
 
720
- def mask_variables():
721
- variables = {
722
- 'src': ('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui'),
723
- 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
724
- 'custom_regex': ('entry', None, None),
725
- 'experiment': ('entry', None, 'exp'),
726
- 'channels': ('combo', ['[0,1,2,3]','[0,1,2]','[0,1]','[0]'], '[0,1,2,3]'),
727
- 'magnification': ('combo', [20, 40, 60,], 40),
728
- 'nucleus_channel': ('combo', [0,1,2,3, None], 0),
729
- 'nucleus_background': ('entry', None, 100),
730
- 'nucleus_Signal_to_noise': ('entry', None, 5),
731
- 'nucleus_CP_prob': ('entry', None, 0),
732
- 'cell_channel': ('combo', [0,1,2,3, None], 3),
733
- 'cell_background': ('entry', None, 100),
734
- 'cell_Signal_to_noise': ('entry', None, 5),
735
- 'cell_CP_prob': ('entry', None, 0),
736
- 'pathogen_channel': ('combo', [0,1,2,3, None], 2),
737
- 'pathogen_background': ('entry', None, 100),
738
- 'pathogen_Signal_to_noise': ('entry', None, 3),
739
- 'pathogen_CP_prob': ('entry', None, 0),
740
- 'preprocess': ('check', None, True),
741
- 'masks': ('check', None, True),
742
- 'examples_to_plot': ('entry', None, 1),
743
- 'randomize': ('check', None, True),
744
- 'batch_size': ('entry', None, 50),
745
- 'timelapse': ('check', None, False),
746
- 'timelapse_displacement': ('entry', None, None),
747
- 'timelapse_memory': ('entry', None, 0),
748
- 'timelapse_frame_limits': ('entry', None, '[0,1000]'),
749
- 'timelapse_remove_transient': ('check', None, True),
750
- 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
751
- 'timelapse_objects': ('combo', ['cell','nucleus','pathogen','cytoplasm', None], None),
752
- 'fps': ('entry', None, 2),
753
- 'remove_background': ('check', None, True),
754
- 'lower_quantile': ('entry', None, 0.01),
755
- 'merge': ('check', None, False),
756
- 'normalize_plots': ('check', None, True),
757
- 'all_to_mip': ('check', None, False),
758
- 'pick_slice': ('check', None, False),
759
- 'skip_mode': ('entry', None, None),
760
- 'save': ('check', None, True),
761
- 'plot': ('check', None, True),
762
- 'workers': ('entry', None, 30),
763
- 'verbose': ('check', None, True),
764
- }
765
- return variables
766
-
767
- def add_mask_gui_defaults(settings):
768
- settings['remove_background'] = True
769
- settings['fps'] = 2
770
- settings['all_to_mip'] = False
771
- settings['pick_slice'] = False
772
- settings['merge'] = False
773
- settings['skip_mode'] = ''
774
- settings['verbose'] = False
775
- settings['normalize_plots'] = True
776
- settings['randomize'] = True
777
- settings['preprocess'] = True
778
- settings['masks'] = True
779
- settings['examples_to_plot'] = 1
780
- return settings
781
-
782
- def generate_fields(variables, scrollable_frame):
783
- vars_dict = {}
784
- row = 0
785
- tooltips = {
786
- "src": "Path to the folder containing the images.",
787
- "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",
788
- "custom_regex": "Custom regex pattern to extract metadata from the image names. This will only be used if 'custom' is selected for 'metadata_type'.",
789
- "experiment": "Name of the experiment. This will be used to name the output files.",
790
- "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.",
791
- "magnification": "At what magnification the images were taken. This will be used to determine the size of the objects in the images.",
792
- "nucleus_channel": "The channel to use for the nucleus. If None, the nucleus will not be segmented.",
793
- "nucleus_background": "The background intensity for the nucleus channel. This will be used to remove background noise.",
794
- "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.",
795
- "nucleus_CP_prob": "The cellpose probability threshold for the nucleus channel. This will be used to segment the nucleus.",
796
- "cell_channel": "The channel to use for the cell. If None, the cell will not be segmented.",
797
- "cell_background": "The background intensity for the cell channel. This will be used to remove background noise.",
798
- "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.",
799
- "cell_CP_prob": "The cellpose probability threshold for the cell channel. This will be used to segment the cell.",
800
- "pathogen_channel": "The channel to use for the pathogen. If None, the pathogen will not be segmented.",
801
- "pathogen_background": "The background intensity for the pathogen channel. This will be used to remove background noise.",
802
- "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.",
803
- "pathogen_CP_prob": "The cellpose probability threshold for the pathogen channel. This will be used to segment the pathogen.",
804
- "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.",
805
- "masks": "Whether to generate masks for the segmented objects. If True, masks will be generated for the nucleus, cell, and pathogen.",
806
- "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 .",
807
- "randomize": "Whether to randomize the order of the images before processing. Recommended to avoid bias in the segmentation.",
808
- "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.",
809
- "timelapse": "Whether to process the images as a timelapse.",
810
- "timelapse_displacement": "The displacement between frames in the timelapse. This will be used to align the frames before processing.",
811
- "timelapse_memory": "The number of frames to in tandem objects must be present in to be considered the same object in the timelapse.",
812
- "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.",
813
- "timelapse_remove_transient": "Whether to remove transient objects in the timelapse. Transient objects are present in fewer than all frames.",
814
- "timelapse_mode": "The mode to use for processing the timelapse. 'trackpy' uses the trackpy library for tracking objects, while 'btrack' uses the btrack library.",
815
- "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.",
816
- "fps": "Frames per second of the automatically generated timelapse movies.",
817
- "remove_background": "Whether to remove background noise from the images. This will help improve the quality of the segmentation.",
818
- "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.",
819
- "merge_pathogens": "Whether to merge pathogen objects that share more than 75% of their perimiter.",
820
- "normalize_plots": "Whether to normalize the plots.",
821
- "all_to_mip": "Whether to convert all images to maximum intensity projections before processing.",
822
- "pick_slice": "Whether to pick a single slice from the z-stack images. If False, the maximum intensity projection will be used.",
823
- "skip_mode": "The mode to use for skipping images. This will determine how to handle images that cannot be processed.",
824
- "save": "Whether to save the results to disk.",
825
- "plot": "Whether to plot the results.",
826
- "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.",
827
- "verbose": "Whether to print verbose output during processing.",
828
- "input_folder": "Path to the folder containing the images.",
829
- "cell_mask_dim": "The dimension of the array the cell mask is saved in.",
830
- "cell_min_size": "The minimum size of cell objects in pixels2.",
831
- "cytoplasm_min_size": "The minimum size of cytoplasm objects in pixels2.",
832
- "nucleus_mask_dim": "The dimension of the array the nucleus mask is saved in.",
833
- "nucleus_min_size": "The minimum size of nucleus objects in pixels2.",
834
- "pathogen_mask_dim": "The dimension of the array the pathogen mask is saved in.",
835
- "pathogen_min_size": "The minimum size of pathogen objects in pixels2.",
836
- "save_png": "Whether to save the segmented objects as PNG images.",
837
- "crop_mode": "The mode to use for cropping the images. This will determine which objects are cropped from the images (cell, nucleus, pathogen, cytoplasm).",
838
- "use_bounding_box": "Whether to use the bounding box of the objects for cropping. If False, only the object itself will be cropped.",
839
- "png_size": "The size of the PNG images to save. This will determine the size of the saved images.",
840
- "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.",
841
- "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].",
842
- "normalize_by": "Whether to normalize the images by field of view (fov) or by PNG image (png).",
843
- "save_measurements": "Whether to save the measurements to disk.",
844
- "representative_images": "Whether to save representative images of the segmented objects (Not working yet).",
845
- "plot": "Whether to plot results.",
846
- "plot_filtration": "Whether to plot the filtration steps.",
847
- "include_uninfected": "Whether to include uninfected cells in the analysis.",
848
- "dialate_pngs": "Whether to dialate the PNG images before saving.",
849
- "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.",
850
- "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.",
851
- "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.",
852
- "cells: ": "The cell types to include in the analysis.",
853
- "cell_loc": "The locations of the cell types in the images.",
854
- "pathogens": "The pathogen types to include in the analysis.",
855
- "pathogen_loc": "The locations of the pathogen types in the images.",
856
- "treatments": "The treatments to include in the analysis.",
857
- "treatment_loc": "The locations of the treatments in the images.",
858
- "channel_of_interest": "The channel of interest to use for the analysis.",
859
- "compartments": "The compartments to measure in the images.",
860
- "measurement": "The measurement to use for the analysis.",
861
- "nr_imgs": "The number of images to plot.",
862
- "um_per_pixel": "The micrometers per pixel for the images.",
863
- }
864
-
865
- for key, (var_type, options, default_value) in variables.items():
866
- label, widget, var = create_input_field(scrollable_frame.scrollable_frame, key, row, var_type, options, default_value)
867
- vars_dict[key] = (label, widget, var) # Store the label, widget, and variable
868
-
869
- # Add tooltip to the label if it exists in the tooltips dictionary
870
- if key in tooltips:
871
- ToolTip(label, tooltips[key])
872
-
873
- row += 1
874
- return vars_dict
875
-
876
- class TextRedirector(object):
877
- def __init__(self, widget, queue):
878
- self.widget = widget
879
- self.queue = queue
880
-
881
- def write(self, str):
882
- self.queue.put(str)
883
-
884
- def flush(self):
885
- pass
886
-
887
- def create_dark_mode(root, style, console_output):
888
- dark_bg = 'black'
889
- light_text = 'white'
890
- dark_text = 'black'
891
- input_bg = '#555555' # Slightly lighter background for input fields
892
-
893
- # Configure ttkcompartments('TFrame', background=dark_bg)
894
- style.configure('TLabel', background=dark_bg, foreground=light_text)
895
- style.configure('TEntry', fieldbackground=input_bg, foreground=dark_text, background=dark_bg)
896
- style.configure('TButton', background=dark_bg, foreground=dark_text)
897
- style.map('TButton', background=[('active', dark_bg)], foreground=[('active', dark_text)])
898
- style.configure('Dark.TCheckbutton', background=dark_bg, foreground=dark_text)
899
- style.map('Dark.TCheckbutton', background=[('active', dark_bg)], foreground=[('active', dark_text)])
900
- style.configure('TCombobox', fieldbackground=input_bg, foreground=dark_text, background=dark_bg, selectbackground=input_bg, selectforeground=dark_text)
901
- style.map('TCombobox', fieldbackground=[('readonly', input_bg)], selectbackground=[('readonly', input_bg)], foreground=[('readonly', dark_text)])
902
-
903
- if console_output != None:
904
- console_output.config(bg=dark_bg, fg=light_text, insertbackground=light_text) #, font=("Helvetica", 12)
905
- root.configure(bg=dark_bg)
906
-
907
- def set_dark_style(style):
908
- style.configure('TFrame', background='black')
909
- style.configure('TLabel', background='black', foreground='white')
910
- style.configure('TEntry', background='black', foreground='white')
911
- style.configure('TCheckbutton', background='black', foreground='white')
912
-
913
- ##@log_function_call
914
- def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
915
- try:
916
- ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
917
- while not q.empty():
918
- message = q.get_nowait()
919
- clean_message = ansi_escape_pattern.sub('', message)
920
- if clean_message.startswith("Progress"):
921
- progress_label.config(text=clean_message)
922
- if clean_message.startswith("\rProgress"):
923
- progress_label.config(text=clean_message)
924
- elif clean_message.startswith("Successfully"):
925
- progress_label.config(text=clean_message)
926
- elif clean_message.startswith("Processing"):
927
- progress_label.config(text=clean_message)
928
- elif clean_message.startswith("scale"):
929
- pass
930
- elif clean_message.startswith("plot_cropped_arrays"):
931
- pass
932
- elif clean_message == "" or clean_message == "\r" or clean_message.strip() == "":
933
- pass
934
- else:
935
- print(clean_message)
936
- except Exception as e:
937
- print(f"Error updating GUI canvas: {e}")
938
- finally:
939
- root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
940
-
941
114
  def process_stdout_stderr(q):
942
115
  """
943
116
  Redirect stdout and stderr to the queue q.
@@ -959,137 +132,8 @@ class WriteToQueue(io.TextIOBase):
959
132
  def flush(self):
960
133
  pass
961
134
 
962
- def clear_canvas(canvas):
963
- # Clear each plot (axes) in the figure
964
- for ax in canvas.figure.get_axes():
965
- ax.clear()
966
-
967
- # Redraw the now empty canvas without changing its size
968
- canvas.draw_idle()
969
-
970
- def measure_crop_wrapper(settings, q, fig_queue):
971
- """
972
- Wraps the measure_crop function to integrate with GUI processes.
973
-
974
- Parameters:
975
- - settings: dict, The settings for the measure_crop function.
976
- - q: multiprocessing.Queue, Queue for logging messages to the GUI.
977
- - fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
978
- """
979
-
980
- def my_show():
981
- """
982
- Replacement for plt.show() that queues figures instead of displaying them.
983
- """
984
- fig = plt.gcf()
985
- fig_queue.put(fig) # Queue the figure for GUI display
986
- plt.close(fig) # Prevent the figure from being shown by plt.show()
987
-
988
- # Temporarily override plt.show
989
- original_show = plt.show
990
- plt.show = my_show
991
-
992
- try:
993
- print('start')
994
- spacr.measure.measure_crop(settings=settings)
995
- except Exception as e:
996
- errorMessage = f"Error during processing: {e}"
997
- q.put(errorMessage) # Send the error message to the GUI via the queue
998
- traceback.print_exc()
999
- finally:
1000
- plt.show = original_show # Restore the original plt.show function
1001
-
1002
- #@log_function_call
1003
- def preprocess_generate_masks_wrapper(settings, q, fig_queue):
1004
- """
1005
- Wraps the measure_crop function to integrate with GUI processes.
1006
-
1007
- Parameters:
1008
- - settings: dict, The settings for the measure_crop function.
1009
- - q: multiprocessing.Queue, Queue for logging messages to the GUI.
1010
- - fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
1011
- """
1012
-
1013
- def my_show():
1014
- """
1015
- Replacement for plt.show() that queues figures instead of displaying them.
1016
- """
1017
- fig = plt.gcf()
1018
- fig_queue.put(fig) # Queue the figure for GUI display
1019
- plt.close(fig) # Prevent the figure from being shown by plt.show()
1020
-
1021
- # Temporarily override plt.show
1022
- original_show = plt.show
1023
- plt.show = my_show
1024
-
1025
- try:
1026
- spacr.core.preprocess_generate_masks(settings['src'], settings=settings)
1027
- except Exception as e:
1028
- errorMessage = f"Error during processing: {e}"
1029
- q.put(errorMessage) # Send the error message to the GUI via the queue
1030
- traceback.print_exc()
1031
- finally:
1032
- plt.show = original_show # Restore the original plt.show function
1033
-
1034
- def train_test_model_wrapper(settings, q, fig_queue):
1035
- """
1036
- Wraps the measure_crop function to integrate with GUI processes.
1037
-
1038
- Parameters:
1039
- - settings: dict, The settings for the measure_crop function.
1040
- - q: multiprocessing.Queue, Queue for logging messages to the GUI.
1041
- - fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
1042
- """
1043
-
1044
- def my_show():
1045
- """
1046
- Replacement for plt.show() that queues figures instead of displaying them.
1047
- """
1048
- fig = plt.gcf()
1049
- fig_queue.put(fig) # Queue the figure for GUI display
1050
- plt.close(fig) # Prevent the figure from being shown by plt.show()
1051
-
1052
- # Temporarily override plt.show
1053
- original_show = plt.show
1054
- plt.show = my_show
1055
-
1056
- try:
1057
- spacr.core.train_test_model(settings['src'], settings=settings)
1058
- except Exception as e:
1059
- errorMessage = f"Error during processing: {e}"
1060
- q.put(errorMessage) # Send the error message to the GUI via the queue
1061
- traceback.print_exc()
1062
- finally:
1063
- plt.show = original_show # Restore the original plt.show function
1064
-
1065
-
1066
- def run_multiple_simulations_wrapper(settings, q, fig_queue):
1067
- """
1068
- Wraps the run_multiple_simulations function to integrate with GUI processes.
1069
-
1070
- Parameters:
1071
- - settings: dict, The settings for the run_multiple_simulations function.
1072
- - q: multiprocessing.Queue, Queue for logging messages to the GUI.
1073
- - fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
1074
- """
1075
-
1076
- def my_show():
1077
- """
1078
- Replacement for plt.show() that queues figures instead of displaying them.
1079
- """
1080
- fig = plt.gcf()
1081
- fig_queue.put(fig) # Queue the figure for GUI display
1082
- plt.close(fig) # Prevent the figure from being shown by plt.show()
1083
-
1084
- # Temporarily override plt.show
1085
- original_show = plt.show
1086
- plt.show = my_show
1087
-
1088
- try:
1089
- spacr.sim.run_multiple_simulations(settings=settings)
1090
- except Exception as e:
1091
- errorMessage = f"Error during processing: {e}"
1092
- q.put(errorMessage) # Send the error message to the GUI via the queue
1093
- traceback.print_exc()
1094
- finally:
1095
- plt.show = original_show # Restore the original plt.show function
135
+ def cancel_after_tasks(frame):
136
+ if hasattr(frame, 'after_tasks'):
137
+ for task in frame.after_tasks:
138
+ frame.after_cancel(task)
139
+ frame.after_tasks.clear()