fix: surface skip reasons and suppress norm_crop FutureWarning
process_face_mode now returns a descriptive string instead of None for
filtered-out faces ("face too small 45x38px, min 90px", "no face
metadata"), so the executor can print a useful reason rather than the
generic "no usable face data".
Also suppresses the InsightFace norm_crop FutureWarning about deprecated
estimate usage, which was noisy at INFO level on every aligned crop.
This commit is contained in:
+2
-1
@@ -202,8 +202,9 @@ def execute_jobs(jobs: list[dict]) -> None:
|
|||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
else:
|
else:
|
||||||
|
reason = saved if isinstance(saved, str) else "no usable face data"
|
||||||
progress.console.print(
|
progress.console.print(
|
||||||
f"[yellow]Skipped {asset['id']} (no usable face data)[/yellow]"
|
f"[yellow]Skipped {asset['id']} ({reason})[/yellow]"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to process asset %s: %s", asset.get("id", "<unknown>"), e)
|
logger.error("Failed to process asset %s: %s", asset.get("id", "<unknown>"), e)
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ def align_face(img: Image.Image, landmarks: list[list[float]] | np.ndarray) -> I
|
|||||||
if lm.shape != (5, 2):
|
if lm.shape != (5, 2):
|
||||||
logger.debug("Invalid landmark shape: %s, expected (5, 2)", lm.shape)
|
logger.debug("Invalid landmark shape: %s, expected (5, 2)", lm.shape)
|
||||||
return None
|
return None
|
||||||
aligned = norm_crop(img_np, lm)
|
with warnings.catch_warnings():
|
||||||
|
warnings.filterwarnings("ignore", message=".*estimate.*is deprecated", category=FutureWarning)
|
||||||
|
aligned = norm_crop(img_np, lm)
|
||||||
return Image.fromarray(aligned)
|
return Image.fromarray(aligned)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.debug("InsightFace not available for face alignment")
|
logger.debug("InsightFace not available for face alignment")
|
||||||
@@ -66,10 +68,11 @@ def process_face_mode(
|
|||||||
count: int,
|
count: int,
|
||||||
min_width: int | None = None,
|
min_width: int | None = None,
|
||||||
insightface_app=None,
|
insightface_app=None,
|
||||||
) -> tuple[int, int] | None:
|
) -> tuple[int, int] | str | None:
|
||||||
"""Crop face based on Immich metadata and save to output directory.
|
"""Crop face based on Immich metadata and save to output directory.
|
||||||
|
|
||||||
Returns (width, height) of the saved crop, or None if no crop was saved.
|
Returns (width, height) of the saved crop, a skip-reason string if the
|
||||||
|
face was filtered out, or None if no crop was saved for other reasons.
|
||||||
When insightface_app is provided and ENABLE_FACE_ALIGNMENT is True,
|
When insightface_app is provided and ENABLE_FACE_ALIGNMENT is True,
|
||||||
re-detects the face in the Immich bbox region using InsightFace to get
|
re-detects the face in the Immich bbox region using InsightFace to get
|
||||||
precise landmarks for a proper 112x112 aligned crop. Falls back to
|
precise landmarks for a proper 112x112 aligned crop. Falls back to
|
||||||
@@ -89,7 +92,7 @@ def process_face_mode(
|
|||||||
|
|
||||||
if not face_info:
|
if not face_info:
|
||||||
logger.debug("No face info for %s in asset %s", person.get("name"), asset.get("id"))
|
logger.debug("No face info for %s in asset %s", person.get("name"), asset.get("id"))
|
||||||
return None
|
return "no face metadata"
|
||||||
|
|
||||||
img_w, img_h = img.size
|
img_w, img_h = img.size
|
||||||
meta_w = face_info.get("imageWidth") or 0
|
meta_w = face_info.get("imageWidth") or 0
|
||||||
@@ -108,7 +111,7 @@ def process_face_mode(
|
|||||||
face_w, face_h = x2 - x1, y2 - y1
|
face_w, face_h = x2 - x1, y2 - y1
|
||||||
if face_w < min_width or face_h < min_width:
|
if face_w < min_width or face_h < min_width:
|
||||||
logger.debug("Face too small (%.1fx%.1f)", face_w, face_h)
|
logger.debug("Face too small (%.1fx%.1f)", face_w, face_h)
|
||||||
return None
|
return f"face too small ({face_w:.0f}x{face_h:.0f}px, min {min_width}px)"
|
||||||
|
|
||||||
# Re-detect face with InsightFace for landmark-based alignment.
|
# Re-detect face with InsightFace for landmark-based alignment.
|
||||||
# Immich's /api/faces endpoint does not include landmarks, so the
|
# Immich's /api/faces endpoint does not include landmarks, so the
|
||||||
|
|||||||
Reference in New Issue
Block a user