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]:
([], [])
../_images/examples_3D_tracking_16_1.png
[ ]: