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 VARIANT=gpu
ARG VERSION=dev ARG VERSION=dev
LABEL org.opencontainers.image.title="winnow" \ 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.source="https://github.com/sudolulo/winnow" \
org.opencontainers.image.licenses="AGPL-3.0-or-later" \ org.opencontainers.image.licenses="AGPL-3.0-or-later" \
org.opencontainers.image.version="${VERSION}" org.opencontainers.image.version="${VERSION}"
@@ -116,7 +116,7 @@ https://repositories.intel.com/graphics/ubuntu jammy flex" \
fi fi
RUN groupadd -g 568 apps && useradd -u 568 -g apps -m -s /bin/bash appuser \ 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 && chown -R appuser:apps /app /models
WORKDIR /app WORKDIR /app
@@ -124,7 +124,7 @@ USER appuser
# PYTHONPATH=/app makes the winnow package importable from the entry point script. # 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 # 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. # 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 HEALTHCHECK CMD test -f /app/entrypoint.sh || exit 1
ENTRYPOINT ["tini", "--", "/app/entrypoint.sh"] 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. # Set AUTO_MODE=true to force auto mode in an interactive terminal.
# To run interactively: docker exec -it winnow winnow # To run interactively: docker exec -it winnow winnow
# - VERBOSE=true # Enable DEBUG-level console output # - 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 = objective diversity (recommended), standard = 30 imgs, broad = 100 imgs
- STRATEGY=auto - STRATEGY=auto
# - LIMIT=50 # Custom image count; overrides STRATEGY preset # - LIMIT=50 # Custom image count; overrides STRATEGY preset
# - OBJECT_CLASS=dog # Object label for object mode (e.g. dog, cat, car)
# ── People Filtering ────────────────────────────────────────────────── # ── People Filtering ──────────────────────────────────────────────────
# - ONLY_PEOPLE=John,Jane # Comma-separated; process only these people # - 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) # - USE_FULL_RESOLUTION=true # Use full-res images vs thumbnails (default: true)
# - MIN_CONFIDENCE=0.7 # Minimum face detection confidence (default: 0.7) # - 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) # - 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 ────────────────────────────────────────────────── # ── Caching & Models ──────────────────────────────────────────────────
# - FORCE_CPU=true # Disable GPU, fall back to CPU # - FORCE_CPU=true # Disable GPU, fall back to CPU
# - OPENVINO_DEVICE=GPU # Intel variant only: use Arc/iGPU instead of CPU (default: CPU) # - OPENVINO_DEVICE=GPU # Intel variant only: use Arc/iGPU instead of CPU (default: CPU)
# - ENABLE_CACHE=false # Disable embedding cache (default: true) # - ENABLE_CACHE=false # Disable embedding cache (default: true)
- CACHE_DIR=/app/.if_cache - CACHE_DIR=/app/.if_cache
- HF_HOME=/models/huggingface
- INSIGHTFACE_HOME=/models/.insightface - INSIGHTFACE_HOME=/models/.insightface
# ── Tracker overrides (one-shot, remove after use) ──────────────────── # ── Tracker overrides (one-shot, remove after use) ────────────────────
Generated
+1 -1536
View File
File diff suppressed because it is too large Load Diff
+7 -9
View File
@@ -173,11 +173,11 @@ def execute_jobs(jobs: list[dict]) -> None:
use_full_res = Config.USE_FULL_RESOLUTION 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 # The model is already resident from the diversity/embedding phase, so this
# is just a singleton lookup — no load cost. # is just a singleton lookup — no load cost.
insightface_app = None 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: try:
from .embeddings import get_insightface_app 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) overall_task = progress.add_task("[green]Overall Progress", total=grand_total)
for job in jobs: for job in jobs:
person, assets, config = job["person"], job["assets"], job["config"] person, assets = job["person"], job["assets"]
name, mode = person["name"], config.get("mode", "face") name = person["name"]
job_task = progress.add_task(f"Processing {name}...", total=len(assets)) job_task = progress.add_task(f"Processing {name}...", total=len(assets))
try: try:
@@ -206,8 +206,7 @@ def execute_jobs(jobs: list[dict]) -> None:
logger.error(str(e)) logger.error(str(e))
continue continue
# Face crops are transient (uploaded then discarded); wipe before each run. # Face crops are transient (uploaded then discarded); wipe before each run.
# Object crops are the deliverable; preserve them across runs. if os.path.isdir(person_dir):
if mode == "face" and os.path.isdir(person_dir):
shutil.rmtree(person_dir) shutil.rmtree(person_dir)
os.makedirs(person_dir, exist_ok=True) os.makedirs(person_dir, exist_ok=True)
@@ -219,9 +218,8 @@ def execute_jobs(jobs: list[dict]) -> None:
count = 0 count = 0
for asset in assets: for asset in assets:
try: try:
# For face mode, enrich the asset with face bounding box data # Enrich the asset with face bounding box data from the Immich
# from the Immich faces API (not included in search/metadata results) # faces API (not included in search/metadata results).
if mode == "face":
asset = _enrich_asset_with_face_data(asset, person) asset = _enrich_asset_with_face_data(asset, person)
# Skip download if detection confidence already disqualifies # Skip download if detection confidence already disqualifies
# the asset — avoids fetching a large image we'll discard. # the asset — avoids fetching a large image we'll discard.
+2 -2
View File
@@ -125,7 +125,7 @@ def _configure_person(person: dict, people: list[dict]) -> dict | None:
name = person["name"] name = person["name"]
console.print(f"\nSelected: [bold green]{name}[/bold green]") 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 # Fetch and filter assets
years = IntPrompt.ask("Filter images older than (years)", default=Config.YEARS_FILTER) 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 = [] jobs = []
for person in valid_people: for person in valid_people:
name = person["name"] name = person["name"]
config = {"name": name, "mode": "face"} config = {"name": name}
all_assets = fetch_all_assets(person) all_assets = fetch_all_assets(person)
recent_assets = filter_recent_assets(all_assets, years=Config.YEARS_FILTER) recent_assets = filter_recent_assets(all_assets, years=Config.YEARS_FILTER)