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/__init__.py +1 -11
- spacr/core.py +226 -287
- spacr/deep_spacr.py +248 -269
- spacr/gui.py +41 -19
- spacr/gui_core.py +404 -151
- spacr/gui_elements.py +778 -179
- spacr/gui_utils.py +163 -106
- spacr/io.py +116 -45
- spacr/measure.py +1 -0
- spacr/plot.py +51 -5
- spacr/sequencing.py +477 -587
- spacr/settings.py +211 -66
- spacr/utils.py +34 -14
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/METADATA +46 -39
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/RECORD +19 -19
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/WHEEL +1 -1
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/LICENSE +0 -0
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.5.dist-info → spacr-0.2.8.dist-info}/top_level.txt +0 -0
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
|
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
|
-
|
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 .
|
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
|
76
|
-
global canvas,
|
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
|
-
|
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.
|
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
|
-
|
90
|
-
|
91
|
-
|
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
|
-
#
|
96
|
-
fig
|
97
|
-
fig.set_facecolor('black')
|
298
|
+
# Standardize the figure appearance before adding it to the list
|
299
|
+
fig = standardize_figure(fig)
|
98
300
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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(
|
314
|
+
after_id = canvas_widget.after(uppdate_frequency, process_fig_queue)
|
107
315
|
parent_frame.after_tasks.append(after_id)
|
108
316
|
|
109
|
-
def
|
110
|
-
global
|
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 =
|
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,
|
176
|
-
from .gui_utils import convert_settings_dict_for_gui
|
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 =
|
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 =
|
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
|
-
|
286
|
-
|
287
|
-
console_output = tk.Text(console_frame, bg=style_out['bg_color'], fg=style_out['fg_color'], font=
|
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
|
326
|
-
from .
|
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
|
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=
|
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 .
|
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(
|
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 =
|
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
|
-
|
436
|
-
|
437
|
-
style.configure("usage.TLabel", font=
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
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(
|
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
|
-
|
644
|
-
|
645
|
-
|
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(
|
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")
|