microlive 1.0.11__py3-none-any.whl → 1.0.19__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.
- microlive/__init__.py +1 -1
- microlive/data/models/spot_detection_cnn.pth +0 -0
- microlive/gui/app.py +1412 -58
- microlive/imports.py +5 -1
- microlive/microscopy.py +51 -9
- microlive/pipelines/pipeline_FRAP.py +212 -23
- microlive/pipelines/pipeline_folding_efficiency.py +29 -25
- microlive/pipelines/pipeline_particle_tracking.py +616 -176
- microlive/utils/__init__.py +11 -0
- microlive/utils/model_downloader.py +293 -0
- {microlive-1.0.11.dist-info → microlive-1.0.19.dist-info}/METADATA +3 -2
- microlive-1.0.19.dist-info/RECORD +27 -0
- microlive/pipelines/pipeline_spot_detection_no_tracking.py +0 -368
- microlive-1.0.11.dist-info/RECORD +0 -26
- {microlive-1.0.11.dist-info → microlive-1.0.19.dist-info}/WHEEL +0 -0
- {microlive-1.0.11.dist-info → microlive-1.0.19.dist-info}/entry_points.txt +0 -0
- {microlive-1.0.11.dist-info → microlive-1.0.19.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,48 +1,237 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Particle tracking pipeline for live-cell microscopy analysis.
|
|
2
2
|
|
|
3
|
-
This module
|
|
3
|
+
This module provides a complete pipeline for automated particle detection,
|
|
4
|
+
tracking, and quantification in live-cell microscopy images. It integrates
|
|
5
|
+
segmentation, spot detection, trajectory linking, MSD analysis, and
|
|
6
|
+
correlation analysis.
|
|
7
|
+
|
|
8
|
+
The pipeline supports:
|
|
9
|
+
- Leica LIF file format with automatic metadata extraction
|
|
10
|
+
- Multi-channel spot detection and tracking
|
|
11
|
+
- Cell segmentation via Cellpose or watershed
|
|
12
|
+
- Photobleaching correction
|
|
13
|
+
- Mean Squared Displacement (MSD) analysis
|
|
14
|
+
- Auto- and cross-correlation analysis
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
>>> import microlive.microscopy as mi
|
|
18
|
+
>>> from microlive.pipelines import pipeline_particle_tracking
|
|
19
|
+
>>>
|
|
20
|
+
>>> results = pipeline_particle_tracking(
|
|
21
|
+
... data_folder_path=pathlib.Path("experiment.lif"),
|
|
22
|
+
... selected_image=0,
|
|
23
|
+
... channels_spots=[0],
|
|
24
|
+
... channels_cytosol=[1],
|
|
25
|
+
... )
|
|
26
|
+
|
|
27
|
+
Authors:
|
|
28
|
+
Luis U. Aguilera, Ning Zhao
|
|
29
|
+
|
|
30
|
+
License:
|
|
31
|
+
GPL v3
|
|
4
32
|
"""
|
|
5
33
|
from microlive.imports import *
|
|
6
34
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
# =============================================================================
|
|
36
|
+
# Constants
|
|
37
|
+
# =============================================================================
|
|
38
|
+
|
|
39
|
+
# Default percentile values for image visualization
|
|
40
|
+
DEFAULT_MIN_PERCENTILE = 0.5
|
|
41
|
+
DEFAULT_MAX_PERCENTILE = 99.9
|
|
42
|
+
DEFAULT_MIN_PERCENTILE_TRACKING = 0.05
|
|
43
|
+
DEFAULT_MAX_PERCENTILE_TRACKING = 99.95
|
|
44
|
+
|
|
45
|
+
# Default quality thresholds
|
|
46
|
+
DEFAULT_MINIMAL_SNR = 0.5
|
|
47
|
+
DEFAULT_MAX_CROP_PERCENTILE = 99
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def pipeline_particle_tracking(
|
|
51
|
+
data_folder_path,
|
|
52
|
+
selected_image,
|
|
53
|
+
channels_spots,
|
|
54
|
+
max_spots_for_threshold=100000,
|
|
55
|
+
show_plot=True,
|
|
56
|
+
channels_cytosol=None,
|
|
57
|
+
channels_nucleus=None,
|
|
58
|
+
memory=1,
|
|
59
|
+
min_length_trajectory=5,
|
|
60
|
+
yx_spot_size_in_px=5,
|
|
61
|
+
z_spot_size_in_px=2,
|
|
62
|
+
maximum_spots_cluster=4,
|
|
63
|
+
cluster_radius_nm=500,
|
|
64
|
+
MINIMAL_SNR=0.5,
|
|
65
|
+
diameter_cytosol=300,
|
|
66
|
+
diameter_nucleus=200,
|
|
67
|
+
segmentation_selection_metric='max_area',
|
|
68
|
+
recalculate_mask=False,
|
|
69
|
+
optimization_segmentation_method='diameter_segmentation',
|
|
70
|
+
pretrained_model_cyto_segmentation=None,
|
|
71
|
+
use_watershed=False,
|
|
72
|
+
list_images_to_process=None,
|
|
73
|
+
save_3d_visualization=False,
|
|
74
|
+
max_percentage_empty_data_in_trajectory=0.1,
|
|
75
|
+
particle_detection_threshold=None,
|
|
76
|
+
save_croparray=False,
|
|
77
|
+
apply_photobleaching_correction=False,
|
|
78
|
+
photobleaching_mode='inside_cell',
|
|
79
|
+
use_maximum_projection=False,
|
|
80
|
+
max_lag_for_MSD=30,
|
|
81
|
+
step_size_in_sec=1,
|
|
82
|
+
separate_clusters_and_spots=False,
|
|
83
|
+
maximum_range_search_pixels=10,
|
|
84
|
+
results_folder_path=None,
|
|
85
|
+
calculate_MSD=True,
|
|
86
|
+
calculate_correlations=True,
|
|
87
|
+
link_particles=True
|
|
88
|
+
):
|
|
89
|
+
"""Execute the complete particle tracking pipeline on LIF microscopy data.
|
|
90
|
+
|
|
91
|
+
This function orchestrates the full analysis workflow: loading images,
|
|
92
|
+
segmentation, spot detection, tracking, and downstream analysis (MSD,
|
|
93
|
+
correlations). It can process a single image or batch process multiple
|
|
94
|
+
images from a LIF file.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
data_folder_path: Path to the LIF file or data folder.
|
|
98
|
+
selected_image: Index of the image to process, or None for batch mode.
|
|
99
|
+
channels_spots: List of channel indices for spot detection.
|
|
100
|
+
max_spots_for_threshold: Maximum spots to consider for threshold
|
|
101
|
+
calculation. Defaults to 100000.
|
|
102
|
+
show_plot: Whether to display plots during processing. Defaults to True.
|
|
103
|
+
channels_cytosol: List of channel indices for cytosol segmentation.
|
|
104
|
+
Defaults to None.
|
|
105
|
+
channels_nucleus: List of channel indices for nucleus segmentation.
|
|
106
|
+
Defaults to None.
|
|
107
|
+
memory: Number of frames to remember for trajectory linking.
|
|
108
|
+
Defaults to 1.
|
|
109
|
+
min_length_trajectory: Minimum trajectory length in frames.
|
|
110
|
+
Defaults to 5.
|
|
111
|
+
yx_spot_size_in_px: Expected spot size in XY (pixels). Defaults to 5.
|
|
112
|
+
z_spot_size_in_px: Expected spot size in Z (pixels). Defaults to 2.
|
|
113
|
+
maximum_spots_cluster: Maximum spots per cluster. Defaults to 4.
|
|
114
|
+
cluster_radius_nm: Cluster radius in nanometers. Defaults to 500.
|
|
115
|
+
MINIMAL_SNR: Minimum signal-to-noise ratio for trajectories.
|
|
116
|
+
Defaults to 0.5.
|
|
117
|
+
diameter_cytosol: Expected cytosol diameter for Cellpose (pixels).
|
|
118
|
+
Defaults to 300.
|
|
119
|
+
diameter_nucleus: Expected nucleus diameter for Cellpose (pixels).
|
|
120
|
+
Defaults to 200.
|
|
121
|
+
segmentation_selection_metric: Metric for mask selection
|
|
122
|
+
('max_area', 'center', etc.). Defaults to 'max_area'.
|
|
123
|
+
recalculate_mask: Force mask recalculation even if cached.
|
|
124
|
+
Defaults to False.
|
|
125
|
+
optimization_segmentation_method: Segmentation optimization method.
|
|
126
|
+
Defaults to 'diameter_segmentation'.
|
|
127
|
+
pretrained_model_cyto_segmentation: Path to pretrained Cellpose model.
|
|
128
|
+
Defaults to None.
|
|
129
|
+
use_watershed: Use watershed instead of Cellpose. Defaults to False.
|
|
130
|
+
list_images_to_process: List of image names to process in batch mode.
|
|
131
|
+
Defaults to None (all images).
|
|
132
|
+
save_3d_visualization: Save 3D Napari visualization. Defaults to False.
|
|
133
|
+
max_percentage_empty_data_in_trajectory: Maximum fraction of NaN values
|
|
134
|
+
allowed in trajectories. Defaults to 0.1.
|
|
135
|
+
particle_detection_threshold: Manual threshold for spot detection.
|
|
136
|
+
Defaults to None (auto-calculate).
|
|
137
|
+
save_croparray: Save crop array visualization. Defaults to False.
|
|
138
|
+
apply_photobleaching_correction: Apply photobleaching correction.
|
|
139
|
+
Defaults to False.
|
|
140
|
+
photobleaching_mode: Mode for photobleaching correction
|
|
141
|
+
('inside_cell', 'whole_image'). Defaults to 'inside_cell'.
|
|
142
|
+
use_maximum_projection: Use Z maximum projection. Defaults to False.
|
|
143
|
+
max_lag_for_MSD: Maximum lag time for MSD calculation.
|
|
144
|
+
Defaults to 30.
|
|
145
|
+
step_size_in_sec: Time step between frames in seconds.
|
|
146
|
+
Defaults to 1.
|
|
147
|
+
separate_clusters_and_spots: Separate clustered spots during detection.
|
|
148
|
+
Defaults to False.
|
|
149
|
+
maximum_range_search_pixels: Maximum search range for linking (pixels).
|
|
150
|
+
Defaults to 10.
|
|
151
|
+
results_folder_path: Custom path for results. Defaults to None.
|
|
152
|
+
calculate_MSD: Whether to calculate MSD. Defaults to True.
|
|
153
|
+
calculate_correlations: Whether to calculate correlations.
|
|
154
|
+
Defaults to True.
|
|
155
|
+
link_particles: Whether to link detected spots into trajectories.
|
|
156
|
+
If False, performs detection-only. Defaults to True.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
tuple: A tuple containing:
|
|
160
|
+
- final_df (pd.DataFrame): Combined tracking results for all images.
|
|
161
|
+
- list_df (list): List of DataFrames, one per processed image.
|
|
162
|
+
- list_masks (list): List of segmentation masks.
|
|
163
|
+
- list_images_tested (list): List of processed images.
|
|
164
|
+
- list_diffusion_coefficient (list): List of diffusion coefficients.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
FileNotFoundError: If the data_folder_path does not exist.
|
|
168
|
+
ValueError: If channels_spots is empty or invalid.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
>>> # Process a single image
|
|
172
|
+
>>> df, dfs, masks, images, D = pipeline_particle_tracking(
|
|
173
|
+
... data_folder_path=pathlib.Path("data.lif"),
|
|
174
|
+
... selected_image=0,
|
|
175
|
+
... channels_spots=[0],
|
|
176
|
+
... channels_cytosol=[1],
|
|
177
|
+
... min_length_trajectory=10,
|
|
178
|
+
... calculate_MSD=True
|
|
179
|
+
... )
|
|
180
|
+
>>> print(f"Found {len(df)} spots, D = {D[0]:.4f} um²/s")
|
|
181
|
+
"""
|
|
182
|
+
# Read images and metadata from LIF file
|
|
183
|
+
(
|
|
184
|
+
list_images,
|
|
185
|
+
list_names,
|
|
186
|
+
pixel_xy_um,
|
|
187
|
+
voxel_z_um,
|
|
188
|
+
channel_names,
|
|
189
|
+
number_color_channels,
|
|
190
|
+
list_time_intervals,
|
|
191
|
+
bit_depth
|
|
192
|
+
) = mi.ReadLif(
|
|
193
|
+
data_folder_path,
|
|
194
|
+
show_metadata=False,
|
|
195
|
+
save_tif=False,
|
|
196
|
+
save_png=False,
|
|
197
|
+
format='TZYXC'
|
|
198
|
+
).read()
|
|
199
|
+
|
|
200
|
+
# Keep a complete copy for reference
|
|
22
201
|
list_images_complete = list_images.copy()
|
|
202
|
+
|
|
203
|
+
# Filter images if a subset is specified
|
|
23
204
|
if list_images_to_process is not None:
|
|
24
|
-
selected_indices = [
|
|
205
|
+
selected_indices = [
|
|
206
|
+
i for i in range(len(list_names))
|
|
207
|
+
if list_names[i] in list_images_to_process
|
|
208
|
+
]
|
|
25
209
|
if use_maximum_projection:
|
|
26
|
-
|
|
27
|
-
|
|
210
|
+
list_images = [
|
|
211
|
+
np.max(list_images[i], axis=1, keepdims=True)
|
|
212
|
+
for i in selected_indices
|
|
213
|
+
]
|
|
28
214
|
else:
|
|
29
215
|
list_images = [list_images[i] for i in selected_indices]
|
|
30
216
|
else:
|
|
31
217
|
selected_indices = range(len(list_names))
|
|
32
218
|
if use_maximum_projection:
|
|
33
|
-
list_images = [
|
|
219
|
+
list_images = [
|
|
220
|
+
np.max(img, axis=1, keepdims=True) for img in list_images
|
|
221
|
+
]
|
|
34
222
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# If selected_image is None, process all images
|
|
223
|
+
# Batch processing: process all images if selected_image is None
|
|
38
224
|
if selected_image is None:
|
|
39
|
-
list_df
|
|
40
|
-
|
|
41
|
-
|
|
225
|
+
list_df = []
|
|
226
|
+
list_masks = []
|
|
227
|
+
list_images_tested = []
|
|
228
|
+
list_diffusion_coefficient = []
|
|
229
|
+
|
|
42
230
|
for idx in range(len(list_images_complete)):
|
|
43
231
|
if idx not in selected_indices:
|
|
44
232
|
continue
|
|
45
|
-
|
|
233
|
+
|
|
234
|
+
df, masks, image, diffusion_coefficient = process_single_image(
|
|
46
235
|
data_folder_path=data_folder_path,
|
|
47
236
|
selected_image=idx,
|
|
48
237
|
channels_spots=channels_spots,
|
|
@@ -52,7 +241,7 @@ def pipeline_particle_tracking(data_folder_path, selected_image, channels_spots,
|
|
|
52
241
|
channels_nucleus=channels_nucleus,
|
|
53
242
|
min_length_trajectory=min_length_trajectory,
|
|
54
243
|
yx_spot_size_in_px=yx_spot_size_in_px,
|
|
55
|
-
z_spot_size_in_px
|
|
244
|
+
z_spot_size_in_px=z_spot_size_in_px,
|
|
56
245
|
maximum_spots_cluster=maximum_spots_cluster,
|
|
57
246
|
cluster_radius_nm=cluster_radius_nm,
|
|
58
247
|
MINIMAL_SNR=MINIMAL_SNR,
|
|
@@ -73,7 +262,7 @@ def pipeline_particle_tracking(data_folder_path, selected_image, channels_spots,
|
|
|
73
262
|
apply_photobleaching_correction=apply_photobleaching_correction,
|
|
74
263
|
photobleaching_mode=photobleaching_mode,
|
|
75
264
|
use_maximum_projection=use_maximum_projection,
|
|
76
|
-
max_lag_for_MSD
|
|
265
|
+
max_lag_for_MSD=max_lag_for_MSD,
|
|
77
266
|
step_size_in_sec=step_size_in_sec,
|
|
78
267
|
separate_clusters_and_spots=separate_clusters_and_spots,
|
|
79
268
|
maximum_range_search_pixels=maximum_range_search_pixels,
|
|
@@ -84,26 +273,33 @@ def pipeline_particle_tracking(data_folder_path, selected_image, channels_spots,
|
|
|
84
273
|
calculate_MSD=calculate_MSD,
|
|
85
274
|
calculate_correlations=calculate_correlations,
|
|
86
275
|
save_croparray=save_croparray,
|
|
276
|
+
link_particles=link_particles,
|
|
87
277
|
)
|
|
88
|
-
|
|
89
|
-
#
|
|
278
|
+
|
|
279
|
+
# Skip empty results
|
|
90
280
|
if df.empty:
|
|
91
281
|
continue
|
|
92
|
-
|
|
282
|
+
|
|
283
|
+
# Update image_id to reflect the original index
|
|
93
284
|
df['image_id'] = idx
|
|
94
285
|
list_df.append(df)
|
|
95
286
|
list_masks.append(masks)
|
|
96
287
|
list_images_tested.append(image)
|
|
97
288
|
list_diffusion_coefficient.append(diffusion_coefficient)
|
|
98
289
|
|
|
99
|
-
|
|
290
|
+
# Combine all DataFrames
|
|
291
|
+
if len(list_df) > 1:
|
|
100
292
|
final_df = pd.concat(list_df, ignore_index=True)
|
|
293
|
+
elif len(list_df) == 1:
|
|
294
|
+
final_df = list_df[0]
|
|
101
295
|
else:
|
|
102
|
-
final_df =
|
|
296
|
+
final_df = pd.DataFrame()
|
|
297
|
+
|
|
103
298
|
return final_df, list_df, list_masks, list_images_tested, list_diffusion_coefficient
|
|
299
|
+
|
|
104
300
|
else:
|
|
105
|
-
#
|
|
106
|
-
df,masks,image, diffusion_coefficient= process_single_image(
|
|
301
|
+
# Single image processing
|
|
302
|
+
df, masks, image, diffusion_coefficient = process_single_image(
|
|
107
303
|
data_folder_path=data_folder_path,
|
|
108
304
|
selected_image=selected_image,
|
|
109
305
|
channels_spots=channels_spots,
|
|
@@ -113,7 +309,7 @@ def pipeline_particle_tracking(data_folder_path, selected_image, channels_spots,
|
|
|
113
309
|
channels_nucleus=channels_nucleus,
|
|
114
310
|
min_length_trajectory=min_length_trajectory,
|
|
115
311
|
yx_spot_size_in_px=yx_spot_size_in_px,
|
|
116
|
-
z_spot_size_in_px
|
|
312
|
+
z_spot_size_in_px=z_spot_size_in_px,
|
|
117
313
|
maximum_spots_cluster=maximum_spots_cluster,
|
|
118
314
|
cluster_radius_nm=cluster_radius_nm,
|
|
119
315
|
MINIMAL_SNR=MINIMAL_SNR,
|
|
@@ -121,11 +317,11 @@ def pipeline_particle_tracking(data_folder_path, selected_image, channels_spots,
|
|
|
121
317
|
diameter_nucleus=diameter_nucleus,
|
|
122
318
|
segmentation_selection_metric=segmentation_selection_metric,
|
|
123
319
|
list_images=list_images,
|
|
124
|
-
list_names=list_names
|
|
320
|
+
list_names=list_names,
|
|
125
321
|
pixel_xy_um=pixel_xy_um,
|
|
126
322
|
voxel_z_um=voxel_z_um,
|
|
127
323
|
channel_names=channel_names,
|
|
128
|
-
image_time_interval
|
|
324
|
+
image_time_interval=list_time_intervals[selected_image],
|
|
129
325
|
recalculate_mask=recalculate_mask,
|
|
130
326
|
optimization_segmentation_method=optimization_segmentation_method,
|
|
131
327
|
pretrained_model_cyto_segmentation=pretrained_model_cyto_segmentation,
|
|
@@ -134,7 +330,7 @@ def pipeline_particle_tracking(data_folder_path, selected_image, channels_spots,
|
|
|
134
330
|
apply_photobleaching_correction=apply_photobleaching_correction,
|
|
135
331
|
photobleaching_mode=photobleaching_mode,
|
|
136
332
|
use_maximum_projection=use_maximum_projection,
|
|
137
|
-
max_lag_for_MSD
|
|
333
|
+
max_lag_for_MSD=max_lag_for_MSD,
|
|
138
334
|
step_size_in_sec=step_size_in_sec,
|
|
139
335
|
separate_clusters_and_spots=separate_clusters_and_spots,
|
|
140
336
|
maximum_range_search_pixels=maximum_range_search_pixels,
|
|
@@ -145,25 +341,122 @@ def pipeline_particle_tracking(data_folder_path, selected_image, channels_spots,
|
|
|
145
341
|
calculate_MSD=calculate_MSD,
|
|
146
342
|
calculate_correlations=calculate_correlations,
|
|
147
343
|
save_croparray=save_croparray,
|
|
344
|
+
link_particles=link_particles,
|
|
148
345
|
)
|
|
346
|
+
|
|
149
347
|
return df, [df], [masks], [image], [diffusion_coefficient]
|
|
150
348
|
|
|
151
349
|
|
|
350
|
+
@mi.Utilities().metadata_decorator(
|
|
351
|
+
metadata_folder_func=mi.Utilities().get_metadata_folder,
|
|
352
|
+
exclude_args=['list_images']
|
|
353
|
+
)
|
|
354
|
+
def process_single_image(
|
|
355
|
+
data_folder_path,
|
|
356
|
+
selected_image,
|
|
357
|
+
channels_spots,
|
|
358
|
+
max_spots_for_threshold=100000,
|
|
359
|
+
show_plot=True,
|
|
360
|
+
channels_cytosol=None,
|
|
361
|
+
channels_nucleus=None,
|
|
362
|
+
memory=1,
|
|
363
|
+
min_length_trajectory=5,
|
|
364
|
+
yx_spot_size_in_px=5,
|
|
365
|
+
z_spot_size_in_px=2,
|
|
366
|
+
maximum_spots_cluster=4,
|
|
367
|
+
cluster_radius_nm=500,
|
|
368
|
+
MINIMAL_SNR=0.5,
|
|
369
|
+
diameter_cytosol=300,
|
|
370
|
+
diameter_nucleus=200,
|
|
371
|
+
segmentation_selection_metric='area',
|
|
372
|
+
list_images=None,
|
|
373
|
+
list_names=None,
|
|
374
|
+
pixel_xy_um=None,
|
|
375
|
+
voxel_z_um=None,
|
|
376
|
+
channel_names=None,
|
|
377
|
+
optimization_segmentation_method='diameter_segmentation',
|
|
378
|
+
recalculate_mask=False,
|
|
379
|
+
use_watershed=False,
|
|
380
|
+
pretrained_model_cyto_segmentation=None,
|
|
381
|
+
particle_detection_threshold=None,
|
|
382
|
+
save_croparray=False,
|
|
383
|
+
image_time_interval=None,
|
|
384
|
+
save_3d_visualization=False,
|
|
385
|
+
apply_photobleaching_correction=False,
|
|
386
|
+
photobleaching_mode='inside_cell',
|
|
387
|
+
max_percentage_empty_data_in_trajectory=0.1,
|
|
388
|
+
use_maximum_projection=False,
|
|
389
|
+
max_lag_for_MSD=30,
|
|
390
|
+
step_size_in_sec=1,
|
|
391
|
+
separate_clusters_and_spots=False,
|
|
392
|
+
maximum_range_search_pixels=10,
|
|
393
|
+
results_folder_path=None,
|
|
394
|
+
calculate_MSD=True,
|
|
395
|
+
calculate_correlations=True,
|
|
396
|
+
link_particles=True
|
|
397
|
+
):
|
|
398
|
+
"""Process a single image through the complete particle tracking workflow.
|
|
152
399
|
|
|
400
|
+
This function handles the core analysis for one image: segmentation,
|
|
401
|
+
spot detection, trajectory linking, quality filtering, and optional
|
|
402
|
+
MSD and correlation analysis.
|
|
153
403
|
|
|
404
|
+
Args:
|
|
405
|
+
data_folder_path: Path to the data folder or LIF file.
|
|
406
|
+
selected_image: Index of the image to process.
|
|
407
|
+
channels_spots: List of channel indices for spot detection.
|
|
408
|
+
max_spots_for_threshold: Maximum spots for threshold calculation.
|
|
409
|
+
show_plot: Whether to display plots.
|
|
410
|
+
channels_cytosol: List of channels for cytosol segmentation.
|
|
411
|
+
channels_nucleus: List of channels for nucleus segmentation.
|
|
412
|
+
memory: Frames to remember for trajectory linking.
|
|
413
|
+
min_length_trajectory: Minimum trajectory length.
|
|
414
|
+
yx_spot_size_in_px: Expected spot size in XY (pixels).
|
|
415
|
+
z_spot_size_in_px: Expected spot size in Z (pixels).
|
|
416
|
+
maximum_spots_cluster: Maximum spots per cluster.
|
|
417
|
+
cluster_radius_nm: Cluster radius in nanometers.
|
|
418
|
+
MINIMAL_SNR: Minimum SNR threshold for quality filtering.
|
|
419
|
+
diameter_cytosol: Expected cytosol diameter (pixels).
|
|
420
|
+
diameter_nucleus: Expected nucleus diameter (pixels).
|
|
421
|
+
segmentation_selection_metric: Metric for mask selection.
|
|
422
|
+
list_images: Pre-loaded list of images.
|
|
423
|
+
list_names: List of image names.
|
|
424
|
+
pixel_xy_um: Pixel size in XY (micrometers).
|
|
425
|
+
voxel_z_um: Voxel size in Z (micrometers).
|
|
426
|
+
channel_names: List of channel names.
|
|
427
|
+
optimization_segmentation_method: Segmentation optimization method.
|
|
428
|
+
recalculate_mask: Force mask recalculation.
|
|
429
|
+
use_watershed: Use watershed segmentation.
|
|
430
|
+
pretrained_model_cyto_segmentation: Path to pretrained model.
|
|
431
|
+
particle_detection_threshold: Manual detection threshold.
|
|
432
|
+
save_croparray: Save crop array visualization.
|
|
433
|
+
image_time_interval: Time interval between frames (seconds).
|
|
434
|
+
save_3d_visualization: Save 3D visualization.
|
|
435
|
+
apply_photobleaching_correction: Apply photobleaching correction.
|
|
436
|
+
photobleaching_mode: Photobleaching correction mode.
|
|
437
|
+
max_percentage_empty_data_in_trajectory: Maximum NaN fraction allowed.
|
|
438
|
+
use_maximum_projection: Use Z maximum projection.
|
|
439
|
+
max_lag_for_MSD: Maximum lag for MSD calculation.
|
|
440
|
+
step_size_in_sec: Time step for MSD (seconds).
|
|
441
|
+
separate_clusters_and_spots: Separate clusters during detection.
|
|
442
|
+
maximum_range_search_pixels: Maximum search range for linking.
|
|
443
|
+
results_folder_path: Custom results folder path.
|
|
444
|
+
calculate_MSD: Whether to calculate MSD.
|
|
445
|
+
calculate_correlations: Whether to calculate correlations.
|
|
446
|
+
link_particles: Whether to link detected spots into trajectories.
|
|
447
|
+
If False, performs detection-only. Defaults to True.
|
|
154
448
|
|
|
449
|
+
Returns:
|
|
450
|
+
tuple: A tuple containing:
|
|
451
|
+
- df_tracking (pd.DataFrame): Tracking results with spot positions,
|
|
452
|
+
intensities, and trajectory IDs.
|
|
453
|
+
- masks (np.ndarray): Cell segmentation mask.
|
|
454
|
+
- original_tested_image (np.ndarray): Original image data.
|
|
455
|
+
- diffusion_coefficient (float or None): Calculated D value.
|
|
155
456
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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):
|
|
457
|
+
Raises:
|
|
458
|
+
ValueError: If list_images or list_names is None.
|
|
459
|
+
"""
|
|
167
460
|
# Ensure lists are properly formatted
|
|
168
461
|
channels_spots = [channels_spots] if not isinstance(channels_spots, list) else channels_spots
|
|
169
462
|
channels_cytosol = [channels_cytosol] if not isinstance(channels_cytosol, list) else channels_cytosol
|
|
@@ -174,30 +467,35 @@ def process_single_image(data_folder_path, selected_image, channels_spots, max_s
|
|
|
174
467
|
voxel_z_nm = int(voxel_z_um * 1000)
|
|
175
468
|
list_voxels = [voxel_z_nm, pixel_xy_nm]
|
|
176
469
|
list_spot_size_px = [z_spot_size_in_px, yx_spot_size_in_px]
|
|
177
|
-
|
|
470
|
+
|
|
471
|
+
# Log progress
|
|
178
472
|
print('--------------------------------------------------')
|
|
179
473
|
print(f'Processing image: {list_names[selected_image]}')
|
|
180
|
-
|
|
474
|
+
|
|
475
|
+
tested_image = list_images[selected_image] # TZYXC format
|
|
181
476
|
original_tested_image = tested_image.copy()
|
|
182
|
-
|
|
183
|
-
|
|
477
|
+
|
|
478
|
+
# Create results folder
|
|
479
|
+
results_name = f'results_{data_folder_path.stem}_cell_id_{selected_image}'
|
|
184
480
|
current_dir = pathlib.Path().absolute()
|
|
185
|
-
|
|
186
|
-
|
|
481
|
+
|
|
187
482
|
if results_folder_path is not None:
|
|
188
|
-
# ensure that results_folder_path is a Path object
|
|
189
483
|
if not isinstance(results_folder_path, pathlib.Path):
|
|
190
484
|
results_folder_path = pathlib.Path(results_folder_path)
|
|
191
485
|
results_folder = results_folder_path.joinpath(results_name)
|
|
192
486
|
else:
|
|
193
487
|
results_folder = current_dir.joinpath('results_live_cell', results_name)
|
|
488
|
+
|
|
194
489
|
results_folder.mkdir(parents=True, exist_ok=True)
|
|
195
490
|
mi.Utilities().clear_folder_except_substring(results_folder, 'mask')
|
|
491
|
+
|
|
196
492
|
# Plot the original image
|
|
197
493
|
plot_name_original = results_folder.joinpath('original_image.png')
|
|
198
|
-
suptitle=
|
|
199
|
-
|
|
200
|
-
|
|
494
|
+
suptitle = (
|
|
495
|
+
f'Image: {data_folder_path.stem[:16]} - '
|
|
496
|
+
f'{list_names[selected_image]} - Cell_ID: {selected_image}'
|
|
497
|
+
)
|
|
498
|
+
|
|
201
499
|
mi.Plots().plot_images(
|
|
202
500
|
image_ZYXC=tested_image[0],
|
|
203
501
|
figsize=(12, 5),
|
|
@@ -205,76 +503,97 @@ def process_single_image(data_folder_path, selected_image, channels_spots, max_s
|
|
|
205
503
|
use_maximum_projection=True,
|
|
206
504
|
use_gaussian_filter=True,
|
|
207
505
|
cmap='binary',
|
|
208
|
-
min_max_percentile=[
|
|
506
|
+
min_max_percentile=[DEFAULT_MIN_PERCENTILE, DEFAULT_MAX_PERCENTILE],
|
|
209
507
|
show_gird=False,
|
|
210
508
|
save_plots=True,
|
|
211
509
|
plot_name=plot_name_original,
|
|
212
510
|
suptitle=suptitle
|
|
213
511
|
)
|
|
512
|
+
|
|
214
513
|
# Read or create masks
|
|
215
|
-
mask_file_name = 'mask_
|
|
514
|
+
mask_file_name = f'mask_{data_folder_path.stem}_image_{selected_image}.tif'
|
|
216
515
|
mask_file_path = results_folder.joinpath(mask_file_name)
|
|
217
516
|
path_mask_exist = os.path.exists(str(mask_file_path))
|
|
218
|
-
|
|
517
|
+
|
|
518
|
+
if path_mask_exist and not recalculate_mask:
|
|
219
519
|
masks = imread(str(mask_file_path)).astype(bool)
|
|
220
520
|
else:
|
|
221
|
-
# Use Cellpose to create masks
|
|
521
|
+
# Use Cellpose or watershed to create masks
|
|
222
522
|
if use_watershed:
|
|
223
|
-
masks_complete_cells = mi.CellSegmentationWatershed(
|
|
224
|
-
|
|
523
|
+
masks_complete_cells = mi.CellSegmentationWatershed(
|
|
524
|
+
np.max(tested_image[:, :, :, :, channels_cytosol[0]], axis=(0, 1)),
|
|
525
|
+
footprint_size=2
|
|
526
|
+
).apply_watershed()
|
|
225
527
|
else:
|
|
226
528
|
masks_complete_cells, _, _ = mi.CellSegmentation(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
529
|
+
tested_image[0],
|
|
530
|
+
channels_cytosol=channels_cytosol,
|
|
531
|
+
channels_nucleus=channels_nucleus,
|
|
532
|
+
diameter_cytosol=diameter_cytosol,
|
|
533
|
+
diameter_nucleus=diameter_nucleus,
|
|
534
|
+
optimization_segmentation_method=optimization_segmentation_method,
|
|
535
|
+
remove_fragmented_cells=False,
|
|
536
|
+
show_plot=show_plot,
|
|
537
|
+
image_name=None,
|
|
538
|
+
NUMBER_OF_CORES=1,
|
|
539
|
+
selection_metric=segmentation_selection_metric,
|
|
540
|
+
pretrained_model_cyto_segmentation=pretrained_model_cyto_segmentation
|
|
541
|
+
).calculate_masks()
|
|
542
|
+
|
|
543
|
+
# Select the mask at the center of the image
|
|
241
544
|
center_y = masks_complete_cells.shape[0] // 2
|
|
242
545
|
center_x = masks_complete_cells.shape[1] // 2
|
|
243
546
|
selected_mask_id = masks_complete_cells[center_y, center_x]
|
|
547
|
+
|
|
244
548
|
if selected_mask_id > 0:
|
|
245
549
|
masks = masks_complete_cells == selected_mask_id
|
|
246
550
|
else:
|
|
247
|
-
#
|
|
551
|
+
# Fall back to the largest mask
|
|
248
552
|
mask_labels = np.unique(masks_complete_cells)
|
|
249
|
-
mask_sizes = [
|
|
553
|
+
mask_sizes = [
|
|
554
|
+
(label, np.sum(masks_complete_cells == label))
|
|
555
|
+
for label in mask_labels if label != 0
|
|
556
|
+
]
|
|
250
557
|
if mask_sizes:
|
|
251
558
|
selected_mask_id = max(mask_sizes, key=lambda x: x[1])[0]
|
|
252
559
|
masks = masks_complete_cells == selected_mask_id
|
|
253
560
|
else:
|
|
254
561
|
masks = np.zeros_like(masks_complete_cells, dtype=bool)
|
|
562
|
+
|
|
255
563
|
# Save the mask
|
|
256
564
|
masks = masks.astype(np.uint8)
|
|
257
565
|
tifffile.imwrite(str(mask_file_path), masks, dtype='uint8')
|
|
258
566
|
|
|
567
|
+
# Apply photobleaching correction if requested
|
|
259
568
|
if apply_photobleaching_correction:
|
|
260
|
-
|
|
261
|
-
corrected_image =
|
|
262
|
-
|
|
569
|
+
file_path_photobleaching = results_folder.joinpath('photobleaching.png')
|
|
570
|
+
corrected_image = mi.Photobleaching(
|
|
571
|
+
image_TZYXC=tested_image,
|
|
572
|
+
mask_YX=masks,
|
|
573
|
+
show_plot=False,
|
|
574
|
+
mode=photobleaching_mode,
|
|
575
|
+
plot_name=file_path_photobleaching
|
|
576
|
+
).apply_photobleaching_correction()
|
|
263
577
|
else:
|
|
264
578
|
corrected_image = tested_image
|
|
579
|
+
|
|
265
580
|
# Calculate the threshold for spot detection
|
|
266
581
|
plot_name_threshold = results_folder.joinpath('threshold_spot_detection.png')
|
|
267
|
-
|
|
582
|
+
|
|
268
583
|
if particle_detection_threshold is None:
|
|
269
584
|
starting_threshold = mi.Utilities().calculate_threshold_for_spot_detection(
|
|
270
|
-
corrected_image,
|
|
585
|
+
corrected_image,
|
|
586
|
+
list_spot_size_px,
|
|
587
|
+
list_voxels,
|
|
588
|
+
channels_spots,
|
|
271
589
|
max_spots_for_threshold=max_spots_for_threshold,
|
|
272
|
-
show_plot=True,
|
|
590
|
+
show_plot=True,
|
|
591
|
+
plot_name=plot_name_threshold
|
|
273
592
|
)
|
|
274
593
|
else:
|
|
275
|
-
starting_threshold =
|
|
276
|
-
|
|
277
|
-
# Run
|
|
594
|
+
starting_threshold = [particle_detection_threshold] * len(channels_spots)
|
|
595
|
+
|
|
596
|
+
# Run particle tracking
|
|
278
597
|
try:
|
|
279
598
|
list_dataframes_trajectories, _ = mi.ParticleTracking(
|
|
280
599
|
image=corrected_image,
|
|
@@ -289,28 +608,32 @@ def process_single_image(data_folder_path, selected_image, channels_spots, max_s
|
|
|
289
608
|
yx_spot_size_in_px=yx_spot_size_in_px,
|
|
290
609
|
z_spot_size_in_px=z_spot_size_in_px,
|
|
291
610
|
maximum_spots_cluster=maximum_spots_cluster,
|
|
292
|
-
cluster_radius_nm
|
|
611
|
+
cluster_radius_nm=cluster_radius_nm,
|
|
293
612
|
separate_clusters_and_spots=separate_clusters_and_spots,
|
|
294
613
|
maximum_range_search_pixels=maximum_range_search_pixels,
|
|
614
|
+
link_particles=link_particles,
|
|
295
615
|
).run()
|
|
296
616
|
except Exception as e:
|
|
297
|
-
print(f'Error: {e}')
|
|
617
|
+
print(f'Error during particle tracking: {e}')
|
|
298
618
|
return pd.DataFrame(), masks, original_tested_image, None
|
|
299
|
-
|
|
619
|
+
|
|
300
620
|
df_tracking = list_dataframes_trajectories[0]
|
|
301
621
|
|
|
302
|
-
if len(df_tracking)==0:
|
|
622
|
+
if len(df_tracking) == 0:
|
|
303
623
|
return pd.DataFrame(), masks, original_tested_image, None
|
|
304
624
|
|
|
305
|
-
|
|
625
|
+
# Combine trajectories from multiple channels if present
|
|
306
626
|
if len(list_dataframes_trajectories) > 1:
|
|
307
627
|
for i in range(1, len(list_dataframes_trajectories)):
|
|
308
|
-
df_tracking = pd.concat(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
628
|
+
df_tracking = pd.concat(
|
|
629
|
+
[df_tracking, list_dataframes_trajectories[i]],
|
|
630
|
+
ignore_index=True
|
|
631
|
+
)
|
|
632
|
+
df_tracking = df_tracking.reset_index(drop=True)
|
|
633
|
+
|
|
634
|
+
# Plot histograms for SNR
|
|
635
|
+
selected_field = 'snr'
|
|
636
|
+
plot_name_snr = results_folder.joinpath(f'spots_{selected_field}.png')
|
|
314
637
|
mean_snr = mi.Plots().plot_histograms_from_df(
|
|
315
638
|
df_tracking,
|
|
316
639
|
selected_field=selected_field,
|
|
@@ -321,9 +644,10 @@ def process_single_image(data_folder_path, selected_image, channels_spots, max_s
|
|
|
321
644
|
list_colors=channel_names,
|
|
322
645
|
remove_outliers=True
|
|
323
646
|
)
|
|
324
|
-
|
|
647
|
+
|
|
648
|
+
# Plot histograms for spot intensity
|
|
325
649
|
selected_field = 'spot_int'
|
|
326
|
-
plot_name_int = results_folder.joinpath('spots_
|
|
650
|
+
plot_name_int = results_folder.joinpath(f'spots_{selected_field}.png')
|
|
327
651
|
mean_int = mi.Plots().plot_histograms_from_df(
|
|
328
652
|
df_tracking,
|
|
329
653
|
selected_field=selected_field,
|
|
@@ -334,11 +658,12 @@ def process_single_image(data_folder_path, selected_image, channels_spots, max_s
|
|
|
334
658
|
list_colors=channel_names,
|
|
335
659
|
remove_outliers=True
|
|
336
660
|
)
|
|
661
|
+
|
|
337
662
|
# Remove tracks with low SNR in the tracking channel
|
|
338
663
|
if MINIMAL_SNR is not None:
|
|
339
664
|
array_selected_field = mi.Utilities().df_trajectories_to_array(
|
|
340
665
|
dataframe=df_tracking,
|
|
341
|
-
selected_field=selected_field
|
|
666
|
+
selected_field=f'{selected_field}_ch_{channels_spots[0]}',
|
|
342
667
|
fill_value='nans'
|
|
343
668
|
)
|
|
344
669
|
mean_snr = np.nanmean(array_selected_field, axis=1)
|
|
@@ -346,12 +671,13 @@ def process_single_image(data_folder_path, selected_image, channels_spots, max_s
|
|
|
346
671
|
df_tracking = df_tracking[~df_tracking['particle'].isin(indices_low_quality_tracks)]
|
|
347
672
|
df_tracking = df_tracking.reset_index(drop=True)
|
|
348
673
|
df_tracking['particle'] = df_tracking.groupby('particle').ngroup()
|
|
674
|
+
|
|
349
675
|
# Plot image intensity histogram
|
|
350
|
-
|
|
351
676
|
masked_data = corrected_image * masks[np.newaxis, np.newaxis, :, :, np.newaxis].astype(float)
|
|
352
|
-
for i in range(len(channels_spots)):
|
|
353
|
-
|
|
354
|
-
|
|
677
|
+
for i in range(len(channels_spots)):
|
|
678
|
+
plot_name_histogram = results_folder.joinpath(
|
|
679
|
+
f'pixel_histogram_in_cell_{channels_spots[i]}.png'
|
|
680
|
+
)
|
|
355
681
|
mi.Plots().plot_image_pixel_intensity_distribution(
|
|
356
682
|
image=np.mean(masked_data, axis=(0, 1)),
|
|
357
683
|
figsize=(8, 2),
|
|
@@ -365,8 +691,12 @@ def process_single_image(data_folder_path, selected_image, channels_spots, max_s
|
|
|
365
691
|
tracking_channel=channels_spots[0],
|
|
366
692
|
threshold_tracking=starting_threshold[i]
|
|
367
693
|
)
|
|
694
|
+
|
|
368
695
|
# Plot original image and tracks
|
|
369
|
-
suptitle =
|
|
696
|
+
suptitle = (
|
|
697
|
+
f'Image: {data_folder_path.stem[:16]} - '
|
|
698
|
+
f'{list_names[selected_image]} - Cell_ID: {selected_image}'
|
|
699
|
+
)
|
|
370
700
|
plot_name_original_image_and_tracks = results_folder.joinpath('original_image_tracking.png')
|
|
371
701
|
mi.Plots().plot_images(
|
|
372
702
|
image_ZYXC=corrected_image[0],
|
|
@@ -380,110 +710,220 @@ def process_single_image(data_folder_path, selected_image, channels_spots, max_s
|
|
|
380
710
|
use_maximum_projection=True,
|
|
381
711
|
use_gaussian_filter=True,
|
|
382
712
|
cmap='binary',
|
|
383
|
-
min_max_percentile=[
|
|
713
|
+
min_max_percentile=[DEFAULT_MIN_PERCENTILE_TRACKING, DEFAULT_MAX_PERCENTILE_TRACKING],
|
|
384
714
|
show_gird=False,
|
|
385
715
|
save_plots=True,
|
|
386
716
|
plot_name=plot_name_original_image_and_tracks
|
|
387
717
|
)
|
|
388
|
-
|
|
718
|
+
|
|
389
719
|
# Combine the original image and the image with tracks
|
|
390
720
|
plot_name_complete_image = results_folder.joinpath('complete_image_tracking.png')
|
|
391
|
-
mi.Utilities().combine_images_vertically(
|
|
721
|
+
mi.Utilities().combine_images_vertically(
|
|
722
|
+
[plot_name_original, plot_name_original_image_and_tracks],
|
|
723
|
+
plot_name_complete_image,
|
|
724
|
+
delete_originals=True
|
|
725
|
+
)
|
|
392
726
|
|
|
393
727
|
# Save the DataFrame
|
|
394
728
|
df_tracking.to_csv(results_folder.joinpath('tracking_results.csv'), index=False)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
729
|
+
|
|
730
|
+
# Prepare crop arrays for visualization
|
|
731
|
+
normalize_each_particle = True
|
|
732
|
+
crop_size = yx_spot_size_in_px + 5
|
|
399
733
|
if crop_size % 2 == 0:
|
|
400
734
|
crop_size += 1
|
|
401
735
|
selected_time_point = None
|
|
402
|
-
|
|
403
|
-
filtered_image = mi.Utilities().gaussian_laplace_filter_image(
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
736
|
+
|
|
737
|
+
filtered_image = mi.Utilities().gaussian_laplace_filter_image(
|
|
738
|
+
corrected_image, list_spot_size_px, list_voxels
|
|
739
|
+
)
|
|
740
|
+
croparray_filtered, mean_crop_filtered, first_appearance, crop_size = mi.CropArray(
|
|
741
|
+
image=filtered_image,
|
|
742
|
+
df_crops=df_tracking,
|
|
743
|
+
crop_size=crop_size,
|
|
744
|
+
remove_outliers=False,
|
|
745
|
+
max_percentile=DEFAULT_MAX_PERCENTILE_TRACKING,
|
|
746
|
+
selected_time_point=selected_time_point,
|
|
747
|
+
normalize_each_particle=normalize_each_particle
|
|
748
|
+
).run()
|
|
749
|
+
|
|
750
|
+
# Save crop array if requested
|
|
751
|
+
if save_croparray:
|
|
409
752
|
path_crop_array = results_folder.joinpath('crop_array.png')
|
|
410
|
-
mi.Plots().plot_croparray(
|
|
411
|
-
|
|
753
|
+
mi.Plots().plot_croparray(
|
|
754
|
+
croparray_filtered,
|
|
755
|
+
crop_size,
|
|
756
|
+
save_plots=True,
|
|
757
|
+
plot_name=path_crop_array,
|
|
758
|
+
suptitle=None,
|
|
759
|
+
show_particle_labels=True,
|
|
760
|
+
cmap='binary_r',
|
|
761
|
+
max_percentile=DEFAULT_MAX_CROP_PERCENTILE
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
# Plot pair of crops
|
|
412
765
|
plot_name_crops_filter = results_folder.joinpath('crops.png')
|
|
413
|
-
mi.Plots().plot_matrix_pair_crops
|
|
766
|
+
mi.Plots().plot_matrix_pair_crops(
|
|
767
|
+
mean_crop_filtered,
|
|
768
|
+
crop_size,
|
|
769
|
+
save_plots=True,
|
|
770
|
+
plot_name=plot_name_crops_filter
|
|
771
|
+
)
|
|
772
|
+
|
|
414
773
|
# Calculate the Mean Squared Displacement
|
|
415
774
|
plot_name_MSD = results_folder.joinpath('MSD_plot.png')
|
|
416
|
-
|
|
417
|
-
#max_lag_for_MSD = 30
|
|
775
|
+
|
|
418
776
|
if image_time_interval is None:
|
|
419
777
|
image_time_interval = step_size_in_sec
|
|
420
|
-
print(
|
|
778
|
+
print(
|
|
779
|
+
f'Warning: image_time_interval not provided. '
|
|
780
|
+
f'Using step_size_in_sec: {step_size_in_sec} seconds.'
|
|
781
|
+
)
|
|
421
782
|
else:
|
|
422
783
|
image_time_interval = float(image_time_interval)
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
784
|
+
|
|
785
|
+
# MSD and correlations require linked trajectories
|
|
786
|
+
if not link_particles:
|
|
787
|
+
if calculate_MSD or calculate_correlations:
|
|
788
|
+
print(
|
|
789
|
+
'Warning: MSD and correlation calculations require linked trajectories. '
|
|
790
|
+
'Skipping because link_particles=False.'
|
|
791
|
+
)
|
|
792
|
+
diffusion_coefficient = None
|
|
793
|
+
elif calculate_MSD:
|
|
794
|
+
# calculate_msd() returns: D_um2_s, D_px2_s, em_um2, em_px2, fit_times, fit_line_msd, trackpy_df
|
|
795
|
+
diffusion_coefficient, _, _, _, _, _, _ = mi.ParticleMotion(
|
|
796
|
+
df_tracking,
|
|
797
|
+
microns_per_pixel=pixel_xy_um,
|
|
798
|
+
step_size_in_sec=image_time_interval,
|
|
799
|
+
max_lagtime=max_lag_for_MSD,
|
|
800
|
+
show_plot=True,
|
|
801
|
+
remove_drift=False,
|
|
802
|
+
plot_name=plot_name_MSD
|
|
803
|
+
).calculate_msd()
|
|
433
804
|
else:
|
|
434
805
|
diffusion_coefficient = None
|
|
435
806
|
|
|
436
|
-
|
|
437
|
-
if calculate_correlations:
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
807
|
+
# Calculate correlations (requires linked trajectories)
|
|
808
|
+
if calculate_correlations and link_particles:
|
|
809
|
+
array_ch0 = mi.Utilities().df_trajectories_to_array(
|
|
810
|
+
dataframe=df_tracking,
|
|
811
|
+
selected_field='spot_int_ch_0',
|
|
812
|
+
fill_value='nans'
|
|
813
|
+
)
|
|
814
|
+
|
|
441
815
|
if 'spot_int_ch_1' in df_tracking.columns:
|
|
442
|
-
array_ch1= mi.Utilities().df_trajectories_to_array(
|
|
443
|
-
|
|
816
|
+
array_ch1 = mi.Utilities().df_trajectories_to_array(
|
|
817
|
+
dataframe=df_tracking,
|
|
818
|
+
selected_field='spot_int_ch_1',
|
|
819
|
+
fill_value='nans'
|
|
820
|
+
)
|
|
821
|
+
intensity_array_ch0_short, intensity_array_ch1_short = mi.Utilities().shift_trajectories(
|
|
822
|
+
array_ch0,
|
|
823
|
+
array_ch1,
|
|
824
|
+
max_percentage_empty_data_in_trajectory=max_percentage_empty_data_in_trajectory
|
|
825
|
+
)
|
|
444
826
|
else:
|
|
445
827
|
array_ch1 = None
|
|
446
|
-
intensity_array_ch0_short = mi.Utilities().shift_trajectories(
|
|
828
|
+
intensity_array_ch0_short = mi.Utilities().shift_trajectories(
|
|
829
|
+
array_ch0,
|
|
830
|
+
max_percentage_empty_data_in_trajectory=max_percentage_empty_data_in_trajectory
|
|
831
|
+
)
|
|
447
832
|
intensity_array_ch1_short = None
|
|
448
833
|
|
|
449
834
|
plot_name_intensity_matrix = results_folder.joinpath('intensity_matrix.png')
|
|
450
|
-
mi.Plots().plot_matrix_sample_time(
|
|
835
|
+
mi.Plots().plot_matrix_sample_time(
|
|
836
|
+
intensity_array_ch0_short,
|
|
837
|
+
intensity_array_ch1_short,
|
|
838
|
+
plot_name=plot_name_intensity_matrix
|
|
839
|
+
)
|
|
451
840
|
|
|
452
841
|
plot_name_AC_ch0 = results_folder.joinpath('AC_plot_ch0.png')
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
842
|
+
(
|
|
843
|
+
mean_correlation_ch0,
|
|
844
|
+
std_correlation_ch0,
|
|
845
|
+
lags_ch0,
|
|
846
|
+
correlations_array_ch0,
|
|
847
|
+
dwell_time_ch0
|
|
848
|
+
) = mi.Correlation(
|
|
849
|
+
primary_data=intensity_array_ch0_short,
|
|
850
|
+
max_lag=None,
|
|
851
|
+
nan_handling='ignore',
|
|
852
|
+
shift_data=True,
|
|
853
|
+
return_full=False,
|
|
854
|
+
time_interval_between_frames_in_seconds=image_time_interval,
|
|
855
|
+
show_plot=True,
|
|
856
|
+
start_lag=1,
|
|
857
|
+
fit_type='exponential',
|
|
858
|
+
use_linear_projection_for_lag_0=True,
|
|
859
|
+
save_plots=True,
|
|
860
|
+
plot_name=plot_name_AC_ch0
|
|
861
|
+
).run()
|
|
862
|
+
|
|
458
863
|
if array_ch1 is not None:
|
|
459
864
|
plot_name_AC_ch1 = results_folder.joinpath('AC_plot_ch1.png')
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
865
|
+
(
|
|
866
|
+
mean_correlation_ch1,
|
|
867
|
+
std_correlation_ch1,
|
|
868
|
+
lags_ch1,
|
|
869
|
+
correlations_array_ch1,
|
|
870
|
+
dwell_time_ch1
|
|
871
|
+
) = mi.Correlation(
|
|
872
|
+
primary_data=intensity_array_ch1_short,
|
|
873
|
+
max_lag=None,
|
|
874
|
+
nan_handling='ignore',
|
|
875
|
+
shift_data=True,
|
|
876
|
+
return_full=False,
|
|
877
|
+
time_interval_between_frames_in_seconds=image_time_interval,
|
|
878
|
+
show_plot=True,
|
|
879
|
+
start_lag=1,
|
|
880
|
+
fit_type='exponential',
|
|
881
|
+
use_linear_projection_for_lag_0=True,
|
|
882
|
+
save_plots=True,
|
|
883
|
+
plot_name=plot_name_AC_ch1
|
|
884
|
+
).run()
|
|
885
|
+
|
|
466
886
|
# Plot cross-correlation
|
|
467
887
|
plot_name_cross_correlation = results_folder.joinpath('cross_correlation.png')
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
888
|
+
(
|
|
889
|
+
mean_cross_correlation,
|
|
890
|
+
std_cross_correlation,
|
|
891
|
+
lags_cross_correlation,
|
|
892
|
+
cross_correlations_array,
|
|
893
|
+
max_lag
|
|
894
|
+
) = mi.Correlation(
|
|
895
|
+
primary_data=intensity_array_ch0_short,
|
|
896
|
+
secondary_data=intensity_array_ch1_short,
|
|
897
|
+
max_lag=None,
|
|
898
|
+
nan_handling='ignore',
|
|
899
|
+
shift_data=False,
|
|
900
|
+
return_full=True,
|
|
901
|
+
time_interval_between_frames_in_seconds=image_time_interval,
|
|
902
|
+
show_plot=True,
|
|
903
|
+
save_plots=True,
|
|
904
|
+
plot_name=plot_name_cross_correlation
|
|
905
|
+
).run()
|
|
473
906
|
|
|
474
|
-
#
|
|
907
|
+
# Plot 3D visualization if requested
|
|
475
908
|
if save_3d_visualization:
|
|
476
909
|
mask_expanded = masks[np.newaxis, np.newaxis, :, :, np.newaxis]
|
|
477
910
|
masked_image_TZYXC = filtered_image * mask_expanded
|
|
478
|
-
# Apply Gaussian filter to reduce background noise
|
|
479
|
-
#from scipy.ndimage import gaussian_filter
|
|
480
911
|
masked_image_TZYXC = gaussian_filter(masked_image_TZYXC, sigma=1)
|
|
481
|
-
|
|
482
|
-
|
|
912
|
+
masked_image_TZYXC = mi.RemoveExtrema(
|
|
913
|
+
masked_image_TZYXC,
|
|
914
|
+
min_percentile=0.001,
|
|
915
|
+
max_percentile=99.995
|
|
916
|
+
).remove_outliers()
|
|
483
917
|
plot_name_3d_visualizer = str(results_folder.joinpath('image_3d.gif'))
|
|
484
|
-
mi.Plots().Napari_Visualizer(
|
|
485
|
-
|
|
486
|
-
|
|
918
|
+
mi.Plots().Napari_Visualizer(
|
|
919
|
+
masked_image_TZYXC,
|
|
920
|
+
df_tracking,
|
|
921
|
+
z_correction=7,
|
|
922
|
+
channels_spots=0,
|
|
923
|
+
plot_name=plot_name_3d_visualizer
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
# Log completion
|
|
487
927
|
print(f'Image {list_names[selected_image]} has been processed.')
|
|
488
|
-
|
|
928
|
+
|
|
489
929
|
return df_tracking, masks, original_tested_image, diffusion_coefficient
|