MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VisionExecutor.hpp
Go to the documentation of this file.
1#pragma once
2
4#include "Features.hpp"
5#include "Gradient.hpp"
6#include "OpticalFlow.hpp"
7#include "VisionOp.hpp"
8
11
12namespace MayaFlux::Core {
13class VKImage;
14}
15
16/**
17 * @file VisionExecutor.hpp
18 * @brief Dispatch engine for VisionSequence execution.
19 *
20 * VisionExecutor::run() is the single entry point for executing a
21 * VisionSequence. It maintains per-frame state (previous gray frame for
22 * optical flow) and returns a VisionResult carrying both the final pixel
23 * image and any structured outputs produced by the terminal step.
24 *
25 * Scratch storage is owned by the executor as DataVariant slots holding
26 * vector<float>. Steps ping-pong between slots via Eigen::Map<ArrayXf>
27 * views, accumulating zero heap allocation in steady state at a fixed
28 * resolution. Gaussian kernels are cached by sigma across frames.
29 *
30 * VisionResult::pixel_image is a DataVariant (vector<float>) moved out of
31 * a scratch slot. Callers read it via EigenAccess::view<Eigen::VectorXf>()
32 * for zero-copy Eigen access, or as_span() for raw float access.
33 */
34
36
37using StructuredOutput = std::variant<
38 std::monostate,
41 std::vector<Contour>,
42 std::vector<Keypoint>,
43 std::vector<TrackResult>>;
44
45/**
46 * @brief Result of executing a VisionSequence on one frame.
47 *
48 * pixel_image holds the final normalised float pixel buffer as a DataVariant
49 * (active alternative: vector<float>). Empty when the terminal step produces
50 * only structured output.
51 *
52 * Callers access pixel data via:
53 * EigenAccess(result.pixel_image).view<Eigen::VectorXf>() -- zero-copy Eigen map
54 * std::get<std::vector<float>>(result.pixel_image) -- direct vector access
55 *
56 * w and h are the dimensions of pixel_image. Both are 0 when pixel_image is empty.
57 */
59 Kakshya::DataVariant pixel_image { std::vector<float> {} };
60 StructuredOutput structured { std::monostate {} };
61 std::vector<SnapshotEntry> snapshots;
62 std::shared_ptr<Core::VKImage> debug_labels;
63 std::shared_ptr<Core::VKImage> debug_contours;
64 uint32_t w { 0 };
65 uint32_t h { 0 };
66
67 /**
68 * @brief Zero-copy float span into pixel_image storage.
69 * @return Empty span if pixel_image is not vector<float> or is empty.
70 */
71 [[nodiscard]] std::span<const float> as_span() const noexcept
72 {
73 const auto* v = std::get_if<std::vector<float>>(&pixel_image);
74 if (!v || v->empty())
75 return {};
76 return { v->data(), v->size() };
77 }
78};
79
80/**
81 * @class VisionExecutor
82 * @brief Stateful executor for a VisionSequence.
83 *
84 * Owns a pool of DataVariant scratch slots (each holding vector<float>) sized
85 * to the working resolution. Steps read via Eigen::Map<const ArrayXf> and
86 * write via Eigen::Map<ArrayXf> into the next slot, then swap indices.
87 * No heap allocation occurs in steady state after the first frame at a given
88 * resolution.
89 *
90 * Gaussian kernels are cached by sigma (keyed on bit-exact float) and reused
91 * across frames and across the three structure tensor smoothing passes in Harris.
92 *
93 * One executor instance per pipeline. Not thread-safe.
94 */
95class MAYAFLUX_API VisionExecutor {
96public:
97 VisionExecutor() = default;
98
99 /**
100 * @brief Execute a VisionSequence on one frame.
101 *
102 * @param sequence Ordered steps to execute.
103 * @param frame Normalised float input. RGBA (4 floats/pixel) for
104 * RgbaToGray/RgbaToHsv; single-channel otherwise.
105 * @param w Frame width in pixels.
106 * @param h Frame height in pixels.
107 * @return VisionResult with pixel_image (DataVariant) and/or structured output.
108 */
109 [[nodiscard]] VisionResult run(
110 const VisionSequence& sequence,
111 std::span<const float> frame,
112 uint32_t w, uint32_t h);
113
114 /**
115 * @brief Clear stored inter-frame state.
116 *
117 * Call when the pixel source changes (camera switch, video seek) so the
118 * next track_keypoints step starts clean. Does not release scratch storage.
119 */
120 void reset();
121
122private:
123 // =========================================================================
124 // Scratch pool
125 //
126 // Each slot is a DataVariant holding vector<float>. Slots are accessed via
127 // slot_vec(i) for direct vector reference and slot_map(i) / slot_map_mut(i)
128 // for zero-copy Eigen views. The working resolution is tracked to detect
129 // geometry changes and resize all slots in one pass.
130 //
131 // Slot assignment (fixed):
132 // 0 current working buffer (ping)
133 // 1 next working buffer (pong)
134 // 2 filter horizontal pass tmp
135 // 3 harris: dx
136 // 4 harris: dy
137 // 5 harris: ixx
138 // 6 harris: iyy
139 // 7 harris: ixy
140 // 8 harris: sxx (smoothed)
141 // 9 harris: syy
142 // 10 harris: sxy
143 // =========================================================================
144
145 static constexpr size_t k_slot_count = 11;
146 static constexpr size_t k_slot_cur = 0;
147 static constexpr size_t k_slot_nxt = 1;
148 static constexpr size_t k_slot_tmp = 2;
149 static constexpr size_t k_slot_dx = 3;
150 static constexpr size_t k_slot_dy = 4;
151 static constexpr size_t k_slot_ixx = 5;
152 static constexpr size_t k_slot_iyy = 6;
153 static constexpr size_t k_slot_ixy = 7;
154 static constexpr size_t k_slot_sxx = 8;
155 static constexpr size_t k_slot_syy = 9;
156 static constexpr size_t k_slot_sxy = 10;
157
158 std::array<Kakshya::DataVariant, k_slot_count> m_slots;
159 uint32_t m_slot_w { 0 };
160 uint32_t m_slot_h { 0 };
161
162 /**
163 * @brief Ensure all slots are sized to n_pixels floats.
164 *
165 * No-op when geometry matches. Resizes and zero-fills all slots otherwise.
166 */
167 void ensure_slots(uint32_t w, uint32_t h);
168
169 /**
170 * @brief Mutable reference to the vector<float> inside slot i.
171 */
172 [[nodiscard]] std::vector<float>& slot_vec(size_t i) noexcept
173 {
174 return std::get<std::vector<float>>(m_slots[i]);
175 }
176
177 /**
178 * @brief Const reference to the vector<float> inside slot i.
179 */
180 [[nodiscard]] const std::vector<float>& slot_vec(size_t i) const noexcept
181 {
182 return std::get<std::vector<float>>(m_slots[i]);
183 }
184
185 /**
186 * @brief Zero-copy read-only Eigen::Map<const ArrayXf> over slot i.
187 */
188 [[nodiscard]] Eigen::Map<const Eigen::ArrayXf> slot_map(size_t i, Eigen::Index n) const noexcept
189 {
190 return { slot_vec(i).data(), n };
191 }
192
193 /**
194 * @brief Zero-copy mutable Eigen::Map<ArrayXf> over slot i.
195 */
196 [[nodiscard]] Eigen::Map<Eigen::ArrayXf> slot_map_mut(size_t i, Eigen::Index n) noexcept
197 {
198 return { slot_vec(i).data(), n };
199 }
200
201 // =========================================================================
202 // Gaussian kernel cache
203 //
204 // Keyed on the bit pattern of the float sigma value. A given sigma produces
205 // an identical kernel every time; recomputing it three times per Harris call
206 // per frame is pure waste. The 1D separable kernel is stored once and reused
207 // for all three structure tensor smoothing passes and for standalone
208 // GaussianBlur steps.
209 // =========================================================================
210
211 std::unordered_map<uint32_t, std::vector<float>> m_kernel_cache;
212
213 /**
214 * @brief Return a reference to the precomputed 1D Gaussian kernel for sigma.
215 *
216 * Computes and caches on first call for a given sigma. The kernel is
217 * normalised to unit sum and has length 2*ceil(3*sigma)+1.
218 */
219 [[nodiscard]] const std::vector<float>& gaussian_kernel(float sigma);
220
221 // =========================================================================
222 // Inter-frame state
223 // =========================================================================
224
225 Kakshya::DataVariant m_prev_gray { std::vector<float> {} };
226 std::vector<float> m_curr_gray_cache;
227 std::vector<Keypoint> m_prev_keypoints;
228};
229
230} // namespace MayaFlux::Kinesis::Vision
Connected component labelling on normalised float masks.
Gradient and edge detection on normalised float image spans.
uint32_t h
Definition InkPress.cpp:28
Sparse optical flow via Lucas-Kanade tracker.
float sigma
Declarative description of a Kinesis::Vision processing sequence.
std::array< Kakshya::DataVariant, k_slot_count > m_slots
Eigen::Map< Eigen::ArrayXf > slot_map_mut(size_t i, Eigen::Index n) noexcept
Zero-copy mutable Eigen::Map<ArrayXf> over slot i.
const std::vector< float > & slot_vec(size_t i) const noexcept
Const reference to the vector<float> inside slot i.
std::vector< float > & slot_vec(size_t i) noexcept
Mutable reference to the vector<float> inside slot i.
std::unordered_map< uint32_t, std::vector< float > > m_kernel_cache
Eigen::Map< const Eigen::ArrayXf > slot_map(size_t i, Eigen::Index n) const noexcept
Zero-copy read-only Eigen::Map<const ArrayXf> over slot i.
Stateful executor for a VisionSequence.
void run()
Definition main.cpp:22
std::variant< std::vector< double >, std::vector< float >, std::vector< uint8_t >, std::vector< uint16_t >, std::vector< uint32_t >, std::vector< std::complex< float > >, std::vector< std::complex< double > >, std::vector< glm::vec2 >, std::vector< glm::vec3 >, std::vector< glm::vec4 >, std::vector< glm::mat4 > > DataVariant
Multi-type data storage for different precision needs.
Definition NDData.hpp:102
std::variant< std::monostate, GradientResult, ComponentResult, std::vector< Contour >, std::vector< Keypoint >, std::vector< TrackResult > > StructuredOutput
Result of connected component labelling.
Gradient maps produced by Sobel and Scharr operators.
Definition Gradient.hpp:22
std::shared_ptr< Core::VKImage > debug_labels
std::vector< SnapshotEntry > snapshots
std::shared_ptr< Core::VKImage > debug_contours
std::span< const float > as_span() const noexcept
Zero-copy float span into pixel_image storage.
Result of executing a VisionSequence on one frame.
Ordered sequence of VisionSteps describing a complete vision pipeline.
Definition VisionOp.hpp:163