After each successful upload, call POST /api/faces/recognize to get
Frigate's own confidence score (0-1) for the uploaded crop. Store it
in the tracker as frigate_scores alongside the existing blur score.
When quality replacement activates and frigate_scores are present,
use them for the replacement comparison instead of blur scores — an
image Frigate recognizes poorly is a worse training image than one it
recognizes well, regardless of sharpness. Falls back to blur scores
on first run before any frigate_scores are populated.
Also surfaces frigate_score in TRACE_CROP_SIZE output.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Frigate classifies images with Laplacian variance < 120 as "very blurry"
and its own docs recommend avoiding blurry training data. Winnow was
accepting images in the 100-120 range that Frigate considers too blurry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
50px crops produce ~2,500–4,225 total pixels — well below Frigate's own
camera capture range of 16k–50k px. 90px guarantees ≥8,100 total pixels
even when face margins are fully clipped by image edges.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Was >=4.57.6; installed version is 5.12.0. Prevents users from
accidentally resolving the old 4.x series.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
setup-qemu-action v3 → v4, build-push-action v6 → v7
Required before June 16 when Node.js 20 actions are forced to Node.js 24
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- quality.py:122: split long tuple line to satisfy E501 (146 → ≤120)
- update-lockfile.yml: add branches filter so tag pushes don't trigger
the workflow (tag checkout is detached HEAD; git push has no target)
- release.yml: split four Docker build steps into parallel jobs (build-gpu,
build-cpu, build-rocm, build-intel), each with its own runner; previously
all four ran in one job and exhausted disk after GPU+CPU builds, leaving
ROCm and Intel cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
C1/C4: Cap blur-score computation at 1440 px before calling assess_quality
so scores are always on the same Laplacian scale as the embedding path
(which operates on Immich preview thumbnails). Also converts the image to
RGB before scoring and stores 0.0 on assess_quality failure so files
uploaded without a score remain eligible for future quality replacement
instead of occupying a slot permanently.
C2: Fall back to the tracker's mapped-filename set as the pre-upload
baseline when the Frigate GET /api/faces endpoint is unreachable at upload
start. Previously, uploads that succeeded during a partial API outage were
never mapped in frigate_files, leaving get_tracked_frigate_file_count
permanently under-counting those files and allowing Frigate to exceed
MAX_AUTO_IMAGES over time.
C3: Track min_quality_score_for_slot when a quality-replacement delete
succeeds but the subsequent upload fails. This ensures the freed slot can
only be filled by a candidate that beats the deleted file's score, not just
the next file in iteration order (which could be lower quality than what
was deleted).
Add get_tracked_frigate_filenames() to upload_tracker and expand tracker
tests to cover the new function and exclude-parameter behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pipeline rewritten as 8 steps: separates thumbnail pass (quality filter +
embeddings) from full-res download and crop; adds quality replacement
logic at the upload step
- Persistence note updated to cover rejected IDs and RETRY_REJECTED
- Auto mode stopping threshold documented (20%/10% of median pairwise distance)
- Add OUTPUT_DIR env var (was undocumented)
- Local Install section notes interactive vs auto mode behaviour
- QUALITY_REPLACEMENT description tightened
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- update-lockfile.yml: rename workflow to "Update lockfiles"
- release.yml: add explicit uv python install 3.13 for consistency with other workflows
- docker-publish.yml: rename cpu cache scope from linux/amd64-cpu to cpu (now multi-arch)
- README: add GitHub release version badge and License badge
- README: shorten QUALITY_REPLACEMENT table cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- update-lockfile.yml: regenerate all four lockfiles (main + cpu/rocm/intel
variants) on any pyproject change; add variant pyproject files to trigger
- docker-publish.yml: add lockfiles to paths-ignore so the bot commit does
not trigger a second Docker build; remove redundant CONTRIBUTING/SECURITY
entries already covered by **.md; add QEMU to build-cpu; set CPU image to
linux/amd64,linux/arm64 to match release
- release.yml: inline lockfile generation now covers all variants; add
workflow_dispatch guard that fails if not dispatched from main
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile: ARG VERSION + OCI labels (title, description, source, licenses, version)
- release.yml: pass VERSION build-arg to all four image builds
- docker-publish.yml: extend paths-ignore to cover community files
- .github/ISSUE_TEMPLATE/config.yml: disable blank issues, link to Discussions and wiki
- README.md: add Getting Help section
- pyproject.toml: expand description; add System Administrators audience classifier
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
C1/C4: Cap blur-score computation at 1440 px before calling assess_quality
so scores are always on the same Laplacian scale as the embedding path
(which operates on Immich preview thumbnails). Also converts the image to
RGB before scoring and stores 0.0 on assess_quality failure so files
uploaded without a score remain eligible for future quality replacement
instead of occupying a slot permanently.
C2: Fall back to the tracker's mapped-filename set as the pre-upload
baseline when the Frigate GET /api/faces endpoint is unreachable at upload
start. Previously, uploads that succeeded during a partial API outage were
never mapped in frigate_files, leaving get_tracked_frigate_file_count
permanently under-counting those files and allowing Frigate to exceed
MAX_AUTO_IMAGES over time.
C3: Track min_quality_score_for_slot when a quality-replacement delete
succeeds but the subsequent upload fails. This ensures the freed slot can
only be filled by a candidate that beats the deleted file's score, not just
the next file in iteration order (which could be lower quality than what
was deleted).
Add get_tracked_frigate_filenames() to upload_tracker and expand tracker
tests to cover the new function and exclude-parameter behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pipeline rewritten as 8 steps: separates thumbnail pass (quality filter +
embeddings) from full-res download and crop; adds quality replacement
logic at the upload step
- Persistence note updated to cover rejected IDs and RETRY_REJECTED
- Auto mode stopping threshold documented (20%/10% of median pairwise distance)
- Add OUTPUT_DIR env var (was undocumented)
- Local Install section notes interactive vs auto mode behaviour
- QUALITY_REPLACEMENT description tightened
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- update-lockfile.yml: rename workflow to "Update lockfiles"
- release.yml: add explicit uv python install 3.13 for consistency with other workflows
- docker-publish.yml: rename cpu cache scope from linux/amd64-cpu to cpu (now multi-arch)
- README: add GitHub release version badge and License badge
- README: shorten QUALITY_REPLACEMENT table cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- update-lockfile.yml: regenerate all four lockfiles (main + cpu/rocm/intel
variants) on any pyproject change; add variant pyproject files to trigger
- docker-publish.yml: add lockfiles to paths-ignore so the bot commit does
not trigger a second Docker build; remove redundant CONTRIBUTING/SECURITY
entries already covered by **.md; add QEMU to build-cpu; set CPU image to
linux/amd64,linux/arm64 to match release
- release.yml: inline lockfile generation now covers all variants; add
workflow_dispatch guard that fails if not dispatched from main
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile: ARG VERSION + OCI labels (title, description, source, licenses, version)
- release.yml: pass VERSION build-arg to all four image builds
- docker-publish.yml: extend paths-ignore to cover community files
- .github/ISSUE_TEMPLATE/config.yml: disable blank issues, link to Discussions and wiki
- README.md: add Getting Help section
- pyproject.toml: expand description; add System Administrators audience classifier
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, effective_count and the jobs.py cap check used the total
Frigate file count (including manually-added files), so any file a user
curated by hand ate into winnow's managed quota. Now:
- get_tracked_frigate_file_count() returns len(frigate_files) from the
tracker — only files winnow uploaded and reconciled
- effective_count in the upload loop uses this tracker count so
manually-added files are invisible to the cap
- jobs.py capacity check uses len(frigate_files) instead of the live
Frigate API count or cached frigate_count
- Frigate API call for known_frigate_files_at_start is now only used
for the post-upload reconciliation diff, not for cap enforcement
Side-effect: fixes audit bug #1 — an unreachable Frigate GET no longer
zeroes effective_count and bypasses the cap, because the cap is now
read from the always-available local tracker.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds quality replacement for Frigate face training images:
- When a person is at MAX_AUTO_IMAGES cap, replace the lowest-quality
mapped Frigate file if a better candidate is available
- Quality score is laplacian blur variance from the quality filtering
pipeline (stored on asset, propagated through tracker)
- Frigate filename mapping uses post-person batch reconciliation:
poll after all uploads complete, map by filename timestamp order
(documented race condition limitation in code)
- QUALITY_REPLACEMENT env var (default true) to disable the feature
- Bug fix: f-string TypeError when no mapped files exist (worst=None)
- CI: more aggressive runner disk cleanup to fix NVIDIA build OOM
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Config tests now verify QUALITY_REPLACEMENT defaults to True and
respects the QUALITY_REPLACEMENT=false env override.
CI: replace minimal disk cleanup with more aggressive removal
(Android SDK ~14GB, Swift, CodeQL, docker system prune) so the
NVIDIA GPU image build no longer exhausts runner disk space.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When no mapped files exist for a person, worst is None and the old
f-string tried to subscript it before the conditional was evaluated.
Extracted worst_score_str as a local variable to avoid the crash.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Track laplacian blur score through quality filtering pipeline
(quality.py: blur_score on QualityResult; diversity.py: store on asset;
executor.py: read via quality_score key)
- Replace per-file polling with post-person batch reconciliation:
after all uploads for a person complete, poll Frigate (up to 15s)
until the expected number of new files appear, then map by filename
timestamp order (Frigate FIFO queue = upload order = timestamp order)
- Document race condition limitation: concurrent external uploads cause
the batch to be skipped entirely (safe but files go unmapped); noted
in code as requiring a Frigate API fix (return filename on upload)
- Add two assess_quality integration tests for blur_score
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Intel renamed libze-intel-gpu1 to level-zero in their graphics repository,
breaking the amd64 Intel GPU image build.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Intel renamed libze-intel-gpu1 to level-zero in their graphics repository,
breaking the amd64 Intel GPU image build.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>