Skip to content

fix: handle empty batches in detection and end-to-end predictors - #2138

Open
linhongyu510 wants to merge 1 commit into
mindee:mainfrom
linhongyu510:fix/empty-batch-predictors
Open

linhongyu510 wants to merge 1 commit into
mindee:mainfrom
linhongyu510:fix/empty-batch-predictors

Conversation

@linhongyu510

Copy link
Copy Markdown

DetectionPredictor, OCRPredictor and KIEPredictor raise on an empty page list instead of returning an empty result. RecognitionPredictor has always short-circuited on empty input (recognition/predictor/pytorch.py:50), and OrientationPredictor gained the same behaviour in #2069 — these three were the remaining gap, so this follows that precedent rather than introducing a new convention.

Filtering a batch down to nothing is ordinary caller code (skip already-processed pages, drop files that failed a check upstream), and today it fails from internals that never mention the empty input.

Reproduction

from doctr.models import ocr_predictor, kie_predictor, detection

ocr_predictor(pretrained=False)([])       # IndexError: list index out of range
kie_predictor(pretrained=False)([])       # IndexError: list index out of range
detection.detection_predictor(pretrained=False)([])  # IndexError

There are three separate failure points on the way down, which is why the fix is not a one-liner in a single helper:

Location Failure
preprocessor/pytorch.py:73 num_batches is correctly 0, then samples[0] is read to pick the tuple/tensor branch → IndexError
utils/geometry.py:124 zip(*(_detach(box) for box in boxes)) over no boxes → ValueError: not enough values to unpack (expected 2, got 0)
models/_utils.py:276 KIE path only: {k: ... for k in x[0]}IndexError

I confirmed the ordering by fixing them one at a time: guarding batch_inputs moved the crash to detach_scores, and guarding that one let the whole call return Document(pages=[]).

Fix

Guard at the three public entry points instead of patching each internal, so batch_inputs, detach_scores and invert_data_structure keep their non-empty precondition and stay simple.

Two details worth flagging:

  • DetectionPredictor returns the shape its own return_maps contract promises: [] normally, ([], []) when return_maps=True.
  • KIEPredictor returns KIEDocument(pages=[]), not Document(pages=[]). KIEDocument subclasses Document, so returning the base class would type-check and pass an isinstance assertion while silently dropping the per-class page shape. The test asserts the exact type for this reason — I verified it catches the degradation by deliberately returning Document there and watching the test go red.

Tests

Added test_predictors_on_empty_batch, which covers detection (both return_maps values), recognition (already-correct, asserted so the three stay consistent), and both end-to-end predictors.

The test is load-bearing: reverting the guards fails it at preprocessor/pytorch.py:73.

tests/pytorch/test_models_zoo_pt.py::test_predictors_on_empty_batch  1 passed
tests/pytorch/test_models_zoo_pt.py                                 14 passed, 6 errors
tests/common/                                                        523 passed, 1 failed, 7 errors
ruff check . / ruff format --check                                   clean
mypy doctr/                                                          clean (171 source files)

The errors and the one failure are pre-existing on this machine and unrelated: the errors are requests.exceptions.ConnectionError from tests that download weights or fixtures, and tests/common/test_io.py::test_read_html fails with OSError: cannot load library (missing WeasyPrint system libs). I verified the test_read_html failure reproduces on a clean checkout via git stash.

AI assistance was used for this change; I reviewed every changed line and ran the commands above locally.

Passing an empty page list crashed three levels deep instead of returning an
empty result. `RecognitionPredictor` has always short-circuited on an empty
input, and `OrientationPredictor` gained the same behaviour in mindee#2069, but the
detection and end-to-end predictors did not, so filtering a batch down to
nothing raised from internals that never mention the empty input:

- `PreProcessor.batch_inputs` computes `num_batches == 0` correctly, then
  reads `samples[0]` to pick the tuple/tensor branch -> `IndexError`
- `detach_scores` calls `zip(*(...))` over no boxes -> `ValueError: not enough
  values to unpack (expected 2, got 0)`
- on the KIE path `invert_data_structure` reads `x[0]` -> `IndexError`

Guard at the three public entry points rather than patching each internal, so
the existing helpers keep their non-empty precondition. `DetectionPredictor`
returns the shape its `return_maps` contract promises, and the end-to-end
predictors return an empty document of their own type -- `Document` for
`OCRPredictor`, `KIEDocument` for `KIEPredictor`, whose per-class page shape
would otherwise be lost to the base class.

Verified `ruff check`, `ruff format --check` and `mypy doctr/` clean; reverting
the guards turns the new test red at `preprocessor/pytorch.py:73`.

Co-authored-by: Claude <noreply@anthropic.com>
@linhongyu510
linhongyu510 force-pushed the fix/empty-batch-predictors branch from 7d8556e to a11910c Compare September 5, 2026 08:42
@codecov

codecov Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.24%. Comparing base (b50ddd1) to head (a11910c).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2138   +/-   ##
=======================================
  Coverage   97.24%   97.24%           
=======================================
  Files         169      169           
  Lines       10040    10046    +6     
=======================================
+ Hits         9763     9769    +6     
  Misses        277      277           
Flag Coverage Δ
unittests 97.24% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@felixdittrich92 felixdittrich92 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @linhongyu510 👋,

Thanks for the PR. Left one comment.

def test_predictors_on_empty_batch(mock_vocab):
"""An empty page list must yield an empty Document instead of raising.

Filtering a batch down to nothing is ordinary caller code, and

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the docstring afterwards fine to merge 👍

@felixdittrich92 felixdittrich92 added this to the 1.2.0 milestone Sep 15, 2026
@felixdittrich92 felixdittrich92 self-assigned this Sep 15, 2026
@felixdittrich92 felixdittrich92 added type: bug Something isn't working module: models Related to doctr.models ext: tests Related to tests folder labels Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext: tests Related to tests folder module: models Related to doctr.models type: bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants