#pragma once #include "fgc/ExposurePolicy.h" #include #include #include namespace fgc { // Per-angle exposure memory: what settings worked last time the gimbal looked // this way. This is what lets a triggered capture start from a setting known to // be roughly right, instead of paying the camera's own auto-exposure several // frames to re-converge at every waypoint. // // Keyed by QUANTISED yaw/pitch rather than waypoint index, so the memory survives // an edit to the scan grid and also serves ControlCode 1 directed moves, which // have no waypoint index at all. struct StoreEntry { double yaw_deg = 0.0; double pitch_deg = 0.0; double exposure_us = 0.0; double gain_db = 0.0; double sharpness = 0.0; // sharpness of the last accepted frame here double mean_luma = 0.0; long long timestamp_ms = 0; // when this entry was last updated (epoch ms) int attempts = 0; // attempts the last visit needed }; class ExposureStore { public: // quantum_deg: angle bucket size. stale_s: how old an entry may be and still // be trusted as this angle's seed. explicit ExposureStore(std::string path, double quantum_deg = 1.0, long long stale_s = 1800); // Load from / save to the CSV. Both are tolerant: a missing file is simply an // empty store, and a malformed row is skipped rather than failing the load, so // a corrupt line can never stop a scan from running. bool load(); bool save() const; // Bucket key for an angle, e.g. "y137_p-5". static std::string keyFor(double yaw_deg, double pitch_deg, double quantum_deg); std::optional find(double yaw_deg, double pitch_deg) const; std::size_t size() const { return entries_.size(); } const std::string& path() const { return path_; } // Three-tier seed, in order of how well each predicts the CURRENT light: // 1. this angle's own entry, if fresher than stale_s; // 2. else the last settings accepted anywhere - light changes affect all // angles together, so a 30 s old reading one heading over beats an // hour-old reading at this exact heading; // 3. else the configured defaults. CaptureSettings seed(double yaw_deg, double pitch_deg, long long now_ms, const CaptureSettings& defaults) const; // This angle's reference sharpness for the relative blur check, or 0 when // unknown or too old to compare against. double referenceSharpness(double yaw_deg, double pitch_deg, long long now_ms) const; // Record an accepted capture. void update(double yaw_deg, double pitch_deg, const CaptureSettings& settings, const ImageMetrics& metrics, long long now_ms, int attempts); private: bool fresh(const StoreEntry& e, long long now_ms) const; std::string path_; double quantum_deg_; long long stale_s_; std::map entries_; std::optional last_accepted_; long long last_accepted_ms_ = 0; }; } // namespace fgc