spacr 0.0.2__py3-none-any.whl → 0.0.6__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
@@ -13,7 +13,7 @@ from ttkthemes import ThemedTk
13
13
 
14
14
  from .logger import log_function_call
15
15
 
16
- from .gui_utils import ScrollableFrame, set_dark_style, set_default_font, create_dark_mode
16
+ from .gui_utils import ScrollableFrame, set_dark_style, set_default_font, create_dark_mode, style_text_boxes, create_menu_bar
17
17
 
18
18
  class modify_masks:
19
19
 
@@ -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:
@@ -753,15 +857,18 @@ class modify_masks:
753
857
  self.mask[labeled_mask == i] = 0 # Remove small objects
754
858
  self.update_display()
755
859
 
756
- @log_function_call
860
+ ##@log_function_call
757
861
  def initiate_mask_app_root(width, height):
758
862
  theme = 'breeze'
759
863
  root = ThemedTk(theme=theme)
760
864
  style = ttk.Style(root)
761
865
  set_dark_style(style)
762
- set_default_font(root, font_name="Arial", size=10)
866
+
867
+ style_text_boxes(style)
868
+ set_default_font(root, font_name="Arial", size=8)
763
869
  root.geometry(f"{width}x{height}")
764
870
  root.title("Mask App")
871
+ create_menu_bar(root)
765
872
 
766
873
  container = tk.PanedWindow(root, orient=tk.HORIZONTAL)
767
874
  container.pack(fill=tk.BOTH, expand=True)
@@ -806,7 +913,7 @@ def initiate_mask_app_root(width, height):
806
913
  create_dark_mode(root, style, console_output=None)
807
914
 
808
915
  run_button = ttk.Button(scrollable_frame.scrollable_frame, text="Run", command=run_app)
809
- run_button.grid(row=row, column=0, columnspan=2, pady=10)
916
+ run_button.grid(row=row, column=0, columnspan=2, pady=10, padx=10)
810
917
 
811
918
  return root
812
919
 
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
@@ -12,6 +11,8 @@ from scipy.ndimage import binary_dilation
12
11
  from skimage.segmentation import find_boundaries
13
12
  from skimage.feature import graycomatrix, graycoprops
14
13
  from mahotas.features import zernike_moments
14
+ from skimage import morphology, measure, filters
15
+ from skimage.util import img_as_bool
15
16
 
16
17
  from .logger import log_function_call
17
18
 
@@ -92,7 +93,70 @@ def _calculate_zernike(mask, df, degree=8):
92
93
  else:
93
94
  return df
94
95
 
95
- @log_function_call
96
+ def _analyze_cytoskeleton(array, mask, channel):
97
+ """
98
+ Analyzes and extracts skeleton properties from labeled objects in a masked image based on microtubule staining intensities.
99
+
100
+ Parameters:
101
+ image : numpy array
102
+ Intensity image where the microtubules are stained.
103
+ mask : numpy array
104
+ Mask where objects are labeled for analysis. Each label corresponds to a unique object.
105
+
106
+ Returns:
107
+ DataFrame
108
+ A pandas DataFrame containing the measured properties of each object's skeleton.
109
+ """
110
+
111
+ image = array[:, :, channel]
112
+
113
+ properties_list = []
114
+
115
+ # Process each object in the mask based on its label
116
+ for label in np.unique(mask):
117
+ if label == 0:
118
+ continue # Skip background
119
+
120
+ # Isolate the object using the label
121
+ object_region = mask == label
122
+ region_intensity = np.where(object_region, image, 0) # Use np.where for more efficient masking
123
+
124
+ # Ensure there are non-zero values to process
125
+ if np.any(region_intensity):
126
+ # Calculate adaptive offset based on intensity percentiles within the object
127
+ valid_pixels = region_intensity[region_intensity > 0]
128
+ if len(valid_pixels) > 1: # Ensure there are enough pixels to compute percentiles
129
+ offset = np.percentile(valid_pixels, 90) - np.percentile(valid_pixels, 50)
130
+ block_size = 35 # Adjust this based on your object sizes and detail needs
131
+ local_thresh = filters.threshold_local(region_intensity, block_size=block_size, offset=offset)
132
+ cytoskeleton = region_intensity > local_thresh
133
+
134
+ # Skeletonize the thresholded cytoskeleton
135
+ skeleton = morphology.skeletonize(img_as_bool(cytoskeleton))
136
+
137
+ # Measure properties of the skeleton
138
+ skeleton_props = measure.regionprops(measure.label(skeleton), intensity_image=image)
139
+ skeleton_length = sum(prop.area for prop in skeleton_props) # Sum of lengths of all skeleton segments
140
+ branch_data = morphology.skeleton_branch_analysis(skeleton)
141
+
142
+ # Store properties
143
+ properties = {
144
+ "object_label": label,
145
+ "skeleton_length": skeleton_length,
146
+ "skeleton_branch_points": len(branch_data['branch_points'])
147
+ }
148
+ properties_list.append(properties)
149
+ else:
150
+ # Handle cases with insufficient pixels
151
+ properties_list.append({
152
+ "object_label": label,
153
+ "skeleton_length": 0,
154
+ "skeleton_branch_points": 0
155
+ })
156
+
157
+ return pd.DataFrame(properties_list)
158
+
159
+ #@log_function_call
96
160
  def _morphological_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, settings, zernike=True, degree=8):
97
161
  """
98
162
  Calculate morphological measurements for cells, nucleus, pathogens, and cytoplasms based on the given masks.
@@ -436,7 +500,7 @@ def _estimate_blur(image):
436
500
  # Compute and return the variance of the Laplacian
437
501
  return lap.var()
438
502
 
439
- @log_function_call
503
+ #@log_function_call
440
504
  def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, channel_arrays, settings, sizes=[3, 6, 12, 24], periphery=True, outside=True):
441
505
  """
442
506
  Calculate various intensity measurements for different regions in the image.
@@ -524,8 +588,9 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
524
588
 
525
589
  return pd.concat(cell_dfs, axis=1), pd.concat(nucleus_dfs, axis=1), pd.concat(pathogen_dfs, axis=1), pd.concat(cytoplasm_dfs, axis=1)
526
590
 
527
- @log_function_call
591
+ #@log_function_call
528
592
  def _measure_crop_core(index, time_ls, file, settings):
593
+
529
594
  """
530
595
  Measure and crop the images based on specified settings.
531
596
 
@@ -624,9 +689,8 @@ def _measure_crop_core(index, time_ls, file, settings):
624
689
  if settings['cytoplasm_min_size'] is not None and settings['cytoplasm_min_size'] != 0:
625
690
  cytoplasm_mask = _filter_object(cytoplasm_mask, settings['cytoplasm_min_size'])
626
691
 
627
- if settings['cell_mask_dim'] is not None and settings['pathogen_mask_dim'] is not None:
628
- if settings['include_uninfected'] == False:
629
- cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask = _exclude_objects(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, include_uninfected=False)
692
+ if settings['cell_mask_dim'] is not None:
693
+ cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask = _exclude_objects(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, include_uninfected=settings['include_uninfected'])
630
694
 
631
695
  # Update data with the new masks
632
696
  if settings['cell_mask_dim'] is not None:
@@ -645,6 +709,10 @@ def _measure_crop_core(index, time_ls, file, settings):
645
709
 
646
710
  cell_df, nucleus_df, pathogen_df, cytoplasm_df = _morphological_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, settings)
647
711
 
712
+ #if settings['skeleton']:
713
+ #skeleton_df = _analyze_cytoskeleton(image=channel_arrays, mask=cell_mask, channel=1)
714
+ #merge skeleton_df with cell_df here
715
+
648
716
  cell_intensity_df, nucleus_intensity_df, pathogen_intensity_df, cytoplasm_intensity_df = _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, channel_arrays, settings, sizes=[1, 2, 3, 4, 5], periphery=True, outside=True)
649
717
  if settings['cell_mask_dim'] is not None:
650
718
  cell_merged_df = _merge_and_save_to_database(cell_df, cell_intensity_df, 'cell', source_folder, file_name, settings['experiment'], settings['timelapse'])
@@ -658,7 +726,6 @@ def _measure_crop_core(index, time_ls, file, settings):
658
726
  if settings['cytoplasm']:
659
727
  cytoplasm_merged_df = _merge_and_save_to_database(cytoplasm_df, cytoplasm_intensity_df, 'cytoplasm', source_folder, file_name, settings['experiment'], settings['timelapse'])
660
728
 
661
-
662
729
  if settings['save_png'] or settings['save_arrays'] or settings['plot']:
663
730
 
664
731
  if isinstance(settings['dialate_pngs'], bool):
@@ -731,7 +798,7 @@ def _measure_crop_core(index, time_ls, file, settings):
731
798
  png_channels = data[:, :, settings['png_dims']].astype(data_type)
732
799
 
733
800
  if settings['normalize_by'] == 'fov':
734
- percentiles_list = _get_percentiles(png_channels, settings['normalize_percentiles'][0],q2=settings['normalize_percentiles'][1])
801
+ percentiles_list = _get_percentiles(png_channels, settings['normalize'][0],q2=settings['normalize'][1])
735
802
 
736
803
  png_channels = _crop_center(png_channels, region, new_width=width, new_height=height)
737
804
 
@@ -788,6 +855,7 @@ def _measure_crop_core(index, time_ls, file, settings):
788
855
  conn.commit()
789
856
  except sqlite3.OperationalError as e:
790
857
  print(f"SQLite error: {e}", flush=True)
858
+ traceback.print_exc()
791
859
 
792
860
  if settings['plot']:
793
861
  _plot_cropped_arrays(png_channels)
@@ -818,15 +886,14 @@ def _measure_crop_core(index, time_ls, file, settings):
818
886
  average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
819
887
  return average_time, cells
820
888
 
821
- @log_function_call
822
- def measure_crop(settings, annotation_settings, advanced_settings):
889
+ #@log_function_call
890
+ def measure_crop(settings):
891
+
823
892
  """
824
893
  Measure the crop of an image based on the provided settings.
825
894
 
826
895
  Args:
827
896
  settings (dict): The settings for measuring the crop.
828
- annotation_settings (dict): The annotation settings.
829
- advanced_settings (dict): The advanced settings.
830
897
 
831
898
  Returns:
832
899
  None
@@ -845,19 +912,6 @@ def measure_crop(settings, annotation_settings, advanced_settings):
845
912
  from .plot import _save_scimg_plot
846
913
  from .utils import _list_endpoint_subdirectories, _generate_representative_images
847
914
 
848
- settings = {**settings, **annotation_settings, **advanced_settings}
849
-
850
- dirname = os.path.dirname(settings['input_folder'])
851
- settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
852
- settings_csv = os.path.join(dirname,'settings','measure_crop_settings.csv')
853
- os.makedirs(os.path.join(dirname,'settings'), exist_ok=True)
854
- settings_df.to_csv(settings_csv, index=False)
855
-
856
- if settings['timelapse_objects'] == 'nucleus':
857
- if not settings['cell_mask_dim'] is None:
858
- tlo = settings['timelapse_objects']
859
- print(f'timelapse object:{tlo}, cells will be relabeled to nucleus labels to track cells.')
860
-
861
915
  #general settings
862
916
  settings['merge_edge_pathogen_cells'] = True
863
917
  settings['radial_dist'] = True
@@ -866,6 +920,26 @@ def measure_crop(settings, annotation_settings, advanced_settings):
866
920
  settings['homogeneity'] = True
867
921
  settings['homogeneity_distances'] = [8,16,32]
868
922
  settings['save_arrays'] = False
923
+
924
+ settings['dialate_pngs'] = False
925
+ settings['dialate_png_ratios'] = [0.2]
926
+ settings['timelapse'] = False
927
+ settings['representative_images'] = False
928
+ settings['timelapse_objects'] = 'cell'
929
+ settings['max_workers'] = os.cpu_count()-2
930
+ settings['experiment'] = 'test'
931
+ settings['cells'] = 'HeLa'
932
+ settings['cell_loc'] = None
933
+ settings['pathogens'] = ['ME49Dku80WT', 'ME49Dku80dgra8:GRA8', 'ME49Dku80dgra8', 'ME49Dku80TKO']
934
+ settings['pathogen_loc'] = [['c1', 'c2', 'c3', 'c4', 'c5', 'c6'], ['c7', 'c8', 'c9', 'c10', 'c11', 'c12'], ['c13', 'c14', 'c15', 'c16', 'c17', 'c18'], ['c19', 'c20', 'c21', 'c22', 'c23', 'c24']]
935
+ settings['treatments'] = ['BR1', 'BR2', 'BR3']
936
+ settings['treatment_loc'] = [['c1', 'c2', 'c7', 'c8', 'c13', 'c14', 'c19', 'c20'], ['c3', 'c4', 'c9', 'c10', 'c15', 'c16', 'c21', 'c22'], ['c5', 'c6', 'c11', 'c12', 'c17', 'c18', 'c23', 'c24']]
937
+ settings['channel_of_interest'] = 2
938
+ settings['compartments'] = ['pathogen', 'cytoplasm']
939
+ settings['measurement'] = 'mean_intensity'
940
+ settings['nr_imgs'] = 32
941
+ settings['um_per_pixel'] = 0.1
942
+ settings['center_crop'] = True
869
943
 
870
944
  if settings['cell_mask_dim'] is None:
871
945
  settings['include_uninfected'] = True
@@ -878,7 +952,18 @@ def measure_crop(settings, annotation_settings, advanced_settings):
878
952
  else:
879
953
  settings['cytoplasm'] = False
880
954
 
881
- settings['center_crop'] = True
955
+ #settings = {**settings, **annotation_settings, **advanced_settings}
956
+
957
+ dirname = os.path.dirname(settings['input_folder'])
958
+ settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
959
+ settings_csv = os.path.join(dirname,'settings','measure_crop_settings.csv')
960
+ os.makedirs(os.path.join(dirname,'settings'), exist_ok=True)
961
+ settings_df.to_csv(settings_csv, index=False)
962
+
963
+ if settings['timelapse_objects'] == 'nucleus':
964
+ if not settings['cell_mask_dim'] is None:
965
+ tlo = settings['timelapse_objects']
966
+ print(f'timelapse object:{tlo}, cells will be relabeled to nucleus labels to track cells.')
882
967
 
883
968
  int_setting_keys = ['cell_mask_dim', 'nucleus_mask_dim', 'pathogen_mask_dim', 'cell_min_size', 'nucleus_min_size', 'pathogen_min_size', 'cytoplasm_min_size']
884
969
 
@@ -922,49 +1007,49 @@ def measure_crop(settings, annotation_settings, advanced_settings):
922
1007
  time_left = (((files_to_process-files_processed)*average_time)/max_workers)/60
923
1008
  print(f'Progress: {files_processed}/{files_to_process} Time/img {average_time:.3f}sec, Time Remaining {time_left:.3f} min.', end='\r', flush=True)
924
1009
  result.get()
925
-
926
- if settings['save_png']:
927
- img_fldr = os.path.join(os.path.dirname(settings['input_folder']), 'data')
928
- sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
929
-
930
- for i, well_src in enumerate(sc_img_fldrs):
931
- if len(os.listdir(well_src)) < 16:
932
- nr_imgs = len(os.listdir(well_src))
933
- standardize = False
934
- else:
935
- nr_imgs = 16
936
- standardize = True
937
- try:
938
- all_folders = len(sc_img_fldrs)
939
- _save_scimg_plot(src=well_src, nr_imgs=nr_imgs, channel_indices=settings['png_dims'], um_per_pixel=0.1, scale_bar_length_um=10, standardize=standardize, fontsize=12, show_filename=True, channel_names=['red','green','blue'], dpi=300, plot=False, i=i, all_folders=all_folders)
940
-
941
- except Exception as e:
942
- print(f"Unable to generate figure for folder {well_src}: {e}", end='\r', flush=True)
943
- #traceback.print_exc()
1010
+
1011
+ if settings['representative_images']:
1012
+ if settings['save_png']:
1013
+ img_fldr = os.path.join(os.path.dirname(settings['input_folder']), 'data')
1014
+ sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
1015
+
1016
+ for i, well_src in enumerate(sc_img_fldrs):
1017
+ if len(os.listdir(well_src)) < 16:
1018
+ nr_imgs = len(os.listdir(well_src))
1019
+ standardize = False
1020
+ else:
1021
+ nr_imgs = 16
1022
+ standardize = True
1023
+ try:
1024
+ all_folders = len(sc_img_fldrs)
1025
+ _save_scimg_plot(src=well_src, nr_imgs=nr_imgs, channel_indices=settings['png_dims'], um_per_pixel=0.1, scale_bar_length_um=10, standardize=standardize, fontsize=12, show_filename=True, channel_names=['red','green','blue'], dpi=300, plot=False, i=i, all_folders=all_folders)
1026
+
1027
+ except Exception as e:
1028
+ print(f"Unable to generate figure for folder {well_src}: {e}", end='\r', flush=True)
1029
+ #traceback.print_exc()
944
1030
 
945
1031
  if settings['save_measurements']:
946
- if settings['representative_images']:
947
- db_path = os.path.join(os.path.dirname(settings['input_folder']), 'measurements', 'measurements.db')
948
- channel_indices = settings['png_dims']
949
- channel_indices = [min(value, 2) for value in channel_indices]
950
- _generate_representative_images(db_path,
951
- cells=settings['cells'],
952
- cell_loc=settings['cell_loc'],
953
- pathogens=settings['pathogens'],
954
- pathogen_loc=settings['pathogen_loc'],
955
- treatments=settings['treatments'],
956
- treatment_loc=settings['treatment_loc'],
957
- channel_of_interest=settings['channel_of_interest'],
958
- compartments = settings['compartments'],
959
- measurement = settings['measurement'],
960
- nr_imgs=settings['nr_imgs'],
961
- channel_indices=channel_indices,
962
- um_per_pixel=settings['um_per_pixel'],
963
- scale_bar_length_um=10,
964
- plot=False,
965
- fontsize=12,
966
- show_filename=True,
967
- channel_names=None)
1032
+ db_path = os.path.join(os.path.dirname(settings['input_folder']), 'measurements', 'measurements.db')
1033
+ channel_indices = settings['png_dims']
1034
+ channel_indices = [min(value, 2) for value in channel_indices]
1035
+ _generate_representative_images(db_path,
1036
+ cells=settings['cells'],
1037
+ cell_loc=settings['cell_loc'],
1038
+ pathogens=settings['pathogens'],
1039
+ pathogen_loc=settings['pathogen_loc'],
1040
+ treatments=settings['treatments'],
1041
+ treatment_loc=settings['treatment_loc'],
1042
+ channel_of_interest=settings['channel_of_interest'],
1043
+ compartments = settings['compartments'],
1044
+ measurement = settings['measurement'],
1045
+ nr_imgs=settings['nr_imgs'],
1046
+ channel_indices=channel_indices,
1047
+ um_per_pixel=settings['um_per_pixel'],
1048
+ scale_bar_length_um=10,
1049
+ plot=False,
1050
+ fontsize=12,
1051
+ show_filename=True,
1052
+ channel_names=None)
968
1053
 
969
1054
  if settings['timelapse']:
970
1055
  if settings['timelapse_objects'] == 'nucleus':
@@ -973,7 +1058,7 @@ def measure_crop(settings, annotation_settings, advanced_settings):
973
1058
  object_types = ['nucleus','pathogen','cell']
974
1059
  _timelapse_masks_to_gif(folder_path, mask_channels, object_types)
975
1060
 
976
- if settings['save_png']:
1061
+ #if settings['save_png']:
977
1062
  img_fldr = os.path.join(os.path.dirname(settings['input_folder']), 'data')
978
1063
  sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
979
1064
  _scmovie(sc_img_fldrs)
@@ -1006,3 +1091,18 @@ def generate_cellpose_train_set(folders, dst, min_objects=5):
1006
1091
  shutil.copy(img_path, new_img)
1007
1092
  except Exception as e:
1008
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
@@ -103,7 +103,7 @@ def run_mask_gui(q):
103
103
  except Exception as e:
104
104
  q.put(f"Error during processing: {e}\n")
105
105
 
106
- @log_function_call
106
+ #@log_function_call
107
107
  def main_thread_update_function(root, q, fig_queue, canvas_widget, progress_label):
108
108
  try:
109
109
  while not q.empty():
@@ -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