Spaces:
Running on Zero
Running on Zero
moose Claude Opus 4.7 (1M context) commited on
Commit ·
3fd36c4
1
Parent(s): 506e019
Wire output through quality-enhancement pipeline
Browse filesFirst stage of the enhancement pipeline: after inference completes, hand
off (inputs + outputs + prompt + params) to an external enhancement
endpoint. Fire-and-forget on a daemon thread for now so generation is
never blocked; downstream stages will eventually return an enhanced
output to display.
Config via QUALITY_ENHANCEMENT_URL / QUALITY_ENHANCEMENT_TOKEN secrets.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app.py +25 -0
- quality_enhancement.py +115 -0
app.py
CHANGED
|
@@ -24,6 +24,8 @@ from PIL import Image
|
|
| 24 |
import os
|
| 25 |
import gradio as gr
|
| 26 |
|
|
|
|
|
|
|
| 27 |
def turn_into_video(input_images, output_images, prompt, progress=gr.Progress(track_tqdm=True)):
|
| 28 |
if not input_images or not output_images:
|
| 29 |
raise gr.Error("Please generate an output image first.")
|
|
@@ -203,6 +205,29 @@ def infer(
|
|
| 203 |
num_images_per_prompt=num_images_per_prompt,
|
| 204 |
).images
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
# Save images to temporary files for proper serving
|
| 207 |
output_paths = []
|
| 208 |
os.makedirs("outputs", exist_ok=True)
|
|
|
|
| 24 |
import os
|
| 25 |
import gradio as gr
|
| 26 |
|
| 27 |
+
from quality_enhancement import send_for_enhancement
|
| 28 |
+
|
| 29 |
def turn_into_video(input_images, output_images, prompt, progress=gr.Progress(track_tqdm=True)):
|
| 30 |
if not input_images or not output_images:
|
| 31 |
raise gr.Error("Please generate an output image first.")
|
|
|
|
| 205 |
num_images_per_prompt=num_images_per_prompt,
|
| 206 |
).images
|
| 207 |
|
| 208 |
+
# Fire-and-forget hand-off to the quality-enhancement pipeline. Runs
|
| 209 |
+
# AFTER generation so we have the actual outputs and the post-
|
| 210 |
+
# randomization seed. Side effect only for now — must NOT block or
|
| 211 |
+
# fail generation. See quality_enhancement.py.
|
| 212 |
+
try:
|
| 213 |
+
send_for_enhancement(
|
| 214 |
+
pil_images,
|
| 215 |
+
images_pil,
|
| 216 |
+
prompt,
|
| 217 |
+
params={
|
| 218 |
+
"seed": seed,
|
| 219 |
+
"randomize_seed": randomize_seed,
|
| 220 |
+
"true_guidance_scale": true_guidance_scale,
|
| 221 |
+
"num_inference_steps": num_inference_steps,
|
| 222 |
+
"height": height,
|
| 223 |
+
"width": width,
|
| 224 |
+
"num_images_per_prompt": num_images_per_prompt,
|
| 225 |
+
"negative_prompt": negative_prompt,
|
| 226 |
+
},
|
| 227 |
+
)
|
| 228 |
+
except Exception as _enhancement_exc:
|
| 229 |
+
print(f"[enhancement] outer guard caught: {_enhancement_exc}")
|
| 230 |
+
|
| 231 |
# Save images to temporary files for proper serving
|
| 232 |
output_paths = []
|
| 233 |
os.makedirs("outputs", exist_ok=True)
|
quality_enhancement.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Quality-enhancement pipeline client.
|
| 2 |
+
|
| 3 |
+
Posts (prompt + input PIL images + output PIL images + generation params) to
|
| 4 |
+
an external enhancement endpoint after inference completes. This is the
|
| 5 |
+
first stage of a planned pipeline where a downstream service will eventually
|
| 6 |
+
return an enhanced version of the output before the user sees it.
|
| 7 |
+
|
| 8 |
+
For now the call is fire-and-forget — generation must continue normally if
|
| 9 |
+
the POST fails or times out. We swallow exceptions on purpose; the only
|
| 10 |
+
visible failure mode is a `print()` to the Space logs.
|
| 11 |
+
|
| 12 |
+
Env vars (set as HF Space secrets):
|
| 13 |
+
QUALITY_ENHANCEMENT_URL — full URL including the secret path segment
|
| 14 |
+
QUALITY_ENHANCEMENT_TOKEN — value for the X-Debug-Token header
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import io
|
| 18 |
+
import json
|
| 19 |
+
import os
|
| 20 |
+
import threading
|
| 21 |
+
from typing import Any, Iterable, Optional
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
import requests # ships with gradio
|
| 25 |
+
except Exception: # pragma: no cover — extremely unlikely in HF Spaces
|
| 26 |
+
requests = None # type: ignore[assignment]
|
| 27 |
+
|
| 28 |
+
from PIL import Image
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
_TIMEOUT_SECONDS = 5.0
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _encode_png(img: Image.Image) -> Optional[bytes]:
|
| 35 |
+
"""PIL image -> PNG bytes; returns None on failure."""
|
| 36 |
+
try:
|
| 37 |
+
buf = io.BytesIO()
|
| 38 |
+
# PNG so we don't pile JPEG artifacts on top of whatever artifacts
|
| 39 |
+
# the source already had — transfers should be lossless.
|
| 40 |
+
img.convert("RGB").save(buf, format="PNG")
|
| 41 |
+
return buf.getvalue()
|
| 42 |
+
except Exception as exc:
|
| 43 |
+
print(f"[enhancement] PNG encode failed: {exc}")
|
| 44 |
+
return None
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _post_enhancement(url: str, token: str, data: dict[str, str],
|
| 48 |
+
files: list[tuple[str, tuple[str, bytes, str]]]) -> None:
|
| 49 |
+
try:
|
| 50 |
+
resp = requests.post(
|
| 51 |
+
url,
|
| 52 |
+
data=data,
|
| 53 |
+
files=files if files else None,
|
| 54 |
+
headers={"X-Debug-Token": token},
|
| 55 |
+
timeout=_TIMEOUT_SECONDS,
|
| 56 |
+
)
|
| 57 |
+
if resp.status_code != 200:
|
| 58 |
+
print(f"[enhancement] non-200 from endpoint: {resp.status_code} {resp.text[:200]}")
|
| 59 |
+
except Exception as exc:
|
| 60 |
+
print(f"[enhancement] failed: {exc}")
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _encode_list(images: Iterable[Image.Image], field_name: str,
|
| 64 |
+
prefix: str) -> list[tuple[str, tuple[str, bytes, str]]]:
|
| 65 |
+
out: list[tuple[str, tuple[str, bytes, str]]] = []
|
| 66 |
+
for idx, img in enumerate(images or []):
|
| 67 |
+
if img is None:
|
| 68 |
+
continue
|
| 69 |
+
png = _encode_png(img)
|
| 70 |
+
if png is None:
|
| 71 |
+
continue
|
| 72 |
+
out.append((field_name, (f"{prefix}_{idx}.png", png, "image/png")))
|
| 73 |
+
return out
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def send_for_enhancement(input_images: Iterable[Image.Image],
|
| 77 |
+
output_images: Iterable[Image.Image],
|
| 78 |
+
prompt: str,
|
| 79 |
+
params: dict[str, Any]) -> None:
|
| 80 |
+
"""Fire-and-forget POST of (inputs, outputs, prompt, params) to the
|
| 81 |
+
enhancement endpoint.
|
| 82 |
+
|
| 83 |
+
Either image list may be empty. Encodes images on the caller's thread
|
| 84 |
+
(fast, lets us fail fast on encode errors), then dispatches the HTTP
|
| 85 |
+
POST to a daemon thread so inference is never blocked by network
|
| 86 |
+
latency or a hung receiver. Losing a transfer if the Space request
|
| 87 |
+
returns before the upload finishes is acceptable for this stage —
|
| 88 |
+
we don't yet read the response.
|
| 89 |
+
"""
|
| 90 |
+
url = os.environ.get("QUALITY_ENHANCEMENT_URL", "")
|
| 91 |
+
token = os.environ.get("QUALITY_ENHANCEMENT_TOKEN", "")
|
| 92 |
+
|
| 93 |
+
if not url or not token:
|
| 94 |
+
return
|
| 95 |
+
if requests is None:
|
| 96 |
+
print("[enhancement] requests library unavailable; skipping")
|
| 97 |
+
return
|
| 98 |
+
|
| 99 |
+
files = _encode_list(input_images, "images[]", "input")
|
| 100 |
+
files += _encode_list(output_images, "output_images[]", "output")
|
| 101 |
+
|
| 102 |
+
if not files:
|
| 103 |
+
# Nothing to send — don't bother the server.
|
| 104 |
+
return
|
| 105 |
+
|
| 106 |
+
data = {
|
| 107 |
+
"prompt": prompt or "",
|
| 108 |
+
"params": json.dumps(params or {}, default=str),
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
threading.Thread(
|
| 112 |
+
target=_post_enhancement,
|
| 113 |
+
args=(url, token, data, files),
|
| 114 |
+
daemon=True,
|
| 115 |
+
).start()
|