#pragma once #include #include namespace fgc { // What the scheduler needs to know about one delivered frame to judge whether // the camera's auto-exposure has settled: its place in the stream, its // brightness (sampled from the pixels, 0..255, negative if unknown) and the // exposure and gain it was taken with (chunk data, negative if unknown). struct FrameStats { uint64_t seq = 0; double brightness = -1.0; double exposure_us = -1.0; double gain_db = -1.0; }; // When has the image settled after the gimbal stops? Measured on 24 forced // exposure steps (docs/raw12-pipeline-measurements.md, "Which frame to keep"): // // "exposure and gain unchanged for 3 frames" right 0 of 24 times // "brightness in the camera's target band, 2 frames" right 12 of 24 // "in the band AND steady (<= 2% change) for 3 frames" right 21 of 24 <- this // // Auto-exposure moves in steps and pauses mid-way, so values that hold still // prove nothing; and coming down from too bright, brightness enters the band // ~15% above where it settles, so "in band" alone fires early. The band is the // camera's own: IntensityControllerTarget +- IntensityControllerTolerance (in // percent of full scale), 50 +- 5 by default. // // A second way to finish: brightness is out of the band but steady, and exposure // and gain are at the limits that would move it towards the band. Waiting cannot // help then (a scene too dark or too bright for the camera's limits). class SettleRule { public: struct Params { double target_pct = 50.0; // IntensityControllerTarget double tolerance_pct = 5.0; // IntensityControllerTolerance int frames = 3; // consecutive frames that must agree double steady = 0.02; // largest frame-to-frame brightness change // Auto-exposure limits; negative = unknown, and the limit test is off. double exposure_min_us = -1.0; double exposure_max_us = -1.0; double gain_min_db = -1.0; double gain_max_db = -1.0; }; enum class Verdict { Waiting, Settled, AtLimit }; SettleRule() = default; explicit SettleRule(const Params& p) : p_(p) {} void setParams(const Params& p) { p_ = p; reset(); } const Params& params() const { return p_; } // Start over (the gimbal stopped at a new waypoint). void reset() { window_.clear(); } // Feed the next frame, in arrival order. A frame without a brightness // breaks the run of agreeing frames. Verdict add(const FrameStats& f); // The band on the 0..255 scale frames are sampled on. double bandLow() const { return (p_.target_pct - p_.tolerance_pct) * 2.55; } double bandHigh() const { return (p_.target_pct + p_.tolerance_pct) * 2.55; } private: bool steady() const; bool atLimit() const; Params p_; std::deque window_; // the last p_.frames frames }; } // namespace fgc