81 lines
2.4 KiB
Python
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
|