affaan-m/ECC · error · ValueError

cannot read plate

Error message

cannot read plate: {path}

What it means

tighten crops a plate PNG around its non-transparent pixels using OpenCV. cv2.imread returns None (rather than raising) when it cannot read a file, so the function converts that into ValueError('cannot read plate: <path>'). Common causes are a nonexistent path or an image OpenCV's codec support cannot decode (e.g. 16-bit PNG with unusual settings, or a non-image file with an image extension).

Solutions

  1. Check the file yourself: os.path.exists / Path.stat, file <path> to confirm it is really a PNG, and try opening it with PIL as a second decoder.
  2. Re-export or re-render the plate if the file is corrupt or zero-byte; tighten cannot repair unreadable input.
  3. Normalize the path: use Path and absolute paths, quote/sanitize spaces and non-ASCII characters.
  4. If format support is the issue, convert to a plain 8-bit PNG: ffmpeg -i plate.webp plate.png (or PIL Image.open(...).convert('RGBA').save('plate.png')).
  5. Pre-validate before calling and fall back to the original path or a clear skip when the plate can't be read.

Example fix

// before
tighten('renders/plate.png')  # ValueError: cannot read plate
// after
from pathlib import Path
p = Path('renders/plate.png')
if not p.exists() or p.stat().st_size == 0:
    raise SystemExit(f'plate missing or empty: {p.resolve()}')
tighten(p)
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
import cv2
p = Path(path)
if not p.is_file() or p.stat().st_size == 0:
    raise SystemExit(f'plate missing or empty: {p.resolve()}')
probe = cv2.imread(str(p), cv2.IMREAD_UNCHANGED)
if probe is None:
    raise SystemExit(f'not decodable by OpenCV: {p} (convert to 8-bit PNG first)')

Try / catch

try:
    tightened = tighten(plate)
except ValueError as e:
    logger.warning('skipping unreadable plate: %s', e)
    tightened = plate  # fall back to the original, untightened plate

Prevention

When it happens

Trigger: tighten(path) where cv2.imread returns None: file does not exist at that path, the file is corrupt/truncated, the extension does not match the actual content (e.g. a text error page saved as .png), or an unsupported format/bit-depth for the installed OpenCV build.

Common situations: Upstream rendering/export step silently failed and left a zero-byte file; paths passed through a config with typos or stale absolute paths from another machine; non-ASCII or spaces in the path mishandled on some platforms; plate exported as WebP/16-bit that the OpenCV build can't decode.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

Thrown at skills/taste-distillation/scripts/taste/plates.py:225

def tighten(path: str | Path, dest: str | Path | None = None, pad: float = 0.06) -> Path:
    """Crop a plate to its own content, so the element fills the file.

    A glow plate is mostly empty by construction - the selection keeps the top
    few percent of pixels by energy, so a typical plate is 2-7% covered and
    97% transparent black. Compositing that at full frame produces a small
    bright dot floating in the middle of the shot, which reads as a sticker
    rather than as light. Measured on the first cut: a plate covering 1.7% of
    its own frame, screen-blended full-frame, was visible only as a coloured
    blob near centre.

    Cropping to the alpha bounding box means the caller controls the element's
    size on screen by scaling, instead of inheriting whatever fraction of the
    source frame the element happened to occupy.
    """
    path = Path(path)
    im = cv2.imread(str(path), cv2.IMREAD_UNCHANGED)
    if im is None:
        raise ValueError(f"cannot read plate: {path}")
    alpha = im[..., 3] if im.shape[2] == 4 else im[..., :3].max(axis=2)
    ys, xs = np.where(alpha > 12)
    if len(ys) == 0:
        return path
    h, w = alpha.shape
    py, px = int(h * pad), int(w * pad)
    y0 = max(0, int(ys.min()) - py); y1 = min(h, int(ys.max()) + py + 1)
    x0 = max(0, int(xs.min()) - px); x1 = min(w, int(xs.max()) + px + 1)
    out = Path(dest) if dest else path.with_name(path.stem + "_tight.png")
    out.parent.mkdir(parents=True, exist_ok=True)
    cv2.imwrite(str(out), im[y0:y1, x0:x1])
    return out


def plate_coverage(path: str | Path) -> float:
    """Share of the plate that is actually lit. Drives element-vs-wash choice."""
    im = cv2.imread(str(path), cv2.IMREAD_UNCHANGED)
    if im is None:

View on GitHub (pinned to 8321021c54)