pagesift_ocr/layout/
mod.rs1pub mod config;
11pub mod decoder;
12pub(crate) mod deform;
13pub mod encoder;
14pub mod hgnet;
15pub(crate) mod layers;
16pub(crate) mod postprocess;
17pub mod preprocess;
18pub(crate) mod query;
19
20use std::path::Path;
21
22use crate::Image;
23
24#[derive(Debug, Clone, PartialEq)]
26pub struct Region {
27 pub x0: f32,
28 pub y0: f32,
29 pub x1: f32,
30 pub y1: f32,
31 pub label: String,
33 pub kind: String,
36 pub score: f32,
38}
39
40const SCORE_THRESHOLD: f32 = 0.5;
42
43#[derive(Debug, Clone)]
45pub struct Layout {
46 pre: preprocess::Preprocess,
47 backbone: hgnet::Backbone,
48 encoder: encoder::Encoder,
49 decoder: decoder::Decoder,
50 labels: Vec<String>,
51 kinds: Vec<String>,
52 pub threshold: f32,
54}
55
56impl Layout {
57 pub fn load(dir: &Path) -> Result<Layout, String> {
59 let cfg = config::Config::from_json(&crate::read_json(dir, "config.json")?)?;
60 let pre =
61 preprocess::Preprocess::from_json(&crate::read_json(dir, "preprocessor_config.json")?)?;
62 let w = crate::read_weights(dir)?;
63 let kinds = std::fs::read_to_string(dir.join("inference.yml"))
64 .map(|s| config::parse_label_list(&s))
65 .unwrap_or_default();
66 Ok(Layout {
67 pre,
68 backbone: hgnet::Backbone::load(&w, "model.backbone.model")?,
70 encoder: encoder::Encoder::load(&w, &cfg)?,
71 decoder: decoder::Decoder::load(&w, &cfg)?,
72 kinds: if kinds.len() == cfg.labels.len() {
73 kinds
74 } else {
75 cfg.labels.clone()
76 },
77 labels: cfg.labels,
78 threshold: SCORE_THRESHOLD,
79 })
80 }
81
82 pub fn detect(&self, img: &Image) -> Result<Vec<Region>, String> {
84 let raw = self.raw(img)?;
85 let (q, classes) = raw.logits.tc();
86 if classes != self.labels.len() {
87 return Err(format!(
88 "the model outputs {classes} classes but the config has {}",
89 self.labels.len()
90 ));
91 }
92 let ranks = postprocess::order_ranks(&raw.order.data, q);
93 let dets = postprocess::select(
94 &raw.logits.data,
95 classes,
96 &raw.boxes,
97 &ranks,
98 self.threshold,
99 img.width as f32,
100 img.height as f32,
101 );
102 Ok(dets
103 .into_iter()
104 .map(|d| Region {
105 x0: d.xyxy[0],
106 y0: d.xyxy[1],
107 x1: d.xyxy[2],
108 y1: d.xyxy[3],
109 label: self.labels[d.class].clone(),
110 kind: self.kinds[d.class].clone(),
111 score: d.score,
112 })
113 .collect())
114 }
115
116 pub fn raw(&self, img: &Image) -> Result<decoder::Raw, String> {
118 if img.width == 0 || img.height == 0 {
119 return Err("empty image".into());
120 }
121 if img.channels != 1 && img.channels != 3 {
123 return Err(format!(
124 "only grayscale or RGB images are supported, got {} channels",
125 img.channels
126 ));
127 }
128 if img.data.len() != img.width * img.height * img.channels {
129 return Err("image data length does not match its width and height".into());
130 }
131 let x = self.pre.run(img);
132 let feats = self.backbone.forward(&x);
133 let enc = self.encoder.forward(&feats);
134 drop(feats);
135 Ok(self.decoder.forward(&enc.feats, &enc.mask_feat))
136 }
137}