79 lines
3.2 KiB
C++
79 lines
3.2 KiB
C++
#pragma once
|
|
|
|
#include "fgc/ExposurePolicy.h"
|
|
|
|
#include <map>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
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<StoreEntry> 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<std::string, StoreEntry> entries_;
|
|
std::optional<CaptureSettings> last_accepted_;
|
|
long long last_accepted_ms_ = 0;
|
|
};
|
|
|
|
} // namespace fgc
|