The tracks coming out of the linker are good. They are not clean. And this is where the idea from Part 1 — that the score is about connections, not cells — finally earns its keep.
The asymmetry that makes gaps expensive
Watch what happens when the detector blinks: one cell, missed, for one frame.
You do not lose one connection. You lose two — the edge coming in, and the edge going out. And because the track is now severed, it snaps into two fragments, so the family tree is wrong from that point onward.
One missed cell. Two lost links. One broken lineage.
That asymmetry is the whole insight. A missed detection costs double, because edges are what the metric counts and every interior node carries two of them. Which flips the obvious priority: the most valuable repair is not finding more cells. It is healing the ones that broke.
You cannot leap the hole
There is a catch, and it comes straight from the metric definition. The score only counts links between consecutive frames.
So the tempting shortcut — notice the gap, draw an edge from t straight to t+2, move on — earns exactly nothing. That edge spans two frames and the metric does not see it.
You have to fill the hole instead: invent a node at t+1 where the detector missed one, interpolate its position, and thread two consecutive edges back through it. Do that and you recover both of the lost links plus the continuity of the lineage.
This is a case where reading the metric definition closely was worth more than any modelling idea. The difference between the version that scores and the version that scores nothing is entirely in a detail of how edges are counted.
The calibration stage
All of this lives in one module that takes (nodes, edges) from the linker and returns a repaired (nodes, edges). It touches no imagery and runs in seconds. Four transforms:
Filter short tracks. Fragments a few frames long are usually noise, and their edges are usually wrong. Dropping them is a small clean win. The public pipeline used a minimum length of 7; my own sweep found 4 was better on this data, and 7 measurably hurt. A tuned constant, but a tuned constant with a measurement behind it.
Add safe divisions. The division term is worth 10% of the score, and most submissions score close to zero on it. Where a track ends near the start of two others, that is a plausible missed division — so insert a conservative second child, gated by per-frame and global caps so it cannot run away.
Close gaps. The repair above: synthesise the missing node and two consecutive edges. This was the single largest lever in the stage.
Line-fit smoothing. Least-squares smoothing of positions along each track. Hold that thought — it is the subject of Part 6, and not for a good reason.
The order matters more than any single transform
This surprised me and is probably the most transferable thing in the post.
These transforms are not commutative. Run them as filter → divisions → gap and the local score is 0.7691. Run the same three as gap → filter → divisions and it is 0.7636 — and, worse, the division count collapses to zero.
The reason is that gap-closing creates short synthetic fragments, which the filter then eats, which removes exactly the track endings that the division detector was going to pair up. Each transform is individually correct. Composed in the wrong order, one of them silently destroys another's input.
No single transform is buggy. The pipeline is. That failure only shows up if you measure the composition rather than the parts.
What it was worth
| Configuration | Local | Leaderboard |
|---|---|---|
| Public U-Net + ILP, no calibration | 0.9423 | 0.867 |
| + graph calibration | 0.9571 | 0.881 |
+0.014 on the leaderboard, and it transferred from local almost exactly — 0.0148 local against 0.014 on the board. For a stage that never touches a pixel and runs in seconds on top of someone else's detector, that is the piece of this project I am happiest with.
It is also, honestly, an order of magnitude smaller than the +0.113 that came from swapping the detector in Part 3. The detector was the lever. This was the edge.
A caveat I want on the record
Almost all of this was tuned on one volume. Later multi-volume validation across five volumes — spanning roughly 6,900 to 73,000 cells — held up, with the shipped configuration gaining about +0.009 mean over baseline on the three usable ones. But those volumes come from a single embryo.
Cross-embryo generalisation is untested. Given that the hidden test set is ~199 volumes, that is the largest methodological risk in the whole project, and I would rather name it than let a leaderboard number imply more robustness than I actually demonstrated.
Next, and the reason this series exists: two experiments that made things worse, in two completely different ways.