spacr 0.2.3__py3-none-any.whl → 0.2.5__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.
Files changed (61) hide show
  1. spacr/app_annotate.py +3 -4
  2. spacr/core.py +100 -282
  3. spacr/gui.py +20 -38
  4. spacr/gui_core.py +406 -499
  5. spacr/gui_elements.py +395 -60
  6. spacr/gui_utils.py +393 -73
  7. spacr/io.py +130 -50
  8. spacr/measure.py +199 -154
  9. spacr/plot.py +108 -42
  10. spacr/resources/font/open_sans/OFL.txt +93 -0
  11. spacr/resources/font/open_sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  12. spacr/resources/font/open_sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  13. spacr/resources/font/open_sans/README.txt +100 -0
  14. spacr/resources/font/open_sans/static/OpenSans-Bold.ttf +0 -0
  15. spacr/resources/font/open_sans/static/OpenSans-BoldItalic.ttf +0 -0
  16. spacr/resources/font/open_sans/static/OpenSans-ExtraBold.ttf +0 -0
  17. spacr/resources/font/open_sans/static/OpenSans-ExtraBoldItalic.ttf +0 -0
  18. spacr/resources/font/open_sans/static/OpenSans-Italic.ttf +0 -0
  19. spacr/resources/font/open_sans/static/OpenSans-Light.ttf +0 -0
  20. spacr/resources/font/open_sans/static/OpenSans-LightItalic.ttf +0 -0
  21. spacr/resources/font/open_sans/static/OpenSans-Medium.ttf +0 -0
  22. spacr/resources/font/open_sans/static/OpenSans-MediumItalic.ttf +0 -0
  23. spacr/resources/font/open_sans/static/OpenSans-Regular.ttf +0 -0
  24. spacr/resources/font/open_sans/static/OpenSans-SemiBold.ttf +0 -0
  25. spacr/resources/font/open_sans/static/OpenSans-SemiBoldItalic.ttf +0 -0
  26. spacr/resources/font/open_sans/static/OpenSans_Condensed-Bold.ttf +0 -0
  27. spacr/resources/font/open_sans/static/OpenSans_Condensed-BoldItalic.ttf +0 -0
  28. spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBold.ttf +0 -0
  29. spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
  30. spacr/resources/font/open_sans/static/OpenSans_Condensed-Italic.ttf +0 -0
  31. spacr/resources/font/open_sans/static/OpenSans_Condensed-Light.ttf +0 -0
  32. spacr/resources/font/open_sans/static/OpenSans_Condensed-LightItalic.ttf +0 -0
  33. spacr/resources/font/open_sans/static/OpenSans_Condensed-Medium.ttf +0 -0
  34. spacr/resources/font/open_sans/static/OpenSans_Condensed-MediumItalic.ttf +0 -0
  35. spacr/resources/font/open_sans/static/OpenSans_Condensed-Regular.ttf +0 -0
  36. spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBold.ttf +0 -0
  37. spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
  38. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Bold.ttf +0 -0
  39. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
  40. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
  41. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  42. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Italic.ttf +0 -0
  43. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Light.ttf +0 -0
  44. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
  45. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Medium.ttf +0 -0
  46. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
  47. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Regular.ttf +0 -0
  48. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
  49. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
  50. spacr/resources/icons/logo.pdf +2786 -6
  51. spacr/resources/icons/logo_spacr.png +0 -0
  52. spacr/resources/icons/logo_spacr_1.png +0 -0
  53. spacr/settings.py +12 -87
  54. spacr/utils.py +45 -10
  55. {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/METADATA +5 -1
  56. spacr-0.2.5.dist-info/RECORD +100 -0
  57. spacr-0.2.3.dist-info/RECORD +0 -58
  58. {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/LICENSE +0 -0
  59. {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/WHEEL +0 -0
  60. {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/entry_points.txt +0 -0
  61. {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/top_level.txt +0 -0
spacr/gui_core.py CHANGED
@@ -1,23 +1,23 @@
1
- import os, traceback, ctypes, matplotlib, requests, csv, matplotlib, time, requests, re
2
- import matplotlib.pyplot as plt
3
- matplotlib.use('Agg')
1
+ import traceback, ctypes, csv, re, time
4
2
  import tkinter as tk
5
3
  from tkinter import ttk
6
4
  from tkinter import filedialog
7
- from multiprocessing import Process, Value, Queue, set_start_method
8
- from multiprocessing.sharedctypes import Synchronized
5
+ from multiprocessing import Process, Value, Queue, set_start_method, Manager
9
6
  from tkinter import ttk, scrolledtext
10
7
  from matplotlib.figure import Figure
11
8
  from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
12
- from huggingface_hub import list_repo_files
13
9
  import numpy as np
10
+ import psutil
11
+ import GPUtil
12
+ import tkinter.font as tkFont
13
+
14
14
  try:
15
15
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
16
16
  except AttributeError:
17
17
  pass
18
18
 
19
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
20
- 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
21
21
 
22
22
  # Define global variables
23
23
  q = None
@@ -32,143 +32,96 @@ fig_queue = None
32
32
 
33
33
  thread_control = {"run_thread": None, "stop_requested": False}
34
34
 
35
- def initiate_abort():
36
- global thread_control
37
- if isinstance(thread_control.get("stop_requested"), Synchronized):
38
- thread_control["stop_requested"].value = 1
39
- if thread_control.get("run_thread") is not None:
40
- thread_control["run_thread"].terminate()
41
- thread_control["run_thread"].join()
42
- thread_control["run_thread"] = None
43
-
44
- def spacrFigShow(fig_queue=None):
45
- """
46
- Replacement for plt.show() that queues figures instead of displaying them.
47
- """
48
- fig = plt.gcf()
49
- if fig_queue:
50
- fig_queue.put(fig)
51
- else:
52
- fig.show()
53
- 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.")
54
41
 
55
- def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imports=1):
42
+ active_categories = set()
56
43
 
57
- """
58
- Wraps the run_multiple_simulations function to integrate with GUI processes.
59
-
60
- Parameters:
61
- - settings: dict, The settings for the run_multiple_simulations function.
62
- - q: multiprocessing.Queue, Queue for logging messages to the GUI.
63
- - fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
64
- """
44
+ def toggle_category(settings):
45
+ for setting in settings:
46
+ if setting in vars_dict:
47
+ label, widget, _, frame = vars_dict[setting]
48
+ if widget.grid_info():
49
+ label.grid_remove()
50
+ widget.grid_remove()
51
+ frame.grid_remove()
52
+ else:
53
+ label.grid()
54
+ widget.grid()
55
+ frame.grid()
65
56
 
66
- # Temporarily override plt.show
67
- original_show = plt.show
68
- plt.show = lambda: spacrFigShow(fig_queue)
57
+ def on_category_select(selected_category):
58
+ if selected_category == "Select Category":
59
+ return
60
+ if selected_category in categories:
61
+ toggle_category(categories[selected_category])
62
+ if selected_category in active_categories:
63
+ active_categories.remove(selected_category)
64
+ else:
65
+ active_categories.add(selected_category)
66
+ category_dropdown.update_styles(active_categories)
67
+ category_var.set("Select Category")
69
68
 
70
- try:
71
- if imports == 1:
72
- function(settings=settings)
73
- elif imports == 2:
74
- function(src=settings['src'], settings=settings)
75
- except Exception as e:
76
- # Send the error message to the GUI via the queue
77
- errorMessage = f"Error during processing: {e}"
78
- q.put(errorMessage)
79
- traceback.print_exc()
80
- finally:
81
- # Restore the original plt.show function
82
- plt.show = original_show
83
-
84
- def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
85
- from .gui_utils import process_stdout_stderr
86
- 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
87
- from .io import generate_cellpose_train_test
88
- from .measure import measure_crop
89
- from .sim import run_multiple_simulations
90
- from .deep_spacr import train_test_model
91
- from .sequencing import analyze_reads, map_barcodes_folder, perform_regression
92
- process_stdout_stderr(q)
93
-
94
- print(f'run_function_gui settings_type: {settings_type}')
95
-
96
- if settings_type == 'mask':
97
- function = preprocess_generate_masks
98
- imports = 2
99
- elif settings_type == 'measure':
100
- function = measure_crop
101
- imports = 1
102
- elif settings_type == 'simulation':
103
- function = run_multiple_simulations
104
- imports = 1
105
- elif settings_type == 'sequencing':
106
- function = analyze_reads
107
- imports = 1
108
- elif settings_type == 'classify':
109
- function = train_test_model
110
- imports = 2
111
- elif settings_type == 'train_cellpose':
112
- function = train_cellpose
113
- imports = 1
114
- elif settings_type == 'ml_analyze':
115
- function = generate_ml_scores
116
- imports = 2
117
- elif settings_type == 'cellpose_masks':
118
- function = identify_masks_finetune
119
- imports = 1
120
- elif settings_type == 'cellpose_all':
121
- function = check_cellpose_models
122
- imports = 1
123
- elif settings_type == 'map_barcodes':
124
- function = map_barcodes_folder
125
- imports = 2
126
- elif settings_type == 'regression':
127
- function = perform_regression
128
- imports = 2
129
- elif settings_type == 'recruitment':
130
- function = analyze_recruitment
131
- imports = 2
132
- else:
133
- raise ValueError(f"Invalid settings type: {settings_type}")
134
- try:
135
- function_gui_wrapper(function, settings, q, fig_queue, imports)
136
- except Exception as e:
137
- q.put(f"Error during processing: {e}")
138
- traceback.print_exc()
139
- finally:
140
- stop_requested.value = 1
69
+ category_var = tk.StringVar()
70
+ non_empty_categories = [category for category, settings in categories.items() if any(setting in vars_dict for setting in settings)]
71
+ category_dropdown = spacrDropdownMenu(button_scrollable_frame.scrollable_frame, category_var, non_empty_categories, command=on_category_select)
72
+ category_dropdown.grid(row=0, column=4, sticky="ew", pady=2, padx=2)
73
+ vars_dict = hide_all_settings(vars_dict, categories)
141
74
 
142
- def start_process(q=None, fig_queue=None, settings_type='mask'):
143
- global thread_control, vars_dict
144
- from .settings import check_settings, expected_types
75
+ def process_fig_queue():
76
+ global canvas, fig_queue, canvas_widget, parent_frame
145
77
 
146
- if q is None:
147
- q = Queue()
148
- if fig_queue is None:
149
- fig_queue = Queue()
78
+ def clear_canvas(canvas):
79
+ for ax in canvas.figure.get_axes():
80
+ ax.clear()
81
+ canvas.draw_idle()
150
82
 
151
83
  try:
152
- settings = check_settings(vars_dict, expected_types, q)
153
- except ValueError as e:
154
- q.put(f"Error: {e}")
155
- return
84
+ while not fig_queue.empty():
85
+ time.sleep(1)
86
+ clear_canvas(canvas)
87
+ fig = fig_queue.get_nowait()
156
88
 
157
- if thread_control.get("run_thread") is not None:
158
- initiate_abort()
159
-
160
- stop_requested = Value('i', 0)
161
- thread_control["stop_requested"] = stop_requested
89
+ for ax in fig.get_axes():
90
+ ax.set_xticks([]) # Remove x-axis ticks
91
+ ax.set_yticks([]) # Remove y-axis ticks
92
+ ax.xaxis.set_visible(False) # Hide the x-axis
93
+ ax.yaxis.set_visible(False) # Hide the y-axis
162
94
 
163
- process_args = (settings_type, settings, q, fig_queue, stop_requested)
164
- 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']:
165
- thread_control["run_thread"] = Process(target=run_function_gui, args=process_args)
166
- else:
167
- q.put(f"Error: Unknown settings type '{settings_type}'")
168
- return
169
- thread_control["run_thread"].start()
95
+ # Adjust layout to minimize spacing between axes
96
+ fig.subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0.01, hspace=0.01)
97
+ fig.set_facecolor('black')
98
+
99
+ canvas.figure = fig
100
+ fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
101
+ fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
102
+ canvas.draw_idle()
103
+ except Exception as e:
104
+ traceback.print_exc()
105
+ finally:
106
+ after_id = canvas_widget.after(1, process_fig_queue)
107
+ parent_frame.after_tasks.append(after_id)
108
+
109
+ 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):
110
+ global thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars
111
+ thread_control = thread_control_var
112
+ q = q_var
113
+ console_output = console_output_var
114
+ parent_frame = parent_frame_var
115
+ vars_dict = vars_dict_var
116
+ canvas = canvas_var
117
+ canvas_widget = canvas_widget_var
118
+ scrollable_frame = scrollable_frame_var
119
+ fig_queue = fig_queue_var
120
+ progress_bar = progress_bar_var
121
+ usage_bars = usage_bars_var
170
122
 
171
123
  def import_settings(settings_type='mask'):
124
+ from .gui_utils import convert_settings_dict_for_gui, hide_all_settings
172
125
  global vars_dict, scrollable_frame, button_scrollable_frame
173
126
  from .settings import generate_fields
174
127
 
@@ -217,74 +170,23 @@ def import_settings(settings_type='mask'):
217
170
  vars_dict = generate_fields(new_settings, scrollable_frame)
218
171
  vars_dict = hide_all_settings(vars_dict, categories=None)
219
172
 
220
- def convert_settings_dict_for_gui(settings):
221
- from torchvision import models as torch_models
222
- torchvision_models = [name for name, obj in torch_models.__dict__.items() if callable(obj)]
223
- chans = ['0', '1', '2', '3', '4', '5', '6', '7', '8', None]
224
- chans_v2 = [0, 1, 2, 3, None]
225
- variables = {}
226
- special_cases = {
227
- 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
228
- 'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
229
- 'channel_dims': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
230
- 'cell_mask_dim': ('combo', chans, None),
231
- 'cell_chann_dim': ('combo', chans, None),
232
- 'nucleus_mask_dim': ('combo', chans, None),
233
- 'nucleus_chann_dim': ('combo', chans, None),
234
- 'pathogen_mask_dim': ('combo', chans, None),
235
- 'pathogen_chann_dim': ('combo', chans, None),
236
- 'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
237
- 'magnification': ('combo', [20, 40, 60], 20),
238
- 'nucleus_channel': ('combo', chans_v2, None),
239
- 'cell_channel': ('combo', chans_v2, None),
240
- 'channel_of_interest': ('combo', chans_v2, None),
241
- 'pathogen_channel': ('combo', chans_v2, None),
242
- 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
243
- 'train_mode': ('combo', ['erm', 'irm'], 'erm'),
244
- 'clustering': ('combo', ['dbscan', 'kmean'], 'dbscan'),
245
- 'reduction_method': ('combo', ['umap', 'tsne'], 'umap'),
246
- 'model_name': ('combo', ['cyto', 'cyto_2', 'cyto_3', 'nuclei'], 'cyto'),
247
- 'regression_type': ('combo', ['ols','gls','wls','rlm','glm','mixed','quantile','logit','probit','poisson','lasso','ridge'], 'ols'),
248
- 'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
249
- 'model_type': ('combo', torchvision_models, 'resnet50'),
250
- 'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
251
- 'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
252
- 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
253
- 'normalize_by': ('combo', ['fov', 'png'], 'png'),
254
- 'agg_type': ('combo', ['mean', 'median'], 'mean'),
255
- 'grouping': ('combo', ['mean', 'median'], 'mean'),
256
- 'min_max': ('combo', ['allq', 'all'], 'allq'),
257
- 'transform': ('combo', ['log', 'sqrt', 'square', None], None)
258
- }
259
-
260
- for key, value in settings.items():
261
- if key in special_cases:
262
- variables[key] = special_cases[key]
263
- elif isinstance(value, bool):
264
- variables[key] = ('check', None, value)
265
- elif isinstance(value, int) or isinstance(value, float):
266
- variables[key] = ('entry', None, value)
267
- elif isinstance(value, str):
268
- variables[key] = ('entry', None, value)
269
- elif value is None:
270
- variables[key] = ('entry', None, value)
271
- elif isinstance(value, list):
272
- variables[key] = ('entry', None, str(value))
273
- else:
274
- variables[key] = ('entry', None, str(value))
275
- return variables
276
-
277
- def setup_settings_panel(vertical_container, settings_type='mask', window_dimensions=[500, 1000]):
173
+ def setup_settings_panel(vertical_container, settings_type='mask'):
278
174
  global vars_dict, scrollable_frame
279
175
  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
176
+ from .gui_utils import convert_settings_dict_for_gui, set_element_size
177
+
178
+ size_dict = set_element_size()
179
+ settings_width = size_dict['settings_width']
180
+
181
+ # Create a PanedWindow for the settings panel
182
+ settings_paned_window = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL, width=size_dict['settings_width'])
183
+ vertical_container.add(settings_paned_window, stretch="always")
280
184
 
281
- width = (window_dimensions[0]) // 6
282
- height = window_dimensions[1]
185
+ settings_frame = tk.Frame(settings_paned_window, width=settings_width)
186
+ settings_frame.pack_propagate(False) # Prevent the frame from resizing based on its children
187
+
188
+ settings_paned_window.add(settings_frame)
283
189
 
284
- settings_frame = tk.Frame(vertical_container)
285
- vertical_container.add(settings_frame, stretch="always")
286
- settings_label = spacrLabel(settings_frame, text="Settings", anchor='center', justify='center', align="center")
287
- settings_label.grid(row=0, column=0, pady=10, padx=10)
288
190
  scrollable_frame = spacrFrame(settings_frame)
289
191
  scrollable_frame.grid(row=1, column=0, sticky="nsew")
290
192
  settings_frame.grid_rowconfigure(1, weight=1)
@@ -321,7 +223,7 @@ def setup_settings_panel(vertical_container, settings_type='mask', window_dimens
321
223
  vars_dict = generate_fields(variables, scrollable_frame)
322
224
 
323
225
  containers = [settings_frame]
324
- widgets = [settings_label, scrollable_frame]
226
+ widgets = [scrollable_frame]
325
227
 
326
228
  style = ttk.Style(vertical_container)
327
229
  _ = set_dark_style(style, containers=containers, widgets=widgets)
@@ -331,8 +233,12 @@ def setup_settings_panel(vertical_container, settings_type='mask', window_dimens
331
233
 
332
234
  def setup_plot_section(vertical_container):
333
235
  global canvas, canvas_widget
334
- plot_frame = tk.PanedWindow(vertical_container, orient=tk.VERTICAL)
236
+
237
+ # Create a frame for the plot section
238
+ plot_frame = tk.Frame(vertical_container, bg='lightgrey')
335
239
  vertical_container.add(plot_frame, stretch="always")
240
+
241
+ # Set up the plot
336
242
  figure = Figure(figsize=(30, 4), dpi=100)
337
243
  plot = figure.add_subplot(111)
338
244
  plot.plot([], []) # This creates an empty plot.
@@ -340,7 +246,11 @@ def setup_plot_section(vertical_container):
340
246
  canvas = FigureCanvasTkAgg(figure, master=plot_frame)
341
247
  canvas.get_tk_widget().configure(cursor='arrow', highlightthickness=0)
342
248
  canvas_widget = canvas.get_tk_widget()
343
- plot_frame.add(canvas_widget, stretch="always")
249
+ canvas_widget.grid(row=0, column=0, sticky="nsew") # Use grid for the canvas widget
250
+
251
+ plot_frame.grid_rowconfigure(0, weight=1)
252
+ plot_frame.grid_columnconfigure(0, weight=1)
253
+
344
254
  canvas.draw()
345
255
  canvas.figure = figure
346
256
  style_out = set_dark_style(ttk.Style())
@@ -351,23 +261,46 @@ def setup_plot_section(vertical_container):
351
261
  widgets = [canvas_widget]
352
262
  style = ttk.Style(vertical_container)
353
263
  _ = set_dark_style(style, containers=containers, widgets=widgets)
264
+
354
265
  return canvas, canvas_widget
355
266
 
356
267
  def setup_console(vertical_container):
357
268
  global console_output
358
- console_frame = tk.Frame(vertical_container)
269
+ from .gui_elements import set_dark_style
270
+
271
+ # Apply dark style and get style output
272
+ style = ttk.Style()
273
+ style_out = set_dark_style(style)
274
+
275
+ # Create a frame for the console section
276
+ console_frame = tk.Frame(vertical_container, bg=style_out['bg_color'])
359
277
  vertical_container.add(console_frame, stretch="always")
360
- console_label = spacrLabel(console_frame, text="Console", anchor='center', justify='center', align="center")
361
- console_label.grid(row=0, column=0, pady=10, padx=10)
362
- console_output = scrolledtext.ScrolledText(console_frame, height=10)
363
- console_output.grid(row=1, column=0, sticky="nsew")
278
+
279
+ # Create a thicker frame at the top for the hover effect
280
+ top_border = tk.Frame(console_frame, height=5, bg=style_out['bg_color'])
281
+ top_border.grid(row=0, column=0, sticky="ew", pady=(0, 2))
282
+
283
+ # Create the scrollable frame (which is a Text widget) with white text
284
+ family = style_out['font_family']
285
+ size = style_out['font_size'] - 2
286
+ font = tkFont.Font(family=family, size=size)
287
+ console_output = tk.Text(console_frame, bg=style_out['bg_color'], fg=style_out['fg_color'], font=font, bd=0, highlightthickness=0)
288
+ console_output.grid(row=1, column=0, sticky="nsew") # Use grid for console_output
289
+
290
+ # Configure the grid to allow expansion
364
291
  console_frame.grid_rowconfigure(1, weight=1)
365
292
  console_frame.grid_columnconfigure(0, weight=1)
366
- containers = [console_frame]
367
- widgets = [console_label, console_output]
368
- style = ttk.Style(vertical_container)
369
- _ = set_dark_style(style, containers=containers, widgets=widgets)
370
- return console_output
293
+
294
+ def on_enter(event):
295
+ top_border.config(bg=style_out['active_color'])
296
+
297
+ def on_leave(event):
298
+ top_border.config(bg=style_out['bg_color'])
299
+
300
+ console_output.bind("<Enter>", on_enter)
301
+ console_output.bind("<Leave>", on_leave)
302
+
303
+ return console_output, console_frame
371
304
 
372
305
  def setup_progress_frame(vertical_container):
373
306
  global progress_output
@@ -387,278 +320,257 @@ def setup_progress_frame(vertical_container):
387
320
  _ = set_dark_style(style, containers=containers, widgets=widgets)
388
321
  return progress_output
389
322
 
390
- def download_hug_dataset():
391
- global vars_dict, q
392
- dataset_repo_id = "einarolafsson/toxo_mito"
393
- settings_repo_id = "einarolafsson/spacr_settings"
394
- dataset_subfolder = "plate1"
395
- local_dir = os.path.join(os.path.expanduser("~"), "datasets") # Set to the home directory
396
-
397
- # Download the dataset
398
- try:
399
- dataset_path = download_dataset(dataset_repo_id, dataset_subfolder, local_dir)
400
- if 'src' in vars_dict:
401
- vars_dict['src'][2].set(dataset_path)
402
- q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
403
- q.put(f"Dataset downloaded to: {dataset_path}\n")
404
- except Exception as e:
405
- q.put(f"Failed to download dataset: {e}\n")
406
-
407
- # Download the settings files
408
- try:
409
- settings_path = download_dataset(settings_repo_id, "", local_dir)
410
- q.put(f"Settings downloaded to: {settings_path}\n")
411
- except Exception as e:
412
- q.put(f"Failed to download settings: {e}\n")
413
-
414
- def download_dataset(repo_id, subfolder, local_dir=None, retries=5, delay=5):
415
- global q
416
- """
417
- Downloads a dataset or settings files from Hugging Face and returns the local path.
418
-
419
- Args:
420
- repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito' or 'einarolafsson/spacr_settings').
421
- subfolder (str): The subfolder path within the repository (e.g., 'plate1' or the settings subfolder).
422
- local_dir (str): The local directory where the files will be saved. Defaults to the user's home directory.
423
- retries (int): Number of retry attempts in case of failure.
424
- delay (int): Delay in seconds between retries.
425
-
426
- Returns:
427
- str: The local path to the downloaded files.
428
- """
429
- if local_dir is None:
430
- local_dir = os.path.join(os.path.expanduser("~"), "datasets")
431
-
432
- local_subfolder_dir = os.path.join(local_dir, subfolder if subfolder else "settings")
433
- if not os.path.exists(local_subfolder_dir):
434
- os.makedirs(local_subfolder_dir)
435
- elif len(os.listdir(local_subfolder_dir)) > 0:
436
- q.put(f"Files already downloaded to: {local_subfolder_dir}")
437
- return local_subfolder_dir
438
-
439
- attempt = 0
440
- while attempt < retries:
441
- try:
442
- files = list_repo_files(repo_id, repo_type="dataset")
443
- subfolder_files = [file for file in files if file.startswith(subfolder) or (subfolder == "" and file.endswith('.csv'))]
444
-
445
- for file_name in subfolder_files:
446
- for download_attempt in range(retries):
447
- try:
448
- url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
449
- response = requests.get(url, stream=True)
450
- response.raise_for_status()
451
-
452
- local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
453
- with open(local_file_path, 'wb') as file:
454
- for chunk in response.iter_content(chunk_size=8192):
455
- file.write(chunk)
456
- q.put(f"Downloaded file: {file_name}")
457
- break
458
- except (requests.HTTPError, requests.Timeout) as e:
459
- q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
460
- time.sleep(delay)
461
- else:
462
- raise Exception(f"Failed to download {file_name} after multiple attempts.")
463
-
464
- return local_subfolder_dir
465
-
466
- except (requests.HTTPError, requests.Timeout) as e:
467
- q.put(f"Error downloading files: {e}. Retrying in {delay} seconds...")
468
- attempt += 1
469
- time.sleep(delay)
470
-
471
- raise Exception("Failed to download files after multiple attempts.")
472
-
473
- def setup_button_section(horizontal_container, settings_type='mask', window_dimensions=[500, 1000], run=True, abort=True, download=True, import_btn=True):
474
- global button_frame, button_scrollable_frame, run_button, abort_button, download_dataset_button, import_button, q, fig_queue, vars_dict, progress_bar
475
- from .settings import descriptions
323
+ def setup_button_section(horizontal_container, settings_type='mask', run=True, abort=True, download=True, import_btn=True):
324
+ 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
325
+ from .gui_utils import set_element_size, download_hug_dataset
326
+ from .settings import categories
476
327
 
477
- width = (window_dimensions[0]) // 8
478
- height = window_dimensions[1]
328
+ size_dict = set_element_size()
329
+ button_section_height = size_dict['panel_height']
330
+ button_frame = tk.Frame(horizontal_container, height=button_section_height)
331
+
332
+ # Prevent the frame from resizing based on the child widget
333
+ #button_frame.pack_propagate(False)
479
334
 
480
- button_frame = tk.Frame(horizontal_container)
481
335
  horizontal_container.add(button_frame, stretch="always", sticky="nsew")
482
- button_frame.grid_rowconfigure(0, weight=0)
483
- button_frame.grid_rowconfigure(1, weight=1)
484
- button_frame.grid_columnconfigure(0, weight=1)
485
-
486
- categories_label = spacrLabel(button_frame, text="Categories", anchor='center', justify='center', align="center")
487
- categories_label.grid(row=0, column=0, pady=10, padx=10)
488
- button_scrollable_frame = spacrFrame(button_frame)
336
+ button_scrollable_frame = spacrFrame(button_frame, scrollbar=False)
489
337
  button_scrollable_frame.grid(row=1, column=0, sticky="nsew")
490
-
491
- widgets = [categories_label, button_scrollable_frame.scrollable_frame]
338
+ widgets = [button_scrollable_frame.scrollable_frame]
492
339
 
493
340
  btn_col = 0
494
- btn_row = 1
341
+ btn_row = 0
495
342
 
496
343
  if run:
497
- print(f'settings_type: {settings_type}')
498
- run_button = spacrButton(button_scrollable_frame.scrollable_frame, text="run", command=lambda: start_process(q, fig_queue, settings_type))
344
+ 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)
499
345
  run_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
500
346
  widgets.append(run_button)
501
- btn_row += 1
347
+ btn_col += 1
502
348
 
503
349
  if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap']:
504
- abort_button = spacrButton(button_scrollable_frame.scrollable_frame, text="abort", command=initiate_abort)
350
+ abort_button = spacrButton(button_scrollable_frame.scrollable_frame, text="abort", command=lambda: initiate_abort(), show_text=False, size=size_dict['btn_size'], animation=False)
505
351
  abort_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
506
352
  widgets.append(abort_button)
507
- btn_row += 1
353
+ btn_col += 1
508
354
 
509
355
  if download and settings_type in ['mask']:
510
- download_dataset_button = spacrButton(button_scrollable_frame.scrollable_frame, text="download", command=download_hug_dataset)
356
+ 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)
511
357
  download_dataset_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
512
358
  widgets.append(download_dataset_button)
513
- btn_row += 1
359
+ btn_col += 1
514
360
 
515
361
  if import_btn:
516
- import_button = spacrButton(button_scrollable_frame.scrollable_frame, text="settings", command=lambda: import_settings(settings_type))
362
+ 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)
517
363
  import_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
518
364
  widgets.append(import_button)
519
365
  btn_row += 1
520
366
 
367
+ # Add the progress bar under the settings category menu
521
368
  progress_bar = spacrProgressBar(button_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate')
522
- progress_bar.grid(row=0, column=0, columnspan=2, pady=5, padx=5, sticky='ew')
369
+ progress_bar.grid(row=btn_row, column=0, columnspan=6, pady=5, padx=5, sticky='ew')
370
+ progress_bar.set_label_position() # Set the label position after grid placement
523
371
  widgets.append(progress_bar)
524
372
 
525
373
  if vars_dict is not None:
526
374
  toggle_settings(button_scrollable_frame)
527
375
 
528
- description_frame = tk.Frame(horizontal_container)
529
- horizontal_container.add(description_frame, stretch="always", sticky="nsew")
530
- description_frame.grid_columnconfigure(0, weight=1)
531
- description_frame.grid_rowconfigure(0, weight=1)
532
-
533
- description_label = tk.Label(description_frame, text="Module Description", anchor='nw', justify='left', wraplength=width - 50)
534
- description_label.grid(row=0, column=0, pady=50, padx=20, sticky='nsew')
535
- description_text = descriptions.get(settings_type, "No description available for this module.")
536
- description_label.config(text=description_text)
537
-
538
- def update_wraplength(event):
539
- new_width = event.width - 40 # Adjust as needed
540
- description_label.config(wraplength=new_width)
541
-
542
- description_label.bind('<Configure>', update_wraplength)
543
-
544
- containers = [button_frame, description_frame]
545
- widgets.extend([description_label])
546
-
547
376
  style = ttk.Style(horizontal_container)
548
- _ = set_dark_style(style, containers=containers, widgets=widgets)
549
-
550
- return button_scrollable_frame
551
-
552
-
553
- def hide_all_settings(vars_dict, categories):
554
- """
555
- Function to initially hide all settings in the GUI.
556
-
557
- Parameters:
558
- - categories: dict, The categories of settings with their corresponding settings.
559
- - vars_dict: dict, The dictionary containing the settings and their corresponding widgets.
560
- """
561
-
562
- if categories is None:
563
- from .settings import categories
377
+ _ = set_dark_style(style, containers=[button_frame], widgets=widgets)
378
+
379
+ return button_scrollable_frame, btn_col
380
+
381
+ def setup_usage_panel(horizontal_container, btn_col):
382
+ global usage_bars
383
+ from .gui_utils import set_element_size
384
+ from .gui_elements import set_dark_style
385
+
386
+ usg_col = 1
387
+
388
+ def update_usage(ram_bar, vram_bar, gpu_bar, usage_bars, parent_frame):
389
+ # Update RAM usage
390
+ ram_usage = psutil.virtual_memory().percent
391
+ ram_bar['value'] = ram_usage
392
+
393
+ # Update GPU and VRAM usage
394
+ gpus = GPUtil.getGPUs()
395
+ if gpus:
396
+ gpu = gpus[0]
397
+ vram_usage = gpu.memoryUtil * 100
398
+ gpu_usage = gpu.load * 100
399
+ vram_bar['value'] = vram_usage
400
+ gpu_bar['value'] = gpu_usage
401
+
402
+ # Update CPU usage for each core
403
+ cpu_percentages = psutil.cpu_percent(percpu=True)
404
+ for bar, usage in zip(usage_bars[3:], cpu_percentages):
405
+ bar['value'] = usage
406
+
407
+ # Schedule the function to run again after 1000 ms (1 second)
408
+ parent_frame.after(1000, update_usage, ram_bar, vram_bar, gpu_bar, usage_bars, parent_frame)
409
+
410
+ size_dict = set_element_size()
411
+ usage_panel_height = size_dict['panel_height']
412
+ usage_frame = tk.Frame(horizontal_container, height=usage_panel_height)
413
+ horizontal_container.add(usage_frame)
414
+
415
+ usage_frame.grid_rowconfigure(0, weight=0)
416
+ usage_frame.grid_rowconfigure(1, weight=1)
417
+ usage_frame.grid_columnconfigure(0, weight=1)
418
+ usage_frame.grid_columnconfigure(1, weight=1)
419
+
420
+ usage_scrollable_frame = spacrFrame(usage_frame, scrollbar=False)
421
+ usage_scrollable_frame.grid(row=1, column=0, sticky="nsew", columnspan=2)
422
+ widgets = [usage_scrollable_frame.scrollable_frame]
423
+
424
+ usage_bars = []
425
+ max_elements_per_column = 4
426
+ row = 0
427
+ col = 0
564
428
 
565
- for category, settings in categories.items():
566
- if any(setting in vars_dict for setting in settings):
567
- vars_dict[category] = (None, None, tk.IntVar(value=0))
568
-
569
- # Initially hide all settings
570
- for setting in settings:
571
- if setting in vars_dict:
572
- label, widget, _ = vars_dict[setting]
573
- label.grid_remove()
574
- widget.grid_remove()
575
- return vars_dict
429
+ # Initialize RAM, VRAM, and GPU bars as None
430
+ ram_bar, vram_bar, gpu_bar = None, None, None
431
+
432
+ # Configure the style for the label
433
+ style = ttk.Style()
434
+ style_out = set_dark_style(style)
435
+ size = style_out['font_size'] - 2
436
+ usage_font = tkFont.Font(family=style_out['font_family'], size=size)
437
+ style.configure("usage.TLabel", font=usage_font, foreground=style_out['fg_color'])
438
+
439
+ # Try adding RAM bar
440
+ try:
441
+ ram_info = psutil.virtual_memory()
442
+ ram_label_text = f"RAM"
443
+ label = ttk.Label(usage_scrollable_frame.scrollable_frame, text=ram_label_text, anchor='w', style="usage.TLabel")
444
+ label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
445
+ ram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
446
+ ram_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
447
+ widgets.append(label)
448
+ widgets.append(ram_bar)
449
+ usage_bars.append(ram_bar)
450
+ row += 1
451
+ except Exception as e:
452
+ print(f"Could not add RAM usage bar: {e}")
576
453
 
577
- def toggle_settings(button_scrollable_frame):
578
- global vars_dict
579
- from .settings import categories
454
+ # Try adding VRAM and GPU usage bars
455
+ try:
456
+ gpus = GPUtil.getGPUs()
457
+ if gpus:
458
+ gpu = gpus[0]
459
+ vram_label_text = f"VRAM"
460
+ label = ttk.Label(usage_scrollable_frame.scrollable_frame, text=vram_label_text, anchor='w', style="usage.TLabel")
461
+ label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
462
+ vram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
463
+ vram_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
464
+ widgets.append(label)
465
+ widgets.append(vram_bar)
466
+ usage_bars.append(vram_bar)
467
+ row += 1
468
+
469
+ gpu_label_text = f"GPU"
470
+ label = ttk.Label(usage_scrollable_frame.scrollable_frame, text=gpu_label_text, anchor='w', style="usage.TLabel")
471
+ label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
472
+ gpu_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
473
+ gpu_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
474
+ widgets.append(label)
475
+ widgets.append(gpu_bar)
476
+ usage_bars.append(gpu_bar)
477
+ row += 1
478
+ except Exception as e:
479
+ print(f"Could not add VRAM or GPU usage bars: {e}")
580
480
 
581
- if vars_dict is None:
582
- raise ValueError("vars_dict is not initialized.")
481
+ # Add CPU core usage bars
482
+ try:
483
+ cpu_cores = psutil.cpu_count(logical=True)
484
+ cpu_freq = psutil.cpu_freq()
485
+
486
+ for core in range(cpu_cores):
487
+ if row > 0 and row % max_elements_per_column == 0:
488
+ col += 1
489
+ row = 0
490
+ label = ttk.Label(usage_scrollable_frame.scrollable_frame, text=f"Core {core+1}", anchor='w', style="usage.TLabel")
491
+ label.grid(row=row, column=2 * col, pady=2, padx=5, sticky='w')
492
+ bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
493
+ bar.grid(row=row, column=2 * col + 1, pady=2, padx=5, sticky='ew')
494
+ widgets.append(label)
495
+ widgets.append(bar)
496
+ usage_bars.append(bar)
497
+ row += 1
498
+ except Exception as e:
499
+ print(f"Could not add CPU core usage bars: {e}")
583
500
 
584
- active_categories = set()
501
+ style = ttk.Style(horizontal_container)
502
+ _ = set_dark_style(style, containers=[usage_frame], widgets=widgets)
503
+
504
+ if ram_bar is None:
505
+ ram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
506
+ if vram_bar is None:
507
+ vram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
508
+ if gpu_bar is None:
509
+ gpu_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
510
+
511
+ update_usage(ram_bar, vram_bar, gpu_bar, usage_bars, usage_frame)
512
+ return usage_scrollable_frame, usage_bars, usg_col
585
513
 
586
- def toggle_category(settings):
587
- for setting in settings:
588
- if setting in vars_dict:
589
- label, widget, _ = vars_dict[setting]
590
- if widget.grid_info():
591
- label.grid_remove()
592
- widget.grid_remove()
593
- else:
594
- label.grid()
595
- widget.grid()
514
+ def initiate_abort():
515
+ global thread_control, q, parent_frame
516
+ if thread_control.get("run_thread") is not None:
517
+ try:
518
+ q.put("Aborting processes...")
519
+ thread_control.get("run_thread").terminate()
520
+ thread_control["run_thread"] = None
521
+ q.put("Processes aborted.")
522
+ except Exception as e:
523
+ q.put(f"Error aborting process: {e}")
596
524
 
597
- def on_category_select(selected_category):
598
- if selected_category == "Select Category":
599
- return
600
- if selected_category in categories:
601
- toggle_category(categories[selected_category])
602
- if selected_category in active_categories:
603
- active_categories.remove(selected_category)
604
- else:
605
- active_categories.add(selected_category)
606
- category_dropdown.update_styles(active_categories)
607
- category_var.set("Select Category")
525
+ thread_control = {"run_thread": None, "stop_requested": False}
608
526
 
609
- category_var = tk.StringVar()
610
- non_empty_categories = [category for category, settings in categories.items() if any(setting in vars_dict for setting in settings)]
611
- category_dropdown = spacrDropdownMenu(button_scrollable_frame.scrollable_frame, category_var, non_empty_categories, command=on_category_select)
612
- category_dropdown.grid(row=5, column=0, sticky="ew", pady=2, padx=2)
613
- vars_dict = hide_all_settings(vars_dict, categories)
614
527
 
615
- def process_fig_queue():
616
- global canvas, fig_queue, canvas_widget, parent_frame
528
+ def start_process(q=None, fig_queue=None, settings_type='mask'):
529
+ global thread_control, vars_dict, parent_frame
530
+ from .settings import check_settings, expected_types
531
+ from .gui_utils import run_function_gui
617
532
 
618
- def clear_canvas(canvas):
619
- for ax in canvas.figure.get_axes():
620
- ax.clear()
621
- canvas.draw_idle()
533
+ if q is None:
534
+ q = Queue()
535
+ if fig_queue is None:
536
+ fig_queue = Queue()
622
537
 
623
538
  try:
624
- while not fig_queue.empty():
625
- clear_canvas(canvas)
626
- fig = fig_queue.get_nowait()
627
- for ax in fig.get_axes():
628
- ax.set_xticks([]) # Remove x-axis ticks
629
- ax.set_yticks([]) # Remove y-axis ticks
630
- ax.xaxis.set_visible(False) # Hide the x-axis
631
- ax.yaxis.set_visible(False) # Hide the y-axis
632
- fig.tight_layout()
633
- fig.set_facecolor('black')
634
- canvas.figure = fig
635
- fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
636
- fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
637
- canvas.draw_idle()
638
- except Exception as e:
639
- traceback.print_exc()
640
- finally:
641
- after_id = canvas_widget.after(100, process_fig_queue)
642
- parent_frame.after_tasks.append(after_id)
539
+ settings = check_settings(vars_dict, expected_types, q)
540
+ except ValueError as e:
541
+ q.put(f"Error: {e}")
542
+ return
643
543
 
644
- def process_console_queue_v1():
645
- global q, console_output, parent_frame
646
- while not q.empty():
647
- message = q.get_nowait()
648
- console_output.insert(tk.END, message)
649
- console_output.see(tk.END)
650
- after_id = console_output.after(100, process_console_queue_v1)
651
- parent_frame.after_tasks.append(after_id)
544
+ if thread_control.get("run_thread") is not None:
545
+ initiate_abort()
546
+
547
+ stop_requested = Value('i', 0)
548
+ thread_control["stop_requested"] = stop_requested
549
+
550
+ process_args = (settings_type, settings, q, fig_queue, stop_requested)
551
+ if settings_type in [
552
+ 'mask', 'measure', 'simulation', 'sequencing', 'classify', 'cellpose_dataset',
553
+ 'train_cellpose', 'ml_analyze', 'cellpose_masks', 'cellpose_all', 'map_barcodes',
554
+ 'regression', 'recruitment', 'plaques', 'cellpose_compare', 'vision_scores',
555
+ 'vision_dataset'
556
+ ]:
557
+ thread_control["run_thread"] = Process(target=run_function_gui, args=process_args)
558
+ else:
559
+ q.put(f"Error: Unknown settings type '{settings_type}'")
560
+ return
561
+ thread_control["run_thread"].start()
652
562
 
653
563
  def process_console_queue():
654
- global q, console_output, parent_frame, progress_bar, progress_label
564
+ global q, console_output, parent_frame, progress_bar
655
565
 
656
566
  # Initialize function attribute if it doesn't exist
657
567
  if not hasattr(process_console_queue, "completed_tasks"):
658
568
  process_console_queue.completed_tasks = []
569
+ if not hasattr(process_console_queue, "current_maximum"):
570
+ process_console_queue.current_maximum = None
659
571
 
660
572
  ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
661
-
573
+
662
574
  while not q.empty():
663
575
  message = q.get_nowait()
664
576
  clean_message = ansi_escape_pattern.sub('', message)
@@ -666,17 +578,24 @@ def process_console_queue():
666
578
  console_output.see(tk.END)
667
579
 
668
580
  # Check if the message contains progress information
669
- if clean_message.startswith("Progress"):
581
+ if clean_message.startswith("Progress:"):
670
582
  try:
671
583
  # Extract the progress information
672
- match = re.search(r'(\d+)/(\d+)', clean_message)
584
+ match = re.search(r'Progress: (\d+)/(\d+), operation_type: ([\w\s]*)(.*)', clean_message)
585
+
673
586
  if match:
674
587
  current_progress = int(match.group(1))
675
588
  total_progress = int(match.group(2))
589
+ operation_type = match.group(3).strip()
590
+ time_info = match.group(4).strip()
591
+
592
+ # Check if the maximum value has changed
593
+ if process_console_queue.current_maximum != total_progress:
594
+ process_console_queue.current_maximum = total_progress
595
+ process_console_queue.completed_tasks = []
676
596
 
677
597
  # Add the task to the completed set
678
598
  process_console_queue.completed_tasks.append(current_progress)
679
- #print('completed_tasks', process_console_queue.completed_tasks)
680
599
 
681
600
  # Calculate the unique progress count
682
601
  unique_progress_count = len(np.unique(process_console_queue.completed_tasks))
@@ -686,85 +605,64 @@ def process_console_queue():
686
605
  progress_bar['maximum'] = total_progress
687
606
  progress_bar['value'] = unique_progress_count
688
607
 
608
+ # Extract and update additional information
609
+ if operation_type:
610
+ progress_bar.operation_type = operation_type
611
+
612
+ time_image_match = re.search(r'Time/image: ([\d.]+) sec', time_info)
613
+ if time_image_match:
614
+ progress_bar.time_image = float(time_image_match.group(1))
615
+
616
+ time_batch_match = re.search(r'Time/batch: ([\d.]+) sec', time_info)
617
+ if time_batch_match:
618
+ progress_bar.time_batch = float(time_batch_match.group(1))
619
+
620
+ time_left_match = re.search(r'Time_left: ([\d.]+) min', time_info)
621
+ if time_left_match:
622
+ progress_bar.time_left = float(time_left_match.group(1))
623
+
689
624
  # Update the progress label
690
- if progress_label:
691
- progress_label_text = f"Processing: {unique_progress_count}/{total_progress}"
692
- progress_label.config(text=progress_label_text)
625
+ if progress_bar.progress_label:
626
+ progress_bar.update_label()
693
627
 
694
628
  # Clear completed tasks when progress is complete
695
- if unique_progress_count > total_progress:
629
+ if unique_progress_count >= total_progress:
696
630
  process_console_queue.completed_tasks.clear()
631
+
697
632
  except Exception as e:
698
633
  print(f"Error parsing progress message: {e}")
699
-
700
- after_id = console_output.after(100, process_console_queue)
634
+ #else:
635
+ # # Only insert messages that do not start with "Progress:"
636
+ # console_output.insert(tk.END, clean_message + "\n")
637
+ # console_output.see(tk.END)
638
+
639
+ after_id = console_output.after(1, process_console_queue)
701
640
  parent_frame.after_tasks.append(after_id)
702
641
 
703
-
704
- 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):
705
- global q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, progress_label, fig_queue, progress_bar
706
- q = q_var
707
- console_output = console_output_var
708
- parent_frame = parent_frame_var
709
- vars_dict = vars_dict_var
710
- canvas = canvas_var
711
- canvas_widget = canvas_widget_var
712
- scrollable_frame = scrollable_frame_var
713
- fig_queue = fig_queue_var
714
- progress_bar = progress_bar_var
715
-
716
- def create_containers(parent_frame):
717
- vertical_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL)
718
- horizontal_container = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL)
719
- settings_frame = tk.Frame(horizontal_container)
720
- return vertical_container, horizontal_container, settings_frame
721
-
722
- def setup_frame(parent_frame):
723
- style = ttk.Style(parent_frame)
724
- vertical_container, horizontal_container, settings_frame = create_containers(parent_frame)
725
- containers = [vertical_container, horizontal_container, settings_frame]
726
-
727
- set_dark_style(style, parent_frame, containers)
728
- set_default_font(parent_frame, font_name="Helvetica", size=8)
729
-
730
- vertical_container.grid(row=0, column=0, sticky=tk.NSEW)
731
- vertical_container.add(horizontal_container, stretch="always")
732
- horizontal_container.grid_columnconfigure(0, weight=1)
733
- horizontal_container.grid_columnconfigure(1, weight=1)
734
- settings_frame.grid_rowconfigure(0, weight=0)
735
- settings_frame.grid_rowconfigure(1, weight=1)
736
- settings_frame.grid_columnconfigure(0, weight=1)
737
- horizontal_container.add(settings_frame, stretch="always", sticky="nsew")
738
-
739
- return parent_frame, vertical_container, horizontal_container
740
-
741
642
  def initiate_root(parent, settings_type='mask'):
742
- global q, fig_queue, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar
743
- from .gui_utils import main_thread_update_function
643
+ global q, fig_queue, thread_control, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar
644
+ from .gui_utils import main_thread_update_function, setup_frame, set_element_size
744
645
  from .gui import gui_app
646
+ from .settings import descriptions
647
+
745
648
  set_start_method('spawn', force=True)
746
649
  print("Initializing root with settings_type:", settings_type)
747
650
 
748
651
  parent_frame = parent
749
- parent_frame.update_idletasks()
750
- frame_width = int(parent_frame.winfo_width())
751
- frame_height = int(parent_frame.winfo_height())
752
- print(frame_width, frame_height)
753
- dims = [frame_width, frame_height]
754
652
 
755
- if not hasattr(parent_frame, 'after_tasks'):
756
- parent_frame.after_tasks = []
653
+ if not isinstance(parent_frame, (tk.Tk, tk.Toplevel)):
654
+ parent_window = parent_frame.winfo_toplevel()
655
+ else:
656
+ parent_window = parent_frame
757
657
 
758
- # Clear previous content instead of destroying the root
759
- for widget in parent_frame.winfo_children():
760
- try:
761
- widget.destroy()
762
- except tk.TclError as e:
763
- print(f"Error destroying widget: {e}")
658
+ parent_window.update_idletasks()
659
+
660
+ if not hasattr(parent_window, 'after_tasks'):
661
+ parent_window.after_tasks = []
764
662
 
765
663
  q = Queue()
766
664
  fig_queue = Queue()
767
- parent_frame, vertical_container, horizontal_container = setup_frame(parent_frame)
665
+ parent_frame, vertical_container, horizontal_container, settings_container = setup_frame(parent_frame)
768
666
 
769
667
  if settings_type == 'annotate':
770
668
  from .app_annotate import initiate_annotation_app
@@ -773,17 +671,26 @@ def initiate_root(parent, settings_type='mask'):
773
671
  from .app_make_masks import initiate_make_mask_app
774
672
  initiate_make_mask_app(horizontal_container)
775
673
  else:
776
- scrollable_frame, vars_dict = setup_settings_panel(horizontal_container, settings_type, window_dimensions=dims)
777
- button_scrollable_frame = setup_button_section(horizontal_container, settings_type, window_dimensions=dims)
674
+ scrollable_frame, vars_dict = setup_settings_panel(settings_container, settings_type)
675
+ print('setup_settings_panel')
778
676
  canvas, canvas_widget = setup_plot_section(vertical_container)
779
- console_output = setup_console(vertical_container)
677
+ console_output, _ = setup_console(vertical_container)
678
+ button_scrollable_frame, btn_col = setup_button_section(horizontal_container, settings_type)
679
+ _, usage_bars, btn_col = setup_usage_panel(horizontal_container, btn_col)
780
680
 
781
- set_globals(q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar)
681
+ set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars)
682
+ description_text = descriptions.get(settings_type, "No description available for this module.")
683
+
684
+ q.put(f"Console")
685
+ q.put(f" ")
686
+ q.put(description_text)
687
+
782
688
  process_console_queue()
783
689
  process_fig_queue()
784
- after_id = parent_frame.after(100, lambda: main_thread_update_function(parent_frame, q, fig_queue, canvas_widget))
785
- parent_frame.after_tasks.append(after_id)
690
+ after_id = parent_window.after(100, lambda: main_thread_update_function(parent_window, q, fig_queue, canvas_widget))
691
+ parent_window.after_tasks.append(after_id)
786
692
 
787
693
  print("Root initialization complete")
788
694
  return parent_frame, vars_dict
789
695
 
696
+