affaan-m/ECC · error · ValueError

analyze() needs at least one frame

Error message

analyze() needs at least one frame

What it means

`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.

Solutions

  1. Check `len(frames) > 0` before calling analyze().
  2. Fix the upstream frame source: ensure `sample_frames()` returns frames (see its 'decoded zero frames' error).
  3. For adaptive flows, fall back to a direct LUT grade when no frames can be measured.
  4. In tests, seed analyze with at least one valid frame array.

Example fix

// before
stats = analyze(frames)              # frames == []
// after
if not frames:
    frames = _frames.sample_frames(src, n=24)
if not frames:
    raise RuntimeError(f"no frames to analyze from {src}")
stats = analyze(frames)
Defensive patterns

Strategy: validation

Validate before calling

if not frames or not all(isinstance(f, np.ndarray) and f.ndim == 3 for f in frames):
    raise ValueError('analyze() requires a non-empty list of frame arrays')

Type guard

def has_frames(frames: list[np.ndarray]) -> bool:
    return len(frames) > 0 and all(isinstance(f, np.ndarray) and f.size > 0 for f in frames)

Try / catch

try:
    stats = analyze(frames)
except ValueError as e:
    if 'at least one frame' in str(e):
        stats = DEFAULT_GRADE_STATS   # neutral fallback
    else:
        raise

Prevention

When it happens

Trigger: Calling `analyze([])` — typically the result of sampling zero frames from a broken video or filtering frames with a mask that removed everything.

Common situations: 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.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at skills/taste-application/scripts/taste/grade.py:214

    populated = [i for i, v in enumerate(raw) if v is not None]
    if not populated:
        g = [float(a.mean()), float(a.std()), float(b.mean()), float(b.std())]
        return [list(g) for _ in range(_N_ZONES)]

    out: list[list] = []
    for z in range(_N_ZONES):
        if raw[z] is not None:
            out.append(raw[z])
        else:
            nearest = min(populated, key=lambda p: abs(p - z))
            out.append(list(raw[nearest]))
    return out


def analyze(frames: list[np.ndarray]) -> GradeStats:
    """Distill grade statistics from a list of float32 RGB frames in [0,1]."""
    if not frames:
        raise ValueError("analyze() needs at least one frame")

    labs = [_to_lab(f) for f in frames]
    stacked = np.concatenate([l.reshape(-1, 3) for l in labs], axis=0)
    L, a, b = stacked[:, 0], stacked[:, 1], stacked[:, 2]

    chroma = np.sqrt(a.astype(np.float64) ** 2 + b.astype(np.float64) ** 2)

    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()),
        tint=float(a.mean()),

View on GitHub (pinned to 8321021c54)