@@ -49,65 +49,31 @@ def count_spots(self, database, threshold=None):
4949 Returns:
5050 polars.DataFrame: DataFrame with number of spots per z-plane.
5151 """
52- data = None
53- results = None
52+ if database is None or database . is_empty ():
53+ return None
5454 if threshold is None :
55- columns = ["n_spots" , "z" , "image_filename" ]
55+ data = (
56+ database .group_by (["image_filename" , "z" ])
57+ .agg (pl .len ().alias ("n_spots" ))
58+ .sort (["image_filename" , "z" ])
59+ .select (["n_spots" , "z" , "image_filename" ])
60+ )
61+ return H_F .clean_database (data , ["n_spots" , "z" , "image_filename" ])
5662 else :
57- columns = ["n_spots_above" , "n_spots_below" , "z" , "image_filename" ]
58- for image_filename in np .sort (np .unique (database ["image_filename" ])):
59- dataslice = database .filter (pl .col ("image_filename" ) == image_filename )
60- z_planes = np .sort (np .unique (dataslice ["z" ].to_numpy ()))
61- if threshold is None :
62- spots_per_plane = [
63- len (dataslice .filter (pl .col ("z" ) == z )["sum_intensity_in_photons" ])
64- for z in z_planes
65- ]
66- else :
67- spots_above = [
68- np .sum (
69- dataslice .filter (pl .col ("z" ) == z )[
70- "sum_intensity_in_photons"
71- ].to_numpy ()
72- > threshold
73- )
74- for z in z_planes
75- ]
76- spots_below = [
77- np .sum (
78- dataslice .filter (pl .col ("z" ) == z )[
79- "sum_intensity_in_photons"
80- ].to_numpy ()
81- <= threshold
82- )
83- for z in z_planes
84- ]
85- spots_per_plane = np .vstack ([spots_above , spots_below ])
86- if results is not None :
87- results = np .hstack (
88- [
89- results ,
90- np .vstack (
91- [
92- spots_per_plane ,
93- z_planes ,
94- np .full (len (z_planes ), image_filename ),
95- ]
96- ),
97- ]
98- )
99- else :
100- results = np .vstack (
101- [spots_per_plane , z_planes , np .full (len (z_planes ), image_filename )]
102- )
103- if results is not None :
104- spot_save = {}
105- for i , column in enumerate (columns ):
106- spot_save [column ] = results [i , :]
107- data = H_F .clean_database (pl .DataFrame (data = spot_save ), columns )
108- else :
109- data = None
110- return data
63+ data = (
64+ database .group_by (["image_filename" , "z" ])
65+ .agg ([
66+ (pl .col ("sum_intensity_in_photons" ) > threshold )
67+ .sum ().alias ("n_spots_above" ),
68+ (pl .col ("sum_intensity_in_photons" ) <= threshold )
69+ .sum ().alias ("n_spots_below" ),
70+ ])
71+ .sort (["image_filename" , "z" ])
72+ .select (["n_spots_above" , "n_spots_below" , "z" , "image_filename" ])
73+ )
74+ return H_F .clean_database (
75+ data , ["n_spots_above" , "n_spots_below" , "z" , "image_filename" ]
76+ )
11177
11278 def generate_indices (self , data , image_size , is_mask = False , is_lo = False ):
11379 """
@@ -893,6 +859,7 @@ def number_of_puncta_per_segmented_cell_with_threshold(
893859 sigma1 = 2.0 ,
894860 sigma2 = 40.0 ,
895861 spacing = (0.5 , 0.11 , 0.11 ),
862+ collect_oligomers = False ,
896863 ):
897864 """
898865 Does analysis of number of oligomers in a mask area per "segmented" cell area.
@@ -943,6 +910,7 @@ def number_of_puncta_per_segmented_cell_with_threshold(
943910
944911 files = np .unique (analysis_data ["image_filename" ].to_numpy ())
945912 cell_punctum_analysis = None
913+ oligomers_df = None
946914 start = time .time ()
947915 analysis_directory = os .path .split (analysis_file )[0 ]
948916
@@ -1080,6 +1048,19 @@ def number_of_puncta_per_segmented_cell_with_threshold(
10801048 )
10811049 filename_tosave = np .full_like (centroids [:, 0 ], file , dtype = "object" )
10821050
1051+ # Collect per-spot arrays (bounds-filtered) for oligomers-per-cell output
1052+ if collect_oligomers and dims == 3 :
1053+ all_spot_flat = np .ravel_multi_index (
1054+ np .vstack ([z .astype (int ), x .astype (int ), y .astype (int )]),
1055+ image_size ,
1056+ order = "F" ,
1057+ )
1058+ sum_photons_b = subset ["sum_intensity_in_photons" ].to_numpy ()[bounds ]
1059+ bg_punctum_b = subset ["bg_per_punctum" ].to_numpy ()[bounds ]
1060+ bg_pixel_b = subset ["bg_per_pixel" ].to_numpy ()[bounds ]
1061+ zi_b = subset ["zi" ].to_numpy ()[bounds ]
1062+ zf_b = subset ["zf" ].to_numpy ()[bounds ]
1063+
10831064 def areaanalysis (coords ):
10841065 if coords .shape [- 1 ] == 2 :
10851066 xm , ym = coords [:, 0 ], coords [:, 1 ]
@@ -1115,6 +1096,47 @@ def areaanalysis(coords):
11151096 n_cell_ratios = np .array ([r [0 ] for r in results ])
11161097 n_spots_in_object = np .array ([r [1 ] for r in results ])
11171098
1099+ # Build cell→oligomer mapping via a label map over the image
1100+ if collect_oligomers and dims == 3 and len (pil_mask ) > 0 :
1101+ n_pixels_total = int (np .prod (image_size ))
1102+ cell_label_map = np .zeros (n_pixels_total , dtype = np .int32 )
1103+ for j_cell , coords in enumerate (pil_mask ):
1104+ c = coords .astype (int )
1105+ if (
1106+ np .any (c [:, 0 ] >= z_lim )
1107+ or np .any (c [:, 1 ] >= x_lim )
1108+ or np .any (c [:, 2 ] >= y_lim )
1109+ ):
1110+ continue
1111+ flat_px = np .ravel_multi_index (c .T , image_size , order = "F" )
1112+ cell_label_map [flat_px ] = j_cell + 1 # 1-based label
1113+
1114+ spot_cell_ids = cell_label_map [all_spot_flat ]
1115+ del cell_label_map
1116+ in_any_cell = spot_cell_ids > 0
1117+
1118+ if in_any_cell .any ():
1119+ n_in = int (in_any_cell .sum ())
1120+ img_oligo_df = pl .DataFrame (
1121+ {
1122+ "z" : z [in_any_cell ].astype ("float64" ),
1123+ "x" : x [in_any_cell ].astype ("float64" ),
1124+ "y" : y [in_any_cell ].astype ("float64" ),
1125+ "sum_intensity_in_photons" : sum_photons_b [in_any_cell ],
1126+ "bg_per_punctum" : bg_punctum_b [in_any_cell ],
1127+ "bg_per_pixel" : bg_pixel_b [in_any_cell ],
1128+ "zi" : zi_b [in_any_cell ],
1129+ "zf" : zf_b [in_any_cell ],
1130+ "image_filename" : [file ] * n_in ,
1131+ "cell_id_in_image" : spot_cell_ids [in_any_cell ].tolist (),
1132+ }
1133+ )
1134+ oligomers_df = (
1135+ pl .concat ([oligomers_df , img_oligo_df ])
1136+ if oligomers_df is not None
1137+ else img_oligo_df
1138+ )
1139+
11181140 if len (areas ) > 0 :
11191141 if dims == 2 :
11201142 data = {
@@ -1127,6 +1149,7 @@ def areaanalysis(coords):
11271149 else "cell_protein_load"
11281150 ): n_cell_ratios ,
11291151 "n_puncta_in_cell" : n_spots_in_object ,
1152+ "cell_id_in_image" : np .arange (1 , len (areas ) + 1 , dtype = np .int32 ),
11301153 "image_filename" : filename_tosave ,
11311154 }
11321155 else :
@@ -1141,6 +1164,7 @@ def areaanalysis(coords):
11411164 else "cell_protein_load"
11421165 ): n_cell_ratios ,
11431166 "n_puncta_in_cell" : n_spots_in_object ,
1167+ "cell_id_in_image" : np .arange (1 , len (areas ) + 1 , dtype = np .int32 ),
11441168 "image_filename" : filename_tosave ,
11451169 }
11461170
@@ -1167,8 +1191,9 @@ def areaanalysis(coords):
11671191
11681192 if cell_punctum_analysis is None :
11691193 print ("WARNING: No cells with valid areas found in any files." )
1170- return None
1171- return cell_punctum_analysis .rechunk ()
1194+ return (None , None ) if collect_oligomers else None
1195+ result = cell_punctum_analysis .rechunk ()
1196+ return (result , oligomers_df ) if collect_oligomers else result
11721197
11731198 def colocalise_spots_with_threshold (
11741199 self ,
0 commit comments