-
Notifications
You must be signed in to change notification settings - Fork 0
Setup
- Immich v1.106+ with face recognition enabled and people tagged
- Frigate v0.16+
- Docker with the appropriate GPU runtime (optional but strongly recommended)
- Open Immich → Account Settings → API Keys
- Click New API Key, give it a name (e.g.
winnow), copy the key
This is the base URL of your Frigate instance, e.g. http://192.168.1.10:5000.
| Tag | Arch | Acceleration |
|---|---|---|
:latest |
amd64 | NVIDIA CUDA 12.8 · requires NVIDIA Container Toolkit · minimum driver 570 |
:rocm |
amd64 | AMD ROCm · pass /dev/kfd + /dev/dri
|
:intel |
amd64 | Intel Arc / iGPU via OpenVINO · pass /dev/dri, set OPENVINO_DEVICE=GPU
|
:cpu |
amd64 + arm64 | CPU only · ~300 MB smaller · no GPU required |
Use :cpu if your host has no supported GPU. Set a container memory limit of at least 2 GB (mem_limit: 2g) — the InsightFace model needs roughly 600 MB–1 GB.
Copy compose.yml to a directory on your host:
mkdir winnow && cd winnow
curl -O https://raw.githubusercontent.com/sudolulo/winnow/main/compose.ymlCreate a .env file with your values:
IMMICH_URL=http://192.168.1.10:2283
API_KEY=your-immich-api-key
FRIGATE_URL=http://192.168.1.10:5000Edit the volume paths in compose.yml to point to directories on your host where models, cache, and output should be stored:
volumes:
- /your/path/to/models:/models
- /your/path/to/data:/app/data
- /your/path/to/output:/app/frigate_trainThese directories will be created automatically by Docker if they don't exist.
Start it:
docker compose up -dLogs:
docker compose logs -f winnowOn the first run, winnow downloads InsightFace Buffalo_L (~300 MB) if it isn't already cached in the models volume. Subsequent runs start immediately using the cached model.
CRON_SCHEDULE controls both the run schedule and container lifetime:
CRON_SCHEDULE |
Behaviour |
|---|---|
| (unset) | Run once on startup, then exit |
| (empty string) | Stay alive; trigger manually with docker exec -it winnow winnow
|
| Cron expression | Run on startup, then repeat on schedule |
Example — every Sunday at 3 AM:
CRON_SCHEDULE=0 3 * * 0
In scheduled mode the process (and loaded model) stays resident between runs, so each subsequent run starts immediately without re-loading the model.
Include the deploy block in your compose.yml (present in the example) and ensure the NVIDIA Container Toolkit is installed on your host:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]Verify GPU access:
docker run --rm --gpus all nvidia/cuda:12.8.1-base-ubuntu22.04 nvidia-smiUse image: ghcr.io/sudoludo/winnow:rocm and replace the deploy: block with:
devices:
- /dev/kfd
- /dev/dri
group_add:
- video
- renderUse image: ghcr.io/sudoludo/winnow:intel and replace the deploy: block with:
devices:
- /dev/dri
group_add:
- render
environment:
- OPENVINO_DEVICE=GPU # omit to run OpenVINO inference on CPU (default)OPENVINO_DEVICE selects the compute device within the OpenVINO runtime:
-
GPU— Intel iGPU or Arc GPU (requires/dev/dripassthrough) -
CPU(default) — CPU cores via OpenVINO's CPU plugin (optimised AVX512 kernels). This is not the iGPU — it runs on the CPU itself but is meaningfully faster than standard CPUExecutionProvider.
Set VERBOSE=true in your .env to enable DEBUG-level output on the console. The log file always captures DEBUG regardless of this setting:
# log file is written to the output volume (frigate_train mount)
docker exec winnow cat /app/frigate_train/winnow.log| Variable | Default | Description |
|---|---|---|
IMMICH_URL |
(required) | Full URL to your Immich instance |
API_KEY |
(required) | Immich API key |
FRIGATE_URL |
(unset) | Frigate base URL — required for face upload |
| Variable | Default | Description |
|---|---|---|
STRATEGY |
adaptive |
adaptive — embedding-based diversity selection, stops when candidates become redundant; standard — fixed 30 images; broad — fixed 100 images. auto is a legacy alias for adaptive
|
LIMIT |
(unset) | Exact image count — overrides STRATEGY preset |
AUTO_MODE |
(auto) | Force non-interactive mode in a terminal; auto-detected otherwise |
VERBOSE |
false |
Enable DEBUG-level console output (log file is always DEBUG) |
| Variable | Default | Description |
|---|---|---|
ONLY_PEOPLE |
(unset) | Comma-separated whitelist — process only these people |
SKIP_PEOPLE |
(unset) | Comma-separated list — skip these people |
MIN_FACE_COUNT |
3 |
Skip people with fewer than N tagged assets in Immich |
YEARS_FILTER |
10 |
Ignore images older than N years |
MERGE_DUPLICATE_PEOPLE |
false |
When Immich has multiple person records with the same name (a common side-effect of face clustering), winnow warns and processes only the largest. Set true to permanently merge duplicates in Immich instead. This modifies Immich and cannot be undone — only enable once you've verified the duplicates are the same person. |
| Variable | Default | Description |
|---|---|---|
MAX_AUTO_IMAGES |
20 |
Maximum training images per person winnow will manage in Frigate. Conservative default so winnow supplements — not dominates — a training set that should be primarily built from manually curated images |
QUALITY_REPLACEMENT |
true |
When at cap, swap a weaker tracked image for a better candidate. With Frigate scoring active, targets the most redundant image (highest pre-upload recognize score); otherwise uses blur score. Never touches manually added Frigate files. Set false to skip people at cap |
The following variables control quality thresholds that are pre-calibrated for Frigate's ArcFace requirements. Changing them may cause image quality issues. Support will not be provided for problems caused by non-default values.
| Variable | Default | Description |
|---|---|---|
ENABLE_FRIGATE_SCORES |
true |
Call Frigate's recognize endpoint pre-upload to store diversity scores used for quality replacement. Adds ~200 ms per upload. Disable to use blur-score replacement only |
FRIGATE_SCORE_CEILING |
(unset) | Below-cap novelty gate. Unset (default): dynamic — skips candidates whose Frigate score exceeds the most-redundant tracked file's score, accounting for manually-added images. 0: disabled. Positive value (e.g. 0.85): fixed hard ceiling. Requires ENABLE_FRIGATE_SCORES=true and at least one prior run |
MIN_FACE_WIDTH |
90 |
Minimum face crop width in pixels |
FACE_MARGIN |
0.15 |
Padding around bounding box crop (fraction of face size) |
ENABLE_FACE_ALIGNMENT |
true |
Align to ArcFace 112×112 format using facial landmarks |
USE_FULL_RESOLUTION |
true |
Download full-resolution originals rather than preview thumbnails |
MIN_CONFIDENCE |
0.7 |
Minimum Immich face detection confidence |
BLUR_THRESHOLD |
120.0 |
Laplacian variance threshold — higher rejects more blur |
| Variable | Default | Description |
|---|---|---|
FORCE_CPU |
false |
Disable GPU — fall back to CPU for all inference |
OPENVINO_DEVICE |
CPU |
Intel variant only: GPU = iGPU/Arc (requires /dev/dri); CPU = OpenVINO CPU plugin (optimised CPU kernels, not the iGPU) |
ENABLE_CACHE |
true |
Cache computed embeddings to disk (speeds up re-runs on the same library) |
DATA_DIR |
data |
Path for embedding cache and upload tracker database (winnow_tracker.db) |
INSIGHTFACE_HOME |
(system) | InsightFace model cache path (Buffalo_L) |
| Variable | Default | Description |
|---|---|---|
OUTPUT_DIR |
./frigate_train |
Directory where face crops are staged before upload and where winnow.log is written. In Docker, set this via the volume mount instead |
| Variable | Default | Description |
|---|---|---|
DRY_RUN |
false |
Preview selection without downloading or uploading |
RETRY_REJECTED |
false |
Re-attempt assets previously rejected by Frigate |
RESET_PERSON |
(unset) | Set to a person's name to clear their upload history and delete their winnow-managed Frigate training files so the next run starts fresh. Set to * to reset all tracked people at once. Manually added Frigate files are never touched |
TRACE_CROP_SIZE |
(unset) | Debug: print all tracked crops whose width or height matches this pixel value, then exit |
| Variable | Default | Description |
|---|---|---|
CRON_SCHEDULE |
(unset) | Unset = run once and exit; empty string = stay alive; cron expression = scheduled |