roboflow/supervision · error · ValueError

Border sizes must be non-negative

Error message

Border sizes must be non-negative

What it means

`cv2.copyMakeBorder` fallback at src/supervision/_cv2/_image.py:47 requires all four border sizes (top, bottom, left, right) to be >= 0. Negative borders would mean cropping, which the function's output-shape construction `(H + top + bottom, W + left + right, ...)` cannot represent, so they are rejected before array allocation.

Source

Thrown at src/supervision/_cv2/_image.py:47

    else:
        raise ValueError(f"Unsupported flip code: {flip_code}")
    return np.ascontiguousarray(np.flip(image, axis=axes))


def _copy_make_border(
    image: npt.NDArray[Any],
    top: int,
    bottom: int,
    left: int,
    right: int,
    border_type: int,
    value: int | float | Sequence[int | float] = 0,
) -> npt.NDArray[Any]:
    """Add a constant border around an image."""
    if border_type != _BORDER_CONSTANT:
        raise ValueError("Only BORDER_CONSTANT is supported by the fallback")
    if min(top, bottom, left, right) < 0:
        raise ValueError("Border sizes must be non-negative")

    height, width = image.shape[:2]
    shape = (height + top + bottom, width + left + right, *image.shape[2:])

    # OpenCV's Scalar(v) fills only channel 0 and zero-pads the rest for
    # multichannel images — a bare scalar is treated the same as a
    # length-1 sequence, not broadcast to every channel.
    sequence_value = value if isinstance(value, Sequence) else (value,)
    values = np.asarray(sequence_value, dtype=image.dtype).reshape(-1)
    if image.ndim == 2:
        fill_value: Any = values[0] if values.size else 0
    else:
        fill = np.zeros(image.shape[2], dtype=image.dtype)
        fill[: min(values.size, image.shape[2])] = values[: image.shape[2]]
        fill_value = fill.reshape((1, 1, -1))

    result = np.full(shape, fill_value, dtype=image.dtype)
    result[top : top + height, left : left + width] = image

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Clamp computed pads to zero: `top = max(0, (target_h - h) // 2)`
  2. If negative pad means crop, do the crop explicitly instead: `img[-top:h+bottom, -left:w+right]`
  3. Validate pad configuration at load time and reject negative values early

Example fix

// before
pad_top = (target_h - h) // 2  # negative when h > target_h
out = cv2.copyMakeBorder(img, pad_top, pad_top, 0, 0, cv2.BORDER_CONSTANT)

// after
pad_top = max(0, (target_h - h) // 2)
out = cv2.copyMakeBorder(img, pad_top, pad_top, 0, 0, cv2.BORDER_CONSTANT)
Defensive patterns

Strategy: validation

Validate before calling

def clamp_pads(top: int, bottom: int, left: int, right: int) -> tuple[int, int, int, int]:
    """copyMakeBorder requires non-negative border sizes."""
    pads = tuple(max(0, int(p)) for p in (top, bottom, left, right))
    if min(top, bottom, left, right) < 0:
        # negative pad usually means the image already exceeds the target — crop instead
        pass
    return pads  # type: ignore[return-value]

Type guard

def is_non_negative_pad(*sizes: int) -> bool:
    """All four border sizes must be >= 0."""
    return all(isinstance(s, int) and s >= 0 for s in sizes)

Prevention

When it happens

Trigger: Passing a negative border width, usually from arithmetic on computed pad sizes: e.g. `pad = (target - size) // 2` going negative when the image is already larger than the target, then forwarded to copyMakeBorder.

Common situations: Letterboxing/resizing helpers that compute symmetric pads without clamping; config-driven pad values where a minus sign typo survives into runtime.

Related errors


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