spacr 0.1.55__py3-none-any.whl → 0.1.61__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
spacr/__init__.py CHANGED
@@ -14,6 +14,10 @@ from . import timelapse
14
14
  from . import deep_spacr
15
15
  from . import app_annotate
16
16
  from . import gui_utils
17
+ from . import gui_elements
18
+ from . import gui_core
19
+ from . import gui_run
20
+ from . import gui_wrappers
17
21
  from . import app_make_masks
18
22
  from . import app_mask
19
23
  from . import app_measure
@@ -37,7 +41,6 @@ __all__ = [
37
41
  "app_annotate",
38
42
  "gui_utils",
39
43
  "app_make_masks",
40
- "app_make_masks_v2",
41
44
  "app_mask",
42
45
  "app_measure",
43
46
  "app_classify",
spacr/app_annotate.py CHANGED
@@ -13,7 +13,7 @@ from IPython.display import display, HTML
13
13
  from tkinter import font as tkFont
14
14
  from tkinter import TclError
15
15
 
16
- from .gui_utils import spacrFrame, spacrButton, set_dark_style, create_menu_bar, set_default_font
16
+ from .gui_elements import spacrFrame, spacrButton, set_dark_style, create_menu_bar, set_default_font
17
17
 
18
18
  class ImageApp:
19
19
  def __init__(self, root, db_path, src, image_type=None, channels=None, grid_rows=None, grid_cols=None, image_size=(200, 200), annotation_column='annotate', normalize=False, percentiles=(1,99), measurement=None, threshold=None):
spacr/app_make_masks.py CHANGED
@@ -9,11 +9,8 @@ from skimage.transform import resize
9
9
  from scipy.ndimage import binary_fill_holes, label
10
10
  import tkinter as tk
11
11
  from tkinter import ttk
12
- from ttkthemes import ThemedTk
13
12
 
14
- from .logger import log_function_call
15
-
16
- from .gui_utils import spacrFrame, spacrButton, set_dark_style, create_menu_bar, set_default_font
13
+ from .gui_elements import spacrFrame, spacrButton, set_dark_style, create_menu_bar, set_default_font
17
14
 
18
15
  class modify_masks:
19
16
 
spacr/core.py CHANGED
@@ -1668,7 +1668,7 @@ def preprocess_generate_masks(src, settings={}):
1668
1668
 
1669
1669
  from .io import preprocess_img_data, _load_and_concatenate_arrays
1670
1670
  from .plot import plot_merged, plot_arrays
1671
- from .utils import _pivot_counts_table, check_mask_folder, adjust_cell_masks, _merge_cells_based_on_parasite_overlap, process_masks
1671
+ from .utils import _pivot_counts_table, check_mask_folder, adjust_cell_masks
1672
1672
  from .settings import set_default_settings_preprocess_generate_masks, set_default_plot_merge_settings
1673
1673
 
1674
1674
  settings = set_default_settings_preprocess_generate_masks(src, settings)
spacr/gui.py CHANGED
@@ -1,10 +1,10 @@
1
1
  import tkinter as tk
2
2
  from tkinter import ttk
3
3
  from PIL import Image, ImageTk
4
- import os
5
- import requests
4
+ import os, requests
6
5
  from multiprocessing import set_start_method
7
- from .gui_utils import set_dark_style, create_menu_bar, initiate_root, spacrButton
6
+ from .gui_elements import spacrButton, create_menu_bar, set_dark_style
7
+ from .gui_core import initiate_root
8
8
  from .app_annotate import initiate_annotation_app_root
9
9
  from .app_make_masks import initiate_mask_app_root
10
10
 
spacr/gui_core.py ADDED
@@ -0,0 +1,608 @@
1
+ import os, traceback, ctypes, matplotlib, requests, csv, re
2
+ matplotlib.use('Agg')
3
+ import tkinter as tk
4
+ from tkinter import ttk
5
+ import tkinter.font as tkFont
6
+ from tkinter import filedialog
7
+ from tkinter import font as tkFont
8
+ from multiprocessing import Process, Value, Queue, Manager, set_start_method
9
+ from tkinter import ttk, scrolledtext
10
+ from matplotlib.figure import Figure
11
+ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
12
+ import time
13
+ import requests
14
+ from huggingface_hub import list_repo_files
15
+
16
+ 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
17
+ from .gui_elements import create_menu_bar, spacrButton, spacrLabel, spacrFrame, spacrCheckbutton, spacrDropdownMenu ,set_dark_style, set_default_font
18
+ from . gui_run import run_mask_gui, run_measure_gui, run_classify_gui, run_sequencing_gui, run_umap_gui
19
+
20
+ try:
21
+ ctypes.windll.shcore.SetProcessDpiAwareness(True)
22
+ except AttributeError:
23
+ pass
24
+
25
+ # Define global variables
26
+ q = None
27
+ console_output = None
28
+ parent_frame = None
29
+ vars_dict = None
30
+ canvas = None
31
+ canvas_widget = None
32
+ scrollable_frame = None
33
+ progress_label = None
34
+ fig_queue = None
35
+
36
+ thread_control = {"run_thread": None, "stop_requested": False}
37
+
38
+ def initiate_abort():
39
+ global thread_control
40
+ if thread_control.get("stop_requested") is not None:
41
+ if isinstance(thread_control["stop_requested"], Value):
42
+ thread_control["stop_requested"].value = 1
43
+
44
+ if thread_control.get("run_thread") is not None:
45
+ thread_control["run_thread"].join(timeout=5)
46
+ if thread_control["run_thread"].is_alive():
47
+ thread_control["run_thread"].terminate()
48
+ thread_control["run_thread"] = None
49
+
50
+ def start_process(q, fig_queue, settings_type='mask'):
51
+ global thread_control, vars_dict
52
+ from .settings import check_settings
53
+
54
+ settings = check_settings(vars_dict)
55
+ if thread_control.get("run_thread") is not None:
56
+ initiate_abort()
57
+ stop_requested = Value('i', 0) # multiprocessing shared value for inter-process communication
58
+ thread_control["stop_requested"] = stop_requested
59
+ if settings_type == 'mask':
60
+ thread_control["run_thread"] = Process(target=run_mask_gui, args=(settings, q, fig_queue, stop_requested))
61
+ elif settings_type == 'measure':
62
+ thread_control["run_thread"] = Process(target=run_measure_gui, args=(settings, q, fig_queue, stop_requested))
63
+ elif settings_type == 'classify':
64
+ thread_control["run_thread"] = Process(target=run_classify_gui, args=(settings, q, fig_queue, stop_requested))
65
+ elif settings_type == 'sequencing':
66
+ thread_control["run_thread"] = Process(target=run_sequencing_gui, args=(settings, q, fig_queue, stop_requested))
67
+ elif settings_type == 'umap':
68
+ thread_control["run_thread"] = Process(target=run_umap_gui, args=(settings, q, fig_queue, stop_requested))
69
+ thread_control["run_thread"].start()
70
+
71
+ def import_settings(settings_type='mask'):
72
+
73
+ global vars_dict, scrollable_frame
74
+ from .settings import generate_fields
75
+
76
+ def read_settings_from_csv(csv_file_path):
77
+ settings = {}
78
+ with open(csv_file_path, newline='') as csvfile:
79
+ reader = csv.DictReader(csvfile)
80
+ for row in reader:
81
+ key = row['Key']
82
+ value = row['Value']
83
+ settings[key] = value
84
+ return settings
85
+
86
+ def update_settings_from_csv(variables, csv_settings):
87
+ new_settings = variables.copy() # Start with a copy of the original settings
88
+ for key, value in csv_settings.items():
89
+ if key in new_settings:
90
+ # Get the variable type and options from the original settings
91
+ var_type, options, _ = new_settings[key]
92
+ # Update the default value with the CSV value, keeping the type and options unchanged
93
+ new_settings[key] = (var_type, options, value)
94
+ return new_settings
95
+
96
+ csv_file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
97
+
98
+ if not csv_file_path: # If no file is selected, return early
99
+ return
100
+
101
+ csv_settings = read_settings_from_csv(csv_file_path)
102
+ if settings_type == 'mask':
103
+ settings = set_default_settings_preprocess_generate_masks(src='path', settings={})
104
+ elif settings_type == 'measure':
105
+ settings = get_measure_crop_settings(settings={})
106
+ elif settings_type == 'classify':
107
+ settings = set_default_train_test_model(settings={})
108
+ elif settings_type == 'sequencing':
109
+ settings = get_analyze_reads_default_settings(settings={})
110
+ elif settings_type == 'umap':
111
+ settings = set_default_umap_image_settings(settings={})
112
+ else:
113
+ raise ValueError(f"Invalid settings type: {settings_type}")
114
+
115
+ variables = convert_settings_dict_for_gui(settings)
116
+ new_settings = update_settings_from_csv(variables, csv_settings)
117
+ vars_dict = generate_fields(new_settings, scrollable_frame)
118
+
119
+ def convert_settings_dict_for_gui(settings):
120
+ variables = {}
121
+ special_cases = {
122
+ 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
123
+ 'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
124
+ 'cell_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
125
+ 'nucleus_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
126
+ 'pathogen_mask_dim': ('combo', ['0', '1', '2', '3', '4', '5', '6', '7', '8', None], None),
127
+ #'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
128
+ 'magnification': ('combo', [20, 40, 60], 20),
129
+ 'nucleus_channel': ('combo', [0, 1, 2, 3, None], None),
130
+ 'cell_channel': ('combo', [0, 1, 2, 3, None], None),
131
+ 'pathogen_channel': ('combo', [0, 1, 2, 3, None], None),
132
+ 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
133
+ 'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
134
+ 'model_type': ('combo', ['resnet50', 'other_model'], 'resnet50'),
135
+ 'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
136
+ 'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
137
+ 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
138
+ 'normalize_by': ('combo', ['fov', 'png'], 'png'),
139
+ }
140
+
141
+ for key, value in settings.items():
142
+ if key in special_cases:
143
+ variables[key] = special_cases[key]
144
+ elif isinstance(value, bool):
145
+ variables[key] = ('check', None, value)
146
+ elif isinstance(value, int) or isinstance(value, float):
147
+ variables[key] = ('entry', None, value)
148
+ elif isinstance(value, str):
149
+ variables[key] = ('entry', None, value)
150
+ elif value is None:
151
+ variables[key] = ('entry', None, value)
152
+ elif isinstance(value, list):
153
+ variables[key] = ('entry', None, str(value))
154
+ else:
155
+ variables[key] = ('entry', None, str(value))
156
+ return variables
157
+
158
+ def setup_settings_panel(vertical_container, settings_type='mask', frame_height=500, frame_width=1000):
159
+ global vars_dict, scrollable_frame
160
+ from .settings import 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
161
+
162
+ settings_frame = tk.Frame(vertical_container, bg='black', height=frame_height, width=frame_width)
163
+ vertical_container.add(settings_frame, stretch="always")
164
+ settings_label = spacrLabel(settings_frame, text="Settings", background="black", foreground="white", anchor='center', justify='center', align="center")
165
+ settings_label.grid(row=0, column=0, pady=10, padx=10)
166
+ scrollable_frame = spacrFrame(settings_frame, bg='black', width=frame_width)
167
+ scrollable_frame.grid(row=1, column=0, sticky="nsew")
168
+ settings_frame.grid_rowconfigure(1, weight=1)
169
+ settings_frame.grid_columnconfigure(0, weight=1)
170
+
171
+ if settings_type == 'mask':
172
+ settings = set_default_settings_preprocess_generate_masks(src='path', settings={})
173
+ elif settings_type == 'measure':
174
+ settings = get_measure_crop_settings(settings={})
175
+ elif settings_type == 'classify':
176
+ settings = set_default_train_test_model(settings={})
177
+ elif settings_type == 'sequencing':
178
+ settings = get_analyze_reads_default_settings(settings={})
179
+ elif settings_type == 'umap':
180
+ settings = set_default_umap_image_settings(settings={})
181
+ else:
182
+ raise ValueError(f"Invalid settings type: {settings_type}")
183
+
184
+ variables = convert_settings_dict_for_gui(settings)
185
+ vars_dict = generate_fields(variables, scrollable_frame)
186
+ print("Settings panel setup complete")
187
+ return scrollable_frame, vars_dict
188
+
189
+ def setup_plot_section(vertical_container):
190
+ global canvas, canvas_widget
191
+ plot_frame = tk.PanedWindow(vertical_container, orient=tk.VERTICAL)
192
+ vertical_container.add(plot_frame, stretch="always")
193
+ figure = Figure(figsize=(30, 4), dpi=100, facecolor='black')
194
+ plot = figure.add_subplot(111)
195
+ plot.plot([], []) # This creates an empty plot.
196
+ plot.axis('off')
197
+ canvas = FigureCanvasTkAgg(figure, master=plot_frame)
198
+ canvas.get_tk_widget().configure(cursor='arrow', background='black', highlightthickness=0)
199
+ canvas_widget = canvas.get_tk_widget()
200
+ plot_frame.add(canvas_widget, stretch="always")
201
+ canvas.draw()
202
+ canvas.figure = figure
203
+ return canvas, canvas_widget
204
+
205
+ def setup_console(vertical_container):
206
+ global console_output
207
+ print("Setting up console frame")
208
+ console_frame = tk.Frame(vertical_container, bg='black')
209
+ vertical_container.add(console_frame, stretch="always")
210
+ console_label = spacrLabel(console_frame, text="Console", background="black", foreground="white", anchor='center', justify='center', align="center")
211
+ console_label.grid(row=0, column=0, pady=10, padx=10)
212
+ console_output = scrolledtext.ScrolledText(console_frame, height=10, bg='black', fg='white', insertbackground='white')
213
+ console_output.grid(row=1, column=0, sticky="nsew")
214
+ console_frame.grid_rowconfigure(1, weight=1)
215
+ console_frame.grid_columnconfigure(0, weight=1)
216
+ print("Console setup complete")
217
+ return console_output
218
+
219
+ def setup_progress_frame(vertical_container):
220
+ global progress_output
221
+ progress_frame = tk.Frame(vertical_container, bg='black')
222
+ vertical_container.add(progress_frame, stretch="always")
223
+ label_frame = tk.Frame(progress_frame, bg='black')
224
+ label_frame.grid(row=0, column=0, sticky="ew", pady=(5, 0), padx=10)
225
+ progress_label = spacrLabel(label_frame, text="Processing: 0%", background="black", foreground="white", font=('Helvetica', 12), anchor='w', justify='left', align="left")
226
+ progress_label.grid(row=0, column=0, sticky="w")
227
+ progress_output = scrolledtext.ScrolledText(progress_frame, height=10, bg='black', fg='white', insertbackground='white')
228
+ progress_output.grid(row=1, column=0, sticky="nsew")
229
+ progress_frame.grid_rowconfigure(1, weight=1)
230
+ progress_frame.grid_columnconfigure(0, weight=1)
231
+ print("Progress frame setup complete")
232
+ return progress_output
233
+
234
+ def download_hug_dataset():
235
+ global vars_dict, q
236
+ dataset_repo_id = "einarolafsson/toxo_mito"
237
+ settings_repo_id = "einarolafsson/spacr_settings"
238
+ dataset_subfolder = "plate1"
239
+ local_dir = os.path.join(os.path.expanduser("~"), "datasets") # Set to the home directory
240
+
241
+ # Download the dataset
242
+ try:
243
+ dataset_path = download_dataset(dataset_repo_id, dataset_subfolder, local_dir)
244
+ if 'src' in vars_dict:
245
+ vars_dict['src'][2].set(dataset_path)
246
+ q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
247
+ q.put(f"Dataset downloaded to: {dataset_path}\n")
248
+ except Exception as e:
249
+ q.put(f"Failed to download dataset: {e}\n")
250
+
251
+ # Download the settings files
252
+ try:
253
+ settings_path = download_dataset(settings_repo_id, "", local_dir)
254
+ q.put(f"Settings downloaded to: {settings_path}\n")
255
+ except Exception as e:
256
+ q.put(f"Failed to download settings: {e}\n")
257
+
258
+ def download_dataset(repo_id, subfolder, local_dir=None, retries=5, delay=5):
259
+ global q
260
+ """
261
+ Downloads a dataset or settings files from Hugging Face and returns the local path.
262
+
263
+ Args:
264
+ repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito' or 'einarolafsson/spacr_settings').
265
+ subfolder (str): The subfolder path within the repository (e.g., 'plate1' or the settings subfolder).
266
+ local_dir (str): The local directory where the files will be saved. Defaults to the user's home directory.
267
+ retries (int): Number of retry attempts in case of failure.
268
+ delay (int): Delay in seconds between retries.
269
+
270
+ Returns:
271
+ str: The local path to the downloaded files.
272
+ """
273
+ if local_dir is None:
274
+ local_dir = os.path.join(os.path.expanduser("~"), "datasets")
275
+
276
+ local_subfolder_dir = os.path.join(local_dir, subfolder if subfolder else "settings")
277
+ if not os.path.exists(local_subfolder_dir):
278
+ os.makedirs(local_subfolder_dir)
279
+ elif len(os.listdir(local_subfolder_dir)) > 0:
280
+ q.put(f"Files already downloaded to: {local_subfolder_dir}")
281
+ return local_subfolder_dir
282
+
283
+ attempt = 0
284
+ while attempt < retries:
285
+ try:
286
+ files = list_repo_files(repo_id, repo_type="dataset")
287
+ subfolder_files = [file for file in files if file.startswith(subfolder) or (subfolder == "" and file.endswith('.csv'))]
288
+
289
+ for file_name in subfolder_files:
290
+ for download_attempt in range(retries):
291
+ try:
292
+ url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
293
+ response = requests.get(url, stream=True)
294
+ response.raise_for_status()
295
+
296
+ local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
297
+ with open(local_file_path, 'wb') as file:
298
+ for chunk in response.iter_content(chunk_size=8192):
299
+ file.write(chunk)
300
+ q.put(f"Downloaded file: {file_name}")
301
+ break
302
+ except (requests.HTTPError, requests.Timeout) as e:
303
+ q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
304
+ time.sleep(delay)
305
+ else:
306
+ raise Exception(f"Failed to download {file_name} after multiple attempts.")
307
+
308
+ return local_subfolder_dir
309
+
310
+ except (requests.HTTPError, requests.Timeout) as e:
311
+ q.put(f"Error downloading files: {e}. Retrying in {delay} seconds...")
312
+ attempt += 1
313
+ time.sleep(delay)
314
+
315
+ raise Exception("Failed to download files after multiple attempts.")
316
+
317
+
318
+ def setup_button_section(horizontal_container, settings_type='mask', settings_row=5, run=True, abort=True, download=True, import_btn=True):
319
+ global button_frame, run_button, abort_button, download_dataset_button, import_button, q, fig_queue, vars_dict
320
+
321
+ button_frame = tk.Frame(horizontal_container, bg='black')
322
+ horizontal_container.add(button_frame, stretch="always", sticky="nsew")
323
+ button_frame.grid_rowconfigure(0, weight=0)
324
+ button_frame.grid_rowconfigure(1, weight=1)
325
+ button_frame.grid_columnconfigure(0, weight=1)
326
+
327
+ categories_label = spacrLabel(button_frame, text="Categories", background="black", foreground="white", font=('Helvetica', 12), anchor='center', justify='center', align="center") # Increase font size
328
+ categories_label.grid(row=0, column=0, pady=10, padx=10)
329
+
330
+ button_scrollable_frame = spacrFrame(button_frame, bg='black')
331
+ button_scrollable_frame.grid(row=1, column=0, sticky="nsew")
332
+
333
+ btn_col = 0
334
+ btn_row = 1
335
+
336
+ if run:
337
+ run_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Run", command=lambda: start_process(q, fig_queue, settings_type), font=('Helvetica', 12))
338
+ run_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
339
+ btn_row += 1
340
+
341
+ if abort and settings_type in ['mask', 'measure', 'classify', 'sequencing', 'umap']:
342
+ abort_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort, font=('Helvetica', 12))
343
+ abort_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
344
+ btn_row += 1
345
+
346
+ if download and settings_type in ['mask']:
347
+ download_dataset_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Download", command=download_hug_dataset, font=('Helvetica', 12))
348
+ download_dataset_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
349
+ btn_row += 1
350
+
351
+ if import_btn:
352
+ import_button = spacrButton(button_scrollable_frame.scrollable_frame, text="Import", command=lambda: import_settings(settings_type), font=('Helvetica', 12))
353
+ import_button.grid(row=btn_row, column=btn_col, pady=5, padx=5, sticky='ew')
354
+
355
+ # Call toggle_settings after vars_dict is initialized
356
+ if vars_dict is not None:
357
+ toggle_settings(button_scrollable_frame)
358
+ return button_scrollable_frame
359
+
360
+ def toggle_settings_v1(button_scrollable_frame):
361
+ global vars_dict
362
+ from .settings import categories
363
+
364
+ if vars_dict is None:
365
+ raise ValueError("vars_dict is not initialized.")
366
+
367
+ def toggle_category(settings, var):
368
+ for setting in settings:
369
+ if setting in vars_dict:
370
+ label, widget, _ = vars_dict[setting]
371
+ if var.get() == 0:
372
+ label.grid_remove()
373
+ widget.grid_remove()
374
+ else:
375
+ label.grid()
376
+ widget.grid()
377
+
378
+ row = 1
379
+ col = 3
380
+ category_idx = 0
381
+
382
+ for category, settings in categories.items():
383
+ if any(setting in vars_dict for setting in settings):
384
+ category_var = tk.IntVar(value=0)
385
+ vars_dict[category] = (None, None, category_var)
386
+ toggle = spacrCheckbutton(
387
+ button_scrollable_frame.scrollable_frame,
388
+ text=category,
389
+ variable=category_var,
390
+ command=lambda cat=settings, var=category_var: toggle_category(cat, var)
391
+ )
392
+ # Directly set the style
393
+ style = ttk.Style()
394
+ font_style = tkFont.Font(family="Helvetica", size=12, weight="bold")
395
+ style.configure('Spacr.TCheckbutton', font=font_style, background='black', foreground='#ffffff', indicatoron=False, relief='flat')
396
+ style.map('Spacr.TCheckbutton', background=[('selected', 'black'), ('active', 'black')], foreground=[('selected', '#ffffff'), ('active', '#ffffff')])
397
+ toggle.configure(style='Spacr.TCheckbutton')
398
+ toggle.grid(row=row, column=col, sticky="w", pady=2, padx=2)
399
+ #row += 1
400
+ col += 1
401
+ category_idx += 1
402
+
403
+ if category_idx % 4 == 0:
404
+ row += 1
405
+ col = 2
406
+
407
+ for settings in categories.values():
408
+ for setting in settings:
409
+ if setting in vars_dict:
410
+ label, widget, _ = vars_dict[setting]
411
+ label.grid_remove()
412
+ widget.grid_remove()
413
+
414
+
415
+ def toggle_settings(button_scrollable_frame):
416
+ global vars_dict
417
+ from .settings import categories
418
+
419
+ if vars_dict is None:
420
+ raise ValueError("vars_dict is not initialized.")
421
+
422
+ active_categories = set()
423
+
424
+ def toggle_category(settings):
425
+ for setting in settings:
426
+ if setting in vars_dict:
427
+ label, widget, _ = vars_dict[setting]
428
+ if widget.grid_info():
429
+ label.grid_remove()
430
+ widget.grid_remove()
431
+ else:
432
+ label.grid()
433
+ widget.grid()
434
+
435
+ def on_category_select(selected_category):
436
+ if selected_category == "Select Category":
437
+ return
438
+ #print(f"Selected category: {selected_category}")
439
+ if selected_category in categories:
440
+ toggle_category(categories[selected_category])
441
+ if selected_category in active_categories:
442
+ active_categories.remove(selected_category)
443
+ else:
444
+ active_categories.add(selected_category)
445
+ category_dropdown.update_styles(active_categories)
446
+ category_var.set("Select Category") # Reset dropdown text to "Select Category"
447
+
448
+ category_var = tk.StringVar()
449
+ non_empty_categories = [category for category, settings in categories.items() if any(setting in vars_dict for setting in settings)]
450
+ category_dropdown = spacrDropdownMenu(button_scrollable_frame.scrollable_frame, category_var, non_empty_categories, command=on_category_select)
451
+ category_dropdown.grid(row=1, column=3, sticky="ew", pady=2, padx=2)
452
+
453
+ for category, settings in categories.items():
454
+ if any(setting in vars_dict for setting in settings):
455
+ vars_dict[category] = (None, None, tk.IntVar(value=0))
456
+ # Initially hide all settings
457
+ for setting in settings:
458
+ if setting in vars_dict:
459
+ label, widget, _ = vars_dict[setting]
460
+ label.grid_remove()
461
+ widget.grid_remove()
462
+
463
+ def process_fig_queue():
464
+ global canvas, fig_queue, canvas_widget, parent_frame
465
+
466
+ def clear_canvas(canvas):
467
+ for ax in canvas.figure.get_axes():
468
+ ax.clear()
469
+ canvas.draw_idle()
470
+
471
+ try:
472
+ while not fig_queue.empty():
473
+ clear_canvas(canvas)
474
+ fig = fig_queue.get_nowait()
475
+ for ax in fig.get_axes():
476
+ ax.set_xticks([]) # Remove x-axis ticks
477
+ ax.set_yticks([]) # Remove y-axis ticks
478
+ ax.xaxis.set_visible(False) # Hide the x-axis
479
+ ax.yaxis.set_visible(False) # Hide the y-axis
480
+ fig.tight_layout()
481
+ fig.set_facecolor('black')
482
+ canvas.figure = fig
483
+ fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
484
+ fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
485
+ canvas.draw_idle()
486
+ except Exception as e:
487
+ traceback.print_exc()
488
+ finally:
489
+ after_id = canvas_widget.after(100, process_fig_queue)
490
+ parent_frame.after_tasks.append(after_id)
491
+
492
+ def process_console_queue():
493
+ global q, console_output, parent_frame
494
+ while not q.empty():
495
+ message = q.get_nowait()
496
+ console_output.insert(tk.END, message)
497
+ console_output.see(tk.END)
498
+ after_id = console_output.after(100, process_console_queue)
499
+ parent_frame.after_tasks.append(after_id)
500
+
501
+ def set_globals(q_var, console_output_var, parent_frame_var, vars_dict_var, canvas_var, canvas_widget_var, scrollable_frame_var, progress_label_var, fig_queue_var):
502
+ global q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, progress_label, fig_queue
503
+ q = q_var
504
+ console_output = console_output_var
505
+ parent_frame = parent_frame_var
506
+ vars_dict = vars_dict_var
507
+ canvas = canvas_var
508
+ canvas_widget = canvas_widget_var
509
+ scrollable_frame = scrollable_frame_var
510
+ progress_label = progress_label_var
511
+ fig_queue = fig_queue_var
512
+
513
+ def setup_frame(parent_frame):
514
+ style = ttk.Style(parent_frame)
515
+ set_dark_style(style)
516
+ set_default_font(parent_frame, font_name="Helvetica", size=8)
517
+ parent_frame.configure(bg='black')
518
+ parent_frame.grid_rowconfigure(0, weight=1)
519
+ parent_frame.grid_columnconfigure(0, weight=1)
520
+ vertical_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL, bg='black')
521
+ vertical_container.grid(row=0, column=0, sticky=tk.NSEW)
522
+ horizontal_container = tk.PanedWindow(vertical_container, orient=tk.HORIZONTAL, bg='black')
523
+ vertical_container.add(horizontal_container, stretch="always")
524
+ horizontal_container.grid_columnconfigure(0, weight=1)
525
+ horizontal_container.grid_columnconfigure(1, weight=1)
526
+ settings_frame = tk.Frame(horizontal_container, bg='black')
527
+ settings_frame.grid_rowconfigure(0, weight=0)
528
+ settings_frame.grid_rowconfigure(1, weight=1)
529
+ settings_frame.grid_columnconfigure(0, weight=1)
530
+ horizontal_container.add(settings_frame, stretch="always", sticky="nsew")
531
+ return parent_frame, vertical_container, horizontal_container
532
+
533
+ def initiate_root(parent, settings_type='mask'):
534
+
535
+ def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
536
+ try:
537
+ ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
538
+ while not q.empty():
539
+ message = q.get_nowait()
540
+ clean_message = ansi_escape_pattern.sub('', message)
541
+ if clean_message.startswith("Progress"):
542
+ progress_label.config(text=clean_message)
543
+ if clean_message.startswith("\rProgress"):
544
+ progress_label.config(text=clean_message)
545
+ elif clean_message.startswith("Successfully"):
546
+ progress_label.config(text=clean_message)
547
+ elif clean_message.startswith("Processing"):
548
+ progress_label.config(text=clean_message)
549
+ elif clean_message.startswith("scale"):
550
+ pass
551
+ elif clean_message.startswith("plot_cropped_arrays"):
552
+ pass
553
+ elif clean_message == "" or clean_message == "\r" or clean_message.strip() == "":
554
+ pass
555
+ else:
556
+ print(clean_message)
557
+ except Exception as e:
558
+ print(f"Error updating GUI canvas: {e}")
559
+ finally:
560
+ root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
561
+
562
+ global q, fig_queue, parent_frame, scrollable_frame, button_frame, vars_dict, canvas, canvas_widget, progress_label, button_scrollable_frame
563
+ print("Initializing root with settings_type:", settings_type)
564
+ parent_frame = parent
565
+
566
+ if not hasattr(parent_frame, 'after_tasks'):
567
+ parent_frame.after_tasks = []
568
+
569
+ for widget in parent_frame.winfo_children():
570
+ if widget.winfo_exists():
571
+ try:
572
+ widget.destroy()
573
+ except tk.TclError as e:
574
+ print(f"Error destroying widget: {e}")
575
+
576
+ q = Queue()
577
+ fig_queue = Queue()
578
+ parent_frame, vertical_container, horizontal_container = setup_frame(parent_frame)
579
+ scrollable_frame, vars_dict = setup_settings_panel(horizontal_container, settings_type) # Adjust height and width as needed
580
+ button_scrollable_frame = setup_button_section(horizontal_container, settings_type)
581
+ canvas, canvas_widget = setup_plot_section(vertical_container)
582
+ console_output = setup_console(vertical_container)
583
+
584
+ if settings_type in ['mask', 'measure', 'classify', 'sequencing']:
585
+ progress_output = setup_progress_frame(vertical_container)
586
+ else:
587
+ progress_output = None
588
+
589
+ set_globals(q, console_output, parent_frame, vars_dict, canvas, canvas_widget, scrollable_frame, progress_label, fig_queue)
590
+ process_console_queue()
591
+ process_fig_queue()
592
+ after_id = parent_frame.after(100, lambda: main_thread_update_function(parent_frame, q, fig_queue, canvas_widget, progress_label))
593
+ parent_frame.after_tasks.append(after_id)
594
+ print("Root initialization complete")
595
+ return parent_frame, vars_dict
596
+
597
+ def start_gui_app(settings_type='mask'):
598
+ global q, fig_queue, parent_frame, scrollable_frame, vars_dict, canvas, canvas_widget, progress_label
599
+ root = tk.Tk()
600
+ width = root.winfo_screenwidth()
601
+ height = root.winfo_screenheight()
602
+ root.geometry(f"{width}x{height}")
603
+ root.title(f"SpaCr: {settings_type.capitalize()}")
604
+ root.content_frame = tk.Frame(root)
605
+ print("Starting GUI app with settings_type:", settings_type)
606
+ initiate_root(root.content_frame, settings_type)
607
+ create_menu_bar(root)
608
+ root.mainloop()