File plugin.cpp
File List > plugins > semantic_python > plugin.cpp
Go to the documentation of this file
#include "plugin.hpp"
#include "nvdec_decoder.hpp"
#include "switchboard_bindings.hpp"
#include
using namespace ILLIXR;
using namespace ILLIXR::data_format;
static pybind11::scoped_interpreter make_interpreter() {
PyConfig config;
PyConfig_InitPythonConfig(&config);
const char* python_home = std::getenv("PYTHONHOME");
if (python_home) {
PyConfig_SetBytesString(&config, &config.home, python_home);
}
const char* python_path = std::getenv("PYTHONPATH");
if (python_path) {
PyConfig_SetBytesString(&config, &config.pythonpath_env, python_path);
}
return pybind11::scoped_interpreter{&config};
}
[[maybe_unused]] semantic_python::semantic_python(const std::string& name, ILLIXR::phonebook* pb)
: plugin{name, pb}
, switchboard_{pb->lookup_impl<switchboard>()}
, voice_query_reader_{switchboard_->get_reader<semantic_xr::voice_query>("semantic_query")}
, response_writer_{switchboard_->get_network_writer<semantic_xr::query_response>("semantic_response", {})}
, guard_{make_interpreter()}
, release_{} {
// Fix sys.path to use the venv Python rather than system Python.
// Must be done before any Python imports, including in the thread.
// Get the venv path from the environment
const char* venv = switchboard_->get_env_char("VIRTUAL_ENV");
if (venv) {
pybind11::gil_scoped_acquire acquire;
pybind11::module_ sys = pybind11::module_::import("sys");
pybind11::module_ site = pybind11::module_::import("site");
auto prefix = sys.attr("prefix").cast<std::string>();
auto version_info = sys.attr("version_info");
int major = version_info.attr("major").cast<int>();
int minor = version_info.attr("minor").cast<int>();
std::string site_packages =
std::string(venv) + "/lib/python" + std::to_string(major) + "." + std::to_string(minor) + "/site-packages";
pybind11::list path = sys.attr("path");
path.attr("insert")(0, site_packages);
// Also call site.addsitedir to pick up .pth files
site.attr("addsitedir")(site_packages);
// Also add PYTHONPATH entries if set
const char* python_path = std::getenv("PYTHONPATH");
if (python_path) {
std::string pp(python_path);
// Split on ':' and insert each entry
std::string::size_type start = 0;
while (start < pp.size()) {
auto colon = pp.find(':', start);
if (colon == std::string::npos)
colon = pp.size();
std::string entry = pp.substr(start, colon - start);
if (!entry.empty())
path.attr("insert")(0, entry);
start = colon + 1;
}
}
}
std::string arg_string = switchboard_->get_env("SEMANTIC_PYTHON_ARGS", "");
parse_py_args(arg_string);
py_exe_ = switchboard_->get_env("SEMANTIC_PYTHON_SCRIPT", "");
spdlog::get("illixr")->debug("Python script: {} with args {}", py_exe_, arg_string);
if (py_exe_.empty())
throw std::runtime_error("No SEMANTIC_PYTHON_SCRIPT given.");
// Register decode callback — fires on the switchboard thread whenever a
// new semantic_frame frame arrives, decoding it immediately into the cache.
switchboard_->schedule<data_format::semantic_frame>(
id_, "semantic_frame", [this](const switchboard::ptr<const data_format::semantic_frame>& frame, std::size_t idx) {
on_semantic_data(frame, idx);
});
}
semantic_python::~semantic_python() {
if (py_thread_.joinable())
py_thread_.join();
}
void semantic_python::start() {
spdlog::get("illixr")->debug("semantic_python start called");
py_thread_ = std::thread(&semantic_python::run_python_thread, this);
}
void semantic_python::run_python_thread() {
pybind11::gil_scoped_acquire acquire;
try {
pybind11::list argv;
argv.append(py_exe_);
for (const auto& [key, val] : py_args_) {
if (key.size() == 1) {
argv.append("-" + key);
} else {
argv.append("--" + key);
}
if (!val.empty())
argv.append(val);
}
pybind11::module_::import("sys").attr("argv") = argv;
// register the module
auto m = pybind11::module_::create_extension_module("illixr_bridge", nullptr, new pybind11::module_::module_def{});
register_bindings(m);
// inject the proxy objects — decoder_ is initialized by the decode
// callback thread (on_semantic_data), not here.
py_semantic_data_reader semantic_proxy{&decoded_frames_, &frame_metadata_};
py_voice_query_reader voice_proxy{&voice_query_reader_};
py_query_response_writer response_proxy{&response_writer_};
pybind11::dict globals = pybind11::globals();
globals["illixr_semantic_reader"] = pybind11::cast(semantic_proxy);
globals["illixr_voice_reader"] = pybind11::cast(voice_proxy);
globals["illixr_response_writer"] = pybind11::cast(response_proxy);
spdlog::get("illixr")->info("[python_bridge] Starting script: {}", py_exe_);
// Remove the script directory from sys.path before running the script
// to prevent it from shadowing installed packages like numpy.
pybind11::module_ sys_mod = pybind11::module_::import("sys");
pybind11::list path = sys_mod.attr("path");
std::string script_dir = py_exe_.substr(0, py_exe_.find_last_of("/\\"));
pybind11::list new_path;
for (auto item : path) {
std::string p = pybind11::str(item);
if (p != script_dir && p != "." && !p.empty())
new_path.append(item);
}
sys_mod.attr("path") = new_path;
pybind11::eval_file(py_exe_, globals);
} catch (const pybind11::error_already_set& e) {
spdlog::get("illixr")->error("[python_bridge] Python exception: {}", e.what());
} catch (const std::exception& e) {
spdlog::get("illixr")->error("[python_bridge] Exception: {}", e.what());
}
}
void semantic_python::parse_py_args(const std::string& input) {
std::string::size_type start = 0;
while (start < input.size()) {
auto comma = input.find(',', start);
if (comma == std::string::npos)
comma = input.size();
std::string token = input.substr(start, comma - start);
if (!token.empty()) {
auto eq = token.find('=');
if (eq == std::string::npos) {
py_args_[token] = "";
} else if (eq == 0) {
spdlog::get("illixr")->warn("Malformed SEMANTIC_PYTHON_ARGS token '{}'", token);
} else {
py_args_[token.substr(0, eq)] = token.substr(eq + 1);
}
}
start = comma + 1;
}
}
void semantic_python::on_semantic_data(const switchboard::ptr<const data_format::semantic_frame>& frame, std::size_t /*idx*/) {
if (!frame) {
spdlog::get("illixr")->warn("[semantic_python] on_semantic_data: null frame");
return;
}
// Store metadata (depth, poses, intrinsics) at arrival, before the
// slower decode call below. decode() may hand back a picture for an
// earlier frame than this one (see nvdec_decoder::decode), so get()
// pairs the decoded image with its metadata by frame_number rather
// than assuming the two arrive together.
frame_metadata_.store(frame->frame_number, frame);
if (frame->image.empty()) {
spdlog::get("illixr")->warn("[semantic_python] on_semantic_data: frame={} has empty image", frame->frame_number);
return;
}
spdlog::get("illixr")->debug("[semantic_python] on_semantic_data: frame={} image={}B depth={}B", frame->frame_number,
frame->image.size(), frame->depth.size());
// Lazy decoder init — constructed on the first callback invocation so
// its CUDA context is bound to the switchboard decode thread.
{
std::lock_guard<std::mutex> lock(decoder_init_mutex_);
if (!decoder_initialized_) {
spdlog::get("illixr")->info("[semantic_python] Initializing NVDEC HEVC decoder...");
try {
decoder_ = std::make_unique<nvdec_decoder>(cudaVideoCodec_HEVC);
decoder_initialized_ = true;
spdlog::get("illixr")->info("[semantic_python] NVDEC HEVC decoder initialized");
} catch (const std::exception& e) {
spdlog::get("illixr")->error("[semantic_python] NVDEC init failed: {}", e.what());
return;
}
}
}
if (!decoder_)
return;
try {
int64_t decoded_frame_number = -1;
const uint8_t* rgb =
decoder_->decode(frame->image.data(), frame->image.size(), frame->frame_number, decoded_frame_number);
if (rgb != nullptr) {
decoded_frames_.store(static_cast<int32_t>(decoded_frame_number), decoder_->width(), decoder_->height(), rgb);
spdlog::get("illixr")->debug("[semantic_python] frame={} decoded (submitted frame={}) -> {}x{} RGB stored in cache",
decoded_frame_number, frame->frame_number, decoder_->width(), decoder_->height());
} else {
// nullptr is normal for SPS/PPS-only packets during decoder init —
// log at debug level so the first few frames don't look like errors.
spdlog::get("illixr")->debug("[semantic_python] frame={} decode returned no output "
"(normal during decoder init with SPS/PPS packets)",
frame->frame_number);
}
} catch (const std::exception& e) {
spdlog::get("illixr")->warn("[semantic_python] NVDEC decode failed frame={}: {}", frame->frame_number, e.what());
}
}
PLUGIN_MAIN(semantic_python)