92 lines
3.8 KiB
C++
92 lines
3.8 KiB
C++
#pragma once
|
|
|
|
#include "fgc/ExposurePolicy.h"
|
|
#include "fgc/ExposureStore.h"
|
|
#include "fgc/ICameraSource.h"
|
|
#include "fgc/ImageQuality.h"
|
|
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
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<ICameraSource> inner, ExposureStore* store, Params params,
|
|
std::function<std::pair<double, double>()> angle,
|
|
std::function<long long()> now_ms = {},
|
|
std::function<void(int)> 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<CameraDeviceInfo> 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<ICameraSource> inner_;
|
|
ExposureStore* store_; // not owned; may be null
|
|
Params params_;
|
|
std::function<std::pair<double, double>()> angle_;
|
|
std::function<long long()> now_ms_;
|
|
std::function<void(int)> sleep_ms_;
|
|
FrameCallback callback_;
|
|
CaptureReport last_report_;
|
|
long long degraded_total_ = 0;
|
|
};
|
|
|
|
} // namespace fgc
|