spacr 0.0.36__py3-none-any.whl → 0.0.62__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/mask_app.py CHANGED
@@ -128,10 +128,13 @@ class modify_masks:
128
128
  self.zoom_active = False
129
129
  self.magic_wand_active = False
130
130
  self.brush_active = False
131
+ self.dividing_line_active = False
132
+ self.dividing_line_coords = []
133
+ self.current_dividing_line = None
131
134
  self.lower_quantile = tk.StringVar(value="1.0")
132
135
  self.upper_quantile = tk.StringVar(value="99.9")
133
136
  self.magic_wand_tolerance = tk.StringVar(value="1000")
134
-
137
+
135
138
  def update_mouse_info(self, event):
136
139
  x, y = event.x, event.y
137
140
  intensity = "N/A"
@@ -187,13 +190,14 @@ class modify_masks:
187
190
  self.max_pixels_entry.pack(side='left')
188
191
  self.erase_btn = tk.Button(self.mode_toolbar, text="Erase", command=self.toggle_erase_mode)
189
192
  self.erase_btn.pack(side='left')
190
-
191
193
  self.brush_btn = tk.Button(self.mode_toolbar, text="Brush", command=self.toggle_brush_mode)
192
194
  self.brush_btn.pack(side='left')
193
195
  self.brush_size_entry = tk.Entry(self.mode_toolbar)
194
- self.brush_size_entry.insert(0, "10")
196
+ self.brush_size_entry.insert(0, "10")
195
197
  self.brush_size_entry.pack(side='left')
196
198
  tk.Label(self.mode_toolbar, text="Brush Size:").pack(side='left')
199
+ self.dividing_line_btn = tk.Button(self.mode_toolbar, text="Dividing Line", command=self.toggle_dividing_line_mode)
200
+ self.dividing_line_btn.pack(side='left')
197
201
 
198
202
  def setup_function_toolbar(self):
199
203
  self.function_toolbar = tk.Frame(self.root)
@@ -393,10 +397,12 @@ class modify_masks:
393
397
  self.magic_wand_active = False
394
398
  self.erase_active = False
395
399
  self.brush_active = False
400
+ self.dividing_line_active = False
396
401
  self.draw_btn.config(text="Draw")
397
402
  self.erase_btn.config(text="Erase")
398
403
  self.magic_wand_btn.config(text="Magic Wand")
399
404
  self.zoom_btn.config(text="Zoom ON")
405
+ self.dividing_line_btn.config(text="Dividing Line")
400
406
  self.canvas.unbind("<Button-1>")
401
407
  self.canvas.unbind("<Button-3>")
402
408
  self.canvas.unbind("<Motion>")
@@ -423,7 +429,7 @@ class modify_masks:
423
429
  self.zoom_mask = None
424
430
  self.zoom_image = None
425
431
  self.zoom_image_orig = None
426
-
432
+
427
433
  def toggle_brush_mode(self):
428
434
  self.brush_active = not self.brush_active
429
435
  if self.brush_active:
@@ -448,7 +454,105 @@ class modify_masks:
448
454
  self.canvas.unbind("<B3-Motion>")
449
455
  self.canvas.unbind("<ButtonRelease-1>")
450
456
  self.canvas.unbind("<ButtonRelease-3>")
451
-
457
+
458
+ def image_to_canvas(self, x_image, y_image):
459
+ x_scale, y_scale = self.get_scaling_factors(
460
+ self.image.shape[1], self.image.shape[0],
461
+ self.canvas_width, self.canvas_height
462
+ )
463
+ x_canvas = int(x_image / x_scale)
464
+ y_canvas = int(y_image / y_scale)
465
+ return x_canvas, y_canvas
466
+
467
+ def toggle_dividing_line_mode(self):
468
+ self.dividing_line_active = not self.dividing_line_active
469
+ if self.dividing_line_active:
470
+ self.drawing = False
471
+ self.magic_wand_active = False
472
+ self.erase_active = False
473
+ self.brush_active = False
474
+ self.draw_btn.config(text="Draw")
475
+ self.erase_btn.config(text="Erase")
476
+ self.magic_wand_btn.config(text="Magic Wand")
477
+ self.brush_btn.config(text="Brush")
478
+ self.dividing_line_btn.config(text="Dividing Line ON")
479
+ self.canvas.unbind("<Button-1>")
480
+ self.canvas.unbind("<ButtonRelease-1>")
481
+ self.canvas.unbind("<Motion>")
482
+ self.canvas.bind("<Button-1>", self.start_dividing_line)
483
+ self.canvas.bind("<ButtonRelease-1>", self.finish_dividing_line)
484
+ self.canvas.bind("<Motion>", self.update_dividing_line_preview)
485
+ else:
486
+ print("Dividing Line Mode: OFF")
487
+ self.dividing_line_active = False
488
+ self.dividing_line_btn.config(text="Dividing Line")
489
+ self.canvas.unbind("<Button-1>")
490
+ self.canvas.unbind("<ButtonRelease-1>")
491
+ self.canvas.unbind("<Motion>")
492
+ self.display_image()
493
+
494
+ def start_dividing_line(self, event):
495
+ if self.dividing_line_active:
496
+ self.dividing_line_coords = [(event.x, event.y)]
497
+ self.current_dividing_line = self.canvas.create_line(event.x, event.y, event.x, event.y, fill="red", width=2)
498
+
499
+ def finish_dividing_line(self, event):
500
+ if self.dividing_line_active:
501
+ self.dividing_line_coords.append((event.x, event.y))
502
+ if self.zoom_active:
503
+ self.dividing_line_coords = [self.canvas_to_image(x, y) for x, y in self.dividing_line_coords]
504
+ self.apply_dividing_line()
505
+ self.canvas.delete(self.current_dividing_line)
506
+ self.current_dividing_line = None
507
+
508
+ def update_dividing_line_preview(self, event):
509
+ if self.dividing_line_active and self.dividing_line_coords:
510
+ x, y = event.x, event.y
511
+ if self.zoom_active:
512
+ x, y = self.canvas_to_image(x, y)
513
+ self.dividing_line_coords.append((x, y))
514
+ canvas_coords = [(self.image_to_canvas(*pt) if self.zoom_active else pt) for pt in self.dividing_line_coords]
515
+ flat_canvas_coords = [coord for pt in canvas_coords for coord in pt]
516
+ self.canvas.coords(self.current_dividing_line, *flat_canvas_coords)
517
+
518
+ def apply_dividing_line(self):
519
+ if self.dividing_line_coords:
520
+ coords = self.dividing_line_coords
521
+ if self.zoom_active:
522
+ coords = [self.canvas_to_image(x, y) for x, y in coords]
523
+
524
+ rr, cc = [], []
525
+ for (x0, y0), (x1, y1) in zip(coords[:-1], coords[1:]):
526
+ line_rr, line_cc = line(y0, x0, y1, x1)
527
+ rr.extend(line_rr)
528
+ cc.extend(line_cc)
529
+ rr, cc = np.array(rr), np.array(cc)
530
+
531
+ mask_copy = self.mask.copy()
532
+
533
+ if self.zoom_active:
534
+ # Update the zoomed mask
535
+ self.zoom_mask[rr, cc] = 0
536
+ # Reflect changes to the original mask
537
+ y0, y1, x0, x1 = self.zoom_y0, self.zoom_y1, self.zoom_x0, self.zoom_x1
538
+ zoomed_mask_resized_back = resize(self.zoom_mask, (y1 - y0, x1 - x0), order=0, preserve_range=True).astype(np.uint8)
539
+ self.mask[y0:y1, x0:x1] = zoomed_mask_resized_back
540
+ else:
541
+ # Directly update the original mask
542
+ mask_copy[rr, cc] = 0
543
+ self.mask = mask_copy
544
+
545
+ labeled_mask, num_labels = label(self.mask > 0)
546
+ self.mask = labeled_mask
547
+ self.update_display()
548
+
549
+ self.dividing_line_coords = []
550
+ self.canvas.unbind("<Button-1>")
551
+ self.canvas.unbind("<ButtonRelease-1>")
552
+ self.canvas.unbind("<Motion>")
553
+ self.dividing_line_active = False
554
+ self.dividing_line_btn.config(text="Dividing Line")
555
+
452
556
  def toggle_draw_mode(self):
453
557
  self.drawing = not self.drawing
454
558
  if self.drawing:
spacr/measure.py CHANGED
@@ -3,7 +3,6 @@ import numpy as np
3
3
  import pandas as pd
4
4
  from collections import defaultdict
5
5
  from scipy.stats import pearsonr
6
- import matplotlib as mpl
7
6
  import multiprocessing as mp
8
7
  from scipy.ndimage import distance_transform_edt, generate_binary_structure
9
8
  from skimage.measure import regionprops, regionprops_table, shannon_entropy
@@ -1092,3 +1091,18 @@ def generate_cellpose_train_set(folders, dst, min_objects=5):
1092
1091
  shutil.copy(img_path, new_img)
1093
1092
  except Exception as e:
1094
1093
  print(f"Error copying {path} to {new_mask}: {e}")
1094
+
1095
+ def get_object_counts(src):
1096
+ database_path = os.path.join(src, 'measurements/measurements.db')
1097
+ # Connect to the SQLite database
1098
+ conn = sqlite3.connect(database_path)
1099
+ # Read the table into a pandas DataFrame
1100
+ df = pd.read_sql_query("SELECT * FROM object_counts", conn)
1101
+ # Group by 'count_type' and calculate the sum of 'object_count' and the average 'object_count' per 'file_name'
1102
+ grouped_df = df.groupby('count_type').agg(
1103
+ total_object_count=('object_count', 'sum'),
1104
+ avg_object_count_per_file_name=('object_count', 'mean')
1105
+ ).reset_index()
1106
+ # Close the database connection
1107
+ conn.close()
1108
+ return grouped_df
Binary file
spacr/old_code.py CHANGED
@@ -287,4 +287,72 @@ def _extract_filename_metadata(filenames, src, images_by_key, regular_expression
287
287
  print(f"Filename {filename} did not match provided regex")
288
288
  continue
289
289
 
290
- return images_by_key
290
+ return images_by_key
291
+
292
+ def compare_cellpose_masks_v1(src, verbose=False, save=False):
293
+
294
+ from .io import _read_mask
295
+ from .plot import visualize_masks, plot_comparison_results, visualize_cellpose_masks
296
+ from .utils import extract_boundaries, boundary_f1_score, compute_segmentation_ap, jaccard_index
297
+
298
+ import os
299
+ import numpy as np
300
+ from skimage.measure import label
301
+
302
+ # Collect all subdirectories in src
303
+ dirs = [os.path.join(src, d) for d in os.listdir(src) if os.path.isdir(os.path.join(src, d))]
304
+
305
+ dirs.sort() # Optional: sort directories if needed
306
+
307
+ # Get common files in all directories
308
+ common_files = set(os.listdir(dirs[0]))
309
+ for d in dirs[1:]:
310
+ common_files.intersection_update(os.listdir(d))
311
+ common_files = list(common_files)
312
+
313
+ results = []
314
+ conditions = [os.path.basename(d) for d in dirs]
315
+
316
+ for index, filename in enumerate(common_files):
317
+ print(f'Processing image {index+1}/{len(common_files)}', end='\r', flush=True)
318
+ paths = [os.path.join(d, filename) for d in dirs]
319
+
320
+ # Check if file exists in all directories
321
+ if not all(os.path.exists(path) for path in paths):
322
+ print(f'Skipping {filename} as it is not present in all directories.')
323
+ continue
324
+
325
+ masks = [_read_mask(path) for path in paths]
326
+ boundaries = [extract_boundaries(mask) for mask in masks]
327
+
328
+ if verbose:
329
+ visualize_cellpose_masks(masks, titles=conditions, comparison_title=f"Masks Comparison for {filename}", save=save, src=src)
330
+
331
+ # Initialize data structure for results
332
+ file_results = {'filename': filename}
333
+
334
+ # Compare each mask with each other
335
+ for i in range(len(masks)):
336
+ for j in range(i + 1, len(masks)):
337
+ condition_i = conditions[i]
338
+ condition_j = conditions[j]
339
+ mask_i = masks[i]
340
+ mask_j = masks[j]
341
+
342
+ # Compute metrics
343
+ boundary_f1 = boundary_f1_score(mask_i, mask_j)
344
+ jaccard = jaccard_index(mask_i, mask_j)
345
+ average_precision = compute_segmentation_ap(mask_i, mask_j)
346
+
347
+ # Store results
348
+ file_results[f'jaccard_{condition_i}_{condition_j}'] = jaccard
349
+ file_results[f'boundary_f1_{condition_i}_{condition_j}'] = boundary_f1
350
+ file_results[f'average_precision_{condition_i}_{condition_j}'] = average_precision
351
+
352
+ results.append(file_results)
353
+
354
+ fig = plot_comparison_results(results)
355
+
356
+ save_results_and_figure(src, fig, results)
357
+
358
+ return results, fig
spacr/plot.py CHANGED
@@ -11,8 +11,6 @@ import statsmodels.api as sm
11
11
  import imageio.v2 as imageio
12
12
  from IPython.display import display
13
13
  from skimage.segmentation import find_boundaries
14
- from skimage.measure import find_contours
15
- from skimage.morphology import square, dilation
16
14
  from skimage import measure
17
15
 
18
16
  from ipywidgets import IntSlider, interact
@@ -376,7 +374,7 @@ def plot_arrays(src, figuresize=50, cmap='inferno', nr=1, normalize=True, q1=1,
376
374
  print(f'Image path:{path}')
377
375
  img = np.load(path)
378
376
  if normalize:
379
- img = normalize_to_dtype(array=img, q1=q1, q2=q2)
377
+ img = normalize_to_dtype(array=img, p1=q1, p2=q2)
380
378
  dim = img.shape
381
379
  if len(img.shape)>2:
382
380
  array_nr = img.shape[2]
@@ -426,9 +424,11 @@ def _normalize_and_outline(image, remove_background, normalize, normalization_pe
426
424
  image[mask] = 0
427
425
 
428
426
  if normalize:
429
- image = normalize_to_dtype(array=image, q1=normalization_percentiles[0], q2=normalization_percentiles[1])
427
+ image = normalize_to_dtype(array=image, p1=normalization_percentiles[0], p2=normalization_percentiles[1])
428
+ else:
429
+ image = normalize_to_dtype(array=image, p1=0, p2=100)
430
430
 
431
- rgb_image = _gen_rgb_image(image, cahnnels=overlay_chans)
431
+ rgb_image = _gen_rgb_image(image, channels=overlay_chans)
432
432
 
433
433
  if overlay:
434
434
  overlayed_image, outlines, image = _outline_and_overlay(image, rgb_image, mask_dims, outline_colors, outline_thickness)
@@ -440,6 +440,7 @@ def _normalize_and_outline(image, remove_background, normalize, normalization_pe
440
440
  image = np.take(image, channels_to_keep, axis=-1)
441
441
  return [], image, []
442
442
 
443
+
443
444
  def _plot_merged_plot(overlay, image, stack, mask_dims, figuresize, overlayed_image, outlines, cmap, outline_colors, print_object_number):
444
445
 
445
446
  """
@@ -513,6 +514,8 @@ def plot_merged(src, settings):
513
514
  None
514
515
  """
515
516
  from .utils import _remove_noninfected
517
+
518
+
516
519
 
517
520
  font = settings['figuresize']/2
518
521
  outline_colors = _get_colours_merged(settings['outline_color'])
@@ -1347,7 +1350,7 @@ def visualize_masks(mask1, mask2, mask3, title="Masks Comparison"):
1347
1350
  plt.suptitle(title)
1348
1351
  plt.show()
1349
1352
 
1350
- def visualize_cellpose_masks(masks, titles=None, comparison_title="Masks Comparison"):
1353
+ def visualize_cellpose_masks(masks, titles=None, filename=None, save=False, src=None):
1351
1354
  """
1352
1355
  Visualize multiple masks with optional titles.
1353
1356
 
@@ -1356,6 +1359,9 @@ def visualize_cellpose_masks(masks, titles=None, comparison_title="Masks Compari
1356
1359
  titles (list of str, optional): A list of titles for the masks. If None, default titles will be used.
1357
1360
  comparison_title (str): Title for the entire figure.
1358
1361
  """
1362
+
1363
+ comparison_title=f"Masks Comparison for {filename}"
1364
+
1359
1365
  if titles is None:
1360
1366
  titles = [f'Mask {i+1}' for i in range(len(masks))]
1361
1367
 
@@ -1376,6 +1382,17 @@ def visualize_cellpose_masks(masks, titles=None, comparison_title="Masks Compari
1376
1382
  plt.suptitle(comparison_title)
1377
1383
  plt.show()
1378
1384
 
1385
+ if save:
1386
+ if src is None:
1387
+ src = os.getcwd()
1388
+ results_dir = os.path.join(src, 'results')
1389
+ os.makedirs(results_dir, exist_ok=True)
1390
+ fig_path = os.path.join(results_dir, f'{filename}.pdf')
1391
+ fig.savefig(fig_path, format='pdf')
1392
+ print(f'Saved figure to {fig_path}')
1393
+ return
1394
+
1395
+
1379
1396
  def plot_comparison_results(comparison_results):
1380
1397
  df = pd.DataFrame(comparison_results)
1381
1398
  df_melted = pd.melt(df, id_vars=['filename'], var_name='metric', value_name='value')