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