an.bench.run#
run_bench: render the corpus, compute the panel, write one ledger row.
**Every encode-side metric is measured against a lossless (-qp 0) encode of
the same frames, not against a second conversion of the PNGs.** That is a
correction, and CI is what made it: the PNG conversion agrees with the
encoder’s own exactly on ffmpeg 8.1 and disagrees by mean 0.63 / max 5 on the
Linux runner’s older build — 42% of coded_luma_edge_error’s whole crf23 value,
which would have been measured as encoder damage on that machine and as nothing
on this one. -qp 0 is lossless, so its decoded luma is the plane libx264
received, on any build. Referencing to it removes the assumption rather than
widening it. See an.bench.imageio.
The PNG conversion is still performed and its distance from the encoder’s input
is recorded as png_to_encoder_input_luma — that number is the build
dependence, and it belongs in provenance rather than inside a gate.
Module Attributes
Family B's two rows, one in each block. |
|
A pairwise minimum needs two frames; every fixture pins at least two, so on a real capture this row is always measured (the render-side panel may not be null — |
|
The pan measurement (an#111). |
|
The floor the tripwire fires below, set at HALF the first bless's measured minimum — the |
|
Said when a run blessed the goldens it would otherwise have compared against. |
Functions
|
How far this build's PNG->YUV conversion sits from the encoder's own. |
|
A human-readable digest of a row — the thing |
|
Encode the frames losslessly and return the decode — the encoder's input. |
|
Which git state NAMES the row: the tree the run left, not the one it read. |
|
|
|
Render the corpus, compute the panel, and (by default) write the row. |
|
The per-shot COMPILE POLICIES a scene rendered under, keyed by shot id. |
|
Viseme keyframes per second of dialogue in a compiled shot, or |
Exceptions
The bench could not produce a row it would be honest to file. |
- exception an.bench.run.BenchError[source]#
Bases:
RuntimeErrorThe bench could not produce a row it would be honest to file.
- an.bench.run.GOLDEN_METRIC_KEY: str = 'min_ssim_win8_vs_golden'#
Family B’s two rows, one in each block. Named here because the golden result fills both from one comparison, and a reader has to be able to see that the boolean and the number are the same evidence read two ways.
- an.bench.run.JUST_BLESSED_DETAIL: str = 'this run WROTE these goldens, so comparing against them is a tautology: the identity holds by construction and no code could have failed it. Run `an bench` again, without --bless, for a comparison that can fail.'#
Said when a run blessed the goldens it would otherwise have compared against.
- an.bench.run.MIN_PINNED_FRAMES_FOR_PAIRWISE: int = 2#
A pairwise minimum needs two frames; every fixture pins at least two, so on a real capture this row is always measured (the render-side panel may not be null —
tests/test_bench_capture.py).
- an.bench.run.STAGE_METRIC_KEY: str = 'stage_min_plane_ratio_gap'#
The pan measurement (an#111). Metric and tripwire come from ONE measurement, as the golden pair does: the boolean and the number must be the same evidence read two ways, or a reader has to reconcile them.
- an.bench.run.STAGE_MIN_RATIO_GAP: float = 0.1875#
The floor the tripwire fires below, set at HALF the first bless’s measured minimum — the
expression_min_pairwise_changed_pxprecedent followed literally.Measured on
stage_panat the first bless: 0.375. Ratios are taken against the largest mover, so depths 0.25 / 1.0 / 2.0 report as 0.125 / 0.5 / 1.0 and the closest pair is far/mid. (An earlier draft of this comment said 0.75, which is the same measurement taken against thedepth == 1plane — a reference the pixel half cannot see, which is why_referenceis now always the largest mover.)
- an.bench.run.conversion_distance(frames_dir, lossless_mp4, *, height, width, frames)[source]#
How far this build’s PNG->YUV conversion sits from the encoder’s own.
Recorded, never gated. It was a gate — a hard equality — and it failed on the Linux runner while passing here, which is precisely the shape of a machine-dependent fact masquerading as a universal one. Now the metrics no longer depend on it, and the number is kept because it is the thing that will explain a future cross-build surprise.
- Return type:
- an.bench.run.format_panel(ledger)[source]#
A human-readable digest of a row — the thing
an benchprints.- Return type:
- an.bench.run.lossless_reference(frames_dir, fps, out, *, delivered)[source]#
Encode the frames losslessly and return the decode — the encoder’s input.
Returned as a path rather than an array so the caller controls its lifetime; every encode-side reference comes from here.
deliveredis the delivered mp4 this leg is the reference FOR, and passing it is what makes the leg’s pixel format match — **probed off the file rather than re-derived** (an#72). Re-deriving is not enough: the delivered encode resolves its format fromRenderContext.pix_fmtor fromrender.DEFAULT_PIX_FMT, so a leg that consults the global tracks the bench’s lever and silently missesan render --pix-fmt yuv444p, which sets the context instead. Only the file knows which seam won.deliveredis keyword-only and has no default, deliberately — the same argumentimageio._reshapemakes for its ownframes: a default here would let a caller opt out of the match by omission, and the failure is invisible (a 4:2:0 reference silently measuring a 4:4:4 delivery). PassingNoneexplicitly means “no delivered file to match” and falls back to the module default; that is right for a caller building a reference for its own sake and wrong for the bench, which always has the file in hand.
- an.bench.run.naming_git_state(git, *, blessed, root)[source]#
Which git state NAMES the row: the tree the run left, not the one it read.
run_bench()readsgit_stateonce, before the corpus loop, because that is the tree the pixels came from. A--blessrun then WRITES into that same tree inside the loop — the golden PNGs, and a bless record whoseblessed_atmoves on every run — so by the time the row is named the two are no longer one fact.Named under the pre-bless state, a bless on a clean tree lands as
<date>-<sha>.json: a filename claiming a commit whose tree that very run then modified, which is exactly what the-dirtysuffix exists to prevent (an.bench.paths.ledger_path()). So the row records both —gitis what rendered,git_after_blessis what the run left behind — and the filename follows the second. A non-bless run re-reads nothing.Deliberately a plain function of
blessedrather than an inline conditional: it is the whole of the fix, and a guard for it must not have to render the corpus to reach it.- Return type:
- an.bench.run.pinned_frames_min_pairwise_changed_px(capture, times)[source]#
expression_min_pairwise_changed_pxfor one scene: decode the pinned frames from today’s render and take the minimum over every pair of the count of pixels that differ (an#98). On a two-frame scene that is the pair’s own change;unavailable, never zero, only when a scene pins a single frame, which the fixture rule forbids.- Return type:
- an.bench.run.run_bench(*, scenes=None, out=None, keep_render=None, write=True, bless='', golden_root=None, lossless_scratch_root=None, root=None)[source]#
Render the corpus, compute the panel, and (by default) write the row.
blessis the reason a re-bless is being made, and passing it is what turns the run into a bless. One argument rather than a--blessflag plus a--reasonstring, so “blessed with no recorded reason” — the failure this rule exists to prevent — is not expressible.lossless_scratch_rootis forwarded to_lossless_scratch_dir()for every scene (an#143) — a caller may point it at a shared parent to prove that two concurrent lossless-leg encodes still get distinct scratch directories under it; production code has no reason to pass it.golden_rootredirects where goldens are read and written, and it exists because without it a test of the bless path has no choice but to overwrite the committed corpus. That is not hypothetical: the first version of an#38’s bless test did exactly that, replacing a real bless record’s reason with the test’s own.rootis the source checkout whose corpusscenesnames (default:an’s own); a genre package runs its own corpus with its own checkout (cutan.bench).- Return type:
- an.bench.run.shot_policy_provenance(shots)[source]#
The per-shot COMPILE POLICIES a scene rendered under, keyed by shot id.
Additive scene-provenance facts read off each staged
scene_json’smeta;shotsis any iterable of objects withshot_idandscene_json(aShotCapture, or a stand-in).blink_phases(an#88): the per-entity blink phase each shot was compiled with. A renamed corpus character re-phases every blink and moves every pixel metric; this makes that a visible diff in the row instead of an unexplained shift.step_hz(an#89): the stepped-timing policy each shot’s tweens were compiled under;None= smooth (the meta key is absent then, by the serializer). Stepping movesscene_contract_sha256by construction — the resampled keyframes ARE the contract — and this says the movement was a timing policy, not a mystery.
>>> from types import SimpleNamespace as NS >>> shot_policy_provenance([NS(shot_id="s1", scene_json={"meta": {"step_hz": 12.0, "blink_phases": {"a": 0.5}}}), ... NS(shot_id="s2", scene_json={"meta": {}})]) {'blink_phases': {'s1': {'a': 0.5}, 's2': {}}, 'step_hz': {'s1': 12.0, 's2': None}, 'viseme_keyframes_per_second': {'s1': None, 's2': None}}
- an.bench.run.viseme_keyframes_per_second(scene_json)[source]#
Viseme keyframes per second of dialogue in a compiled shot, or
Nonewhen nothing speaks (an#97).Counted over the
__viseme__clips: keyframes minus one per channel (the trailing rest is an invariant, not a decision — today’s emission has one channel per clip), over the clips’ summed durations, which are the frame-ceiled windows rather than the lines’ exact lengths (a small downward bias, the same for every row). Keyframes, not distinct shapes — a repeated code (the carried rest before the terminal one) counts. The co-articulation passes bring this below the RAW provider track’s rate; against the old drop-not-hold condenser it can rise, because that loop was cheaper only by dropping shapes. Recorded as provenance rather than a panel metric because no lever in the registry moves it.>>> viseme_keyframes_per_second({"animations": {}}) >>> viseme_keyframes_per_second({"animations": {"__viseme__s_0_m": {"duration": 2.0, ... "channels": [{"keyframes": [{"time": 0}, {"time": 0.5}, {"time": 1.0}, {"time": 2.0}]}]}}}) 1.5