{"record":{"id":"5a73bd1bdf7f866a","repo":"docling-project/docling","slug":"page-image-dimensions-im-size-do-not-match-page","errorCode":null,"errorMessage":"Page image dimensions {im.size} do not match page geometry ({page_size.width}x{page_size.height}).","messagePattern":"Page image dimensions (.+?) do not match page geometry \\((.+?)x(.+?)\\)\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"docling/backend/mets_gbs_backend.py","lineNumber":156,"sourceCode":"        images = dpage.bitmap_resources\n\n        for img in images:\n            cropbox = img.rect.to_bounding_box().to_top_left_origin(\n                self.get_size().height\n            )\n\n            if cropbox.area() > AREA_THRESHOLD:\n                cropbox = cropbox.scaled(scale=scale)\n\n                yield cropbox\n\n    def get_page_image(\n        self, scale: float = 1, cropbox: BoundingBox | None = None\n    ) -> Image.Image:\n        im = self._require_image()\n        page_size = self.get_size()\n        if page_size.width != im.size[0] or page_size.height != im.size[1]:\n            raise RuntimeError(\n                f\"Page image dimensions {im.size} do not match page geometry \"\n                f\"({page_size.width}x{page_size.height}).\"\n            )\n\n        if not cropbox:\n            cropbox = BoundingBox(\n                l=0,\n                r=page_size.width,\n                t=0,\n                b=page_size.height,\n                coord_origin=CoordOrigin.TOPLEFT,\n            )\n\n        image = im.resize(\n            size=(round(page_size.width * scale), round(page_size.height * scale))\n        ).crop(cropbox.scaled(scale=scale).as_tuple())\n        return image\n","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/backend/mets_gbs_backend.py#L138-L174","documentation":"RuntimeError from MetsGbsDocumentBackend.get_page_image(): the backend loads a page image from the METS/GBS tar archive and compares its pixel size against the page geometry declared in the METS OCR XML (page div attributes). If the image file's width/height do not equal the declared page size, the archive is internally inconsistent and coordinate transforms would be wrong, so it refuses to continue.","triggerScenarios":"Converting a METS/GBS (.tar.gz with Google Books scan structure) archive where the ocr_page div's width/height attributes disagree with the actual embedded PNG/JPEG dimensions — e.g. mismatched image/OCR pairs, rescaled images, or a corrupted/hand-edited archive.","commonSituations":"Digitization pipelines that regenerate page images at a different resolution without updating OCR metadata, archives assembled from mixed sources, or truncated image members that PIL still opens with a partial header.","solutions":["Verify the archive is a well-formed METS/GBS export where image dimensions match the OCR page geometry (extract and compare with PIL before conversion).","Re-generate or obtain a consistent archive from the source digitization system.","If you must repair: rewrite the ocr_page div attributes to the true image size, or resample images to the declared size, then reconvert.","Report upstream if a stock Google Books export triggers it — stock exports should always be consistent."],"exampleFix":"# before\nresult = converter.convert(mets_tar_path)  # RuntimeError: dims mismatch\n\n# after (pre-validate the archive)\nimport tarfile, io\nfrom PIL import Image\nwith tarfile.open(mets_tar_path) as t:\n    for m in t.getmembers():\n        if m.name.endswith(('.png', '.jpg')):\n            im = Image.open(t.extractfile(m))\n            assert im.size == declared_size.get(m.name), m.name  # repair before converting\nresult = converter.convert(mets_tar_path)","handlingStrategy":"validation","validationCode":"import tarfile, io, re\nfrom PIL import Image\n\ndef mets_image_dims_match(tar_path: str) -> bool:\n    with tarfile.open(tar_path) as t:\n        ocr = next((m for m in t.getmembers() if m.name.endswith('.html') or 'coordOCR' in m.name), None)\n        img = next((m for m in t.getmembers() if m.name.endswith(('.png', '.jpg', '.jpeg'))), None)\n        if not (ocr and img):\n            return True  # cannot pre-check; let backend decide\n        m = re.search(rb\"bbox\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\", t.extractfile(ocr).read(4096))\n        if not m:\n            return True\n        w, h = int(m.group(3)) - int(m.group(1)), int(m.group(4)) - int(m.group(2))\n        return Image.open(t.extractfile(img)).size == (w, h)","typeGuard":null,"tryCatchPattern":"try:\n    result = converter.convert(mets_path)\nexcept RuntimeError as e:\n    if 'do not match page geometry' in str(e):\n        log.error('inconsistent METS archive %s — image/OCR mismatch', mets_path)\n        quarantine(mets_path)","preventionTips":["Never regenerate page images without updating OCR bbox metadata.","Validate image-vs-OCR dimensions when assembling METS packages.","Keep archives from digitization systems unmodified rather than repacking by hand."],"tags":["mets","gbs","archive","data-integrity","image"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}