From 4cd5729eb33d78dcce121d8efae5e600be96f712 Mon Sep 17 00:00:00 2001 From: ExtReMLapin <3909752+ExtReMLapin@users.noreply.github.com> Date: Sun, 11 Oct 2026 17:38:30 +0200 Subject: [PATCH 1/2] perf: reuse the last scaled render for cropped get_image calls Signed-off-by: ExtReMLapin <3909752+ExtReMLapin@users.noreply.github.com> --- docling_parse/pdf_parser.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docling_parse/pdf_parser.py b/docling_parse/pdf_parser.py index e42d71737..ba777ed41 100644 --- a/docling_parse/pdf_parser.py +++ b/docling_parse/pdf_parser.py @@ -1139,6 +1139,9 @@ def __init__( self._pages: Dict[tuple[int, int, int, int, int, bool], SegmentedPdfPage] = {} self._page_decoder: PdfPageDecoder | None = None self._default_image: PILImage.Image | None = None + # Last image rendered at a non-default scale: OCR crops several regions + # of a page at the same scale, and each crop would re-render the page. + self._scaled_image: tuple[float, PILImage.Image] | None = None self.doc_key: str = raw_result.doc_key self.page_number: int = raw_result.page_number + 1 @@ -1370,6 +1373,8 @@ def _get_default_image(self) -> PILImage.Image: return self._default_image def _render_image_at_scale(self, scale: float) -> PILImage.Image: + if self._scaled_image is not None and self._scaled_image[0] == scale: + return self._scaled_image[1] page_decoder = self._require_page_decoder() render_config = self._rendering_config() render_config.scale = scale @@ -1380,7 +1385,8 @@ def _render_image_at_scale(self, scale: float) -> PILImage.Image: raise RuntimeError( f"Rendered image is empty for page {self.page_number} of {self.doc_key}" ) - return self._image_from_bytes(raw_bytes, image_shape) + self._scaled_image = (scale, self._image_from_bytes(raw_bytes, image_shape)) + return self._scaled_image[1] def _render_image_at_canvas_size( self, canvas_size: tuple[int, int] From 06b82cd4c66613af350b6ad2de0f7b380408bad0 Mon Sep 17 00:00:00 2001 From: Christoph Auer Date: Wed, 28 Oct 2026 13:25:07 +0100 Subject: [PATCH 2/2] perf: keep the scaled render only for cropped get_image calls Caching every non-default-scale render keeps one full page image per live page. Docling asks for full-page images at images_scale in preprocessing and at scale 2 for tables, and caches those itself in Page._image_cache, so the parser cache only duplicated them. On a 40-page scanned document with images_scale=2 that added about 330 MiB at peak. Keep the render only when the request has a cropbox, which is the OCR case this cache is for. A full-page request still reuses a cached render at the same scale. Signed-off-by: Christoph Auer --- docling_parse/pdf_parser.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docling_parse/pdf_parser.py b/docling_parse/pdf_parser.py index ba777ed41..1e31cb898 100644 --- a/docling_parse/pdf_parser.py +++ b/docling_parse/pdf_parser.py @@ -1139,8 +1139,10 @@ def __init__( self._pages: Dict[tuple[int, int, int, int, int, bool], SegmentedPdfPage] = {} self._page_decoder: PdfPageDecoder | None = None self._default_image: PILImage.Image | None = None - # Last image rendered at a non-default scale: OCR crops several regions - # of a page at the same scale, and each crop would re-render the page. + # Last image rendered at a non-default scale for a cropped request: OCR + # crops several regions of a page at the same scale, and each crop would + # re-render the page. Full-page requests are not kept: callers such as + # Docling cache those already, and one per live page adds up quickly. self._scaled_image: tuple[float, PILImage.Image] | None = None self.doc_key: str = raw_result.doc_key @@ -1372,7 +1374,7 @@ def _get_default_image(self) -> PILImage.Image: ) return self._default_image - def _render_image_at_scale(self, scale: float) -> PILImage.Image: + def _render_image_at_scale(self, scale: float, cache: bool) -> PILImage.Image: if self._scaled_image is not None and self._scaled_image[0] == scale: return self._scaled_image[1] page_decoder = self._require_page_decoder() @@ -1385,8 +1387,10 @@ def _render_image_at_scale(self, scale: float) -> PILImage.Image: raise RuntimeError( f"Rendered image is empty for page {self.page_number} of {self.doc_key}" ) - self._scaled_image = (scale, self._image_from_bytes(raw_bytes, image_shape)) - return self._scaled_image[1] + image = self._image_from_bytes(raw_bytes, image_shape) + if cache: + self._scaled_image = (scale, image) + return image def _render_image_at_canvas_size( self, canvas_size: tuple[int, int] @@ -1446,7 +1450,7 @@ def get_image( ): image = self._get_default_image() else: - image = self._render_image_at_scale(scale) + image = self._render_image_at_scale(scale, cache=cropbox is not None) else: assert canvas_size is not None if canvas_size[0] <= 0 or canvas_size[1] <= 0: