chore: remove remaining dead-mode references from executor, jobs, compose, Dockerfile

After removing the object pipeline, several dead 'mode' artifacts remained:

- executor.py: unpack `config` from job even though it was no longer read
- jobs.py: set `"mode": "face"` in both configure paths (key never consumed)
- compose.yml: TRAINING_MODE=face env, OBJECT_CLASS comment, HF_HOME, stale MAX_AUTO_IMAGES default note
- Dockerfile: "and object classification" label, /models/huggingface mkdir, HF_HOME ENV
This commit is contained in:
2026-06-14 17:42:27 +00:00
parent 2d7b52470e
commit 274d50ee99
5 changed files with 27 additions and 1569 deletions
+3 -3
View File
@@ -67,7 +67,7 @@ FROM base-${TARGETARCH}-${VARIANT} AS runtime
ARG VARIANT=gpu
ARG VERSION=dev
LABEL org.opencontainers.image.title="winnow" \
org.opencontainers.image.description="Selects diverse, high-quality photos from Immich as training data for Frigate face recognition and object classification." \
org.opencontainers.image.description="Selects diverse, high-quality photos from Immich as training data for Frigate face recognition." \
org.opencontainers.image.source="https://github.com/sudolulo/winnow" \
org.opencontainers.image.licenses="AGPL-3.0-or-later" \
org.opencontainers.image.version="${VERSION}"
@@ -116,7 +116,7 @@ https://repositories.intel.com/graphics/ubuntu jammy flex" \
fi
RUN groupadd -g 568 apps && useradd -u 568 -g apps -m -s /bin/bash appuser \
&& mkdir -p /models/.insightface /models/huggingface \
&& mkdir -p /models/.insightface \
&& chown -R appuser:apps /app /models
WORKDIR /app
@@ -124,7 +124,7 @@ USER appuser
# PYTHONPATH=/app makes the winnow package importable from the entry point script.
# uv sync builds the wheel before winnow/ is COPY'd, so site-packages has only
# the dist-info. Explicitly adding /app lets Python find winnow/__init__.py there.
ENV HF_HOME=/models/huggingface INSIGHTFACE_HOME=/models/.insightface PYTHONPATH=/app
ENV INSIGHTFACE_HOME=/models/.insightface PYTHONPATH=/app
HEALTHCHECK CMD test -f /app/entrypoint.sh || exit 1
ENTRYPOINT ["tini", "--", "/app/entrypoint.sh"]
+1 -6
View File
@@ -13,13 +13,9 @@ services:
# Set AUTO_MODE=true to force auto mode in an interactive terminal.
# To run interactively: docker exec -it winnow winnow
# - VERBOSE=true # Enable DEBUG-level console output
# TRAINING_MODE: face = upload to Frigate face recognition API
# object = save crops to output dir for manual Frigate placement
- TRAINING_MODE=face
# STRATEGY: auto = objective diversity (recommended), standard = 30 imgs, broad = 100 imgs
- STRATEGY=auto
# - LIMIT=50 # Custom image count; overrides STRATEGY preset
# - OBJECT_CLASS=dog # Object label for object mode (e.g. dog, cat, car)
# ── People Filtering ──────────────────────────────────────────────────
# - ONLY_PEOPLE=John,Jane # Comma-separated; process only these people
@@ -35,14 +31,13 @@ services:
# - USE_FULL_RESOLUTION=true # Use full-res images vs thumbnails (default: true)
# - MIN_CONFIDENCE=0.7 # Minimum face detection confidence (default: 0.7)
# - BLUR_THRESHOLD=100.0 # Laplacian blur threshold; lower = accept more blur (default: 100.0)
# - MAX_AUTO_IMAGES=80 # Hard cap on auto-diversity selection (default: 80)
# - MAX_AUTO_IMAGES=80 # Hard cap on auto-diversity selection (default: 20)
# ── Caching & Models ──────────────────────────────────────────────────
# - FORCE_CPU=true # Disable GPU, fall back to CPU
# - OPENVINO_DEVICE=GPU # Intel variant only: use Arc/iGPU instead of CPU (default: CPU)
# - ENABLE_CACHE=false # Disable embedding cache (default: true)
- CACHE_DIR=/app/.if_cache
- HF_HOME=/models/huggingface
- INSIGHTFACE_HOME=/models/.insightface
# ── Tracker overrides (one-shot, remove after use) ────────────────────
Generated
+1 -1536
View File
File diff suppressed because it is too large Load Diff
+20 -22
View File
@@ -173,11 +173,11 @@ def execute_jobs(jobs: list[dict]) -> None:
use_full_res = Config.USE_FULL_RESOLUTION
# Load InsightFace app for landmark-based crop alignment (face mode only).
# Load InsightFace app for landmark-based crop alignment.
# The model is already resident from the diversity/embedding phase, so this
# is just a singleton lookup — no load cost.
insightface_app = None
if any(j["config"].get("mode", "face") == "face" for j in jobs) and Config.ENABLE_FACE_ALIGNMENT:
if Config.ENABLE_FACE_ALIGNMENT:
try:
from .embeddings import get_insightface_app
@@ -196,8 +196,8 @@ def execute_jobs(jobs: list[dict]) -> None:
overall_task = progress.add_task("[green]Overall Progress", total=grand_total)
for job in jobs:
person, assets, config = job["person"], job["assets"], job["config"]
name, mode = person["name"], config.get("mode", "face")
person, assets = job["person"], job["assets"]
name = person["name"]
job_task = progress.add_task(f"Processing {name}...", total=len(assets))
try:
@@ -206,8 +206,7 @@ def execute_jobs(jobs: list[dict]) -> None:
logger.error(str(e))
continue
# Face crops are transient (uploaded then discarded); wipe before each run.
# Object crops are the deliverable; preserve them across runs.
if mode == "face" and os.path.isdir(person_dir):
if os.path.isdir(person_dir):
shutil.rmtree(person_dir)
os.makedirs(person_dir, exist_ok=True)
@@ -219,22 +218,21 @@ def execute_jobs(jobs: list[dict]) -> None:
count = 0
for asset in assets:
try:
# For face mode, enrich the asset with face bounding box data
# from the Immich faces API (not included in search/metadata results)
if mode == "face":
asset = _enrich_asset_with_face_data(asset, person)
# Skip download if detection confidence already disqualifies
# the asset — avoids fetching a large image we'll discard.
conf = asset.get("face_confidence")
if conf is not None and conf < Config.MIN_CONFIDENCE:
progress.console.print(
f"[yellow]Skipped {asset['id']}"
f" (detection confidence {conf:.2f} < {Config.MIN_CONFIDENCE})[/yellow]"
)
mark_rejected(asset["id"], person_name=name)
progress.advance(job_task)
progress.advance(overall_task)
continue
# Enrich the asset with face bounding box data from the Immich
# faces API (not included in search/metadata results).
asset = _enrich_asset_with_face_data(asset, person)
# Skip download if detection confidence already disqualifies
# the asset — avoids fetching a large image we'll discard.
conf = asset.get("face_confidence")
if conf is not None and conf < Config.MIN_CONFIDENCE:
progress.console.print(
f"[yellow]Skipped {asset['id']}"
f" (detection confidence {conf:.2f} < {Config.MIN_CONFIDENCE})[/yellow]"
)
mark_rejected(asset["id"], person_name=name)
progress.advance(job_task)
progress.advance(overall_task)
continue
# Use full-resolution for final output when configured
if use_full_res:
+2 -2
View File
@@ -125,7 +125,7 @@ def _configure_person(person: dict, people: list[dict]) -> dict | None:
name = person["name"]
console.print(f"\nSelected: [bold green]{name}[/bold green]")
config = {"name": name, "mode": "face", "quality_replacement": Config.QUALITY_REPLACEMENT}
config = {"name": name, "quality_replacement": Config.QUALITY_REPLACEMENT}
# Fetch and filter assets
years = IntPrompt.ask("Filter images older than (years)", default=Config.YEARS_FILTER)
@@ -240,7 +240,7 @@ def auto_configure(people: list[dict]) -> list[dict]:
jobs = []
for person in valid_people:
name = person["name"]
config = {"name": name, "mode": "face"}
config = {"name": name}
all_assets = fetch_all_assets(person)
recent_assets = filter_recent_assets(all_assets, years=Config.YEARS_FILTER)