{"record":{"id":"a1f84f048c97275d","repo":"affaan-m/ECC","slug":"expected-n-3-pixels-got-pixels-shape","errorCode":null,"errorMessage":"expected (N, 3) pixels, got {pixels.shape}","messagePattern":"expected \\(N, 3\\) pixels, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/taste/grade.py","lineNumber":615,"sourceCode":"\ndef load_stats(path: str | Path) -> GradeStats:\n    return GradeStats.from_dict(json.loads(Path(path).read_text(encoding=\"utf-8\")))\n\n\ndef analyze_pixels(\n    pixels: np.ndarray,\n    noise_frames: list[np.ndarray] | None = None,\n    palette_pixels: np.ndarray | None = None,\n) -> GradeStats:\n    \"\"\"Same statistics as :func:`analyze`, but from a flat (N, 3) pixel array.\n\n    This is the masked path: callers pool only the pixels that survived\n    content masking, across references of differing frame sizes, and pass\n    them here. Grain still needs 2-D neighbourhoods, so ``noise_frames``\n    carries a handful of cropped frames purely for that estimate.\n    \"\"\"\n    if pixels.ndim != 2 or pixels.shape[1] != 3:\n        raise ValueError(f\"expected (N, 3) pixels, got {pixels.shape}\")\n\n    lab = _to_lab(np.ascontiguousarray(pixels.reshape(1, -1, 3), np.float32)).reshape(-1, 3)\n    L, a, b = lab[:, 0], lab[:, 1], lab[:, 2]\n    chroma = np.sqrt(a.astype(np.float64) ** 2 + b.astype(np.float64) ** 2)\n\n    pal_src = palette_pixels if palette_pixels is not None else pixels\n    pal = _palette([pal_src.reshape(1, -1, 3)])\n\n    return GradeStats(\n        zones=_zone_stats(L, a, b),\n        lab_mean=[float(L.mean()), float(a.mean()), float(b.mean())],\n        lab_std=[float(L.std()), float(a.std()), float(b.std())],\n        l_cdf=[float(v) for v in _cdf_of_l(L)],\n        black_point=float(np.percentile(L, 1)),\n        white_point=float(np.percentile(L, 99)),\n        contrast=float(L.std()),\n        saturation=float(chroma.mean()),\n        warmth=float(b.mean()),","sourceCodeStart":597,"sourceCodeEnd":633,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/taste/grade.py#L597-L633","documentation":"`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`.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nstats = analyze_pixels(frame)                       # frame is (H, W, 3)\n// after\npixels = frame.astype(np.float32) / 255.0\nstats = analyze_pixels(pixels.reshape(-1, 3))","handlingStrategy":"type-guard","validationCode":"pixels = np.asarray(pixels)\nif pixels.ndim != 2 or pixels.shape[1] != 3:\n    pixels = pixels.reshape(-1, 3)   # or convert RGBA -> RGB first","typeGuard":"def is_pixel_matrix(a) -> bool:\n    import numpy as np\n    a = np.asarray(a)\n    return a.ndim == 2 and a.shape[1] == 3","tryCatchPattern":"try:\n    stats = analyze_pixels(pixels, ...)\nexcept ValueError as e:\n    if 'expected (N, 3)' in str(e):\n        stats = analyze_pixels(pixels.reshape(-1, 3), ...)\n    else:\n        raise","preventionTips":["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"],"tags":["shape-mismatch","numpy","valueerror","color-grading"],"backgroundTag":"tensor-shape-mismatch","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}