docling-project / docling-project/docling

OCR-recognized text is dropped from output for 180°-rotated images, even with force_full_page_ocr

Open
#3,839 11 comments 1 reaction 0 assignees View on GitHub
triage/close-fixed
Dominant language
Python
Stars
66.4k
Forks
4.8k
Avg merge
3d 4h
Merged PRs (30d)
95

Description

### Bug

For a page that is rotated 180° (upside-down scan/photo), OCR correctly detects the
orientation and correctly recognizes the text internally, but the final converted document
(`md_content` via docling-serve, or `export_to_markdown()` via the library) is **completely
empty** — the recognized text never makes it into the output.

This reproduces with a plain standalone raster image (no PDF, no rotation metadata involved —
so it's a different code path than #2038/#1822, which are about PDF `/Rotate` metadata), and
persists with `force_full_page_ocr=True`, ruling out the OCR-region/bitmap-coverage selection
logic as the cause.

I instrumented `TesseractOcrCliModel.__call__` directly (`tesseract_ocr_cli_model.py`) to trace
where the content disappears:

```
[DEBUG] entered enabled=True
[DEBUG] OSD SUCCESS doc_orientation=180
[DEBUG] df_result shape=(12, 12) # 12 words correctly recognized, e.g. "WARRANTY", "CODE:", "ZQXPHOENIX-7", "742", "This", "document", "certifies", ... — all correct, 90-96% confidence
[DEBUG] all_ocr_cells_len=12 textline_cells_len=12 has_lines=True # <- right after post_process_cells()
```

So at the point immediately after `post_process_cells()`, the page object has 12 valid,
correctly-recognized `TextCell`s with `has_lines=True`. But the final serialized document is
empty. The drop happens somewhere after the OCR stage — layout/reading-order assembly or
markdown export — not in OCR, orientation detection, or cell filtering.

This reproduces identically whether `ocr_preset` is `tesseract` (`TesseractOcrCliModel`,
confirmed via the trace above) or the default `auto`/`rapidocr` (`RapidOcrModel` — same empty
final result, though I didn't instrument that backend directly since the `tesseract` trace
already pinpoints the drop as being *after* the OCR-model stage, which should be
backend-independent).

### Related issues
This looks like the same underlying "orientation handling in the assembly pipeline" gap as:
- #1822 (rotated content silently skipped, works if re-exported via LibreOffice — still open)
- #2038 (rotation metadata not handled by `get_bitmap_rects`; closed as known/unresolved,
points at #683 and #1822)
- #639 / #1167 (added OCR-level OSD auto-rotate for tesseract backends — this is the code I
traced above; it correctly detects and corrects orientation *for OCR recognition*, but
doesn't fix the document-assembly stage downstream of it)

Given #1822 is closed by nobody and #2038 says "a general solution is still in progress," this
seems to be a known gap, but I wanted to file the specific trace since it pinpoints exactly
which stage still drops the content even after the #1167 OCR-level fix.

### Steps to reproduce

```python
from PIL import Image, ImageDraw, ImageFont
from docling.document_converter import DocumentConverter, ImageFormatOption
from docling.datamodel.pipeline_options import PdfPipelineOptions, TesseractCliOcrOptions
from docling.datamodel.base_models import InputFormat

# Build a simple upright test image, then rotate it 180°
img = Image.new("RGB", (1700, 2200), "white")
d = ImageDraw.Draw(img)
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 40)
d.text((100, 100), "WARRANTY CODE: ZQXPHOENIX-7742", fill="black", font=font)
d.text((100, 180), "This document certifies the north garage roof warranty.", fill="black", font=font)
img.rotate(180).save("/tmp/upsidedown_scan.png")

pipeline_options = PdfPipelineOptions()
pipeline_options.do_ocr = True
pipeline_options.ocr_options = TesseractCliOcrOptions(force_full_page_ocr=True)

converter = DocumentConverter(
format_options={InputFormat.IMAGE: ImageFormatOption(pipeline_options=pipeline_options)},
)
result = converter.convert("/tmp/upsidedown_scan.png")
print(result.status) # ConversionStatus.SUCCESS
print(repr(result.document.export_to_markdown())) # '' -- empty, despite OCR finding the text
```

Same result via `docling-serve`'s REST API (`POST /v1/convert/file` with
`ocr_preset=tesseract` and/or `force_full_page_ocr=true`) — `document.md_content` comes back
`""`.

### Docling version
`2.96.1` (`docling-serve:latest`, freshly pulled 2026-07-21 — confirmed this is not fixed in
the current published release)

### Python version
3.12

Contributor guide

Open the contributing guide

Research direction

Start with the supplied standalone-image reproduction and trace the conversion from TesseractOcrCliModel.__call__ in tesseract_ocr_cli_model.py through post_process_cells() to export_to_markdown(). Compare the populated TextCells with the layout or reading-order assembly output; done means the recognized text appears in md_content and export_to_markdown() for the 180° image.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.