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
- 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.
- Re-export or re-render the plate if the file is corrupt or zero-byte; tighten cannot repair unreadable input.
- Normalize the path: use Path and absolute paths, quote/sanitize spaces and non-ASCII characters.
- 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')).
- 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
- After any export/render step, assert plate files are non-empty before downstream use.
- Keep plates as plain 8-bit RGBA PNGs; avoid 16-bit or exotic formats for pipeline assets.
- Store plate paths in a config generated by the producing step, never retyped by hand.
- Sanitize paths (absolute, no stray whitespace) when moving work between machines.
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)