1pub mod contour;
9pub mod geometry;
10pub mod neck;
11pub mod neck_medium;
12pub mod postprocess;
13pub mod preprocess;
14
15use std::path::Path;
16
17use pagesift_infer::Tensor;
18use pagesift_infer::exec::{Cpu, Exec};
19
20use crate::{Image, Quad, lcnet};
21use postprocess::{DbParams, ProbMap};
22use preprocess::Preprocess;
23
24const LINE_TOL: f32 = 10.0;
26
27enum AnyNeck {
29 Small(neck::Neck),
30 Medium(neck_medium::MediumNeck),
31}
32
33pub struct Det {
35 pre: Preprocess,
36 backbone: lcnet::Backbone,
37 neck: AnyNeck,
38 head: neck::Head,
39 pub params: DbParams,
41}
42
43impl Det {
44 pub fn load(dir: &Path) -> Result<Det, String> {
46 let cfg = crate::read_json(dir, "config.json")?;
47 let pre = Preprocess::from_json(&crate::read_json(dir, "preprocessor_config.json")?)?;
48 let w = crate::read_weights(dir)?;
49 let bcfg = lcnet::Config::from_json(&cfg)?;
50 let neck = match cfg["model_type"].as_str().unwrap_or("") {
53 "pp_ocrv6_medium_det" => {
54 AnyNeck::Medium(neck_medium::MediumNeck::load(&w, "model.neck")?)
55 }
56 _ => AnyNeck::Small(neck::Neck::load(
57 &w,
58 "model.neck",
59 &neck::NeckConfig::from_json(&cfg)?,
60 )?),
61 };
62 Ok(Det {
63 pre,
64 backbone: lcnet::Backbone::load(&w, "model.backbone.encoder", &bcfg)?,
65 neck,
66 head: neck::Head::load(&w, "head", neck::head_kernels(&cfg)?)?,
67 params: DbParams::default(),
68 })
69 }
70
71 pub fn prob_map(&self, page: &Image) -> Result<(Vec<f32>, usize, usize), String> {
73 let x = self.preprocess(page)?;
74 let prob = self.prob_on(&Cpu, &x)?;
75 let (_, h, w) = prob.chw();
76 Ok((prob.data, w, h))
77 }
78
79 pub fn preprocess(&self, page: &Image) -> Result<Tensor, String> {
81 if page.width == 0 || page.height == 0 {
82 return Err("empty image".into());
83 }
84 if page.channels != 1 && page.channels != 3 {
85 return Err(format!(
86 "only grayscale or RGB images are supported, got {} channels",
87 page.channels
88 ));
89 }
90 Ok(self.pre.run(page))
91 }
92
93 pub fn prob_on<E: Exec>(&self, e: &E, x: &E::T) -> Result<E::T, String> {
95 let feats = self.backbone.forward_on(e, x);
96 let fused = match &self.neck {
97 AnyNeck::Small(n) => n.forward_on(e, &feats)?,
98 AnyNeck::Medium(n) => n.forward_on(e, &feats)?,
99 };
100 drop(feats);
101 let prob = self.head.forward_on(e, &fused);
102 let (got, want) = (e.shape(&prob), e.shape(x));
103 if got[1..] != want[1..] {
104 return Err(format!(
105 "probability map size {got:?} does not match the input {want:?}"
106 ));
107 }
108 Ok(prob)
109 }
110
111 pub fn detect(&self, page: &Image) -> Result<Vec<Quad>, String> {
113 Ok(self
114 .detect_scored(page)?
115 .into_iter()
116 .map(|(q, _)| q)
117 .collect())
118 }
119
120 pub fn detect_scored(&self, page: &Image) -> Result<Vec<(Quad, f32)>, String> {
122 let (prob, w, h) = self.prob_map(page)?;
123 Ok(self.boxes_from_prob(&prob, w, h, page.width, page.height))
124 }
125
126 pub fn boxes_from_prob(
128 &self,
129 prob: &[f32],
130 w: usize,
131 h: usize,
132 page_w: usize,
133 page_h: usize,
134 ) -> Vec<(Quad, f32)> {
135 let pm = ProbMap {
136 data: prob,
137 width: w,
138 height: h,
139 };
140 let mut boxes = postprocess::boxes_from_prob(&pm, page_w, page_h, &self.params);
141 postprocess::sort_reading_order(&mut boxes, LINE_TOL);
142 boxes
143 }
144}