"""A2 real-landmark + segmentation recompose spike (no Gemini call). Detects landmarks on the ORIGINAL and the already-generated raw face crop using an OPTIONAL backend (MediaPipe / InsightFace). If no backend is installed it falls back to synthetic landmarks (clearly recorded as landmark_detected=NO) so the structure still runs for inspection. Aligns via eyes/nose/mouth, protects everything outside the face-contour landmark polygon, color-matches, and blends onto the original. No API key, no model weights committed. Outputs under runtime/ (gitignored). EXPERIMENTAL / NOT_PRODUCTION_READY. Human QA required. Pilot Ready: NOT CONFIRMED. """ from __future__ import annotations import argparse import sys from io import BytesIO from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from PIL import Image, ImageDraw # noqa: E402 from app.services.face_pipeline.geometry_align import recompose_a2 # noqa: E402 from app.services.face_pipeline.landmark_detector import ( # noqa: E402 detect_face_landmarks, landmark_backend, synthetic_landmarks, ) from app.services.face_pipeline.segmentation_mask import ( # noqa: E402 segmentation_backend_available, ) from app.services.gemini_client import normalize_image_orientation_bytes # noqa: E402 def _parse_norm(value: str) -> tuple[float, float, float, float]: parts = [float(p) for p in value.split(",")] if len(parts) != 4: raise ValueError("crop-box-norm must be 'x1,y1,x2,y2'") return parts[0], parts[1], parts[2], parts[3] def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="A2 real-landmark recompose spike (no API)") parser.add_argument("--original", required=True) parser.add_argument("--generated-crop", required=True) parser.add_argument("--crop-box-norm", default="0.33,0.39,0.69,0.78") parser.add_argument("--evidence-root", default="runtime/gemini-smoke-evidence") parser.add_argument("--tag", default="salon-crop-08-a2-real-landmark") parser.add_argument("--warp-mode", default="affine", choices=["affine", "piecewise"]) parser.add_argument("--blend-mode", default="safe", choices=["safe", "medium", "strong"]) parser.add_argument("--color-match-strength", type=float, default=None) parser.add_argument("--feature-strength", type=float, default=None) parser.add_argument("--inner-core-alpha", type=int, default=None) parser.add_argument("--boundary-feather", type=int, default=None) parser.add_argument("--visual-mode", default=None, choices=["natural", "balanced", "strong"]) parser.add_argument("--eye-protect-strength", type=float, default=None) parser.add_argument("--max-eye-alpha", type=int, default=None) parser.add_argument("--max-forehead-alpha", type=int, default=None) parser.add_argument("--max-boundary-alpha", type=int, default=None) args = parser.parse_args(argv) original_path = Path(args.original) crop_path = Path(args.generated_crop) if not original_path.exists(): print(f"REFUSED: original not found: {original_path}") return 2 if not crop_path.exists(): print(f"REFUSED: generated crop not found: {crop_path}") return 2 smoke = Path(args.evidence_root) / "gemini-smoke" out_png = smoke / "output" / f"gemini-output-{args.tag}.png" mask_png = smoke / "masks" / f"gemini-mask-{args.tag}.png" compare_png = smoke / "comparison" / f"gemini-compare-{args.tag}.png" qa_md = smoke / "qa" / f"gemini-a2-qa-{args.tag}.md" for p in (out_png, mask_png, compare_png, qa_md): p.parent.mkdir(parents=True, exist_ok=True) original_bytes = normalize_image_orientation_bytes(original_path.read_bytes()) crop_bytes = crop_path.read_bytes() base = Image.open(BytesIO(original_bytes)).convert("RGB") width_img, height_img = base.size nx1, ny1, nx2, ny2 = _parse_norm(args.crop_box_norm) crop_box = (int(nx1 * width_img), int(ny1 * height_img), int(nx2 * width_img), int(ny2 * height_img)) backend = landmark_backend() crop_img = Image.open(BytesIO(crop_bytes)).convert("RGB") target_landmarks = detect_face_landmarks(original_bytes) source_landmarks = detect_face_landmarks(crop_bytes) if target_landmarks is not None and source_landmarks is not None: landmark_source = backend or "unknown" else: # Graceful fallback: synthetic landmarks so the structure still runs. print(f"NOTE: landmark backend unavailable ({backend or 'none'}); " "using synthetic landmarks (landmark_detected=NO).") face_xywh = (crop_box[0], crop_box[1], crop_box[2] - crop_box[0], crop_box[3] - crop_box[1]) target_landmarks = synthetic_landmarks(base.size, face_xywh) source_landmarks = synthetic_landmarks(crop_img.size, (0, 0, *crop_img.size)) landmark_source = "synthetic" final_bytes, qa, editable, _warped = recompose_a2( original_bytes, crop_bytes, target_landmarks=target_landmarks, source_landmarks=source_landmarks, landmark_source=landmark_source, segmentation_used=segmentation_backend_available(), warp_mode=args.warp_mode, blend_mode=args.blend_mode, color_match_strength=args.color_match_strength, feature_strength=args.feature_strength, inner_core_alpha=args.inner_core_alpha, boundary_feather=args.boundary_feather, visual_mode=args.visual_mode, eye_protect_strength=args.eye_protect_strength, max_eye_alpha=args.max_eye_alpha, max_forehead_alpha=args.max_forehead_alpha, max_boundary_alpha=args.max_boundary_alpha, return_debug=True, ) out_png.write_bytes(final_bytes) editable.save(mask_png) raw_crop = crop_img final_img = Image.open(BytesIO(final_bytes)).convert("RGB") tint = Image.new("RGB", base.size, (255, 40, 40)) seg_panel = Image.composite(Image.blend(base, tint, 0.45), base, editable) panels = [("original", base), ("raw crop", raw_crop), ("segmentation mask", seg_panel), ("final composite", final_img)] h = 360 thumbs = [(lbl, im.resize((max(1, int(im.width * h / im.height)), h))) for lbl, im in panels] pad, label_h = 12, 26 total_w = sum(t.width for _, t in thumbs) + pad * (len(thumbs) + 1) canvas = Image.new("RGB", (total_w, h + label_h + pad * 2), (245, 245, 245)) cd = ImageDraw.Draw(canvas) x = pad for lbl, t in thumbs: canvas.paste(t, (x, pad + label_h)) cd.text((x, pad + 6), lbl, fill=(20, 20, 20)) x += t.width + pad canvas.save(compare_png) qa_lines = "\n".join(f"- {k}: {v}" for k, v in qa.items()) qa_md.write_text( f"# A2 real-landmark recompose QA - {args.tag}\n\n" f"- landmark_backend: {backend or 'none (synthetic fallback)'}\n" f"- crop_box: {crop_box}\n" f"{qa_lines}\n\n" "> EXPERIMENTAL / NOT_PRODUCTION_READY. Human QA required.\n" "> Pilot Ready: NOT CONFIRMED.\n", encoding="utf-8", ) print("recompose_a2_spike: DONE (no API call, no key used)") print(f"landmark_backend={backend or 'none'} landmark_source={landmark_source}") for k, v in qa.items(): print(f"{k}={v}") print(f"output={out_png.name} mask={mask_png.name} compare={compare_png.name}") print("Human QA required. Pilot Ready: NOT CONFIRMED.") return 0 if __name__ == "__main__": sys.exit(main())