spacr 0.1.12__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,186 +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
12
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
13
23
 
14
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
15
26
 
16
27
  try:
17
28
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
18
29
  except AttributeError:
19
30
  pass
20
31
 
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()
37
-
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 = []
49
-
50
- # Exit functionality only for the annotation app
51
- if app_name == "Annotate" and hasattr(root, 'current_app_exit_func'):
52
- root.current_app_exit_func()
53
-
54
- # Clear the current content frame
55
- if hasattr(root, 'content_frame'):
56
- for widget in root.content_frame.winfo_children():
57
- widget.destroy()
58
- else:
59
- root.content_frame = tk.Frame(root)
60
- root.content_frame.grid(row=1, column=0, sticky="nsew")
61
- root.grid_rowconfigure(1, weight=1)
62
- root.grid_columnconfigure(0, weight=1)
63
-
64
- # Initialize the new app in the content frame
65
- app_func(root.content_frame)
66
-
67
- def create_menu_bar(root):
68
- from .app_mask import initiate_mask_root
69
- from .app_measure import initiate_measure_root
70
- from .app_annotate import initiate_annotation_app_root
71
- from .app_make_masks import initiate_mask_app_root
72
- from .app_classify import initiate_classify_root
73
-
74
- gui_apps = {
75
- "Mask": initiate_mask_root,
76
- "Measure": initiate_measure_root,
77
- "Annotate": initiate_annotation_app_root,
78
- "Make Masks": initiate_mask_app_root,
79
- "Classify": initiate_classify_root
80
- }
81
-
82
- def load_app_wrapper(app_name, app_func):
83
- load_app(root, app_name, app_func)
84
-
85
- # Create the menu bar
86
- menu_bar = tk.Menu(root, bg="#008080", fg="white")
87
- # Create a "SpaCr Applications" menu
88
- app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
89
- menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
90
- # Add options to the "SpaCr Applications" menu
91
- for app_name, app_func in gui_apps.items():
92
- app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app_wrapper(app_name, app_func))
93
- # Add a separator and an exit option
94
- app_menu.add_separator()
95
- app_menu.add_command(label="Exit", command=root.quit)
96
- # Configure the menu for the root window
97
- root.config(menu=menu_bar)
98
-
99
- def proceed_with_app(root, app_name, app_func):
100
-
101
- from .app_mask import gui_mask
102
- from .app_measure import gui_measure
103
- from .app_annotate import gui_annotate
104
- from .app_make_masks import gui_make_masks
105
- from .app_classify import gui_classify
106
- from .gui import gui_app
107
-
108
- # Clear the current content frame
109
- if hasattr(root, 'content_frame'):
110
- for widget in root.content_frame.winfo_children():
111
- widget.destroy()
112
- else:
113
- root.content_frame = tk.Frame(root)
114
- root.content_frame.grid(row=1, column=0, sticky="nsew")
115
- root.grid_rowconfigure(1, weight=1)
116
- root.grid_columnconfigure(0, weight=1)
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
117
42
 
118
- # Initialize the new app in the content frame
119
- if app_name == "Main App":
120
- root.destroy() # Close the current window
121
- gui_app() # Open the main app window
122
- elif app_name == "Mask":
123
- gui_mask()
124
- elif app_name == "Measure":
125
- gui_measure()
126
- elif app_name == "Annotate":
127
- gui_annotate()
128
- elif app_name == "Make Masks":
129
- gui_make_masks()
130
- elif app_name == "Classify":
131
- gui_classify()
132
- else:
133
- raise ValueError(f"Invalid app name: {app_name}")
43
+ thread_control = {"run_thread": None, "stop_requested": False}
134
44
 
135
- def load_app(root, app_name, app_func):
136
- # Cancel all scheduled after tasks
137
- if hasattr(root, 'after_tasks'):
138
- for task in root.after_tasks:
139
- root.after_cancel(task)
140
- root.after_tasks = []
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
67
+
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'))
141
72
 
142
- # Exit functionality only for the annotation app
143
- if app_name != "Annotate" and hasattr(root, 'current_app_exit_func'):
144
- root.next_app_func = proceed_with_app
145
- root.next_app_args = (app_name, app_func) # Ensure correct arguments
146
- root.current_app_exit_func()
147
- else:
148
- proceed_with_app(root, app_name, app_func)
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)
149
79
 
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")
150
100
 
151
- def create_menu_bar(root):
152
- from .app_mask import initiate_mask_root
153
- from .app_measure import initiate_measure_root
154
- from .app_annotate import initiate_annotation_app_root
155
- from .app_make_masks import initiate_mask_app_root
156
- from .app_classify import initiate_classify_root
157
- 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')
158
107
 
159
- gui_apps = {
160
- "Main App": gui_app,
161
- "Mask": initiate_mask_root,
162
- "Measure": initiate_measure_root,
163
- "Annotate": initiate_annotation_app_root,
164
- "Make Masks": initiate_mask_app_root,
165
- "Classify": initiate_classify_root
166
- }
108
+ class StdoutRedirector:
109
+ def __init__(self, text_widget):
110
+ self.text_widget = text_widget
167
111
 
168
- def load_app_wrapper(app_name, app_func):
169
- 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
170
119
 
171
- # Create the menu bar
172
- menu_bar = tk.Menu(root, bg="#008080", fg="white")
173
- # Create a "SpaCr Applications" menu
174
- app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
175
- menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
176
- # Add options to the "SpaCr Applications" menu
177
- for app_name, app_func in gui_apps.items():
178
- app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app_wrapper(app_name, app_func))
179
- # Add a separator and an exit option
180
- app_menu.add_separator()
181
- app_menu.add_command(label="Exit", command=root.destroy) # Use root.destroy instead of root.quit
182
- # Configure the menu for the root window
183
- root.config(menu=menu_bar)
120
+ def flush(self):
121
+ pass
184
122
 
185
123
  class CustomButton(tk.Frame):
186
124
  def __init__(self, parent, text="", command=None, font=None, *args, **kwargs):
@@ -188,14 +126,19 @@ class CustomButton(tk.Frame):
188
126
  self.text = text
189
127
  self.command = command
190
128
 
191
- self.canvas = tk.Canvas(self, width=150, height=50, highlightthickness=0, bg="black")
129
+ # Detect screen height and calculate button dimensions
130
+ screen_height = self.winfo_screenheight()
131
+ button_height = screen_height // 50
132
+ button_width = button_height * 3
133
+
134
+ self.canvas = tk.Canvas(self, width=button_width, height=button_height, highlightthickness=0, bg="black")
192
135
  self.canvas.grid(row=0, column=0)
193
136
 
194
- self.button_bg = self.create_rounded_rectangle(0, 0, 150, 50, radius=20, fill="#800080")
137
+ self.button_bg = self.create_rounded_rectangle(0, 0, button_width, button_height, radius=20, fill="#800080")
195
138
 
196
139
  # Use the passed font or default to Helvetica if not provided
197
140
  self.font_style = font if font else tkFont.Font(family="Helvetica", size=12, weight=tkFont.NORMAL)
198
- self.button_text = self.canvas.create_text(75, 25, text=self.text, fill="white", font=self.font_style)
141
+ self.button_text = self.canvas.create_text(button_width // 2, button_height // 2, text=self.text, fill="white", font=self.font_style)
199
142
 
200
143
  self.bind("<Enter>", self.on_enter)
201
144
  self.bind("<Leave>", self.on_leave)
@@ -325,104 +268,104 @@ class ToggleSwitch(ttk.Frame):
325
268
  x1, y1]
326
269
 
327
270
  return self.canvas.create_polygon(points, **kwargs, smooth=True)
328
-
329
- def set_default_font(root, font_name="Helvetica", size=12):
330
- default_font = (font_name, size)
331
- root.option_add("*Font", default_font)
332
- root.option_add("*TButton.Font", default_font)
333
- root.option_add("*TLabel.Font", default_font)
334
- root.option_add("*TEntry.Font", default_font)
335
271
 
336
- def check_and_download_font_v1():
337
- font_name = "Helvetica"
338
- font_dir = "fonts"
339
- font_path = os.path.join(font_dir, "OpenSans-Regular.ttf")
340
-
341
- # Check if the font is already available
342
- available_fonts = list(tkFont.families())
343
- if font_name not in available_fonts:
344
- print(f"Font '{font_name}' not found. Downloading...")
345
- if not os.path.exists(font_dir):
346
- os.makedirs(font_dir)
347
-
348
- if not os.path.exists(font_path):
349
- url = "https://github.com/google/fonts/blob/main/apache/opensans/OpenSans-Regular.ttf?raw=true"
350
- response = requests.get(url)
351
- with open(font_path, "wb") as f:
352
- f.write(response.content)
353
-
354
- # Load the font
355
- try:
356
- tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
357
- tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
358
- tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
359
- except tk.TclError:
360
- tkFont.nametofont("TkDefaultFont").configure(family="Helvetica", size=10)
361
- tkFont.nametofont("TkTextFont").configure(family="Helvetica", size=10)
362
- tkFont.nametofont("TkHeadingFont").configure(family="Helvetica", size=12)
363
- else:
364
- tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
365
- tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
366
- tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
367
-
368
- def check_and_download_font():
369
- font_name = "Helvetica"
370
- font_dir = "fonts"
371
- font_path = os.path.join(font_dir, "OpenSans-Regular.ttf")
372
-
373
- # Check if the font is already available
374
- available_fonts = list(tkFont.families())
375
- if font_name not in available_fonts:
376
- print(f"Font '{font_name}' not found. Downloading...")
377
- if not os.path.exists(font_dir):
378
- os.makedirs(font_dir)
379
-
380
- if not os.path.exists(font_path):
381
- url = "https://github.com/google/fonts/blob/main/apache/opensans/OpenSans-Regular.ttf?raw=true"
382
- response = requests.get(url)
383
- with open(font_path, "wb") as f:
384
- f.write(response.content)
385
-
386
- # Load the font
387
- try:
388
- tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
389
- tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
390
- tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
391
- except tk.TclError:
392
- tkFont.nametofont("TkDefaultFont").configure(family="Helvetica", size=10)
393
- tkFont.nametofont("TkTextFont").configure(family="Helvetica", size=10)
394
- 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}")
395
335
  else:
396
- tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
397
- tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
398
- tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
399
-
400
- def style_text_boxes_v1(style):
401
- check_and_download_font()
402
- font_style = tkFont.Font(family="Helvetica", size=10) # Define the Helvetica font
403
- style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='#000000', foreground='#ffffff', font=font_style)
404
- style.configure('TCombobox', fieldbackground='#000000', background='#000000', foreground='#ffffff', font=font_style)
405
- style.configure('Custom.TButton', padding='10 10 10 10', borderwidth=1, relief='solid', background='#008080', foreground='#ffffff', font=font_style)
406
- style.map('Custom.TButton',
407
- background=[('active', '#66b2b2'), ('disabled', '#004d4d'), ('!disabled', '#008080')],
408
- foreground=[('active', '#ffffff'), ('disabled', '#888888')])
409
- style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='#000000', foreground='#ffffff', font=font_style)
410
- style.configure('TCheckbutton', background='black', foreground='#ffffff', indicatoron=False, relief='flat', font=font_style)
411
- 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)
412
340
 
413
- def style_text_boxes(style):
414
- font_style = tkFont.Font(family="Helvetica", size=10)
415
- style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='black', foreground='#ffffff', font=font_style)
416
- style.configure('TCombobox', fieldbackground='black', background='black', foreground='#ffffff', font=font_style)
417
- style.configure('Custom.TButton', padding='10 10 10 10', borderwidth=1, relief='solid', background='#008080', foreground='#ffffff', font=font_style)
418
- style.map('Custom.TButton',
419
- background=[('active', '#66b2b2'), ('disabled', '#004d4d'), ('!disabled', '#008080')],
420
- foreground=[('active', '#ffffff'), ('disabled', '#888888')])
421
- style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='#000000', foreground='#ffffff', font=font_style)
422
- style.configure('TCheckbutton', background='black', foreground='#ffffff', indicatoron=False, relief='flat', font=font_style)
423
- style.map('TCheckbutton', background=[('selected', '#555555'), ('active', '#555555')])
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}")
424
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 = []
425
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)
426
369
 
427
370
  def read_settings_from_csv(csv_file_path):
428
371
  settings = {}
@@ -444,344 +387,321 @@ def update_settings_from_csv(variables, csv_settings):
444
387
  new_settings[key] = (var_type, options, value)
445
388
  return new_settings
446
389
 
447
- def safe_literal_eval(value):
390
+ def parse_list(value):
448
391
  try:
449
- # First, try to evaluate as a literal
450
- return ast.literal_eval(value)
451
- except (ValueError, SyntaxError):
452
- # If it fails, return the value as it is (a string)
453
- return value
454
-
455
- def disable_interactivity(fig):
456
- if hasattr(fig.canvas, 'toolbar'):
457
- fig.canvas.toolbar.pack_forget()
458
-
459
- event_handlers = fig.canvas.callbacks.callbacks
460
- for event, handlers in list(event_handlers.items()):
461
- for handler_id in list(handlers.keys()):
462
- fig.canvas.mpl_disconnect(handler_id)
463
-
464
- class ScrollableFrame(ttk.Frame):
465
- def __init__(self, container, *args, bg='black', **kwargs):
466
- super().__init__(container, *args, **kwargs)
467
- self.configure(style='TFrame') # Ensure this uses the styled frame from dark mode
468
-
469
- canvas = tk.Canvas(self, bg=bg) # Set canvas background to match dark mode
470
- scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
471
-
472
- self.scrollable_frame = ttk.Frame(canvas, style='TFrame') # Ensure it uses the styled frame
473
- self.scrollable_frame.bind(
474
- "<Configure>",
475
- lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
476
- )
477
-
478
- canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
479
- canvas.configure(yscrollcommand=scrollbar.set)
480
-
481
- canvas.pack(side="left", fill="both", expand=True)
482
- scrollbar.pack(side="right", fill="y")
483
-
484
- class StdoutRedirector_v1(object):
485
- def __init__(self, text_widget):
486
- self.text_widget = text_widget
487
-
488
- def write(self, string):
489
- self.text_widget.insert(tk.END, string)
490
- self.text_widget.see(tk.END)
491
-
492
- def flush(self):
493
- pass
494
-
495
- class StdoutRedirector:
496
- def __init__(self, text_widget):
497
- self.text_widget = text_widget
498
-
499
- def write(self, string):
500
- try:
501
- if self.text_widget.winfo_exists():
502
- self.text_widget.insert(tk.END, string)
503
- self.text_widget.see(tk.END)
504
- except tk.TclError:
505
- pass # Handle or log the error as needed
506
-
507
- def flush(self):
508
- pass
509
-
510
- def check_mask_gui_settings(vars_dict):
511
- settings = {}
512
- for key, var in vars_dict.items():
513
- value = var.get()
514
-
515
- # Handle conversion for specific types if necessary
516
- if key in ['channels', 'timelapse_frame_limits']: # Assuming these should be lists
517
- try:
518
- # Convert string representation of a list into an actual list
519
- settings[key] = eval(value)
520
- except:
521
- messagebox.showerror("Error", f"Invalid format for {key}. Please enter a valid list.")
522
- return
523
- elif key in ['nucleus_channel', 'cell_channel', 'pathogen_channel', 'examples_to_plot', 'batch_size', 'timelapse_memory', 'workers', 'fps', 'magnification']: # Assuming these should be integers
524
- try:
525
- settings[key] = int(value) if value else None
526
- except ValueError:
527
- messagebox.showerror("Error", f"Invalid number for {key}.")
528
- return
529
- 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
530
- try:
531
- settings[key] = float(value) if value else None
532
- except ValueError:
533
- messagebox.showerror("Error", f"Invalid number for {key}.")
534
- return
392
+ parsed_value = ast.literal_eval(value)
393
+ if isinstance(parsed_value, list):
394
+ return parsed_value
535
395
  else:
536
- settings[key] = value
537
- return settings
396
+ raise ValueError
397
+ except (ValueError, SyntaxError):
398
+ raise ValueError("Invalid format for list")
399
+
538
400
 
539
- def check_measure_gui_settings(vars_dict):
401
+ def check_settings(vars_dict):
402
+ global q
540
403
  settings = {}
541
- for key, var in vars_dict.items():
542
- 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
+ }
543
654
 
544
- try:
545
- if key in ['channels', 'png_dims']:
546
- 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"]:
547
658
 
548
- elif key in ['cell_loc', 'pathogen_loc', 'treatment_loc']:
549
- # Convert to a list of lists of strings, ensuring all structures are lists.
550
- settings[key] = [list(map(str, sublist)) for sublist in ast.literal_eval(value)] if value else []
551
-
552
- elif key == 'dialate_png_ratios':
553
- settings[key] = [float(num) for num in ast.literal_eval(value)] if value else []
554
-
555
- elif key == 'normalize':
556
- settings[key] = [int(num) for num in ast.literal_eval(value)] if value else []
557
-
558
- # Directly assign string values for these specific keys
559
- elif key in ['normalize_by', 'experiment', 'measurement', 'input_folder']:
560
- settings[key] = value
561
-
562
- elif key == 'png_size':
563
- settings[key] = [list(map(int, dim)) for dim in ast.literal_eval(value)] if value else []
564
-
565
- # Ensure these are lists of strings, converting from tuples if necessary
566
- elif key in ['timelapse_objects', 'crop_mode', 'cells', 'pathogens', 'treatments']:
567
- eval_value = ast.literal_eval(value) if value else []
568
- settings[key] = list(map(str, eval_value)) if isinstance(eval_value, (list, tuple)) else [str(eval_value)]
569
-
570
- # Handling for single non-string values (int, float, bool)
571
- 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']:
572
- settings[key] = int(value) if value else None
573
-
574
- elif key == 'um_per_pixel':
575
- settings[key] = float(value) if value else None
576
-
577
- # Handling boolean values based on checkboxes
578
- elif key in ['save_png', 'use_bounding_box', 'save_measurements', 'plot', 'plot_filtration', 'include_uninfected', 'dialate_pngs', 'timelapse', 'representative_images']:
579
- settings[key] = var.get()
580
-
581
- except SyntaxError as e:
582
- print(f"Syntax error processing {key}: {str(e)}")
583
- #messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
584
- return None
585
- except Exception as e:
586
- print(f"Error processing {key}: {str(e)}")
587
- #messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
588
- return None
589
-
590
- return settings
591
-
592
- def check_classify_gui_settings(vars_dict):
593
- settings = {}
594
- for key, var in vars_dict.items():
595
- value = var.get() # This retrieves the string representation for entries or the actual value for checkboxes and combos
659
+ q.put(f"Key {key} not found in expected types.")
660
+ continue
596
661
 
597
- try:
598
- if key in ['src', 'measurement']:
599
- # Directly assign string values
600
- settings[key] = str(value)
601
- elif key in ['cell_mask_dim', 'image_size', 'batch_size', 'epochs', 'gradient_accumulation_steps', 'num_workers']:
602
- # Convert to integer
603
- settings[key] = int(value)
604
- elif key in ['val_split', 'learning_rate', 'weight_decay', 'dropout_rate']:
605
- # Convert to float
606
- settings[key] = float(value)
607
- elif key == 'classes':
608
- # Evaluate as list
609
- settings[key] = ast.literal_eval(value)
610
-
611
- elif key in ['model_type','optimizer_type','schedule','loss_type','train_mode']:
612
- settings[key] = value
613
-
614
- elif key in ['gradient_accumulation','normalize','save','plot', 'init_weights','amsgrad','use_checkpoint','intermedeate_save','pin_memory', 'num_workers','verbose']:
615
- settings[key] = bool(value)
616
-
617
- except SyntaxError as e:
618
- messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
619
- return None
620
- except Exception as e:
621
- messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
622
- return None
623
-
624
- return settings
625
-
626
- def check_sim_gui_settings(vars_dict):
627
- settings = {}
628
- for key, var in vars_dict.items():
629
- 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)
630
664
 
631
665
  try:
632
- if key in ['src', 'name', 'variable']:
633
- # Directly assign string values
634
- settings[key] = str(value)
635
-
636
- elif key in ['nr_plates', 'number_of_genes','number_of_active_genes','avg_genes_per_well','avg_cells_per_well','avg_reads_per_gene']:
637
- #generate list of integers from list
638
- ls = [int(num) for num in ast.literal_eval(value)]
639
- if len(ls) == 3 and ls[2] > 0:
640
- list_of_integers = list(range(ls[0], ls[1], ls[2]))
641
- 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")
642
673
  else:
643
- list_of_integers = [ls[0]]
644
- settings[key] = list_of_integers
645
-
646
- elif key in ['sequencing_error','well_ineq_coeff','gene_ineq_coeff', 'positive_mean']:
647
- #generate list of floats from list
648
- ls = [float(num) for num in ast.literal_eval(value)]
649
- if len(ls) == 3 and ls[2] > 0:
650
- list_of_floats = np.linspace(ls[0], ls[1], ls[2])
651
- list_of_floats.tolist()
652
- 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
653
694
  else:
654
- list_of_floats = [ls[0]]
655
- settings[key] = list_of_floats
656
-
657
- elif key in ['plot', 'random_seed']:
658
- # Evaluate as bool
659
- settings[key] = bool(value)
660
-
661
- elif key in ['number_of_control_genes', 'replicates', 'max_workers']:
662
- # Convert to integer
663
- settings[key] = int(value)
664
-
665
- except SyntaxError as e:
666
- messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
667
- return None
668
- except Exception as e:
669
- messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
670
- return None
671
-
672
- return settings
673
-
674
- def sim_variables():
675
- variables = {
676
- 'name':('entry', None, 'plates_2_4_8'),
677
- 'variable':('entry', None, 'all'),
678
- 'src':('entry', None, '/home/olafsson/Desktop/simulations'),
679
- 'number_of_control_genes':('entry', None, 30),
680
- 'replicates':('entry', None, 4),
681
- 'max_workers':('entry', None, 1),
682
- 'plot':('check', None, True),
683
- 'random_seed':('check', None, True),
684
- 'nr_plates': ('entry', None, '[8,8,0]'),# '[2,2,8]'
685
- 'number_of_genes': ('entry', None, '[100, 100, 0]'), #[1384, 1384, 0]
686
- 'number_of_active_genes': ('entry', None, '[10,10,0]'),
687
- 'avg_genes_per_well': ('entry', None, '[2, 10, 2]'),
688
- 'avg_cells_per_well': ('entry', None, '[100, 100, 0]'),
689
- 'positive_mean': ('entry', None, '[0.8, 0.8, 0]'),
690
- 'avg_reads_per_gene': ('entry', None, '[1000,1000, 0]'),
691
- 'sequencing_error': ('entry', None, '[0.01, 0.01, 0]'),
692
- 'well_ineq_coeff': ('entry', None, '[0.3,0.3,0]'),
693
- 'gene_ineq_coeff': ('entry', None, '[0.8,0.8,0]'),
694
- }
695
- 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
696
702
 
697
- def add_measure_gui_defaults(settings):
698
- settings['compartments'] = ['pathogen', 'cytoplasm']
699
703
  return settings
700
704
 
701
- def measure_variables():
702
- variables = {
703
- 'input_folder':('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui/merged'),
704
- 'channels': ('combo', ['[0,1,2,3]','[0,1,2]','[0,1]','[0]'], '[0,1,2,3]'),
705
- 'cell_mask_dim':('entry', None, 4),
706
- 'cell_min_size':('entry', None, 0),
707
- 'cytoplasm_min_size':('entry', None, 0),
708
- 'nucleus_mask_dim':('entry', None, 5),
709
- 'nucleus_min_size':('entry', None, 0),
710
- 'pathogen_mask_dim':('entry', None, 6),
711
- 'pathogen_min_size':('entry', None, 0),
712
- 'save_png':('check', None, True),
713
- 'crop_mode':('entry', None, '["cell"]'),
714
- 'use_bounding_box':('check', None, True),
715
- 'png_size': ('entry', None, '[[224,224]]'),
716
- 'normalize':('entry', None, '[2,98]'),
717
- 'png_dims':('entry', None, '[1,2,3]'),
718
- 'normalize_by':('combo', ['fov', 'png'], 'png'),
719
- 'save_measurements':('check', None, True),
720
- 'representative_images':('check', None, True),
721
- 'plot':('check', None, True),
722
- 'plot_filtration':('check', None, True),
723
- 'include_uninfected':('check', None, True),
724
- 'dialate_pngs':('check', None, False),
725
- 'dialate_png_ratios':('entry', None, '[0.2]'),
726
- 'timelapse':('check', None, False),
727
- 'timelapse_objects':('combo', ['["cell"]', '["nucleus"]', '["pathogen"]', '["cytoplasm"]'], '["cell"]'),
728
- 'max_workers':('entry', None, 30),
729
- 'experiment':('entry', None, 'experiment name'),
730
- 'cells':('entry', None, ['HeLa']),
731
- 'cell_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
732
- 'pathogens':('entry', None, '["wt","mutant"]'),
733
- 'pathogen_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
734
- 'treatments': ('entry', None, '["cm","lovastatin_20uM"]'),
735
- 'treatment_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
736
- 'channel_of_interest':('entry', None, 3),
737
- 'compartments':('entry', None, '["pathogen","cytoplasm"]'),
738
- 'measurement':('entry', None, 'mean_intensity'),
739
- 'nr_imgs':('entry', None, 32),
740
- 'um_per_pixel':('entry', None, 0.1)
741
- }
742
- return variables
743
-
744
- def classify_variables():
745
-
746
- def get_torchvision_models():
747
- # Fetch all public callable attributes from torchvision.models that are functions
748
- model_names = [name for name, obj in inspect.getmembers(models)
749
- if inspect.isfunction(obj) and not name.startswith("__")]
750
- return model_names
751
-
752
- model_names = get_torchvision_models()
753
- variables = {
754
- 'src':('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui/merged'),
755
- 'cell_mask_dim':('entry', None, 4),
756
- 'classes':('entry', None, '["nc","pc"]'),
757
- 'measurement':('entry', None, 'mean_intensity'),
758
- 'model_type': ('combo', model_names, 'resnet50'),
759
- 'optimizer_type': ('combo', ['adamw','adam'], 'adamw'),
760
- 'schedule': ('combo', ['reduce_lr_on_plateau','step_lr'], 'reduce_lr_on_plateau'),
761
- 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
762
- 'image_size': ('entry', None, 224),
763
- 'batch_size': ('entry', None, 12),
764
- 'epochs': ('entry', None, 2),
765
- 'val_split': ('entry', None, 0.1),
766
- 'train_mode': ('combo', ['erm', 'irm'], 'erm'),
767
- 'learning_rate': ('entry', None, 0.0001),
768
- 'weight_decay': ('entry', None, 0.00001),
769
- 'dropout_rate': ('entry', None, 0.1),
770
- 'gradient_accumulation': ('check', None, True),
771
- 'gradient_accumulation_steps': ('entry', None, 4),
772
- 'normalize': ('check', None, True),
773
- 'save': ('check', None, True),
774
- 'plot': ('check', None, True),
775
- 'init_weights': ('check', None, True),
776
- 'amsgrad': ('check', None, True),
777
- 'use_checkpoint': ('check', None, True),
778
- 'intermedeate_save': ('check', None, True),
779
- 'pin_memory': ('check', None, True),
780
- 'num_workers': ('entry', None, 30),
781
- 'verbose': ('check', None, True),
782
- }
783
- return variables
784
-
785
705
  def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
786
706
  label = ttk.Label(frame, text=label_text, style='Custom.TLabel') # Apply Custom.TLabel style for labels
787
707
  label.grid(column=0, row=row, sticky=tk.W, padx=5, pady=5)
@@ -793,7 +713,7 @@ def create_input_field(frame, label_text, row, var_type='entry', options=None, d
793
713
  return (label, entry, var) # Return both the label and the entry, and the variable
794
714
  elif var_type == 'check':
795
715
  var = tk.BooleanVar(value=default_value) # Set default value (True/False)
796
- check = ToggleSwitch(frame, text=label_text, variable=var) # Use ToggleSwitch class
716
+ check = Checkbutton(frame, text="", variable=var)
797
717
  check.grid(column=1, row=row, sticky=tk.W, padx=5)
798
718
  return (label, check, var) # Return both the label and the checkbutton, and the variable
799
719
  elif var_type == 'combo':
@@ -806,75 +726,13 @@ def create_input_field(frame, label_text, row, var_type='entry', options=None, d
806
726
  else:
807
727
  var = None # Placeholder in case of an undefined var_type
808
728
  return (label, None, var)
809
-
810
- def mask_variables():
811
- variables = {
812
- 'src': ('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui'),
813
- 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
814
- 'custom_regex': ('entry', None, None),
815
- 'experiment': ('entry', None, 'exp'),
816
- 'channels': ('combo', ['[0,1,2,3]','[0,1,2]','[0,1]','[0]'], '[0,1,2,3]'),
817
- 'magnification': ('combo', [20, 40, 60,], 40),
818
- 'nucleus_channel': ('combo', [0,1,2,3, None], 0),
819
- 'nucleus_background': ('entry', None, 100),
820
- 'nucleus_Signal_to_noise': ('entry', None, 5),
821
- 'nucleus_CP_prob': ('entry', None, 0),
822
- 'cell_channel': ('combo', [0,1,2,3, None], 3),
823
- 'cell_background': ('entry', None, 100),
824
- 'cell_Signal_to_noise': ('entry', None, 5),
825
- 'cell_CP_prob': ('entry', None, 0),
826
- 'pathogen_channel': ('combo', [0,1,2,3, None], 2),
827
- 'pathogen_background': ('entry', None, 100),
828
- 'pathogen_Signal_to_noise': ('entry', None, 3),
829
- 'pathogen_CP_prob': ('entry', None, 0),
830
- 'preprocess': ('check', None, True),
831
- 'masks': ('check', None, True),
832
- 'examples_to_plot': ('entry', None, 1),
833
- 'randomize': ('check', None, True),
834
- 'batch_size': ('entry', None, 50),
835
- 'timelapse': ('check', None, False),
836
- 'timelapse_displacement': ('entry', None, None),
837
- 'timelapse_memory': ('entry', None, 0),
838
- 'timelapse_frame_limits': ('entry', None, '[0,1000]'),
839
- 'timelapse_remove_transient': ('check', None, True),
840
- 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
841
- 'timelapse_objects': ('combo', ['cell','nucleus','pathogen','cytoplasm', None], None),
842
- 'fps': ('entry', None, 2),
843
- 'remove_background': ('check', None, True),
844
- 'lower_quantile': ('entry', None, 0.01),
845
- 'merge': ('check', None, False),
846
- 'normalize_plots': ('check', None, True),
847
- 'all_to_mip': ('check', None, False),
848
- 'pick_slice': ('check', None, False),
849
- 'skip_mode': ('entry', None, None),
850
- 'save': ('check', None, True),
851
- 'plot': ('check', None, True),
852
- 'workers': ('entry', None, 30),
853
- 'verbose': ('check', None, True),
854
- }
855
- return variables
856
-
857
- def add_mask_gui_defaults(settings):
858
- settings['remove_background'] = True
859
- settings['fps'] = 2
860
- settings['all_to_mip'] = False
861
- settings['pick_slice'] = False
862
- settings['merge'] = False
863
- settings['skip_mode'] = ''
864
- settings['verbose'] = False
865
- settings['normalize_plots'] = True
866
- settings['randomize'] = True
867
- settings['preprocess'] = True
868
- settings['masks'] = True
869
- settings['examples_to_plot'] = 1
870
- return settings
871
-
729
+
872
730
  def generate_fields(variables, scrollable_frame):
731
+ row = 1
873
732
  vars_dict = {}
874
- row = 0
875
733
  tooltips = {
876
734
  "src": "Path to the folder containing the images.",
877
- "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.",
878
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'.",
879
737
  "experiment": "Name of the experiment. This will be used to name the output files.",
880
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.",
@@ -883,17 +741,20 @@ def generate_fields(variables, scrollable_frame):
883
741
  "nucleus_background": "The background intensity for the nucleus channel. This will be used to remove background noise.",
884
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.",
885
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.",
886
745
  "cell_channel": "The channel to use for the cell. If None, the cell will not be segmented.",
887
746
  "cell_background": "The background intensity for the cell channel. This will be used to remove background noise.",
888
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.",
889
- "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.",
890
750
  "pathogen_channel": "The channel to use for the pathogen. If None, the pathogen will not be segmented.",
891
751
  "pathogen_background": "The background intensity for the pathogen channel. This will be used to remove background noise.",
892
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.",
893
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.",
894
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.",
895
756
  "masks": "Whether to generate masks for the segmented objects. If True, masks will be generated for the nucleus, cell, and pathogen.",
896
- "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.",
897
758
  "randomize": "Whether to randomize the order of the images before processing. Recommended to avoid bias in the segmentation.",
898
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.",
899
760
  "timelapse": "Whether to process the images as a timelapse.",
@@ -905,41 +766,41 @@ def generate_fields(variables, scrollable_frame):
905
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.",
906
767
  "fps": "Frames per second of the automatically generated timelapse movies.",
907
768
  "remove_background": "Whether to remove background noise from the images. This will help improve the quality of the segmentation.",
908
- "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.",
909
- "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.",
910
771
  "normalize_plots": "Whether to normalize the plots.",
911
772
  "all_to_mip": "Whether to convert all images to maximum intensity projections before processing.",
912
773
  "pick_slice": "Whether to pick a single slice from the z-stack images. If False, the maximum intensity projection will be used.",
913
774
  "skip_mode": "The mode to use for skipping images. This will determine how to handle images that cannot be processed.",
914
775
  "save": "Whether to save the results to disk.",
776
+ "merge_edge_pathogen_cells": "Whether to merge cells that share pathogen objects.",
915
777
  "plot": "Whether to plot the results.",
916
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.",
917
779
  "verbose": "Whether to print verbose output during processing.",
918
780
  "input_folder": "Path to the folder containing the images.",
919
781
  "cell_mask_dim": "The dimension of the array the cell mask is saved in.",
920
- "cell_min_size": "The minimum size of cell objects in pixels2.",
921
- "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.",
922
785
  "nucleus_mask_dim": "The dimension of the array the nucleus mask is saved in.",
923
- "nucleus_min_size": "The minimum size of nucleus objects in pixels2.",
786
+ "nucleus_min_size": "The minimum size of nucleus objects in pixels^2.",
924
787
  "pathogen_mask_dim": "The dimension of the array the pathogen mask is saved in.",
925
- "pathogen_min_size": "The minimum size of pathogen objects in pixels2.",
788
+ "pathogen_min_size": "The minimum size of pathogen objects in pixels^2.",
926
789
  "save_png": "Whether to save the segmented objects as PNG images.",
927
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).",
928
791
  "use_bounding_box": "Whether to use the bounding box of the objects for cropping. If False, only the object itself will be cropped.",
929
792
  "png_size": "The size of the PNG images to save. This will determine the size of the saved images.",
930
- "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.",
931
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].",
932
795
  "normalize_by": "Whether to normalize the images by field of view (fov) or by PNG image (png).",
933
796
  "save_measurements": "Whether to save the measurements to disk.",
934
797
  "representative_images": "Whether to save representative images of the segmented objects (Not working yet).",
935
- "plot": "Whether to plot results.",
936
798
  "plot_filtration": "Whether to plot the filtration steps.",
937
799
  "include_uninfected": "Whether to include uninfected cells in the analysis.",
938
- "dialate_pngs": "Whether to dialate the PNG images before saving.",
939
- "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.",
940
- "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.",
941
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.",
942
- "cells: ": "The cell types to include in the analysis.",
803
+ "cells": "The cell types to include in the analysis.",
943
804
  "cell_loc": "The locations of the cell types in the images.",
944
805
  "pathogens": "The pathogen types to include in the analysis.",
945
806
  "pathogen_loc": "The locations of the pathogen types in the images.",
@@ -949,7 +810,7 @@ def generate_fields(variables, scrollable_frame):
949
810
  "compartments": "The compartments to measure in the images.",
950
811
  "measurement": "The measurement to use for the analysis.",
951
812
  "nr_imgs": "The number of images to plot.",
952
- "um_per_pixel": "The micrometers per pixel for the images.",
813
+ "um_per_pixel": "The micrometers per pixel for the images."
953
814
  }
954
815
 
955
816
  for key, (var_type, options, default_value) in variables.items():
@@ -959,7 +820,6 @@ def generate_fields(variables, scrollable_frame):
959
820
  # Add tooltip to the label if it exists in the tooltips dictionary
960
821
  if key in tooltips:
961
822
  ToolTip(label, tooltips[key])
962
-
963
823
  row += 1
964
824
  return vars_dict
965
825
 
@@ -993,12 +853,6 @@ def create_dark_mode(root, style, console_output):
993
853
  if console_output != None:
994
854
  console_output.config(bg=dark_bg, fg=light_text, insertbackground=light_text) #, font=("Helvetica", 12)
995
855
  root.configure(bg=dark_bg)
996
-
997
- def set_dark_style(style):
998
- style.configure('TFrame', background='black')
999
- style.configure('TLabel', background='black', foreground='white')
1000
- style.configure('TEntry', background='black', foreground='white')
1001
- style.configure('TCheckbutton', background='black', foreground='white')
1002
856
 
1003
857
  ##@log_function_call
1004
858
  def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
@@ -1113,7 +967,7 @@ def preprocess_generate_masks_wrapper(settings, q, fig_queue):
1113
967
  plt.show = my_show
1114
968
 
1115
969
  try:
1116
- spacr.core.preprocess_generate_masks(settings['src'], settings=settings)
970
+ spacr.core.preprocess_generate_masks(src=settings['src'], settings=settings)
1117
971
  except Exception as e:
1118
972
  errorMessage = f"Error during processing: {e}"
1119
973
  q.put(errorMessage) # Send the error message to the GUI via the queue
@@ -1182,4 +1036,490 @@ def run_multiple_simulations_wrapper(settings, q, fig_queue):
1182
1036
  q.put(errorMessage) # Send the error message to the GUI via the queue
1183
1037
  traceback.print_exc()
1184
1038
  finally:
1185
- 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()