keras-team/keras · error · ValueError

Input arrays must be multi-channel 2D images.

Error message

Input arrays must be multi-channel 2D images.

What it means

apply_affine_transform works only on 3D arrays (a 2D image plus a channel dimension). If x.ndim != 3 it raises this ValueError right after the axis checks. Grayscale images stored as 2D arrays and batched 4D tensors are both rejected.

Source

Thrown at keras/src/legacy/preprocessing/image.py:1808

    """
    # Input sanity checks:
    # 1. x must 2D image with one or more channels (i.e., a 3D tensor)
    # 2. channels must be either first or last dimension
    if np.unique([row_axis, col_axis, channel_axis]).size != 3:
        raise ValueError(
            "'row_axis', 'col_axis', and 'channel_axis' must be distinct"
        )

    # shall we support negative indices?
    valid_indices = set([0, 1, 2])
    actual_indices = set([row_axis, col_axis, channel_axis])
    if actual_indices != valid_indices:
        raise ValueError(
            f"Invalid axis' indices: {actual_indices - valid_indices}"
        )

    if x.ndim != 3:
        raise ValueError("Input arrays must be multi-channel 2D images.")
    if channel_axis not in [0, 2]:
        raise ValueError(
            "Channels are allowed and the first and last dimensions."
        )

    transform_matrix = None
    if theta != 0:
        theta = np.deg2rad(theta)
        rotation_matrix = np.array(
            [
                [np.cos(theta), -np.sin(theta), 0],
                [np.sin(theta), np.cos(theta), 0],
                [0, 0, 1],
            ]
        )
        transform_matrix = rotation_matrix

    if tx != 0 or ty != 0:

View on GitHub (pinned to 7a34a03db6)

Solutions

  1. Expand grayscale images to (H, W, 1) with np.expand_dims(img, -1)
  2. For batches, loop: out[i] = apply_affine_transform(x[i], ...)
  3. Prefer tf.keras.layers.RandomRotation and other preprocessing layers for batched data

Example fix

# before
random_rotation(gray_img, 20)  # shape (H, W)
# after
random_rotation(np.expand_dims(gray_img, -1), 20)  # (H, W, 1)
Defensive patterns

Strategy: validation

Validate before calling

assert img.ndim == 3, f'expected 3D image, got {img.ndim}D'
if img.ndim == 2:
    img = img[..., None]

Type guard

def is_single_image(x):
    return getattr(x, 'ndim', None) == 3

Prevention

When it happens

Trigger: Passing a 2D grayscale image (H, W) with no channel axis; passing a 4D batch (N, H, W, C) directly to apply_affine_transform or random_rotation/random_shift/random_shear/random_zoom.

Common situations: Loading grayscale images with PIL/imageio that yield shape (H, W); forgetting to slice a batch tensor before augmenting one image at a time.

Related errors


AI-assisted analysis of keras-team/keras@7a34a03db6 (2026-08-25). Data as JSON: /api/errors/46d67839ec761ade. Report an issue: GitHub.