diff --git a/exercise.ipynb b/exercise.ipynb index f3a2d8e..bf7219c 100644 --- a/exercise.ipynb +++ b/exercise.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "bd905588", + "id": "5e36aa81", "metadata": {}, "source": [ "# Exercise 9: Tracking-by-detection with an integer linear program (ILP)\n", @@ -32,7 +32,7 @@ }, { "cell_type": "markdown", - "id": "865d05b3", + "id": "164dedbf", "metadata": {}, "source": [ "## Import packages" @@ -41,7 +41,7 @@ { "cell_type": "code", "execution_count": null, - "id": "12036992", + "id": "ee0f4c71", "metadata": {}, "outputs": [], "source": [ @@ -52,7 +52,7 @@ { "cell_type": "code", "execution_count": null, - "id": "808c0823", + "id": "5c60788d", "metadata": {}, "outputs": [], "source": [ @@ -94,7 +94,7 @@ }, { "cell_type": "markdown", - "id": "6bbe41ce", + "id": "422cf013", "metadata": {}, "source": [ "## Load the dataset and inspect it in napari" @@ -102,7 +102,7 @@ }, { "cell_type": "markdown", - "id": "52284c99", + "id": "02ac8645", "metadata": {}, "source": [ "For this exercise we will be working with a fluorescence microscopy time-lapse of breast cancer cells with stained nuclei (SiR-DNA). It is similar to the dataset at https://zenodo.org/record/4034976#.YwZRCJPP1qt. The raw data, pre-computed segmentations, and detection probabilities are saved in a zarr, and the ground truth tracks are saved in a csv. The segmentation was generated with a pre-trained StartDist model, so there may be some segmentation errors which can affect the tracking process. The detection probabilities also come from StarDist, and are downsampled in x and y by 2 compared to the detections and raw data." @@ -111,7 +111,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c1a84cea", + "id": "d2bb7d88", "metadata": {}, "outputs": [], "source": [ @@ -124,7 +124,7 @@ }, { "cell_type": "markdown", - "id": "166433c4", + "id": "ebe4ca8a", "metadata": {}, "source": [ "## Task 1: Read in the ground truth graph\n", @@ -146,7 +146,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ab7eb9e9", + "id": "8e4c655a", "metadata": {}, "outputs": [], "source": [ @@ -160,7 +160,7 @@ }, { "cell_type": "markdown", - "id": "e431dc54", + "id": "a164b804", "metadata": {}, "source": [ "Let's use [napari](https://napari.org/tutorials/fundamentals/getting_started.html) to visualize the data. Napari is a wonderful viewer for imaging data that you can interact with in python, even directly out of jupyter notebooks. If you've never used napari, you might want to take a few minutes to go through [this tutorial](https://napari.org/stable/tutorials/fundamentals/viewer.html)." @@ -168,7 +168,7 @@ }, { "cell_type": "markdown", - "id": "f6ed5dd8", + "id": "cb146657", "metadata": {}, "source": [ "

Napari in a jupyter notebook:

\n", @@ -182,7 +182,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a305cd1d", + "id": "ee839564", "metadata": {}, "outputs": [], "source": [ @@ -200,7 +200,7 @@ { "cell_type": "code", "execution_count": null, - "id": "05d5371b", + "id": "dcbe2e43", "metadata": { "lines_to_next_cell": 2 }, @@ -213,7 +213,7 @@ }, { "cell_type": "markdown", - "id": "1c0eeeff", + "id": "b8145019", "metadata": {}, "source": [ "## Task 2: Build a candidate graph from the detections\n", @@ -224,7 +224,7 @@ }, { "cell_type": "markdown", - "id": "015bc217", + "id": "8cc7c4e2", "metadata": { "lines_to_next_cell": 2 }, @@ -239,7 +239,7 @@ { "cell_type": "code", "execution_count": null, - "id": "35bed8fa", + "id": "554d62dd", "metadata": {}, "outputs": [], "source": [ @@ -341,7 +341,7 @@ }, { "cell_type": "markdown", - "id": "a61a958a", + "id": "a26e28d1", "metadata": {}, "source": [ "## Checkpoint 1\n", @@ -353,7 +353,7 @@ }, { "cell_type": "markdown", - "id": "a378e7fa", + "id": "9a02be20", "metadata": {}, "source": [ "## Setting Up the Tracking Optimization Problem" @@ -361,7 +361,7 @@ }, { "cell_type": "markdown", - "id": "d23ce646", + "id": "fc6c06e6", "metadata": {}, "source": [ "As hinted earlier, our goal is to prune the candidate graph. More formally we want to find a graph $\\tilde{G}=(\\tilde{V}, \\tilde{E})$ whose vertices $\\tilde{V}$ are a subset of the candidate graph vertices $V$ and whose edges $\\tilde{E}$ are a subset of the candidate graph edges $E$.\n", @@ -376,7 +376,7 @@ }, { "cell_type": "markdown", - "id": "f702bc28", + "id": "c0642623", "metadata": {}, "source": [ "## Task 3 - Basic Tracking with Motile\n", @@ -388,7 +388,7 @@ { "cell_type": "code", "execution_count": null, - "id": "621dcef5", + "id": "ea88dbb4", "metadata": {}, "outputs": [], "source": [ @@ -412,7 +412,7 @@ }, { "cell_type": "markdown", - "id": "8064f68f", + "id": "c1a2d5a0", "metadata": {}, "source": [ "Here is a utility function to gauge some statistics of a solution." @@ -421,7 +421,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4452c5e2", + "id": "5fe6971a", "metadata": {}, "outputs": [], "source": [ @@ -448,7 +448,7 @@ }, { "cell_type": "markdown", - "id": "8da24e6b", + "id": "a2f10822", "metadata": {}, "source": [ "Here we actually run the optimization, and compare the found solution to the ground truth.\n", @@ -463,7 +463,7 @@ }, { "cell_type": "markdown", - "id": "92081ccd", + "id": "14e25aaf", "metadata": {}, "source": [ "## Visualize the Result" @@ -472,17 +472,17 @@ { "cell_type": "code", "execution_count": null, - "id": "9caa68a0", + "id": "595bbc1a", "metadata": {}, "outputs": [], "source": [ - "tracks_layer = to_napari_tracks_layer(solution, frame_key=\"time\", location_key=\"pos\", name=\"solution_tracks\")\n", + "tracks_layer = to_napari_tracks_layer(solution_graph, frame_key=\"time\", location_key=\"pos\", name=\"solution_tracks\")\n", "viewer.add_layer(tracks_layer)" ] }, { "cell_type": "markdown", - "id": "747a34d7", + "id": "0222c184", "metadata": { "lines_to_next_cell": 2 }, @@ -493,7 +493,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7c651153", + "id": "4f5dbddb", "metadata": {}, "outputs": [], "source": [ @@ -543,7 +543,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1103fd9f", + "id": "9ff4e25f", "metadata": { "lines_to_next_cell": 2 }, @@ -556,7 +556,7 @@ }, { "cell_type": "markdown", - "id": "509c8834", + "id": "5c507175", "metadata": { "lines_to_next_cell": 2 }, @@ -573,7 +573,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a63763f6", + "id": "550d19ba", "metadata": {}, "outputs": [], "source": [ @@ -619,7 +619,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9ab3820e", + "id": "d2fbe830", "metadata": {}, "outputs": [], "source": [ @@ -628,30 +628,109 @@ }, { "cell_type": "markdown", - "id": "3c7077f3", + "id": "d1cd0d7a", "metadata": {}, "source": [ - "## Task 4 - Tune your motile tracking pipeline\n", - "

Task 4: Tune your motile tracking pipeline

\n", - "

Now that you have ways to determine how good the output is, try adjusting your weights or using different combinations of Costs and Constraints to get better results. For now, stick to those implemented in `motile`, but consider what kinds of custom costs and constraints you could implement to improve performance, since that is what we will do next!

\n", + "## Task 4 - Add an appear cost, but not at the boundary\n", + "The [Appear](https://funkelab.github.io/motile/api.html#motile.costs.Appear_) cost penalizes starting a new track, encouraging continuous tracks. However, you do not want to penalize tracks that appear in the first frame. In our case, we probably also do not want to penalize appearing at the \"bottom\" of the dataset. The built in Appear cost has an `ignore_attribute` argument, where if the node has that attribute and it evaluates to True, the Appear cost will not be paid for that node.\n", + "\n", + "

Task 4: Add an appear cost, but not at the boundary

\n", + "

Add an attribute to the nodes of our candidate graph that is True if the appear cost should NOT be paid for that node, and False (or not present) otherwise. Then add an Appear cost to our motile pipeline using our new attribute as the `ignore_attribute` argument, and re-solve to see if performance improves.

\n", "
" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce7ad022", + "metadata": {}, + "outputs": [], + "source": [ + "def add_appear_ignore_attr(cand_graph):\n", + " ### YOUR CODE HERE ###\n", + " pass # delete this\n", + "\n", + "add_appear_ignore_attr(cand_graph)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98a3a0a2", + "metadata": {}, + "outputs": [], + "source": [ + "def solve_appear_optimization(graph, edge_weight, edge_constant):\n", + " \"\"\"Set up and solve the network flow problem.\n", + "\n", + " Args:\n", + " graph (motile.TrackGraph): The candidate graph.\n", + " edge_weight (float): The weighting factor of the edge selection cost.\n", + " edge_constant(float): The constant cost of selecting any edge.\n", + "\n", + " Returns:\n", + " motile.Solver: The solver object, ready to be inspected.\n", + " \"\"\"\n", + " solver = motile.Solver(graph)\n", + "\n", + " solver.add_costs(\n", + " motile.costs.EdgeDistance(weight=edge_weight, constant=edge_constant, position_attribute=\"pos\")\n", + " )\n", + " solver.add_costs(\n", + " motile.costs.Appear(constant=50, ignore_attribute=\"ignore_appear\") \n", + " )\n", + "\n", + " solver.add_constraints(motile.constraints.MaxParents(1))\n", + " solver.add_constraints(motile.constraints.MaxChildren(2))\n", + "\n", + " solution = solver.solve()\n", + "\n", + " return solver\n", + "\n", + "solver = solve_appear_optimization(cand_trackgraph, 1, -20)\n", + "solution_graph = graph_to_nx(solver.get_selected_subgraph())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c844952d", + "metadata": {}, + "outputs": [], + "source": [ + "tracks_layer = to_napari_tracks_layer(solution_graph, frame_key=\"time\", location_key=\"pos\", name=\"solution_appear_tracks\")\n", + "viewer.add_layer(tracks_layer)\n", + "solution_seg = relabel_segmentation(solution_graph, segmentation)\n", + "viewer.add_labels(solution_seg, name=\"solution_appear_seg\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eda28ca7", + "metadata": {}, + "outputs": [], + "source": [ + "get_metrics(gt_tracks, None, solution_graph, solution_seg)" + ] + }, { "cell_type": "markdown", - "id": "32fe4a87", + "id": "b544fe83", "metadata": {}, "source": [ "## Checkpoint 2\n", "

Checkpoint 2

\n", - "We have run an ILP to get tracks, visualized the output, evaluated the results, and tuned the pipeline to try and improve performance. When most people have reached this checkpoint, we will go around and\n", + "We have run an ILP to get tracks, visualized the output, evaluated the results, and added an Appear cost that does not take effect at the boundary. If you reach this Checkpoint early, try adjusting your weights or using different combinations of Costs and Constraints to get better results. For now, stick to those implemented in motile, but consider what kinds of custom costs and constraints you could implement to improve performance, since that is what we will do next!\n", + "\n", + "When most people have reached this checkpoint, we will go around and\n", "share what worked and what didn't, and discuss ideas for custom costs or constraints.\n", "
" ] }, { "cell_type": "markdown", - "id": "e076f8a2", + "id": "10842164", "metadata": {}, "source": [ "## Customizing the Tracking Task\n", @@ -664,7 +743,7 @@ }, { "cell_type": "markdown", - "id": "4a7d6340", + "id": "d0d7041f", "metadata": {}, "source": [ "# Task 5 - Incorporating Known Direction of Motion\n", @@ -679,20 +758,15 @@ { "cell_type": "code", "execution_count": null, - "id": "f62a3ebd", + "id": "b4ccf733", "metadata": {}, "outputs": [], "source": [ - "######################\n", - "### YOUR CODE HERE ###\n", - "######################\n", - "drift = # fill in this\n", + "drift = ... ### YOUR CODE HERE ###\n", "\n", "def add_drift_dist_attr(cand_graph, drift):\n", " for edge in cand_graph.edges():\n", - " ######################\n", " ### YOUR CODE HERE ###\n", - " ######################\n", " # get the location of the endpoints of the edge\n", " # then compute the distance between the expected movement and the actual movement\n", " # and save it in the \"drift_dist\" attribute (below)\n", @@ -705,7 +779,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0cde3676", + "id": "d75637ba", "metadata": {}, "outputs": [], "source": [ @@ -725,6 +799,9 @@ " solver.add_costs(\n", " motile.costs.EdgeSelection(weight=edge_weight, constant=edge_constant, attribute=\"drift_dist\")\n", " )\n", + " solver.add_costs(\n", + " motile.costs.Appear(constant=50, ignore_attribute=\"ignore_appear\") \n", + " )\n", "\n", " solver.add_constraints(motile.constraints.MaxParents(1))\n", " solver.add_constraints(motile.constraints.MaxChildren(2))\n", @@ -740,7 +817,7 @@ { "cell_type": "code", "execution_count": null, - "id": "361658ff", + "id": "c34be725", "metadata": {}, "outputs": [], "source": [ @@ -754,7 +831,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8a5ae14e", + "id": "f7e72f7d", "metadata": {}, "outputs": [], "source": [ @@ -763,7 +840,7 @@ }, { "cell_type": "markdown", - "id": "1d272ecf", + "id": "32a57d92", "metadata": {}, "source": [ "## Bonus: Learning the Weights" @@ -771,7 +848,7 @@ }, { "cell_type": "markdown", - "id": "e8d92d6c", + "id": "334774b1", "metadata": {}, "source": [] } diff --git a/solution.ipynb b/solution.ipynb index 4ea31a8..0033386 100644 --- a/solution.ipynb +++ b/solution.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "bd905588", + "id": "5e36aa81", "metadata": {}, "source": [ "# Exercise 9: Tracking-by-detection with an integer linear program (ILP)\n", @@ -32,7 +32,7 @@ }, { "cell_type": "markdown", - "id": "865d05b3", + "id": "164dedbf", "metadata": {}, "source": [ "## Import packages" @@ -41,7 +41,7 @@ { "cell_type": "code", "execution_count": null, - "id": "12036992", + "id": "ee0f4c71", "metadata": {}, "outputs": [], "source": [ @@ -52,7 +52,7 @@ { "cell_type": "code", "execution_count": null, - "id": "808c0823", + "id": "5c60788d", "metadata": {}, "outputs": [], "source": [ @@ -94,7 +94,7 @@ }, { "cell_type": "markdown", - "id": "6bbe41ce", + "id": "422cf013", "metadata": {}, "source": [ "## Load the dataset and inspect it in napari" @@ -102,7 +102,7 @@ }, { "cell_type": "markdown", - "id": "52284c99", + "id": "02ac8645", "metadata": {}, "source": [ "For this exercise we will be working with a fluorescence microscopy time-lapse of breast cancer cells with stained nuclei (SiR-DNA). It is similar to the dataset at https://zenodo.org/record/4034976#.YwZRCJPP1qt. The raw data, pre-computed segmentations, and detection probabilities are saved in a zarr, and the ground truth tracks are saved in a csv. The segmentation was generated with a pre-trained StartDist model, so there may be some segmentation errors which can affect the tracking process. The detection probabilities also come from StarDist, and are downsampled in x and y by 2 compared to the detections and raw data." @@ -111,7 +111,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c1a84cea", + "id": "d2bb7d88", "metadata": {}, "outputs": [], "source": [ @@ -124,7 +124,7 @@ }, { "cell_type": "markdown", - "id": "166433c4", + "id": "ebe4ca8a", "metadata": {}, "source": [ "## Task 1: Read in the ground truth graph\n", @@ -146,7 +146,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ab7eb9e9", + "id": "8e4c655a", "metadata": {}, "outputs": [], "source": [ @@ -161,7 +161,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fce30f6c", + "id": "654c4ab2", "metadata": { "tags": [ "solution" @@ -190,7 +190,7 @@ }, { "cell_type": "markdown", - "id": "e431dc54", + "id": "a164b804", "metadata": {}, "source": [ "Let's use [napari](https://napari.org/tutorials/fundamentals/getting_started.html) to visualize the data. Napari is a wonderful viewer for imaging data that you can interact with in python, even directly out of jupyter notebooks. If you've never used napari, you might want to take a few minutes to go through [this tutorial](https://napari.org/stable/tutorials/fundamentals/viewer.html)." @@ -198,7 +198,7 @@ }, { "cell_type": "markdown", - "id": "f6ed5dd8", + "id": "cb146657", "metadata": {}, "source": [ "

Napari in a jupyter notebook:

\n", @@ -212,7 +212,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a305cd1d", + "id": "ee839564", "metadata": {}, "outputs": [], "source": [ @@ -230,7 +230,7 @@ { "cell_type": "code", "execution_count": null, - "id": "05d5371b", + "id": "dcbe2e43", "metadata": { "lines_to_next_cell": 2 }, @@ -243,7 +243,7 @@ }, { "cell_type": "markdown", - "id": "1c0eeeff", + "id": "b8145019", "metadata": {}, "source": [ "## Task 2: Build a candidate graph from the detections\n", @@ -254,7 +254,7 @@ }, { "cell_type": "markdown", - "id": "015bc217", + "id": "8cc7c4e2", "metadata": { "lines_to_next_cell": 2 }, @@ -269,7 +269,7 @@ { "cell_type": "code", "execution_count": null, - "id": "35bed8fa", + "id": "554d62dd", "metadata": {}, "outputs": [], "source": [ @@ -371,7 +371,7 @@ }, { "cell_type": "markdown", - "id": "a61a958a", + "id": "a26e28d1", "metadata": {}, "source": [ "## Checkpoint 1\n", @@ -383,7 +383,7 @@ }, { "cell_type": "markdown", - "id": "a378e7fa", + "id": "9a02be20", "metadata": {}, "source": [ "## Setting Up the Tracking Optimization Problem" @@ -391,7 +391,7 @@ }, { "cell_type": "markdown", - "id": "d23ce646", + "id": "fc6c06e6", "metadata": {}, "source": [ "As hinted earlier, our goal is to prune the candidate graph. More formally we want to find a graph $\\tilde{G}=(\\tilde{V}, \\tilde{E})$ whose vertices $\\tilde{V}$ are a subset of the candidate graph vertices $V$ and whose edges $\\tilde{E}$ are a subset of the candidate graph edges $E$.\n", @@ -406,7 +406,7 @@ }, { "cell_type": "markdown", - "id": "f702bc28", + "id": "c0642623", "metadata": {}, "source": [ "## Task 3 - Basic Tracking with Motile\n", @@ -418,7 +418,7 @@ { "cell_type": "code", "execution_count": null, - "id": "621dcef5", + "id": "ea88dbb4", "metadata": {}, "outputs": [], "source": [ @@ -443,7 +443,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ed591089", + "id": "f203f775", "metadata": { "tags": [ "solution" @@ -465,7 +465,7 @@ " solver = motile.Solver(graph)\n", "\n", " solver.add_costs(\n", - " motile.costs.EdgeDistance(weight=edge_weight, constant=edge_constant, position_attribute=\"pos\") # Adapt this weight\n", + " motile.costs.EdgeDistance(weight=edge_weight, constant=edge_constant, position_attribute=\"pos\")\n", " )\n", "\n", " solver.add_constraints(motile.constraints.MaxParents(1))\n", @@ -478,7 +478,7 @@ }, { "cell_type": "markdown", - "id": "8064f68f", + "id": "c1a2d5a0", "metadata": {}, "source": [ "Here is a utility function to gauge some statistics of a solution." @@ -487,7 +487,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4452c5e2", + "id": "5fe6971a", "metadata": {}, "outputs": [], "source": [ @@ -514,7 +514,7 @@ }, { "cell_type": "markdown", - "id": "8da24e6b", + "id": "a2f10822", "metadata": {}, "source": [ "Here we actually run the optimization, and compare the found solution to the ground truth.\n", @@ -530,7 +530,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8b0c1d72", + "id": "ee6ab309", "metadata": { "lines_to_next_cell": 2, "tags": [ @@ -558,7 +558,7 @@ }, { "cell_type": "markdown", - "id": "92081ccd", + "id": "14e25aaf", "metadata": {}, "source": [ "## Visualize the Result" @@ -567,17 +567,17 @@ { "cell_type": "code", "execution_count": null, - "id": "9caa68a0", + "id": "595bbc1a", "metadata": {}, "outputs": [], "source": [ - "tracks_layer = to_napari_tracks_layer(solution, frame_key=\"time\", location_key=\"pos\", name=\"solution_tracks\")\n", + "tracks_layer = to_napari_tracks_layer(solution_graph, frame_key=\"time\", location_key=\"pos\", name=\"solution_tracks\")\n", "viewer.add_layer(tracks_layer)" ] }, { "cell_type": "markdown", - "id": "747a34d7", + "id": "0222c184", "metadata": { "lines_to_next_cell": 2 }, @@ -588,7 +588,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7c651153", + "id": "4f5dbddb", "metadata": {}, "outputs": [], "source": [ @@ -638,7 +638,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1103fd9f", + "id": "9ff4e25f", "metadata": { "lines_to_next_cell": 2 }, @@ -651,7 +651,7 @@ }, { "cell_type": "markdown", - "id": "509c8834", + "id": "5c507175", "metadata": { "lines_to_next_cell": 2 }, @@ -668,7 +668,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a63763f6", + "id": "550d19ba", "metadata": {}, "outputs": [], "source": [ @@ -714,7 +714,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9ab3820e", + "id": "d2fbe830", "metadata": {}, "outputs": [], "source": [ @@ -723,30 +723,131 @@ }, { "cell_type": "markdown", - "id": "3c7077f3", + "id": "d1cd0d7a", "metadata": {}, "source": [ - "## Task 4 - Tune your motile tracking pipeline\n", - "

Task 4: Tune your motile tracking pipeline

\n", - "

Now that you have ways to determine how good the output is, try adjusting your weights or using different combinations of Costs and Constraints to get better results. For now, stick to those implemented in `motile`, but consider what kinds of custom costs and constraints you could implement to improve performance, since that is what we will do next!

\n", + "## Task 4 - Add an appear cost, but not at the boundary\n", + "The [Appear](https://funkelab.github.io/motile/api.html#motile.costs.Appear_) cost penalizes starting a new track, encouraging continuous tracks. However, you do not want to penalize tracks that appear in the first frame. In our case, we probably also do not want to penalize appearing at the \"bottom\" of the dataset. The built in Appear cost has an `ignore_attribute` argument, where if the node has that attribute and it evaluates to True, the Appear cost will not be paid for that node.\n", + "\n", + "

Task 4: Add an appear cost, but not at the boundary

\n", + "

Add an attribute to the nodes of our candidate graph that is True if the appear cost should NOT be paid for that node, and False (or not present) otherwise. Then add an Appear cost to our motile pipeline using our new attribute as the `ignore_attribute` argument, and re-solve to see if performance improves.

\n", "
" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce7ad022", + "metadata": {}, + "outputs": [], + "source": [ + "def add_appear_ignore_attr(cand_graph):\n", + " ### YOUR CODE HERE ###\n", + " pass # delete this\n", + "\n", + "add_appear_ignore_attr(cand_graph)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23b4d5fa", + "metadata": { + "tags": [ + "solution" + ] + }, + "outputs": [], + "source": [ + "def add_appear_ignore_attr(cand_graph):\n", + " for node in cand_graph.nodes():\n", + " time = cand_graph.nodes[node][\"time\"]\n", + " pos_x = cand_graph.nodes[node][\"pos\"][0]\n", + " if time == 0 or pos_x >= 710:\n", + " cand_graph.nodes[node][\"ignore_appear\"] = True\n", + "\n", + "add_appear_ignore_attr(cand_graph)\n", + "cand_trackgraph = motile.TrackGraph(cand_graph, frame_attribute=\"time\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98a3a0a2", + "metadata": {}, + "outputs": [], + "source": [ + "def solve_appear_optimization(graph, edge_weight, edge_constant):\n", + " \"\"\"Set up and solve the network flow problem.\n", + "\n", + " Args:\n", + " graph (motile.TrackGraph): The candidate graph.\n", + " edge_weight (float): The weighting factor of the edge selection cost.\n", + " edge_constant(float): The constant cost of selecting any edge.\n", + "\n", + " Returns:\n", + " motile.Solver: The solver object, ready to be inspected.\n", + " \"\"\"\n", + " solver = motile.Solver(graph)\n", + "\n", + " solver.add_costs(\n", + " motile.costs.EdgeDistance(weight=edge_weight, constant=edge_constant, position_attribute=\"pos\")\n", + " )\n", + " solver.add_costs(\n", + " motile.costs.Appear(constant=50, ignore_attribute=\"ignore_appear\") \n", + " )\n", + "\n", + " solver.add_constraints(motile.constraints.MaxParents(1))\n", + " solver.add_constraints(motile.constraints.MaxChildren(2))\n", + "\n", + " solution = solver.solve()\n", + "\n", + " return solver\n", + "\n", + "solver = solve_appear_optimization(cand_trackgraph, 1, -20)\n", + "solution_graph = graph_to_nx(solver.get_selected_subgraph())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c844952d", + "metadata": {}, + "outputs": [], + "source": [ + "tracks_layer = to_napari_tracks_layer(solution_graph, frame_key=\"time\", location_key=\"pos\", name=\"solution_appear_tracks\")\n", + "viewer.add_layer(tracks_layer)\n", + "solution_seg = relabel_segmentation(solution_graph, segmentation)\n", + "viewer.add_labels(solution_seg, name=\"solution_appear_seg\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eda28ca7", + "metadata": {}, + "outputs": [], + "source": [ + "get_metrics(gt_tracks, None, solution_graph, solution_seg)" + ] + }, { "cell_type": "markdown", - "id": "32fe4a87", + "id": "b544fe83", "metadata": {}, "source": [ "## Checkpoint 2\n", "

Checkpoint 2

\n", - "We have run an ILP to get tracks, visualized the output, evaluated the results, and tuned the pipeline to try and improve performance. When most people have reached this checkpoint, we will go around and\n", + "We have run an ILP to get tracks, visualized the output, evaluated the results, and added an Appear cost that does not take effect at the boundary. If you reach this Checkpoint early, try adjusting your weights or using different combinations of Costs and Constraints to get better results. For now, stick to those implemented in motile, but consider what kinds of custom costs and constraints you could implement to improve performance, since that is what we will do next!\n", + "\n", + "When most people have reached this checkpoint, we will go around and\n", "share what worked and what didn't, and discuss ideas for custom costs or constraints.\n", "
" ] }, { "cell_type": "markdown", - "id": "e076f8a2", + "id": "10842164", "metadata": {}, "source": [ "## Customizing the Tracking Task\n", @@ -759,7 +860,7 @@ }, { "cell_type": "markdown", - "id": "4a7d6340", + "id": "d0d7041f", "metadata": {}, "source": [ "# Task 5 - Incorporating Known Direction of Motion\n", @@ -774,20 +875,15 @@ { "cell_type": "code", "execution_count": null, - "id": "f62a3ebd", + "id": "b4ccf733", "metadata": {}, "outputs": [], "source": [ - "######################\n", - "### YOUR CODE HERE ###\n", - "######################\n", - "drift = # fill in this\n", + "drift = ... ### YOUR CODE HERE ###\n", "\n", "def add_drift_dist_attr(cand_graph, drift):\n", " for edge in cand_graph.edges():\n", - " ######################\n", " ### YOUR CODE HERE ###\n", - " ######################\n", " # get the location of the endpoints of the edge\n", " # then compute the distance between the expected movement and the actual movement\n", " # and save it in the \"drift_dist\" attribute (below)\n", @@ -800,7 +896,7 @@ { "cell_type": "code", "execution_count": null, - "id": "568dfd13", + "id": "f04a85d0", "metadata": { "tags": [ "solution" @@ -826,7 +922,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0cde3676", + "id": "d75637ba", "metadata": {}, "outputs": [], "source": [ @@ -846,6 +942,9 @@ " solver.add_costs(\n", " motile.costs.EdgeSelection(weight=edge_weight, constant=edge_constant, attribute=\"drift_dist\")\n", " )\n", + " solver.add_costs(\n", + " motile.costs.Appear(constant=50, ignore_attribute=\"ignore_appear\") \n", + " )\n", "\n", " solver.add_constraints(motile.constraints.MaxParents(1))\n", " solver.add_constraints(motile.constraints.MaxChildren(2))\n", @@ -861,7 +960,7 @@ { "cell_type": "code", "execution_count": null, - "id": "361658ff", + "id": "c34be725", "metadata": {}, "outputs": [], "source": [ @@ -875,7 +974,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8a5ae14e", + "id": "f7e72f7d", "metadata": {}, "outputs": [], "source": [ @@ -884,7 +983,7 @@ }, { "cell_type": "markdown", - "id": "1d272ecf", + "id": "32a57d92", "metadata": {}, "source": [ "## Bonus: Learning the Weights" @@ -892,7 +991,7 @@ }, { "cell_type": "markdown", - "id": "e8d92d6c", + "id": "334774b1", "metadata": {}, "source": [] }