File size: 7,052 Bytes
247f6df
 
 
231a433
3167cfe
 
247f6df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a202f8d
 
247f6df
 
 
 
 
 
 
 
 
 
 
 
 
 
a202f8d
 
 
 
 
247f6df
 
 
a202f8d
247f6df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a202f8d
 
 
 
 
 
247f6df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3167cfe
 
231a433
247f6df
 
 
 
 
 
 
 
 
231a433
247f6df
 
 
231a433
247f6df
 
 
 
3167cfe
247f6df
3167cfe
247f6df
 
 
 
 
 
 
 
 
 
 
 
3167cfe
247f6df
 
 
 
 
3167cfe
247f6df
 
 
 
 
 
 
 
3167cfe
 
247f6df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3167cfe
247f6df
 
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
import json
import numpy as np
import rerun as rr
import spaces
import gradio as gr
from gradio_rerun import Rerun
from scipy.spatial.transform import Rotation
import tempfile
import os
from typing import Optional, Dict, Any, List, Tuple


def vector3_to_numpy(vec):
    """Convert Vector3 dictionary to numpy array"""
    return np.array([vec['x'], vec['y'], vec['z']])


def euler_to_quaternion(euler):
    """Convert Euler angles dictionary to quaternion"""
    return Rotation.from_euler('xyz', [euler['x'], euler['y'], euler['z']]).as_quat()


def create_subject_mesh(subject):
    """Create a simple cube mesh for a subject"""
    position = vector3_to_numpy(subject['position'])
    size = vector3_to_numpy(subject['size'])

    # Create cube vertices
    vertices = np.array([
        [-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5],
        [-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5]
    ]) * size.reshape(1, 3) + position.reshape(1, 3)

    # Create cube faces
    faces = np.array([
        [0, 1, 2], [0, 2, 3],  # front
        [1, 5, 6], [1, 6, 2],  # right
        [5, 4, 7], [5, 7, 6],  # back
        [4, 0, 3], [4, 3, 7],  # left
        [3, 2, 6], [3, 6, 7],  # top
        [4, 5, 1], [4, 1, 0]   # bottom
    ])

    return vertices, faces


def log_simulation(simulation_data: Dict[str, Any]) -> None:
    """Log single simulation data to Rerun"""
    rr.init("camera_simulation")

    subjects = simulation_data['subjects']
    camera_frames = simulation_data['cameraFrames']
    instructions = simulation_data['instructions']

    # Log simulation metadata
    rr.log("metadata/instructions", rr.TextDocument(
        "\n".join([
            f"Instruction {i+1}:\n" +
            f"  Movement: {inst['cameraMovement']}\n" +
            f"  Easing: {inst['movementEasing']}\n" +
            f"  Frames: {inst['frameCount']}\n" +
            f"  Camera Angle: {inst.get('initialCameraAngle', 'N/A')}\n" +
            f"  Shot Type: {inst.get('initialShotType', 'N/A')}\n" +
            f"  Subject Index: {inst.get('subjectIndex', 'N/A')}"
            for i, inst in enumerate(instructions)
        ])
    ), timeless=True)

    # Set up world coordinate system
    rr.log("world", rr.ViewCoordinates.RIGHT_HAND_Y_UP, timeless=True)

    # Log subjects (as simple cubes)
    for idx, subject in enumerate(subjects):
        vertices, faces = create_subject_mesh(subject)
        subject_color = [0.8, 0.2, 0.2, 1.0] if idx == simulation_data.get(
            'selectedSubject') else [0.8, 0.8, 0.8, 1.0]

        rr.log(
            f"world/subject_{idx}",
            rr.Mesh3D(
                vertex_positions=vertices,
                indices=faces,
                # Apply color to all vertices
                colors=np.tile(subject_color, (len(vertices), 1))
            ),
            timeless=True
        )

        # Log subject class
        rr.log(f"world/subject_{idx}/class",
               rr.TextDocument(subject['objectClass']),
               timeless=True)

    # Log camera trajectory
    camera_positions = np.array(
        [vector3_to_numpy(frame['position']) for frame in camera_frames])
    rr.log(
        "world/camera_trajectory",
        rr.Points3D(
            camera_positions,
            # Cyan color for trajectory
            colors=np.full((len(camera_positions), 4), [0.0, 0.8, 0.8, 1.0])
        ),
        timeless=True
    )

    # Log camera movement over time
    for frame_idx, camera_frame in enumerate(camera_frames):
        rr.set_time_sequence("frame", frame_idx)

        position = vector3_to_numpy(camera_frame['position'])
        rotation_q = euler_to_quaternion(camera_frame['angle'])

        # Log camera transform
        rr.log(
            "world/camera",
            rr.Transform3D(
                translation=position,
                rotation=rr.Quaternion(xyzw=rotation_q)
            )
        )

        # Log camera frustum
        rr.log(
            "world/camera/view",
            rr.Pinhole(
                focal_length=camera_frame['focalLength'],
                width=1920,
                height=1080
            )
        )

        # Log frame number
        rr.log(
            "metadata/current_frame",
            rr.TextDocument(f"Frame: {frame_idx + 1}/{len(camera_frames)}"),
        )


def load_simulation_data(file) -> Tuple[Optional[List[Dict[str, Any]]], Optional[List[str]]]:
    """Load simulation data from JSON file and return simulations with their descriptions"""
    if file is None:
        return None, None

    try:
        json_data = json.load(open(file.name))
        simulations = json_data['simulations']

        # Create descriptions for each simulation
        descriptions = [
            f"Simulation {i}: {len(sim['subjects'])} subjects, {len(sim['instructions'])} instructions"
            for i, sim in enumerate(simulations)
        ]

        return simulations, descriptions
    except Exception as e:
        print(f"Error loading simulation data: {str(e)}")
        return None, None


@spaces.GPU
def visualize_simulation(file, simulation_index: int) -> Optional[str]:
    """Process selected simulation and create Rerun visualization"""
    if file is None:
        return None

    try:
        simulations, _ = load_simulation_data(file)
        if simulations is None or simulation_index >= len(simulations):
            return None

        # Create temporary file for RRD
        temp_dir = tempfile.mkdtemp()
        rrd_path = os.path.join(temp_dir, "simulation.rrd")

        # Log selected simulation
        simulation = simulations[simulation_index]
        log_simulation(simulation)
        rr.save(rrd_path)

        return rrd_path

    except Exception as e:
        print(f"Error processing simulation: {str(e)}")
        return None


def update_simulation_dropdown(file):
    """Update simulation dropdown when file is uploaded"""
    _, descriptions = load_simulation_data(file)
    return gr.Dropdown(choices=descriptions if descriptions else [], value=None)


# Create Gradio interface
with gr.Blocks() as demo:
    gr.Markdown("""
    # Camera Simulation Visualizer
    Upload a JSON file containing camera simulation data and select a simulation to visualize.
    """)

    with gr.Row():
        file_input = gr.File(
            label="Upload Simulation JSON",
            file_types=[".json"]
        )
        simulation_dropdown = gr.Dropdown(
            label="Select Simulation",
            choices=[],
            type="index"
        )

    with gr.Row():
        viewer = Rerun(streaming=False)

    # Update dropdown when file is uploaded
    file_input.change(
        update_simulation_dropdown,
        inputs=[file_input],
        outputs=[simulation_dropdown]
    )

    # Visualize selected simulation
    simulation_dropdown.change(
        visualize_simulation,
        inputs=[file_input, simulation_dropdown],
        outputs=[viewer]
    )

if __name__ == "__main__":
    demo.queue().launch(share=False)