Synthetic face pool — 1000 StyleGAN headshots, ComfyUI hot-swap, 60x smaller thumbs
Worker cards now ship a real photo per person instead of monogram tiles:
- fetch_face_pool.py pulls 1000 faces from thispersondoesnotexist.com
- tag_face_pool.py runs deepface for gender/race/age, excludes <22yo
- manifest.jsonl: 952 servable, gender/race buckets populated
- /headshots/_thumbs/ pre-resized to 384px webp (587KB -> 11KB,
60x smaller; without this Chrome's parallel-connection budget
drops ~75% of tiles in a 40-card grid)
- /headshots/:key gender x race x age intersection bucketing with
gender-only fallback when intersection is sparse
- /headshots/generate/:key ComfyUI on-demand for the contractor
profile spotlight (cold ~1.5s, cached ~1ms; worker-derived
djb2 seed makes faces deterministic-per-worker but unique
across workers sharing the same prompt)
- serve_imagegen.py _cache_key() now includes seed (was caching
by prompt only -> 3 different worker seeds collapsed to 1
cached image; verified fix produces 3 distinct md5s)
- confidence-default name resolution: Xavier->man+hispanic,
Aisha->woman+black, etc. Every worker resolves to a bucket.
End-to-end: playwright run on /?q=forklift+operators+IL -> 21/21
cards loaded, 0 broken, all 384px webp.
Cache + binary pool gitignored; manifest tracked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,8 +29,14 @@ CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
WORKFLOW_PATH = "/opt/ComfyUI/workflows/editorial_hero.json"
|
||||
|
||||
|
||||
def _cache_key(prompt, width, height, steps):
|
||||
return hashlib.sha256(f"{prompt}|{width}|{height}|{steps}".encode()).hexdigest()[:24]
|
||||
def _cache_key(prompt, width, height, steps, seed=None):
|
||||
# Include seed so callers can vary outputs deterministically without
|
||||
# the proxy collapsing to a single cached image. None == legacy
|
||||
# (omitted from the key for backward compatibility).
|
||||
bits = f"{prompt}|{width}|{height}|{steps}"
|
||||
if seed is not None:
|
||||
bits += f"|{seed}"
|
||||
return hashlib.sha256(bits.encode()).hexdigest()[:24]
|
||||
|
||||
def _cache_get(key):
|
||||
fp = CACHE_DIR / f"{key}.webp"
|
||||
@@ -178,8 +184,9 @@ class ImageHandler(BaseHTTPRequestHandler):
|
||||
steps = min(max(int(body.get("steps", 50)), 1), 80)
|
||||
seed = body.get("seed")
|
||||
|
||||
# Cache check
|
||||
key = _cache_key(prompt, width, height, steps)
|
||||
# Cache check — seed is part of the key so per-worker requests
|
||||
# don't collapse to a single cached portrait.
|
||||
key = _cache_key(prompt, width, height, steps, seed)
|
||||
cached = _cache_get(key)
|
||||
if cached:
|
||||
self._json(200, {"image": cached, "format": "webp", "width": width, "height": height,
|
||||
@@ -210,6 +217,11 @@ class ImageHandler(BaseHTTPRequestHandler):
|
||||
|
||||
elapsed_ms = int((time.time() - t0) * 1000)
|
||||
img_b64 = base64.b64encode(img_bytes).decode()
|
||||
# Recompute key with the actual seed used (when caller passed
|
||||
# None, _comfyui_generate picks a random one and we want the
|
||||
# cache to reflect that so re-requests with the same returned
|
||||
# seed hit the disk).
|
||||
key = _cache_key(prompt, width, height, steps, seed)
|
||||
_cache_put(key, img_bytes)
|
||||
|
||||
self._json(200, {
|
||||
|
||||
Reference in New Issue
Block a user