cutan.impacts.truth¶
Ground truth: what was intended, what was executed, and what each frame shows.
Every position here comes from one of two sources, and they are checked against each other at every instant the renderer captures:
the compiled document, evaluated by
an.stage.timeline— the executable spec of the JS runtime (parity-tested under node) — and projected to canvas pixels byscreen_position. The per-frame keypoints come from here, at exactly the instants the renderer captured;the analytic stroke (
cutan.impacts.stroke.Stroke), mapped through the object’s affine channels. The dense trajectory comes from here, because it is exact at any instant and cheap.
A frame with an open shutter is the AVERAGE of its sample instants, so what it shows is the average of the keypoint’s positions over them — not the position at mid-exposure. The two differ most exactly where estimators are scored: a surface contact is a V in position, and its average sits above contact (by ~6 px at 30 fps and a 180-degree shutter). So each frame records both, and the observation stream carries the average.
ground_truth() refuses (TruthMismatch) if the two sources
disagree at any sample instant, or if the compiled document does not put the
object at contact at every executed impact; write_impact_clip additionally
refuses if the document the renderer staged is not the one the truth was read
from. A sidecar that could silently drift from its video would be worse than
none.
**The truth.json schema** (schema: "cutan.impacts/truth", version
TRUTH_SCHEMA_VERSION; every time is scene seconds, every position canvas
pixels with the origin at the top-left and y down; objects are a LIST so a
clip with two can be described without changing any reader):
generator—{package, version, module}.spec— thecutan.impacts.clip.ImpactClipSpec, verbatim;ImpactClipSpec.from_dict(truth["spec"])regenerates the clip (byte for byte under the sameanversion).clock— the resolvedan.frame_clock.FrameClock.tempo—{points: [[beat, bpm], ...]}, piecewise-linear in beats.clip—{duration, fps, width, height, frame_count, rendered, files}.objects— one entry per striking object:{name, keypoints: [names], impact_keypoint, params, surface_xy (top-centre of the surface, or null), stroke: {kind, segments: [{t0, t1, h0, h1, easing}], channels: [{target, property, contact_value, stroke_extent}]}}.his exact and continuous; each channel’s value iscontact_value - stroke_extent * h.events— one per impact:object(a name fromobjects),index,beat,amplitude;t_grid— INTENDED time (the tempo grid);t_impact— EXECUTED time, continuous;offset = t_impact - t_grid;kind—surface(contact; velocity reverses att_impact) orair(turning point; velocity is zero att_impact);t_peak_speed(= t_impactfor surface,t_impact - brakefor air),peak_speed(stroke heights/s),peak_speed_px(the impact keypoint’s px/s),fall,brakeandrise(the stroke into this impact and the one out of it, as executed, seconds; drawn per stroke when the spec spreads them, cutan#27);impact_xy— the impact keypoint att_impact;frames— what the frames show:before(last frame whose exposure closed at or before the impact),after(first to open at or after it),during(the frame whose exposure contains it, elsenull),nearest(+nearest_error, by exposure midpoint), andlowest— the frame a naive “lowest point” detector picks, by the stroke height the frame SHOWS (averaged over its samples) — withlowest_h,lowest_t_reportedandlowest_error(its reported time minust_impact: the frame-snapped baseline’s error).
frames— one per video frame:index,t_nominal(index / fps, what the mp4 says),t_open,t_close,t_mid,samples(the instants rendered and averaged),t_reported(whatkeypoints.ndjsoncarries ast),keypoints({object: {name: [x, y]}}, averaged over the samples — what the frame shows) andkeypoints_mid(att_mid; equal tokeypointswhen the shutter is instantaneous).
keypoints.ndjson is one line per frame,
{"tick", "t", "value": {"width", "height", "keypoints": [{"object", "name",
"x", "y"}]}} — observations only (the frame’s keypoints, at its reported
time). trajectory.csv is t, h, <object>.<kp>_x, <object>.<kp>_y, ... at
spec.trajectory_hz.
Module Attributes
Bumped on any change a reader must know about; additive fields bump MINOR. |
|
The analytic and compiled keypoints must agree to this many pixels. |
Functions
|
The truth document for one clip (see the module docstring for its schema). |
|
One observation per frame, in thoremin's recorder shape. |
|
The dense continuous trajectory: a header row, then one row per |
Exceptions
The ground truth and the thing it describes disagree. |
- cutan.impacts.truth.KEYPOINT_TOLERANCE_PX: float = 1e-06¶
The analytic and compiled keypoints must agree to this many pixels. Both are double-precision evaluations of the same easing, so any real disagreement is orders of magnitude larger.
- cutan.impacts.truth.TRUTH_SCHEMA_VERSION: str = '1.1.0'¶
Bumped on any change a reader must know about; additive fields bump MINOR. 1.1.0: each event’s
rise(cutan#27).
- exception cutan.impacts.truth.TruthMismatch[source]¶
Bases:
RuntimeErrorThe ground truth and the thing it describes disagree.
- cutan.impacts.truth.ground_truth(*, scene, obj, stroke, events, frames, header)[source]¶
The truth document for one clip (see the module docstring for its schema).
headeris merged in first (spec, clock, tempo, clip, generator); this function ownsobjects,eventsandframes.
- cutan.impacts.truth.keypoint_lines(truth, *, digits=4)[source]¶
One observation per frame, in thoremin’s recorder shape.
{"tick", "t", "value": {"width", "height", "keypoints": [{"object", "name", "x", "y"}]}}—tis the frame’s REPORTED timestamp and the points are what the frame shows; nothing else from the truth leaks in.