File switchboard_bindings.hpp
File List > plugins > semantic_python > switchboard_bindings.hpp
Go to the documentation of this file
#pragma once
#include "plugin.hpp"
#include
#include
#include
#include
#include
#include
#include
namespace ILLIXR {
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
static pybind11::array_t<uint8_t> to_numpy_flat_safe(std::shared_ptr<const data_format::semantic_frame> owner,
const uint8_t* data, size_t size) {
pybind11::capsule base(new std::shared_ptr<const data_format::semantic_frame>(std::move(owner)), [](void* p) {
delete static_cast<std::shared_ptr<const data_format::semantic_frame>*>(p);
});
return pybind11::array_t<uint8_t>({static_cast<pybind11::ssize_t>(size)}, {static_cast<pybind11::ssize_t>(1)}, data, base);
}
static pybind11::array_t<float> to_numpy_4x4_safe(std::shared_ptr<const data_format::semantic_frame> owner, const float* data) {
pybind11::capsule base(new std::shared_ptr<const data_format::semantic_frame>(std::move(owner)), [](void* p) {
delete static_cast<std::shared_ptr<const data_format::semantic_frame>*>(p);
});
return pybind11::array_t<float>(
{static_cast<pybind11::ssize_t>(4), static_cast<pybind11::ssize_t>(4)},
{static_cast<pybind11::ssize_t>(sizeof(float) * 4), static_cast<pybind11::ssize_t>(sizeof(float))}, data, base);
}
// Convert a camera_intrinsics struct to a 1D numpy float32 array [fx, fy, cx, cy].
// Copies the four floats by value — the struct is not contiguous in memory, so
// zero-copy is not applicable here.
static pybind11::array_t<float> intrinsics_to_numpy(const data_format::camera_intrinsics& intr) {
auto result = pybind11::array_t<float>(4);
auto buf = result.mutable_unchecked<1>();
buf(0) = intr.fx;
buf(1) = intr.fy;
buf(2) = intr.cx;
buf(3) = intr.cy;
return result;
}
// Wrap a DecodedFrameCache::Entry's RGB vector as a (H, W, 3) uint8 numpy
// array without copying. The capsule keeps the Entry alive via a shared_ptr
// to the cache entry data. Since Entry is owned by the cache (fixed array),
// we share-own a copy of the rgb vector data instead.
static pybind11::array_t<uint8_t> entry_to_numpy(const decode::decoded_frame_cache::Entry* entry) {
if (entry == nullptr || entry->rgb.empty())
return pybind11::array_t<uint8_t>();
// Copy the vector into a heap-allocated buffer owned by the capsule.
// This is one memcpy but avoids lifetime issues with the cache slot
// being overwritten while Python holds a reference.
auto* buf = new std::vector<uint8_t>(entry->rgb);
pybind11::capsule base(buf, [](void* p) {
delete static_cast<std::vector<uint8_t>*>(p);
});
return pybind11::array_t<uint8_t>({static_cast<pybind11::ssize_t>(entry->height),
static_cast<pybind11::ssize_t>(entry->width), static_cast<pybind11::ssize_t>(3)},
{static_cast<pybind11::ssize_t>(entry->width * 3), static_cast<pybind11::ssize_t>(3),
static_cast<pybind11::ssize_t>(1)},
buf->data(), base);
}
// ---------------------------------------------------------------------------
// SemanticDataReader proxy
// ---------------------------------------------------------------------------
struct py_semantic_data_reader {
decode::decoded_frame_cache* cache;
decode::semantic_metadata_cache* metadata_cache;
[[nodiscard]] pybind11::object get() const {
auto entry_opt = cache ? cache->latest() : std::nullopt;
if (!entry_opt)
return pybind11::none();
auto meta_opt = metadata_cache ? metadata_cache->find(entry_opt->frame_number) : std::nullopt;
if (!meta_opt)
return pybind11::none();
// entry_opt->rgb is our own copy — safe to use without any lock.
// meta_opt->data is a shared_ptr — refcount keeps the frame alive.
auto val = meta_opt->data;
pybind11::dict data;
data["image"] = entry_to_numpy(&*entry_opt);
data["frame_number"] = entry_opt->frame_number;
data["image_width"] = val->intrinsics.width;
data["image_height"] = val->intrinsics.height;
data["depth"] = to_numpy_flat_safe(val, val->depth.data(), val->depth.size());
data["depth_width"] = val->depth_intrinsics.width;
data["depth_height"] = val->depth_intrinsics.height;
data["depth_near_z"] = val->depth_near_z;
data["intrinsics"] = intrinsics_to_numpy(val->intrinsics);
data["depth_intrinsics"] = intrinsics_to_numpy(val->depth_intrinsics);
data["rgb_camera_pose"] = to_numpy_4x4_safe(val, val->rgb_camera_pose);
data["depth_pose"] = to_numpy_4x4_safe(val, val->depth_pose);
data["max_depth_m"] = val->max_depth;
data["rgb_timestamp_ns"] = val->rgb_timestamp_ns;
data["depth_timestamp_ns"] = val->depth_timestamp_ns;
return data;
}
};
// -----------------------------------------------------------------------
// VoiceQueryReader
// -----------------------------------------------------------------------
struct py_voice_query_reader {
switchboard::reader<data_format::semantic_xr::voice_query>* reader_;
uint64_t last_query_id_ = 0;
pybind11::object get() {
auto val = reader_->get_ro_nullable();
if (!val)
return pybind11::none();
// Only return if this is a new query we haven't delivered yet
if (val->query_id == last_query_id_)
return pybind11::none();
last_query_id_ = val->query_id;
pybind11::dict d;
d["query_id"] = val->query_id;
d["similarity_threshold"] = val->similarity_threshold;
d["min_match_similarity"] = val->min_match_similarity;
// PCM data — zero-copy with capsule
pybind11::capsule base(new std::shared_ptr<const data_format::semantic_xr::voice_query>(val), [](void* p) {
delete static_cast<std::shared_ptr<const data_format::semantic_xr::voice_query>*>(p);
});
d["pcm_data"] = pybind11::array_t<uint8_t>({static_cast<pybind11::ssize_t>(val->pcm_data.size())},
{static_cast<pybind11::ssize_t>(1)}, val->pcm_data.data(), base);
return d;
}
};
// -----------------------------------------------------------------------
// QueryResponseWriter
// -----------------------------------------------------------------------
struct py_query_response_writer {
switchboard::writer<data_format::semantic_xr::query_response>* writer_;
void put(uint64_t query_id, pybind11::list point_clouds, pybind11::list colors, float server_latency,
const std::string& text_query) const {
auto resp = std::make_shared<data_format::semantic_xr::query_response>();
resp->query_id = query_id;
resp->server_query_processing = server_latency;
resp->text_query = text_query;
resp->num_point_clouds = static_cast<int32_t>(point_clouds.size());
resp->point_clouds.reserve(point_clouds.size());
for (auto& item : point_clouds) {
auto pc_dict = item.cast<pybind11::dict>();
data_format::semantic_xr::point_cloud pc;
pc.points = pc_dict["points"].cast<std::vector<float>>();
pc.num_points = static_cast<int32_t>(pc.points.size() / 3);
pc.centroid = pc_dict["centroid"].cast<std::vector<float>>();
resp->point_clouds.push_back(std::move(pc));
}
resp->colors = colors.cast<std::vector<float>>();
writer_->put(std::move(resp));
}
};
inline void register_bindings(pybind11::module_& m) {
pybind11::class_<py_semantic_data_reader>(m, "DnnInputReader").def("get", &py_semantic_data_reader::get);
pybind11::class_<py_voice_query_reader>(m, "VoiceQueryReader")
.def("get", &py_voice_query_reader::get, "Return the latest voice query as a dict, or None if no new query.");
pybind11::class_<py_query_response_writer>(m, "QueryResponseWriter")
.def("put", &py_query_response_writer::put, pybind11::arg("query_id"), pybind11::arg("point_clouds"),
pybind11::arg("colors"), pybind11::arg("server_latency"), pybind11::arg("text_query"),
"Write a query response to the switchboard.");
}
} // namespace ILLIXR