chore: remove dead embedding paths and suppress insightface FutureWarning in image_processing

- immich_api.py: remove FaceData.embedding field and numpy import — Immich face
  embeddings were never consumed after being fetched; bbox/confidence is all that's used
- embeddings.py: remove immich_embedding param from get_embedding() — never passed by
  any caller; simplify docstring accordingly
- image_processing.py: wrap insightface_app.get() in warnings filter to suppress the
  scikit-image FutureWarning about estimate being deprecated (already suppressed in
  embeddings.py for the diversity path, was leaking from the crop-alignment path)
- README.md: remove two stale "object mode" / "object classification" fragments
This commit is contained in:
2026-06-14 17:47:30 +00:00
parent 72dbbfa18a
commit 0ef15c5c12
4 changed files with 9 additions and 23 deletions
+2 -2
View File
@@ -9,7 +9,7 @@
**Docs:** [Setup](https://github.com/sudolulo/winnow/wiki/Setup) · [Troubleshooting](https://github.com/sudolulo/winnow/wiki/Troubleshooting) · [FAQ](https://github.com/sudolulo/winnow/wiki/FAQ)
`winnow` pulls photos from your [Immich](https://immich.app) library, selects the most diverse and highest-quality subset using AI embeddings, and delivers them as training data for [Frigate](https://frigate.video)'s face recognition and object classification models.
`winnow` pulls photos from your [Immich](https://immich.app) library, selects the most diverse and highest-quality subset using AI embeddings, and delivers them as training data for [Frigate](https://frigate.video)'s face recognition.
The best Frigate training data is images you curate manually — photos taken specifically for recognition, in controlled conditions, uploaded directly through Frigate's UI. For people you can do that for, do it. winnow is for everyone else: people in your library you want Frigate to recognise but don't have dedicated training photos for. It mines your existing Immich library for the most diverse spread of real-world appearances and fills the gap.
@@ -257,7 +257,7 @@ When run with a terminal attached, winnow starts an interactive session: select
## Requirements
- **Immich** v1.106+
- **Frigate** v0.16+ (face mode only — object mode has no Frigate dependency)
- **Frigate** v0.16+
- **GPU** recommended: NVIDIA (CUDA), AMD (ROCm), or Intel (Arc / iGPU via OpenVINO)
- **Python** 3.13+
+2 -10
View File
@@ -205,25 +205,17 @@ def get_face_embedding(img_pil: Image.Image) -> np.ndarray | None:
def get_embedding(
img_pil: Image.Image,
asset_id: str | None = None,
immich_embedding: np.ndarray | None = None,
) -> np.ndarray | None:
"""Get embedding for a face image.
Priority:
1. Pre-fetched Immich embedding (if provided)
2. Disk cache (if enabled and asset_id provided)
3. Local InsightFace computation
Checks disk cache first (if enabled and asset_id provided),
then falls back to local InsightFace computation.
"""
from .config import Config
use_cache = Config.ENABLE_CACHE and asset_id is not None
cache = get_cache(Config.CACHE_DIR) if use_cache else None
if immich_embedding is not None:
if cache:
cache.put(asset_id, immich_embedding, "insightface")
return immich_embedding
if cache:
cached = cache.get(asset_id, "insightface")
if cached is not None:
+4 -1
View File
@@ -2,6 +2,7 @@
import logging
import os
import warnings
import numpy as np
from PIL import Image
@@ -113,7 +114,9 @@ def process_face_mode(
min(img_h, y2 + pad_y),
)
search_crop = img.crop(search_box)
detected = insightface_app.get(np.asarray(search_crop))
with warnings.catch_warnings():
warnings.filterwarnings("ignore", message=".*estimate.*is deprecated", category=FutureWarning)
detected = insightface_app.get(np.asarray(search_crop))
if detected:
cx, cy = search_crop.width / 2, search_crop.height / 2
best = min(
+1 -10
View File
@@ -5,7 +5,6 @@ from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from io import BytesIO
import numpy as np
import requests
from PIL import Image, ImageOps
@@ -21,7 +20,6 @@ _MAX_ASSETS_PER_PERSON = 5000 # Stop fetching after this many — diversity poo
class FaceData:
"""Pre-computed face data from Immich."""
embedding: np.ndarray | None
bbox: tuple[float, float, float, float] # (x1, y1, x2, y2)
confidence: float | None
image_width: int
@@ -120,7 +118,7 @@ def fetch_face_data(asset_id: str, person_id: str | None = None) -> FaceData | N
person_id: Optional person ID to match the specific face
Returns:
FaceData with embedding, bbox, and confidence, or None if unavailable
FaceData with bbox and confidence, or None if unavailable
"""
try:
resp = requests.get(
@@ -148,12 +146,6 @@ def fetch_face_data(asset_id: str, person_id: str | None = None) -> FaceData | N
if face is None:
face = faces[0] # Fall back to first/largest face
# Extract embedding if available
embedding = None
if "embedding" in face:
embedding = np.array(face["embedding"], dtype=np.float32)
# Extract bounding box
bbox = (
face.get("boundingBoxX1", 0),
face.get("boundingBoxY1", 0),
@@ -163,7 +155,6 @@ def fetch_face_data(asset_id: str, person_id: str | None = None) -> FaceData | N
score = face.get("score")
return FaceData(
embedding=embedding,
bbox=bbox,
confidence=score if score is not None else face.get("confidence"),
image_width=face.get("imageWidth", 0),