spacr 0.0.1__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,6 +93,70 @@ def _calculate_zernike(mask, df, degree=8):
92
93
  else:
93
94
  return df
94
95
 
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
95
160
  def _morphological_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, settings, zernike=True, degree=8):
96
161
  """
97
162
  Calculate morphological measurements for cells, nucleus, pathogens, and cytoplasms based on the given masks.
@@ -435,6 +500,7 @@ def _estimate_blur(image):
435
500
  # Compute and return the variance of the Laplacian
436
501
  return lap.var()
437
502
 
503
+ #@log_function_call
438
504
  def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, channel_arrays, settings, sizes=[3, 6, 12, 24], periphery=True, outside=True):
439
505
  """
440
506
  Calculate various intensity measurements for different regions in the image.
@@ -522,8 +588,9 @@ def _intensity_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_ma
522
588
 
523
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)
524
590
 
525
- @log_function_call
591
+ #@log_function_call
526
592
  def _measure_crop_core(index, time_ls, file, settings):
593
+
527
594
  """
528
595
  Measure and crop the images based on specified settings.
529
596
 
@@ -622,9 +689,8 @@ def _measure_crop_core(index, time_ls, file, settings):
622
689
  if settings['cytoplasm_min_size'] is not None and settings['cytoplasm_min_size'] != 0:
623
690
  cytoplasm_mask = _filter_object(cytoplasm_mask, settings['cytoplasm_min_size'])
624
691
 
625
- if settings['cell_mask_dim'] is not None and settings['pathogen_mask_dim'] is not None:
626
- if settings['include_uninfected'] == False:
627
- 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'])
628
694
 
629
695
  # Update data with the new masks
630
696
  if settings['cell_mask_dim'] is not None:
@@ -643,6 +709,10 @@ def _measure_crop_core(index, time_ls, file, settings):
643
709
 
644
710
  cell_df, nucleus_df, pathogen_df, cytoplasm_df = _morphological_measurements(cell_mask, nucleus_mask, pathogen_mask, cytoplasm_mask, settings)
645
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
+
646
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)
647
717
  if settings['cell_mask_dim'] is not None:
648
718
  cell_merged_df = _merge_and_save_to_database(cell_df, cell_intensity_df, 'cell', source_folder, file_name, settings['experiment'], settings['timelapse'])
@@ -656,7 +726,6 @@ def _measure_crop_core(index, time_ls, file, settings):
656
726
  if settings['cytoplasm']:
657
727
  cytoplasm_merged_df = _merge_and_save_to_database(cytoplasm_df, cytoplasm_intensity_df, 'cytoplasm', source_folder, file_name, settings['experiment'], settings['timelapse'])
658
728
 
659
-
660
729
  if settings['save_png'] or settings['save_arrays'] or settings['plot']:
661
730
 
662
731
  if isinstance(settings['dialate_pngs'], bool):
@@ -676,7 +745,6 @@ def _measure_crop_core(index, time_ls, file, settings):
676
745
  crop_ls = settings['crop_mode']
677
746
  size_ls = settings['png_size']
678
747
  for crop_idx, crop_mode in enumerate(crop_ls):
679
- print(crop_idx, crop_mode)
680
748
  width, height = size_ls[crop_idx]
681
749
  if crop_mode == 'cell':
682
750
  crop_mask = cell_mask.copy()
@@ -730,7 +798,7 @@ def _measure_crop_core(index, time_ls, file, settings):
730
798
  png_channels = data[:, :, settings['png_dims']].astype(data_type)
731
799
 
732
800
  if settings['normalize_by'] == 'fov':
733
- 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])
734
802
 
735
803
  png_channels = _crop_center(png_channels, region, new_width=width, new_height=height)
736
804
 
@@ -787,6 +855,7 @@ def _measure_crop_core(index, time_ls, file, settings):
787
855
  conn.commit()
788
856
  except sqlite3.OperationalError as e:
789
857
  print(f"SQLite error: {e}", flush=True)
858
+ traceback.print_exc()
790
859
 
791
860
  if settings['plot']:
792
861
  _plot_cropped_arrays(png_channels)
@@ -817,38 +886,32 @@ def _measure_crop_core(index, time_ls, file, settings):
817
886
  average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
818
887
  return average_time, cells
819
888
 
820
- @log_function_call
821
- def measure_crop(settings, annotation_settings, advanced_settings):
889
+ #@log_function_call
890
+ def measure_crop(settings):
891
+
822
892
  """
823
893
  Measure the crop of an image based on the provided settings.
824
894
 
825
895
  Args:
826
896
  settings (dict): The settings for measuring the crop.
827
- annotation_settings (dict): The annotation settings.
828
- advanced_settings (dict): The advanced settings.
829
897
 
830
898
  Returns:
831
899
  None
832
900
  """
901
+
902
+ if settings.get('test_mode', False):
903
+ if not os.basename(settings['src']) == 'test':
904
+ src = os.path.join(src, 'test')
905
+ settings['src'] = src
906
+ print(f'Changed source folder to {src} for test mode')
907
+ else:
908
+ print(f'Test mode enabled, using source folder {settings["src"]}')
833
909
 
834
910
  from .io import _save_settings_to_db
835
911
  from .timelapse import _timelapse_masks_to_gif, _scmovie
836
912
  from .plot import _save_scimg_plot
837
913
  from .utils import _list_endpoint_subdirectories, _generate_representative_images
838
914
 
839
- settings = {**settings, **annotation_settings, **advanced_settings}
840
-
841
- dirname = os.path.dirname(settings['input_folder'])
842
- settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
843
- settings_csv = os.path.join(dirname,'settings','measure_crop_settings.csv')
844
- os.makedirs(os.path.join(dirname,'settings'), exist_ok=True)
845
- settings_df.to_csv(settings_csv, index=False)
846
-
847
- if settings['timelapse_objects'] == 'nucleus':
848
- if not settings['cell_mask_dim'] is None:
849
- tlo = settings['timelapse_objects']
850
- print(f'timelapse object:{tlo}, cells will be relabeled to nucleus labels to track cells.')
851
-
852
915
  #general settings
853
916
  settings['merge_edge_pathogen_cells'] = True
854
917
  settings['radial_dist'] = True
@@ -857,6 +920,26 @@ def measure_crop(settings, annotation_settings, advanced_settings):
857
920
  settings['homogeneity'] = True
858
921
  settings['homogeneity_distances'] = [8,16,32]
859
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
860
943
 
861
944
  if settings['cell_mask_dim'] is None:
862
945
  settings['include_uninfected'] = True
@@ -869,7 +952,18 @@ def measure_crop(settings, annotation_settings, advanced_settings):
869
952
  else:
870
953
  settings['cytoplasm'] = False
871
954
 
872
- 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.')
873
967
 
874
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']
875
969
 
@@ -913,64 +1007,49 @@ def measure_crop(settings, annotation_settings, advanced_settings):
913
1007
  time_left = (((files_to_process-files_processed)*average_time)/max_workers)/60
914
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)
915
1009
  result.get()
916
-
917
- #if settings['save_png']:
918
- # img_fldr = os.path.join(os.path.dirname(settings['input_folder']), 'data')
919
- # sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
920
- # for well_src in sc_img_fldrs:
921
- # if len(os.listdir(well_src)) < 16:
922
- # nr_imgs = len(os.listdir(well_src))
923
- # standardize = False
924
- # else:
925
- # nr_imgs = 16
926
- # standardize = True
927
- # try:
928
- # _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)
929
- # except Exception as e: # Consider catching a more specific exception if possible
930
- # print(f"Unable to generate figure for folder {well_src}: {e}", flush=True)
931
-
932
- if settings['save_png']:
933
- img_fldr = os.path.join(os.path.dirname(settings['input_folder']), 'data')
934
- sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
935
-
936
- for i, well_src in enumerate(sc_img_fldrs):
937
- if len(os.listdir(well_src)) < 16:
938
- nr_imgs = len(os.listdir(well_src))
939
- standardize = False
940
- else:
941
- nr_imgs = 16
942
- standardize = True
943
- try:
944
- all_folders = len(sc_img_fldrs)
945
- _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)
946
-
947
- except Exception as e:
948
- print(f"Unable to generate figure for folder {well_src}: {e}", end='\r', flush=True)
949
- #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()
950
1030
 
951
1031
  if settings['save_measurements']:
952
- if settings['representative_images']:
953
- db_path = os.path.join(os.path.dirname(settings['input_folder']), 'measurements', 'measurements.db')
954
- channel_indices = settings['png_dims']
955
- channel_indices = [min(value, 2) for value in channel_indices]
956
- _generate_representative_images(db_path,
957
- cells=settings['cells'],
958
- cell_loc=settings['cell_loc'],
959
- pathogens=settings['pathogens'],
960
- pathogen_loc=settings['pathogen_loc'],
961
- treatments=settings['treatments'],
962
- treatment_loc=settings['treatment_loc'],
963
- channel_of_interest=settings['channel_of_interest'],
964
- compartments = settings['compartments'],
965
- measurement = settings['measurement'],
966
- nr_imgs=settings['nr_imgs'],
967
- channel_indices=channel_indices,
968
- um_per_pixel=settings['um_per_pixel'],
969
- scale_bar_length_um=10,
970
- plot=False,
971
- fontsize=12,
972
- show_filename=True,
973
- 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)
974
1053
 
975
1054
  if settings['timelapse']:
976
1055
  if settings['timelapse_objects'] == 'nucleus':
@@ -979,11 +1058,11 @@ def measure_crop(settings, annotation_settings, advanced_settings):
979
1058
  object_types = ['nucleus','pathogen','cell']
980
1059
  _timelapse_masks_to_gif(folder_path, mask_channels, object_types)
981
1060
 
982
- if settings['save_png']:
1061
+ #if settings['save_png']:
983
1062
  img_fldr = os.path.join(os.path.dirname(settings['input_folder']), 'data')
984
1063
  sc_img_fldrs = _list_endpoint_subdirectories(img_fldr)
985
1064
  _scmovie(sc_img_fldrs)
986
-
1065
+ print("Successfully completed run")
987
1066
 
988
1067
  def generate_cellpose_train_set(folders, dst, min_objects=5):
989
1068
  os.makedirs(dst, exist_ok=True)
@@ -1012,3 +1091,18 @@ def generate_cellpose_train_set(folders, dst, min_objects=5):
1012
1091
  shutil.copy(img_path, new_img)
1013
1092
  except Exception as e:
1014
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