spacr 0.1.6__py3-none-any.whl → 0.1.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,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
@@ -16,12 +16,6 @@ from skimage.util import img_as_bool
16
16
 
17
17
  from .logger import log_function_call
18
18
 
19
- #from .io import create_database, _save_settings_to_db
20
- #from .timelapse import _timelapse_masks_to_gif, _scmovie
21
- #from .plot import _plot_cropped_arrays, _save_scimg_plot
22
- #from .utils import _merge_overlapping_objects, _filter_object, _relabel_parent_with_child_labels, _exclude_objects
23
- #from .utils import _merge_and_save_to_database, _crop_center, _find_bounding_box, _generate_names, _get_percentiles, normalize_to_dtype, _map_wells_png, _list_endpoint_subdirectories, _generate_representative_images
24
-
25
19
  def get_components(cell_mask, nucleus_mask, pathogen_mask):
26
20
  """
27
21
  Get the components (nucleus and pathogens) for each cell in the given masks.
@@ -626,8 +620,14 @@ def _measure_crop_core(index, time_ls, file, settings):
626
620
 
627
621
  file_name = os.path.splitext(file)[0]
628
622
  data = np.load(os.path.join(settings['input_folder'], file))
629
-
630
623
  data_type = data.dtype
624
+ if data_type not in ['uint8','uint16']:
625
+ data_type_before = data_type
626
+ data = data.astype(np.uint16)
627
+ data_type = data.dtype
628
+ if settings['verbose']:
629
+ print(f'Converted data from {data_type_before} to {data_type}')
630
+
631
631
  if settings['save_measurements']:
632
632
  os.makedirs(source_folder+'/measurements', exist_ok=True)
633
633
  _create_database(source_folder+'/measurements/measurements.db')
@@ -832,7 +832,6 @@ def _measure_crop_core(index, time_ls, file, settings):
832
832
  png_channels = normalize_to_dtype(png_channels, settings['normalize'][0], settings['normalize'][1], percentile_list=percentile_list)
833
833
  else:
834
834
  png_channels = normalize_to_dtype(png_channels, 0, 100)
835
-
836
835
  os.makedirs(png_folder, exist_ok=True)
837
836
 
838
837
  if png_channels.shape[2] == 2:
@@ -999,12 +998,12 @@ def measure_crop(settings):
999
998
  _save_settings_to_db(settings)
1000
999
 
1001
1000
  files = [f for f in os.listdir(settings['input_folder']) if f.endswith('.npy')]
1002
- max_workers = settings['max_workers'] or mp.cpu_count()-4
1003
- 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')
1004
1003
 
1005
1004
  with mp.Manager() as manager:
1006
1005
  time_ls = manager.list()
1007
- with mp.Pool(max_workers) as pool:
1006
+ with mp.Pool(n_jobs) as pool:
1008
1007
  result = pool.starmap_async(_measure_crop_core, [(index, time_ls, file, settings) for index, file in enumerate(files)])
1009
1008
 
1010
1009
  # Track progress in the main process
@@ -1013,7 +1012,7 @@ def measure_crop(settings):
1013
1012
  files_processed = len(time_ls)
1014
1013
  files_to_process = len(files)
1015
1014
  average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
1016
- 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
1017
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)
1018
1017
  result.get()
1019
1018
 
spacr/plot.py CHANGED
@@ -1565,4 +1565,56 @@ def plot_feature_importance(feature_importance_df):
1565
1565
  ax.set_xlabel('Feature Importance', fontsize=font_size)
1566
1566
  ax.tick_params(axis='both', which='major', labelsize=font_size)
1567
1567
  plt.tight_layout()
1568
- return fig
1568
+ return fig
1569
+
1570
+ def read_and_plot__vision_results(base_dir, y_axis='accuracy', name_split='_time', y_lim=[0.8, 0.9]):
1571
+ # List to store data from all CSV files
1572
+ data_frames = []
1573
+
1574
+ dst = os.path.join(base_dir, 'result')
1575
+ os.mkdir(dst,exists=True)
1576
+
1577
+ # Walk through the directory
1578
+ for root, dirs, files in os.walk(base_dir):
1579
+ for file in files:
1580
+ if file.endswith("_test_result.csv"):
1581
+ file_path = os.path.join(root, file)
1582
+ # Extract model information from the file name
1583
+ file_name = os.path.basename(file_path)
1584
+ model = file_name.split(f'{name_split}')[0]
1585
+
1586
+ # Extract epoch information from the file name
1587
+ epoch_info = file_name.split('_time')[1]
1588
+ base_folder = os.path.dirname(file_path)
1589
+ epoch = os.path.basename(base_folder)
1590
+
1591
+ # Read the CSV file
1592
+ df = pd.read_csv(file_path)
1593
+ df['model'] = model
1594
+ df['epoch'] = epoch
1595
+
1596
+ # Append the data frame to the list
1597
+ data_frames.append(df)
1598
+
1599
+ # Concatenate all data frames
1600
+ if data_frames:
1601
+ result_df = pd.concat(data_frames, ignore_index=True)
1602
+
1603
+ # Calculate average y_axis per model
1604
+ avg_metric = result_df.groupby('model')[y_axis].mean().reset_index()
1605
+ avg_metric = avg_metric.sort_values(by=y_axis)
1606
+ print(avg_metric)
1607
+
1608
+ # Plotting the results
1609
+ plt.figure(figsize=(10, 6))
1610
+ plt.bar(avg_metric['model'], avg_metric[y_axis])
1611
+ plt.xlabel('Model')
1612
+ plt.ylabel(f'{y_axis}')
1613
+ plt.title(f'Average {y_axis.capitalize()} per Model')
1614
+ plt.xticks(rotation=45)
1615
+ plt.tight_layout()
1616
+ if y_lim is not None:
1617
+ plt.ylim(y_lim)
1618
+ plt.show()
1619
+ else:
1620
+ print("No CSV files found in the specified directory.")