fix: audit — score falsy-zero, crop person mismatch, adaptive cap, suppress_output, ldconfig glob, scheduler sleep
- immich_api: `score or confidence` treated 0.0 score as falsy; use explicit None check - diversity: same falsy-zero fix in _get_face_confidence - diversity: _crop_face_from_thumbnail scale loop now filters by person_id (was using first person's imageWidth/imageHeight regardless of target in group photos) - jobs: partially-trained auto mode kept limit="auto" for adaptive stopping, then caps result to remaining capacity (was converting to int, silently disabling FPS adaptive threshold and early-stop) - embeddings: _suppress_output finally block wraps first dup2 in try/finally so stderr is always restored even if stdout restore raises OSError - Dockerfile: ldconfig find uses python3.* glob instead of hardcoded python3.13 - scheduler: sleep until next_run instead of fixed 60s; eliminates late-fire jitter and unnecessary wakeups on long schedules Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -75,7 +75,7 @@ COPY --from=build /usr/local/bin/uv /usr/local/bin/uv
|
||||
# Register every nvidia pip-package lib/ directory with ldconfig so that
|
||||
# onnxruntime-gpu and torch can find libcudnn, libcublas, libcufft, etc.
|
||||
# without a hand-maintained LD_LIBRARY_PATH. Skipped silently on cpu builds.
|
||||
RUN find /app/.venv/lib/python3.13/site-packages/nvidia -type d -name "lib" \
|
||||
RUN find /app/.venv/lib/python3.*/site-packages/nvidia -type d -name "lib" \
|
||||
2>/dev/null > /etc/ld.so.conf.d/nvidia-pip.conf && ldconfig || true
|
||||
|
||||
RUN groupadd -g 568 apps && useradd -u 568 -g apps -m -s /bin/bash appuser \
|
||||
|
||||
+1
-1
@@ -49,4 +49,4 @@ while True:
|
||||
logger.error(f"winnow run failed: {e}", exc_info=True)
|
||||
print(f"winnow run failed: {e}", flush=True)
|
||||
next_run = cron.get_next(float)
|
||||
time.sleep(60)
|
||||
time.sleep(max(1, next_run - time.time()))
|
||||
|
||||
+6
-2
@@ -105,7 +105,9 @@ def _get_face_confidence(asset: dict, person_id: str | None = None) -> float | N
|
||||
continue
|
||||
faces = person.get("faces", [])
|
||||
if faces:
|
||||
return faces[0].get("score") or faces[0].get("confidence")
|
||||
f = faces[0]
|
||||
score = f.get("score")
|
||||
return score if score is not None else f.get("confidence")
|
||||
return None
|
||||
|
||||
|
||||
@@ -136,8 +138,10 @@ def _crop_face_from_thumbnail(
|
||||
x1, y1, x2, y2 = bbox
|
||||
img_w, img_h = img.size
|
||||
|
||||
# Get metadata dimensions to scale bbox
|
||||
# Get metadata dimensions to scale bbox — must match the same person as _get_face_bbox
|
||||
for person in asset.get("people", []):
|
||||
if person_id and person.get("id") != person_id:
|
||||
continue
|
||||
faces = person.get("faces", [])
|
||||
if faces:
|
||||
meta_w = faces[0].get("imageWidth") or img_w
|
||||
|
||||
@@ -33,8 +33,10 @@ def _suppress_output():
|
||||
os.dup2(devnull_fd, 2)
|
||||
yield
|
||||
finally:
|
||||
os.dup2(saved_out, 1)
|
||||
os.dup2(saved_err, 2)
|
||||
try:
|
||||
os.dup2(saved_out, 1)
|
||||
finally:
|
||||
os.dup2(saved_err, 2)
|
||||
os.close(devnull_fd)
|
||||
os.close(saved_out)
|
||||
os.close(saved_err)
|
||||
|
||||
@@ -140,10 +140,11 @@ def fetch_face_data(asset_id: str, person_id: str | None = None) -> FaceData | N
|
||||
face.get("boundingBoxY2", 0),
|
||||
)
|
||||
|
||||
score = face.get("score")
|
||||
return FaceData(
|
||||
embedding=embedding,
|
||||
bbox=bbox,
|
||||
confidence=face.get("score") or face.get("confidence"),
|
||||
confidence=score if score is not None else face.get("confidence"),
|
||||
image_width=face.get("imageWidth", 0),
|
||||
image_height=face.get("imageHeight", 0),
|
||||
)
|
||||
|
||||
+7
-2
@@ -299,10 +299,13 @@ def auto_configure(people: list[dict]) -> list[dict]:
|
||||
has_embedding = is_embedding_available(entity_type)
|
||||
limit, selection_mode = _resolve_strategy(strategy, has_embedding)
|
||||
|
||||
# Cap selection to remaining capacity
|
||||
# Cap selection to remaining capacity.
|
||||
# For auto mode with partial training, keep "auto" so adaptive stopping
|
||||
# still runs — just trim the result to the remaining capacity afterward.
|
||||
auto_cap = None
|
||||
if limit == "auto":
|
||||
if already_uploaded > 0:
|
||||
limit = capacity # partially filled — select exactly what remains
|
||||
auto_cap = capacity
|
||||
else:
|
||||
limit = min(limit, capacity)
|
||||
|
||||
@@ -312,6 +315,8 @@ def auto_configure(people: list[dict]) -> list[dict]:
|
||||
selected_assets = _perform_selection(
|
||||
recent_assets, limit, name, selection_mode, entity_type, person_id=person["id"]
|
||||
)
|
||||
if auto_cap is not None:
|
||||
selected_assets = selected_assets[:auto_cap]
|
||||
|
||||
if selected_assets:
|
||||
rprint(f" [green]Queued {len(selected_assets)} images for {name}.[/green]")
|
||||
|
||||
Reference in New Issue
Block a user