//  [python]
def _process_dataset(self, test_img_dir, test_meta_fn, train_img_dir,
                         train_meta_fn, num_val):
        """Read and process the datasets using PyTorch its ImageFolder class.

        Additionally, this method splits the training set into train
        and validation set.

        The following attributes are added to the class:
            _torch_ds_train: A PyTorch Dataset class representing the training
                set.
            _torch_ds_test: A PyTorch Dataset class representing the validation
                set (corresponds to the dataset in "val_dir").
            _torch_ds_val: A PyTorch Dataset class representing the validation
                set (A subset of the training set).

        Args:
            (....): See docstring of constructor.
            test_img_dir: Path to test images.
            test_meta_fn: Path to test ``interpolated.csv`` file.
            train_img_dir: Path to training images.
            train_meta_fn: Path to training ``interpolated.csv`` file.
        """
        # Read raw dataset using the PyTorch ImageFolder class.
        train_transform, test_transform = \
            UdacityCh2Data.torch_input_transforms()
        ds_train = datasets.ImageFolder(train_img_dir, train_transform)
        ds_test = datasets.ImageFolder(test_img_dir, test_transform)
        ds_val = None

        ### Read interpolated steering angles ###
        test_meta = pd.read_csv(test_meta_fn)
        train_meta = pd.read_csv(train_meta_fn)

        # In case only the HMB_3_release bag is available, and not the actual
        # test set.
        self._test_angles_available = True
        if np.all(np.isclose(test_meta['angle'].to_numpy(), 0)):
            self._test_angles_available = False
            warn('No steering angles are available for the test data! Zeroes ' +
                 'will be filled in instead.')

        for df, ds, dd in zip([test_meta, train_meta],
                              [ds_test, ds_train],
                              [test_img_dir, train_img_dir]):
            lbl_dict = {}
            for index, row in df.iterrows():
                if row['frame_id'] != 'center_camera':
                    continue

                fn = os.path.join(dd, row['filename'])
                lbl_dict[fn] = row['angle']

            for ii, ss in enumerate(ds.samples):
                ds.samples[ii] = (ss[0], lbl_dict[ss[0]])
                ds.targets[ii] = lbl_dict[ss[0]]

        ### Split training set into train/val set.
        if num_val > 0:
            orig_samples = ds_train.samples
            ds_train.samples = None
            ds_train.imgs = None
            ds_train.targets = None

            ds_val = deepcopy(ds_train)
            ds_val.transform = test_transform
            assert ds_val.target_transform is None

            ds_train.samples = []
            ds_train.imgs = ds_train.samples
            ds_val.samples = []
            ds_val.imgs = ds_val.samples

            # Always same validation set!
            rstate = np.random.RandomState(42)
            train_ind, val_ind = train_test_split( \
                np.arange(len(orig_samples)),
                test_size=num_val, shuffle=True, random_state=rstate,
                stratify=None)

            ds_train.samples.extend([orig_samples[ii] for ii in train_ind])
            ds_val.samples.extend([orig_samples[ii] for ii in val_ind])

            ds_train.targets = [s[1] for s in ds_train.samples]
            ds_val.targets = [s[1] for s in ds_val.samples]

            for ds_obj in [ds_train, ds_val]:
                assert len(ds_obj.samples) == len(ds_obj.imgs) and \
                       len(ds_obj.samples) == len(ds_obj.targets)

        self._torch_ds_train = ds_train
        self._torch_ds_test = ds_test
        self._torch_ds_val = ds_val