Commit ·
85803c6
1
Parent(s): 4874ac3
still initial commit
Browse files
app.py
CHANGED
|
@@ -12,84 +12,137 @@ USE_GPU_INFERENCE = os.getenv("USE_GPU_INFERENCE", "false").lower() == "true"
|
|
| 12 |
try:
|
| 13 |
if USE_GPU_INFERENCE:
|
| 14 |
print("🚀 Mode: Full GPU FLUX.2 Klein Inference")
|
| 15 |
-
# Modern Class binding syntax for Modal 1.0+
|
| 16 |
voxel_model_cls = modal.Cls.from_name("flux-klein-voxel-backend", "VoxelModel")
|
| 17 |
voxel_backend = voxel_model_cls().process_frame
|
| 18 |
else:
|
| 19 |
print("🏎️ Mode: Zero-latency WebRTC Passthrough Demo")
|
| 20 |
-
# Modern Standalone function lookup syntax for Modal 1.0+
|
| 21 |
voxel_backend = modal.Function.from_name("flux-klein-voxel-backend", "demo_stream_frame")
|
| 22 |
except Exception as e:
|
| 23 |
print(f"⚠️ Could not bind Modal backend function layout: {e}")
|
| 24 |
voxel_backend = None
|
| 25 |
|
|
|
|
|
|
|
| 26 |
def process_video_stream(frame: np.ndarray, prompt: str, strength: float) -> np.ndarray:
|
| 27 |
"""
|
| 28 |
-
|
| 29 |
-
|
| 30 |
"""
|
| 31 |
if frame is None:
|
| 32 |
return None
|
| 33 |
|
| 34 |
-
# Fallback state if the backend app isn't active or authenticated yet
|
| 35 |
if voxel_backend is None:
|
| 36 |
output_frame = frame.copy()
|
| 37 |
cv2.putText(output_frame, "ERROR: Backend App Offline", (20, 40),
|
| 38 |
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
|
| 39 |
-
cv2.putText(output_frame, "Check HF Space Secrets for MODAL keys.", (20, 70),
|
| 40 |
-
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
| 41 |
return output_frame
|
| 42 |
|
| 43 |
-
# Step 1: Compress high-res frames to a lean JPEG byte stream to prevent browser pipe congestion
|
| 44 |
success, encoded_image = cv2.imencode(".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 85])
|
| 45 |
if not success:
|
| 46 |
return frame
|
| 47 |
|
| 48 |
frame_bytes = encoded_image.tobytes()
|
| 49 |
|
| 50 |
-
# Step 2: Route request to serverless GPU infrastructure
|
| 51 |
try:
|
| 52 |
-
# Dynamically matches positional signature parameters to prevent signature TypeErrors
|
| 53 |
if USE_GPU_INFERENCE:
|
| 54 |
processed_bytes = voxel_backend.remote(frame_bytes, prompt, strength)
|
| 55 |
else:
|
| 56 |
processed_bytes = voxel_backend.remote(frame_bytes)
|
| 57 |
|
| 58 |
-
# Step 3: Reconstruction of the returned processed image array
|
| 59 |
numpy_buffer = np.frombuffer(processed_bytes, dtype=np.uint8)
|
| 60 |
-
|
| 61 |
-
return voxel_frame
|
| 62 |
-
|
| 63 |
except Exception as err:
|
| 64 |
-
# Handle serverless cold starts visually instead of freezing or crashing the stream
|
| 65 |
fallback_frame = frame.copy()
|
| 66 |
-
cv2.putText(fallback_frame, "⚡
|
| 67 |
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
|
| 68 |
-
cv2.putText(fallback_frame, "Loading FLUX Klein weights into cloud VRAM", (20, 70),
|
| 69 |
-
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
| 70 |
return fallback_frame
|
| 71 |
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
custom_css = """
|
| 74 |
-
#container { max-width:
|
| 75 |
.header-text { text-align: center; margin-bottom: 25px; }
|
| 76 |
.header-text h1 { color: #5c8e32; font-family: 'Courier New', Courier, monospace; font-weight: bold; margin-bottom: 5px; }
|
| 77 |
.header-text p { color: #666; font-size: 1.1em; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
"""
|
| 79 |
|
| 80 |
-
# --- GRADIO INTERFACE ARCHITECTURE ---
|
| 81 |
with gr.Blocks(css=custom_css, title="Minecraft Spatial Voxel Filter") as demo:
|
| 82 |
|
| 83 |
-
# gr.Group behaves exactly like a <div> tag container for CSS styling wrappers
|
| 84 |
with gr.Group(elem_id="container"):
|
| 85 |
with gr.Group(elem_classes="header-text"):
|
| 86 |
gr.Markdown("# ⛏️ MINECRAFT SPATIAL VOXEL FILTER ⛏️")
|
| 87 |
gr.Markdown("Transform your physical environment into an interactive, real-time 3D blocky landscape running on FLUX.2 Klein.")
|
| 88 |
|
| 89 |
-
gr.HTML("<hr style='border: 1px solid #ddd; margin-bottom:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
with gr.Row():
|
| 92 |
-
# Left Hand Side
|
| 93 |
with gr.Column(scale=1):
|
| 94 |
gr.Markdown("### 🎛️ Environmental Filters")
|
| 95 |
|
|
@@ -115,25 +168,48 @@ with gr.Blocks(css=custom_css, title="Minecraft Spatial Voxel Filter") as demo:
|
|
| 115 |
"""
|
| 116 |
)
|
| 117 |
|
| 118 |
-
# Right Hand Side
|
| 119 |
with gr.Column(scale=2):
|
| 120 |
-
gr.Markdown("### 📺 Spatial Render Pipeline")
|
| 121 |
|
| 122 |
-
#
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
outputs=[webrtc_stream],
|
| 135 |
-
time_limit=150 # Automatically closes connection after inactivity to prevent runaway token spend
|
| 136 |
)
|
| 137 |
|
| 138 |
-
|
| 139 |
-
demo.launch()
|
|
|
|
| 12 |
try:
|
| 13 |
if USE_GPU_INFERENCE:
|
| 14 |
print("🚀 Mode: Full GPU FLUX.2 Klein Inference")
|
|
|
|
| 15 |
voxel_model_cls = modal.Cls.from_name("flux-klein-voxel-backend", "VoxelModel")
|
| 16 |
voxel_backend = voxel_model_cls().process_frame
|
| 17 |
else:
|
| 18 |
print("🏎️ Mode: Zero-latency WebRTC Passthrough Demo")
|
|
|
|
| 19 |
voxel_backend = modal.Function.from_name("flux-klein-voxel-backend", "demo_stream_frame")
|
| 20 |
except Exception as e:
|
| 21 |
print(f"⚠️ Could not bind Modal backend function layout: {e}")
|
| 22 |
voxel_backend = None
|
| 23 |
|
| 24 |
+
# --- STREAM HANDLING ARCHITECTURE ---
|
| 25 |
+
|
| 26 |
def process_video_stream(frame: np.ndarray, prompt: str, strength: float) -> np.ndarray:
|
| 27 |
"""
|
| 28 |
+
Handles single viewport mode (Minecraft Filter).
|
| 29 |
+
Receives frames, processes them via Modal, and returns them to the same component.
|
| 30 |
"""
|
| 31 |
if frame is None:
|
| 32 |
return None
|
| 33 |
|
|
|
|
| 34 |
if voxel_backend is None:
|
| 35 |
output_frame = frame.copy()
|
| 36 |
cv2.putText(output_frame, "ERROR: Backend App Offline", (20, 40),
|
| 37 |
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
|
|
|
|
|
|
|
| 38 |
return output_frame
|
| 39 |
|
|
|
|
| 40 |
success, encoded_image = cv2.imencode(".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 85])
|
| 41 |
if not success:
|
| 42 |
return frame
|
| 43 |
|
| 44 |
frame_bytes = encoded_image.tobytes()
|
| 45 |
|
|
|
|
| 46 |
try:
|
|
|
|
| 47 |
if USE_GPU_INFERENCE:
|
| 48 |
processed_bytes = voxel_backend.remote(frame_bytes, prompt, strength)
|
| 49 |
else:
|
| 50 |
processed_bytes = voxel_backend.remote(frame_bytes)
|
| 51 |
|
|
|
|
| 52 |
numpy_buffer = np.frombuffer(processed_bytes, dtype=np.uint8)
|
| 53 |
+
return cv2.imdecode(numpy_buffer, cv2.IMREAD_COLOR)
|
|
|
|
|
|
|
| 54 |
except Exception as err:
|
|
|
|
| 55 |
fallback_frame = frame.copy()
|
| 56 |
+
cv2.putText(fallback_frame, "⚡ Serverless Warm-up...", (20, 40),
|
| 57 |
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
|
|
|
|
|
|
|
| 58 |
return fallback_frame
|
| 59 |
|
| 60 |
+
|
| 61 |
+
def process_demo_stream(frame: np.ndarray) -> np.ndarray:
|
| 62 |
+
"""
|
| 63 |
+
Handles separate multi-viewport tracking (Streaming Demo).
|
| 64 |
+
Ingests frames from the raw hardware box and outputs onto the separate Modal display container.
|
| 65 |
+
"""
|
| 66 |
+
if frame is None:
|
| 67 |
+
return None
|
| 68 |
+
|
| 69 |
+
# High-visibility on-screen error rendering for Demo Mode
|
| 70 |
+
if voxel_backend is None:
|
| 71 |
+
error_frame = np.zeros((480, 640, 3), dtype=np.uint8)
|
| 72 |
+
error_frame[:] = 30 # Dark slate gray canvas
|
| 73 |
+
cv2.putText(error_frame, "⚠️ BACKEND APP OFFLINE", (40, 200),
|
| 74 |
+
cv2.FONT_HERSHEY_DUPLEX, 0.9, (0, 0, 255), 2)
|
| 75 |
+
cv2.putText(error_frame, "Status: Inactive or unauthorized Modal keys.", (40, 250),
|
| 76 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (180, 180, 180), 1)
|
| 77 |
+
return error_frame
|
| 78 |
+
|
| 79 |
+
success, encoded_image = cv2.imencode(".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 85])
|
| 80 |
+
if not success:
|
| 81 |
+
return frame
|
| 82 |
+
|
| 83 |
+
frame_bytes = encoded_image.tobytes()
|
| 84 |
+
|
| 85 |
+
try:
|
| 86 |
+
if USE_GPU_INFERENCE:
|
| 87 |
+
processed_bytes = voxel_backend.remote(frame_bytes, "vanilla minecraft voxel landscape", 0.55)
|
| 88 |
+
else:
|
| 89 |
+
processed_bytes = voxel_backend.remote(frame_bytes)
|
| 90 |
+
|
| 91 |
+
numpy_buffer = np.frombuffer(processed_bytes, dtype=np.uint8)
|
| 92 |
+
return cv2.imdecode(numpy_buffer, cv2.IMREAD_COLOR)
|
| 93 |
+
|
| 94 |
+
except Exception as err:
|
| 95 |
+
# Format and write runtime cluster/cold-start exceptions right onto the video frame bounding container
|
| 96 |
+
error_frame = np.zeros((480, 640, 3), dtype=np.uint8)
|
| 97 |
+
error_frame[:] = 20
|
| 98 |
+
cv2.putText(error_frame, "⚡ MODAL RUNTIME EXCEPTION", (40, 180),
|
| 99 |
+
cv2.FONT_HERSHEY_DUPLEX, 0.8, (0, 140, 255), 2)
|
| 100 |
+
|
| 101 |
+
err_msg = str(err)
|
| 102 |
+
cv2.putText(error_frame, f"Error trace: {err_msg[:50]}...", (40, 240),
|
| 103 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
| 104 |
+
cv2.putText(error_frame, "Check cluster logs for container scaling behavior.", (40, 290),
|
| 105 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 255), 1)
|
| 106 |
+
return error_frame
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# --- UI LAYOUT & STYLE RULES ---
|
| 110 |
custom_css = """
|
| 111 |
+
#container { max-width: 1200px; margin: 0 auto; padding-top: 20px; }
|
| 112 |
.header-text { text-align: center; margin-bottom: 25px; }
|
| 113 |
.header-text h1 { color: #5c8e32; font-family: 'Courier New', Courier, monospace; font-weight: bold; margin-bottom: 5px; }
|
| 114 |
.header-text p { color: #666; font-size: 1.1em; }
|
| 115 |
+
|
| 116 |
+
/* Constrain video bounding tracks to stop screen-flooding layout shifts */
|
| 117 |
+
video, .webrtc-video, div[class*="webrtc"] {
|
| 118 |
+
max-height: 440px !important;
|
| 119 |
+
width: 100% !important;
|
| 120 |
+
max-width: 580px !important;
|
| 121 |
+
margin: 0 auto !important;
|
| 122 |
+
object-fit: contain !important;
|
| 123 |
+
border-radius: 10px;
|
| 124 |
+
}
|
| 125 |
"""
|
| 126 |
|
|
|
|
| 127 |
with gr.Blocks(css=custom_css, title="Minecraft Spatial Voxel Filter") as demo:
|
| 128 |
|
|
|
|
| 129 |
with gr.Group(elem_id="container"):
|
| 130 |
with gr.Group(elem_classes="header-text"):
|
| 131 |
gr.Markdown("# ⛏️ MINECRAFT SPATIAL VOXEL FILTER ⛏️")
|
| 132 |
gr.Markdown("Transform your physical environment into an interactive, real-time 3D blocky landscape running on FLUX.2 Klein.")
|
| 133 |
|
| 134 |
+
gr.HTML("<hr style='border: 1px solid #ddd; margin-bottom: 20px;'>")
|
| 135 |
+
|
| 136 |
+
# Context switcher selection interface
|
| 137 |
+
mode_dropdown = gr.Dropdown(
|
| 138 |
+
choices=["Streaming Demo", "Minecraft Filter"],
|
| 139 |
+
value="Minecraft Filter",
|
| 140 |
+
label="🎯 Pipeline View Configuration",
|
| 141 |
+
interactive=True
|
| 142 |
+
)
|
| 143 |
|
| 144 |
with gr.Row():
|
| 145 |
+
# Left Hand Side Settings Column
|
| 146 |
with gr.Column(scale=1):
|
| 147 |
gr.Markdown("### 🎛️ Environmental Filters")
|
| 148 |
|
|
|
|
| 168 |
"""
|
| 169 |
)
|
| 170 |
|
| 171 |
+
# Right Hand Side Viewport Area
|
| 172 |
with gr.Column(scale=2):
|
|
|
|
| 173 |
|
| 174 |
+
# --- LAYOUT VARIANT 1: MINECRAFT FILTER MODE (Standard Single Viewport) ---
|
| 175 |
+
with gr.Column(visible=True) as minecraft_layout:
|
| 176 |
+
gr.Markdown("### 📺 Spatial Render Pipeline")
|
| 177 |
+
webrtc_single = WebRTC(
|
| 178 |
+
label="Live Voxel Viewport",
|
| 179 |
+
modality="video",
|
| 180 |
+
mode="send-receive",
|
| 181 |
+
rtc_configuration=get_cloudflare_turn_credentials
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
# --- LAYOUT VARIANT 2: STREAMING DEMO MODE (Decoupled Dual Viewports) ---
|
| 185 |
+
with gr.Column(visible=False) as demo_layout:
|
| 186 |
+
gr.Markdown("### 📺 Dual-Feed Stream Monitor")
|
| 187 |
+
with gr.Row():
|
| 188 |
+
webrtc_raw = WebRTC(
|
| 189 |
+
label="1. Your Camera Feed (Raw Local)",
|
| 190 |
+
modality="video",
|
| 191 |
+
mode="send",
|
| 192 |
+
rtc_configuration=get_cloudflare_turn_credentials
|
| 193 |
+
)
|
| 194 |
+
webrtc_processed = WebRTC(
|
| 195 |
+
label="2. Streamed from Modal (Processed Backend)",
|
| 196 |
+
modality="video",
|
| 197 |
+
mode="receive",
|
| 198 |
+
rtc_configuration=get_cloudflare_turn_credentials
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
# --- ROUTING LOGIC & EVENT WIRES ---
|
| 202 |
+
|
| 203 |
+
def switch_layout(selected_mode):
|
| 204 |
+
if selected_mode == "Minecraft Filter":
|
| 205 |
+
return gr.update(visible=True), gr.update(visible=False)
|
| 206 |
+
else:
|
| 207 |
+
return gr.update(visible=False), gr.update(visible=True)
|
| 208 |
|
| 209 |
+
mode_dropdown.change(
|
| 210 |
+
fn=switch_layout,
|
| 211 |
+
inputs=[mode_dropdown],
|
| 212 |
+
outputs=[minecraft_layout, demo_layout]
|
|
|
|
|
|
|
| 213 |
)
|
| 214 |
|
| 215 |
+
# Wire
|
|
|