spacr 0.1.50__py3-none-any.whl → 0.1.61__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 +8 -2
- spacr/app_annotate.py +3 -3
- spacr/app_make_masks.py +3 -6
- spacr/app_make_masks_v2.py +4 -4
- spacr/app_sequencing.py +8 -0
- spacr/app_umap.py +8 -0
- spacr/core.py +9 -7
- spacr/gui.py +12 -6
- spacr/gui_core.py +608 -0
- spacr/gui_elements.py +322 -0
- spacr/gui_run.py +58 -0
- spacr/gui_utils.py +21 -1407
- spacr/gui_wrappers.py +137 -0
- spacr/measure.py +6 -8
- spacr/plot.py +53 -1
- spacr/sequencing.py +1 -17
- spacr/settings.py +428 -4
- spacr/utils.py +19 -11
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/METADATA +1 -1
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/RECORD +24 -18
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/LICENSE +0 -0
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/WHEEL +0 -0
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/entry_points.txt +0 -0
- {spacr-0.1.50.dist-info → spacr-0.1.61.dist-info}/top_level.txt +0 -0
spacr/gui_core.py
ADDED
@@ -0,0 +1,608 @@
|
|
1
|
+
import os, traceback, ctypes, matplotlib, requests, csv, re
|
2
|
+
matplotlib.use('Agg')
|
3
|
+
import tkinter as tk
|
4
|
+
from tkinter import ttk
|
5
|
+
import tkinter.font as tkFont
|
6
|
+
from tkinter import filedialog
|
7
|
+
from tkinter import font as tkFont
|
8
|
+
from multiprocessing import Process, Value, Queue, Manager, set_start_method
|
9
|
+
from tkinter import ttk, scrolledtext
|
10
|
+
from matplotlib.figure import Figure
|
11
|
+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
12
|
+
import time
|
13
|
+
import requests
|
14
|
+
from huggingface_hub import list_repo_files
|
15
|
+
|
16
|
+
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
|
17
|
+
from .gui_elements import create_menu_bar, spacrButton, spacrLabel, spacrFrame, spacrCheckbutton, spacrDropdownMenu ,set_dark_style, set_default_font
|
18
|
+
from . gui_run import run_mask_gui, run_measure_gui, run_classify_gui, run_sequencing_gui, run_umap_gui
|
19
|
+
|
20
|
+
try:
|
21
|
+
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
22
|
+
except AttributeError:
|
23
|
+
pass
|
24
|
+
|
25
|
+
# Define global variables
|
26
|
+
q = None
|
27
|
+
console_output = None
|
28
|
+
parent_frame = None
|
29
|
+
vars_dict = None
|
30
|
+
canvas = None
|
31
|
+
canvas_widget = None
|
32
|
+
scrollable_frame = None
|
33
|
+
progress_label = None
|
34
|
+
fig_queue = None
|
35
|
+
|
36
|
+
thread_control = {"run_thread": None, "stop_requested": False}
|
37
|
+
|
38
|
+
def initiate_abort():
|
39
|
+
global thread_control
|
40
|
+
if thread_control.get("stop_requested") is not None:
|
41
|
+
if isinstance(thread_control["stop_requested"], Value):
|
42
|
+
thread_control["stop_requested"].value = 1
|
43
|
+
|
44
|
+
if thread_control.get("run_thread") is not None:
|
45
|
+
thread_control["run_thread"].join(timeout=5)
|
46
|
+
if thread_control["run_thread"].is_alive():
|
47
|
+
thread_control["run_thread"].terminate()
|
48
|
+
thread_control["run_thread"] = None
|
49
|
+
|
50
|
+
def start_process(q, fig_queue, settings_type='mask'):
|
51
|
+
global thread_control, vars_dict
|
52
|
+
from .settings import check_settings
|
53
|
+
|
54
|
+
settings = check_settings(vars_dict)
|
55
|
+
if thread_control.get("run_thread") is not None:
|
56
|
+
initiate_abort()
|
57
|
+
stop_requested = Value('i', 0) # multiprocessing shared value for inter-process communication
|
58
|
+
thread_control["stop_requested"] = stop_requested
|
59
|
+
if settings_type == 'mask':
|
60
|
+
thread_control["run_thread"] = Process(target=run_mask_gui, args=(settings, q, fig_queue, stop_requested))
|
61
|
+
elif settings_type == 'measure':
|
62
|
+
thread_control["run_thread"] = Process(target=run_measure_gui, args=(settings, q, fig_queue, stop_requested))
|
63
|
+
elif settings_type == 'classify':
|
64
|
+
thread_control["run_thread"] = Process(target=run_classify_gui, args=(settings, q, fig_queue, stop_requested))
|
65
|
+
elif settings_type == 'sequencing':
|
66
|
+
thread_control["run_thread"] = Process(target=run_sequencing_gui, args=(settings, q, fig_queue, stop_requested))
|
67
|
+
elif settings_type == 'umap':
|
68
|
+
thread_control["run_thread"] = Process(target=run_umap_gui, args=(settings, q, fig_queue, stop_requested))
|
69
|
+
thread_control["run_thread"].start()
|
70
|
+
|
71
|
+
def import_settings(settings_type='mask'):
|
72
|
+
|
73
|
+
global vars_dict, scrollable_frame
|
74
|
+
from .settings import generate_fields
|
75
|
+
|
76
|
+
def read_settings_from_csv(csv_file_path):
|
77
|
+
settings = {}
|
78
|
+
with open(csv_file_path, newline='') as csvfile:
|
79
|
+
reader = csv.DictReader(csvfile)
|
80
|
+
for row in reader:
|
81
|
+
key = row['Key']
|
82
|
+
value = row['Value']
|
83
|
+
settings[key] = value
|
84
|
+
return settings
|
85
|
+
|
86
|
+
def update_settings_from_csv(variables, csv_settings):
|
87
|
+
new_settings = variables.copy() # Start with a copy of the original settings
|
88
|
+
for key, value in csv_settings.items():
|
89
|
+
if key in new_settings:
|
90
|
+
# Get the variable type and options from the original settings
|
91
|
+
var_type, options, _ = new_settings[key]
|
92
|
+
# Update the default value with the CSV value, keeping the type and options unchanged
|
93
|
+
new_settings[key] = (var_type, options, value)
|
94
|
+
return new_settings
|
95
|
+
|
96
|
+
csv_file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
|
97
|
+
|
98
|
+
if not csv_file_path: # If no file is selected, return early
|
99
|
+
return
|
100
|
+
|
101
|
+
csv_settings = read_settings_from_csv(csv_file_path)
|
102
|
+
if settings_type == 'mask':
|
103
|
+
settings = set_default_settings_preprocess_generate_masks(src='path', settings={})
|
104
|
+
elif settings_type == 'measure':
|
105
|
+
settings = get_measure_crop_settings(settings={})
|
106
|
+
elif settings_type == 'classify':
|
107
|
+
settings = set_default_train_test_model(settings={})
|
108
|
+
elif settings_type == 'sequencing':
|
109
|
+
settings = get_analyze_reads_default_settings(settings={})
|
110
|
+
elif settings_type == 'umap':
|
111
|
+
settings = set_default_umap_image_settings(settings={})
|
112
|
+
else:
|
113
|
+
raise ValueError(f"Invalid settings type: {settings_type}")
|
114
|
+
|
115
|
+
variables = convert_settings_dict_for_gui(settings)
|
116
|
+
new_settings = update_settings_from_csv(variables, csv_settings)
|
117
|
+
vars_dict = generate_fields(new_settings, scrollable_frame)
|
118
|
+
|
119
|
+
def convert_settings_dict_for_gui(settings):
|
120
|
+
variables = {}
|
121
|
+
special_cases = {
|
122
|
+
'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
|
123
|
+
'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
|
124
|
+
'cell_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
|
125
|
+
'nucleus_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
|
126
|
+
'pathogen_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
|
127
|
+
#'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
|
128
|
+
'magnification': ('combo', [20, 40, 60], 20),
|
129
|
+
'nucleus_channel': ('combo', [0, 1, 2, 3, None], None),
|
130
|
+
'cell_channel': ('combo', [0, 1, 2, 3, None], None),
|
131
|
+
'pathogen_channel': ('combo', [0, 1, 2, 3, None], None),
|
132
|
+
'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
|
133
|
+
'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
|
134
|
+
'model_type': ('combo', ['resnet50', 'other_model'], 'resnet50'),
|
135
|
+
'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
|
136
|
+
'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
|
137
|
+
'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
|
138
|
+
'normalize_by': ('combo', ['fov', 'png'], 'png'),
|
139
|
+
}
|
140
|
+
|
141
|
+
for key, value in settings.items():
|
142
|
+
if key in special_cases:
|
143
|
+
variables[key] = special_cases[key]
|
144
|
+
elif isinstance(value, bool):
|
145
|
+
variables[key] = ('check', None, value)
|
146
|
+
elif isinstance(value, int) or isinstance(value, float):
|
147
|
+
variables[key] = ('entry', None, value)
|
148
|
+
elif isinstance(value, str):
|
149
|
+
variables[key] = ('entry', None, value)
|
150
|
+
elif value is None:
|
151
|
+
variables[key] = ('entry', None, value)
|
152
|
+
elif isinstance(value, list):
|
153
|
+
variables[key] = ('entry', None, str(value))
|
154
|
+
else:
|
155
|
+
variables[key] = ('entry', None, str(value))
|
156
|
+
return variables
|
157
|
+
|
158
|
+
def setup_settings_panel(vertical_container, settings_type='mask', frame_height=500, frame_width=1000):
|
159
|
+
global vars_dict, scrollable_frame
|
160
|
+
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
|
161
|
+
|
162
|
+
settings_frame = tk.Frame(vertical_container, bg='black', height=frame_height, width=frame_width)
|
163
|
+
vertical_container.add(settings_frame, stretch="always")
|
164
|
+
settings_label = spacrLabel(settings_frame, text="Settings", background="black", foreground="white", anchor='center', justify='center', align="center")
|
165
|
+
settings_label.grid(row=0, column=0, pady=10, padx=10)
|
166
|
+
scrollable_frame = spacrFrame(settings_frame, bg='black', width=frame_width)
|
167
|
+
scrollable_frame.grid(row=1, column=0, sticky="nsew")
|
168
|
+
settings_frame.grid_rowconfigure(1, weight=1)
|
169
|
+
settings_frame.grid_columnconfigure(0, weight=1)
|
170
|
+
|
171
|
+
if settings_type == 'mask':
|
172
|
+
settings = set_default_settings_preprocess_generate_masks(src='path', settings={})
|
173
|
+
elif settings_type == 'measure':
|
174
|
+
settings = get_measure_crop_settings(settings={})
|
175
|
+
elif settings_type == 'classify':
|
176
|
+
settings = set_default_train_test_model(settings={})
|
177
|
+
elif settings_type == 'sequencing':
|
178
|
+
settings = get_analyze_reads_default_settings(settings={})
|
179
|
+
elif settings_type == 'umap':
|
180
|
+
settings = set_default_umap_image_settings(settings={})
|
181
|
+
else:
|
182
|
+
raise ValueError(f"Invalid settings type: {settings_type}")
|
183
|
+
|
184
|
+
variables = convert_settings_dict_for_gui(settings)
|
185
|
+
vars_dict = generate_fields(variables, scrollable_frame)
|
186
|
+
print("Settings panel setup complete")
|
187
|
+
return scrollable_frame, vars_dict
|
188
|
+
|
189
|
+
def setup_plot_section(vertical_container):
|
190
|
+
global canvas, canvas_widget
|
191
|
+
plot_frame = tk.PanedWindow(vertical_container, orient=tk.VERTICAL)
|
192
|
+
vertical_container.add(plot_frame, stretch="always")
|
193
|
+
figure = Figure(figsize=(30, 4), dpi=100, facecolor='black')
|
194
|
+
plot = figure.add_subplot(111)
|
195
|
+
plot.plot([], []) # This creates an empty plot.
|
196
|
+
plot.axis('off')
|
197
|
+
canvas = FigureCanvasTkAgg(figure, master=plot_frame)
|
198
|
+
canvas.get_tk_widget().configure(cursor='arrow', background='black', highlightthickness=0)
|
199
|
+
canvas_widget = canvas.get_tk_widget()
|
200
|
+
plot_frame.add(canvas_widget, stretch="always")
|
201
|
+
canvas.draw()
|
202
|
+
canvas.figure = figure
|
203
|
+
return canvas, canvas_widget
|
204
|
+
|
205
|
+
def setup_console(vertical_container):
|
206
|
+
global console_output
|
207
|
+
print("Setting up console frame")
|
208
|
+
console_frame = tk.Frame(vertical_container, bg='black')
|
209
|
+
vertical_container.add(console_frame, stretch="always")
|
210
|
+
console_label = spacrLabel(console_frame, text="Console", background="black", foreground="white", anchor='center', justify='center', align="center")
|
211
|
+
console_label.grid(row=0, column=0, pady=10, padx=10)
|
212
|
+
console_output = scrolledtext.ScrolledText(console_frame, height=10, bg='black', fg='white', insertbackground='white')
|
213
|
+
console_output.grid(row=1, column=0, sticky="nsew")
|
214
|
+
console_frame.grid_rowconfigure(1, weight=1)
|
215
|
+
console_frame.grid_columnconfigure(0, weight=1)
|
216
|
+
print("Console setup complete")
|
217
|
+
return console_output
|
218
|
+
|
219
|
+
def setup_progress_frame(vertical_container):
|
220
|
+
global progress_output
|
221
|
+
progress_frame = tk.Frame(vertical_container, bg='black')
|
222
|
+
vertical_container.add(progress_frame, stretch="always")
|
223
|
+
label_frame = tk.Frame(progress_frame, bg='black')
|
224
|
+
label_frame.grid(row=0, column=0, sticky="ew", pady=(5, 0), padx=10)
|
225
|
+
progress_label = spacrLabel(label_frame, text="Processing: 0%", background="black", foreground="white", font=('Helvetica', 12), anchor='w', justify='left', align="left")
|
226
|
+
progress_label.grid(row=0, column=0, sticky="w")
|
227
|
+
progress_output = scrolledtext.ScrolledText(progress_frame, height=10, bg='black', fg='white', insertbackground='white')
|
228
|
+
progress_output.grid(row=1, column=0, sticky="nsew")
|
229
|
+
progress_frame.grid_rowconfigure(1, weight=1)
|
230
|
+
progress_frame.grid_columnconfigure(0, weight=1)
|
231
|
+
print("Progress frame setup complete")
|
232
|
+
return progress_output
|
233
|
+
|
234
|
+
def download_hug_dataset():
|
235
|
+
global vars_dict, q
|
236
|
+
dataset_repo_id = "einarolafsson/toxo_mito"
|
237
|
+
settings_repo_id = "einarolafsson/spacr_settings"
|
238
|
+
dataset_subfolder = "plate1"
|
239
|
+
local_dir = os.path.join(os.path.expanduser("~"), "datasets") # Set to the home directory
|
240
|
+
|
241
|
+
# Download the dataset
|
242
|
+
try:
|
243
|
+
dataset_path = download_dataset(dataset_repo_id, dataset_subfolder, local_dir)
|
244
|
+
if 'src' in vars_dict:
|
245
|
+
vars_dict['src'][2].set(dataset_path)
|
246
|
+
q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
|
247
|
+
q.put(f"Dataset downloaded to: {dataset_path}\n")
|
248
|
+
except Exception as e:
|
249
|
+
q.put(f"Failed to download dataset: {e}\n")
|
250
|
+
|
251
|
+
# Download the settings files
|
252
|
+
try:
|
253
|
+
settings_path = download_dataset(settings_repo_id, "", local_dir)
|
254
|
+
q.put(f"Settings downloaded to: {settings_path}\n")
|
255
|
+
except Exception as e:
|
256
|
+
q.put(f"Failed to download settings: {e}\n")
|
257
|
+
|
258
|
+
def download_dataset(repo_id, subfolder, local_dir=None, retries=5, delay=5):
|
259
|
+
global q
|
260
|
+
"""
|
261
|
+
Downloads a dataset or settings files from Hugging Face and returns the local path.
|
262
|
+
|
263
|
+
Args:
|
264
|
+
repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito' or 'einarolafsson/spacr_settings').
|
265
|
+
subfolder (str): The subfolder path within the repository (e.g., 'plate1' or the settings subfolder).
|
266
|
+
local_dir (str): The local directory where the files will be saved. Defaults to the user's home directory.
|
267
|
+
retries (int): Number of retry attempts in case of failure.
|
268
|
+
delay (int): Delay in seconds between retries.
|
269
|
+
|
270
|
+
Returns:
|
271
|
+
str: The local path to the downloaded files.
|
272
|
+
"""
|
273
|
+
if local_dir is None:
|
274
|
+
local_dir = os.path.join(os.path.expanduser("~"), "datasets")
|
275
|
+
|
276
|
+
local_subfolder_dir = os.path.join(local_dir, subfolder if subfolder else "settings")
|
277
|
+
if not os.path.exists(local_subfolder_dir):
|
278
|
+
os.makedirs(local_subfolder_dir)
|
279
|
+
elif len(os.listdir(local_subfolder_dir)) > 0:
|
280
|
+
q.put(f"Files already downloaded to: {local_subfolder_dir}")
|
281
|
+
return local_subfolder_dir
|
282
|
+
|
283
|
+
attempt = 0
|
284
|
+
while attempt < retries:
|
285
|
+
try:
|
286
|
+
files = list_repo_files(repo_id, repo_type="dataset")
|
287
|
+
subfolder_files = [file for file in files if file.startswith(subfolder) or (subfolder == "" and file.endswith('.csv'))]
|
288
|
+
|
289
|
+
for file_name in subfolder_files:
|
290
|
+
for download_attempt in range(retries):
|
291
|
+
try:
|
292
|
+
url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
|
293
|
+
response = requests.get(url, stream=True)
|
294
|
+
response.raise_for_status()
|
295
|
+
|
296
|
+
local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
|
297
|
+
with open(local_file_path, 'wb') as file:
|
298
|
+
for chunk in response.iter_content(chunk_size=8192):
|
299
|
+
file.write(chunk)
|
300
|
+
q.put(f"Downloaded file: {file_name}")
|
301
|
+
break
|
302
|
+
except (requests.HTTPError, requests.Timeout) as e:
|
303
|
+
q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
|
304
|
+
time.sleep(delay)
|
305
|
+
else:
|
306
|
+
raise Exception(f"Failed to download {file_name} after multiple attempts.")
|
307
|
+
|
308
|
+
return local_subfolder_dir
|
309
|
+
|
310
|
+
except (requests.HTTPError, requests.Timeout) as e:
|
311
|
+
q.put(f"Error downloading files: {e}. Retrying in {delay} seconds...")
|
312
|
+
attempt += 1
|
313
|
+
time.sleep(delay)
|
314
|
+
|
315
|
+
raise Exception("Failed to download files after multiple attempts.")
|
316
|
+
|
317
|
+
|
318
|
+
def setup_button_section(horizontal_container, settings_type='mask', settings_row=5, run=True, abort=True, download=True, import_btn=True):
|
319
|
+
global button_frame, run_button, abort_button, download_dataset_button, import_button, q, fig_queue, vars_dict
|
320
|
+
|
321
|
+
button_frame = tk.Frame(horizontal_container, bg='black')
|
322
|
+
horizontal_container.add(button_frame, stretch="always", sticky="nsew")
|
323
|
+
button_frame.grid_rowconfigure(0, weight=0)
|
324
|
+
button_frame.grid_rowconfigure(1, weight=1)
|
325
|
+
button_frame.grid_columnconfigure(0, weight=1)
|
326
|
+
|
327
|
+
categories_label = spacrLabel(button_frame, text="Categories", background="black", foreground="white", font=('Helvetica', 12), anchor='center', justify='center', align="center") # Increase font size
|
328
|
+
categories_label.grid(row=0, column=0, pady=10, padx=10)
|
329
|
+
|
330
|
+
button_scrollable_frame = spacrFrame(button_frame, bg='black')
|
331
|
+
button_scrollable_frame.grid(row=1, column=0, sticky="nsew")
|
332
|
+
|
333
|
+
btn_col = 0
|
334
|
+
btn_row = 1
|
335
|
+
|
336
|
+
if run:
|
337
|
+
run_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Run", command=lambda: start_process(q, fig_queue, settings_type), font=('Helvetica', 12))
|
338
|
+
run_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
|
339
|
+
btn_row += 1
|
340
|
+
|
341
|
+
if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap']:
|
342
|
+
abort_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort, font=('Helvetica', 12))
|
343
|
+
abort_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
|
344
|
+
btn_row += 1
|
345
|
+
|
346
|
+
if download and settings_type in ['mask']:
|
347
|
+
download_dataset_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Download", command=download_hug_dataset, font=('Helvetica', 12))
|
348
|
+
download_dataset_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
|
349
|
+
btn_row += 1
|
350
|
+
|
351
|
+
if import_btn:
|
352
|
+
import_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Import", command=lambda: import_settings(settings_type), font=('Helvetica', 12))
|
353
|
+
import_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
|
354
|
+
|
355
|
+
# Call toggle_settings after vars_dict is initialized
|
356
|
+
if vars_dict is not None:
|
357
|
+
toggle_settings(button_scrollable_frame)
|
358
|
+
return button_scrollable_frame
|
359
|
+
|
360
|
+
def toggle_settings_v1(button_scrollable_frame):
|
361
|
+
global vars_dict
|
362
|
+
from .settings import categories
|
363
|
+
|
364
|
+
if vars_dict is None:
|
365
|
+
raise ValueError("vars_dict is not initialized.")
|
366
|
+
|
367
|
+
def toggle_category(settings, var):
|
368
|
+
for setting in settings:
|
369
|
+
if setting in vars_dict:
|
370
|
+
label, widget, _ = vars_dict[setting]
|
371
|
+
if var.get() == 0:
|
372
|
+
label.grid_remove()
|
373
|
+
widget.grid_remove()
|
374
|
+
else:
|
375
|
+
label.grid()
|
376
|
+
widget.grid()
|
377
|
+
|
378
|
+
row = 1
|
379
|
+
col = 3
|
380
|
+
category_idx = 0
|
381
|
+
|
382
|
+
for category, settings in categories.items():
|
383
|
+
if any(setting in vars_dict for setting in settings):
|
384
|
+
category_var = tk.IntVar(value=0)
|
385
|
+
vars_dict[category] = (None, None, category_var)
|
386
|
+
toggle = spacrCheckbutton(
|
387
|
+
button_scrollable_frame.scrollable_frame,
|
388
|
+
text=category,
|
389
|
+
variable=category_var,
|
390
|
+
command=lambda cat=settings, var=category_var: toggle_category(cat, var)
|
391
|
+
)
|
392
|
+
# Directly set the style
|
393
|
+
style = ttk.Style()
|
394
|
+
font_style = tkFont.Font(family="Helvetica", size=12, weight="bold")
|
395
|
+
style.configure('Spacr.TCheckbutton', font=font_style, background='black', foreground='#ffffff', indicatoron=False, relief='flat')
|
396
|
+
style.map('Spacr.TCheckbutton', background=[('selected', 'black'), ('active', 'black')], foreground=[('selected', '#ffffff'), ('active', '#ffffff')])
|
397
|
+
toggle.configure(style='Spacr.TCheckbutton')
|
398
|
+
toggle.grid(row=row, column=col, sticky="w", pady=2, padx=2)
|
399
|
+
#row += 1
|
400
|
+
col += 1
|
401
|
+
category_idx += 1
|
402
|
+
|
403
|
+
if category_idx % 4 == 0:
|
404
|
+
row += 1
|
405
|
+
col = 2
|
406
|
+
|
407
|
+
for settings in categories.values():
|
408
|
+
for setting in settings:
|
409
|
+
if setting in vars_dict:
|
410
|
+
label, widget, _ = vars_dict[setting]
|
411
|
+
label.grid_remove()
|
412
|
+
widget.grid_remove()
|
413
|
+
|
414
|
+
|
415
|
+
def toggle_settings(button_scrollable_frame):
|
416
|
+
global vars_dict
|
417
|
+
from .settings import categories
|
418
|
+
|
419
|
+
if vars_dict is None:
|
420
|
+
raise ValueError("vars_dict is not initialized.")
|
421
|
+
|
422
|
+
active_categories = set()
|
423
|
+
|
424
|
+
def toggle_category(settings):
|
425
|
+
for setting in settings:
|
426
|
+
if setting in vars_dict:
|
427
|
+
label, widget, _ = vars_dict[setting]
|
428
|
+
if widget.grid_info():
|
429
|
+
label.grid_remove()
|
430
|
+
widget.grid_remove()
|
431
|
+
else:
|
432
|
+
label.grid()
|
433
|
+
widget.grid()
|
434
|
+
|
435
|
+
def on_category_select(selected_category):
|
436
|
+
if selected_category == "Select Category":
|
437
|
+
return
|
438
|
+
#print(f"Selected category: {selected_category}")
|
439
|
+
if selected_category in categories:
|
440
|
+
toggle_category(categories[selected_category])
|
441
|
+
if selected_category in active_categories:
|
442
|
+
active_categories.remove(selected_category)
|
443
|
+
else:
|
444
|
+
active_categories.add(selected_category)
|
445
|
+
category_dropdown.update_styles(active_categories)
|
446
|
+
category_var.set("Select Category") # Reset dropdown text to "Select Category"
|
447
|
+
|
448
|
+
category_var = tk.StringVar()
|
449
|
+
non_empty_categories = [category for category, settings in categories.items() if any(setting in vars_dict for setting in settings)]
|
450
|
+
category_dropdown = spacrDropdownMenu(button_scrollable_frame.scrollable_frame, category_var, non_empty_categories, command=on_category_select)
|
451
|
+
category_dropdown.grid(row=1, column=3, sticky="ew", pady=2, padx=2)
|
452
|
+
|
453
|
+
for category, settings in categories.items():
|
454
|
+
if any(setting in vars_dict for setting in settings):
|
455
|
+
vars_dict[category] = (None, None, tk.IntVar(value=0))
|
456
|
+
# Initially hide all settings
|
457
|
+
for setting in settings:
|
458
|
+
if setting in vars_dict:
|
459
|
+
label, widget, _ = vars_dict[setting]
|
460
|
+
label.grid_remove()
|
461
|
+
widget.grid_remove()
|
462
|
+
|
463
|
+
def process_fig_queue():
|
464
|
+
global canvas, fig_queue, canvas_widget, parent_frame
|
465
|
+
|
466
|
+
def clear_canvas(canvas):
|
467
|
+
for ax in canvas.figure.get_axes():
|
468
|
+
ax.clear()
|
469
|
+
canvas.draw_idle()
|
470
|
+
|
471
|
+
try:
|
472
|
+
while not fig_queue.empty():
|
473
|
+
clear_canvas(canvas)
|
474
|
+
fig = fig_queue.get_nowait()
|
475
|
+
for ax in fig.get_axes():
|
476
|
+
ax.set_xticks([]) # Remove x-axis ticks
|
477
|
+
ax.set_yticks([]) # Remove y-axis ticks
|
478
|
+
ax.xaxis.set_visible(False) # Hide the x-axis
|
479
|
+
ax.yaxis.set_visible(False) # Hide the y-axis
|
480
|
+
fig.tight_layout()
|
481
|
+
fig.set_facecolor('black')
|
482
|
+
canvas.figure = fig
|
483
|
+
fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
|
484
|
+
fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
|
485
|
+
canvas.draw_idle()
|
486
|
+
except Exception as e:
|
487
|
+
traceback.print_exc()
|
488
|
+
finally:
|
489
|
+
after_id = canvas_widget.after(100, process_fig_queue)
|
490
|
+
parent_frame.after_tasks.append(after_id)
|
491
|
+
|
492
|
+
def process_console_queue():
|
493
|
+
global q, console_output, parent_frame
|
494
|
+
while not q.empty():
|
495
|
+
message = q.get_nowait()
|
496
|
+
console_output.insert(tk.END, message)
|
497
|
+
console_output.see(tk.END)
|
498
|
+
after_id = console_output.after(100, process_console_queue)
|
499
|
+
parent_frame.after_tasks.append(after_id)
|
500
|
+
|
501
|
+
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):
|
502
|
+
global q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, progress_label, fig_queue
|
503
|
+
q = q_var
|
504
|
+
console_output = console_output_var
|
505
|
+
parent_frame = parent_frame_var
|
506
|
+
vars_dict = vars_dict_var
|
507
|
+
canvas = canvas_var
|
508
|
+
canvas_widget = canvas_widget_var
|
509
|
+
scrollable_frame = scrollable_frame_var
|
510
|
+
progress_label = progress_label_var
|
511
|
+
fig_queue = fig_queue_var
|
512
|
+
|
513
|
+
def setup_frame(parent_frame):
|
514
|
+
style = ttk.Style(parent_frame)
|
515
|
+
set_dark_style(style)
|
516
|
+
set_default_font(parent_frame, font_name="Helvetica", size=8)
|
517
|
+
parent_frame.configure(bg='black')
|
518
|
+
parent_frame.grid_rowconfigure(0, weight=1)
|
519
|
+
parent_frame.grid_columnconfigure(0, weight=1)
|
520
|
+
vertical_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL, bg='black')
|
521
|
+
vertical_container.grid(row=0, column=0, sticky=tk.NSEW)
|
522
|
+
horizontal_container = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL, bg='black')
|
523
|
+
vertical_container.add(horizontal_container, stretch="always")
|
524
|
+
horizontal_container.grid_columnconfigure(0, weight=1)
|
525
|
+
horizontal_container.grid_columnconfigure(1, weight=1)
|
526
|
+
settings_frame = tk.Frame(horizontal_container, bg='black')
|
527
|
+
settings_frame.grid_rowconfigure(0, weight=0)
|
528
|
+
settings_frame.grid_rowconfigure(1, weight=1)
|
529
|
+
settings_frame.grid_columnconfigure(0, weight=1)
|
530
|
+
horizontal_container.add(settings_frame, stretch="always", sticky="nsew")
|
531
|
+
return parent_frame, vertical_container, horizontal_container
|
532
|
+
|
533
|
+
def initiate_root(parent, settings_type='mask'):
|
534
|
+
|
535
|
+
def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
|
536
|
+
try:
|
537
|
+
ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
|
538
|
+
while not q.empty():
|
539
|
+
message = q.get_nowait()
|
540
|
+
clean_message = ansi_escape_pattern.sub('', message)
|
541
|
+
if clean_message.startswith("Progress"):
|
542
|
+
progress_label.config(text=clean_message)
|
543
|
+
if clean_message.startswith("\rProgress"):
|
544
|
+
progress_label.config(text=clean_message)
|
545
|
+
elif clean_message.startswith("Successfully"):
|
546
|
+
progress_label.config(text=clean_message)
|
547
|
+
elif clean_message.startswith("Processing"):
|
548
|
+
progress_label.config(text=clean_message)
|
549
|
+
elif clean_message.startswith("scale"):
|
550
|
+
pass
|
551
|
+
elif clean_message.startswith("plot_cropped_arrays"):
|
552
|
+
pass
|
553
|
+
elif clean_message == "" or clean_message == "\r" or clean_message.strip() == "":
|
554
|
+
pass
|
555
|
+
else:
|
556
|
+
print(clean_message)
|
557
|
+
except Exception as e:
|
558
|
+
print(f"Error updating GUI canvas: {e}")
|
559
|
+
finally:
|
560
|
+
root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
|
561
|
+
|
562
|
+
global q, fig_queue, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, progress_label, button_scrollable_frame
|
563
|
+
print("Initializing root with settings_type:", settings_type)
|
564
|
+
parent_frame = parent
|
565
|
+
|
566
|
+
if not hasattr(parent_frame, 'after_tasks'):
|
567
|
+
parent_frame.after_tasks = []
|
568
|
+
|
569
|
+
for widget in parent_frame.winfo_children():
|
570
|
+
if widget.winfo_exists():
|
571
|
+
try:
|
572
|
+
widget.destroy()
|
573
|
+
except tk.TclError as e:
|
574
|
+
print(f"Error destroying widget: {e}")
|
575
|
+
|
576
|
+
q = Queue()
|
577
|
+
fig_queue = Queue()
|
578
|
+
parent_frame, vertical_container, horizontal_container = setup_frame(parent_frame)
|
579
|
+
scrollable_frame, vars_dict = setup_settings_panel(horizontal_container, settings_type) # Adjust height and width as needed
|
580
|
+
button_scrollable_frame = setup_button_section(horizontal_container, settings_type)
|
581
|
+
canvas, canvas_widget = setup_plot_section(vertical_container)
|
582
|
+
console_output = setup_console(vertical_container)
|
583
|
+
|
584
|
+
if settings_type in ['mask', 'measure', 'classify', 'sequencing']:
|
585
|
+
progress_output = setup_progress_frame(vertical_container)
|
586
|
+
else:
|
587
|
+
progress_output = None
|
588
|
+
|
589
|
+
set_globals(q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, progress_label, fig_queue)
|
590
|
+
process_console_queue()
|
591
|
+
process_fig_queue()
|
592
|
+
after_id = parent_frame.after(100, lambda: main_thread_update_function(parent_frame, q, fig_queue, canvas_widget, progress_label))
|
593
|
+
parent_frame.after_tasks.append(after_id)
|
594
|
+
print("Root initialization complete")
|
595
|
+
return parent_frame, vars_dict
|
596
|
+
|
597
|
+
def start_gui_app(settings_type='mask'):
|
598
|
+
global q, fig_queue, parent_frame, scrollable_frame, vars_dict, canvas, canvas_widget, progress_label
|
599
|
+
root = tk.Tk()
|
600
|
+
width = root.winfo_screenwidth()
|
601
|
+
height = root.winfo_screenheight()
|
602
|
+
root.geometry(f"{width}x{height}")
|
603
|
+
root.title(f"SpaCr: {settings_type.capitalize()}")
|
604
|
+
root.content_frame = tk.Frame(root)
|
605
|
+
print("Starting GUI app with settings_type:", settings_type)
|
606
|
+
initiate_root(root.content_frame, settings_type)
|
607
|
+
create_menu_bar(root)
|
608
|
+
root.mainloop()
|