spacr 0.2.53__py3-none-any.whl → 0.2.61__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/core.py +218 -283
- spacr/deep_spacr.py +248 -269
- spacr/gui.py +1 -1
- spacr/gui_core.py +301 -94
- spacr/gui_elements.py +43 -20
- spacr/gui_utils.py +81 -47
- spacr/io.py +116 -45
- spacr/plot.py +47 -1
- spacr/sequencing.py +443 -643
- spacr/settings.py +192 -64
- spacr/utils.py +22 -13
- {spacr-0.2.53.dist-info → spacr-0.2.61.dist-info}/METADATA +2 -1
- {spacr-0.2.53.dist-info → spacr-0.2.61.dist-info}/RECORD +17 -17
- {spacr-0.2.53.dist-info → spacr-0.2.61.dist-info}/LICENSE +0 -0
- {spacr-0.2.53.dist-info → spacr-0.2.61.dist-info}/WHEEL +0 -0
- {spacr-0.2.53.dist-info → spacr-0.2.61.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.53.dist-info → spacr-0.2.61.dist-info}/top_level.txt +0 -0
spacr/gui.py
CHANGED
@@ -27,7 +27,7 @@ class MainApp(tk.Tk):
|
|
27
27
|
}
|
28
28
|
|
29
29
|
self.additional_gui_apps = {
|
30
|
-
"Sequencing": (lambda frame: initiate_root(self, 'sequencing'), "Analyze sequencing data."),
|
30
|
+
#"Sequencing": (lambda frame: initiate_root(self, 'sequencing'), "Analyze sequencing data."),
|
31
31
|
"Umap": (lambda frame: initiate_root(self, 'umap'), "Generate UMAP embeddings with datapoints represented as images."),
|
32
32
|
"Train Cellpose": (lambda frame: initiate_root(self, 'train_cellpose'), "Train custom Cellpose models."),
|
33
33
|
"ML Analyze": (lambda frame: initiate_root(self, 'ml_analyze'), "Machine learning analysis of data."),
|
spacr/gui_core.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import traceback, ctypes, csv, re
|
1
|
+
import traceback, ctypes, csv, re
|
2
2
|
import tkinter as tk
|
3
3
|
from tkinter import ttk
|
4
4
|
from tkinter import filedialog
|
@@ -9,14 +9,17 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|
9
9
|
import numpy as np
|
10
10
|
import psutil
|
11
11
|
import GPUtil
|
12
|
+
from collections import deque
|
13
|
+
import tracemalloc
|
14
|
+
from tkinter import Menu
|
15
|
+
import io
|
12
16
|
|
13
17
|
try:
|
14
18
|
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
15
19
|
except AttributeError:
|
16
20
|
pass
|
17
21
|
|
18
|
-
from .
|
19
|
-
from .gui_elements import spacrProgressBar, spacrButton, spacrLabel, spacrFrame, spacrDropdownMenu ,set_dark_style
|
22
|
+
from .gui_elements import spacrProgressBar, spacrButton, spacrLabel, spacrFrame, spacrDropdownMenu , set_dark_style
|
20
23
|
|
21
24
|
# Define global variables
|
22
25
|
q = None
|
@@ -28,6 +31,12 @@ canvas_widget = None
|
|
28
31
|
scrollable_frame = None
|
29
32
|
progress_label = None
|
30
33
|
fig_queue = None
|
34
|
+
figures = None
|
35
|
+
figure_index = None
|
36
|
+
progress_bar = None
|
37
|
+
usage_bars = None
|
38
|
+
fig_memory_limit = None
|
39
|
+
figure_current_memory_usage = None
|
31
40
|
|
32
41
|
thread_control = {"run_thread": None, "stop_requested": False}
|
33
42
|
|
@@ -72,41 +81,185 @@ def toggle_settings(button_scrollable_frame):
|
|
72
81
|
vars_dict = hide_all_settings(vars_dict, categories)
|
73
82
|
|
74
83
|
def process_fig_queue():
|
75
|
-
global canvas, fig_queue, canvas_widget, parent_frame
|
76
|
-
|
77
|
-
def clear_canvas(canvas):
|
78
|
-
for ax in canvas.figure.get_axes():
|
79
|
-
ax.clear()
|
80
|
-
canvas.draw_idle()
|
84
|
+
global canvas, fig_queue, canvas_widget, parent_frame, uppdate_frequency, figures, figure_index
|
81
85
|
|
82
86
|
try:
|
83
87
|
while not fig_queue.empty():
|
84
|
-
time.sleep(1)
|
85
|
-
clear_canvas(canvas)
|
86
88
|
fig = fig_queue.get_nowait()
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
ax.set_yticks([]) # Remove y-axis ticks
|
91
|
-
ax.xaxis.set_visible(False) # Hide the x-axis
|
92
|
-
ax.yaxis.set_visible(False) # Hide the y-axis
|
93
|
-
|
94
|
-
# Adjust layout to minimize spacing between axes
|
95
|
-
fig.subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0.01, hspace=0.01)
|
96
|
-
fig.set_facecolor('black')
|
97
|
-
|
98
|
-
canvas.figure = fig
|
99
|
-
fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
|
100
|
-
fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
|
101
|
-
canvas.draw_idle()
|
89
|
+
figures.append(fig)
|
90
|
+
figure_index = len(figures) - 1
|
91
|
+
display_figure(fig)
|
102
92
|
except Exception as e:
|
103
93
|
traceback.print_exc()
|
104
94
|
finally:
|
105
|
-
after_id = canvas_widget.after(
|
95
|
+
after_id = canvas_widget.after(uppdate_frequency, process_fig_queue)
|
106
96
|
parent_frame.after_tasks.append(after_id)
|
107
97
|
|
108
|
-
def
|
109
|
-
global
|
98
|
+
def display_figure(fig):
|
99
|
+
global canvas, canvas_widget
|
100
|
+
|
101
|
+
# Clear previous canvas content
|
102
|
+
canvas.get_tk_widget().destroy()
|
103
|
+
|
104
|
+
# Create a new canvas for the figure
|
105
|
+
new_canvas = FigureCanvasTkAgg(fig, master=canvas_widget.master)
|
106
|
+
new_canvas.draw()
|
107
|
+
new_canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew")
|
108
|
+
|
109
|
+
# Update the global canvas and canvas_widget references
|
110
|
+
canvas = new_canvas
|
111
|
+
canvas_widget = new_canvas.get_tk_widget()
|
112
|
+
|
113
|
+
# Apply the dark style to the context menu
|
114
|
+
style_out = set_dark_style(ttk.Style())
|
115
|
+
bg_color = style_out['bg_color']
|
116
|
+
fg_color = style_out['fg_color']
|
117
|
+
|
118
|
+
# Define the save functions for the context menu
|
119
|
+
def save_figure_as_format(format):
|
120
|
+
file_path = filedialog.asksaveasfilename(defaultextension=f".{format}", filetypes=[(f"{format.upper()} files", f"*.{format}"), ("All files", "*.*")])
|
121
|
+
if file_path:
|
122
|
+
fig.savefig(file_path, format=format)
|
123
|
+
|
124
|
+
def modify_figure():
|
125
|
+
def apply_modifications():
|
126
|
+
try:
|
127
|
+
x_width = float(x_width_var.get())
|
128
|
+
y_height = float(y_height_var.get())
|
129
|
+
line_width = float(line_width_var.get())
|
130
|
+
font_size = int(font_size_var.get())
|
131
|
+
modify_figure_properties(fig, x_width=x_width, y_height=y_height, line_width=line_width)
|
132
|
+
for ax in fig.get_axes():
|
133
|
+
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
134
|
+
label.set_fontsize(font_size)
|
135
|
+
ax.title.set_fontsize(font_size)
|
136
|
+
ax.xaxis.label.set_fontsize(font_size)
|
137
|
+
ax.yaxis.label.set_fontsize(font_size)
|
138
|
+
canvas.draw_idle() # Redraw the canvas after modifications
|
139
|
+
except ValueError:
|
140
|
+
print("Invalid input; please enter numeric values.")
|
141
|
+
|
142
|
+
# Create a new window for user input
|
143
|
+
modify_window = tk.Toplevel()
|
144
|
+
modify_window.title("Modify Figure Properties")
|
145
|
+
|
146
|
+
# Apply dark style to the popup window
|
147
|
+
modify_window.configure(bg=bg_color)
|
148
|
+
|
149
|
+
# Create and style the input fields
|
150
|
+
tk.Label(modify_window, text="X Axis Width:", bg=bg_color, fg=fg_color).grid(row=0, column=0, padx=10, pady=5)
|
151
|
+
x_width_var = tk.StringVar()
|
152
|
+
tk.Entry(modify_window, textvariable=x_width_var, bg=bg_color, fg=fg_color).grid(row=0, column=1, padx=10, pady=5)
|
153
|
+
|
154
|
+
tk.Label(modify_window, text="Y Axis Height:", bg=bg_color, fg=fg_color).grid(row=1, column=0, padx=10, pady=5)
|
155
|
+
y_height_var = tk.StringVar()
|
156
|
+
tk.Entry(modify_window, textvariable=y_height_var, bg=bg_color, fg=fg_color).grid(row=1, column=1, padx=10, pady=5)
|
157
|
+
|
158
|
+
tk.Label(modify_window, text="Line Width:", bg=bg_color, fg=fg_color).grid(row=2, column=0, padx=10, pady=5)
|
159
|
+
line_width_var = tk.StringVar()
|
160
|
+
tk.Entry(modify_window, textvariable=line_width_var, bg=bg_color, fg=fg_color).grid(row=2, column=1, padx=10, pady=5)
|
161
|
+
|
162
|
+
tk.Label(modify_window, text="Font Size:", bg=bg_color, fg=fg_color).grid(row=3, column=0, padx=10, pady=5)
|
163
|
+
font_size_var = tk.StringVar()
|
164
|
+
tk.Entry(modify_window, textvariable=font_size_var, bg=bg_color, fg=fg_color).grid(row=3, column=1, padx=10, pady=5)
|
165
|
+
|
166
|
+
# Apply button
|
167
|
+
apply_button = tk.Button(modify_window, text="Apply", command=apply_modifications, bg=bg_color, fg=fg_color)
|
168
|
+
apply_button.grid(row=4, column=0, columnspan=2, pady=10)
|
169
|
+
|
170
|
+
# Create the context menu
|
171
|
+
context_menu = tk.Menu(canvas_widget, tearoff=0, bg=bg_color, fg=fg_color)
|
172
|
+
context_menu.add_command(label="Save Figure as PDF", command=lambda: save_figure_as_format('pdf'))
|
173
|
+
context_menu.add_command(label="Save Figure as PNG", command=lambda: save_figure_as_format('png'))
|
174
|
+
context_menu.add_command(label="Modify Figure", command=modify_figure)
|
175
|
+
|
176
|
+
def on_right_click(event):
|
177
|
+
context_menu.post(event.x_root, event.y_root)
|
178
|
+
|
179
|
+
new_canvas.get_tk_widget().bind("<Button-3>", on_right_click)
|
180
|
+
|
181
|
+
def clear_unused_figures():
|
182
|
+
global figures, figure_index
|
183
|
+
|
184
|
+
lower_bound = max(0, figure_index - 20)
|
185
|
+
upper_bound = min(len(figures), figure_index + 20)
|
186
|
+
|
187
|
+
# Clear figures outside of the +/- 20 range
|
188
|
+
figures = deque([fig for i, fig in enumerate(figures) if lower_bound <= i <= upper_bound])
|
189
|
+
|
190
|
+
# Update the figure index after clearing
|
191
|
+
figure_index = min(max(figure_index, 0), len(figures) - 1)
|
192
|
+
|
193
|
+
def show_previous_figure():
|
194
|
+
global figure_index, figures
|
195
|
+
if figure_index is not None and figure_index > 0:
|
196
|
+
figure_index -= 1
|
197
|
+
display_figure(figures[figure_index])
|
198
|
+
clear_unused_figures()
|
199
|
+
|
200
|
+
def show_next_figure():
|
201
|
+
global figure_index, figures
|
202
|
+
if figure_index is not None and figure_index < len(figures) - 1:
|
203
|
+
figure_index += 1
|
204
|
+
display_figure(figures[figure_index])
|
205
|
+
clear_unused_figures()
|
206
|
+
|
207
|
+
def save_figure_as_format(fig, file_format):
|
208
|
+
file_path = filedialog.asksaveasfilename(defaultextension=f".{file_format}", filetypes=[(f"{file_format.upper()} files", f"*.{file_format}"), ("All files", "*.*")])
|
209
|
+
if file_path:
|
210
|
+
try:
|
211
|
+
fig.savefig(file_path, format=file_format)
|
212
|
+
print(f"Figure saved as {file_format.upper()} at {file_path}")
|
213
|
+
except Exception as e:
|
214
|
+
print(f"Error saving figure: {e}")
|
215
|
+
|
216
|
+
def modify_figure_properties(fig, x_width=None, y_height=None, line_width=None):
|
217
|
+
"""
|
218
|
+
Modifies the properties of the figure, including axis dimensions and line widths.
|
219
|
+
|
220
|
+
Parameters:
|
221
|
+
- fig: The matplotlib figure object to modify.
|
222
|
+
- x_width: Desired width for the x-axis (optional).
|
223
|
+
- y_height: Desired height for the y-axis (optional).
|
224
|
+
- line_width: Desired line width for all lines (optional).
|
225
|
+
"""
|
226
|
+
for ax in fig.get_axes():
|
227
|
+
# Scaling the figure
|
228
|
+
if x_width is not None or y_height is not None:
|
229
|
+
# Get the current axis limits
|
230
|
+
current_xlim = ax.get_xlim()
|
231
|
+
current_ylim = ax.get_ylim()
|
232
|
+
|
233
|
+
# Set new limits
|
234
|
+
if x_width is not None:
|
235
|
+
scale_factor_x = x_width / (current_xlim[1] - current_xlim[0])
|
236
|
+
else:
|
237
|
+
scale_factor_x = 1
|
238
|
+
|
239
|
+
if y_height is not None:
|
240
|
+
scale_factor_y = y_height / (current_ylim[1] - current_ylim[0])
|
241
|
+
else:
|
242
|
+
scale_factor_y = 1
|
243
|
+
|
244
|
+
# Adjust the figure size and elements proportionally
|
245
|
+
fig.set_size_inches(fig.get_size_inches() * scale_factor_x * scale_factor_y, forward=True)
|
246
|
+
ax.set_xlim(left=current_xlim[0] * scale_factor_x, right=current_xlim[1] * scale_factor_x)
|
247
|
+
ax.set_ylim(bottom=current_ylim[0] * scale_factor_y, top=current_ylim[1] * scale_factor_y)
|
248
|
+
|
249
|
+
# Adjust line width and other elements if specified
|
250
|
+
if line_width is not None:
|
251
|
+
for line in ax.get_lines():
|
252
|
+
line.set_linewidth(line_width)
|
253
|
+
for spine in ax.spines.values(): # Modify width of spines (e.g., scale bars)
|
254
|
+
spine.set_linewidth(line_width)
|
255
|
+
ax.tick_params(width=line_width) # Modify width of ticks
|
256
|
+
for text in ax.get_xticklabels() + ax.get_yticklabels():
|
257
|
+
text.set_fontsize(ax.get_xticklabels()[0].get_fontsize())
|
258
|
+
|
259
|
+
fig.canvas.draw_idle()
|
260
|
+
|
261
|
+
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, progress_bar_var, usage_bars_var, fig_memory_limit_var, figure_current_memory_usage_var):
|
262
|
+
global thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, figures, figure_index, progress_bar, usage_bars, fig_memory_limit, figure_current_memory_usage
|
110
263
|
thread_control = thread_control_var
|
111
264
|
q = q_var
|
112
265
|
console_output = console_output_var
|
@@ -116,13 +269,17 @@ def set_globals(thread_control_var, q_var, console_output_var, parent_frame_var,
|
|
116
269
|
canvas_widget = canvas_widget_var
|
117
270
|
scrollable_frame = scrollable_frame_var
|
118
271
|
fig_queue = fig_queue_var
|
272
|
+
figures = figures_var
|
273
|
+
figure_index = figure_index_var
|
119
274
|
progress_bar = progress_bar_var
|
120
275
|
usage_bars = usage_bars_var
|
276
|
+
fig_memory_limit = fig_memory_limit_var
|
277
|
+
figure_current_memory_usage = figure_current_memory_usage_var
|
121
278
|
|
122
279
|
def import_settings(settings_type='mask'):
|
123
280
|
from .gui_utils import convert_settings_dict_for_gui, hide_all_settings
|
124
281
|
global vars_dict, scrollable_frame, button_scrollable_frame
|
125
|
-
from .settings import generate_fields
|
282
|
+
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
|
126
283
|
|
127
284
|
def read_settings_from_csv(csv_file_path):
|
128
285
|
settings = {}
|
@@ -158,9 +315,11 @@ def import_settings(settings_type='mask'):
|
|
158
315
|
elif settings_type == 'classify':
|
159
316
|
settings = set_default_train_test_model(settings={})
|
160
317
|
elif settings_type == 'sequencing':
|
161
|
-
settings =
|
318
|
+
settings = set_default_generate_barecode_mapping(settings={})
|
162
319
|
elif settings_type == 'umap':
|
163
320
|
settings = set_default_umap_image_settings(settings={})
|
321
|
+
elif settings_type == 'recruitment':
|
322
|
+
settings = get_analyze_recruitment_default_settings(settings={})
|
164
323
|
else:
|
165
324
|
raise ValueError(f"Invalid settings type: {settings_type}")
|
166
325
|
|
@@ -171,7 +330,7 @@ def import_settings(settings_type='mask'):
|
|
171
330
|
|
172
331
|
def setup_settings_panel(vertical_container, settings_type='mask'):
|
173
332
|
global vars_dict, scrollable_frame
|
174
|
-
from .settings import get_identify_masks_finetune_default_settings, set_default_analyze_screen, set_default_settings_preprocess_generate_masks, get_measure_crop_settings,
|
333
|
+
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
|
175
334
|
from .gui_utils import convert_settings_dict_for_gui
|
176
335
|
from .gui_elements import set_element_size
|
177
336
|
|
@@ -197,9 +356,7 @@ def setup_settings_panel(vertical_container, settings_type='mask'):
|
|
197
356
|
elif settings_type == 'measure':
|
198
357
|
settings = get_measure_crop_settings(settings={})
|
199
358
|
elif settings_type == 'classify':
|
200
|
-
settings =
|
201
|
-
elif settings_type == 'sequencing':
|
202
|
-
settings = get_analyze_reads_default_settings(settings={})
|
359
|
+
settings = deep_spacr_defaults(settings={})
|
203
360
|
elif settings_type == 'umap':
|
204
361
|
settings = set_default_umap_image_settings(settings={})
|
205
362
|
elif settings_type == 'train_cellpose':
|
@@ -211,7 +368,7 @@ def setup_settings_panel(vertical_container, settings_type='mask'):
|
|
211
368
|
elif settings_type == 'cellpose_all':
|
212
369
|
settings = get_check_cellpose_models_default_settings(settings={})
|
213
370
|
elif settings_type == 'map_barcodes':
|
214
|
-
settings =
|
371
|
+
settings = set_default_generate_barecode_mapping(settings={})
|
215
372
|
elif settings_type == 'regression':
|
216
373
|
settings = get_perform_regression_default_settings(settings={})
|
217
374
|
elif settings_type == 'recruitment':
|
@@ -232,21 +389,28 @@ def setup_settings_panel(vertical_container, settings_type='mask'):
|
|
232
389
|
return scrollable_frame, vars_dict
|
233
390
|
|
234
391
|
def setup_plot_section(vertical_container):
|
235
|
-
global canvas, canvas_widget
|
392
|
+
global canvas, canvas_widget, figures, figure_index
|
393
|
+
|
394
|
+
from .gui_elements import set_element_size
|
395
|
+
|
396
|
+
# Initialize deque for storing figures and the current index
|
397
|
+
figures = deque()
|
398
|
+
figure_index = -1
|
236
399
|
|
237
400
|
# Create a frame for the plot section
|
238
|
-
plot_frame = tk.Frame(vertical_container
|
401
|
+
plot_frame = tk.Frame(vertical_container)
|
239
402
|
vertical_container.add(plot_frame, stretch="always")
|
240
403
|
|
241
404
|
# Set up the plot
|
242
405
|
figure = Figure(figsize=(30, 4), dpi=100)
|
243
406
|
plot = figure.add_subplot(111)
|
244
|
-
plot.plot([], [])
|
407
|
+
plot.plot([], [])
|
245
408
|
plot.axis('off')
|
409
|
+
|
246
410
|
canvas = FigureCanvasTkAgg(figure, master=plot_frame)
|
247
411
|
canvas.get_tk_widget().configure(cursor='arrow', highlightthickness=0)
|
248
412
|
canvas_widget = canvas.get_tk_widget()
|
249
|
-
canvas_widget.grid(row=0, column=0, sticky="nsew")
|
413
|
+
canvas_widget.grid(row=0, column=0, sticky="nsew")
|
250
414
|
|
251
415
|
plot_frame.grid_rowconfigure(0, weight=1)
|
252
416
|
plot_frame.grid_columnconfigure(0, weight=1)
|
@@ -262,6 +426,19 @@ def setup_plot_section(vertical_container):
|
|
262
426
|
style = ttk.Style(vertical_container)
|
263
427
|
_ = set_dark_style(style, containers=containers, widgets=widgets)
|
264
428
|
|
429
|
+
# Add navigation buttons using spacrButton
|
430
|
+
button_frame = tk.Frame(plot_frame, bg=style_out['bg_color'])
|
431
|
+
button_frame.grid(row=1, column=0, sticky='ew', pady=5)
|
432
|
+
|
433
|
+
size_dict = set_element_size()
|
434
|
+
|
435
|
+
btn_size = int(size_dict['btn_size']*0.75)
|
436
|
+
prev_button = spacrButton(button_frame, text="Previous", command=show_previous_figure, bg=style_out['bg_color'], show_text=False, size=btn_size, animation=False)
|
437
|
+
prev_button.pack(side='left', padx=5)
|
438
|
+
|
439
|
+
next_button = spacrButton(button_frame, text="Next", command=show_next_figure, bg=style_out['bg_color'], show_text=False, size=btn_size, animation=False)
|
440
|
+
next_button.pack(side='right', padx=5)
|
441
|
+
|
265
442
|
return canvas, canvas_widget
|
266
443
|
|
267
444
|
def setup_console(vertical_container):
|
@@ -282,7 +459,7 @@ def setup_console(vertical_container):
|
|
282
459
|
|
283
460
|
# Create the scrollable frame (which is a Text widget) with white text
|
284
461
|
family = style_out['font_family']
|
285
|
-
font_size = style_out['font_size']
|
462
|
+
font_size = style_out['font_size']
|
286
463
|
font_loader = style_out['font_loader']
|
287
464
|
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)
|
288
465
|
|
@@ -333,9 +510,6 @@ def setup_button_section(horizontal_container, settings_type='mask', run=True, a
|
|
333
510
|
button_section_height = size_dict['panel_height']
|
334
511
|
button_frame = tk.Frame(horizontal_container, height=button_section_height)
|
335
512
|
|
336
|
-
# Prevent the frame from resizing based on the child widget
|
337
|
-
#button_frame.pack_propagate(False)
|
338
|
-
|
339
513
|
horizontal_container.add(button_frame, stretch="always", sticky="nsew")
|
340
514
|
button_scrollable_frame = spacrFrame(button_frame, scrollbar=False)
|
341
515
|
button_scrollable_frame.grid(row=1, column=0, sticky="nsew")
|
@@ -350,7 +524,7 @@ def setup_button_section(horizontal_container, settings_type='mask', run=True, a
|
|
350
524
|
widgets.append(run_button)
|
351
525
|
btn_col += 1
|
352
526
|
|
353
|
-
if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap']:
|
527
|
+
if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap', 'map_barcodes']:
|
354
528
|
abort_button = spacrButton(button_scrollable_frame.scrollable_frame, text="abort", command=lambda: initiate_abort(), show_text=False, size=size_dict['btn_size'], animation=False)
|
355
529
|
abort_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
|
356
530
|
widgets.append(abort_button)
|
@@ -368,7 +542,7 @@ def setup_button_section(horizontal_container, settings_type='mask', run=True, a
|
|
368
542
|
widgets.append(import_button)
|
369
543
|
btn_row += 1
|
370
544
|
|
371
|
-
# Add the progress bar
|
545
|
+
# Add the batch progress bar
|
372
546
|
progress_bar = spacrProgressBar(button_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate')
|
373
547
|
progress_bar.grid(row=btn_row, column=0, columnspan=7, pady=5, padx=5, sticky='ew')
|
374
548
|
progress_bar.set_label_position() # Set the label position after grid placement
|
@@ -382,7 +556,7 @@ def setup_button_section(horizontal_container, settings_type='mask', run=True, a
|
|
382
556
|
|
383
557
|
return button_scrollable_frame, btn_col
|
384
558
|
|
385
|
-
def setup_usage_panel(horizontal_container, btn_col):
|
559
|
+
def setup_usage_panel(horizontal_container, btn_col, uppdate_frequency):
|
386
560
|
global usage_bars
|
387
561
|
from .gui_elements import set_dark_style, set_element_size
|
388
562
|
|
@@ -408,7 +582,7 @@ def setup_usage_panel(horizontal_container, btn_col):
|
|
408
582
|
bar['value'] = usage
|
409
583
|
|
410
584
|
# Schedule the function to run again after 1000 ms (1 second)
|
411
|
-
parent_frame.after(
|
585
|
+
parent_frame.after(uppdate_frequency, update_usage, ram_bar, vram_bar, gpu_bar, usage_bars, parent_frame)
|
412
586
|
|
413
587
|
size_dict = set_element_size()
|
414
588
|
usage_panel_height = size_dict['panel_height']
|
@@ -425,7 +599,7 @@ def setup_usage_panel(horizontal_container, btn_col):
|
|
425
599
|
widgets = [usage_scrollable_frame.scrollable_frame]
|
426
600
|
|
427
601
|
usage_bars = []
|
428
|
-
max_elements_per_column =
|
602
|
+
max_elements_per_column = 6
|
429
603
|
row = 0
|
430
604
|
col = 0
|
431
605
|
|
@@ -443,7 +617,7 @@ def setup_usage_panel(horizontal_container, btn_col):
|
|
443
617
|
try:
|
444
618
|
ram_info = psutil.virtual_memory()
|
445
619
|
ram_label_text = f"RAM"
|
446
|
-
label =
|
620
|
+
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'])
|
447
621
|
label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
|
448
622
|
ram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
|
449
623
|
ram_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
|
@@ -460,7 +634,7 @@ def setup_usage_panel(horizontal_container, btn_col):
|
|
460
634
|
if gpus:
|
461
635
|
gpu = gpus[0]
|
462
636
|
vram_label_text = f"VRAM"
|
463
|
-
label =
|
637
|
+
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'])
|
464
638
|
label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
|
465
639
|
vram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
|
466
640
|
vram_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
|
@@ -470,7 +644,7 @@ def setup_usage_panel(horizontal_container, btn_col):
|
|
470
644
|
row += 1
|
471
645
|
|
472
646
|
gpu_label_text = f"GPU"
|
473
|
-
label =
|
647
|
+
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'])
|
474
648
|
label.grid(row=row, column=2 * col, pady=5, padx=5, sticky='w')
|
475
649
|
gpu_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
|
476
650
|
gpu_bar.grid(row=row, column=2 * col + 1, pady=5, padx=5, sticky='ew')
|
@@ -490,7 +664,8 @@ def setup_usage_panel(horizontal_container, btn_col):
|
|
490
664
|
if row > 0 and row % max_elements_per_column == 0:
|
491
665
|
col += 1
|
492
666
|
row = 0
|
493
|
-
|
667
|
+
|
668
|
+
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'])
|
494
669
|
label.grid(row=row, column=2 * col, pady=2, padx=5, sticky='w')
|
495
670
|
bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
|
496
671
|
bar.grid(row=row, column=2 * col + 1, pady=2, padx=5, sticky='ew')
|
@@ -527,11 +702,10 @@ def initiate_abort():
|
|
527
702
|
|
528
703
|
thread_control = {"run_thread": None, "stop_requested": False}
|
529
704
|
|
530
|
-
|
531
705
|
def start_process(q=None, fig_queue=None, settings_type='mask'):
|
532
706
|
global thread_control, vars_dict, parent_frame
|
533
707
|
from .settings import check_settings, expected_types
|
534
|
-
from .gui_utils import run_function_gui
|
708
|
+
from .gui_utils import run_function_gui, set_high_priority, set_cpu_affinity, initialize_cuda
|
535
709
|
|
536
710
|
if q is None:
|
537
711
|
q = Queue()
|
@@ -549,21 +723,32 @@ def start_process(q=None, fig_queue=None, settings_type='mask'):
|
|
549
723
|
stop_requested = Value('i', 0)
|
550
724
|
thread_control["stop_requested"] = stop_requested
|
551
725
|
|
726
|
+
# Initialize CUDA in the main process
|
727
|
+
initialize_cuda()
|
728
|
+
|
552
729
|
process_args = (settings_type, settings, q, fig_queue, stop_requested)
|
553
|
-
if settings_type in [
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
730
|
+
if settings_type in ['mask', 'umap', 'measure', 'simulation', 'sequencing',
|
731
|
+
'classify', 'cellpose_dataset', 'train_cellpose', 'ml_analyze', 'cellpose_masks', 'cellpose_all', 'map_barcodes',
|
732
|
+
'regression', 'recruitment', 'plaques', 'cellpose_compare', 'vision_scores', 'vision_dataset']:
|
733
|
+
|
734
|
+
# Start the process
|
735
|
+
process = Process(target=run_function_gui, args=process_args)
|
736
|
+
process.start()
|
737
|
+
|
738
|
+
# Set high priority for the process
|
739
|
+
#set_high_priority(process)
|
740
|
+
|
741
|
+
# Set CPU affinity if necessary
|
742
|
+
set_cpu_affinity(process)
|
743
|
+
|
744
|
+
# Store the process in thread_control for future reference
|
745
|
+
thread_control["run_thread"] = process
|
560
746
|
else:
|
561
747
|
q.put(f"Error: Unknown settings type '{settings_type}'")
|
562
748
|
return
|
563
|
-
thread_control["run_thread"].start()
|
564
749
|
|
565
750
|
def process_console_queue():
|
566
|
-
global q, console_output, parent_frame, progress_bar
|
751
|
+
global q, console_output, parent_frame, progress_bar, process_console_queue
|
567
752
|
|
568
753
|
# Initialize function attribute if it doesn't exist
|
569
754
|
if not hasattr(process_console_queue, "completed_tasks"):
|
@@ -576,21 +761,21 @@ def process_console_queue():
|
|
576
761
|
while not q.empty():
|
577
762
|
message = q.get_nowait()
|
578
763
|
clean_message = ansi_escape_pattern.sub('', message)
|
579
|
-
console_output.insert(tk.END, clean_message + "\n")
|
580
|
-
console_output.see(tk.END)
|
764
|
+
#console_output.insert(tk.END, clean_message + "\n")
|
765
|
+
#console_output.see(tk.END)
|
581
766
|
|
582
767
|
# Check if the message contains progress information
|
583
768
|
if clean_message.startswith("Progress:"):
|
584
769
|
try:
|
585
770
|
# Extract the progress information
|
586
|
-
match = re.search(r'Progress: (\d+)/(\d+), operation_type: ([\w\s]*)(.*)', clean_message)
|
771
|
+
match = re.search(r'Progress: (\d+)/(\d+), operation_type: ([\w\s]*),(.*)', clean_message)
|
587
772
|
|
588
773
|
if match:
|
589
774
|
current_progress = int(match.group(1))
|
590
775
|
total_progress = int(match.group(2))
|
591
776
|
operation_type = match.group(3).strip()
|
592
|
-
|
593
|
-
|
777
|
+
additional_info = match.group(4).strip() # Capture everything after operation_type
|
778
|
+
|
594
779
|
# Check if the maximum value has changed
|
595
780
|
if process_console_queue.current_maximum != total_progress:
|
596
781
|
process_console_queue.current_maximum = total_progress
|
@@ -601,55 +786,77 @@ def process_console_queue():
|
|
601
786
|
|
602
787
|
# Calculate the unique progress count
|
603
788
|
unique_progress_count = len(np.unique(process_console_queue.completed_tasks))
|
604
|
-
|
789
|
+
|
605
790
|
# Update the progress bar
|
606
791
|
if progress_bar:
|
607
792
|
progress_bar['maximum'] = total_progress
|
608
793
|
progress_bar['value'] = unique_progress_count
|
609
794
|
|
610
|
-
#
|
795
|
+
# Store operation type and additional info
|
611
796
|
if operation_type:
|
612
797
|
progress_bar.operation_type = operation_type
|
613
|
-
|
614
|
-
time_image_match = re.search(r'Time/image: ([\d.]+) sec', time_info)
|
615
|
-
if time_image_match:
|
616
|
-
progress_bar.time_image = float(time_image_match.group(1))
|
617
|
-
|
618
|
-
time_batch_match = re.search(r'Time/batch: ([\d.]+) sec', time_info)
|
619
|
-
if time_batch_match:
|
620
|
-
progress_bar.time_batch = float(time_batch_match.group(1))
|
621
|
-
|
622
|
-
time_left_match = re.search(r'Time_left: ([\d.]+) min', time_info)
|
623
|
-
if time_left_match:
|
624
|
-
progress_bar.time_left = float(time_left_match.group(1))
|
798
|
+
progress_bar.additional_info = additional_info
|
625
799
|
|
626
800
|
# Update the progress label
|
627
801
|
if progress_bar.progress_label:
|
628
802
|
progress_bar.update_label()
|
629
|
-
|
803
|
+
|
630
804
|
# Clear completed tasks when progress is complete
|
631
805
|
if unique_progress_count >= total_progress:
|
632
806
|
process_console_queue.completed_tasks.clear()
|
633
807
|
|
634
808
|
except Exception as e:
|
635
809
|
print(f"Error parsing progress message: {e}")
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
810
|
+
else:
|
811
|
+
# Only insert messages that do not start with "Progress:"
|
812
|
+
console_output.insert(tk.END, clean_message + "\n")
|
813
|
+
console_output.see(tk.END)
|
640
814
|
|
641
|
-
after_id = console_output.after(
|
815
|
+
after_id = console_output.after(uppdate_frequency, process_console_queue)
|
642
816
|
parent_frame.after_tasks.append(after_id)
|
643
817
|
|
818
|
+
def main_thread_update_function(root, q, fig_queue, canvas_widget):
|
819
|
+
global uppdate_frequency
|
820
|
+
try:
|
821
|
+
while not q.empty():
|
822
|
+
message = q.get_nowait()
|
823
|
+
except Exception as e:
|
824
|
+
print(f"Error updating GUI canvas: {e}")
|
825
|
+
finally:
|
826
|
+
root.after(uppdate_frequency, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget))
|
644
827
|
|
645
828
|
def initiate_root(parent, settings_type='mask'):
|
646
|
-
|
647
|
-
|
829
|
+
"""
|
830
|
+
Initializes the root window and sets up the GUI components based on the specified settings type.
|
831
|
+
|
832
|
+
Args:
|
833
|
+
parent (tkinter.Tk or tkinter.Toplevel): The parent window for the GUI.
|
834
|
+
settings_type (str, optional): The type of settings to be displayed in the GUI. Defaults to 'mask'.
|
835
|
+
|
836
|
+
Returns:
|
837
|
+
tuple: A tuple containing the parent frame and the dictionary of variables used in the GUI.
|
838
|
+
"""
|
839
|
+
|
840
|
+
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, fig_memory_limit, figure_current_memory_usage
|
841
|
+
|
842
|
+
from .gui_utils import setup_frame
|
648
843
|
from .settings import descriptions
|
649
844
|
|
845
|
+
uppdate_frequency = 1000
|
846
|
+
|
847
|
+
# Start tracemalloc and initialize global variables
|
848
|
+
tracemalloc.start()
|
849
|
+
|
650
850
|
set_start_method('spawn', force=True)
|
851
|
+
#set_start_method('forkserver', force=True)
|
651
852
|
print("Initializing root with settings_type:", settings_type)
|
652
853
|
|
854
|
+
# Initialize global variables
|
855
|
+
figures = deque()
|
856
|
+
figure_index = -1
|
857
|
+
fig_memory_limit = 200 * 1024 * 1024 # 200 MB limit
|
858
|
+
figure_current_memory_usage = 0
|
859
|
+
|
653
860
|
parent_frame = parent
|
654
861
|
|
655
862
|
if not isinstance(parent_frame, (tk.Tk, tk.Toplevel)):
|
@@ -678,9 +885,9 @@ def initiate_root(parent, settings_type='mask'):
|
|
678
885
|
canvas, canvas_widget = setup_plot_section(vertical_container)
|
679
886
|
console_output, _ = setup_console(vertical_container)
|
680
887
|
button_scrollable_frame, btn_col = setup_button_section(horizontal_container, settings_type)
|
681
|
-
_, usage_bars, btn_col = setup_usage_panel(horizontal_container, btn_col)
|
888
|
+
_, usage_bars, btn_col = setup_usage_panel(horizontal_container, btn_col, uppdate_frequency)
|
682
889
|
|
683
|
-
set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars)
|
890
|
+
set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, figures, figure_index, progress_bar, usage_bars, fig_memory_limit, figure_current_memory_usage)
|
684
891
|
description_text = descriptions.get(settings_type, "No description available for this module.")
|
685
892
|
|
686
893
|
q.put(f"Console")
|
@@ -689,7 +896,7 @@ def initiate_root(parent, settings_type='mask'):
|
|
689
896
|
|
690
897
|
process_console_queue()
|
691
898
|
process_fig_queue()
|
692
|
-
after_id = parent_window.after(
|
899
|
+
after_id = parent_window.after(uppdate_frequency, lambda: main_thread_update_function(parent_window, q, fig_queue, canvas_widget))
|
693
900
|
parent_window.after_tasks.append(after_id)
|
694
901
|
|
695
902
|
print("Root initialization complete")
|