affaan-m/ECC · error · ValueError

expected (N, 3) pixels, got

Error message

expected (N, 3) pixels, got {pixels.shape}

What it means

analyze_pixels() is the masked-pixel variant of the analysis and expects a 2-D (N, 3) array of pooled RGB pixels. It validates the shape up front because grain estimation, Lab conversion, and reshaping all assume exactly that layout; anything else (1-D, (N,4) RGBA, (H,W,3)) is rejected.

Solutions

  1. Reshape/flatten your pixels to (N, 3) before calling: pixels.reshape(-1, 3)
  2. Strip the alpha channel: arr[..., :3] for RGBA inputs
  3. Check the mask kept pixels: if pixels.shape[0] == 0, widen the mask or skip the clip
  4. Verify you are not passing a full frame; use analyze() for frame lists instead

Example fix

// before
stats = analyze_pixels(rgba_frame, ...)  # (H, W, 4)
// after
pixels = rgba_frame[..., :3].reshape(-1, 3).astype(np.float32)
stats = analyze_pixels(pixels, ...)
Defensive patterns

Strategy: type-guard

Validate before calling

import numpy as np
if not (isinstance(pixels, np.ndarray) and pixels.ndim == 2 and pixels.shape[1] == 3):
    raise TypeError("analyze_pixels expects (N, 3) float array")

Type guard

def is_pixel_matrix(x) -> bool:
    return isinstance(x, np.ndarray) and x.ndim == 2 and x.shape[1] == 3 and x.shape[0] > 0

Try / catch

try:
    stats = analyze_pixels(pixels, ...)
except ValueError as e:
    if "expected (N, 3) pixels" in str(e):
        pixels = np.asarray(pixels)[..., :3].reshape(-1, 3)
        stats = analyze_pixels(pixels, ...)
    else: raise

Prevention

When it happens

Trigger: Passing an RGBA frame's pixels (4 channels), a single 1-D pixel array, a (H, W, 3) image instead of flattened pixels, or an empty (0, 3) array produced by a mask that kept no pixels.

Common situations: Forgetting to slice [:, :3] off RGBA arrays; calling analyze_pixels with a raw frame instead of pooled pixel matrix; over-aggressive content masks leaving zero surviving pixels; float/uint8 confusion doesn't trigger this but often accompanies it.

Understand the failure class

Background: Tensor shape mismatch errors ("must have shape", "expected shape ... got ..."): when tensor dimensions disagree with what an op or layer was told to expect — this error's family across 6 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/806a3a190b51655a. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-distillation/scripts/taste/grade.py:615

def load_stats(path: str | Path) -> GradeStats:
    return GradeStats.from_dict(json.loads(Path(path).read_text(encoding="utf-8")))


def analyze_pixels(
    pixels: np.ndarray,
    noise_frames: list[np.ndarray] | None = None,
    palette_pixels: np.ndarray | None = None,
) -> GradeStats:
    """Same statistics as :func:`analyze`, but from a flat (N, 3) pixel array.

    This is the masked path: callers pool only the pixels that survived
    content masking, across references of differing frame sizes, and pass
    them here. Grain still needs 2-D neighbourhoods, so ``noise_frames``
    carries a handful of cropped frames purely for that estimate.
    """
    if pixels.ndim != 2 or pixels.shape[1] != 3:
        raise ValueError(f"expected (N, 3) pixels, got {pixels.shape}")

    lab = _to_lab(np.ascontiguousarray(pixels.reshape(1, -1, 3), np.float32)).reshape(-1, 3)
    L, a, b = lab[:, 0], lab[:, 1], lab[:, 2]
    chroma = np.sqrt(a.astype(np.float64) ** 2 + b.astype(np.float64) ** 2)

    pal_src = palette_pixels if palette_pixels is not None else pixels
    pal = _palette([pal_src.reshape(1, -1, 3)])

    return GradeStats(
        zones=_zone_stats(L, a, b),
        lab_mean=[float(L.mean()), float(a.mean()), float(b.mean())],
        lab_std=[float(L.std()), float(a.std()), float(b.std())],
        l_cdf=[float(v) for v in _cdf_of_l(L)],
        black_point=float(np.percentile(L, 1)),
        white_point=float(np.percentile(L, 99)),
        contrast=float(L.std()),
        saturation=float(chroma.mean()),
        warmth=float(b.mean()),

View on GitHub (pinned to 8321021c54)