Program Listing for File pose_kinematics.hpp#

Return to documentation for file (src/analysis/pose_kinematics.hpp)

#pragma once
#include <QPointF>
#include <QVector>
#include <cstdint>
#include <limits>

#include "analysis/pose_analysis_result.hpp"

namespace mosaic {

/// @brief One derived sample along a single keypoint's trajectory.
///
/// speedPxPerS is NaN for the first valid sample (no prior point to diff
/// against); accelPxPerS2 is NaN for the first two.
struct KinematicSample {
    int64_t timestampNs = 0;
    QPointF position; ///< px, post-smoothing
    double speedPxPerS  = std::numeric_limits<double>::quiet_NaN();
    double accelPxPerS2 = std::numeric_limits<double>::quiet_NaN();
};

/// @brief Summary statistics over one compute_kinematics() call's samples.
struct KinematicStats {
    double totalDistancePx = 0.0;
    double avgSpeedPxPerS  = std::numeric_limits<double>::quiet_NaN(); ///< NaN if <2 valid samples
    double maxSpeedPxPerS  = std::numeric_limits<double>::quiet_NaN();
};

/// @brief Output of compute_kinematics() — per-sample series plus summary stats.
struct KinematicsSeries {
    QVector<KinematicSample> samples;
    KinematicStats stats;
};

/// @brief Derives speed/acceleration for one keypoint of one subject from an
/// already-loaded PoseAnalysisResult.
///
/// Frames where the subject/keypoint is missing or has visibility < 0.1
/// (matching SkeletonOverlayW's paint threshold) are skipped rather than
/// interpolated — velocity across the resulting gap uses the real elapsed
/// time between the two nearest valid samples, so a detection dropout
/// yields an honest average speed across it instead of a value fabricated
/// from an assumed frame rate.
///
/// @param smoothingWindow Number of samples in a centered moving average
///     applied to position before differentiating (1 = no smoothing). Even
///     values are treated as the next-lower odd width (a centered window
///     needs a middle sample); the UI is expected to only offer odd
///     values, but this function stays safe if called directly with an
///     even one.
///
/// @see docs/math/pose_kinematics.rst for the full derivation, including
///      why avgSpeedPxPerS is time-weighted rather than a naive mean of
///      per-interval speeds.
[[nodiscard]] KinematicsSeries compute_kinematics(const PoseAnalysisResult& result,
                                                  int keypointIndex, int subjectIndex,
                                                  int smoothingWindow);

} // namespace mosaic