spacr 0.0.1__py3-none-any.whl → 0.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spacr/__init__.py +6 -2
- spacr/__main__.py +0 -2
- spacr/alpha.py +807 -0
- spacr/annotate_app.py +118 -120
- spacr/chris.py +50 -0
- spacr/cli.py +25 -187
- spacr/core.py +1611 -389
- spacr/deep_spacr.py +696 -0
- spacr/foldseek.py +779 -0
- spacr/get_alfafold_structures.py +72 -0
- spacr/graph_learning.py +320 -0
- spacr/graph_learning_lap.py +84 -0
- spacr/gui.py +145 -0
- spacr/gui_2.py +90 -0
- spacr/gui_classify_app.py +187 -0
- spacr/gui_mask_app.py +149 -174
- spacr/gui_measure_app.py +116 -109
- spacr/gui_sim_app.py +0 -0
- spacr/gui_utils.py +679 -139
- spacr/io.py +620 -469
- spacr/mask_app.py +116 -9
- spacr/measure.py +178 -84
- spacr/models/cp/toxo_pv_lumen.CP_model +0 -0
- spacr/old_code.py +255 -1
- spacr/plot.py +263 -100
- spacr/sequencing.py +1130 -0
- spacr/sim.py +634 -122
- spacr/timelapse.py +343 -53
- spacr/train.py +195 -22
- spacr/umap.py +0 -689
- spacr/utils.py +1530 -188
- spacr-0.0.6.dist-info/METADATA +118 -0
- spacr-0.0.6.dist-info/RECORD +39 -0
- {spacr-0.0.1.dist-info → spacr-0.0.6.dist-info}/WHEEL +1 -1
- spacr-0.0.6.dist-info/entry_points.txt +9 -0
- spacr-0.0.1.dist-info/METADATA +0 -64
- spacr-0.0.1.dist-info/RECORD +0 -26
- spacr-0.0.1.dist-info/entry_points.txt +0 -5
- {spacr-0.0.1.dist-info → spacr-0.0.6.dist-info}/LICENSE +0 -0
- {spacr-0.0.1.dist-info → spacr-0.0.6.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py
CHANGED
@@ -1,23 +1,304 @@
|
|
1
|
+
import os, spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib, re, csv, requests
|
2
|
+
import matplotlib.pyplot as plt
|
3
|
+
matplotlib.use('Agg')
|
4
|
+
import numpy as np
|
1
5
|
import tkinter as tk
|
2
6
|
from tkinter import ttk, messagebox
|
3
|
-
|
4
|
-
import
|
5
|
-
#
|
6
|
-
|
7
|
-
import
|
8
|
-
|
9
|
-
|
10
|
-
import
|
11
|
-
import io
|
12
|
-
import traceback
|
13
|
-
import spacr
|
7
|
+
import tkinter.font as tkFont
|
8
|
+
from torchvision import models
|
9
|
+
#from ttf_opensans import opensans
|
10
|
+
|
11
|
+
from tkinter import font as tkFont
|
12
|
+
|
13
|
+
|
14
|
+
from .logger import log_function_call
|
14
15
|
|
15
16
|
try:
|
16
17
|
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
17
18
|
except AttributeError:
|
18
19
|
pass
|
19
20
|
|
20
|
-
|
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
|
+
|
44
|
+
def load_app(root, app_name, app_func):
|
45
|
+
# Destroy the current window
|
46
|
+
root.destroy()
|
47
|
+
# Create a new window for the app
|
48
|
+
app_window = tk.Tk()
|
49
|
+
app_window.title(f"SpaCr - {app_name}")
|
50
|
+
app_window.geometry("1200x800")
|
51
|
+
#app_window.attributes('-fullscreen', True)
|
52
|
+
app_window.configure(bg="black")
|
53
|
+
create_menu_bar(app_window) # Add menu to the new window
|
54
|
+
app_func(app_window, app_window.winfo_width(), app_window.winfo_height())
|
55
|
+
|
56
|
+
def create_menu_bar(root):
|
57
|
+
|
58
|
+
from .gui_mask_app import initiate_mask_root
|
59
|
+
from .gui_measure_app import initiate_measure_root
|
60
|
+
from .annotate_app import initiate_annotation_app_root
|
61
|
+
from .mask_app import initiate_mask_app_root
|
62
|
+
from .gui_classify_app import initiate_classify_root
|
63
|
+
|
64
|
+
gui_apps = {
|
65
|
+
"Mask": initiate_mask_root,
|
66
|
+
"Measure": initiate_measure_root,
|
67
|
+
"Annotate": initiate_annotation_app_root,
|
68
|
+
"Make Masks": initiate_mask_app_root,
|
69
|
+
"Classify": initiate_classify_root
|
70
|
+
}
|
71
|
+
# Create the menu bar
|
72
|
+
menu_bar = tk.Menu(root, bg="#008080", fg="white")
|
73
|
+
# Create a "SpaCr Applications" menu
|
74
|
+
app_menu = tk.Menu(menu_bar, tearoff=0, bg="#008080", fg="white")
|
75
|
+
menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
|
76
|
+
# Add options to the "SpaCr Applications" menu
|
77
|
+
for app_name, app_func in gui_apps.items():
|
78
|
+
app_menu.add_command(label=app_name, command=lambda app_name=app_name, app_func=app_func: load_app(root, app_name, app_func))
|
79
|
+
# Add a separator and an exit option
|
80
|
+
app_menu.add_separator()
|
81
|
+
app_menu.add_command(label="Exit", command=root.quit)
|
82
|
+
# Configure the menu for the root window
|
83
|
+
root.config(menu=menu_bar)
|
84
|
+
|
85
|
+
class CustomButton(tk.Frame):
|
86
|
+
def __init__(self, parent, text="", command=None, *args, **kwargs):
|
87
|
+
super().__init__(parent, *args, **kwargs)
|
88
|
+
self.text = text
|
89
|
+
self.command = command
|
90
|
+
|
91
|
+
self.canvas = tk.Canvas(self, width=150, height=50, highlightthickness=0, bg="black")
|
92
|
+
self.canvas.grid(row=0, column=0)
|
93
|
+
|
94
|
+
self.button_bg = self.create_rounded_rectangle(0, 0, 150, 50, radius=20, fill="#800080")
|
95
|
+
|
96
|
+
# Create a Tkinter font object using OpenSans
|
97
|
+
self.font_style = tkFont.Font(family="Arial", size=12, weight=tkFont.NORMAL)
|
98
|
+
self.button_text = self.canvas.create_text(75, 25, text=self.text, fill="white", font=self.font_style)
|
99
|
+
|
100
|
+
self.bind("<Enter>", self.on_enter)
|
101
|
+
self.bind("<Leave>", self.on_leave)
|
102
|
+
self.bind("<Button-1>", self.on_click)
|
103
|
+
self.canvas.bind("<Enter>", self.on_enter)
|
104
|
+
self.canvas.bind("<Leave>", self.on_leave)
|
105
|
+
self.canvas.bind("<Button-1>", self.on_click)
|
106
|
+
|
107
|
+
def on_enter(self, event=None):
|
108
|
+
self.canvas.itemconfig(self.button_bg, fill="#993399")
|
109
|
+
|
110
|
+
def on_leave(self, event=None):
|
111
|
+
self.canvas.itemconfig(self.button_bg, fill="#800080")
|
112
|
+
|
113
|
+
def on_click(self, event=None):
|
114
|
+
if self.command:
|
115
|
+
self.command()
|
116
|
+
|
117
|
+
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=20, **kwargs):
|
118
|
+
points = [x1 + radius, y1,
|
119
|
+
x1 + radius, y1,
|
120
|
+
x2 - radius, y1,
|
121
|
+
x2 - radius, y1,
|
122
|
+
x2, y1,
|
123
|
+
x2, y1 + radius,
|
124
|
+
x2, y1 + radius,
|
125
|
+
x2, y2 - radius,
|
126
|
+
x2, y2 - radius,
|
127
|
+
x2, y2,
|
128
|
+
x2 - radius, y2,
|
129
|
+
x2 - radius, y2,
|
130
|
+
x1 + radius, y2,
|
131
|
+
x1 + radius, y2,
|
132
|
+
x1, y2,
|
133
|
+
x1, y2 - radius,
|
134
|
+
x1, y2 - radius,
|
135
|
+
x1, y1 + radius,
|
136
|
+
x1, y1 + radius,
|
137
|
+
x1, y1]
|
138
|
+
|
139
|
+
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
140
|
+
|
141
|
+
class ToggleSwitch(ttk.Frame):
|
142
|
+
def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
|
143
|
+
super().__init__(parent, *args, **kwargs)
|
144
|
+
self.text = text
|
145
|
+
self.variable = variable if variable else tk.BooleanVar()
|
146
|
+
self.command = command
|
147
|
+
|
148
|
+
self.canvas = tk.Canvas(self, width=40, height=20, highlightthickness=0, bd=0, bg="black")
|
149
|
+
self.canvas.grid(row=0, column=1, padx=(10, 0))
|
150
|
+
|
151
|
+
# Background rounded rectangle with smaller dimensions and no outline
|
152
|
+
self.switch_bg = self.create_rounded_rectangle(2, 2, 38, 18, radius=9, outline="", fill="#fff")
|
153
|
+
|
154
|
+
# Switch ball with no outline
|
155
|
+
self.switch = self.canvas.create_oval(4, 4, 16, 16, outline="", fill="#800080") # Purple initially
|
156
|
+
|
157
|
+
self.label = ttk.Label(self, text=self.text, background="black", foreground="white")
|
158
|
+
self.label.grid(row=0, column=0, padx=(0, 10))
|
159
|
+
|
160
|
+
self.bind("<Button-1>", self.toggle)
|
161
|
+
self.canvas.bind("<Button-1>", self.toggle)
|
162
|
+
self.label.bind("<Button-1>", self.toggle)
|
163
|
+
|
164
|
+
self.update_switch()
|
165
|
+
|
166
|
+
def toggle(self, event=None):
|
167
|
+
self.variable.set(not self.variable.get())
|
168
|
+
self.animate_switch()
|
169
|
+
if self.command:
|
170
|
+
self.command()
|
171
|
+
|
172
|
+
def update_switch(self):
|
173
|
+
if self.variable.get():
|
174
|
+
self.canvas.itemconfig(self.switch, fill="#008080") # Teal
|
175
|
+
self.canvas.coords(self.switch, 24, 4, 36, 16) # Move switch to the right
|
176
|
+
else:
|
177
|
+
self.canvas.itemconfig(self.switch, fill="#800080") # Purple
|
178
|
+
self.canvas.coords(self.switch, 4, 4, 16, 16) # Move switch to the left
|
179
|
+
|
180
|
+
def animate_switch(self):
|
181
|
+
if self.variable.get():
|
182
|
+
start_x, end_x = 4, 24
|
183
|
+
final_color = "#008080" # Teal
|
184
|
+
else:
|
185
|
+
start_x, end_x = 24, 4
|
186
|
+
final_color = "#800080" # Purple
|
187
|
+
|
188
|
+
self.animate_movement(start_x, end_x, final_color)
|
189
|
+
|
190
|
+
def animate_movement(self, start_x, end_x, final_color):
|
191
|
+
step = 1 if start_x < end_x else -1
|
192
|
+
for i in range(start_x, end_x, step):
|
193
|
+
self.canvas.coords(self.switch, i, 4, i + 12, 16)
|
194
|
+
self.canvas.update()
|
195
|
+
self.after(10) # Small delay for smooth animation
|
196
|
+
self.canvas.itemconfig(self.switch, fill=final_color)
|
197
|
+
|
198
|
+
def get(self):
|
199
|
+
return self.variable.get()
|
200
|
+
|
201
|
+
def set(self, value):
|
202
|
+
self.variable.set(value)
|
203
|
+
self.update_switch()
|
204
|
+
|
205
|
+
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=9, **kwargs): # Smaller radius for smaller switch
|
206
|
+
points = [x1 + radius, y1,
|
207
|
+
x1 + radius, y1,
|
208
|
+
x2 - radius, y1,
|
209
|
+
x2 - radius, y1,
|
210
|
+
x2, y1,
|
211
|
+
x2, y1 + radius,
|
212
|
+
x2, y1 + radius,
|
213
|
+
x2, y2 - radius,
|
214
|
+
x2, y2 - radius,
|
215
|
+
x2, y2,
|
216
|
+
x2 - radius, y2,
|
217
|
+
x2 - radius, y2,
|
218
|
+
x1 + radius, y2,
|
219
|
+
x1 + radius, y2,
|
220
|
+
x1, y2,
|
221
|
+
x1, y2 - radius,
|
222
|
+
x1, y2 - radius,
|
223
|
+
x1, y1 + radius,
|
224
|
+
x1, y1 + radius,
|
225
|
+
x1, y1]
|
226
|
+
|
227
|
+
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
228
|
+
|
229
|
+
def set_default_font(root, font_name="Arial", size=12):
|
230
|
+
default_font = (font_name, size)
|
231
|
+
root.option_add("*Font", default_font)
|
232
|
+
root.option_add("*TButton.Font", default_font)
|
233
|
+
root.option_add("*TLabel.Font", default_font)
|
234
|
+
root.option_add("*TEntry.Font", default_font)
|
235
|
+
|
236
|
+
def check_and_download_font():
|
237
|
+
font_name = "Arial"
|
238
|
+
font_dir = "fonts"
|
239
|
+
font_path = os.path.join(font_dir, "OpenSans-Regular.ttf")
|
240
|
+
|
241
|
+
# Check if the font is already available
|
242
|
+
available_fonts = list(tkFont.families())
|
243
|
+
if font_name not in available_fonts:
|
244
|
+
print(f"Font '{font_name}' not found. Downloading...")
|
245
|
+
if not os.path.exists(font_dir):
|
246
|
+
os.makedirs(font_dir)
|
247
|
+
|
248
|
+
if not os.path.exists(font_path):
|
249
|
+
url = "https://github.com/google/fonts/blob/main/apache/opensans/OpenSans-Regular.ttf?raw=true"
|
250
|
+
response = requests.get(url)
|
251
|
+
with open(font_path, "wb") as f:
|
252
|
+
f.write(response.content)
|
253
|
+
|
254
|
+
# Load the font
|
255
|
+
try:
|
256
|
+
tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
|
257
|
+
tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
|
258
|
+
tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
|
259
|
+
except tk.TclError:
|
260
|
+
tkFont.nametofont("TkDefaultFont").configure(family="Arial", size=10)
|
261
|
+
tkFont.nametofont("TkTextFont").configure(family="Arial", size=10)
|
262
|
+
tkFont.nametofont("TkHeadingFont").configure(family="Arial", size=12)
|
263
|
+
else:
|
264
|
+
tkFont.nametofont("TkDefaultFont").configure(family=font_name, size=10)
|
265
|
+
tkFont.nametofont("TkTextFont").configure(family=font_name, size=10)
|
266
|
+
tkFont.nametofont("TkHeadingFont").configure(family=font_name, size=12)
|
267
|
+
|
268
|
+
def style_text_boxes(style):
|
269
|
+
check_and_download_font()
|
270
|
+
font_style = tkFont.Font(family="Arial", size=10) # Define the Arial font
|
271
|
+
style.configure('TEntry', padding='5 5 5 5', borderwidth=1, relief='solid', fieldbackground='#000000', foreground='#ffffff', font=font_style)
|
272
|
+
style.configure('TCombobox', fieldbackground='#000000', background='#000000', foreground='#ffffff', font=font_style)
|
273
|
+
style.configure('Custom.TButton', padding='10 10 10 10', borderwidth=1, relief='solid', background='#008080', foreground='#ffffff', font=font_style)
|
274
|
+
style.map('Custom.TButton',
|
275
|
+
background=[('active', '#66b2b2'), ('disabled', '#004d4d'), ('!disabled', '#008080')],
|
276
|
+
foreground=[('active', '#ffffff'), ('disabled', '#888888')])
|
277
|
+
style.configure('Custom.TLabel', padding='5 5 5 5', borderwidth=1, relief='flat', background='#000000', foreground='#ffffff', font=font_style)
|
278
|
+
style.configure('TCheckbutton', background='#333333', foreground='#ffffff', indicatoron=False, relief='flat', font=font_style)
|
279
|
+
style.map('TCheckbutton', background=[('selected', '#555555'), ('active', '#555555')])
|
280
|
+
|
281
|
+
|
282
|
+
|
283
|
+
def read_settings_from_csv(csv_file_path):
|
284
|
+
settings = {}
|
285
|
+
with open(csv_file_path, newline='') as csvfile:
|
286
|
+
reader = csv.DictReader(csvfile)
|
287
|
+
for row in reader:
|
288
|
+
key = row['Key']
|
289
|
+
value = row['Value']
|
290
|
+
settings[key] = value
|
291
|
+
return settings
|
292
|
+
|
293
|
+
def update_settings_from_csv(variables, csv_settings):
|
294
|
+
new_settings = variables.copy() # Start with a copy of the original settings
|
295
|
+
for key, value in csv_settings.items():
|
296
|
+
if key in new_settings:
|
297
|
+
# Get the variable type and options from the original settings
|
298
|
+
var_type, options, _ = new_settings[key]
|
299
|
+
# Update the default value with the CSV value, keeping the type and options unchanged
|
300
|
+
new_settings[key] = (var_type, options, value)
|
301
|
+
return new_settings
|
21
302
|
|
22
303
|
def safe_literal_eval(value):
|
23
304
|
try:
|
@@ -36,17 +317,8 @@ def disable_interactivity(fig):
|
|
36
317
|
for handler_id in list(handlers.keys()):
|
37
318
|
fig.canvas.mpl_disconnect(handler_id)
|
38
319
|
|
39
|
-
def set_default_font(app, font_name="Arial Bold", size=10):
|
40
|
-
default_font = nametofont("TkDefaultFont")
|
41
|
-
text_font = nametofont("TkTextFont")
|
42
|
-
fixed_font = nametofont("TkFixedFont")
|
43
|
-
|
44
|
-
# Set the family to Open Sans and size as desired
|
45
|
-
for font in (default_font, text_font, fixed_font):
|
46
|
-
font.config(family=font_name, size=size)
|
47
|
-
|
48
320
|
class ScrollableFrame(ttk.Frame):
|
49
|
-
def __init__(self, container, *args, bg='
|
321
|
+
def __init__(self, container, *args, bg='black', **kwargs):
|
50
322
|
super().__init__(container, *args, **kwargs)
|
51
323
|
self.configure(style='TFrame') # Ensure this uses the styled frame from dark mode
|
52
324
|
|
@@ -105,114 +377,132 @@ def check_mask_gui_settings(vars_dict):
|
|
105
377
|
settings[key] = value
|
106
378
|
return settings
|
107
379
|
|
108
|
-
def
|
380
|
+
def check_measure_gui_settings(vars_dict):
|
109
381
|
settings = {}
|
110
382
|
for key, var in vars_dict.items():
|
111
|
-
value = var.get() #
|
383
|
+
value = var.get() # Retrieves the string representation for entries or the actual value for checkboxes and combos.
|
112
384
|
|
113
385
|
try:
|
114
|
-
|
115
|
-
|
116
|
-
settings[key] = [int(chan) for chan in eval(value)] if value else []
|
386
|
+
if key in ['channels', 'png_dims']:
|
387
|
+
settings[key] = [int(chan) for chan in ast.literal_eval(value)] if value else []
|
117
388
|
|
118
|
-
elif key == 'png_size':
|
119
|
-
temp_val = ast.literal_eval(value) if value else []
|
120
|
-
settings[key] = [list(map(int, dim)) for dim in temp_val] if temp_val else None
|
121
|
-
|
122
389
|
elif key in ['cell_loc', 'pathogen_loc', 'treatment_loc']:
|
123
|
-
|
390
|
+
# Convert to a list of lists of strings, ensuring all structures are lists.
|
391
|
+
settings[key] = [list(map(str, sublist)) for sublist in ast.literal_eval(value)] if value else []
|
124
392
|
|
125
393
|
elif key == 'dialate_png_ratios':
|
126
|
-
settings[key] = [float(num) for num in
|
394
|
+
settings[key] = [float(num) for num in ast.literal_eval(value)] if value else []
|
127
395
|
|
128
|
-
elif key
|
129
|
-
settings[key] =
|
396
|
+
elif key == 'normalize':
|
397
|
+
settings[key] = [int(num) for num in ast.literal_eval(value)] if value else []
|
130
398
|
|
131
|
-
|
132
|
-
|
133
|
-
settings[key] =
|
399
|
+
# Directly assign string values for these specific keys
|
400
|
+
elif key in ['normalize_by', 'experiment', 'measurement', 'input_folder']:
|
401
|
+
settings[key] = value
|
134
402
|
|
135
|
-
|
136
|
-
|
137
|
-
|
403
|
+
elif key == 'png_size':
|
404
|
+
settings[key] = [list(map(int, dim)) for dim in ast.literal_eval(value)] if value else []
|
405
|
+
|
406
|
+
# Ensure these are lists of strings, converting from tuples if necessary
|
407
|
+
elif key in ['timelapse_objects', 'crop_mode', 'cells', 'pathogens', 'treatments']:
|
408
|
+
eval_value = ast.literal_eval(value) if value else []
|
409
|
+
settings[key] = list(map(str, eval_value)) if isinstance(eval_value, (list, tuple)) else [str(eval_value)]
|
138
410
|
|
139
|
-
# Handling for single values
|
411
|
+
# Handling for single non-string values (int, float, bool)
|
140
412
|
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']:
|
141
413
|
settings[key] = int(value) if value else None
|
142
414
|
|
143
415
|
elif key == 'um_per_pixel':
|
144
416
|
settings[key] = float(value) if value else None
|
145
417
|
|
146
|
-
#
|
418
|
+
# Handling boolean values based on checkboxes
|
147
419
|
elif key in ['save_png', 'use_bounding_box', 'save_measurements', 'plot', 'plot_filtration', 'include_uninfected', 'dialate_pngs', 'timelapse', 'representative_images']:
|
148
|
-
settings[key] =
|
149
|
-
|
150
|
-
else:
|
151
|
-
settings[key] = value
|
420
|
+
settings[key] = var.get()
|
152
421
|
|
153
422
|
except SyntaxError as e:
|
154
|
-
|
423
|
+
print(f"Syntax error processing {key}: {str(e)}")
|
424
|
+
#messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
|
155
425
|
return None
|
156
426
|
except Exception as e:
|
157
|
-
|
427
|
+
print(f"Error processing {key}: {str(e)}")
|
428
|
+
#messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
|
158
429
|
return None
|
159
430
|
|
160
431
|
return settings
|
161
432
|
|
162
|
-
def
|
433
|
+
def check_classify_gui_settings(vars_dict):
|
163
434
|
settings = {}
|
164
435
|
for key, var in vars_dict.items():
|
165
436
|
value = var.get() # This retrieves the string representation for entries or the actual value for checkboxes and combos
|
166
437
|
|
167
438
|
try:
|
168
|
-
if key
|
169
|
-
#
|
170
|
-
settings[key] =
|
171
|
-
|
172
|
-
|
173
|
-
settings[key] =
|
174
|
-
|
175
|
-
|
176
|
-
settings[key] =
|
439
|
+
if key in ['src', 'measurement']:
|
440
|
+
# Directly assign string values
|
441
|
+
settings[key] = str(value)
|
442
|
+
elif key in ['cell_mask_dim', 'image_size', 'batch_size', 'epochs', 'gradient_accumulation_steps', 'num_workers']:
|
443
|
+
# Convert to integer
|
444
|
+
settings[key] = int(value)
|
445
|
+
elif key in ['val_split', 'learning_rate', 'weight_decay', 'dropout_rate']:
|
446
|
+
# Convert to float
|
447
|
+
settings[key] = float(value)
|
448
|
+
elif key == 'classes':
|
449
|
+
# Evaluate as list
|
450
|
+
settings[key] = ast.literal_eval(value)
|
177
451
|
|
178
|
-
elif key
|
179
|
-
# Converts 'normalize' into a list of two integers
|
180
|
-
settings[key] = [int(num) for num in ast.literal_eval(value)] if value else None
|
181
|
-
|
182
|
-
elif key == 'normalize_by':
|
183
|
-
# 'normalize_by' is a string, so directly assign the value
|
452
|
+
elif key in ['model_type','optimizer_type','schedule','loss_type','train_mode']:
|
184
453
|
settings[key] = value
|
185
454
|
|
186
|
-
elif key
|
187
|
-
|
188
|
-
temp_val = ast.literal_eval(value) if value else []
|
189
|
-
settings[key] = [list(map(int, dim)) for dim in temp_val] if temp_val else None
|
190
|
-
|
191
|
-
# Handling for other keys as in your original function...
|
192
|
-
|
193
|
-
elif key in ['pathogens', 'treatments', 'cells', 'crop_mode', 'timelapse_objects']:
|
194
|
-
# Ensuring these are evaluated correctly as lists or other structures
|
195
|
-
settings[key] = ast.literal_eval(value) if value else None
|
455
|
+
elif key in ['gradient_accumulation','normalize','save','plot', 'init_weights','amsgrad','use_checkpoint','intermedeate_save','pin_memory', 'num_workers','verbose']:
|
456
|
+
settings[key] = bool(value)
|
196
457
|
|
197
|
-
|
198
|
-
|
199
|
-
|
458
|
+
except SyntaxError as e:
|
459
|
+
messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
|
460
|
+
return None
|
461
|
+
except Exception as e:
|
462
|
+
messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
|
463
|
+
return None
|
200
464
|
|
201
|
-
|
202
|
-
elif key in ['normalize_by', 'experiment', 'measurement', 'input_folder']:
|
203
|
-
settings[key] = str(value) if value else None
|
465
|
+
return settings
|
204
466
|
|
205
|
-
|
206
|
-
|
207
|
-
|
467
|
+
def check_sim_gui_settings(vars_dict):
|
468
|
+
settings = {}
|
469
|
+
for key, var in vars_dict.items():
|
470
|
+
value = var.get() # This retrieves the string representation for entries or the actual value for checkboxes and combos
|
208
471
|
|
209
|
-
|
210
|
-
|
472
|
+
try:
|
473
|
+
if key in ['src', 'name', 'variable']:
|
474
|
+
# Directly assign string values
|
475
|
+
settings[key] = str(value)
|
476
|
+
|
477
|
+
elif key in ['nr_plates', 'number_of_genes','number_of_active_genes','avg_genes_per_well','avg_cells_per_well','avg_reads_per_gene']:
|
478
|
+
#generate list of integers from list
|
479
|
+
ls = [int(num) for num in ast.literal_eval(value)]
|
480
|
+
if len(ls) == 3 and ls[2] > 0:
|
481
|
+
list_of_integers = list(range(ls[0], ls[1], ls[2]))
|
482
|
+
list_of_integers = [num + 1 if num == 0 else num for num in list_of_integers]
|
483
|
+
else:
|
484
|
+
list_of_integers = [ls[0]]
|
485
|
+
settings[key] = list_of_integers
|
486
|
+
|
487
|
+
elif key in ['sequencing_error','well_ineq_coeff','gene_ineq_coeff', 'positive_mean']:
|
488
|
+
#generate list of floats from list
|
489
|
+
ls = [float(num) for num in ast.literal_eval(value)]
|
490
|
+
if len(ls) == 3 and ls[2] > 0:
|
491
|
+
list_of_floats = np.linspace(ls[0], ls[1], ls[2])
|
492
|
+
list_of_floats.tolist()
|
493
|
+
list_of_floats = [x if x != 0.0 else x + 0.01 for x in list_of_floats]
|
494
|
+
else:
|
495
|
+
list_of_floats = [ls[0]]
|
496
|
+
settings[key] = list_of_floats
|
211
497
|
|
212
|
-
|
213
|
-
|
498
|
+
elif key in ['plot', 'random_seed']:
|
499
|
+
# Evaluate as bool
|
214
500
|
settings[key] = bool(value)
|
215
|
-
|
501
|
+
|
502
|
+
elif key in ['number_of_control_genes', 'replicates', 'max_workers']:
|
503
|
+
# Convert to integer
|
504
|
+
settings[key] = int(value)
|
505
|
+
|
216
506
|
except SyntaxError as e:
|
217
507
|
messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
|
218
508
|
return None
|
@@ -222,17 +512,44 @@ def check_measure_gui_settings(vars_dict):
|
|
222
512
|
|
223
513
|
return settings
|
224
514
|
|
515
|
+
def sim_variables():
|
516
|
+
variables = {
|
517
|
+
'name':('entry', None, 'plates_2_4_8'),
|
518
|
+
'variable':('entry', None, 'all'),
|
519
|
+
'src':('entry', None, '/home/olafsson/Desktop/simulations'),
|
520
|
+
'number_of_control_genes':('entry', None, 30),
|
521
|
+
'replicates':('entry', None, 4),
|
522
|
+
'max_workers':('entry', None, 1),
|
523
|
+
'plot':('check', None, True),
|
524
|
+
'random_seed':('check', None, True),
|
525
|
+
'nr_plates': ('entry', None, '[8,8,0]'),# '[2,2,8]'
|
526
|
+
'number_of_genes': ('entry', None, '[100, 100, 0]'), #[1384, 1384, 0]
|
527
|
+
'number_of_active_genes': ('entry', None, '[10,10,0]'),
|
528
|
+
'avg_genes_per_well': ('entry', None, '[2, 10, 2]'),
|
529
|
+
'avg_cells_per_well': ('entry', None, '[100, 100, 0]'),
|
530
|
+
'positive_mean': ('entry', None, '[0.8, 0.8, 0]'),
|
531
|
+
'avg_reads_per_gene': ('entry', None, '[1000,1000, 0]'),
|
532
|
+
'sequencing_error': ('entry', None, '[0.01, 0.01, 0]'),
|
533
|
+
'well_ineq_coeff': ('entry', None, '[0.3,0.3,0]'),
|
534
|
+
'gene_ineq_coeff': ('entry', None, '[0.8,0.8,0]'),
|
535
|
+
}
|
536
|
+
return variables
|
537
|
+
|
538
|
+
def add_measure_gui_defaults(settings):
|
539
|
+
settings['compartments'] = ['pathogen', 'cytoplasm']
|
540
|
+
return settings
|
541
|
+
|
225
542
|
def measure_variables():
|
226
543
|
variables = {
|
227
544
|
'input_folder':('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui/merged'),
|
228
545
|
'channels': ('combo', ['[0,1,2,3]','[0,1,2]','[0,1]','[0]'], '[0,1,2,3]'),
|
229
546
|
'cell_mask_dim':('entry', None, 4),
|
230
547
|
'cell_min_size':('entry', None, 0),
|
548
|
+
'cytoplasm_min_size':('entry', None, 0),
|
231
549
|
'nucleus_mask_dim':('entry', None, 5),
|
232
550
|
'nucleus_min_size':('entry', None, 0),
|
233
551
|
'pathogen_mask_dim':('entry', None, 6),
|
234
552
|
'pathogen_min_size':('entry', None, 0),
|
235
|
-
'cytoplasm_min_size':('entry', None, 0),
|
236
553
|
'save_png':('check', None, True),
|
237
554
|
'crop_mode':('entry', None, '["cell"]'),
|
238
555
|
'use_bounding_box':('check', None, True),
|
@@ -258,42 +575,79 @@ def measure_variables():
|
|
258
575
|
'treatments': ('entry', None, '["cm","lovastatin_20uM"]'),
|
259
576
|
'treatment_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
|
260
577
|
'channel_of_interest':('entry', None, 3),
|
578
|
+
'compartments':('entry', None, '["pathogen","cytoplasm"]'),
|
261
579
|
'measurement':('entry', None, 'mean_intensity'),
|
262
580
|
'nr_imgs':('entry', None, 32),
|
263
581
|
'um_per_pixel':('entry', None, 0.1)
|
264
582
|
}
|
265
583
|
return variables
|
266
|
-
|
267
584
|
|
268
|
-
|
585
|
+
def classify_variables():
|
586
|
+
|
587
|
+
def get_torchvision_models():
|
588
|
+
# Fetch all public callable attributes from torchvision.models that are functions
|
589
|
+
model_names = [name for name, obj in inspect.getmembers(models)
|
590
|
+
if inspect.isfunction(obj) and not name.startswith("__")]
|
591
|
+
return model_names
|
592
|
+
|
593
|
+
model_names = get_torchvision_models()
|
594
|
+
variables = {
|
595
|
+
'src':('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui/merged'),
|
596
|
+
'cell_mask_dim':('entry', None, 4),
|
597
|
+
'classes':('entry', None, '["nc","pc"]'),
|
598
|
+
'measurement':('entry', None, 'mean_intensity'),
|
599
|
+
'model_type': ('combo', model_names, 'resnet50'),
|
600
|
+
'optimizer_type': ('combo', ['adamw','adam'], 'adamw'),
|
601
|
+
'schedule': ('combo', ['reduce_lr_on_plateau','step_lr'], 'reduce_lr_on_plateau'),
|
602
|
+
'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
|
603
|
+
'image_size': ('entry', None, 224),
|
604
|
+
'batch_size': ('entry', None, 12),
|
605
|
+
'epochs': ('entry', None, 2),
|
606
|
+
'val_split': ('entry', None, 0.1),
|
607
|
+
'train_mode': ('combo', ['erm', 'irm'], 'erm'),
|
608
|
+
'learning_rate': ('entry', None, 0.0001),
|
609
|
+
'weight_decay': ('entry', None, 0.00001),
|
610
|
+
'dropout_rate': ('entry', None, 0.1),
|
611
|
+
'gradient_accumulation': ('check', None, True),
|
612
|
+
'gradient_accumulation_steps': ('entry', None, 4),
|
613
|
+
'normalize': ('check', None, True),
|
614
|
+
'save': ('check', None, True),
|
615
|
+
'plot': ('check', None, True),
|
616
|
+
'init_weights': ('check', None, True),
|
617
|
+
'amsgrad': ('check', None, True),
|
618
|
+
'use_checkpoint': ('check', None, True),
|
619
|
+
'intermedeate_save': ('check', None, True),
|
620
|
+
'pin_memory': ('check', None, True),
|
621
|
+
'num_workers': ('entry', None, 30),
|
622
|
+
'verbose': ('check', None, True),
|
623
|
+
}
|
624
|
+
return variables
|
625
|
+
|
269
626
|
def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
|
270
|
-
label = ttk.Label(frame, text=label_text, style='TLabel') #
|
627
|
+
label = ttk.Label(frame, text=label_text, style='Custom.TLabel') # Apply Custom.TLabel style for labels
|
271
628
|
label.grid(column=0, row=row, sticky=tk.W, padx=5, pady=5)
|
272
629
|
|
273
630
|
if var_type == 'entry':
|
274
631
|
var = tk.StringVar(value=default_value) # Set default value
|
275
|
-
entry = ttk.Entry(frame, textvariable=var, style='TEntry') #
|
632
|
+
entry = ttk.Entry(frame, textvariable=var, style='TEntry') # Apply TEntry style for entries
|
276
633
|
entry.grid(column=1, row=row, sticky=tk.EW, padx=5)
|
634
|
+
return (label, entry, var) # Return both the label and the entry, and the variable
|
277
635
|
elif var_type == 'check':
|
278
636
|
var = tk.BooleanVar(value=default_value) # Set default value (True/False)
|
279
|
-
|
280
|
-
check = ttk.Checkbutton(frame, variable=var, style='Dark.TCheckbutton')
|
637
|
+
check = ToggleSwitch(frame, text=label_text, variable=var) # Use ToggleSwitch class
|
281
638
|
check.grid(column=1, row=row, sticky=tk.W, padx=5)
|
639
|
+
return (label, check, var) # Return both the label and the checkbutton, and the variable
|
282
640
|
elif var_type == 'combo':
|
283
641
|
var = tk.StringVar(value=default_value) # Set default value
|
284
|
-
combo = ttk.Combobox(frame, textvariable=var, values=options, style='TCombobox') #
|
642
|
+
combo = ttk.Combobox(frame, textvariable=var, values=options, style='TCombobox') # Apply TCombobox style
|
285
643
|
combo.grid(column=1, row=row, sticky=tk.EW, padx=5)
|
286
644
|
if default_value:
|
287
645
|
combo.set(default_value)
|
646
|
+
return (label, combo, var) # Return both the label and the combobox, and the variable
|
288
647
|
else:
|
289
648
|
var = None # Placeholder in case of an undefined var_type
|
290
|
-
|
291
|
-
return var
|
649
|
+
return (label, None, var)
|
292
650
|
|
293
|
-
def add_measure_gui_defaults(settings):
|
294
|
-
settings['compartments'] = ['pathogen', 'cytoplasm']
|
295
|
-
return settings
|
296
|
-
|
297
651
|
def mask_variables():
|
298
652
|
variables = {
|
299
653
|
'src': ('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui'),
|
@@ -314,10 +668,10 @@ def mask_variables():
|
|
314
668
|
'pathogen_background': ('entry', None, 100),
|
315
669
|
'pathogen_Signal_to_noise': ('entry', None, 3),
|
316
670
|
'pathogen_CP_prob': ('entry', None, 0),
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
671
|
+
'preprocess': ('check', None, True),
|
672
|
+
'masks': ('check', None, True),
|
673
|
+
'examples_to_plot': ('entry', None, 1),
|
674
|
+
'randomize': ('check', None, True),
|
321
675
|
'batch_size': ('entry', None, 50),
|
322
676
|
'timelapse': ('check', None, False),
|
323
677
|
'timelapse_displacement': ('entry', None, None),
|
@@ -326,14 +680,14 @@ def mask_variables():
|
|
326
680
|
'timelapse_remove_transient': ('check', None, True),
|
327
681
|
'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
|
328
682
|
'timelapse_objects': ('combo', ['cell','nucleus','pathogen','cytoplasm', None], None),
|
329
|
-
|
330
|
-
|
683
|
+
'fps': ('entry', None, 2),
|
684
|
+
'remove_background': ('check', None, True),
|
331
685
|
'lower_quantile': ('entry', None, 0.01),
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
686
|
+
'merge': ('check', None, False),
|
687
|
+
'normalize_plots': ('check', None, True),
|
688
|
+
'all_to_mip': ('check', None, False),
|
689
|
+
'pick_slice': ('check', None, False),
|
690
|
+
'skip_mode': ('entry', None, None),
|
337
691
|
'save': ('check', None, True),
|
338
692
|
'plot': ('check', None, True),
|
339
693
|
'workers': ('entry', None, 30),
|
@@ -359,10 +713,98 @@ def add_mask_gui_defaults(settings):
|
|
359
713
|
def generate_fields(variables, scrollable_frame):
|
360
714
|
vars_dict = {}
|
361
715
|
row = 0
|
716
|
+
tooltips = {
|
717
|
+
"src": "Path to the folder containing the images.",
|
718
|
+
"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",
|
719
|
+
"custom_regex": "Custom regex pattern to extract metadata from the image names. This will only be used if 'custom' is selected for 'metadata_type'.",
|
720
|
+
"experiment": "Name of the experiment. This will be used to name the output files.",
|
721
|
+
"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.",
|
722
|
+
"magnification": "At what magnification the images were taken. This will be used to determine the size of the objects in the images.",
|
723
|
+
"nucleus_channel": "The channel to use for the nucleus. If None, the nucleus will not be segmented.",
|
724
|
+
"nucleus_background": "The background intensity for the nucleus channel. This will be used to remove background noise.",
|
725
|
+
"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.",
|
726
|
+
"nucleus_CP_prob": "The cellpose probability threshold for the nucleus channel. This will be used to segment the nucleus.",
|
727
|
+
"cell_channel": "The channel to use for the cell. If None, the cell will not be segmented.",
|
728
|
+
"cell_background": "The background intensity for the cell channel. This will be used to remove background noise.",
|
729
|
+
"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.",
|
730
|
+
"cell_CP_prob": "The cellpose probability threshold for the cell channel. This will be used to segment the cell.",
|
731
|
+
"pathogen_channel": "The channel to use for the pathogen. If None, the pathogen will not be segmented.",
|
732
|
+
"pathogen_background": "The background intensity for the pathogen channel. This will be used to remove background noise.",
|
733
|
+
"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.",
|
734
|
+
"pathogen_CP_prob": "The cellpose probability threshold for the pathogen channel. This will be used to segment the pathogen.",
|
735
|
+
"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.",
|
736
|
+
"masks": "Whether to generate masks for the segmented objects. If True, masks will be generated for the nucleus, cell, and pathogen.",
|
737
|
+
"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 .",
|
738
|
+
"randomize": "Whether to randomize the order of the images before processing. Recommended to avoid bias in the segmentation.",
|
739
|
+
"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.",
|
740
|
+
"timelapse": "Whether to process the images as a timelapse.",
|
741
|
+
"timelapse_displacement": "The displacement between frames in the timelapse. This will be used to align the frames before processing.",
|
742
|
+
"timelapse_memory": "The number of frames to in tandem objects must be present in to be considered the same object in the timelapse.",
|
743
|
+
"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.",
|
744
|
+
"timelapse_remove_transient": "Whether to remove transient objects in the timelapse. Transient objects are present in fewer than all frames.",
|
745
|
+
"timelapse_mode": "The mode to use for processing the timelapse. 'trackpy' uses the trackpy library for tracking objects, while 'btrack' uses the btrack library.",
|
746
|
+
"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.",
|
747
|
+
"fps": "Frames per second of the automatically generated timelapse movies.",
|
748
|
+
"remove_background": "Whether to remove background noise from the images. This will help improve the quality of the segmentation.",
|
749
|
+
"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.",
|
750
|
+
"merge_pathogens": "Whether to merge pathogen objects that share more than 75% of their perimiter.",
|
751
|
+
"normalize_plots": "Whether to normalize the plots.",
|
752
|
+
"all_to_mip": "Whether to convert all images to maximum intensity projections before processing.",
|
753
|
+
"pick_slice": "Whether to pick a single slice from the z-stack images. If False, the maximum intensity projection will be used.",
|
754
|
+
"skip_mode": "The mode to use for skipping images. This will determine how to handle images that cannot be processed.",
|
755
|
+
"save": "Whether to save the results to disk.",
|
756
|
+
"plot": "Whether to plot the results.",
|
757
|
+
"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.",
|
758
|
+
"verbose": "Whether to print verbose output during processing.",
|
759
|
+
"input_folder": "Path to the folder containing the images.",
|
760
|
+
"cell_mask_dim": "The dimension of the array the cell mask is saved in.",
|
761
|
+
"cell_min_size": "The minimum size of cell objects in pixels2.",
|
762
|
+
"cytoplasm_min_size": "The minimum size of cytoplasm objects in pixels2.",
|
763
|
+
"nucleus_mask_dim": "The dimension of the array the nucleus mask is saved in.",
|
764
|
+
"nucleus_min_size": "The minimum size of nucleus objects in pixels2.",
|
765
|
+
"pathogen_mask_dim": "The dimension of the array the pathogen mask is saved in.",
|
766
|
+
"pathogen_min_size": "The minimum size of pathogen objects in pixels2.",
|
767
|
+
"save_png": "Whether to save the segmented objects as PNG images.",
|
768
|
+
"crop_mode": "The mode to use for cropping the images. This will determine which objects are cropped from the images (cell, nucleus, pathogen, cytoplasm).",
|
769
|
+
"use_bounding_box": "Whether to use the bounding box of the objects for cropping. If False, only the object itself will be cropped.",
|
770
|
+
"png_size": "The size of the PNG images to save. This will determine the size of the saved images.",
|
771
|
+
"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.",
|
772
|
+
"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].",
|
773
|
+
"normalize_by": "Whether to normalize the images by field of view (fov) or by PNG image (png).",
|
774
|
+
"save_measurements": "Whether to save the measurements to disk.",
|
775
|
+
"representative_images": "Whether to save representative images of the segmented objects (Not working yet).",
|
776
|
+
"plot": "Whether to plot results.",
|
777
|
+
"plot_filtration": "Whether to plot the filtration steps.",
|
778
|
+
"include_uninfected": "Whether to include uninfected cells in the analysis.",
|
779
|
+
"dialate_pngs": "Whether to dialate the PNG images before saving.",
|
780
|
+
"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.",
|
781
|
+
"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.",
|
782
|
+
"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.",
|
783
|
+
"cells: ": "The cell types to include in the analysis.",
|
784
|
+
"cell_loc": "The locations of the cell types in the images.",
|
785
|
+
"pathogens": "The pathogen types to include in the analysis.",
|
786
|
+
"pathogen_loc": "The locations of the pathogen types in the images.",
|
787
|
+
"treatments": "The treatments to include in the analysis.",
|
788
|
+
"treatment_loc": "The locations of the treatments in the images.",
|
789
|
+
"channel_of_interest": "The channel of interest to use for the analysis.",
|
790
|
+
"compartments": "The compartments to measure in the images.",
|
791
|
+
"measurement": "The measurement to use for the analysis.",
|
792
|
+
"nr_imgs": "The number of images to plot.",
|
793
|
+
"um_per_pixel": "The micrometers per pixel for the images.",
|
794
|
+
}
|
795
|
+
|
362
796
|
for key, (var_type, options, default_value) in variables.items():
|
363
|
-
|
797
|
+
label, widget, var = create_input_field(scrollable_frame.scrollable_frame, key, row, var_type, options, default_value)
|
798
|
+
vars_dict[key] = (label, widget, var) # Store the label, widget, and variable
|
799
|
+
|
800
|
+
# Add tooltip to the label if it exists in the tooltips dictionary
|
801
|
+
if key in tooltips:
|
802
|
+
ToolTip(label, tooltips[key])
|
803
|
+
|
364
804
|
row += 1
|
365
805
|
return vars_dict
|
806
|
+
|
807
|
+
|
366
808
|
|
367
809
|
class TextRedirector(object):
|
368
810
|
def __init__(self, widget, queue):
|
@@ -376,7 +818,7 @@ class TextRedirector(object):
|
|
376
818
|
pass
|
377
819
|
|
378
820
|
def create_dark_mode(root, style, console_output):
|
379
|
-
dark_bg = '
|
821
|
+
dark_bg = 'black'
|
380
822
|
light_text = 'white'
|
381
823
|
dark_text = 'black'
|
382
824
|
input_bg = '#555555' # Slightly lighter background for input fields
|
@@ -396,34 +838,39 @@ def create_dark_mode(root, style, console_output):
|
|
396
838
|
root.configure(bg=dark_bg)
|
397
839
|
|
398
840
|
def set_dark_style(style):
|
399
|
-
style.configure('TFrame', background='
|
400
|
-
style.configure('TLabel', background='
|
401
|
-
style.configure('TEntry', background='
|
402
|
-
style.configure('TCheckbutton', background='
|
403
|
-
|
404
|
-
|
841
|
+
style.configure('TFrame', background='black')
|
842
|
+
style.configure('TLabel', background='black', foreground='white')
|
843
|
+
style.configure('TEntry', background='black', foreground='white')
|
844
|
+
style.configure('TCheckbutton', background='black', foreground='white')
|
405
845
|
|
406
|
-
|
846
|
+
##@log_function_call
|
407
847
|
def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
|
408
848
|
try:
|
849
|
+
ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
|
409
850
|
while not q.empty():
|
410
851
|
message = q.get_nowait()
|
411
|
-
|
412
|
-
|
413
|
-
|
852
|
+
clean_message = ansi_escape_pattern.sub('', message)
|
853
|
+
if clean_message.startswith("Progress"):
|
854
|
+
progress_label.config(text=clean_message)
|
855
|
+
if clean_message.startswith("\rProgress"):
|
856
|
+
progress_label.config(text=clean_message)
|
857
|
+
elif clean_message.startswith("Successfully"):
|
858
|
+
progress_label.config(text=clean_message)
|
859
|
+
elif clean_message.startswith("Processing"):
|
860
|
+
progress_label.config(text=clean_message)
|
861
|
+
elif clean_message.startswith("scale"):
|
862
|
+
pass
|
863
|
+
elif clean_message.startswith("plot_cropped_arrays"):
|
864
|
+
pass
|
865
|
+
elif clean_message == "" or clean_message == "\r" or clean_message.strip() == "":
|
414
866
|
pass
|
415
867
|
else:
|
416
|
-
print(
|
417
|
-
# For non-progress messages, you can still print them to the console or handle them as needed.
|
418
|
-
|
419
|
-
while not fig_queue.empty():
|
420
|
-
fig = fig_queue.get_nowait()
|
421
|
-
clear_canvas(canvas_widget)
|
868
|
+
print(clean_message)
|
422
869
|
except Exception as e:
|
423
|
-
print(f"Error updating GUI: {e}")
|
870
|
+
print(f"Error updating GUI canvas: {e}")
|
424
871
|
finally:
|
425
872
|
root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
|
426
|
-
|
873
|
+
|
427
874
|
def process_stdout_stderr(q):
|
428
875
|
"""
|
429
876
|
Redirect stdout and stderr to the queue q.
|
@@ -453,7 +900,6 @@ def clear_canvas(canvas):
|
|
453
900
|
# Redraw the now empty canvas without changing its size
|
454
901
|
canvas.draw_idle()
|
455
902
|
|
456
|
-
@log_function_call
|
457
903
|
def measure_crop_wrapper(settings, q, fig_queue):
|
458
904
|
"""
|
459
905
|
Wraps the measure_crop function to integrate with GUI processes.
|
@@ -463,7 +909,103 @@ def measure_crop_wrapper(settings, q, fig_queue):
|
|
463
909
|
- q: multiprocessing.Queue, Queue for logging messages to the GUI.
|
464
910
|
- fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
|
465
911
|
"""
|
912
|
+
|
913
|
+
def my_show():
|
914
|
+
"""
|
915
|
+
Replacement for plt.show() that queues figures instead of displaying them.
|
916
|
+
"""
|
917
|
+
fig = plt.gcf()
|
918
|
+
fig_queue.put(fig) # Queue the figure for GUI display
|
919
|
+
plt.close(fig) # Prevent the figure from being shown by plt.show()
|
920
|
+
|
921
|
+
# Temporarily override plt.show
|
922
|
+
original_show = plt.show
|
923
|
+
plt.show = my_show
|
924
|
+
|
925
|
+
try:
|
926
|
+
print('start')
|
927
|
+
spacr.measure.measure_crop(settings=settings)
|
928
|
+
except Exception as e:
|
929
|
+
errorMessage = f"Error during processing: {e}"
|
930
|
+
q.put(errorMessage) # Send the error message to the GUI via the queue
|
931
|
+
traceback.print_exc()
|
932
|
+
finally:
|
933
|
+
plt.show = original_show # Restore the original plt.show function
|
934
|
+
|
935
|
+
#@log_function_call
|
936
|
+
def preprocess_generate_masks_wrapper(settings, q, fig_queue):
|
937
|
+
"""
|
938
|
+
Wraps the measure_crop function to integrate with GUI processes.
|
939
|
+
|
940
|
+
Parameters:
|
941
|
+
- settings: dict, The settings for the measure_crop function.
|
942
|
+
- q: multiprocessing.Queue, Queue for logging messages to the GUI.
|
943
|
+
- fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
|
944
|
+
"""
|
945
|
+
|
946
|
+
def my_show():
|
947
|
+
"""
|
948
|
+
Replacement for plt.show() that queues figures instead of displaying them.
|
949
|
+
"""
|
950
|
+
fig = plt.gcf()
|
951
|
+
fig_queue.put(fig) # Queue the figure for GUI display
|
952
|
+
plt.close(fig) # Prevent the figure from being shown by plt.show()
|
953
|
+
|
954
|
+
# Temporarily override plt.show
|
955
|
+
original_show = plt.show
|
956
|
+
plt.show = my_show
|
957
|
+
|
958
|
+
try:
|
959
|
+
spacr.core.preprocess_generate_masks(settings['src'], settings=settings)
|
960
|
+
except Exception as e:
|
961
|
+
errorMessage = f"Error during processing: {e}"
|
962
|
+
q.put(errorMessage) # Send the error message to the GUI via the queue
|
963
|
+
traceback.print_exc()
|
964
|
+
finally:
|
965
|
+
plt.show = original_show # Restore the original plt.show function
|
966
|
+
|
967
|
+
def train_test_model_wrapper(settings, q, fig_queue):
|
968
|
+
"""
|
969
|
+
Wraps the measure_crop function to integrate with GUI processes.
|
466
970
|
|
971
|
+
Parameters:
|
972
|
+
- settings: dict, The settings for the measure_crop function.
|
973
|
+
- q: multiprocessing.Queue, Queue for logging messages to the GUI.
|
974
|
+
- fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
|
975
|
+
"""
|
976
|
+
|
977
|
+
def my_show():
|
978
|
+
"""
|
979
|
+
Replacement for plt.show() that queues figures instead of displaying them.
|
980
|
+
"""
|
981
|
+
fig = plt.gcf()
|
982
|
+
fig_queue.put(fig) # Queue the figure for GUI display
|
983
|
+
plt.close(fig) # Prevent the figure from being shown by plt.show()
|
984
|
+
|
985
|
+
# Temporarily override plt.show
|
986
|
+
original_show = plt.show
|
987
|
+
plt.show = my_show
|
988
|
+
|
989
|
+
try:
|
990
|
+
spacr.core.train_test_model(settings['src'], settings=settings)
|
991
|
+
except Exception as e:
|
992
|
+
errorMessage = f"Error during processing: {e}"
|
993
|
+
q.put(errorMessage) # Send the error message to the GUI via the queue
|
994
|
+
traceback.print_exc()
|
995
|
+
finally:
|
996
|
+
plt.show = original_show # Restore the original plt.show function
|
997
|
+
|
998
|
+
|
999
|
+
def run_multiple_simulations_wrapper(settings, q, fig_queue):
|
1000
|
+
"""
|
1001
|
+
Wraps the run_multiple_simulations function to integrate with GUI processes.
|
1002
|
+
|
1003
|
+
Parameters:
|
1004
|
+
- settings: dict, The settings for the run_multiple_simulations function.
|
1005
|
+
- q: multiprocessing.Queue, Queue for logging messages to the GUI.
|
1006
|
+
- fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
|
1007
|
+
"""
|
1008
|
+
|
467
1009
|
def my_show():
|
468
1010
|
"""
|
469
1011
|
Replacement for plt.show() that queues figures instead of displaying them.
|
@@ -477,12 +1019,10 @@ def measure_crop_wrapper(settings, q, fig_queue):
|
|
477
1019
|
plt.show = my_show
|
478
1020
|
|
479
1021
|
try:
|
480
|
-
|
481
|
-
# Pass settings as a named argument, along with any other needed arguments
|
482
|
-
spacr.measure.measure_crop(settings=settings, annotation_settings={}, advanced_settings={})
|
1022
|
+
spacr.sim.run_multiple_simulations(settings=settings)
|
483
1023
|
except Exception as e:
|
484
1024
|
errorMessage = f"Error during processing: {e}"
|
485
1025
|
q.put(errorMessage) # Send the error message to the GUI via the queue
|
486
1026
|
traceback.print_exc()
|
487
1027
|
finally:
|
488
|
-
plt.show = original_show # Restore the original plt.show function
|
1028
|
+
plt.show = original_show # Restore the original plt.show function
|