1use crate::pdfium_backend::TextCell;
8use image::RgbImage;
9use ort::session::Session;
10use ort::value::{DynValue, Tensor};
11
12const SIDE: u32 = 448;
13#[allow(clippy::excessive_precision)]
16const MEAN: [f32; 3] = [0.94247851, 0.94254675, 0.94292611];
17#[allow(clippy::excessive_precision)]
18const STD: [f32; 3] = [0.17910956, 0.17940403, 0.17931663];
19const MAX_STEPS: usize = 1024;
20const N_LAYERS: usize = 6;
23const EMBED_DIM: usize = 512;
24
25pub const START: i64 = 2;
27pub const END: i64 = 3;
28pub const ECEL: i64 = 4; pub const FCEL: i64 = 5; pub const LCEL: i64 = 6; pub const UCEL: i64 = 7; pub const XCEL: i64 = 8; pub const NL: i64 = 9; pub const CHED: i64 = 10; pub const RHED: i64 = 11; pub const SROW: i64 = 12; #[derive(Debug, Clone)]
42pub struct TableCell {
43 pub row: usize,
44 pub col: usize,
45 pub colspan: usize,
46 pub rowspan: usize,
47 pub tag: i64,
48 pub class: i64,
49 pub cx: f32,
50 pub cy: f32,
51 pub w: f32,
52 pub h: f32,
53}
54
55pub struct TableFormer {
56 encoder: Session,
57 decoder: Session,
58 bbox: Session,
59 kv: bool,
65}
66
67const KV_HEADS: usize = 8;
70const KV_HEAD_DIM: usize = 64;
71
72#[derive(Default)]
76struct DecodeCache {
77 a: Option<DynValue>,
78 b: Option<DynValue>,
79}
80
81type EmptyCache = (Tensor<f32>, Option<Tensor<f32>>);
84
85struct EncodeOut {
90 ck: DynValue,
91 cv: DynValue,
92 eo: DynValue,
93}
94
95impl TableFormer {
96 pub fn load() -> Option<Self> {
100 Self::load_with(crate::intra_threads())
101 }
102
103 pub fn load_with(intra: usize) -> Option<Self> {
107 let enc = std::env::var("DOCLING_TABLEFORMER_ENCODER")
108 .unwrap_or_else(|_| crate::resolve_asset("models/tableformer/encoder.onnx"));
109 let dec = std::env::var("DOCLING_TABLEFORMER_DECODER").unwrap_or_else(|_| {
120 let candidates: &[&str] = if crate::fp32_forced() {
121 &[
122 "models/tableformer/decoder_kv.onnx",
123 "models/tableformer/decoder.onnx",
124 ]
125 } else {
126 &[
127 "models/tableformer/decoder_kv_int8.onnx",
128 "models/tableformer/decoder_int8.onnx",
129 "models/tableformer/decoder_kv.onnx",
130 "models/tableformer/decoder.onnx",
131 ]
132 };
133 candidates
134 .iter()
135 .map(|p| crate::resolve_asset(p))
136 .find(|p| std::path::Path::new(p).exists())
137 .unwrap_or_else(|| "models/tableformer/decoder.onnx".to_string())
138 });
139 let bbx = std::env::var("DOCLING_TABLEFORMER_BBOX")
140 .unwrap_or_else(|_| crate::resolve_asset("models/tableformer/bbox.onnx"));
141 if crate::timing::enabled() {
142 eprintln!("docling-pdf: tableformer decoder: {dec}");
143 }
144 if [&enc, &dec, &bbx]
145 .iter()
146 .any(|p| !std::path::Path::new(p).exists())
147 {
148 warn_missing_once(&enc, &dec, &bbx);
155 return None;
156 }
157 let build = |path: &str, mem_pattern: bool| -> Result<Session, String> {
163 Session::builder()
164 .map_err(|e| e.to_string())?
165 .with_intra_threads(intra)
166 .map_err(|e| e.to_string())?
167 .with_memory_pattern(mem_pattern)
168 .map_err(|e| e.to_string())?
169 .commit_from_file(path)
170 .map_err(|e| format!("tableformer load {path}: {e}"))
171 };
172 match (build(&enc, true), build(&dec, false), build(&bbx, true)) {
173 (Ok(encoder), Ok(decoder), Ok(bbox)) => {
174 let kv = decoder.inputs().iter().any(|i| i.name() == "cache_k");
175 Some(Self {
176 encoder,
177 decoder,
178 bbox,
179 kv,
180 })
181 }
182 _ => None,
183 }
184 }
185
186 fn encode(&mut self, img: &RgbImage) -> Result<EncodeOut, String> {
190 let input = preprocess(img)?;
191 let mut enc_out = self
192 .encoder
193 .run(ort::inputs!["image" => input])
194 .map_err(|e| format!("tableformer: encode: {e}"))?;
195 let mut grab = |name: &str| -> Result<DynValue, String> {
196 enc_out
197 .remove(name)
198 .ok_or_else(|| format!("tableformer: encoder output {name} missing"))
199 };
200 Ok(EncodeOut {
201 ck: grab("cross_k")?,
202 cv: grab("cross_v")?,
203 eo: grab("enc_out")?,
204 })
205 }
206
207 fn decode_step(
216 &mut self,
217 tags: &[i64],
218 enc: &EncodeOut,
219 cache: &mut DecodeCache,
220 empty: &EmptyCache,
221 ) -> Result<(i64, Vec<f32>), String> {
222 let mut dout = if self.kv {
223 let last = *tags.last().expect("decode starts from <start>");
226 let tag_t = Tensor::from_array(([1usize, 1usize], vec![last]))
227 .map_err(|e| format!("tableformer: tag: {e}"))?;
228 match (cache.a.as_ref(), cache.b.as_ref()) {
229 (Some(k), Some(v)) => self.decoder.run(ort::inputs![
230 "tag" => tag_t, "cross_k" => &enc.ck, "cross_v" => &enc.cv,
231 "cache_k" => k, "cache_v" => v]),
232 _ => self.decoder.run(ort::inputs![
233 "tag" => tag_t, "cross_k" => &enc.ck, "cross_v" => &enc.cv,
234 "cache_k" => &empty.0,
235 "cache_v" => empty.1.as_ref().expect("kv empty cache has both halves")]),
236 }
237 } else {
238 let tags_t = Tensor::from_array(([tags.len(), 1usize], tags.to_vec()))
239 .map_err(|e| format!("tableformer: tags: {e}"))?;
240 match cache.a.as_ref() {
241 None => self.decoder.run(ort::inputs![
242 "tags" => tags_t, "cross_k" => &enc.ck, "cross_v" => &enc.cv,
243 "cache" => &empty.0]),
244 Some(c) => self.decoder.run(ort::inputs![
245 "tags" => tags_t, "cross_k" => &enc.ck, "cross_v" => &enc.cv,
246 "cache" => c]),
247 }
248 }
249 .map_err(|e| format!("tableformer: decode: {e}"))?;
250 let (_, logits) = dout["logits"]
251 .try_extract_tensor::<f32>()
252 .map_err(|e| format!("tableformer: logits: {e}"))?;
253 let raw = argmax(logits) as i64;
254 let (_, hidden) = dout["hidden"]
255 .try_extract_tensor::<f32>()
256 .map_err(|e| format!("tableformer: hidden: {e}"))?;
257 let hidden = hidden.to_vec();
258 if self.kv {
259 cache.a = Some(
260 dout.remove("out_cache_k")
261 .ok_or_else(|| "tableformer: out_cache_k missing".to_string())?,
262 );
263 cache.b = Some(
264 dout.remove("out_cache_v")
265 .ok_or_else(|| "tableformer: out_cache_v missing".to_string())?,
266 );
267 } else {
268 cache.a = Some(
269 dout.remove("out_cache")
270 .ok_or_else(|| "tableformer: decoder output out_cache missing".to_string())?,
271 );
272 }
273 Ok((raw, hidden))
274 }
275
276 fn empty_cache(&self) -> Result<EmptyCache, String> {
280 let alloc = self.decoder.allocator();
281 if self.kv {
282 let mk = || {
283 Tensor::<f32>::new(alloc, [N_LAYERS, 1, KV_HEADS, 0usize, KV_HEAD_DIM])
284 .map_err(|e| format!("tableformer: empty kv cache: {e}"))
285 };
286 Ok((mk()?, Some(mk()?)))
287 } else {
288 let c = Tensor::<f32>::new(alloc, [N_LAYERS, 0usize, 1, EMBED_DIM])
289 .map_err(|e| format!("tableformer: empty cache: {e}"))?;
290 Ok((c, None))
291 }
292 }
293
294 pub fn predict_otsl(&mut self, img: &RgbImage) -> Result<Vec<i64>, String> {
296 let enc = self.encode(img)?;
297 let mut tags: Vec<i64> = vec![START];
300 let mut out: Vec<i64> = Vec::new();
301 let mut prev_ucel = false;
302 let mut cache = DecodeCache::default();
303 let empty = self.empty_cache()?;
304 while out.len() < MAX_STEPS {
305 let (raw, _hidden) = self.decode_step(&tags, &enc, &mut cache, &empty)?;
306 let mut tag = raw;
307 if tag == XCEL {
308 tag = LCEL;
309 }
310 if prev_ucel && tag == LCEL {
311 tag = FCEL;
312 }
313 if tag == END {
314 break;
315 }
316 out.push(tag);
317 tags.push(tag);
318 prev_ucel = tag == UCEL;
319 }
320 Ok(out)
321 }
322
323 pub fn predict_table_structure(&mut self, img: &RgbImage) -> Result<Vec<TableCell>, String> {
329 let enc = self.encode(img)?;
330
331 let mut tags: Vec<i64> = vec![START];
332 let mut otsl: Vec<i64> = Vec::new();
333 let mut hiddens: Vec<f32> = Vec::new(); let mut n = 0usize;
335 let mut prev_ucel = false;
336 let mut skip = true; let mut first_lcel = true;
338 let mut bbox_ind = 0usize;
339 let mut cur_bbox_ind = 0usize;
340 let mut merge: std::collections::HashMap<usize, i64> = std::collections::HashMap::new();
341 let mut cache = DecodeCache::default();
342 let empty = self.empty_cache()?;
343 while otsl.len() < MAX_STEPS {
344 let (raw, hidden) = self.decode_step(&tags, &enc, &mut cache, &empty)?;
345 let mut tag = raw;
346 if tag == XCEL {
347 tag = LCEL;
348 }
349 if prev_ucel && tag == LCEL {
350 tag = FCEL;
351 }
352 if tag == END {
353 break;
354 }
355 if !skip && matches!(tag, FCEL | ECEL | CHED | RHED | SROW | NL | UCEL) {
357 hiddens.extend_from_slice(&hidden);
358 n += 1;
359 if !first_lcel {
360 merge.insert(cur_bbox_ind, bbox_ind as i64);
361 }
362 bbox_ind += 1;
363 }
364 if tag != LCEL {
365 first_lcel = true;
366 } else if first_lcel {
367 hiddens.extend_from_slice(&hidden);
368 n += 1;
369 first_lcel = false;
370 cur_bbox_ind = bbox_ind;
371 merge.insert(cur_bbox_ind, -1);
372 bbox_ind += 1;
373 }
374 skip = matches!(tag, NL | UCEL | XCEL);
375 prev_ucel = tag == UCEL;
376 otsl.push(tag);
377 tags.push(tag);
378 }
379 if n == 0 {
380 return Ok(Vec::new());
381 }
382 let tag_h = Tensor::from_array(([n, 512usize], hiddens))
383 .map_err(|e| format!("tableformer: tag_h: {e}"))?;
384 let bout = self
385 .bbox
386 .run(ort::inputs!["enc_out" => &enc.eo, "tag_h" => tag_h])
387 .map_err(|e| format!("tableformer: bbox: {e}"))?;
388 let (_, raw) = bout["boxes"]
389 .try_extract_tensor::<f32>()
390 .map_err(|e| format!("tableformer: boxes: {e}"))?;
391 let boxes: Vec<[f32; 4]> = raw
392 .chunks_exact(4)
393 .map(|c| [c[0], c[1], c[2], c[3]])
394 .collect();
395 let (_, craw) = bout["classes"]
397 .try_extract_tensor::<f32>()
398 .map_err(|e| format!("tableformer: classes: {e}"))?;
399 let classes: Vec<i64> = craw.chunks_exact(3).map(|c| argmax(c) as i64).collect();
400 let (merged, merged_classes) = merge_spans(&boxes, &classes, &merge);
401 Ok(build_table_cells(&otsl, &merged, &merged_classes))
402 }
403
404 pub fn predict_table_rows(
411 &mut self,
412 page_image: &RgbImage,
413 region: [f32; 4],
414 words: &[TextCell],
415 ) -> Option<Vec<Vec<String>>> {
416 let sf = 1024.0 / page_image.height() as f32;
425 let pw = (page_image.width() as f32 * sf) as u32;
426 let page1024 = crate::timing::timed("tableformer.inter_area", || {
427 crate::resample::inter_area(page_image, pw, 1024)
428 });
429 let k = 2.0 * 1024.0 / page_image.height() as f64;
430 let px = |v: f32| (v as f64).round_ties_even() * k;
431 let x = (px(region[0]).round_ties_even()).max(0.0) as u32;
432 let y = (px(region[1]).round_ties_even()).max(0.0) as u32;
433 let x2 = (px(region[2]).round_ties_even() as u32).min(page1024.width());
434 let y2 = (px(region[3]).round_ties_even() as u32).min(page1024.height());
435 if x2 <= x || y2 <= y {
436 return None;
437 }
438 let crop = image::imageops::crop_imm(&page1024, x, y, x2 - x, y2 - y).to_image();
439 let cells = crate::timing::timed("tableformer.structure", || {
440 self.predict_table_structure(&crop)
441 })
442 .ok()?;
443 if cells.is_empty() {
444 return None;
445 }
446 let table_words: Vec<crate::tf_match::PdfWord> = words
450 .iter()
451 .enumerate()
452 .filter(|(_, w)| !w.text.trim().is_empty())
453 .filter_map(|(wi, w)| {
454 let (l, t, r, b) = (w.l as f64, w.t as f64, w.r as f64, w.b as f64);
455 let area = (r - l) * (b - t);
456 let iw = (r.min(region[2] as f64) - l.max(region[0] as f64)).max(0.0);
457 let ih = (b.min(region[3] as f64) - t.max(region[1] as f64)).max(0.0);
458 if area > 0.0 && iw * ih / area > 0.8 {
459 Some(crate::tf_match::PdfWord {
460 id: wi,
461 bbox: [l, t, r, b],
462 text: w.text.trim().to_string(),
463 })
464 } else {
465 None
466 }
467 })
468 .collect();
469
470 if !table_words.is_empty() && !simple_match() {
471 return docling_match_rows(&cells, region, &table_words, words);
472 }
473
474 let (rw, rh) = (region[2] - region[0], region[3] - region[1]);
475
476 let boxes: Vec<[f32; 4]> = cells
478 .iter()
479 .map(|c| {
480 [
481 region[0] + (c.cx - c.w / 2.0) * rw,
482 region[1] + (c.cy - c.h / 2.0) * rh,
483 region[0] + (c.cx + c.w / 2.0) * rw,
484 region[1] + (c.cy + c.h / 2.0) * rh,
485 ]
486 })
487 .collect();
488
489 let mut cell_words: Vec<Vec<usize>> = vec![Vec::new(); cells.len()];
491 for (wi, w) in words.iter().enumerate() {
492 let wa = ((w.r - w.l) * (w.b - w.t)).max(1.0);
493 let mut best: Option<(f32, usize)> = None;
494 for (ci, b) in boxes.iter().enumerate() {
495 let ix = (w.r.min(b[2]) - w.l.max(b[0])).max(0.0);
496 let iy = (w.b.min(b[3]) - w.t.max(b[1])).max(0.0);
497 let io = ix * iy / wa;
498 if io > 0.0 && best.is_none_or(|(bo, _)| io > bo) {
499 best = Some((io, ci));
500 }
501 }
502 if let Some((_, ci)) = best {
503 cell_words[ci].push(wi);
504 }
505 }
506
507 let num_rows = cells.iter().map(|c| c.row + c.rowspan).max().unwrap_or(0);
508 let num_cols = cells.iter().map(|c| c.col + c.colspan).max().unwrap_or(0);
509 if num_rows == 0 || num_cols == 0 {
510 return None;
511 }
512 let mut grid = vec![vec![String::new(); num_cols]; num_rows];
513 for (ci, c) in cells.iter().enumerate() {
514 let wis = std::mem::take(&mut cell_words[ci]);
518 let text = wis
519 .iter()
520 .map(|&i| words[i].text.trim())
521 .collect::<Vec<_>>()
522 .join(" ");
523 let text = normalize_cell_text(text);
524 for row in grid.iter_mut().skip(c.row).take(c.rowspan) {
526 for cell in row.iter_mut().skip(c.col).take(c.colspan) {
527 *cell = text.clone();
528 }
529 }
530 }
531 Some(grid)
532 }
533}
534
535fn dump_match_inputs(
538 dir: &str,
539 tf_cells: &[crate::tf_match::TfCell],
540 words: &[crate::tf_match::PdfWord],
541) {
542 use std::io::Write;
543 let cells: Vec<String> = tf_cells
544 .iter()
545 .map(|c| {
546 format!(
547 r#"{{"bbox":[{},{},{},{}],"cell_id":{},"row_id":{},"column_id":{},"cell_class":{},"colspan_val":{},"rowspan_val":{}}}"#,
548 c.bbox[0], c.bbox[1], c.bbox[2], c.bbox[3],
549 c.cell_id, c.row_id, c.column_id, c.cell_class,
550 c.colspan_val, c.rowspan_val
551 )
552 })
553 .collect();
554 let ws: Vec<String> = words
555 .iter()
556 .map(|w| {
557 format!(
558 r#"{{"id":{},"bbox":[{},{},{},{}],"text":{}}}"#,
559 w.id,
560 w.bbox[0],
561 w.bbox[1],
562 w.bbox[2],
563 w.bbox[3],
564 serde_json_escape(&w.text)
565 )
566 })
567 .collect();
568 let line = format!(
569 r#"{{"table_cells":[{}],"pdf_cells":[{}]}}"#,
570 cells.join(","),
571 ws.join(",")
572 );
573 if let Ok(mut f) = std::fs::OpenOptions::new()
574 .create(true)
575 .append(true)
576 .open(format!("{dir}/tf_match_dump.jsonl"))
577 {
578 let _ = writeln!(f, "{line}");
579 }
580}
581
582fn serde_json_escape(s: &str) -> String {
584 let mut out = String::with_capacity(s.len() + 2);
585 out.push('"');
586 for ch in s.chars() {
587 match ch {
588 '"' => out.push_str("\\\""),
589 '\\' => out.push_str("\\\\"),
590 '\n' => out.push_str("\\n"),
591 '\r' => out.push_str("\\r"),
592 '\t' => out.push_str("\\t"),
593 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
594 c => out.push(c),
595 }
596 }
597 out.push('"');
598 out
599}
600
601fn simple_match() -> bool {
604 std::env::var("DOCLING_RS_TF_SIMPLE_MATCH").is_ok_and(|v| !v.is_empty() && v != "0")
605}
606
607fn normalize_cell_text(text: String) -> String {
613 text.replace("@ ", "@")
614}
615
616fn docling_match_rows(
624 cells: &[TableCell],
625 region: [f32; 4],
626 table_words: &[crate::tf_match::PdfWord],
627 words: &[TextCell],
628) -> Option<Vec<Vec<String>>> {
629 const SCALE: f64 = 2.0; let sl = (region[0] as f64).round_ties_even() * SCALE;
631 let st = (region[1] as f64).round_ties_even() * SCALE;
632 let sr = (region[2] as f64).round_ties_even() * SCALE;
633 let sb = (region[3] as f64).round_ties_even() * SCALE;
634 let (w2, h2) = (sr - sl, sb - st);
635
636 let tf_cells: Vec<crate::tf_match::TfCell> = cells
637 .iter()
638 .enumerate()
639 .map(|(i, c)| {
640 let (cx, cy) = (c.cx as f64, c.cy as f64);
641 let (w, h) = (c.w as f64, c.h as f64);
642 crate::tf_match::TfCell {
643 bbox: [
644 sl + (cx - w / 2.0) * w2,
645 st + (cy - h / 2.0) * h2,
646 sl + (cx + w / 2.0) * w2,
647 st + (cy + h / 2.0) * h2,
648 ],
649 cell_id: i,
650 row_id: c.row,
651 column_id: c.col,
652 cell_class: c.class,
653 colspan_val: if c.colspan > 1 { c.colspan } else { 0 },
654 rowspan_val: if c.rowspan > 1 { c.rowspan } else { 0 },
655 }
656 })
657 .collect();
658
659 let scaled_words: Vec<crate::tf_match::PdfWord> = table_words
660 .iter()
661 .map(|w| crate::tf_match::PdfWord {
662 id: w.id,
663 bbox: [
664 w.bbox[0] * SCALE,
665 w.bbox[1] * SCALE,
666 w.bbox[2] * SCALE,
667 w.bbox[3] * SCALE,
668 ],
669 text: w.text.clone(),
670 })
671 .collect();
672
673 if let Ok(dir) = std::env::var("DOCLING_RS_TF_MATCH_DUMP") {
676 if !dir.is_empty() {
677 dump_match_inputs(&dir, &tf_cells, &scaled_words);
678 }
679 }
680
681 let (cells_wo, final_matches) =
682 crate::tf_match::match_and_post_process(tf_cells, &scaled_words);
683
684 struct Merged {
687 start_row: usize,
688 start_col: usize,
689 row_span: usize,
690 col_span: usize,
691 word_ids: Vec<usize>,
692 }
693 let mut merged: Vec<Merged> = Vec::new();
694 let mut key_ix: std::collections::HashMap<(usize, usize), usize> =
695 std::collections::HashMap::new();
696 for (&pdf_id, list) in &final_matches {
697 let tm = list[0].table_cell_id;
698 let Some(cell) = cells_wo.iter().find(|c| c.cell_id == tm) else {
699 continue;
700 };
701 match key_ix.entry((cell.column_id, cell.row_id)) {
702 std::collections::hash_map::Entry::Occupied(e) => {
703 merged[*e.get()].word_ids.push(pdf_id);
704 }
705 std::collections::hash_map::Entry::Vacant(e) => {
706 e.insert(merged.len());
707 merged.push(Merged {
708 start_row: cell.row_id,
709 start_col: cell.column_id,
710 row_span: cell.rowspan_val.max(1),
711 col_span: cell.colspan_val.max(1),
712 word_ids: vec![pdf_id],
713 });
714 }
715 }
716 }
717 if merged.is_empty() {
718 return None;
719 }
720
721 let mut start_cols: Vec<usize> = merged.iter().map(|m| m.start_col).collect();
724 start_cols.sort_unstable();
725 start_cols.dedup();
726 let mut start_rows: Vec<usize> = merged.iter().map(|m| m.start_row).collect();
727 start_rows.sort_unstable();
728 start_rows.dedup();
729 let mut num_rows = 0;
730 let mut num_cols = 0;
731 for m in &mut merged {
732 m.start_col = start_cols.binary_search(&m.start_col).expect("own value");
733 m.start_row = start_rows.binary_search(&m.start_row).expect("own value");
734 num_cols = num_cols.max(m.start_col + m.col_span);
735 num_rows = num_rows.max(m.start_row + m.row_span);
736 }
737 if num_rows == 0 || num_cols == 0 {
738 return None;
739 }
740
741 let mut grid = vec![vec![String::new(); num_cols]; num_rows];
742 for m in &merged {
743 let text = m
744 .word_ids
745 .iter()
746 .map(|&i| words[i].text.trim())
747 .collect::<Vec<_>>()
748 .join(" ");
749 let text = normalize_cell_text(text);
750 for row in grid.iter_mut().skip(m.start_row).take(m.row_span) {
751 for cell in row.iter_mut().skip(m.start_col).take(m.col_span) {
752 *cell = text.clone();
753 }
754 }
755 }
756 Some(grid)
757}
758
759fn warn_missing_once(enc: &str, dec: &str, bbx: &str) {
766 static WARNED: std::sync::Once = std::sync::Once::new();
767 WARNED.call_once(|| {
768 eprintln!(
769 "docling.rs: TableFormer models not found (checked {enc}, {dec}, {bbx}); \
770 tables will use geometric reconstruction instead of ML table-structure \
771 recognition. Set DOCLING_TABLEFORMER_ENCODER / DOCLING_TABLEFORMER_DECODER \
772 / DOCLING_TABLEFORMER_BBOX to enable it (see README.md)."
773 );
774 });
775}
776
777fn preprocess(img: &RgbImage) -> Result<Tensor<f32>, String> {
782 let nn = (SIDE * SIDE) as usize;
783 let side = SIDE as usize;
784 let (sw, sh) = (img.width() as i32, img.height() as i32);
785 let sxr = sw as f32 / SIDE as f32;
786 let syr = sh as f32 / SIDE as f32;
787 let mut data = vec![0f32; 3 * nn];
788 for h in 0..side {
789 let fy = (h as f32 + 0.5) * syr - 0.5;
790 let wy = fy - fy.floor();
791 let y0c = (fy.floor() as i32).clamp(0, sh - 1) as u32;
792 let y1c = (fy.floor() as i32 + 1).clamp(0, sh - 1) as u32;
793 for w in 0..side {
794 let fx = (w as f32 + 0.5) * sxr - 0.5;
795 let wx = fx - fx.floor();
796 let x0c = (fx.floor() as i32).clamp(0, sw - 1) as u32;
797 let x1c = (fx.floor() as i32 + 1).clamp(0, sw - 1) as u32;
798 let p00 = img.get_pixel(x0c, y0c);
799 let p01 = img.get_pixel(x1c, y0c);
800 let p10 = img.get_pixel(x0c, y1c);
801 let p11 = img.get_pixel(x1c, y1c);
802 let idx = w * side + h; for c in 0..3 {
804 let top = p00[c] as f32 * (1.0 - wx) + p01[c] as f32 * wx;
805 let bot = p10[c] as f32 * (1.0 - wx) + p11[c] as f32 * wx;
806 let v = top * (1.0 - wy) + bot * wy;
807 data[c * nn + idx] = (v / 255.0 - MEAN[c]) / STD[c];
808 }
809 }
810 }
811 Tensor::from_array(([1usize, 3, side, side], data))
812 .map_err(|e| format!("tableformer: input: {e}"))
813}
814
815fn mergebboxes(b1: [f32; 4], b2: [f32; 4]) -> [f32; 4] {
818 let new_w = (b2[0] + b2[2] / 2.0) - (b1[0] - b1[2] / 2.0);
819 let new_h = (b2[1] + b2[3] / 2.0) - (b1[1] - b1[3] / 2.0);
820 let new_left = b1[0] - b1[2] / 2.0;
821 let new_top = (b2[1] - b2[3] / 2.0).min(b1[1] - b1[3] / 2.0);
822 [new_left + new_w / 2.0, new_top + new_h / 2.0, new_w, new_h]
823}
824
825fn merge_spans(
829 boxes: &[[f32; 4]],
830 classes: &[i64],
831 merge: &std::collections::HashMap<usize, i64>,
832) -> (Vec<[f32; 4]>, Vec<i64>) {
833 let skip: std::collections::HashSet<usize> = merge
834 .values()
835 .filter(|&&v| v >= 0)
836 .map(|&v| v as usize)
837 .collect();
838 let mut out = Vec::new();
839 let mut out_classes = Vec::new();
840 for (i, &b) in boxes.iter().enumerate() {
841 let class = classes.get(i).copied().unwrap_or(2);
842 if let Some(&j) = merge.get(&i) {
843 let partner = if j < 0 { boxes.len() - 1 } else { j as usize };
844 out.push(mergebboxes(b, boxes[partner.min(boxes.len() - 1)]));
845 out_classes.push(class);
846 } else if !skip.contains(&i) {
847 out.push(b);
848 out_classes.push(class);
849 }
850 }
851 (out, out_classes)
852}
853
854const CELL_TAGS: [i64; 6] = [FCEL, ECEL, XCEL, CHED, RHED, SROW];
855
856fn build_table_cells(otsl: &[i64], boxes: &[[f32; 4]], classes: &[i64]) -> Vec<TableCell> {
862 let mut grid: Vec<Vec<i64>> = vec![Vec::new()];
864 for &t in otsl {
865 if t == NL {
866 grid.push(Vec::new());
867 } else {
868 grid.last_mut().unwrap().push(t);
869 }
870 }
871 let mut cells = Vec::new();
872 let mut cell_id = 0usize;
873 for (r, row) in grid.iter().enumerate() {
874 for (c, &tag) in row.iter().enumerate() {
875 if !CELL_TAGS.contains(&tag) {
876 continue;
877 }
878 let mut colspan = 1;
879 while c + colspan < row.len() && matches!(row[c + colspan], LCEL | XCEL) {
880 colspan += 1;
881 }
882 let mut rowspan = 1;
883 while r + rowspan < grid.len()
884 && grid[r + rowspan]
885 .get(c)
886 .is_some_and(|&t| matches!(t, UCEL | XCEL))
887 {
888 rowspan += 1;
889 }
890 let b = boxes.get(cell_id).copied().unwrap_or([0.0; 4]);
891 let class = classes.get(cell_id).copied().unwrap_or(2);
893 cells.push(TableCell {
894 row: r,
895 col: c,
896 colspan,
897 rowspan,
898 tag,
899 class,
900 cx: b[0],
901 cy: b[1],
902 w: b[2],
903 h: b[3],
904 });
905 cell_id += 1;
906 }
907 }
908 cells
909}
910
911fn argmax(v: &[f32]) -> usize {
912 v.iter()
913 .enumerate()
914 .max_by(|a, b| a.1.total_cmp(b.1))
915 .map(|(i, _)| i)
916 .unwrap_or(0)
917}