From f75e60d5e3ce829cf38b4f09a95d5b39f2748297 Mon Sep 17 00:00:00 2001 From: lmanan Date: Sat, 13 Apr 2024 21:05:07 -0400 Subject: [PATCH 01/10] Specify python version to be `3.10` onwards. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8706b10..be6b317 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = ["setuptools", "wheel"] name = "motile_toolbox" description = "A toolbox for tracking with (motile)[https://github.com/funkelab/motile]." readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.10" classifiers = [ "Programming Language :: Python :: 3", ] From 6c27a49743524530421ec2092d1f1af5329bdf1d Mon Sep 17 00:00:00 2001 From: lmanan Date: Sat, 13 Apr 2024 21:05:49 -0400 Subject: [PATCH 02/10] Obtain `motile` from github. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index be6b317..c7b41e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ authors = [ ] dynamic = ["version"] dependencies = [ - "motile", + "motile @git+https://github.com/funkelab/motile.git", "networkx", "numpy", "matplotlib", From 210371fadeab6d25b66bf96347b03f323996cff0 Mon Sep 17 00:00:00 2001 From: lmanan Date: Sat, 13 Apr 2024 21:06:13 -0400 Subject: [PATCH 03/10] Add `BBOX` and `FLOW` as node attributes, by default. --- src/motile_toolbox/candidate_graph/graph_attributes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/motile_toolbox/candidate_graph/graph_attributes.py b/src/motile_toolbox/candidate_graph/graph_attributes.py index 478c2b3..a3d5faa 100644 --- a/src/motile_toolbox/candidate_graph/graph_attributes.py +++ b/src/motile_toolbox/candidate_graph/graph_attributes.py @@ -11,6 +11,8 @@ class NodeAttr(Enum): TIME = "time" SEG_ID = "seg_id" SEG_HYPO = "seg_hypo" + BBOX = "bbox" + FLOW = "flow" class EdgeAttr(Enum): From 5eed914a141ff5bb8c2a103cd26709002fa0460e Mon Sep 17 00:00:00 2001 From: lmanan Date: Sat, 13 Apr 2024 21:07:13 -0400 Subject: [PATCH 04/10] Specify zero flow by default, and bounding box using regionprops. --- src/motile_toolbox/candidate_graph/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/motile_toolbox/candidate_graph/utils.py b/src/motile_toolbox/candidate_graph/utils.py index 520d471..ed5a736 100644 --- a/src/motile_toolbox/candidate_graph/utils.py +++ b/src/motile_toolbox/candidate_graph/utils.py @@ -71,7 +71,10 @@ def nodes_from_segmentation( if hypo_id is not None: attrs[NodeAttr.SEG_HYPO.value] = hypo_id centroid = regionprop.centroid # [z,] y, x + bbox = regionprop.bbox attrs[NodeAttr.POS.value] = centroid + attrs[NodeAttr.BBOX.value] = bbox + attrs[NodeAttr.FLOW.value] = (0,) * len(centroid) cand_graph.add_node(node_id, **attrs) nodes_in_frame.append(node_id) if nodes_in_frame: From faa5f5ff63fc1f8b52af14c1b6940b38d494fdee Mon Sep 17 00:00:00 2001 From: lmanan Date: Sat, 13 Apr 2024 21:07:48 -0400 Subject: [PATCH 05/10] Compare only position attributes, while comparing nodes. --- .../test_compute_graph.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/test_candidate_graph/test_compute_graph.py b/tests/test_candidate_graph/test_compute_graph.py index eaeb241..da4a265 100644 --- a/tests/test_candidate_graph/test_compute_graph.py +++ b/tests/test_candidate_graph/test_compute_graph.py @@ -1,7 +1,7 @@ from collections import Counter import pytest -from motile_toolbox.candidate_graph import EdgeAttr, get_candidate_graph +from motile_toolbox.candidate_graph import EdgeAttr, NodeAttr, get_candidate_graph def test_graph_from_segmentation_2d(segmentation_2d, graph_2d): @@ -14,7 +14,11 @@ def test_graph_from_segmentation_2d(segmentation_2d, graph_2d): assert Counter(list(cand_graph.nodes)) == Counter(list(graph_2d.nodes)) assert Counter(list(cand_graph.edges)) == Counter(list(graph_2d.edges)) for node in cand_graph.nodes: - assert Counter(cand_graph.nodes[node]) == Counter(graph_2d.nodes[node]) + assert ( + pytest.approx(cand_graph.nodes[node][NodeAttr.POS.value], abs=0.01) + == graph_2d.nodes[node][NodeAttr.POS.value] + ) + for edge in cand_graph.edges: print(cand_graph.edges[edge]) assert ( @@ -46,8 +50,13 @@ def test_graph_from_segmentation_3d(segmentation_3d, graph_3d): ) assert Counter(list(cand_graph.nodes)) == Counter(list(graph_3d.nodes)) assert Counter(list(cand_graph.edges)) == Counter(list(graph_3d.edges)) + for node in cand_graph.nodes: - assert Counter(cand_graph.nodes[node]) == Counter(graph_3d.nodes[node]) + assert ( + pytest.approx(cand_graph.nodes[node][NodeAttr.POS.value], abs=0.01) + == graph_3d.nodes[node][NodeAttr.POS.value] + ) + for edge in cand_graph.edges: assert pytest.approx(cand_graph.edges[edge], abs=0.01) == graph_3d.edges[edge] @@ -68,9 +77,11 @@ def test_graph_from_multi_segmentation_2d( list(multi_hypothesis_graph_2d.edges) ) for node in cand_graph.nodes: - assert Counter(cand_graph.nodes[node]) == Counter( - multi_hypothesis_graph_2d.nodes[node] + assert ( + pytest.approx(cand_graph.nodes[node][NodeAttr.POS.value], abs=0.01) + == multi_hypothesis_graph_2d.nodes[node][NodeAttr.POS.value] ) + for edge in cand_graph.edges: assert ( pytest.approx(cand_graph.edges[edge][EdgeAttr.DISTANCE.value], abs=0.01) From 77d2a854d1b98fec73089899593fff7682c50295 Mon Sep 17 00:00:00 2001 From: lmanan Date: Sat, 13 Apr 2024 21:12:47 -0400 Subject: [PATCH 06/10] Add flow calculation using phase cross correlation. --- src/motile_toolbox/utils/flow.py | 77 ++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/motile_toolbox/utils/flow.py diff --git a/src/motile_toolbox/utils/flow.py b/src/motile_toolbox/utils/flow.py new file mode 100644 index 0000000..12847b7 --- /dev/null +++ b/src/motile_toolbox/utils/flow.py @@ -0,0 +1,77 @@ +import math + +import networkx as nx +import numpy as np +from skimage.registration import phase_cross_correlation + +from motile_toolbox.candidate_graph.graph_attributes import EdgeAttr, NodeAttr + + +def compute_pcc_flow(candidate_graph: nx.DiGraph, images: np.ndarray): + """This calculates the flow for the image cropped around an object + at `t` and the same region of interest at `t+1`, and updates the + `NodeAttr.FLOW`. + + Args: + candidate_graph (nx.DiGraph): Existing candidate graph with nodes. + + images (np.ndarray): Raw images (t, c, [z], y, x). + + """ + for node in candidate_graph.nodes(data=True): + frame = node[1][NodeAttr.TIME.value] + if frame + 1 >= len(images): + continue + loc = node[1][NodeAttr.POS.value] + bbox = node[1][NodeAttr.BBOX.value] + if len(loc) == 2: + reference_image = images[frame][ + 0, bbox[0] : bbox[2] + 1, bbox[1] : bbox[3] + 1 + ] + shifted_image = images[frame + 1][ + 0, bbox[0] : bbox[2] + 1, bbox[1] : bbox[3] + 1 + ] + elif len(loc) == 3: + reference_image = ( + images[frame][ + 0, + bbox[0] : bbox[3] + 1, + bbox[1] : bbox[4] + 1, + bbox[2] : bbox[5] + 1, + ], + ) + shifted_image = images[frame + 1][ + 0, + bbox[0] : bbox[3] + 1, + bbox[1] : bbox[4] + 1, + bbox[2] : bbox[5] + 1, + ] + shift, _, _ = phase_cross_correlation(reference_image, shifted_image) + node[1][NodeAttr.FLOW.value] = shift + + +def correct_edge_distance(candidate_graph: nx.DiGraph): + """This corrects for the edge distance in case the flow at a segmentation + node is available. The EdgeAttr.DISTANCE.value is set equal to + the L2 norm of (pos@t+1 - (flow + pos@t). + + + Args: + candidate_graph (nx.DiGraph): Existing candidate graph with nodes and + edges. + + Returns: + candidate_graph (nx.DiGraph): Updated candidate graph. (Edge + distance attribute is updated, by taking flow into account). + + """ + for edge in candidate_graph.edges(data=True): + in_node = candidate_graph.nodes[edge[0]] + out_node = candidate_graph.nodes[edge[1]] + dist = math.dist( + out_node[NodeAttr.POS.value], + in_node[NodeAttr.POS.value] + in_node[NodeAttr.FLOW.value], + ) + edge[2][EdgeAttr.DISTANCE.value] = dist + + return candidate_graph From 7ed84bb81fc9e13965cd1b9ead0766b1bc255a8b Mon Sep 17 00:00:00 2001 From: lmanan Date: Sat, 13 Apr 2024 21:43:29 -0400 Subject: [PATCH 07/10] Update docstring. --- src/motile_toolbox/utils/flow.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/motile_toolbox/utils/flow.py b/src/motile_toolbox/utils/flow.py index 12847b7..8392231 100644 --- a/src/motile_toolbox/utils/flow.py +++ b/src/motile_toolbox/utils/flow.py @@ -8,9 +8,10 @@ def compute_pcc_flow(candidate_graph: nx.DiGraph, images: np.ndarray): - """This calculates the flow for the image cropped around an object - at `t` and the same region of interest at `t+1`, and updates the - `NodeAttr.FLOW`. + """This calculates the flow using phase cross correlation + for the image cropped around an object + at `t` and the same region of interest at `t+1`, + and updates the `NodeAttr.FLOW`. Args: candidate_graph (nx.DiGraph): Existing candidate graph with nodes. From f31144bb7758893c835f7c54850b36b52ef8330b Mon Sep 17 00:00:00 2001 From: lmanan Date: Sat, 13 Apr 2024 21:44:00 -0400 Subject: [PATCH 08/10] Avoid setting the flow attribute by default. --- src/motile_toolbox/candidate_graph/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/motile_toolbox/candidate_graph/utils.py b/src/motile_toolbox/candidate_graph/utils.py index ed5a736..7c7667f 100644 --- a/src/motile_toolbox/candidate_graph/utils.py +++ b/src/motile_toolbox/candidate_graph/utils.py @@ -74,7 +74,6 @@ def nodes_from_segmentation( bbox = regionprop.bbox attrs[NodeAttr.POS.value] = centroid attrs[NodeAttr.BBOX.value] = bbox - attrs[NodeAttr.FLOW.value] = (0,) * len(centroid) cand_graph.add_node(node_id, **attrs) nodes_in_frame.append(node_id) if nodes_in_frame: From e4805863892cc4fef52624c15844523b4efcd64b Mon Sep 17 00:00:00 2001 From: Caroline Malin-Mayor Date: Tue, 14 May 2024 15:11:11 +0200 Subject: [PATCH 09/10] Add pypi workflow --- .github/workflows/deploy.yaml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/deploy.yaml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..8d3df16 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,29 @@ +name: Deploy + +on: + push: + tags: ["*"] + workflow_dispatch: + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Build a binary wheel and a source tarball + run: | + python -m pip install -U pip + python -m pip install build + python -m build --sdist --wheel --outdir dist/ + - name: Publish to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + - uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true From 570c9762cc6da2718b420fb0c0f2a83790376e2b Mon Sep 17 00:00:00 2001 From: Caroline Malin-Mayor Date: Tue, 14 May 2024 15:12:33 +0200 Subject: [PATCH 10/10] Revert "Add pypi workflow" This reverts commit e4805863892cc4fef52624c15844523b4efcd64b. --- .github/workflows/deploy.yaml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .github/workflows/deploy.yaml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml deleted file mode 100644 index 8d3df16..0000000 --- a/.github/workflows/deploy.yaml +++ /dev/null @@ -1,29 +0,0 @@ -name: Deploy - -on: - push: - tags: ["*"] - workflow_dispatch: - -jobs: - build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Build a binary wheel and a source tarball - run: | - python -m pip install -U pip - python -m pip install build - python -m build --sdist --wheel --outdir dist/ - - name: Publish to PyPI - if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_API_TOKEN }} - - uses: softprops/action-gh-release@v2 - with: - generate_release_notes: true