#![cfg(feature = "analytics")]
use std::path::PathBuf;
use std::process::Command;
use g2g_core::memory::SystemSlice;
use g2g_core::{
AnalyticsMeta, AsyncElement, Caps, Dim, Frame, FrameTiming, G2gError, MemoryDomain, OutputSink,
PipelinePacket, PropValue, PushOutcome, Rate, RawVideoFormat,
};
use g2g_python::PyTransform;
const W: u32 = 640;
const H: u32 = 640;
#[derive(Default)]
struct CollectSink {
packets: Vec<PipelinePacket>,
}
impl OutputSink for CollectSink {
fn poll_push(
&mut self,
_cx: &mut core::task::Context<'_>,
packet_slot: &mut Option<PipelinePacket>,
) -> core::task::Poll<Result<PushOutcome, G2gError>> {
let packet = packet_slot.take().expect("poll_push without a packet");
self.packets.push(packet);
core::task::Poll::Ready(Ok(PushOutcome::Accepted))
}
}
fn decode_people_frame_rgba(pyml: &str) -> Option<Vec<u8>> {
let venv_py = PathBuf::from(pyml).join(".venv/bin/python");
let out_path = std::env::temp_dir().join("g2g_m322_people_640_rgba.raw");
let script = format!(
r#"
import cv2, numpy as np
cap = cv2.VideoCapture("{pyml}/data/people.mp4")
cap.set(cv2.CAP_PROP_POS_FRAMES, 30)
ok, bgr = cap.read(); cap.release()
assert ok, "failed to read frame"
rgb = cv2.cvtColor(cv2.resize(bgr, ({W}, {H})), cv2.COLOR_BGR2RGB)
rgba = np.dstack([rgb, np.full(({H}, {W}), 255, np.uint8)])
np.ascontiguousarray(rgba).tofile("{out}")
"#,
pyml = pyml,
W = W,
H = H,
out = out_path.display(),
);
let status = Command::new(&venv_py)
.arg("-c")
.arg(&script)
.status()
.ok()?;
if !status.success() {
return None;
}
let bytes = std::fs::read(&out_path).ok()?;
(bytes.len() == (W * H * 4) as usize).then_some(bytes)
}
#[test]
fn hosted_objectdetector_loads_model_and_detects() {
let Ok(pyml) = std::env::var("G2G_PYML_DIR") else {
eprintln!("skip: set G2G_PYML_DIR to a gst-python-ml checkout to run this host-only test");
return;
};
let device = std::env::var("G2G_PYML_DEVICE").unwrap_or_else(|_| "cuda:0".into());
let pv = "python3.14";
let pythonpath = [
format!("{pyml}/plugins/python"),
format!("{pyml}/.venv/lib/{pv}/site-packages"),
format!("{pyml}/.venv/lib64/{pv}/site-packages"),
format!(
"{}/.local/lib/{pv}/site-packages",
std::env::var("HOME").unwrap_or_default()
),
]
.join(":");
std::env::set_var("PYTHONPATH", pythonpath);
let Some(rgba) = decode_people_frame_rgba(&pyml) else {
eprintln!("skip: could not decode data/people.mp4 via the checkout venv");
return;
};
let mut el = PyTransform::new("objectdetector", "ObjectDetector");
el.set_property("engine-name", PropValue::Str("onnx".into()))
.unwrap();
el.set_property("model-name", PropValue::Str(format!("{pyml}/yolo11m.onnx")))
.unwrap();
el.set_property("device", PropValue::Str(device)).unwrap();
let caps = Caps::RawVideo {
format: RawVideoFormat::Rgba8,
width: Dim::Fixed(W),
height: Dim::Fixed(H),
framerate: Rate::Fixed(30),
interlace: g2g_core::Interlace::Any,
};
el.configure_pipeline(&caps).unwrap();
let frame = Frame {
domain: MemoryDomain::System(SystemSlice::from_boxed(rgba.into_boxed_slice())),
timing: FrameTiming {
pts_ns: 0,
dts_ns: 0,
duration_ns: 0,
capture_ns: 0,
arrival_ns: 0,
keyframe: true,
},
sequence: 0,
meta: Default::default(),
};
let mut sink = CollectSink::default();
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
rt.block_on(el.process(PipelinePacket::DataFrame(frame), &mut sink))
.expect("hosted objectdetector should run inference without error");
let PipelinePacket::DataFrame(frame) = &sink.packets[0] else {
panic!("expected a DataFrame downstream");
};
let analytics = frame
.meta
.get::<AnalyticsMeta>()
.expect("the detector should attach detections as AnalyticsMeta");
let dets: Vec<_> = analytics.detections().collect();
eprintln!(
"hosted objectdetector produced {} detections on the people frame",
dets.len()
);
assert!(
!dets.is_empty(),
"expected at least one detection on a frame full of people"
);
assert!(
dets.iter().any(|d| d.label == 0),
"expected at least one person (label 0) detection"
);
}