Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix #176

Merged
merged 7 commits into from
Aug 10, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "laptrack"
version = "0.3.0"
version = "0.3.1"
description = "LapTrack"
authors = ["Yohsuke Fukai <ysk@yfukai.net>"]
license = "BSD-3-Clause"
1 change: 0 additions & 1 deletion src/laptrack/_tracking.py
Original file line number Diff line number Diff line change
@@ -422,7 +422,6 @@ def _link_frames(
track_start_cost=self.track_start_cost,
track_end_cost=self.track_end_cost,
)
print(cost_matrix.todense())
xs, _ = lap_optimization(cost_matrix)

count1 = dist_matrix.shape[0]
4 changes: 3 additions & 1 deletion src/laptrack/_typing_utils.py
Original file line number Diff line number Diff line change
@@ -16,4 +16,6 @@
Float = Union[float, np.float_]

Matrix = Union[FloatArray, coo_matrix, lil_matrix]
EdgeType = Union[nx.classes.reportviews.EdgeView, Sequence[Tuple[Int, Int]]]
EdgeType = Union[
nx.classes.reportviews.EdgeView, Sequence[Tuple[Tuple[Int, Int], Tuple[Int, Int]]]
]
11 changes: 4 additions & 7 deletions src/laptrack/data_conversion.py
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ def convert_dataframe_to_coords(


def convert_tree_to_dataframe(
tree: nx.Graph,
tree: nx.DiGraph,
) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
"""Convert the track tree to dataframes.

@@ -95,11 +95,8 @@ def convert_tree_to_dataframe(
splits: List[Tuple[IntTuple, List[IntTuple]]] = []
merges: List[Tuple[IntTuple, List[IntTuple]]] = []
for node in tree.nodes:
frame0, _index0 = node
neighbors = list(tree.neighbors(node))
children = [(frame, index) for (frame, index) in neighbors if frame > frame0]
parents = [(frame, index) for (frame, index) in neighbors if frame < frame0]
assert len(children) + len(parents) == len(neighbors)
children = list(tree.successors(node))
parents = list(tree.predecessors(node))
if len(children) > 1:
for child in children:
if tree2.has_edge(node, child):
@@ -113,7 +110,7 @@ def convert_tree_to_dataframe(
if node not in [p[0] for p in merges]:
merges.append((node, parents))

connected_components = list(nx.connected_components(tree2))
connected_components = list(nx.connected_components(nx.Graph(tree2)))
for track_id, nodes in enumerate(connected_components):
for (frame, index) in nodes:
df.loc[(frame, index), "track_id"] = track_id
37 changes: 24 additions & 13 deletions src/laptrack/scores.py
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@

from ._typing_utils import EdgeType
from .data_conversion import convert_tree_to_dataframe
from .utils import order_edges


def _add_split_edges(track_df, split_df):
@@ -49,17 +50,22 @@ def _calc_overlap_score(reference_edgess, overlap_edgess):
)


def calc_scores(true_edges: EdgeType, predicted_edges: EdgeType) -> Dict[str, float]:
def calc_scores(
true_edges: EdgeType, predicted_edges: EdgeType, exclude_true_edges: EdgeType = []
) -> Dict[str, float]:
"""
Calculate track prediction scores.

Parameters
----------
true_edges : Sequence[Tuple[Int,Int]]
true_edges : EdgeType
the list of true edges. assumes ((frame1,index1), (frame2,index2)) for each edge

predicted_edges : Sequence[Tuple[Int,Int]]
the list of predicted edges. assumes ((frame1,index1), (frame2,index2)) for each edge
predicted_edges : EdgeType
the list of predicted edges. see `true_edges` for format

exclude_true_edges : EdgeType, default []
the list of true edges to be excluded from "*_ratio". see `true_edges` for format

Returns
-------
@@ -73,22 +79,20 @@ def calc_scores(true_edges: EdgeType, predicted_edges: EdgeType) -> Dict[str, fl
"division_recovery" : the number of divisions that were correctly predicted.
"""
# return the count o
te = set(true_edges)
pe = set(predicted_edges)
if len(pe) == 0:
if len(list(predicted_edges)) == 0:
return {
"union_ratio": 0,
"true_ratio": 0,
"predicted_ratio": 0,
"track_purity": 0,
"target_efficiency": 0,
"target_effectiveness": 0,
"division_recovery": 0,
}
else:
gt_tree = nx.Graph()
gt_tree.add_edges_from(te)
pred_tree = nx.Graph()
pred_tree.add_edges_from(pe)
gt_tree = nx.from_edgelist(order_edges(true_edges), create_using=nx.DiGraph)
pred_tree = nx.from_edgelist(
order_edges(predicted_edges), create_using=nx.DiGraph
)
gt_track_df, gt_split_df, _gt_merge_df = convert_tree_to_dataframe(gt_tree)
pred_track_df, pred_split_df, _pred_merge_df = convert_tree_to_dataframe(
pred_tree
@@ -111,10 +115,17 @@ def get_children(m):
division_recovery_count = 0
for m in dividing_nodes:
children = get_children(m)
if all([(n, m) in pe or (m, n) in pe for n in children]):
if all(
[
(n, m) in predicted_edges or (m, n) in predicted_edges
for n in children
]
):
division_recovery_count += 1
division_recovery = division_recovery_count / len(dividing_nodes)

te = set(true_edges) - set(exclude_true_edges)
pe = set(predicted_edges) - set(exclude_true_edges)
return {
"union_ratio": len(te & pe) / len(te | pe),
"true_ratio": len(te & pe) / len(te),
24 changes: 24 additions & 0 deletions src/laptrack/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Miscellous utilities."""
from typing import List
from typing import Tuple

from ._typing_utils import EdgeType
from ._typing_utils import Int


def order_edges(edges: EdgeType) -> List[Tuple[Tuple[Int, Int], Tuple[Int, Int]]]:
"""
Order edges so that it points to the temporal order.

Parameters
----------
edges : EdgeType
the list of edges. assumes ((frame1,index1), (frame2,index2)) for each edge

Returns
-------
edges : List[Tuple[Tuple[Int,Int],Tuple[Int,Int]]]
the sorted edges

"""
return [(n1, n2) if n1[0] < n2[0] else (n2, n1) for (n1, n2) in edges]
28 changes: 27 additions & 1 deletion tests/test_data_conversion.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import networkx as nx
import numpy as np
import pandas as pd
import pytest

from laptrack import data_conversion
from laptrack import LapTrack
from laptrack import LapTrackMulti


def test_convert_dataframe_to_coords():
@@ -40,7 +43,8 @@ def test_convert_tree_to_dataframe():
((2, 2), (3, 2)),
((3, 2), (4, 2)),
((1, 3), (2, 2)),
]
],
create_using=nx.DiGraph,
)
segments = [
[(0, 0), (1, 0), (2, 0)],
@@ -86,3 +90,25 @@ def test_convert_tree_to_dataframe():
assert np.all(
merge_df[["parent_track_id", "child_track_id"]].values == merge_df_target
)


@pytest.mark.parametrize("track_class", [LapTrack, LapTrackMulti])
def test_integration(track_class):
df = pd.DataFrame(
{
"frame": [0, 0, 0, 1, 1, 2, 2, 2, 2, 2],
"x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
"y": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
"z": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
}
)
coords_target = [
np.array([[0, 0], [1, 1], [2, 2]]),
np.array([[3, 3], [4, 4]]),
np.array([[5, 5], [6, 6], [7, 7], [8, 8], [9, 9]]),
]

coords = data_conversion.convert_dataframe_to_coords(df, ["x", "y"])
lt = track_class()
tree = lt.predict(coords)
data_conversion.convert_tree_to_dataframe(tree)
2 changes: 1 addition & 1 deletion tests/test_tracking_routines.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
from laptrack._tracking import _remove_no_split_merge_links


def test_reproducing_trackmate() -> None:
def test_remove_no_split_merge_links() -> None:
test_tree = nx.Graph()
test_tree.add_edges_from(
[