Skip to main content

formula_enc_check/
formula_enc_check.rs

1//! 公式编码器检查:载入模型,对一张图(P6 PPM;不给图就用合成的公式状图)跑一次编码,
2//! 逐段打印形状、min/max/mean、NaN/Inf 个数和耗时。
3//!     cargo run --profile release-fast -p pagesift-ocr --example formula_enc_check -- <模型目录> [图.ppm]
4use std::time::Instant;
5
6use pagesift_infer::Tensor;
7use pagesift_ocr::Image;
8use pagesift_ocr::formula_enc::{Encoder, preprocess};
9
10/// 最小的 P6 PPM 读取:头是 "P6 宽 高 255"(空白分隔,可有 # 注释),之后紧跟 RGB 数据。
11fn read_ppm(path: &str) -> Image {
12    let b = std::fs::read(path).unwrap_or_else(|e| panic!("读 {path} 失败:{e}"));
13    let mut fields = Vec::new();
14    let mut i = 0;
15    while fields.len() < 4 {
16        while b[i].is_ascii_whitespace() {
17            i += 1;
18        }
19        if b[i] == b'#' {
20            while b[i] != b'\n' {
21                i += 1;
22            }
23            continue;
24        }
25        let s = i;
26        while !b[i].is_ascii_whitespace() {
27            i += 1;
28        }
29        fields.push(String::from_utf8_lossy(&b[s..i]).into_owned());
30    }
31    assert_eq!(fields[0], "P6", "{path} 不是 P6 PPM");
32    assert_eq!(fields[3], "255", "{path} 不是 8 位");
33    let (w, h): (usize, usize) = (fields[1].parse().unwrap(), fields[2].parse().unwrap());
34    let data = b[i + 1..i + 1 + w * h * 3].to_vec();
35    Image {
36        width: w,
37        height: h,
38        channels: 3,
39        data,
40    }
41}
42
43/// 合成图:白底上几条黑色横竖笔画,宽 400、高 100。没有真实公式图时看数值范围用。
44fn synthetic() -> Image {
45    let (w, h) = (400, 100);
46    let mut data = vec![255u8; w * h];
47    for y in 0..h {
48        for x in 0..w {
49            let bar = (20..380).contains(&x) && (48..52).contains(&y);
50            let stem = x % 60 < 4 && (15..85).contains(&y) && (20..380).contains(&x);
51            if bar || stem {
52                data[y * w + x] = 0;
53            }
54        }
55    }
56    Image {
57        width: w,
58        height: h,
59        channels: 1,
60        data,
61    }
62}
63
64fn stats(name: &str, t: &Tensor, ms: f64) {
65    let bad = t.data.iter().filter(|v| !v.is_finite()).count();
66    let (mut lo, mut hi, mut sum) = (f32::INFINITY, f32::NEG_INFINITY, 0f64);
67    for &v in t.data.iter().filter(|v| v.is_finite()) {
68        lo = lo.min(v);
69        hi = hi.max(v);
70        sum += f64::from(v);
71    }
72    let mean = sum / t.data.len().max(1) as f64;
73    println!(
74        "{name:<16} {:<16} min {lo:>10.4} max {hi:>10.4} mean {mean:>9.4} 非有限 {bad:<6} {ms:>8.1} ms",
75        format!("{:?}", t.shape)
76    );
77}
78
79fn main() {
80    let a: Vec<String> = std::env::args().skip(1).collect();
81    let t = Instant::now();
82    let enc = Encoder::load(std::path::Path::new(&a[0])).unwrap();
83    println!("载入 {:.0} ms", t.elapsed().as_secs_f64() * 1e3);
84    let img = a.get(1).map_or_else(synthetic, |p| read_ppm(p));
85    let t = Instant::now();
86    let x = preprocess(&img);
87    let pre_ms = t.elapsed().as_secs_f64() * 1e3;
88    stats("preprocess", &x, pre_ms);
89    let t0 = Instant::now();
90    let mut last = t0;
91    let m = enc
92        .encode_traced(&x, |name, t| {
93            stats(name, t, last.elapsed().as_secs_f64() * 1e3);
94            last = Instant::now();
95        })
96        .unwrap();
97    println!(
98        "编码合计 {:.0} ms,输出 {:?}",
99        t0.elapsed().as_secs_f64() * 1e3,
100        m.shape
101    );
102    // 不带逐段统计再跑一次,得到干净的耗时
103    let t = Instant::now();
104    let m2 = enc.encode(&x).unwrap();
105    println!("第二次编码 {:.0} ms", t.elapsed().as_secs_f64() * 1e3);
106    assert_eq!(m.data, m2.data, "两次编码结果应逐位相同");
107}