#11175·cvat

PDF: an explicit stop_frame drops the last frame, and stop_frame=0 fails with "No image found"

Author: zaher-mCreated Sep 11, 2026Updated Sep 14, 2026

Actions before raising this issue

  • I searched the existing issues and did not find anything similar.
  • I read/searched the docs

Steps to Reproduce

PdfReader can be exercised directly from the released image, so this needs no running CVAT stack:

bash
docker run --rm -i --entrypoint python3 cvat/server:v2.75.0 - <<'EOF'
import os, django, tempfile
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cvat.settings.testing")
django.setup()
import numpy as np
from pathlib import Path
from PIL import Image
from cvat.apps.engine.media_extractors import PdfReader

src = Path(tempfile.mkdtemp()) / "p.pdf"
imgs = [Image.fromarray(np.ones((50, 100, 3), dtype=np.uint8)) for _ in range(10)]
imgs[0].save(src, "pdf", save_all=True, resolution=200, append_images=imgs[1:])

for stop, expected in [(None, 10), (4, 5), (1, 2), (0, 1)]:
    work = Path(tempfile.mkdtemp()) / "p.pdf"
    work.write_bytes(src.read_bytes())
    try:
        got = len(list(PdfReader([work], stop=stop).frame_range))
    except Exception as e:
        got = f"{type(e).__name__}: {e}"
    print(f"stop_frame={str(stop):>5}   expected={expected:>2}   got={got}")
EOF

Output on a 10-page PDF:

stop_frame= None   expected=10   got=10
stop_frame=    4   expected= 5   got=4
stop_frame=    1   expected= 2   got=1
stop_frame=    0   expected= 1   got=Exception: No image found

Through the REST API this corresponds to POST /api/tasks/{id}/data with a PDF and an explicit stop_frame. To be clear about what I actually ran: I verified the extractor directly as above, and traced the request path by reading views.py/task.py rather than executing it, so the API-level symptom is inferred rather than observed.

Expected Behavior

stop_frame is documented as "Last frame index" (serializers.py#L2716), and ImageListReader treats it as a 0-based inclusive index (media_extractors.py#L308-L311).

So stop_frame=4 should yield 5 frames (0..4), and stop_frame=0 should yield 1 frame.

Instead every explicit value yields one frame fewer, with no error raised, and stop_frame=0 aborts with No image found, which points the user at their file rather than at the frame range.

Possible Solution

PdfReader passes stop straight through to pdf2image's last_page (media_extractors.py#L463-L470), but the two use different conventions:

  • CVAT's stop: 0-based inclusive frame index
  • pdf2image's last_page: 1-based inclusive page number

pdf2image 1.14.0 additionally returns [] when first_page > last_page, so last_page=0 converts nothing and ImageListReader then raises No image found (media_extractors.py#L306).

Converting at the call site should cover both, keeping 0 distinct from "unset" the way views.py#L1449-L1452 already does for the omitted case.

Context

Creating a task from a PDF while restricting the frame range.

The silent case is the one that concerns me more than the exception: the task is created successfully and simply contains one frame fewer than requested, with nothing in the API response or the UI indicating that anything was truncated.

Environment

- CVAT v2.75.0 (2a8340fb10ac85f45fd0eef18aaf3f814178cb04);
  same code on develop @ 1d0c39576
- Image: official cvat/server:v2.75.0
- pdf2image 1.14.0 (as pinned in cvat/requirements/base.in)
- Docker 29.x, Ubuntu, x86_64

I'd be glad to put together a fix and a regression test if you agree this is worth changing.