spacr 0.2.4__py3-none-any.whl → 0.2.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. spacr/__init__.py +1 -11
  2. spacr/core.py +277 -349
  3. spacr/deep_spacr.py +248 -269
  4. spacr/gui.py +58 -54
  5. spacr/gui_core.py +689 -535
  6. spacr/gui_elements.py +1002 -153
  7. spacr/gui_utils.py +452 -107
  8. spacr/io.py +158 -91
  9. spacr/measure.py +199 -151
  10. spacr/plot.py +159 -47
  11. spacr/resources/font/open_sans/OFL.txt +93 -0
  12. spacr/resources/font/open_sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  13. spacr/resources/font/open_sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  14. spacr/resources/font/open_sans/README.txt +100 -0
  15. spacr/resources/font/open_sans/static/OpenSans-Bold.ttf +0 -0
  16. spacr/resources/font/open_sans/static/OpenSans-BoldItalic.ttf +0 -0
  17. spacr/resources/font/open_sans/static/OpenSans-ExtraBold.ttf +0 -0
  18. spacr/resources/font/open_sans/static/OpenSans-ExtraBoldItalic.ttf +0 -0
  19. spacr/resources/font/open_sans/static/OpenSans-Italic.ttf +0 -0
  20. spacr/resources/font/open_sans/static/OpenSans-Light.ttf +0 -0
  21. spacr/resources/font/open_sans/static/OpenSans-LightItalic.ttf +0 -0
  22. spacr/resources/font/open_sans/static/OpenSans-Medium.ttf +0 -0
  23. spacr/resources/font/open_sans/static/OpenSans-MediumItalic.ttf +0 -0
  24. spacr/resources/font/open_sans/static/OpenSans-Regular.ttf +0 -0
  25. spacr/resources/font/open_sans/static/OpenSans-SemiBold.ttf +0 -0
  26. spacr/resources/font/open_sans/static/OpenSans-SemiBoldItalic.ttf +0 -0
  27. spacr/resources/font/open_sans/static/OpenSans_Condensed-Bold.ttf +0 -0
  28. spacr/resources/font/open_sans/static/OpenSans_Condensed-BoldItalic.ttf +0 -0
  29. spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBold.ttf +0 -0
  30. spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
  31. spacr/resources/font/open_sans/static/OpenSans_Condensed-Italic.ttf +0 -0
  32. spacr/resources/font/open_sans/static/OpenSans_Condensed-Light.ttf +0 -0
  33. spacr/resources/font/open_sans/static/OpenSans_Condensed-LightItalic.ttf +0 -0
  34. spacr/resources/font/open_sans/static/OpenSans_Condensed-Medium.ttf +0 -0
  35. spacr/resources/font/open_sans/static/OpenSans_Condensed-MediumItalic.ttf +0 -0
  36. spacr/resources/font/open_sans/static/OpenSans_Condensed-Regular.ttf +0 -0
  37. spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBold.ttf +0 -0
  38. spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
  39. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Bold.ttf +0 -0
  40. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
  41. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
  42. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  43. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Italic.ttf +0 -0
  44. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Light.ttf +0 -0
  45. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
  46. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Medium.ttf +0 -0
  47. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
  48. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Regular.ttf +0 -0
  49. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
  50. spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
  51. spacr/resources/icons/logo.pdf +2786 -6
  52. spacr/resources/icons/logo_spacr.png +0 -0
  53. spacr/resources/icons/logo_spacr_1.png +0 -0
  54. spacr/sequencing.py +477 -587
  55. spacr/settings.py +217 -144
  56. spacr/utils.py +46 -46
  57. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/METADATA +46 -35
  58. spacr-0.2.8.dist-info/RECORD +100 -0
  59. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/WHEEL +1 -1
  60. spacr-0.2.4.dist-info/RECORD +0 -58
  61. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/LICENSE +0 -0
  62. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/entry_points.txt +0 -0
  63. {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/top_level.txt +0 -0
spacr/gui_utils.py CHANGED
@@ -1,48 +1,48 @@
1
- import os, io, sys, ast, ctypes, re, ast, sqlite3
1
+ import os, io, sys, ast, ctypes, ast, sqlite3, requests, time, traceback, torch
2
2
  import tkinter as tk
3
3
  from tkinter import ttk
4
+ import matplotlib
5
+ import matplotlib.pyplot as plt
6
+ matplotlib.use('Agg')
7
+ from huggingface_hub import list_repo_files
8
+ import psutil
4
9
 
5
- from . gui_core import initiate_root
6
- from .gui_elements import spacrLabel, spacrCheckbutton, AnnotateApp, spacrEntry, spacrCheck, spacrCombo, set_default_font
10
+ from .gui_elements import AnnotateApp, spacrEntry, spacrCheck, spacrCombo
7
11
 
8
12
  try:
9
13
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
10
14
  except AttributeError:
11
15
  pass
12
16
 
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}")
17
+ def initialize_cuda():
18
+ """
19
+ Initializes CUDA in the main process by performing a simple GPU operation.
20
+ """
21
+ if torch.cuda.is_available():
22
+ # Allocate a small tensor on the GPU
23
+ _ = torch.tensor([0.0], device='cuda')
24
+ print("CUDA initialized in the main process.")
23
25
  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)
26
+ print("CUDA is not available.")
28
27
 
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}")
28
+ def set_high_priority(process):
29
+ try:
30
+ p = psutil.Process(process.pid)
31
+ if os.name == 'nt': # Windows
32
+ p.nice(psutil.HIGH_PRIORITY_CLASS)
33
+ else: # Unix-like systems
34
+ p.nice(-10) # Adjusted priority level
35
+ print(f"Successfully set high priority for process: {process.pid}")
36
+ except psutil.AccessDenied as e:
37
+ print(f"Access denied when trying to set high priority for process {process.pid}: {e}")
38
+ except psutil.NoSuchProcess as e:
39
+ print(f"No such process {process.pid}: {e}")
40
+ except Exception as e:
41
+ print(f"Failed to set high priority for process {process.pid}: {e}")
42
+
43
+ def set_cpu_affinity(process):
44
+ p = psutil.Process(process.pid)
45
+ p.cpu_affinity(list(range(os.cpu_count())))
46
46
 
47
47
  def proceed_with_app(root, app_name, app_func):
48
48
  # Clear the current content frame
@@ -57,6 +57,10 @@ def proceed_with_app(root, app_name, app_func):
57
57
  app_func(root.content_frame)
58
58
 
59
59
  def load_app(root, app_name, app_func):
60
+ # Clear the canvas if it exists
61
+ if root.canvas is not None:
62
+ root.clear_frame(root.canvas)
63
+
60
64
  # Cancel all scheduled after tasks
61
65
  if hasattr(root, 'after_tasks'):
62
66
  for task in root.after_tasks:
@@ -66,64 +70,115 @@ def load_app(root, app_name, app_func):
66
70
  # Exit functionality only for the annotation and make_masks apps
67
71
  if app_name not in ["Annotate", "make_masks"] and hasattr(root, 'current_app_exit_func'):
68
72
  root.next_app_func = proceed_with_app
69
- root.next_app_args = (app_name, app_func) # Ensure correct arguments
73
+ root.next_app_args = (app_name, app_func)
70
74
  root.current_app_exit_func()
71
75
  else:
72
76
  proceed_with_app(root, app_name, app_func)
73
-
74
- def parse_list_v1(value):
75
- try:
76
- parsed_value = ast.literal_eval(value)
77
- if isinstance(parsed_value, list):
78
- return parsed_value
79
- else:
80
- raise ValueError
81
- except (ValueError, SyntaxError):
82
- raise ValueError("Invalid format for list")
83
77
 
84
78
  def parse_list(value):
79
+ """
80
+ Parses a string representation of a list and returns the parsed list.
81
+
82
+ Args:
83
+ value (str): The string representation of the list.
84
+
85
+ Returns:
86
+ list: The parsed list.
87
+
88
+ Raises:
89
+ ValueError: If the input value is not a valid list format or contains mixed types or unsupported types.
90
+ """
85
91
  try:
86
92
  parsed_value = ast.literal_eval(value)
87
93
  if isinstance(parsed_value, list):
88
- return parsed_value
94
+ # Check if the list elements are homogeneous (all int or all str)
95
+ if all(isinstance(item, int) for item in parsed_value):
96
+ return parsed_value
97
+ elif all(isinstance(item, str) for item in parsed_value):
98
+ return parsed_value
99
+ else:
100
+ raise ValueError("List contains mixed types or unsupported types")
89
101
  else:
90
102
  raise ValueError(f"Expected a list but got {type(parsed_value).__name__}")
91
103
  except (ValueError, SyntaxError) as e:
92
104
  raise ValueError(f"Invalid format for list: {value}. Error: {e}")
93
-
105
+
94
106
  # Usage example in your create_input_field function
95
107
  def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
108
+ """
109
+ Create an input field in the specified frame.
110
+
111
+ Args:
112
+ frame (tk.Frame): The frame in which the input field will be created.
113
+ label_text (str): The text to be displayed as the label for the input field.
114
+ row (int): The row in which the input field will be placed.
115
+ var_type (str, optional): The type of input field to create. Defaults to 'entry'.
116
+ options (list, optional): The list of options for a combo box input field. Defaults to None.
117
+ default_value (str, optional): The default value for the input field. Defaults to None.
118
+
119
+ Returns:
120
+ tuple: A tuple containing the label, input widget, variable, and custom frame.
121
+
122
+ Raises:
123
+ Exception: If an error occurs while creating the input field.
124
+
125
+ """
126
+ from .gui_elements import set_dark_style, set_element_size
127
+
96
128
  label_column = 0
97
- widget_column = 1
129
+ widget_column = 0 # Both label and widget will be in the same column
130
+
131
+ style_out = set_dark_style(ttk.Style())
132
+ font_loader = style_out['font_loader']
133
+ font_size = style_out['font_size']
134
+ size_dict = set_element_size()
135
+ size_dict['settings_width'] = size_dict['settings_width'] - int(size_dict['settings_width']*0.1)
136
+
137
+ # Replace underscores with spaces and capitalize the first letter
138
+
139
+ label_text = label_text.replace('_', ' ').capitalize()
98
140
 
99
141
  # 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
102
-
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
106
-
107
- 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
112
- elif var_type == 'check':
113
- 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
117
- elif var_type == 'combo':
118
- 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
121
- if default_value:
122
- combo.set(default_value)
123
- return (label, combo, var) # Return both the label and the combobox, and the variable
124
- else:
125
- var = None # Placeholder in case of an undefined var_type
126
- return (label, None, var)
142
+ frame.grid_columnconfigure(label_column, weight=1) # Allow the column to expand
143
+
144
+ # Create a custom frame with a translucent background and rounded edges
145
+ custom_frame = tk.Frame(frame, bg=style_out['bg_color'], bd=2, relief='solid', width=size_dict['settings_width'])
146
+ custom_frame.grid(column=label_column, row=row, sticky=tk.EW, padx=(5, 5), pady=5)
147
+
148
+ # Apply styles to custom frame
149
+ custom_frame.update_idletasks()
150
+ custom_frame.config(highlightbackground=style_out['bg_color'], highlightthickness=1, bd=2)
151
+
152
+ # Create and configure the label
153
+ label = tk.Label(custom_frame, text=label_text, bg=style_out['bg_color'], fg=style_out['fg_color'], font=font_loader.get_font(size=font_size), anchor='e', justify='right')
154
+ label.grid(column=label_column, row=0, sticky=tk.W, padx=(5, 2), pady=5) # Place the label in the first row
155
+
156
+ # Create and configure the input widget based on var_type
157
+ try:
158
+ if var_type == 'entry':
159
+ var = tk.StringVar(value=default_value)
160
+ entry = spacrEntry(custom_frame, textvariable=var, outline=False, width=size_dict['settings_width'])
161
+ entry.grid(column=widget_column, row=1, sticky=tk.W, padx=(2, 5), pady=5) # Place the entry in the second row
162
+ return (label, entry, var, custom_frame) # Return both the label and the entry, and the variable
163
+ elif var_type == 'check':
164
+ var = tk.BooleanVar(value=default_value) # Set default value (True/False)
165
+ check = spacrCheck(custom_frame, text="", variable=var)
166
+ check.grid(column=widget_column, row=1, sticky=tk.W, padx=(2, 5), pady=5) # Place the checkbutton in the second row
167
+ return (label, check, var, custom_frame) # Return both the label and the checkbutton, and the variable
168
+ elif var_type == 'combo':
169
+ var = tk.StringVar(value=default_value) # Set default value
170
+ combo = spacrCombo(custom_frame, textvariable=var, values=options, width=size_dict['settings_width']) # Apply TCombobox style
171
+ combo.grid(column=widget_column, row=1, sticky=tk.W, padx=(2, 5), pady=5) # Place the combobox in the second row
172
+ if default_value:
173
+ combo.set(default_value)
174
+ return (label, combo, var, custom_frame) # Return both the label and the combobox, and the variable
175
+ else:
176
+ var = None # Placeholder in case of an undefined var_type
177
+ return (label, None, var, custom_frame)
178
+ except Exception as e:
179
+ traceback.print_exc()
180
+ print(f"Error creating input field: {e}")
181
+ print(f"Wrong type for {label_text} Expected {var_type}")
127
182
 
128
183
  def process_stdout_stderr(q):
129
184
  """
@@ -139,10 +194,9 @@ class WriteToQueue(io.TextIOBase):
139
194
  """
140
195
  def __init__(self, q):
141
196
  self.q = q
142
-
143
197
  def write(self, msg):
144
- self.q.put(msg)
145
-
198
+ if msg.strip(): # Avoid empty messages
199
+ self.q.put(msg)
146
200
  def flush(self):
147
201
  pass
148
202
 
@@ -152,33 +206,6 @@ def cancel_after_tasks(frame):
152
206
  frame.after_cancel(task)
153
207
  frame.after_tasks.clear()
154
208
 
155
- def main_thread_update_function(root, q, fig_queue, canvas_widget):
156
- try:
157
- #ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
158
- while not q.empty():
159
- 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
- except Exception as e:
178
- print(f"Error updating GUI canvas: {e}")
179
- finally:
180
- root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget))
181
-
182
209
  def annotate(settings):
183
210
  from .settings import set_annotate_default_settings
184
211
  settings = set_annotate_default_settings(settings)
@@ -209,6 +236,12 @@ def annotate(settings):
209
236
 
210
237
  def generate_annotate_fields(frame):
211
238
  from .settings import set_annotate_default_settings
239
+ from .gui_elements import set_dark_style
240
+
241
+ style_out = set_dark_style(ttk.Style())
242
+ font_loader = style_out['font_loader']
243
+ font_size = style_out['font_size'] - 2
244
+
212
245
  vars_dict = {}
213
246
  settings = set_annotate_default_settings(settings={})
214
247
 
@@ -220,8 +253,8 @@ def generate_annotate_fields(frame):
220
253
 
221
254
  # Arrange input fields and labels
222
255
  for row, (name, data) in enumerate(vars_dict.items()):
223
- ttk.Label(frame, text=f"{name.replace('_', ' ').capitalize()}:",
224
- background="black", foreground="white").grid(row=row, column=0)
256
+ tk.Label(frame, text=f"{name.replace('_', ' ').capitalize()}:", bg=style_out['bg_color'], fg=style_out['fg_color'], font=font_loader.get_font(size=font_size)).grid(row=row, column=0)
257
+ #ttk.Label(frame, text=f"{name.replace('_', ' ').capitalize()}:", background="black", foreground="white").grid(row=row, column=0)
225
258
  if isinstance(data['value'], list):
226
259
  # Convert lists to comma-separated strings
227
260
  data['entry'].insert(0, ','.join(map(str, data['value'])))
@@ -349,4 +382,316 @@ def annotate_with_image_refs(settings, root, shutdown_callback):
349
382
  # Call load_images after setting up the root window
350
383
  app.load_images()
351
384
 
385
+ def convert_settings_dict_for_gui(settings):
386
+ from torchvision import models as torch_models
387
+ torchvision_models = [name for name, obj in torch_models.__dict__.items() if callable(obj)]
388
+ chans = ['0', '1', '2', '3', '4', '5', '6', '7', '8', None]
389
+ chans_v2 = [0, 1, 2, 3, None]
390
+ variables = {}
391
+ special_cases = {
392
+ 'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
393
+ 'channels': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
394
+ 'train_channels': ('combo', ["['r','g','b']", "['r','g']", "['r','b']", "['g','b']", "['r']", "['g']", "['b']"], "['r','g','b']"),
395
+ 'channel_dims': ('combo', ['[0,1,2,3]', '[0,1,2]', '[0,1]', '[0]'], '[0,1,2,3]'),
396
+ 'dataset_mode': ('combo', ['annotation', 'metadata', 'recruitment'], 'metadata'),
397
+ 'cell_mask_dim': ('combo', chans, None),
398
+ 'cell_chann_dim': ('combo', chans, None),
399
+ 'nucleus_mask_dim': ('combo', chans, None),
400
+ 'nucleus_chann_dim': ('combo', chans, None),
401
+ 'pathogen_mask_dim': ('combo', chans, None),
402
+ 'pathogen_chann_dim': ('combo', chans, None),
403
+ 'crop_mode': ('combo', ['cell', 'nucleus', 'pathogen', '[cell, nucleus, pathogen]', '[cell,nucleus, pathogen]'], ['cell']),
404
+ 'magnification': ('combo', [20, 40, 60], 20),
405
+ 'nucleus_channel': ('combo', chans_v2, None),
406
+ 'cell_channel': ('combo', chans_v2, None),
407
+ 'channel_of_interest': ('combo', chans_v2, None),
408
+ 'pathogen_channel': ('combo', chans_v2, None),
409
+ 'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
410
+ 'train_mode': ('combo', ['erm', 'irm'], 'erm'),
411
+ 'clustering': ('combo', ['dbscan', 'kmean'], 'dbscan'),
412
+ 'reduction_method': ('combo', ['umap', 'tsne'], 'umap'),
413
+ 'model_name': ('combo', ['cyto', 'cyto_2', 'cyto_3', 'nuclei'], 'cyto'),
414
+ 'regression_type': ('combo', ['ols','gls','wls','rlm','glm','mixed','quantile','logit','probit','poisson','lasso','ridge'], 'ols'),
415
+ 'timelapse_objects': ('combo', ['cell', 'nucleus', 'pathogen', 'cytoplasm', None], None),
416
+ 'model_type': ('combo', torchvision_models, 'resnet50'),
417
+ 'optimizer_type': ('combo', ['adamw', 'adam'], 'adamw'),
418
+ 'schedule': ('combo', ['reduce_lr_on_plateau', 'step_lr'], 'reduce_lr_on_plateau'),
419
+ 'loss_type': ('combo', ['focal_loss', 'binary_cross_entropy_with_logits'], 'focal_loss'),
420
+ 'normalize_by': ('combo', ['fov', 'png'], 'png'),
421
+ 'agg_type': ('combo', ['mean', 'median'], 'mean'),
422
+ 'grouping': ('combo', ['mean', 'median'], 'mean'),
423
+ 'min_max': ('combo', ['allq', 'all'], 'allq'),
424
+ 'transform': ('combo', ['log', 'sqrt', 'square', None], None)
425
+ }
426
+
427
+ for key, value in settings.items():
428
+ if key in special_cases:
429
+ variables[key] = special_cases[key]
430
+ elif isinstance(value, bool):
431
+ variables[key] = ('check', None, value)
432
+ elif isinstance(value, int) or isinstance(value, float):
433
+ variables[key] = ('entry', None, value)
434
+ elif isinstance(value, str):
435
+ variables[key] = ('entry', None, value)
436
+ elif value is None:
437
+ variables[key] = ('entry', None, value)
438
+ elif isinstance(value, list):
439
+ variables[key] = ('entry', None, str(value))
440
+ else:
441
+ variables[key] = ('entry', None, str(value))
442
+
443
+ return variables
444
+
445
+
446
+ def spacrFigShow(fig_queue=None):
447
+ """
448
+ Replacement for plt.show() that queues figures instead of displaying them.
449
+ """
450
+ fig = plt.gcf()
451
+ if fig_queue:
452
+ fig_queue.put(fig)
453
+ else:
454
+ fig.show()
455
+ plt.close(fig)
456
+
457
+ def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imports=1):
458
+
459
+ """
460
+ Wraps the run_multiple_simulations function to integrate with GUI processes.
461
+
462
+ Parameters:
463
+ - settings: dict, The settings for the run_multiple_simulations function.
464
+ - q: multiprocessing.Queue, Queue for logging messages to the GUI.
465
+ - fig_queue: multiprocessing.Queue, Queue for sending figures to the GUI.
466
+ """
467
+
468
+ # Temporarily override plt.show
469
+ original_show = plt.show
470
+ plt.show = lambda: spacrFigShow(fig_queue)
471
+
472
+ try:
473
+ if imports == 1:
474
+ function(settings=settings)
475
+ elif imports == 2:
476
+ function(src=settings['src'], settings=settings)
477
+ except Exception as e:
478
+ # Send the error message to the GUI via the queue
479
+ errorMessage = f"Error during processing: {e}"
480
+ q.put(errorMessage)
481
+ traceback.print_exc()
482
+ finally:
483
+ # Restore the original plt.show function
484
+ plt.show = original_show
485
+
486
+ def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
487
+
488
+ from .gui_utils import process_stdout_stderr
489
+ from .core import generate_image_umap, 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
490
+ from .io import generate_cellpose_train_test
491
+ from .measure import measure_crop
492
+ from .sim import run_multiple_simulations
493
+ from .deep_spacr import deep_spacr
494
+ from .sequencing import generate_barecode_mapping, perform_regression
495
+ process_stdout_stderr(q)
496
+
497
+ print(f'run_function_gui settings_type: {settings_type}')
498
+
499
+ if settings_type == 'mask':
500
+ function = preprocess_generate_masks
501
+ imports = 2
502
+ elif settings_type == 'measure':
503
+ function = measure_crop
504
+ imports = 1
505
+ elif settings_type == 'simulation':
506
+ function = run_multiple_simulations
507
+ imports = 1
508
+ elif settings_type == 'classify':
509
+ function = deep_spacr
510
+ imports = 1
511
+ elif settings_type == 'train_cellpose':
512
+ function = train_cellpose
513
+ imports = 1
514
+ elif settings_type == 'ml_analyze':
515
+ function = generate_ml_scores
516
+ imports = 2
517
+ elif settings_type == 'cellpose_masks':
518
+ function = identify_masks_finetune
519
+ imports = 1
520
+ elif settings_type == 'cellpose_all':
521
+ function = check_cellpose_models
522
+ imports = 1
523
+ elif settings_type == 'map_barcodes':
524
+ function = generate_barecode_mapping
525
+ imports = 1
526
+ elif settings_type == 'regression':
527
+ function = perform_regression
528
+ imports = 2
529
+ elif settings_type == 'recruitment':
530
+ function = analyze_recruitment
531
+ imports = 1
532
+ elif settings_type == 'umap':
533
+ function = generate_image_umap
534
+ imports = 1
535
+ else:
536
+ raise ValueError(f"Invalid settings type: {settings_type}")
537
+ try:
538
+ function_gui_wrapper(function, settings, q, fig_queue, imports)
539
+ except Exception as e:
540
+ q.put(f"Error during processing: {e}")
541
+ traceback.print_exc()
542
+ finally:
543
+ stop_requested.value = 1
544
+
545
+ def hide_all_settings(vars_dict, categories):
546
+ """
547
+ Function to initially hide all settings in the GUI.
548
+
549
+ Parameters:
550
+ - categories: dict, The categories of settings with their corresponding settings.
551
+ - vars_dict: dict, The dictionary containing the settings and their corresponding widgets.
552
+ """
352
553
 
554
+ if categories is None:
555
+ from .settings import categories
556
+
557
+ for category, settings in categories.items():
558
+ if any(setting in vars_dict for setting in settings):
559
+ vars_dict[category] = (None, None, tk.IntVar(value=0), None)
560
+
561
+ # Initially hide all settings
562
+ for setting in settings:
563
+ if setting in vars_dict:
564
+ label, widget, _, frame = vars_dict[setting]
565
+ label.grid_remove()
566
+ widget.grid_remove()
567
+ frame.grid_remove()
568
+ return vars_dict
569
+
570
+ def setup_frame(parent_frame):
571
+ from .gui_elements import set_dark_style, set_element_size
572
+
573
+ style = ttk.Style(parent_frame)
574
+ size_dict = set_element_size()
575
+ style_out = set_dark_style(style)
576
+
577
+ # Configure the main layout using PanedWindow
578
+ main_paned = tk.PanedWindow(parent_frame, orient=tk.HORIZONTAL, bg=style_out['bg_color'], bd=0, relief='flat')
579
+ main_paned.grid(row=0, column=0, sticky="nsew")
580
+
581
+ # Allow the main_paned to expand and fill the window
582
+ parent_frame.grid_rowconfigure(0, weight=1)
583
+ parent_frame.grid_columnconfigure(0, weight=1)
584
+
585
+ # Create the settings container on the left
586
+ settings_container = tk.PanedWindow(main_paned, orient=tk.VERTICAL, width=size_dict['settings_width'], bg=style_out['bg_color'], bd=0, relief='flat')
587
+ main_paned.add(settings_container, minsize=100) # Allow resizing with a minimum size
588
+
589
+ # Create a right container frame to hold vertical and horizontal containers
590
+ right_frame = tk.Frame(main_paned, bg=style_out['bg_color'], bd=0, highlightthickness=0, relief='flat')
591
+ main_paned.add(right_frame, stretch="always")
592
+
593
+ # Configure the right_frame grid layout
594
+ right_frame.grid_rowconfigure(0, weight=1) # Vertical container expands
595
+ right_frame.grid_rowconfigure(1, weight=0) # Horizontal container at bottom
596
+ right_frame.grid_columnconfigure(0, weight=1)
597
+
598
+ # Inside right_frame, add vertical_container at the top
599
+ vertical_container = tk.PanedWindow(right_frame, orient=tk.VERTICAL, bg=style_out['bg_color'], bd=0, relief='flat')
600
+ vertical_container.grid(row=0, column=0, sticky="nsew")
601
+
602
+ # Add horizontal_container aligned with the bottom of settings_container
603
+ horizontal_container = tk.PanedWindow(right_frame, orient=tk.HORIZONTAL, height=size_dict['panel_height'], bg=style_out['bg_color'], bd=0, relief='flat')
604
+ horizontal_container.grid(row=1, column=0, sticky="ew")
605
+
606
+ # Example content for settings_container
607
+ tk.Label(settings_container, text="Settings Container", bg=style_out['bg_color']).pack(fill=tk.BOTH, expand=True)
608
+
609
+ set_dark_style(style, parent_frame, [settings_container, vertical_container, horizontal_container, main_paned])
610
+
611
+ return parent_frame, vertical_container, horizontal_container, settings_container
612
+
613
+
614
+ def download_hug_dataset(q, vars_dict):
615
+ dataset_repo_id = "einarolafsson/toxo_mito"
616
+ settings_repo_id = "einarolafsson/spacr_settings"
617
+ dataset_subfolder = "plate1"
618
+ local_dir = os.path.join(os.path.expanduser("~"), "datasets")
619
+
620
+ # Download the dataset
621
+ try:
622
+ dataset_path = download_dataset(q, dataset_repo_id, dataset_subfolder, local_dir)
623
+ if 'src' in vars_dict:
624
+ vars_dict['src'][2].set(dataset_path)
625
+ q.put(f"Set source path to: {vars_dict['src'][2].get()}\n")
626
+ q.put(f"Dataset downloaded to: {dataset_path}\n")
627
+ except Exception as e:
628
+ q.put(f"Failed to download dataset: {e}\n")
629
+
630
+ # Download the settings files
631
+ try:
632
+ settings_path = download_dataset(q, settings_repo_id, "", local_dir)
633
+ q.put(f"Settings downloaded to: {settings_path}\n")
634
+ except Exception as e:
635
+ q.put(f"Failed to download settings: {e}\n")
636
+
637
+ def download_dataset(q, repo_id, subfolder, local_dir=None, retries=5, delay=5):
638
+ """
639
+ Downloads a dataset or settings files from Hugging Face and returns the local path.
640
+
641
+ Args:
642
+ repo_id (str): The repository ID (e.g., 'einarolafsson/toxo_mito' or 'einarolafsson/spacr_settings').
643
+ subfolder (str): The subfolder path within the repository (e.g., 'plate1' or the settings subfolder).
644
+ local_dir (str): The local directory where the files will be saved. Defaults to the user's home directory.
645
+ retries (int): Number of retry attempts in case of failure.
646
+ delay (int): Delay in seconds between retries.
647
+
648
+ Returns:
649
+ str: The local path to the downloaded files.
650
+ """
651
+ if local_dir is None:
652
+ local_dir = os.path.join(os.path.expanduser("~"), "datasets")
653
+
654
+ local_subfolder_dir = os.path.join(local_dir, subfolder if subfolder else "settings")
655
+ if not os.path.exists(local_subfolder_dir):
656
+ os.makedirs(local_subfolder_dir)
657
+ elif len(os.listdir(local_subfolder_dir)) > 0:
658
+ q.put(f"Files already downloaded to: {local_subfolder_dir}")
659
+ return local_subfolder_dir
660
+
661
+ attempt = 0
662
+ while attempt < retries:
663
+ try:
664
+ files = list_repo_files(repo_id, repo_type="dataset")
665
+ subfolder_files = [file for file in files if file.startswith(subfolder) or (subfolder == "" and file.endswith('.csv'))]
666
+
667
+ for file_name in subfolder_files:
668
+ for download_attempt in range(retries):
669
+ try:
670
+ url = f"https://huggingface.co/datasets/{repo_id}/resolve/main/{file_name}?download=true"
671
+ response = requests.get(url, stream=True)
672
+ response.raise_for_status()
673
+
674
+ local_file_path = os.path.join(local_subfolder_dir, os.path.basename(file_name))
675
+ with open(local_file_path, 'wb') as file:
676
+ for chunk in response.iter_content(chunk_size=8192):
677
+ file.write(chunk)
678
+ q.put(f"Downloaded file: {file_name}")
679
+ break
680
+ except (requests.HTTPError, requests.Timeout) as e:
681
+ q.put(f"Error downloading {file_name}: {e}. Retrying in {delay} seconds...")
682
+ time.sleep(delay)
683
+ else:
684
+ raise Exception(f"Failed to download {file_name} after multiple attempts.")
685
+
686
+ return local_subfolder_dir
687
+
688
+ except (requests.HTTPError, requests.Timeout) as e:
689
+ q.put(f"Error downloading files: {e}. Retrying in {delay} seconds...")
690
+ attempt += 1
691
+ time.sleep(delay)
692
+
693
+ raise Exception("Failed to download files after multiple attempts.")
694
+
695
+ def ensure_after_tasks(frame):
696
+ if not hasattr(frame, 'after_tasks'):
697
+ frame.after_tasks = []