pub mod config;
pub mod decoder;
pub(crate) mod deform;
pub mod encoder;
pub mod hgnet;
pub(crate) mod layers;
pub(crate) mod postprocess;
pub mod preprocess;
pub(crate) mod query;
use std::path::Path;
use crate::Image;
#[derive(Debug, Clone, PartialEq)]
pub struct Region {
pub x0: f32,
pub y0: f32,
pub x1: f32,
pub y1: f32,
pub label: String,
pub kind: String,
pub score: f32,
}
const SCORE_THRESHOLD: f32 = 0.5;
#[derive(Debug, Clone)]
pub struct Layout {
pre: preprocess::Preprocess,
backbone: hgnet::Backbone,
encoder: encoder::Encoder,
decoder: decoder::Decoder,
labels: Vec<String>,
kinds: Vec<String>,
pub threshold: f32,
}
impl Layout {
pub fn load(dir: &Path) -> Result<Layout, String> {
let cfg = config::Config::from_json(&crate::read_json(dir, "config.json")?)?;
let pre =
preprocess::Preprocess::from_json(&crate::read_json(dir, "preprocessor_config.json")?)?;
let w = crate::read_weights(dir)?;
let kinds = std::fs::read_to_string(dir.join("inference.yml"))
.map(|s| config::parse_label_list(&s))
.unwrap_or_default();
Ok(Layout {
pre,
backbone: hgnet::Backbone::load(&w, "model.backbone.model")?,
encoder: encoder::Encoder::load(&w, &cfg)?,
decoder: decoder::Decoder::load(&w, &cfg)?,
kinds: if kinds.len() == cfg.labels.len() {
kinds
} else {
cfg.labels.clone()
},
labels: cfg.labels,
threshold: SCORE_THRESHOLD,
})
}
pub fn detect(&self, img: &Image) -> Result<Vec<Region>, String> {
let raw = self.raw(img)?;
let (q, classes) = raw.logits.tc();
if classes != self.labels.len() {
return Err(format!(
"the model outputs {classes} classes but the config has {}",
self.labels.len()
));
}
let ranks = postprocess::order_ranks(&raw.order.data, q);
let dets = postprocess::select(
&raw.logits.data,
classes,
&raw.boxes,
&ranks,
self.threshold,
img.width as f32,
img.height as f32,
);
Ok(dets
.into_iter()
.map(|d| Region {
x0: d.xyxy[0],
y0: d.xyxy[1],
x1: d.xyxy[2],
y1: d.xyxy[3],
label: self.labels[d.class].clone(),
kind: self.kinds[d.class].clone(),
score: d.score,
})
.collect())
}
pub fn raw(&self, img: &Image) -> Result<decoder::Raw, String> {
if img.width == 0 || img.height == 0 {
return Err("empty image".into());
}
if img.channels != 1 && img.channels != 3 {
return Err(format!(
"only grayscale or RGB images are supported, got {} channels",
img.channels
));
}
if img.data.len() != img.width * img.height * img.channels {
return Err("image data length does not match its width and height".into());
}
let x = self.pre.run(img);
let feats = self.backbone.forward(&x);
let enc = self.encoder.forward(&feats);
drop(feats);
Ok(self.decoder.forward(&enc.feats, &enc.mask_feat))
}
}