roboflow/supervision · error · ValueError

Detection index {detection_idx} is out of bounds for detecti

Error message

Detection index {detection_idx} is out of bounds for detections of length {len(detections)}

What it means

Raised by resolve_color_idx() in supervision.annotators.utils when detection_idx is >= len(detections) while resolving a color for one detection. Annotators iterate detections and index into them per box; an index past the end means the detections object and the per-detection metadata (labels, custom colors) have diverged, which the helper catches before it would raise a confusing NumPy IndexError.

Source

Thrown at src/supervision/annotators/utils.py:44

        - `TRACK`: Colors are determined by the tracking identifier of the object.
    """

    INDEX = "index"
    CLASS = "class"
    TRACK = "track"

    @classmethod
    def list(cls) -> list[str]:
        return list(map(lambda c: c.value, cls))


def resolve_color_idx(
    detections: Detections,
    detection_idx: int,
    color_lookup: ColorLookup | npt.NDArray[np.int_] = ColorLookup.CLASS,
) -> int:
    if detection_idx >= len(detections):
        raise ValueError(
            f"Detection index {detection_idx} "
            f"is out of bounds for detections of length {len(detections)}"
        )

    if isinstance(color_lookup, np.ndarray):
        if len(color_lookup) != len(detections):
            raise ValueError(
                f"Length of color lookup {len(color_lookup)} "
                f"does not match length of detections {len(detections)}"
            )
        return int(color_lookup[detection_idx])
    elif color_lookup == ColorLookup.INDEX:
        return detection_idx
    elif color_lookup == ColorLookup.CLASS:
        if detections.class_id is None:
            raise ValueError(
                "Could not resolve color by class because "
                "Detections do not have class_id. If using an annotator, "

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Recompute labels/colors after every filter: labels = [labels[i] for i in kept_indices].
  2. Ensure len(color_lookup_array) == len(detections) when passing an ndarray.
  3. Guard empty detections: if len(detections) == 0: skip annotate().
  4. In custom loops, iterate enumerate(detections) rather than a separate cached list.

Example fix

// before
detections = detections[keep_mask]  # labels still old length
annotator.annotate(scene, detections, labels=labels)

// after
detections = detections[keep_mask]
labels = [l for l, k in zip(labels, keep_mask) if k]
annotator.annotate(scene, detections, labels=labels)
Defensive patterns

Strategy: validation

Validate before calling

if len(detections) == 0:
    return scene  # nothing to annotate
if isinstance(color_lookup, np.ndarray):
    assert len(color_lookup) == len(detections), (len(color_lookup), len(detections))

Try / catch

try:
    annotator.annotate(scene, detections)
except ValueError as e:
    if 'out of bounds' in str(e):
        raise ValueError('labels/colors desynced from detections after filtering') from e
    raise

Prevention

When it happens

Trigger: Annotating with a custom color_lookup array longer than detections; filtering detections between building labels and calling annotate(); passing detections.empty() to an annotator with a stale nonzero index; manual loops that enumerate a different list than the detections passed in.

Common situations: Code that slices detections (e.g. detections[np.array([0,2])]) but keeps the old labels list; race conditions in streaming pipelines where detections are replaced between construction and annotation; off-by-one in custom annotator subclasses.

Related errors


AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15). Data as JSON: /api/errors/dc7995130665c7be. Report an issue: GitHub.