spacr 0.2.41__py3-none-any.whl → 0.2.45__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 +46 -59
- spacr/gui.py +20 -38
- spacr/gui_core.py +345 -614
- spacr/gui_elements.py +175 -24
- spacr/gui_utils.py +310 -62
- spacr/io.py +29 -38
- spacr/plot.py +106 -0
- spacr/resources/icons/logo.pdf +2786 -6
- spacr/resources/icons/logo_spacr.png +0 -0
- spacr/resources/icons/logo_spacr_1.png +0 -0
- spacr/settings.py +0 -2
- spacr/utils.py +4 -24
- {spacr-0.2.41.dist-info → spacr-0.2.45.dist-info}/METADATA +1 -1
- {spacr-0.2.41.dist-info → spacr-0.2.45.dist-info}/RECORD +18 -16
- {spacr-0.2.41.dist-info → spacr-0.2.45.dist-info}/LICENSE +0 -0
- {spacr-0.2.41.dist-info → spacr-0.2.45.dist-info}/WHEEL +0 -0
- {spacr-0.2.41.dist-info → spacr-0.2.45.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.41.dist-info → spacr-0.2.45.dist-info}/top_level.txt +0 -0
spacr/gui_core.py
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
import os, traceback, ctypes,
|
2
|
-
import matplotlib.pyplot as plt
|
3
|
-
matplotlib.use('Agg')
|
1
|
+
import os, traceback, ctypes, requests, csv, time, requests, re
|
4
2
|
import tkinter as tk
|
5
3
|
from tkinter import ttk
|
6
4
|
from tkinter import filedialog
|
@@ -9,14 +7,9 @@ from multiprocessing.sharedctypes import Synchronized
|
|
9
7
|
from tkinter import ttk, scrolledtext
|
10
8
|
from matplotlib.figure import Figure
|
11
9
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
12
|
-
from huggingface_hub import list_repo_files
|
13
10
|
import numpy as np
|
14
|
-
|
15
|
-
import psutil, gpustat
|
11
|
+
import psutil
|
16
12
|
import GPUtil
|
17
|
-
from threading import Thread
|
18
|
-
from time import sleep
|
19
|
-
|
20
13
|
|
21
14
|
try:
|
22
15
|
ctypes.windll.shcore.SetProcessDpiAwareness(True)
|
@@ -24,7 +17,7 @@ except AttributeError:
|
|
24
17
|
pass
|
25
18
|
|
26
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
|
27
|
-
from .gui_elements import spacrProgressBar, spacrButton, spacrLabel, spacrFrame, spacrDropdownMenu ,set_dark_style
|
20
|
+
from .gui_elements import spacrProgressBar, spacrButton, spacrLabel, spacrFrame, spacrDropdownMenu ,set_dark_style
|
28
21
|
|
29
22
|
# Define global variables
|
30
23
|
q = None
|
@@ -39,143 +32,92 @@ fig_queue = None
|
|
39
32
|
|
40
33
|
thread_control = {"run_thread": None, "stop_requested": False}
|
41
34
|
|
42
|
-
def
|
43
|
-
global
|
44
|
-
|
45
|
-
|
46
|
-
if
|
47
|
-
|
48
|
-
thread_control["run_thread"].join()
|
49
|
-
thread_control["run_thread"] = None
|
50
|
-
|
51
|
-
def spacrFigShow(fig_queue=None):
|
52
|
-
"""
|
53
|
-
Replacement for plt.show() that queues figures instead of displaying them.
|
54
|
-
"""
|
55
|
-
fig = plt.gcf()
|
56
|
-
if fig_queue:
|
57
|
-
fig_queue.put(fig)
|
58
|
-
else:
|
59
|
-
fig.show()
|
60
|
-
plt.close(fig)
|
35
|
+
def toggle_settings(button_scrollable_frame):
|
36
|
+
global vars_dict
|
37
|
+
from .settings import categories
|
38
|
+
from .gui_utils import hide_all_settings
|
39
|
+
if vars_dict is None:
|
40
|
+
raise ValueError("vars_dict is not initialized.")
|
61
41
|
|
62
|
-
|
42
|
+
active_categories = set()
|
63
43
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
44
|
+
def toggle_category(settings):
|
45
|
+
for setting in settings:
|
46
|
+
if setting in vars_dict:
|
47
|
+
label, widget, _ = vars_dict[setting]
|
48
|
+
if widget.grid_info():
|
49
|
+
label.grid_remove()
|
50
|
+
widget.grid_remove()
|
51
|
+
else:
|
52
|
+
label.grid()
|
53
|
+
widget.grid()
|
54
|
+
|
55
|
+
def on_category_select(selected_category):
|
56
|
+
if selected_category == "Select Category":
|
57
|
+
return
|
58
|
+
if selected_category in categories:
|
59
|
+
toggle_category(categories[selected_category])
|
60
|
+
if selected_category in active_categories:
|
61
|
+
active_categories.remove(selected_category)
|
62
|
+
else:
|
63
|
+
active_categories.add(selected_category)
|
64
|
+
category_dropdown.update_styles(active_categories)
|
65
|
+
category_var.set("Select Category")
|
72
66
|
|
73
|
-
|
74
|
-
|
75
|
-
|
67
|
+
category_var = tk.StringVar()
|
68
|
+
non_empty_categories = [category for category, settings in categories.items() if any(setting in vars_dict for setting in settings)]
|
69
|
+
category_dropdown = spacrDropdownMenu(button_scrollable_frame.scrollable_frame, category_var, non_empty_categories, command=on_category_select)
|
70
|
+
category_dropdown.grid(row=0, column=4, sticky="ew", pady=2, padx=2)
|
71
|
+
vars_dict = hide_all_settings(vars_dict, categories)
|
72
|
+
|
73
|
+
def process_fig_queue():
|
74
|
+
global canvas, fig_queue, canvas_widget, parent_frame
|
75
|
+
|
76
|
+
def clear_canvas(canvas):
|
77
|
+
for ax in canvas.figure.get_axes():
|
78
|
+
ax.clear()
|
79
|
+
canvas.draw_idle()
|
76
80
|
|
77
81
|
try:
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
from .gui_utils import process_stdout_stderr
|
93
|
-
from .core import preprocess_generate_masks, generate_ml_scores, identify_masks_finetune, check_cellpose_models, analyze_recruitment, train_cellpose, compare_cellpose_masks, analyze_plaques, generate_dataset, apply_model_to_tar
|
94
|
-
from .io import generate_cellpose_train_test
|
95
|
-
from .measure import measure_crop
|
96
|
-
from .sim import run_multiple_simulations
|
97
|
-
from .deep_spacr import train_test_model
|
98
|
-
from .sequencing import analyze_reads, map_barcodes_folder, perform_regression
|
99
|
-
process_stdout_stderr(q)
|
100
|
-
|
101
|
-
print(f'run_function_gui settings_type: {settings_type}')
|
102
|
-
|
103
|
-
if settings_type == 'mask':
|
104
|
-
function = preprocess_generate_masks
|
105
|
-
imports = 2
|
106
|
-
elif settings_type == 'measure':
|
107
|
-
function = measure_crop
|
108
|
-
imports = 1
|
109
|
-
elif settings_type == 'simulation':
|
110
|
-
function = run_multiple_simulations
|
111
|
-
imports = 1
|
112
|
-
elif settings_type == 'sequencing':
|
113
|
-
function = analyze_reads
|
114
|
-
imports = 1
|
115
|
-
elif settings_type == 'classify':
|
116
|
-
function = train_test_model
|
117
|
-
imports = 2
|
118
|
-
elif settings_type == 'train_cellpose':
|
119
|
-
function = train_cellpose
|
120
|
-
imports = 1
|
121
|
-
elif settings_type == 'ml_analyze':
|
122
|
-
function = generate_ml_scores
|
123
|
-
imports = 2
|
124
|
-
elif settings_type == 'cellpose_masks':
|
125
|
-
function = identify_masks_finetune
|
126
|
-
imports = 1
|
127
|
-
elif settings_type == 'cellpose_all':
|
128
|
-
function = check_cellpose_models
|
129
|
-
imports = 1
|
130
|
-
elif settings_type == 'map_barcodes':
|
131
|
-
function = map_barcodes_folder
|
132
|
-
imports = 2
|
133
|
-
elif settings_type == 'regression':
|
134
|
-
function = perform_regression
|
135
|
-
imports = 2
|
136
|
-
elif settings_type == 'recruitment':
|
137
|
-
function = analyze_recruitment
|
138
|
-
imports = 2
|
139
|
-
else:
|
140
|
-
raise ValueError(f"Invalid settings type: {settings_type}")
|
141
|
-
try:
|
142
|
-
function_gui_wrapper(function, settings, q, fig_queue, imports)
|
82
|
+
while not fig_queue.empty():
|
83
|
+
clear_canvas(canvas)
|
84
|
+
fig = fig_queue.get_nowait()
|
85
|
+
for ax in fig.get_axes():
|
86
|
+
ax.set_xticks([]) # Remove x-axis ticks
|
87
|
+
ax.set_yticks([]) # Remove y-axis ticks
|
88
|
+
ax.xaxis.set_visible(False) # Hide the x-axis
|
89
|
+
ax.yaxis.set_visible(False) # Hide the y-axis
|
90
|
+
fig.tight_layout()
|
91
|
+
fig.set_facecolor('black')
|
92
|
+
canvas.figure = fig
|
93
|
+
fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
|
94
|
+
fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
|
95
|
+
canvas.draw_idle()
|
143
96
|
except Exception as e:
|
144
|
-
q.put(f"Error during processing: {e}")
|
145
97
|
traceback.print_exc()
|
146
98
|
finally:
|
147
|
-
|
148
|
-
|
149
|
-
def start_process(q=None, fig_queue=None, settings_type='mask'):
|
150
|
-
global thread_control, vars_dict
|
151
|
-
from .settings import check_settings, expected_types
|
99
|
+
after_id = canvas_widget.after(100, process_fig_queue)
|
100
|
+
parent_frame.after_tasks.append(after_id)
|
152
101
|
|
153
|
-
if q is None:
|
154
|
-
q = Queue()
|
155
|
-
if fig_queue is None:
|
156
|
-
fig_queue = Queue()
|
157
102
|
|
158
|
-
try:
|
159
|
-
settings = check_settings(vars_dict, expected_types, q)
|
160
|
-
except ValueError as e:
|
161
|
-
q.put(f"Error: {e}")
|
162
|
-
return
|
163
103
|
|
164
|
-
if thread_control.get("run_thread") is not None:
|
165
|
-
initiate_abort()
|
166
|
-
|
167
|
-
stop_requested = Value('i', 0)
|
168
|
-
thread_control["stop_requested"] = stop_requested
|
169
104
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
105
|
+
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):
|
106
|
+
global thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars
|
107
|
+
thread_control = thread_control_var
|
108
|
+
q = q_var
|
109
|
+
console_output = console_output_var
|
110
|
+
parent_frame = parent_frame_var
|
111
|
+
vars_dict = vars_dict_var
|
112
|
+
canvas = canvas_var
|
113
|
+
canvas_widget = canvas_widget_var
|
114
|
+
scrollable_frame = scrollable_frame_var
|
115
|
+
fig_queue = fig_queue_var
|
116
|
+
progress_bar = progress_bar_var
|
117
|
+
usage_bars = usage_bars_var
|
177
118
|
|
178
119
|
def import_settings(settings_type='mask'):
|
120
|
+
from .gui_utils import convert_settings_dict_for_gui, hide_all_settings
|
179
121
|
global vars_dict, scrollable_frame, button_scrollable_frame
|
180
122
|
from .settings import generate_fields
|
181
123
|
|
@@ -224,74 +166,23 @@ def import_settings(settings_type='mask'):
|
|
224
166
|
vars_dict = generate_fields(new_settings, scrollable_frame)
|
225
167
|
vars_dict = hide_all_settings(vars_dict, categories=None)
|
226
168
|
|
227
|
-
def
|
228
|
-
from torchvision import models as torch_models
|
229
|
-
torchvision_models = [name for name, obj in torch_models.__dict__.items() if callable(obj)]
|
230
|
-
chans = ['0', '1', '2', '3', '4', '5', '6', '7', '8', None]
|
231
|
-
chans_v2 = [0, 1, 2, 3, None]
|
232
|
-
variables = {}
|
233
|
-
special_cases = {
|
234
|
-
'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
|
235
|
-
'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
|
236
|
-
'channel_dims': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
|
237
|
-
'cell_mask_dim': ('combo', chans, None),
|
238
|
-
'cell_chann_dim': ('combo', chans, None),
|
239
|
-
'nucleus_mask_dim': ('combo', chans, None),
|
240
|
-
'nucleus_chann_dim': ('combo', chans, None),
|
241
|
-
'pathogen_mask_dim': ('combo', chans, None),
|
242
|
-
'pathogen_chann_dim': ('combo', chans, None),
|
243
|
-
'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
|
244
|
-
'magnification': ('combo', [20, 40, 60], 20),
|
245
|
-
'nucleus_channel': ('combo', chans_v2, None),
|
246
|
-
'cell_channel': ('combo', chans_v2, None),
|
247
|
-
'channel_of_interest': ('combo', chans_v2, None),
|
248
|
-
'pathogen_channel': ('combo', chans_v2, None),
|
249
|
-
'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
|
250
|
-
'train_mode': ('combo', ['erm', 'irm'], 'erm'),
|
251
|
-
'clustering': ('combo', ['dbscan', 'kmean'], 'dbscan'),
|
252
|
-
'reduction_method': ('combo', ['umap', 'tsne'], 'umap'),
|
253
|
-
'model_name': ('combo', ['cyto', 'cyto_2', 'cyto_3', 'nuclei'], 'cyto'),
|
254
|
-
'regression_type': ('combo', ['ols','gls','wls','rlm','glm','mixed','quantile','logit','probit','poisson','lasso','ridge'], 'ols'),
|
255
|
-
'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
|
256
|
-
'model_type': ('combo', torchvision_models, 'resnet50'),
|
257
|
-
'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
|
258
|
-
'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
|
259
|
-
'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
|
260
|
-
'normalize_by': ('combo', ['fov', 'png'], 'png'),
|
261
|
-
'agg_type': ('combo', ['mean', 'median'], 'mean'),
|
262
|
-
'grouping': ('combo', ['mean', 'median'], 'mean'),
|
263
|
-
'min_max': ('combo', ['allq', 'all'], 'allq'),
|
264
|
-
'transform': ('combo', ['log', 'sqrt', 'square', None], None)
|
265
|
-
}
|
266
|
-
|
267
|
-
for key, value in settings.items():
|
268
|
-
if key in special_cases:
|
269
|
-
variables[key] = special_cases[key]
|
270
|
-
elif isinstance(value, bool):
|
271
|
-
variables[key] = ('check', None, value)
|
272
|
-
elif isinstance(value, int) or isinstance(value, float):
|
273
|
-
variables[key] = ('entry', None, value)
|
274
|
-
elif isinstance(value, str):
|
275
|
-
variables[key] = ('entry', None, value)
|
276
|
-
elif value is None:
|
277
|
-
variables[key] = ('entry', None, value)
|
278
|
-
elif isinstance(value, list):
|
279
|
-
variables[key] = ('entry', None, str(value))
|
280
|
-
else:
|
281
|
-
variables[key] = ('entry', None, str(value))
|
282
|
-
return variables
|
283
|
-
|
284
|
-
def setup_settings_panel(vertical_container, settings_type='mask', window_dimensions=[500, 1000]):
|
169
|
+
def setup_settings_panel(vertical_container, settings_type='mask'):
|
285
170
|
global vars_dict, scrollable_frame
|
286
171
|
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
|
172
|
+
from .gui_utils import convert_settings_dict_for_gui, set_element_size
|
173
|
+
|
174
|
+
size_dict = set_element_size(vertical_container)
|
175
|
+
settings_width = size_dict['settings_width']
|
176
|
+
|
177
|
+
# Create a PanedWindow for the settings panel
|
178
|
+
settings_paned_window = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL)
|
179
|
+
vertical_container.add(settings_paned_window, stretch="always")
|
180
|
+
|
181
|
+
settings_frame = tk.Frame(settings_paned_window, width=settings_width)
|
182
|
+
settings_frame.pack_propagate(False) # Prevent the frame from resizing based on its children
|
287
183
|
|
288
|
-
|
289
|
-
height = window_dimensions[1]
|
184
|
+
settings_paned_window.add(settings_frame)
|
290
185
|
|
291
|
-
settings_frame = tk.Frame(vertical_container)
|
292
|
-
vertical_container.add(settings_frame, stretch="always")
|
293
|
-
settings_label = spacrLabel(settings_frame, text="Settings", anchor='center', justify='center', align="center")
|
294
|
-
settings_label.grid(row=0, column=0, pady=10, padx=10)
|
295
186
|
scrollable_frame = spacrFrame(settings_frame)
|
296
187
|
scrollable_frame.grid(row=1, column=0, sticky="nsew")
|
297
188
|
settings_frame.grid_rowconfigure(1, weight=1)
|
@@ -328,7 +219,7 @@ def setup_settings_panel(vertical_container, settings_type='mask', window_dimens
|
|
328
219
|
vars_dict = generate_fields(variables, scrollable_frame)
|
329
220
|
|
330
221
|
containers = [settings_frame]
|
331
|
-
widgets = [
|
222
|
+
widgets = [scrollable_frame]
|
332
223
|
|
333
224
|
style = ttk.Style(vertical_container)
|
334
225
|
_ = set_dark_style(style, containers=containers, widgets=widgets)
|
@@ -362,19 +253,50 @@ def setup_plot_section(vertical_container):
|
|
362
253
|
|
363
254
|
def setup_console(vertical_container):
|
364
255
|
global console_output
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
256
|
+
|
257
|
+
# Create a frame to hold the console and button sections
|
258
|
+
console_button_frame = tk.Frame(vertical_container)
|
259
|
+
vertical_container.add(console_button_frame, stretch="always")
|
260
|
+
|
261
|
+
# Create a PanedWindow for resizing height
|
262
|
+
console_paned_window = tk.PanedWindow(console_button_frame, orient=tk.VERTICAL, bg='black')
|
263
|
+
console_paned_window.pack(fill=tk.BOTH, expand=True)
|
264
|
+
|
265
|
+
# Create the main console frame using spacrFrame with textbox=True
|
266
|
+
console_frame = spacrFrame(console_paned_window, textbox=True)
|
267
|
+
console_paned_window.add(console_frame, stretch="always")
|
268
|
+
|
269
|
+
# Assign the scrollable frame (which is a Text widget) to console_output
|
270
|
+
console_output = console_frame.scrollable_frame
|
271
|
+
|
272
|
+
# Ensure the Text widget spans the entire console frame
|
273
|
+
console_output.grid(row=0, column=0, sticky="nsew")
|
274
|
+
|
275
|
+
# Configure the grid to allow expansion
|
276
|
+
console_frame.grid_rowconfigure(0, weight=1)
|
372
277
|
console_frame.grid_columnconfigure(0, weight=1)
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
278
|
+
|
279
|
+
# Create a lower frame to act as the anchor point
|
280
|
+
lower_frame = tk.Frame(console_paned_window)
|
281
|
+
console_paned_window.add(lower_frame, minsize=10) # Adjust minsize to ensure usability
|
282
|
+
|
283
|
+
# Dynamically adjust the height and width of the console_frame
|
284
|
+
def adjust_frame_size(event):
|
285
|
+
console_frame.update_idletasks()
|
286
|
+
new_width = console_paned_window.winfo_width()
|
287
|
+
new_height = console_paned_window.winfo_height()
|
288
|
+
console_frame.config(width=new_width, height=new_height)
|
289
|
+
console_output.config(width=new_width, height=new_height)
|
290
|
+
|
291
|
+
# Bind the configure event to dynamically adjust size
|
292
|
+
console_paned_window.bind("<Configure>", adjust_frame_size)
|
293
|
+
|
294
|
+
# Apply dark style to the PanedWindow and lower frame
|
295
|
+
style = ttk.Style()
|
296
|
+
set_dark_style(style, containers=[console_paned_window, lower_frame])
|
297
|
+
|
298
|
+
return console_output, console_frame
|
299
|
+
|
378
300
|
|
379
301
|
def setup_progress_frame(vertical_container):
|
380
302
|
global progress_output
|
@@ -394,139 +316,56 @@ def setup_progress_frame(vertical_container):
|
|
394
316
|
_ = set_dark_style(style, containers=containers, widgets=widgets)
|
395
317
|
return progress_output
|
396
318
|
|
397
|
-
def
|
398
|
-
global vars_dict,
|
399
|
-
|
400
|
-
|
401
|
-
dataset_subfolder = "plate1"
|
402
|
-
local_dir = os.path.join(os.path.expanduser("~"), "datasets") # Set to the home directory
|
403
|
-
|
404
|
-
# Download the dataset
|
405
|
-
try:
|
406
|
-
dataset_path = download_dataset(dataset_repo_id, dataset_subfolder, local_dir)
|
407
|
-
if 'src' in vars_dict:
|
408
|
-
vars_dict['src'][2].set(dataset_path)
|
409
|
-
q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
|
410
|
-
q.put(f"Dataset downloaded to: {dataset_path}\n")
|
411
|
-
except Exception as e:
|
412
|
-
q.put(f"Failed to download dataset: {e}\n")
|
413
|
-
|
414
|
-
# Download the settings files
|
415
|
-
try:
|
416
|
-
settings_path = download_dataset(settings_repo_id, "", local_dir)
|
417
|
-
q.put(f"Settings downloaded to: {settings_path}\n")
|
418
|
-
except Exception as e:
|
419
|
-
q.put(f"Failed to download settings: {e}\n")
|
420
|
-
|
421
|
-
def download_dataset(repo_id, subfolder, local_dir=None, retries=5, delay=5):
|
422
|
-
global q
|
423
|
-
"""
|
424
|
-
Downloads a dataset or settings files from Hugging Face and returns the local path.
|
425
|
-
|
426
|
-
Args:
|
427
|
-
repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito' or 'einarolafsson/spacr_settings').
|
428
|
-
subfolder (str): The subfolder path within the repository (e.g., 'plate1' or the settings subfolder).
|
429
|
-
local_dir (str): The local directory where the files will be saved. Defaults to the user's home directory.
|
430
|
-
retries (int): Number of retry attempts in case of failure.
|
431
|
-
delay (int): Delay in seconds between retries.
|
432
|
-
|
433
|
-
Returns:
|
434
|
-
str: The local path to the downloaded files.
|
435
|
-
"""
|
436
|
-
if local_dir is None:
|
437
|
-
local_dir = os.path.join(os.path.expanduser("~"), "datasets")
|
438
|
-
|
439
|
-
local_subfolder_dir = os.path.join(local_dir, subfolder if subfolder else "settings")
|
440
|
-
if not os.path.exists(local_subfolder_dir):
|
441
|
-
os.makedirs(local_subfolder_dir)
|
442
|
-
elif len(os.listdir(local_subfolder_dir)) > 0:
|
443
|
-
q.put(f"Files already downloaded to: {local_subfolder_dir}")
|
444
|
-
return local_subfolder_dir
|
445
|
-
|
446
|
-
attempt = 0
|
447
|
-
while attempt < retries:
|
448
|
-
try:
|
449
|
-
files = list_repo_files(repo_id, repo_type="dataset")
|
450
|
-
subfolder_files = [file for file in files if file.startswith(subfolder) or (subfolder == "" and file.endswith('.csv'))]
|
451
|
-
|
452
|
-
for file_name in subfolder_files:
|
453
|
-
for download_attempt in range(retries):
|
454
|
-
try:
|
455
|
-
url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
|
456
|
-
response = requests.get(url, stream=True)
|
457
|
-
response.raise_for_status()
|
458
|
-
|
459
|
-
local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
|
460
|
-
with open(local_file_path, 'wb') as file:
|
461
|
-
for chunk in response.iter_content(chunk_size=8192):
|
462
|
-
file.write(chunk)
|
463
|
-
q.put(f"Downloaded file: {file_name}")
|
464
|
-
break
|
465
|
-
except (requests.HTTPError, requests.Timeout) as e:
|
466
|
-
q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
|
467
|
-
time.sleep(delay)
|
468
|
-
else:
|
469
|
-
raise Exception(f"Failed to download {file_name} after multiple attempts.")
|
470
|
-
|
471
|
-
return local_subfolder_dir
|
472
|
-
|
473
|
-
except (requests.HTTPError, requests.Timeout) as e:
|
474
|
-
q.put(f"Error downloading files: {e}. Retrying in {delay} seconds...")
|
475
|
-
attempt += 1
|
476
|
-
time.sleep(delay)
|
477
|
-
|
478
|
-
raise Exception("Failed to download files after multiple attempts.")
|
479
|
-
|
319
|
+
def setup_button_section(horizontal_container, settings_type='mask', run=True, abort=True, download=True, import_btn=True):
|
320
|
+
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
|
321
|
+
from .gui_utils import set_element_size, download_hug_dataset
|
322
|
+
from .settings import categories
|
480
323
|
|
481
324
|
|
482
|
-
def setup_button_section(horizontal_container, settings_type='mask', run=True, abort=True, download=True, import_btn=True):
|
483
|
-
global button_frame, button_scrollable_frame, run_button, abort_button, download_dataset_button, import_button, q, fig_queue, vars_dict, progress_bar
|
484
|
-
from .gui_utils import set_element_size
|
485
325
|
size_dict = set_element_size(horizontal_container)
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
button_frame.
|
326
|
+
button_section_height = size_dict['panel_height']
|
327
|
+
button_frame = tk.Frame(horizontal_container, height=button_section_height)
|
328
|
+
|
329
|
+
# Prevent the frame from resizing based on the child widget
|
330
|
+
button_frame.pack_propagate(False)
|
491
331
|
|
492
|
-
|
493
|
-
|
494
|
-
button_scrollable_frame = spacrFrame(button_frame)
|
332
|
+
horizontal_container.add(button_frame, stretch="always", sticky="nsew")
|
333
|
+
button_scrollable_frame = spacrFrame(button_frame, scrollbar=False)
|
495
334
|
button_scrollable_frame.grid(row=1, column=0, sticky="nsew")
|
496
|
-
|
497
|
-
widgets = [categories_label, button_scrollable_frame.scrollable_frame]
|
335
|
+
widgets = [button_scrollable_frame.scrollable_frame]
|
498
336
|
|
499
337
|
btn_col = 0
|
500
|
-
btn_row =
|
338
|
+
btn_row = 0
|
501
339
|
|
502
340
|
if run:
|
503
341
|
print(f'settings_type: {settings_type}')
|
504
|
-
run_button = spacrButton(button_scrollable_frame.scrollable_frame, text="run", command=lambda: start_process(q, fig_queue, settings_type), show_text=False, size=size_dict['btn_size'])
|
342
|
+
run_button = spacrButton(button_scrollable_frame.scrollable_frame, text="run", command=lambda: start_process(q, fig_queue, settings_type), show_text=False, size=size_dict['btn_size'], animation=False)
|
505
343
|
run_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
|
506
344
|
widgets.append(run_button)
|
507
|
-
|
345
|
+
btn_col += 1
|
508
346
|
|
509
347
|
if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap']:
|
510
|
-
abort_button = spacrButton(button_scrollable_frame.scrollable_frame, text="abort", command=initiate_abort, show_text=False, size=size_dict['btn_size'])
|
348
|
+
abort_button = spacrButton(button_scrollable_frame.scrollable_frame, text="abort", command=lambda: initiate_abort(), show_text=False, size=size_dict['btn_size'], animation=False)
|
511
349
|
abort_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
|
512
350
|
widgets.append(abort_button)
|
513
|
-
|
351
|
+
btn_col += 1
|
514
352
|
|
515
353
|
if download and settings_type in ['mask']:
|
516
|
-
download_dataset_button = spacrButton(button_scrollable_frame.scrollable_frame, text="download", command=download_hug_dataset, show_text=False, size=size_dict['btn_size'])
|
354
|
+
download_dataset_button = spacrButton(button_scrollable_frame.scrollable_frame, text="download", command=lambda: download_hug_dataset(q, vars_dict), show_text=False, size=size_dict['btn_size'], animation=False)
|
517
355
|
download_dataset_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
|
518
356
|
widgets.append(download_dataset_button)
|
519
|
-
|
357
|
+
btn_col += 1
|
520
358
|
|
521
359
|
if import_btn:
|
522
|
-
import_button = spacrButton(button_scrollable_frame.scrollable_frame, text="settings", command=lambda: import_settings(settings_type),show_text=False, size=size_dict['btn_size'])
|
360
|
+
import_button = spacrButton(button_scrollable_frame.scrollable_frame, text="settings", command=lambda: import_settings(settings_type), show_text=False, size=size_dict['btn_size'], animation=False)
|
523
361
|
import_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
|
524
362
|
widgets.append(import_button)
|
525
363
|
btn_row += 1
|
526
364
|
|
527
365
|
# Add the progress bar under the settings category menu
|
528
366
|
progress_bar = spacrProgressBar(button_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate')
|
529
|
-
progress_bar.grid(row=
|
367
|
+
progress_bar.grid(row=btn_row, column=0, columnspan=5, pady=5, padx=5, sticky='ew')
|
368
|
+
progress_bar.set_label_position() # Set the label position after grid placement
|
530
369
|
widgets.append(progress_bar)
|
531
370
|
|
532
371
|
if vars_dict is not None:
|
@@ -537,234 +376,6 @@ def setup_button_section(horizontal_container, settings_type='mask', run=True, a
|
|
537
376
|
|
538
377
|
return button_scrollable_frame
|
539
378
|
|
540
|
-
def setup_help_section(horizontal_container, settings_type='mask'):
|
541
|
-
from .settings import descriptions
|
542
|
-
|
543
|
-
description_frame = tk.Frame(horizontal_container)
|
544
|
-
horizontal_container.add(description_frame, stretch="always", sticky="nsew")
|
545
|
-
description_frame.grid_columnconfigure(0, weight=1)
|
546
|
-
description_frame.grid_rowconfigure(1, weight=1) # Ensure the text widget row is expandable
|
547
|
-
|
548
|
-
description_label = spacrLabel(description_frame, text=f"{settings_type} Module", anchor='center', justify='center', align="center")
|
549
|
-
description_label.grid(row=0, column=0, pady=10, padx=10, sticky='ew')
|
550
|
-
|
551
|
-
# Set background color directly
|
552
|
-
style_out = set_dark_style(ttk.Style())
|
553
|
-
bg_color = style_out['bg_color']
|
554
|
-
fg_color = style_out['fg_color']
|
555
|
-
|
556
|
-
description_text_widget = tk.Text(description_frame, wrap="word", bg=bg_color, fg=fg_color)
|
557
|
-
description_text_widget.grid(row=1, column=0, sticky="nsew")
|
558
|
-
|
559
|
-
description_text = descriptions.get(settings_type, "No description available for this module.")
|
560
|
-
description_text_widget.insert("1.0", description_text)
|
561
|
-
description_text_widget.config(state="disabled") # Make the text widget read-only
|
562
|
-
|
563
|
-
def update_wraplength(event):
|
564
|
-
new_width = event.width - 20 # Adjust as needed
|
565
|
-
description_text_widget.config(width=new_width)
|
566
|
-
|
567
|
-
description_text_widget.bind('<Configure>', update_wraplength)
|
568
|
-
|
569
|
-
style = ttk.Style(horizontal_container)
|
570
|
-
_ = set_dark_style(style, containers=[description_frame], widgets=[description_label, description_text_widget])
|
571
|
-
|
572
|
-
return description_frame
|
573
|
-
|
574
|
-
def hide_all_settings(vars_dict, categories):
|
575
|
-
"""
|
576
|
-
Function to initially hide all settings in the GUI.
|
577
|
-
|
578
|
-
Parameters:
|
579
|
-
- categories: dict, The categories of settings with their corresponding settings.
|
580
|
-
- vars_dict: dict, The dictionary containing the settings and their corresponding widgets.
|
581
|
-
"""
|
582
|
-
|
583
|
-
if categories is None:
|
584
|
-
from .settings import categories
|
585
|
-
|
586
|
-
for category, settings in categories.items():
|
587
|
-
if any(setting in vars_dict for setting in settings):
|
588
|
-
vars_dict[category] = (None, None, tk.IntVar(value=0))
|
589
|
-
|
590
|
-
# Initially hide all settings
|
591
|
-
for setting in settings:
|
592
|
-
if setting in vars_dict:
|
593
|
-
label, widget, _ = vars_dict[setting]
|
594
|
-
label.grid_remove()
|
595
|
-
widget.grid_remove()
|
596
|
-
return vars_dict
|
597
|
-
|
598
|
-
def toggle_settings(button_scrollable_frame):
|
599
|
-
global vars_dict
|
600
|
-
from .settings import categories
|
601
|
-
|
602
|
-
if vars_dict is None:
|
603
|
-
raise ValueError("vars_dict is not initialized.")
|
604
|
-
|
605
|
-
active_categories = set()
|
606
|
-
|
607
|
-
def toggle_category(settings):
|
608
|
-
for setting in settings:
|
609
|
-
if setting in vars_dict:
|
610
|
-
label, widget, _ = vars_dict[setting]
|
611
|
-
if widget.grid_info():
|
612
|
-
label.grid_remove()
|
613
|
-
widget.grid_remove()
|
614
|
-
else:
|
615
|
-
label.grid()
|
616
|
-
widget.grid()
|
617
|
-
|
618
|
-
def on_category_select(selected_category):
|
619
|
-
if selected_category == "Select Category":
|
620
|
-
return
|
621
|
-
if selected_category in categories:
|
622
|
-
toggle_category(categories[selected_category])
|
623
|
-
if selected_category in active_categories:
|
624
|
-
active_categories.remove(selected_category)
|
625
|
-
else:
|
626
|
-
active_categories.add(selected_category)
|
627
|
-
category_dropdown.update_styles(active_categories)
|
628
|
-
category_var.set("Select Category")
|
629
|
-
|
630
|
-
category_var = tk.StringVar()
|
631
|
-
non_empty_categories = [category for category, settings in categories.items() if any(setting in vars_dict for setting in settings)]
|
632
|
-
category_dropdown = spacrDropdownMenu(button_scrollable_frame.scrollable_frame, category_var, non_empty_categories, command=on_category_select)
|
633
|
-
category_dropdown.grid(row=7, column=0, sticky="ew", pady=2, padx=2)
|
634
|
-
vars_dict = hide_all_settings(vars_dict, categories)
|
635
|
-
|
636
|
-
def process_fig_queue():
|
637
|
-
global canvas, fig_queue, canvas_widget, parent_frame
|
638
|
-
|
639
|
-
def clear_canvas(canvas):
|
640
|
-
for ax in canvas.figure.get_axes():
|
641
|
-
ax.clear()
|
642
|
-
canvas.draw_idle()
|
643
|
-
|
644
|
-
try:
|
645
|
-
while not fig_queue.empty():
|
646
|
-
clear_canvas(canvas)
|
647
|
-
fig = fig_queue.get_nowait()
|
648
|
-
for ax in fig.get_axes():
|
649
|
-
ax.set_xticks([]) # Remove x-axis ticks
|
650
|
-
ax.set_yticks([]) # Remove y-axis ticks
|
651
|
-
ax.xaxis.set_visible(False) # Hide the x-axis
|
652
|
-
ax.yaxis.set_visible(False) # Hide the y-axis
|
653
|
-
fig.tight_layout()
|
654
|
-
fig.set_facecolor('black')
|
655
|
-
canvas.figure = fig
|
656
|
-
fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
|
657
|
-
fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
|
658
|
-
canvas.draw_idle()
|
659
|
-
except Exception as e:
|
660
|
-
traceback.print_exc()
|
661
|
-
finally:
|
662
|
-
after_id = canvas_widget.after(100, process_fig_queue)
|
663
|
-
parent_frame.after_tasks.append(after_id)
|
664
|
-
|
665
|
-
def process_console_queue():
|
666
|
-
global q, console_output, parent_frame, progress_bar
|
667
|
-
|
668
|
-
# Initialize function attribute if it doesn't exist
|
669
|
-
if not hasattr(process_console_queue, "completed_tasks"):
|
670
|
-
process_console_queue.completed_tasks = []
|
671
|
-
|
672
|
-
ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
|
673
|
-
|
674
|
-
while not q.empty():
|
675
|
-
message = q.get_nowait()
|
676
|
-
clean_message = ansi_escape_pattern.sub('', message)
|
677
|
-
console_output.insert(tk.END, clean_message + "\n")
|
678
|
-
console_output.see(tk.END)
|
679
|
-
|
680
|
-
# Check if the message contains progress information
|
681
|
-
if clean_message.startswith("Progress"):
|
682
|
-
try:
|
683
|
-
# Extract the progress information
|
684
|
-
match = re.search(r'(\d+)/(\d+)', clean_message)
|
685
|
-
if match:
|
686
|
-
current_progress = int(match.group(1))
|
687
|
-
total_progress = int(match.group(2))
|
688
|
-
|
689
|
-
# Add the task to the completed set
|
690
|
-
process_console_queue.completed_tasks.append(current_progress)
|
691
|
-
|
692
|
-
# Calculate the unique progress count
|
693
|
-
unique_progress_count = len(np.unique(process_console_queue.completed_tasks))
|
694
|
-
|
695
|
-
# Update the progress bar
|
696
|
-
if progress_bar:
|
697
|
-
progress_bar['maximum'] = total_progress
|
698
|
-
progress_bar['value'] = unique_progress_count
|
699
|
-
|
700
|
-
# Extract and update additional information
|
701
|
-
operation_match = re.search(r'operation_type: ([\w\s]+)', clean_message)
|
702
|
-
if operation_match:
|
703
|
-
progress_bar.operation_type = operation_match.group(1)
|
704
|
-
|
705
|
-
time_image_match = re.search(r'Time/image: ([\d.]+) sec', clean_message)
|
706
|
-
if time_image_match:
|
707
|
-
progress_bar.time_image = float(time_image_match.group(1))
|
708
|
-
|
709
|
-
time_batch_match = re.search(r'Time/batch: ([\d.]+) sec', clean_message)
|
710
|
-
if time_batch_match:
|
711
|
-
progress_bar.time_batch = float(time_batch_match.group(1))
|
712
|
-
|
713
|
-
time_left_match = re.search(r'Time_left: ([\d.]+) min', clean_message)
|
714
|
-
if time_left_match:
|
715
|
-
progress_bar.time_left = float(time_left_match.group(1))
|
716
|
-
|
717
|
-
# Update the progress label
|
718
|
-
if progress_bar.progress_label:
|
719
|
-
progress_bar.update_label()
|
720
|
-
|
721
|
-
# Clear completed tasks when progress is complete
|
722
|
-
if unique_progress_count >= total_progress:
|
723
|
-
process_console_queue.completed_tasks.clear()
|
724
|
-
except Exception as e:
|
725
|
-
print(f"Error parsing progress message: {e}")
|
726
|
-
|
727
|
-
after_id = console_output.after(100, process_console_queue)
|
728
|
-
parent_frame.after_tasks.append(after_id)
|
729
|
-
|
730
|
-
def set_globals(q_var, console_output_var, parent_frame_var, vars_dict_var, canvas_var, canvas_widget_var, scrollable_frame_var, fig_queue_var, progress_bar_var, usage_bars_var):
|
731
|
-
global q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars
|
732
|
-
q = q_var
|
733
|
-
console_output = console_output_var
|
734
|
-
parent_frame = parent_frame_var
|
735
|
-
vars_dict = vars_dict_var
|
736
|
-
canvas = canvas_var
|
737
|
-
canvas_widget = canvas_widget_var
|
738
|
-
scrollable_frame = scrollable_frame_var
|
739
|
-
fig_queue = fig_queue_var
|
740
|
-
progress_bar = progress_bar_var
|
741
|
-
usage_bars = usage_bars_var
|
742
|
-
|
743
|
-
def create_containers(parent_frame):
|
744
|
-
vertical_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL)
|
745
|
-
horizontal_container = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL)
|
746
|
-
settings_frame = tk.Frame(horizontal_container)
|
747
|
-
return vertical_container, horizontal_container, settings_frame
|
748
|
-
|
749
|
-
def setup_frame(parent_frame):
|
750
|
-
style = ttk.Style(parent_frame)
|
751
|
-
vertical_container, horizontal_container, settings_frame = create_containers(parent_frame)
|
752
|
-
containers = [vertical_container, horizontal_container, settings_frame]
|
753
|
-
|
754
|
-
set_dark_style(style, parent_frame, containers)
|
755
|
-
set_default_font(parent_frame, font_name="Helvetica", size=8)
|
756
|
-
|
757
|
-
vertical_container.grid(row=0, column=0, sticky=tk.NSEW)
|
758
|
-
vertical_container.add(horizontal_container, stretch="always")
|
759
|
-
horizontal_container.grid_columnconfigure(0, weight=1)
|
760
|
-
horizontal_container.grid_columnconfigure(1, weight=1)
|
761
|
-
settings_frame.grid_rowconfigure(0, weight=0)
|
762
|
-
settings_frame.grid_rowconfigure(1, weight=1)
|
763
|
-
settings_frame.grid_columnconfigure(0, weight=1)
|
764
|
-
horizontal_container.add(settings_frame, stretch="always", sticky="nsew")
|
765
|
-
|
766
|
-
return parent_frame, vertical_container, horizontal_container
|
767
|
-
|
768
379
|
def setup_usage_panel(horizontal_container):
|
769
380
|
global usage_bars
|
770
381
|
from .gui_utils import set_element_size
|
@@ -792,23 +403,20 @@ def setup_usage_panel(horizontal_container):
|
|
792
403
|
parent_frame.after(1000, update_usage, ram_bar, vram_bar, gpu_bar, usage_bars, parent_frame)
|
793
404
|
|
794
405
|
size_dict = set_element_size(horizontal_container)
|
795
|
-
|
796
|
-
|
797
|
-
usage_frame = tk.Frame(horizontal_container)
|
406
|
+
usage_panel_height = size_dict['panel_height']
|
407
|
+
usage_frame = tk.Frame(horizontal_container, height=usage_panel_height)
|
798
408
|
horizontal_container.add(usage_frame, stretch="always", sticky="nsew")
|
409
|
+
|
799
410
|
usage_frame.grid_rowconfigure(0, weight=0)
|
800
411
|
usage_frame.grid_rowconfigure(1, weight=1)
|
801
412
|
usage_frame.grid_columnconfigure(0, weight=1)
|
802
413
|
usage_frame.grid_columnconfigure(1, weight=1)
|
803
414
|
|
804
|
-
|
805
|
-
usage_label.grid(row=0, column=0, pady=10, padx=10, columnspan=2)
|
806
|
-
|
807
|
-
usage_scrollable_frame = spacrFrame(usage_frame)
|
415
|
+
usage_scrollable_frame = spacrFrame(usage_frame, scrollbar=False)
|
808
416
|
usage_scrollable_frame.grid(row=1, column=0, sticky="nsew", columnspan=2)
|
809
|
-
widgets = [
|
417
|
+
widgets = [usage_scrollable_frame.scrollable_frame]
|
810
418
|
usage_bars = []
|
811
|
-
max_elements_per_column =
|
419
|
+
max_elements_per_column = 6
|
812
420
|
row = 0
|
813
421
|
col = 0
|
814
422
|
|
@@ -861,7 +469,7 @@ def setup_usage_panel(horizontal_container):
|
|
861
469
|
try:
|
862
470
|
cpu_cores = psutil.cpu_count(logical=True)
|
863
471
|
cpu_freq = psutil.cpu_freq()
|
864
|
-
|
472
|
+
|
865
473
|
for core in range(cpu_cores):
|
866
474
|
if row > 0 and row % max_elements_per_column == 0:
|
867
475
|
col += 1
|
@@ -877,42 +485,8 @@ def setup_usage_panel(horizontal_container):
|
|
877
485
|
except Exception as e:
|
878
486
|
print(f"Could not add CPU core usage bars: {e}")
|
879
487
|
|
880
|
-
# Adding the text box for hardware information
|
881
|
-
#hardware_frame = tk.Frame(horizontal_container)
|
882
|
-
#horizontal_container.add(hardware_frame, stretch="always", sticky="nsew")
|
883
|
-
#hardware_frame.grid_columnconfigure(0, weight=1)
|
884
|
-
|
885
|
-
#hardware_info = tk.Text(hardware_frame, height=1, wrap='none', bg='black', fg='white', bd=0)
|
886
|
-
#hardware_info.grid(row=0, column=0, pady=10, padx=5, sticky='ew')
|
887
|
-
|
888
|
-
#hardware_text = ""
|
889
|
-
#try:
|
890
|
-
# ram_info = psutil.virtual_memory()
|
891
|
-
# hardware_text += f"RAM: {ram_info.total / (1024 ** 3):.1f} GB "
|
892
|
-
#except Exception as e:
|
893
|
-
# hardware_text += f"RAM: Could not retrieve ({e}) "
|
894
|
-
|
895
|
-
#try:
|
896
|
-
# gpus = GPUtil.getGPUs()
|
897
|
-
# if gpus:
|
898
|
-
# gpu = gpus[0]
|
899
|
-
# hardware_text += f"VRAM: {gpu.memoryTotal / 1024:.1f} GB "
|
900
|
-
# hardware_text += f"GPU: {gpu.name} "
|
901
|
-
#except Exception as e:
|
902
|
-
# hardware_text += f"VRAM and GPU: Could not retrieve ({e}) "
|
903
|
-
|
904
|
-
#try:
|
905
|
-
# if cpu_freq:
|
906
|
-
# hardware_text += f"CPU Max Clock Speed: {cpu_freq.max / 1000:.0f} GHz"
|
907
|
-
#except Exception as e:
|
908
|
-
# hardware_text += f"CPU Max Clock Speed: Could not retrieve ({e})"
|
909
|
-
|
910
|
-
#hardware_info.insert(tk.END, hardware_text)
|
911
|
-
#hardware_info.configure(state='disabled')
|
912
|
-
#widgets.append(hardware_info)
|
913
|
-
|
914
488
|
style = ttk.Style(horizontal_container)
|
915
|
-
_ = set_dark_style(style, containers=[usage_frame], widgets=widgets)
|
489
|
+
_ = set_dark_style(style, containers=[usage_frame], widgets=widgets)
|
916
490
|
|
917
491
|
if ram_bar is None:
|
918
492
|
ram_bar = spacrProgressBar(usage_scrollable_frame.scrollable_frame, orient='horizontal', mode='determinate', length=size_dict['bar_size'], label=False)
|
@@ -924,33 +498,192 @@ def setup_usage_panel(horizontal_container):
|
|
924
498
|
update_usage(ram_bar, vram_bar, gpu_bar, usage_bars, usage_frame)
|
925
499
|
return usage_scrollable_frame, usage_bars
|
926
500
|
|
501
|
+
def setup_help_section(horizontal_container, settings_type='mask'):
|
502
|
+
from .settings import descriptions
|
503
|
+
from .gui_utils import set_element_size
|
504
|
+
|
505
|
+
size_dict = set_element_size(horizontal_container)
|
506
|
+
help_section_height = size_dict['panel_height']
|
507
|
+
|
508
|
+
# Create the frame for the help section
|
509
|
+
description_frame = tk.Frame(horizontal_container, height=help_section_height)
|
510
|
+
description_frame.pack_propagate(False)
|
511
|
+
|
512
|
+
# Add the description frame to the horizontal container
|
513
|
+
horizontal_container.add(description_frame, stretch="always", sticky="nsew")
|
514
|
+
description_frame.grid_columnconfigure(0, weight=1)
|
515
|
+
description_frame.grid_rowconfigure(0, weight=1) # Ensure the text widget row is expandable
|
516
|
+
|
517
|
+
style_out = set_dark_style(ttk.Style())
|
518
|
+
bg_color = style_out['bg_color']
|
519
|
+
fg_color = style_out['fg_color']
|
520
|
+
|
521
|
+
# Insert the description text
|
522
|
+
description_text = descriptions.get(settings_type, "No description available for this module.")
|
523
|
+
first_line, *rest_of_description = description_text.split('\n', 1)
|
524
|
+
|
525
|
+
# Create the first line label
|
526
|
+
first_line_label = ttk.Label(description_frame, text=first_line, anchor='w', background=bg_color, foreground=fg_color)
|
527
|
+
first_line_label.grid(row=0, column=0, sticky='ew')
|
528
|
+
|
529
|
+
# Create the text widget with the appropriate background and foreground colors
|
530
|
+
description_text_widget = tk.Text(description_frame, wrap="word", bg=bg_color, fg=fg_color, bd=0, highlightthickness=0)
|
531
|
+
description_text_widget.grid(row=1, column=0, sticky="nsew")
|
532
|
+
|
533
|
+
# Insert the rest of the description text
|
534
|
+
if rest_of_description:
|
535
|
+
description_text_widget.insert("1.0", rest_of_description[0].lstrip())
|
536
|
+
description_text_widget.config(state="disabled") # Make the text widget read-only
|
537
|
+
|
538
|
+
def update_wraplength(event):
|
539
|
+
new_width = event.width - 20 # Adjust as needed
|
540
|
+
description_text_widget.config(width=new_width)
|
541
|
+
|
542
|
+
description_text_widget.bind('<Configure>', update_wraplength)
|
543
|
+
|
544
|
+
# Apply dark style
|
545
|
+
style = ttk.Style(horizontal_container)
|
546
|
+
_ = set_dark_style(style, containers=[description_frame], widgets=[description_text_widget, first_line_label])
|
547
|
+
|
548
|
+
return description_frame
|
549
|
+
|
550
|
+
def initiate_abort():
|
551
|
+
global thread_control, q, parent_frame
|
552
|
+
if thread_control.get("run_thread") is not None:
|
553
|
+
try:
|
554
|
+
q.put("Aborting processes...")
|
555
|
+
thread_control.get("run_thread").terminate()
|
556
|
+
thread_control["run_thread"] = None
|
557
|
+
q.put("Processes aborted.")
|
558
|
+
except Exception as e:
|
559
|
+
q.put(f"Error aborting process: {e}")
|
560
|
+
|
561
|
+
thread_control = {"run_thread": None, "stop_requested": False}
|
562
|
+
|
563
|
+
|
564
|
+
def start_process(q=None, fig_queue=None, settings_type='mask'):
|
565
|
+
global thread_control, vars_dict, parent_frame
|
566
|
+
from .settings import check_settings, expected_types
|
567
|
+
from .gui_utils import run_function_gui
|
568
|
+
|
569
|
+
if q is None:
|
570
|
+
q = Queue()
|
571
|
+
if fig_queue is None:
|
572
|
+
fig_queue = Queue()
|
573
|
+
|
574
|
+
try:
|
575
|
+
settings = check_settings(vars_dict, expected_types, q)
|
576
|
+
except ValueError as e:
|
577
|
+
q.put(f"Error: {e}")
|
578
|
+
return
|
579
|
+
|
580
|
+
if thread_control.get("run_thread") is not None:
|
581
|
+
initiate_abort()
|
582
|
+
|
583
|
+
stop_requested = Value('i', 0)
|
584
|
+
thread_control["stop_requested"] = stop_requested
|
585
|
+
|
586
|
+
process_args = (settings_type, settings, q, fig_queue, stop_requested)
|
587
|
+
if settings_type in [
|
588
|
+
'mask', 'measure', 'simulation', 'sequencing', 'classify', 'cellpose_dataset',
|
589
|
+
'train_cellpose', 'ml_analyze', 'cellpose_masks', 'cellpose_all', 'map_barcodes',
|
590
|
+
'regression', 'recruitment', 'plaques', 'cellpose_compare', 'vision_scores',
|
591
|
+
'vision_dataset'
|
592
|
+
]:
|
593
|
+
thread_control["run_thread"] = Process(target=run_function_gui, args=process_args)
|
594
|
+
else:
|
595
|
+
q.put(f"Error: Unknown settings type '{settings_type}'")
|
596
|
+
return
|
597
|
+
thread_control["run_thread"].start()
|
598
|
+
|
599
|
+
def process_console_queue():
|
600
|
+
global q, console_output, parent_frame, progress_bar
|
601
|
+
|
602
|
+
# Initialize function attribute if it doesn't exist
|
603
|
+
if not hasattr(process_console_queue, "completed_tasks"):
|
604
|
+
process_console_queue.completed_tasks = []
|
605
|
+
|
606
|
+
ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
|
607
|
+
|
608
|
+
while not q.empty():
|
609
|
+
message = q.get_nowait()
|
610
|
+
clean_message = ansi_escape_pattern.sub('', message)
|
611
|
+
console_output.insert(tk.END, clean_message + "\n")
|
612
|
+
console_output.see(tk.END)
|
613
|
+
|
614
|
+
# Check if the message contains progress information
|
615
|
+
if clean_message.startswith("Progress:"):
|
616
|
+
try:
|
617
|
+
# Extract the progress information
|
618
|
+
match = re.search(r'Progress: (\d+)/(\d+), operation_type: ([\w\s]*)(.*)', clean_message)
|
619
|
+
if match:
|
620
|
+
current_progress = int(match.group(1))
|
621
|
+
total_progress = int(match.group(2))
|
622
|
+
operation_type = match.group(3).strip()
|
623
|
+
time_info = match.group(4).strip()
|
624
|
+
|
625
|
+
# Add the task to the completed set
|
626
|
+
process_console_queue.completed_tasks.append(current_progress)
|
627
|
+
|
628
|
+
# Calculate the unique progress count
|
629
|
+
unique_progress_count = len(np.unique(process_console_queue.completed_tasks))
|
630
|
+
|
631
|
+
# Update the progress bar
|
632
|
+
if progress_bar:
|
633
|
+
progress_bar['maximum'] = total_progress
|
634
|
+
progress_bar['value'] = unique_progress_count
|
635
|
+
|
636
|
+
# Extract and update additional information
|
637
|
+
if operation_type:
|
638
|
+
progress_bar.operation_type = operation_type
|
639
|
+
|
640
|
+
time_image_match = re.search(r'Time/image: ([\d.]+) sec', time_info)
|
641
|
+
if time_image_match:
|
642
|
+
progress_bar.time_image = float(time_image_match.group(1))
|
643
|
+
|
644
|
+
time_batch_match = re.search(r'Time/batch: ([\d.]+) sec', time_info)
|
645
|
+
if time_batch_match:
|
646
|
+
progress_bar.time_batch = float(time_batch_match.group(1))
|
647
|
+
|
648
|
+
time_left_match = re.search(r'Time_left: ([\d.]+) min', time_info)
|
649
|
+
if time_left_match:
|
650
|
+
progress_bar.time_left = float(time_left_match.group(1))
|
651
|
+
|
652
|
+
# Update the progress label
|
653
|
+
if progress_bar.progress_label:
|
654
|
+
progress_bar.update_label()
|
655
|
+
|
656
|
+
# Clear completed tasks when progress is complete
|
657
|
+
if unique_progress_count >= total_progress:
|
658
|
+
process_console_queue.completed_tasks.clear()
|
659
|
+
except Exception as e:
|
660
|
+
print(f"Error parsing progress message: {e}")
|
661
|
+
|
662
|
+
after_id = console_output.after(100, process_console_queue)
|
663
|
+
parent_frame.after_tasks.append(after_id)
|
664
|
+
|
927
665
|
def initiate_root(parent, settings_type='mask'):
|
928
|
-
global q, fig_queue, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar
|
929
|
-
from .gui_utils import main_thread_update_function
|
666
|
+
global q, fig_queue, thread_control, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, button_scrollable_frame, progress_bar
|
667
|
+
from .gui_utils import main_thread_update_function, setup_frame, set_element_size
|
930
668
|
from .gui import gui_app
|
931
669
|
set_start_method('spawn', force=True)
|
932
670
|
print("Initializing root with settings_type:", settings_type)
|
933
671
|
|
934
672
|
parent_frame = parent
|
935
|
-
parent_frame.update_idletasks()
|
936
|
-
frame_width = int(parent_frame.winfo_width())
|
937
|
-
frame_height = int(parent_frame.winfo_height())
|
938
|
-
print(frame_width, frame_height)
|
939
|
-
dims = [frame_width, frame_height]
|
940
673
|
|
941
|
-
if not
|
942
|
-
|
674
|
+
if not isinstance(parent_frame, (tk.Tk, tk.Toplevel)):
|
675
|
+
parent_window = parent_frame.winfo_toplevel()
|
676
|
+
else:
|
677
|
+
parent_window = parent_frame
|
943
678
|
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
except tk.TclError as e:
|
949
|
-
print(f"Error destroying widget: {e}")
|
679
|
+
parent_window.update_idletasks()
|
680
|
+
|
681
|
+
if not hasattr(parent_window, 'after_tasks'):
|
682
|
+
parent_window.after_tasks = []
|
950
683
|
|
951
684
|
q = Queue()
|
952
685
|
fig_queue = Queue()
|
953
|
-
parent_frame, vertical_container, horizontal_container = setup_frame(parent_frame)
|
686
|
+
parent_frame, vertical_container, horizontal_container, settings_container = setup_frame(parent_frame)
|
954
687
|
|
955
688
|
if settings_type == 'annotate':
|
956
689
|
from .app_annotate import initiate_annotation_app
|
@@ -959,21 +692,19 @@ def initiate_root(parent, settings_type='mask'):
|
|
959
692
|
from .app_make_masks import initiate_make_mask_app
|
960
693
|
initiate_make_mask_app(horizontal_container)
|
961
694
|
else:
|
962
|
-
scrollable_frame, vars_dict = setup_settings_panel(
|
695
|
+
scrollable_frame, vars_dict = setup_settings_panel(settings_container, settings_type)
|
696
|
+
canvas, canvas_widget = setup_plot_section(vertical_container)
|
697
|
+
console_output, console_frame = setup_console(vertical_container)
|
963
698
|
button_scrollable_frame = setup_button_section(horizontal_container, settings_type)
|
964
|
-
|
965
699
|
_, usage_bars = setup_usage_panel(horizontal_container)
|
966
700
|
_ = setup_help_section(horizontal_container, settings_type)
|
967
|
-
|
968
|
-
canvas, canvas_widget
|
969
|
-
|
970
|
-
|
971
|
-
set_globals(q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars)
|
701
|
+
|
702
|
+
set_globals(thread_control, q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, fig_queue, progress_bar, usage_bars)
|
703
|
+
q.put(f"Console")
|
972
704
|
process_console_queue()
|
973
705
|
process_fig_queue()
|
974
|
-
after_id =
|
975
|
-
|
706
|
+
after_id = parent_window.after(100, lambda: main_thread_update_function(parent_window, q, fig_queue, canvas_widget))
|
707
|
+
parent_window.after_tasks.append(after_id)
|
976
708
|
|
977
709
|
print("Root initialization complete")
|
978
710
|
return parent_frame, vars_dict
|
979
|
-
|