spacr.timeflows_baseline¶
The plain stitcher the timeflows fork has to beat, and the score that says so.
WHAT IT IS FOR¶
Step 2 of the timeflows plan is a number, not a feature. Before a third Cellpose head is written, trained and maintained, there has to be a baseline: segment every frame independently, link the frames by mask overlap, and score the result against the ground truth with the measures the tracking field already agrees on. Whatever the fork produces is then either better than this or it is not, and the plan says plainly that “the plain stitcher is already good enough” is a real possible outcome that should be accepted if it happens. That outcome is only visible if this number exists first.
WHAT IS IN HERE¶
stitch_by_iou()– per-frame labels in, one consistent id per object out. Hungarian assignment on the IoU between consecutive frames, which is the standard overlap stitcher and deliberately nothing cleverer.det_score()andtra_score()– the Cell Tracking Challenge’s DET and TRA, built on the AOGM cost of Matula et al. (2015): the weighted count of the edit operations that would turn the computed tracking graph into the ground-truth one, normalised by the cost of building the ground truth from nothing.identity_switches()– how often one ground-truth object changes computed id. TRA already pays for this, mixed in with everything else; the count is reported separately because it is the failure a biologist sees, and because a tracker can trade it against segmentation errors without TRA moving much.score_tracking()– all of it in one row, with the raw operation counts beside the scores.
WHY THE OPERATION COUNTS ARE PUBLIC¶
DET and TRA are one number each and both are dominated by the vertex terms: a false negative costs ten and a redundant edge costs one, so a tracker can lose half its links and still score well if its segmentation is good. The counts of false negatives, false positives, splits, and added, deleted and changed edges say which half of the problem the number came from. The plan needs exactly that distinction later – the gap between linking given perfect segmentation and end-to-end linking is what decides whether to work on the head or on the backbone – so the counts are part of the output rather than an internal.
HOW A TRACKING IS REPRESENTED¶
The Cell Tracking Challenge convention, because the metrics are its: a label stack where THE LABEL IS THE TRACK ID, so an object keeps its label for as long as it is the same object, plus an optional lineage mapping each label to its parent label. A division is therefore two new labels whose parent is the old one; a tracker that cannot express divisions passes no lineage and pays for the two missing parent edges, which is the honest price of a plain stitcher.
WHY IT DOES NOT CALL spacr.timelapse¶
spacr.timelapse.link_by_iou() does the same linking and is the one the
pipeline uses. It is not imported here for two reasons. Importing
spacr.timelapse pulls in matplotlib, OpenCV and the tracking backends,
which is a heavy import for a scoring module that has to run inside a training
loop, and its overlap arithmetic is a Python double loop over pairs of boolean
masks – fine for the frames a run produces, far too slow for scoring a held
out set every epoch. The arithmetic here is a single cross-tabulation per
frame pair. Where the two are meant to agree – which pairs of labels are
matched at a given threshold – the tests say so.
Functions¶
|
Count the edit operations between a computed tracking and the truth. |
|
The Cell Tracking Challenge's DET: detection only, no links. |
|
Count how often a ground-truth object changes computed identity. |
|
Intersection over union for every pair of labels in two frames. |
|
Match the labels of two consecutive frames one to one. |
|
Match every ground-truth object to the computed segment that detects it. |
|
Cross-tabulate the pixels shared by the labels of two frames. |
|
Run the baseline stitcher on a segmentation and score what it produced. |
|
Score one computed tracking against the truth, counts and all. |
|
Put scored trackings in one table, best TRA first. |
|
Give one id to the same object through a movie, by overlap alone. |
|
The Cell Tracking Challenge's TRA: detection and links together. |
|
Build the acyclic oriented graph the AOGM measures are defined on. |
Module Contents¶
- spacr.timeflows_baseline.aogm_costs(gt_masks, pred_masks, gt_lineage=None, pred_lineage=None, weights=None)[source]¶
Count the edit operations between a computed tracking and the truth.
Vertex operations follow the detection matching: a ground-truth object nothing detects is a false negative, a computed segment that detects nothing is a false positive, and a computed segment that detects several objects has to be split once per extra object.
Edge operations are counted on the mapped graph. A computed edge is carried onto the ground truth only when BOTH of its endpoints detect exactly one ground-truth object each; anything else has an endpoint that does not exist in the truth, so the edge is deleted. A ground-truth edge with no computed counterpart is added, and one whose counterpart has the other semantics – a division link scored as a plain track link, or the reverse – is changed rather than rebuilt.
- Parameters:
gt_masks – ground-truth label stack, label = track id.
pred_masks – computed label stack, label = track id.
gt_lineage – mapping of ground-truth label to parent label.
pred_lineage – mapping of computed label to parent label.
weights – AOGM weights. Anything left out keeps its
AOGM_WEIGHTSvalue, so{'fp': 10.0}changes the price of a false positive and nothing else.
- Returns:
dict of the six operation counts (
fn,fp,ns,ed,ea,ec), the graph sizes (n_gt_vertices,n_pred_vertices,n_gt_edges,n_pred_edges) and the four costs:aogm_dandaogm_d0for detection,aogmandaogm_0for tracking.
- spacr.timeflows_baseline.det_score(gt_masks, pred_masks, weights=None, costs=None)[source]¶
The Cell Tracking Challenge’s DET: detection only, no links.
- Parameters:
gt_masks – ground-truth label stack.
pred_masks – computed label stack.
weights – AOGM weights;
AOGM_WEIGHTSwhenNone.costs – an
aogm_costs()result to reuse instead of measuring again.
- Returns:
float in
[0, 1];1.0when every object is detected once.
- spacr.timeflows_baseline.identity_switches(gt_masks, pred_masks)[source]¶
Count how often a ground-truth object changes computed identity.
Each ground-truth track is walked in frame order over the frames where it was detected, and every change of computed label is one switch. Frames where the object was missed are skipped rather than ending the track, so an object that is lost and then found again under a NEW id is counted as a switch. That is the failure a biologist reads as “the cell became another cell”, and a tracker that hides it by dropping the frame should not score better for dropping it.
- Parameters:
gt_masks – ground-truth label stack, label = track id.
pred_masks – computed label stack, label = track id.
- Returns:
dict with
switches,tracks_with_switchesandgt_tracks.
- spacr.timeflows_baseline.iou_matrix(previous, current)[source]¶
Intersection over union for every pair of labels in two frames.
- Parameters:
previous – 2-D label frame.
current – 2-D label frame of the same shape.
- Returns:
(labels_previous, labels_current, iou)whereiou[i, j]is in[0, 1].
- spacr.timeflows_baseline.link_frames(previous, current, iou_threshold=0.1)[source]¶
Match the labels of two consecutive frames one to one.
Hungarian assignment maximising total IoU, then every pair below the threshold is dropped. The assignment matters where a cell divides or two cells touch: greedy best-match links both children to the parent and produces two objects with one id, which the metrics then punish twice.
- Parameters:
previous – 2-D label frame.
current – 2-D label frame of the same shape.
iou_threshold – smallest IoU that is accepted as the same object.
- Returns:
list of
(label_previous, label_current, iou), ordered by the previous frame’s labels.
- spacr.timeflows_baseline.match_vertices(gt_masks, pred_masks)[source]¶
Match every ground-truth object to the computed segment that detects it.
The Cell Tracking Challenge’s criterion: a computed segment detects a ground-truth object when it covers more than half of that object’s pixels.
- Parameters:
gt_masks – ground-truth label stack.
pred_masks – computed label stack of the same shape.
- Returns:
(matched, shared).matchedmaps a ground-truth vertex(frame, label)to the computed vertex that detects it;sharedmaps a computed vertex to the number of ground-truth objects it detects, which is above one exactly where two objects were segmented as one.- Raises:
ValueError – when the two stacks have different shapes.
- spacr.timeflows_baseline.overlap_counts(previous, current)[source]¶
Cross-tabulate the pixels shared by the labels of two frames.
One pass over the pixels where both frames are labelled, so the cost is the image rather than the product of the two label counts.
- Parameters:
previous – 2-D label frame.
current – 2-D label frame of the same shape.
- Returns:
(labels_previous, labels_current, counts).counts[i, j]is the number of pixels carryinglabels_previous[i]in the first frame andlabels_current[j]in the second. Background is excluded from both label arrays.
- spacr.timeflows_baseline.score_stitcher(gt_masks, segmentation, gt_lineage=None, iou_threshold=0.1, weights=None, name='iou-stitcher')[source]¶
Run the baseline stitcher on a segmentation and score what it produced.
This is the number step 2 exists to produce, and the number step 3’s fork has to beat.
- Parameters:
gt_masks – ground-truth label stack, label = track id.
segmentation – per-frame segmentation whose labels mean nothing across frames. Pass the ground-truth stack itself to measure LINKING GIVEN PERFECT SEGMENTATION, which is the upper bound the plan asks for beside the end-to-end number.
gt_lineage – mapping of ground-truth label to parent label.
iou_threshold – smallest IoU the stitcher accepts as the same object.
weights – AOGM weights;
AOGM_WEIGHTSwhenNone.name – what the row is called.
- Returns:
(row, tracked, table)– thescore_tracking()row, the stitched label stack, and the stitcher’s own link table.
- spacr.timeflows_baseline.score_tracking(gt_masks, pred_masks, gt_lineage=None, pred_lineage=None, weights=None, name='tracking')[source]¶
Score one computed tracking against the truth, counts and all.
- Parameters:
gt_masks – ground-truth label stack, label = track id.
pred_masks – computed label stack, label = track id.
gt_lineage – mapping of ground-truth label to parent label.
pred_lineage – mapping of computed label to parent label.
weights – AOGM weights;
AOGM_WEIGHTSwhenNone.name – what the row is called, so several trackings concatenate into one table.
- Returns:
dict carrying
name,det,tra,switches,tracks_with_switches,gt_tracksand every field ofaogm_costs().
- spacr.timeflows_baseline.scores_table(rows)[source]¶
Put scored trackings in one table, best TRA first.
- Parameters:
rows – iterable of
score_tracking()results.- Returns:
DataFramewith the scores first and the operation counts after them.
- spacr.timeflows_baseline.stitch_by_iou(masks, iou_threshold=0.1)[source]¶
Give one id to the same object through a movie, by overlap alone.
This is the baseline the plan calls “the plain stitcher”: each frame is segmented independently, consecutive frames are matched by IoU, an object that matches nothing starts a new id, and nothing is bridged across a gap. It has no notion of a division – both children are new objects, and the parent simply ends – which is exactly the limitation the time-flow head is supposed to remove, so it is left in rather than patched around.
- Parameters:
masks – per-frame segmentation,
(T, Y, X)or a sequence of 2-D frames, whose labels mean nothing across frames.iou_threshold – smallest IoU that is accepted as the same object.
- Returns:
(tracked, table).trackedis a(T, Y, X)integer array in which the label IS the track id;tableis aDataFrameofframe,original_label,track_idand theiouthe link was made on (NaNwhere the track starts).
- spacr.timeflows_baseline.tra_score(gt_masks, pred_masks, gt_lineage=None, pred_lineage=None, weights=None, costs=None)[source]¶
The Cell Tracking Challenge’s TRA: detection and links together.
- Parameters:
gt_masks – ground-truth label stack, label = track id.
pred_masks – computed label stack, label = track id.
gt_lineage – mapping of ground-truth label to parent label.
pred_lineage – mapping of computed label to parent label.
weights – AOGM weights;
AOGM_WEIGHTSwhenNone.costs – an
aogm_costs()result to reuse instead of measuring again.
- Returns:
float in
[0, 1];1.0for a tracking identical to the truth.
- spacr.timeflows_baseline.tracking_graph(masks, lineage=None)[source]¶
Build the acyclic oriented graph the AOGM measures are defined on.
A label whose frames are not contiguous – a tracker that bridged a gap, or annotation that let an object vanish for a frame and kept its id – is not refused, but the gap earns NO EDGE. The challenge’s format has no way to say “the same object, not visible for a while”, and an edge across the gap would credit a link the graph cannot express and the truth does not contain. The tracking is scored on the links it can show; the identity it claims across the gap is still honoured by
identity_switches(), which is where that claim belongs.- Parameters:
masks – label stack in which the label is the track id.
lineage – optional mapping of label to parent label.
0or a label that never appears means no parent.
- Returns:
dict with
vertices(set of(frame, label)) andedges(dict from((frame, label), (frame, label))to'track'or'division'). Track edges join consecutive frames of one label; division edges join a parent’s last frame to a child’s first.