spacr 0.2.4__py3-none-any.whl → 0.2.8__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 +1 -11
- spacr/core.py +277 -349
- spacr/deep_spacr.py +248 -269
- spacr/gui.py +58 -54
- spacr/gui_core.py +689 -535
- spacr/gui_elements.py +1002 -153
- spacr/gui_utils.py +452 -107
- spacr/io.py +158 -91
- spacr/measure.py +199 -151
- spacr/plot.py +159 -47
- spacr/resources/font/open_sans/OFL.txt +93 -0
- spacr/resources/font/open_sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- spacr/resources/font/open_sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- spacr/resources/font/open_sans/README.txt +100 -0
- spacr/resources/font/open_sans/static/OpenSans-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-SemiBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
- spacr/resources/icons/logo.pdf +2786 -6
- spacr/resources/icons/logo_spacr.png +0 -0
- spacr/resources/icons/logo_spacr_1.png +0 -0
- spacr/sequencing.py +477 -587
- spacr/settings.py +217 -144
- spacr/utils.py +46 -46
- {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/METADATA +46 -35
- spacr-0.2.8.dist-info/RECORD +100 -0
- {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/WHEEL +1 -1
- spacr-0.2.4.dist-info/RECORD +0 -58
- {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/LICENSE +0 -0
- {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py
CHANGED
@@ -1,48 +1,48 @@
|
|
1
|
-
import os, io, sys, ast, ctypes,
|
1
|
+
import os, io, sys, ast, ctypes, ast, sqlite3, requests, time, traceback, torch
|
2
2
|
import tkinter as tk
|
3
3
|
from tkinter import ttk
|
4
|
+
import matplotlib
|
5
|
+
import matplotlib.pyplot as plt
|
6
|
+
matplotlib.use('Agg')
|
7
|
+
from huggingface_hub import list_repo_files
|
8
|
+
import psutil
|
4
9
|
|
5
|
-
from .
|
6
|
-
from .gui_elements import spacrLabel, spacrCheckbutton, AnnotateApp, spacrEntry, spacrCheck, spacrCombo, set_default_font
|
10
|
+
from .gui_elements import AnnotateApp, spacrEntry, spacrCheck, spacrCombo
|
7
11
|
|
8
12
|
try:
|
9
13
|
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
10
14
|
except AttributeError:
|
11
15
|
pass
|
12
16
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
if
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
except tk.TclError as e:
|
22
|
-
print(f"Error destroying widget: {e}")
|
17
|
+
def initialize_cuda():
|
18
|
+
"""
|
19
|
+
Initializes CUDA in the main process by performing a simple GPU operation.
|
20
|
+
"""
|
21
|
+
if torch.cuda.is_available():
|
22
|
+
# Allocate a small tensor on the GPU
|
23
|
+
_ = torch.tensor([0.0], device='cuda')
|
24
|
+
print("CUDA initialized in the main process.")
|
23
25
|
else:
|
24
|
-
|
25
|
-
root.content_frame.grid(row=1, column=0, sticky="nsew")
|
26
|
-
root.grid_rowconfigure(1, weight=1)
|
27
|
-
root.grid_columnconfigure(0, weight=1)
|
26
|
+
print("CUDA is not available.")
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
28
|
+
def set_high_priority(process):
|
29
|
+
try:
|
30
|
+
p = psutil.Process(process.pid)
|
31
|
+
if os.name == 'nt': # Windows
|
32
|
+
p.nice(psutil.HIGH_PRIORITY_CLASS)
|
33
|
+
else: # Unix-like systems
|
34
|
+
p.nice(-10) # Adjusted priority level
|
35
|
+
print(f"Successfully set high priority for process: {process.pid}")
|
36
|
+
except psutil.AccessDenied as e:
|
37
|
+
print(f"Access denied when trying to set high priority for process {process.pid}: {e}")
|
38
|
+
except psutil.NoSuchProcess as e:
|
39
|
+
print(f"No such process {process.pid}: {e}")
|
40
|
+
except Exception as e:
|
41
|
+
print(f"Failed to set high priority for process {process.pid}: {e}")
|
42
|
+
|
43
|
+
def set_cpu_affinity(process):
|
44
|
+
p = psutil.Process(process.pid)
|
45
|
+
p.cpu_affinity(list(range(os.cpu_count())))
|
46
46
|
|
47
47
|
def proceed_with_app(root, app_name, app_func):
|
48
48
|
# Clear the current content frame
|
@@ -57,6 +57,10 @@ def proceed_with_app(root, app_name, app_func):
|
|
57
57
|
app_func(root.content_frame)
|
58
58
|
|
59
59
|
def load_app(root, app_name, app_func):
|
60
|
+
# Clear the canvas if it exists
|
61
|
+
if root.canvas is not None:
|
62
|
+
root.clear_frame(root.canvas)
|
63
|
+
|
60
64
|
# Cancel all scheduled after tasks
|
61
65
|
if hasattr(root, 'after_tasks'):
|
62
66
|
for task in root.after_tasks:
|
@@ -66,64 +70,115 @@ def load_app(root, app_name, app_func):
|
|
66
70
|
# Exit functionality only for the annotation and make_masks apps
|
67
71
|
if app_name not in ["Annotate", "make_masks"] and hasattr(root, 'current_app_exit_func'):
|
68
72
|
root.next_app_func = proceed_with_app
|
69
|
-
root.next_app_args = (app_name, app_func)
|
73
|
+
root.next_app_args = (app_name, app_func)
|
70
74
|
root.current_app_exit_func()
|
71
75
|
else:
|
72
76
|
proceed_with_app(root, app_name, app_func)
|
73
|
-
|
74
|
-
def parse_list_v1(value):
|
75
|
-
try:
|
76
|
-
parsed_value = ast.literal_eval(value)
|
77
|
-
if isinstance(parsed_value, list):
|
78
|
-
return parsed_value
|
79
|
-
else:
|
80
|
-
raise ValueError
|
81
|
-
except (ValueError, SyntaxError):
|
82
|
-
raise ValueError("Invalid format for list")
|
83
77
|
|
84
78
|
def parse_list(value):
|
79
|
+
"""
|
80
|
+
Parses a string representation of a list and returns the parsed list.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
value (str): The string representation of the list.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
list: The parsed list.
|
87
|
+
|
88
|
+
Raises:
|
89
|
+
ValueError: If the input value is not a valid list format or contains mixed types or unsupported types.
|
90
|
+
"""
|
85
91
|
try:
|
86
92
|
parsed_value = ast.literal_eval(value)
|
87
93
|
if isinstance(parsed_value, list):
|
88
|
-
|
94
|
+
# Check if the list elements are homogeneous (all int or all str)
|
95
|
+
if all(isinstance(item, int) for item in parsed_value):
|
96
|
+
return parsed_value
|
97
|
+
elif all(isinstance(item, str) for item in parsed_value):
|
98
|
+
return parsed_value
|
99
|
+
else:
|
100
|
+
raise ValueError("List contains mixed types or unsupported types")
|
89
101
|
else:
|
90
102
|
raise ValueError(f"Expected a list but got {type(parsed_value).__name__}")
|
91
103
|
except (ValueError, SyntaxError) as e:
|
92
104
|
raise ValueError(f"Invalid format for list: {value}. Error: {e}")
|
93
|
-
|
105
|
+
|
94
106
|
# Usage example in your create_input_field function
|
95
107
|
def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
|
108
|
+
"""
|
109
|
+
Create an input field in the specified frame.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
frame (tk.Frame): The frame in which the input field will be created.
|
113
|
+
label_text (str): The text to be displayed as the label for the input field.
|
114
|
+
row (int): The row in which the input field will be placed.
|
115
|
+
var_type (str, optional): The type of input field to create. Defaults to 'entry'.
|
116
|
+
options (list, optional): The list of options for a combo box input field. Defaults to None.
|
117
|
+
default_value (str, optional): The default value for the input field. Defaults to None.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
tuple: A tuple containing the label, input widget, variable, and custom frame.
|
121
|
+
|
122
|
+
Raises:
|
123
|
+
Exception: If an error occurs while creating the input field.
|
124
|
+
|
125
|
+
"""
|
126
|
+
from .gui_elements import set_dark_style, set_element_size
|
127
|
+
|
96
128
|
label_column = 0
|
97
|
-
widget_column =
|
129
|
+
widget_column = 0 # Both label and widget will be in the same column
|
130
|
+
|
131
|
+
style_out = set_dark_style(ttk.Style())
|
132
|
+
font_loader = style_out['font_loader']
|
133
|
+
font_size = style_out['font_size']
|
134
|
+
size_dict = set_element_size()
|
135
|
+
size_dict['settings_width'] = size_dict['settings_width'] - int(size_dict['settings_width']*0.1)
|
136
|
+
|
137
|
+
# Replace underscores with spaces and capitalize the first letter
|
138
|
+
|
139
|
+
label_text = label_text.replace('_', ' ').capitalize()
|
98
140
|
|
99
141
|
# Configure the column widths
|
100
|
-
frame.grid_columnconfigure(label_column, weight=
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
142
|
+
frame.grid_columnconfigure(label_column, weight=1) # Allow the column to expand
|
143
|
+
|
144
|
+
# Create a custom frame with a translucent background and rounded edges
|
145
|
+
custom_frame = tk.Frame(frame, bg=style_out['bg_color'], bd=2, relief='solid', width=size_dict['settings_width'])
|
146
|
+
custom_frame.grid(column=label_column, row=row, sticky=tk.EW, padx=(5, 5), pady=5)
|
147
|
+
|
148
|
+
# Apply styles to custom frame
|
149
|
+
custom_frame.update_idletasks()
|
150
|
+
custom_frame.config(highlightbackground=style_out['bg_color'], highlightthickness=1, bd=2)
|
151
|
+
|
152
|
+
# Create and configure the label
|
153
|
+
label = tk.Label(custom_frame, text=label_text, bg=style_out['bg_color'], fg=style_out['fg_color'], font=font_loader.get_font(size=font_size), anchor='e', justify='right')
|
154
|
+
label.grid(column=label_column, row=0, sticky=tk.W, padx=(5, 2), pady=5) # Place the label in the first row
|
155
|
+
|
156
|
+
# Create and configure the input widget based on var_type
|
157
|
+
try:
|
158
|
+
if var_type == 'entry':
|
159
|
+
var = tk.StringVar(value=default_value)
|
160
|
+
entry = spacrEntry(custom_frame, textvariable=var, outline=False, width=size_dict['settings_width'])
|
161
|
+
entry.grid(column=widget_column, row=1, sticky=tk.W, padx=(2, 5), pady=5) # Place the entry in the second row
|
162
|
+
return (label, entry, var, custom_frame) # Return both the label and the entry, and the variable
|
163
|
+
elif var_type == 'check':
|
164
|
+
var = tk.BooleanVar(value=default_value) # Set default value (True/False)
|
165
|
+
check = spacrCheck(custom_frame, text="", variable=var)
|
166
|
+
check.grid(column=widget_column, row=1, sticky=tk.W, padx=(2, 5), pady=5) # Place the checkbutton in the second row
|
167
|
+
return (label, check, var, custom_frame) # Return both the label and the checkbutton, and the variable
|
168
|
+
elif var_type == 'combo':
|
169
|
+
var = tk.StringVar(value=default_value) # Set default value
|
170
|
+
combo = spacrCombo(custom_frame, textvariable=var, values=options, width=size_dict['settings_width']) # Apply TCombobox style
|
171
|
+
combo.grid(column=widget_column, row=1, sticky=tk.W, padx=(2, 5), pady=5) # Place the combobox in the second row
|
172
|
+
if default_value:
|
173
|
+
combo.set(default_value)
|
174
|
+
return (label, combo, var, custom_frame) # Return both the label and the combobox, and the variable
|
175
|
+
else:
|
176
|
+
var = None # Placeholder in case of an undefined var_type
|
177
|
+
return (label, None, var, custom_frame)
|
178
|
+
except Exception as e:
|
179
|
+
traceback.print_exc()
|
180
|
+
print(f"Error creating input field: {e}")
|
181
|
+
print(f"Wrong type for {label_text} Expected {var_type}")
|
127
182
|
|
128
183
|
def process_stdout_stderr(q):
|
129
184
|
"""
|
@@ -139,10 +194,9 @@ class WriteToQueue(io.TextIOBase):
|
|
139
194
|
"""
|
140
195
|
def __init__(self, q):
|
141
196
|
self.q = q
|
142
|
-
|
143
197
|
def write(self, msg):
|
144
|
-
|
145
|
-
|
198
|
+
if msg.strip(): # Avoid empty messages
|
199
|
+
self.q.put(msg)
|
146
200
|
def flush(self):
|
147
201
|
pass
|
148
202
|
|
@@ -152,33 +206,6 @@ def cancel_after_tasks(frame):
|
|
152
206
|
frame.after_cancel(task)
|
153
207
|
frame.after_tasks.clear()
|
154
208
|
|
155
|
-
def main_thread_update_function(root, q, fig_queue, canvas_widget):
|
156
|
-
try:
|
157
|
-
#ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
|
158
|
-
while not q.empty():
|
159
|
-
message = q.get_nowait()
|
160
|
-
#clean_message = ansi_escape_pattern.sub('', message)
|
161
|
-
#if clean_message.startswith("Progress"):
|
162
|
-
# progress_label.config(text=clean_message)
|
163
|
-
#if clean_message.startswith("\rProgress"):
|
164
|
-
# progress_label.config(text=clean_message)
|
165
|
-
#elif clean_message.startswith("Successfully"):
|
166
|
-
# progress_label.config(text=clean_message)
|
167
|
-
#elif clean_message.startswith("Processing"):
|
168
|
-
# progress_label.config(text=clean_message)
|
169
|
-
#elif clean_message.startswith("scale"):
|
170
|
-
# pass
|
171
|
-
#elif clean_message.startswith("plot_cropped_arrays"):
|
172
|
-
# pass
|
173
|
-
#elif clean_message == "" or clean_message == "\r" or clean_message.strip() == "":
|
174
|
-
# pass
|
175
|
-
#else:
|
176
|
-
# print(clean_message)
|
177
|
-
except Exception as e:
|
178
|
-
print(f"Error updating GUI canvas: {e}")
|
179
|
-
finally:
|
180
|
-
root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget))
|
181
|
-
|
182
209
|
def annotate(settings):
|
183
210
|
from .settings import set_annotate_default_settings
|
184
211
|
settings = set_annotate_default_settings(settings)
|
@@ -209,6 +236,12 @@ def annotate(settings):
|
|
209
236
|
|
210
237
|
def generate_annotate_fields(frame):
|
211
238
|
from .settings import set_annotate_default_settings
|
239
|
+
from .gui_elements import set_dark_style
|
240
|
+
|
241
|
+
style_out = set_dark_style(ttk.Style())
|
242
|
+
font_loader = style_out['font_loader']
|
243
|
+
font_size = style_out['font_size'] - 2
|
244
|
+
|
212
245
|
vars_dict = {}
|
213
246
|
settings = set_annotate_default_settings(settings={})
|
214
247
|
|
@@ -220,8 +253,8 @@ def generate_annotate_fields(frame):
|
|
220
253
|
|
221
254
|
# Arrange input fields and labels
|
222
255
|
for row, (name, data) in enumerate(vars_dict.items()):
|
223
|
-
|
224
|
-
|
256
|
+
tk.Label(frame, text=f"{name.replace('_', ' ').capitalize()}:", bg=style_out['bg_color'], fg=style_out['fg_color'], font=font_loader.get_font(size=font_size)).grid(row=row, column=0)
|
257
|
+
#ttk.Label(frame, text=f"{name.replace('_', ' ').capitalize()}:", background="black", foreground="white").grid(row=row, column=0)
|
225
258
|
if isinstance(data['value'], list):
|
226
259
|
# Convert lists to comma-separated strings
|
227
260
|
data['entry'].insert(0, ','.join(map(str, data['value'])))
|
@@ -349,4 +382,316 @@ def annotate_with_image_refs(settings, root, shutdown_callback):
|
|
349
382
|
# Call load_images after setting up the root window
|
350
383
|
app.load_images()
|
351
384
|
|
385
|
+
def convert_settings_dict_for_gui(settings):
|
386
|
+
from torchvision import models as torch_models
|
387
|
+
torchvision_models = [name for name, obj in torch_models.__dict__.items() if callable(obj)]
|
388
|
+
chans = ['0', '1', '2', '3', '4', '5', '6', '7', '8', None]
|
389
|
+
chans_v2 = [0, 1, 2, 3, None]
|
390
|
+
variables = {}
|
391
|
+
special_cases = {
|
392
|
+
'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
|
393
|
+
'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
|
394
|
+
'train_channels': ('combo', ["['r','g','b']", "['r','g']", "['r','b']", "['g','b']", "['r']", "['g']", "['b']"], "['r','g','b']"),
|
395
|
+
'channel_dims': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
|
396
|
+
'dataset_mode': ('combo', ['annotation', 'metadata', 'recruitment'], 'metadata'),
|
397
|
+
'cell_mask_dim': ('combo', chans, None),
|
398
|
+
'cell_chann_dim': ('combo', chans, None),
|
399
|
+
'nucleus_mask_dim': ('combo', chans, None),
|
400
|
+
'nucleus_chann_dim': ('combo', chans, None),
|
401
|
+
'pathogen_mask_dim': ('combo', chans, None),
|
402
|
+
'pathogen_chann_dim': ('combo', chans, None),
|
403
|
+
'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
|
404
|
+
'magnification': ('combo', [20, 40, 60], 20),
|
405
|
+
'nucleus_channel': ('combo', chans_v2, None),
|
406
|
+
'cell_channel': ('combo', chans_v2, None),
|
407
|
+
'channel_of_interest': ('combo', chans_v2, None),
|
408
|
+
'pathogen_channel': ('combo', chans_v2, None),
|
409
|
+
'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
|
410
|
+
'train_mode': ('combo', ['erm', 'irm'], 'erm'),
|
411
|
+
'clustering': ('combo', ['dbscan', 'kmean'], 'dbscan'),
|
412
|
+
'reduction_method': ('combo', ['umap', 'tsne'], 'umap'),
|
413
|
+
'model_name': ('combo', ['cyto', 'cyto_2', 'cyto_3', 'nuclei'], 'cyto'),
|
414
|
+
'regression_type': ('combo', ['ols','gls','wls','rlm','glm','mixed','quantile','logit','probit','poisson','lasso','ridge'], 'ols'),
|
415
|
+
'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
|
416
|
+
'model_type': ('combo', torchvision_models, 'resnet50'),
|
417
|
+
'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
|
418
|
+
'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
|
419
|
+
'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
|
420
|
+
'normalize_by': ('combo', ['fov', 'png'], 'png'),
|
421
|
+
'agg_type': ('combo', ['mean', 'median'], 'mean'),
|
422
|
+
'grouping': ('combo', ['mean', 'median'], 'mean'),
|
423
|
+
'min_max': ('combo', ['allq', 'all'], 'allq'),
|
424
|
+
'transform': ('combo', ['log', 'sqrt', 'square', None], None)
|
425
|
+
}
|
426
|
+
|
427
|
+
for key, value in settings.items():
|
428
|
+
if key in special_cases:
|
429
|
+
variables[key] = special_cases[key]
|
430
|
+
elif isinstance(value, bool):
|
431
|
+
variables[key] = ('check', None, value)
|
432
|
+
elif isinstance(value, int) or isinstance(value, float):
|
433
|
+
variables[key] = ('entry', None, value)
|
434
|
+
elif isinstance(value, str):
|
435
|
+
variables[key] = ('entry', None, value)
|
436
|
+
elif value is None:
|
437
|
+
variables[key] = ('entry', None, value)
|
438
|
+
elif isinstance(value, list):
|
439
|
+
variables[key] = ('entry', None, str(value))
|
440
|
+
else:
|
441
|
+
variables[key] = ('entry', None, str(value))
|
442
|
+
|
443
|
+
return variables
|
444
|
+
|
445
|
+
|
446
|
+
def spacrFigShow(fig_queue=None):
|
447
|
+
"""
|
448
|
+
Replacement for plt.show() that queues figures instead of displaying them.
|
449
|
+
"""
|
450
|
+
fig = plt.gcf()
|
451
|
+
if fig_queue:
|
452
|
+
fig_queue.put(fig)
|
453
|
+
else:
|
454
|
+
fig.show()
|
455
|
+
plt.close(fig)
|
456
|
+
|
457
|
+
def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imports=1):
|
458
|
+
|
459
|
+
"""
|
460
|
+
Wraps the run_multiple_simulations function to integrate with GUI processes.
|
461
|
+
|
462
|
+
Parameters:
|
463
|
+
- settings: dict, The settings for the run_multiple_simulations function.
|
464
|
+
- q: multiprocessing.Queue, Queue for logging messages to the GUI.
|
465
|
+
- fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
|
466
|
+
"""
|
467
|
+
|
468
|
+
# Temporarily override plt.show
|
469
|
+
original_show = plt.show
|
470
|
+
plt.show = lambda: spacrFigShow(fig_queue)
|
471
|
+
|
472
|
+
try:
|
473
|
+
if imports == 1:
|
474
|
+
function(settings=settings)
|
475
|
+
elif imports == 2:
|
476
|
+
function(src=settings['src'], settings=settings)
|
477
|
+
except Exception as e:
|
478
|
+
# Send the error message to the GUI via the queue
|
479
|
+
errorMessage = f"Error during processing: {e}"
|
480
|
+
q.put(errorMessage)
|
481
|
+
traceback.print_exc()
|
482
|
+
finally:
|
483
|
+
# Restore the original plt.show function
|
484
|
+
plt.show = original_show
|
485
|
+
|
486
|
+
def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
|
487
|
+
|
488
|
+
from .gui_utils import process_stdout_stderr
|
489
|
+
from .core import generate_image_umap, preprocess_generate_masks, generate_ml_scores, identify_masks_finetune, check_cellpose_models, analyze_recruitment, train_cellpose, compare_cellpose_masks, analyze_plaques, generate_dataset, apply_model_to_tar
|
490
|
+
from .io import generate_cellpose_train_test
|
491
|
+
from .measure import measure_crop
|
492
|
+
from .sim import run_multiple_simulations
|
493
|
+
from .deep_spacr import deep_spacr
|
494
|
+
from .sequencing import generate_barecode_mapping, perform_regression
|
495
|
+
process_stdout_stderr(q)
|
496
|
+
|
497
|
+
print(f'run_function_gui settings_type: {settings_type}')
|
498
|
+
|
499
|
+
if settings_type == 'mask':
|
500
|
+
function = preprocess_generate_masks
|
501
|
+
imports = 2
|
502
|
+
elif settings_type == 'measure':
|
503
|
+
function = measure_crop
|
504
|
+
imports = 1
|
505
|
+
elif settings_type == 'simulation':
|
506
|
+
function = run_multiple_simulations
|
507
|
+
imports = 1
|
508
|
+
elif settings_type == 'classify':
|
509
|
+
function = deep_spacr
|
510
|
+
imports = 1
|
511
|
+
elif settings_type == 'train_cellpose':
|
512
|
+
function = train_cellpose
|
513
|
+
imports = 1
|
514
|
+
elif settings_type == 'ml_analyze':
|
515
|
+
function = generate_ml_scores
|
516
|
+
imports = 2
|
517
|
+
elif settings_type == 'cellpose_masks':
|
518
|
+
function = identify_masks_finetune
|
519
|
+
imports = 1
|
520
|
+
elif settings_type == 'cellpose_all':
|
521
|
+
function = check_cellpose_models
|
522
|
+
imports = 1
|
523
|
+
elif settings_type == 'map_barcodes':
|
524
|
+
function = generate_barecode_mapping
|
525
|
+
imports = 1
|
526
|
+
elif settings_type == 'regression':
|
527
|
+
function = perform_regression
|
528
|
+
imports = 2
|
529
|
+
elif settings_type == 'recruitment':
|
530
|
+
function = analyze_recruitment
|
531
|
+
imports = 1
|
532
|
+
elif settings_type == 'umap':
|
533
|
+
function = generate_image_umap
|
534
|
+
imports = 1
|
535
|
+
else:
|
536
|
+
raise ValueError(f"Invalid settings type: {settings_type}")
|
537
|
+
try:
|
538
|
+
function_gui_wrapper(function, settings, q, fig_queue, imports)
|
539
|
+
except Exception as e:
|
540
|
+
q.put(f"Error during processing: {e}")
|
541
|
+
traceback.print_exc()
|
542
|
+
finally:
|
543
|
+
stop_requested.value = 1
|
544
|
+
|
545
|
+
def hide_all_settings(vars_dict, categories):
|
546
|
+
"""
|
547
|
+
Function to initially hide all settings in the GUI.
|
548
|
+
|
549
|
+
Parameters:
|
550
|
+
- categories: dict, The categories of settings with their corresponding settings.
|
551
|
+
- vars_dict: dict, The dictionary containing the settings and their corresponding widgets.
|
552
|
+
"""
|
352
553
|
|
554
|
+
if categories is None:
|
555
|
+
from .settings import categories
|
556
|
+
|
557
|
+
for category, settings in categories.items():
|
558
|
+
if any(setting in vars_dict for setting in settings):
|
559
|
+
vars_dict[category] = (None, None, tk.IntVar(value=0), None)
|
560
|
+
|
561
|
+
# Initially hide all settings
|
562
|
+
for setting in settings:
|
563
|
+
if setting in vars_dict:
|
564
|
+
label, widget, _, frame = vars_dict[setting]
|
565
|
+
label.grid_remove()
|
566
|
+
widget.grid_remove()
|
567
|
+
frame.grid_remove()
|
568
|
+
return vars_dict
|
569
|
+
|
570
|
+
def setup_frame(parent_frame):
|
571
|
+
from .gui_elements import set_dark_style, set_element_size
|
572
|
+
|
573
|
+
style = ttk.Style(parent_frame)
|
574
|
+
size_dict = set_element_size()
|
575
|
+
style_out = set_dark_style(style)
|
576
|
+
|
577
|
+
# Configure the main layout using PanedWindow
|
578
|
+
main_paned = tk.PanedWindow(parent_frame, orient=tk.HORIZONTAL, bg=style_out['bg_color'], bd=0, relief='flat')
|
579
|
+
main_paned.grid(row=0, column=0, sticky="nsew")
|
580
|
+
|
581
|
+
# Allow the main_paned to expand and fill the window
|
582
|
+
parent_frame.grid_rowconfigure(0, weight=1)
|
583
|
+
parent_frame.grid_columnconfigure(0, weight=1)
|
584
|
+
|
585
|
+
# Create the settings container on the left
|
586
|
+
settings_container = tk.PanedWindow(main_paned, orient=tk.VERTICAL, width=size_dict['settings_width'], bg=style_out['bg_color'], bd=0, relief='flat')
|
587
|
+
main_paned.add(settings_container, minsize=100) # Allow resizing with a minimum size
|
588
|
+
|
589
|
+
# Create a right container frame to hold vertical and horizontal containers
|
590
|
+
right_frame = tk.Frame(main_paned, bg=style_out['bg_color'], bd=0, highlightthickness=0, relief='flat')
|
591
|
+
main_paned.add(right_frame, stretch="always")
|
592
|
+
|
593
|
+
# Configure the right_frame grid layout
|
594
|
+
right_frame.grid_rowconfigure(0, weight=1) # Vertical container expands
|
595
|
+
right_frame.grid_rowconfigure(1, weight=0) # Horizontal container at bottom
|
596
|
+
right_frame.grid_columnconfigure(0, weight=1)
|
597
|
+
|
598
|
+
# Inside right_frame, add vertical_container at the top
|
599
|
+
vertical_container = tk.PanedWindow(right_frame, orient=tk.VERTICAL, bg=style_out['bg_color'], bd=0, relief='flat')
|
600
|
+
vertical_container.grid(row=0, column=0, sticky="nsew")
|
601
|
+
|
602
|
+
# Add horizontal_container aligned with the bottom of settings_container
|
603
|
+
horizontal_container = tk.PanedWindow(right_frame, orient=tk.HORIZONTAL, height=size_dict['panel_height'], bg=style_out['bg_color'], bd=0, relief='flat')
|
604
|
+
horizontal_container.grid(row=1, column=0, sticky="ew")
|
605
|
+
|
606
|
+
# Example content for settings_container
|
607
|
+
tk.Label(settings_container, text="Settings Container", bg=style_out['bg_color']).pack(fill=tk.BOTH, expand=True)
|
608
|
+
|
609
|
+
set_dark_style(style, parent_frame, [settings_container, vertical_container, horizontal_container, main_paned])
|
610
|
+
|
611
|
+
return parent_frame, vertical_container, horizontal_container, settings_container
|
612
|
+
|
613
|
+
|
614
|
+
def download_hug_dataset(q, vars_dict):
|
615
|
+
dataset_repo_id = "einarolafsson/toxo_mito"
|
616
|
+
settings_repo_id = "einarolafsson/spacr_settings"
|
617
|
+
dataset_subfolder = "plate1"
|
618
|
+
local_dir = os.path.join(os.path.expanduser("~"), "datasets")
|
619
|
+
|
620
|
+
# Download the dataset
|
621
|
+
try:
|
622
|
+
dataset_path = download_dataset(q, dataset_repo_id, dataset_subfolder, local_dir)
|
623
|
+
if 'src' in vars_dict:
|
624
|
+
vars_dict['src'][2].set(dataset_path)
|
625
|
+
q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
|
626
|
+
q.put(f"Dataset downloaded to: {dataset_path}\n")
|
627
|
+
except Exception as e:
|
628
|
+
q.put(f"Failed to download dataset: {e}\n")
|
629
|
+
|
630
|
+
# Download the settings files
|
631
|
+
try:
|
632
|
+
settings_path = download_dataset(q, settings_repo_id, "", local_dir)
|
633
|
+
q.put(f"Settings downloaded to: {settings_path}\n")
|
634
|
+
except Exception as e:
|
635
|
+
q.put(f"Failed to download settings: {e}\n")
|
636
|
+
|
637
|
+
def download_dataset(q, repo_id, subfolder, local_dir=None, retries=5, delay=5):
|
638
|
+
"""
|
639
|
+
Downloads a dataset or settings files from Hugging Face and returns the local path.
|
640
|
+
|
641
|
+
Args:
|
642
|
+
repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito' or 'einarolafsson/spacr_settings').
|
643
|
+
subfolder (str): The subfolder path within the repository (e.g., 'plate1' or the settings subfolder).
|
644
|
+
local_dir (str): The local directory where the files will be saved. Defaults to the user's home directory.
|
645
|
+
retries (int): Number of retry attempts in case of failure.
|
646
|
+
delay (int): Delay in seconds between retries.
|
647
|
+
|
648
|
+
Returns:
|
649
|
+
str: The local path to the downloaded files.
|
650
|
+
"""
|
651
|
+
if local_dir is None:
|
652
|
+
local_dir = os.path.join(os.path.expanduser("~"), "datasets")
|
653
|
+
|
654
|
+
local_subfolder_dir = os.path.join(local_dir, subfolder if subfolder else "settings")
|
655
|
+
if not os.path.exists(local_subfolder_dir):
|
656
|
+
os.makedirs(local_subfolder_dir)
|
657
|
+
elif len(os.listdir(local_subfolder_dir)) > 0:
|
658
|
+
q.put(f"Files already downloaded to: {local_subfolder_dir}")
|
659
|
+
return local_subfolder_dir
|
660
|
+
|
661
|
+
attempt = 0
|
662
|
+
while attempt < retries:
|
663
|
+
try:
|
664
|
+
files = list_repo_files(repo_id, repo_type="dataset")
|
665
|
+
subfolder_files = [file for file in files if file.startswith(subfolder) or (subfolder == "" and file.endswith('.csv'))]
|
666
|
+
|
667
|
+
for file_name in subfolder_files:
|
668
|
+
for download_attempt in range(retries):
|
669
|
+
try:
|
670
|
+
url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
|
671
|
+
response = requests.get(url, stream=True)
|
672
|
+
response.raise_for_status()
|
673
|
+
|
674
|
+
local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
|
675
|
+
with open(local_file_path, 'wb') as file:
|
676
|
+
for chunk in response.iter_content(chunk_size=8192):
|
677
|
+
file.write(chunk)
|
678
|
+
q.put(f"Downloaded file: {file_name}")
|
679
|
+
break
|
680
|
+
except (requests.HTTPError, requests.Timeout) as e:
|
681
|
+
q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
|
682
|
+
time.sleep(delay)
|
683
|
+
else:
|
684
|
+
raise Exception(f"Failed to download {file_name} after multiple attempts.")
|
685
|
+
|
686
|
+
return local_subfolder_dir
|
687
|
+
|
688
|
+
except (requests.HTTPError, requests.Timeout) as e:
|
689
|
+
q.put(f"Error downloading files: {e}. Retrying in {delay} seconds...")
|
690
|
+
attempt += 1
|
691
|
+
time.sleep(delay)
|
692
|
+
|
693
|
+
raise Exception("Failed to download files after multiple attempts.")
|
694
|
+
|
695
|
+
def ensure_after_tasks(frame):
|
696
|
+
if not hasattr(frame, 'after_tasks'):
|
697
|
+
frame.after_tasks = []
|