1use pagesift_infer::Tensor;
6
7use crate::Image;
8
9const CHANNELS: usize = 3;
11const DEFAULT_SIZE: u64 = 800;
13const MAX_U8: f32 = 255.0;
15const TAPS: usize = 4;
17
18#[derive(Debug, Clone)]
20pub struct Preprocess {
21 pub height: usize,
22 pub width: usize,
23 pub rescale: f32,
24 pub mean: [f32; CHANNELS],
25 pub std: [f32; CHANNELS],
26}
27
28const RESAMPLE_BICUBIC: u64 = 3;
30
31impl Preprocess {
32 pub fn from_json(v: &serde_json::Value) -> Result<Preprocess, String> {
33 if v["do_resize"].as_bool() == Some(false)
34 || v["do_rescale"].as_bool() == Some(false)
35 || v["do_normalize"].as_bool() == Some(false)
36 {
37 return Err(
38 "only preprocessing with resize, rescale and normalize all on is supported".into(),
39 );
40 }
41 if v["resample"].as_u64().unwrap_or(RESAMPLE_BICUBIC) != RESAMPLE_BICUBIC {
42 return Err("only bicubic resampling is supported".into());
43 }
44 let triple = |key: &str, def: f32| -> Result<[f32; CHANNELS], String> {
45 match v[key].as_array() {
46 None => Ok([def; CHANNELS]),
47 Some(a) if a.len() == CHANNELS => {
48 let mut out = [0f32; CHANNELS];
49 for (o, x) in out.iter_mut().zip(a) {
50 *o = x.as_f64().ok_or(format!("{key} must be a number"))? as f32;
51 }
52 Ok(out)
53 }
54 Some(_) => Err(format!("{key} must have {CHANNELS} numbers")),
55 }
56 };
57 Ok(Preprocess {
59 height: v["size"]["height"].as_u64().unwrap_or(DEFAULT_SIZE) as usize,
60 width: v["size"]["width"].as_u64().unwrap_or(DEFAULT_SIZE) as usize,
61 rescale: v["rescale_factor"]
62 .as_f64()
63 .unwrap_or(1.0 / f64::from(MAX_U8)) as f32,
64 mean: triple("image_mean", 0.0)?,
65 std: triple("image_std", 1.0)?,
66 })
67 }
68
69 pub fn run(&self, img: &Image) -> Tensor {
71 let planes = if img.channels == 1 { 1 } else { CHANNELS };
72 let (h, w) = (self.height, self.width);
73 let mut out = vec![0f32; CHANNELS * h * w];
74 for c in 0..planes {
75 let src: Vec<f32> = (0..img.height * img.width)
76 .map(|i| f32::from(img.data[i * img.channels + c]))
77 .collect();
78 let r = resize_bicubic(&src, img.height, img.width, h, w);
79 let dst = &mut out[c * h * w..(c + 1) * h * w];
80 for (d, v) in dst.iter_mut().zip(r) {
81 let v = v.round_ties_even().clamp(0.0, MAX_U8);
83 *d = (v * self.rescale - self.mean[c]) / self.std[c];
84 }
85 }
86 if planes == 1 {
87 let (first, rest) = out.split_at_mut(h * w);
88 rest[..h * w].copy_from_slice(first);
89 rest[h * w..].copy_from_slice(first);
90 }
91 Tensor::new(vec![CHANNELS, h, w], out)
92 }
93}
94
95fn cubic_taps(input: usize, output: usize) -> Vec<(isize, [f32; TAPS])> {
97 let scale = input as f32 / output as f32;
98 (0..output)
99 .map(|o| {
100 let src = (o as f32 + 0.5) * scale - 0.5;
102 let floor = src.floor();
103 (floor as isize - 1, cubic_weights(src - floor))
104 })
105 .collect()
106}
107
108fn cubic_weights(t: f32) -> [f32; TAPS] {
110 [
111 cubic_outer(t + 1.0),
112 cubic_inner(t),
113 cubic_inner(1.0 - t),
114 cubic_outer(2.0 - t),
115 ]
116}
117
118const CUBIC_A: f32 = -0.75;
120
121fn cubic_inner(x: f32) -> f32 {
123 ((CUBIC_A + 2.0) * x - (CUBIC_A + 3.0)) * x * x + 1.0
125}
126
127fn cubic_outer(x: f32) -> f32 {
129 ((CUBIC_A * x - 5.0 * CUBIC_A) * x + 8.0 * CUBIC_A) * x - 4.0 * CUBIC_A
131}
132
133fn resize_bicubic(src: &[f32], h: usize, w: usize, oh: usize, ow: usize) -> Vec<f32> {
135 let tx = cubic_taps(w, ow);
136 let ty = cubic_taps(h, oh);
137 let at = |i: isize, n: usize| i.clamp(0, n as isize - 1) as usize;
138 let mut used = vec![false; h];
140 for &(s, _) in &ty {
141 for k in 0..TAPS as isize {
142 used[at(s + k, h)] = true;
143 }
144 }
145 let mut mid = vec![0f32; h * ow];
146 for y in (0..h).filter(|&y| used[y]) {
147 let row = &src[y * w..(y + 1) * w];
148 for (d, (s, wt)) in mid[y * ow..(y + 1) * ow].iter_mut().zip(&tx) {
149 *d = (0..TAPS)
150 .map(|k| row[at(s + k as isize, w)] * wt[k])
151 .sum::<f32>();
152 }
153 }
154 let mut out = vec![0f32; oh * ow];
155 for (drow, (s, wt)) in out.chunks_exact_mut(ow).zip(&ty) {
156 for (k, wk) in wt.iter().enumerate() {
157 let y = at(s + k as isize, h);
158 let srow = &mid[y * ow..(y + 1) * ow];
159 for (d, v) in drow.iter_mut().zip(srow) {
160 *d += v * wk;
161 }
162 }
163 }
164 out
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn 权重和为一且在节点上取原值() {
173 for t in [0.0f32, 0.25, 0.5, 0.9] {
174 let s: f32 = cubic_weights(t).iter().sum();
175 assert!((s - 1.0).abs() < 1e-5, "{t}: {s}");
176 }
177 let w0 = cubic_weights(0.0);
178 assert!(w0[0].abs() < 1e-6 && (w0[1] - 1.0).abs() < 1e-6 && w0[2].abs() < 1e-6);
179 }
180
181 #[test]
182 fn 同尺寸不变_常数保持() {
183 let src: Vec<f32> = (0..16).map(|v| v as f32).collect();
184 let same = resize_bicubic(&src, 4, 4, 4, 4);
185 for (a, b) in same.iter().zip(&src) {
186 assert!((a - b).abs() < 1e-5);
187 }
188 let flat = resize_bicubic(&[7.0; 30], 5, 6, 3, 2);
189 assert!(flat.iter().all(|v| (v - 7.0).abs() < 1e-4));
190 }
191
192 #[test]
193 fn 灰度图三通道相同且缩放到一() {
194 let p = Preprocess {
195 height: 4,
196 width: 4,
197 rescale: 1.0 / 255.0,
198 mean: [0.0; 3],
199 std: [1.0; 3],
200 };
201 let img = Image {
202 width: 8,
203 height: 8,
204 channels: 1,
205 data: vec![255; 64],
206 };
207 let t = p.run(&img);
208 assert_eq!(t.shape, [3, 4, 4]);
209 assert!(t.data.iter().all(|v| (v - 1.0).abs() < 1e-6));
210 }
211
212 #[test]
213 fn 彩色图通道不混() {
214 let p = Preprocess {
215 height: 2,
216 width: 2,
217 rescale: 1.0 / 255.0,
218 mean: [0.0; 3],
219 std: [1.0; 3],
220 };
221 let img = Image {
222 width: 2,
223 height: 2,
224 channels: 3,
225 data: [255u8, 0, 51].repeat(4),
226 };
227 let t = p.run(&img);
228 assert!((t.data[0] - 1.0).abs() < 1e-6);
229 assert!(t.data[4].abs() < 1e-6);
230 assert!((t.data[8] - 0.2).abs() < 1e-6);
231 }
232}