Analyze: Aggregate Result Statistics

After a movie is traced, the Analyze page computes aggregate statistics from the per-frame trackpoint data, in addition to the position-vs-time charts. The calculations are ported from the legacy iOS app’s showResult() and live in the pure, DOM-free module src/app/static/analysis_results.mjs (issue #986). The display wiring lives in canvas_tracer_controller.mjs (display_results()).

Result modes

A selector on the Analyze page (#result-mode-select) chooses which statistics to show:

  • All (default) — both groups below.

  • Gravitropism — Distance and Angle.

  • Circumnutation — Max Amplitude.

The tracked “tip” marker is the Apex if present, otherwise the first graphable (non-ruler) marker. Ruler markers set the millimeter scale; when two or more are present, distances are reported in mm, otherwise in pixels.

Gravitropism

Computed from the tip in the first and last (trimmed) frames, and the Inflection Point marker (see Reserved marker names below).

  • Distance — Euclidean displacement of the tip from first to last frame, multiplied by the scale.

  • Angle — the bend at the inflection pivot, via the law of cosines. With b = first-tip→first-inflection, c = last-tip→last-inflection, and a = first-tip→last-tip:

    angle = acos((b² + c² − a²) / (2·b·c)) · 180/π
    

    The inflection point is tracked per frame, so b uses its first-frame position and c its last-frame position. The angle is scale-invariant. It is omitted when no inflection point is present or the triangle is degenerate (a tip coincides with the inflection point).

Circumnutation

  • Max Amplitude — horizontal range of the tip over all (trimmed) frames, (max x min x) × scale.

  • RateMax Amplitude ÷ elapsed time between the x-extreme frames (legacy semantics; see #1053). Null when the extremes fall on the same frame.

Rate and the capture interval (fpm)

Rate is displacement per unit time. The time base is the capture interval fpm (frames per minute), a per-movie value the user supplies at upload or on the Analyze page (#1056). The conversion is:

elapsed_time(min) = frame_span ÷ fpm
  • Gravitropism Rate = Distance ÷ elapsed_time over the first→last frame.

  • Circumnutation Rate = Max Amplitude ÷ elapsed_time between the frames of the minimum and maximum x.

When fpm is unset (legacy movies, or not yet entered), the graph x-axis stays in frames and Rate is reported per frame (mm/frame or pixel/frame); once a positive fpm is set it is reported per minute. fpm is distinct from the encoded playback fps and is stored as a string on the movie row (authoritative) with a best-effort snapshot in the traced MP4 (see Movie attribution and research metadata). Editing fpm on the Analyze page only rescales time and rate — it does not require retracing.

This replaces the legacy frames × 20 ÷ framerate formula (an iOS playback-time artifact); the FPS = 20 constant is not used.

Reserved marker names

Two marker names are reserved and given special treatment:

  • Ruler Nmm (e.g. Ruler 0mm, Ruler 10mm) — define the mm/pixel scale; excluded from graphing.

  • Inflection Point (matched case-insensitively) — the gravitropism pivot. It is tracked and graphed like any plant marker, but its reserved name lets the dedicated Add Inflection Point button and the Angle calculation locate it. Only one is allowed per movie. See issue #1033.