small-fish-gui 2.1.3__py3-none-any.whl → 2.1.5__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.
@@ -37,8 +37,8 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37
37
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38
38
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
39
  """
40
- __version__ = "2.1.3"
41
- __wiki__ = "https://github.com/2Echoes/small_fish_gui/wiki"
40
+ __version__ = "2.1.5"
41
+ __wiki__ = "https://github.com/SmallFishGUI/small_fish_gui/wiki"
42
42
 
43
43
  import os, platform
44
44
  system_type = platform.system()
@@ -0,0 +1,65 @@
1
+ List of keys for batch 'values' dict instance :
2
+
3
+ Batch_folder
4
+ 0
5
+ image_path
6
+ 3D stack
7
+ multichannel
8
+ Dense regions deconvolution
9
+ do_cluster_computation
10
+ Segmentation
11
+ Napari correction
12
+ x
13
+ y
14
+ z
15
+ c
16
+ t
17
+ cyto_model_name
18
+ cytoplasm_channel
19
+ cytoplasm_diameter
20
+ nucleus_model_name
21
+ nucleus channel
22
+ nucleus_diameter
23
+ segment_only_nuclei
24
+ show_segmentation
25
+ saving path
26
+ filename
27
+ threshold
28
+ threshold penalty
29
+ channel to compute
30
+ voxel_size_z
31
+ voxel_size_y
32
+ voxel_size_x
33
+ spot_size_z
34
+ spot_size_y
35
+ spot_size_x
36
+ log_kernel_size_z
37
+ log_kernel_size_y
38
+ log_kernel_size_x
39
+ minimum_distance_z
40
+ minimum_distance_y
41
+ minimum_distance_x
42
+ nucleus channel signal
43
+ alpha
44
+ beta
45
+ gamma
46
+ deconvolution_kernel_z
47
+ deconvolution_kernel_y
48
+ deconvolution_kernel_x
49
+ cluster size
50
+ min number of spots
51
+ show_interactive_threshold_selector
52
+ spots_extraction_folder
53
+ spots_filename
54
+ do_spots_csv
55
+ do_spots_excel
56
+ do_spots_feather
57
+ output_folder
58
+ batch_name
59
+ save segmentation
60
+ save detection
61
+ extract spots
62
+ csv
63
+ xlsx
64
+ feather
65
+ 2
@@ -19,6 +19,7 @@ from ..utils import compute_anisotropy_coef
19
19
  def correct_spots(
20
20
  image,
21
21
  spots,
22
+ segment_only_nuclei,
22
23
  voxel_size= (1,1,1),
23
24
  clusters= None,
24
25
  spot_cluster_id= None,
@@ -26,7 +27,7 @@ def correct_spots(
26
27
  min_spot_number=0,
27
28
  cell_label= None,
28
29
  nucleus_label= None,
29
- other_images =[]
30
+ other_images =[],
30
31
  ):
31
32
  """
32
33
  Open Napari viewer for user to visualize and corrects spots, clusters.
@@ -98,12 +99,32 @@ def correct_spots(
98
99
  feature_defaults= {"spot_number" : min_spot_number, "cluster_id" : -2, "end" : True} # napari features default will not work with np.nan passing -2 instead.
99
100
  )
100
101
 
101
- if type(cell_label) != type(None) and not np.array_equal(nucleus_label, cell_label) :
102
- cell_label_layer = Viewer.add_labels(cell_label, scale=scale, opacity= 0.2, blending= 'additive')
103
- if type(nucleus_label) != type(None) :
102
+ if type(nucleus_label) != type(None) :
104
103
  nucleus_label_layer = Viewer.add_labels(nucleus_label, scale=scale, opacity= 0.2, blending= 'additive')
104
+ nucleus_label_layer.preserve_labels = True
105
+ labels_layer_list = [nucleus_label_layer]
105
106
 
107
+ if type(cell_label) != type(None) and not segment_only_nuclei :
108
+ cell_label_layer = Viewer.add_labels(cell_label, scale=scale, opacity= 0.2, blending= 'additive')
109
+ cell_label_layer.preserve_labels = True
110
+ labels_layer_list += [cell_label_layer]
111
+
106
112
  #Adding widget
113
+ if type(nucleus_label) != type(None) :
114
+ label_reseter = SegmentationReseter(labels_layer_list)
115
+ label_eraser = CellLabelEraser(labels_layer_list)
116
+ label_picker = FreeLabelPicker(labels_layer_list)
117
+ label_reseter = SegmentationReseter(labels_layer_list)
118
+ changes_applier = ChangesPropagater(labels_layer_list)
119
+
120
+ buttons_container = widgets.Container(widgets=[label_picker.widget, label_reseter.widget], labels=False, layout='horizontal')
121
+ changes_applier = widgets.Container(widgets=[changes_applier.widget], labels=False, layout='horizontal')
122
+ seg_tools_container = widgets.Container(
123
+ widgets = [buttons_container, changes_applier, label_eraser.widget],
124
+ labels=False,
125
+ )
126
+ Viewer.window.add_dock_widget(seg_tools_container, name='Segmentation', area='left')
127
+
107
128
  if type(clusters) != type(None) :
108
129
  initialize_all_cluster_wizards(
109
130
  single_layer=single_layer,
@@ -131,7 +152,8 @@ def correct_spots(
131
152
  widgets = [updater_container, buttons_container],
132
153
  labels=False,
133
154
  )
134
- Viewer.window.add_dock_widget(tools_container, name='SmallFish', area='left')
155
+
156
+ Viewer.window.add_dock_widget(tools_container, name='Cluster', area='left', tabify=True)
135
157
 
136
158
  Viewer.show(block=False)
137
159
  napari.run()
@@ -158,8 +180,10 @@ def correct_spots(
158
180
  new_min_spot_number = None
159
181
 
160
182
  #Preparing updated segmentation masks
161
- if type(cell_label) != type(None) and not np.array_equal(nucleus_label, cell_label) :
183
+ if type(cell_label) != type(None) and not segment_only_nuclei :
162
184
  new_cell_label = cell_label_layer.data
185
+ elif type(nucleus_label) != type(None) and segment_only_nuclei :
186
+ new_cell_label = nucleus_label_layer.data
163
187
  else :
164
188
  new_cell_label = cell_label
165
189
  if type(nucleus_label) != type(None) :
@@ -1,5 +1,5 @@
1
1
  {
2
- "working_directory": "/media/SSD_floricslimani",
2
+ "working_directory": "/home/floric/Documents/fish_images",
3
3
  "do_background_removal": false,
4
4
  "background_channel": 0,
5
5
  "multichannel_stack": true,
@@ -32,7 +32,7 @@
32
32
  "do_dense_regions_deconvolution": false,
33
33
  "do_cluster": false,
34
34
  "show_napari_corrector": true,
35
- "interactive_threshold_selector": false,
35
+ "interactive_threshold_selector": true,
36
36
  "alpha": 0.5,
37
37
  "beta": 1.0,
38
38
  "gamma": 3.0,
@@ -41,7 +41,7 @@
41
41
  "coloc_range": 400,
42
42
  "do_csv": false,
43
43
  "do_excel": false,
44
- "spot_extraction_folder": "/home/floric",
44
+ "spot_extraction_folder": "/home/floric/my_projects/small_fish_gui",
45
45
  "voxel_size": [
46
46
  1,
47
47
  2,
@@ -228,7 +228,7 @@ def _global_coloc(acquisition_id1,acquisition_id2, result_dataframe, colocalisat
228
228
  - fraction_spot2_coloc_spots
229
229
 
230
230
  """
231
-
231
+ CLUSTER_KEY = "clustered_spots_coords"
232
232
  acquisition1 = result_dataframe.loc[result_dataframe['acquisition_id'] == acquisition_id1]
233
233
  acquisition2 = result_dataframe.loc[result_dataframe['acquisition_id'] == acquisition_id2]
234
234
 
@@ -259,7 +259,7 @@ def _global_coloc(acquisition_id1,acquisition_id2, result_dataframe, colocalisat
259
259
  fraction_spots1_coloc_spots2 = np.nan
260
260
  fraction_spots2_coloc_spots1 = np.nan
261
261
 
262
- if 'clusters' in acquisition1.columns :
262
+ if CLUSTER_KEY in acquisition1.columns :
263
263
  try :
264
264
  clusters_id_1 = np.array(acquisition1.iloc[0].at['spots_cluster_id'], dtype=int)
265
265
  fraction_spots2_coloc_cluster1 = spots_colocalisation(spot_list1=spots2, spot_list2=spots1[clusters_id_1 != -1], distance= colocalisation_distance, voxel_size=voxel_size) / spot2_total
@@ -272,7 +272,7 @@ def _global_coloc(acquisition_id1,acquisition_id2, result_dataframe, colocalisat
272
272
 
273
273
  else : fraction_spots2_coloc_cluster1 = np.nan
274
274
 
275
- if 'clusters' in acquisition2.columns :
275
+ if CLUSTER_KEY in acquisition2.columns :
276
276
  try :
277
277
  clusters_id_2 = np.array(acquisition2.iloc[0].at['spots_cluster_id'], dtype=int)
278
278
  fraction_spots1_coloc_cluster2 = spots_colocalisation(spot_list1=spots1, spot_list2=spots2[clusters_id_2 != -1], distance= colocalisation_distance, voxel_size=voxel_size) / spot1_total
@@ -285,7 +285,7 @@ def _global_coloc(acquisition_id1,acquisition_id2, result_dataframe, colocalisat
285
285
 
286
286
  else : fraction_spots1_coloc_cluster2 = np.nan
287
287
 
288
- if 'clusters' in acquisition2.columns and 'clusters' in acquisition1.columns :
288
+ if CLUSTER_KEY in acquisition2.columns and CLUSTER_KEY in acquisition1.columns :
289
289
  try :
290
290
  total_clustered_spots1 = len(spots1[clusters_id_1 != -1])
291
291
  total_clustered_spots2 = len(spots2[clusters_id_2 != -1])
@@ -340,6 +340,8 @@ def _cell_coloc(
340
340
 
341
341
  acquisition1 = result_dataframe.loc[result_dataframe['acquisition_id'] == acquisition_id1]
342
342
  acquisition2 = result_dataframe.loc[result_dataframe['acquisition_id'] == acquisition_id2]
343
+ has_clusters_1 = "clustered_spots_coords" in acquisition1.columns or "clusters" in acquisition1.columns
344
+ has_clusters_2 = "clustered_spots_coords" in acquisition2.columns or "clusters" in acquisition2.columns
343
345
 
344
346
  acquisition_name_id1 = acquisition1['name'].iat[0]
345
347
  acquisition_name_id2 = acquisition2['name'].iat[0]
@@ -357,15 +359,22 @@ def _cell_coloc(
357
359
 
358
360
  #Putting spots lists in 2 cols for corresponding cells
359
361
  pivot_values_columns = ['rna_coords', 'total_rna_number']
360
- if 'clusters' in acquisition2.columns or 'clusters' in acquisition1.columns:
362
+ if has_clusters_1 or has_clusters_2 :
361
363
  pivot_values_columns.extend(['clustered_spots_coords','clustered_spot_number'])
362
- print(cell_dataframe.loc[:,["clustered_spots_coords"]])
363
364
  cell_dataframe.loc[:,['cell_id']] = cell_dataframe['cell_id'].astype(int)
365
+
366
+ if has_clusters_1 or has_clusters_2 :
367
+ target_column = "clustered_spots_coords"
368
+ target_mask = cell_dataframe.loc[:, target_column].apply(len) == 0
369
+ cell_dataframe.loc[target_mask,target_column] = pd.Series([np.empty(shape=(0,3), dtype=int)]* sum(target_mask), dtype=object)
370
+
364
371
  colocalisation_df = cell_dataframe.pivot(
365
372
  columns=['name', 'acquisition_id'],
366
373
  values= pivot_values_columns,
367
374
  index= 'cell_id'
368
375
  )
376
+ colocalisation_df = colocalisation_df.dropna(axis=0)
377
+
369
378
  #spots _vs spots
370
379
  colocalisation_df[("spots_with_spots_count",coloc_name_forward,"forward")] = colocalisation_df['rna_coords'].apply(
371
380
  lambda x: spots_colocalisation(
@@ -387,10 +396,9 @@ def _cell_coloc(
387
396
  )
388
397
  colocalisation_df[("spots_with_spots_fraction",coloc_name_backward,"backward")] = colocalisation_df[("spots_with_spots_count",coloc_name_backward,"backward")].astype(float) / colocalisation_df[('total_rna_number',acquisition_name_id2,acquisition_id2)].astype(float)
389
398
 
390
- if 'clusters' in acquisition2.columns:
391
- if len(acquisition2['clusters'].iat[0]) > 0 or len(acquisition2['clustered_spots_coords'][acquisition2['clustered_spots_coords'] != -1]) > 0 :
392
-
393
- print("COLOCALISATION_DF\n", colocalisation_df.loc[:,('clustered_spots_coords',acquisition_name_id2,acquisition_id2)])
399
+ if has_clusters_2:
400
+ CLUSTER_KEY2 = "clustered_spots_coords" if "clustered_spots_coords" in acquisition2.columns else "clusters"
401
+ if len(acquisition2[CLUSTER_KEY2].iat[0]) > 0 :
394
402
 
395
403
  #spots to clusters
396
404
  colocalisation_df[("spots_with_clustered_spots_count",coloc_name_forward,"forward")] = colocalisation_df.apply(
@@ -403,8 +411,9 @@ def _cell_coloc(
403
411
  )
404
412
  colocalisation_df[("spots_with_clustered_spots_fraction",coloc_name_forward,"forward")] = colocalisation_df[("spots_with_clustered_spots_count",coloc_name_forward,"forward")].astype(float) / colocalisation_df[('total_rna_number',acquisition_name_id1,acquisition_id1)].astype(float)
405
413
 
406
- if 'clusters' in acquisition1.columns:
407
- if len(acquisition1['clusters'].iat[0]) > 0 :
414
+ if has_clusters_1:
415
+ CLUSTER_KEY1 = "clustered_spots_coords" if "clustered_spots_coords" in acquisition1.columns else "clusters"
416
+ if len(acquisition1[CLUSTER_KEY1].iat[0]) > 0 :
408
417
  colocalisation_df[("spots_with_clustered_spots_count",coloc_name_backward,"backward")] = colocalisation_df.apply(
409
418
  lambda x: spots_colocalisation(
410
419
  spot_list1= x[('rna_coords',acquisition_name_id2,acquisition_id2)],
@@ -416,8 +425,9 @@ def _cell_coloc(
416
425
 
417
426
  colocalisation_df[("spots_with_clustered_spots_fraction",coloc_name_backward,"backward")] = colocalisation_df[("spots_with_clustered_spots_count",coloc_name_backward,"backward")].astype(float) / colocalisation_df[('total_rna_number',acquisition_name_id2,acquisition_id2)].astype(float)
418
427
 
419
- if 'clusters' in acquisition2.columns and 'clusters' in acquisition1.columns:
420
- if len(acquisition1['clusters'].iat[0]) > 0 and len(acquisition2['clusters'].iat[0]) > 0 :
428
+ if has_clusters_1 and has_clusters_2:
429
+
430
+ if len(acquisition1[CLUSTER_KEY1].iat[0]) > 0 and len(acquisition2[CLUSTER_KEY2].iat[0]) > 0 :
421
431
  #clusters to clusters
422
432
  colocalisation_df[("clustered_spots_with_clustered_spots_count",coloc_name_forward,"forward")] = colocalisation_df.apply(
423
433
  lambda x: spots_colocalisation(
@@ -441,7 +451,7 @@ def _cell_coloc(
441
451
 
442
452
  colocalisation_df = colocalisation_df.sort_index(axis=0).sort_index(axis=1, level=0)
443
453
 
444
- if 'clustered_spots_coords' in cell_dataframe.columns : colocalisation_df = colocalisation_df.drop('clustered_spots_coords', axis=1)
454
+ if 'clustered_spots_coords' in colocalisation_df.columns : colocalisation_df = colocalisation_df.drop('clustered_spots_coords', axis=1)
445
455
  colocalisation_df = colocalisation_df.drop('rna_coords', axis=1)
446
456
  colocalisation_df['voxel_size'] = [voxel_size]*len(colocalisation_df)
447
457
  colocalisation_df['pair_name'] = [(acquisition_name_id1, acquisition_name_id2)] * len(colocalisation_df)
@@ -452,11 +462,19 @@ def _cell_coloc(
452
462
  return colocalisation_df
453
463
 
454
464
  @add_default_loading
455
- def launch_colocalisation(acquisition_id1, acquisition_id2, result_dataframe, cell_result_dataframe, colocalisation_distance, global_coloc_df, cell_coloc_df: dict) :
465
+ def launch_colocalisation(
466
+ acquisition_id1 : int,
467
+ acquisition_id2 : int,
468
+ result_dataframe : pd.DataFrame,
469
+ cell_result_dataframe : pd.DataFrame,
470
+ colocalisation_distance : pd.DataFrame,
471
+ global_coloc_df : pd.DataFrame,
472
+ cell_coloc_df: dict) :
456
473
 
457
474
 
458
475
  if acquisition_id1 in list(cell_result_dataframe['acquisition_id']) and acquisition_id2 in list(cell_result_dataframe['acquisition_id']) :
459
476
  print("Launching cell to cell colocalisation.")
477
+
460
478
  new_coloc = _cell_coloc(
461
479
  acquisition_id1 = acquisition_id1,
462
480
  acquisition_id2 = acquisition_id2,
@@ -213,23 +213,26 @@ def initiate_detection(user_parameters : pipeline_parameters, map_, shape) :
213
213
  #Attempt to read voxel size from metadata
214
214
  voxel_size = get_voxel_size(user_parameters['image_path'])
215
215
  if voxel_size is None :
216
- if not user_parameters.get('voxel_size') is None:
217
- pass
218
- else :
216
+ if user_parameters.get('voxel_size') is None:
219
217
  detection_parameters['voxel_size_z'] = None
220
218
  detection_parameters['voxel_size_y'] = None
221
219
  detection_parameters['voxel_size_x'] = None
220
+ else :
221
+ if is_3D_stack : detection_parameters['voxel_size_z'] = user_parameters.get['voxel_size'][0]
222
+ detection_parameters['voxel_size_y'] = user_parameters.get['voxel_size'][0 + is_3D_stack]
223
+ detection_parameters['voxel_size_x'] = user_parameters.get['voxel_size'][1 + is_3D_stack]
224
+
222
225
  else :
223
226
  detection_parameters['voxel_size'] = [round(v) if isinstance(v, (float,int)) else None for v in voxel_size]
224
- detection_parameters['voxel_size_z'] = detection_parameters['voxel_size'][0] if isinstance(detection_parameters['voxel_size'][0], (float,int)) else None
225
- detection_parameters['voxel_size_y'] = detection_parameters['voxel_size'][1] if isinstance(detection_parameters['voxel_size'][1], (float,int)) else None
226
- detection_parameters['voxel_size_x'] = detection_parameters['voxel_size'][2] if isinstance(detection_parameters['voxel_size'][2], (float,int)) else None
227
+ detection_parameters['voxel_size_z'] = detection_parameters['voxel_size'][0] if isinstance(detection_parameters.get('voxel_size')[0], (float,int)) else None
228
+ detection_parameters['voxel_size_y'] = detection_parameters['voxel_size'][0 + is_3D_stack] if isinstance(detection_parameters.get('voxel_size')[0 + is_3D_stack], (float,int)) else None
229
+ detection_parameters['voxel_size_x'] = detection_parameters['voxel_size'][1 + is_3D_stack] if isinstance(detection_parameters.get('voxel_size')[1 + is_3D_stack], (float,int)) else None
227
230
 
228
231
  #Setting default spot size to 1.5 voxel
229
232
  if detection_parameters.get('spot_size') is None and not detection_parameters.get('voxel_size') is None:
230
233
  detection_parameters['spot_size_z'] = round(detection_parameters['voxel_size_z']*1.5) if isinstance(detection_parameters.get('voxel_size_z'), (float,int)) else None
231
- detection_parameters['spot_size_y'] = round(detection_parameters['voxel_size_y']*1.5) if isinstance(detection_parameters['voxel_size_y'],(float,int)) else None
232
- detection_parameters['spot_size_x'] = round(detection_parameters['voxel_size_x']*1.5) if isinstance(detection_parameters['voxel_size_x'],(float,int)) else None
234
+ detection_parameters['spot_size_y'] = round(detection_parameters['voxel_size_y']*1.5) if isinstance(detection_parameters.get('voxel_size_y'),(float,int)) else None
235
+ detection_parameters['spot_size_x'] = round(detection_parameters['voxel_size_x']*1.5) if isinstance(detection_parameters.get('voxel_size_x'),(float,int)) else None
233
236
 
234
237
  while True :
235
238
  detection_parameters = detection_parameters_promt(
@@ -659,18 +662,19 @@ def launch_detection(
659
662
  spots_cluster_id = None
660
663
 
661
664
  if user_parameters['show_napari_corrector'] :
662
-
665
+
663
666
  spots, clusters, new_cluster_radius, new_min_spot_number, nucleus_label, cell_label = correct_spots(
664
667
  image,
665
668
  spots,
666
- user_parameters['voxel_size'],
669
+ voxel_size=user_parameters['voxel_size'],
667
670
  clusters=clusters,
668
671
  spot_cluster_id = spots_cluster_id,
669
672
  cluster_size= user_parameters.get('cluster_size'),
670
673
  min_spot_number= user_parameters.setdefault('min_number_of_spots', 0),
671
674
  cell_label=cell_label,
672
675
  nucleus_label=nucleus_label,
673
- other_images=other_image
676
+ other_images=other_image,
677
+ segment_only_nuclei=user_parameters['segment_only_nuclei'],
674
678
  )
675
679
 
676
680
  if type(new_cluster_radius) != type(None) :
@@ -60,6 +60,17 @@ def compute_Spots(
60
60
  else :
61
61
  in_nuc_list = np.nan
62
62
  if type(cell_label) != type(None) :
63
+
64
+ # Collect all labels that are on fov edge
65
+ on_edge_labels = np.unique(
66
+ np.concatenate([
67
+ cell_label[:,0],
68
+ cell_label[:,-1],
69
+ cell_label[0,:],
70
+ cell_label[-1,:],
71
+ ])
72
+ ).astype(int)
73
+
63
74
  if cell_label.ndim == 3 :
64
75
  cell_label_list = list(cell_label[index])
65
76
  else :
@@ -79,6 +90,10 @@ def compute_Spots(
79
90
  'coordinates' : coord_list,
80
91
  'cluster_id' : cluster_id,
81
92
  })
93
+
94
+ if type(cell_label) != type(None) : #Filter on edge cells
95
+ target_index = Spots.loc[Spots["cell_label"].isin(on_edge_labels)].index
96
+ Spots.loc[target_index,["cell_label"]] = 0
82
97
 
83
98
  return Spots
84
99
 
@@ -126,7 +141,6 @@ def reconstruct_acquisition_data(
126
141
  clusters = np.empty(shape=(0,5), dtype=int)
127
142
  spot_cluster_id = Spots['cluster_id'].to_numpy().astype(int)
128
143
  clustered_spots = spots[spot_cluster_id != -1]
129
- print("clustered_spots : ", clustered_spots)
130
144
 
131
145
  new_acquisition = pd.DataFrame({
132
146
  'acquisition_id' : [max_id + 1],
@@ -151,10 +165,6 @@ def reconstruct_acquisition_data(
151
165
  'voxel_size' : [voxel_size],
152
166
  })
153
167
 
154
- print("Reconstructed acquisition : \n", new_acquisition)
155
- print("\n", new_acquisition.columns)
156
- if 'clusters' in new_acquisition.columns :
157
- print("\n", new_acquisition['clusters'])
158
168
 
159
169
  return new_acquisition
160
170
 
@@ -176,23 +186,25 @@ def reconstruct_cell_data(
176
186
  ) :
177
187
 
178
188
  has_cluster = not Spots['cluster_id'].isna().all()
189
+ if 'cell_label' in Spots.columns : Spots = Spots.loc[Spots["cell_label"] !=0]
179
190
  coordinates = reconstruct_spots(Spots['coordinates'])
180
- Spots['coordinates'] = pd.Series(coordinates.tolist(), dtype=object, index= Spots.index)
191
+ Spots.loc[:,['coordinates']] = pd.Series(coordinates.tolist(), dtype=object, index= Spots.index)
181
192
 
182
193
  cell = Spots.groupby('cell_label')['coordinates'].apply(np.array).rename("rna_coords").reset_index(drop=False)
183
194
 
184
195
  #Handle cells with no spots
185
196
  na_mask =cell[cell['rna_coords'].isna()].index
186
197
  cell.loc[na_mask, ['rna_coords']] = pd.Series([np.empty(shape=(0,3))]*len(na_mask), dtype= object, index=na_mask)
198
+ cell.loc[~na_mask, "rna_coords"] = cell.loc[~na_mask, "rna_coords"].apply(list).apply(np.array)
187
199
 
188
200
  cell['total_rna_number'] = cell['rna_coords'].apply(len)
189
-
190
201
  if has_cluster :
191
202
  cell['clustered_spots_coords'] = Spots[Spots['cluster_id'] !=-1].groupby('cell_label')['coordinates'].apply(np.array).rename("clustered_spots_coords")
192
203
 
193
204
  #Handle cells with no clusters
194
205
  na_mask =cell[cell['clustered_spots_coords'].isna()].index
195
206
  cell.loc[na_mask, ['clustered_spots_coords']] = pd.Series([np.empty(shape=(0,3))]*len(na_mask), dtype= object, index=na_mask)
207
+ cell["clustered_spots_coords"] = cell["clustered_spots_coords"].apply(list).apply(np.array)
196
208
 
197
209
  cell['clustered_spot_number'] = cell['clustered_spots_coords'].apply(len)
198
210