spacr 0.2.41__py3-none-any.whl → 0.2.45__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 CHANGED
@@ -1,6 +1,4 @@
1
- import os, traceback, ctypes, matplotlib, requests, csv, matplotlib, time, requests, re
2
- import matplotlib.pyplot as plt
3
- matplotlib.use('Agg')
1
+ import os, traceback, ctypes, requests, csv, time, requests, re
4
2
  import tkinter as tk
5
3
  from tkinter import ttk
6
4
  from tkinter import filedialog
@@ -9,14 +7,9 @@ from multiprocessing.sharedctypes import Synchronized
9
7
  from tkinter import ttk, scrolledtext
10
8
  from matplotlib.figure import Figure
11
9
  from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
12
- from huggingface_hub import list_repo_files
13
10
  import numpy as np
14
-
15
- import psutil, gpustat
11
+ import psutil
16
12
  import GPUtil
17
- from threading import Thread
18
- from time import sleep
19
-
20
13
 
21
14
  try:
22
15
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
@@ -24,7 +17,7 @@ except AttributeError:
24
17
  pass
25
18
 
26
19
  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
27
- from .gui_elements import spacrProgressBar, spacrButton, spacrLabel, spacrFrame, spacrDropdownMenu ,set_dark_style, set_default_font
20
+ from .gui_elements import spacrProgressBar, spacrButton, spacrLabel, spacrFrame, spacrDropdownMenu ,set_dark_style
28
21
 
29
22
  # Define global variables
30
23
  q = None
@@ -39,143 +32,92 @@ fig_queue = None
39
32
 
40
33
  thread_control = {"run_thread": None, "stop_requested": False}
41
34
 
42
- def initiate_abort():
43
- global thread_control
44
- if isinstance(thread_control.get("stop_requested"), Synchronized):
45
- thread_control["stop_requested"].value = 1
46
- if thread_control.get("run_thread") is not None:
47
- thread_control["run_thread"].terminate()
48
- thread_control["run_thread"].join()
49
- thread_control["run_thread"] = None
50
-
51
- def spacrFigShow(fig_queue=None):
52
- """
53
- Replacement for plt.show() that queues figures instead of displaying them.
54
- """
55
- fig = plt.gcf()
56
- if fig_queue:
57
- fig_queue.put(fig)
58
- else:
59
- fig.show()
60
- plt.close(fig)
35
+ def toggle_settings(button_scrollable_frame):
36
+ global vars_dict
37
+ from .settings import categories
38
+ from .gui_utils import hide_all_settings
39
+ if vars_dict is None:
40
+ raise ValueError("vars_dict is not initialized.")
61
41
 
62
- def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imports=1):
42
+ active_categories = set()
63
43
 
64
- """
65
- Wraps the run_multiple_simulations function to integrate with GUI processes.
66
-
67
- Parameters:
68
- - settings: dict, The settings for the run_multiple_simulations function.
69
- - q: multiprocessing.Queue, Queue for logging messages to the GUI.
70
- - fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
71
- """
44
+ def toggle_category(settings):
45
+ for setting in settings:
46
+ if setting in vars_dict:
47
+ label, widget, _ = vars_dict[setting]
48
+ if widget.grid_info():
49
+ label.grid_remove()
50
+ widget.grid_remove()
51
+ else:
52
+ label.grid()
53
+ widget.grid()
54
+
55
+ def on_category_select(selected_category):
56
+ if selected_category == "Select Category":
57
+ return
58
+ if selected_category in categories:
59
+ toggle_category(categories[selected_category])
60
+ if selected_category in active_categories:
61
+ active_categories.remove(selected_category)
62
+ else:
63
+ active_categories.add(selected_category)
64
+ category_dropdown.update_styles(active_categories)
65
+ category_var.set("Select Category")
72
66
 
73
- # Temporarily override plt.show
74
- original_show = plt.show
75
- plt.show = lambda: spacrFigShow(fig_queue)
67
+ category_var = tk.StringVar()
68
+ non_empty_categories = [category for category, settings in categories.items() if any(setting in vars_dict for setting in settings)]
69
+ category_dropdown = spacrDropdownMenu(button_scrollable_frame.scrollable_frame, category_var, non_empty_categories, command=on_category_select)
70
+ category_dropdown.grid(row=0, column=4, sticky="ew", pady=2, padx=2)
71
+ vars_dict = hide_all_settings(vars_dict, categories)
72
+
73
+ def process_fig_queue():
74
+ global canvas, fig_queue, canvas_widget, parent_frame
75
+
76
+ def clear_canvas(canvas):
77
+ for ax in canvas.figure.get_axes():
78
+ ax.clear()
79
+ canvas.draw_idle()
76
80
 
77
81
  try:
78
- if imports == 1:
79
- function(settings=settings)
80
- elif imports == 2:
81
- function(src=settings['src'], settings=settings)
82
- except Exception as e:
83
- # Send the error message to the GUI via the queue
84
- errorMessage = f"Error during processing: {e}"
85
- q.put(errorMessage)
86
- traceback.print_exc()
87
- finally:
88
- # Restore the original plt.show function
89
- plt.show = original_show
90
-
91
- def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
92
- from .gui_utils import process_stdout_stderr
93
- from .core import 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
94
- from .io import generate_cellpose_train_test
95
- from .measure import measure_crop
96
- from .sim import run_multiple_simulations
97
- from .deep_spacr import train_test_model
98
- from .sequencing import analyze_reads, map_barcodes_folder, perform_regression
99
- process_stdout_stderr(q)
100
-
101
- print(f'run_function_gui settings_type: {settings_type}')
102
-
103
- if settings_type == 'mask':
104
- function = preprocess_generate_masks
105
- imports = 2
106
- elif settings_type == 'measure':
107
- function = measure_crop
108
- imports = 1
109
- elif settings_type == 'simulation':
110
- function = run_multiple_simulations
111
- imports = 1
112
- elif settings_type == 'sequencing':
113
- function = analyze_reads
114
- imports = 1
115
- elif settings_type == 'classify':
116
- function = train_test_model
117
- imports = 2
118
- elif settings_type == 'train_cellpose':
119
- function = train_cellpose
120
- imports = 1
121
- elif settings_type == 'ml_analyze':
122
- function = generate_ml_scores
123
- imports = 2
124
- elif settings_type == 'cellpose_masks':
125
- function = identify_masks_finetune
126
- imports = 1
127
- elif settings_type == 'cellpose_all':
128
- function = check_cellpose_models
129
- imports = 1
130
- elif settings_type == 'map_barcodes':
131
- function = map_barcodes_folder
132
- imports = 2
133
- elif settings_type == 'regression':
134
- function = perform_regression
135
- imports = 2
136
- elif settings_type == 'recruitment':
137
- function = analyze_recruitment
138
- imports = 2
139
- else:
140
- raise ValueError(f"Invalid settings type: {settings_type}")
141
- try:
142
- function_gui_wrapper(function, settings, q, fig_queue, imports)
82
+ while not fig_queue.empty():
83
+ clear_canvas(canvas)
84
+ fig = fig_queue.get_nowait()
85
+ for ax in fig.get_axes():
86
+ ax.set_xticks([]) # Remove x-axis ticks
87
+ ax.set_yticks([]) # Remove y-axis ticks
88
+ ax.xaxis.set_visible(False) # Hide the x-axis
89
+ ax.yaxis.set_visible(False) # Hide the y-axis
90
+ fig.tight_layout()
91
+ fig.set_facecolor('black')
92
+ canvas.figure = fig
93
+ fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
94
+ fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
95
+ canvas.draw_idle()
143
96
  except Exception as e:
144
- q.put(f"Error during processing: {e}")
145
97
  traceback.print_exc()
146
98
  finally:
147
- stop_requested.value = 1
148
-
149
- def start_process(q=None, fig_queue=None, settings_type='mask'):
150
- global thread_control, vars_dict
151
- from .settings import check_settings, expected_types
99
+ after_id = canvas_widget.after(100, process_fig_queue)
100
+ parent_frame.after_tasks.append(after_id)
152
101
 
153
- if q is None:
154
- q = Queue()
155
- if fig_queue is None:
156
- fig_queue = Queue()
157
102
 
158
- try:
159
- settings = check_settings(vars_dict, expected_types, q)
160
- except ValueError as e:
161
- q.put(f"Error: {e}")
162
- return
163
103
 
164
- if thread_control.get("run_thread") is not None:
165
- initiate_abort()
166
-
167
- stop_requested = Value('i', 0)
168
- thread_control["stop_requested"] = stop_requested
169
104
 
170
- process_args = (settings_type, settings, q, fig_queue, stop_requested)
171
- if settings_type in ['mask','measure','simulation','sequencing','classify','cellpose_dataset','train_cellpose','ml_analyze','cellpose_masks','cellpose_all','map_barcodes','regression','recruitment','plaques','cellpose_compare','vision_scores','vision_dataset']:
172
- thread_control["run_thread"] = Process(target=run_function_gui, args=process_args)
173
- else:
174
- q.put(f"Error: Unknown settings type '{settings_type}'")
175
- return
176
- thread_control["run_thread"].start()
105
+ def set_globals(thread_control_var, q_var, console_output_var, parent_frame_var, vars_dict_var, canvas_var, canvas_widget_var, scrollable_frame_var, fig_queue_var, progress_bar_var, usage_bars_var):
106
+ global thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars
107
+ thread_control = thread_control_var
108
+ q = q_var
109
+ console_output = console_output_var
110
+ parent_frame = parent_frame_var
111
+ vars_dict = vars_dict_var
112
+ canvas = canvas_var
113
+ canvas_widget = canvas_widget_var
114
+ scrollable_frame = scrollable_frame_var
115
+ fig_queue = fig_queue_var
116
+ progress_bar = progress_bar_var
117
+ usage_bars = usage_bars_var
177
118
 
178
119
  def import_settings(settings_type='mask'):
120
+ from .gui_utils import convert_settings_dict_for_gui, hide_all_settings
179
121
  global vars_dict, scrollable_frame, button_scrollable_frame
180
122
  from .settings import generate_fields
181
123
 
@@ -224,74 +166,23 @@ def import_settings(settings_type='mask'):
224
166
  vars_dict = generate_fields(new_settings, scrollable_frame)
225
167
  vars_dict = hide_all_settings(vars_dict, categories=None)
226
168
 
227
- def convert_settings_dict_for_gui(settings):
228
- from torchvision import models as torch_models
229
- torchvision_models = [name for name, obj in torch_models.__dict__.items() if callable(obj)]
230
- chans = ['0', '1', '2', '3', '4', '5', '6', '7', '8', None]
231
- chans_v2 = [0, 1, 2, 3, None]
232
- variables = {}
233
- special_cases = {
234
- 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
235
- 'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
236
- 'channel_dims': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
237
- 'cell_mask_dim': ('combo', chans, None),
238
- 'cell_chann_dim': ('combo', chans, None),
239
- 'nucleus_mask_dim': ('combo', chans, None),
240
- 'nucleus_chann_dim': ('combo', chans, None),
241
- 'pathogen_mask_dim': ('combo', chans, None),
242
- 'pathogen_chann_dim': ('combo', chans, None),
243
- 'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
244
- 'magnification': ('combo', [20, 40, 60], 20),
245
- 'nucleus_channel': ('combo', chans_v2, None),
246
- 'cell_channel': ('combo', chans_v2, None),
247
- 'channel_of_interest': ('combo', chans_v2, None),
248
- 'pathogen_channel': ('combo', chans_v2, None),
249
- 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
250
- 'train_mode': ('combo', ['erm', 'irm'], 'erm'),
251
- 'clustering': ('combo', ['dbscan', 'kmean'], 'dbscan'),
252
- 'reduction_method': ('combo', ['umap', 'tsne'], 'umap'),
253
- 'model_name': ('combo', ['cyto', 'cyto_2', 'cyto_3', 'nuclei'], 'cyto'),
254
- 'regression_type': ('combo', ['ols','gls','wls','rlm','glm','mixed','quantile','logit','probit','poisson','lasso','ridge'], 'ols'),
255
- 'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
256
- 'model_type': ('combo', torchvision_models, 'resnet50'),
257
- 'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
258
- 'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
259
- 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
260
- 'normalize_by': ('combo', ['fov', 'png'], 'png'),
261
- 'agg_type': ('combo', ['mean', 'median'], 'mean'),
262
- 'grouping': ('combo', ['mean', 'median'], 'mean'),
263
- 'min_max': ('combo', ['allq', 'all'], 'allq'),
264
- 'transform': ('combo', ['log', 'sqrt', 'square', None], None)
265
- }
266
-
267
- for key, value in settings.items():
268
- if key in special_cases:
269
- variables[key] = special_cases[key]
270
- elif isinstance(value, bool):
271
- variables[key] = ('check', None, value)
272
- elif isinstance(value, int) or isinstance(value, float):
273
- variables[key] = ('entry', None, value)
274
- elif isinstance(value, str):
275
- variables[key] = ('entry', None, value)
276
- elif value is None:
277
- variables[key] = ('entry', None, value)
278
- elif isinstance(value, list):
279
- variables[key] = ('entry', None, str(value))
280
- else:
281
- variables[key] = ('entry', None, str(value))
282
- return variables
283
-
284
- def setup_settings_panel(vertical_container, settings_type='mask', window_dimensions=[500, 1000]):
169
+ def setup_settings_panel(vertical_container, settings_type='mask'):
285
170
  global vars_dict, scrollable_frame
286
171
  from .settings import get_identify_masks_finetune_default_settings, set_default_analyze_screen, 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, get_perform_regression_default_settings, get_train_cellpose_default_settings, get_map_barcodes_default_settings, get_analyze_recruitment_default_settings, get_check_cellpose_models_default_settings
172
+ from .gui_utils import convert_settings_dict_for_gui, set_element_size
173
+
174
+ size_dict = set_element_size(vertical_container)
175
+ settings_width = size_dict['settings_width']
176
+
177
+ # Create a PanedWindow for the settings panel
178
+ settings_paned_window = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL)
179
+ vertical_container.add(settings_paned_window, stretch="always")
180
+
181
+ settings_frame = tk.Frame(settings_paned_window, width=settings_width)
182
+ settings_frame.pack_propagate(False) # Prevent the frame from resizing based on its children
287
183
 
288
- width = (window_dimensions[0]) // 6
289
- height = window_dimensions[1]
184
+ settings_paned_window.add(settings_frame)
290
185
 
291
- settings_frame = tk.Frame(vertical_container)
292
- vertical_container.add(settings_frame, stretch="always")
293
- settings_label = spacrLabel(settings_frame, text="Settings", anchor='center', justify='center', align="center")
294
- settings_label.grid(row=0, column=0, pady=10, padx=10)
295
186
  scrollable_frame = spacrFrame(settings_frame)
296
187
  scrollable_frame.grid(row=1, column=0, sticky="nsew")
297
188
  settings_frame.grid_rowconfigure(1, weight=1)
@@ -328,7 +219,7 @@ def setup_settings_panel(vertical_container, settings_type='mask', window_dimens
328
219
  vars_dict = generate_fields(variables, scrollable_frame)
329
220
 
330
221
  containers = [settings_frame]
331
- widgets = [settings_label, scrollable_frame]
222
+ widgets = [scrollable_frame]
332
223
 
333
224
  style = ttk.Style(vertical_container)
334
225
  _ = set_dark_style(style, containers=containers, widgets=widgets)
@@ -362,19 +253,50 @@ def setup_plot_section(vertical_container):
362
253
 
363
254
  def setup_console(vertical_container):
364
255
  global console_output
365
- console_frame = tk.Frame(vertical_container)
366
- vertical_container.add(console_frame, stretch="always")
367
- console_label = spacrLabel(console_frame, text="Console", anchor='center', justify='center', align="center")
368
- console_label.grid(row=0, column=0, pady=10, padx=10)
369
- console_output = scrolledtext.ScrolledText(console_frame, height=10)
370
- console_output.grid(row=1, column=0, sticky="nsew")
371
- console_frame.grid_rowconfigure(1, weight=1)
256
+
257
+ # Create a frame to hold the console and button sections
258
+ console_button_frame = tk.Frame(vertical_container)
259
+ vertical_container.add(console_button_frame, stretch="always")
260
+
261
+ # Create a PanedWindow for resizing height
262
+ console_paned_window = tk.PanedWindow(console_button_frame, orient=tk.VERTICAL, bg='black')
263
+ console_paned_window.pack(fill=tk.BOTH, expand=True)
264
+
265
+ # Create the main console frame using spacrFrame with textbox=True
266
+ console_frame = spacrFrame(console_paned_window, textbox=True)
267
+ console_paned_window.add(console_frame, stretch="always")
268
+
269
+ # Assign the scrollable frame (which is a Text widget) to console_output
270
+ console_output = console_frame.scrollable_frame
271
+
272
+ # Ensure the Text widget spans the entire console frame
273
+ console_output.grid(row=0, column=0, sticky="nsew")
274
+
275
+ # Configure the grid to allow expansion
276
+ console_frame.grid_rowconfigure(0, weight=1)
372
277
  console_frame.grid_columnconfigure(0, weight=1)
373
- containers = [console_frame]
374
- widgets = [console_label, console_output]
375
- style = ttk.Style(vertical_container)
376
- _ = set_dark_style(style, containers=containers, widgets=widgets)
377
- return console_output
278
+
279
+ # Create a lower frame to act as the anchor point
280
+ lower_frame = tk.Frame(console_paned_window)
281
+ console_paned_window.add(lower_frame, minsize=10) # Adjust minsize to ensure usability
282
+
283
+ # Dynamically adjust the height and width of the console_frame
284
+ def adjust_frame_size(event):
285
+ console_frame.update_idletasks()
286
+ new_width = console_paned_window.winfo_width()
287
+ new_height = console_paned_window.winfo_height()
288
+ console_frame.config(width=new_width, height=new_height)
289
+ console_output.config(width=new_width, height=new_height)
290
+
291
+ # Bind the configure event to dynamically adjust size
292
+ console_paned_window.bind("<Configure>", adjust_frame_size)
293
+
294
+ # Apply dark style to the PanedWindow and lower frame
295
+ style = ttk.Style()
296
+ set_dark_style(style, containers=[console_paned_window, lower_frame])
297
+
298
+ return console_output, console_frame
299
+
378
300
 
379
301
  def setup_progress_frame(vertical_container):
380
302
  global progress_output
@@ -394,139 +316,56 @@ def setup_progress_frame(vertical_container):
394
316
  _ = set_dark_style(style, containers=containers, widgets=widgets)
395
317
  return progress_output
396
318
 
397
- def download_hug_dataset():
398
- global vars_dict, q
399
- dataset_repo_id = "einarolafsson/toxo_mito"
400
- settings_repo_id = "einarolafsson/spacr_settings"
401
- dataset_subfolder = "plate1"
402
- local_dir = os.path.join(os.path.expanduser("~"), "datasets") # Set to the home directory
403
-
404
- # Download the dataset
405
- try:
406
- dataset_path = download_dataset(dataset_repo_id, dataset_subfolder, local_dir)
407
- if 'src' in vars_dict:
408
- vars_dict['src'][2].set(dataset_path)
409
- q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
410
- q.put(f"Dataset downloaded to: {dataset_path}\n")
411
- except Exception as e:
412
- q.put(f"Failed to download dataset: {e}\n")
413
-
414
- # Download the settings files
415
- try:
416
- settings_path = download_dataset(settings_repo_id, "", local_dir)
417
- q.put(f"Settings downloaded to: {settings_path}\n")
418
- except Exception as e:
419
- q.put(f"Failed to download settings: {e}\n")
420
-
421
- def download_dataset(repo_id, subfolder, local_dir=None, retries=5, delay=5):
422
- global q
423
- """
424
- Downloads a dataset or settings files from Hugging Face and returns the local path.
425
-
426
- Args:
427
- repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito' or 'einarolafsson/spacr_settings').
428
- subfolder (str): The subfolder path within the repository (e.g., 'plate1' or the settings subfolder).
429
- local_dir (str): The local directory where the files will be saved. Defaults to the user's home directory.
430
- retries (int): Number of retry attempts in case of failure.
431
- delay (int): Delay in seconds between retries.
432
-
433
- Returns:
434
- str: The local path to the downloaded files.
435
- """
436
- if local_dir is None:
437
- local_dir = os.path.join(os.path.expanduser("~"), "datasets")
438
-
439
- local_subfolder_dir = os.path.join(local_dir, subfolder if subfolder else "settings")
440
- if not os.path.exists(local_subfolder_dir):
441
- os.makedirs(local_subfolder_dir)
442
- elif len(os.listdir(local_subfolder_dir)) > 0:
443
- q.put(f"Files already downloaded to: {local_subfolder_dir}")
444
- return local_subfolder_dir
445
-
446
- attempt = 0
447
- while attempt < retries:
448
- try:
449
- files = list_repo_files(repo_id, repo_type="dataset")
450
- subfolder_files = [file for file in files if file.startswith(subfolder) or (subfolder == "" and file.endswith('.csv'))]
451
-
452
- for file_name in subfolder_files:
453
- for download_attempt in range(retries):
454
- try:
455
- url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
456
- response = requests.get(url, stream=True)
457
- response.raise_for_status()
458
-
459
- local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
460
- with open(local_file_path, 'wb') as file:
461
- for chunk in response.iter_content(chunk_size=8192):
462
- file.write(chunk)
463
- q.put(f"Downloaded file: {file_name}")
464
- break
465
- except (requests.HTTPError, requests.Timeout) as e:
466
- q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
467
- time.sleep(delay)
468
- else:
469
- raise Exception(f"Failed to download {file_name} after multiple attempts.")
470
-
471
- return local_subfolder_dir
472
-
473
- except (requests.HTTPError, requests.Timeout) as e:
474
- q.put(f"Error downloading files: {e}. Retrying in {delay} seconds...")
475
- attempt += 1
476
- time.sleep(delay)
477
-
478
- raise Exception("Failed to download files after multiple attempts.")
479
-
319
+ def setup_button_section(horizontal_container, settings_type='mask', run=True, abort=True, download=True, import_btn=True):
320
+ global thread_control, parent_frame, button_frame, button_scrollable_frame, run_button, abort_button, download_dataset_button, import_button, q, fig_queue, vars_dict, progress_bar
321
+ from .gui_utils import set_element_size, download_hug_dataset
322
+ from .settings import categories
480
323
 
481
324
 
482
- def setup_button_section(horizontal_container, settings_type='mask', run=True, abort=True, download=True, import_btn=True):
483
- global button_frame, button_scrollable_frame, run_button, abort_button, download_dataset_button, import_button, q, fig_queue, vars_dict, progress_bar
484
- from .gui_utils import set_element_size
485
325
  size_dict = set_element_size(horizontal_container)
486
- button_frame = tk.Frame(horizontal_container)
487
- horizontal_container.add(button_frame, stretch="always", sticky="nsew")
488
- button_frame.grid_rowconfigure(0, weight=0)
489
- button_frame.grid_rowconfigure(1, weight=1)
490
- button_frame.grid_columnconfigure(0, weight=1)
326
+ button_section_height = size_dict['panel_height']
327
+ button_frame = tk.Frame(horizontal_container, height=button_section_height)
328
+
329
+ # Prevent the frame from resizing based on the child widget
330
+ button_frame.pack_propagate(False)
491
331
 
492
- categories_label = spacrLabel(button_frame, text="Categories", anchor='center', justify='center', align="center")
493
- categories_label.grid(row=0, column=0, pady=10, padx=10)
494
- button_scrollable_frame = spacrFrame(button_frame)
332
+ horizontal_container.add(button_frame, stretch="always", sticky="nsew")
333
+ button_scrollable_frame = spacrFrame(button_frame, scrollbar=False)
495
334
  button_scrollable_frame.grid(row=1, column=0, sticky="nsew")
496
-
497
- widgets = [categories_label, button_scrollable_frame.scrollable_frame]
335
+ widgets = [button_scrollable_frame.scrollable_frame]
498
336
 
499
337
  btn_col = 0
500
- btn_row = 3
338
+ btn_row = 0
501
339
 
502
340
  if run:
503
341
  print(f'settings_type: {settings_type}')
504
- run_button = spacrButton(button_scrollable_frame.scrollable_frame, text="run", command=lambda: start_process(q, fig_queue, settings_type), show_text=False, size=size_dict['btn_size'])
342
+ run_button = spacrButton(button_scrollable_frame.scrollable_frame, text="run", command=lambda: start_process(q, fig_queue, settings_type), show_text=False, size=size_dict['btn_size'], animation=False)
505
343
  run_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
506
344
  widgets.append(run_button)
507
- btn_row += 1
345
+ btn_col += 1
508
346
 
509
347
  if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap']:
510
- abort_button = spacrButton(button_scrollable_frame.scrollable_frame, text="abort", command=initiate_abort, show_text=False, size=size_dict['btn_size'])
348
+ abort_button = spacrButton(button_scrollable_frame.scrollable_frame, text="abort", command=lambda: initiate_abort(), show_text=False, size=size_dict['btn_size'], animation=False)
511
349
  abort_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
512
350
  widgets.append(abort_button)
513
- btn_row += 1
351
+ btn_col += 1
514
352
 
515
353
  if download and settings_type in ['mask']:
516
- download_dataset_button = spacrButton(button_scrollable_frame.scrollable_frame, text="download", command=download_hug_dataset, show_text=False, size=size_dict['btn_size'])
354
+ download_dataset_button = spacrButton(button_scrollable_frame.scrollable_frame, text="download", command=lambda: download_hug_dataset(q, vars_dict), show_text=False, size=size_dict['btn_size'], animation=False)
517
355
  download_dataset_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
518
356
  widgets.append(download_dataset_button)
519
- btn_row += 1
357
+ btn_col += 1
520
358
 
521
359
  if import_btn:
522
- import_button = spacrButton(button_scrollable_frame.scrollable_frame, text="settings", command=lambda: import_settings(settings_type),show_text=False, size=size_dict['btn_size'])
360
+ import_button = spacrButton(button_scrollable_frame.scrollable_frame, text="settings", command=lambda: import_settings(settings_type), show_text=False, size=size_dict['btn_size'], animation=False)
523
361
  import_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
524
362
  widgets.append(import_button)
525
363
  btn_row += 1
526
364
 
527
365
  # Add the progress bar under the settings category menu
528
366
  progress_bar = spacrProgressBar(button_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate')
529
- progress_bar.grid(row=0, column=0, columnspan=2, pady=5, padx=5, sticky='ew')
367
+ progress_bar.grid(row=btn_row, column=0, columnspan=5, pady=5, padx=5, sticky='ew')
368
+ progress_bar.set_label_position() # Set the label position after grid placement
530
369
  widgets.append(progress_bar)
531
370
 
532
371
  if vars_dict is not None:
@@ -537,234 +376,6 @@ def setup_button_section(horizontal_container, settings_type='mask', run=True, a
537
376
 
538
377
  return button_scrollable_frame
539
378
 
540
- def setup_help_section(horizontal_container, settings_type='mask'):
541
- from .settings import descriptions
542
-
543
- description_frame = tk.Frame(horizontal_container)
544
- horizontal_container.add(description_frame, stretch="always", sticky="nsew")
545
- description_frame.grid_columnconfigure(0, weight=1)
546
- description_frame.grid_rowconfigure(1, weight=1) # Ensure the text widget row is expandable
547
-
548
- description_label = spacrLabel(description_frame, text=f"{settings_type} Module", anchor='center', justify='center', align="center")
549
- description_label.grid(row=0, column=0, pady=10, padx=10, sticky='ew')
550
-
551
- # Set background color directly
552
- style_out = set_dark_style(ttk.Style())
553
- bg_color = style_out['bg_color']
554
- fg_color = style_out['fg_color']
555
-
556
- description_text_widget = tk.Text(description_frame, wrap="word", bg=bg_color, fg=fg_color)
557
- description_text_widget.grid(row=1, column=0, sticky="nsew")
558
-
559
- description_text = descriptions.get(settings_type, "No description available for this module.")
560
- description_text_widget.insert("1.0", description_text)
561
- description_text_widget.config(state="disabled") # Make the text widget read-only
562
-
563
- def update_wraplength(event):
564
- new_width = event.width - 20 # Adjust as needed
565
- description_text_widget.config(width=new_width)
566
-
567
- description_text_widget.bind('<Configure>', update_wraplength)
568
-
569
- style = ttk.Style(horizontal_container)
570
- _ = set_dark_style(style, containers=[description_frame], widgets=[description_label, description_text_widget])
571
-
572
- return description_frame
573
-
574
- def hide_all_settings(vars_dict, categories):
575
- """
576
- Function to initially hide all settings in the GUI.
577
-
578
- Parameters:
579
- - categories: dict, The categories of settings with their corresponding settings.
580
- - vars_dict: dict, The dictionary containing the settings and their corresponding widgets.
581
- """
582
-
583
- if categories is None:
584
- from .settings import categories
585
-
586
- for category, settings in categories.items():
587
- if any(setting in vars_dict for setting in settings):
588
- vars_dict[category] = (None, None, tk.IntVar(value=0))
589
-
590
- # Initially hide all settings
591
- for setting in settings:
592
- if setting in vars_dict:
593
- label, widget, _ = vars_dict[setting]
594
- label.grid_remove()
595
- widget.grid_remove()
596
- return vars_dict
597
-
598
- def toggle_settings(button_scrollable_frame):
599
- global vars_dict
600
- from .settings import categories
601
-
602
- if vars_dict is None:
603
- raise ValueError("vars_dict is not initialized.")
604
-
605
- active_categories = set()
606
-
607
- def toggle_category(settings):
608
- for setting in settings:
609
- if setting in vars_dict:
610
- label, widget, _ = vars_dict[setting]
611
- if widget.grid_info():
612
- label.grid_remove()
613
- widget.grid_remove()
614
- else:
615
- label.grid()
616
- widget.grid()
617
-
618
- def on_category_select(selected_category):
619
- if selected_category == "Select Category":
620
- return
621
- if selected_category in categories:
622
- toggle_category(categories[selected_category])
623
- if selected_category in active_categories:
624
- active_categories.remove(selected_category)
625
- else:
626
- active_categories.add(selected_category)
627
- category_dropdown.update_styles(active_categories)
628
- category_var.set("Select Category")
629
-
630
- category_var = tk.StringVar()
631
- non_empty_categories = [category for category, settings in categories.items() if any(setting in vars_dict for setting in settings)]
632
- category_dropdown = spacrDropdownMenu(button_scrollable_frame.scrollable_frame, category_var, non_empty_categories, command=on_category_select)
633
- category_dropdown.grid(row=7, column=0, sticky="ew", pady=2, padx=2)
634
- vars_dict = hide_all_settings(vars_dict, categories)
635
-
636
- def process_fig_queue():
637
- global canvas, fig_queue, canvas_widget, parent_frame
638
-
639
- def clear_canvas(canvas):
640
- for ax in canvas.figure.get_axes():
641
- ax.clear()
642
- canvas.draw_idle()
643
-
644
- try:
645
- while not fig_queue.empty():
646
- clear_canvas(canvas)
647
- fig = fig_queue.get_nowait()
648
- for ax in fig.get_axes():
649
- ax.set_xticks([]) # Remove x-axis ticks
650
- ax.set_yticks([]) # Remove y-axis ticks
651
- ax.xaxis.set_visible(False) # Hide the x-axis
652
- ax.yaxis.set_visible(False) # Hide the y-axis
653
- fig.tight_layout()
654
- fig.set_facecolor('black')
655
- canvas.figure = fig
656
- fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
657
- fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
658
- canvas.draw_idle()
659
- except Exception as e:
660
- traceback.print_exc()
661
- finally:
662
- after_id = canvas_widget.after(100, process_fig_queue)
663
- parent_frame.after_tasks.append(after_id)
664
-
665
- def process_console_queue():
666
- global q, console_output, parent_frame, progress_bar
667
-
668
- # Initialize function attribute if it doesn't exist
669
- if not hasattr(process_console_queue, "completed_tasks"):
670
- process_console_queue.completed_tasks = []
671
-
672
- ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
673
-
674
- while not q.empty():
675
- message = q.get_nowait()
676
- clean_message = ansi_escape_pattern.sub('', message)
677
- console_output.insert(tk.END, clean_message + "\n")
678
- console_output.see(tk.END)
679
-
680
- # Check if the message contains progress information
681
- if clean_message.startswith("Progress"):
682
- try:
683
- # Extract the progress information
684
- match = re.search(r'(\d+)/(\d+)', clean_message)
685
- if match:
686
- current_progress = int(match.group(1))
687
- total_progress = int(match.group(2))
688
-
689
- # Add the task to the completed set
690
- process_console_queue.completed_tasks.append(current_progress)
691
-
692
- # Calculate the unique progress count
693
- unique_progress_count = len(np.unique(process_console_queue.completed_tasks))
694
-
695
- # Update the progress bar
696
- if progress_bar:
697
- progress_bar['maximum'] = total_progress
698
- progress_bar['value'] = unique_progress_count
699
-
700
- # Extract and update additional information
701
- operation_match = re.search(r'operation_type: ([\w\s]+)', clean_message)
702
- if operation_match:
703
- progress_bar.operation_type = operation_match.group(1)
704
-
705
- time_image_match = re.search(r'Time/image: ([\d.]+) sec', clean_message)
706
- if time_image_match:
707
- progress_bar.time_image = float(time_image_match.group(1))
708
-
709
- time_batch_match = re.search(r'Time/batch: ([\d.]+) sec', clean_message)
710
- if time_batch_match:
711
- progress_bar.time_batch = float(time_batch_match.group(1))
712
-
713
- time_left_match = re.search(r'Time_left: ([\d.]+) min', clean_message)
714
- if time_left_match:
715
- progress_bar.time_left = float(time_left_match.group(1))
716
-
717
- # Update the progress label
718
- if progress_bar.progress_label:
719
- progress_bar.update_label()
720
-
721
- # Clear completed tasks when progress is complete
722
- if unique_progress_count >= total_progress:
723
- process_console_queue.completed_tasks.clear()
724
- except Exception as e:
725
- print(f"Error parsing progress message: {e}")
726
-
727
- after_id = console_output.after(100, process_console_queue)
728
- parent_frame.after_tasks.append(after_id)
729
-
730
- def set_globals(q_var, console_output_var, parent_frame_var, vars_dict_var, canvas_var, canvas_widget_var, scrollable_frame_var, fig_queue_var, progress_bar_var, usage_bars_var):
731
- global q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars
732
- q = q_var
733
- console_output = console_output_var
734
- parent_frame = parent_frame_var
735
- vars_dict = vars_dict_var
736
- canvas = canvas_var
737
- canvas_widget = canvas_widget_var
738
- scrollable_frame = scrollable_frame_var
739
- fig_queue = fig_queue_var
740
- progress_bar = progress_bar_var
741
- usage_bars = usage_bars_var
742
-
743
- def create_containers(parent_frame):
744
- vertical_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL)
745
- horizontal_container = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL)
746
- settings_frame = tk.Frame(horizontal_container)
747
- return vertical_container, horizontal_container, settings_frame
748
-
749
- def setup_frame(parent_frame):
750
- style = ttk.Style(parent_frame)
751
- vertical_container, horizontal_container, settings_frame = create_containers(parent_frame)
752
- containers = [vertical_container, horizontal_container, settings_frame]
753
-
754
- set_dark_style(style, parent_frame, containers)
755
- set_default_font(parent_frame, font_name="Helvetica", size=8)
756
-
757
- vertical_container.grid(row=0, column=0, sticky=tk.NSEW)
758
- vertical_container.add(horizontal_container, stretch="always")
759
- horizontal_container.grid_columnconfigure(0, weight=1)
760
- horizontal_container.grid_columnconfigure(1, weight=1)
761
- settings_frame.grid_rowconfigure(0, weight=0)
762
- settings_frame.grid_rowconfigure(1, weight=1)
763
- settings_frame.grid_columnconfigure(0, weight=1)
764
- horizontal_container.add(settings_frame, stretch="always", sticky="nsew")
765
-
766
- return parent_frame, vertical_container, horizontal_container
767
-
768
379
  def setup_usage_panel(horizontal_container):
769
380
  global usage_bars
770
381
  from .gui_utils import set_element_size
@@ -792,23 +403,20 @@ def setup_usage_panel(horizontal_container):
792
403
  parent_frame.after(1000, update_usage, ram_bar, vram_bar, gpu_bar, usage_bars, parent_frame)
793
404
 
794
405
  size_dict = set_element_size(horizontal_container)
795
- print(size_dict)
796
-
797
- usage_frame = tk.Frame(horizontal_container)
406
+ usage_panel_height = size_dict['panel_height']
407
+ usage_frame = tk.Frame(horizontal_container, height=usage_panel_height)
798
408
  horizontal_container.add(usage_frame, stretch="always", sticky="nsew")
409
+
799
410
  usage_frame.grid_rowconfigure(0, weight=0)
800
411
  usage_frame.grid_rowconfigure(1, weight=1)
801
412
  usage_frame.grid_columnconfigure(0, weight=1)
802
413
  usage_frame.grid_columnconfigure(1, weight=1)
803
414
 
804
- usage_label = spacrLabel(usage_frame, text="Hardware Stats", anchor='center', justify='center', align="center")
805
- usage_label.grid(row=0, column=0, pady=10, padx=10, columnspan=2)
806
-
807
- usage_scrollable_frame = spacrFrame(usage_frame)
415
+ usage_scrollable_frame = spacrFrame(usage_frame, scrollbar=False)
808
416
  usage_scrollable_frame.grid(row=1, column=0, sticky="nsew", columnspan=2)
809
- widgets = [usage_label, usage_scrollable_frame.scrollable_frame]
417
+ widgets = [usage_scrollable_frame.scrollable_frame]
810
418
  usage_bars = []
811
- max_elements_per_column = 12
419
+ max_elements_per_column = 6
812
420
  row = 0
813
421
  col = 0
814
422
 
@@ -861,7 +469,7 @@ def setup_usage_panel(horizontal_container):
861
469
  try:
862
470
  cpu_cores = psutil.cpu_count(logical=True)
863
471
  cpu_freq = psutil.cpu_freq()
864
-
472
+
865
473
  for core in range(cpu_cores):
866
474
  if row > 0 and row % max_elements_per_column == 0:
867
475
  col += 1
@@ -877,42 +485,8 @@ def setup_usage_panel(horizontal_container):
877
485
  except Exception as e:
878
486
  print(f"Could not add CPU core usage bars: {e}")
879
487
 
880
- # Adding the text box for hardware information
881
- #hardware_frame = tk.Frame(horizontal_container)
882
- #horizontal_container.add(hardware_frame, stretch="always", sticky="nsew")
883
- #hardware_frame.grid_columnconfigure(0, weight=1)
884
-
885
- #hardware_info = tk.Text(hardware_frame, height=1, wrap='none', bg='black', fg='white', bd=0)
886
- #hardware_info.grid(row=0, column=0, pady=10, padx=5, sticky='ew')
887
-
888
- #hardware_text = ""
889
- #try:
890
- # ram_info = psutil.virtual_memory()
891
- # hardware_text += f"RAM: {ram_info.total / (1024 ** 3):.1f} GB "
892
- #except Exception as e:
893
- # hardware_text += f"RAM: Could not retrieve ({e}) "
894
-
895
- #try:
896
- # gpus = GPUtil.getGPUs()
897
- # if gpus:
898
- # gpu = gpus[0]
899
- # hardware_text += f"VRAM: {gpu.memoryTotal / 1024:.1f} GB "
900
- # hardware_text += f"GPU: {gpu.name} "
901
- #except Exception as e:
902
- # hardware_text += f"VRAM and GPU: Could not retrieve ({e}) "
903
-
904
- #try:
905
- # if cpu_freq:
906
- # hardware_text += f"CPU Max Clock Speed: {cpu_freq.max / 1000:.0f} GHz"
907
- #except Exception as e:
908
- # hardware_text += f"CPU Max Clock Speed: Could not retrieve ({e})"
909
-
910
- #hardware_info.insert(tk.END, hardware_text)
911
- #hardware_info.configure(state='disabled')
912
- #widgets.append(hardware_info)
913
-
914
488
  style = ttk.Style(horizontal_container)
915
- _ = set_dark_style(style, containers=[usage_frame], widgets=widgets) # hardware_frame
489
+ _ = set_dark_style(style, containers=[usage_frame], widgets=widgets)
916
490
 
917
491
  if ram_bar is None:
918
492
  ram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
@@ -924,33 +498,192 @@ def setup_usage_panel(horizontal_container):
924
498
  update_usage(ram_bar, vram_bar, gpu_bar, usage_bars, usage_frame)
925
499
  return usage_scrollable_frame, usage_bars
926
500
 
501
+ def setup_help_section(horizontal_container, settings_type='mask'):
502
+ from .settings import descriptions
503
+ from .gui_utils import set_element_size
504
+
505
+ size_dict = set_element_size(horizontal_container)
506
+ help_section_height = size_dict['panel_height']
507
+
508
+ # Create the frame for the help section
509
+ description_frame = tk.Frame(horizontal_container, height=help_section_height)
510
+ description_frame.pack_propagate(False)
511
+
512
+ # Add the description frame to the horizontal container
513
+ horizontal_container.add(description_frame, stretch="always", sticky="nsew")
514
+ description_frame.grid_columnconfigure(0, weight=1)
515
+ description_frame.grid_rowconfigure(0, weight=1) # Ensure the text widget row is expandable
516
+
517
+ style_out = set_dark_style(ttk.Style())
518
+ bg_color = style_out['bg_color']
519
+ fg_color = style_out['fg_color']
520
+
521
+ # Insert the description text
522
+ description_text = descriptions.get(settings_type, "No description available for this module.")
523
+ first_line, *rest_of_description = description_text.split('\n', 1)
524
+
525
+ # Create the first line label
526
+ first_line_label = ttk.Label(description_frame, text=first_line, anchor='w', background=bg_color, foreground=fg_color)
527
+ first_line_label.grid(row=0, column=0, sticky='ew')
528
+
529
+ # Create the text widget with the appropriate background and foreground colors
530
+ description_text_widget = tk.Text(description_frame, wrap="word", bg=bg_color, fg=fg_color, bd=0, highlightthickness=0)
531
+ description_text_widget.grid(row=1, column=0, sticky="nsew")
532
+
533
+ # Insert the rest of the description text
534
+ if rest_of_description:
535
+ description_text_widget.insert("1.0", rest_of_description[0].lstrip())
536
+ description_text_widget.config(state="disabled") # Make the text widget read-only
537
+
538
+ def update_wraplength(event):
539
+ new_width = event.width - 20 # Adjust as needed
540
+ description_text_widget.config(width=new_width)
541
+
542
+ description_text_widget.bind('<Configure>', update_wraplength)
543
+
544
+ # Apply dark style
545
+ style = ttk.Style(horizontal_container)
546
+ _ = set_dark_style(style, containers=[description_frame], widgets=[description_text_widget, first_line_label])
547
+
548
+ return description_frame
549
+
550
+ def initiate_abort():
551
+ global thread_control, q, parent_frame
552
+ if thread_control.get("run_thread") is not None:
553
+ try:
554
+ q.put("Aborting processes...")
555
+ thread_control.get("run_thread").terminate()
556
+ thread_control["run_thread"] = None
557
+ q.put("Processes aborted.")
558
+ except Exception as e:
559
+ q.put(f"Error aborting process: {e}")
560
+
561
+ thread_control = {"run_thread": None, "stop_requested": False}
562
+
563
+
564
+ def start_process(q=None, fig_queue=None, settings_type='mask'):
565
+ global thread_control, vars_dict, parent_frame
566
+ from .settings import check_settings, expected_types
567
+ from .gui_utils import run_function_gui
568
+
569
+ if q is None:
570
+ q = Queue()
571
+ if fig_queue is None:
572
+ fig_queue = Queue()
573
+
574
+ try:
575
+ settings = check_settings(vars_dict, expected_types, q)
576
+ except ValueError as e:
577
+ q.put(f"Error: {e}")
578
+ return
579
+
580
+ if thread_control.get("run_thread") is not None:
581
+ initiate_abort()
582
+
583
+ stop_requested = Value('i', 0)
584
+ thread_control["stop_requested"] = stop_requested
585
+
586
+ process_args = (settings_type, settings, q, fig_queue, stop_requested)
587
+ if settings_type in [
588
+ 'mask', 'measure', 'simulation', 'sequencing', 'classify', 'cellpose_dataset',
589
+ 'train_cellpose', 'ml_analyze', 'cellpose_masks', 'cellpose_all', 'map_barcodes',
590
+ 'regression', 'recruitment', 'plaques', 'cellpose_compare', 'vision_scores',
591
+ 'vision_dataset'
592
+ ]:
593
+ thread_control["run_thread"] = Process(target=run_function_gui, args=process_args)
594
+ else:
595
+ q.put(f"Error: Unknown settings type '{settings_type}'")
596
+ return
597
+ thread_control["run_thread"].start()
598
+
599
+ def process_console_queue():
600
+ global q, console_output, parent_frame, progress_bar
601
+
602
+ # Initialize function attribute if it doesn't exist
603
+ if not hasattr(process_console_queue, "completed_tasks"):
604
+ process_console_queue.completed_tasks = []
605
+
606
+ ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
607
+
608
+ while not q.empty():
609
+ message = q.get_nowait()
610
+ clean_message = ansi_escape_pattern.sub('', message)
611
+ console_output.insert(tk.END, clean_message + "\n")
612
+ console_output.see(tk.END)
613
+
614
+ # Check if the message contains progress information
615
+ if clean_message.startswith("Progress:"):
616
+ try:
617
+ # Extract the progress information
618
+ match = re.search(r'Progress: (\d+)/(\d+), operation_type: ([\w\s]*)(.*)', clean_message)
619
+ if match:
620
+ current_progress = int(match.group(1))
621
+ total_progress = int(match.group(2))
622
+ operation_type = match.group(3).strip()
623
+ time_info = match.group(4).strip()
624
+
625
+ # Add the task to the completed set
626
+ process_console_queue.completed_tasks.append(current_progress)
627
+
628
+ # Calculate the unique progress count
629
+ unique_progress_count = len(np.unique(process_console_queue.completed_tasks))
630
+
631
+ # Update the progress bar
632
+ if progress_bar:
633
+ progress_bar['maximum'] = total_progress
634
+ progress_bar['value'] = unique_progress_count
635
+
636
+ # Extract and update additional information
637
+ if operation_type:
638
+ progress_bar.operation_type = operation_type
639
+
640
+ time_image_match = re.search(r'Time/image: ([\d.]+) sec', time_info)
641
+ if time_image_match:
642
+ progress_bar.time_image = float(time_image_match.group(1))
643
+
644
+ time_batch_match = re.search(r'Time/batch: ([\d.]+) sec', time_info)
645
+ if time_batch_match:
646
+ progress_bar.time_batch = float(time_batch_match.group(1))
647
+
648
+ time_left_match = re.search(r'Time_left: ([\d.]+) min', time_info)
649
+ if time_left_match:
650
+ progress_bar.time_left = float(time_left_match.group(1))
651
+
652
+ # Update the progress label
653
+ if progress_bar.progress_label:
654
+ progress_bar.update_label()
655
+
656
+ # Clear completed tasks when progress is complete
657
+ if unique_progress_count >= total_progress:
658
+ process_console_queue.completed_tasks.clear()
659
+ except Exception as e:
660
+ print(f"Error parsing progress message: {e}")
661
+
662
+ after_id = console_output.after(100, process_console_queue)
663
+ parent_frame.after_tasks.append(after_id)
664
+
927
665
  def initiate_root(parent, settings_type='mask'):
928
- global q, fig_queue, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar
929
- from .gui_utils import main_thread_update_function
666
+ global q, fig_queue, thread_control, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar
667
+ from .gui_utils import main_thread_update_function, setup_frame, set_element_size
930
668
  from .gui import gui_app
931
669
  set_start_method('spawn', force=True)
932
670
  print("Initializing root with settings_type:", settings_type)
933
671
 
934
672
  parent_frame = parent
935
- parent_frame.update_idletasks()
936
- frame_width = int(parent_frame.winfo_width())
937
- frame_height = int(parent_frame.winfo_height())
938
- print(frame_width, frame_height)
939
- dims = [frame_width, frame_height]
940
673
 
941
- if not hasattr(parent_frame, 'after_tasks'):
942
- parent_frame.after_tasks = []
674
+ if not isinstance(parent_frame, (tk.Tk, tk.Toplevel)):
675
+ parent_window = parent_frame.winfo_toplevel()
676
+ else:
677
+ parent_window = parent_frame
943
678
 
944
- # Clear previous content instead of destroying the root
945
- for widget in parent_frame.winfo_children():
946
- try:
947
- widget.destroy()
948
- except tk.TclError as e:
949
- print(f"Error destroying widget: {e}")
679
+ parent_window.update_idletasks()
680
+
681
+ if not hasattr(parent_window, 'after_tasks'):
682
+ parent_window.after_tasks = []
950
683
 
951
684
  q = Queue()
952
685
  fig_queue = Queue()
953
- parent_frame, vertical_container, horizontal_container = setup_frame(parent_frame)
686
+ parent_frame, vertical_container, horizontal_container, settings_container = setup_frame(parent_frame)
954
687
 
955
688
  if settings_type == 'annotate':
956
689
  from .app_annotate import initiate_annotation_app
@@ -959,21 +692,19 @@ def initiate_root(parent, settings_type='mask'):
959
692
  from .app_make_masks import initiate_make_mask_app
960
693
  initiate_make_mask_app(horizontal_container)
961
694
  else:
962
- scrollable_frame, vars_dict = setup_settings_panel(horizontal_container, settings_type, window_dimensions=dims)
695
+ scrollable_frame, vars_dict = setup_settings_panel(settings_container, settings_type)
696
+ canvas, canvas_widget = setup_plot_section(vertical_container)
697
+ console_output, console_frame = setup_console(vertical_container)
963
698
  button_scrollable_frame = setup_button_section(horizontal_container, settings_type)
964
-
965
699
  _, usage_bars = setup_usage_panel(horizontal_container)
966
700
  _ = setup_help_section(horizontal_container, settings_type)
967
-
968
- canvas, canvas_widget = setup_plot_section(vertical_container)
969
- console_output = setup_console(vertical_container)
970
-
971
- set_globals(q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars)
701
+
702
+ set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars)
703
+ q.put(f"Console")
972
704
  process_console_queue()
973
705
  process_fig_queue()
974
- after_id = parent_frame.after(100, lambda: main_thread_update_function(parent_frame, q, fig_queue, canvas_widget))
975
- parent_frame.after_tasks.append(after_id)
706
+ after_id = parent_window.after(100, lambda: main_thread_update_function(parent_window, q, fig_queue, canvas_widget))
707
+ parent_window.after_tasks.append(after_id)
976
708
 
977
709
  print("Root initialization complete")
978
710
  return parent_frame, vars_dict
979
-