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