MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VisionSorter.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "UniversalSorter.hpp"
4
7
8namespace MayaFlux::Yantra {
9
10/**
11 * @enum VisionSortKey
12 * @brief Named scalar attributes on vision detection results used as sort keys.
13 */
14enum class VisionSortKey : uint8_t {
15 AREA,
19 WIDTH,
20 HEIGHT,
21};
22
23/**
24 * @class VisionSorter
25 * @brief Reorders detection results in a VisionAnalysis by a scalar key or callable.
26 *
27 * Reads VisionAnalysis from input.metadata["vision_analysis"], sorts the
28 * populated detection collection (boxes or contours) by the configured key
29 * and direction, writes the reordered VisionAnalysis back to
30 * output.metadata["vision_analysis"], and passes input.data through unmodified.
31 *
32 * CPU path: std::stable_sort via permutation index.
33 *
34 * GPU path: attach a ShaderExecutionContext configured with
35 * KernelTemplate::BitonicSort. apply_operation_internal computes scalar keys
36 * on CPU, stages them alongside a linear index array, runs the assembled
37 * bitonic sort shader, reads back sorted indices, and applies the permutation
38 * to the analysis collections before returning.
39 *
40 * The callable overload receives the element index and the full VisionAnalysis.
41 * When both a key and a callable are set, the callable takes precedence.
42 *
43 * @note The GPU backend must be a ShaderExecutionContext<> with default
44 * InputType/OutputType (vector<DataVariant>). The spec must be built
45 * with KernelTemplate::BitonicSort, two InOut FLOAT32 SSBOs (keys at
46 * binding 0, indices at binding 1), and four UINT32 PC fields in order:
47 * stage, pass, count, descending.
48 *
49 * @code
50 * auto spec = ShaderSpec::Assemble{}
51 * .tmpl(KernelTemplate::BitonicSort)
52 * .ssbo("keys", BindingDirection::InOut, GpuDataFormat::FLOAT32)
53 * .ssbo("indices", BindingDirection::InOut, GpuDataFormat::FLOAT32)
54 * .pc("stage", GpuDataFormat::UINT32)
55 * .pc("pass", GpuDataFormat::UINT32)
56 * .pc("count", GpuDataFormat::UINT32)
57 * .pc("descending", GpuDataFormat::UINT32)
58 * .workgroup(256)
59 * .build();
60 * auto ctx = std::make_shared<ShaderExecutionContext<>>(
61 * config_from_spec(spec), bindings_from_spec(spec));
62 * sorter->set_gpu_backend(ctx);
63 * @endcode
64 *
65 * @tparam InputType Any ComputeData type. Defaults to shared_ptr<SignalSourceContainer>.
66 * @tparam OutputType Defaults to InputType; data passes through unmodified.
67 */
68template <
69 ComputeData InputType = std::shared_ptr<Kakshya::SignalSourceContainer>,
70 ComputeData OutputType = InputType>
72 : public UniversalSorter<InputType, OutputType> {
73public:
76
77 using KeyFn = std::function<float(size_t index, const VisionAnalysis&)>;
78
79 explicit VisionSorter(
81 SortingDirection direction = SortingDirection::DESCENDING)
82 : m_key(key)
83 {
84 this->set_direction(direction);
87 }
88
89 explicit VisionSorter(
90 KeyFn key_fn,
91 SortingDirection direction = SortingDirection::DESCENDING)
92 : m_key_fn(std::move(key_fn))
93 {
94 this->set_direction(direction);
97 }
98
100 {
101 m_key = key;
102 m_key_fn = nullptr;
103 }
104 void set_key_fn(KeyFn key_fn) { m_key_fn = std::move(key_fn); }
105
106 [[nodiscard]] VisionSortKey get_key() const { return m_key; }
107 [[nodiscard]] bool has_key_fn() const { return m_key_fn != nullptr; }
108
109 [[nodiscard]] SortingType get_sorting_type() const override
110 {
112 }
113
114 [[nodiscard]] std::vector<std::string> get_available_methods() const
115 {
116 return Reflect::get_enum_names_lowercase<VisionSortKey>();
117 }
118
119protected:
120 [[nodiscard]] std::string get_sorter_name() const override
121 {
122 return "VisionSorter";
123 }
124
126 const input_type& input, const ExecutionContext& context) override
127 {
128 if (this->m_gpu_backend && this->m_gpu_backend->ensure_gpu_ready()) {
129 const auto analysis_it = input.metadata.find("vision_analysis");
130 if (analysis_it != input.metadata.end() && analysis_it->second.has_value()) {
131 auto analysis = safe_any_cast_or_throw<VisionAnalysis>(analysis_it->second);
132
133 const size_t n = !analysis.frame.boxes.empty()
134 ? analysis.frame.boxes.size()
135 : analysis.frame.contours.size();
136
137 if (n > 1) {
138 uint32_t padded = 1;
139 while (padded < static_cast<uint32_t>(n))
140 padded <<= 1;
141
142 std::vector<float> keys(padded, std::numeric_limits<float>::infinity());
143 for (size_t i = 0; i < n; ++i)
144 keys[i] = compute_key(i, analysis);
145
146 std::vector<float> indices(padded);
147 for (uint32_t i = 0; i < padded; ++i)
148 indices[i] = static_cast<float>(i);
149
150 const auto k = static_cast<uint32_t>(
151 std::ceil(std::log2(static_cast<double>(padded))));
152 const uint32_t total_passes = k * (k + 1) / 2;
153 const uint32_t desc = (this->get_direction() == SortingDirection::DESCENDING) ? 1U : 0U;
154 const uint32_t count = padded;
155
156 auto* ctx = dynamic_cast<ShaderExecutionContext<>*>(
157 this->m_gpu_backend.get());
158
159 if (ctx) {
160 ctx->in_out(keys).in_out(indices);
161 ctx->set_multipass(total_passes,
162 [k, desc, count](uint32_t p, void* pc_ptr) {
163 uint32_t stage = 0, pass = 0, remaining = p;
164 for (uint32_t s = 0; s < k; ++s) {
165 if (remaining <= s) {
166 stage = s;
167 pass = remaining;
168 break;
169 }
170 remaining -= (s + 1);
171 }
172 struct PC {
173 uint32_t stage, pass, count, descending;
174 };
175 *static_cast<PC*>(pc_ptr) = { stage, pass, count, desc };
176 });
177
178 auto* ctx = dynamic_cast<ShaderExecutionContext<InputType, OutputType>*>(
179 this->m_gpu_backend.get());
180 auto gpu_result = this->m_gpu_backend->execute(input, context);
181
182 std::vector<float> sorted_idx_floats(padded);
183 ctx->download_binding(1, sorted_idx_floats.data(),
184 padded * sizeof(float));
185
186 auto apply_permutation = [&]<typename T>(std::vector<T>& coll) {
187 std::vector<T> sorted(n);
188 for (size_t i = 0; i < n; ++i) {
189 const auto src = static_cast<size_t>(sorted_idx_floats[i]);
190 if (src < n)
191 sorted[i] = coll[src];
192 }
193 coll = std::move(sorted);
194 };
195
196 if (!analysis.frame.boxes.empty())
197 apply_permutation(analysis.frame.boxes);
198 if (!analysis.frame.contours.empty())
199 apply_permutation(analysis.frame.contours);
200
201 output_type out { input.data };
202 out.metadata = input.metadata;
203 out.metadata["vision_analysis"] = std::move(analysis);
204 return out;
205 }
206 }
207 }
208 }
209
211 }
212
214 {
215 const auto analysis_it = input.metadata.find("vision_analysis");
216 if (analysis_it == input.metadata.end() || !analysis_it->second.has_value()) {
217 error<std::runtime_error>(
220 std::source_location::current(),
221 "VisionSorter: no vision_analysis in input metadata");
222 }
223
224 auto analysis = safe_any_cast_or_throw<VisionAnalysis>(analysis_it->second);
225
226 const bool descending = (this->get_direction() == SortingDirection::DESCENDING);
227
228 auto sort_collection = [&]<typename T>(std::vector<T>& coll) {
229 const size_t n = coll.size();
230 std::vector<size_t> idx(n);
231 std::iota(idx.begin(), idx.end(), 0);
232 std::stable_sort(idx.begin(), idx.end(),
233 [&](size_t a, size_t b) {
234 const float ka = m_key_fn
235 ? m_key_fn(a, analysis)
236 : key_for(a, coll, analysis);
237 const float kb = m_key_fn
238 ? m_key_fn(b, analysis)
239 : key_for(b, coll, analysis);
240 return descending ? ka > kb : ka < kb;
241 });
242 std::vector<T> sorted(n);
243 for (size_t i = 0; i < n; ++i)
244 sorted[i] = coll[idx[i]];
245 coll = std::move(sorted);
246 };
247
248 if (!analysis.frame.boxes.empty())
249 sort_collection(analysis.frame.boxes);
250 if (!analysis.frame.contours.empty())
251 sort_collection(analysis.frame.contours);
252
253 output_type out { input.data };
254 out.metadata = input.metadata;
255 out.metadata["vision_analysis"] = std::move(analysis);
256 return out;
257 }
258
259private:
262
263 [[nodiscard]] float compute_key(size_t index, const VisionAnalysis& analysis) const
264 {
265 if (m_key_fn)
266 return m_key_fn(index, analysis);
267 if (!analysis.frame.boxes.empty())
268 return key_for_box(analysis.frame.boxes[index]);
269 if (!analysis.frame.contours.empty())
270 return key_for_contour(analysis.frame.contours[index]);
271 return 0.F;
272 }
273
274 template <typename T>
275 [[nodiscard]] float key_for(size_t index, const std::vector<T>& coll,
276 const VisionAnalysis& analysis) const
277 {
278 if constexpr (std::is_same_v<T, Kinesis::Vision::BoundingBox>) {
279 return key_for_box(coll[index]);
280 } else {
281 return key_for_contour(coll[index]);
282 }
283 (void)analysis;
284 }
285
286 [[nodiscard]] float key_for_box(const Kinesis::Vision::BoundingBox& box) const
287 {
288 switch (m_key) {
290 return box.w * box.h;
292 return 2.F * (box.w + box.h);
294 return box.x + box.w * 0.5F;
296 return box.y + box.h * 0.5F;
298 return box.w;
300 return box.h;
301 }
302 return 0.F;
303 }
304
305 [[nodiscard]] float key_for_contour(const Kinesis::Vision::Contour& contour) const
306 {
307 switch (m_key) {
309 return contour.area;
311 return contour.perimeter;
316 if (contour.points.empty())
317 return 0.F;
318 glm::vec2 mn = contour.points[0], mx = contour.points[0];
319 for (const auto& p : contour.points) {
320 if (p.x < mn.x)
321 mn.x = p.x;
322 if (p.y < mn.y)
323 mn.y = p.y;
324 if (p.x > mx.x)
325 mx.x = p.x;
326 if (p.y > mx.y)
327 mx.y = p.y;
328 }
330 return mx.x - mn.x;
332 return mx.y - mn.y;
334 return (mn.x + mx.x) * 0.5F;
335 return (mn.y + mx.y) * 0.5F;
336 }
337 }
338 return 0.F;
339 }
340};
341
342// ============================================================================
343// Aliases
344// ============================================================================
345
347 std::shared_ptr<Kakshya::SignalSourceContainer>,
348 std::shared_ptr<Kakshya::SignalSourceContainer>>;
349
351 std::vector<Kakshya::DataVariant>,
352 std::vector<Kakshya::DataVariant>>;
353
354} // namespace MayaFlux::Yantra
Core::GlobalInputConfig input
Definition Config.cpp:38
size_t a
size_t b
Modern, digital-first universal sorting framework for Maya Flux.
size_t count
uint32_t pass
float k
std::shared_ptr< GpuExecutionContext< InputType, OutputType > > m_gpu_backend
virtual output_type apply_operation_internal(const input_type &input, const ExecutionContext &context)
Internal execution method - ComputeMatrix can access this.
output_type execute(const input_type &input, const ExecutionContext &ctx) override
Injects multipass configuration into the context before dispatch when set_multipass() has been called...
ShaderExecutionContext & set_multipass(uint32_t pass_count, std::function< void(uint32_t, void *)> pc_updater)
Configure multi-pass (CHAINED) dispatch.
ShaderExecutionContext & in_out(const std::vector< T > &data, GpuBufferBinding::ElementType type=GpuBufferBinding::ElementType::FLOAT32)
Add an INPUT_OUTPUT binding, inferring the next available binding index.
Concrete GpuExecutionContext for a single fixed shader with fixed bindings.
void set_strategy(SortingStrategy strategy)
Configure sorting strategy.
void set_granularity(SortingGranularity granularity)
Configure output granularity.
void set_direction(SortingDirection direction)
Configure sorting direction.
SortingDirection get_direction() const
Template-flexible sorter base with instance-defined I/O types.
float key_for_box(const Kinesis::Vision::BoundingBox &box) const
SortingType get_sorting_type() const override
Gets the sorting type category for this sorter.
output_type sort_implementation(const input_type &input) override
Pure virtual sorting implementation - derived classes implement this.
VisionSorter(KeyFn key_fn, SortingDirection direction=SortingDirection::DESCENDING)
output_type apply_operation_internal(const input_type &input, const ExecutionContext &context) override
Internal execution method - ComputeMatrix can access this.
VisionSortKey get_key() const
VisionSorter(VisionSortKey key=VisionSortKey::AREA, SortingDirection direction=SortingDirection::DESCENDING)
float compute_key(size_t index, const VisionAnalysis &analysis) const
float key_for_contour(const Kinesis::Vision::Contour &contour) const
std::vector< std::string > get_available_methods() const
std::function< float(size_t index, const VisionAnalysis &)> KeyFn
float key_for(size_t index, const std::vector< T > &coll, const VisionAnalysis &analysis) const
std::string get_sorter_name() const override
Get sorter-specific name (derived classes override this)
void set_key(VisionSortKey key)
Reorders detection results in a VisionAnalysis by a scalar key or callable.
@ ComputeMatrix
Compute operations (Yantra - algorithms, matrices, DSP)
@ Yantra
DSP algorithms, computational units, matrix operations, Grammar.
SortingDirection
Ascending or descending sort order.
Definition Sort.hpp:30
@ COPY_SORT
Create sorted copy (preserves input)
SortingType
Categories of sorting operations for discovery and organization.
@ SPATIAL
Multi-dimensional spatial sorting.
VisionSortKey
Named scalar attributes on vision detection results used as sort keys.
Axis-aligned bounding box in normalised image coordinates.
Definition Features.hpp:15
std::vector< glm::vec2 > points
Definition Features.hpp:43
Closed contour extracted from a binary mask.
Definition Features.hpp:42
T data
The actual computation data.
Definition DataIO.hpp:25
std::unordered_map< std::string, std::any > metadata
Associated metadata.
Definition DataIO.hpp:28
Input/Output container for computation pipeline data flow with structure preservation.
Definition DataIO.hpp:24
std::vector< Kinesis::Vision::Contour > contours
std::vector< Kinesis::Vision::BoundingBox > boxes
Context information controlling how a compute operation executes.
Analysis result produced by VisionAnalyzer for one frame.