From fd32f2a1c957292d1d1c291b11bc8b822b034cae Mon Sep 17 00:00:00 2001 From: Draga Doncila Date: Mon, 4 Dec 2023 11:50:11 +1100 Subject: [PATCH 1/8] Add ctc and mbc tests for graphs with gap closing --- tests/loaders/test_ctc.py | 15 ++++ tests/metrics/test_ctc_metrics.py | 18 ++++- tests/test_utils.py | 103 +++++++++++++++++++++++++++ tests/track_errors/test_divisions.py | 28 +++++++- 4 files changed, 161 insertions(+), 3 deletions(-) diff --git a/tests/loaders/test_ctc.py b/tests/loaders/test_ctc.py index 38dcc06f..318b3ae3 100644 --- a/tests/loaders/test_ctc.py +++ b/tests/loaders/test_ctc.py @@ -65,6 +65,21 @@ def test_ctc_single_nodes(): # This should raise an error if there are no times for single nodes TrackingGraph(G) +def test_ctc_with_gap_closing(): + data = [ + {"Cell_ID": 1, "Start": 0, "End": 1, "Parent_ID": 0}, + {"Cell_ID": 2, "Start": 0, "End": 1, "Parent_ID": 0}, + # Connecting frame 1 to frame 3 + {"Cell_ID": 3, "Start": 3, "End": 5, "Parent_ID": 1}, + # Connecting frame 1 to frame 6 + {"Cell_ID": 4, "Start": 6, "End": 8, "Parent_ID": 2}, + ] + df = pd.DataFrame(data) + G = _ctc.ctc_to_graph( + df, pd.DataFrame({"segmentation_id": [], "x": [], "y": [], "z": [], "t": []}) + ) + assert G.has_edge("1_1", "3_3") + assert G.has_edge("2_1", "4_6") def test_load_data(): test_dir = os.path.abspath(__file__) diff --git a/tests/metrics/test_ctc_metrics.py b/tests/metrics/test_ctc_metrics.py index bd92fc95..4f9ca03b 100644 --- a/tests/metrics/test_ctc_metrics.py +++ b/tests/metrics/test_ctc_metrics.py @@ -1,7 +1,8 @@ +from traccuracy._tracking_graph import EdgeAttr, NodeAttr, TrackingGraph +from traccuracy.matchers._base import Matched from traccuracy.matchers._ctc import CTCMatcher from traccuracy.metrics._ctc import CTCMetrics - -from tests.test_utils import get_movie_with_graph +from tests.test_utils import get_movie_with_graph, get_gap_close_graphs def test_compute_mapping(): @@ -17,3 +18,16 @@ def test_compute_mapping(): assert "DET" in results assert results["TRA"] == 1 assert results["DET"] == 1 + +def test_compute_metrics_gap_close(): + g_gt, g_pred, mapper = get_gap_close_graphs() + matched = Matched(gt_graph=TrackingGraph(g_gt), pred_graph=TrackingGraph(g_pred), mapping=mapper) + results = CTCMetrics().compute(matched) + + # check that missing gap closing edge is false negative + assert g_gt.edges[("1_1", "2_3")][EdgeAttr.FALSE_NEG] + # check that "extra" node is FP + assert g_pred.nodes["1_2"][NodeAttr.FALSE_POS] + # check that correct edge is not annotated with errors + for error_attr in [EdgeAttr.FALSE_POS, EdgeAttr.WRONG_SEMANTIC]: + assert not g_pred.edges[("2_6", "4_10")][error_attr] \ No newline at end of file diff --git a/tests/test_utils.py b/tests/test_utils.py index 1dbf6d7c..01afe14d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -148,3 +148,106 @@ def get_division_graphs(): mapper = [("1_0", "1_0"), ("1_1", "1_1"), ("2_4", "2_4"), ("3_4", "3_4")] return G1, G2, mapper + + +def get_gap_close_graphs(): + """ + G1 + 3_5 -- 3_6 -- -- -- 5_10 + 1_0 -- 1_1 -- -- -- 2_3 -- 2_4 -< + 4_5 -- 4_6 + G2 + 2_5 -- 2_6 -- -- -- 4_10 + 1_0 -- 1_1 -- 1_2 -- 1_3 -- 1_4 -< + 3_5 -- 3_6 + """ + G1 = nx.DiGraph() + G1.add_edge("1_0", "1_1") + # gap closing edge + G1.add_edge("1_1", "2_3") + G1.add_edge("2_3", "2_4") + # Divide to generate 3 lineage + G1.add_edge("2_4", "3_5") + G1.add_edge("3_5", "3_6") + # gap closing edge + G1.add_edge("3_6", "5_10") + # Divide to generate 4 lineage + G1.add_edge("2_4", "4_5") + G1.add_edge("4_5", "4_6") + + attrs = {} + for node in G1.nodes: + attrs[node] = {"t": int(node[-1:]), "x": 0, "y": 0} + nx.set_node_attributes(G1, attrs) + + G2 = nx.DiGraph() + G2.add_edge("1_0", "1_1") + # missing gap closing edge + G2.add_edge("1_1", "1_2") + G2.add_edge("1_2", "1_3") + G2.add_edge("1_3", "1_4") + # Divide to generate 2 lineage + G2.add_edge("1_4", "2_5") + G2.add_edge("2_5", "2_6") + # correct gap closing edge + G2.add_edge("2_6", "4_10") + # Divide to generate 3 lineage + G2.add_edge("1_4", "3_5") + G2.add_edge("3_5", "3_6") + + attrs = {} + for node in G2.nodes: + attrs[node] = {"t": int(node[-1:]), "x": 0, "y": 0} + nx.set_node_attributes(G2, attrs) + + # G1, G2 mapper + mapper = [("1_0", "1_0"), ("1_1", "1_1"), ("2_3", "1_3"), ("2_4", "1_4"), ("3_5", "2_5"), ("3_6", "2_6"), ("5_10", "4_10"), ("4_5", "3_5"), ("4_6", "3_6")] + + return G1, G2, mapper + +def get_division_gap_close_graphs(): + """ + G1 + -- -- 2_3 -- 2_4 + 1_0 -- 1_1 -< + 3_2 -- 3_3 -- 3_4 + G2 + 2_2 -- 2_3 -- 2_4 + 1_0 -- 1_1 -< + 3_2 -- -- -- 4_4 + """ + + G1 = nx.DiGraph() + G1.add_edge("1_0", "1_1") + # gap division + G1.add_edge("1_1", "2_3") + G1.add_edge("2_3", "2_4") + # divide into 3 lineage + G1.add_edge("1_1", "3_2") + G1.add_edge("3_2", "3_3") + G1.add_edge("3_3", "3_4") + + attrs = {} + for node in G1.nodes: + attrs[node] = {"t": int(node[-1:]), "x": 0, "y": 0} + nx.set_node_attributes(G1, attrs) + + G2 = nx.DiGraph() + G2.add_edge("1_0", "1_1") + # Divide to generate 2 lineage + G2.add_edge("1_1", "2_2") + G2.add_edge("2_2", "2_3") + G2.add_edge("2_3", "2_4") + # Divide to generate 3 lineage + G2.add_edge("1_1", "3_2") + # incorrect gap closing edge + G2.add_edge("3_2", "4_4") + + attrs = {} + for node in G2.nodes: + attrs[node] = {"t": int(node[-1:]), "x": 0, "y": 0} + nx.set_node_attributes(G2, attrs) + + mapper = [("1_0", "1_0"), ("1_1", "1_1"), ("2_3", "2_3"), ("2_4", "2_4"), ("3_2", "3_2"), ("3_4", "4_4")] + + return G1, G2, mapper \ No newline at end of file diff --git a/tests/track_errors/test_divisions.py b/tests/track_errors/test_divisions.py index 6538e644..3a789814 100644 --- a/tests/track_errors/test_divisions.py +++ b/tests/track_errors/test_divisions.py @@ -11,7 +11,7 @@ _get_succ_by_t, ) -from tests.test_utils import get_division_graphs +from tests.test_utils import get_division_graphs, get_division_gap_close_graphs @pytest.fixture @@ -220,3 +220,29 @@ def test_evaluate_division_events(): results = _evaluate_division_events(matched_data, frame_buffer=frame_buffer) assert np.all([isinstance(k, int) for k in results.keys()]) + +def test_gap_close_divisions(): + g_gt, g_pred, mapper = get_division_gap_close_graphs() + matched_data = Matched(TrackingGraph(g_gt), TrackingGraph(g_pred), mapper) + _classify_divisions(matched_data) + + # missing gap close div edge so FN DIV + assert g_gt.nodes['1_1'][NodeAttr.FN_DIV] + + # fix division, assert it's identified correctly + g_pred.remove_node('2_2') + g_pred.add_edge('1_1', '2_3') + # mapper doesn't need to change as removed node was always missing + matched_data = Matched(TrackingGraph(g_gt), TrackingGraph(g_pred), mapper) + _classify_divisions(matched_data) + assert g_gt.nodes['1_1'][NodeAttr.TP_DIV] + assert g_gt.nodes['1_1'][NodeAttr.TP_DIV] + + g_gt, g_pred, mapper = get_division_gap_close_graphs() + # remove gt division + g_gt.remove_edge('1_1', '2_3') + g_gt.remove_edge('1_1', '3_2') + matched_data = Matched(TrackingGraph(g_gt), TrackingGraph(g_pred), mapper) + _classify_divisions(matched_data) + # assert fp division classified correctly + assert g_pred.nodes['1_1'][NodeAttr.FP_DIV] \ No newline at end of file From 4d0b5b3a1ae79594e6fff7571aa4a754314d0e66 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 00:54:56 +0000 Subject: [PATCH 2/8] style(pre-commit.ci): auto fixes [...] --- tests/loaders/test_ctc.py | 2 ++ tests/metrics/test_ctc_metrics.py | 10 +++++++--- tests/test_utils.py | 28 +++++++++++++++++++++++----- tests/track_errors/test_divisions.py | 21 +++++++++++---------- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/tests/loaders/test_ctc.py b/tests/loaders/test_ctc.py index 318b3ae3..a621fa4d 100644 --- a/tests/loaders/test_ctc.py +++ b/tests/loaders/test_ctc.py @@ -65,6 +65,7 @@ def test_ctc_single_nodes(): # This should raise an error if there are no times for single nodes TrackingGraph(G) + def test_ctc_with_gap_closing(): data = [ {"Cell_ID": 1, "Start": 0, "End": 1, "Parent_ID": 0}, @@ -81,6 +82,7 @@ def test_ctc_with_gap_closing(): assert G.has_edge("1_1", "3_3") assert G.has_edge("2_1", "4_6") + def test_load_data(): test_dir = os.path.abspath(__file__) data_dir = os.path.abspath( diff --git a/tests/metrics/test_ctc_metrics.py b/tests/metrics/test_ctc_metrics.py index 4f9ca03b..2a5a0206 100644 --- a/tests/metrics/test_ctc_metrics.py +++ b/tests/metrics/test_ctc_metrics.py @@ -2,7 +2,8 @@ from traccuracy.matchers._base import Matched from traccuracy.matchers._ctc import CTCMatcher from traccuracy.metrics._ctc import CTCMetrics -from tests.test_utils import get_movie_with_graph, get_gap_close_graphs + +from tests.test_utils import get_gap_close_graphs, get_movie_with_graph def test_compute_mapping(): @@ -19,9 +20,12 @@ def test_compute_mapping(): assert results["TRA"] == 1 assert results["DET"] == 1 + def test_compute_metrics_gap_close(): g_gt, g_pred, mapper = get_gap_close_graphs() - matched = Matched(gt_graph=TrackingGraph(g_gt), pred_graph=TrackingGraph(g_pred), mapping=mapper) + matched = Matched( + gt_graph=TrackingGraph(g_gt), pred_graph=TrackingGraph(g_pred), mapping=mapper + ) results = CTCMetrics().compute(matched) # check that missing gap closing edge is false negative @@ -30,4 +34,4 @@ def test_compute_metrics_gap_close(): assert g_pred.nodes["1_2"][NodeAttr.FALSE_POS] # check that correct edge is not annotated with errors for error_attr in [EdgeAttr.FALSE_POS, EdgeAttr.WRONG_SEMANTIC]: - assert not g_pred.edges[("2_6", "4_10")][error_attr] \ No newline at end of file + assert not g_pred.edges[("2_6", "4_10")][error_attr] diff --git a/tests/test_utils.py b/tests/test_utils.py index 01afe14d..9d98cdb9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -153,11 +153,11 @@ def get_division_graphs(): def get_gap_close_graphs(): """ G1 - 3_5 -- 3_6 -- -- -- 5_10 + 3_5 -- 3_6 -- -- -- 5_10 1_0 -- 1_1 -- -- -- 2_3 -- 2_4 -< 4_5 -- 4_6 G2 - 2_5 -- 2_6 -- -- -- 4_10 + 2_5 -- 2_6 -- -- -- 4_10 1_0 -- 1_1 -- 1_2 -- 1_3 -- 1_4 -< 3_5 -- 3_6 """ @@ -201,10 +201,21 @@ def get_gap_close_graphs(): nx.set_node_attributes(G2, attrs) # G1, G2 mapper - mapper = [("1_0", "1_0"), ("1_1", "1_1"), ("2_3", "1_3"), ("2_4", "1_4"), ("3_5", "2_5"), ("3_6", "2_6"), ("5_10", "4_10"), ("4_5", "3_5"), ("4_6", "3_6")] + mapper = [ + ("1_0", "1_0"), + ("1_1", "1_1"), + ("2_3", "1_3"), + ("2_4", "1_4"), + ("3_5", "2_5"), + ("3_6", "2_6"), + ("5_10", "4_10"), + ("4_5", "3_5"), + ("4_6", "3_6"), + ] return G1, G2, mapper + def get_division_gap_close_graphs(): """ G1 @@ -248,6 +259,13 @@ def get_division_gap_close_graphs(): attrs[node] = {"t": int(node[-1:]), "x": 0, "y": 0} nx.set_node_attributes(G2, attrs) - mapper = [("1_0", "1_0"), ("1_1", "1_1"), ("2_3", "2_3"), ("2_4", "2_4"), ("3_2", "3_2"), ("3_4", "4_4")] + mapper = [ + ("1_0", "1_0"), + ("1_1", "1_1"), + ("2_3", "2_3"), + ("2_4", "2_4"), + ("3_2", "3_2"), + ("3_4", "4_4"), + ] - return G1, G2, mapper \ No newline at end of file + return G1, G2, mapper diff --git a/tests/track_errors/test_divisions.py b/tests/track_errors/test_divisions.py index 3a789814..1ff6a65f 100644 --- a/tests/track_errors/test_divisions.py +++ b/tests/track_errors/test_divisions.py @@ -11,7 +11,7 @@ _get_succ_by_t, ) -from tests.test_utils import get_division_graphs, get_division_gap_close_graphs +from tests.test_utils import get_division_gap_close_graphs, get_division_graphs @pytest.fixture @@ -221,28 +221,29 @@ def test_evaluate_division_events(): assert np.all([isinstance(k, int) for k in results.keys()]) + def test_gap_close_divisions(): g_gt, g_pred, mapper = get_division_gap_close_graphs() matched_data = Matched(TrackingGraph(g_gt), TrackingGraph(g_pred), mapper) _classify_divisions(matched_data) # missing gap close div edge so FN DIV - assert g_gt.nodes['1_1'][NodeAttr.FN_DIV] - + assert g_gt.nodes["1_1"][NodeAttr.FN_DIV] + # fix division, assert it's identified correctly - g_pred.remove_node('2_2') - g_pred.add_edge('1_1', '2_3') + g_pred.remove_node("2_2") + g_pred.add_edge("1_1", "2_3") # mapper doesn't need to change as removed node was always missing matched_data = Matched(TrackingGraph(g_gt), TrackingGraph(g_pred), mapper) _classify_divisions(matched_data) - assert g_gt.nodes['1_1'][NodeAttr.TP_DIV] - assert g_gt.nodes['1_1'][NodeAttr.TP_DIV] + assert g_gt.nodes["1_1"][NodeAttr.TP_DIV] + assert g_gt.nodes["1_1"][NodeAttr.TP_DIV] g_gt, g_pred, mapper = get_division_gap_close_graphs() # remove gt division - g_gt.remove_edge('1_1', '2_3') - g_gt.remove_edge('1_1', '3_2') + g_gt.remove_edge("1_1", "2_3") + g_gt.remove_edge("1_1", "3_2") matched_data = Matched(TrackingGraph(g_gt), TrackingGraph(g_pred), mapper) _classify_divisions(matched_data) # assert fp division classified correctly - assert g_pred.nodes['1_1'][NodeAttr.FP_DIV] \ No newline at end of file + assert g_pred.nodes["1_1"][NodeAttr.FP_DIV] From 1c990eee43c1354311eeb42f86c3aeab26fe9836 Mon Sep 17 00:00:00 2001 From: Draga Doncila Date: Mon, 4 Dec 2023 11:59:02 +1100 Subject: [PATCH 3/8] Remove unused var --- tests/metrics/test_ctc_metrics.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/metrics/test_ctc_metrics.py b/tests/metrics/test_ctc_metrics.py index 4f9ca03b..e8885b8a 100644 --- a/tests/metrics/test_ctc_metrics.py +++ b/tests/metrics/test_ctc_metrics.py @@ -2,7 +2,8 @@ from traccuracy.matchers._base import Matched from traccuracy.matchers._ctc import CTCMatcher from traccuracy.metrics._ctc import CTCMetrics -from tests.test_utils import get_movie_with_graph, get_gap_close_graphs + +from tests.test_utils import get_gap_close_graphs, get_movie_with_graph def test_compute_mapping(): @@ -19,10 +20,13 @@ def test_compute_mapping(): assert results["TRA"] == 1 assert results["DET"] == 1 + def test_compute_metrics_gap_close(): g_gt, g_pred, mapper = get_gap_close_graphs() - matched = Matched(gt_graph=TrackingGraph(g_gt), pred_graph=TrackingGraph(g_pred), mapping=mapper) - results = CTCMetrics().compute(matched) + matched = Matched( + gt_graph=TrackingGraph(g_gt), pred_graph=TrackingGraph(g_pred), mapping=mapper + ) + CTCMetrics().compute(matched) # check that missing gap closing edge is false negative assert g_gt.edges[("1_1", "2_3")][EdgeAttr.FALSE_NEG] @@ -30,4 +34,4 @@ def test_compute_metrics_gap_close(): assert g_pred.nodes["1_2"][NodeAttr.FALSE_POS] # check that correct edge is not annotated with errors for error_attr in [EdgeAttr.FALSE_POS, EdgeAttr.WRONG_SEMANTIC]: - assert not g_pred.edges[("2_6", "4_10")][error_attr] \ No newline at end of file + assert not g_pred.edges[("2_6", "4_10")][error_attr] From f9d3ecbcb6dc7d23d92088e563e04a23c07904cb Mon Sep 17 00:00:00 2001 From: Draga Doncila Date: Mon, 4 Dec 2023 13:24:52 +1100 Subject: [PATCH 4/8] Add track overlap test with gap close --- src/traccuracy/metrics/_track_overlap.py | 1 - tests/metrics/test_track_overlap_metrics.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/traccuracy/metrics/_track_overlap.py b/src/traccuracy/metrics/_track_overlap.py index f4f1f827..c5b17c23 100644 --- a/src/traccuracy/metrics/_track_overlap.py +++ b/src/traccuracy/metrics/_track_overlap.py @@ -126,5 +126,4 @@ def _calc_overlap_score( max_overlap = max(overlaps) correct_count += max_overlap total_count += len(reference_tracklet.edges()) - return correct_count / total_count if total_count > 0 else -1 diff --git a/tests/metrics/test_track_overlap_metrics.py b/tests/metrics/test_track_overlap_metrics.py index f70a9304..8a2da45e 100644 --- a/tests/metrics/test_track_overlap_metrics.py +++ b/tests/metrics/test_track_overlap_metrics.py @@ -6,6 +6,8 @@ from traccuracy.matchers import Matched from traccuracy.metrics._track_overlap import TrackOverlapMetrics, _mapping_to_dict +from tests.test_utils import get_gap_close_graphs + def add_frame(tree): attrs = {} @@ -183,6 +185,19 @@ def test_track_overlap_metrics(data, inverse) -> None: assert results == expected, f"{data['name']} failed without division edges" +def test_track_overlap_gap_close(): + g_gt, g_pred, mapping = get_gap_close_graphs() + matched = Matched( + TrackingGraph(g_gt), + TrackingGraph(g_pred), + mapping, + ) + metric = TrackOverlapMetrics() + results = metric.compute(matched) + assert results["track_purity"] == 7 / 9 + assert results["target_effectiveness"] == 7 / 8 + + def test_mapping_to_dict(): mapping = [("1", "2"), ("2", "3"), ("1", "3"), ("2", "3")] mapping_dict = _mapping_to_dict(mapping) From 0980248c84cba486b9215a5d612882d2992617da Mon Sep 17 00:00:00 2001 From: Draga Doncila Date: Mon, 4 Dec 2023 13:40:52 +1100 Subject: [PATCH 5/8] Add back whitespace --- src/traccuracy/metrics/_track_overlap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/traccuracy/metrics/_track_overlap.py b/src/traccuracy/metrics/_track_overlap.py index c5b17c23..f4f1f827 100644 --- a/src/traccuracy/metrics/_track_overlap.py +++ b/src/traccuracy/metrics/_track_overlap.py @@ -126,4 +126,5 @@ def _calc_overlap_score( max_overlap = max(overlaps) correct_count += max_overlap total_count += len(reference_tracklet.edges()) + return correct_count / total_count if total_count > 0 else -1 From 6b27436315b04b463fa3d7ed82eb9ada3271d4ea Mon Sep 17 00:00:00 2001 From: Draga Doncila Date: Mon, 4 Dec 2023 15:01:11 +1100 Subject: [PATCH 6/8] Fix some documentation --- README.md | 7 +++++-- src/traccuracy/_tracking_graph.py | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0b0b3adc..700fba1e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The `traccuracy` library has three main components: loaders, matchers, and metri Loaders load tracking graphs from other formats, such as the CTC format, into a [TrackingGraph](https://traccuracy.readthedocs.io/en/latest/autoapi/traccuracy/index.html#traccuracy.TrackingGraph) object. A TrackingGraph is a spatiotemporal graph. Nodes represent a single cell in a given time point, and are annotated with a time and a location. -Edges point from a node representing a cell in time point `t` to the same cell or its daughter in `t+1`. +Edges point forward in time from a node representing a cell in time point `t` to the same cell or its daughter in frames `t+1` (or beyond, to represent gap closing). To load TrackingGraphs from a custom format, you will likely need to implement a loader: see documentation [here](https://traccuracy.readthedocs.io/en/latest/autoapi/traccuracy/loaders/index.html#module-traccuracy.loaders) for more information. @@ -55,4 +55,7 @@ pipelines, [documented here](https://traccuracy.readthedocs.io/en/latest/cli.htm : A single non-dividing cell tracked over time. In graph terms, this is the connected component of a track between divisions (daughter to next parent). Tracklets can also start or end with a non-dividing cell at the beginning and end of the captured time or if the track leaves the field of view. **Track** -: A single cell and all of its progeny. In graph terms, a connected component including divisions. \ No newline at end of file +: A single cell and all of its progeny. In graph terms, a connected component including divisions. + +**Gap-Closing** +: Also known as *frame-skipping*, these are edges that connect non-consecutive frames to signify a cell being occluded or missing for some frames, before the track continues. \ No newline at end of file diff --git a/src/traccuracy/_tracking_graph.py b/src/traccuracy/_tracking_graph.py index 082e0ac3..8f21bc62 100644 --- a/src/traccuracy/_tracking_graph.py +++ b/src/traccuracy/_tracking_graph.py @@ -81,6 +81,9 @@ class TrackingGraph: location (defaults to 'x' and 'y'). As in networkx, every cell must have a unique id, but these can be of any (hashable) type. + Edges typically connect nodes across consecutive frames, but gap closing or frame + skipping edges are valid, which connect nodes in frame t to nodes in frames beyond t+1. + We provide common functions for accessing parts of the track graph, for example all nodes in a certain frame, or all previous or next edges for a given node. Additional functionality can be accessed by querying the stored networkx graph From a885b5f78b2c5a16831a86463276b5573d72db8e Mon Sep 17 00:00:00 2001 From: Draga Doncila Pop <17995243+DragaDoncila@users.noreply.github.com> Date: Wed, 6 Dec 2023 10:04:06 +1100 Subject: [PATCH 7/8] Update tests/track_errors/test_divisions.py Co-authored-by: Morgan Schwartz --- tests/track_errors/test_divisions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/track_errors/test_divisions.py b/tests/track_errors/test_divisions.py index 1ff6a65f..8822c75e 100644 --- a/tests/track_errors/test_divisions.py +++ b/tests/track_errors/test_divisions.py @@ -237,7 +237,7 @@ def test_gap_close_divisions(): matched_data = Matched(TrackingGraph(g_gt), TrackingGraph(g_pred), mapper) _classify_divisions(matched_data) assert g_gt.nodes["1_1"][NodeAttr.TP_DIV] - assert g_gt.nodes["1_1"][NodeAttr.TP_DIV] + assert g_pred.nodes["1_1"][NodeAttr.TP_DIV] g_gt, g_pred, mapper = get_division_gap_close_graphs() # remove gt division From 428c50de7b1300b637ec74db1812413593344c9e Mon Sep 17 00:00:00 2001 From: Draga Doncila Date: Fri, 15 Dec 2023 11:29:38 +1100 Subject: [PATCH 8/8] small grammar fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 700fba1e..1e84692c 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The `traccuracy` library has three main components: loaders, matchers, and metri Loaders load tracking graphs from other formats, such as the CTC format, into a [TrackingGraph](https://traccuracy.readthedocs.io/en/latest/autoapi/traccuracy/index.html#traccuracy.TrackingGraph) object. A TrackingGraph is a spatiotemporal graph. Nodes represent a single cell in a given time point, and are annotated with a time and a location. -Edges point forward in time from a node representing a cell in time point `t` to the same cell or its daughter in frames `t+1` (or beyond, to represent gap closing). +Edges point forward in time from a node representing a cell in time point `t` to the same cell or its daughter in frame `t+1` (or beyond, to represent gap-closing). To load TrackingGraphs from a custom format, you will likely need to implement a loader: see documentation [here](https://traccuracy.readthedocs.io/en/latest/autoapi/traccuracy/loaders/index.html#module-traccuracy.loaders) for more information.