spacr 0.2.3__py3-none-any.whl → 0.2.5__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.
Files changed (61) hide show
  1. spacr/app_annotate.py +3 -4
  2. spacr/core.py +100 -282
  3. spacr/gui.py +20 -38
  4. spacr/gui_core.py +406 -499
  5. spacr/gui_elements.py +395 -60
  6. spacr/gui_utils.py +393 -73
  7. spacr/io.py +130 -50
  8. spacr/measure.py +199 -154
  9. spacr/plot.py +108 -42
  10. spacr/resources/font/open_sans/OFL.txt +93 -0
  11. spacr/resources/font/open_sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  12. spacr/resources/font/open_sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  13. spacr/resources/font/open_sans/README.txt +100 -0
  14. spacr/resources/font/open_sans/static/OpenSans-Bold.ttf +0 -0
  15. spacr/resources/font/open_sans/static/OpenSans-BoldItalic.ttf +0 -0
  16. spacr/resources/font/open_sans/static/OpenSans-ExtraBold.ttf +0 -0
  17. spacr/resources/font/open_sans/static/OpenSans-ExtraBoldItalic.ttf +0 -0
  18. spacr/resources/font/open_sans/static/OpenSans-Italic.ttf +0 -0
  19. spacr/resources/font/open_sans/static/OpenSans-Light.ttf +0 -0
  20. spacr/resources/font/open_sans/static/OpenSans-LightItalic.ttf +0 -0
  21. spacr/resources/font/open_sans/static/OpenSans-Medium.ttf +0 -0
  22. spacr/resources/font/open_sans/static/OpenSans-MediumItalic.ttf +0 -0
  23. spacr/resources/font/open_sans/static/OpenSans-Regular.ttf +0 -0
  24. spacr/resources/font/open_sans/static/OpenSans-SemiBold.ttf +0 -0
  25. spacr/resources/font/open_sans/static/OpenSans-SemiBoldItalic.ttf +0 -0
  26. spacr/resources/font/open_sans/static/OpenSans_Condensed-Bold.ttf +0 -0
  27. spacr/resources/font/open_sans/static/OpenSans_Condensed-BoldItalic.ttf +0 -0
  28. spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBold.ttf +0 -0
  29. spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
  30. spacr/resources/font/open_sans/static/OpenSans_Condensed-Italic.ttf +0 -0
  31. spacr/resources/font/open_sans/static/OpenSans_Condensed-Light.ttf +0 -0
  32. spacr/resources/font/open_sans/static/OpenSans_Condensed-LightItalic.ttf +0 -0
  33. spacr/resources/font/open_sans/static/OpenSans_Condensed-Medium.ttf +0 -0
  34. spacr/resources/font/open_sans/static/OpenSans_Condensed-MediumItalic.ttf +0 -0
  35. spacr/resources/font/open_sans/static/OpenSans_Condensed-Regular.ttf +0 -0
  36. spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBold.ttf +0 -0
  37. spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
  38. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Bold.ttf +0 -0
  39. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
  40. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
  41. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  42. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Italic.ttf +0 -0
  43. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Light.ttf +0 -0
  44. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
  45. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Medium.ttf +0 -0
  46. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
  47. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Regular.ttf +0 -0
  48. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
  49. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
  50. spacr/resources/icons/logo.pdf +2786 -6
  51. spacr/resources/icons/logo_spacr.png +0 -0
  52. spacr/resources/icons/logo_spacr_1.png +0 -0
  53. spacr/settings.py +12 -87
  54. spacr/utils.py +45 -10
  55. {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/METADATA +5 -1
  56. spacr-0.2.5.dist-info/RECORD +100 -0
  57. spacr-0.2.3.dist-info/RECORD +0 -58
  58. {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/LICENSE +0 -0
  59. {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/WHEEL +0 -0
  60. {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/entry_points.txt +0 -0
  61. {spacr-0.2.3.dist-info → spacr-0.2.5.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py CHANGED
@@ -1,48 +1,20 @@
1
- import os, io, sys, ast, ctypes, re, ast, sqlite3
1
+ import os, io, sys, ast, ctypes, ast, sqlite3, requests, time, traceback, pyautogui
2
+ import traceback
2
3
  import tkinter as tk
3
4
  from tkinter import ttk
5
+ import matplotlib
6
+ import matplotlib.pyplot as plt
7
+ matplotlib.use('Agg')
8
+ from huggingface_hub import list_repo_files
9
+ import traceback
4
10
 
5
11
  from . gui_core import initiate_root
6
- from .gui_elements import spacrLabel, spacrCheckbutton, AnnotateApp, spacrEntry, spacrCheck, spacrCombo, set_default_font
12
+ from .gui_elements import AnnotateApp, spacrEntry, spacrCheck, spacrCombo, set_default_font
7
13
 
8
14
  try:
9
15
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
10
16
  except AttributeError:
11
17
  pass
12
-
13
- def proceed_with_app_v1(root, app_name, app_func):
14
- from .gui import gui_app
15
-
16
- # Clear the current content frame
17
- if hasattr(root, 'content_frame'):
18
- for widget in root.content_frame.winfo_children():
19
- try:
20
- widget.destroy()
21
- except tk.TclError as e:
22
- print(f"Error destroying widget: {e}")
23
- else:
24
- root.content_frame = tk.Frame(root)
25
- root.content_frame.grid(row=1, column=0, sticky="nsew")
26
- root.grid_rowconfigure(1, weight=1)
27
- root.grid_columnconfigure(0, weight=1)
28
-
29
- # Initialize the new app in the content frame
30
- if app_name == "Mask":
31
- initiate_root(root.content_frame, 'mask')
32
- elif app_name == "Measure":
33
- initiate_root(root.content_frame, 'measure')
34
- elif app_name == "Classify":
35
- initiate_root(root.content_frame, 'classify')
36
- elif app_name == "Sequencing":
37
- initiate_root(root.content_frame, 'sequencing')
38
- elif app_name == "Umap":
39
- initiate_root(root.content_frame, 'umap')
40
- elif app_name == "Annotate":
41
- initiate_root(root.content_frame, 'annotate')
42
- elif app_name == "Make Masks":
43
- initiate_root(root.content_frame, 'make_masks')
44
- else:
45
- raise ValueError(f"Invalid app name: {app_name}")
46
18
 
47
19
  def proceed_with_app(root, app_name, app_func):
48
20
  # Clear the current content frame
@@ -93,37 +65,55 @@ def parse_list(value):
93
65
 
94
66
  # Usage example in your create_input_field function
95
67
  def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
68
+ from .gui_elements import set_dark_style
96
69
  label_column = 0
97
- widget_column = 1
70
+ widget_column = 0 # Both label and widget will be in the same column
71
+
72
+ style_out = set_dark_style(ttk.Style())
73
+ size_dict = set_element_size()
74
+ size_dict['settings_width'] = size_dict['settings_width'] - int(size_dict['settings_width']*0.1)
75
+
76
+ # Replace underscores with spaces and capitalize the first letter
77
+ label_text = label_text.replace('_', ' ').capitalize()
98
78
 
99
79
  # Configure the column widths
100
- frame.grid_columnconfigure(label_column, weight=0) # Allow the label column to expand
101
- frame.grid_columnconfigure(widget_column, weight=1) # Allow the widget column to expand
80
+ frame.grid_columnconfigure(label_column, weight=1) # Allow the column to expand
81
+
82
+ # Create a custom frame with a translucent background and rounded edges
83
+ custom_frame = tk.Frame(frame, bg=style_out['bg_color'], bd=2, relief='solid', width=size_dict['settings_width'])
84
+ custom_frame.grid(column=label_column, row=row, sticky=tk.EW, padx=(5, 5), pady=5)
85
+
86
+ # Apply styles to custom frame
87
+ custom_frame.update_idletasks()
88
+ custom_frame.config(highlightbackground=style_out['bg_color'], highlightthickness=1, bd=2)
89
+ #custom_frame.bind("<Enter>", lambda e: custom_frame.config(highlightbackground=style_out['active_color']))
90
+ #custom_frame.bind("<Leave>", lambda e: custom_frame.config(highlightbackground="white"))
102
91
 
103
- # Right-align the label text and the label itself
104
- label = ttk.Label(frame, text=label_text, background="black", foreground="white", anchor='e', justify='right')
105
- label.grid(column=label_column, row=row, sticky=tk.E, padx=(5, 2), pady=5) # Align label to the right
92
+ # Create and configure the label
93
+ label = ttk.Label(custom_frame, text=label_text, background=style_out['bg_color'], foreground=style_out['fg_color'], font=(style_out['font_family'], style_out['font_size']), anchor='e', justify='right')
94
+ label.grid(column=label_column, row=0, sticky=tk.W, padx=(5, 2), pady=5) # Place the label in the first row
106
95
 
96
+ # Create and configure the input widget based on var_type
107
97
  if var_type == 'entry':
108
- var = tk.StringVar(value=default_value) # Set default value
109
- entry = spacrEntry(frame, textvariable=var, outline=False)
110
- entry.grid(column=widget_column, row=row, sticky=tk.W, padx=(2, 5), pady=5) # Align widget to the left
111
- return (label, entry, var) # Return both the label and the entry, and the variable
98
+ var = tk.StringVar(value=default_value)
99
+ entry = spacrEntry(custom_frame, textvariable=var, outline=False, width=size_dict['settings_width'])
100
+ entry.grid(column=widget_column, row=1, sticky=tk.W, padx=(2, 5), pady=5) # Place the entry in the second row
101
+ return (label, entry, var, custom_frame) # Return both the label and the entry, and the variable
112
102
  elif var_type == 'check':
113
103
  var = tk.BooleanVar(value=default_value) # Set default value (True/False)
114
- check = spacrCheck(frame, text="", variable=var)
115
- check.grid(column=widget_column, row=row, sticky=tk.W, padx=(2, 5), pady=5) # Align widget to the left
116
- return (label, check, var) # Return both the label and the checkbutton, and the variable
104
+ check = spacrCheck(custom_frame, text="", variable=var)
105
+ check.grid(column=widget_column, row=1, sticky=tk.W, padx=(2, 5), pady=5) # Place the checkbutton in the second row
106
+ return (label, check, var, custom_frame) # Return both the label and the checkbutton, and the variable
117
107
  elif var_type == 'combo':
118
108
  var = tk.StringVar(value=default_value) # Set default value
119
- combo = spacrCombo(frame, textvariable=var, values=options) # Apply TCombobox style
120
- combo.grid(column=widget_column, row=row, sticky=tk.W, padx=(2, 5), pady=5) # Align widget to the left
109
+ combo = spacrCombo(custom_frame, textvariable=var, values=options, width=size_dict['settings_width']) # Apply TCombobox style
110
+ combo.grid(column=widget_column, row=1, sticky=tk.W, padx=(2, 5), pady=5) # Place the combobox in the second row
121
111
  if default_value:
122
112
  combo.set(default_value)
123
- return (label, combo, var) # Return both the label and the combobox, and the variable
113
+ return (label, combo, var, custom_frame) # Return both the label and the combobox, and the variable
124
114
  else:
125
115
  var = None # Placeholder in case of an undefined var_type
126
- return (label, None, var)
116
+ return (label, None, var, custom_frame)
127
117
 
128
118
  def process_stdout_stderr(q):
129
119
  """
@@ -139,10 +129,9 @@ class WriteToQueue(io.TextIOBase):
139
129
  """
140
130
  def __init__(self, q):
141
131
  self.q = q
142
-
143
132
  def write(self, msg):
144
- self.q.put(msg)
145
-
133
+ if msg.strip(): # Avoid empty messages
134
+ self.q.put(msg)
146
135
  def flush(self):
147
136
  pass
148
137
 
@@ -157,23 +146,6 @@ def main_thread_update_function(root, q, fig_queue, canvas_widget):
157
146
  #ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
158
147
  while not q.empty():
159
148
  message = q.get_nowait()
160
- #clean_message = ansi_escape_pattern.sub('', message)
161
- #if clean_message.startswith("Progress"):
162
- # progress_label.config(text=clean_message)
163
- #if clean_message.startswith("\rProgress"):
164
- # progress_label.config(text=clean_message)
165
- #elif clean_message.startswith("Successfully"):
166
- # progress_label.config(text=clean_message)
167
- #elif clean_message.startswith("Processing"):
168
- # progress_label.config(text=clean_message)
169
- #elif clean_message.startswith("scale"):
170
- # pass
171
- #elif clean_message.startswith("plot_cropped_arrays"):
172
- # pass
173
- #elif clean_message == "" or clean_message == "\r" or clean_message.strip() == "":
174
- # pass
175
- #else:
176
- # print(clean_message)
177
149
  except Exception as e:
178
150
  print(f"Error updating GUI canvas: {e}")
179
151
  finally:
@@ -318,3 +290,351 @@ def annotate_with_image_refs(settings, root, shutdown_callback):
318
290
  # Call load_images after setting up the root window
319
291
  app.load_images()
320
292
 
293
+ def annotate_with_image_refs(settings, root, shutdown_callback):
294
+ from .settings import set_annotate_default_settings
295
+
296
+ settings = set_annotate_default_settings(settings)
297
+ src = settings['src']
298
+
299
+ db = os.path.join(src, 'measurements/measurements.db')
300
+ conn = sqlite3.connect(db)
301
+ c = conn.cursor()
302
+ c.execute('PRAGMA table_info(png_list)')
303
+ cols = c.fetchall()
304
+ if settings['annotation_column'] not in [col[1] for col in cols]:
305
+ c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
306
+ conn.commit()
307
+ conn.close()
308
+
309
+ screen_width = root.winfo_screenwidth()
310
+ screen_height = root.winfo_screenheight()
311
+ root.geometry(f"{screen_width}x{screen_height}")
312
+
313
+ app = AnnotateApp(root, db, src, image_type=settings['image_type'], channels=settings['channels'], image_size=settings['img_size'], annotation_column=settings['annotation_column'], normalize=settings['normalize'], percentiles=settings['percentiles'], measurement=settings['measurement'], threshold=settings['threshold'])
314
+
315
+ # Set the canvas background to black
316
+ root.configure(bg='black')
317
+
318
+ # Store the shutdown function and next app details in the root
319
+ root.current_app_exit_func = lambda: [app.shutdown(), shutdown_callback()]
320
+
321
+ # Call load_images after setting up the root window
322
+ app.load_images()
323
+
324
+ def set_element_size():
325
+
326
+ screen_width, screen_height = pyautogui.size()
327
+ screen_area = screen_width * screen_height
328
+
329
+ # Calculate sizes based on screen dimensions
330
+ btn_size = int((screen_area * 0.002) ** 0.5) # Button size as a fraction of screen area
331
+ bar_size = screen_height // 30 # Bar size based on screen height
332
+ settings_width = screen_width // 4 # Settings panel width as a fraction of screen width
333
+ panel_width = screen_width - settings_width # Panel width as a fraction of screen width
334
+ panel_height = screen_height // 10 # Panel height as a fraction of screen height
335
+
336
+ size_dict = {
337
+ 'btn_size': btn_size,
338
+ 'bar_size': bar_size,
339
+ 'settings_width': settings_width,
340
+ 'panel_width': panel_width,
341
+ 'panel_height': panel_height
342
+ }
343
+ return size_dict
344
+
345
+ def convert_settings_dict_for_gui(settings):
346
+ from torchvision import models as torch_models
347
+ torchvision_models = [name for name, obj in torch_models.__dict__.items() if callable(obj)]
348
+ chans = ['0', '1', '2', '3', '4', '5', '6', '7', '8', None]
349
+ chans_v2 = [0, 1, 2, 3, None]
350
+ variables = {}
351
+ special_cases = {
352
+ 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
353
+ 'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
354
+ 'channel_dims': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
355
+ 'cell_mask_dim': ('combo', chans, None),
356
+ 'cell_chann_dim': ('combo', chans, None),
357
+ 'nucleus_mask_dim': ('combo', chans, None),
358
+ 'nucleus_chann_dim': ('combo', chans, None),
359
+ 'pathogen_mask_dim': ('combo', chans, None),
360
+ 'pathogen_chann_dim': ('combo', chans, None),
361
+ 'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
362
+ 'magnification': ('combo', [20, 40, 60], 20),
363
+ 'nucleus_channel': ('combo', chans_v2, None),
364
+ 'cell_channel': ('combo', chans_v2, None),
365
+ 'channel_of_interest': ('combo', chans_v2, None),
366
+ 'pathogen_channel': ('combo', chans_v2, None),
367
+ 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
368
+ 'train_mode': ('combo', ['erm', 'irm'], 'erm'),
369
+ 'clustering': ('combo', ['dbscan', 'kmean'], 'dbscan'),
370
+ 'reduction_method': ('combo', ['umap', 'tsne'], 'umap'),
371
+ 'model_name': ('combo', ['cyto', 'cyto_2', 'cyto_3', 'nuclei'], 'cyto'),
372
+ 'regression_type': ('combo', ['ols','gls','wls','rlm','glm','mixed','quantile','logit','probit','poisson','lasso','ridge'], 'ols'),
373
+ 'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
374
+ 'model_type': ('combo', torchvision_models, 'resnet50'),
375
+ 'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
376
+ 'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
377
+ 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
378
+ 'normalize_by': ('combo', ['fov', 'png'], 'png'),
379
+ 'agg_type': ('combo', ['mean', 'median'], 'mean'),
380
+ 'grouping': ('combo', ['mean', 'median'], 'mean'),
381
+ 'min_max': ('combo', ['allq', 'all'], 'allq'),
382
+ 'transform': ('combo', ['log', 'sqrt', 'square', None], None)
383
+ }
384
+
385
+ for key, value in settings.items():
386
+ if key in special_cases:
387
+ variables[key] = special_cases[key]
388
+ elif isinstance(value, bool):
389
+ variables[key] = ('check', None, value)
390
+ elif isinstance(value, int) or isinstance(value, float):
391
+ variables[key] = ('entry', None, value)
392
+ elif isinstance(value, str):
393
+ variables[key] = ('entry', None, value)
394
+ elif value is None:
395
+ variables[key] = ('entry', None, value)
396
+ elif isinstance(value, list):
397
+ variables[key] = ('entry', None, str(value))
398
+ else:
399
+ variables[key] = ('entry', None, str(value))
400
+ return variables
401
+
402
+
403
+ def spacrFigShow(fig_queue=None):
404
+ """
405
+ Replacement for plt.show() that queues figures instead of displaying them.
406
+ """
407
+ fig = plt.gcf()
408
+ if fig_queue:
409
+ fig_queue.put(fig)
410
+ else:
411
+ fig.show()
412
+ plt.close(fig)
413
+
414
+ def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imports=1):
415
+
416
+ """
417
+ Wraps the run_multiple_simulations function to integrate with GUI processes.
418
+
419
+ Parameters:
420
+ - settings: dict, The settings for the run_multiple_simulations function.
421
+ - q: multiprocessing.Queue, Queue for logging messages to the GUI.
422
+ - fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
423
+ """
424
+
425
+ # Temporarily override plt.show
426
+ original_show = plt.show
427
+ plt.show = lambda: spacrFigShow(fig_queue)
428
+
429
+ try:
430
+ if imports == 1:
431
+ function(settings=settings)
432
+ elif imports == 2:
433
+ function(src=settings['src'], settings=settings)
434
+ except Exception as e:
435
+ # Send the error message to the GUI via the queue
436
+ errorMessage = f"Error during processing: {e}"
437
+ q.put(errorMessage)
438
+ traceback.print_exc()
439
+ finally:
440
+ # Restore the original plt.show function
441
+ plt.show = original_show
442
+
443
+
444
+ def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
445
+ from .gui_utils import process_stdout_stderr
446
+ 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
447
+ from .io import generate_cellpose_train_test
448
+ from .measure import measure_crop
449
+ from .sim import run_multiple_simulations
450
+ from .deep_spacr import train_test_model
451
+ from .sequencing import analyze_reads, map_barcodes_folder, perform_regression
452
+ process_stdout_stderr(q)
453
+
454
+ print(f'run_function_gui settings_type: {settings_type}')
455
+
456
+ if settings_type == 'mask':
457
+ function = preprocess_generate_masks
458
+ imports = 2
459
+ elif settings_type == 'measure':
460
+ function = measure_crop
461
+ imports = 1
462
+ elif settings_type == 'simulation':
463
+ function = run_multiple_simulations
464
+ imports = 1
465
+ elif settings_type == 'sequencing':
466
+ function = analyze_reads
467
+ imports = 1
468
+ elif settings_type == 'classify':
469
+ function = train_test_model
470
+ imports = 2
471
+ elif settings_type == 'train_cellpose':
472
+ function = train_cellpose
473
+ imports = 1
474
+ elif settings_type == 'ml_analyze':
475
+ function = generate_ml_scores
476
+ imports = 2
477
+ elif settings_type == 'cellpose_masks':
478
+ function = identify_masks_finetune
479
+ imports = 1
480
+ elif settings_type == 'cellpose_all':
481
+ function = check_cellpose_models
482
+ imports = 1
483
+ elif settings_type == 'map_barcodes':
484
+ function = map_barcodes_folder
485
+ imports = 2
486
+ elif settings_type == 'regression':
487
+ function = perform_regression
488
+ imports = 2
489
+ elif settings_type == 'recruitment':
490
+ function = analyze_recruitment
491
+ imports = 2
492
+ else:
493
+ raise ValueError(f"Invalid settings type: {settings_type}")
494
+ try:
495
+ function_gui_wrapper(function, settings, q, fig_queue, imports)
496
+ except Exception as e:
497
+ q.put(f"Error during processing: {e}")
498
+ traceback.print_exc()
499
+ finally:
500
+ stop_requested.value = 1
501
+
502
+
503
+ def hide_all_settings(vars_dict, categories):
504
+ """
505
+ Function to initially hide all settings in the GUI.
506
+
507
+ Parameters:
508
+ - categories: dict, The categories of settings with their corresponding settings.
509
+ - vars_dict: dict, The dictionary containing the settings and their corresponding widgets.
510
+ """
511
+
512
+ if categories is None:
513
+ from .settings import categories
514
+
515
+ for category, settings in categories.items():
516
+ if any(setting in vars_dict for setting in settings):
517
+ vars_dict[category] = (None, None, tk.IntVar(value=0), None)
518
+
519
+ # Initially hide all settings
520
+ for setting in settings:
521
+ if setting in vars_dict:
522
+ label, widget, _, frame = vars_dict[setting]
523
+ label.grid_remove()
524
+ widget.grid_remove()
525
+ frame.grid_remove()
526
+ return vars_dict
527
+
528
+ def setup_frame(parent_frame):
529
+ from .gui_elements import set_dark_style, set_default_font
530
+
531
+ style = ttk.Style(parent_frame)
532
+ size_dict = set_element_size()
533
+ style_out = set_dark_style(style)
534
+
535
+ settings_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL, width=size_dict['settings_width'], bg=style_out['bg_color'])
536
+ vertical_container = tk.PanedWindow(parent_frame, orient=tk.VERTICAL, width=size_dict['panel_width'], bg=style_out['bg_color'])
537
+ horizontal_container = tk.PanedWindow(parent_frame, orient=tk.HORIZONTAL, height=size_dict['panel_height'], width=size_dict['panel_width'], bg=style_out['bg_color'])
538
+
539
+ parent_frame.grid_rowconfigure(0, weight=1)
540
+ parent_frame.grid_rowconfigure(1, weight=0)
541
+ parent_frame.grid_columnconfigure(0, weight=0)
542
+ parent_frame.grid_columnconfigure(1, weight=1)
543
+
544
+ settings_container.grid(row=0, column=0, rowspan=2, sticky="nsew")
545
+ vertical_container.grid(row=0, column=1, sticky="nsew")
546
+ horizontal_container.grid(row=1, column=1, sticky="ew")
547
+
548
+ # Ensure settings_container maintains its width
549
+ settings_container.grid_propagate(False)
550
+ settings_container.update_idletasks()
551
+
552
+ tk.Label(settings_container, text="Settings Container", bg=style_out['bg_color']).grid(row=0, column=0, sticky="ew")
553
+
554
+ set_dark_style(style, parent_frame, [settings_container, vertical_container, horizontal_container])
555
+
556
+ size = style_out['font_size'] - 2
557
+ set_default_font(parent_frame, font_name=style_out['font_family'], size=size)
558
+
559
+ return parent_frame, vertical_container, horizontal_container, settings_container
560
+
561
+ def download_hug_dataset(q, vars_dict):
562
+ dataset_repo_id = "einarolafsson/toxo_mito"
563
+ settings_repo_id = "einarolafsson/spacr_settings"
564
+ dataset_subfolder = "plate1"
565
+ local_dir = os.path.join(os.path.expanduser("~"), "datasets")
566
+
567
+ # Download the dataset
568
+ try:
569
+ dataset_path = download_dataset(q, dataset_repo_id, dataset_subfolder, local_dir)
570
+ if 'src' in vars_dict:
571
+ vars_dict['src'][2].set(dataset_path)
572
+ q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
573
+ q.put(f"Dataset downloaded to: {dataset_path}\n")
574
+ except Exception as e:
575
+ q.put(f"Failed to download dataset: {e}\n")
576
+
577
+ # Download the settings files
578
+ try:
579
+ settings_path = download_dataset(q, settings_repo_id, "", local_dir)
580
+ q.put(f"Settings downloaded to: {settings_path}\n")
581
+ except Exception as e:
582
+ q.put(f"Failed to download settings: {e}\n")
583
+
584
+ def download_dataset(q, repo_id, subfolder, local_dir=None, retries=5, delay=5):
585
+ """
586
+ Downloads a dataset or settings files from Hugging Face and returns the local path.
587
+
588
+ Args:
589
+ repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito' or 'einarolafsson/spacr_settings').
590
+ subfolder (str): The subfolder path within the repository (e.g., 'plate1' or the settings subfolder).
591
+ local_dir (str): The local directory where the files will be saved. Defaults to the user's home directory.
592
+ retries (int): Number of retry attempts in case of failure.
593
+ delay (int): Delay in seconds between retries.
594
+
595
+ Returns:
596
+ str: The local path to the downloaded files.
597
+ """
598
+ if local_dir is None:
599
+ local_dir = os.path.join(os.path.expanduser("~"), "datasets")
600
+
601
+ local_subfolder_dir = os.path.join(local_dir, subfolder if subfolder else "settings")
602
+ if not os.path.exists(local_subfolder_dir):
603
+ os.makedirs(local_subfolder_dir)
604
+ elif len(os.listdir(local_subfolder_dir)) > 0:
605
+ q.put(f"Files already downloaded to: {local_subfolder_dir}")
606
+ return local_subfolder_dir
607
+
608
+ attempt = 0
609
+ while attempt < retries:
610
+ try:
611
+ files = list_repo_files(repo_id, repo_type="dataset")
612
+ subfolder_files = [file for file in files if file.startswith(subfolder) or (subfolder == "" and file.endswith('.csv'))]
613
+
614
+ for file_name in subfolder_files:
615
+ for download_attempt in range(retries):
616
+ try:
617
+ url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
618
+ response = requests.get(url, stream=True)
619
+ response.raise_for_status()
620
+
621
+ local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
622
+ with open(local_file_path, 'wb') as file:
623
+ for chunk in response.iter_content(chunk_size=8192):
624
+ file.write(chunk)
625
+ q.put(f"Downloaded file: {file_name}")
626
+ break
627
+ except (requests.HTTPError, requests.Timeout) as e:
628
+ q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
629
+ time.sleep(delay)
630
+ else:
631
+ raise Exception(f"Failed to download {file_name} after multiple attempts.")
632
+
633
+ return local_subfolder_dir
634
+
635
+ except (requests.HTTPError, requests.Timeout) as e:
636
+ q.put(f"Error downloading files: {e}. Retrying in {delay} seconds...")
637
+ attempt += 1
638
+ time.sleep(delay)
639
+
640
+ raise Exception("Failed to download files after multiple attempts.")