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/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()