1use super::weights::{
19 DET_DW_KEYS, DET_ONNX_PW, OcrGraphBuilder, assert_weights_drained, detection_input_hw,
20};
21use anyhow::Result;
22use rlx_core::vision_ops_ir::{
23 conv_transpose2d_k3s2_bias_trim, conv2d_bias, conv2d_bias_groups, max_pool2d_2x2, sigmoid_nchw,
24};
25use rlx_core::weight_map::WeightMap;
26use rlx_ir::hir::HirNodeId;
27use rlx_ir::{DType, HirGraphExt, Shape};
28
29#[allow(dead_code)]
31pub const DEFAULT_DETECTION_INPUT_HW: (usize, usize) = (800, 600);
32
33#[derive(Clone, Copy, Debug)]
34pub struct DetectionGraphConfig {
35 pub batch: usize,
36 pub height: usize,
37 pub width: usize,
38}
39
40impl Default for DetectionGraphConfig {
41 fn default() -> Self {
42 let (height, width) = detection_input_hw();
43 Self {
44 batch: 1,
45 height,
46 width,
47 }
48 }
49}
50
51const DEPTH_SCALE: [usize; 7] = [8, 16, 32, 32, 64, 128, 256];
53
54pub fn build_detection_graph(
55 wm: &mut WeightMap,
56 cfg: DetectionGraphConfig,
57) -> Result<(rlx_ir::Graph, std::collections::HashMap<String, Vec<f32>>)> {
58 build_detection_graph_to_stage(wm, cfg, None)
59}
60
61pub fn build_detection_graph_to_stage(
66 wm: &mut WeightMap,
67 cfg: DetectionGraphConfig,
68 stop: Option<u8>,
69) -> Result<(rlx_ir::Graph, std::collections::HashMap<String, Vec<f32>>)> {
70 let mut b = OcrGraphBuilder::new("ocr_detection");
71 let f = DType::F32;
72 let batch = cfg.batch;
73 let mut h = cfg.height;
74 let mut w = cfg.width;
75
76 let image = b.m().input("image", Shape::new(&[batch, 1, h, w], f));
77
78 if matches!(stop, Some(50..=53)) {
80 let s = stop.unwrap();
81 let c0 = DEPTH_SCALE[0];
82 let mut x = depthwise_conv(&mut b, wm, image, DET_DW_KEYS[0], 1, batch, h, w)?;
83 if s == 50 {
84 b.m().set_outputs(vec![x]);
85 return b.finish();
86 }
87 let (pw0_w, pw0_b) = DET_ONNX_PW[0];
88 x = pointwise_relu(&mut b, wm, x, pw0_w, pw0_b, 1, c0, batch, h, w)?;
89 if s == 51 {
90 b.m().set_outputs(vec![x]);
91 return b.finish();
92 }
93 x = depthwise_conv(&mut b, wm, x, DET_DW_KEYS[1], c0, batch, h, w)?;
94 if s == 52 {
95 b.m().set_outputs(vec![x]);
96 return b.finish();
97 }
98 let (pw1_w, pw1_b) = DET_ONNX_PW[1];
99 x = pointwise_relu(&mut b, wm, x, pw1_w, pw1_b, c0, c0, batch, h, w)?;
100 b.m().set_outputs(vec![x]);
101 return b.finish();
102 }
103
104 let mut block = 0usize;
105 let mut x = double_conv(
106 &mut b,
107 wm,
108 image,
109 &mut block,
110 1,
111 DEPTH_SCALE[0],
112 batch,
113 h,
114 w,
115 )?;
116 if stop == Some(0) {
117 b.m().set_outputs(vec![x]);
118 return b.finish();
119 }
120 let in_conv_skip = (x, h, w);
121
122 let mut x_down: Vec<(HirNodeId, usize, usize)> = Vec::new();
123 for level in 0..DEPTH_SCALE.len() - 1 {
124 let in_c = DEPTH_SCALE[level];
125 let out_c = DEPTH_SCALE[level + 1];
126 x = double_conv(&mut b, wm, x, &mut block, in_c, out_c, batch, h, w)?;
127 x = max_pool2d_2x2(&mut b.m(), x, batch, out_c, h, w);
128 h /= 2;
129 w /= 2;
130 x_down.push((x, h, w));
131 if stop == Some(1 + level as u8) {
132 b.m().set_outputs(vec![x]);
133 return b.finish();
134 }
135 }
136
137 let mut x_up = x;
138 let mut up_h = h;
139 let mut up_w = w;
140 for (up_stage, up_idx) in (7u8..).zip((0..DEPTH_SCALE.len() - 1).rev()) {
141 let out_c = DEPTH_SCALE[up_idx];
142 let cross_c = DEPTH_SCALE[up_idx];
143 let (skip, skip_h, skip_w) = if up_idx == 0 {
144 (in_conv_skip.0, in_conv_skip.1, in_conv_skip.2)
145 } else {
146 let (skip_node, sh, sw) = x_down[up_idx - 1];
147 (skip_node, sh, sw)
148 };
149
150 let up_w_key = format!("up.{up_idx}.up.weight");
151 let up_b_key = format!("up.{up_idx}.up.bias");
152 let up_weight = b.load_param(wm, &up_w_key)?;
153 let up_bias = b.load_param(wm, &up_b_key)?;
154 let upscaled = conv_transpose2d_k3s2_bias_trim(
155 &mut b.m(),
156 x_up,
157 up_weight,
158 up_bias,
159 batch,
160 out_c,
161 up_h,
162 up_w,
163 skip_h,
164 skip_w,
165 );
166 up_h = skip_h;
167 up_w = skip_w;
168
169 let cat = b.m().concat_(vec![upscaled, skip], 1);
170 x_up = double_conv(
171 &mut b,
172 wm,
173 cat,
174 &mut block,
175 out_c + cross_c,
176 out_c,
177 batch,
178 up_h,
179 up_w,
180 )?;
181 if stop == Some(up_stage) {
182 b.m().set_outputs(vec![x_up]);
183 return b.finish();
184 }
185 }
186
187 let out_w = b.load_param(wm, "out_conv.0.weight")?;
188 let out_b = b.load_param(wm, "out_conv.0.bias")?;
189 let logits = conv2d_bias(
190 &mut b.m(),
191 x_up,
192 out_w,
193 out_b,
194 batch,
195 1,
196 1,
197 1,
198 [1, 1],
199 [0, 0],
200 up_h,
201 up_w,
202 );
203 if stop == Some(13) {
204 b.m().set_outputs(vec![logits]);
205 return b.finish();
206 }
207 let mask = sigmoid_nchw(&mut b.m(), logits);
208 b.m().set_outputs(vec![mask]);
209
210 if stop.is_none() {
211 assert_weights_drained(wm, "detection graph")?;
212 }
213 b.finish()
214}
215
216fn double_conv(
217 b: &mut OcrGraphBuilder,
218 wm: &mut WeightMap,
219 mut x: HirNodeId,
220 block: &mut usize,
221 in_c: usize,
222 out_c: usize,
223 batch: usize,
224 h: usize,
225 w: usize,
226) -> Result<HirNodeId> {
227 let (pw0_w, pw0_b) = DET_ONNX_PW[*block];
228 x = depthwise_conv(b, wm, x, DET_DW_KEYS[*block], in_c, batch, h, w)?;
230 x = pointwise_relu(b, wm, x, pw0_w, pw0_b, in_c, out_c, batch, h, w)?;
231 *block += 1;
232 let (pw1_w, pw1_b) = DET_ONNX_PW[*block];
233 x = depthwise_conv(b, wm, x, DET_DW_KEYS[*block], out_c, batch, h, w)?;
234 x = pointwise_relu(b, wm, x, pw1_w, pw1_b, out_c, out_c, batch, h, w)?;
235 *block += 1;
236 Ok(x)
237}
238
239fn depthwise_conv(
240 b: &mut OcrGraphBuilder,
241 wm: &mut WeightMap,
242 x: HirNodeId,
243 dw_key: &str,
244 channels: usize,
245 batch: usize,
246 h: usize,
247 w: usize,
248) -> Result<HirNodeId> {
249 let weight = b.load_param(wm, dw_key)?;
250 let bias = b.zero_bias(channels)?;
251 Ok(conv2d_bias_groups(
252 &mut b.m(),
253 x,
254 weight,
255 bias,
256 batch,
257 channels,
258 3,
259 3,
260 [1, 1],
261 [1, 1],
262 channels,
263 h,
264 w,
265 ))
266}
267
268fn pointwise_relu(
269 b: &mut OcrGraphBuilder,
270 wm: &mut WeightMap,
271 x: HirNodeId,
272 w_key: &str,
273 b_key: &str,
274 _in_c: usize,
275 out_c: usize,
276 batch: usize,
277 h: usize,
278 w: usize,
279) -> Result<HirNodeId> {
280 let weight = b.load_param(wm, w_key)?;
281 let bias = b.load_param(wm, b_key)?;
282 let y = conv2d_bias(
283 &mut b.m(),
284 x,
285 weight,
286 bias,
287 batch,
288 out_c,
289 1,
290 1,
291 [1, 1],
292 [0, 0],
293 h,
294 w,
295 );
296 Ok(b.m().relu(y))
297}