Move the panorama keypoint detector onto the engine, and probe with a detector

XFeat's two exports are a Keypoints role now; the crate no longer names
tract, and the app compiles TensorRT engines for both ahead of the
first merge. The probe picks the smallest *detector* rather than the
smallest file: the tablet's first run chose the 112 KB eye classifier,
which has no int8 form, and reported the Hexagon as failed for want of
one.
This commit is contained in:
2026-09-19 16:05:08 +02:00
parent 76bc5652d7
commit 7a436e2549
10 changed files with 157 additions and 130 deletions
+10
View File
@@ -63,3 +63,13 @@ pub enum PanoError {
#[error("inference: {0}")]
Inference(#[source] ort::Error),
}
#[cfg(feature = "xfeat")]
impl From<dr_inference_engine::Error> for PanoError {
fn from(e: dr_inference_engine::Error) -> Self {
match e {
dr_inference_engine::Error::Inference(e) => PanoError::Inference(e),
dr_inference_engine::Error::Io(e) => PanoError::ModelRead(e),
}
}
}
+22 -28
View File
@@ -2,10 +2,11 @@
//! The XFeat detector — the network under tract, and the decoder after it.
//!
//! Apache-2.0 weights (`models/LICENCE.md`), exported at a fixed shape by
//! `tools/export-xfeat.sh` and loaded through the same `ort`-over-tract
//! backend `dr-segment` and `dr-face` use, so this adds no runtime and no C
//! to the tree. ~300 ms per frame on the reference desktop, ~400 ms on the
//! tablet (S15.2, S15.4).
//! `tools/export-xfeat.sh` and loaded through the same `dr-inference-engine`
//! `dr-segment` and `dr-face` use, so this adds no runtime and no C to the
//! tree; what runs it is the device's business (docs/inference.md). ~300 ms
//! per frame on tract on the reference desktop, ~400 ms on the tablet
//! (S15.2, S15.4).
use crate::features::{decode_xfeat, DecodeOptions, Features, XFeatMaps, DESCRIPTOR_LEN};
use crate::image::Gray;
@@ -30,11 +31,18 @@ const EMBEDDED_PORTRAIT: &[u8] = include_bytes!("../../../models/keypoints/xfeat
/// A loaded detector: the network at both shapes.
pub struct XFeat {
landscape: ort::session::Session,
portrait: ort::session::Session,
landscape: dr_inference_engine::Model,
portrait: dr_inference_engine::Model,
pub options: DecodeOptions,
}
/// The bytes of both exports compiled into the binary, for whoever compiles
/// engines ahead of the first request (docs/inference.md §6).
#[cfg(feature = "embedded-model")]
pub fn embedded_model_bytes() -> [&'static [u8]; 2] {
[EMBEDDED_LANDSCAPE, EMBEDDED_PORTRAIT]
}
impl XFeat {
/// The weights compiled into the binary.
#[cfg(feature = "embedded-model")]
@@ -53,16 +61,10 @@ impl XFeat {
}
pub fn from_bytes(landscape: &[u8], portrait: &[u8]) -> Result<Self, PanoError> {
install_backend();
let session = |bytes: &[u8]| {
ort::session::Session::builder()
.map_err(PanoError::Inference)?
.commit_from_memory(bytes)
.map_err(PanoError::Inference)
};
use dr_inference_engine::{Form, Role};
Ok(XFeat {
landscape: session(landscape)?,
portrait: session(portrait)?,
landscape: dr_inference_engine::open(Role::Keypoints, Form::F32, landscape)?,
portrait: dr_inference_engine::open(Role::Keypoints, Form::F32, portrait)?,
options: DecodeOptions::default(),
})
}
@@ -75,11 +77,13 @@ impl XFeat {
/// so a caller that already scaled a frame to a proxy maps them on with
/// the scale it used and nothing else.
pub fn detect(&mut self, image: &Gray) -> Result<Features, PanoError> {
let ((in_w, in_h), session) = if image.height > image.width {
(INPUT_PORTRAIT, &mut self.portrait)
let ((in_w, in_h), model) = if image.height > image.width {
(INPUT_PORTRAIT, &self.portrait)
} else {
(INPUT_LANDSCAPE, &mut self.landscape)
(INPUT_LANDSCAPE, &self.landscape)
};
let acquired = model.acquire()?;
let mut session = acquired.lock();
let (fitted, scale) = image.fitted(in_w, in_h);
let padded = fitted.padded(in_w, in_h);
@@ -145,13 +149,3 @@ impl XFeat {
Ok(features)
}
}
fn install_backend() {
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
// False if another crate installed it first, which is fine: there is
// one backend compiled in for it to have chosen.
let _ = ort::set_api(ort_tract::api());
});
}