AnimeOverlord commited on
Commit
507a1f0
·
1 Parent(s): b4a016b

still initial commit

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