MySafeCode commited on
Commit
101e226
·
verified ·
1 Parent(s): 7fa511a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +203 -203
app.py CHANGED
@@ -1,203 +1,203 @@
1
- import gradio as gr
2
- import cv2
3
- import numpy as np
4
- import trimesh
5
- import tempfile
6
- import os
7
-
8
- # -------------------------
9
- # GLOBAL (checkerboard persistence)
10
- # -------------------------
11
- _checkerboard_colors = None
12
-
13
- # -------------------------
14
- # VIDEO LOADING (BGR → RGB FIXED ✅)
15
- # -------------------------
16
- def read_video_frames(video_path, start=0, end=None, frame_step=1):
17
- cap = cv2.VideoCapture(video_path)
18
- frames = []
19
-
20
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
21
- if end is None:
22
- end = total_frames
23
-
24
- count = 0
25
-
26
- while True:
27
- ret, frame = cap.read()
28
- if not ret or count >= end:
29
- break
30
-
31
- if count >= start and (count - start) % frame_step == 0:
32
- # FIX COLOR ORDER HERE
33
- frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
34
- frames.append(frame)
35
-
36
- count += 1
37
-
38
- cap.release()
39
- return np.array(frames)
40
-
41
-
42
- # -------------------------
43
- # DOWNSAMPLING
44
- # -------------------------
45
- def downsample_frames(frames, block_size=1, method='stride'):
46
- if block_size == 1:
47
- return frames
48
-
49
- z, h, w, c = frames.shape
50
-
51
- if method == 'stride':
52
- return frames[:, ::block_size, ::block_size]
53
-
54
- elif method == 'mean':
55
- new_h = h // block_size
56
- new_w = w // block_size
57
- out = np.zeros((z, new_h, new_w, c), dtype=np.uint8)
58
-
59
- for zi in range(z):
60
- for i in range(new_h):
61
- for j in range(new_w):
62
- block = frames[
63
- zi,
64
- i*block_size:(i+1)*block_size,
65
- j*block_size:(j+1)*block_size
66
- ]
67
- out[zi, i, j] = block.mean(axis=(0,1))
68
- return out
69
-
70
-
71
- # -------------------------
72
- # VOXEL MASK
73
- # -------------------------
74
- def frames_to_voxels(frames, threshold=10):
75
- return (np.sum(frames, axis=3) > threshold)
76
-
77
-
78
- # -------------------------
79
- # VOXEL → MESH (FIXED COLORS ✅)
80
- # -------------------------
81
- def voxels_to_mesh(frames, voxels, voxel_size=1.0):
82
- meshes = []
83
- z_len, h, w = voxels.shape
84
-
85
- for z in range(z_len):
86
- for y in range(h):
87
- for x in range(w):
88
- if voxels[z, y, x]:
89
- color = frames[z, frames.shape[1] - 1 - y, x].astype(np.uint8)
90
-
91
- cube = trimesh.creation.box(extents=[voxel_size]*3)
92
- cube.apply_translation([x, y, z])
93
-
94
- # Apply colors correctly (RGBA uint8)
95
- rgba = np.append(color, 255)
96
- cube.visual.face_colors = np.tile(rgba, (12,1))
97
-
98
- meshes.append(cube)
99
-
100
- if meshes:
101
- return trimesh.util.concatenate(meshes)
102
- return trimesh.Scene()
103
-
104
-
105
- # -------------------------
106
- # RANDOM CHECKERBOARD (ONE-TIME COLORS ✅)
107
- # -------------------------
108
- def default_checkerboard():
109
- global _checkerboard_colors
110
-
111
- h, w, z_len = 10, 10, 2
112
- frames = np.zeros((z_len, h, w, 3), dtype=np.uint8)
113
-
114
- if _checkerboard_colors is None:
115
- _checkerboard_colors = np.random.randint(
116
- 0, 256, size=(z_len, h, w, 3), dtype=np.uint8
117
- )
118
-
119
- for z in range(z_len):
120
- for y in range(h):
121
- for x in range(w):
122
- if (x + y + z) % 2 == 0:
123
- frames[z, y, x] = [0, 0, 0]
124
- else:
125
- frames[z, y, x] = _checkerboard_colors[z, y, x]
126
-
127
- voxels = frames_to_voxels(frames, threshold=1)
128
- mesh = voxels_to_mesh(frames, voxels, voxel_size=2)
129
-
130
- tmp = tempfile.gettempdir()
131
- obj = os.path.join(tmp, "checkerboard.obj")
132
- glb = os.path.join(tmp, "checkerboard.glb")
133
-
134
- mesh.export(obj)
135
- mesh.export(glb)
136
-
137
- return obj, glb, glb
138
-
139
-
140
- # -------------------------
141
- # MAIN GENERATOR
142
- # -------------------------
143
- def generate_voxel_files(
144
- video_file,
145
- start_frame,
146
- end_frame,
147
- frame_step,
148
- block_size,
149
- downsample_method
150
- ):
151
- if video_file is None:
152
- return default_checkerboard()
153
-
154
- frames = read_video_frames(
155
- video_file.name,
156
- start=start_frame,
157
- end=end_frame,
158
- frame_step=frame_step
159
- )
160
-
161
- frames = downsample_frames(
162
- frames,
163
- block_size=block_size,
164
- method=downsample_method
165
- )
166
-
167
- voxels = frames_to_voxels(frames)
168
- mesh = voxels_to_mesh(frames, voxels)
169
-
170
- tmp = tempfile.gettempdir()
171
- obj = os.path.join(tmp, "output.obj")
172
- glb = os.path.join(tmp, "output.glb")
173
-
174
- mesh.export(obj)
175
- mesh.export(glb)
176
-
177
- return obj, glb, glb
178
-
179
-
180
- # -------------------------
181
- # GRADIO UI
182
- # -------------------------
183
- iface = gr.Interface(
184
- fn=generate_voxel_files,
185
- inputs=[
186
- gr.File(label="Upload MP4 (or leave empty for checkerboard)"),
187
- gr.Slider(0, 500, value=0, step=1, label="Start Frame"),
188
- gr.Slider(0, 500, value=50, step=1, label="End Frame"),
189
- gr.Slider(1, 10, value=1, step=1, label="Frame Step"),
190
- gr.Slider(1, 32, value=1, step=1, label="Pixel Block Size"),
191
- gr.Radio(["stride", "mean"], value="stride", label="Downsample Method"),
192
- ],
193
- outputs=[
194
- gr.File(label="OBJ"),
195
- gr.File(label="GLB"),
196
- gr.Model3D(label="3D Preview"),
197
- ],
198
- title="MP4 → Voxels → 3D",
199
- description="If no file is uploaded, a random-color checkerboard appears."
200
- )
201
-
202
- if __name__ == "__main__":
203
- iface.launch()
 
1
+ import gradio as gr
2
+ import cv2
3
+ import numpy as np
4
+ import trimesh
5
+ import tempfile
6
+ import os
7
+
8
+ # -------------------------
9
+ # GLOBAL (checkerboard persistence)
10
+ # -------------------------
11
+ _checkerboard_colors = None
12
+
13
+ # -------------------------
14
+ # VIDEO LOADING (BGR → RGB FIXED ✅)
15
+ # -------------------------
16
+ def read_video_frames(video_path, start=0, end=None, frame_step=1):
17
+ cap = cv2.VideoCapture(video_path)
18
+ frames = []
19
+
20
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
21
+ if end is None:
22
+ end = total_frames
23
+
24
+ count = 0
25
+
26
+ while True:
27
+ ret, frame = cap.read()
28
+ if not ret or count >= end:
29
+ break
30
+
31
+ if count >= start and (count - start) % frame_step == 0:
32
+ # FIX COLOR ORDER HERE
33
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
34
+ frames.append(frame)
35
+
36
+ count += 1
37
+
38
+ cap.release()
39
+ return np.array(frames)
40
+
41
+
42
+ # -------------------------
43
+ # DOWNSAMPLING
44
+ # -------------------------
45
+ def downsample_frames(frames, block_size=1, method='stride'):
46
+ if block_size == 1:
47
+ return frames
48
+
49
+ z, h, w, c = frames.shape
50
+
51
+ if method == 'stride':
52
+ return frames[:, ::block_size, ::block_size]
53
+
54
+ elif method == 'mean':
55
+ new_h = h // block_size
56
+ new_w = w // block_size
57
+ out = np.zeros((z, new_h, new_w, c), dtype=np.uint8)
58
+
59
+ for zi in range(z):
60
+ for i in range(new_h):
61
+ for j in range(new_w):
62
+ block = frames[
63
+ zi,
64
+ i*block_size:(i+1)*block_size,
65
+ j*block_size:(j+1)*block_size
66
+ ]
67
+ out[zi, i, j] = block.mean(axis=(0,1))
68
+ return out
69
+
70
+
71
+ # -------------------------
72
+ # VOXEL MASK
73
+ # -------------------------
74
+ def frames_to_voxels(frames, threshold=10):
75
+ return (np.sum(frames, axis=3) > threshold)
76
+
77
+
78
+ # -------------------------
79
+ # VOXEL → MESH (FIXED COLORS ✅)
80
+ # -------------------------
81
+ def voxels_to_mesh(frames, voxels, voxel_size=1.0):
82
+ meshes = []
83
+ z_len, h, w = voxels.shape
84
+
85
+ for z in range(z_len):
86
+ for y in range(h):
87
+ for x in range(w):
88
+ if voxels[z, y, x]:
89
+ color = frames[z, frames.shape[1] - 1 - y, x].astype(np.uint8)
90
+
91
+ cube = trimesh.creation.box(extents=[voxel_size]*3)
92
+ cube.apply_translation([x, y, z])
93
+
94
+ # Apply colors correctly (RGBA uint8)
95
+ rgba = np.append(color, 255)
96
+ cube.visual.face_colors = np.tile(rgba, (12,1))
97
+
98
+ meshes.append(cube)
99
+
100
+ if meshes:
101
+ return trimesh.util.concatenate(meshes)
102
+ return trimesh.Scene()
103
+
104
+
105
+ # -------------------------
106
+ # RANDOM CHECKERBOARD (ONE-TIME COLORS ✅)
107
+ # -------------------------
108
+ def default_checkerboard():
109
+ global _checkerboard_colors
110
+
111
+ h, w, z_len = 10, 10, 2
112
+ frames = np.zeros((z_len, h, w, 3), dtype=np.uint8)
113
+
114
+ if _checkerboard_colors is None:
115
+ _checkerboard_colors = np.random.randint(
116
+ 0, 256, size=(z_len, h, w, 3), dtype=np.uint8
117
+ )
118
+
119
+ for z in range(z_len):
120
+ for y in range(h):
121
+ for x in range(w):
122
+ if (x + y + z) % 2 == 0:
123
+ frames[z, y, x] = [0, 0, 0]
124
+ else:
125
+ frames[z, y, x] = _checkerboard_colors[z, y, x]
126
+
127
+ voxels = frames_to_voxels(frames, threshold=1)
128
+ mesh = voxels_to_mesh(frames, voxels, voxel_size=2)
129
+
130
+ tmp = tempfile.gettempdir()
131
+ obj = os.path.join(tmp, "checkerboard.obj")
132
+ glb = os.path.join(tmp, "checkerboard.glb")
133
+
134
+ mesh.export(obj)
135
+ mesh.export(glb)
136
+
137
+ return obj, glb, glb
138
+
139
+
140
+ # -------------------------
141
+ # MAIN GENERATOR
142
+ # -------------------------
143
+ def generate_voxel_files(
144
+ video_file,
145
+ start_frame,
146
+ end_frame,
147
+ frame_step,
148
+ block_size,
149
+ downsample_method
150
+ ):
151
+ if video_file is None:
152
+ return default_checkerboard()
153
+
154
+ frames = read_video_frames(
155
+ video_file.name,
156
+ start=start_frame,
157
+ end=end_frame,
158
+ frame_step=frame_step
159
+ )
160
+
161
+ frames = downsample_frames(
162
+ frames,
163
+ block_size=block_size,
164
+ method=downsample_method
165
+ )
166
+
167
+ voxels = frames_to_voxels(frames)
168
+ mesh = voxels_to_mesh(frames, voxels)
169
+
170
+ tmp = tempfile.gettempdir()
171
+ obj = os.path.join(tmp, "output.obj")
172
+ glb = os.path.join(tmp, "output.glb")
173
+
174
+ mesh.export(obj)
175
+ mesh.export(glb)
176
+
177
+ return obj, glb, glb
178
+
179
+
180
+ # -------------------------
181
+ # GRADIO UI
182
+ # -------------------------
183
+ iface = gr.Interface(
184
+ fn=generate_voxel_files,
185
+ inputs=[
186
+ gr.File(label="Upload MP4 (or leave empty for checkerboard)"),
187
+ gr.Slider(0, 500, value=0, step=1, label="Start Frame"),
188
+ gr.Slider(0, 500, value=50, step=1, label="End Frame"),
189
+ gr.Slider(1, 10, value=1, step=1, label="Frame Step"),
190
+ gr.Slider(1, 32, value=1, step=1, label="Pixel Block Size"),
191
+ gr.Radio(["stride", "mean"], value="stride", label="Downsample Method"),
192
+ ],
193
+ outputs=[
194
+ gr.File(label="OBJ"),
195
+ gr.File(label="GLB"),
196
+ gr.Model3D(label="3D Preview"),
197
+ ],
198
+ title="MP4 → Voxels → 3D",
199
+ description="If no file is uploaded, a random-color checkerboard appears."
200
+ )
201
+
202
+ if __name__ == "__main__":
203
+ iface.launch(server_name="0.0.0.0", server_port=7860, debug=True)