Comfy-Org/ComfyUI · error · ValueError

Expected 4D image tensor, got shape {tuple(images.shape)}

Error message

Expected 4D image tensor, got shape {tuple(images.shape)}

What it means

_ensure_image_list is the shared normalizer for the dataset processing node family: given a single torch tensor it must be 4D ([B, H, W, C]) so it can be sliced per-batch-item into [1, H, W, C] entries. A 3D tensor (single image without batch dim) or 5D tensor fails here with its full shape printed.

Source

Thrown at comfy_extras/nodes_dataset.py:654

        if has_process and has_group:
            raise ValueError(
                f"{cls.__name__}: Cannot override both _process and _group_process. "
                "Override only one, or set is_group_process explicitly."
            )
        if not has_process and not has_group:
            raise ValueError(
                f"{cls.__name__}: Must override either _process or _group_process"
            )

        return has_group

    @classmethod
    def _ensure_image_list(cls, images):
        """Normalize to a flat list of [1, H, W, C] tensors."""
        if isinstance(images, torch.Tensor):
            if images.ndim != 4:
                raise ValueError(f"Expected 4D image tensor, got shape {tuple(images.shape)}")
            return [images[i:i+1] for i in range(images.shape[0])]

        flat = []
        for item in images:
            if not isinstance(item, torch.Tensor) or item.ndim != 4:
                raise ValueError(f"Expected 4D image tensor, got {type(item).__name__} shape {getattr(item, 'shape', None)}")
            flat.extend([item[i:i+1] for i in range(item.shape[0])])
        return flat

    @classmethod
    def define_schema(cls):
        if cls.node_id is None:
            raise NotImplementedError(f"{cls.__name__} must set node_id class variable")

        is_group = cls._detect_processing_mode()

        # Auto-detect is_output_list if not explicitly set
        # Single processing: False (backend collects results into list)

View on GitHub (pinned to 1c6d8d45b3)

Solutions

  1. Add the batch dimension: images = images.unsqueeze(0) for a single image.
  2. Remove extra dimensions for 5D input: select a frame or reshape to [B, H, W, C].
  3. Pass the whole 4D batch straight through instead of pre-slicing it.

Example fix

# before
images = images[0]           # shape (H, W, C)
# after
images = images[0].unsqueeze(0)  # shape (1, H, W, C)
Defensive patterns

Strategy: type-guard

Validate before calling

import torch
def as_batch(t):
    if t.ndim == 3:
        t = t.unsqueeze(0)
    if t.ndim != 4:
        raise ValueError(f"need 4D, got {tuple(t.shape)}")
    return t

Type guard

def is_4d_image_tensor(t) -> bool:
    return isinstance(t, torch.Tensor) and t.ndim == 4

Prevention

When it happens

Trigger: Passing images.shape == (H, W, C) directly (missing batch dimension), or a 5D video-like tensor, into a node whose _process expects per-image [1,H,W,C] tensors.

Common situations: Slicing a batch with images[0] upstream (drops to 3D) and forgetting unsqueeze(0); mixing VAE-latent-shaped or video tensors into an image-processing dataset node.

Related errors


AI-assisted analysis of Comfy-Org/ComfyUI@1c6d8d45b3 (2026-08-14). Data as JSON: /api/errors/caf27b2fe1f9d9e2. Report an issue: GitHub.