114 lines
4.5 KiB
C++
114 lines
4.5 KiB
C++
#pragma once
|
|
|
|
#include "fgc/ICameraSource.h"
|
|
#include "fgc/IControlChannel.h"
|
|
|
|
#include <atomic>
|
|
#include <condition_variable>
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <queue>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
namespace fgc {
|
|
|
|
// Gimbal orientation in degrees, stamped onto each CamEvent.
|
|
struct Orientation {
|
|
float yaw_deg = 0.f;
|
|
float pitch_deg = 0.f;
|
|
};
|
|
|
|
// Consumes captured frames and turns them into stored artifacts: rotate by
|
|
// Camera.rotation_deg, encode to JPEG XL, write to
|
|
// <output_dir>/<label>/<timestamp>.jxl, and publish a CamEvent. Runs a
|
|
// background worker so encoding never blocks acquisition. Split out of the old
|
|
// VimbaHandler::SaveImage so any camera source (real or mock) can feed it.
|
|
class ImagePipeline {
|
|
public:
|
|
// Per-camera output settings. Label, rotation and encoding are all properties
|
|
// of the individual camera now, so they travel together and are indexed by
|
|
// Frame::cam_id rather than assumed uniform across the rig.
|
|
struct CameraOutput {
|
|
std::string label; // output subdir + CamEvent "cam"
|
|
int rotation_deg = 0; // clockwise; 0/90/180/270
|
|
double jxl_distance = 0.8; // 0 = lossless
|
|
int jxl_effort = 4;
|
|
};
|
|
|
|
struct Params {
|
|
std::string output_dir;
|
|
std::vector<CameraOutput> cameras; // by camera index, in open order
|
|
std::string tower;
|
|
bool demo = false; // copy demo_image instead of encoding
|
|
std::string demo_image = "test_smoke.jxl";
|
|
bool display = false; // show an OpenCV preview window
|
|
};
|
|
|
|
// orientation: returns the current gimbal yaw+pitch (degrees) for CamEvent.
|
|
// Called from submit(), i.e. on the camera's delivery thread at the moment the
|
|
// frame arrives - see submit() for why it must not be sampled any later.
|
|
ImagePipeline(IControlChannel& channel, std::function<Orientation()> orientation, Params params);
|
|
~ImagePipeline();
|
|
|
|
void start();
|
|
void stop();
|
|
|
|
// Enqueue a frame for processing (thread-safe; copies the frame).
|
|
//
|
|
// The gimbal orientation is sampled HERE, not in the worker, and travels with
|
|
// the frame. Sampling it after encoding would attribute to the image whatever
|
|
// direction the gimbal had reached by the time the encode finished - which is
|
|
// only harmless while captures happen at standstill and nothing overlaps. The
|
|
// moment encoding runs while the gimbal is already slewing to the next
|
|
// waypoint, or the queue backs up, the published heading and pitch belong to a
|
|
// different pose than the pixels.
|
|
void submit(const Frame& frame);
|
|
|
|
// Runtime-adjustable encoder settings (console "set camera ..." commands).
|
|
// These apply to every camera: they exist for quick experiments, and asking
|
|
// which camera on every keystroke would make that worse, not better.
|
|
void setDistance(double d) { for (auto& c : params_.cameras) c.jxl_distance = d; }
|
|
void setEffort(int e) { for (auto& c : params_.cameras) c.jxl_effort = e; }
|
|
void setDisplay(bool on) { params_.display = on; }
|
|
|
|
// Last CamEvent published (camera label, heading/pitch, timestamp), for the
|
|
// UI's camera panel. Thread-safe; nullopt until the first capture is saved.
|
|
std::optional<CamEvent> lastEvent() const;
|
|
|
|
// Number of images successfully written this session (for the camera view).
|
|
long long imagesSaved() const { return saved_.load(); }
|
|
|
|
private:
|
|
// A frame and the pose it was taken at, captured together at submit() time so
|
|
// encoding latency and queue depth cannot separate them.
|
|
struct QueuedFrame {
|
|
Frame frame;
|
|
Orientation orientation;
|
|
};
|
|
|
|
void run();
|
|
void process(const QueuedFrame& queued);
|
|
// Output settings for a camera index, or sane defaults if the index is out of
|
|
// range (a source delivering more cameras than were configured).
|
|
const CameraOutput& outputFor(int cam_id) const;
|
|
|
|
IControlChannel& channel_;
|
|
std::function<Orientation()> orientation_;
|
|
Params params_;
|
|
|
|
std::queue<QueuedFrame> queue_;
|
|
std::mutex mutex_;
|
|
std::condition_variable cv_;
|
|
std::atomic<bool> running_{false};
|
|
std::thread worker_;
|
|
|
|
mutable std::mutex last_event_mutex_;
|
|
std::optional<CamEvent> last_event_;
|
|
std::atomic<long long> saved_{0}; // images successfully written this session
|
|
};
|
|
|
|
} // namespace fgc
|