spacr 0.1.16__py3-none-any.whl → 0.1.50__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,184 +1,124 @@
1
- import os, spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib, re, csv, requests
1
+ import os, spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib, re, csv, requests, ast
2
2
  import matplotlib.pyplot as plt
3
3
  matplotlib.use('Agg')
4
4
  import numpy as np
5
5
  import tkinter as tk
6
6
  from tkinter import ttk, messagebox
7
7
  import tkinter.font as tkFont
8
+ from tkinter import filedialog
9
+ from tkinter import Checkbutton
10
+ from tkinter import font as tkFont
8
11
  from torchvision import models
9
- #from ttf_opensans import opensans
10
12
 
11
- from tkinter import font as tkFont
13
+ from multiprocessing import Process, Value, Queue, Manager, set_start_method
14
+ import multiprocessing as mp
15
+
16
+ from tkinter import ttk, scrolledtext
17
+ from matplotlib.figure import Figure
18
+ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
19
+ import time
20
+ import requests
21
+ from requests.exceptions import HTTPError, Timeout
22
+ from huggingface_hub import list_repo_files, hf_hub_download
12
23
 
13
24
  from .logger import log_function_call
25
+ from .settings import set_default_train_test_model, get_measure_crop_settings, set_default_settings_preprocess_generate_masks
14
26
 
15
27
  try:
16
28
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
17
29
  except AttributeError:
18
30
  pass
19
31
 
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):
32
+ # Define global variables
33
+ q = None
34
+ console_output = None
35
+ parent_frame = None
36
+ vars_dict = None
37
+ canvas = None
38
+ canvas_widget = None
39
+ scrollable_frame = None
40
+ progress_label = None
41
+ fig_queue = None
99
42
 
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
43
+ thread_control = {"run_thread": None, "stop_requested": False}
106
44
 
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)
45
+ def set_dark_style(style):
46
+ font_style = tkFont.Font(family="Helvetica", size=10)
47
+ style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='black', foreground='#ffffff', font=font_style) # Entry
48
+ style.configure('TCombobox', fieldbackground='black', background='black', foreground='#ffffff', font=font_style) # Combobox
49
+ style.configure('Custom.TButton', background='black', foreground='white', bordercolor='white', focusthickness=3, focuscolor='white', font=('Helvetica', 12))
50
+ style.map('Custom.TButton',
51
+ background=[('active', 'teal'), ('!active', 'black')],
52
+ foreground=[('active', 'white'), ('!active', 'white')],
53
+ bordercolor=[('active', 'white'), ('!active', 'white')])
54
+ style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='black', foreground='#ffffff', font=font_style) # Custom Label
55
+ style.configure('TCheckbutton', background='black', foreground='#ffffff', indicatoron=False, relief='flat', font=font_style) # Checkbutton
56
+ style.map('TCheckbutton', background=[('selected', '#555555'), ('active', '#555555')])
57
+ style.configure('TLabel', background='black', foreground='#ffffff', font=font_style) # Label
58
+ style.configure('TFrame', background='black') # Frame
59
+ style.configure('TPanedwindow', background='black') # PanedWindow
60
+ style.configure('TNotebook', background='black', tabmargins=[2, 5, 2, 0]) # Notebook
61
+ style.configure('TNotebook.Tab', background='black', foreground='#ffffff', padding=[5, 5], font=font_style)
62
+ style.map('TNotebook.Tab', background=[('selected', '#555555'), ('active', '#555555')])
63
+ style.configure('TButton', background='black', foreground='#ffffff', padding='5 5 5 5', font=font_style) # Button (regular)
64
+ style.map('TButton', background=[('active', '#555555'), ('disabled', '#333333')])
65
+ style.configure('Vertical.TScrollbar', background='black', troughcolor='black', bordercolor='black') # Scrollbar
66
+ style.configure('Horizontal.TScrollbar', background='black', troughcolor='black', bordercolor='black') # Scrollbar
116
67
 
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}")
68
+ # Define custom LabelFrame style
69
+ style.configure('Custom.TLabelFrame', font=('Helvetica', 10, 'bold'), background='black', foreground='white', relief='solid', borderwidth=1)
70
+ style.configure('Custom.TLabelFrame.Label', background='black', foreground='white') # Style for the Label inside LabelFrame
71
+ style.configure('Custom.TLabelFrame.Label', font=('Helvetica', 10, 'bold'))
133
72
 
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 = []
73
+ def set_default_font(root, font_name="Helvetica", size=12):
74
+ default_font = (font_name, size)
75
+ root.option_add("*Font", default_font)
76
+ root.option_add("*TButton.Font", default_font)
77
+ root.option_add("*TLabel.Font", default_font)
78
+ root.option_add("*TEntry.Font", default_font)
140
79
 
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)
80
+ class ScrollableFrame(ttk.Frame):
81
+ def __init__(self, container, width=None, *args, bg='black', **kwargs):
82
+ super().__init__(container, *args, **kwargs)
83
+ self.configure(style='TFrame')
84
+ if width is None:
85
+ screen_width = self.winfo_screenwidth()
86
+ width = screen_width // 4
87
+ canvas = tk.Canvas(self, bg=bg, width=width)
88
+ scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
89
+
90
+ self.scrollable_frame = ttk.Frame(canvas, style='TFrame')
91
+ self.scrollable_frame.bind(
92
+ "<Configure>",
93
+ lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
94
+ )
95
+ canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
96
+ canvas.configure(yscrollcommand=scrollbar.set)
97
+
98
+ canvas.grid(row=0, column=0, sticky="nsew")
99
+ scrollbar.grid(row=0, column=1, sticky="ns")
148
100
 
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
101
+ self.grid_rowconfigure(0, weight=1)
102
+ self.grid_columnconfigure(0, weight=1)
103
+ self.grid_columnconfigure(1, weight=0)
104
+
105
+ for child in self.scrollable_frame.winfo_children():
106
+ child.configure(bg='black')
156
107
 
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
- }
108
+ class StdoutRedirector:
109
+ def __init__(self, text_widget):
110
+ self.text_widget = text_widget
165
111
 
166
- def load_app_wrapper(app_name, app_func):
167
- load_app(root, app_name, app_func)
112
+ def write(self, string):
113
+ try:
114
+ if self.text_widget.winfo_exists():
115
+ self.text_widget.insert(tk.END, string)
116
+ self.text_widget.see(tk.END)
117
+ except tk.TclError:
118
+ pass # Handle or log the error as needed
168
119
 
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)
120
+ def flush(self):
121
+ pass
182
122
 
183
123
  class CustomButton(tk.Frame):
184
124
  def __init__(self, parent, text="", command=None, font=None, *args, **kwargs):
@@ -328,77 +268,104 @@ class ToggleSwitch(ttk.Frame):
328
268
  x1, y1]
329
269
 
330
270
  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
271
 
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)
272
+ class ToolTip:
273
+ def __init__(self, widget, text):
274
+ self.widget = widget
275
+ self.text = text
276
+ self.tooltip_window = None
277
+ widget.bind("<Enter>", self.show_tooltip)
278
+ widget.bind("<Leave>", self.hide_tooltip)
279
+
280
+ def show_tooltip(self, event):
281
+ x = event.x_root + 20
282
+ y = event.y_root + 10
283
+ self.tooltip_window = tk.Toplevel(self.widget)
284
+ self.tooltip_window.wm_overrideredirect(True)
285
+ self.tooltip_window.wm_geometry(f"+{x}+{y}")
286
+ label = tk.Label(self.tooltip_window, text=self.text, background="yellow", relief='solid', borderwidth=1)
287
+ label.grid(row=0, column=0, padx=5, pady=5)
288
+
289
+ def hide_tooltip(self, event):
290
+ if self.tooltip_window:
291
+ self.tooltip_window.destroy()
292
+ self.tooltip_window = None
293
+
294
+ def create_menu_bar(root):
295
+ from .app_annotate import initiate_annotation_app_root
296
+ from .app_make_masks import initiate_mask_app_root
297
+
298
+ gui_apps = {
299
+ "Mask": 'mask',
300
+ "Measure": 'measure',
301
+ "Annotate": initiate_annotation_app_root,
302
+ "Make Masks": initiate_mask_app_root,
303
+ "Classify": 'classify'
304
+ }
305
+
306
+ def load_app_wrapper(app_name, app_func):
307
+ load_app(root, app_name, app_func)
308
+
309
+ # Create the menu bar
310
+ menu_bar = tk.Menu(root, bg="#008080", fg="white")
311
+ # Create a "SpaCr Applications" menu
312
+ app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
313
+ menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
314
+ # Add options to the "SpaCr Applications" menu
315
+ for app_name, app_func in gui_apps.items():
316
+ app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app_wrapper(app_name, app_func))
317
+ # Add a separator and an exit option
318
+ app_menu.add_separator()
319
+ app_menu.add_command(label="Exit", command=root.quit)
320
+ # Configure the menu for the root window
321
+ root.config(menu=menu_bar)
322
+
323
+ def proceed_with_app(root, app_name, app_func):
324
+ from .app_annotate import gui_annotate
325
+ from .app_make_masks import gui_make_masks
326
+ from .gui import gui_app
327
+
328
+ # Clear the current content frame
329
+ if hasattr(root, 'content_frame'):
330
+ for widget in root.content_frame.winfo_children():
331
+ try:
332
+ widget.destroy()
333
+ except tk.TclError as e:
334
+ print(f"Error destroying widget: {e}")
364
335
  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)
368
-
369
- def set_dark_style_v1(style):
370
- font_style = tkFont.Font(family="Helvetica", size=10)
371
- style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='black', foreground='#ffffff', font=font_style)
372
- 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')])
377
- 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')])
336
+ root.content_frame = tk.Frame(root)
337
+ root.content_frame.grid(row=1, column=0, sticky="nsew")
338
+ root.grid_rowconfigure(1, weight=1)
339
+ root.grid_columnconfigure(0, weight=1)
380
340
 
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
396
- 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)
399
- style.map('TButton', background=[('active', '#555555'), ('disabled', '#333333')])
400
- style.configure('Vertical.TScrollbar', background='black', troughcolor='black', bordercolor='black') # Scrollbar
401
- style.configure('Horizontal.TScrollbar', background='black', troughcolor='black', bordercolor='black')
341
+ # Initialize the new app in the content frame
342
+ if app_name == "Mask":
343
+ initiate_root(root.content_frame, 'mask')
344
+ elif app_name == "Measure":
345
+ initiate_root(root.content_frame, 'measure')
346
+ elif app_name == "Classify":
347
+ initiate_root(root.content_frame, 'classify')
348
+ elif app_name == "Annotate":
349
+ gui_annotate()
350
+ elif app_name == "Make Masks":
351
+ gui_make_masks()
352
+ else:
353
+ raise ValueError(f"Invalid app name: {app_name}")
354
+
355
+ def load_app(root, app_name, app_func):
356
+ # Cancel all scheduled after tasks
357
+ if hasattr(root, 'after_tasks'):
358
+ for task in root.after_tasks:
359
+ root.after_cancel(task)
360
+ root.after_tasks = []
361
+
362
+ # Exit functionality only for the annotation app
363
+ if app_name != "Annotate" and hasattr(root, 'current_app_exit_func'):
364
+ root.next_app_func = proceed_with_app
365
+ root.next_app_args = (app_name, app_func) # Ensure correct arguments
366
+ root.current_app_exit_func()
367
+ else:
368
+ proceed_with_app(root, app_name, app_func)
402
369
 
403
370
  def read_settings_from_csv(csv_file_path):
404
371
  settings = {}
@@ -420,353 +387,321 @@ def update_settings_from_csv(variables, csv_settings):
420
387
  new_settings[key] = (var_type, options, value)
421
388
  return new_settings
422
389
 
423
- def safe_literal_eval(value):
390
+ def parse_list(value):
424
391
  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)
439
-
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")
457
-
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')
479
-
480
- class StdoutRedirector:
481
- def __init__(self, text_widget):
482
- self.text_widget = text_widget
483
-
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
491
-
492
- def flush(self):
493
- pass
494
-
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
515
- try:
516
- settings[key] = float(value) if value else None
517
- except ValueError:
518
- messagebox.showerror("Error", f"Invalid number for {key}.")
519
- return
392
+ parsed_value = ast.literal_eval(value)
393
+ if isinstance(parsed_value, list):
394
+ return parsed_value
520
395
  else:
521
- settings[key] = value
522
- return settings
396
+ raise ValueError
397
+ except (ValueError, SyntaxError):
398
+ raise ValueError("Invalid format for list")
399
+
523
400
 
524
- def check_measure_gui_settings(vars_dict):
401
+ def check_settings(vars_dict):
402
+ global q
525
403
  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.
404
+ # Define the expected types for each key, including None where applicable
405
+ expected_types = {
406
+ "src": str,
407
+ "metadata_type": str,
408
+ "custom_regex": (str, type(None)),
409
+ "experiment": str,
410
+ "channels": list,
411
+ "magnification": int,
412
+ "nucleus_channel": (int, type(None)),
413
+ "nucleus_background": int,
414
+ "nucleus_Signal_to_noise": float,
415
+ "nucleus_CP_prob": float,
416
+ "nucleus_FT": float,
417
+ "cell_channel": (int, type(None)),
418
+ "cell_background": (int, float),
419
+ "cell_Signal_to_noise": (int, float),
420
+ "cell_CP_prob": (int, float),
421
+ "cell_FT": (int, float),
422
+ "pathogen_channel": (int, type(None)),
423
+ "pathogen_background": (int, float),
424
+ "pathogen_Signal_to_noise": (int, float),
425
+ "pathogen_CP_prob": (int, float),
426
+ "pathogen_FT": (int, float),
427
+ "preprocess": bool,
428
+ "masks": bool,
429
+ "examples_to_plot": int,
430
+ "randomize": bool,
431
+ "batch_size": int,
432
+ "timelapse": bool,
433
+ "timelapse_displacement": int,
434
+ "timelapse_memory": int,
435
+ "timelapse_frame_limits": list, # This can be a list of lists
436
+ "timelapse_remove_transient": bool,
437
+ "timelapse_mode": str,
438
+ "timelapse_objects": list,
439
+ "fps": int,
440
+ "remove_background": bool,
441
+ "lower_percentile": (int, float),
442
+ "merge_pathogens": bool,
443
+ "normalize_plots": bool,
444
+ "all_to_mip": bool,
445
+ "pick_slice": bool,
446
+ "skip_mode": str,
447
+ "save": bool,
448
+ "plot": bool,
449
+ "workers": int,
450
+ "verbose": bool,
451
+ "input_folder": str,
452
+ "cell_mask_dim": int,
453
+ "cell_min_size": int,
454
+ "cytoplasm_min_size": int,
455
+ "nucleus_mask_dim": int,
456
+ "nucleus_min_size": int,
457
+ "pathogen_mask_dim": int,
458
+ "pathogen_min_size": int,
459
+ "save_png": bool,
460
+ "crop_mode": list,
461
+ "use_bounding_box": bool,
462
+ "png_size": list, # This can be a list of lists
463
+ "normalize": bool,
464
+ "png_dims": list,
465
+ "normalize_by": str,
466
+ "save_measurements": bool,
467
+ "representative_images": bool,
468
+ "plot_filtration": bool,
469
+ "include_uninfected": bool,
470
+ "dialate_pngs": bool,
471
+ "dialate_png_ratios": list,
472
+ "max_workers": int,
473
+ "cells": list,
474
+ "cell_loc": list,
475
+ "pathogens": list,
476
+ "pathogen_loc": (list, list), # This can be a list of lists
477
+ "treatments": list,
478
+ "treatment_loc": (list, list), # This can be a list of lists
479
+ "channel_of_interest": int,
480
+ "compartments": list,
481
+ "measurement": str,
482
+ "nr_imgs": int,
483
+ "um_per_pixel": (int, float),
484
+ # Additional settings based on provided defaults
485
+ "include_noninfected": bool,
486
+ "include_multiinfected": bool,
487
+ "include_multinucleated": bool,
488
+ "filter_min_max": (list, type(None)),
489
+ "channel_dims": list,
490
+ "backgrounds": list,
491
+ "outline_thickness": int,
492
+ "outline_color": str,
493
+ "overlay_chans": list,
494
+ "overlay": bool,
495
+ "normalization_percentiles": list,
496
+ "print_object_number": bool,
497
+ "nr": int,
498
+ "figuresize": int,
499
+ "cmap": str,
500
+ "test_mode": bool,
501
+ "test_images": int,
502
+ "remove_background_cell": bool,
503
+ "remove_background_nucleus": bool,
504
+ "remove_background_pathogen": bool,
505
+ "pathogen_model": (str, type(None)),
506
+ "filter": bool,
507
+ "upscale": bool,
508
+ "upscale_factor": float,
509
+ "adjust_cells": bool,
510
+ "row_limit": int,
511
+ "tables": list,
512
+ "visualize": str,
513
+ "image_nr": int,
514
+ "dot_size": int,
515
+ "n_neighbors": int,
516
+ "min_dist": float,
517
+ "metric": str,
518
+ "eps": float,
519
+ "min_samples": int,
520
+ "filter_by": str,
521
+ "img_zoom": float,
522
+ "plot_by_cluster": bool,
523
+ "plot_cluster_grids": bool,
524
+ "remove_cluster_noise": bool,
525
+ "remove_highly_correlated": bool,
526
+ "log_data": bool,
527
+ "black_background": bool,
528
+ "remove_image_canvas": bool,
529
+ "plot_outlines": bool,
530
+ "plot_points": bool,
531
+ "smooth_lines": bool,
532
+ "clustering": str,
533
+ "exclude": (str, type(None)),
534
+ "col_to_compare": str,
535
+ "pos": str,
536
+ "neg": str,
537
+ "embedding_by_controls": bool,
538
+ "plot_images": bool,
539
+ "reduction_method": str,
540
+ "save_figure": bool,
541
+ "color_by": (str, type(None)),
542
+ "analyze_clusters": bool,
543
+ "resnet_features": bool,
544
+ "test_nr": int,
545
+ "radial_dist": bool,
546
+ "calculate_correlation": bool,
547
+ "manders_thresholds": list,
548
+ "homogeneity": bool,
549
+ "homogeneity_distances": list,
550
+ "save_arrays": bool,
551
+ "cytoplasm": bool,
552
+ "merge_edge_pathogen_cells": bool,
553
+ "cells_per_well": int,
554
+ "pathogen_size_range": list,
555
+ "nucleus_size_range": list,
556
+ "cell_size_range": list,
557
+ "pathogen_intensity_range": list,
558
+ "nucleus_intensity_range": list,
559
+ "cell_intensity_range": list,
560
+ "target_intensity_min": int,
561
+ "model_type": str,
562
+ "heatmap_feature": str,
563
+ "grouping": str,
564
+ "min_max": str,
565
+ "minimum_cell_count": int,
566
+ "n_estimators": int,
567
+ "test_size": float,
568
+ "location_column": str,
569
+ "positive_control": str,
570
+ "negative_control": str,
571
+ "n_repeats": int,
572
+ "top_features": int,
573
+ "remove_low_variance_features": bool,
574
+ "n_jobs": int,
575
+ "classes": list,
576
+ "schedule": str,
577
+ "loss_type": str,
578
+ "image_size": int,
579
+ "epochs": int,
580
+ "val_split": float,
581
+ "train_mode": str,
582
+ "learning_rate": float,
583
+ "weight_decay": float,
584
+ "dropout_rate": float,
585
+ "init_weights": bool,
586
+ "amsgrad": bool,
587
+ "use_checkpoint": bool,
588
+ "gradient_accumulation": bool,
589
+ "gradient_accumulation_steps": int,
590
+ "intermedeate_save": bool,
591
+ "pin_memory": bool,
592
+ "num_workers": int,
593
+ "augment": bool,
594
+ "target": str,
595
+ "cell_types": list,
596
+ "cell_plate_metadata": (list, type(None)),
597
+ "pathogen_types": list,
598
+ "pathogen_plate_metadata": (list, list), # This can be a list of lists
599
+ "treatment_plate_metadata": (list, list), # This can be a list of lists
600
+ "metadata_types": list,
601
+ "cell_chann_dim": int,
602
+ "nucleus_chann_dim": int,
603
+ "pathogen_chann_dim": int,
604
+ "plot_nr": int,
605
+ "plot_control": bool,
606
+ "remove_background": bool,
607
+ "target": str,
608
+ "upstream": str,
609
+ "downstream": str,
610
+ "barecode_length_1": int,
611
+ "barecode_length_2": int,
612
+ "chunk_size": int,
613
+ "grna": str,
614
+ "barcodes": str,
615
+ "plate_dict": dict,
616
+ "pc": str,
617
+ "pc_loc": str,
618
+ "nc": str,
619
+ "nc_loc": str,
620
+ "dependent_variable": str,
621
+ "transform": (str, type(None)),
622
+ "agg_type": str,
623
+ "min_cell_count": int,
624
+ "regression_type": str,
625
+ "remove_row_column_effect": bool,
626
+ "alpha": float,
627
+ "fraction_threshold": float,
628
+ "class_1_threshold": (float, type(None)),
629
+ "batch_size": int,
630
+ "CP_prob": float,
631
+ "flow_threshold": float,
632
+ "percentiles": (list, type(None)),
633
+ "circular": bool,
634
+ "invert": bool,
635
+ "diameter": int,
636
+ "grayscale": bool,
637
+ "resize": bool,
638
+ "target_height": (int, type(None)),
639
+ "target_width": (int, type(None)),
640
+ "rescale": bool,
641
+ "resample": bool,
642
+ "model_name": str,
643
+ "Signal_to_noise": int,
644
+ "learning_rate": float,
645
+ "weight_decay": float,
646
+ "batch_size": int,
647
+ "n_epochs": int,
648
+ "from_scratch": bool,
649
+ "width_height": list,
650
+ "resize": bool,
651
+ "gene_weights_csv": str,
652
+ "fraction_threshold": float,
653
+ }
528
654
 
529
- try:
530
- if key in ['channels', 'png_dims']:
531
- settings[key] = [int(chan) for chan in ast.literal_eval(value)] if value else []
655
+ for key, (label, widget, var) in vars_dict.items():
656
+ if key not in expected_types:
657
+ if key not in ["General","Nucleus","Cell","Pathogen","Timelapse","Plot","Object Image","Annotate Data","Measurements","Advanced","Miscellaneous","Test"]:
532
658
 
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
574
-
575
- return settings
576
-
577
- def check_classify_gui_settings(vars_dict):
578
- 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
-
609
- return settings
659
+ q.put(f"Key {key} not found in expected types.")
660
+ continue
610
661
 
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
662
+ value = var.get()
663
+ expected_type = expected_types.get(key, str)
615
664
 
616
665
  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]
666
+ if key in ["png_size", "pathogen_plate_metadata", "treatment_plate_metadata"]:
667
+ parsed_value = ast.literal_eval(value) if value else None
668
+ if isinstance(parsed_value, list):
669
+ if all(isinstance(i, list) for i in parsed_value) or all(not isinstance(i, list) for i in parsed_value):
670
+ settings[key] = parsed_value
671
+ else:
672
+ raise ValueError("Invalid format: Mixed list and list of lists")
627
673
  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]
674
+ raise ValueError("Invalid format for list or list of lists")
675
+ elif expected_type == list:
676
+ settings[key] = parse_list(value) if value else None
677
+ elif expected_type == bool:
678
+ settings[key] = value if isinstance(value, bool) else value.lower() in ['true', '1', 't', 'y', 'yes']
679
+ elif expected_type == (int, type(None)):
680
+ settings[key] = int(value) if value else None
681
+ elif expected_type == (float, type(None)):
682
+ settings[key] = float(value) if value else None
683
+ elif expected_type == (int, float):
684
+ settings[key] = float(value) if '.' in value else int(value)
685
+ elif expected_type == (str, type(None)):
686
+ settings[key] = str(value) if value else None
687
+ elif isinstance(expected_type, tuple):
688
+ for typ in expected_type:
689
+ try:
690
+ settings[key] = typ(value) if value else None
691
+ break
692
+ except (ValueError, TypeError):
693
+ continue
638
694
  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
658
-
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
695
+ raise ValueError
696
+ else:
697
+ settings[key] = expected_type(value) if value else None
698
+ except (ValueError, SyntaxError):
699
+ expected_type_name = ' or '.join([t.__name__ for t in expected_type]) if isinstance(expected_type, tuple) else expected_type.__name__
700
+ q.put(f"Error: Invalid format for {key}. Expected type: {expected_type_name}.")
701
+ return
681
702
 
682
- def add_measure_gui_defaults(settings):
683
- settings['compartments'] = ['pathogen', 'cytoplasm']
684
703
  return settings
685
704
 
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
728
-
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
705
  def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
771
706
  label = ttk.Label(frame, text=label_text, style='Custom.TLabel') # Apply Custom.TLabel style for labels
772
707
  label.grid(column=0, row=row, sticky=tk.W, padx=5, pady=5)
@@ -778,7 +713,7 @@ def create_input_field(frame, label_text, row, var_type='entry', options=None, d
778
713
  return (label, entry, var) # Return both the label and the entry, and the variable
779
714
  elif var_type == 'check':
780
715
  var = tk.BooleanVar(value=default_value) # Set default value (True/False)
781
- check = ToggleSwitch(frame, text="", variable=var) # Use ToggleSwitch class
716
+ check = Checkbutton(frame, text="", variable=var)
782
717
  check.grid(column=1, row=row, sticky=tk.W, padx=5)
783
718
  return (label, check, var) # Return both the label and the checkbutton, and the variable
784
719
  elif var_type == 'combo':
@@ -792,115 +727,12 @@ def create_input_field(frame, label_text, row, var_type='entry', options=None, d
792
727
  var = None # Placeholder in case of an undefined var_type
793
728
  return (label, None, var)
794
729
 
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
730
  def generate_fields(variables, scrollable_frame):
731
+ row = 1
899
732
  vars_dict = {}
900
- row = 5
901
733
  tooltips = {
902
734
  "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",
735
+ "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
736
  "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
737
  "experiment": "Name of the experiment. This will be used to name the output files.",
906
738
  "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.",
@@ -909,17 +741,20 @@ def generate_fields(variables, scrollable_frame):
909
741
  "nucleus_background": "The background intensity for the nucleus channel. This will be used to remove background noise.",
910
742
  "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
743
  "nucleus_CP_prob": "The cellpose probability threshold for the nucleus channel. This will be used to segment the nucleus.",
744
+ "nucleus_FT": "The flow threshold for nucleus objects. This will be used in nuclues segmentation.",
912
745
  "cell_channel": "The channel to use for the cell. If None, the cell will not be segmented.",
913
746
  "cell_background": "The background intensity for the cell channel. This will be used to remove background noise.",
914
747
  "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.",
748
+ "cell_CP_prob": "The cellpose probability threshold for the cell channel. This will be used in cell segmentation.",
749
+ "cell_FT": "The flow threshold for cell objects. This will be used to segment the cells.",
916
750
  "pathogen_channel": "The channel to use for the pathogen. If None, the pathogen will not be segmented.",
917
751
  "pathogen_background": "The background intensity for the pathogen channel. This will be used to remove background noise.",
918
752
  "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
753
  "pathogen_CP_prob": "The cellpose probability threshold for the pathogen channel. This will be used to segment the pathogen.",
754
+ "pathogen_FT": "The flow threshold for pathogen objects. This will be used in pathogen segmentation.",
920
755
  "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
756
  "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 .",
757
+ "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
758
  "randomize": "Whether to randomize the order of the images before processing. Recommended to avoid bias in the segmentation.",
924
759
  "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
760
  "timelapse": "Whether to process the images as a timelapse.",
@@ -931,41 +766,41 @@ def generate_fields(variables, scrollable_frame):
931
766
  "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
767
  "fps": "Frames per second of the automatically generated timelapse movies.",
933
768
  "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.",
769
+ "lower_percentile": "The lower quantile to use for normalizing the images. This will be used to determine the range of intensities to normalize images to.",
770
+ "merge_pathogens": "Whether to merge pathogen objects that share more than 75% of their perimeter.",
936
771
  "normalize_plots": "Whether to normalize the plots.",
937
772
  "all_to_mip": "Whether to convert all images to maximum intensity projections before processing.",
938
773
  "pick_slice": "Whether to pick a single slice from the z-stack images. If False, the maximum intensity projection will be used.",
939
774
  "skip_mode": "The mode to use for skipping images. This will determine how to handle images that cannot be processed.",
940
775
  "save": "Whether to save the results to disk.",
776
+ "merge_edge_pathogen_cells": "Whether to merge cells that share pathogen objects.",
941
777
  "plot": "Whether to plot the results.",
942
778
  "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
779
  "verbose": "Whether to print verbose output during processing.",
944
780
  "input_folder": "Path to the folder containing the images.",
945
781
  "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.",
782
+ "cell_min_size": "The minimum size of cell objects in pixels^2.",
783
+ "cytoplasm": "Whether to segment the cytoplasm (Cell - Nucleus + Pathogen).",
784
+ "cytoplasm_min_size": "The minimum size of cytoplasm objects in pixels^2.",
948
785
  "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.",
786
+ "nucleus_min_size": "The minimum size of nucleus objects in pixels^2.",
950
787
  "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.",
788
+ "pathogen_min_size": "The minimum size of pathogen objects in pixels^2.",
952
789
  "save_png": "Whether to save the segmented objects as PNG images.",
953
790
  "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
791
  "use_bounding_box": "Whether to use the bounding box of the objects for cropping. If False, only the object itself will be cropped.",
955
792
  "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.",
793
+ "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
794
  "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
795
  "normalize_by": "Whether to normalize the images by field of view (fov) or by PNG image (png).",
959
796
  "save_measurements": "Whether to save the measurements to disk.",
960
797
  "representative_images": "Whether to save representative images of the segmented objects (Not working yet).",
961
- "plot": "Whether to plot results.",
962
798
  "plot_filtration": "Whether to plot the filtration steps.",
963
799
  "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.",
800
+ "dialate_pngs": "Whether to dilate the PNG images before saving.",
801
+ "dialate_png_ratios": "The ratios to use for dilating the PNG images. This will determine the amount of dilation applied to the images before cropping.",
967
802
  "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.",
803
+ "cells": "The cell types to include in the analysis.",
969
804
  "cell_loc": "The locations of the cell types in the images.",
970
805
  "pathogens": "The pathogen types to include in the analysis.",
971
806
  "pathogen_loc": "The locations of the pathogen types in the images.",
@@ -975,7 +810,7 @@ def generate_fields(variables, scrollable_frame):
975
810
  "compartments": "The compartments to measure in the images.",
976
811
  "measurement": "The measurement to use for the analysis.",
977
812
  "nr_imgs": "The number of images to plot.",
978
- "um_per_pixel": "The micrometers per pixel for the images.",
813
+ "um_per_pixel": "The micrometers per pixel for the images."
979
814
  }
980
815
 
981
816
  for key, (var_type, options, default_value) in variables.items():
@@ -985,7 +820,6 @@ def generate_fields(variables, scrollable_frame):
985
820
  # Add tooltip to the label if it exists in the tooltips dictionary
986
821
  if key in tooltips:
987
822
  ToolTip(label, tooltips[key])
988
-
989
823
  row += 1
990
824
  return vars_dict
991
825
 
@@ -1133,7 +967,7 @@ def preprocess_generate_masks_wrapper(settings, q, fig_queue):
1133
967
  plt.show = my_show
1134
968
 
1135
969
  try:
1136
- spacr.core.preprocess_generate_masks(settings['src'], settings=settings)
970
+ spacr.core.preprocess_generate_masks(src=settings['src'], settings=settings)
1137
971
  except Exception as e:
1138
972
  errorMessage = f"Error during processing: {e}"
1139
973
  q.put(errorMessage) # Send the error message to the GUI via the queue
@@ -1202,4 +1036,490 @@ def run_multiple_simulations_wrapper(settings, q, fig_queue):
1202
1036
  q.put(errorMessage) # Send the error message to the GUI via the queue
1203
1037
  traceback.print_exc()
1204
1038
  finally:
1205
- plt.show = original_show # Restore the original plt.show function
1039
+ plt.show = original_show # Restore the original plt.show function
1040
+
1041
+ def convert_settings_dict_for_gui(settings):
1042
+ variables = {}
1043
+ special_cases = {
1044
+ 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
1045
+ 'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
1046
+ 'cell_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
1047
+ 'nucleus_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
1048
+ 'pathogen_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
1049
+ #'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
1050
+ 'magnification': ('combo', [20, 40, 60], 20),
1051
+ 'nucleus_channel': ('combo', [0, 1, 2, 3, None], None),
1052
+ 'cell_channel': ('combo', [0, 1, 2, 3, None], None),
1053
+ 'pathogen_channel': ('combo', [0, 1, 2, 3, None], None),
1054
+ 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
1055
+ 'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
1056
+ 'model_type': ('combo', ['resnet50', 'other_model'], 'resnet50'),
1057
+ 'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
1058
+ 'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
1059
+ 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
1060
+ 'normalize_by': ('combo', ['fov', 'png'], 'png'),
1061
+ }
1062
+
1063
+ for key, value in settings.items():
1064
+ if key in special_cases:
1065
+ variables[key] = special_cases[key]
1066
+ elif isinstance(value, bool):
1067
+ variables[key] = ('check', None, value)
1068
+ elif isinstance(value, int) or isinstance(value, float):
1069
+ variables[key] = ('entry', None, value)
1070
+ elif isinstance(value, str):
1071
+ variables[key] = ('entry', None, value)
1072
+ elif value is None:
1073
+ variables[key] = ('entry', None, value)
1074
+ elif isinstance(value, list):
1075
+ variables[key] = ('entry', None, str(value))
1076
+ else:
1077
+ variables[key] = ('entry', None, str(value))
1078
+ return variables
1079
+
1080
+ def setup_settings_panel(vertical_container, settings_type='mask', frame_height=500, frame_width=1000):
1081
+ global vars_dict, scrollable_frame
1082
+ from .settings import set_default_settings_preprocess_generate_masks, get_measure_crop_settings, set_default_train_test_model
1083
+
1084
+ print("Setting up settings panel")
1085
+
1086
+ # Create settings frame
1087
+ settings_frame = tk.Frame(vertical_container, bg='black', height=frame_height, width=frame_width)
1088
+ vertical_container.add(settings_frame, stretch="always")
1089
+
1090
+ # Add settings label
1091
+ settings_label = ttk.Label(settings_frame, text="Settings", style="Custom.TLabel", background="black", foreground="white")
1092
+ settings_label.grid(row=0, column=0, pady=10, padx=10)
1093
+
1094
+ # Create a ScrollableFrame inside the settings_frame
1095
+ scrollable_frame = ScrollableFrame(settings_frame, bg='black', width=frame_width)
1096
+ scrollable_frame.grid(row=1, column=0, sticky="nsew")
1097
+
1098
+ # Configure the weights for resizing
1099
+ settings_frame.grid_rowconfigure(1, weight=1)
1100
+ settings_frame.grid_columnconfigure(0, weight=1)
1101
+
1102
+ # Load settings based on type
1103
+ if settings_type == 'mask':
1104
+ settings = set_default_settings_preprocess_generate_masks(src='path', settings={})
1105
+ elif settings_type == 'measure':
1106
+ settings = get_measure_crop_settings(settings={})
1107
+ elif settings_type == 'classify':
1108
+ settings = set_default_train_test_model(settings={})
1109
+ else:
1110
+ raise ValueError(f"Invalid settings type: {settings_type}")
1111
+
1112
+ # Generate fields for settings
1113
+ variables = convert_settings_dict_for_gui(settings)
1114
+ vars_dict = generate_fields(variables, scrollable_frame)
1115
+
1116
+ print("Settings panel setup complete")
1117
+ return scrollable_frame, vars_dict
1118
+
1119
+
1120
+ def setup_plot_section(vertical_container):
1121
+ global canvas, canvas_widget
1122
+ plot_frame = tk.PanedWindow(vertical_container, orient=tk.VERTICAL)
1123
+ vertical_container.add(plot_frame, stretch="always")
1124
+ figure = Figure(figsize=(30, 4), dpi=100, facecolor='black')
1125
+ plot = figure.add_subplot(111)
1126
+ plot.plot([], []) # This creates an empty plot.
1127
+ plot.axis('off')
1128
+ canvas = FigureCanvasTkAgg(figure, master=plot_frame)
1129
+ canvas.get_tk_widget().configure(cursor='arrow', background='black', highlightthickness=0)
1130
+ canvas_widget = canvas.get_tk_widget()
1131
+ plot_frame.add(canvas_widget, stretch="always")
1132
+ canvas.draw()
1133
+ canvas.figure = figure
1134
+ return canvas, canvas_widget
1135
+
1136
+ def download_hug_dataset():
1137
+ global vars_dict, q
1138
+ repo_id = "einarolafsson/toxo_mito"
1139
+ subfolder = "plate1"
1140
+ local_dir = os.path.join(os.path.expanduser("~"), "datasets") # Set to the home directory
1141
+ try:
1142
+ local_path = download_dataset(repo_id, subfolder, local_dir)
1143
+ if 'src' in vars_dict:
1144
+ vars_dict['src'][2].set(local_path) # Assuming vars_dict['src'] is a tuple and the 3rd element is a StringVar
1145
+ q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
1146
+ q.put(f"Dataset downloaded to: {local_path}\n")
1147
+ except Exception as e:
1148
+ q.put(f"Failed to download dataset: {e}\n")
1149
+
1150
+ def download_dataset(repo_id, subfolder, local_dir=None, retries=5, delay=5):
1151
+ global q
1152
+ """
1153
+ Downloads a dataset from Hugging Face and returns the local path.
1154
+
1155
+ Args:
1156
+ repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito').
1157
+ subfolder (str): The subfolder path within the repository (e.g., 'plate1').
1158
+ local_dir (str): The local directory where the dataset will be saved. Defaults to the user's home directory.
1159
+ retries (int): Number of retry attempts in case of failure.
1160
+ delay (int): Delay in seconds between retries.
1161
+
1162
+ Returns:
1163
+ str: The local path to the downloaded dataset.
1164
+ """
1165
+ if local_dir is None:
1166
+ local_dir = os.path.join(os.path.expanduser("~"), "datasets")
1167
+
1168
+ local_subfolder_dir = os.path.join(local_dir, subfolder)
1169
+ if not os.path.exists(local_subfolder_dir):
1170
+ os.makedirs(local_subfolder_dir)
1171
+ elif len(os.listdir(local_subfolder_dir)) == 40:
1172
+ q.put(f"Dataset already downloaded to: {local_subfolder_dir}")
1173
+ return local_subfolder_dir
1174
+
1175
+ attempt = 0
1176
+ while attempt < retries:
1177
+ try:
1178
+ files = list_repo_files(repo_id, repo_type="dataset")
1179
+ subfolder_files = [file for file in files if file.startswith(subfolder)]
1180
+
1181
+ for file_name in subfolder_files:
1182
+ for attempt in range(retries):
1183
+ try:
1184
+ url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
1185
+ response = requests.get(url, stream=True)
1186
+ response.raise_for_status()
1187
+
1188
+ local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
1189
+ with open(local_file_path, 'wb') as file:
1190
+ for chunk in response.iter_content(chunk_size=8192):
1191
+ file.write(chunk)
1192
+ q.put(f"Downloaded file: {file_name}")
1193
+ break
1194
+ except (requests.HTTPError, requests.Timeout) as e:
1195
+ q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
1196
+ time.sleep(delay)
1197
+ else:
1198
+ raise Exception(f"Failed to download {file_name} after multiple attempts.")
1199
+
1200
+ return local_subfolder_dir
1201
+
1202
+ except (requests.HTTPError, requests.Timeout) as e:
1203
+ q.put(f"Error downloading dataset: {e}. Retrying in {delay} seconds...")
1204
+ attempt += 1
1205
+ time.sleep(delay)
1206
+
1207
+ raise Exception("Failed to download dataset after multiple attempts.")
1208
+
1209
+ def setup_button_section(horizontal_container, settings_type='mask', btn_row=1, settings_row=5, run=True, abort=True, download=True, import_btn=True, progress=True):
1210
+ global button_frame, run_button, abort_button, download_dataset_button, import_button, progress_label, q, fig_queue, vars_dict
1211
+
1212
+ button_frame = tk.Frame(horizontal_container, bg='black')
1213
+ horizontal_container.add(button_frame, stretch="always", sticky="nsew")
1214
+ button_frame.grid_rowconfigure(0, weight=0)
1215
+ button_frame.grid_rowconfigure(1, weight=1)
1216
+ button_frame.grid_columnconfigure(0, weight=1)
1217
+
1218
+ categories_label = ttk.Label(button_frame, text="Categories", style="Custom.TLabel", background="black", foreground="white")
1219
+ categories_label.grid(row=0, column=0, pady=10, padx=10)
1220
+
1221
+ button_scrollable_frame = ScrollableFrame(button_frame, bg='black')
1222
+ button_scrollable_frame.grid(row=1, column=0, sticky="nsew")
1223
+
1224
+ button_scrollable_frame.scrollable_frame.grid_columnconfigure(0, weight=1, minsize=100)
1225
+ button_scrollable_frame.scrollable_frame.grid_columnconfigure(1, weight=1, minsize=100)
1226
+ button_scrollable_frame.scrollable_frame.grid_columnconfigure(2, weight=1, minsize=100)
1227
+
1228
+ if run:
1229
+ run_button = ttk.Button(button_scrollable_frame.scrollable_frame, text="Run", command=lambda: start_process(q, fig_queue, settings_type), style='Custom.TButton')
1230
+ run_button.grid(row=btn_row, column=0, pady=5, padx=5, sticky='ew')
1231
+ if abort:
1232
+ abort_button = ttk.Button(button_scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort, style='Custom.TButton')
1233
+ abort_button.grid(row=btn_row, column=1, pady=5, padx=5, sticky='ew')
1234
+ btn_row += 1
1235
+ if download:
1236
+ download_dataset_button = ttk.Button(button_scrollable_frame.scrollable_frame, text="Download", command=download_hug_dataset, style='Custom.TButton')
1237
+ download_dataset_button.grid(row=btn_row, column=0, pady=5, padx=5, sticky='ew')
1238
+ if import_btn:
1239
+ import_button = ttk.Button(button_scrollable_frame.scrollable_frame, text="Import", command=lambda: import_settings(settings_row, settings_type), style='Custom.TButton')
1240
+ import_button.grid(row=btn_row, column=1, pady=5, padx=5, sticky='ew')
1241
+ btn_row += 1
1242
+ if progress:
1243
+ progress_label = ttk.Label(button_scrollable_frame.scrollable_frame, text="Processing: 0%", background="black", foreground="white")
1244
+ progress_label.grid(row=btn_row, column=0, columnspan=2, sticky="ew", pady=(5, 0), padx=10)
1245
+
1246
+ # Call toggle_settings after vars_dict is initialized
1247
+ if vars_dict is not None:
1248
+ toggle_settings(button_scrollable_frame)
1249
+
1250
+ return progress_label
1251
+
1252
+
1253
+ def setup_console(vertical_container):
1254
+ global console_output
1255
+ print("Setting up console frame")
1256
+ console_frame = tk.Frame(vertical_container, bg='black')
1257
+ vertical_container.add(console_frame, stretch="always")
1258
+ console_label = ttk.Label(console_frame, text="Console", background="black", foreground="white")
1259
+ console_label.grid(row=0, column=0, pady=10, padx=10)
1260
+ console_output = scrolledtext.ScrolledText(console_frame, height=10, bg='black', fg='white', insertbackground='white')
1261
+ console_output.grid(row=1, column=0, sticky="nsew")
1262
+ console_frame.grid_rowconfigure(1, weight=1)
1263
+ console_frame.grid_columnconfigure(0, weight=1)
1264
+ print("Console setup complete")
1265
+ return console_output
1266
+
1267
+ def toggle_test_mode():
1268
+ global vars_dict, test_mode_button
1269
+ current_state = vars_dict['test_mode'][2].get()
1270
+ new_state = not current_state
1271
+ vars_dict['test_mode'][2].set(new_state)
1272
+ if new_state:
1273
+ test_mode_button.config(bg="blue")
1274
+ else:
1275
+ test_mode_button.config(bg="gray")
1276
+
1277
+ def toggle_settings(button_scrollable_frame):
1278
+ global vars_dict
1279
+
1280
+ if vars_dict is None:
1281
+ raise ValueError("vars_dict is not initialized.")
1282
+
1283
+ categories = {
1284
+ "General": ["src", "input_folder", "metadata_type", "custom_regex", "experiment", "channels", "magnification"],
1285
+ "Nucleus": ["nucleus_channel", "nucleus_background", "nucleus_Signal_to_noise", "nucleus_CP_prob", "nucleus_FT", "remove_background_nucleus", "nucleus_min_size", "nucleus_mask_dim", "nucleus_loc"],
1286
+ "Cell": ["cell_channel", "cell_background", "cell_Signal_to_noise", "cell_CP_prob", "cell_FT", "remove_background_cell", "cell_min_size", "cell_mask_dim", "cytoplasm", "cytoplasm_min_size", "include_uninfected", "merge_edge_pathogen_cells", "adjust_cells"],
1287
+ "Pathogen": ["pathogen_channel", "pathogen_background", "pathogen_Signal_to_noise", "pathogen_CP_prob", "pathogen_FT", "pathogen_model", "remove_background_pathogen", "pathogen_min_size", "pathogen_mask_dim"],
1288
+ "Timelapse": ["timelapse", "fps", "timelapse_displacement", "timelapse_memory", "timelapse_frame_limits", "timelapse_remove_transient", "timelapse_mode", "timelapse_objects", "compartments"],
1289
+ "Plot": ["plot_filtration", "examples_to_plot", "normalize_plots", "normalize", "cmap", "figuresize", "plot"],
1290
+ "Object Image": ["save_png", "dialate_pngs", "dialate_png_ratios", "png_size", "png_dims", "save_arrays", "normalize_by", "dialate_png_ratios", "crop_mode", "dialate_pngs", "normalize", "use_bounding_box"],
1291
+ "Annotate Data": ["treatment_loc", "cells", "cell_loc", "pathogens", "pathogen_loc", "channel_of_interest", "measurement", "treatments", "representative_images", "um_per_pixel", "nr_imgs"],
1292
+ "Measurements": ["homogeneity", "homogeneity_distances", "radial_dist", "calculate_correlation", "manders_thresholds", "save_measurements"],
1293
+ "Advanced": ["preprocess", "remove_background", "normalize", "lower_percentile", "merge_pathogens", "batch_size", "filter", "save", "masks", "verbose", "randomize", "max_workers", "workers"],
1294
+ "Miscellaneous": ["all_to_mip", "pick_slice", "skip_mode", "upscale", "upscale_factor"],
1295
+ "Test": ["test_mode", "test_images", "random_test", "test_nr"]
1296
+ }
1297
+
1298
+ def toggle_category(settings, var):
1299
+ for setting in settings:
1300
+ if setting in vars_dict:
1301
+ label, widget, _ = vars_dict[setting]
1302
+ if var.get() == 0:
1303
+ label.grid_remove()
1304
+ widget.grid_remove()
1305
+ else:
1306
+ label.grid()
1307
+ widget.grid()
1308
+
1309
+ row = 1
1310
+ col = 2 # Start from column 2 to avoid overlap with buttons
1311
+ category_idx = 0
1312
+
1313
+ for category, settings in categories.items():
1314
+ if any(setting in vars_dict for setting in settings):
1315
+ category_var = tk.IntVar(value=0)
1316
+ vars_dict[category] = (None, None, category_var)
1317
+ toggle = ttk.Checkbutton(
1318
+ button_scrollable_frame.scrollable_frame,
1319
+ text=category,
1320
+ variable=category_var,
1321
+ command=lambda cat=settings, var=category_var: toggle_category(cat, var),
1322
+ style='TCheckbutton'
1323
+ )
1324
+ toggle.grid(row=row, column=col, sticky="w", pady=2, padx=2)
1325
+ col += 1
1326
+ category_idx += 1
1327
+
1328
+ if category_idx % 4 == 0:
1329
+ row += 1
1330
+ col = 2 # Reset column to 2
1331
+
1332
+ for settings in categories.values():
1333
+ for setting in settings:
1334
+ if setting in vars_dict:
1335
+ label, widget, _ = vars_dict[setting]
1336
+ label.grid_remove()
1337
+ widget.grid_remove()
1338
+
1339
+ def initiate_abort():
1340
+ global thread_control
1341
+ if thread_control.get("stop_requested") is not None:
1342
+ thread_control["stop_requested"].value = 1
1343
+
1344
+ if thread_control.get("run_thread") is not None:
1345
+ thread_control["run_thread"].join(timeout=5)
1346
+ if thread_control["run_thread"].is_alive():
1347
+ thread_control["run_thread"].terminate()
1348
+ thread_control["run_thread"] = None
1349
+
1350
+ def run_mask_gui(settings, q, fig_queue, stop_requested):
1351
+ process_stdout_stderr(q)
1352
+ try:
1353
+ preprocess_generate_masks_wrapper(settings, q, fig_queue)
1354
+ except Exception as e:
1355
+ q.put(f"Error during processing: {e}")
1356
+ traceback.print_exc()
1357
+ finally:
1358
+ stop_requested.value = 1
1359
+
1360
+ def start_process(q, fig_queue, settings_type='mask'):
1361
+ global thread_control, vars_dict
1362
+ settings = check_settings(vars_dict)
1363
+ if thread_control.get("run_thread") is not None:
1364
+ initiate_abort()
1365
+ stop_requested = Value('i', 0) # multiprocessing shared value for inter-process communication
1366
+ thread_control["stop_requested"] = stop_requested
1367
+ if settings_type == 'mask':
1368
+ thread_control["run_thread"] = Process(target=run_mask_gui, args=(settings, q, fig_queue, stop_requested))
1369
+ elif settings_type == 'measure':
1370
+ thread_control["run_thread"] = Process(target=run_measure_gui, args=(settings, q, fig_queue, stop_requested))
1371
+ elif settings_type == 'classify':
1372
+ thread_control["run_thread"] = Process(target=run_classify_gui, args=(settings, q, fig_queue, stop_requested))
1373
+ thread_control["run_thread"].start()
1374
+
1375
+ def import_settings(settings_type='mask'):
1376
+ global vars_dict, scrollable_frame
1377
+ csv_file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
1378
+ csv_settings = read_settings_from_csv(csv_file_path)
1379
+ if settings_type == 'mask':
1380
+ settings = set_default_settings_preprocess_generate_masks(src='path', settings={})
1381
+ elif settings_type == 'measure':
1382
+ settings = get_measure_crop_settings(settings={})
1383
+ elif settings_type == 'classify':
1384
+ settings = set_default_train_test_model(settings={})
1385
+ else:
1386
+ raise ValueError(f"Invalid settings type: {settings_type}")
1387
+
1388
+ variables = convert_settings_dict_for_gui(settings)
1389
+ new_settings = update_settings_from_csv(variables, csv_settings)
1390
+ vars_dict = generate_fields(new_settings, scrollable_frame)
1391
+
1392
+ def process_fig_queue():
1393
+ global canvas, fig_queue, canvas_widget, parent_frame
1394
+ try:
1395
+ while not fig_queue.empty():
1396
+ clear_canvas(canvas)
1397
+ fig = fig_queue.get_nowait()
1398
+ for ax in fig.get_axes():
1399
+ ax.set_xticks([]) # Remove x-axis ticks
1400
+ ax.set_yticks([]) # Remove y-axis ticks
1401
+ ax.xaxis.set_visible(False) # Hide the x-axis
1402
+ ax.yaxis.set_visible(False) # Hide the y-axis
1403
+ fig.tight_layout()
1404
+ fig.set_facecolor('black')
1405
+ canvas.figure = fig
1406
+ fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
1407
+ fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
1408
+ canvas.draw_idle()
1409
+ except Exception as e:
1410
+ traceback.print_exc()
1411
+ finally:
1412
+ after_id = canvas_widget.after(100, process_fig_queue)
1413
+ parent_frame.after_tasks.append(after_id)
1414
+
1415
+ def process_console_queue():
1416
+ global q, console_output, parent_frame
1417
+ while not q.empty():
1418
+ message = q.get_nowait()
1419
+ console_output.insert(tk.END, message)
1420
+ console_output.see(tk.END)
1421
+ after_id = console_output.after(100, process_console_queue)
1422
+ parent_frame.after_tasks.append(after_id)
1423
+
1424
+ def setup_frame(parent_frame):
1425
+ style = ttk.Style(parent_frame)
1426
+ set_dark_style(style)
1427
+ set_default_font(parent_frame, font_name="Helvetica", size=8)
1428
+ parent_frame.configure(bg='black')
1429
+ parent_frame.grid_rowconfigure(0, weight=1)
1430
+ parent_frame.grid_columnconfigure(0, weight=1)
1431
+ vertical_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL, bg='black')
1432
+ vertical_container.grid(row=0, column=0, sticky=tk.NSEW)
1433
+ horizontal_container = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL, bg='black')
1434
+ vertical_container.add(horizontal_container, stretch="always")
1435
+ horizontal_container.grid_columnconfigure(0, weight=1)
1436
+ horizontal_container.grid_columnconfigure(1, weight=1)
1437
+ settings_frame = tk.Frame(horizontal_container, bg='black')
1438
+ settings_frame.grid_rowconfigure(0, weight=0)
1439
+ settings_frame.grid_rowconfigure(1, weight=1)
1440
+ settings_frame.grid_columnconfigure(0, weight=1)
1441
+ horizontal_container.add(settings_frame, stretch="always", sticky="nsew")
1442
+ return parent_frame, vertical_container, horizontal_container
1443
+
1444
+ def run_measure_gui(settings, q, fig_queue, stop_requested):
1445
+ process_stdout_stderr(q)
1446
+ try:
1447
+ settings['input_folder'] = settings['src']
1448
+ measure_crop_wrapper(settings=settings, q=q, fig_queue=fig_queue)
1449
+ except Exception as e:
1450
+ q.put(f"Error during processing: {e}")
1451
+ traceback.print_exc()
1452
+ finally:
1453
+ stop_requested.value = 1
1454
+
1455
+ def run_classify_gui(settings, q, fig_queue, stop_requested):
1456
+ process_stdout_stderr(q)
1457
+ try:
1458
+ train_test_model_wrapper(settings['src'], settings)
1459
+ except Exception as e:
1460
+ q.put(f"Error during processing: {e}")
1461
+ traceback.print_exc()
1462
+ finally:
1463
+ stop_requested.value = 1
1464
+
1465
+ 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):
1466
+ global q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, progress_label, fig_queue
1467
+ q = q_var
1468
+ console_output = console_output_var
1469
+ parent_frame = parent_frame_var
1470
+ vars_dict = vars_dict_var
1471
+ canvas = canvas_var
1472
+ canvas_widget = canvas_widget_var
1473
+ scrollable_frame = scrollable_frame_var
1474
+ progress_label = progress_label_var
1475
+ fig_queue = fig_queue_var
1476
+
1477
+ def initiate_root(parent, settings_type='mask'):
1478
+ global q, fig_queue, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, progress_label
1479
+ print("Initializing root with settings_type:", settings_type)
1480
+ parent_frame = parent
1481
+
1482
+ if not hasattr(parent_frame, 'after_tasks'):
1483
+ parent_frame.after_tasks = []
1484
+
1485
+ for widget in parent_frame.winfo_children():
1486
+ if widget.winfo_exists():
1487
+ try:
1488
+ widget.destroy()
1489
+ except tk.TclError as e:
1490
+ print(f"Error destroying widget: {e}")
1491
+
1492
+ q = Queue()
1493
+ fig_queue = Queue()
1494
+ parent_frame, vertical_container, horizontal_container = setup_frame(parent_frame)
1495
+ scrollable_frame, vars_dict = setup_settings_panel(horizontal_container, settings_type) # Adjust height and width as needed
1496
+ progress_label = setup_button_section(horizontal_container, settings_type)
1497
+ canvas, canvas_widget = setup_plot_section(vertical_container)
1498
+ console_output = setup_console(vertical_container)
1499
+ set_globals(q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, progress_label, fig_queue)
1500
+ process_console_queue()
1501
+ process_fig_queue()
1502
+ after_id = parent_frame.after(100, lambda: main_thread_update_function(parent_frame, q, fig_queue, canvas_widget, progress_label))
1503
+ parent_frame.after_tasks.append(after_id)
1504
+ print("Root initialization complete")
1505
+ return parent_frame, vars_dict
1506
+
1507
+
1508
+ def cancel_after_tasks(frame):
1509
+ if hasattr(frame, 'after_tasks'):
1510
+ for task in frame.after_tasks:
1511
+ frame.after_cancel(task)
1512
+ frame.after_tasks.clear()
1513
+
1514
+ def start_gui_app(settings_type='mask'):
1515
+ global q, fig_queue, parent_frame, scrollable_frame, vars_dict, canvas, canvas_widget, progress_label
1516
+ root = tk.Tk()
1517
+ width = root.winfo_screenwidth()
1518
+ height = root.winfo_screenheight()
1519
+ root.geometry(f"{width}x{height}")
1520
+ root.title(f"SpaCr: {settings_type.capitalize()}")
1521
+ root.content_frame = tk.Frame(root)
1522
+ print("Starting GUI app with settings_type:", settings_type)
1523
+ initiate_root(root.content_frame, settings_type)
1524
+ create_menu_bar(root)
1525
+ root.mainloop()