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
:overlap
over the area of the union of the labeled regionsratio_1
:overlap
over the area of thelabel1
regionratio_2
:overlap
over the area of thelabel2
region.
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(
track_cost_cutoff=0.9,
track_dist_metric_coefs = (1.0, -1.0, 0.0, 0.0, 0.0),
gap_closing_dist_metric_coefs = (1.0, -1.0, 0.0, 0.0, 0.0),
gap_closing_max_frame_count=1,
splitting_cost_cutoff=0.9,
splitting_dist_metric_coefs = (1.0, 0.0, 0.0, 0.0, -1.0),
)
[4]:
olt = OverLapTrack(
track_cost_cutoff=0.9,
track_dist_metric_coefs=(1.0, -1.0, 0.0, 0.0, 0.0),
gap_closing_dist_metric_coefs=(1.0, -1.0, 0.0, 0.0, 0.0),
gap_closing_max_frame_count=1,
splitting_cost_cutoff=0.9,
splitting_dist_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]:
([], [])
[ ]: