Program Listing for File dyadic_kinematics.hpp#
↰ Return to documentation for file (src/analysis/dyadic_kinematics.hpp)
#pragma once
#include <QVector>
#include <cstdint>
#include <limits>
#include "analysis/skeleton3d_result.hpp"
namespace mosaic {
/// @brief One derived sample of the relationship between two tracked people
/// at one fused tick.
///
/// Fields are independently NaN whenever that specific metric's own data
/// requirement isn't met at this tick (see compute_dyadic_kinematics()) —
/// never fabricated/interpolated across a tracking gap.
struct DyadicSample {
int64_t timestampNs = 0;
double distanceMm = std::numeric_limits<double>::quiet_NaN(); ///< hip-midpoint to hip-midpoint
double approachRateMmPerS = std::numeric_limits<double>::quiet_NaN(); ///< negative = closing
double facingCosine = std::numeric_limits<double>::quiet_NaN(); ///< see class doc below
double congruentMotionCorr =
std::numeric_limits<double>::quiet_NaN(); ///< rolling Pearson r of speed
};
/// @brief Summary statistics over one compute_dyadic_kinematics() call's samples.
struct DyadicStats {
double meanDistanceMm = std::numeric_limits<double>::quiet_NaN();
double minDistanceMm = std::numeric_limits<double>::quiet_NaN();
double maxDistanceMm = std::numeric_limits<double>::quiet_NaN();
double meanFacingCosine = std::numeric_limits<double>::quiet_NaN();
double meanCongruentMotionCorr = std::numeric_limits<double>::quiet_NaN();
/// Fraction (0-1, not already scaled to %) of the result's own tick
/// range where both A and B have a valid hip midpoint — i.e. where
/// distanceMm is computable at all. 0 if the result has no frames.
double pctTicksBothPresent = 0.0;
};
/// @brief Output of compute_dyadic_kinematics() — per-tick series plus summary stats.
struct DyadicKinematicsSeries {
/// One entry per tick in the source Skeleton3DResult, in the same
/// order — gaps are left with NaN fields, never skipped, so a caller
/// can see exactly where data was and wasn't available.
QVector<DyadicSample> samples;
DyadicStats stats;
};
/// @brief Derives interpersonal distance/approach-rate/facingness/congruent-motion
/// between two already-reconstructed people (by trackId) from an
/// already-loaded Skeleton3DResult (the 3D Pose Reconstruction plugin's own
/// session-level result).
///
/// Requires the result's keypoint_names() to contain "nose",
/// "left_shoulder", "right_shoulder", "left_hip", and "right_hip" (resolved
/// once by name, not a hardcoded COCO index) — facingCosine stays
/// permanently NaN for every sample (distanceMm/approachRateMmPerS still
/// compute normally) if any of the five is missing, rather than crashing or
/// silently reading the wrong index on a non-COCO skeleton.
///
/// Distance/approach-rate use each person's hip midpoint
/// (average of their left_hip/right_hip positionRoom). Facingness needs the
/// same hip midpoint plus the shoulder midpoint and nose, per person — see
/// the .cpp's facing_vector() for the full derivation: a "chest-forward"
/// unit vector from cross(spine, shoulders), with its inherent front/back
/// sign ambiguity resolved by checking which sign points toward that
/// person's own nose. facingCosine is the dot product of the two people's
/// forward vectors: near -1 when oriented in opposite directions (commonly
/// — but not provably, since this is a position-independent orientation
/// comparison — face-to-face in a two-person interaction; also consistent
/// with standing back-to-back facing directly away from each other), near
/// +1 when oriented the same way (e.g. side-by-side), near 0 when
/// perpendicular. This limitation is documented, not a bug: the metric is a
/// pure torso-heading correlation, not a "looking at each other" detector.
///
/// Approach rate and each person's own speed (used internally for
/// congruent motion) both follow pose_kinematics.hpp's established
/// discipline exactly: skip missing samples rather than interpolate, and
/// derive using the REAL elapsed time between the two nearest valid
/// samples, so a tracking gap yields an honest average rather than a
/// fabricated spike.
///
/// @param congruentMotionWindow Trailing window size (in paired valid speed
/// samples, not ticks) for the rolling Pearson correlation of A's and
/// B's instantaneous speed. A tick's congruentMotionCorr stays NaN
/// until at least 3 paired samples exist in its trailing window, and
/// also if either signal has zero variance in that window (e.g. one
/// person standing still — not an error, just undefined correlation).
[[nodiscard]] DyadicKinematicsSeries compute_dyadic_kinematics(const Skeleton3DResult& result,
int trackIdA, int trackIdB,
int congruentMotionWindow = 30);
} // namespace mosaic