From 0ef15c5c120adcb3c91a00d1da47d13c6e39a351 Mon Sep 17 00:00:00 2001 From: Holden Date: Sun, 14 Jun 2026 17:47:30 +0000 Subject: [PATCH] chore: remove dead embedding paths and suppress insightface FutureWarning in image_processing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- README.md | 4 ++-- winnow/embeddings.py | 12 ++---------- winnow/image_processing.py | 5 ++++- winnow/immich_api.py | 11 +---------- 4 files changed, 9 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 432a6cc..3883647 100644 --- a/README.md +++ b/README.md @@ -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+ diff --git a/winnow/embeddings.py b/winnow/embeddings.py index 2c533d0..1f6ed46 100644 --- a/winnow/embeddings.py +++ b/winnow/embeddings.py @@ -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: diff --git a/winnow/image_processing.py b/winnow/image_processing.py index 7c77479..cc08bbe 100644 --- a/winnow/image_processing.py +++ b/winnow/image_processing.py @@ -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( diff --git a/winnow/immich_api.py b/winnow/immich_api.py index b3f7aed..59cac53 100644 --- a/winnow/immich_api.py +++ b/winnow/immich_api.py @@ -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),