microlive 1.0.2__py2.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.
@@ -0,0 +1,489 @@
1
+ """Pipeline module for MicroLive.
2
+
3
+ This module is part of the microlive package.
4
+ """
5
+ from microlive.imports import *
6
+
7
+ def pipeline_particle_tracking(data_folder_path, selected_image, channels_spots, max_spots_for_threshold=100000,
8
+ show_plot=True, channels_cytosol=None, channels_nucleus=None,memory=1,
9
+ min_length_trajectory=5, yx_spot_size_in_px=5, z_spot_size_in_px=2, maximum_spots_cluster=4,cluster_radius_nm =500,
10
+ MINIMAL_SNR=0.5, diameter_cytosol=300, diameter_nucleus=200,
11
+ segmentation_selection_metric='max_area',recalculate_mask=False,optimization_segmentation_method='diameter_segmentation',
12
+ pretrained_model_cyto_segmentation=None,use_watershed=False,list_images_to_process=None,save_3d_visualization=False,
13
+ max_percentage_empty_data_in_trajectory=0.1,particle_detection_threshold=None,save_croparray=False,
14
+ apply_photobleaching_correction=False,photobleaching_mode='inside_cell',use_maximum_projection=False,
15
+ max_lag_for_MSD=30,step_size_in_sec=1,separate_clusters_and_spots=False,maximum_range_search_pixels=10,results_folder_path=None,
16
+ calculate_MSD=True,calculate_correlations=True):
17
+ # Read images and metadata
18
+ # detect if the data is a lif file
19
+ list_images, list_names, pixel_xy_um, voxel_z_um, channel_names, number_color_channels, list_time_intervals, bit_depth = \
20
+ mi.ReadLif(data_folder_path, show_metadata=False, save_tif=False, save_png=False, format='TZYXC').read()
21
+ # Prepare full list of images and set up indices to process
22
+ list_images_complete = list_images.copy()
23
+ if list_images_to_process is not None:
24
+ selected_indices = [i for i in range(len(list_names)) if list_names[i] in list_images_to_process]
25
+ if use_maximum_projection:
26
+ # use the maximum projection in Z. but keep the image shape with only one Z slice
27
+ list_images = [np.max(list_images[i], axis=1, keepdims=True) for i in selected_indices]
28
+ else:
29
+ list_images = [list_images[i] for i in selected_indices]
30
+ else:
31
+ selected_indices = range(len(list_names))
32
+ if use_maximum_projection:
33
+ list_images = [np.max(img, axis=1, keepdims=True) for img in list_images]
34
+
35
+
36
+
37
+ # If selected_image is None, process all images
38
+ if selected_image is None:
39
+ list_df, list_masks, list_images_tested, list_diffusion_coefficient = [], [], [], []
40
+
41
+ #for idx in range(len(list_images)):
42
+ for idx in range(len(list_images_complete)):
43
+ if idx not in selected_indices:
44
+ continue
45
+ df, masks,image,diffusion_coefficient = process_single_image(
46
+ data_folder_path=data_folder_path,
47
+ selected_image=idx,
48
+ channels_spots=channels_spots,
49
+ max_spots_for_threshold=max_spots_for_threshold,
50
+ show_plot=show_plot,
51
+ channels_cytosol=channels_cytosol,
52
+ channels_nucleus=channels_nucleus,
53
+ min_length_trajectory=min_length_trajectory,
54
+ yx_spot_size_in_px=yx_spot_size_in_px,
55
+ z_spot_size_in_px = z_spot_size_in_px,
56
+ maximum_spots_cluster=maximum_spots_cluster,
57
+ cluster_radius_nm=cluster_radius_nm,
58
+ MINIMAL_SNR=MINIMAL_SNR,
59
+ diameter_cytosol=diameter_cytosol,
60
+ diameter_nucleus=diameter_nucleus,
61
+ segmentation_selection_metric=segmentation_selection_metric,
62
+ list_images=list_images_complete,
63
+ list_names=list_names,
64
+ pixel_xy_um=pixel_xy_um,
65
+ voxel_z_um=voxel_z_um,
66
+ channel_names=channel_names,
67
+ image_time_interval=list_time_intervals[idx],
68
+ recalculate_mask=recalculate_mask,
69
+ optimization_segmentation_method=optimization_segmentation_method,
70
+ pretrained_model_cyto_segmentation=pretrained_model_cyto_segmentation,
71
+ use_watershed=use_watershed,
72
+ save_3d_visualization=save_3d_visualization,
73
+ apply_photobleaching_correction=apply_photobleaching_correction,
74
+ photobleaching_mode=photobleaching_mode,
75
+ use_maximum_projection=use_maximum_projection,
76
+ max_lag_for_MSD = max_lag_for_MSD,
77
+ step_size_in_sec=step_size_in_sec,
78
+ separate_clusters_and_spots=separate_clusters_and_spots,
79
+ maximum_range_search_pixels=maximum_range_search_pixels,
80
+ max_percentage_empty_data_in_trajectory=max_percentage_empty_data_in_trajectory,
81
+ memory=memory,
82
+ particle_detection_threshold=particle_detection_threshold,
83
+ results_folder_path=results_folder_path,
84
+ calculate_MSD=calculate_MSD,
85
+ calculate_correlations=calculate_correlations,
86
+ save_croparray=save_croparray,
87
+ )
88
+ #if df is None:
89
+ # if the df is None or empty, continue to the next image
90
+ if df.empty:
91
+ continue
92
+ # rename the field image_id to idx
93
+ df['image_id'] = idx
94
+ list_df.append(df)
95
+ list_masks.append(masks)
96
+ list_images_tested.append(image)
97
+ list_diffusion_coefficient.append(diffusion_coefficient)
98
+
99
+ if len(list_df) >1 :
100
+ final_df = pd.concat(list_df, ignore_index=True)
101
+ else:
102
+ final_df = df
103
+ return final_df, list_df, list_masks, list_images_tested, list_diffusion_coefficient
104
+ else:
105
+ # Process single image
106
+ df,masks,image, diffusion_coefficient= process_single_image(
107
+ data_folder_path=data_folder_path,
108
+ selected_image=selected_image,
109
+ channels_spots=channels_spots,
110
+ max_spots_for_threshold=max_spots_for_threshold,
111
+ show_plot=show_plot,
112
+ channels_cytosol=channels_cytosol,
113
+ channels_nucleus=channels_nucleus,
114
+ min_length_trajectory=min_length_trajectory,
115
+ yx_spot_size_in_px=yx_spot_size_in_px,
116
+ z_spot_size_in_px = z_spot_size_in_px,
117
+ maximum_spots_cluster=maximum_spots_cluster,
118
+ cluster_radius_nm=cluster_radius_nm,
119
+ MINIMAL_SNR=MINIMAL_SNR,
120
+ diameter_cytosol=diameter_cytosol,
121
+ diameter_nucleus=diameter_nucleus,
122
+ segmentation_selection_metric=segmentation_selection_metric,
123
+ list_images=list_images,
124
+ list_names=list_names[idx],
125
+ pixel_xy_um=pixel_xy_um,
126
+ voxel_z_um=voxel_z_um,
127
+ channel_names=channel_names,
128
+ image_time_interval = list_time_intervals[selected_image],
129
+ recalculate_mask=recalculate_mask,
130
+ optimization_segmentation_method=optimization_segmentation_method,
131
+ pretrained_model_cyto_segmentation=pretrained_model_cyto_segmentation,
132
+ use_watershed=use_watershed,
133
+ save_3d_visualization=save_3d_visualization,
134
+ apply_photobleaching_correction=apply_photobleaching_correction,
135
+ photobleaching_mode=photobleaching_mode,
136
+ use_maximum_projection=use_maximum_projection,
137
+ max_lag_for_MSD = max_lag_for_MSD,
138
+ step_size_in_sec=step_size_in_sec,
139
+ separate_clusters_and_spots=separate_clusters_and_spots,
140
+ maximum_range_search_pixels=maximum_range_search_pixels,
141
+ max_percentage_empty_data_in_trajectory=max_percentage_empty_data_in_trajectory,
142
+ memory=memory,
143
+ particle_detection_threshold=particle_detection_threshold,
144
+ results_folder_path=results_folder_path,
145
+ calculate_MSD=calculate_MSD,
146
+ calculate_correlations=calculate_correlations,
147
+ save_croparray=save_croparray,
148
+ )
149
+ return df, [df], [masks], [image], [diffusion_coefficient]
150
+
151
+
152
+
153
+
154
+
155
+
156
+ @mi.Utilities().metadata_decorator(metadata_folder_func=mi.Utilities().get_metadata_folder,exclude_args=['list_images',]) # exclude_args=['list_images', 'list_names' ]
157
+ def process_single_image(data_folder_path, selected_image, channels_spots, max_spots_for_threshold=100000,
158
+ show_plot=True, channels_cytosol=None, channels_nucleus=None,memory=1,
159
+ min_length_trajectory=5, yx_spot_size_in_px=5, z_spot_size_in_px=2 , maximum_spots_cluster=4,cluster_radius_nm=500,
160
+ MINIMAL_SNR=0.5, diameter_cytosol=300, diameter_nucleus=200, segmentation_selection_metric='area',
161
+ list_images=None, list_names=None, pixel_xy_um=None, voxel_z_um=None,
162
+ channel_names=None, optimization_segmentation_method='diameter_segmentation',
163
+ recalculate_mask=False,use_watershed=False, pretrained_model_cyto_segmentation=None,particle_detection_threshold=None,save_croparray=False,
164
+ image_time_interval=None,save_3d_visualization=False,apply_photobleaching_correction=False,photobleaching_mode='inside_cell',max_percentage_empty_data_in_trajectory=0.1,
165
+ use_maximum_projection=False,max_lag_for_MSD=30,step_size_in_sec=1,separate_clusters_and_spots=False,maximum_range_search_pixels=10,results_folder_path=None,
166
+ calculate_MSD=True,calculate_correlations=True):
167
+ # Ensure lists are properly formatted
168
+ channels_spots = [channels_spots] if not isinstance(channels_spots, list) else channels_spots
169
+ channels_cytosol = [channels_cytosol] if not isinstance(channels_cytosol, list) else channels_cytosol
170
+ channels_nucleus = [channels_nucleus] if not isinstance(channels_nucleus, list) else channels_nucleus
171
+
172
+ # Convert pixel and voxel sizes to nm
173
+ pixel_xy_nm = int(pixel_xy_um * 1000)
174
+ voxel_z_nm = int(voxel_z_um * 1000)
175
+ list_voxels = [voxel_z_nm, pixel_xy_nm]
176
+ list_spot_size_px = [z_spot_size_in_px, yx_spot_size_in_px]
177
+ # print a line
178
+ print('--------------------------------------------------')
179
+ print(f'Processing image: {list_names[selected_image]}')
180
+ tested_image = list_images[selected_image] # TZYXC
181
+ original_tested_image = tested_image.copy()
182
+ # Creating the results folder
183
+ results_name = 'results_' + data_folder_path.stem + '_cell_id_' + str(selected_image)
184
+ current_dir = pathlib.Path().absolute()
185
+
186
+
187
+ if results_folder_path is not None:
188
+ # ensure that results_folder_path is a Path object
189
+ if not isinstance(results_folder_path, pathlib.Path):
190
+ results_folder_path = pathlib.Path(results_folder_path)
191
+ results_folder = results_folder_path.joinpath(results_name)
192
+ else:
193
+ results_folder = current_dir.joinpath('results_live_cell', results_name)
194
+ results_folder.mkdir(parents=True, exist_ok=True)
195
+ mi.Utilities().clear_folder_except_substring(results_folder, 'mask')
196
+ # Plot the original image
197
+ plot_name_original = results_folder.joinpath('original_image.png')
198
+ suptitle=f'Image: {data_folder_path.stem[:16]} - {list_names[selected_image]} - Cell_ID: {selected_image}'
199
+
200
+
201
+ mi.Plots().plot_images(
202
+ image_ZYXC=tested_image[0],
203
+ figsize=(12, 5),
204
+ show_plot=show_plot,
205
+ use_maximum_projection=True,
206
+ use_gaussian_filter=True,
207
+ cmap='binary',
208
+ min_max_percentile=[0.5, 99.9],
209
+ show_gird=False,
210
+ save_plots=True,
211
+ plot_name=plot_name_original,
212
+ suptitle=suptitle
213
+ )
214
+ # Read or create masks
215
+ mask_file_name = 'mask_' + data_folder_path.stem + '_image_' + str(selected_image) + '.tif'
216
+ mask_file_path = results_folder.joinpath(mask_file_name)
217
+ path_mask_exist = os.path.exists(str(mask_file_path))
218
+ if path_mask_exist and recalculate_mask is False:
219
+ masks = imread(str(mask_file_path)).astype(bool)
220
+ else:
221
+ # Use Cellpose to create masks
222
+ if use_watershed:
223
+ masks_complete_cells = mi.CellSegmentationWatershed(np.max(tested_image[:,:,:,:,channels_cytosol[0]],
224
+ axis=(0,1)), footprint_size=2, ).apply_watershed()
225
+ else:
226
+ masks_complete_cells, _, _ = mi.CellSegmentation(
227
+ tested_image[0],
228
+ channels_cytosol=channels_cytosol,
229
+ channels_nucleus=channels_nucleus,
230
+ diameter_cytosol=diameter_cytosol,
231
+ diameter_nucleus=diameter_nucleus,
232
+ optimization_segmentation_method=optimization_segmentation_method,
233
+ remove_fragmented_cells=False,
234
+ show_plot=show_plot,
235
+ image_name=None,
236
+ NUMBER_OF_CORES=1,
237
+ selection_metric=segmentation_selection_metric,
238
+ pretrained_model_cyto_segmentation = pretrained_model_cyto_segmentation
239
+ ).calculate_masks()
240
+ # Selecting the mask that is in the center of the image
241
+ center_y = masks_complete_cells.shape[0] // 2
242
+ center_x = masks_complete_cells.shape[1] // 2
243
+ selected_mask_id = masks_complete_cells[center_y, center_x]
244
+ if selected_mask_id > 0:
245
+ masks = masks_complete_cells == selected_mask_id
246
+ else:
247
+ # Select the largest mask that is not the background mask (0)
248
+ mask_labels = np.unique(masks_complete_cells)
249
+ mask_sizes = [(label, np.sum(masks_complete_cells == label)) for label in mask_labels if label != 0]
250
+ if mask_sizes:
251
+ selected_mask_id = max(mask_sizes, key=lambda x: x[1])[0]
252
+ masks = masks_complete_cells == selected_mask_id
253
+ else:
254
+ masks = np.zeros_like(masks_complete_cells, dtype=bool)
255
+ # Save the mask
256
+ masks = masks.astype(np.uint8)
257
+ tifffile.imwrite(str(mask_file_path), masks, dtype='uint8')
258
+
259
+ if apply_photobleaching_correction:
260
+ file_path_photobleacing = results_folder.joinpath('photobleaching.png')
261
+ corrected_image = mi.Photobleaching(image_TZYXC=tested_image,mask_YX=masks, show_plot=False, mode= photobleaching_mode,plot_name=file_path_photobleacing).apply_photobleaching_correction() #mi.PhotobleachingCorrection(tested_image).apply_correction()
262
+
263
+ else:
264
+ corrected_image = tested_image
265
+ # Calculate the threshold for spot detection
266
+ plot_name_threshold = results_folder.joinpath('threshold_spot_detection.png')
267
+
268
+ if particle_detection_threshold is None:
269
+ starting_threshold = mi.Utilities().calculate_threshold_for_spot_detection(
270
+ corrected_image, list_spot_size_px, list_voxels, channels_spots,
271
+ max_spots_for_threshold=max_spots_for_threshold,
272
+ show_plot=True,plot_name=plot_name_threshold
273
+ )
274
+ else:
275
+ starting_threshold = [particle_detection_threshold]*len(channels_spots)
276
+
277
+ # Run the particle tracking
278
+ try:
279
+ list_dataframes_trajectories, _ = mi.ParticleTracking(
280
+ image=corrected_image,
281
+ channels_spots=channels_spots,
282
+ masks=masks,
283
+ list_voxels=list_voxels,
284
+ memory=memory,
285
+ channels_cytosol=channels_cytosol,
286
+ channels_nucleus=channels_nucleus,
287
+ min_length_trajectory=min_length_trajectory,
288
+ threshold_for_spot_detection=starting_threshold,
289
+ yx_spot_size_in_px=yx_spot_size_in_px,
290
+ z_spot_size_in_px=z_spot_size_in_px,
291
+ maximum_spots_cluster=maximum_spots_cluster,
292
+ cluster_radius_nm = cluster_radius_nm,
293
+ separate_clusters_and_spots=separate_clusters_and_spots,
294
+ maximum_range_search_pixels=maximum_range_search_pixels,
295
+ ).run()
296
+ except Exception as e:
297
+ print(f'Error: {e}')
298
+ return pd.DataFrame(), masks, original_tested_image, None
299
+ #df_tracking = list_dataframes_trajectories[0]
300
+ df_tracking = list_dataframes_trajectories[0]
301
+
302
+ if len(df_tracking)==0:
303
+ return pd.DataFrame(), masks, original_tested_image, None
304
+
305
+
306
+ if len(list_dataframes_trajectories) > 1:
307
+ for i in range(1, len(list_dataframes_trajectories)):
308
+ df_tracking = pd.concat([df_tracking, list_dataframes_trajectories[i]], ignore_index=True)
309
+ df_tracking = df_tracking.reset_index(drop=True)
310
+ #print(df_tracking)
311
+ # Plot histograms for the SNR
312
+ selected_field = 'snr' # options are: psf_sigma, snr, 'spot_int'
313
+ plot_name_snr = results_folder.joinpath('spots_' + selected_field + '.png')
314
+ mean_snr = mi.Plots().plot_histograms_from_df(
315
+ df_tracking,
316
+ selected_field=selected_field,
317
+ figsize=(8, 2),
318
+ plot_name=plot_name_snr,
319
+ bin_count=60,
320
+ save_plot=True,
321
+ list_colors=channel_names,
322
+ remove_outliers=True
323
+ )
324
+ # Plot histograms for the spot intensity
325
+ selected_field = 'spot_int'
326
+ plot_name_int = results_folder.joinpath('spots_' + selected_field + '.png')
327
+ mean_int = mi.Plots().plot_histograms_from_df(
328
+ df_tracking,
329
+ selected_field=selected_field,
330
+ figsize=(8, 2),
331
+ plot_name=plot_name_int,
332
+ bin_count=60,
333
+ save_plot=True,
334
+ list_colors=channel_names,
335
+ remove_outliers=True
336
+ )
337
+ # Remove tracks with low SNR in the tracking channel
338
+ if MINIMAL_SNR is not None:
339
+ array_selected_field = mi.Utilities().df_trajectories_to_array(
340
+ dataframe=df_tracking,
341
+ selected_field=selected_field + '_ch_' + str(channels_spots[0]),
342
+ fill_value='nans'
343
+ )
344
+ mean_snr = np.nanmean(array_selected_field, axis=1)
345
+ indices_low_quality_tracks = np.where(mean_snr < MINIMAL_SNR)[0]
346
+ df_tracking = df_tracking[~df_tracking['particle'].isin(indices_low_quality_tracks)]
347
+ df_tracking = df_tracking.reset_index(drop=True)
348
+ df_tracking['particle'] = df_tracking.groupby('particle').ngroup()
349
+ # Plot image intensity histogram
350
+
351
+ masked_data = corrected_image * masks[np.newaxis, np.newaxis, :, :, np.newaxis].astype(float)
352
+ for i in range(len(channels_spots)):
353
+ #plot_name_histogram = results_folder.joinpath('pixel_histogram_in_cell.png')
354
+ plot_name_histogram = results_folder.joinpath('pixel_histogram_in_cell_'+str(channels_spots[i])+'.png')
355
+ mi.Plots().plot_image_pixel_intensity_distribution(
356
+ image=np.mean(masked_data, axis=(0, 1)),
357
+ figsize=(8, 2),
358
+ bins=100,
359
+ remove_outliers=True,
360
+ remove_zeros=True,
361
+ save_plots=True,
362
+ plot_name=plot_name_histogram,
363
+ single_color=None,
364
+ list_colors=channel_names,
365
+ tracking_channel=channels_spots[0],
366
+ threshold_tracking=starting_threshold[i]
367
+ )
368
+ # Plot original image and tracks
369
+ suptitle = f'Image: {data_folder_path.stem[:16]} - {list_names[selected_image]} - Cell_ID: {selected_image}'
370
+ plot_name_original_image_and_tracks = results_folder.joinpath('original_image_tracking.png')
371
+ mi.Plots().plot_images(
372
+ image_ZYXC=corrected_image[0],
373
+ df=df_tracking,
374
+ masks=masks,
375
+ show_trajectories=True,
376
+ suptitle=suptitle,
377
+ figsize=(12, 3),
378
+ show_plot=True,
379
+ selected_time=0,
380
+ use_maximum_projection=True,
381
+ use_gaussian_filter=True,
382
+ cmap='binary',
383
+ min_max_percentile=[0.05, 99.95],
384
+ show_gird=False,
385
+ save_plots=True,
386
+ plot_name=plot_name_original_image_and_tracks
387
+ )
388
+
389
+ # Combine the original image and the image with tracks
390
+ plot_name_complete_image = results_folder.joinpath('complete_image_tracking.png')
391
+ mi.Utilities().combine_images_vertically([plot_name_original, plot_name_original_image_and_tracks], plot_name_complete_image, delete_originals=True)
392
+
393
+ # Save the DataFrame
394
+ df_tracking.to_csv(results_folder.joinpath('tracking_results.csv'), index=False)
395
+ PLOT_FILTERED_IMAGES = True
396
+ normalize_each_particle = True
397
+ crop_size = yx_spot_size_in_px + 5 # 3 pixels for the border
398
+ # add 5 pixels to crop_size, check if the crop_size is odd, if not, add 1
399
+ if crop_size % 2 == 0:
400
+ crop_size += 1
401
+ selected_time_point = None
402
+ #if PLOT_FILTERED_IMAGES:
403
+ filtered_image = mi.Utilities().gaussian_laplace_filter_image(corrected_image, list_spot_size_px, list_voxels)
404
+ croparray_filtered, mean_crop_filtered, first_appearance, crop_size = mi.CropArray(image=filtered_image, df_crops=df_tracking, crop_size=crop_size, remove_outliers=False, max_percentile=99.95,selected_time_point=selected_time_point,normalize_each_particle=normalize_each_particle).run()
405
+ #else:
406
+ # croparray_filtered, mean_crop_filtered, first_appearance, crop_size = mi.CropArray(image=tested_image, df_crops=df_tracking, crop_size=crop_size, remove_outliers=False, max_percentile=99.9,selected_time_point=selected_time_point,normalize_each_particle=normalize_each_particle).run()
407
+ # Plot all crops
408
+ if save_croparray:
409
+ path_crop_array = results_folder.joinpath('crop_array.png')
410
+ mi.Plots().plot_croparray(croparray_filtered, crop_size, save_plots=True,plot_name= path_crop_array,suptitle=None,show_particle_labels=True, cmap='binary_r',max_percentile = 99) # flag_vector=flag_vector
411
+ # plot pair of crops
412
+ plot_name_crops_filter = results_folder.joinpath('crops.png')
413
+ mi.Plots().plot_matrix_pair_crops (mean_crop_filtered, crop_size,save_plots=True,plot_name=plot_name_crops_filter) # flag_vector=flag_vector
414
+ # Calculate the Mean Squared Displacement
415
+ plot_name_MSD = results_folder.joinpath('MSD_plot.png')
416
+
417
+ #max_lag_for_MSD = 30
418
+ if image_time_interval is None:
419
+ image_time_interval = step_size_in_sec
420
+ print(f'Warning: The image_time_interval was not provided. Using the step_size_in_sec as the image_time_interval: {step_size_in_sec} seconds.')
421
+ else:
422
+ image_time_interval = float(image_time_interval)
423
+ # print a warning message indicating that we are using the step_size_in_sec as the image_time_interval
424
+
425
+ if calculate_MSD:
426
+ diffusion_coefficient, em, time_range, model_fit, trackpy_df = mi.ParticleMotion(df_tracking,
427
+ microns_per_pixel=pixel_xy_um,
428
+ step_size_in_sec=image_time_interval,
429
+ max_lagtime=max_lag_for_MSD,
430
+ show_plot=True,
431
+ remove_drift=False,
432
+ plot_name=plot_name_MSD).calculate_msd()
433
+ else:
434
+ diffusion_coefficient = None
435
+
436
+
437
+ if calculate_correlations:
438
+ # calculate and plot the autocorrelation
439
+ array_ch0= mi.Utilities().df_trajectories_to_array(dataframe=df_tracking, selected_field='spot_int_ch_0', fill_value='nans')
440
+
441
+ if 'spot_int_ch_1' in df_tracking.columns:
442
+ array_ch1= mi.Utilities().df_trajectories_to_array(dataframe=df_tracking, selected_field='spot_int_ch_1', fill_value='nans')
443
+ intensity_array_ch0_short, intensity_array_ch1_short = mi.Utilities().shift_trajectories(array_ch0, array_ch1,max_percentage_empty_data_in_trajectory=max_percentage_empty_data_in_trajectory)
444
+ else:
445
+ array_ch1 = None
446
+ intensity_array_ch0_short = mi.Utilities().shift_trajectories(array_ch0,max_percentage_empty_data_in_trajectory=max_percentage_empty_data_in_trajectory)
447
+ intensity_array_ch1_short = None
448
+
449
+ plot_name_intensity_matrix = results_folder.joinpath('intensity_matrix.png')
450
+ mi.Plots().plot_matrix_sample_time(intensity_array_ch0_short, intensity_array_ch1_short,plot_name=plot_name_intensity_matrix)
451
+
452
+ plot_name_AC_ch0 = results_folder.joinpath('AC_plot_ch0.png')
453
+ mean_correlation_ch0, std_correlation_ch0, lags_ch0, correlations_array_ch0,dwell_time_ch0 = mi.Correlation(primary_data=intensity_array_ch0_short, max_lag=None,
454
+ nan_handling='ignore',shift_data=True,return_full=False,
455
+ time_interval_between_frames_in_seconds=image_time_interval,
456
+ show_plot=True,start_lag=1,fit_type='exponential',
457
+ use_linear_projection_for_lag_0=True,save_plots=True,plot_name=plot_name_AC_ch0).run()
458
+ if array_ch1 is not None:
459
+ plot_name_AC_ch1 = results_folder.joinpath('AC_plot_ch1.png')
460
+ mean_correlation_ch1, std_correlation_ch1, lags_ch1, correlations_array_ch1,dwell_time_ch1 = mi.Correlation(primary_data=intensity_array_ch1_short, max_lag=None,
461
+ nan_handling='ignore',shift_data=True,return_full=False,
462
+ time_interval_between_frames_in_seconds=image_time_interval,
463
+ show_plot=True,start_lag=1,fit_type='exponential',
464
+ use_linear_projection_for_lag_0=True,save_plots=True,plot_name=plot_name_AC_ch1).run()
465
+
466
+ # Plot cross-correlation
467
+ plot_name_cross_correlation = results_folder.joinpath('cross_correlation.png')
468
+ mean_cross_correlation, std_cross_correlation, lags_cross_correlation, cross_correlations_array, max_lag = mi.Correlation(primary_data=intensity_array_ch0_short, secondary_data=intensity_array_ch1_short,
469
+ max_lag=None, nan_handling='ignore', shift_data=False, return_full=True,
470
+ time_interval_between_frames_in_seconds=image_time_interval,show_plot=True,
471
+ save_plots=True,plot_name=plot_name_cross_correlation).run()
472
+
473
+
474
+ # plot napari visualizer
475
+ if save_3d_visualization:
476
+ mask_expanded = masks[np.newaxis, np.newaxis, :, :, np.newaxis]
477
+ masked_image_TZYXC = filtered_image * mask_expanded
478
+ # Apply Gaussian filter to reduce background noise
479
+ #from scipy.ndimage import gaussian_filter
480
+ masked_image_TZYXC = gaussian_filter(masked_image_TZYXC, sigma=1)
481
+ # Remove extreme values from the image
482
+ masked_image_TZYXC = mi.RemoveExtrema(masked_image_TZYXC, min_percentile=0.001, max_percentile=99.995).remove_outliers()
483
+ plot_name_3d_visualizer = str(results_folder.joinpath('image_3d.gif'))
484
+ mi.Plots().Napari_Visualizer(masked_image_TZYXC, df_tracking, z_correction=7, channels_spots=0, plot_name=plot_name_3d_visualizer)
485
+
486
+ # print the process has finished for the selected image
487
+ print(f'Image {list_names[selected_image]} has been processed.')
488
+
489
+ return df_tracking, masks, original_tested_image, diffusion_coefficient
@@ -0,0 +1,153 @@
1
+ """Pipeline module for MicroLive.
2
+
3
+ This module is part of the microlive package.
4
+ """
5
+ from microlive.imports import *
6
+
7
+ def metadata_decorator(metadata_folder_func=None, metadata_filename=None):
8
+ def decorator(func):
9
+ @wraps(func)
10
+ def wrapper(*args, **kwargs):
11
+ # Capture function arguments
12
+ sig = inspect.signature(func)
13
+ bound_args = sig.bind(*args, **kwargs)
14
+ bound_args.apply_defaults()
15
+ # Build metadata dictionary
16
+ metadata = {}
17
+ # Add Date and Time
18
+ metadata["Date and Time"] = pd.Timestamp.now().round('min')
19
+ # Add computer user name
20
+ metadata["computer_user_name"] = getpass.getuser()
21
+ # Add function arguments
22
+ for name, value in bound_args.arguments.items():
23
+ metadata[name] = value
24
+
25
+ try:
26
+ # Call the original function
27
+ result = func(*args, **kwargs)
28
+ #metadata["Function Result"] = str(result)
29
+ except Exception as e:
30
+ # Capture exception details
31
+ metadata["Exception"] = str(e)
32
+ metadata["Traceback"] = traceback.format_exc()
33
+ raise # Re-raise the exception after logging
34
+ finally:
35
+ # Determine metadata folder path
36
+ if metadata_folder_func is not None:
37
+ metadata_folder = metadata_folder_func(*args, **kwargs)
38
+ else:
39
+ metadata_folder = 'temp_metadata_folder' # default folder
40
+
41
+ # Create metadata folder if it doesn't exist
42
+ metadata_folder_path = Path(metadata_folder)
43
+ metadata_folder_path.mkdir(parents=True, exist_ok=True)
44
+
45
+ # Customize metadata file name
46
+ if metadata_filename:
47
+ metadata_file = f"{metadata_filename}.txt"
48
+ else:
49
+ metadata_file = f"{func.__name__}_metadata.txt"
50
+ metadata_file_path = metadata_folder_path / metadata_file
51
+
52
+ # Write metadata to text file
53
+ with open(metadata_file_path, 'w') as f:
54
+ for key, value in metadata.items():
55
+ f.write(f"{key}: {value}\n")
56
+
57
+ return result
58
+ return wrapper
59
+ return decorator
60
+
61
+
62
+ def get_metadata_folder(*args, **kwargs):
63
+ # Extract data_folder_path and selected_image from args or kwargs
64
+ sig = inspect.signature(pipeline_particle_tracking)
65
+ bound_args = sig.bind(*args, **kwargs)
66
+ bound_args.apply_defaults()
67
+ data_folder_path = Path(bound_args.arguments['data_folder_path'])
68
+ selected_image = bound_args.arguments['selected_image']
69
+ results_name = 'results_' + data_folder_path.stem + '_cell_id_' + str(selected_image)
70
+ current_dir = Path().absolute()
71
+ results_folder = current_dir.joinpath('results_live_cell', results_name)
72
+ return results_folder
73
+
74
+
75
+ #@metadata_decorator(metadata_folder)
76
+ @metadata_decorator(metadata_folder_func=get_metadata_folder)
77
+ def pipeline_particle_tracking(data_folder_path, selected_image, channels_spots, max_spots_for_threshold = 100000, show_plot = True, channels_cytosol = None, channels_nucleus = None, min_length_trajectory = 5, yx_spot_size_in_px = 3, maximum_spots_cluster = 4, MINIMAL_SNR = 0.5,diameter_cytosol = 300, diameter_nucleus = 200,segmentation_selection_metric = 'area'):
78
+ # if channels_spots is not a list make it a list.
79
+ channels_spots = [channels_spots] if not isinstance(channels_spots, list) else channels_spots
80
+ # if channels_cytosol is not a list make it a list.
81
+ channels_cytosol = [channels_cytosol] if not isinstance(channels_cytosol, list) else channels_cytosol
82
+ # if channels_nucleus is not a list make it a list.
83
+ channels_nucleus = [channels_nucleus] if not isinstance(channels_nucleus, list) else channels_nucleus
84
+ list_images, list_names, pixel_xy_um, voxel_z_um, channel_names, number_color_channels,list_time_intervals, bit_depth = mi.ReadLif(data_folder_path,show_metadata=False,save_tif=False,save_png=False,format='TZYXC').read()
85
+ # Converting the pixel and voxel sizes to nm
86
+ pixel_xy_nm = int(pixel_xy_um*1000)
87
+ voxel_z_nm = int(voxel_z_um*1000)
88
+ list_voxels = [voxel_z_nm , pixel_xy_nm] # , [voxel_z_nm , pixel_xy_nm] ]
89
+ list_psfs = [voxel_z_nm ,pixel_xy_nm] #, [voxel_z_nm , pixel_xy_nm] ]
90
+
91
+ # Selecting the image to be analyzed
92
+ tested_image =list_images[selected_image] # TZYXC
93
+ # Creating the results folder
94
+ results_name = 'results_'+data_folder_path.stem + '_cell_id_'+str(selected_image)
95
+ current_dir = pathlib.Path().absolute()
96
+ results_folder = current_dir.joinpath('results_live_cell',results_name)
97
+ results_folder.mkdir(parents=True, exist_ok=True)
98
+ mi.Utilities().clear_folder_except_substring(results_folder, 'mask')
99
+ # Plot the original image
100
+ plot_name = results_folder.joinpath('original_image.png')
101
+ mi.Plots().plot_images(image_ZYXC=tested_image[0],figsize=(12, 5), show_plot=show_plot, use_maximum_projection=True, use_gaussian_filter=True,cmap='binary',min_max_percentile=[0.5,99.9],show_gird=False,save_plots=True,plot_name=plot_name)
102
+
103
+ # Read the tif files containing the masks as numpy arrays
104
+ mask_file_name = 'mask_'+data_folder_path.stem+'_image_'+str(selected_image)+'.tif'
105
+ if os.path.exists(str(results_folder.joinpath(mask_file_name))):
106
+ masks = imread(str(results_folder.joinpath(mask_file_name))).astype(bool)
107
+ else: # use cellpose to create the masks
108
+ masks_complete_cells, masks_nuclei, masks_cytosol_no_nuclei = mi.CellSegmentation(tested_image[0], channels_cytosol = channels_cytosol, channels_nucleus= channels_nucleus, diameter_cytosol = diameter_cytosol, diameter_nucleus = diameter_nucleus, optimization_segmentation_method='diameter_segmentation', remove_fragmented_cells=False, show_plot= show_plot, image_name = None,NUMBER_OF_CORES=1,selection_metric=segmentation_selection_metric).calculate_masks()
109
+ # Selecting the mask that is in the center of the image
110
+ selected_mask_id = masks_complete_cells[masks_complete_cells.shape[0] // 2, masks_complete_cells.shape[1] // 2]
111
+ if selected_mask_id > 0:
112
+ masks = masks_complete_cells == selected_mask_id
113
+ else:
114
+ # select the largest mask that is not the background mask (0).
115
+ mask_sizes = [np.sum(mask) for mask in masks_complete_cells]
116
+ selected_mask_id = np.argmax(mask_sizes[1:]) + 1
117
+ masks = masks_complete_cells == selected_mask_id
118
+ # Save the mask
119
+ masks = masks.astype(np.uint8)
120
+ tifffile.imwrite(str(results_folder.joinpath(mask_file_name)), masks,dtype='uint8',)
121
+
122
+ # Calculate the threshold for spot detection
123
+ starting_threshold = mi.Utilities().calculate_threshold_for_spot_detection(tested_image,list_psfs,list_voxels,channels_spots, max_spots_for_threshold = max_spots_for_threshold, show_plot=show_plot)
124
+ # Run the particle tracking
125
+ list_dataframes_trajectories, _ = mi.ParticleTracking (image=tested_image,channels_spots=channels_spots, masks=masks, list_voxels=list_voxels,list_psfs=list_psfs, channels_cytosol=channels_cytosol,channels_nucleus=channels_nucleus,min_length_trajectory=min_length_trajectory,threshold_for_spot_detection=starting_threshold,yx_spot_size_in_px=yx_spot_size_in_px,maximum_spots_cluster=maximum_spots_cluster).run()
126
+ df_tracking= list_dataframes_trajectories[0]
127
+ threshold_tracking = starting_threshold
128
+ filtered_image = mi.Utilities().gaussian_laplace_filter_image(tested_image,list_psfs,list_voxels)
129
+ # Plot histigrams for the SNR
130
+ selected_field = 'snr' # options are: psf_sigma, snr, 'spot_int'
131
+ plot_name_snr = results_folder.joinpath('spots_'+selected_field+'.png')
132
+ mean_snr = mi.Plots().plot_histograms_from_df(df_tracking, selected_field=selected_field,figsize=(8,2), plot_name=plot_name_snr, bin_count=60, save_plot=True, list_colors= channel_names,remove_outliers=True)
133
+ # Plotting histograms for the spot intensity
134
+ selected_field = 'spot_int' # options are: psf_sigma, snr, 'spot_int'
135
+ plot_name_int = results_folder.joinpath('spots_'+selected_field+'.png')
136
+ mean_snr = mi.Plots().plot_histograms_from_df(df_tracking, selected_field=plot_name_int,figsize=(8,2), plot_name=plot_name_snr, bin_count=60, save_plot=True, list_colors= channel_names,remove_outliers=True)
137
+ # Remove tracks with low SNR in the tracking channel
138
+ if MINIMAL_SNR is not None:
139
+ # filtering the tracks based on the SNR on a selected channel [channels_spots]
140
+ array_selected_field= mi.Utilities().df_trajectories_to_array(dataframe=df_tracking, selected_field=selected_field+'_ch_'+str(channels_spots[0]), fill_value='nans')
141
+ mean_snr = np.nanmean(array_selected_field, axis=1)
142
+ indices_low_quality_tracks = np.where(mean_snr < MINIMAL_SNR)[0]
143
+ df_tracking = df_tracking[~df_tracking['particle'].isin(indices_low_quality_tracks)]
144
+ df_tracking = df_tracking.reset_index(drop=True)
145
+ df_tracking['particle'] = df_tracking.groupby('particle').ngroup()
146
+ # plot image intensity histogram
147
+ plot_name_histogram = results_folder.joinpath('pixel_histogram_in_cell.png')
148
+ masked_data = tested_image * masks[np.newaxis, np.newaxis, :, :, np.newaxis].astype(float)
149
+ list_median_intensity = mi.Plots().plot_image_pixel_intensity_distribution(image=np.mean(masked_data[:,:,:,:,:],axis=0),figsize=(8, 2),bins=100,remove_outliers=True,remove_zeros=True,save_plots=True, plot_name=plot_name_histogram ,single_color =None,list_colors=channel_names,tracking_channel = channels_spots[0],threshold_tracking=threshold_tracking)
150
+ suptitle = 'Image: ' + data_folder_path.stem[:16]+'- '+list_names[selected_image] +' - Cell_ID: '+ str(selected_image)
151
+ plot_name_original_image_and_tracks = results_folder.joinpath('original_image_tracking.png')
152
+ mi.Plots().plot_images(image_ZYXC=tested_image[0], df=df_tracking, masks=masks, show_trajectories=True, suptitle=suptitle,figsize=(12, 3), show_plot=True,selected_time=0, use_maximum_projection=True, use_gaussian_filter=True,cmap='binary',min_max_percentile=[0.05,99.95],show_gird=False,save_plots=True,plot_name=plot_name_original_image_and_tracks)
153
+ return df_tracking