{"record":{"id":"c5cec421a9b2e9c9","repo":"odysseus-dev/odysseus","slug":"face-enhancement-failed","errorCode":null,"errorMessage":"Face enhancement failed","messagePattern":"Face enhancement failed","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"routes/gallery/gallery_routes.py","lineNumber":2120,"sourceCode":"            result_img.save(buf, format=\"PNG\")\n            return {\"image\": base64.b64encode(buf.getvalue()).decode()}\n\n        except ImportError:\n            # GFPGAN not available — use PIL-based enhancement (no AI, but works everywhere)\n            logger.info(\"GFPGAN not available — using PIL enhancement fallback\")\n            # Multi-step enhancement: denoise → sharpen → contrast → color boost\n            enhanced = img.filter(ImageFilter.MedianFilter(size=3))  # light denoise\n            enhanced = enhanced.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3))  # sharpen\n            enhanced = ImageEnhance.Contrast(enhanced).enhance(1.15)  # slight contrast boost\n            enhanced = ImageEnhance.Color(enhanced).enhance(1.1)  # subtle color boost\n            enhanced = ImageEnhance.Brightness(enhanced).enhance(1.05)  # slight brightness lift\n\n            buf = io.BytesIO()\n            enhanced.save(buf, format=\"PNG\")\n            return {\"image\": base64.b64encode(buf.getvalue()).decode(), \"method\": \"pil\"}\n        except Exception:\n            logger.exception(\"enhance_face: failed\")\n            raise HTTPException(500, \"Face enhancement failed\")\n\n    # ---- Album management (path-param routes) ----\n\n    def _get_or_404_album(db, album_id: str, user):\n        album = db.query(GalleryAlbum).filter(GalleryAlbum.id == album_id).first()\n        if not album:\n            raise HTTPException(404, \"Album not found\")\n        if not user or album.owner != user:\n            raise HTTPException(404, \"Album not found\")\n        return album\n\n    def _get_or_404_image(db, image_id: str, user):\n        img = db.query(GalleryImage).filter(GalleryImage.id == image_id).first()\n        if not img:\n            raise HTTPException(404, \"Image not found\")\n        if not user or img.owner != user:\n            raise HTTPException(404, \"Image not found\")\n        return img","sourceCodeStart":2102,"sourceCodeEnd":2138,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/gallery/gallery_routes.py#L2102-L2138","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# client pre-flight\nfrom PIL import Image\nimg = Image.open(io.BytesIO(base64.b64decode(b64))).convert('RGB')\nassert img.size[0] > 0 and img.size[1] > 0","handlingStrategy":"try-catch","validationCode":"# verify the payload decodes and re-encodes cleanly before the call\nfrom PIL import Image\nimg = Image.open(io.BytesIO(base64.b64decode(image_b64))).convert('RGB')\nassert img.size[0] > 0 and img.size[1] > 0\nimg.save(io.BytesIO(), format='PNG')","typeGuard":null,"tryCatchPattern":"try:\n    resp = requests.post(f'{base}/api/image/enhance-face', json=payload, timeout=60)\n    resp.raise_for_status()\nexcept requests.HTTPError:\n    if resp.status_code == 500:\n        payload['image'] = reencode_png(payload['image'])  # normalize exotic modes\n        resp = requests.post(f'{base}/api/image/enhance-face', json=payload, timeout=60)\n        resp.raise_for_status()","preventionTips":["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"],"tags":["http-500","pillow","face-enhancement","fallback","image-processing"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}