Files
2026-06-30 14:29:19 +08:00

81 lines
2.4 KiB
Python

from __future__ import annotations
from dataclasses import asdict, dataclass
from pathlib import Path
import numpy as np
from PIL import Image
@dataclass(frozen=True)
class QualityMetrics:
path: str
width: int
height: int
alpha_min: int
alpha_max: int
alpha_mean: float
nonzero_alpha_pixels: int
edge_pixels: int
edge_green_excess_mean: float
edge_green_excess_p95: float
edge_green_excess_max: float
def to_dict(self) -> dict:
return asdict(self)
def measure_rgba(path: str | Path) -> QualityMetrics:
image = Image.open(path).convert("RGBA")
arr = np.asarray(image).astype(np.float32) / 255.0
rgb = arr[..., :3]
alpha = arr[..., 3]
edge = (alpha > 0.02) & (alpha < 0.98)
green_excess = np.maximum(rgb[..., 1] - np.maximum(rgb[..., 0], rgb[..., 2]), 0.0)
edge_values = green_excess[edge]
if edge_values.size:
edge_mean = float(edge_values.mean())
edge_p95 = float(np.percentile(edge_values, 95))
edge_max = float(edge_values.max())
else:
edge_mean = edge_p95 = edge_max = 0.0
alpha_u8 = (alpha * 255.0).round().astype(np.uint8)
return QualityMetrics(
path=str(path),
width=image.width,
height=image.height,
alpha_min=int(alpha_u8.min()),
alpha_max=int(alpha_u8.max()),
alpha_mean=float(alpha_u8.mean()),
nonzero_alpha_pixels=int((alpha_u8 > 0).sum()),
edge_pixels=int(edge.sum()),
edge_green_excess_mean=edge_mean,
edge_green_excess_p95=edge_p95,
edge_green_excess_max=edge_max,
)
def assert_quality(
metrics: QualityMetrics,
max_edge_green_excess_p95: float | None = None,
require_alpha_range: bool = True,
) -> list[str]:
failures: list[str] = []
if require_alpha_range:
if metrics.alpha_min != 0:
failures.append(f"alpha_min is {metrics.alpha_min}, expected 0")
if metrics.alpha_max != 255:
failures.append(f"alpha_max is {metrics.alpha_max}, expected 255")
if metrics.nonzero_alpha_pixels <= 0:
failures.append("nonzero alpha pixel count is 0")
if (
max_edge_green_excess_p95 is not None
and metrics.edge_green_excess_p95 > max_edge_green_excess_p95
):
failures.append(
"edge_green_excess_p95 "
f"{metrics.edge_green_excess_p95:.4f} exceeds {max_edge_green_excess_p95:.4f}"
)
return failures