def _is_moving(o_id: int, transforms: Transforms,
                   rigidbodies: Rigidbodies) -> bool:
        """
        :param o_id: The ID of the object.
        :param transforms: The Transforms output data.
        :param rigidbodies: The Rigidbodies output data.

        :return: True if the object is still moving.
        """

        y: Optional[float] = None
        sleeping: bool = False

        for i in range(transforms.get_num()):
            if transforms.get_id(i) == o_id:
                y = transforms.get_position(i)[1]
                break
        assert y is not None, f"y value is none for {o_id}"

        for i in range(rigidbodies.get_num()):
            if rigidbodies.get_id(i) == o_id:
                sleeping = rigidbodies.get_sleeping(i)
                break
        # If the object isn't sleeping, it is still moving.
        # If the object fell into the abyss, we don't count it as moving (to prevent an infinitely long simulation).
        return not sleeping and y > -10
Exemple #2
0
 def _set_tower_height_now(self, resp: List[bytes]) -> None:
     top_obj_id = self.object_ids[-1]
     for r in resp[:-1]:
         r_id = OutputData.get_data_type_id(r)
         if r_id == "tran":
             tr = Transforms(r)
             for i in range(tr.get_num()):
                 if tr.get_id(i) == top_obj_id:
                     self.tower_height = tr.get_position(i)[1]
    def get_object_position(self, obj_id: int, resp: List[bytes]) -> None:
        position = None
        for r in resp:
            r_id = OutputData.get_data_type_id(r)
            if r_id == "tran":
                tr = Transforms(r)
                for i in range(tr.get_num()):
                    if tr.get_id(i) == obj_id:
                        position = tr.get_position(i)

        return position
Exemple #4
0
 def is_done(self, resp: List[bytes], frame: int) -> bool:
     for r in resp[:-1]:
         r_id = OutputData.get_data_type_id(r)
         # If the ball reaches or overshoots the destination, the trial is done.
         if r_id == "tran":
             t = Transforms(r)
             d0 = TDWUtils.get_distance(
                 TDWUtils.array_to_vector3(t.get_position(0)), self._p0)
             d1 = TDWUtils.get_distance(self._p0, self._p1)
             return d0 > d1 * 1.5
     return False
Exemple #5
0
    def is_done(self, resp: List[bytes], frame: int) -> bool:
        # The ball must have a positive x coordinate (moving away from occluder) and be out of frame.
        positive_x = False
        segmentation_color = False
        for r in resp:
            r_id = OutputData.get_data_type_id(r)
            if r_id == "tran":
                t = Transforms(r)
                for i in range(t.get_num()):
                    if t.get_id(i) == self._ball_id:
                        positive_x = t.get_position(i)[0] >= 2
            elif r_id == "ipsc":
                ip = IdPassSegmentationColors(r)
                segmentation_color = ip.get_num_segmentation_colors() == 1

        return positive_x and segmentation_color
Exemple #6
0
    def get_transforms_data(self, object_ids):
        resp = self.communicate({
            "$type": "send_transforms",
            "frequency": "once",
            "ids": object_ids
        })

        transforms = Transforms(resp[0])
        return transforms
Exemple #7
0
    def move_objects_along_forward(self, transforms: Transforms, delta, y=0):
        commands = []
        for i in range(transforms.get_num()):
            object_id = transforms.get_id(i)
            position = transforms.get_position(i)
            forward = transforms.get_forward(i)

            deltas = [d * delta for d in forward]

            position = {
                "x": position[0] + deltas[0],
                "y": y,
                "z": position[2] + deltas[2]
            }
            commands.append({
                "$type": "teleport_object",
                "id": object_id,
                "position": position
            })
        self.communicate(commands)
    def _get_transforms(resp: List[bytes]) -> Transforms:
        """
        :param resp: The output data response.

        :return: Transforms data.
        """

        for r in resp[:-1]:
            if OutputData.get_data_type_id(r) == "tran":
                return Transforms(r)
        raise Exception("Transforms output data not found!")
Exemple #9
0
    def _get_frame_state(t: Transforms, r: Rigidbodies, num: int) -> dict:
        """
        Returns a dictionary representing the current frame state.

        :param t: The transforms data.
        :param r: The rigidbodies data.
        :param num: The current frame.
        """

        objects = dict()
        assert t.get_num() == r.get_num()
        for i in range(t.get_num()):
            objects.update({
                t.get_id(i): {
                    "position": t.get_position(i),
                    "rotation": t.get_rotation(i),
                    "forward": t.get_forward(i),
                    "velocity": r.get_velocity(i),
                    "angular_velocity": r.get_angular_velocity(i),
                    "mass": r.get_mass(i)
                }
            })
        return {"frame": num, "objects": objects}
Exemple #10
0
    def run(self):
        rng = np.random.RandomState(0)
        self.model_librarian = ModelLibrarian("models_special.json")
        self.start()
        commands = [TDWUtils.create_empty_room(100, 100)]

        # The starting height of the objects.
        y = 10
        # The radius of the circle of objects.
        r = 7.0
        # The mass of each object.
        mass = 5

        # Get all points within the circle defined by the radius.
        p0 = np.array((0, 0))
        o_id = 0
        for x in np.arange(-r, r, 1):
            for z in np.arange(-r, r, 1):
                p1 = np.array((x, z))
                dist = np.linalg.norm(p0 - p1)
                if dist < r:
                    # Add an object.
                    # Set its mass, physics properties, and color.
                    commands.extend([self.get_add_object("prim_cone",
                                                         object_id=o_id,
                                                         position={"x": x, "y": y, "z": z},
                                                         rotation={"x": 0, "y": 0, "z": 180}),
                                     {"$type": "set_mass",
                                      "id": o_id,
                                      "mass": mass},
                                     {"$type": "set_physic_material",
                                      "dynamic_friction": 0.8,
                                      "static_friction": 0.7,
                                      "bounciness": 0.5,
                                      "id": o_id},
                                     {"$type": "set_color",
                                      "color": {"r": rng.random_sample(),
                                                "g": rng.random_sample(),
                                                "b": rng.random_sample(),
                                                "a": 1.0},
                                      "id": o_id}])
                    o_id += 1
        # Request transforms per frame.
        commands.extend([{"$type": "send_transforms",
                          "frequency": "always"}])
        # Create an avatar to observe the grisly spectacle.
        avatar_position = {"x": -20, "y": 8, "z": 18}
        commands.extend(TDWUtils.create_avatar(position=avatar_position, look_at=TDWUtils.VECTOR3_ZERO))
        commands.append({"$type": "set_focus_distance",
                         "focus_distance": TDWUtils.get_distance(avatar_position, TDWUtils.VECTOR3_ZERO)})
        resp = self.communicate(commands)

        # If an objects are this far away from (0, 0, 0) the forcefield "activates".
        forcefield_radius = 5
        # The forcefield will bounce objects away at this force.
        forcefield_force = -10
        zeros = np.array((0, 0, 0))
        for i in range(1000):
            transforms = Transforms(resp[0])
            commands = []
            for j in range(transforms.get_num()):
                pos = transforms.get_position(j)
                pos = np.array(pos)
                # If the object is in the forcefield, apply a force.
                if TDWUtils.get_distance(TDWUtils.array_to_vector3(pos), TDWUtils.VECTOR3_ZERO) <= forcefield_radius:
                    # Get the normalized directional vector and multiply it by the force magnitude.
                    d = zeros - pos
                    d = d / np.linalg.norm(d)
                    d = d * forcefield_force
                    commands.append({"$type": "apply_force_to_object",
                                     "id": transforms.get_id(j),
                                     "force": TDWUtils.array_to_vector3(d)})
            resp = self.communicate(commands)
    def _write_frame(self, frames_grp: h5py.Group, resp: List[bytes], frame_num: int) -> \
            Tuple[h5py.Group, h5py.Group, dict, bool]:
        num_objects = len(self.object_ids)

        # Create a group for this frame.
        frame = frames_grp.create_group(TDWUtils.zero_padding(frame_num, 4))

        # Create a group for images.
        images = frame.create_group("images")

        # Transforms data.
        positions = np.empty(dtype=np.float32, shape=(num_objects, 3))
        forwards = np.empty(dtype=np.float32, shape=(num_objects, 3))
        rotations = np.empty(dtype=np.float32, shape=(num_objects, 4))

        camera_matrices = frame.create_group("camera_matrices")

        # Parse the data in an ordered manner so that it can be mapped back to the object IDs.
        tr_dict = dict()

        for r in resp[:-1]:
            r_id = OutputData.get_data_type_id(r)
            if r_id == "tran":
                tr = Transforms(r)
                for i in range(tr.get_num()):
                    pos = tr.get_position(i)
                    tr_dict.update({
                        tr.get_id(i): {
                            "pos": pos,
                            "for": tr.get_forward(i),
                            "rot": tr.get_rotation(i)
                        }
                    })
                # Add the Transforms data.
                for o_id, i in zip(self.object_ids, range(num_objects)):
                    if o_id not in tr_dict:
                        continue
                    positions[i] = tr_dict[o_id]["pos"]
                    forwards[i] = tr_dict[o_id]["for"]
                    rotations[i] = tr_dict[o_id]["rot"]
            elif r_id == "imag":
                im = Images(r)
                # Add each image.
                for i in range(im.get_num_passes()):
                    pass_mask = im.get_pass_mask(i)
                    # Reshape the depth pass array.
                    if pass_mask == "_depth":
                        image_data = TDWUtils.get_shaped_depth_pass(images=im,
                                                                    index=i)
                    else:
                        image_data = im.get_image(i)
                    images.create_dataset(pass_mask,
                                          data=image_data,
                                          compression="gzip")

                    # Save PNGs
                    if pass_mask in self.save_passes:
                        filename = pass_mask[1:] + "_" + TDWUtils.zero_padding(
                            frame_num, 4) + "." + im.get_extension(i)
                        path = self.png_dir.joinpath(filename)
                        if pass_mask in ["_depth", "_depth_simple"]:
                            Image.fromarray(
                                TDWUtils.get_shaped_depth_pass(
                                    images=im, index=i)).save(path)
                        else:
                            with open(path, "wb") as f:
                                f.write(im.get_image(i))

            # Add the camera matrices.
            elif OutputData.get_data_type_id(r) == "cama":
                matrices = CameraMatrices(r)
                camera_matrices.create_dataset(
                    "projection_matrix", data=matrices.get_projection_matrix())
                camera_matrices.create_dataset(
                    "camera_matrix", data=matrices.get_camera_matrix())

        objs = frame.create_group("objects")
        objs.create_dataset("positions",
                            data=positions.reshape(num_objects, 3),
                            compression="gzip")
        objs.create_dataset("forwards",
                            data=forwards.reshape(num_objects, 3),
                            compression="gzip")
        objs.create_dataset("rotations",
                            data=rotations.reshape(num_objects, 4),
                            compression="gzip")

        return frame, objs, tr_dict, False
Exemple #12
0
    def get_video(self,
                  a_and_s: _AnimationAndScene,
                  humanoids: List[HumanoidRecord],
                  skyboxes: List[HDRISkyboxRecord],
                  pbar: Optional[tqdm] = None,
                  num_camera_iterations: int = 1,
                  h_id: int = 0,
                  min_duration: float = 2.5,
                  overwrite: bool = False) -> None:
        """
        Create a single animation video as a series of images that will be written to the disk.

        :param a_and_s: The animation/scene combo.
        :param humanoids: The humanoid records. Iterate through all of these.
        :param skyboxes: The HDRI skyboxes. Iterate through some of these (equal to num_camera_iterations).
        :param pbar: The progress bar (optional).
        :param num_camera_iterations: Make this many videos of each specific animation/humanoid/scene/skybox combo, changing the camera angle each time.
        :param h_id: The object ID of the humanoid.
        :param min_duration: The minimum duration of the video in seconds.
        :param overwrite: If True, overwrite existing images.
        """

        # Skip some frames to maintain 30 FPS.
        save_per = a_and_s.animation.framerate / 30

        # Randomize the skyboxes without randomizing the original list.
        skyboxes_temp = skyboxes[:]
        HumanoidVideo.RNG.shuffle(skyboxes_temp)
        assert num_camera_iterations < len(skyboxes_temp), f"Number of camera iterations ({num_camera_iterations})" \
                                                           f" exceeds number of skyboxes ({len(skyboxes_temp)}"

        # Determine how many times to loop the animation.
        total_duration = 0
        num_loops = 0
        while total_duration < min_duration:
            num_loops += 1
            total_duration += a_and_s.animation.duration

        for humanoid in humanoids:
            for i in range(num_camera_iterations):
                name = a_and_s.animation.name + "-" + a_and_s.scene.name + "-" + humanoid.name + "-" + str(
                    i)
                # Prepare the output directory for the images.
                output_dir = self.root_dir.joinpath(name)
                metadata_path = output_dir.joinpath("metadata.json")

                # Skip a completed video.
                if metadata_path.exists() and not overwrite:
                    if pbar is not None:
                        pbar.update(1)
                    continue

                if not output_dir.exists():
                    output_dir.mkdir(parents=True)
                output_dir = str(output_dir.resolve())

                # Add a skybox if this scene is HDRI-enabled and exterior.
                add_skybox = a_and_s.scene.hdri and a_and_s.scene.location == "exterior"
                skybox = skyboxes_temp.pop(0)

                if pbar is not None:
                    pbar.set_description(name)

                # Per-frame, focus on the object and look at the object.
                # Pitch the camera slightly upward so that it isn't pointing at the stomach.
                per_frame_commands = [{
                    "$type": "focus_on_object",
                    "object_id": h_id,
                    "use_centroid": True
                }, {
                    "$type": "look_at",
                    "object_id": h_id,
                    "use_centroid": True
                }, {
                    "$type": "rotate_sensor_container_by",
                    "axis": "pitch",
                    "angle": -5
                }, {
                    "$type": "send_images",
                    "frequency": "once"
                }]

                # Write metadata to disk.
                metadata = {
                    "humanoid": humanoid.name,
                    "animation": a_and_s.animation.name,
                    "scene": a_and_s.scene.name,
                    "location": a_and_s.scene.location,
                    "skybox": skybox.name if add_skybox else "",
                    "datetime": str(datetime.now()),
                    "tdw_version": __version__
                }

                # Load the scene, avatar, and humanoid.
                # Play the animation and begin image capture.
                commands = [{
                    "$type": "add_scene",
                    "name": a_and_s.scene.name,
                    "url": a_and_s.scene.get_url()
                }, {
                    "$type": "add_humanoid_animation",
                    "name": a_and_s.animation.name,
                    "url": a_and_s.animation.get_url()
                }, {
                    "$type": "create_avatar",
                    "type": "A_Img_Caps_Kinematic",
                    "id": "a"
                }, {
                    "$type": "add_humanoid",
                    "name": humanoid.name,
                    "url": humanoid.get_url(),
                    "position": a_and_s.position,
                    "rotation": a_and_s.rotation,
                    "id": h_id
                }, {
                    "$type": "set_pass_masks",
                    "pass_masks": ["_img"]
                }]
                if add_skybox:
                    commands.extend([
                        self.get_add_hdri_skybox(skybox.name), {
                            "$type": "set_post_exposure",
                            "post_exposure": 0.4
                        }, {
                            "$type": "set_aperture",
                            "aperture": 1.6
                        }, {
                            "$type": "set_contrast",
                            "contrast": -20
                        }
                    ])
                else:
                    # Set default post-processing values.
                    commands.extend([{
                        "$type": "set_post_exposure"
                    }, {
                        "$type": "set_aperture"
                    }, {
                        "$type": "set_contrast"
                    }])
                commands.extend(per_frame_commands)
                self.communicate(commands)

                frame = 0
                for loop in range(num_loops):
                    # Start to play the animation.
                    # Try to get all of the humanoid in the viewport.
                    init_loop_commands = per_frame_commands[:-1]
                    init_loop_commands.extend([{
                        "$type": "play_humanoid_animation",
                        "name": a_and_s.animation.name,
                        "id": h_id
                    }])
                    self.communicate(init_loop_commands)

                    if loop == 0:
                        resp = self.communicate({
                            "$type": "send_humanoids",
                            "frequency": "once",
                            "ids": [h_id]
                        })
                        tr = Transforms(resp[0])
                        o_pos = tr.get_position(0)
                        # Camera distance from the humanoid.
                        avatar_d = HumanoidVideo.RNG.uniform(-2.2, -4.5)
                        # A reasonable rotation.
                        avatar_theta = np.radians(
                            HumanoidVideo.RNG.uniform(-85, 85))
                        # Get the avatar position from the angle and distance.
                        avatar_x = o_pos[0] + (avatar_d * np.cos(avatar_theta))
                        avatar_z = o_pos[2] + (avatar_d * np.sin(avatar_theta))
                        avatar_y = HumanoidVideo.RNG.uniform(
                            1.8, 2 + (avatar_d * 0.12))
                        per_frame_commands.append({
                            "$type": "teleport_avatar_to",
                            "position": {
                                "x": avatar_x,
                                "y": avatar_y,
                                "z": avatar_z
                            }
                        })
                    # Play the animation.
                    count = 0
                    while count < a_and_s.animation.get_num_frames():
                        # Drop frames to stay at 30 FPS.
                        if count % save_per == 0:
                            resp = self.communicate(per_frame_commands)
                            TDWUtils.save_images(Images(resp[0]),
                                                 TDWUtils.zero_padding(frame),
                                                 output_dir,
                                                 append_pass=False)
                            frame += 1
                        else:
                            self.communicate([])
                        count += 1

                # Destroy the humanoid.
                self.communicate({"$type": "destroy_humanoid", "id": h_id})

                # Write the metadata file (signalling that the video was completed).
                metadata_path.write_text(json.dumps(metadata, indent=4),
                                         encoding="utf-8")

                if pbar is not None:
                    pbar.update(1)
Exemple #13
0
                        "z": 0
                    },
                    rotation={
                        "x": 0,
                        "y": 0,
                        "z": 0
                    },
                    library="models_full.json")
c.communicate({"$type": "send_transforms", "frequency": "always"})

char = getch()

if char == b'w':
    resp = c.communicate({
        "$type": "teleport_object",
        "position": {
            "x": 1,
            "y": 0,
            "z": 0
        },
        "id": o_id
    })
    transform = Transforms(resp[0])

    # Get the position and rotation of the iron_box object.
    position = transform.get_position(0)
    rotation = transform.get_rotation(0)

    print("Position: " + position)
    print("Rotation: " + rotation)
Exemple #14
0
    def run(self, c: Controller):
        """
        Run the trial and save the output.

        :param c: The controller.
        """

        print(f"Images will be saved to: {self.output_dir}")

        # Initialize the scene.
        resp = c.communicate(self.init_commands)
        # Get a map of the segmentation colors.
        segm = SegmentationColors(resp[0])
        for i in range(segm.get_num()):
            for obj in self.moving_objects:
                if obj.object_id == segm.get_object_id(i):
                    obj.possibility.segmentation_color = segm.get_object_color(
                        i)

        # Request scene data and images per frame.
        frame_data: List[dict] = []
        resp = c.communicate([{
            "$type": "send_images",
            "frequency": "always"
        }, {
            "$type": "send_transforms",
            "ids": self.m_ids,
            "frequency": "always"
        }, {
            "$type": "send_rigidbodies",
            "ids": self.m_ids,
            "frequency": "always"
        }])

        # Run the trial.
        for frame in range(self.num_frames):
            colors: Dict[int, Tuple[int, int, int]] = {}
            transforms: Dict[int, Tuple[float, float, float]] = {}

            transform_data = None
            rigidbody_data = None

            # Parse the output data.
            for r in resp[:-1]:
                r_id = OutputData.get_data_type_id(r)
                # Record all Transforms data.
                if r_id == "tran":
                    transform_data = Transforms(r)
                    for i in range(transform_data.get_num()):
                        transforms.update({
                            transform_data.get_id(i):
                            transform_data.get_position(i)
                        })
                # Record all Rigidbodies data.
                elif r_id == "rigi":
                    rigidbody_data = Rigidbodies(r)
                # Save the images.
                elif r_id == "imag":
                    images = Images(r)
                    for p in range(images.get_num_passes()):
                        if images.get_pass_mask(p) == "_id":
                            image_colors = TDWUtils.get_pil_image(
                                images, p).getcolors()
                            for ic in image_colors:
                                color = ic[1]
                                for obj in self.moving_objects:
                                    if obj.possibility.segmentation_color == color:
                                        colors.update({obj.object_id: color})

                    TDWUtils.save_images(Images(r),
                                         TDWUtils.zero_padding(frame),
                                         output_directory=self.output_dir)

            # Append frame data.
            frame_data.append(
                Trial._get_frame_state(transform_data, rigidbody_data, frame))

            # Build the frame state.
            state = State(colors, transforms, frame)

            # Apply object actions.
            commands = []
            for o in self.occluders:
                commands.extend(o.get_frame_commands(state))
            for mo in self.moving_objects:
                commands.extend(mo.get_frame_commands(state))
            if len(commands) == 0:
                commands = [{"$type": "do_nothing"}]

            # Send the commands and update the state.
            resp = c.communicate(commands)

        # Cleanup.
        c.communicate([{
            "$type": "destroy_all_objects"
        }, {
            "$type": "unload_asset_bundles"
        }, {
            "$type": "send_images",
            "frequency": "never"
        }, {
            "$type": "send_transforms",
            "ids": self.m_ids,
            "frequency": "never"
        }, {
            "$type": "send_rigidbodies",
            "ids": self.m_ids,
            "frequency": "never"
        }])

        print("\tGenerated images.")
        # Output the scene metadata.
        Path(self.output_dir).joinpath("state.json").write_text(
            json.dumps({"frames": frame_data}), encoding="utf-8")
        print("\tWrote state file.")

        # Get _id passes with randomized colors.
        self._randomize_segmentation_colors()
        print("\tCreated random segmentation colors.")

        # Organize the images.
        self._organize_output()
        print("\tOrganized files")
Exemple #15
0
    def get_real_grayscale(
            self, o_id: int, a_id: str,
            e: Environment) -> (float, float, dict, dict, dict, dict):
        """
        Get the "real" grayscale value of an image we hope to capture.

        :param o_id: The ID of the object.
        :param a_id: The ID of the avatar.
        :param e: The environment.
        
        :return: (grayscale, distance, avatar_position, object_position, object_rotation, avatar_rotation)
        """

        # Get a random position for the avatar.
        a_p = np.array([
            RNG.uniform(-e.w / 2, e.w / 2) + e.x,
            RNG.uniform(0.4, e.h * self.max_height),
            RNG.uniform(-e.l / 2, e.l / 2) + e.z
        ])

        # Get a random distance from the avatar.
        d = RNG.uniform(0.8, 3)

        # Get a random position for the object constrained to the environment bounds.
        o_p = SingleObject.sample_spherical() * d
        # Clamp the y value to positive.
        o_p[1] = abs(o_p[1])
        o_p = a_p + o_p

        # Clamp the y value of the object.
        if o_p[1] > e.h * self.max_height:
            o_p[1] = e.h * self.max_height

        # Convert the avatar's position to a Vector3.
        a_p = TDWUtils.array_to_vector3(a_p)

        # Set random camera rotations.
        yaw = RNG.uniform(-15, 15)
        pitch = RNG.uniform(-15, 15)

        # Convert the object position to a Vector3.
        o_p = TDWUtils.array_to_vector3(o_p)

        # Add rotation commands.
        # If we're clamping the rotation, rotate the object within +/- 30 degrees on each axis.
        if self.clamp_rotation:
            o_rot = None
            commands = [{
                "$type": "rotate_object_to",
                "id": o_id,
                "rotation": {
                    "x": 0,
                    "y": 0,
                    "z": 0,
                    "w": 0
                }
            }, {
                "$type": "rotate_object_by",
                "id": o_id,
                "angle": RNG.uniform(-30, 30),
                "axis": "pitch"
            }, {
                "$type": "rotate_object_by",
                "id": o_id,
                "angle": RNG.uniform(-30, 30),
                "axis": "yaw"
            }, {
                "$type": "rotate_object_by",
                "id": o_id,
                "angle": RNG.uniform(-30, 30),
                "axis": "roll"
            }]
        # Set a totally random rotation.
        else:
            o_rot = {
                "x": RNG.uniform(-360, 360),
                "y": RNG.uniform(-360, 360),
                "z": RNG.uniform(-360, 360),
                "w": RNG.uniform(-360, 360),
            }
            commands = [{
                "$type": "rotate_object_to",
                "id": o_id,
                "rotation": o_rot
            }]

        # After rotating the object:
        # 1. Teleport the object.
        # 2. Teleport the avatar.
        # 3. Look at the object.
        # 4. Perturb the camera slightly.
        # 5. Send grayscale data and image sensor data.
        commands.extend([{
            "$type": "teleport_object",
            "id": o_id,
            "position": o_p
        }, {
            "$type": "teleport_avatar_to",
            "avatar_id": a_id,
            "position": a_p
        }, {
            "$type": "look_at",
            "avatar_id": a_id,
            "object_id": o_id,
            "use_centroid": True
        }, {
            "$type": "rotate_sensor_container_by",
            "angle": pitch,
            "avatar_id": "a",
            "axis": "pitch"
        }, {
            "$type": "rotate_sensor_container_by",
            "angle": yaw,
            "avatar_id": "a",
            "axis": "yaw"
        }, {
            "$type": "send_id_pass_grayscale",
            "frequency": "once"
        }, {
            "$type": "send_image_sensors",
            "frequency": "once"
        }])

        resp = self.communicate(commands)

        # Parse the output data:
        # 1. The grayscale value of the image.
        # 2. The camera rotation.
        grayscale = 0
        cam_rot = None
        for r in resp[:-1]:
            r_id = OutputData.get_data_type_id(r)
            if r_id == "idgs":
                grayscale = IdPassGrayscale(r).get_grayscale()
            elif r_id == "imse":
                cam_rot = ImageSensors(r).get_sensor_rotation(0)
                cam_rot = {
                    "x": cam_rot[0],
                    "y": cam_rot[1],
                    "z": cam_rot[2],
                    "w": cam_rot[3]
                }

        # If we clamped the rotation of the object, we need to know its quaternion.
        if self.clamp_rotation:
            resp = self.communicate({
                "$type": "send_transforms",
                "frequency": "once",
                "ids": [o_id]
            })
            t = Transforms(resp[0])
            o_rot = t.get_rotation(0)
            o_rot = {
                "x": o_rot[0],
                "y": o_rot[1],
                "z": o_rot[2],
                "w": o_rot[3],
            }

        return grayscale, d, a_p, o_p, o_rot, cam_rot
    def _write_frame(self, frames_grp: h5py.Group, resp: List[bytes], frame_num: int) -> \
            Tuple[h5py.Group, h5py.Group, dict, bool]:
        num_objects = len(self.object_ids)

        # Create a group for this frame.
        frame = frames_grp.create_group(TDWUtils.zero_padding(frame_num, 4))
        # Create a group for images.
        images = frame.create_group("images")

        # Transforms data.
        positions = np.empty(dtype=np.float32, shape=(num_objects, 3))
        forwards = np.empty(dtype=np.float32, shape=(num_objects, 3))
        rotations = np.empty(dtype=np.float32, shape=(num_objects, 4))

        camera_matrices = frame.create_group("camera_matrices")

        # Parse the data in an ordered manner so that it can be mapped back to the object IDs.
        tr_dict = dict()

        for r in resp[:-1]:
            r_id = OutputData.get_data_type_id(r)
            if r_id == "tran":
                tr = Transforms(r)
                for i in range(tr.get_num()):
                    pos = tr.get_position(i)
                    tr_dict.update({
                        tr.get_id(i): {
                            "pos": pos,
                            "for": tr.get_forward(i),
                            "rot": tr.get_rotation(i)
                        }
                    })
                # Add the Transforms data.
                for o_id, i in zip(self.object_ids, range(num_objects)):
                    if o_id not in tr_dict:
                        continue
                    positions[i] = tr_dict[o_id]["pos"]
                    forwards[i] = tr_dict[o_id]["for"]
                    rotations[i] = tr_dict[o_id]["rot"]
            elif r_id == "imag":
                im = Images(r)
                # Add each image.
                for i in range(im.get_num_passes()):
                    images.create_dataset(im.get_pass_mask(i),
                                          data=im.get_image(i),
                                          compression="gzip")
            # Add the camera matrices.
            elif OutputData.get_data_type_id(r) == "cama":
                matrices = CameraMatrices(r)
                camera_matrices.create_dataset(
                    "projection_matrix", data=matrices.get_projection_matrix())
                camera_matrices.create_dataset(
                    "camera_matrix", data=matrices.get_camera_matrix())

        objs = frame.create_group("objects")
        objs.create_dataset("positions",
                            data=positions.reshape(num_objects, 3),
                            compression="gzip")
        objs.create_dataset("forwards",
                            data=forwards.reshape(num_objects, 3),
                            compression="gzip")
        objs.create_dataset("rotations",
                            data=rotations.reshape(num_objects, 4),
                            compression="gzip")

        return frame, objs, tr_dict, False