spacr 0.2.5__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.
spacr/gui_core.py CHANGED
@@ -1,23 +1,25 @@
1
- import traceback, ctypes, csv, re, time
1
+ import traceback, ctypes, csv, re, platform, time
2
2
  import tkinter as tk
3
3
  from tkinter import ttk
4
4
  from tkinter import filedialog
5
- from multiprocessing import Process, Value, Queue, set_start_method, Manager
5
+ from multiprocessing import Process, Value, Queue, set_start_method
6
6
  from tkinter import ttk, scrolledtext
7
7
  from matplotlib.figure import Figure
8
8
  from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
9
9
  import numpy as np
10
10
  import psutil
11
11
  import GPUtil
12
- import tkinter.font as tkFont
12
+ from collections import deque
13
+ import tracemalloc
14
+ from tkinter import Menu
15
+ import io
13
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
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,6 +31,10 @@ 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
 
@@ -72,42 +78,309 @@ def toggle_settings(button_scrollable_frame):
72
78
  category_dropdown.grid(row=0, column=4, sticky="ew", pady=2, padx=2)
73
79
  vars_dict = hide_all_settings(vars_dict, categories)
74
80
 
75
- def process_fig_queue():
76
- global canvas, fig_queue, canvas_widget, parent_frame
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()
77
192
 
78
- def clear_canvas(canvas):
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
79
218
  for ax in canvas.figure.get_axes():
80
- ax.clear()
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
81
233
  canvas.draw_idle()
82
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
268
+
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)
285
+
286
+ def process_fig_queue():
287
+ global canvas, fig_queue, canvas_widget, parent_frame, uppdate_frequency, figures, figure_index, index_control
288
+
289
+ from .gui_elements import standardize_figure
83
290
  try:
84
291
  while not fig_queue.empty():
85
- time.sleep(1)
86
- clear_canvas(canvas)
87
292
  fig = fig_queue.get_nowait()
88
293
 
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
294
+ if fig is None:
295
+ print("Warning: Retrieved a None figure from fig_queue.")
296
+ continue # Skip processing if the figure is None
94
297
 
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')
298
+ # Standardize the figure appearance before adding it to the list
299
+ fig = standardize_figure(fig)
98
300
 
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()
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
+
103
311
  except Exception as e:
104
312
  traceback.print_exc()
105
313
  finally:
106
- after_id = canvas_widget.after(1, process_fig_queue)
314
+ after_id = canvas_widget.after(uppdate_frequency, process_fig_queue)
107
315
  parent_frame.after_tasks.append(after_id)
108
316
 
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
317
+ def update_figure(value):
318
+ global figure_index, figures
319
+
320
+ # Convert the value to an integer
321
+ index = int(value)
322
+
323
+ # Check if the index is valid
324
+ if 0 <= index < len(figures):
325
+ figure_index = index
326
+ display_figure(figures[figure_index])
327
+
328
+ # Update the index control widget's range and value
329
+ index_control.set_to(len(figures) - 1)
330
+ index_control.set(figure_index)
331
+
332
+ def setup_plot_section(vertical_container):
333
+ global canvas, canvas_widget, figures, figure_index, index_control
334
+
335
+ # Initialize deque for storing figures and the current index
336
+ figures = deque()
337
+
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')
347
+
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")
352
+
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
111
384
  thread_control = thread_control_var
112
385
  q = q_var
113
386
  console_output = console_output_var
@@ -117,13 +390,16 @@ def set_globals(thread_control_var, q_var, console_output_var, parent_frame_var,
117
390
  canvas_widget = canvas_widget_var
118
391
  scrollable_frame = scrollable_frame_var
119
392
  fig_queue = fig_queue_var
393
+ figures = figures_var
394
+ figure_index = figure_index_var
120
395
  progress_bar = progress_bar_var
121
396
  usage_bars = usage_bars_var
397
+ index_control = index_control_var
122
398
 
123
399
  def import_settings(settings_type='mask'):
124
400
  from .gui_utils import convert_settings_dict_for_gui, hide_all_settings
125
401
  global vars_dict, scrollable_frame, button_scrollable_frame
126
- 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
127
403
 
128
404
  def read_settings_from_csv(csv_file_path):
129
405
  settings = {}
@@ -159,9 +435,11 @@ def import_settings(settings_type='mask'):
159
435
  elif settings_type == 'classify':
160
436
  settings = set_default_train_test_model(settings={})
161
437
  elif settings_type == 'sequencing':
162
- settings = get_analyze_reads_default_settings(settings={})
438
+ settings = set_default_generate_barecode_mapping(settings={})
163
439
  elif settings_type == 'umap':
164
440
  settings = set_default_umap_image_settings(settings={})
441
+ elif settings_type == 'recruitment':
442
+ settings = get_analyze_recruitment_default_settings(settings={})
165
443
  else:
166
444
  raise ValueError(f"Invalid settings type: {settings_type}")
167
445
 
@@ -172,8 +450,9 @@ def import_settings(settings_type='mask'):
172
450
 
173
451
  def setup_settings_panel(vertical_container, settings_type='mask'):
174
452
  global vars_dict, scrollable_frame
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
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
177
456
 
178
457
  size_dict = set_element_size()
179
458
  settings_width = size_dict['settings_width']
@@ -197,9 +476,7 @@ def setup_settings_panel(vertical_container, settings_type='mask'):
197
476
  elif settings_type == 'measure':
198
477
  settings = get_measure_crop_settings(settings={})
199
478
  elif settings_type == 'classify':
200
- settings = set_default_train_test_model(settings={})
201
- elif settings_type == 'sequencing':
202
- settings = get_analyze_reads_default_settings(settings={})
479
+ settings = deep_spacr_defaults(settings={})
203
480
  elif settings_type == 'umap':
204
481
  settings = set_default_umap_image_settings(settings={})
205
482
  elif settings_type == 'train_cellpose':
@@ -211,7 +488,7 @@ def setup_settings_panel(vertical_container, settings_type='mask'):
211
488
  elif settings_type == 'cellpose_all':
212
489
  settings = get_check_cellpose_models_default_settings(settings={})
213
490
  elif settings_type == 'map_barcodes':
214
- settings = get_map_barcodes_default_settings(settings={})
491
+ settings = set_default_generate_barecode_mapping(settings={})
215
492
  elif settings_type == 'regression':
216
493
  settings = get_perform_regression_default_settings(settings={})
217
494
  elif settings_type == 'recruitment':
@@ -231,39 +508,6 @@ def setup_settings_panel(vertical_container, settings_type='mask'):
231
508
  print("Settings panel setup complete")
232
509
  return scrollable_frame, vars_dict
233
510
 
234
- def setup_plot_section(vertical_container):
235
- global canvas, canvas_widget
236
-
237
- # Create a frame for the plot section
238
- plot_frame = tk.Frame(vertical_container, bg='lightgrey')
239
- vertical_container.add(plot_frame, stretch="always")
240
-
241
- # Set up the plot
242
- figure = Figure(figsize=(30, 4), dpi=100)
243
- plot = figure.add_subplot(111)
244
- plot.plot([], []) # This creates an empty plot.
245
- plot.axis('off')
246
- canvas = FigureCanvasTkAgg(figure, master=plot_frame)
247
- canvas.get_tk_widget().configure(cursor='arrow', highlightthickness=0)
248
- canvas_widget = canvas.get_tk_widget()
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
-
254
- canvas.draw()
255
- canvas.figure = figure
256
- style_out = set_dark_style(ttk.Style())
257
-
258
- figure.patch.set_facecolor(style_out['bg_color'])
259
- plot.set_facecolor(style_out['bg_color'])
260
- containers = [plot_frame]
261
- widgets = [canvas_widget]
262
- style = ttk.Style(vertical_container)
263
- _ = set_dark_style(style, containers=containers, widgets=widgets)
264
-
265
- return canvas, canvas_widget
266
-
267
511
  def setup_console(vertical_container):
268
512
  global console_output
269
513
  from .gui_elements import set_dark_style
@@ -282,9 +526,10 @@ def setup_console(vertical_container):
282
526
 
283
527
  # Create the scrollable frame (which is a Text widget) with white text
284
528
  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)
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
+
288
533
  console_output.grid(row=1, column=0, sticky="nsew") # Use grid for console_output
289
534
 
290
535
  # Configure the grid to allow expansion
@@ -302,36 +547,15 @@ def setup_console(vertical_container):
302
547
 
303
548
  return console_output, console_frame
304
549
 
305
- def setup_progress_frame(vertical_container):
306
- global progress_output
307
- progress_frame = tk.Frame(vertical_container)
308
- vertical_container.add(progress_frame, stretch="always")
309
- label_frame = tk.Frame(progress_frame)
310
- label_frame.grid(row=0, column=0, sticky="ew", pady=(5, 0), padx=10)
311
- progress_label = spacrLabel(label_frame, text="Processing: 0%", font=('Helvetica', 12), anchor='w', justify='left', align="left")
312
- progress_label.grid(row=0, column=0, sticky="w")
313
- progress_output = scrolledtext.ScrolledText(progress_frame, height=10)
314
- progress_output.grid(row=1, column=0, sticky="nsew")
315
- progress_frame.grid_rowconfigure(1, weight=1)
316
- progress_frame.grid_columnconfigure(0, weight=1)
317
- containers = [progress_frame, label_frame]
318
- widgets = [progress_label, progress_output]
319
- style = ttk.Style(vertical_container)
320
- _ = set_dark_style(style, containers=containers, widgets=widgets)
321
- return progress_output
322
-
323
550
  def setup_button_section(horizontal_container, settings_type='mask', run=True, abort=True, download=True, import_btn=True):
324
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
325
- from .gui_utils import set_element_size, download_hug_dataset
326
- from .settings import categories
552
+ from .gui_utils import download_hug_dataset
553
+ from .gui_elements import set_element_size
327
554
 
328
555
  size_dict = set_element_size()
329
556
  button_section_height = size_dict['panel_height']
330
557
  button_frame = tk.Frame(horizontal_container, height=button_section_height)
331
558
 
332
- # Prevent the frame from resizing based on the child widget
333
- #button_frame.pack_propagate(False)
334
-
335
559
  horizontal_container.add(button_frame, stretch="always", sticky="nsew")
336
560
  button_scrollable_frame = spacrFrame(button_frame, scrollbar=False)
337
561
  button_scrollable_frame.grid(row=1, column=0, sticky="nsew")
@@ -346,7 +570,7 @@ def setup_button_section(horizontal_container, settings_type='mask', run=True, a
346
570
  widgets.append(run_button)
347
571
  btn_col += 1
348
572
 
349
- if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap']:
573
+ if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap', 'map_barcodes']:
350
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)
351
575
  abort_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
352
576
  widgets.append(abort_button)
@@ -364,9 +588,9 @@ def setup_button_section(horizontal_container, settings_type='mask', run=True, a
364
588
  widgets.append(import_button)
365
589
  btn_row += 1
366
590
 
367
- # Add the progress bar under the settings category menu
591
+ # Add the batch progress bar
368
592
  progress_bar = spacrProgressBar(button_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate')
369
- progress_bar.grid(row=btn_row, column=0, columnspan=6, pady=5, padx=5, sticky='ew')
593
+ progress_bar.grid(row=btn_row, column=0, columnspan=7, pady=5, padx=5, sticky='ew')
370
594
  progress_bar.set_label_position() # Set the label position after grid placement
371
595
  widgets.append(progress_bar)
372
596
 
@@ -378,10 +602,9 @@ def setup_button_section(horizontal_container, settings_type='mask', run=True, a
378
602
 
379
603
  return button_scrollable_frame, btn_col
380
604
 
381
- def setup_usage_panel(horizontal_container, btn_col):
605
+ def setup_usage_panel(horizontal_container, btn_col, uppdate_frequency):
382
606
  global usage_bars
383
- from .gui_utils import set_element_size
384
- from .gui_elements import set_dark_style
607
+ from .gui_elements import set_dark_style, set_element_size
385
608
 
386
609
  usg_col = 1
387
610
 
@@ -405,7 +628,7 @@ def setup_usage_panel(horizontal_container, btn_col):
405
628
  bar['value'] = usage
406
629
 
407
630
  # 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)
631
+ parent_frame.after(uppdate_frequency, update_usage, ram_bar, vram_bar, gpu_bar, usage_bars, parent_frame)
409
632
 
410
633
  size_dict = set_element_size()
411
634
  usage_panel_height = size_dict['panel_height']
@@ -422,7 +645,7 @@ def setup_usage_panel(horizontal_container, btn_col):
422
645
  widgets = [usage_scrollable_frame.scrollable_frame]
423
646
 
424
647
  usage_bars = []
425
- max_elements_per_column = 4
648
+ max_elements_per_column = 6
426
649
  row = 0
427
650
  col = 0
428
651
 
@@ -432,15 +655,15 @@ def setup_usage_panel(horizontal_container, btn_col):
432
655
  # Configure the style for the label
433
656
  style = ttk.Style()
434
657
  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'])
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'])
438
661
 
439
662
  # Try adding RAM bar
440
663
  try:
441
664
  ram_info = psutil.virtual_memory()
442
665
  ram_label_text = f"RAM"
443
- label = ttk.Label(usage_scrollable_frame.scrollable_frame, text=ram_label_text, anchor='w', style="usage.TLabel")
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'])
444
667
  label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
445
668
  ram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
446
669
  ram_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
@@ -457,7 +680,7 @@ def setup_usage_panel(horizontal_container, btn_col):
457
680
  if gpus:
458
681
  gpu = gpus[0]
459
682
  vram_label_text = f"VRAM"
460
- label = ttk.Label(usage_scrollable_frame.scrollable_frame, text=vram_label_text, anchor='w', style="usage.TLabel")
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'])
461
684
  label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
462
685
  vram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
463
686
  vram_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
@@ -467,7 +690,7 @@ def setup_usage_panel(horizontal_container, btn_col):
467
690
  row += 1
468
691
 
469
692
  gpu_label_text = f"GPU"
470
- label = ttk.Label(usage_scrollable_frame.scrollable_frame, text=gpu_label_text, anchor='w', style="usage.TLabel")
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'])
471
694
  label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
472
695
  gpu_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
473
696
  gpu_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
@@ -487,7 +710,8 @@ def setup_usage_panel(horizontal_container, btn_col):
487
710
  if row > 0 and row % max_elements_per_column == 0:
488
711
  col += 1
489
712
  row = 0
490
- label = ttk.Label(usage_scrollable_frame.scrollable_frame, text=f"Core {core+1}", anchor='w', style="usage.TLabel")
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'])
491
715
  label.grid(row=row, column=2 * col, pady=2, padx=5, sticky='w')
492
716
  bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
493
717
  bar.grid(row=row, column=2 * col + 1, pady=2, padx=5, sticky='ew')
@@ -524,17 +748,15 @@ def initiate_abort():
524
748
 
525
749
  thread_control = {"run_thread": None, "stop_requested": False}
526
750
 
527
-
528
751
  def start_process(q=None, fig_queue=None, settings_type='mask'):
529
752
  global thread_control, vars_dict, parent_frame
530
753
  from .settings import check_settings, expected_types
531
- from .gui_utils import run_function_gui
754
+ from .gui_utils import run_function_gui, set_high_priority, set_cpu_affinity, initialize_cuda
532
755
 
533
756
  if q is None:
534
757
  q = Queue()
535
758
  if fig_queue is None:
536
759
  fig_queue = Queue()
537
-
538
760
  try:
539
761
  settings = check_settings(vars_dict, expected_types, q)
540
762
  except ValueError as e:
@@ -547,21 +769,32 @@ def start_process(q=None, fig_queue=None, settings_type='mask'):
547
769
  stop_requested = Value('i', 0)
548
770
  thread_control["stop_requested"] = stop_requested
549
771
 
772
+ # Initialize CUDA in the main process
773
+ initialize_cuda()
774
+
550
775
  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)
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']:
779
+
780
+ # Start the process
781
+ process = Process(target=run_function_gui, args=process_args)
782
+ process.start()
783
+
784
+ # Set high priority for the process
785
+ #set_high_priority(process)
786
+
787
+ # Set CPU affinity if necessary
788
+ set_cpu_affinity(process)
789
+
790
+ # Store the process in thread_control for future reference
791
+ thread_control["run_thread"] = process
558
792
  else:
559
793
  q.put(f"Error: Unknown settings type '{settings_type}'")
560
794
  return
561
- thread_control["run_thread"].start()
562
795
 
563
796
  def process_console_queue():
564
- global q, console_output, parent_frame, progress_bar
797
+ global q, console_output, parent_frame, progress_bar, process_console_queue
565
798
 
566
799
  # Initialize function attribute if it doesn't exist
567
800
  if not hasattr(process_console_queue, "completed_tasks"):
@@ -574,21 +807,21 @@ def process_console_queue():
574
807
  while not q.empty():
575
808
  message = q.get_nowait()
576
809
  clean_message = ansi_escape_pattern.sub('', message)
577
- console_output.insert(tk.END, clean_message + "\n")
578
- console_output.see(tk.END)
810
+ #console_output.insert(tk.END, clean_message + "\n")
811
+ #console_output.see(tk.END)
579
812
 
580
813
  # Check if the message contains progress information
581
814
  if clean_message.startswith("Progress:"):
582
815
  try:
583
816
  # Extract the progress information
584
- match = re.search(r'Progress: (\d+)/(\d+), operation_type: ([\w\s]*)(.*)', clean_message)
817
+ match = re.search(r'Progress: (\d+)/(\d+), operation_type: ([\w\s]*),(.*)', clean_message)
585
818
 
586
819
  if match:
587
820
  current_progress = int(match.group(1))
588
821
  total_progress = int(match.group(2))
589
822
  operation_type = match.group(3).strip()
590
- time_info = match.group(4).strip()
591
-
823
+ additional_info = match.group(4).strip() # Capture everything after operation_type
824
+
592
825
  # Check if the maximum value has changed
593
826
  if process_console_queue.current_maximum != total_progress:
594
827
  process_console_queue.current_maximum = total_progress
@@ -599,55 +832,75 @@ def process_console_queue():
599
832
 
600
833
  # Calculate the unique progress count
601
834
  unique_progress_count = len(np.unique(process_console_queue.completed_tasks))
602
-
835
+
603
836
  # Update the progress bar
604
837
  if progress_bar:
605
838
  progress_bar['maximum'] = total_progress
606
839
  progress_bar['value'] = unique_progress_count
607
840
 
608
- # Extract and update additional information
841
+ # Store operation type and additional info
609
842
  if operation_type:
610
843
  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))
844
+ progress_bar.additional_info = additional_info
623
845
 
624
846
  # Update the progress label
625
847
  if progress_bar.progress_label:
626
848
  progress_bar.update_label()
627
-
849
+
628
850
  # Clear completed tasks when progress is complete
629
851
  if unique_progress_count >= total_progress:
630
852
  process_console_queue.completed_tasks.clear()
631
853
 
632
854
  except Exception as e:
633
855
  print(f"Error parsing progress message: {e}")
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)
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)
638
860
 
639
- after_id = console_output.after(1, process_console_queue)
861
+ after_id = console_output.after(uppdate_frequency, process_console_queue)
640
862
  parent_frame.after_tasks.append(after_id)
641
863
 
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))
873
+
642
874
  def initiate_root(parent, settings_type='mask'):
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
645
- from .gui import gui_app
875
+ """
876
+ Initializes the root window and sets up the GUI components based on the specified settings type.
877
+
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
+ """
885
+
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
887
+
888
+ from .gui_utils import setup_frame
646
889
  from .settings import descriptions
647
890
 
891
+ uppdate_frequency = 500
892
+
893
+ # Start tracemalloc and initialize global variables
894
+ tracemalloc.start()
895
+
648
896
  set_start_method('spawn', force=True)
897
+ #set_start_method('forkserver', force=True)
649
898
  print("Initializing root with settings_type:", settings_type)
650
899
 
900
+ # Initialize global variables
901
+ figures = deque()
902
+ figure_index = -1
903
+
651
904
  parent_frame = parent
652
905
 
653
906
  if not isinstance(parent_frame, (tk.Tk, tk.Toplevel)):
@@ -676,9 +929,9 @@ def initiate_root(parent, settings_type='mask'):
676
929
  canvas, canvas_widget = setup_plot_section(vertical_container)
677
930
  console_output, _ = setup_console(vertical_container)
678
931
  button_scrollable_frame, btn_col = setup_button_section(horizontal_container, settings_type)
679
- _, usage_bars, btn_col = setup_usage_panel(horizontal_container, btn_col)
932
+ _, usage_bars, btn_col = setup_usage_panel(horizontal_container, btn_col, uppdate_frequency)
680
933
 
681
- set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars)
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)
682
935
  description_text = descriptions.get(settings_type, "No description available for this module.")
683
936
 
684
937
  q.put(f"Console")
@@ -687,7 +940,7 @@ def initiate_root(parent, settings_type='mask'):
687
940
 
688
941
  process_console_queue()
689
942
  process_fig_queue()
690
- after_id = parent_window.after(100, lambda: main_thread_update_function(parent_window, q, fig_queue, canvas_widget))
943
+ after_id = parent_window.after(uppdate_frequency, lambda: main_thread_update_function(parent_window, q, fig_queue, canvas_widget))
691
944
  parent_window.after_tasks.append(after_id)
692
945
 
693
946
  print("Root initialization complete")