- Config: remove _ConfigAccessor and ConfigManager; use __getattr__ for lazy
loading on single _Config class; re-register self as _instance in __getattr__
so reset() always clears the correct object (item 1)
- upload_tracker: replace hand-rolled JSON store with sqlite3; auto-migrates
existing JSON on first run; remove dead record_frigate_file function;
connection re-opens when CACHE_DIR changes for test isolation (items 2, 8)
- diversity: move ThreadPoolExecutor import to module level; inject optional
fetch_fn parameter for testability (items 3, 6)
- pyproject: consolidate 4 variant files into extras (gpu/rocm/intel/cpu);
update Dockerfile to use --extra flag; delete variant pyproject/lock files;
uv.lock needs regen with `uv lock` after this change (item 4)
- jobs: extract _build_job helper to separate business logic from terminal I/O;
auto_configure delegates dedup/selection to _build_job (item 5)
- logging: convert f-string log calls to % interpolation throughout all winnow/
modules (item 7)
- reconcile: new module with reconcile_frigate_mappings and
enrich_asset_with_face_data extracted from executor.py (item 9)
- scheduler: print next scheduled run time after startup and after each run;
fix f-string logger.error call (item 10)
- 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
winnow is a face recognition training tool. Object mode required manual
file placement with no Frigate API, pulled in torch/torchvision/transformers/
ultralytics (~2 GB), and was architecturally misaligned with the project goal.
Removed:
- process_object_mode (YOLO inference), get_yolo_model
- SigLIP model stack (get_siglip_model, get_object_embedding, batch variant)
- entity_type branching throughout diversity, embeddings, jobs, executor
- TRAINING_MODE and OBJECT_CLASS env vars
- torch, torchvision, transformers, ultralytics dependencies
- pytorch index entries from pyproject.toml
- Object mode from README (Modes section, env var table, How It Works)
Immich's /api/faces endpoint only returns bounding boxes, not facial
landmarks. This meant align_face() never fired and all crops fell back
to a plain bbox rectangle — producing partial crops (forehead-only,
off-angle faces) when Immich's detection was slightly off.
Now, when ENABLE_FACE_ALIGNMENT is true and InsightFace is loaded,
execute_jobs() passes the app to process_face_mode(). For each face,
it expands the Immich bbox by 50%, crops that search region, runs
InsightFace detection within it, and aligns the nearest face to the
standard ArcFace 112×112 format using norm_crop(). Falls back to bbox
crop if InsightFace finds no face in the search region.
The InsightFace model is already in GPU memory from the diversity/
embedding phase, so the singleton lookup adds no load cost.
Store (width, height) of each face crop in the tracker at upload time
alongside the existing blur score. Expose TRACE_CROP_SIZE=<px> to look
up which Immich asset produced a crop with that pixel dimension, making
it straightforward to trace unexpected or low-quality images visible in
Frigate back to their source.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New image variants:
- :rocm — InsightFace via ROCmExecutionProvider, SigLIP via PyTorch ROCm 6.3
- :intel — InsightFace via OpenVINOExecutionProvider (onnxruntime-openvino);
Intel GPU compute runtime auto-installed from Intel graphics repo;
OPENVINO_DEVICE=GPU opts into Arc/iGPU inference (default: CPU)
Also adds:
- pyproject-rocm.toml + uv-rocm.lock, pyproject-intel.toml + uv-intel.lock
- compose.yml device passthrough snippets for AMD and Intel
- CI: build-rocm and build-intel jobs in docker-publish.yml; all four
variants built and tagged in release.yml
- README reworked: cleaner structure, GPU variant quick-start examples,
OPENVINO_DEVICE env var documented
- CHANGELOG entry and version bump to 0.2.12
Fix: IntPrompt in dict literal was eagerly evaluated in the no-embedding
fallback path of _get_strategy_choice, prompting users for a custom count
regardless of which strategy they picked.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>