Example #1
0
def test_TimeSeries_append():
    max_val = 2.0
    max_idx = 156
    data_orig = np.random.rand(10, 10, 1000)
    data_orig[4, 4, max_idx] = max_val
    ts_orig = utils.TimeSeries(1.0, data_orig)

    # Make sure adding two TimeSeries objects works:
    # Must have the right type and size
    ts = copy.deepcopy(ts_orig)
    with pytest.raises(TypeError):
        ts.append(4.0)
    with pytest.raises(ValueError):
        ts_wrong_size = utils.TimeSeries(1.0, np.ones((2, 2)))
        ts.append(ts_wrong_size)

    # Adding messes only with the last dimension of the array
    ts = copy.deepcopy(ts_orig)
    ts.append(ts)
    npt.assert_equal(ts.shape[:-1], ts_orig.shape[:-1])
    npt.assert_equal(ts.shape[-1], ts_orig.shape[-1] * 2)

    # If necessary, the second pulse train is resampled to the first
    ts = copy.deepcopy(ts_orig)
    tsample_new = 2.0
    ts_new = ts.resample(tsample_new)
    ts.append(ts_new)
    npt.assert_equal(ts.shape[:-1], ts_orig.shape[:-1])
    npt.assert_equal(ts.shape[-1], ts_orig.shape[-1] * 2)
    ts_add = copy.deepcopy(ts_new)
    ts_add.append(ts_orig)
    npt.assert_equal(ts_add.shape[:-1], ts_new.shape[:-1])
    npt.assert_equal(ts_add.shape[-1], ts_new.shape[-1] * 2)
Example #2
0
def test_parse_pulse_trains():
    # Specify pulse trains in a number of different ways and make sure they
    # are all identical after parsing

    # Create some p2p.implants
    argus = implants.ArgusI()
    simple = implants.ElectrodeArray('subretinal', 0, 0, 0, 0)

    pt_zero = utils.TimeSeries(1, np.zeros(1000))
    pt_nonzero = utils.TimeSeries(1, np.random.rand(1000))

    # Test 1
    # ------
    # Specify wrong number of pulse trains
    with pytest.raises(ValueError):
        stimuli.parse_pulse_trains(pt_nonzero, argus)
    with pytest.raises(ValueError):
        stimuli.parse_pulse_trains([pt_nonzero], argus)
    with pytest.raises(ValueError):
        stimuli.parse_pulse_trains([pt_nonzero] *
                                   (argus.num_electrodes - 1),
                                   argus)
    with pytest.raises(ValueError):
        stimuli.parse_pulse_trains([pt_nonzero] * 2, simple)

    # Test 2
    # ------
    # Send non-zero pulse train to specific electrode
    el_name = 'B3'
    el_idx = argus.get_index(el_name)

    # Specify a list of 16 pulse trains (one for each electrode)
    pt0_in = [pt_zero] * argus.num_electrodes
    pt0_in[el_idx] = pt_nonzero
    pt0_out = stimuli.parse_pulse_trains(pt0_in, argus)

    # Specify a dict with non-zero pulse trains
    pt1_in = {el_name: pt_nonzero}
    pt1_out = stimuli.parse_pulse_trains(pt1_in, argus)

    # Make sure the two give the same result
    for p0, p1 in zip(pt0_out, pt1_out):
        npt.assert_equal(p0.data, p1.data)

    # Test 3
    # ------
    # Smoke testing
    stimuli.parse_pulse_trains([pt_zero] * argus.num_electrodes, argus)
    stimuli.parse_pulse_trains(pt_zero, simple)
    stimuli.parse_pulse_trains([pt_zero], simple)
Example #3
0
def parse_pulse_trains(stim, implant):
    """Parse input stimulus and convert to list of pulse trains

    Parameters
    ----------
    stim : utils.TimeSeries|list|dict
        There are several ways to specify an input stimulus:

        - For a single-electrode array, pass a single pulse train; i.e., a
          single utils.TimeSeries object.
        - For a multi-electrode array, pass a list of pulse trains, where
          every pulse train is a utils.TimeSeries object; i.e., one pulse
          train per electrode.
        - For a multi-electrode array, specify all electrodes that should
          receive non-zero pulse trains by name in a dictionary. The key
          of each element is the electrode name, the value is a pulse train.
          Example: stim = {'E1': pt, 'stim': pt}, where 'E1' and 'stim' are
          electrode names, and `pt` is a utils.TimeSeries object.
    implant : p2p.implants.ElectrodeArray
        A p2p.implants.ElectrodeArray object that describes the implant.

    Returns
    -------
    A list of pulse trains; one pulse train per electrode.
    """
    # Parse input stimulus
    if isinstance(stim, utils.TimeSeries):
        # `stim` is a single object: This is only allowed if the implant
        # has only one electrode
        if implant.num_electrodes > 1:
            e_s = "More than 1 electrode given, use a list of pulse trains"
            raise ValueError(e_s)
        pt = [copy.deepcopy(stim)]
    elif isinstance(stim, dict):
        # `stim` is a dictionary: Look up electrode names and assign pulse
        # trains, fill the rest with zeros

        # Get right size from first dict element, then generate all zeros
        idx0 = list(stim.keys())[0]
        pt_zero = utils.TimeSeries(stim[idx0].tsample,
                                   np.zeros_like(stim[idx0].data))
        pt = [pt_zero] * implant.num_electrodes

        # Iterate over dictionary and assign non-zero pulse trains to
        # corresponding electrodes
        for key, value in stim.items():
            el_idx = implant.get_index(key)
            if el_idx is not None:
                pt[el_idx] = copy.deepcopy(value)
            else:
                e_s = "Could not find electrode with name '%s'" % key
                raise ValueError(e_s)
    else:
        # Else, `stim` must be a list of pulse trains, one for each electrode
        if len(stim) != implant.num_electrodes:
            e_s = "Number of pulse trains must match number of electrodes"
            raise ValueError(e_s)
        pt = copy.deepcopy(stim)

    return pt
Example #4
0
def test_TimeSeries_resample():
    max_val = 2.0
    max_idx = 156
    data_orig = np.random.rand(10, 10, 1000)
    data_orig[4, 4, max_idx] = max_val
    ts = utils.TimeSeries(1.0, data_orig)
    tmax, vmax = ts.max()

    # Resampling with same sampling step shouldn't change anything
    ts_new = ts.resample(ts.tsample)
    npt.assert_equal(ts_new.shape, ts.shape)
    npt.assert_equal(ts_new.duration, ts.duration)

    # Make sure resampling works
    tsample_new = 4
    ts_new = ts.resample(tsample_new)
    npt.assert_equal(ts_new.tsample, tsample_new)
    npt.assert_equal(ts_new.data.shape[-1], ts.data.shape[-1] / tsample_new)
    npt.assert_equal(ts_new.duration, ts.duration)
    tmax_new, vmax_new = ts_new.max()
    npt.assert_equal(tmax_new, tmax)
    npt.assert_equal(vmax_new, vmax)

    # Make sure resampling leaves old data unaffected (deep copy)
    ts_new.data[0, 0, 0] = max_val * 2.0
    tmax_new, vmax_new = ts_new.max()
    npt.assert_equal(tmax_new, 0)
    npt.assert_equal(vmax_new, max_val * 2.0)
    tmax, vmax = ts.max()
    npt.assert_equal(tmax, max_idx)
    npt.assert_equal(vmax, max_val)
Example #5
0
def test_save_video_sidebyside():
    reload(files)
    from skvideo import datasets
    videoin = files.load_video(datasets.bikes(), as_timeseries=False)
    fps = files.load_video_framerate(datasets.bikes())
    tsample = 1.0 / float(fps)
    rollaxes = np.roll(range(videoin.ndim), -1)
    percept = utils.TimeSeries(tsample, np.transpose(videoin, rollaxes))

    files.save_video_sidebyside(datasets.bikes(), percept, 'mymovie.mp4',
                                fps=fps)
    videout = files.load_video('mymovie.mp4', as_timeseries=False)
    npt.assert_equal(videout.shape[0], videoin.shape[0])
    npt.assert_equal(videout.shape[1], videoin.shape[1])
    npt.assert_equal(videout.shape[2], videoin.shape[2] * 2)
    npt.assert_equal(videout.shape[3], videoin.shape[3])

    with pytest.raises(TypeError):
        files.save_video_sidebyside(datasets.bikes(), [2, 3, 4], 'invalid.avi')

    with mock.patch.dict("sys.modules", {"skvideo": {}}):
        with pytest.raises(ImportError):
            reload(files)
            files.save_video_sidebyside(datasets.bikes(), percept,
                                        'invalid.avi')
    with mock.patch.dict("sys.modules", {"skimage": {}}):
        with pytest.raises(ImportError):
            reload(files)
            files.save_video_sidebyside(datasets.bikes(), percept,
                                        'invalid.avi')
Example #6
0
def test_save_video():
    # Load a test example
    reload(files)
    from skvideo import datasets

    # There and back again: ndarray
    videoin = files.load_video(datasets.bikes(), as_timeseries=False)
    fpsin = files.load_video_framerate(datasets.bikes())
    files.save_video(videoin, 'myvideo.mp4', fps=fpsin)
    videout = files.load_video('myvideo.mp4', as_timeseries=False)
    npt.assert_equal(videoin.shape, videout.shape)
    npt.assert_almost_equal(videout / 255.0, videoin / 255.0, decimal=0)

    # Write to file with different frame rate, widths, and heights
    fpsout = 15
    files.save_video(videoin, 'myvideo.mp4', width=100, fps=fpsout)
    npt.assert_equal(files.load_video_framerate('myvideo.mp4'), fpsout)
    videout = files.load_video('myvideo.mp4', as_timeseries=False)
    npt.assert_equal(videout.shape[2], 100)
    files.save_video(videoin, 'myvideo.mp4', height=20, fps=fpsout)
    videout = files.load_video('myvideo.mp4', as_timeseries=False)
    npt.assert_equal(videout.shape[1], 20)
    videout = None

    # There and back again: TimeSeries
    tsamplein = 1.0 / float(fpsin)
    tsampleout = 1.0 / float(fpsout)
    rollaxes = np.roll(range(videoin.ndim), -1)
    tsin = utils.TimeSeries(tsamplein, np.transpose(videoin, rollaxes))
    files.save_video(tsin, 'myvideo.mp4', fps=fpsout)
    npt.assert_equal(tsin.tsample, tsamplein)
    tsout = files.load_video('myvideo.mp4', as_timeseries=True)
    npt.assert_equal(files.load_video_framerate('myvideo.mp4'), fpsout)
    npt.assert_equal(isinstance(tsout, utils.TimeSeries), True)
    npt.assert_almost_equal(tsout.tsample, tsampleout)

    # Also verify the actual data
    tsres = tsin.resample(tsampleout)
    npt.assert_equal(tsout.shape, tsres.shape)
    npt.assert_almost_equal(tsout.data / 255.0, tsres.data / tsres.data.max(),
                            decimal=0)

    with pytest.raises(TypeError):
        files.save_video([2, 3, 4], 'invalid.avi')

    # Trigger an import error
    with mock.patch.dict("sys.modules", {"skvideo": {}}):
        with pytest.raises(ImportError):
            reload(files)
            files.save_video(videoin, 'invalid.avi')
    with mock.patch.dict("sys.modules", {"skimage": {}}):
        with pytest.raises(ImportError):
            reload(files)
            files.save_video(videoin, 'invalid.avi')
Example #7
0
    def model_cascade(self, in_arr, pt_list, layers, use_jit):
        """Horsager model cascade

        Parameters
        ----------
        in_arr: array-like
            A 2D array specifying the effective current values
            at a particular spatial location (pixel); one value
            per retinal layer and electrode.
            Dimensions: <#layers x #electrodes>
        pt_list : list
            List of pulse train 'data' containers.
            Dimensions: <#electrodes x #time points>
        layers : list
            List of retinal layers to simulate.
            Choose from:
            - 'OFL': optic fiber layer
            - 'GCL': ganglion cell layer
        use_jit : bool
            If True, applies just-in-time (JIT) compilation to
            expensive computations for additional speed-up
            (requires Numba).
        """
        if 'INL' in layers:
            raise ValueError("The Nanduri2012 model does not support an inner "
                             "nuclear layer.")

        # Although the paper says to use cathodic-first, the code only
        # reproduces if we use what we now call anodic-first. So flip the sign
        # on the stimulus here:
        stim = -self.calc_layer_current(in_arr, pt_list, layers)

        # R1 convolved the entire stimulus (with both pos + neg parts)
        r1 = self.tsample * utils.conv(
            stim, self.gamma1, mode='full', method='sparse')[:stim.size]

        # It's possible that charge accumulation was done on the anodic phase.
        # It might not matter too much (timing is slightly different, but the
        # data are not accurate enough to warrant using one over the other).
        # Thus use what makes the most sense: accumulate on cathodic
        ca = self.tsample * np.cumsum(np.maximum(0, -stim))
        ca = self.tsample * utils.conv(
            ca, self.gamma2, mode='full', method='fft')[:stim.size]
        r2 = r1 - self.epsilon * ca

        # Then half-rectify and pass through the power-nonlinearity
        r3 = np.maximum(0.0, r2)**self.beta

        # Then convolve with slow gamma
        r4 = self.tsample * utils.conv(
            r3, self.gamma3, mode='full', method='fft')[:stim.size]

        return utils.TimeSeries(self.tsample, r4)
Example #8
0
    def model_cascade(self, in_arr, pt_list, layers, use_jit):
        """Nanduri model cascade

        Parameters
        ----------
        in_arr: array-like
            A 2D array specifying the effective current values
            at a particular spatial location (pixel); one value
            per retinal layer and electrode.
            Dimensions: <#layers x #electrodes>
        pt_list : list
            List of pulse train 'data' containers.
            Dimensions: <#electrodes x #time points>
        layers : list
            List of retinal layers to simulate.
            Choose from:
            - 'OFL': optic fiber layer
            - 'GCL': ganglion cell layer
        use_jit : bool
            If True, applies just-in-time (JIT) compilation to
            expensive computations for additional speed-up
            (requires Numba).
        """
        if 'INL' in layers:
            raise ValueError("The Nanduri2012 model does not support an inner "
                             "nuclear layer.")

        # `b1` contains a scaled PulseTrain per layer for this particular
        # pixel: Use as input to model cascade
        b1 = self.calc_layer_current(in_arr, pt_list, layers)

        # Fast response
        b2 = self.tsample * utils.conv(
            b1, self.gamma1, mode='full', method='sparse',
            use_jit=use_jit)[:b1.size]

        # Charge accumulation
        ca = self.tsample * np.cumsum(np.maximum(0, b1))
        ca = self.tsample * utils.conv(
            ca, self.gamma2, mode='full', method='fft')[:b1.size]
        b3 = np.maximum(0, b2 - self.eps * ca)

        # Stationary nonlinearity
        sigmoid = ss.expit((b3.max() - self.shift) / self.slope)
        b4 = b3 * sigmoid * self.asymptote

        # Slow response
        b5 = self.tsample * utils.conv(
            b4, self.gamma3, mode='full', method='fft')[:b1.size]

        return utils.TimeSeries(self.tsample, b5)
Example #9
0
def test_TimeSeries():
    max_val = 2.0
    max_idx = 156
    data_orig = np.random.rand(10, 10, 1000)
    data_orig[4, 4, max_idx] = max_val
    ts = utils.TimeSeries(1.0, data_orig)

    # Make sure function returns largest element
    tmax, vmax = ts.max()
    npt.assert_equal(tmax, max_idx)
    npt.assert_equal(vmax, max_val)

    # Make sure function returns largest frame
    tmax, fmax = ts.max_frame()
    npt.assert_equal(tmax, max_idx)
    npt.assert_equal(fmax.data, data_orig[:, :, max_idx])

    # Make sure getitem works
    npt.assert_equal(isinstance(ts[3], utils.TimeSeries), True)
    npt.assert_equal(ts[3].data, ts.data[3])
Example #10
0
    def model_cascade(self, ecs_item, pt_list, layers, use_jit):
        """The Temporal Sensitivity model

        This function applies the model of temporal sensitivity to a single
        retinal cell (i.e., a pixel). The model is inspired by Nanduri
        et al. (2012), with some extended functionality.

        Parameters
        ----------
        ecs_item: array-like
            A 2D array specifying the effective current values at a particular
            spatial location (pixel); one value per retinal layer and
            electrode.
            Dimensions: <#layers x #electrodes>
        pt_list: list
            A list of PulseTrain `data` containers.
            Dimensions: <#electrodes x #time points>
        layers : list
            List of retinal layers to simulate. Choose from:
            - 'OFL': optic fiber layer
            - 'GCL': ganglion cell layer
            - 'INL': inner nuclear layer
        use_jit : bool
            If True, applies just-in-time (JIT) compilation to expensive
            computations for additional speed-up (requires Numba).

        Returns
        -------
        Brightness response over time. In Nanduri et al. (2012), the
        maximum value of this signal was used to represent the perceptual
        brightness of a particular location in space, B(r).
        """
        # For each layer in the model, scale the pulse train data with the
        # effective current:
        ecm = self.calc_layer_current(ecs_item, pt_list, layers)

        # Calculate charge accumulation on the input
        ca = self.charge_accumulation(ecm)

        # Sparse convolution is faster if input is sparse. This is true for
        # the first convolution in the cascade, but not for subsequent ones.
        if 'INL' in layers:
            fr_inl = self.fast_response(ecm[0],
                                        self.gamma_inl,
                                        use_jit=use_jit,
                                        method='sparse')

            # Cathodic and anodic parts are treated separately: They have the
            # same charge accumulation, but anodic currents contribute less to
            # the response
            fr_inl_cath = np.maximum(0, -fr_inl)
            fr_inl_anod = self.aweight * np.maximum(0, fr_inl)
            resp_inl = np.maximum(0, fr_inl_cath + fr_inl_anod - ca[0, :])
        else:
            resp_inl = np.zeros_like(ecm[0])

        if ('GCL' or 'OFL') in layers:
            fr_gcl = self.fast_response(ecm[1],
                                        self.gamma_gcl,
                                        use_jit=use_jit,
                                        method='sparse')

            # Cathodic and anodic parts are treated separately: They have the
            # same charge accumulation, but anodic currents contribute less to
            # the response
            fr_gcl_cath = np.maximum(0, -fr_gcl)
            fr_gcl_anod = self.aweight * np.maximum(0, fr_gcl)
            resp_gcl = np.maximum(0, fr_gcl_cath + fr_gcl_anod - ca[1, :])
        else:
            resp_gcl = np.zeros_like(ecm[1])

        resp = resp_gcl + self.lweight * resp_inl
        resp = self.stationary_nonlinearity(resp)
        resp = self.slow_response(resp)
        return utils.TimeSeries(self.tsample, resp)
Example #11
0
    def pulse2percept(self,
                      stim,
                      t_percept=None,
                      tol=0.05,
                      layers=['OFL', 'GCL', 'INL']):
        """Transforms an input stimulus to a percept

        Parameters
        ----------
        stim : utils.TimeSeries|list|dict
            There are several ways to specify an input stimulus:

            - For a single-electrode array, pass a single pulse train; i.e.,
              a single utils.TimeSeries object.
            - For a multi-electrode array, pass a list of pulse trains; i.e.,
              one pulse train per electrode.
            - For a multi-electrode array, specify all electrodes that should
              receive non-zero pulse trains by name.
        t_percept : float, optional, default: inherit from `stim` object
            The desired time sampling of the output (seconds).
        tol : float, optional, default: 0.05
            Ignore pixels whose effective current is smaller than a fraction
            `tol` of the max value.
        layers : list, optional, default: ['OFL', 'GCL', 'INL']
            A list of retina layers to simulate (order does not matter):
            - 'OFL': Includes the optic fiber layer in the simulation.
                     If omitted, the tissue activation map will not account
                     for axon streaks.
            - 'GCL': Includes the ganglion cell layer in the simulation.
            - 'INL': Includes the inner nuclear layer in the simulation.
                     If omitted, bipolar cell activity does not contribute
                     to ganglion cell activity.

        Returns
        -------
        A utils.TimeSeries object whose data container comprises the predicted
        brightness over time at each retinal location (x, y), with the last
        dimension of the container representing time (t).

        Examples
        --------
        Simulate a single-electrode array:

        >>> import pulse2percept as p2p
        >>> implant = p2p.implants.ElectrodeArray('subretinal', 0, 0, 0)
        >>> stim = p2p.stimuli.PulseTrain(tsample=5e-6, freq=50, amp=20)
        >>> sim = p2p.Simulation(implant)
        >>> percept = sim.pulse2percept(stim)  # doctest: +SKIP

        Simulate an Argus I array centered on the fovea, where a single
        electrode is being stimulated ('C3'):

        >>> import pulse2percept as p2p
        >>> implant = p2p.implants.ArgusI()
        >>> stim = {'C3': stimuli.PulseTrain(tsample=5e-6, freq=50,
        ...                                              amp=20)}
        >>> sim = p2p.Simulation(implant)
        >>> resp = sim.pulse2percept(stim, implant)  # doctest: +SKIP
        """
        logging.getLogger(__name__).info("Starting pulse2percept...")

        # Get a flattened, all-uppercase list of layers
        layers = np.array([layers]).flatten()
        layers = np.array([l.upper() for l in layers])

        # Make sure all specified layers exist
        not_supported = np.array(
            [l not in retina.SUPPORTED_LAYERS for l in layers], dtype=bool)
        if any(not_supported):
            msg = ', '.join(layers[not_supported])
            msg = "Specified layer %s not supported. " % msg
            msg += "Choose from %s." % ', '.join(retina.SUPPORTED_LAYERS)
            raise ValueError(msg)

        # Set up all layers that haven't been set up yet
        self._set_layers()

        # Parse `stim` (either single pulse train or a list/dict of pulse
        # trains), and generate a list of pulse trains, one for each electrode
        pt_list = stimuli.parse_pulse_trains(stim, self.implant)
        pt_data = [pt.data for pt in pt_list]

        if not np.allclose([p.tsample for p in pt_list], self.gcl.tsample):
            e_s = "For now, all pulse trains must have the same sampling "
            e_s += "time step as the ganglion cell layer. In the future, "
            e_s += "this requirement might be relaxed."
            raise ValueError(e_s)

        # Tissue activation maps: If OFL is simulated, includes axon streaks.
        if 'OFL' in layers:
            ecs, _ = self.ofl.electrode_ecs(self.implant)
        else:
            _, ecs = self.ofl.electrode_ecs(self.implant)

        # Calculate the max of every current spread map
        lmax = np.zeros((2, ecs.shape[-1]))
        if 'INL' in layers:
            lmax[0, :] = ecs[:, :, 0, :].max(axis=(0, 1))
        if ('GCL' or 'OFL') in layers:
            lmax[1, :] = ecs[:, :, 1, :].max(axis=(0, 1))

        # `ecs_list` is a pixel by `n` list where `n` is the number of layers
        # being simulated. Each value in `ecs_list` is the current contributed
        # by each electrode for that spatial location
        ecs_list = []
        idx_list = []
        for xx in range(self.ofl.gridx.shape[1]):
            for yy in range(self.ofl.gridx.shape[0]):
                # If any of the used current spread maps at [yy, xx] are above
                # tolerance, we need to process that pixel
                process_pixel = False
                if 'INL' in layers:
                    # For this pixel: Check if the ecs in any layer is large
                    # enough compared to the max across pixels within the layer
                    process_pixel |= np.any(
                        ecs[yy, xx, 0, :] >= tol * lmax[0, :])
                if ('GCL' or 'OFL') in layers:
                    process_pixel |= np.any(
                        ecs[yy, xx, 1, :] >= tol * lmax[1, :])

                if process_pixel:
                    ecs_list.append(ecs[yy, xx])
                    idx_list.append([yy, xx])

        s_info = "tol=%.1f%%, %d/%d px selected" % (tol * 100, len(ecs_list),
                                                    np.prod(ecs.shape[:2]))
        logging.getLogger(__name__).info(s_info)

        sr_list = utils.parfor(self.gcl.model_cascade,
                               ecs_list,
                               n_jobs=self.n_jobs,
                               engine=self.engine,
                               scheduler=self.scheduler,
                               func_args=[pt_data, layers, self.use_jit])
        bm = np.zeros(self.ofl.gridx.shape + (sr_list[0].data.shape[-1], ))
        idxer = tuple(np.array(idx_list)[:, i] for i in range(2))
        bm[idxer] = [sr.data for sr in sr_list]
        percept = utils.TimeSeries(sr_list[0].tsample, bm)

        # It is possible to specify an additional sampling rate for the
        # percept: If different from the input sampling rate, need to resample.
        if t_percept != percept.tsample:
            percept = percept.resample(t_percept)

        logging.getLogger(__name__).info("Done.")

        return percept
Example #12
0
def load_video(filename, as_timeseries=True, as_gray=False, ffmpeg_path=None,
               libav_path=None):
    """Loads a video from file.

    This function loads a video from file with the help of Scikit-Video, and
    returns the data either as a NumPy array (if `as_timeseries` is False)
    or as a ``p2p.utils.TimeSeries`` object (if `as_timeseries` is True).

    Parameters
    ----------
    filename : str
        Video file name
    as_timeseries: bool, optional, default: True
        If True, returns the data as a ``p2p.utils.TimeSeries`` object.
    as_gray : bool, optional, default: False
        If True, loads only the luminance channel of the video.
    ffmpeg_path : str, optional, default: system's default path
        Path to ffmpeg library.
    libav_path : str, optional, default: system's default path
        Path to libav library.

    Returns
    -------
    video : ndarray | p2p2.utils.TimeSeries
        If `as_timeseries` is False, returns video data according to the
        Scikit-Video standard; that is, an ndarray of dimension (T, M, N, C),
        (T, M, N), (M, N, C), or (M, N), where T is the number of frames,
        M is the height, N is the width, and C is the number of channels (will
        be either 1 for grayscale or 3 for RGB).

        If `as_timeseries` is True, returns video data as a TimeSeries object
        of dimension (M, N, C), (M, N, T), (M, N, C), or (M, N).
        The sampling rate corresponds to 1 / frame rate.

    Examples
    --------
    Load a video as a ``p2p.utils.TimeSeries`` object:

    >>> from skvideo import datasets
    >>> video = load_video(datasets.bikes())
    >>> video.tsample
    0.04
    >>> video.shape
    (272, 640, 3, 250)

    Load a video as a NumPy ndarray:

    >>> from skvideo import datasets
    >>> video = load_video(datasets.bikes(), as_timeseries=False)
    >>> video.shape
    (250, 272, 640, 3)

    Load a video as a NumPy ndarray and convert to grayscale:

    >>> from skvideo import datasets
    >>> video = load_video(datasets.bikes(), as_timeseries=False, as_gray=True)
    >>> video.shape
    (250, 272, 640, 1)

    """
    if not has_skvideo:
        raise ImportError("You do not have scikit-video installed. "
                          "You can install it via $ pip install sk-video.")

    # Set the path if necessary
    set_skvideo_path(ffmpeg_path, libav_path)

    if skvideo._HAS_FFMPEG:
        backend = 'ffmpeg'
    else:
        backend = 'libav'
    video = svio.vread(filename, as_grey=as_gray, backend=backend)
    logging.getLogger(__name__).info("Loaded video from file '%s'." % filename)
    d_s = "Loaded video has shape (T, M, N, C) = " + str(video.shape)
    logging.getLogger(__name__).debug(d_s)

    if as_timeseries:
        # TimeSeries has the time as the last dimensions: re-order dimensions,
        # then squeeze out singleton dimensions
        axes = np.roll(range(video.ndim), -1)
        video = np.squeeze(np.transpose(video, axes=axes))
        fps = load_video_framerate(filename)
        d_s = "Reshaped video to shape (M, N, C, T) = " + str(video.shape)
        logging.getLogger(__name__).debug(d_s)
        return utils.TimeSeries(1.0 / fps, video)
    else:
        # Return as ndarray
        return video