{"record":{"id":"ffe5c7648431db8e","repo":"PaddlePaddle/PaddleOCR","slug":"degenerate-text-crop-zero-width-height","errorCode":null,"errorMessage":"degenerate text crop (zero width/height)","messagePattern":"degenerate text crop \\(zero width/height\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"deploy/ios_demo/scripts/build_onnx_calib_npy.py","lineNumber":143,"sourceCode":"    else:\n        index_b, index_c = 3, 2\n    box = [pts[index_a], pts[index_b], pts[index_c], pts[index_d]]\n    pts4 = [np.array(p, dtype=np.float32) for p in box]\n    assert len(pts4) == 4, \"shape of points must be 4*2\"\n    img_crop_width = int(\n        max(\n            np.linalg.norm(pts4[0] - pts4[1]),\n            np.linalg.norm(pts4[2] - pts4[3]),\n        )\n    )\n    img_crop_height = int(\n        max(\n            np.linalg.norm(pts4[0] - pts4[3]),\n            np.linalg.norm(pts4[1] - pts4[2]),\n        )\n    )\n    if img_crop_width < 1 or img_crop_height < 1:\n        raise ValueError(\"degenerate text crop (zero width/height)\")\n    pts_std = np.float32(\n        [\n            [0, 0],\n            [img_crop_width, 0],\n            [img_crop_width, img_crop_height],\n            [0, img_crop_height],\n        ]\n    )\n    pstack = np.stack(pts4, axis=0)\n    m = cv2.getPerspectiveTransform(pstack, pts_std)\n    dst = cv2.warpPerspective(\n        img,\n        m,\n        (img_crop_width, img_crop_height),\n        borderMode=cv2.BORDER_REPLICATE,\n        flags=cv2.INTER_CUBIC,\n    )\n    dh, dw = dst.shape[0:2]","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/deploy/ios_demo/scripts/build_onnx_calib_npy.py#L125-L161","documentation":"Raised by the calibration-sample builder for the iOS ONNX demo when a perspective-crop of a detected text quadrilateral degenerates: the computed crop width or height (max of opposing edge lengths via np.linalg.norm) rounds down below 1 pixel. This means the detected box is collapsed, collinear, or so tiny that a valid crop rectangle cannot be formed.","triggerScenarios":"A detection quad whose opposing edges have length < 1.0 — e.g. all four points nearly identical, points collinear (zero height), or sub-pixel boxes produced by a downscaled/quantized detector during ONNX calibration-set generation.","commonSituations":"Running build_onnx_calib_npy.py over annotation files that contain degenerate or corrupted boxes; calibration images resized so small that legitimate boxes shrink below 1px; hand-edited label files with duplicated corner coordinates.","solutions":["Sanitize the input annotations: drop quads whose width or height norms are < 1px before running the script.","Skip the degenerate crop instead of aborting: wrap the check in a filter and continue with remaining samples.","If boxes should be valid, inspect the offending quad coordinates (print pts4) and fix the upstream detector/labels."],"exampleFix":"# before\nif img_crop_width < 1 or img_crop_height < 1:\n    raise ValueError(\"degenerate text crop (zero width/height)\")\n\n# after\nif img_crop_width < 1 or img_crop_height < 1:\n    print(f\"skip degenerate crop: {pts4.tolist()}\")\n    continue","handlingStrategy":"validation","validationCode":"import numpy as np\n\ndef crop_is_valid(pts4: np.ndarray, min_px: float = 1.0) -> bool:\n    w = max(np.linalg.norm(pts4[0] - pts4[1]), np.linalg.norm(pts4[2] - pts4[3]))\n    h = max(np.linalg.norm(pts4[0] - pts4[3]), np.linalg.norm(pts4[1] - pts4[2]))\n    return w >= min_px and h >= min_px\n\n# filter before cropping\nsamples = [s for s in samples if crop_is_valid(np.asarray(s[\"polygon\"], np.float32))]","typeGuard":"def is_valid_quad(pts) -> bool:\n    import numpy as np\n    p = np.asarray(pts, dtype=np.float32)\n    return p.shape == (4, 2) and np.all(np.isfinite(p)) and len(np.unique(p, axis=0)) >= 3","tryCatchPattern":"try:\n    crop = make_crop(img, pts4)\nexcept ValueError as e:\n    if \"degenerate\" in str(e):\n        continue  # skip this sample, keep processing the calibration set\n    raise","preventionTips":["Validate annotation quads (4 unique points, non-collinear, >=1px edges) before batch runs.","Skip-and-log degenerate boxes instead of aborting long calibration jobs.","Re-check annotations after regenerating labels with a new detector."],"tags":["paddleocr","ios","onnx","calibration","geometry"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}