Mindykkyan's picture
feat: Aesthetic Dissection Panel
2431945 verified
Raw
History Blame Contribute Delete
6.81 kB
"""
Persist test uploads and flattened analysis metrics to CSV + JSON.
"""
from __future__ import annotations
import csv
import json
from datetime import datetime, timezone
from pathlib import Path
from PIL import Image
from .config import LOG_DIR
try:
import fcntl
_HAS_FCNTL = True
except ImportError:
_HAS_FCNTL = False
CSV_NAME = "analysis_log.csv"
CSV_COLUMNS = [
"image_name",
"saved_filename",
"analyzed_at",
"lang",
"device",
"rationale_source",
"laion_aesthetic_score",
"color_harmony_value",
"color_harmony_level",
"hue_diversity",
"luminance_contrast",
"composition_balance_value",
"composition_balance_level",
"centeredness",
"center_offset",
"saturation_intensity_value",
"saturation_intensity_level",
"mean_saturation",
"edge_complexity_value",
"edge_complexity_level",
"edge_density",
"warm_cool_value",
"warm_cool_level",
"warmth_index",
"hue_centroid",
"depth_foreground_pct",
"depth_mid_pct",
"depth_background_pct",
"depth_spread",
"depth_error",
]
_IMAGES_DIR = LOG_DIR / "test_images"
_JSON_DIR = LOG_DIR / "analysis_json"
_CSV_PATH = LOG_DIR / CSV_NAME
def ensure_log_dirs() -> None:
LOG_DIR.mkdir(parents=True, exist_ok=True)
_IMAGES_DIR.mkdir(parents=True, exist_ok=True)
_JSON_DIR.mkdir(parents=True, exist_ok=True)
def extract_image_name(image) -> str:
"""Best-effort original filename from Gradio upload payload."""
if isinstance(image, dict):
orig = image.get("orig_name")
if orig:
return Path(str(orig)).name
path = image.get("path")
if path:
return Path(str(path)).name
return f"upload_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
def versioned_filename(original_name: str, ts: datetime | None = None) -> str:
ts = ts or datetime.now(timezone.utc)
path = Path(original_name)
stem = path.stem or "upload"
stamp = ts.strftime("%Y%m%d_%H%M%S_%f")
return f"{stem}_{stamp}.jpg"
def _dim_map(detail: dict) -> dict[str, dict]:
return {d["id"]: d for d in detail.get("dimensions", []) if "id" in d}
def _sub_value(dim: dict, label: str) -> str:
for sub in dim.get("sub_metrics", []):
if sub.get("label") == label:
return str(sub.get("value", ""))
return ""
def flatten_analysis(
detail: dict,
lang: str,
image_name: str,
saved_filename: str,
analyzed_at: datetime | None = None,
) -> dict[str, str]:
analyzed_at = analyzed_at or datetime.now(timezone.utc)
dims = _dim_map(detail)
color = dims.get("color_harmony", {})
comp = dims.get("composition_balance", {})
sat = dims.get("saturation_intensity", {})
edge = dims.get("edge_complexity", {})
warm = dims.get("warm_cool", {})
depth = detail.get("depth_layers") or {}
centeredness = _sub_value(comp, "Centeredness") or comp.get("value", "")
center_offset = _sub_value(comp, "Offset from center") or comp.get("raw", {}).get("offset", "")
row = {
"image_name": image_name,
"saved_filename": saved_filename,
"analyzed_at": analyzed_at.isoformat(timespec="seconds"),
"lang": lang,
"device": str(detail.get("device", "")),
"rationale_source": str(detail.get("rationale_source", "")),
"laion_aesthetic_score": str(detail.get("laion_aesthetic_score", "")),
"color_harmony_value": str(color.get("value", "")),
"color_harmony_level": str(color.get("level", "")),
"hue_diversity": str(color.get("raw", {}).get("hue_entropy_norm", "")),
"luminance_contrast": str(color.get("raw", {}).get("luminance_contrast", "")),
"composition_balance_value": str(comp.get("value", "")),
"composition_balance_level": str(comp.get("level", "")),
"centeredness": str(centeredness),
"center_offset": str(center_offset),
"saturation_intensity_value": str(sat.get("value", "")),
"saturation_intensity_level": str(sat.get("level", "")),
"mean_saturation": str(sat.get("raw", {}).get("mean_saturation", "")),
"edge_complexity_value": str(edge.get("value", "")),
"edge_complexity_level": str(edge.get("level", "")),
"edge_density": str(edge.get("raw", {}).get("edge_density", "")),
"warm_cool_value": str(warm.get("value", "")),
"warm_cool_level": str(warm.get("level", "")),
"warmth_index": _sub_value(warm, "Warmth index") or str(warm.get("value", "")),
"hue_centroid": str(warm.get("raw", {}).get("hue_centroid", "")),
"depth_foreground_pct": str(depth.get("foreground_coverage", "")),
"depth_mid_pct": str(depth.get("midground_coverage", "")),
"depth_background_pct": str(depth.get("background_coverage", "")),
"depth_spread": str(depth.get("depth_spread", "")),
"depth_error": str(detail.get("depth_error") or ""),
}
return {col: row.get(col, "") for col in CSV_COLUMNS}
def save_test_image(pil: Image.Image, original_name: str) -> tuple[str, Path]:
ensure_log_dirs()
saved_filename = versioned_filename(original_name)
dest = _IMAGES_DIR / saved_filename
pil.convert("RGB").save(dest, format="JPEG", quality=92)
return saved_filename, dest
def save_analysis_json(saved_filename: str, detail: dict) -> Path:
ensure_log_dirs()
stem = Path(saved_filename).stem
dest = _JSON_DIR / f"{stem}.json"
dest.write_text(json.dumps(detail, indent=2, ensure_ascii=False), encoding="utf-8")
return dest
def append_analysis_row(row: dict[str, str]) -> Path:
ensure_log_dirs()
write_header = not _CSV_PATH.exists() or _CSV_PATH.stat().st_size == 0
with _CSV_PATH.open("a", encoding="utf-8", newline="") as fh:
if _HAS_FCNTL:
fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
try:
writer = csv.DictWriter(fh, fieldnames=CSV_COLUMNS, extrasaction="ignore")
if write_header:
writer.writeheader()
writer.writerow(row)
finally:
if _HAS_FCNTL:
fcntl.flock(fh.fileno(), fcntl.LOCK_UN)
return _CSV_PATH
def csv_log_path() -> Path | None:
return _CSV_PATH if _CSV_PATH.exists() else None
def persist_analysis(
pil: Image.Image,
detail: dict,
lang: str,
image,
) -> tuple[str, Path, Path]:
"""Save image, JSON detail, and append CSV row. Returns (saved_filename, image_path, csv_path)."""
image_name = extract_image_name(image)
saved_filename, image_path = save_test_image(pil, image_name)
row = flatten_analysis(detail, lang, image_name, saved_filename)
save_analysis_json(saved_filename, detail)
csv_path = append_analysis_row(row)
return saved_filename, image_path, csv_path