spacr 0.0.17__py3-none-any.whl → 0.0.20__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_sim_app.py CHANGED
@@ -1,213 +0,0 @@
1
- import sys, ctypes, csv, matplotlib, traceback
2
- import tkinter as tk
3
- from tkinter import ttk, scrolledtext
4
- from ttkthemes import ThemedTk
5
- from matplotlib.figure import Figure
6
- from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
7
- from matplotlib.figure import Figure
8
- matplotlib.use('Agg')
9
- from tkinter import filedialog
10
- from multiprocessing import Process, Queue, Value
11
-
12
- try:
13
- ctypes.windll.shcore.SetProcessDpiAwareness(True)
14
- except AttributeError:
15
- pass
16
-
17
- from .logger import log_function_call
18
-
19
- from .gui_utils import ScrollableFrame, StdoutRedirector
20
- from .gui_utils import create_dark_mode, set_dark_style, set_default_font, generate_fields, process_stdout_stderr, safe_literal_eval, clear_canvas, main_thread_update_function
21
- from .gui_utils import sim_variables, check_sim_gui_settings, run_multiple_simulations_wrapper
22
-
23
- thread_control = {"run_thread": None, "stop_requested": False}
24
-
25
- @log_function_call
26
- def run_sim_gui(q, fig_queue, stop_requested):
27
- global vars_dict
28
- process_stdout_stderr(q)
29
- try:
30
- settings = check_sim_gui_settings(vars_dict)
31
- for key in settings:
32
- value = settings[key]
33
- print(key, value, type(value))
34
- run_multiple_simulations_wrapper(settings, q, fig_queue)
35
- except Exception as e:
36
- q.put(f"Error during processing: {e}")
37
- traceback.print_exc()
38
- finally:
39
- stop_requested.value = 1
40
-
41
- @log_function_call
42
- def start_process(q, fig_queue):
43
- global thread_control
44
- if thread_control.get("run_thread") is not None:
45
- initiate_abort()
46
-
47
- stop_requested = Value('i', 0) # multiprocessing shared value for inter-process communication
48
- thread_control["stop_requested"] = stop_requested
49
- thread_control["run_thread"] = Process(target=run_sim_gui, args=(q, fig_queue, stop_requested))
50
- thread_control["run_thread"].start()
51
-
52
- @log_function_call
53
- def initiate_abort():
54
- global thread_control
55
- if thread_control.get("stop_requested") is not None:
56
- thread_control["stop_requested"].value = 1
57
-
58
- if thread_control.get("run_thread") is not None:
59
- thread_control["run_thread"].join(timeout=5)
60
- if thread_control["run_thread"].is_alive():
61
- thread_control["run_thread"].terminate()
62
- thread_control["run_thread"] = None
63
-
64
- def import_settings(scrollable_frame):
65
- global vars_dict, original_variables_structure
66
-
67
- csv_file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
68
-
69
- if not csv_file_path:
70
- return
71
-
72
- imported_variables = {}
73
-
74
- with open(csv_file_path, newline='') as csvfile:
75
- reader = csv.DictReader(csvfile)
76
- for row in reader:
77
- key = row['Key']
78
- value = row['Value']
79
- # Evaluate the value safely using safe_literal_eval
80
- imported_variables[key] = safe_literal_eval(value)
81
-
82
- # Track changed variables and apply the imported ones, printing changes as we go
83
- for key, var in vars_dict.items():
84
- if key in imported_variables and var.get() != imported_variables[key]:
85
- print(f"Updating '{key}' from '{var.get()}' to '{imported_variables[key]}'")
86
- var.set(imported_variables[key])
87
-
88
- @log_function_call
89
- def initiate_sim_root(width, height):
90
- global root, vars_dict, q, canvas, fig_queue, canvas_widget, thread_control
91
-
92
- theme = 'breeze'
93
-
94
- if theme in ['clam']:
95
- root = tk.Tk()
96
- style = ttk.Style(root)
97
- style.theme_use(theme) #plastik, clearlooks, elegance, default was clam #alt, breeze, arc
98
- set_dark_style(style)
99
- elif theme in ['breeze']:
100
- root = ThemedTk(theme="breeze")
101
- style = ttk.Style(root)
102
- set_dark_style(style)
103
-
104
- set_default_font(root, font_name="Arial", size=10)
105
- #root.state('zoomed') # For Windows to maximize the window
106
- root.attributes('-fullscreen', True)
107
- root.geometry(f"{width}x{height}")
108
- root.title("SpaCer: Simulate screen")
109
- fig_queue = Queue()
110
-
111
- def _process_fig_queue():
112
- global canvas
113
- try:
114
- while not fig_queue.empty():
115
- clear_canvas(canvas)
116
- fig = fig_queue.get_nowait()
117
- #set_fig_text_properties(fig, font_size=8)
118
- for ax in fig.get_axes():
119
- ax.set_xticks([]) # Remove x-axis ticks
120
- ax.set_yticks([]) # Remove y-axis ticks
121
- ax.xaxis.set_visible(False) # Hide the x-axis
122
- ax.yaxis.set_visible(False) # Hide the y-axis
123
- #ax.title.set_fontsize(14)
124
- #disable_interactivity(fig)
125
- fig.tight_layout()
126
- fig.set_facecolor('#333333')
127
- canvas.figure = fig
128
- fig_width, fig_height = canvas_widget.winfo_width(), canvas_widget.winfo_height()
129
- fig.set_size_inches(fig_width / fig.dpi, fig_height / fig.dpi, forward=True)
130
- canvas.draw_idle()
131
- except Exception as e:
132
- traceback.print_exc()
133
- #pass
134
- finally:
135
- canvas_widget.after(100, _process_fig_queue)
136
-
137
- # Process queue for console output
138
- def _process_console_queue():
139
- while not q.empty():
140
- message = q.get_nowait()
141
- console_output.insert(tk.END, message)
142
- console_output.see(tk.END)
143
- console_output.after(100, _process_console_queue)
144
-
145
- # Vertical container for settings and console
146
- vertical_container = tk.PanedWindow(root, orient=tk.HORIZONTAL) #VERTICAL
147
- vertical_container.pack(fill=tk.BOTH, expand=True)
148
-
149
- # Scrollable Frame for user settings
150
- scrollable_frame = ScrollableFrame(vertical_container, bg='#333333')
151
- vertical_container.add(scrollable_frame, stretch="always")
152
-
153
- # Setup for user input fields (variables)
154
- variables = sim_variables()
155
- vars_dict = generate_fields(variables, scrollable_frame)
156
-
157
- # Horizontal container for Matplotlib figure and the vertical pane (for settings and console)
158
- horizontal_container = tk.PanedWindow(vertical_container, orient=tk.VERTICAL) #HORIZONTAL
159
- vertical_container.add(horizontal_container, stretch="always")
160
-
161
- # Matplotlib figure setup
162
- figure = Figure(figsize=(30, 4), dpi=100, facecolor='#333333')
163
- plot = figure.add_subplot(111)
164
- plot.plot([], []) # This creates an empty plot.
165
- plot.axis('off')
166
-
167
- # Embedding the Matplotlib figure in the Tkinter window
168
- canvas = FigureCanvasTkAgg(figure, master=horizontal_container)
169
- canvas.get_tk_widget().configure(cursor='arrow', background='#333333', highlightthickness=0)
170
- #canvas.get_tk_widget().configure(cursor='arrow')
171
- canvas_widget = canvas.get_tk_widget()
172
- horizontal_container.add(canvas_widget, stretch="always")
173
- canvas.draw()
174
- canvas.figure = figure
175
-
176
- # Console output setup below the settings
177
- console_output = scrolledtext.ScrolledText(vertical_container, height=10)
178
- vertical_container.add(console_output, stretch="always")
179
-
180
- # Queue and redirection setup for updating console output safely
181
- q = Queue()
182
- sys.stdout = StdoutRedirector(console_output)
183
- sys.stderr = StdoutRedirector(console_output)
184
-
185
- # This is your GUI setup where you create the Run button
186
- run_button = ttk.Button(scrollable_frame.scrollable_frame, text="Run",command=lambda: start_process(q, fig_queue))
187
- run_button.grid(row=40, column=0, pady=10)
188
-
189
- abort_button = ttk.Button(scrollable_frame.scrollable_frame, text="Abort", command=initiate_abort)
190
- abort_button.grid(row=40, column=1, pady=10)
191
-
192
- progress_label = ttk.Label(scrollable_frame.scrollable_frame, text="Processing: 0%", background="#333333", foreground="white")
193
- progress_label.grid(row=41, column=0, columnspan=2, sticky="ew", pady=(5, 0))
194
-
195
- # Create the Import Settings button
196
- import_btn = tk.Button(root, text="Import Settings", command=lambda: import_settings(scrollable_frame))
197
- import_btn.pack(pady=20)
198
-
199
- _process_console_queue()
200
- _process_fig_queue()
201
- create_dark_mode(root, style, console_output)
202
-
203
- root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
204
-
205
- return root, vars_dict
206
-
207
- def gui_sim():
208
- global vars_dict, root
209
- root, vars_dict = initiate_sim_root(1000, 1500)
210
- root.mainloop()
211
-
212
- if __name__ == "__main__":
213
- gui_sim()
spacr/gui_utils.py CHANGED
@@ -1,6 +1,6 @@
1
- import spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib
2
- matplotlib.use('Agg')
1
+ import spacr, inspect, traceback, io, sys, ast, ctypes, matplotlib, re, csv
3
2
  import matplotlib.pyplot as plt
3
+ matplotlib.use('Agg')
4
4
  import numpy as np
5
5
  import tkinter as tk
6
6
  from tkinter import ttk, messagebox
@@ -14,6 +14,26 @@ except AttributeError:
14
14
 
15
15
  from .logger import log_function_call
16
16
 
17
+ def read_settings_from_csv(csv_file_path):
18
+ settings = {}
19
+ with open(csv_file_path, newline='') as csvfile:
20
+ reader = csv.DictReader(csvfile)
21
+ for row in reader:
22
+ key = row['Key']
23
+ value = row['Value']
24
+ settings[key] = value
25
+ return settings
26
+
27
+ def update_settings_from_csv(variables, csv_settings):
28
+ new_settings = variables.copy() # Start with a copy of the original settings
29
+ for key, value in csv_settings.items():
30
+ if key in new_settings:
31
+ # Get the variable type and options from the original settings
32
+ var_type, options, _ = new_settings[key]
33
+ # Update the default value with the CSV value, keeping the type and options unchanged
34
+ new_settings[key] = (var_type, options, value)
35
+ return new_settings
36
+
17
37
  def safe_literal_eval(value):
18
38
  try:
19
39
  # First, try to evaluate as a literal
@@ -103,62 +123,52 @@ def check_mask_gui_settings(vars_dict):
103
123
  def check_measure_gui_settings(vars_dict):
104
124
  settings = {}
105
125
  for key, var in vars_dict.items():
106
- value = var.get() # This retrieves the string representation for entries or the actual value for checkboxes and combos
126
+ value = var.get() # Retrieves the string representation for entries or the actual value for checkboxes and combos.
107
127
 
108
128
  try:
109
- if key == 'channels' or key == 'png_dims':
110
- # Converts string representation to a list of integers
129
+ if key in ['channels', 'png_dims']:
111
130
  settings[key] = [int(chan) for chan in ast.literal_eval(value)] if value else []
112
131
 
113
132
  elif key in ['cell_loc', 'pathogen_loc', 'treatment_loc']:
114
- settings[key] = ast.literal_eval(value) if value else None
115
-
133
+ # Convert to a list of lists of strings, ensuring all structures are lists.
134
+ settings[key] = [list(map(str, sublist)) for sublist in ast.literal_eval(value)] if value else []
135
+
116
136
  elif key == 'dialate_png_ratios':
117
- settings[key] = [float(num) for num in eval(value)] if value else None
137
+ settings[key] = [float(num) for num in ast.literal_eval(value)] if value else []
118
138
 
119
139
  elif key == 'normalize':
120
- # Converts 'normalize' into a list of two integers
121
- settings[key] = [int(num) for num in ast.literal_eval(value)] if value else None
140
+ settings[key] = [int(num) for num in ast.literal_eval(value)] if value else []
122
141
 
123
- elif key == 'normalize_by':
124
- # 'normalize_by' is a string, so directly assign the value
142
+ # Directly assign string values for these specific keys
143
+ elif key in ['normalize_by', 'experiment', 'measurement', 'input_folder']:
125
144
  settings[key] = value
126
145
 
127
146
  elif key == 'png_size':
128
- # Converts string representation into a list of lists of integers
129
- temp_val = ast.literal_eval(value) if value else []
130
- settings[key] = [list(map(int, dim)) for dim in temp_val] if temp_val else None
131
-
132
- # Handling for other keys as in your original function...
147
+ settings[key] = [list(map(int, dim)) for dim in ast.literal_eval(value)] if value else []
133
148
 
134
- elif key in ['pathogens', 'treatments', 'cells', 'crop_mode', 'timelapse_objects']:
135
- # Ensuring these are evaluated correctly as lists or other structures
136
- settings[key] = ast.literal_eval(value) if value else None
137
-
138
- elif key == 'timelapse_objects':
139
- # Ensure it's a list of strings
140
- settings[key] = eval(value) if value else []
141
-
142
- # Handling for keys that should be treated as strings directly
143
- elif key in ['normalize_by', 'experiment', 'measurement', 'input_folder']:
144
- settings[key] = str(value) if value else None
149
+ # Ensure these are lists of strings, converting from tuples if necessary
150
+ elif key in ['timelapse_objects', 'crop_mode', 'cells', 'pathogens', 'treatments']:
151
+ eval_value = ast.literal_eval(value) if value else []
152
+ settings[key] = list(map(str, eval_value)) if isinstance(eval_value, (list, tuple)) else [str(eval_value)]
145
153
 
146
- # Handling for single values that are not strings (int, float, bool)
154
+ # Handling for single non-string values (int, float, bool)
147
155
  elif key in ['cell_mask_dim', 'cell_min_size', 'nucleus_mask_dim', 'nucleus_min_size', 'pathogen_mask_dim', 'pathogen_min_size', 'cytoplasm_min_size', 'max_workers', 'channel_of_interest', 'nr_imgs']:
148
156
  settings[key] = int(value) if value else None
149
157
 
150
158
  elif key == 'um_per_pixel':
151
159
  settings[key] = float(value) if value else None
152
160
 
153
- # Direct handling of boolean values based on checkboxes
161
+ # Handling boolean values based on checkboxes
154
162
  elif key in ['save_png', 'use_bounding_box', 'save_measurements', 'plot', 'plot_filtration', 'include_uninfected', 'dialate_pngs', 'timelapse', 'representative_images']:
155
- settings[key] = bool(value)
163
+ settings[key] = var.get()
156
164
 
157
165
  except SyntaxError as e:
158
- messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
166
+ print(f"Syntax error processing {key}: {str(e)}")
167
+ #messagebox.showerror("Error", f"Syntax error processing {key}: {str(e)}")
159
168
  return None
160
169
  except Exception as e:
161
- messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
170
+ print(f"Error processing {key}: {str(e)}")
171
+ #messagebox.showerror("Error", f"Error processing {key}: {str(e)}")
162
172
  return None
163
173
 
164
174
  return settings
@@ -268,11 +278,21 @@ def sim_variables():
268
278
  }
269
279
  return variables
270
280
 
281
+ def add_measure_gui_defaults(settings):
282
+ settings['compartments'] = ['pathogen', 'cytoplasm']
283
+ return settings
284
+
271
285
  def measure_variables():
272
286
  variables = {
273
287
  'input_folder':('entry', None, '/mnt/data/CellVoyager/40x/einar/mitotrackerHeLaToxoDsRed_20240224_123156/test_gui/merged'),
274
288
  'channels': ('combo', ['[0,1,2,3]','[0,1,2]','[0,1]','[0]'], '[0,1,2,3]'),
275
289
  'cell_mask_dim':('entry', None, 4),
290
+ 'cell_min_size':('entry', None, 0),
291
+ 'cytoplasm_min_size':('entry', None, 0),
292
+ 'nucleus_mask_dim':('entry', None, 5),
293
+ 'nucleus_min_size':('entry', None, 0),
294
+ 'pathogen_mask_dim':('entry', None, 6),
295
+ 'pathogen_min_size':('entry', None, 0),
276
296
  'save_png':('check', None, True),
277
297
  'crop_mode':('entry', None, '["cell"]'),
278
298
  'use_bounding_box':('check', None, True),
@@ -298,6 +318,7 @@ def measure_variables():
298
318
  'treatments': ('entry', None, '["cm","lovastatin_20uM"]'),
299
319
  'treatment_loc': ('entry', None, '[["c1","c2"], ["c3","c4"]]'),
300
320
  'channel_of_interest':('entry', None, 3),
321
+ 'compartments':('entry', None, '["pathogen","cytoplasm"]'),
301
322
  'measurement':('entry', None, 'mean_intensity'),
302
323
  'nr_imgs':('entry', None, 32),
303
324
  'um_per_pixel':('entry', None, 0.1)
@@ -346,7 +367,7 @@ def classify_variables():
346
367
  return variables
347
368
 
348
369
 
349
- @log_function_call
370
+ #@log_function_call
350
371
  def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
351
372
  label = ttk.Label(frame, text=label_text, style='TLabel') # Assuming you have a dark mode style for labels too
352
373
  label.grid(column=0, row=row, sticky=tk.W, padx=5, pady=5)
@@ -370,10 +391,6 @@ def create_input_field(frame, label_text, row, var_type='entry', options=None, d
370
391
  var = None # Placeholder in case of an undefined var_type
371
392
 
372
393
  return var
373
-
374
- def add_measure_gui_defaults(settings):
375
- settings['compartments'] = ['pathogen', 'cytoplasm']
376
- return settings
377
394
 
378
395
  def mask_variables():
379
396
  variables = {
@@ -395,10 +412,10 @@ def mask_variables():
395
412
  'pathogen_background': ('entry', None, 100),
396
413
  'pathogen_Signal_to_noise': ('entry', None, 3),
397
414
  'pathogen_CP_prob': ('entry', None, 0),
398
- #'preprocess': ('check', None, True),
399
- #'masks': ('check', None, True),
400
- #'examples_to_plot': ('entry', None, 1),
401
- #'randomize': ('check', None, True),
415
+ 'preprocess': ('check', None, True),
416
+ 'masks': ('check', None, True),
417
+ 'examples_to_plot': ('entry', None, 1),
418
+ 'randomize': ('check', None, True),
402
419
  'batch_size': ('entry', None, 50),
403
420
  'timelapse': ('check', None, False),
404
421
  'timelapse_displacement': ('entry', None, None),
@@ -407,14 +424,14 @@ def mask_variables():
407
424
  'timelapse_remove_transient': ('check', None, True),
408
425
  'timelapse_mode': ('combo', ['trackpy', 'btrack'], 'trackpy'),
409
426
  'timelapse_objects': ('combo', ['cell','nucleus','pathogen','cytoplasm', None], None),
410
- #'fps': ('entry', None, 2),
411
- #'remove_background': ('check', None, True),
427
+ 'fps': ('entry', None, 2),
428
+ 'remove_background': ('check', None, True),
412
429
  'lower_quantile': ('entry', None, 0.01),
413
- #'merge': ('check', None, False),
414
- #'normalize_plots': ('check', None, True),
415
- #'all_to_mip': ('check', None, False),
416
- #'pick_slice': ('check', None, False),
417
- #'skip_mode': ('entry', None, None),
430
+ 'merge': ('check', None, False),
431
+ 'normalize_plots': ('check', None, True),
432
+ 'all_to_mip': ('check', None, False),
433
+ 'pick_slice': ('check', None, False),
434
+ 'skip_mode': ('entry', None, None),
418
435
  'save': ('check', None, True),
419
436
  'plot': ('check', None, True),
420
437
  'workers': ('entry', None, 30),
@@ -482,30 +499,31 @@ def set_dark_style(style):
482
499
  style.configure('TEntry', background='#333333', foreground='white')
483
500
  style.configure('TCheckbutton', background='#333333', foreground='white')
484
501
 
485
- @log_function_call
502
+ #@log_function_call
486
503
  def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
487
504
  try:
505
+ ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
488
506
  while not q.empty():
489
507
  message = q.get_nowait()
490
- if message.startswith("Progress"):
491
- progress_label.config(text=message)
492
- elif message.startswith("Processing"):
493
- progress_label.config(text=message)
494
- elif message == "" or message == "\r":
495
- pass
496
- elif message.startswith("/"):
508
+ clean_message = ansi_escape_pattern.sub('', message)
509
+ if clean_message.startswith("Progress"):
510
+ progress_label.config(text=clean_message)
511
+ if clean_message.startswith("\rProgress"):
512
+ progress_label.config(text=clean_message)
513
+ elif clean_message.startswith("Successfully"):
514
+ progress_label.config(text=clean_message)
515
+ elif clean_message.startswith("Processing"):
516
+ progress_label.config(text=clean_message)
517
+ elif clean_message.startswith("scale"):
497
518
  pass
498
- elif message.startswith("\\"):
519
+ elif clean_message.startswith("plot_cropped_arrays"):
499
520
  pass
500
- elif message.startswith(""):
521
+ elif clean_message == "" or clean_message == "\r" or clean_message.strip() == "":
501
522
  pass
502
523
  else:
503
- print(message) # Or handle other messages differently
504
- while not fig_queue.empty():
505
- fig = fig_queue.get_nowait()
506
- clear_canvas(canvas_widget)
524
+ print(clean_message)
507
525
  except Exception as e:
508
- print(f"Error updating GUI: {e}")
526
+ print(f"Error updating GUI canvas: {e}")
509
527
  finally:
510
528
  root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
511
529
 
@@ -538,7 +556,6 @@ def clear_canvas(canvas):
538
556
  # Redraw the now empty canvas without changing its size
539
557
  canvas.draw_idle()
540
558
 
541
- @log_function_call
542
559
  def measure_crop_wrapper(settings, q, fig_queue):
543
560
  """
544
561
  Wraps the measure_crop function to integrate with GUI processes.
@@ -562,6 +579,7 @@ def measure_crop_wrapper(settings, q, fig_queue):
562
579
  plt.show = my_show
563
580
 
564
581
  try:
582
+ print('start')
565
583
  spacr.measure.measure_crop(settings=settings, annotation_settings={}, advanced_settings={})
566
584
  except Exception as e:
567
585
  errorMessage = f"Error during processing: {e}"
@@ -594,7 +612,7 @@ def preprocess_generate_masks_wrapper(settings, q, fig_queue):
594
612
  plt.show = my_show
595
613
 
596
614
  try:
597
- spacr.core.preprocess_generate_masks(settings['src'], settings=settings, advanced_settings={})
615
+ spacr.core.preprocess_generate_masks(settings['src'], settings=settings)
598
616
  except Exception as e:
599
617
  errorMessage = f"Error during processing: {e}"
600
618
  q.put(errorMessage) # Send the error message to the GUI via the queue