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)