Tracking by overlaps
This example illustrates the usage of OverLapTrack API by tracking segmented cells by their overlaps.
[ ]:
%pip install -q --upgrade -r requirements.txt
Importing packages
laptrack.OverLapTrack is an extension of laptrack.LapTrack, which can be used for tracking labeled objects by overlaps.
We also import regionprops_table from skimage to calculate centroids of the segmentation masks.
[1]:
import napari
from skimage.measure import regionprops_table
from matplotlib import pyplot as plt
from itertools import product
import numpy as np
import pandas as pd
from laptrack import OverLapTrack
from laptrack import datasets
from laptrack.data_conversion import convert_split_merge_df_to_napari_graph
Loading data
Loading segmentation data and show it in the viewer.
Note: this data is generated by cropping segmentation.npy in https://github.com/NoneqPhysLivingMatterLab/cell_interaction_gnn .
[2]:
labels = datasets.mouse_epidermis()
[3]:
viewer = napari.Viewer()
viewer.add_labels(labels)
[3]:
<Labels layer 'labels' at 0x7fb241233c10>
Tracking
Tracking by overlap
OverLapTrack API can be used instead of LapTrack to track integer-labeled objects in a image. Note that predict_overlap_dataframe is used instead predict or predict_dataframe, which takes labeled images as the input instead of the coordinates.
For labeled regions with the label label1 and label2 at the frame frame1 < frame2, respectively, the cost function is calculated using four quantities:
overlap: the area of the intersection of the labeled regionsiou:overlapover the area of the union of the labeled regionsratio_1:overlapover the area of thelabel1regionratio_2:overlapover the area of thelabel2region.
For each connection (tracking, gap_closing, splitting and merging), the cost is calculated by:
offset + overlap_coef * overlap + iou_coef * iou + ratio_1_coef * ratio_1 + ratio_2_coef * ratio_2
The coefficients can be specified by a tuple of 5 floats of (offset, overlap_coef, iou_coef, ratio_1_coef, ratio_2_coef). For example, to use the 1-overlap for first tracking distance and 1-ratio_2 for splitting distance, one can write as follows:
OverLapTrack(
cutoff=0.9,
metric_coefs = (1.0, -1.0, 0.0, 0.0, 0.0),
gap_closing_metric_coefs = (1.0, -1.0, 0.0, 0.0, 0.0),
gap_closing_max_frame_count=1,
splitting_cutoff=0.9,
splitting_metric_coefs = (1.0, 0.0, 0.0, 0.0, -1.0),
)
[4]:
olt = OverLapTrack(
cutoff=0.9,
metric_coefs=(1.0, -1.0, 0.0, 0.0, 0.0),
gap_closing_metric_coefs=(1.0, -1.0, 0.0, 0.0, 0.0),
gap_closing_max_frame_count=1,
splitting_cutoff=0.9,
splitting_metric_coefs=(1.0, 0.0, 0.0, 0.0, -1.0),
)
track_df, split_df, merge_df = olt.predict_overlap_dataframe(labels)
[5]:
display(track_df.head())
display(split_df.head())
| tree_id | track_id | ||
|---|---|---|---|
| frame | label | ||
| 0 | 1 | 0 | 0 |
| 2 | 1 | 1 | |
| 3 | 2 | 2 | |
| 4 | 3 | 3 | |
| 5 | 4 | 4 |
| parent_track_id | child_track_id | |
|---|---|---|
| 0 | 11 | 134 |
| 1 | 11 | 132 |
| 2 | 15 | 135 |
| 3 | 15 | 133 |
| 4 | 42 | 138 |
Showing clonal cells by the same colors
To check the result, we show the clonal cell regions by the same colors.
[6]:
new_labels = np.zeros_like(labels)
for tree_id, grp in track_df.reset_index().groupby("tree_id"):
for _, row in grp.iterrows():
frame = int(row["frame"])
label = int(row["label"])
new_labels[frame][labels[frame] == label] = tree_id + 1
[7]:
viewer.layers["labels"].visible = False
viewer.add_labels(new_labels)
[7]:
<Labels layer 'new_labels' at 0x7fb250d453c0>
Adding tracks connecting the centroids
Create coordinate dataframe
Make the coordinate dataframe including the centroids to show the track data in the viewer.
[8]:
dfs = []
for frame in range(len(labels)):
df = pd.DataFrame(
regionprops_table(labels[frame], properties=["label", "centroid"])
)
df["frame"] = frame
dfs.append(df)
coordinate_df = pd.concat(dfs).set_index(["frame", "label"])
track_df2 = pd.merge(
track_df, coordinate_df, right_index=True, left_index=True
).reset_index()
display(track_df2.head())
| frame | label | tree_id | track_id | centroid-0 | centroid-1 | |
|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 0 | 148.526627 | 2.278107 |
| 1 | 0 | 2 | 1 | 1 | 108.118367 | 3.742857 |
| 2 | 0 | 3 | 2 | 2 | 62.957143 | 2.964286 |
| 3 | 0 | 4 | 3 | 3 | 249.611111 | 0.222222 |
| 4 | 0 | 5 | 4 | 4 | 5.279279 | 3.927928 |
To check the result, we add the tracks to to a Tracks layer.
[9]:
graph = convert_split_merge_df_to_napari_graph(split_df, merge_df)
viewer.add_tracks(
track_df2[["track_id", "frame", "centroid-0", "centroid-1"]].values,
graph=graph,
tail_length=1,
)
[9]:
<Tracks layer 'Tracks' at 0x7fb2457595d0>
Show screenshot
[10]:
viewer.dims.current_step = (4, 0, 0)
plt.imshow(viewer.screenshot())
plt.xticks([])
plt.yticks([])
[10]:
([], [])
[ ]: