Tracking 3D object by their centroids
[ ]:
%pip install -q --upgrade -r requirements.txt
Importing packages
laptrack.LapTrack
is the core object for tracking.
[1]:
import napari
import numpy as np
import pandas as pd
from skimage.measure import regionprops_table
from matplotlib import pyplot as plt
from laptrack import LapTrack
from laptrack import datasets
from laptrack import data_conversion
Intel MKL WARNING: Support of Intel(R) Streaming SIMD Extensions 4.2 (Intel(R) SSE4.2) enabled only processors has been deprecated. Intel oneAPI Math Kernel Library 2025.0 will require Intel(R) Advanced Vector Extensions (Intel(R) AVX) instructions.
Loading images and showing them in napari
[2]:
viewer = napari.Viewer(ndisplay=3)
images, labels = datasets.HL60_3D_synthesized()
viewer.add_image(images)
viewer.add_labels(labels)
[2]:
<Labels layer 'labels' at 0x7f9ce0c2ca90>
Calculate centroids of the labels
[3]:
dfs = []
for frame, l in enumerate(labels):
dfs.append(
pd.DataFrame(regionprops_table(l, properties=["label", "centroid"])).assign(
frame=frame
)
)
regionprops_df = pd.concat(dfs, ignore_index=True)
regionprops_df.rename(
columns={"centroid-0": "z", "centroid-1": "y", "centroid-2": "x"}, inplace=True
)
regionprops_df.head()
[3]:
label | z | y | x | frame | |
---|---|---|---|---|---|
0 | 3 | 8.576642 | 28.700730 | 46.386861 | 0 |
1 | 4 | 8.557971 | 26.311594 | 53.833333 | 0 |
2 | 5 | 9.209524 | 28.704762 | 33.990476 | 0 |
3 | 7 | 8.666667 | 34.642276 | 38.333333 | 0 |
4 | 9 | 8.830189 | 25.452830 | 17.273585 | 0 |
[4]:
viewer.add_points(
regionprops_df[["frame", "z", "y", "x"]],
size=2,
name="centroid",
blending="additive",
)
[4]:
<Points layer 'centroid' at 0x7f9ce27618a0>
Track centroid
First, initialize the LapTrack
object with parameters.
[5]:
max_distance = 15
lt = LapTrack(
track_dist_metric="sqeuclidean", # The similarity metric for particles. See `scipy.spatial.distance.cdist` for allowed values.
splitting_dist_metric="sqeuclidean", # The similarity metric for splits.
# the square of the cutoff distance for the "sqeuclidean" metric
track_cost_cutoff=max_distance**2,
gap_closing_cost_cutoff=max_distance**2,
splitting_cost_cutoff=max_distance**2,
)
[6]:
track_df, split_df, merge_df = lt.predict_dataframe(
regionprops_df,
coordinate_cols=["z", "y", "x"], # the column names for the coordinates
frame_col="frame", # the column name for the frame (default "frame")
only_coordinate_cols=False,
)
track_df = track_df.reset_index()
track_id : unique ID for each track
tree_id : unique ID for each “clonal” set of track (track sharing the same ancestor)
[7]:
track_df.head()
[7]:
frame | index | label | z | y | x | frame_y | tree_id | track_id | |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 3 | 8.576642 | 28.700730 | 46.386861 | 0 | 0 | 0 |
1 | 0 | 1 | 4 | 8.557971 | 26.311594 | 53.833333 | 0 | 1 | 1 |
2 | 0 | 2 | 5 | 9.209524 | 28.704762 | 33.990476 | 0 | 2 | 2 |
3 | 0 | 3 | 7 | 8.666667 | 34.642276 | 38.333333 | 0 | 3 | 3 |
4 | 0 | 4 | 9 | 8.830189 | 25.452830 | 17.273585 | 0 | 4 | 4 |
[8]:
graph = data_conversion.convert_split_merge_df_to_napari_graph(split_df, merge_df)
viewer.add_tracks(track_df[["track_id", "frame", "z", "y", "x"]], graph=graph)
[8]:
<Tracks layer 'Tracks' at 0x7f9ce246beb0>
[10]:
viewer.dims.current_step = (74, 0, 0, 0)
plt.imshow(viewer.screenshot())
plt.xticks([])
plt.yticks([])
[10]:
([], [])
[ ]: