spacr 0.2.4__py3-none-any.whl → 0.2.8__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 (63) hide show
  1. spacr/__init__.py +1 -11
  2. spacr/core.py +277 -349
  3. spacr/deep_spacr.py +248 -269
  4. spacr/gui.py +58 -54
  5. spacr/gui_core.py +689 -535
  6. spacr/gui_elements.py +1002 -153
  7. spacr/gui_utils.py +452 -107
  8. spacr/io.py +158 -91
  9. spacr/measure.py +199 -151
  10. spacr/plot.py +159 -47
  11. spacr/resources/font/open_sans/OFL.txt +93 -0
  12. spacr/resources/font/open_sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  13. spacr/resources/font/open_sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  14. spacr/resources/font/open_sans/README.txt +100 -0
  15. spacr/resources/font/open_sans/static/OpenSans-Bold.ttf +0 -0
  16. spacr/resources/font/open_sans/static/OpenSans-BoldItalic.ttf +0 -0
  17. spacr/resources/font/open_sans/static/OpenSans-ExtraBold.ttf +0 -0
  18. spacr/resources/font/open_sans/static/OpenSans-ExtraBoldItalic.ttf +0 -0
  19. spacr/resources/font/open_sans/static/OpenSans-Italic.ttf +0 -0
  20. spacr/resources/font/open_sans/static/OpenSans-Light.ttf +0 -0
  21. spacr/resources/font/open_sans/static/OpenSans-LightItalic.ttf +0 -0
  22. spacr/resources/font/open_sans/static/OpenSans-Medium.ttf +0 -0
  23. spacr/resources/font/open_sans/static/OpenSans-MediumItalic.ttf +0 -0
  24. spacr/resources/font/open_sans/static/OpenSans-Regular.ttf +0 -0
  25. spacr/resources/font/open_sans/static/OpenSans-SemiBold.ttf +0 -0
  26. spacr/resources/font/open_sans/static/OpenSans-SemiBoldItalic.ttf +0 -0
  27. spacr/resources/font/open_sans/static/OpenSans_Condensed-Bold.ttf +0 -0
  28. spacr/resources/font/open_sans/static/OpenSans_Condensed-BoldItalic.ttf +0 -0
  29. spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBold.ttf +0 -0
  30. spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
  31. spacr/resources/font/open_sans/static/OpenSans_Condensed-Italic.ttf +0 -0
  32. spacr/resources/font/open_sans/static/OpenSans_Condensed-Light.ttf +0 -0
  33. spacr/resources/font/open_sans/static/OpenSans_Condensed-LightItalic.ttf +0 -0
  34. spacr/resources/font/open_sans/static/OpenSans_Condensed-Medium.ttf +0 -0
  35. spacr/resources/font/open_sans/static/OpenSans_Condensed-MediumItalic.ttf +0 -0
  36. spacr/resources/font/open_sans/static/OpenSans_Condensed-Regular.ttf +0 -0
  37. spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBold.ttf +0 -0
  38. spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
  39. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Bold.ttf +0 -0
  40. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
  41. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
  42. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  43. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Italic.ttf +0 -0
  44. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Light.ttf +0 -0
  45. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
  46. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Medium.ttf +0 -0
  47. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
  48. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Regular.ttf +0 -0
  49. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
  50. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
  51. spacr/resources/icons/logo.pdf +2786 -6
  52. spacr/resources/icons/logo_spacr.png +0 -0
  53. spacr/resources/icons/logo_spacr_1.png +0 -0
  54. spacr/sequencing.py +477 -587
  55. spacr/settings.py +217 -144
  56. spacr/utils.py +46 -46
  57. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/METADATA +46 -35
  58. spacr-0.2.8.dist-info/RECORD +100 -0
  59. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/WHEEL +1 -1
  60. spacr-0.2.4.dist-info/RECORD +0 -58
  61. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/LICENSE +0 -0
  62. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/entry_points.txt +0 -0
  63. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/top_level.txt +0 -0
spacr/gui_core.py CHANGED
@@ -1,23 +1,25 @@
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, platform, time
4
2
  import tkinter as tk
5
3
  from tkinter import ttk
6
4
  from tkinter import filedialog
7
5
  from multiprocessing import Process, Value, Queue, set_start_method
8
- from multiprocessing.sharedctypes import Synchronized
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
+ from collections import deque
13
+ import tracemalloc
14
+ from tkinter import Menu
15
+ import io
16
+
14
17
  try:
15
18
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
16
19
  except AttributeError:
17
20
  pass
18
21
 
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
22
+ from .gui_elements import spacrProgressBar, spacrButton, spacrLabel, spacrFrame, spacrDropdownMenu , spacrSlider, set_dark_style, standardize_figure
21
23
 
22
24
  # Define global variables
23
25
  q = None
@@ -29,148 +31,375 @@ canvas_widget = None
29
31
  scrollable_frame = None
30
32
  progress_label = None
31
33
  fig_queue = None
34
+ figures = None
35
+ figure_index = None
36
+ progress_bar = None
37
+ usage_bars = None
32
38
 
33
39
  thread_control = {"run_thread": None, "stop_requested": False}
34
40
 
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
41
+ def toggle_settings(button_scrollable_frame):
42
+ global vars_dict
43
+ from .settings import categories
44
+ from .gui_utils import hide_all_settings
45
+ if vars_dict is None:
46
+ raise ValueError("vars_dict is not initialized.")
43
47
 
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)
48
+ active_categories = set()
54
49
 
55
- def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imports=1):
50
+ def toggle_category(settings):
51
+ for setting in settings:
52
+ if setting in vars_dict:
53
+ label, widget, _, frame = vars_dict[setting]
54
+ if widget.grid_info():
55
+ label.grid_remove()
56
+ widget.grid_remove()
57
+ frame.grid_remove()
58
+ else:
59
+ label.grid()
60
+ widget.grid()
61
+ frame.grid()
56
62
 
57
- """
58
- Wraps the run_multiple_simulations function to integrate with GUI processes.
63
+ def on_category_select(selected_category):
64
+ if selected_category == "Select Category":
65
+ return
66
+ if selected_category in categories:
67
+ toggle_category(categories[selected_category])
68
+ if selected_category in active_categories:
69
+ active_categories.remove(selected_category)
70
+ else:
71
+ active_categories.add(selected_category)
72
+ category_dropdown.update_styles(active_categories)
73
+ category_var.set("Select Category")
74
+
75
+ category_var = tk.StringVar()
76
+ non_empty_categories = [category for category, settings in categories.items() if any(setting in vars_dict for setting in settings)]
77
+ category_dropdown = spacrDropdownMenu(button_scrollable_frame.scrollable_frame, category_var, non_empty_categories, command=on_category_select)
78
+ category_dropdown.grid(row=0, column=4, sticky="ew", pady=2, padx=2)
79
+ vars_dict = hide_all_settings(vars_dict, categories)
80
+
81
+ def display_figure(fig):
82
+ global canvas, canvas_widget
83
+
84
+ from .gui_elements import save_figure_as_format, modify_figure
85
+
86
+ # Apply the dark style to the context menu
87
+ style_out = set_dark_style(ttk.Style())
88
+ bg_color = style_out['bg_color']
89
+ fg_color = style_out['fg_color']
90
+
91
+ # Initialize the scale factor for zooming
92
+ scale_factor = 1.0
93
+
94
+ # Save the original x and y limits of the first axis (assuming all axes have the same limits)
95
+ original_xlim = [ax.get_xlim() for ax in fig.get_axes()]
96
+ original_ylim = [ax.get_ylim() for ax in fig.get_axes()]
97
+
98
+ # Clear previous canvas content
99
+ if canvas:
100
+ canvas.get_tk_widget().destroy()
101
+
102
+ # Create a new canvas for the figure
103
+ new_canvas = FigureCanvasTkAgg(fig, master=canvas_widget.master)
104
+ new_canvas.draw()
105
+ new_canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew")
106
+
107
+ # Update the global canvas and canvas_widget references
108
+ canvas = new_canvas
109
+ canvas_widget = new_canvas.get_tk_widget()
110
+ canvas_widget.configure(bg=bg_color)
111
+
112
+ # Create the context menu
113
+ context_menu = tk.Menu(canvas_widget, tearoff=0, bg=bg_color, fg=fg_color)
114
+ context_menu.add_command(label="Save Figure as PDF", command=lambda: save_figure_as_format(fig, 'pdf'))
115
+ context_menu.add_command(label="Save Figure as PNG", command=lambda: save_figure_as_format(fig, 'png'))
116
+ context_menu.add_command(label="Modify Figure", command=lambda: modify_figure(fig))
117
+ context_menu.add_command(label="Reset Zoom", command=lambda: reset_zoom(fig)) # Add Reset Zoom option
118
+
119
+ def reset_zoom(fig):
120
+ global scale_factor
121
+ scale_factor = 1.0 # Reset the scale factor
122
+
123
+ for i, ax in enumerate(fig.get_axes()):
124
+ ax.set_xlim(original_xlim[i])
125
+ ax.set_ylim(original_ylim[i])
126
+ fig.canvas.draw_idle()
127
+
128
+ def on_right_click(event):
129
+ context_menu.post(event.x_root, event.y_root)
130
+
131
+ def on_hover(event):
132
+ widget_width = event.widget.winfo_width()
133
+ x_position = event.x
134
+
135
+ if x_position < widget_width / 2:
136
+ canvas_widget.config(cursor="hand2")
137
+ else:
138
+ canvas_widget.config(cursor="hand2")
139
+
140
+ def on_leave(event):
141
+ canvas_widget.config(cursor="arrow")
142
+
143
+ def flash_feedback(side):
144
+ flash = tk.Toplevel(canvas_widget.master)
145
+ flash.overrideredirect(True)
146
+ flash_width = int(canvas_widget.winfo_width() / 2)
147
+ flash_height = canvas_widget.winfo_height()
148
+ flash.configure(bg='white')
149
+ flash.attributes('-alpha', 0.9)
150
+
151
+ if side == "left":
152
+ flash.geometry(f"{flash_width}x{flash_height}+{canvas_widget.winfo_rootx()}+{canvas_widget.winfo_rooty()}")
153
+ else:
154
+ flash.geometry(f"{flash_width}x{flash_height}+{canvas_widget.winfo_rootx() + flash_width}+{canvas_widget.winfo_rooty()}")
155
+
156
+ flash.lift()
157
+
158
+ # Ensure the flash covers the correct area only
159
+ flash.update_idletasks()
160
+ flash.after(100, flash.destroy)
161
+
162
+ def on_click(event):
163
+ widget_width = event.widget.winfo_width()
164
+ x_position = event.x
165
+
166
+ if x_position < widget_width / 2:
167
+ #flash_feedback("left")
168
+ show_previous_figure()
169
+ else:
170
+ #flash_feedback("right")
171
+ show_next_figure()
172
+
173
+ def zoom_v1(event):
174
+ nonlocal scale_factor
175
+
176
+ zoom_speed = 0.1 # Adjust the zoom speed for smoother experience
177
+
178
+ # Adjust zoom factor based on the operating system and mouse event
179
+ if event.num == 4 or event.delta > 0: # Scroll up
180
+ scale_factor *= (1 + zoom_speed)
181
+ elif event.num == 5 or event.delta < 0: # Scroll down
182
+ scale_factor /= (1 + zoom_speed)
183
+
184
+ # Get mouse position relative to the figure
185
+ x_mouse, y_mouse = event.x, event.y
186
+ x_ratio = x_mouse / canvas_widget.winfo_width()
187
+ y_ratio = y_mouse / canvas_widget.winfo_height()
188
+
189
+ for ax in fig.get_axes():
190
+ xlim = ax.get_xlim()
191
+ ylim = ax.get_ylim()
192
+
193
+ # Calculate the new limits
194
+ x_center = xlim[0] + x_ratio * (xlim[1] - xlim[0])
195
+ y_center = ylim[0] + (1 - y_ratio) * (ylim[1] - ylim[0])
196
+
197
+ x_range = (xlim[1] - xlim[0]) * scale_factor
198
+ y_range = (ylim[1] - ylim[0]) * scale_factor
199
+
200
+ ax.set_xlim([x_center - x_range * x_ratio, x_center + x_range * (1 - x_ratio)])
201
+ ax.set_ylim([y_center - y_range * (1 - y_ratio), y_center + y_range * y_ratio])
202
+
203
+ # Redraw the figure
204
+ fig.canvas.draw_idle()
205
+
206
+ def zoom(event):
207
+ nonlocal scale_factor
208
+
209
+ zoom_speed = 0.1 # Adjust the zoom speed for smoother experience
210
+
211
+ # Determine the zoom direction based on the scroll event
212
+ if event.num == 4 or event.delta > 0: # Scroll up (zoom in)
213
+ scale_factor /= (1 + zoom_speed) # Divide to zoom in
214
+ elif event.num == 5 or event.delta < 0: # Scroll down (zoom out)
215
+ scale_factor *= (1 + zoom_speed) # Multiply to zoom out
216
+
217
+ # Adjust the axes limits based on the new scale factor
218
+ for ax in canvas.figure.get_axes():
219
+ xlim = ax.get_xlim()
220
+ ylim = ax.get_ylim()
221
+
222
+ x_center = (xlim[1] + xlim[0]) / 2
223
+ y_center = (ylim[1] + ylim[0]) / 2
224
+
225
+ x_range = (xlim[1] - xlim[0]) * scale_factor
226
+ y_range = (ylim[1] - ylim[0]) * scale_factor
227
+
228
+ # Set the new limits
229
+ ax.set_xlim([x_center - x_range / 2, x_center + x_range / 2])
230
+ ax.set_ylim([y_center - y_range / 2, y_center + y_range / 2])
231
+
232
+ # Redraw the figure efficiently
233
+ canvas.draw_idle()
234
+
235
+
236
+ # Bind events for hover, click interactions, and zoom
237
+ canvas_widget.bind("<Motion>", on_hover)
238
+ canvas_widget.bind("<Leave>", on_leave)
239
+ canvas_widget.bind("<Button-1>", on_click)
240
+ canvas_widget.bind("<Button-3>", on_right_click)
241
+
242
+
243
+ # Detect the operating system and bind the appropriate mouse wheel events
244
+ current_os = platform.system()
245
+
246
+ if current_os == "Windows":
247
+ canvas_widget.bind("<MouseWheel>", zoom) # Windows
248
+ elif current_os == "Darwin": # macOS
249
+ canvas_widget.bind("<MouseWheel>", zoom)
250
+ canvas_widget.bind("<Button-4>", zoom) # Scroll up
251
+ canvas_widget.bind("<Button-5>", zoom) # Scroll down
252
+ elif current_os == "Linux":
253
+ canvas_widget.bind("<Button-4>", zoom) # Linux Scroll up
254
+ canvas_widget.bind("<Button-5>", zoom) # Linux Scroll down
255
+
256
+ def clear_unused_figures():
257
+ global figures, figure_index
258
+
259
+ lower_bound = max(0, figure_index - 20)
260
+ upper_bound = min(len(figures), figure_index + 20)
261
+ # Clear figures outside of the +/- 20 range
262
+ figures = deque([fig for i, fig in enumerate(figures) if lower_bound <= i <= upper_bound])
263
+ # Update the figure index after clearing
264
+ figure_index = min(max(figure_index, 0), len(figures) - 1)
265
+
266
+ def show_previous_figure():
267
+ global figure_index, figures, fig_queue
59
268
 
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
- """
269
+ if figure_index is not None and figure_index > 0:
270
+ figure_index -= 1
271
+ display_figure(figures[figure_index])
272
+ clear_unused_figures()
273
+
274
+ def show_next_figure():
275
+ global figure_index, figures, fig_queue
276
+ if figure_index is not None and figure_index < len(figures) - 1:
277
+ figure_index += 1
278
+ display_figure(figures[figure_index])
279
+ clear_unused_figures()
280
+ elif figure_index == len(figures) - 1 and not fig_queue.empty():
281
+ fig = fig_queue.get_nowait()
282
+ figures.append(fig)
283
+ figure_index += 1
284
+ display_figure(fig)
65
285
 
66
- # Temporarily override plt.show
67
- original_show = plt.show
68
- plt.show = lambda: spacrFigShow(fig_queue)
286
+ def process_fig_queue():
287
+ global canvas, fig_queue, canvas_widget, parent_frame, uppdate_frequency, figures, figure_index, index_control
69
288
 
289
+ from .gui_elements import standardize_figure
70
290
  try:
71
- if imports == 1:
72
- function(settings=settings)
73
- elif imports == 2:
74
- function(src=settings['src'], settings=settings)
291
+ while not fig_queue.empty():
292
+ fig = fig_queue.get_nowait()
293
+
294
+ if fig is None:
295
+ print("Warning: Retrieved a None figure from fig_queue.")
296
+ continue # Skip processing if the figure is None
297
+
298
+ # Standardize the figure appearance before adding it to the list
299
+ fig = standardize_figure(fig)
300
+
301
+ figures.append(fig)
302
+
303
+ # Update the slider range and set the value to the latest figure index
304
+ index_control.set_to(len(figures) - 1)
305
+
306
+ if figure_index == -1:
307
+ figure_index += 1
308
+ display_figure(figures[figure_index])
309
+ index_control.set(figure_index)
310
+
75
311
  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
312
  traceback.print_exc()
80
313
  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}')
314
+ after_id = canvas_widget.after(uppdate_frequency, process_fig_queue)
315
+ parent_frame.after_tasks.append(after_id)
316
+
317
+ def update_figure(value):
318
+ global figure_index, figures
319
+
320
+ # Convert the value to an integer
321
+ index = int(value)
95
322
 
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
323
+ # Check if the index is valid
324
+ if 0 <= index < len(figures):
325
+ figure_index = index
326
+ display_figure(figures[figure_index])
141
327
 
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
328
+ # Update the index control widget's range and value
329
+ index_control.set_to(len(figures) - 1)
330
+ index_control.set(figure_index)
145
331
 
146
- if q is None:
147
- q = Queue()
148
- if fig_queue is None:
149
- fig_queue = Queue()
332
+ def setup_plot_section(vertical_container):
333
+ global canvas, canvas_widget, figures, figure_index, index_control
150
334
 
151
- try:
152
- settings = check_settings(vars_dict, expected_types, q)
153
- except ValueError as e:
154
- q.put(f"Error: {e}")
155
- return
335
+ # Initialize deque for storing figures and the current index
336
+ figures = deque()
156
337
 
157
- if thread_control.get("run_thread") is not None:
158
- initiate_abort()
338
+ # Create a frame for the plot section
339
+ plot_frame = tk.Frame(vertical_container)
340
+ vertical_container.add(plot_frame, stretch="always")
341
+
342
+ # Set up the plot
343
+ figure = Figure(figsize=(30, 4), dpi=100)
344
+ plot = figure.add_subplot(111)
345
+ plot.plot([], [])
346
+ plot.axis('off')
159
347
 
160
- stop_requested = Value('i', 0)
161
- thread_control["stop_requested"] = stop_requested
348
+ canvas = FigureCanvasTkAgg(figure, master=plot_frame)
349
+ canvas.get_tk_widget().configure(cursor='arrow', highlightthickness=0)
350
+ canvas_widget = canvas.get_tk_widget()
351
+ canvas_widget.grid(row=0, column=0, sticky="nsew")
162
352
 
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()
353
+ plot_frame.grid_rowconfigure(0, weight=1)
354
+ plot_frame.grid_columnconfigure(0, weight=1)
355
+
356
+ canvas.draw()
357
+ canvas.figure = figure # Ensure that the figure is linked to the canvas
358
+ style_out = set_dark_style(ttk.Style())
359
+ bg = style_out['bg_color']
360
+ fg = style_out['fg_color']
361
+
362
+ figure.patch.set_facecolor(bg)
363
+ plot.set_facecolor(bg)
364
+ containers = [plot_frame]
365
+
366
+ # Create slider
367
+ control_frame = tk.Frame(plot_frame, height=15*2, bg=bg) # Fixed height based on knob_radius
368
+ control_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=5)
369
+ control_frame.grid_propagate(False) # Prevent the frame from resizing
370
+
371
+ # Pass the update_figure function as the command to spacrSlider
372
+ index_control = spacrSlider(control_frame, from_=0, to=0, value=0, thickness=2, knob_radius=10, position="center", show_index=True, command=update_figure)
373
+ index_control.grid(row=0, column=0, sticky="ew")
374
+ control_frame.grid_columnconfigure(0, weight=1)
375
+
376
+ widgets = [canvas_widget, index_control]
377
+ style = ttk.Style(vertical_container)
378
+ _ = set_dark_style(style, containers=containers, widgets=widgets)
379
+
380
+ return canvas, canvas_widget
381
+
382
+ 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, figures_var, figure_index_var, index_control_var, progress_bar_var, usage_bars_var):
383
+ global thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, figures, figure_index, progress_bar, usage_bars, index_control
384
+ thread_control = thread_control_var
385
+ q = q_var
386
+ console_output = console_output_var
387
+ parent_frame = parent_frame_var
388
+ vars_dict = vars_dict_var
389
+ canvas = canvas_var
390
+ canvas_widget = canvas_widget_var
391
+ scrollable_frame = scrollable_frame_var
392
+ fig_queue = fig_queue_var
393
+ figures = figures_var
394
+ figure_index = figure_index_var
395
+ progress_bar = progress_bar_var
396
+ usage_bars = usage_bars_var
397
+ index_control = index_control_var
170
398
 
171
399
  def import_settings(settings_type='mask'):
400
+ from .gui_utils import convert_settings_dict_for_gui, hide_all_settings
172
401
  global vars_dict, scrollable_frame, button_scrollable_frame
173
- from .settings import generate_fields
402
+ from .settings import generate_fields, set_default_settings_preprocess_generate_masks, get_measure_crop_settings, set_default_train_test_model, set_default_generate_barecode_mapping, set_default_umap_image_settings, get_analyze_recruitment_default_settings
174
403
 
175
404
  def read_settings_from_csv(csv_file_path):
176
405
  settings = {}
@@ -206,9 +435,11 @@ def import_settings(settings_type='mask'):
206
435
  elif settings_type == 'classify':
207
436
  settings = set_default_train_test_model(settings={})
208
437
  elif settings_type == 'sequencing':
209
- settings = get_analyze_reads_default_settings(settings={})
438
+ settings = set_default_generate_barecode_mapping(settings={})
210
439
  elif settings_type == 'umap':
211
440
  settings = set_default_umap_image_settings(settings={})
441
+ elif settings_type == 'recruitment':
442
+ settings = get_analyze_recruitment_default_settings(settings={})
212
443
  else:
213
444
  raise ValueError(f"Invalid settings type: {settings_type}")
214
445
 
@@ -217,74 +448,24 @@ def import_settings(settings_type='mask'):
217
448
  vars_dict = generate_fields(new_settings, scrollable_frame)
218
449
  vars_dict = hide_all_settings(vars_dict, categories=None)
219
450
 
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]):
451
+ def setup_settings_panel(vertical_container, settings_type='mask'):
278
452
  global vars_dict, scrollable_frame
279
- 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
453
+ from .settings import get_identify_masks_finetune_default_settings, set_default_analyze_screen, set_default_settings_preprocess_generate_masks, get_measure_crop_settings, deep_spacr_defaults, set_default_generate_barecode_mapping, 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
454
+ from .gui_utils import convert_settings_dict_for_gui
455
+ from .gui_elements import set_element_size
456
+
457
+ size_dict = set_element_size()
458
+ settings_width = size_dict['settings_width']
459
+
460
+ # Create a PanedWindow for the settings panel
461
+ settings_paned_window = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL, width=size_dict['settings_width'])
462
+ vertical_container.add(settings_paned_window, stretch="always")
280
463
 
281
- width = (window_dimensions[0]) // 6
282
- height = window_dimensions[1]
464
+ settings_frame = tk.Frame(settings_paned_window, width=settings_width)
465
+ settings_frame.pack_propagate(False) # Prevent the frame from resizing based on its children
466
+
467
+ settings_paned_window.add(settings_frame)
283
468
 
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
469
  scrollable_frame = spacrFrame(settings_frame)
289
470
  scrollable_frame.grid(row=1, column=0, sticky="nsew")
290
471
  settings_frame.grid_rowconfigure(1, weight=1)
@@ -295,9 +476,7 @@ def setup_settings_panel(vertical_container, settings_type='mask', window_dimens
295
476
  elif settings_type == 'measure':
296
477
  settings = get_measure_crop_settings(settings={})
297
478
  elif settings_type == 'classify':
298
- settings = set_default_train_test_model(settings={})
299
- elif settings_type == 'sequencing':
300
- settings = get_analyze_reads_default_settings(settings={})
479
+ settings = deep_spacr_defaults(settings={})
301
480
  elif settings_type == 'umap':
302
481
  settings = set_default_umap_image_settings(settings={})
303
482
  elif settings_type == 'train_cellpose':
@@ -309,7 +488,7 @@ def setup_settings_panel(vertical_container, settings_type='mask', window_dimens
309
488
  elif settings_type == 'cellpose_all':
310
489
  settings = get_check_cellpose_models_default_settings(settings={})
311
490
  elif settings_type == 'map_barcodes':
312
- settings = get_map_barcodes_default_settings(settings={})
491
+ settings = set_default_generate_barecode_mapping(settings={})
313
492
  elif settings_type == 'regression':
314
493
  settings = get_perform_regression_default_settings(settings={})
315
494
  elif settings_type == 'recruitment':
@@ -321,7 +500,7 @@ def setup_settings_panel(vertical_container, settings_type='mask', window_dimens
321
500
  vars_dict = generate_fields(variables, scrollable_frame)
322
501
 
323
502
  containers = [settings_frame]
324
- widgets = [settings_label, scrollable_frame]
503
+ widgets = [scrollable_frame]
325
504
 
326
505
  style = ttk.Style(vertical_container)
327
506
  _ = set_dark_style(style, containers=containers, widgets=widgets)
@@ -329,448 +508,414 @@ def setup_settings_panel(vertical_container, settings_type='mask', window_dimens
329
508
  print("Settings panel setup complete")
330
509
  return scrollable_frame, vars_dict
331
510
 
332
- def setup_plot_section(vertical_container):
333
- global canvas, canvas_widget
334
- plot_frame = tk.PanedWindow(vertical_container, orient=tk.VERTICAL)
335
- vertical_container.add(plot_frame, stretch="always")
336
- figure = Figure(figsize=(30, 4), dpi=100)
337
- plot = figure.add_subplot(111)
338
- plot.plot([], []) # This creates an empty plot.
339
- plot.axis('off')
340
- canvas = FigureCanvasTkAgg(figure, master=plot_frame)
341
- canvas.get_tk_widget().configure(cursor='arrow', highlightthickness=0)
342
- canvas_widget = canvas.get_tk_widget()
343
- plot_frame.add(canvas_widget, stretch="always")
344
- canvas.draw()
345
- canvas.figure = figure
346
- style_out = set_dark_style(ttk.Style())
347
-
348
- figure.patch.set_facecolor(style_out['bg_color'])
349
- plot.set_facecolor(style_out['bg_color'])
350
- containers = [plot_frame]
351
- widgets = [canvas_widget]
352
- style = ttk.Style(vertical_container)
353
- _ = set_dark_style(style, containers=containers, widgets=widgets)
354
- return canvas, canvas_widget
355
-
356
511
  def setup_console(vertical_container):
357
512
  global console_output
358
- console_frame = tk.Frame(vertical_container)
359
- 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")
364
- console_frame.grid_rowconfigure(1, weight=1)
365
- 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
371
-
372
- def setup_progress_frame(vertical_container):
373
- global progress_output
374
- progress_frame = tk.Frame(vertical_container)
375
- vertical_container.add(progress_frame, stretch="always")
376
- label_frame = tk.Frame(progress_frame)
377
- label_frame.grid(row=0, column=0, sticky="ew", pady=(5, 0), padx=10)
378
- progress_label = spacrLabel(label_frame, text="Processing: 0%", font=('Helvetica', 12), anchor='w', justify='left', align="left")
379
- progress_label.grid(row=0, column=0, sticky="w")
380
- progress_output = scrolledtext.ScrolledText(progress_frame, height=10)
381
- progress_output.grid(row=1, column=0, sticky="nsew")
382
- progress_frame.grid_rowconfigure(1, weight=1)
383
- progress_frame.grid_columnconfigure(0, weight=1)
384
- containers = [progress_frame, label_frame]
385
- widgets = [progress_label, progress_output]
386
- style = ttk.Style(vertical_container)
387
- _ = set_dark_style(style, containers=containers, widgets=widgets)
388
- return progress_output
513
+ from .gui_elements import set_dark_style
389
514
 
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")
515
+ # Apply dark style and get style output
516
+ style = ttk.Style()
517
+ style_out = set_dark_style(style)
406
518
 
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")
519
+ # Create a frame for the console section
520
+ console_frame = tk.Frame(vertical_container, bg=style_out['bg_color'])
521
+ vertical_container.add(console_frame, stretch="always")
413
522
 
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.
523
+ # Create a thicker frame at the top for the hover effect
524
+ top_border = tk.Frame(console_frame, height=5, bg=style_out['bg_color'])
525
+ top_border.grid(row=0, column=0, sticky="ew", pady=(0, 2))
418
526
 
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.
527
+ # Create the scrollable frame (which is a Text widget) with white text
528
+ family = style_out['font_family']
529
+ font_size = style_out['font_size']
530
+ font_loader = style_out['font_loader']
531
+ console_output = tk.Text(console_frame, bg=style_out['bg_color'], fg=style_out['fg_color'], font=font_loader.get_font(size=font_size), bd=0, highlightthickness=0)
532
+
533
+ console_output.grid(row=1, column=0, sticky="nsew") # Use grid for console_output
425
534
 
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.")
535
+ # Configure the grid to allow expansion
536
+ console_frame.grid_rowconfigure(1, weight=1)
537
+ console_frame.grid_columnconfigure(0, weight=1)
463
538
 
464
- return local_subfolder_dir
539
+ def on_enter(event):
540
+ top_border.config(bg=style_out['active_color'])
465
541
 
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)
542
+ def on_leave(event):
543
+ top_border.config(bg=style_out['bg_color'])
470
544
 
471
- raise Exception("Failed to download files after multiple attempts.")
545
+ console_output.bind("<Enter>", on_enter)
546
+ console_output.bind("<Leave>", on_leave)
472
547
 
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
548
+ return console_output, console_frame
476
549
 
477
- width = (window_dimensions[0]) // 8
478
- height = window_dimensions[1]
550
+ def setup_button_section(horizontal_container, settings_type='mask', run=True, abort=True, download=True, import_btn=True):
551
+ 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
552
+ from .gui_utils import download_hug_dataset
553
+ from .gui_elements import set_element_size
479
554
 
480
- button_frame = tk.Frame(horizontal_container)
555
+ size_dict = set_element_size()
556
+ button_section_height = size_dict['panel_height']
557
+ button_frame = tk.Frame(horizontal_container, height=button_section_height)
558
+
481
559
  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)
560
+ button_scrollable_frame = spacrFrame(button_frame, scrollbar=False)
489
561
  button_scrollable_frame.grid(row=1, column=0, sticky="nsew")
490
-
491
- widgets = [categories_label, button_scrollable_frame.scrollable_frame]
562
+ widgets = [button_scrollable_frame.scrollable_frame]
492
563
 
493
564
  btn_col = 0
494
- btn_row = 3
565
+ btn_row = 0
495
566
 
496
567
  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))
568
+ 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
569
  run_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
500
570
  widgets.append(run_button)
501
- btn_row += 1
571
+ btn_col += 1
502
572
 
503
- 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)
573
+ if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap', 'map_barcodes']:
574
+ 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
575
  abort_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
506
576
  widgets.append(abort_button)
507
- btn_row += 1
577
+ btn_col += 1
508
578
 
509
579
  if download and settings_type in ['mask']:
510
- download_dataset_button = spacrButton(button_scrollable_frame.scrollable_frame, text="download", command=download_hug_dataset)
580
+ 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
581
  download_dataset_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
512
582
  widgets.append(download_dataset_button)
513
- btn_row += 1
583
+ btn_col += 1
514
584
 
515
585
  if import_btn:
516
- import_button = spacrButton(button_scrollable_frame.scrollable_frame, text="settings", command=lambda: import_settings(settings_type))
586
+ 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
587
  import_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
518
588
  widgets.append(import_button)
519
589
  btn_row += 1
520
590
 
521
- # Add the progress bar under the settings category menu
591
+ # Add the batch progress bar
522
592
  progress_bar = spacrProgressBar(button_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate')
523
- progress_bar.grid(row=0, column=0, columnspan=2, pady=5, padx=5, sticky='ew')
593
+ progress_bar.grid(row=btn_row, column=0, columnspan=7, pady=5, padx=5, sticky='ew')
594
+ progress_bar.set_label_position() # Set the label position after grid placement
524
595
  widgets.append(progress_bar)
525
596
 
526
597
  if vars_dict is not None:
527
598
  toggle_settings(button_scrollable_frame)
528
599
 
529
- description_frame = tk.Frame(horizontal_container)
530
- horizontal_container.add(description_frame, stretch="always", sticky="nsew")
531
- description_frame.grid_columnconfigure(0, weight=1)
532
- description_frame.grid_rowconfigure(0, weight=1) # Add this line to make the row expandable
533
-
534
- description_label = tk.Label(description_frame, text="Module Description", anchor='nw', justify='left', wraplength=width - 50)
535
- description_label.grid(row=0, column=0, pady=50, padx=20, sticky='nsew')
536
- description_text = descriptions.get(settings_type, "No description available for this module.")
537
- description_label.config(text=description_text)
538
-
539
- def update_wraplength(event):
540
- new_width = event.width - 40 # Adjust as needed
541
- description_label.config(wraplength=new_width)
542
-
543
- description_label.bind('<Configure>', update_wraplength)
544
-
545
- containers = [button_frame, description_frame]
546
- widgets.extend([description_label])
547
-
548
600
  style = ttk.Style(horizontal_container)
549
- _ = set_dark_style(style, containers=containers, widgets=widgets)
601
+ _ = set_dark_style(style, containers=[button_frame], widgets=widgets)
602
+
603
+ return button_scrollable_frame, btn_col
604
+
605
+ def setup_usage_panel(horizontal_container, btn_col, uppdate_frequency):
606
+ global usage_bars
607
+ from .gui_elements import set_dark_style, set_element_size
608
+
609
+ usg_col = 1
610
+
611
+ def update_usage(ram_bar, vram_bar, gpu_bar, usage_bars, parent_frame):
612
+ # Update RAM usage
613
+ ram_usage = psutil.virtual_memory().percent
614
+ ram_bar['value'] = ram_usage
615
+
616
+ # Update GPU and VRAM usage
617
+ gpus = GPUtil.getGPUs()
618
+ if gpus:
619
+ gpu = gpus[0]
620
+ vram_usage = gpu.memoryUtil * 100
621
+ gpu_usage = gpu.load * 100
622
+ vram_bar['value'] = vram_usage
623
+ gpu_bar['value'] = gpu_usage
624
+
625
+ # Update CPU usage for each core
626
+ cpu_percentages = psutil.cpu_percent(percpu=True)
627
+ for bar, usage in zip(usage_bars[3:], cpu_percentages):
628
+ bar['value'] = usage
629
+
630
+ # Schedule the function to run again after 1000 ms (1 second)
631
+ parent_frame.after(uppdate_frequency, update_usage, ram_bar, vram_bar, gpu_bar, usage_bars, parent_frame)
632
+
633
+ size_dict = set_element_size()
634
+ usage_panel_height = size_dict['panel_height']
635
+ usage_frame = tk.Frame(horizontal_container, height=usage_panel_height)
636
+ horizontal_container.add(usage_frame)
637
+
638
+ usage_frame.grid_rowconfigure(0, weight=0)
639
+ usage_frame.grid_rowconfigure(1, weight=1)
640
+ usage_frame.grid_columnconfigure(0, weight=1)
641
+ usage_frame.grid_columnconfigure(1, weight=1)
642
+
643
+ usage_scrollable_frame = spacrFrame(usage_frame, scrollbar=False)
644
+ usage_scrollable_frame.grid(row=1, column=0, sticky="nsew", columnspan=2)
645
+ widgets = [usage_scrollable_frame.scrollable_frame]
646
+
647
+ usage_bars = []
648
+ max_elements_per_column = 6
649
+ row = 0
650
+ col = 0
550
651
 
551
- return button_scrollable_frame
652
+ # Initialize RAM, VRAM, and GPU bars as None
653
+ ram_bar, vram_bar, gpu_bar = None, None, None
654
+
655
+ # Configure the style for the label
656
+ style = ttk.Style()
657
+ style_out = set_dark_style(style)
658
+ font_loader = style_out['font_loader']
659
+ font_size = style_out['font_size'] - 2
660
+ style.configure("usage.TLabel", font=font_loader.get_font(size=font_size), foreground=style_out['fg_color'])
661
+
662
+ # Try adding RAM bar
663
+ try:
664
+ ram_info = psutil.virtual_memory()
665
+ ram_label_text = f"RAM"
666
+ label = tk.Label(usage_scrollable_frame.scrollable_frame,text=ram_label_text,anchor='w',font=font_loader.get_font(size=font_size),bg=style_out['bg_color'],fg=style_out['fg_color'])
667
+ label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
668
+ ram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
669
+ ram_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
670
+ widgets.append(label)
671
+ widgets.append(ram_bar)
672
+ usage_bars.append(ram_bar)
673
+ row += 1
674
+ except Exception as e:
675
+ print(f"Could not add RAM usage bar: {e}")
552
676
 
677
+ # Try adding VRAM and GPU usage bars
678
+ try:
679
+ gpus = GPUtil.getGPUs()
680
+ if gpus:
681
+ gpu = gpus[0]
682
+ vram_label_text = f"VRAM"
683
+ label = tk.Label(usage_scrollable_frame.scrollable_frame,text=vram_label_text,anchor='w',font=font_loader.get_font(size=font_size),bg=style_out['bg_color'],fg=style_out['fg_color'])
684
+ label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
685
+ vram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
686
+ vram_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
687
+ widgets.append(label)
688
+ widgets.append(vram_bar)
689
+ usage_bars.append(vram_bar)
690
+ row += 1
691
+
692
+ gpu_label_text = f"GPU"
693
+ label = tk.Label(usage_scrollable_frame.scrollable_frame,text=gpu_label_text,anchor='w',font=font_loader.get_font(size=font_size),bg=style_out['bg_color'],fg=style_out['fg_color'])
694
+ label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
695
+ gpu_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
696
+ gpu_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
697
+ widgets.append(label)
698
+ widgets.append(gpu_bar)
699
+ usage_bars.append(gpu_bar)
700
+ row += 1
701
+ except Exception as e:
702
+ print(f"Could not add VRAM or GPU usage bars: {e}")
553
703
 
554
- def hide_all_settings(vars_dict, categories):
555
- """
556
- Function to initially hide all settings in the GUI.
704
+ # Add CPU core usage bars
705
+ try:
706
+ cpu_cores = psutil.cpu_count(logical=True)
707
+ cpu_freq = psutil.cpu_freq()
708
+
709
+ for core in range(cpu_cores):
710
+ if row > 0 and row % max_elements_per_column == 0:
711
+ col += 1
712
+ row = 0
713
+
714
+ label = tk.Label(usage_scrollable_frame.scrollable_frame,text=f"C{core+1}",anchor='w',font=font_loader.get_font(size=font_size),bg=style_out['bg_color'],fg=style_out['fg_color'])
715
+ label.grid(row=row, column=2 * col, pady=2, padx=5, sticky='w')
716
+ bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
717
+ bar.grid(row=row, column=2 * col + 1, pady=2, padx=5, sticky='ew')
718
+ widgets.append(label)
719
+ widgets.append(bar)
720
+ usage_bars.append(bar)
721
+ row += 1
722
+ except Exception as e:
723
+ print(f"Could not add CPU core usage bars: {e}")
557
724
 
558
- Parameters:
559
- - categories: dict, The categories of settings with their corresponding settings.
560
- - vars_dict: dict, The dictionary containing the settings and their corresponding widgets.
561
- """
725
+ style = ttk.Style(horizontal_container)
726
+ _ = set_dark_style(style, containers=[usage_frame], widgets=widgets)
727
+
728
+ if ram_bar is None:
729
+ ram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
730
+ if vram_bar is None:
731
+ vram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
732
+ if gpu_bar is None:
733
+ gpu_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
734
+
735
+ update_usage(ram_bar, vram_bar, gpu_bar, usage_bars, usage_frame)
736
+ return usage_scrollable_frame, usage_bars, usg_col
562
737
 
563
- if categories is None:
564
- from .settings import categories
738
+ def initiate_abort():
739
+ global thread_control, q, parent_frame
740
+ if thread_control.get("run_thread") is not None:
741
+ try:
742
+ q.put("Aborting processes...")
743
+ thread_control.get("run_thread").terminate()
744
+ thread_control["run_thread"] = None
745
+ q.put("Processes aborted.")
746
+ except Exception as e:
747
+ q.put(f"Error aborting process: {e}")
565
748
 
566
- for category, settings in categories.items():
567
- if any(setting in vars_dict for setting in settings):
568
- vars_dict[category] = (None, None, tk.IntVar(value=0))
569
-
570
- # Initially hide all settings
571
- for setting in settings:
572
- if setting in vars_dict:
573
- label, widget, _ = vars_dict[setting]
574
- label.grid_remove()
575
- widget.grid_remove()
576
- return vars_dict
749
+ thread_control = {"run_thread": None, "stop_requested": False}
577
750
 
578
- def toggle_settings(button_scrollable_frame):
579
- global vars_dict
580
- from .settings import categories
751
+ def start_process(q=None, fig_queue=None, settings_type='mask'):
752
+ global thread_control, vars_dict, parent_frame
753
+ from .settings import check_settings, expected_types
754
+ from .gui_utils import run_function_gui, set_high_priority, set_cpu_affinity, initialize_cuda
581
755
 
582
- if vars_dict is None:
583
- raise ValueError("vars_dict is not initialized.")
756
+ if q is None:
757
+ q = Queue()
758
+ if fig_queue is None:
759
+ fig_queue = Queue()
760
+ try:
761
+ settings = check_settings(vars_dict, expected_types, q)
762
+ except ValueError as e:
763
+ q.put(f"Error: {e}")
764
+ return
584
765
 
585
- active_categories = set()
766
+ if thread_control.get("run_thread") is not None:
767
+ initiate_abort()
768
+
769
+ stop_requested = Value('i', 0)
770
+ thread_control["stop_requested"] = stop_requested
586
771
 
587
- def toggle_category(settings):
588
- for setting in settings:
589
- if setting in vars_dict:
590
- label, widget, _ = vars_dict[setting]
591
- if widget.grid_info():
592
- label.grid_remove()
593
- widget.grid_remove()
594
- else:
595
- label.grid()
596
- widget.grid()
772
+ # Initialize CUDA in the main process
773
+ initialize_cuda()
597
774
 
598
- def on_category_select(selected_category):
599
- if selected_category == "Select Category":
600
- return
601
- if selected_category in categories:
602
- toggle_category(categories[selected_category])
603
- if selected_category in active_categories:
604
- active_categories.remove(selected_category)
605
- else:
606
- active_categories.add(selected_category)
607
- category_dropdown.update_styles(active_categories)
608
- category_var.set("Select Category")
775
+ process_args = (settings_type, settings, q, fig_queue, stop_requested)
776
+ if settings_type in ['mask', 'umap', 'measure', 'simulation', 'sequencing',
777
+ 'classify', 'cellpose_dataset', 'train_cellpose', 'ml_analyze', 'cellpose_masks', 'cellpose_all', 'map_barcodes',
778
+ 'regression', 'recruitment', 'plaques', 'cellpose_compare', 'vision_scores', 'vision_dataset']:
609
779
 
610
- category_var = tk.StringVar()
611
- non_empty_categories = [category for category, settings in categories.items() if any(setting in vars_dict for setting in settings)]
612
- category_dropdown = spacrDropdownMenu(button_scrollable_frame.scrollable_frame, category_var, non_empty_categories, command=on_category_select)
613
- category_dropdown.grid(row=7, column=0, sticky="ew", pady=2, padx=2)
614
- vars_dict = hide_all_settings(vars_dict, categories)
780
+ # Start the process
781
+ process = Process(target=run_function_gui, args=process_args)
782
+ process.start()
615
783
 
616
- def process_fig_queue():
617
- global canvas, fig_queue, canvas_widget, parent_frame
784
+ # Set high priority for the process
785
+ #set_high_priority(process)
618
786
 
619
- def clear_canvas(canvas):
620
- for ax in canvas.figure.get_axes():
621
- ax.clear()
622
- canvas.draw_idle()
787
+ # Set CPU affinity if necessary
788
+ set_cpu_affinity(process)
623
789
 
624
- try:
625
- while not fig_queue.empty():
626
- clear_canvas(canvas)
627
- fig = fig_queue.get_nowait()
628
- for ax in fig.get_axes():
629
- ax.set_xticks([]) # Remove x-axis ticks
630
- ax.set_yticks([]) # Remove y-axis ticks
631
- ax.xaxis.set_visible(False) # Hide the x-axis
632
- ax.yaxis.set_visible(False) # Hide the y-axis
633
- fig.tight_layout()
634
- fig.set_facecolor('black')
635
- canvas.figure = fig
636
- fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
637
- fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
638
- canvas.draw_idle()
639
- except Exception as e:
640
- traceback.print_exc()
641
- finally:
642
- after_id = canvas_widget.after(100, process_fig_queue)
643
- parent_frame.after_tasks.append(after_id)
790
+ # Store the process in thread_control for future reference
791
+ thread_control["run_thread"] = process
792
+ else:
793
+ q.put(f"Error: Unknown settings type '{settings_type}'")
794
+ return
644
795
 
645
796
  def process_console_queue():
646
- global q, console_output, parent_frame, progress_bar
797
+ global q, console_output, parent_frame, progress_bar, process_console_queue
647
798
 
648
799
  # Initialize function attribute if it doesn't exist
649
800
  if not hasattr(process_console_queue, "completed_tasks"):
650
801
  process_console_queue.completed_tasks = []
802
+ if not hasattr(process_console_queue, "current_maximum"):
803
+ process_console_queue.current_maximum = None
651
804
 
652
805
  ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
653
-
806
+
654
807
  while not q.empty():
655
808
  message = q.get_nowait()
656
809
  clean_message = ansi_escape_pattern.sub('', message)
657
- console_output.insert(tk.END, clean_message + "\n")
658
- console_output.see(tk.END)
810
+ #console_output.insert(tk.END, clean_message + "\n")
811
+ #console_output.see(tk.END)
659
812
 
660
813
  # Check if the message contains progress information
661
- if clean_message.startswith("Progress"):
814
+ if clean_message.startswith("Progress:"):
662
815
  try:
663
816
  # Extract the progress information
664
- match = re.search(r'(\d+)/(\d+)', clean_message)
817
+ match = re.search(r'Progress: (\d+)/(\d+), operation_type: ([\w\s]*),(.*)', clean_message)
818
+
665
819
  if match:
666
820
  current_progress = int(match.group(1))
667
821
  total_progress = int(match.group(2))
822
+ operation_type = match.group(3).strip()
823
+ additional_info = match.group(4).strip() # Capture everything after operation_type
824
+
825
+ # Check if the maximum value has changed
826
+ if process_console_queue.current_maximum != total_progress:
827
+ process_console_queue.current_maximum = total_progress
828
+ process_console_queue.completed_tasks = []
668
829
 
669
830
  # Add the task to the completed set
670
831
  process_console_queue.completed_tasks.append(current_progress)
671
832
 
672
833
  # Calculate the unique progress count
673
834
  unique_progress_count = len(np.unique(process_console_queue.completed_tasks))
674
-
835
+
675
836
  # Update the progress bar
676
837
  if progress_bar:
677
838
  progress_bar['maximum'] = total_progress
678
839
  progress_bar['value'] = unique_progress_count
679
840
 
680
- # Extract and update additional information
681
- operation_match = re.search(r'operation_type: ([\w\s]+)', clean_message)
682
- if operation_match:
683
- progress_bar.operation_type = operation_match.group(1)
684
-
685
- time_image_match = re.search(r'Time/image: ([\d.]+) sec', clean_message)
686
- if time_image_match:
687
- progress_bar.time_image = float(time_image_match.group(1))
688
-
689
- time_batch_match = re.search(r'Time/batch: ([\d.]+) sec', clean_message)
690
- if time_batch_match:
691
- progress_bar.time_batch = float(time_batch_match.group(1))
692
-
693
- time_left_match = re.search(r'Time_left: ([\d.]+) min', clean_message)
694
- if time_left_match:
695
- progress_bar.time_left = float(time_left_match.group(1))
841
+ # Store operation type and additional info
842
+ if operation_type:
843
+ progress_bar.operation_type = operation_type
844
+ progress_bar.additional_info = additional_info
696
845
 
697
846
  # Update the progress label
698
847
  if progress_bar.progress_label:
699
848
  progress_bar.update_label()
700
-
849
+
701
850
  # Clear completed tasks when progress is complete
702
851
  if unique_progress_count >= total_progress:
703
852
  process_console_queue.completed_tasks.clear()
853
+
704
854
  except Exception as e:
705
855
  print(f"Error parsing progress message: {e}")
706
-
707
- after_id = console_output.after(100, process_console_queue)
856
+ else:
857
+ # Only insert messages that do not start with "Progress:"
858
+ console_output.insert(tk.END, clean_message + "\n")
859
+ console_output.see(tk.END)
860
+
861
+ after_id = console_output.after(uppdate_frequency, process_console_queue)
708
862
  parent_frame.after_tasks.append(after_id)
709
863
 
710
- 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):
711
- global q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar
712
- q = q_var
713
- console_output = console_output_var
714
- parent_frame = parent_frame_var
715
- vars_dict = vars_dict_var
716
- canvas = canvas_var
717
- canvas_widget = canvas_widget_var
718
- scrollable_frame = scrollable_frame_var
719
- fig_queue = fig_queue_var
720
- progress_bar = progress_bar_var
864
+ def main_thread_update_function(root, q, fig_queue, canvas_widget):
865
+ global uppdate_frequency
866
+ try:
867
+ while not q.empty():
868
+ message = q.get_nowait()
869
+ except Exception as e:
870
+ print(f"Error updating GUI canvas: {e}")
871
+ finally:
872
+ root.after(uppdate_frequency, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget))
721
873
 
722
- def create_containers(parent_frame):
723
- vertical_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL)
724
- horizontal_container = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL)
725
- settings_frame = tk.Frame(horizontal_container)
726
- return vertical_container, horizontal_container, settings_frame
874
+ def initiate_root(parent, settings_type='mask'):
875
+ """
876
+ Initializes the root window and sets up the GUI components based on the specified settings type.
727
877
 
728
- def setup_frame(parent_frame):
729
- style = ttk.Style(parent_frame)
730
- vertical_container, horizontal_container, settings_frame = create_containers(parent_frame)
731
- containers = [vertical_container, horizontal_container, settings_frame]
732
-
733
- set_dark_style(style, parent_frame, containers)
734
- set_default_font(parent_frame, font_name="Helvetica", size=8)
878
+ Args:
879
+ parent (tkinter.Tk or tkinter.Toplevel): The parent window for the GUI.
880
+ settings_type (str, optional): The type of settings to be displayed in the GUI. Defaults to 'mask'.
881
+
882
+ Returns:
883
+ tuple: A tuple containing the parent frame and the dictionary of variables used in the GUI.
884
+ """
735
885
 
736
- vertical_container.grid(row=0, column=0, sticky=tk.NSEW)
737
- vertical_container.add(horizontal_container, stretch="always")
738
- horizontal_container.grid_columnconfigure(0, weight=1)
739
- horizontal_container.grid_columnconfigure(1, weight=1)
740
- settings_frame.grid_rowconfigure(0, weight=0)
741
- settings_frame.grid_rowconfigure(1, weight=1)
742
- settings_frame.grid_columnconfigure(0, weight=1)
743
- horizontal_container.add(settings_frame, stretch="always", sticky="nsew")
886
+ global q, fig_queue, thread_control, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar, uppdate_frequency, figures, figure_index, index_control, usage_bars
744
887
 
745
- return parent_frame, vertical_container, horizontal_container
888
+ from .gui_utils import setup_frame
889
+ from .settings import descriptions
890
+
891
+ uppdate_frequency = 500
892
+
893
+ # Start tracemalloc and initialize global variables
894
+ tracemalloc.start()
746
895
 
747
- def initiate_root(parent, settings_type='mask'):
748
- global q, fig_queue, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar
749
- from .gui_utils import main_thread_update_function
750
- from .gui import gui_app
751
896
  set_start_method('spawn', force=True)
897
+ #set_start_method('forkserver', force=True)
752
898
  print("Initializing root with settings_type:", settings_type)
753
899
 
900
+ # Initialize global variables
901
+ figures = deque()
902
+ figure_index = -1
903
+
754
904
  parent_frame = parent
755
- parent_frame.update_idletasks()
756
- frame_width = int(parent_frame.winfo_width())
757
- frame_height = int(parent_frame.winfo_height())
758
- print(frame_width, frame_height)
759
- dims = [frame_width, frame_height]
760
905
 
761
- if not hasattr(parent_frame, 'after_tasks'):
762
- parent_frame.after_tasks = []
906
+ if not isinstance(parent_frame, (tk.Tk, tk.Toplevel)):
907
+ parent_window = parent_frame.winfo_toplevel()
908
+ else:
909
+ parent_window = parent_frame
763
910
 
764
- # Clear previous content instead of destroying the root
765
- for widget in parent_frame.winfo_children():
766
- try:
767
- widget.destroy()
768
- except tk.TclError as e:
769
- print(f"Error destroying widget: {e}")
911
+ parent_window.update_idletasks()
912
+
913
+ if not hasattr(parent_window, 'after_tasks'):
914
+ parent_window.after_tasks = []
770
915
 
771
916
  q = Queue()
772
917
  fig_queue = Queue()
773
- parent_frame, vertical_container, horizontal_container = setup_frame(parent_frame)
918
+ parent_frame, vertical_container, horizontal_container, settings_container = setup_frame(parent_frame)
774
919
 
775
920
  if settings_type == 'annotate':
776
921
  from .app_annotate import initiate_annotation_app
@@ -779,17 +924,26 @@ def initiate_root(parent, settings_type='mask'):
779
924
  from .app_make_masks import initiate_make_mask_app
780
925
  initiate_make_mask_app(horizontal_container)
781
926
  else:
782
- scrollable_frame, vars_dict = setup_settings_panel(horizontal_container, settings_type, window_dimensions=dims)
783
- button_scrollable_frame = setup_button_section(horizontal_container, settings_type, window_dimensions=dims)
927
+ scrollable_frame, vars_dict = setup_settings_panel(settings_container, settings_type)
928
+ print('setup_settings_panel')
784
929
  canvas, canvas_widget = setup_plot_section(vertical_container)
785
- console_output = setup_console(vertical_container)
930
+ console_output, _ = setup_console(vertical_container)
931
+ button_scrollable_frame, btn_col = setup_button_section(horizontal_container, settings_type)
932
+ _, usage_bars, btn_col = setup_usage_panel(horizontal_container, btn_col, uppdate_frequency)
786
933
 
787
- set_globals(q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar)
934
+ set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, figures, figure_index, index_control, progress_bar, usage_bars)
935
+ description_text = descriptions.get(settings_type, "No description available for this module.")
936
+
937
+ q.put(f"Console")
938
+ q.put(f" ")
939
+ q.put(description_text)
940
+
788
941
  process_console_queue()
789
942
  process_fig_queue()
790
- after_id = parent_frame.after(100, lambda: main_thread_update_function(parent_frame, q, fig_queue, canvas_widget))
791
- parent_frame.after_tasks.append(after_id)
943
+ after_id = parent_window.after(uppdate_frequency, lambda: main_thread_update_function(parent_window, q, fig_queue, canvas_widget))
944
+ parent_window.after_tasks.append(after_id)
792
945
 
793
946
  print("Root initialization complete")
794
947
  return parent_frame, vars_dict
795
948
 
949
+