@@ -47,13 +47,12 @@ pub struct Region {
4747 pub b : f32 ,
4848}
4949
50- #[ cfg( feature = "ml" ) ]
5150/// Base confidence threshold (docling-ibm-models `base_threshold`): the raw
5251/// RT-DETR floor before docling's `LayoutPostprocessor` applies its stricter
5352/// per-label thresholds ([`label_threshold`]).
5453const THRESHOLD : f32 = 0.3 ;
55- # [ cfg ( feature = "ml" ) ]
56- const SIDE : u32 = 640 ;
54+ /// RT-DETR's fixed square input side.
55+ pub const SIDE : u32 = 640 ;
5756
5857/// Per-label confidence threshold, ported from docling's
5958/// `LayoutPostprocessor.CONFIDENCE_THRESHOLDS`. The raw predictor keeps every
@@ -222,46 +221,81 @@ impl LayoutModel {
222221 let logits =
223222 & logits[ p * num_queries * num_classes..( p + 1 ) * num_queries * num_classes] ;
224223 let boxes = & boxes[ p * num_queries * 4 ..( p + 1 ) * num_queries * 4 ] ;
225-
226- // sigmoid over every (query, class); take the top `num_queries` scores.
227- let mut scored: Vec < ( f32 , usize ) > = ( 0 ..num_queries * num_classes)
228- . map ( |idx| ( sigmoid ( logits[ idx] ) , idx) )
229- . collect ( ) ;
230- scored. sort_unstable_by ( |a, b| b. 0 . total_cmp ( & a. 0 ) ) ;
231- scored. truncate ( num_queries) ;
232-
233- let mut regions = Vec :: new ( ) ;
234- for ( score, idx) in scored {
235- if score <= THRESHOLD {
236- continue ;
237- }
238- let label_id = idx % num_classes;
239- let q = idx / num_classes;
240- let cx = boxes[ q * 4 ] ;
241- let cy = boxes[ q * 4 + 1 ] ;
242- let w = boxes[ q * 4 + 2 ] ;
243- let h = boxes[ q * 4 + 3 ] ;
244- // center_to_corners, then scale normalized coords to page points.
245- let l = ( cx - w / 2.0 ) * page_w;
246- let t = ( cy - h / 2.0 ) * page_h;
247- let r = ( cx + w / 2.0 ) * page_w;
248- let b = ( cy + h / 2.0 ) * page_h;
249- regions. push ( Region {
250- label : LABELS . get ( label_id) . copied ( ) . unwrap_or ( "text" ) ,
251- score,
252- l,
253- t,
254- r,
255- b,
256- } ) ;
257- }
258- all. push ( regions) ;
224+ all. push ( decode_layout (
225+ logits,
226+ boxes,
227+ num_queries,
228+ num_classes,
229+ * page_w,
230+ * page_h,
231+ ) ) ;
259232 }
260233 Ok ( all)
261234 }
262235}
263236
264- #[ cfg( feature = "ml" ) ]
265237fn sigmoid ( x : f32 ) -> f32 {
266238 1.0 / ( 1.0 + ( -x) . exp ( ) )
267239}
240+
241+ /// Pack one page image into the model's `(1, 3, SIDE, SIDE)` input: resize
242+ /// (aspect ignored, RT-DETR convention), rescale to `[0,1]`, CHW. Shared
243+ /// with the browser build (#157), which delegates only the session call.
244+ #[ cfg( feature = "ocr-prep" ) ]
245+ pub fn layout_input ( img : & image:: RgbImage ) -> Vec < f32 > {
246+ let n = ( SIDE * SIDE ) as usize ;
247+ let mut data = vec ! [ 0f32 ; 3 * n] ;
248+ let resized = image:: imageops:: resize ( img, SIDE , SIDE , image:: imageops:: FilterType :: Triangle ) ;
249+ for ( i, px) in resized. pixels ( ) . enumerate ( ) {
250+ data[ i] = px[ 0 ] as f32 / 255.0 ;
251+ data[ n + i] = px[ 1 ] as f32 / 255.0 ;
252+ data[ 2 * n + i] = px[ 2 ] as f32 / 255.0 ;
253+ }
254+ data
255+ }
256+
257+ /// Decode one page's raw RT-DETR outputs into scored [`Region`]s in page
258+ /// points — sigmoid over every (query, class), top-`num_queries` kept, boxes
259+ /// converted center→corners and scaled. Shared with the browser build; the
260+ /// native batch path calls it per page, so both decode identically.
261+ pub fn decode_layout (
262+ logits : & [ f32 ] ,
263+ boxes : & [ f32 ] ,
264+ num_queries : usize ,
265+ num_classes : usize ,
266+ page_w : f32 ,
267+ page_h : f32 ,
268+ ) -> Vec < Region > {
269+ let mut scored: Vec < ( f32 , usize ) > = ( 0 ..num_queries * num_classes)
270+ . map ( |idx| ( sigmoid ( logits[ idx] ) , idx) )
271+ . collect ( ) ;
272+ scored. sort_unstable_by ( |a, b| b. 0 . total_cmp ( & a. 0 ) ) ;
273+ scored. truncate ( num_queries) ;
274+
275+ let mut regions = Vec :: new ( ) ;
276+ for ( score, idx) in scored {
277+ if score <= THRESHOLD {
278+ continue ;
279+ }
280+ let label_id = idx % num_classes;
281+ let q = idx / num_classes;
282+ let cx = boxes[ q * 4 ] ;
283+ let cy = boxes[ q * 4 + 1 ] ;
284+ let w = boxes[ q * 4 + 2 ] ;
285+ let h = boxes[ q * 4 + 3 ] ;
286+ // center_to_corners, then scale normalized coords to page points.
287+ let l = ( cx - w / 2.0 ) * page_w;
288+ let t = ( cy - h / 2.0 ) * page_h;
289+ let r = ( cx + w / 2.0 ) * page_w;
290+ let b = ( cy + h / 2.0 ) * page_h;
291+ regions. push ( Region {
292+ label : LABELS . get ( label_id) . copied ( ) . unwrap_or ( "text" ) ,
293+ score,
294+ l,
295+ t,
296+ r,
297+ b,
298+ } ) ;
299+ }
300+ regions
301+ }
0 commit comments