{"record":{"id":"94131118714fb609","repo":"docling-project/docling","slug":"do-prediction-on-image-to-table-duplicate-cell-i","errorCode":null,"errorMessage":"_do_prediction_on_image_to_table: duplicate cell indices detected ({len(cell_ids) - len(set(cell_ids))} duplicates). All TextCell.index values must be unique; ensure callers assign a distinct index to each cell (default index=-1 causes this).","messagePattern":"_do_prediction_on_image_to_table: duplicate cell indices detected \\((.+?) duplicates\\)\\. All TextCell\\.index values must be unique; ensure callers assign a distinct index to each cell \\(default index=-1 causes this\\)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"docling/models/stages/table_structure/table_structure_model.py","lineNumber":344,"sourceCode":"        scale = img_width / bbox_width if bbox_width > 0 else self.scale\n\n        # The table box spans the entire cropped image\n        tbl_box = [0.0, 0.0, float(img_width), float(img_height)]\n\n        # Sanity-check: every non-empty cell must have a unique index so that\n        # the predictor's matching dict (keyed by cell id) works correctly.\n        # The most common mistake is leaving all indices at the default -1.\n        non_empty_cells = [c for c in table_cluster.cells if len(c.text.strip()) > 0]\n        cell_ids = [c.index for c in non_empty_cells]\n        if len(cell_ids) != len(set(cell_ids)):\n            msg = (\n                f\"_do_prediction_on_image_to_table: duplicate cell indices detected \"\n                f\"({len(cell_ids) - len(set(cell_ids))} duplicates). \"\n                f\"All TextCell.index values must be unique; ensure callers assign \"\n                f\"a distinct index to each cell (default index=-1 causes this).\"\n            )\n            _log.error(msg)\n            raise ValueError(msg)\n\n        # Translate cell coordinates from page space to image-local space\n        tokens = []\n        for c in table_cluster.cells:\n            if len(c.text.strip()) > 0:\n                new_cell = copy.deepcopy(c)\n                cell_bbox = new_cell.rect.to_bounding_box()\n                local_bbox = BoundingBox(\n                    l=(cell_bbox.l - table_cluster.bbox.l) * scale,\n                    t=(cell_bbox.t - table_cluster.bbox.t) * scale,\n                    r=(cell_bbox.r - table_cluster.bbox.l) * scale,\n                    b=(cell_bbox.b - table_cluster.bbox.t) * scale,\n                    coord_origin=cell_bbox.coord_origin,\n                )\n                new_cell.rect = BoundingRectangle.from_bounding_box(local_bbox)\n                tokens.append(\n                    {\n                        \"id\": new_cell.index,","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/models/stages/table_structure/table_structure_model.py#L326-L362","documentation":"The image-to-table structure predictor matches detected table cells to OCR TextCells via each cell's index. If two non-empty cells share the same index, the matching dict silently loses cells, so Docling validates uniqueness and raises this ValueError. The default TextCell index is -1, so forgetting to assign indices is the canonical cause (the message calls this out).","triggerScenarios":"Building a TableCluster/TextCell list manually or from a custom OCR adapter where every cell keeps index=-1, then running the table structure model's image-to-table prediction; duplicates count is reported explicitly.","commonSituations":"Custom OCR engines or preprocessing code that constructs TextCell objects without setting index; copy-deepmodify flows that clone cells; adapters ported from older versions that relied on list position.","solutions":["Assign a unique index to every TextCell at creation, e.g. enumerate(cells) and set cell.index = i.","If integrating a custom OCR engine, map each detected text box to its position in the detection output order.","As an immediate diagnostic, assert len({c.index for c in cells if c.text.strip()}) == count before invoking the model."],"exampleFix":"# before\ncells = [TextCell(index=-1, text=t, rect=r) for t, r in ocr_results]\n\n# after\ncells = [TextCell(index=i, text=t, rect=r) for i, (t, r) in enumerate(ocr_results)]","handlingStrategy":"validation","validationCode":"non_empty = [c for c in table_cluster.cells if c.text.strip()]\nids = [c.index for c in non_empty]\nassert len(ids) == len(set(ids)), (\n    f\"duplicate TextCell indices: {len(ids) - len(set(ids))}; assign unique cell.index\"\n)","typeGuard":"def has_unique_cell_indices(cells: list) -> bool:\n    ids = [c.index for c in cells if c.text.strip()]\n    return len(ids) == len(set(ids))","tryCatchPattern":"try:\n    table = model._do_prediction_on_image_to_table(scale, table_cluster, ...)\nexcept ValueError as e:\n    if \"duplicate cell indices\" in str(e):\n        for i, c in enumerate(table_cluster.cells):\n            c.index = i  # repair and retry once\n        table = model._do_prediction_on_image_to_table(scale, table_cluster, ...)\n    else:\n        raise","preventionTips":["Always assign cell.index = i from enumerate() when constructing TextCell lists.","In OCR adapters, propagate the detection-order index into TextCell.index.","Add an assertion for index uniqueness in tests of custom table pipelines."],"tags":["table-structure","ocr","validation","data-integrity"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}