예제 #1
0
def split_polygon_by_mask(X, mask):
    # split X by inclusion or exclusion
    indices = np.where(np.diff(mask, prepend=np.nan))[0]
    l = np.split(X, indices)[1:]
    X_inclusion = l[::2] if mask[0] else l[1::2]
    X_exclusion = l[::2] if not mask[0] else l[1::2]
    if len(X_inclusion) % 2 == 0:
        # fold all
        n = len(X_inclusion)
        X_inclusion = util.map_to_list(
            np.concatenate,
            zip(X_inclusion[:n // 2], X_inclusion[:n // 2 - 1:-1]))
        # take middle and fold rest
        n = len(X_exclusion)
        X_exclusion = util.map_to_list(np.concatenate, zip(X_exclusion[:n // 2], X_exclusion[:n // 2:-1])) \
                + [X_exclusion[n // 2]]
    else:
        # take middle and fold rest
        n = len(X_inclusion)
        X_inclusion = util.map_to_list(np.concatenate, zip(X_inclusion[:n // 2], X_inclusion[:n // 2:-1])) \
                + [X_inclusion[n // 2]]
        # fold all
        n = len(X_exclusion)
        X_exclusion = util.map_to_list(
            np.concatenate,
            zip(X_exclusion[:n // 2], X_exclusion[:n // 2 - 1:-1]))
    return X_inclusion, X_exclusion
예제 #2
0
def keep_blueprint(bp):
    if int(bp.get_attribute('number_of_wheels')) != 4:
        return False
    name = bp.id
    endswith = util.map_to_list(lambda skip_name: name.endswith(skip_name),
                                SKIP_VEHICLE_NAMES)
    return not any(endswith)
예제 #3
0
    def __init__(self):
        """Load map data from cache and collect shapes of all the intersections."""
        self.map_datum = {}
        # map to Shapely MultiPolygon for junction entrance/exits
        self.map_to_smpolys = {}
        # map to Shapely Circle covering junction region
        self.map_to_scircles = {}

        logger.info("Retrieve map from cache.")
        for map_name in CARLA_MAP_NAMES:
            cachepath = f"{CACHEDIR}/map_data.{map_name}.pkl"
            with open(cachepath, "rb") as f:
                payload = dill.load(f, encoding="latin1")
            self.map_datum[map_name] = util.AttrDict(
                road_polygons=payload["road_polygons"],
                white_lines=payload["white_lines"],
                yellow_lines=payload["yellow_lines"],
                junctions=payload["junctions"],
            )

        logger.info("Retrieving some data from map.")
        for map_name in CARLA_MAP_NAMES:
            for _junction in self.map_datum[map_name].junctions:
                f = lambda x, y, yaw, l: util.vertices_from_bbox(
                    np.array([x, y]), yaw, np.array([5.0, 0.95 * l]))
                vertex_set = util.map_to_ndarray(
                    lambda wps: util.starmap(f, wps), _junction.waypoints)
                smpolys = util.map_to_list(vertex_set_to_smpoly, vertex_set)
                util.setget_list_from_dict(self.map_to_smpolys,
                                           map_name).extend(smpolys)
                x, y = _junction.pos
                scircle = shapely.geometry.Point(x, y).buffer(
                    self.TLIGHT_DETECT_RADIUS)
                util.setget_list_from_dict(self.map_to_scircles,
                                           map_name).append(scircle)
예제 #4
0
 def __lidar_snapshot_to_populate_vehicle_visibility(
         self, lidar_measurement, points, labels, object_ids):
     """Called by capture_lidar() to obtain vehicle visibility from
     segmented LIDAR points.
     """
     frame = lidar_measurement.frame
     if (frame -
             self.__first_frame) % self.__scene_config.record_interval == 0:
         frame_id = int((frame - self.__first_frame) /
                        self.__scene_config.record_interval)
         vehicle_label_mask = labels == SegmentationLabel.Vehicle.value
         object_ids = np.unique(object_ids[vehicle_label_mask])
         self.__vehicle_visibility[frame_id] = set(
             util.map_to_list(str, object_ids))
         self.__vehicle_visibility[frame_id].add("ego")
예제 #5
0
def get_straight_lanes(start_wp, tol=2.0):
    """Get the points corresponding to the straight parts of all
    lanes in road starting from start_wp going in the same direction.

    Parameters
    ==========
    start_wp : carla.Waypoint
        The starting point of the road lane.
    tol : float (optional)
        The tolerance to select straight part of the road in meters.

    Returns
    =======
    list of np.array
        Each item in the list are points for one lane of of shape (N_i, 2).
        Each (x, y) point is in meters in world coordinates.
    """
    _, start_yaw, _ = carlautil.to_rotation_ndarray(start_wp)
    wps = get_adjacent_waypoints(start_wp)
    f = lambda wp: get_straight_line(wp, start_yaw, tol=tol)
    return np.concatenate(util.map_to_list(f, wps))
    def __init__(self,
                 frame,
                 phi,
                 world,
                 other_vehicles,
                 player_transforms,
                 others_transforms,
                 player_bbox,
                 other_id_ordering=None,
                 radius=200):
        """
        1. generates a history of player and other vehicle position coordinates
           of size len(player_transforms)

        Parameters
        ----------
        frame : int
            Frame ID of observation
        world : carla.World
        other_vehicles : list of carla.Vehicle
        player_transform : collections.deque of carla.Transform
            Collection of transforms of player
            Ordered by timestep where last in deque is current timestep
        others_transforms : collections.deque of (dict of int : carla.Trajectory)
            Collection of transforms of other vehicles by vehicle ID
            Ordered by timestep where last in deque is current timestep
        A : int
            Number of vehicles in observation, including ego vehicle. Must be A > 1.
        other_id_ordering : list of int
            IDs of other (not player) vehicles (not walkers).
            All IDs in other_id_ordering belong to some vehicle ID
        radius : int
        """
        _, _, self.T_past, _ = tensoru.shape(self.phi.S_past_world_frame)
        self.B, self.A, self.T, self.D = tensoru.shape(
            self.phi.S_future_world_frame)
        self.B, self.H, self.W, self.C = tensoru.shape(
            self.phi.overhead_features)
        assert (len(player_transforms) == len(others_transforms))
        assert (self.A > 0)

        # player_transform : carla.Transform
        # transform of current player
        self.player_transform = player_transforms[-1]

        # player_positions_world : ndarray of shape (len(player_transforms), 3)
        self.player_positions_world = carlautil.transforms_to_location_ndarray(
            self.player_transforms)
        # player_positions_local : ndarray of shape (len(player_transforms), 3)
        self.player_positions_local = self.player_positions_world \
                - carlautil.transform_to_location_ndarray(self.player_transform)
        # player_yaw : float
        self.player_yaw = carlautil.transform_to_yaw(self.player_transform)

        if self.other_id_ordering is None:
            # get list of A agents within radius close to us
            # note that other_id_ordering may have size smaller than A-1
            ids = self.__get_nearby_agent_ids()
            self.other_id_ordering = ids[:self.A - 1]

        if self.other_id_ordering.shape != (0, ):
            others_positions_list = [None] * len(self.others_transforms)
            for idx, others_transform in enumerate(self.others_transforms):
                others_positions_list[idx] = util.map_to_list(
                    lambda aid: carlautil.transform_to_location_ndarray(
                        others_transform[aid]), self.other_id_ordering)

            # agent_positions_world : ndarray of shape (A-1, len(self.others_transforms), 3)
            self.agent_positions_world = np.array(
                others_positions_list).transpose(1, 0, 2)

            others_transform = self.others_transforms[-1]
            # agent_yaws : ndarray of shape (A-1,)
            self.agent_yaws = util.map_to_ndarray(
                lambda aid: carlautil.transform_to_yaw(others_transform[aid]),
                self.other_id_ordering)

            self.n_missing = max(
                self.A - 1 - self.agent_positions_world.shape[0], 0)
            self.has_other_agents = True
        else:
            self.agent_positions_world = np.array([])
            self.agent_yaws = np.array([])
            self.n_missing = self.A - 1
            self.has_other_agents = False

        if self.n_missing > 0:
            faraway_position = carlautil.transform_to_location_ndarray(self.player_transform) \
                    + np.array([0, 300, 0])
            faraway_tile = np.tile(
                faraway_position,
                (self.n_missing, len(self.others_transforms), 1))
            if self.n_missing == self.A - 1:
                self.agent_positions_world = faraway_tile
                self.agent_yaws = np.zeros(self.A - 1)
            else:
                self.agent_positions_world = np.concatenate(
                    (self.agent_positions_world, faraway_tile), axis=0)
                self.agent_yaws = np.concatenate(
                    (self.agent_yaws, np.zeros(self.n_missing)), axis=0)

        # agent_positions_local : ndarray of shape (A-1, len(self.others_transforms), 3)
        self.agent_positions_local = self.agent_positions_world \
                - carlautil.transform_to_location_ndarray(self.player_transform)
예제 #7
0
def get_road_segment_enclosure(start_wp, tol=2.0):
    """Get rectangle that tightly inner approximates of the road segment
    containing the starting waypoint.

    Parameters
    ==========
    start_wp : carla.Waypoint
        A starting waypoint of the road.
    tol : float (optional)
        The tolerance to select straight part of the road in meters.

    Returns
    =======
    np.array
        The position and the heading angle of the starting waypoint
        of the road of form [x, y, angle] in (meters, meters, radians).
    np.array
        The 2D bounding box enclosure in world coordinates of shape (4, 2)
        enclosing the road segment.
    np.array
        The parameters of the enclosure of form
        (b_length, f_length, r_width, l_width)
        If the enclosure is in the reference frame such that the starting
        waypoint points along +x-axis, then the enclosure has these length
        and widths:
        ____________________________________
        |              l_width             |
        |               |                  |
        |               |                  |
        | b_length -- (x, y)-> -- f_length |
        |               |                  |
        |               |                  |
        |              r_width             |
        ------------------------------------
    """
    _LENGTH = -2
    _, start_yaw, _ = carlautil.to_rotation_ndarray(start_wp)
    adj_wps = get_adjacent_waypoints(start_wp)
    # mtx : np.array
    #   Rotation matrix from world coordinates, both frames in UE orientation
    mtx = util.rotation_2d(-start_yaw)
    # rev_mtx : np.array
    #   Rotation matrix to world coordinates, both frames in UE orientation
    rev_mtx = util.rotation_2d(start_yaw)
    s_x, s_y, _ = carlautil.to_location_ndarray(start_wp)

    # Get points of lanes
    f = lambda wp: get_straight_line(wp, start_yaw, tol=tol)
    pc = util.map_to_list(f, adj_wps)

    # Get length of bb for lanes
    def g(points):
        points = points - np.array([s_x, s_y])
        points = (rev_mtx @ points.T)[0]
        return np.abs(np.max(points) - np.min(points))

    lane_lengths = util.map_to_ndarray(g, pc)
    length = np.min(lane_lengths)

    # Get width of bb for lanes
    lwp, rwp = adj_wps[0], adj_wps[-1]
    l_x, l_y, _ = carlautil.to_location_ndarray(lwp)
    r_x, r_y, _ = carlautil.to_location_ndarray(rwp)
    points = np.array([[l_x, l_y], [s_x, s_y], [r_x, r_y]])
    points = points @ rev_mtx.T
    l_width = np.abs(points[0, 1] - points[1, 1]) + lwp.lane_width / 2.
    r_width = np.abs(points[1, 1] - points[2, 1]) + rwp.lane_width / 2.

    # construct bounding box of road segment
    x, y, _ = carlautil.to_location_ndarray(start_wp)
    vec = np.array([[0, 0], [_LENGTH, 0]]) @ mtx.T
    dx0, dy0 = vec[1, 0], vec[1, 1]
    vec = np.array([[0, 0], [length, 0]]) @ mtx.T
    dx1, dy1 = vec[1, 0], vec[1, 1]
    vec = np.array([[0, 0], [0, -l_width]]) @ mtx.T
    dx2, dy2 = vec[1, 0], vec[1, 1]
    vec = np.array([[0, 0], [0, r_width]]) @ mtx.T
    dx3, dy3 = vec[1, 0], vec[1, 1]
    bbox = np.array([[x + dx0 + dx3, y + dy0 + dy3],
                     [x + dx1 + dx3, y + dy1 + dy3],
                     [x + dx1 + dx2, y + dy1 + dy2],
                     [x + dx0 + dx2, y + dy0 + dy2]])
    start_wp_spec = np.array([s_x, s_y, start_yaw])
    bbox_spec = np.array([_LENGTH, length, r_width, l_width])
    return start_wp_spec, bbox, bbox_spec
예제 #8
0
 def __capture_agents_within_radius(self, frame_id):
     labels = self.__map_reader.get_actor_labels(self.__ego_vehicle)
     if self.__should_exclude_dataset_sample(labels):
         self.__remove_scene_builder()
     player_location = carlautil.to_location_ndarray(self.__ego_vehicle)
     player_z = player_location[2]
     if len(self.__other_vehicles):
         other_ids = list(self.__other_vehicles.keys())
         other_vehicles = self.__other_vehicles.values()
         others_data = carlautil.actors_to_Lxyz_Vxyz_Axyz_Rpyr_ndarray(
             other_vehicles).T
         other_locations = others_data[:3].T
         distances = np.linalg.norm(other_locations - player_location,
                                    axis=1)
         df = pd.DataFrame({
             "frame_id":
             np.full((len(other_ids), ), frame_id),
             "type":
             [self.__scene_config.node_type.VEHICLE] * len(other_ids),
             "node_id":
             util.map_to_list(str, other_ids),
             "robot": [False] * len(other_ids),
             "distances":
             distances,
             "x":
             others_data[0],
             "y":
             others_data[1],
             "z":
             others_data[2],
             "v_x":
             others_data[3],
             "v_y":
             others_data[4],
             "v_z":
             others_data[5],
             "a_x":
             others_data[6],
             "a_y":
             others_data[7],
             "a_z":
             others_data[8],
             "length":
             others_data[9],
             "width":
             others_data[10],
             "height":
             others_data[11],
             "heading":
             others_data[13],
         })
         df = df[df["distances"] < self.__radius]
         df = df[df["z"].between(
             player_z - self.Z_LOWERBOUND,
             player_z + self.Z_UPPERBOUND,
             inclusive="neither",
         )]
         del df["distances"]
     else:
         df = pd.DataFrame(columns=[
             "frame_id",
             "type",
             "node_id",
             "robot",
             "x",
             "y",
             "z",
             "length",
             "width",
             "height",
             "heading",
         ])
     ego_data = carlautil.actor_to_Lxyz_Vxyz_Axyz_Rpyr_ndarray(
         self.__ego_vehicle)
     data_point = pd.DataFrame.from_records([{
         "frame_id":
         frame_id,
         "type":
         self.__scene_config.node_type.VEHICLE,
         "node_id":
         "ego",
         "robot":
         True,
         "x":
         ego_data[0],
         "y":
         ego_data[1],
         "z":
         ego_data[2],
         "v_x":
         ego_data[3],
         "v_y":
         ego_data[4],
         "v_z":
         ego_data[5],
         "a_x":
         ego_data[6],
         "a_y":
         ego_data[7],
         "a_z":
         ego_data[8],
         "length":
         ego_data[9],
         "width":
         ego_data[10],
         "height":
         ego_data[11],
         "heading":
         ego_data[13],
     }])
     df = pd.concat((df, data_point), ignore_index=True)
     # df = df.append(data_point, ignore_index=True)
     if self.__trajectory_data is None:
         self.__trajectory_data = df
     else:
         self.__trajectory_data = pd.concat((self.__trajectory_data, df),
                                            ignore_index=True)
예제 #9
0
def actors_to_location_ndarray(alist):
    """Converts iterable of carla.Actor to a ndarray of size (len(alist), 3)"""
    return np.array(util.map_to_list(actor_to_location_ndarray, alist))
예제 #10
0
def transforms_to_location_ndarray(ts):
    """Converts an iterable of carla.Transform to a ndarray of size (len(iterable), 3)"""
    return np.array(util.map_to_list(transform_to_location_ndarray, ts))
예제 #11
0
def vehicles_to_xyz_lwh_pyr_ndarray(alist):
    """Converts iterable of carla.Actor transformation
    to an ndarray of size (len(alist), 9) where each row is
    [x, y, z, length, width, height, pitch, yaw, roll]"""
    return np.array(util.map_to_list(vehicle_to_xyz_lwh_pyr_ndarray, alist))
예제 #12
0
    def make_local_params(self, frame, ovehicles):
        """Get the local optimization parameters used for current MPC step."""
        """Get parameters to construct control and state variables."""
        params = util.AttrDict()
        params.frame = frame
        p_0_x, p_0_y, _ = carlautil.to_location_ndarray(self.__ego_vehicle,
                                                        flip_y=True)
        _, psi_0, _ = carlautil.actor_to_rotation_ndarray(self.__ego_vehicle,
                                                          flip_y=True)
        v_0_mag = self.get_current_velocity()
        x_init = np.array([p_0_x, p_0_y, psi_0, v_0_mag])
        initial_state = util.AttrDict(world=x_init,
                                      local=np.array([0, 0, 0, v_0_mag]))
        params.initial_state = initial_state
        # try:
        #     u_init = self.__U_warmstarting[self.__step_horizon]
        # except TypeError:
        #     u_init = np.array([0., 0.])
        # Using previous control doesn't work
        u_init = np.array([0.0, 0.0])
        x_bar, u_bar, Gamma, nx, nu = self.__vehicle_model.get_optimization_ltv(
            x_init, u_init)
        params.x_bar, params.u_bar, params.Gamma = x_bar, u_bar, Gamma
        params.nx, params.nu = nx, nu
        """Get parameters for other vehicles."""
        # O - number of obstacles
        params.O = len(ovehicles)
        # K - for each o=1,...,O K[o] is the number of outer approximations for vehicle o
        params.K = np.zeros(params.O, dtype=int)
        for idx, vehicle in enumerate(ovehicles):
            params.K[idx] = vehicle.n_states
        """Get parameters for (random) multiple coinciding control."""
        # N_traj : int
        #   Number of planned trajectories possible to compute
        params.N_traj = np.prod(params.K)
        if self.__random_mcc:
            """How does random multiple coinciding control work?
            each item in the product set S_1 X S_2 X S_3 X S_4
            represents the particular choices each vehicles make 
            get the subset of the product set S_1 X S_2 X S_3 X S_4
            such that for each i = 1..4, for each j in S_i there is
            a tuple in the subset with j in the i-th place.
            """
            vehicle_n_states = [ovehicle.n_states for ovehicle in ovehicles]
            n_states_max = max(vehicle_n_states)
            vehicle_state_ids = [
                util.range_to_list(n_states) for n_states in vehicle_n_states
            ]

            def preprocess_state_ids(state_ids):
                state_ids = state_ids + random.choices(
                    state_ids, k=n_states_max - len(state_ids))
                random.shuffle(state_ids)
                return state_ids

            vehicle_state_ids = util.map_to_list(preprocess_state_ids,
                                                 vehicle_state_ids)
            # sublist_joint_decisions : list of (list of int)
            #   Subset of S_1 X S_2 X S_3 X S_4 of joint decisions
            params.sublist_joint_decisions = np.array(
                vehicle_state_ids).T.tolist()
            # N_select : int
            #   Number of planned trajectories to compute
            params.N_select = len(params.sublist_joint_decisions)

        else:
            # sublist_joint_decisions : list of (list of int)
            #   Entire set of S_1 X S_2 X S_3 X S_4 of joint decision outcomes
            params.sublist_joint_decisions = util.product_list_of_list([
                util.range_to_list(ovehicle.n_states) for ovehicle in ovehicles
            ])
            # N_select : int
            #   Number of planned trajectories to compute, equal to N_traj.
            params.N_select = len(params.sublist_joint_decisions)

        return params
예제 #13
0
    (["is_at_intersection",
      "is_significant_car"], ["significant_at_intersection"]),
    (["is_at_intersection", "is_stopped_car"], ["stopped_at_intersection"]),
    (["is_at_intersection"], ["other_at_intersection"]),
    ### counts outside of intersection (other)
    (["is_major_turn", "is_significant_car"], ["turn_at_other"]),
    (["is_minor_turn", "is_significant_car"], ["turn_at_other"]),
    (["is_significant_car"], ["significant_at_other"]),
    (["is_stopped_car"], ["stopped_at_other"]),
    ([], ["other_at_other"])
]

# List of scene attributes
SCENEATTRS = util.deduplicate(
    util.merge_list_of_list(
        util.map_to_list(util.second, NODEATTR_SCENEATTR_MAP)))


class TrajectronSceneData(object):
    """Trajectron++ scene data. Used to inspect data, count samples in the dataset,
    and reweight samples when generating a dataset.
    
    Attributes
    ==========
    n_nodes : int
        Number of nodes.
    nodeid_scene_dict : dict of (str, Scene)
        scene+node ID => scene the node is in
    sceneid_scene_dict : dict of (str, Scene)
        scene ID => scene
    sceneid_count_dict : dict of (str, util.AttrDict)
예제 #14
0
def main(config):
    pp.pprint(vars(config))
    group_creator = SampleGroupCreator(config)
    split_creator = CrossValidationSplitCreator(config)

    logger.info("Loading the data.")
    envs = []
    for file_path in config.data_files:
        try:
            loadpath = os.path.join(config.data_dir, file_path)
            with open(loadpath, "rb") as f:
                env = dill.load(f, encoding="latin1")
                envs.append(env)
        except:
            logger.warning(f"Failed to load {file_path} from data directory")
            logger.warning(config.data_dir)

    env = None
    if len(envs) == 0:
        logger.warning("No data. Doing nothing.")
        return
    elif len(envs) > 0:
        logger.info(f"Loaded {len(envs)} data payloads.")
        env = envs[0]
    for _env in envs[1:]:
        env.scenes.extend(_env.scenes)
    logger.info(f"Collected {len(env.scenes)} samples.")

    sample_dict = {scene.name: scene for scene in env.scenes}
    sample_ids = list(sample_dict.keys())
    if config.weighted_resampling:
        logger.info("Inspecting scene data to use for weighted resampling.")
        scene_data = TrajectronSceneData(env.scenes)
        scene_data.log_node_count(filename="raw_scene_histogram")
    else:
        scene_data = None
    logger.info("Creating group indices of samples.")
    groups = group_creator.make_groups(sample_ids)
    logger.info("Creating split indices of samples.")
    splits = split_creator.make_splits(groups)

    augmentations_dict = dict()
    for idx, split in enumerate(splits[: config.n_splits]):
        split_idx = idx + 1
        logger.info(f"Making (train, val, test) split {split_idx}.")
        train_env = make_environment(env.name)
        val_env = make_environment(env.name)
        test_env = make_environment(env.name)

        if config.weighted_resampling:
            train_split = scene_data.sample_scenes(config.n_train, scene_ids=split[0])
        else:
            train_split = split[0][slice(config.n_train)]

        # create augmented scenes as needed
        unaugmented_ids = set(train_split) - set(augmentations_dict.keys())
        logger.info(f"Augmenting {len(unaugmented_ids)} scenes for split {split_idx}.")
        for sample_id in tqdm(unaugmented_ids):
            scene = sample_dict[sample_id]
            augmentations_dict[sample_id] = list()
            angles = np.arange(0, 360, 15)
            for angle in angles:
                augmentations_dict[sample_id].append(augment_scene(scene, angle))

        # add all existing augmentations to all scenes right before saving train set.
        for sample_id, augments in augmentations_dict.items():
            sample_dict[sample_id].augmented = augments

        train_env.scenes = util.map_to_list(sample_dict.get, train_split)
        n_train_samples = len(train_env.scenes)

        logger.info(f"Train set has {n_train_samples} scenes.")
        logger.info("Saving train set.")
        savepath = os.path.join(
            config.split_dir,
            "{label}_split{split_idx}_train.pkl".format(
                split_idx=split_idx, label=config.label
            ),
        )
        with open(savepath, "wb") as f:
            dill.dump(train_env, f, protocol=dill.HIGHEST_PROTOCOL)

        # remove all existing augmentations from all scenes right after saving train set.
        for sample_id in augmentations_dict.keys():
            del sample_dict[sample_id].augmented

        if config.weighted_resampling:
            val_split = scene_data.sample_scenes(config.n_val, scene_ids=split[1])
            test_split = scene_data.sample_scenes(config.n_test, scene_ids=split[2])
        else:
            val_split = split[1][slice(config.n_val)]
            test_split = split[2][slice(config.n_test)]
        val_env.scenes = util.map_to_list(sample_dict.get, val_split)
        n_val_samples = len(val_env.scenes)
        test_env.scenes = util.map_to_list(sample_dict.get, test_split)
        n_test_samples = len(test_env.scenes)

        # naive augmenting for each split
        # for scene in tqdm(train_env.scenes):
        #     scene.augmented = list()
        #     angles = np.arange(0, 360, 15)
        #     for angle in angles:
        #         scene.augmented.append(augment_scene(scene, angle))

        logger.info(f"Val set has {n_val_samples} scenes.")
        logger.info("Saving val set.")
        savepath = os.path.join(
            config.split_dir,
            "{label}_split{split_idx}_val.pkl".format(
                split_idx=split_idx, label=config.label
            ),
        )
        with open(savepath, "wb") as f:
            dill.dump(val_env, f, protocol=dill.HIGHEST_PROTOCOL)

        logger.info(f"Test set has {n_test_samples} scenes.")
        logger.info("Saving test set.")
        savepath = os.path.join(
            config.split_dir,
            "{label}_split{split_idx}_test.pkl".format(
                split_idx=split_idx, label=config.label
            ),
        )
        with open(savepath, "wb") as f:
            dill.dump(test_env, f, protocol=dill.HIGHEST_PROTOCOL)

    logger.info("Done.")