{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Example of custom metric" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This example illustrates the usage of the custom distance metrics, by tracking segmented cells by their overlaps.\n", "\n", "**Note that a simpler API for overlap tracking is implemented** as OverLapTrack and illustrated [here](https://laptrack.readthedocs.io/examples/overlap_tracking.html). **This notebook uses the custom metric only for the purpose of illustration.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install -q --upgrade -r requirements.txt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Importing packages\n", "\n", "`laptrack.LapTrack` is the core object for tracking. \n", "\n", "We also import `regionprops_table` from `skimage` to calculate centroids of the segmentation masks. \n", "\n", "To calculate the label overlaps, we import `LabelOverlap` from `laptrack.metric_utils` (requires `laptrack>=0.9.0`)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import napari\n", "from skimage.measure import regionprops_table\n", "from itertools import product\n", "import numpy as np\n", "import pandas as pd\n", "from laptrack import LapTrack\n", "from laptrack import datasets\n", "from laptrack.metric_utils import LabelOverlap\n", "from laptrack.data_conversion import convert_split_merge_df_to_napari_graph" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Loading data " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Loading segmentation data and show it in the viewer.\n", "\n", "Note: this data is generated by cropping `segmentation.npy` in https://github.com/NoneqPhysLivingMatterLab/cell_interaction_gnn ." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "labels = datasets.mouse_epidermis()" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "viewer = napari.Viewer()\n", "viewer.add_labels(labels)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calculating segmentation overlaps " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`LabelOverlap` is an utility object to calculate the overlap between segmentation regions. " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "lo = LabelOverlap(labels)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n" ] } ], "source": [ "overlap_records = []\n", "for f in range(labels.shape[0] - 1):\n", " print(f)\n", " l1s = np.unique(labels[f])\n", " l1s = l1s[l1s != 0]\n", " l2s = np.unique(labels[f + 1])\n", " l2s = l2s[l2s != 0]\n", " for l1, l2 in product(l1s, l2s):\n", " overlap, iou, ratio_1, ratio_2 = lo.calc_overlap(f, l1, f + 1, l2)\n", " overlap_records.append(\n", " {\n", " \"frame\": f,\n", " \"label1\": l1,\n", " \"label2\": l2,\n", " \"overlap\": overlap,\n", " \"iou\": iou,\n", " \"ratio_1\": ratio_1,\n", " \"ratio_2\": ratio_2,\n", " }\n", " )\n", "overlap_df = pd.DataFrame.from_records(overlap_records)\n", "overlap_df = overlap_df[overlap_df[\"overlap\"] > 0]\n", "overlap_df = overlap_df.set_index([\"frame\", \"label1\", \"label2\"]).copy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`overlap_df` contains the overlap values between every label pairs. The paris with no overlap is ignored." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
overlapiouratio_1ratio_2
framelabel1label2
015880.2321900.5207100.295302
890.0171760.0532540.024725
13540.0919930.3195270.114407
21160.0506330.0653060.183908
81870.4431280.7632650.513736
\n", "
" ], "text/plain": [ " overlap iou ratio_1 ratio_2\n", "frame label1 label2 \n", "0 1 5 88 0.232190 0.520710 0.295302\n", " 8 9 0.017176 0.053254 0.024725\n", " 13 54 0.091993 0.319527 0.114407\n", " 2 1 16 0.050633 0.065306 0.183908\n", " 8 187 0.443128 0.763265 0.513736" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display(overlap_df.head())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tracking " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create coordinate dataframe" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Make the coordinate dataframe including `(frame, label)` to get the overlap data from `overlap_df` and the centroid to show the track data in the viewer." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
labelcentroid-0centroid-1frame
01148.5266272.2781070
12108.1183673.7428570
2362.9571432.9642860
34249.6111110.2222220
455.2792793.9279280
\n", "
" ], "text/plain": [ " label centroid-0 centroid-1 frame\n", "0 1 148.526627 2.278107 0\n", "1 2 108.118367 3.742857 0\n", "2 3 62.957143 2.964286 0\n", "3 4 249.611111 0.222222 0\n", "4 5 5.279279 3.927928 0" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "dfs = []\n", "for frame in range(len(labels)):\n", " df = pd.DataFrame(\n", " regionprops_table(labels[frame], properties=[\"label\", \"centroid\"])\n", " )\n", " df[\"frame\"] = frame\n", " dfs.append(df)\n", "coordinate_df = pd.concat(dfs)\n", "display(coordinate_df.head())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Define metric function " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is the core of this example: we can define an arbitrary function as the metric to measure how points are close.\n", "\n", "In this example, `metric` function retrive the pre-computed overlap between the segmented regions, and use\n", "\n", "$$\n", "1- \\frac{\\text{(label overlap between frame $t$ and $t+1$)}}{\\text{(area at frame $t+1$)}}\n", "$$\n", "\n", "as the \"distance\" between the points." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def metric(c1, c2):\n", " (frame1, label1), (frame2, label2) = c1, c2\n", " if frame1 == frame2 + 1:\n", " tmp = (frame1, label1)\n", " (frame1, label1) = (frame2, label2)\n", " (frame2, label2) = tmp\n", " assert frame1 + 1 == frame2\n", " ind = (frame1, label1, label2)\n", " if ind in overlap_df.index:\n", " ratio_2 = overlap_df.loc[ind][\"ratio_2\"]\n", " return 1 - ratio_2\n", " else:\n", " return 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Execute tracking " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The defined `metric` function is used for the frame-to-frame linking (`metric`), gap closing (`gap_closing_metric`) and the splitting connection (`splitting_metric`)." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "lt = LapTrack(\n", " metric=metric,\n", " cutoff=0.9,\n", " gap_closing_metric=metric,\n", " gap_closing_max_frame_count=1,\n", " splitting_metric=metric,\n", " splitting_cutoff=0.9,\n", ")" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "track_df, split_df, merge_df = lt.predict_dataframe(\n", " coordinate_df, coordinate_cols=[\"frame\", \"label\"], only_coordinate_cols=False\n", ")\n", "track_df = track_df.reset_index()" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
frameindexlabelcentroid-0centroid-1frame_ytree_idtrack_id
0001148.5266272.278107000
1012108.1183673.742857011
202362.9571432.964286022
3034249.6111110.222222033
40455.2792793.927928044
\n", "
" ], "text/plain": [ " frame index label centroid-0 centroid-1 frame_y tree_id track_id\n", "0 0 0 1 148.526627 2.278107 0 0 0\n", "1 0 1 2 108.118367 3.742857 0 1 1\n", "2 0 2 3 62.957143 2.964286 0 2 2\n", "3 0 3 4 249.611111 0.222222 0 3 3\n", "4 0 4 5 5.279279 3.927928 0 4 4" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
parent_track_idchild_track_id
01133
11132
211134
311137
414138
\n", "
" ], "text/plain": [ " parent_track_id child_track_id\n", "0 1 133\n", "1 1 132\n", "2 11 134\n", "3 11 137\n", "4 14 138" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display(track_df.head())\n", "display(split_df.head())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Showing clonal cells by the same colors\n", "\n", "To check the result, we show the clonal cell regions by the same colors." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "new_labels = np.zeros_like(labels)\n", "for tree_id, grp in track_df.groupby(\"tree_id\"):\n", " for _, row in grp.iterrows():\n", " frame = int(row[\"frame\"])\n", " label = int(row[\"label\"])\n", " new_labels[frame][labels[frame] == label] = tree_id + 1" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "viewer.layers[\"labels\"].visible = False\n", "viewer.add_labels(new_labels)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Adding tracks connecting the centroids" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To check the result, we add the tracks to to a `Tracks` layer." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "graph = convert_split_merge_df_to_napari_graph(split_df, merge_df)\n", "viewer.add_tracks(\n", " track_df[[\"track_id\", \"frame\", \"centroid-0\", \"centroid-1\"]].values,\n", " graph=graph,\n", " tail_length=1,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3.10.5 64-bit ('image_analysis')", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.11" }, "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "112c4654da1b8403c1d0efe0680f4afdb1a0816fe1d810c3856d3bd0e24d8e17" } } }, "nbformat": 4, "nbformat_minor": 2 }