#pragma once #include "fgc/ExposurePolicy.h" #include "fgc/ExposureStore.h" #include "fgc/ICameraSource.h" #include "fgc/ImageQuality.h" #include #include #include namespace fgc { // Outcome of one gated capture, for logging and the TUI. struct CaptureReport { bool captured = false; // a frame was delivered at all bool degraded = false; // budget exhausted; this is the best effort int attempts = 0; std::string reason; // verdict of the attempt we kept ImageMetrics metrics; // metrics of the frame we kept CaptureSettings settings; // settings that produced it double yaw_deg = 0.0, pitch_deg = 0.0; }; // An ICameraSource that wraps another one and turns a single trigger() into // "shoot, judge, correct, reshoot until acceptable", seeded from per-angle memory. // // It is a DECORATOR so that nothing above it changes: CaptureScheduler still just // calls trigger(), and the accepted frame still reaches ImagePipeline through the // ordinary frame callback. Because it depends only on ICameraSource plus the pure // policy/metric modules, the entire retry loop is unit-testable against a fake // camera - no hardware, no OpenCV. class GatedCameraSource : public ICameraSource { public: struct Params { bool enabled = true; int max_attempts = 3; int min_attempts = 1; // >1 always shoots extra and keeps the sharpest int acquire_timeout_ms = 2000; int settle_delay_ms = 0; // extra pause after the move, before attempt 1 CaptureSettings defaults; // used when the store has nothing QualityParams quality; PolicyParams policy; }; // angle: where the gimbal is pointing now (degrees), used to key the store. // now_ms: injectable clock, for deterministic tests. GatedCameraSource(std::unique_ptr inner, ExposureStore* store, Params params, std::function()> angle, std::function now_ms = {}, std::function sleep_ms = {}); ~GatedCameraSource() override; void open() override; void close() override; void start() override; void stop() override; bool trigger() override; bool setFrameRate(double fps) override; void setFrameCallback(FrameCallback cb) override; int cameraCount() const override; std::vector deviceInfo() override; // Pass-throughs, so the gate can itself be wrapped or inspected. bool acquireFrame(Frame& out, int timeout_ms) override; bool setExposure(double us) override; bool setGain(double db) override; bool setAutoExposureGain(bool on) override; double currentGain() override; // Result of the most recent trigger(), for the log and the TUI. const CaptureReport& lastReport() const { return last_report_; } // How many captures this session fell back to a best effort. A rising count is // the signal that thresholds or the exposure limits need revisiting. long long degradedTotal() const { return degraded_total_; } private: std::unique_ptr inner_; ExposureStore* store_; // not owned; may be null Params params_; std::function()> angle_; std::function now_ms_; std::function sleep_ms_; FrameCallback callback_; CaptureReport last_report_; long long degraded_total_ = 0; }; } // namespace fgc