File size: 8,970 Bytes
9e606d9 d40224e 9a09edb d40224e 9a09edb 9e606d9 6ba9d20 9e606d9 178c9ad 6ba9d20 9e606d9 6ba9d20 178c9ad 6ba9d20 178c9ad 6ba9d20 178c9ad 94f0003 6ba9d20 176f153 94f0003 6ba9d20 b97cf3f 077cba7 9e606d9 b97cf3f 077cba7 9e606d9 077cba7 9e606d9 6ba9d20 99ea5fd 6ba9d20 b97cf3f 94f0003 b97cf3f 94f0003 6ba9d20 85803c6 6ba9d20 077cba7 94f0003 6ba9d20 1c0a58f 6ba9d20 b97cf3f 94f0003 6ba9d20 99ea5fd 6ba9d20 99ea5fd 6ba9d20 99ea5fd 61b3a19 99ea5fd 6ba9d20 1c0a58f 6ba9d20 99ea5fd 6ba9d20 99ea5fd 61b3a19 99ea5fd 6ba9d20 b97cf3f 61b3a19 99ea5fd b97cf3f 3a269bf 99ea5fd 178c9ad 9a09edb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | import os
import cv2
import numpy as np
from datetime import datetime
# ── 🛠️ 0. THE "DUCT TAPE" MONKEYPATCHES ──────────────────────────────────────
# Python 3.13 + Gradio 4.44.0 + Hugging Face Spaces requires these overrides.
# Patch A: Hugging Face Hub (Fixes: ImportError: cannot import name 'HfFolder')
import huggingface_hub
if not hasattr(huggingface_hub, "HfFolder"):
class MockHfFolder:
@staticmethod
def get_token():
return os.environ.get("HF_TOKEN")
huggingface_hub.HfFolder = MockHfFolder
# Patch B: Gradio Client Schema Parser (Fixes: TypeError: argument of type 'bool' is not iterable)
try:
import gradio_client.utils
if hasattr(gradio_client.utils, "get_type"):
old_get_type = gradio_client.utils.get_type
def patched_get_type(schema):
if isinstance(schema, bool):
return "Any"
return old_get_type(schema)
gradio_client.utils.get_type = patched_get_type
if hasattr(gradio_client.utils, "_json_schema_to_python_type"):
old_internal_parser = gradio_client.utils._json_schema_to_python_type
def patched_internal_parser(schema, defs=None):
if isinstance(schema, bool):
return "Any"
return old_internal_parser(schema, defs)
gradio_client.utils._json_schema_to_python_type = patched_internal_parser
except Exception:
pass
# ─────────────────────────────────────────────────────────────────────────────
import gradio as gr
import modal
# ── Logging Setup ───────────────────────────────────────────────────────────
init_logs = []
def log_system_event(message: str):
"""Formats logs with a timestamp and syncs to stdout and UI."""
timestamp = datetime.now().strftime("%H:%M:%S")
formatted_log = f"[{timestamp}] {message}"
print(formatted_log)
init_logs.append(formatted_log)
# ── Modal Backend Connection ────────────────────────────────────────────────
log_system_event("Initializing connection to Modal remote infrastructure...")
try:
VoxelModelCls = modal.Cls.from_name("flux-klein-voxel-backend", "VoxelModel")
voxel_backend = VoxelModelCls().process_frame
log_system_event("✅ Success: Bound directly to Modal Class deployment ('VoxelModel').")
except Exception as e:
log_system_event(f"⚠️ Modal Class lookup failed ({e}). Attempting fallback function router...")
try:
voxel_backend = modal.Function.from_name("flux-klein-voxel-backend", "demo_stream_frame")
log_system_event("✅ Success: Hooked into fallback standalone Modal Function.")
except Exception as ex:
log_system_event(f"❌ Critical: All remote Modal endpoints are unreachable. Error: {ex}")
voxel_backend = None
status_color = "🟢" if voxel_backend is not None else "🔴"
status_text = "Connected" if voxel_backend is not None else "Offline"
# ── Helpers ─────────────────────────────────────────────────────────────────
def _offline_frame(frame: np.ndarray, message: str) -> np.ndarray:
"""Generates a styled placeholder frame when backend is offline."""
h, w = (frame.shape[:2] if frame is not None else (480, 640))
out = np.zeros((h, w, 3), dtype=np.uint8)
cv2.putText(out, message, (max(10, w // 8), h // 2),
cv2.FONT_HERSHEY_DUPLEX, 0.8, (0, 0, 220), 2)
return out
def _run_voxel_backend(frame: np.ndarray) -> np.ndarray:
"""Encodes and ships ONLY raw image bytes to the Modal worker."""
success, encoded = cv2.imencode(".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 85])
if not success:
return frame
try:
# Pushing ONLY the camera feed byte data across the network
processed_bytes = voxel_backend.remote(encoded.tobytes())
result = cv2.imdecode(np.frombuffer(processed_bytes, dtype=np.uint8), cv2.IMREAD_COLOR)
return result if result is not None else frame
except Exception as err:
out = frame.copy()
cv2.putText(out, f"Modal error: {str(err)[:45]}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 255), 1)
return out
# ── Core Stream Handler ─────────────────────────────────────────────────────
frame_counter = 0
def process_video_stream(frame: np.ndarray, mode: str, is_running: bool) -> np.ndarray:
"""
Accepts incoming frame from the webcam, pipeline settings, and execution state.
"""
global frame_counter
if frame is None:
return None
# If user hasn't toggled "Start Processing", safely pass raw video back as preview
if not is_running:
return frame
if voxel_backend is None:
err_frame = _offline_frame(frame, "Modal Backend Offline")
if mode == "Streaming Demo":
return np.hstack([frame, err_frame])
return err_frame
# Runtime console indicator: logs transmission health to terminal every 15 frames
frame_counter += 1
if frame_counter % 15 == 0:
print(f"🚀 [LIVE PIPELINE] Transmitting frames. Dispatched {frame_counter} payloads to Modal.")
# Process via the single-image pipeline
processed = _run_voxel_backend(frame)
# Mode A: Full view rendering
if mode == "Minecraft Filter":
return processed
# Mode B: Side-by-side split rendering
elif mode == "Streaming Demo":
if processed.shape[0] != frame.shape[0]:
processed = cv2.resize(processed, (frame.shape[1], frame.shape[0]))
raw_labeled = frame.copy()
cv2.putText(raw_labeled, "RAW", (10, 28), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2)
cv2.putText(processed, "MINECRAFT", (10, 28), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2)
return np.hstack([raw_labeled, processed])
return processed
# ── Custom Gradio Interface Layout ──────────────────────────────────────────
with gr.Blocks(title="⛏️ Minecraft Spatial Voxel Filter") as demo:
# State tracking engine variable
is_running = gr.State(value=False)
gr.Markdown("# ⛏️ Minecraft Spatial Voxel Filter")
with gr.Row():
# Configuration Settings & Log Panel (Left Column)
with gr.Column(scale=1):
gr.Markdown(
f"### ⚡ Backend Connection Status\n"
f"Status: {status_color} **{status_text}**"
)
# Live Diagnostic Log Display View
ui_logs = gr.Textbox(
value="\n".join(init_logs),
label="💻 System Initialization Logs",
lines=4,
max_lines=5,
interactive=False,
)
mode_dropdown = gr.Dropdown(
choices=["Minecraft Filter", "Streaming Demo"],
value="Minecraft Filter",
label="🎯 Pipeline Mode",
interactive=True,
)
with gr.Row():
start_btn = gr.Button("🚀 Start Processing", variant="primary")
stop_btn = gr.Button("🛑 Stop", variant="secondary")
# Video Capture Viewports (Right Column)
with gr.Column(scale=2):
input_stream = gr.Image(sources=["webcam"], streaming=True, label="Live Webcam Input")
output_stream = gr.Image(interactive=False, label="Voxel Output Viewport")
# Wire up button interface trigger mappings to flip our State flag
start_btn.click(fn=lambda: True, inputs=None, outputs=is_running)
stop_btn.click(fn=lambda: False, inputs=None, outputs=is_running)
# Core engine transmission loop linked up with ONLY the stream, mode, and run-state
input_stream.stream(
fn=process_video_stream,
inputs=[input_stream, mode_dropdown, is_running],
outputs=[output_stream],
trigger_mode="always_last", # Drops intermediate backlog frames when backend is busy
concurrency_limit=1 # Ensures only one frame flies over the network at a time
)
if __name__ == "__main__":
# Bound to 0.0.0.0 to bypass Hugging Face Spaces localhost proxy issues
demo.launch(server_name="0.0.0.0", server_port=7860) |