odysseus-dev/odysseus · error · HTTPException
Face enhancement failed
Error message
Face enhancement failed
What it means
HTTP 500 from enhance-face: the PIL fallback path (median filter denoise, unsharp mask, contrast/color/brightness enhancement, PNG save) threw an exception. This fires only when GFPGAN was unavailable or failed and the code fell back to pure-PIL enhancement, which then also failed — usually on a corrupt/unsupported image or a PIL build without the needed filters.
Source
Thrown at routes/gallery/gallery_routes.py:2120
result_img.save(buf, format="PNG")
return {"image": base64.b64encode(buf.getvalue()).decode()}
except ImportError:
# GFPGAN not available — use PIL-based enhancement (no AI, but works everywhere)
logger.info("GFPGAN not available — using PIL enhancement fallback")
# Multi-step enhancement: denoise → sharpen → contrast → color boost
enhanced = img.filter(ImageFilter.MedianFilter(size=3)) # light denoise
enhanced = enhanced.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3)) # sharpen
enhanced = ImageEnhance.Contrast(enhanced).enhance(1.15) # slight contrast boost
enhanced = ImageEnhance.Color(enhanced).enhance(1.1) # subtle color boost
enhanced = ImageEnhance.Brightness(enhanced).enhance(1.05) # slight brightness lift
buf = io.BytesIO()
enhanced.save(buf, format="PNG")
return {"image": base64.b64encode(buf.getvalue()).decode(), "method": "pil"}
except Exception:
logger.exception("enhance_face: failed")
raise HTTPException(500, "Face enhancement failed")
# ---- Album management (path-param routes) ----
def _get_or_404_album(db, album_id: str, user):
album = db.query(GalleryAlbum).filter(GalleryAlbum.id == album_id).first()
if not album:
raise HTTPException(404, "Album not found")
if not user or album.owner != user:
raise HTTPException(404, "Album not found")
return album
def _get_or_404_image(db, image_id: str, user):
img = db.query(GalleryImage).filter(GalleryImage.id == image_id).first()
if not img:
raise HTTPException(404, "Image not found")
if not user or img.owner != user:
raise HTTPException(404, "Image not found")
return imgView on GitHub (pinned to f9235ebbf1)
Solutions
- Check server logs for the 'enhance_face: failed' traceback to get the root cause
- Verify the image opens and re-saves locally with PIL before sending: Image.open(...).convert('RGB').save(...)
- Re-encode the image to a plain PNG/JPEG before upload to strip exotic modes
- If GFPGAN is intended, install it so the primary path runs instead of the fragile fallback
Example fix
# client pre-flight
from PIL import Image
img = Image.open(io.BytesIO(base64.b64decode(b64))).convert('RGB')
assert img.size[0] > 0 and img.size[1] > 0 Defensive patterns
Strategy: try-catch
Validate before calling
# verify the payload decodes and re-encodes cleanly before the call
from PIL import Image
img = Image.open(io.BytesIO(base64.b64decode(image_b64))).convert('RGB')
assert img.size[0] > 0 and img.size[1] > 0
img.save(io.BytesIO(), format='PNG') Try / catch
try:
resp = requests.post(f'{base}/api/image/enhance-face', json=payload, timeout=60)
resp.raise_for_status()
except requests.HTTPError:
if resp.status_code == 500:
payload['image'] = reencode_png(payload['image']) # normalize exotic modes
resp = requests.post(f'{base}/api/image/enhance-face', json=payload, timeout=60)
resp.raise_for_status() Prevention
- Re-encode uploads to plain RGB PNG/JPEG client-side to avoid truncated or exotic-mode images reaching PIL filters
- Install GFPGAN on servers meant to do face restoration so the fragile PIL fallback is not the active path
- Correlate client 500s with the server's 'enhance_face: failed' traceback before changing client code
When it happens
Trigger: POST an image that decodes to RGB but is truncated (PIL raises on filter/save), an exotic format, or zero-dimension images; the except logs 'enhance_face: failed' with the traceback.
Common situations: Client sends a partially-uploaded base64 blob; image is a CMYK/16-bit TIFF that convert('RGB') mishandles; disk full so PNG save to BytesIO is fine but earlier temp-file GFPGAN path corrupted state.
Related errors
- d.error || 'Failed to create calendar'
- data.detail || data.error || `Mask failed (${res.status})`
- Failed to delete calendar
- Failed to list calendars
- Failed to list events
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/c5cec421a9b2e9c9.
Report an issue: GitHub.