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