pub mod contour;
pub mod geometry;
pub mod neck;
pub mod neck_medium;
pub mod postprocess;
pub mod preprocess;
use std::path::Path;
use pagesift_infer::Tensor;
use pagesift_infer::exec::{Cpu, Exec};
use crate::{Image, Quad, lcnet};
use postprocess::{DbParams, ProbMap};
use preprocess::Preprocess;
const LINE_TOL: f32 = 10.0;
enum AnyNeck {
Small(neck::Neck),
Medium(neck_medium::MediumNeck),
}
pub struct Det {
pre: Preprocess,
backbone: lcnet::Backbone,
neck: AnyNeck,
head: neck::Head,
pub params: DbParams,
}
impl Det {
pub fn load(dir: &Path) -> Result<Det, String> {
let cfg = crate::read_json(dir, "config.json")?;
let pre = Preprocess::from_json(&crate::read_json(dir, "preprocessor_config.json")?)?;
let w = crate::read_weights(dir)?;
let bcfg = lcnet::Config::from_json(&cfg)?;
let neck = match cfg["model_type"].as_str().unwrap_or("") {
"pp_ocrv6_medium_det" => {
AnyNeck::Medium(neck_medium::MediumNeck::load(&w, "model.neck")?)
}
_ => AnyNeck::Small(neck::Neck::load(
&w,
"model.neck",
&neck::NeckConfig::from_json(&cfg)?,
)?),
};
Ok(Det {
pre,
backbone: lcnet::Backbone::load(&w, "model.backbone.encoder", &bcfg)?,
neck,
head: neck::Head::load(&w, "head", neck::head_kernels(&cfg)?)?,
params: DbParams::default(),
})
}
pub fn prob_map(&self, page: &Image) -> Result<(Vec<f32>, usize, usize), String> {
let x = self.preprocess(page)?;
let prob = self.prob_on(&Cpu, &x)?;
let (_, h, w) = prob.chw();
Ok((prob.data, w, h))
}
pub fn preprocess(&self, page: &Image) -> Result<Tensor, String> {
if page.width == 0 || page.height == 0 {
return Err("empty image".into());
}
if page.channels != 1 && page.channels != 3 {
return Err(format!(
"only grayscale or RGB images are supported, got {} channels",
page.channels
));
}
Ok(self.pre.run(page))
}
pub fn prob_on<E: Exec>(&self, e: &E, x: &E::T) -> Result<E::T, String> {
let feats = self.backbone.forward_on(e, x);
let fused = match &self.neck {
AnyNeck::Small(n) => n.forward_on(e, &feats)?,
AnyNeck::Medium(n) => n.forward_on(e, &feats)?,
};
drop(feats);
let prob = self.head.forward_on(e, &fused);
let (got, want) = (e.shape(&prob), e.shape(x));
if got[1..] != want[1..] {
return Err(format!(
"probability map size {got:?} does not match the input {want:?}"
));
}
Ok(prob)
}
pub fn detect(&self, page: &Image) -> Result<Vec<Quad>, String> {
Ok(self
.detect_scored(page)?
.into_iter()
.map(|(q, _)| q)
.collect())
}
pub fn detect_scored(&self, page: &Image) -> Result<Vec<(Quad, f32)>, String> {
let (prob, w, h) = self.prob_map(page)?;
Ok(self.boxes_from_prob(&prob, w, h, page.width, page.height))
}
pub fn boxes_from_prob(
&self,
prob: &[f32],
w: usize,
h: usize,
page_w: usize,
page_h: usize,
) -> Vec<(Quad, f32)> {
let pm = ProbMap {
data: prob,
width: w,
height: h,
};
let mut boxes = postprocess::boxes_from_prob(&pm, page_w, page_h, &self.params);
postprocess::sort_reading_order(&mut boxes, LINE_TOL);
boxes
}
}