This commit is contained in:
Hossein 2025-12-01 15:17:48 +03:30
parent 0f00b8ffbd
commit 60f86db416

269
main.py
View File

@ -80,6 +80,93 @@ def find_first_image_part(resp):
return None return None
async def generate_tryon_image(user_part, outfit_part, prompt, previous_image_part=None):
"""
Helper function to generate try-on image from Gemini.
Returns the raw image bytes.
"""
contents = [prompt, user_part, outfit_part]
if previous_image_part:
contents.append(previous_image_part)
contents.append("Third image = previous incorrect attempt. Fix it by keeping the original face exactly.")
else:
contents.append("First image = user selfie. Second image = outfit reference.")
print(f"[DEBUG] Generating try-on image with model: {IMAGE_MODEL}")
if previous_image_part:
print("[DEBUG] Correction mode: regenerating with face-lock instructions")
try:
response = client.models.generate_content(
model=IMAGE_MODEL,
contents=contents,
)
except Exception as e:
error_msg = str(e)
if "API key" in error_msg or "INVALID_ARGUMENT" in error_msg or "API_KEY" in error_msg:
raise HTTPException(
status_code=401,
detail=(
f"Gemini API key error: {error_msg}. "
"Please verify your GEMINI_API_KEY in the .env file is correct."
),
)
import traceback
print(f"[ERROR] Gemini API error: {error_msg}")
print(traceback.format_exc())
raise HTTPException(status_code=502, detail=f"Gemini error: {error_msg}")
# Extract output image
img_part = find_first_image_part(response)
if not img_part or not getattr(img_part, "data", None):
message = "No image returned from Gemini."
if getattr(response, "candidates", None) and len(response.candidates) > 0:
cand = response.candidates[0]
finish_reason = getattr(cand, "finish_reason", "unknown")
if hasattr(cand, "content") and getattr(cand.content, "parts", None):
for part in cand.content.parts:
txt = getattr(part, "text", None)
if txt:
message += f" Gemini says: {txt[:500]}"
break
if hasattr(cand, "safety_ratings"):
safety_ratings = cand.safety_ratings
if safety_ratings:
blocked = [
f"{getattr(r, 'category', 'unknown')}"
for r in safety_ratings
if hasattr(r, "blocked") and getattr(r, "blocked", False)
]
if blocked:
message += f" Safety blocked: {', '.join(blocked)}"
raise HTTPException(
status_code=502,
detail=f"{message} Finish reason: {finish_reason}",
)
else:
raise HTTPException(
status_code=502,
detail="No image returned from Gemini. No candidates in response.",
)
raw = img_part.data
# Ensure valid image bytes
try:
Image.open(BytesIO(raw))
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Returned image is invalid: {e}. Image size: {len(raw)} bytes",
)
return raw
def faces_match_strict(original_bytes: bytes, edited_bytes: bytes) -> bool: def faces_match_strict(original_bytes: bytes, edited_bytes: bytes) -> bool:
""" """
Second Gemini call: compare ORIGINAL vs EDITED and decide Second Gemini call: compare ORIGINAL vs EDITED and decide
@ -281,6 +368,11 @@ These identity rules are STRONGER than all other instructions. If you cannot fol
ABSOLUTE FACE LOCK (READ-ONLY REGION): ABSOLUTE FACE LOCK (READ-ONLY REGION):
- Treat the entire face, head, ears, and visible neck area of the FIRST image as a **READ-ONLY REGION**. - Treat the entire face, head, ears, and visible neck area of the FIRST image as a **READ-ONLY REGION**.
- DO NOT TOUCH the face at all.
- DO NOT TOUCH identity.
- Only modify clothing regions.
- Keep head/face exactly as the original.
- Do not redraw the face at all.
- You MUST NOT re-render, redraw, regenerate, beautify, smooth, reshape, de-age, re-gender, - You MUST NOT re-render, redraw, regenerate, beautify, smooth, reshape, de-age, re-gender,
or otherwise alter the face in any way. or otherwise alter the face in any way.
- The face region in the final image must be **indistinguishable** from the original FIRST image, - The face region in the final image must be **indistinguishable** from the original FIRST image,
@ -384,133 +476,104 @@ SECOND image = outfit_photo (outfit reference).
mime_type=outfit_photo.content_type or "image/jpeg", mime_type=outfit_photo.content_type or "image/jpeg",
) )
# ---- Call Gemini (image model) ---- print(f"\n[DEBUG] Metadata received: {metadata[:200] if metadata else '(empty)'}")
print(f"\n[DEBUG] Using image model: {IMAGE_MODEL}")
print(f"[DEBUG] Metadata received: {metadata[:200] if metadata else '(empty)'}")
# ============================================================
# STEP A: Generate initial try-on image (clothing only)
# ============================================================
print("\n[STEP A] Generating initial try-on image...")
try: try:
response = client.models.generate_content( raw_v1 = await generate_tryon_image(user_part, outfit_part, prompt)
model=IMAGE_MODEL, print("[STEP A] ✅ Initial try-on image generated")
contents=[ except HTTPException:
prompt, raise
user_part,
outfit_part,
"First image = user selfie. Second image = outfit reference.",
],
)
except Exception as e: except Exception as e:
error_msg = str(e)
if "API key" in error_msg or "INVALID_ARGUMENT" in error_msg or "API_KEY" in error_msg:
raise HTTPException(
status_code=401,
detail=(
f"Gemini API key error: {error_msg}. "
"Please verify your GEMINI_API_KEY in the .env file is correct."
),
)
import traceback import traceback
print(f"[ERROR] Gemini API error: {error_msg}") print(f"[ERROR] Step A failed: {e}")
print(traceback.format_exc()) print(traceback.format_exc())
raise HTTPException(status_code=502, detail=f"Gemini error: {error_msg}") raise HTTPException(status_code=502, detail=f"Failed to generate initial try-on: {str(e)}")
# ---- Debug: Inspect response ---- # ============================================================
print("\n[DEBUG] === Gemini Response Analysis ===") # STEP B: Identity validation check
if not getattr(response, "candidates", None): # ============================================================
print("[DEBUG] No candidates at all. Full response:") print("\n[STEP B] Running identity consistency check on initial result...")
pprint(response) same_identity = faces_match_strict(user_bytes, raw_v1)
else: print(f"[STEP B] Identity check result: same_identity={same_identity}")
cand = response.candidates[0]
finish_reason = getattr(cand, "finish_reason", None)
print(f"[DEBUG] Finish reason: {finish_reason}")
print(f"[DEBUG] Safety ratings: {getattr(cand, 'safety_ratings', None)}")
if hasattr(cand, "content") and getattr(cand.content, "parts", None): if same_identity:
print(f"[DEBUG] Parts in first candidate ({len(cand.content.parts)} total):") # ✅ Identity preserved - return the image
for i, part in enumerate(cand.content.parts): print("[STEP B] ✅ Identity validated - returning initial result")
part_type = type(part).__name__ headers = {}
print(f" Part {i}: type={part_type}") if request_id:
headers["X-TryOn-Request-Id"] = str(request_id)
return Response(content=raw_v1, media_type="image/png", headers=headers)
text = getattr(part, "text", None) # ============================================================
if text: # STEP C: Identity changed - regenerate with HARD face lock
print(f" TEXT: {text[:300]}") # ============================================================
print("\n[STEP C] ❌ Identity changed detected - regenerating with face-lock correction...")
if hasattr(part, "inline_data"): correction_prompt = f"""
inline = part.inline_data You changed the user's identity in the previous image generation attempt.
if inline: THIS IS ABSOLUTELY FORBIDDEN.
data_size = len(getattr(inline, "data", b""))
mime = getattr(inline, "mime_type", "unknown")
print(f" INLINE_DATA: mime={mime}, size={data_size} bytes")
if hasattr(part, "blob"): 🚫 CRITICAL CORRECTION INSTRUCTIONS:
blob = part.blob
if blob:
data_size = len(getattr(blob, "data", b""))
mime = getattr(blob, "mime_type", "unknown")
print(f" BLOB: mime={mime}, size={data_size} bytes")
print("[DEBUG] === End Response Analysis ===\n")
# ---- Extract output image ---- DO NOT TOUCH THE USER'S FACE AT ALL
img_part = find_first_image_part(response) DO NOT REDRAW THE FACE
if not img_part or not getattr(img_part, "data", None): DO NOT CHANGE IDENTITY, HAIRLINE, BEARD, OR ANY FACIAL FEATURES
message = "No image returned from Gemini." DO NOT CHANGE HEAD SHAPE OR NECK
DO NOT BEAUTIFY, SMOOTH, OR RESHAPE THE FACE
DO NOT MODIFY SKIN TONE ON THE FACE
if getattr(response, "candidates", None) and len(response.candidates) > 0: YOU MUST KEEP THE FIRST IMAGE FACE EXACTLY, PIXEL BY PIXEL IF POSSIBLE.
cand = response.candidates[0]
finish_reason = getattr(cand, "finish_reason", "unknown")
if hasattr(cand, "content") and getattr(cand.content, "parts", None): ONLY modify clothing areas using the outfit reference from the SECOND image.
for part in cand.content.parts: Keep the background, lighting, pose, and identity EXACTLY as in the original user photo (FIRST image).
txt = getattr(part, "text", None)
if txt:
message += f" Gemini says: {txt[:500]}"
break
if hasattr(cand, "safety_ratings"): The face, head, ears, and neck region from the FIRST image are READ-ONLY.
safety_ratings = cand.safety_ratings Only clothing below the neck and on the body should be changed.
if safety_ratings:
blocked = [
f"{getattr(r, 'category', 'unknown')}"
for r in safety_ratings
if hasattr(r, "blocked") and getattr(r, "blocked", False)
]
if blocked:
message += f" Safety blocked: {', '.join(blocked)}"
raise HTTPException( {metadata_text}
status_code=502,
detail=f"{message} Finish reason: {finish_reason}",
)
else:
raise HTTPException(
status_code=502,
detail="No image returned from Gemini. No candidates in response.",
)
raw = img_part.data {extra_rules}
"""
# Create a Part from the previous incorrect image for reference
previous_image_part = Part.from_bytes(
data=raw_v1,
mime_type="image/png",
)
# Ensure valid image bytes
try: try:
Image.open(BytesIO(raw)) raw_v2 = await generate_tryon_image(user_part, outfit_part, correction_prompt, previous_image_part)
print("[STEP C] ✅ Correction image generated")
except HTTPException:
raise
except Exception as e: except Exception as e:
raise HTTPException( import traceback
status_code=500, print(f"[ERROR] Step C failed: {e}")
detail=f"Returned image is invalid: {e}. Image size: {len(raw)} bytes", print(traceback.format_exc())
)
# ---- SECOND CALL: IDENTITY VALIDATION ----
print("[DEBUG] Running identity consistency check...")
same_identity = faces_match_strict(user_bytes, raw)
print(f"[DEBUG] Identity check result: same_identity={same_identity}")
if not same_identity:
# Hard fail: we do NOT return the image if identity changed
raise HTTPException( raise HTTPException(
status_code=502, status_code=502,
detail="Model changed user identity; try-on result rejected by identity guard.", detail="Failed to generate corrected try-on after identity mismatch.",
) )
# ============================================================
# STEP D: Re-validate corrected version (optional check, always return)
# ============================================================
print("\n[STEP D] Running identity consistency check on corrected result...")
same_identity_v2 = faces_match_strict(user_bytes, raw_v2)
print(f"[STEP D] Identity check result: same_identity={same_identity_v2}")
if same_identity_v2:
print("[STEP D] ✅ Identity validated after correction")
else:
print("[STEP D] ⚠️ Identity check failed, but returning corrected image anyway")
# Always return the corrected image, regardless of identity check result
headers = {} headers = {}
if request_id: if request_id:
headers["X-TryOn-Request-Id"] = str(request_id) headers["X-TryOn-Request-Id"] = str(request_id)
return Response(content=raw, media_type="image/png", headers=headers) return Response(content=raw_v2, media_type="image/png", headers=headers)