Skip to main content
Field-by-field types and nullability live in the API reference. This page is the meaning behind them: what a number counts, what it is a count of, and which readings of it are wrong. Derivations are given wherever knowing one changes how you should read the number. Calibration is not: the cutoffs, models, tooling and agreement rules behind a measurement are ours, and are represented on the wire only by opaque version tokens. Where that boundary bites, it says so.

Populations and denominators

These names recur across every endpoint, and getting them wrong is the easiest way to compute a confident wrong answer. On the cleanliness response these reconcile exactly, and it is worth asserting:
clips_analyzed and clips_measured are different populations, and the second is not a rounding error away from the first. Anything computed over one must not be divided by the other. clips_analyzed is also per endpoint, because each names the run that answered it. Vocabulary and cleanliness resolve their runs independently, so their clips_analyzed are two facts about two runs and need not agree. Compare them only when the echoed run tokens say they came from the same measurement. segments_unassessed and segments_unusable are published rather than absorbed because they are the honest edge of the answer. A corpus with a large count in either is one where the shares describe a smaller slice of the footage than the clip count suggests.

Vocabulary

The corpus described in three sections: environments (where the footage was shot), objects (what is handled), actions (what is done).

Terms

A term is a published concept, not a raw string. Independent passes over the footage produce wordings that mean the same thing; those are merged, and spellings lists every variant that folded into the published term. This is why term matching in GET /api/quality/clips is evidence-based: any spelling that was merged finds the term, and a spelling nothing ever produced finds nothing. kind is the coarser category the term groups under, absent when the term is its own kind. It is what makes distinct_kinds auditable rather than asserted — you can re-derive the kind count from the term table. clip_count and occurrences both report how many distinct clips carry the term, counted once however many passes agreed on it.
How many independent passes had to agree before a term was published is not on the wire. That describes the filter, and the filter is ours. What you get is the surviving vocabulary and the evidence behind each of its members.

Counting a vocabulary

Four counts, and they answer four different questions. distinct_terms — published terms that carry a tier. The plain count of what is in the corpus. It rises with how much footage was read, so it is not comparable across datasets. effective_distinct_terms — the repetition-corrected count. Formally exp(H) over the occurrence distribution, where H is Shannon entropy: a Hill number of order one, the “effective number of species” of ecology, the perplexity of the distribution. Read it as a count, because it is measured in terms and behaves like one. Ten evenly-used terms give back exactly 10.0. Two hundred kitchen clips plus one glimpse of a garage give back 1.06, not 2. A corpus naming 134 objects where four of them account for nearly every clip is not as rich as one naming 134 evenly, and this is the number that can tell them apart. rarefied_distinct_termsdistinct_terms interpolated down to a fixed common depth, stamped in comparable_depth_clips. This is the only count here that may be set beside another dataset’s. A raw term count keeps rising with how much footage was read and that curve never flattens, so comparing two raw counts compares sampling effort as much as content. It is null when the corpus is thinner than the common depth, because rarefaction interpolates and must not extrapolate — an estimate for a depth never reached would be a guess wearing a number’s clothes. distinct_kinds — how many coarser categories the published terms fall into. Answers “how varied is this corpus” where the term count answers “what is in it”.

Tiers

Each section’s terms are partitioned into tiers. For objects and actions these are ordinal — they rank how much dexterity handling or performing the term demands, so a higher tier is a harder thing and not merely a different one. For environments they are not: the tiers there are collection settings, which are categories with no order between them. One wire shape, two meanings, and only the first can be summed into anything resembling a difficulty. The response enumerates every tier, including ones no term landed in, with its stable wire name, its display label, its share of the section’s occurrences, and its most frequent members. Reading the labels and the examples from the response is the intended way to learn the scheme; it means the tier set cannot drift away from this page. untiered_terms lists published terms that could not be placed. They are in neither distinct_terms nor the shares, which is why the term table can be longer than the counts imply. Every published term is either counted in distinct_terms or named in untiered_terms — that identity is worth asserting in your own pipeline.

Cleanliness

An exhaustive partition of the decoded frames into three categories, plus the denominators behind it. The endpoint decodes nothing and calls no model; it reduces measurements already stored. usable_frames + distorted_frames + jerky_frames == frames_measured, exactly. The three _share fields are derived from that same denominator and are absent — never three zeros — when nothing was measured. Two derivations that change how the counts read:
  • Distortion takes precedence. A frame that is both unreadable and abrupt is counted distorted, never jerky, so the two categories never double-count and jerky means “legible but abrupt”.
  • A frame that could not be measured is distorted, not usable. A frame with no measurable focus or brightness carries no image information, and the conservative direction is to flag it rather than let it inflate the usable share. The first frame of a clip has no predecessor and so is never jerky, though it is still judged on its own terms.
jerky cannot distinguish camera shake from fast subject motion. It responds to both, and on egocentric footage the wearer’s own head movement produces it constantly. Treat it as a signal to weigh against what you are training, not as a rejection verdict.
classification_version is an opaque token naming the rule that produced the partition. Two partitions carrying the same token were produced by the same rule and can be compared directly. Two carrying different tokens cannot, and a corpus should be re-read rather than re-interpreted when the token moves. The token exists precisely so that comparability is available without the cutoffs being published — which is the only thing the cutoffs would have told you, and publishing them would let a reader re-partition a corpus into whatever answer suited them.

Hand activity

Footage-weighted shares over measured_seconds. The measure is defined in terms of the camera wearer, so it says something only about egocentric footage. “Camera-wearer” is load-bearing. Another person’s hands in frame are not the wearer’s and do not count, nothing is inferred from a hand that is out of frame or behind an object, and any amount of visibility counts — fingertips included. Visibility is presence, not use. two_hands_visible is a strict subset of hands_visible: one-hand and two-hand stretches are recorded as disjoint spans and summed. Manipulation is observed independently of visibility, so no containment holds between them in either direction, and you should not expect active_manipulation ≤ hands_visible to be true of every corpus. Every share is null rather than 0.0 when measured_seconds is zero.

Clips

One row per clip: counts and seconds throughout, never shares. A share commits you to our denominator; counts let you re-aggregate over any subset — one operator, one site, one week — which is the reason this endpoint exists beside the corpus-level ones. clip is the clip’s identity: the source file and the span within it. No renumbering changes it, so it is the join key to use between pages, between endpoints, and against your own records. analysed_secs is a duration of footage assessed, not an offset into the source. episode_index is the position the clip had when analysed, and is absent when two passes disagreed about it. A corpus renumbered between passes means either number would address somebody else’s footage, so neither is published. Join on clip. frame_quality and hand_activity are opt-in, via frame_quality=true and hand_activity=true. They are booleans rather than addresses because they add columns to rows the vocabulary has already chosen.
An absent object is not a zeroed one. A clip carrying no frame_quality was never decoded. A clip whose frame_quality.active is 0 was decoded and found to contain no usable frames. Those are opposite facts, and treating the first as the second silently drags every average down. Check for the field before you aggregate it — nothing is filled in with zeros to make the rows uniform.

Reading a run token

Every metric response echoes the run that answered it — vocabulary, analysis, frame_quality. You need not supply one: name a dataset and you get its most recent measurements. The tokens are opaque and exist for exactly one purpose: telling two answers apart. The same token in two responses a week apart means the corpus was not re-measured between them and the numbers are directly comparable. A different token means it was, and any drift you see is real change plus re-measurement, not one or the other. Do not pin a token. It addresses one stored run, and a re-measurement will leave a pinned integration reading an answer that no longer reflects the corpus.