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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
spacr/gui_utils.py CHANGED
@@ -1,20 +1,48 @@
1
- import os, io, sys, ast, ctypes, ast, sqlite3, requests, time, traceback, pyautogui
2
- import traceback
1
+ import os, io, sys, ast, ctypes, ast, sqlite3, requests, time, traceback, torch
3
2
  import tkinter as tk
4
3
  from tkinter import ttk
5
4
  import matplotlib
6
5
  import matplotlib.pyplot as plt
7
6
  matplotlib.use('Agg')
8
7
  from huggingface_hub import list_repo_files
9
- import traceback
8
+ import psutil
10
9
 
11
- from . gui_core import initiate_root
12
- from .gui_elements import AnnotateApp, spacrEntry, spacrCheck, spacrCombo, set_default_font
10
+ from .gui_elements import AnnotateApp, spacrEntry, spacrCheck, spacrCombo
13
11
 
14
12
  try:
15
13
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
16
14
  except AttributeError:
17
15
  pass
16
+
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.")
25
+ else:
26
+ print("CUDA is not available.")
27
+
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())))
18
46
 
19
47
  def proceed_with_app(root, app_name, app_func):
20
48
  # Clear the current content frame
@@ -29,6 +57,10 @@ def proceed_with_app(root, app_name, app_func):
29
57
  app_func(root.content_frame)
30
58
 
31
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
+
32
64
  # Cancel all scheduled after tasks
33
65
  if hasattr(root, 'after_tasks'):
34
66
  for task in root.after_tasks:
@@ -38,42 +70,72 @@ def load_app(root, app_name, app_func):
38
70
  # Exit functionality only for the annotation and make_masks apps
39
71
  if app_name not in ["Annotate", "make_masks"] and hasattr(root, 'current_app_exit_func'):
40
72
  root.next_app_func = proceed_with_app
41
- root.next_app_args = (app_name, app_func) # Ensure correct arguments
73
+ root.next_app_args = (app_name, app_func)
42
74
  root.current_app_exit_func()
43
75
  else:
44
76
  proceed_with_app(root, app_name, app_func)
45
-
46
- def parse_list_v1(value):
47
- try:
48
- parsed_value = ast.literal_eval(value)
49
- if isinstance(parsed_value, list):
50
- return parsed_value
51
- else:
52
- raise ValueError
53
- except (ValueError, SyntaxError):
54
- raise ValueError("Invalid format for list")
55
77
 
56
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
+ """
57
91
  try:
58
92
  parsed_value = ast.literal_eval(value)
59
93
  if isinstance(parsed_value, list):
60
- 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")
61
101
  else:
62
102
  raise ValueError(f"Expected a list but got {type(parsed_value).__name__}")
63
103
  except (ValueError, SyntaxError) as e:
64
104
  raise ValueError(f"Invalid format for list: {value}. Error: {e}")
65
-
105
+
66
106
  # Usage example in your create_input_field function
67
107
  def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
68
- from .gui_elements import set_dark_style
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
+
69
128
  label_column = 0
70
129
  widget_column = 0 # Both label and widget will be in the same column
71
130
 
72
131
  style_out = set_dark_style(ttk.Style())
132
+ font_loader = style_out['font_loader']
133
+ font_size = style_out['font_size']
73
134
  size_dict = set_element_size()
74
135
  size_dict['settings_width'] = size_dict['settings_width'] - int(size_dict['settings_width']*0.1)
75
136
 
76
137
  # Replace underscores with spaces and capitalize the first letter
138
+
77
139
  label_text = label_text.replace('_', ' ').capitalize()
78
140
 
79
141
  # Configure the column widths
@@ -86,34 +148,37 @@ def create_input_field(frame, label_text, row, var_type='entry', options=None, d
86
148
  # Apply styles to custom frame
87
149
  custom_frame.update_idletasks()
88
150
  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"))
91
151
 
92
152
  # 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')
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')
94
154
  label.grid(column=label_column, row=0, sticky=tk.W, padx=(5, 2), pady=5) # Place the label in the first row
95
155
 
96
156
  # Create and configure the input widget based on var_type
97
- if var_type == 'entry':
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
102
- elif var_type == 'check':
103
- var = tk.BooleanVar(value=default_value) # Set default value (True/False)
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
107
- elif var_type == 'combo':
108
- var = tk.StringVar(value=default_value) # Set default value
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
111
- if default_value:
112
- combo.set(default_value)
113
- return (label, combo, var, custom_frame) # Return both the label and the combobox, and the variable
114
- else:
115
- var = None # Placeholder in case of an undefined var_type
116
- return (label, None, var, custom_frame)
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}")
117
182
 
118
183
  def process_stdout_stderr(q):
119
184
  """
@@ -141,16 +206,6 @@ def cancel_after_tasks(frame):
141
206
  frame.after_cancel(task)
142
207
  frame.after_tasks.clear()
143
208
 
144
- def main_thread_update_function(root, q, fig_queue, canvas_widget):
145
- try:
146
- #ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
147
- while not q.empty():
148
- message = q.get_nowait()
149
- except Exception as e:
150
- print(f"Error updating GUI canvas: {e}")
151
- finally:
152
- root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget))
153
-
154
209
  def annotate(settings):
155
210
  from .settings import set_annotate_default_settings
156
211
  settings = set_annotate_default_settings(settings)
@@ -181,6 +236,12 @@ def annotate(settings):
181
236
 
182
237
  def generate_annotate_fields(frame):
183
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
+
184
245
  vars_dict = {}
185
246
  settings = set_annotate_default_settings(settings={})
186
247
 
@@ -192,8 +253,8 @@ def generate_annotate_fields(frame):
192
253
 
193
254
  # Arrange input fields and labels
194
255
  for row, (name, data) in enumerate(vars_dict.items()):
195
- ttk.Label(frame, text=f"{name.replace('_', ' ').capitalize()}:",
196
- 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)
197
258
  if isinstance(data['value'], list):
198
259
  # Convert lists to comma-separated strings
199
260
  data['entry'].insert(0, ','.join(map(str, data['value'])))
@@ -321,27 +382,6 @@ def annotate_with_image_refs(settings, root, shutdown_callback):
321
382
  # Call load_images after setting up the root window
322
383
  app.load_images()
323
384
 
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
385
  def convert_settings_dict_for_gui(settings):
346
386
  from torchvision import models as torch_models
347
387
  torchvision_models = [name for name, obj in torch_models.__dict__.items() if callable(obj)]
@@ -351,7 +391,9 @@ def convert_settings_dict_for_gui(settings):
351
391
  special_cases = {
352
392
  'metadata_type': ('combo', ['cellvoyager', 'cq1', 'nikon', 'zeis', 'custom'], 'cellvoyager'),
353
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']"),
354
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'),
355
397
  'cell_mask_dim': ('combo', chans, None),
356
398
  'cell_chann_dim': ('combo', chans, None),
357
399
  'nucleus_mask_dim': ('combo', chans, None),
@@ -397,6 +439,7 @@ def convert_settings_dict_for_gui(settings):
397
439
  variables[key] = ('entry', None, str(value))
398
440
  else:
399
441
  variables[key] = ('entry', None, str(value))
442
+
400
443
  return variables
401
444
 
402
445
 
@@ -440,15 +483,15 @@ def function_gui_wrapper(function=None, settings={}, q=None, fig_queue=None, imp
440
483
  # Restore the original plt.show function
441
484
  plt.show = original_show
442
485
 
443
-
444
486
  def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
487
+
445
488
  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
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
447
490
  from .io import generate_cellpose_train_test
448
491
  from .measure import measure_crop
449
492
  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
493
+ from .deep_spacr import deep_spacr
494
+ from .sequencing import generate_barecode_mapping, perform_regression
452
495
  process_stdout_stderr(q)
453
496
 
454
497
  print(f'run_function_gui settings_type: {settings_type}')
@@ -462,12 +505,9 @@ def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
462
505
  elif settings_type == 'simulation':
463
506
  function = run_multiple_simulations
464
507
  imports = 1
465
- elif settings_type == 'sequencing':
466
- function = analyze_reads
467
- imports = 1
468
508
  elif settings_type == 'classify':
469
- function = train_test_model
470
- imports = 2
509
+ function = deep_spacr
510
+ imports = 1
471
511
  elif settings_type == 'train_cellpose':
472
512
  function = train_cellpose
473
513
  imports = 1
@@ -481,14 +521,17 @@ def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
481
521
  function = check_cellpose_models
482
522
  imports = 1
483
523
  elif settings_type == 'map_barcodes':
484
- function = map_barcodes_folder
485
- imports = 2
524
+ function = generate_barecode_mapping
525
+ imports = 1
486
526
  elif settings_type == 'regression':
487
527
  function = perform_regression
488
528
  imports = 2
489
529
  elif settings_type == 'recruitment':
490
530
  function = analyze_recruitment
491
- imports = 2
531
+ imports = 1
532
+ elif settings_type == 'umap':
533
+ function = generate_image_umap
534
+ imports = 1
492
535
  else:
493
536
  raise ValueError(f"Invalid settings type: {settings_type}")
494
537
  try:
@@ -499,7 +542,6 @@ def run_function_gui(settings_type, settings, q, fig_queue, stop_requested):
499
542
  finally:
500
543
  stop_requested.value = 1
501
544
 
502
-
503
545
  def hide_all_settings(vars_dict, categories):
504
546
  """
505
547
  Function to initially hide all settings in the GUI.
@@ -526,38 +568,49 @@ def hide_all_settings(vars_dict, categories):
526
568
  return vars_dict
527
569
 
528
570
  def setup_frame(parent_frame):
529
- from .gui_elements import set_dark_style, set_default_font
571
+ from .gui_elements import set_dark_style, set_element_size
530
572
 
531
573
  style = ttk.Style(parent_frame)
532
574
  size_dict = set_element_size()
533
575
  style_out = set_dark_style(style)
534
576
 
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'])
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")
538
580
 
581
+ # Allow the main_paned to expand and fill the window
539
582
  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)
583
+ parent_frame.grid_columnconfigure(0, weight=1)
543
584
 
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")
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
547
588
 
548
- # Ensure settings_container maintains its width
549
- settings_container.grid_propagate(False)
550
- settings_container.update_idletasks()
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")
551
592
 
552
- tk.Label(settings_container, text="Settings Container", bg=style_out['bg_color']).grid(row=0, column=0, sticky="ew")
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)
553
597
 
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)
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])
558
610
 
559
611
  return parent_frame, vertical_container, horizontal_container, settings_container
560
612
 
613
+
561
614
  def download_hug_dataset(q, vars_dict):
562
615
  dataset_repo_id = "einarolafsson/toxo_mito"
563
616
  settings_repo_id = "einarolafsson/spacr_settings"
@@ -638,3 +691,7 @@ def download_dataset(q, repo_id, subfolder, local_dir=None, retries=5, delay=5):
638
691
  time.sleep(delay)
639
692
 
640
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 = []