affaan-m/ECC · error · ValueError
expected (N, 3) pixels, got
Error message
expected (N, 3) pixels, got {pixels.shape} What it means
`analyze_pixels()` computes grade statistics from a pooled pixel matrix and validates its shape, raising a ValueError unless the array is exactly (N, 3) — N pixels, 3 color channels (RGB). This is the masked-pixel path where callers pool surviving pixels from references of differing sizes, so a wrong reshape or extra channel trips this guard before Lab conversion. Grain estimation gets separate 2-D frames via `noise_frames`.
Solutions
- Reshape/convert input to (N, 3): `pixels = img.reshape(-1, 3)` for a single HxWx3 frame.
- Strip alpha: `pixels = rgba[..., :3].reshape(-1, 3)` before pooling.
- Convert grayscale to RGB with `np.stack([g, g, g], axis=-1)` before pooling.
- Assert `pixels.ndim == 2 and pixels.shape[1] == 3` in the caller before invoking.
Example fix
// before stats = analyze_pixels(frame) # frame is (H, W, 3) // after pixels = frame.astype(np.float32) / 255.0 stats = analyze_pixels(pixels.reshape(-1, 3))
Defensive patterns
Strategy: type-guard
Validate before calling
pixels = np.asarray(pixels)
if pixels.ndim != 2 or pixels.shape[1] != 3:
pixels = pixels.reshape(-1, 3) # or convert RGBA -> RGB first Type guard
def is_pixel_matrix(a) -> bool:
import numpy as np
a = np.asarray(a)
return a.ndim == 2 and a.shape[1] == 3 Try / catch
try:
stats = analyze_pixels(pixels, ...)
except ValueError as e:
if 'expected (N, 3)' in str(e):
stats = analyze_pixels(pixels.reshape(-1, 3), ...)
else:
raise Prevention
- Always reshape pooled pixels to (-1, 3) at the call site
- Strip alpha channels before pooling
- Convert grayscale frames to RGB before stacking
- Assert input shape in helper functions that build pixel pools
When it happens
Trigger: Calling `analyze_pixels(pixels, ...)` with a 1-D array, a (H, W, 3) image instead of pooled pixels, an (N, 4) RGBA array, or an (N, 1) single-channel array.
Common situations: Forgetting `.reshape(-1, 3)` on a per-frame image; passing raw uint8 RGBA masks instead of RGB; stacking frames with an alpha channel; mixing grayscale frames into the pool.
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
- analyze() needs at least one frame
- analyze() needs at least one frame
- Cannot merge ECC configuration at
- expected (N, 3) pixels, got
- ffmpeg grade failed
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/a1f84f048c97275d.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/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)