{"record":{"id":"95f66819fff6bfc7","repo":"affaan-m/ECC","slug":"analyze-needs-at-least-one-frame","errorCode":null,"errorMessage":"analyze() needs at least one frame","messagePattern":"analyze\\(\\) needs at least one frame","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/taste/grade.py","lineNumber":214,"sourceCode":"    populated = [i for i, v in enumerate(raw) if v is not None]\n    if not populated:\n        g = [float(a.mean()), float(a.std()), float(b.mean()), float(b.std())]\n        return [list(g) for _ in range(_N_ZONES)]\n\n    out: list[list] = []\n    for z in range(_N_ZONES):\n        if raw[z] is not None:\n            out.append(raw[z])\n        else:\n            nearest = min(populated, key=lambda p: abs(p - z))\n            out.append(list(raw[nearest]))\n    return out\n\n\ndef analyze(frames: list[np.ndarray]) -> GradeStats:\n    \"\"\"Distill grade statistics from a list of float32 RGB frames in [0,1].\"\"\"\n    if not frames:\n        raise ValueError(\"analyze() needs at least one frame\")\n\n    labs = [_to_lab(f) for f in frames]\n    stacked = np.concatenate([l.reshape(-1, 3) for l in labs], axis=0)\n    L, a, b = stacked[:, 0], stacked[:, 1], stacked[:, 2]\n\n    chroma = np.sqrt(a.astype(np.float64) ** 2 + b.astype(np.float64) ** 2)\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()),\n        tint=float(a.mean()),","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/taste/grade.py#L196-L232","documentation":"`analyze()` distills grade statistics from a list of float32 RGB frames and raises a ValueError when the list is empty. Statistics like mean Lab values, chroma, and contrast are undefined without pixels, so the function fails fast instead of producing NaNs. It is called by neutral_stats, grade_clip_adaptive, and grade_clip_direct.","triggerScenarios":"Calling `analyze([])` — typically the result of sampling zero frames from a broken video or filtering frames with a mask that removed everything.","commonSituations":"Passing the output of `sample_frames()` on an empty/corrupt video; building the frame list inside a loop whose condition never appended; an upstream filter dropping all frames before analysis.","solutions":["Check `len(frames) > 0` before calling analyze().","Fix the upstream frame source: ensure `sample_frames()` returns frames (see its 'decoded zero frames' error).","For adaptive flows, fall back to a direct LUT grade when no frames can be measured.","In tests, seed analyze with at least one valid frame array."],"exampleFix":"// before\nstats = analyze(frames)              # frames == []\n// after\nif not frames:\n    frames = _frames.sample_frames(src, n=24)\nif not frames:\n    raise RuntimeError(f\"no frames to analyze from {src}\")\nstats = analyze(frames)","handlingStrategy":"validation","validationCode":"if not frames or not all(isinstance(f, np.ndarray) and f.ndim == 3 for f in frames):\n    raise ValueError('analyze() requires a non-empty list of frame arrays')","typeGuard":"def has_frames(frames: list[np.ndarray]) -> bool:\n    return len(frames) > 0 and all(isinstance(f, np.ndarray) and f.size > 0 for f in frames)","tryCatchPattern":"try:\n    stats = analyze(frames)\nexcept ValueError as e:\n    if 'at least one frame' in str(e):\n        stats = DEFAULT_GRADE_STATS   # neutral fallback\n    else:\n        raise","preventionTips":["Never call analyze() without a length check on the frame list","Sanity-check sample_frames() output before analysis","Provide a neutral default stats object for degenerate clips","Add a unit test for analyze([]) in downstream wrappers"],"tags":["video","empty-input","valueerror","color-grading"],"backgroundTag":"empty-required-field","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"}