{"record":{"id":"74571f1d12e7f458","repo":"docling-project/docling","slug":"invalid-tesseract-document-orientation-orientatio","errorCode":null,"errorMessage":"invalid tesseract document orientation {orientation}, expected orientation: {sorted(CLIPPED_ORIENTATIONS)}","messagePattern":"invalid tesseract document orientation (.+?), expected orientation: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"docling/utils/ocr_utils.py","lineNumber":29,"sourceCode":"    if script == \"Katakana\" or script == \"Hiragana\":\n        script = \"Japanese\"\n    elif script == \"Han\":\n        script = \"HanS\"\n    elif script == \"Korean\":\n        script = \"Hangul\"\n    return script\n\n\ndef parse_tesseract_orientation(orientation: str) -> int:\n    # Tesseract orientation is [0, 90, 180, 270] clockwise, bounding rectangle angles\n    # are [0, 360[ counterclockwise\n    parsed = int(orientation)\n    if parsed not in CLIPPED_ORIENTATIONS:\n        msg = (\n            f\"invalid tesseract document orientation {orientation}, \"\n            f\"expected orientation: {sorted(CLIPPED_ORIENTATIONS)}\"\n        )\n        raise ValueError(msg)\n    parsed = -parsed\n    parsed %= 360\n    return parsed\n\n\ndef tesseract_box_to_bounding_rectangle(\n    bbox: BoundingBox,\n    *,\n    original_offset: Optional[BoundingBox] = None,\n    scale: float,\n    orientation: int,\n    im_size: Tuple[int, int],\n) -> BoundingRectangle:\n    # box is in the top, left, height, width format, top left coordinates\n    rect = rotate_bounding_box(bbox, angle=orientation, im_size=im_size)\n    rect = BoundingRectangle(\n        r_x0=rect.r_x0 / scale,\n        r_y0=rect.r_y0 / scale,","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/utils/ocr_utils.py#L11-L47","documentation":"Raised by parse_tesseract_orientation() in docling/utils/ocr_utils.py when the orientation string reported for a page is not one of the four Tesseract orientation classes (0, 90, 180, 270 clockwise). The function converts Tesseract's clockwise bounding-rectangle angle into Docling's counterclockwise [0,360[ convention, so it only accepts those four discrete values. Any other numeric value is rejected before conversion.","triggerScenarios":"Calling parse_tesseract_orientation(orientation) with a string that parses to an int but is not in {0, 90, 180, 270}, e.g. \"45\", \"360\", \"-90\", or a value coming from a custom/patched Tesseract orientation output or an OCR engine adapter that emits arbitrary angles.","commonSituations":"Feeding orientation values from a non-Tesseract OCR pipeline or precomputed metadata into Docling's Tesseract OCR path; upgrading or patching Tesseract so its orientation output format changes; manually computing orientation angles (e.g. via image rotation detection) and passing fractional or non-orthogonal angles.","solutions":["Snap the detected angle to the nearest multiple of 90 and normalize with angle % 360 before calling the function (e.g. round(angle / 90) * 90 % 360).","Verify the value originates from Tesseract's orientation script output and is not corrupted by upstream parsing (check for leading/trailing characters or sign flips).","If you genuinely need arbitrary-angle deskew, handle rotation upstream of OCR rather than through this function, which only supports document-level 90-degree orientations."],"exampleFix":"# before\nangle = detect_rotation(image)  # e.g. 45.0\nparsed = parse_tesseract_orientation(str(angle))  # ValueError\n\n# after\nangle = detect_rotation(image)\nsnapped = round(angle / 90) * 90 % 360  # nearest of 0/90/180/270\nparsed = parse_tesseract_orientation(str(int(snapped)))","handlingStrategy":"validation","validationCode":"CLIPPED = {0, 90, 180, 270}\nangle = round(detected_angle / 90) * 90 % 360\nif int(angle) not in CLIPPED:\n    raise ValueError(f\"unsupported angle {detected_angle}; snapping failed\")\nparsed = parse_tesseract_orientation(str(int(angle)))","typeGuard":"def is_valid_tesseract_orientation(value: str) -> bool:\n    try:\n        return int(value) in {0, 90, 180, 270}\n    except ValueError:\n        return False","tryCatchPattern":"try:\n    parsed = parse_tesseract_orientation(orientation_str)\nexcept ValueError as exc:\n    logger.warning(\"skipping page with bad orientation %r: %s\", orientation_str, exc)\n    parsed = 0  # or skip the page","preventionTips":["Snap all detected angles to round(angle / 90) * 90 % 360 before passing them in.","Only feed orientation strings produced by Tesseract's own orientation script output.","In batch pipelines, wrap the call per page and continue on failure instead of aborting the document."],"tags":["ocr","tesseract","orientation","validation"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}