13 struct TrackReducePC {
24 struct RegionSamplePC {
28 [[nodiscard]]
bool inside(
const Kinesis::Vision::BoundingBox& box,
const glm::vec2& p)
30 return p.x >= box.x && p.x <= box.x + box.w
31 && p.y >= box.y && p.y <= box.y + box.h;
36 std::optional<std::vector<Kinesis::Vision::TrackResult>>& raw_tracks,
37 std::optional<std::vector<Kinesis::Vision::Keypoint>>& raw_keypoints,
38 Kinesis::Vision::VisionAnalysis& analysis,
39 const Kinesis::Vision::VisionResult& result)
41 if (
const auto* tracks = std::get_if<std::vector<Kinesis::Vision::TrackResult>>(&result.structured))
44 if (
const auto* keypoints = std::get_if<std::vector<Kinesis::Vision::Keypoint>>(&result.structured))
45 raw_keypoints = *keypoints;
47 if (
const auto* contours = std::get_if<std::vector<Kinesis::Vision::Contour>>(&result.structured)) {
48 analysis.find_elements = Kinesis::Vision::FindElementsAnalysis {
49 .contours = *contours,
50 .boxes = result.component_boxes,
55 analysis.estimate_motion = result.flow;
56 analysis.motion_activity = result.motion_energy;
59 if (result.images.canny)
60 analysis.detect_edges = result.images.canny;
81 m_results = std::make_shared<const std::vector<Kinesis::Vision::VisionResult>>();
92 const std::vector<Kinesis::Vision::TrackResult>& tracks, uint32_t w, uint32_t
h)
100 std::vector<float> track_data;
101 track_data.reserve(tracks.size() * 4);
102 std::vector<float> extra_data;
103 extra_data.reserve(tracks.size() * 2);
104 for (
const auto& t : tracks) {
105 track_data.push_back(t.position.x);
106 track_data.push_back(t.position.y);
107 track_data.push_back(t.previous.x);
108 track_data.push_back(t.previous.y);
109 extra_data.push_back(
static_cast<float>(t.age));
110 extra_data.push_back(t.tracked ? 1.0F : 0.0F);
117 .workgroup_size = { 256, 1, 1 },
118 .push_constant_size =
sizeof(TrackReducePC) });
122 .output(1, 3 *
sizeof(glm::vec4))
123 .
input(2, extra_data)
124 .push(TrackReducePC { .count =
static_cast<uint32_t
>(tracks.size()) });
129 if (raw.size() >= 12) {
130 const auto n =
static_cast<float>(tracks.size());
131 result.
centroid = { raw[0] / n, raw[1] / n };
132 result.
velocity = { raw[2] / n, raw[3] / n };
136 .w = raw[6] - raw[4],
137 .h = raw[7] - raw[5],
140 result.
velocity.x *
static_cast<float>(w),
141 result.
velocity.y *
static_cast<float>(
h) });
143 result.
max_age =
static_cast<uint32_t
>(std::lround(raw[9]));
144 result.
tracked_count =
static_cast<uint32_t
>(std::lround(raw[10]));
152 const std::vector<Kinesis::Vision::Keypoint>& keypoints)
157 if (keypoints.empty())
160 std::vector<float> point_data;
161 point_data.reserve(keypoints.size() * 4);
162 std::vector<float> extra_data;
163 extra_data.reserve(keypoints.size() * 2);
164 for (
const auto& k : keypoints) {
165 point_data.push_back(k.position.x);
166 point_data.push_back(k.position.y);
167 point_data.push_back(k.position.x);
168 point_data.push_back(k.position.y);
169 extra_data.push_back(k.response);
170 extra_data.push_back(0.0F);
177 .workgroup_size = { 256, 1, 1 },
178 .push_constant_size =
sizeof(TrackReducePC) });
182 .output(1, 3 *
sizeof(glm::vec4))
183 .
input(2, extra_data)
184 .push(TrackReducePC { .count =
static_cast<uint32_t
>(keypoints.size()) });
189 if (raw.size() >= 12) {
190 const auto n =
static_cast<float>(keypoints.size());
191 result.
centroid = { raw[0] / n, raw[1] / n };
196 .w = raw[6] - raw[4],
197 .h = raw[7] - raw[5],
206 const auto n = fe.
boxes.size();
212 const auto fw =
static_cast<float>(w);
213 const auto fh =
static_cast<float>(
h);
215 std::vector<float> element_data;
216 element_data.reserve(n * 4);
217 std::vector<float> centroid_data;
218 centroid_data.reserve(n * 2);
220 for (
const auto&
b : fe.
boxes) {
221 const auto it = std::ranges::find_if(fe.
contours,
222 [&](
const auto& c) { return c.label_id == b.label_id; });
223 const float area_px = (it != fe.
contours.end()) ? it->area * fw * fh : 0.0F;
224 const float perimeter_px = (it != fe.
contours.end()) ? it->perimeter : 0.0F;
225 element_data.push_back(area_px);
226 element_data.push_back(perimeter_px);
227 element_data.push_back(
b.w * fw);
228 element_data.push_back(
b.h * fh);
230 centroid_data.push_back((
b.x +
b.w * 0.5F) * fw);
231 centroid_data.push_back((
b.y +
b.h * 0.5F) * fh);
235 m_shape_ctx = std::make_shared<ShaderExecutionContext<>>(
238 .workgroup_size = { 256, 1, 1 },
239 .push_constant_size =
sizeof(TrackReducePC) });
242 .output(1, n *
sizeof(glm::vec2))
243 .push(TrackReducePC { .count =
static_cast<uint32_t
>(n) });
247 for (
size_t i = 0; i < n && (i + 1) * 2 <= shape_raw.size(); ++i) {
248 fe.
shapes[i].compactness = shape_raw[i * 2];
249 fe.
shapes[i].aspect_ratio = shape_raw[i * 2 + 1];
259 .workgroup_size = { 256, 1, 1 },
260 .push_constant_size =
sizeof(TrackReducePC) });
263 .output(1, n *
sizeof(
float))
264 .push(TrackReducePC { .count =
static_cast<uint32_t
>(n) });
268 for (
size_t i = 0; i < n && i < dist_raw.size(); ++i)
274 const std::shared_ptr<Core::VKImage>& gray,
275 uint32_t w, uint32_t
h)
291 const std::shared_ptr<Core::VKImage>&
image, uint32_t w, uint32_t
h)
297 .workgroup_size = { 256, 1, 1 },
298 .push_constant_size =
sizeof(RegionSamplePC) },
299 Portal::Graphics::ImageFormat::RGBA8,
302 std::vector<GpuBufferBinding> {
315 foundry.wait_for_fence(
fence);
316 foundry.release_fence(
fence);
321 if (
auto it = gpu_result.aux.find(2); it != gpu_result.aux.end() && it->second.size() >= 3 *
sizeof(glm::vec4)) {
322 std::array<glm::vec4, 3> raw {};
323 std::memcpy(raw.data(), it->second.data(),
sizeof(raw));
325 out.min_val = raw[1];
326 out.max_val = raw[2];
332 const std::shared_ptr<Core::VKImage>&
image, uint32_t w, uint32_t
h)
338 .workgroup_size = { 8, 8, 1 },
339 .push_constant_size =
sizeof(HistogramPC) },
340 Portal::Graphics::ImageFormat::RGBA8,
343 std::vector<GpuBufferBinding> {
351 const std::array<uint32_t, 768> zero {};
352 m_histogram_ctx->upload_shared_raw(0, 3,
reinterpret_cast<const uint8_t*
>(zero.data()),
sizeof(zero));
355 m_histogram_ctx->set_push_constants(HistogramPC { .width = w, .height =
h, .channels = 0 });
361 foundry.wait_for_fence(
fence);
362 foundry.release_fence(
fence);
364 std::array<uint32_t, 256> histogram {};
365 m_histogram_ctx->download_shared(0, 3, histogram.data(),
sizeof(histogram));
375 .workgroup_size = { 256, 1, 1 },
376 .push_constant_size = 0 });
379 const std::vector<uint32_t> hist_vec(histogram.begin(), histogram.end());
381 .output(1,
sizeof(
float));
385 return raw.empty() ? 0.0F : raw[0];
389 const std::shared_ptr<Kakshya::SignalSourceContainer>& source,
395 if (
auto tc = std::dynamic_pointer_cast<Kakshya::TextureContainer>(source))
396 return tc->to_image(
static_cast<uint32_t
>(
index));
398 if (
auto wc = std::dynamic_pointer_cast<Kakshya::WindowContainer>(source))
399 return wc->to_image();
401 auto vc = std::dynamic_pointer_cast<Kakshya::VideoStreamContainer>(source);
405 const uint32_t w = vc->get_width();
406 const uint32_t
h = vc->get_height();
408 const void* raw = source->get_raw_data();
414 w,
h, Portal::Graphics::ImageFormat::RGBA8,
nullptr);
426 const std::shared_ptr<Kakshya::SignalSourceContainer>& source,
437 const std::shared_ptr<Core::VKImage>&
image)
443 const std::shared_ptr<Core::VKImage>&
image)
455 for (
auto& step : entry.sequence.steps) {
458 step.deferred =
true;
479 m_results = std::make_shared<const std::vector<Kinesis::Vision::VisionResult>>(std::move(
m_run.
results));
487 .width =
image->get_width(),
489 .format = Portal::Graphics::ImageFormat::RGBA32F,
494 if (!held || !loom.can_blit(
image, held, filter) || !loom.blit_layer(
image, held, { .filter = filter }))
500 const std::shared_ptr<Core::VKImage>&
image)
504 std::vector<Kinesis::Vision::VisionResult> results;
505 results.reserve(resolved.size());
508 for (
size_t lane = 0; lane < resolved.size(); ++lane) {
510 results.emplace_back();
514 m_results = std::make_shared<const std::vector<Kinesis::Vision::VisionResult>>(std::move(results));
520 const std::vector<Kinesis::Vision::ResolvedSequence>& resolved,
521 std::vector<Kinesis::Vision::VisionResult>& results,
522 const std::shared_ptr<Core::VKImage>&
image)
527 const auto& entry = resolved[lane];
530 executor = std::make_unique<VisionGpuExecutor>();
532 if (std::ranges::any_of(entry.sequence.steps,
533 [](
const auto& step) { return step.op == Kinesis::Vision::VisionOp::ConnectedComponents; }))
536 const auto seeded = entry.seed_field ? entry.seed_field(results[entry.seed_lane]) :
nullptr;
537 const auto& source = seeded ? seeded :
image;
538 auto result = executor->run(entry.sequence, source, source->get_width(), source->get_height());
543 results.push_back(std::move(result));
548 const std::vector<Kinesis::Vision::ResolvedSequence>& resolved,
549 const std::vector<Kinesis::Vision::VisionResult>& results,
550 uint32_t w, uint32_t
h)
553 std::optional<std::vector<Kinesis::Vision::TrackResult>> raw_tracks;
554 std::optional<std::vector<Kinesis::Vision::Keypoint>> raw_keypoints;
556 for (
size_t lane = 0; lane < results.size(); ++lane) {
557 const auto& result = results[lane];
558 const auto& entry = resolved[lane];
560 collect_into(raw_tracks, raw_keypoints, analysis, result);
562 if (result.images.sobel) {
563 const auto& gray = result.gray || !entry.seed_field ? result.gray : results[entry.seed_lane].gray;
570 if (ctx.subject_track_id) {
571 const auto id = *ctx.subject_track_id;
572 std::erase_if(*raw_tracks, [&](
const auto& t) {
return t.id != id; });
573 }
else if (ctx.subject_bounds) {
574 const auto& bounds = *ctx.subject_bounds;
575 std::erase_if(*raw_tracks, [&](
const auto& t) {
return !inside(bounds, t.position); });
584 std::erase_if(*raw_keypoints, [&](
const auto& k) {
return !inside(bounds, k.position); });
608 output.
metadata[
"error"] = std::string(
"VisionAnalyzer: missing container");
612 const auto index = Kakshya::get_metadata_value<size_t>(
input.metadata,
"container_index").value_or(0);
Core::GlobalInputConfig input
std::vector< float > * out
FunctionalOperation<shared_ptr<SignalSourceContainer>, vector<DataVariant>> whose real API matches En...
void upload_data(const std::shared_ptr< Core::VKImage > &image, const void *data, size_t size)
Upload pixel data to an existing texture.
static TextureLoom & instance()
std::shared_ptr< Core::VKImage > create_2d(uint32_t width, uint32_t height, ImageFormat format=ImageFormat::RGBA8, const void *data=nullptr, uint32_t mip_levels=1)
Create a 2D texture.
Concrete GpuExecutionContext for a single fixed shader with fixed bindings.
@ SCALAR
Return SSBO readback via collect_result(); no image download.
Kinesis::Vision::MeasureAppearanceAnalysis measure_appearance(const Kinesis::Vision::VisionResult &result, const std::shared_ptr< Core::VKImage > &gray, uint32_t w, uint32_t h)
MeasureAppearance's whole-frame gradient sample (region_sample.comp over result.images....
float compute_mean_brightness(const std::array< uint32_t, 256 > &histogram)
Dispatch histogram_reduce.comp over an already-computed histogram.
std::shared_ptr< Core::VKImage > m_upload_image
std::shared_ptr< ShaderExecutionContext<> > m_shape_ctx
Kinesis::Vision::VisionQuery m_query
Kinesis::Vision::FieldSample sample_full_frame(const std::shared_ptr< Core::VKImage > &image, uint32_t w, uint32_t h)
Dispatch region_sample.comp over the whole image (no region concept for MeasureAppearance).
void compute_shapes(Kinesis::Vision::FindElementsAnalysis &fe, uint32_t w, uint32_t h)
Shape descriptors (element_shape.comp) and nearest-neighbour distances (nearest_neighbor....
std::shared_ptr< Core::VKImage > m_frame
Kinesis::Vision::VisionAnalysis analyze_resolved(const std::shared_ptr< Core::VKImage > &image)
Shared body of both analyze_vision() overloads, once a GPU image is in hand: resolve the configured q...
bool run_lane(size_t lane, const std::vector< Kinesis::Vision::ResolvedSequence > &resolved, std::vector< Kinesis::Vision::VisionResult > &results, const std::shared_ptr< Core::VKImage > &image)
Run or poll sequence lane of resolved on its executor.
std::shared_ptr< Core::VKImage > hold_frame(const std::shared_ptr< Core::VKImage > &image)
Copy image into this analyzer's own frame image.
Portal::Graphics::ImageCacheEntry m_frame_cache
Kinesis::Vision::DetectFeaturesAnalysis reduce_keypoints(const std::vector< Kinesis::Vision::Keypoint > &keypoints)
Dispatch track_reduce.comp over keypoint positions (fed as their own previous, so velocity reduces to...
VisionAnalyzer(Kinesis::Vision::VisionQuery query={}, Kinesis::Vision::VisionAnalysisContext context={})
Construct with the intents to satisfy and the caller-supplied filtering context, matching EnergyAnaly...
std::vector< std::unique_ptr< VisionGpuExecutor > > m_executors
std::shared_ptr< TextureExecutionContext > m_histogram_ctx
std::shared_ptr< ShaderExecutionContext<> > m_neighbor_ctx
std::shared_ptr< const std::vector< Kinesis::Vision::VisionResult > > m_results
void reset()
Abandon outstanding work and clear retained executor state.
VisionGpuExecutor * get_executor() const
The executor that ran the FindElements sequence, or the first executor when the query has none.
std::optional< Kinesis::Vision::VisionAnalysis > advance(const std::shared_ptr< Core::VKImage > &image)
Advance an analysis of image by one sequence per call, without waiting on deferred GPU work.
std::shared_ptr< ShaderExecutionContext<> > m_brightness_reducer
Kinesis::Vision::VisionAnalysisContext m_context
Kinesis::Vision::VisionAnalysis assemble(const std::vector< Kinesis::Vision::ResolvedSequence > &resolved, const std::vector< Kinesis::Vision::VisionResult > &results, uint32_t w, uint32_t h)
Route every completed sequence result into one VisionAnalysis, apply context filtering and run the GP...
Kinesis::Vision::TrackObjectsAnalysis reduce_tracks(const std::vector< Kinesis::Vision::TrackResult > &tracks, uint32_t w, uint32_t h)
Dispatch track_reduce.comp over tracks and fill centroid/velocity/bounds/mean_speed_px/age stats on t...
std::shared_ptr< Core::VKImage > resolve_image(const std::shared_ptr< Kakshya::SignalSourceContainer > &source, size_t index)
Resolve source/index to a GPU image, the same way VisionProcessor does: TextureContainer's own to_ima...
Kinesis::Vision::VisionAnalysis analyze_vision(const std::shared_ptr< Kakshya::SignalSourceContainer > &source, size_t index=0)
Real entry point: resolve the configured query against source, run it, run the GPU analysis.
std::shared_ptr< TextureExecutionContext > m_gradient_sample_ctx
std::shared_ptr< ShaderExecutionContext<> > m_track_reducer
std::array< uint32_t, 256 > compute_histogram(const std::shared_ptr< Core::VKImage > &image, uint32_t w, uint32_t h)
Dispatch otsu_histogram.comp over image, zeroing the buffer first.
output_type run_operation(const input_type &input)
ComputeOperation adapter: uses input.data as the source container, pulls index out of Datum metadata ...
Stateful GPU dispatch engine for VisionSequence execution.
std::optional< T > get_metadata_value(const std::unordered_map< std::string, std::any > &metadata, const std::string &key)
Get a value from a metadata map by key.
std::vector< ResolvedSequence > resolve(const VisionQuery &query)
Resolve a VisionQuery into the ResolvedSequences needed to satisfy every active intent.
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
@ NEAREST
Nearest neighbor (pixelated)
@ COMPUTE
Storage buffer for compute shaders (device-local)
std::vector< Keypoint > keypoints
Keypoint reduction, computed on GPU by the same reduction shader as TrackObjectsAnalysis (track_reduc...
Per-channel mean/min/max over a sampled image region, computed on GPU (Yantra's region_sample....
std::vector< Contour > contours
std::vector< float > nearest_neighbor_distance_px
std::vector< BoundingBox > boxes
std::vector< ElementShape > shapes
FindElements output: contours plus the per-component BoundingBoxes ConnectedComponents already comput...
std::shared_ptr< Core::VKImage > sobel
MeasureAppearance output: whole-frame photometric characterisation, computed on GPU (region_sample....
std::vector< TrackResult > tracks
Track reduction, computed on GPU (Yantra's track_reduce.comp) from the (context-filtered) subject tra...
std::optional< TrackObjectsContext > track_objects
std::optional< BoundingBox > detect_features_bounds
Composed context: caller-supplied data for whichever intents need it, consulted after a VisionQuery's...
std::optional< FindElementsAnalysis > find_elements
std::optional< MeasureAppearanceAnalysis > measure_appearance
std::optional< DetectFeaturesAnalysis > detect_features
std::optional< TrackObjectsAnalysis > track_objects
Composed analysis output: one populated field per active intent that produced a result this call.
Composed query: which VisionIntents are active, and each active one's parameters.
ImageOutputs images
Image output of whichever image-producing ops ran in this sequence, one named slot per op.
Result of executing a VisionSequence on one frame.
enum MayaFlux::Portal::Graphics::GpuBufferBinding::ElementType FLOAT32
Plain-data description of the compute shader to dispatch.
std::unordered_map< std::string, std::any > metadata
Associated metadata.
Input/Output container for computation pipeline data flow with structure preservation.
Context information controlling how a compute operation executes.
std::vector< Kinesis::Vision::ResolvedSequence > resolved
std::vector< Kinesis::Vision::VisionResult > results