Spaces:
Running
on
Zero
Running
on
Zero
support simulation json
Browse files
app.py
CHANGED
@@ -1,25 +1,220 @@
|
|
|
|
|
|
|
|
|
|
1 |
import spaces
|
2 |
import gradio as gr
|
3 |
from gradio_rerun import Rerun
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
|
6 |
@spaces.GPU
|
7 |
-
def
|
8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
|
|
|
|
|
|
|
10 |
|
11 |
-
|
12 |
-
|
|
|
|
|
13 |
|
|
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
with gr.Blocks() as demo:
|
|
|
|
|
|
|
|
|
|
|
16 |
with gr.Row():
|
17 |
-
file_input = gr.File(
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
21 |
)
|
22 |
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
-
|
|
|
|
1 |
+
import json
|
2 |
+
import numpy as np
|
3 |
+
import rerun as rr
|
4 |
+
from rerun.components import Material
|
5 |
import spaces
|
6 |
import gradio as gr
|
7 |
from gradio_rerun import Rerun
|
8 |
+
from scipy.spatial.transform import Rotation
|
9 |
+
import tempfile
|
10 |
+
import os
|
11 |
+
from typing import Optional, Dict, Any, List, Tuple
|
12 |
+
|
13 |
+
|
14 |
+
def vector3_to_numpy(vec):
|
15 |
+
"""Convert Vector3 dictionary to numpy array"""
|
16 |
+
return np.array([vec['x'], vec['y'], vec['z']])
|
17 |
+
|
18 |
+
|
19 |
+
def euler_to_quaternion(euler):
|
20 |
+
"""Convert Euler angles dictionary to quaternion"""
|
21 |
+
return Rotation.from_euler('xyz', [euler['x'], euler['y'], euler['z']]).as_quat()
|
22 |
+
|
23 |
+
|
24 |
+
def create_subject_mesh(subject):
|
25 |
+
"""Create a simple cube mesh for a subject"""
|
26 |
+
position = vector3_to_numpy(subject['position'])
|
27 |
+
size = vector3_to_numpy(subject['size'])
|
28 |
+
|
29 |
+
# Create cube vertices
|
30 |
+
vertices = np.array([
|
31 |
+
[-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5],
|
32 |
+
[-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5]
|
33 |
+
]) * size.reshape(1, 3) + position.reshape(1, 3)
|
34 |
+
|
35 |
+
# Create cube faces
|
36 |
+
faces = np.array([
|
37 |
+
[0, 1, 2], [0, 2, 3], # front
|
38 |
+
[1, 5, 6], [1, 6, 2], # right
|
39 |
+
[5, 4, 7], [5, 7, 6], # back
|
40 |
+
[4, 0, 3], [4, 3, 7], # left
|
41 |
+
[3, 2, 6], [3, 6, 7], # top
|
42 |
+
[4, 5, 1], [4, 1, 0] # bottom
|
43 |
+
])
|
44 |
+
|
45 |
+
return vertices, faces
|
46 |
+
|
47 |
+
|
48 |
+
def log_simulation(simulation_data: Dict[str, Any]) -> None:
|
49 |
+
"""Log single simulation data to Rerun"""
|
50 |
+
rr.init("camera_simulation")
|
51 |
+
|
52 |
+
subjects = simulation_data['subjects']
|
53 |
+
camera_frames = simulation_data['cameraFrames']
|
54 |
+
instructions = simulation_data['instructions']
|
55 |
+
|
56 |
+
# Log simulation metadata
|
57 |
+
rr.log("metadata/instructions", rr.TextDocument(
|
58 |
+
"\n".join([
|
59 |
+
f"Instruction {i+1}:\n" +
|
60 |
+
f" Movement: {inst['cameraMovement']}\n" +
|
61 |
+
f" Easing: {inst['movementEasing']}\n" +
|
62 |
+
f" Frames: {inst['frameCount']}\n" +
|
63 |
+
f" Camera Angle: {inst.get('initialCameraAngle', 'N/A')}\n" +
|
64 |
+
f" Shot Type: {inst.get('initialShotType', 'N/A')}\n" +
|
65 |
+
f" Subject Index: {inst.get('subjectIndex', 'N/A')}"
|
66 |
+
for i, inst in enumerate(instructions)
|
67 |
+
])
|
68 |
+
), timeless=True)
|
69 |
+
|
70 |
+
# Set up world coordinate system
|
71 |
+
rr.log("world", rr.ViewCoordinates.RIGHT_HAND_Y_UP, timeless=True)
|
72 |
+
|
73 |
+
# Log subjects (as simple cubes)
|
74 |
+
for idx, subject in enumerate(subjects):
|
75 |
+
vertices, faces = create_subject_mesh(subject)
|
76 |
+
subject_color = [0.8, 0.2, 0.2, 1.0] if idx == simulation_data.get(
|
77 |
+
'selectedSubject') else [0.8, 0.8, 0.8, 1.0]
|
78 |
+
|
79 |
+
rr.log(
|
80 |
+
f"world/subject_{idx}",
|
81 |
+
rr.Mesh3D(
|
82 |
+
vertex_positions=vertices,
|
83 |
+
indices=faces,
|
84 |
+
mesh_material=Material(albedo_factor=subject_color)
|
85 |
+
),
|
86 |
+
timeless=True
|
87 |
+
)
|
88 |
+
|
89 |
+
# Log subject class
|
90 |
+
rr.log(f"world/subject_{idx}/class",
|
91 |
+
rr.TextDocument(subject['objectClass']),
|
92 |
+
timeless=True)
|
93 |
+
|
94 |
+
# Log camera trajectory
|
95 |
+
camera_positions = np.array(
|
96 |
+
[vector3_to_numpy(frame['position']) for frame in camera_frames])
|
97 |
+
rr.log(
|
98 |
+
"world/camera_trajectory",
|
99 |
+
rr.Points3D(camera_positions),
|
100 |
+
timeless=True
|
101 |
+
)
|
102 |
+
|
103 |
+
# Log camera positions over time
|
104 |
+
for frame_idx, camera_frame in enumerate(camera_frames):
|
105 |
+
rr.set_time_sequence("frame", frame_idx)
|
106 |
+
|
107 |
+
position = vector3_to_numpy(camera_frame['position'])
|
108 |
+
rotation_q = euler_to_quaternion(camera_frame['angle'])
|
109 |
+
|
110 |
+
# Log camera transform
|
111 |
+
rr.log(
|
112 |
+
"world/camera",
|
113 |
+
rr.Transform3D(
|
114 |
+
translation=position,
|
115 |
+
rotation=rr.Quaternion(xyzw=rotation_q)
|
116 |
+
)
|
117 |
+
)
|
118 |
+
|
119 |
+
# Log camera frustum
|
120 |
+
rr.log(
|
121 |
+
"world/camera/view",
|
122 |
+
rr.Pinhole(
|
123 |
+
focal_length=camera_frame['focalLength'],
|
124 |
+
width=1920,
|
125 |
+
height=1080
|
126 |
+
)
|
127 |
+
)
|
128 |
+
|
129 |
+
|
130 |
+
def load_simulation_data(file) -> Tuple[Optional[List[Dict[str, Any]]], Optional[List[str]]]:
|
131 |
+
"""Load simulation data from JSON file and return simulations with their descriptions"""
|
132 |
+
if file is None:
|
133 |
+
return None, None
|
134 |
+
|
135 |
+
try:
|
136 |
+
json_data = json.load(open(file.name))
|
137 |
+
simulations = json_data['simulations']
|
138 |
+
|
139 |
+
# Create descriptions for each simulation
|
140 |
+
descriptions = [
|
141 |
+
f"Simulation {i}: {len(sim['subjects'])} subjects, {len(sim['instructions'])} instructions"
|
142 |
+
for i, sim in enumerate(simulations)
|
143 |
+
]
|
144 |
+
|
145 |
+
return simulations, descriptions
|
146 |
+
except Exception as e:
|
147 |
+
print(f"Error loading simulation data: {str(e)}")
|
148 |
+
return None, None
|
149 |
|
150 |
|
151 |
@spaces.GPU
|
152 |
+
def visualize_simulation(file, simulation_index: int) -> Optional[str]:
|
153 |
+
"""Process selected simulation and create Rerun visualization"""
|
154 |
+
if file is None:
|
155 |
+
return None
|
156 |
+
|
157 |
+
try:
|
158 |
+
simulations, _ = load_simulation_data(file)
|
159 |
+
if simulations is None or simulation_index >= len(simulations):
|
160 |
+
return None
|
161 |
|
162 |
+
# Create temporary file for RRD
|
163 |
+
temp_dir = tempfile.mkdtemp()
|
164 |
+
rrd_path = os.path.join(temp_dir, "simulation.rrd")
|
165 |
|
166 |
+
# Log selected simulation
|
167 |
+
simulation = simulations[simulation_index]
|
168 |
+
log_simulation(simulation)
|
169 |
+
rr.save(rrd_path)
|
170 |
|
171 |
+
return rrd_path
|
172 |
|
173 |
+
except Exception as e:
|
174 |
+
print(f"Error processing simulation: {str(e)}")
|
175 |
+
return None
|
176 |
+
|
177 |
+
|
178 |
+
def update_simulation_dropdown(file):
|
179 |
+
"""Update simulation dropdown when file is uploaded"""
|
180 |
+
_, descriptions = load_simulation_data(file)
|
181 |
+
return gr.Dropdown(choices=descriptions if descriptions else [], value=None)
|
182 |
+
|
183 |
+
|
184 |
+
# Create Gradio interface
|
185 |
with gr.Blocks() as demo:
|
186 |
+
gr.Markdown("""
|
187 |
+
# Camera Simulation Visualizer
|
188 |
+
Upload a JSON file containing camera simulation data and select a simulation to visualize.
|
189 |
+
""")
|
190 |
+
|
191 |
with gr.Row():
|
192 |
+
file_input = gr.File(
|
193 |
+
label="Upload Simulation JSON",
|
194 |
+
file_types=[".json"]
|
195 |
+
)
|
196 |
+
simulation_dropdown = gr.Dropdown(
|
197 |
+
label="Select Simulation",
|
198 |
+
choices=[],
|
199 |
+
type="index"
|
200 |
)
|
201 |
|
202 |
+
with gr.Row():
|
203 |
+
viewer = Rerun(streaming=False)
|
204 |
+
|
205 |
+
# Update dropdown when file is uploaded
|
206 |
+
file_input.change(
|
207 |
+
update_simulation_dropdown,
|
208 |
+
inputs=[file_input],
|
209 |
+
outputs=[simulation_dropdown]
|
210 |
+
)
|
211 |
+
|
212 |
+
# Visualize selected simulation
|
213 |
+
simulation_dropdown.change(
|
214 |
+
visualize_simulation,
|
215 |
+
inputs=[file_input, simulation_dropdown],
|
216 |
+
outputs=[viewer]
|
217 |
+
)
|
218 |
|
219 |
+
if __name__ == "__main__":
|
220 |
+
demo.queue().launch(share=False)
|