spacr 0.1.64__py3-none-any.whl → 0.1.76__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,9 +1,9 @@
1
- import io, sys, ast, ctypes, re, csv, ast
1
+ import os, io, sys, ast, ctypes, re, ast, sqlite3
2
2
  import tkinter as tk
3
3
  from tkinter import ttk
4
4
 
5
5
  from . gui_core import initiate_root
6
- from .gui_elements import spacrLabel, spacrCheckbutton, set_dark_style
6
+ from .gui_elements import spacrLabel, spacrCheckbutton, ImageApp
7
7
 
8
8
  try:
9
9
  ctypes.windll.shcore.SetProcessDpiAwareness(True)
@@ -17,9 +17,7 @@ def set_default_font(root, font_name="Helvetica", size=12):
17
17
  root.option_add("*TLabel.Font", default_font)
18
18
  root.option_add("*TEntry.Font", default_font)
19
19
 
20
- def proceed_with_app(root, app_name, app_func):
21
- from .app_annotate import gui_annotate
22
- from .app_make_masks import gui_make_masks
20
+ def proceed_with_app_v1(root, app_name, app_func):
23
21
  from .gui import gui_app
24
22
 
25
23
  # Clear the current content frame
@@ -47,11 +45,23 @@ def proceed_with_app(root, app_name, app_func):
47
45
  elif app_name == "Umap":
48
46
  initiate_root(root.content_frame, 'umap')
49
47
  elif app_name == "Annotate":
50
- gui_annotate()
48
+ initiate_root(root.content_frame, 'annotate')
51
49
  elif app_name == "Make Masks":
52
- gui_make_masks()
50
+ initiate_root(root.content_frame, 'make_masks')
53
51
  else:
54
52
  raise ValueError(f"Invalid app name: {app_name}")
53
+
54
+ def proceed_with_app(root, app_name, app_func):
55
+ # Clear the current content frame
56
+ if hasattr(root, 'content_frame'):
57
+ for widget in root.content_frame.winfo_children():
58
+ try:
59
+ widget.destroy()
60
+ except tk.TclError as e:
61
+ print(f"Error destroying widget: {e}")
62
+
63
+ # Initialize the new app in the content frame
64
+ app_func(root.content_frame)
55
65
 
56
66
  def load_app(root, app_name, app_func):
57
67
  # Cancel all scheduled after tasks
@@ -60,15 +70,15 @@ def load_app(root, app_name, app_func):
60
70
  root.after_cancel(task)
61
71
  root.after_tasks = []
62
72
 
63
- # Exit functionality only for the annotation app
64
- if app_name != "Annotate" and hasattr(root, 'current_app_exit_func'):
73
+ # Exit functionality only for the annotation and make_masks apps
74
+ if app_name not in ["Annotate", "make_masks"] and hasattr(root, 'current_app_exit_func'):
65
75
  root.next_app_func = proceed_with_app
66
76
  root.next_app_args = (app_name, app_func) # Ensure correct arguments
67
77
  root.current_app_exit_func()
68
78
  else:
69
79
  proceed_with_app(root, app_name, app_func)
70
80
 
71
- def parse_list(value):
81
+ def parse_list_v1(value):
72
82
  try:
73
83
  parsed_value = ast.literal_eval(value)
74
84
  if isinstance(parsed_value, list):
@@ -78,6 +88,16 @@ def parse_list(value):
78
88
  except (ValueError, SyntaxError):
79
89
  raise ValueError("Invalid format for list")
80
90
 
91
+ def parse_list(value):
92
+ try:
93
+ parsed_value = ast.literal_eval(value)
94
+ if isinstance(parsed_value, list):
95
+ return parsed_value
96
+ else:
97
+ raise ValueError(f"Expected a list but got {type(parsed_value).__name__}")
98
+ except (ValueError, SyntaxError) as e:
99
+ raise ValueError(f"Invalid format for list: {value}. Error: {e}")
100
+
81
101
  def create_input_field(frame, label_text, row, var_type='entry', options=None, default_value=None):
82
102
  label_column = 0
83
103
  widget_column = 1
@@ -137,3 +157,170 @@ def cancel_after_tasks(frame):
137
157
  for task in frame.after_tasks:
138
158
  frame.after_cancel(task)
139
159
  frame.after_tasks.clear()
160
+
161
+ def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
162
+ try:
163
+ ansi_escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
164
+ while not q.empty():
165
+ message = q.get_nowait()
166
+ clean_message = ansi_escape_pattern.sub('', message)
167
+ if clean_message.startswith("Progress"):
168
+ progress_label.config(text=clean_message)
169
+ if clean_message.startswith("\rProgress"):
170
+ progress_label.config(text=clean_message)
171
+ elif clean_message.startswith("Successfully"):
172
+ progress_label.config(text=clean_message)
173
+ elif clean_message.startswith("Processing"):
174
+ progress_label.config(text=clean_message)
175
+ elif clean_message.startswith("scale"):
176
+ pass
177
+ elif clean_message.startswith("plot_cropped_arrays"):
178
+ pass
179
+ elif clean_message == "" or clean_message == "\r" or clean_message.strip() == "":
180
+ pass
181
+ else:
182
+ print(clean_message)
183
+ except Exception as e:
184
+ print(f"Error updating GUI canvas: {e}")
185
+ finally:
186
+ root.after(100, lambda: main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label))
187
+
188
+ def annotate(settings):
189
+ from .settings import set_annotate_default_settings
190
+ settings = set_annotate_default_settings(settings)
191
+ src = settings['src']
192
+
193
+ db = os.path.join(src, 'measurements/measurements.db')
194
+ conn = sqlite3.connect(db)
195
+ c = conn.cursor()
196
+ c.execute('PRAGMA table_info(png_list)')
197
+ cols = c.fetchall()
198
+ if settings['annotation_column'] not in [col[1] for col in cols]:
199
+ c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
200
+ conn.commit()
201
+ conn.close()
202
+
203
+ root = tk.Tk()
204
+ root.geometry(settings['geom'])
205
+ app = ImageApp(root, db, src, image_type=settings['image_type'], channels=settings['channels'], image_size=settings['img_size'], grid_rows=settings['rows'], grid_cols=settings['columns'], annotation_column=settings['annotation_column'], normalize=settings['normalize'], percentiles=settings['percentiles'], measurement=settings['measurement'], threshold=settings['threshold'])
206
+ next_button = tk.Button(root, text="Next", command=app.next_page)
207
+ next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
208
+ back_button = tk.Button(root, text="Back", command=app.previous_page)
209
+ back_button.grid(row=app.grid_rows, column=app.grid_cols - 2)
210
+ exit_button = tk.Button(root, text="Exit", command=app.shutdown)
211
+ exit_button.grid(row=app.grid_rows, column=app.grid_cols - 3)
212
+
213
+ app.load_images()
214
+ root.mainloop()
215
+
216
+ def generate_annotate_fields(frame):
217
+ from .settings import set_annotate_default_settings
218
+ vars_dict = {}
219
+ settings = set_annotate_default_settings(settings={})
220
+
221
+ for setting in settings:
222
+ vars_dict[setting] = {
223
+ 'entry': ttk.Entry(frame),
224
+ 'value': settings[setting]
225
+ }
226
+
227
+ # Arrange input fields and labels
228
+ for row, (name, data) in enumerate(vars_dict.items()):
229
+ ttk.Label(frame, text=f"{name.replace('_', ' ').capitalize()}:",
230
+ background="black", foreground="white").grid(row=row, column=0)
231
+ if isinstance(data['value'], list):
232
+ # Convert lists to comma-separated strings
233
+ data['entry'].insert(0, ','.join(map(str, data['value'])))
234
+ else:
235
+ data['entry'].insert(0, data['value'])
236
+ data['entry'].grid(row=row, column=1)
237
+
238
+ return vars_dict
239
+
240
+ def run_annotate_app(vars_dict, parent_frame):
241
+ settings = {key: data['entry'].get() for key, data in vars_dict.items()}
242
+ settings['channels'] = settings['channels'].split(',')
243
+ settings['img_size'] = list(map(int, settings['img_size'].split(','))) # Convert string to list of integers
244
+ settings['percentiles'] = list(map(int, settings['percentiles'].split(','))) # Convert string to list of integers
245
+ settings['normalize'] = settings['normalize'].lower() == 'true'
246
+ settings['rows'] = int(settings['rows'])
247
+ settings['columns'] = int(settings['columns'])
248
+ settings['measurement'] = settings['measurement'].split(',')
249
+ settings['threshold'] = None if settings['threshold'].lower() == 'none' else int(settings['threshold'])
250
+
251
+ # Clear previous content instead of destroying the root
252
+ if hasattr(parent_frame, 'winfo_children'):
253
+ for widget in parent_frame.winfo_children():
254
+ widget.destroy()
255
+
256
+ # Start the annotate application in the same root window
257
+ annotate_app(parent_frame, settings)
258
+
259
+ # Global list to keep references to PhotoImage objects
260
+ global_image_refs = []
261
+
262
+ def annotate_app(parent_frame, settings):
263
+ global global_image_refs
264
+ global_image_refs.clear()
265
+ root = parent_frame.winfo_toplevel()
266
+ annotate_with_image_refs(settings, root, lambda: load_next_app(root))
267
+
268
+ def load_next_app(root):
269
+ # Get the next app function and arguments
270
+ next_app_func = root.next_app_func
271
+ next_app_args = root.next_app_args
272
+
273
+ if next_app_func:
274
+ try:
275
+ if not root.winfo_exists():
276
+ raise tk.TclError
277
+ next_app_func(root, *next_app_args)
278
+ except tk.TclError:
279
+ # Reinitialize root if it has been destroyed
280
+ new_root = tk.Tk()
281
+ width = new_root.winfo_screenwidth()
282
+ height = new_root.winfo_screenheight()
283
+ new_root.geometry(f"{width}x{height}")
284
+ new_root.title("SpaCr Application")
285
+ next_app_func(new_root, *next_app_args)
286
+
287
+ def annotate_with_image_refs(settings, root, shutdown_callback):
288
+ #from .gui_utils import proceed_with_app
289
+ from .gui import gui_app
290
+ from .settings import set_annotate_default_settings
291
+
292
+ settings = set_annotate_default_settings(settings)
293
+ src = settings['src']
294
+
295
+ db = os.path.join(src, 'measurements/measurements.db')
296
+ conn = sqlite3.connect(db)
297
+ c = conn.cursor()
298
+ c.execute('PRAGMA table_info(png_list)')
299
+ cols = c.fetchall()
300
+ if settings['annotation_column'] not in [col[1] for col in cols]:
301
+ c.execute(f"ALTER TABLE png_list ADD COLUMN {settings['annotation_column']} integer")
302
+ conn.commit()
303
+ conn.close()
304
+
305
+ app = ImageApp(root, db, src, image_type=settings['image_type'], channels=settings['channels'], image_size=settings['img_size'], grid_rows=settings['rows'], grid_cols=settings['columns'], annotation_column=settings['annotation_column'], normalize=settings['normalize'], percentiles=settings['percentiles'], measurement=settings['measurement'], threshold=settings['threshold'])
306
+
307
+ # Set the canvas background to black
308
+ root.configure(bg='black')
309
+
310
+ next_button = tk.Button(root, text="Next", command=app.next_page, background='black', foreground='white')
311
+ next_button.grid(row=app.grid_rows, column=app.grid_cols - 1)
312
+ back_button = tk.Button(root, text="Back", command=app.previous_page, background='black', foreground='white')
313
+ back_button.grid(row=app.grid_rows, column=app.grid_cols - 2)
314
+ exit_button = tk.Button(root, text="Exit", command=lambda: [app.shutdown(), shutdown_callback()], background='black', foreground='white')
315
+ exit_button.grid(row=app.grid_rows, column=app.grid_cols - 3)
316
+
317
+ #app.load_images()
318
+
319
+ # Store the shutdown function and next app details in the root
320
+ root.current_app_exit_func = lambda: [app.shutdown(), shutdown_callback()]
321
+ root.next_app_func = proceed_with_app
322
+ root.next_app_args = ("Main App", gui_app)
323
+
324
+ # Call load_images after setting up the root window
325
+ app.load_images()
326
+
spacr/gui_wrappers.py CHANGED
@@ -4,15 +4,27 @@ matplotlib.use('Agg')
4
4
 
5
5
  fig_queue = None
6
6
 
7
- def my_show():
7
+ def spacrFigShow_v1():
8
8
  """
9
9
  Replacement for plt.show() that queues figures instead of displaying them.
10
10
  """
11
+ global fig_queue
11
12
  fig = plt.gcf()
12
13
  fig_queue.put(fig)
13
14
  plt.close(fig)
14
15
 
15
- def measure_crop_wrapper(settings, q, fig_queue):
16
+ def spacrFigShow(fig_queue=None):
17
+ """
18
+ Replacement for plt.show() that queues figures instead of displaying them.
19
+ """
20
+ fig = plt.gcf()
21
+ if fig_queue:
22
+ fig_queue.put(fig)
23
+ else:
24
+ fig.show()
25
+ plt.close(fig)
26
+
27
+ def preprocess_generate_masks_wrapper(settings, q, fig_queue):
16
28
  """
17
29
  Wraps the measure_crop function to integrate with GUI processes.
18
30
 
@@ -24,19 +36,18 @@ def measure_crop_wrapper(settings, q, fig_queue):
24
36
 
25
37
  # Temporarily override plt.show
26
38
  original_show = plt.show
27
- plt.show = my_show
39
+ plt.show = lambda: spacrFigShow(fig_queue)
28
40
 
29
41
  try:
30
- print('start')
31
- spacr.measure.measure_crop(settings=settings)
42
+ spacr.core.preprocess_generate_masks(src=settings['src'], settings=settings)
32
43
  except Exception as e:
33
44
  errorMessage = f"Error during processing: {e}"
34
45
  q.put(errorMessage)
35
46
  traceback.print_exc()
36
47
  finally:
37
- plt.show = original_show
38
-
39
- def preprocess_generate_masks_wrapper(settings, q, fig_queue):
48
+ plt.show = original_show
49
+
50
+ def measure_crop_wrapper(settings, q, fig_queue):
40
51
  """
41
52
  Wraps the measure_crop function to integrate with GUI processes.
42
53
 
@@ -48,22 +59,23 @@ def preprocess_generate_masks_wrapper(settings, q, fig_queue):
48
59
 
49
60
  # Temporarily override plt.show
50
61
  original_show = plt.show
51
- plt.show = my_show
62
+ plt.show = lambda: spacrFigShow(fig_queue)
52
63
 
53
64
  try:
54
- spacr.core.preprocess_generate_masks(src=settings['src'], settings=settings)
65
+ print('start')
66
+ spacr.measure.measure_crop(settings=settings)
55
67
  except Exception as e:
56
68
  errorMessage = f"Error during processing: {e}"
57
69
  q.put(errorMessage)
58
70
  traceback.print_exc()
59
71
  finally:
60
- plt.show = original_show
72
+ plt.show = original_show
61
73
 
62
74
  def sequencing_wrapper(settings, q, fig_queue):
63
75
 
64
76
  # Temporarily override plt.show
65
77
  original_show = plt.show
66
- plt.show = my_show
78
+ plt.show = lambda: spacrFigShow(fig_queue)
67
79
 
68
80
  try:
69
81
  spacr.sequencing.analyze_reads(settings=settings)
@@ -78,7 +90,7 @@ def umap_wrapper(settings, q, fig_queue):
78
90
 
79
91
  # Temporarily override plt.show
80
92
  original_show = plt.show
81
- plt.show = my_show
93
+ plt.show = lambda: spacrFigShow(fig_queue)
82
94
 
83
95
  try:
84
96
  spacr.core.generate_image_umap(settings=settings)
@@ -101,7 +113,7 @@ def train_test_model_wrapper(settings, q, fig_queue):
101
113
 
102
114
  # Temporarily override plt.show
103
115
  original_show = plt.show
104
- plt.show = my_show
116
+ plt.show = lambda: spacrFigShow(fig_queue)
105
117
 
106
118
  try:
107
119
  spacr.core.train_test_model(settings['src'], settings=settings)
@@ -125,7 +137,7 @@ def run_multiple_simulations_wrapper(settings, q, fig_queue):
125
137
 
126
138
  # Temporarily override plt.show
127
139
  original_show = plt.show
128
- plt.show = my_show
140
+ plt.show = lambda: spacrFigShow(fig_queue)
129
141
 
130
142
  try:
131
143
  spacr.sim.run_multiple_simulations(settings=settings)
spacr/measure.py CHANGED
@@ -998,12 +998,12 @@ def measure_crop(settings):
998
998
  _save_settings_to_db(settings)
999
999
 
1000
1000
  files = [f for f in os.listdir(settings['input_folder']) if f.endswith('.npy')]
1001
- max_workers = settings['max_workers'] or mp.cpu_count()-4
1002
- print(f'using {max_workers} cpu cores')
1001
+ n_jobs = settings['n_jobs'] or mp.cpu_count()-4
1002
+ print(f'using {n_jobs} cpu cores')
1003
1003
 
1004
1004
  with mp.Manager() as manager:
1005
1005
  time_ls = manager.list()
1006
- with mp.Pool(max_workers) as pool:
1006
+ with mp.Pool(n_jobs) as pool:
1007
1007
  result = pool.starmap_async(_measure_crop_core, [(index, time_ls, file, settings) for index, file in enumerate(files)])
1008
1008
 
1009
1009
  # Track progress in the main process
@@ -1012,7 +1012,7 @@ def measure_crop(settings):
1012
1012
  files_processed = len(time_ls)
1013
1013
  files_to_process = len(files)
1014
1014
  average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
1015
- time_left = (((files_to_process-files_processed)*average_time)/max_workers)/60
1015
+ time_left = (((files_to_process-files_processed)*average_time)/n_jobs)/60
1016
1016
  print(f'Progress: {files_processed}/{files_to_process} Time/img {average_time:.3f}sec, Time Remaining {time_left:.3f} min.', end='\r', flush=True)
1017
1017
  result.get()
1018
1018