8419ebd761
The last piece: a Dockerised desktop-GPU worker that talks to FC ONLY over HTTP (lease → fetch pixels → detect figures + CCIP-embed → submit), so Redis/Postgres stay private. New top-level agent/ (outside CI scope — verified by running it): - fc_agent/worker.py: the lease/compute/submit loop, concurrency 1, start/pause/ stop (stop frees the card; unprocessed leases expire + re-queue). - fc_agent/models.py: imgutils wrappers — detect_person (figures) + CCIP embed. The two API seams to verify against the installed dghs-imgutils (flagged). - fc_agent/media.py: stills + video frame sampling (ffmpeg) at FC's cadence → per-frame instances (the bag). - fc_agent/crops.py: vendored crop primitive. client.py: the FC HTTP client. - fc_agent/app.py: FastAPI localhost control UI (start/pause/stop + progress + queue depth). Dockerfile (CUDA + onnxruntime-gpu + ffmpeg) + requirements + README (token → build → run --gpus all → Start; CPU-fallback path). This completes the CCIP pipeline end to end: agent produces region CCIP vectors → RegionService stores → matcher suggests characters → rail. Verified by running on the desktop (not CI). README calls out the imgutils API + model-string checks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttrj5P7upUTueSfoJcxEqa
37 lines
1.2 KiB
Python
37 lines
1.2 KiB
Python
"""Crop primitive — vendored from backend/app/services/ml/crops.py so the agent
|
|
is self-contained. Keep in sync if the floor logic changes."""
|
|
from PIL import Image
|
|
|
|
MIN_CROP_FRACTION = 0.10
|
|
MIN_CROP_PX = 64
|
|
|
|
|
|
def crop_region(
|
|
img: Image.Image,
|
|
bbox: tuple[float, float, float, float],
|
|
*,
|
|
pad: float = 0.0,
|
|
min_fraction: float = MIN_CROP_FRACTION,
|
|
min_px: int = MIN_CROP_PX,
|
|
) -> Image.Image | None:
|
|
"""Crop a NORMALIZED bbox (x, y, w, h in [0,1]); None if below the size
|
|
floor (max of a fraction-of-short-side and an absolute pixel floor)."""
|
|
iw, ih = img.size
|
|
x, y, w, h = bbox
|
|
px, py, pw, ph = x * iw, y * ih, w * iw, h * ih
|
|
if pad:
|
|
px -= pw * pad / 2.0
|
|
py -= ph * pad / 2.0
|
|
pw *= (1.0 + pad)
|
|
ph *= (1.0 + pad)
|
|
left = max(0, int(round(px)))
|
|
top = max(0, int(round(py)))
|
|
right = min(iw, int(round(px + pw)))
|
|
bottom = min(ih, int(round(py + ph)))
|
|
if right <= left or bottom <= top:
|
|
return None
|
|
floor = max(min_px, int(min_fraction * min(iw, ih)))
|
|
if min(right - left, bottom - top) < floor:
|
|
return None
|
|
return img.crop((left, top, right, bottom)).convert("RGB")
|