Пример #1
0
    def set_roi(video_path):
        """Manually set the ROIs for a given set of videos
        TODO Improve docstring
        TODO A method for setting ROIs by label
        """
        frame = vidio.get_video_frame(str(video_path), 0)

        def line_select_callback(eclick, erelease):
            """
            Callback for line selection.

            *eclick* and *erelease* are the press and release events.
            """
            x1, y1 = eclick.xdata, eclick.ydata
            x2, y2 = erelease.xdata, erelease.ydata
            print("(%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2))
            return np.array([[x1, x2], [y1, y2]])

        plt.imshow(frame)
        roi = RectangleSelector(
            plt.gca(),
            line_select_callback,
            drawtype='box',
            useblit=True,
            button=[1, 3],  # don't use middle button
            minspanx=5,
            minspany=5,
            spancoords='pixels',
            interactive=True)
        plt.show()
        ((x1, x2, *_), (y1, *_, y2)) = roi.corners
        col = np.arange(round(x1), round(x2), dtype=int)
        row = np.arange(round(y1), round(y2), dtype=int)
        return col, row
Пример #2
0
 def test_get_video_frame(self):
     n = 50  # Frame number to fetch
     frame = vidio.get_video_frame(self.video_path, n)
     expected_shape = (1024, 1280, 3)
     self.assertEqual(frame.shape, expected_shape)
     expected = np.array([[156, 222, 157, 75, 36, 15, 19, 20, 23]],
                         dtype=np.uint8)
     np.testing.assert_array_equal(frame[:1, :9, 0], expected)
Пример #3
0
one = ONE(silent=True)
eid = 'edd22318-216c-44ff-bc24-49ce8be78374'  # 2020-08-19_1_CSH_ZAD_019

# Example 1: get the remote video URL from eid
urls = vidio.url_from_eid(eid, one=one)
# Without the `label` kwarg, returns a dictionary of camera URLs
url = urls['left']  # URL for the left camera

# Example 2: get the video label from a video file path or URL
label = vidio.label_from_path(url)
print(f'Using URL for the {label} camera')

# Example 3: loading a single frame
frame_n = 1000  # Frame number to fetch.  Indexing starts from 0.
frame = vidio.get_video_frame(url, frame_n)
assert frame is not None, 'failed to load frame'

# Example 4: loading multiple frames
"""
The preload function will by default pre-allocate the memory before loading the frames, 
and will return the frames as a numpy array of the shape (l, h, w, 3), where l = the number of 
frame indices given.  The indices must be an iterable of positive integers.  Because the videos 
are in black and white the values of each color channel are identical.   Therefore to save on 
memory you can provide a slice that returns only one of the three channels for each frame.  The 
resulting shape will be (l, h, w).  NB: Any slice or boolean array may be provided which is 
useful for cropping to an ROI.

If you don't need to apply operations over all the fetched frames you can use the `as_list` 
kwarg to return the frames as a list.  This is slightly faster than fetching as an ndarray.
Пример #4
0
def dlc_qc_plot(session_path, one=None):
    """
    Creates DLC QC plot.
    Data is searched first locally, then on Alyx. Panels that lack required data are skipped.

    Required data to create all panels
     'raw_video_data/_iblrig_bodyCamera.raw.mp4',
     'raw_video_data/_iblrig_leftCamera.raw.mp4',
     'raw_video_data/_iblrig_rightCamera.raw.mp4',
     'alf/_ibl_bodyCamera.dlc.pqt',
     'alf/_ibl_leftCamera.dlc.pqt',
     'alf/_ibl_rightCamera.dlc.pqt',
     'alf/_ibl_bodyCamera.times.npy',
     'alf/_ibl_leftCamera.times.npy',
     'alf/_ibl_rightCamera.times.npy',
     'alf/_ibl_leftCamera.features.pqt',
     'alf/_ibl_rightCamera.features.pqt',
     'alf/rightROIMotionEnergy.position.npy',
     'alf/leftROIMotionEnergy.position.npy',
     'alf/bodyROIMotionEnergy.position.npy',
     'alf/_ibl_trials.choice.npy',
     'alf/_ibl_trials.feedbackType.npy',
     'alf/_ibl_trials.feedback_times.npy',
     'alf/_ibl_trials.stimOn_times.npy',
     'alf/_ibl_wheel.position.npy',
     'alf/_ibl_wheel.timestamps.npy',
     'alf/licks.times.npy',

    :params session_path: Path to session data on disk
    :params one: ONE instance, if None is given, default ONE is instantiated
    :returns: Matplotlib figure
    """

    one = one or ONE()
    # hack for running on cortexlab local server
    if one.alyx.base_url == 'https://alyx.cortexlab.net':
        one = ONE(base_url='https://alyx.internationalbrainlab.org')
    data = {}
    cams = ['left', 'right', 'body']
    session_path = Path(session_path)

    # Load data for each camera
    for cam in cams:
        # Load a single frame for each video
        # Check if video data is available locally,if yes, load a single frame
        video_path = session_path.joinpath('raw_video_data',
                                           f'_iblrig_{cam}Camera.raw.mp4')
        if video_path.exists():
            data[f'{cam}_frame'] = get_video_frame(video_path,
                                                   frame_number=5 * 60 *
                                                   SAMPLING[cam])[:, :, 0]
        # If not, try to stream a frame (try three times)
        else:
            try:
                video_url = url_from_eid(one.path2eid(session_path),
                                         one=one)[cam]
                for tries in range(3):
                    try:
                        data[f'{cam}_frame'] = get_video_frame(
                            video_url,
                            frame_number=5 * 60 * SAMPLING[cam])[:, :, 0]
                        break
                    except BaseException:
                        if tries < 2:
                            tries += 1
                            logger.info(
                                f"Streaming {cam} video failed, retrying x{tries}"
                            )
                            time.sleep(30)
                        else:
                            logger.warning(
                                f"Could not load video frame for {cam} cam. Skipping trace on frame."
                            )
                            data[f'{cam}_frame'] = None
            except KeyError:
                logger.warning(
                    f"Could not load video frame for {cam} cam. Skipping trace on frame."
                )
                data[f'{cam}_frame'] = None
        # Other camera associated data
        for feat in ['dlc', 'times', 'features', 'ROIMotionEnergy']:
            # Check locally first, then try to load from alyx, if nothing works, set to None
            if feat == 'features' and cam == 'body':  # this doesn't exist for body cam
                continue
            local_file = list(
                session_path.joinpath('alf').glob(f'*{cam}Camera.{feat}*'))
            if len(local_file) > 0:
                data[f'{cam}_{feat}'] = alfio.load_file_content(local_file[0])
            else:
                alyx_ds = [
                    ds for ds in one.list_datasets(one.path2eid(session_path))
                    if f'{cam}Camera.{feat}' in ds
                ]
                if len(alyx_ds) > 0:
                    data[f'{cam}_{feat}'] = one.load_dataset(
                        one.path2eid(session_path), alyx_ds[0])
                else:
                    logger.warning(
                        f"Could not load _ibl_{cam}Camera.{feat} some plots have to be skipped."
                    )
                    data[f'{cam}_{feat}'] = None
            # Sometimes there is a file but the object is empty, set to None
            if data[f'{cam}_{feat}'] is not None and len(
                    data[f'{cam}_{feat}']) == 0:
                logger.warning(
                    f"Object loaded from _ibl_{cam}Camera.{feat} is empty, some plots have to be skipped."
                )
                data[f'{cam}_{feat}'] = None

    # If we have no frame and/or no DLC and/or no times for all cams, raise an error, something is really wrong
    assert any([data[f'{cam}_frame'] is not None
                for cam in cams]), "No camera data could be loaded, aborting."
    assert any([data[f'{cam}_dlc'] is not None
                for cam in cams]), "No DLC data could be loaded, aborting."
    assert any([data[f'{cam}_times'] is not None for cam in cams
                ]), "No camera times data could be loaded, aborting."

    # Load session level data
    for alf_object in ['trials', 'wheel', 'licks']:
        try:
            data[f'{alf_object}'] = alfio.load_object(
                session_path.joinpath('alf'), alf_object)  # load locally
            continue
        except ALFObjectNotFound:
            pass
        try:
            data[f'{alf_object}'] = one.load_object(
                one.path2eid(session_path), alf_object)  # then try from alyx
        except ALFObjectNotFound:
            logger.warning(
                f"Could not load {alf_object} object, some plots have to be skipped."
            )
            data[f'{alf_object}'] = None

    # Simplify and clean up trials data
    if data['trials']:
        data['trials'] = pd.DataFrame({
            k: data['trials'][k]
            for k in
            ['stimOn_times', 'feedback_times', 'choice', 'feedbackType']
        })
        # Discard nan events and too long trials
        data['trials'] = data['trials'].dropna()
        data['trials'] = data['trials'].drop(
            data['trials'][(data['trials']['feedback_times'] -
                            data['trials']['stimOn_times']) > 10].index)

    # Make a list of panels, if inputs are missing, instead input a text to display
    panels = []
    # Panel A, B, C: Trace on frame
    for cam in cams:
        if data[f'{cam}_frame'] is not None and data[f'{cam}_dlc'] is not None:
            panels.append((plot_trace_on_frame, {
                'frame': data[f'{cam}_frame'],
                'dlc_df': data[f'{cam}_dlc'],
                'cam': cam
            }))
        else:
            panels.append(
                (None, f'Data missing\n{cam.capitalize()} cam trace on frame'))

    # If trials data is not there, we cannot plot any of the trial average plots, skip all remaining panels
    if data['trials'] is None:
        panels.extend([(None, 'No trial data,\ncannot compute trial avgs')
                       for i in range(7)])
    else:
        # Panel D: Motion energy
        camera_dict = {
            'left': {
                'motion_energy': data['left_ROIMotionEnergy'],
                'times': data['left_times']
            },
            'right': {
                'motion_energy': data['right_ROIMotionEnergy'],
                'times': data['right_times']
            },
            'body': {
                'motion_energy': data['body_ROIMotionEnergy'],
                'times': data['body_times']
            }
        }
        for cam in [
                'left', 'right', 'body'
        ]:  # Remove cameras where we don't have motion energy AND camera times
            if camera_dict[cam]['motion_energy'] is None or camera_dict[cam][
                    'times'] is None:
                _ = camera_dict.pop(cam)
        if len(camera_dict) > 0:
            panels.append((plot_motion_energy_hist, {
                'camera_dict': camera_dict,
                'trials_df': data['trials']
            }))
        else:
            panels.append((None, 'Data missing\nMotion energy'))

        # Panel E: Wheel position
        if data['wheel']:
            panels.append((plot_wheel_position, {
                'wheel_position': data['wheel'].position,
                'wheel_time': data['wheel'].timestamps,
                'trials_df': data['trials']
            }))
        else:
            panels.append((None, 'Data missing\nWheel position'))

        # Panel F, G: Paw speed and nose speed
        # Try if all data is there for left cam first, otherwise right
        for cam in ['left', 'right']:
            fail = False
            if (data[f'{cam}_dlc'] is not None
                    and data[f'{cam}_times'] is not None
                    and len(data[f'{cam}_times']) >= len(data[f'{cam}_dlc'])):
                break
            fail = True
        if not fail:
            paw = 'r' if cam == 'left' else 'l'
            panels.append((plot_speed_hist, {
                'dlc_df': data[f'{cam}_dlc'],
                'cam_times': data[f'{cam}_times'],
                'trials_df': data['trials'],
                'feature': f'paw_{paw}',
                'cam': cam
            }))
            panels.append((plot_speed_hist, {
                'dlc_df': data[f'{cam}_dlc'],
                'cam_times': data[f'{cam}_times'],
                'trials_df': data['trials'],
                'feature': 'nose_tip',
                'legend': False,
                'cam': cam
            }))
        else:
            panels.extend([(None, 'Data missing or corrupt\nSpeed histograms')
                           for i in range(2)])

        # Panel H and I: Lick plots
        if data['licks'] and data['licks'].times.shape[0] > 0:
            panels.append((plot_lick_hist, {
                'lick_times': data['licks'].times,
                'trials_df': data['trials']
            }))
            panels.append((plot_lick_raster, {
                'lick_times': data['licks'].times,
                'trials_df': data['trials']
            }))
        else:
            panels.extend([(None, 'Data missing\nLicks plots')
                           for i in range(2)])

        # Panel J: pupil plot
        # Try if all data is there for left cam first, otherwise right
        for cam in ['left', 'right']:
            fail = False
            if (data[f'{cam}_times'] is not None
                    and data[f'{cam}_features'] is not None and
                    len(data[f'{cam}_times']) >= len(data[f'{cam}_features'])
                    and not np.all(
                        np.isnan(
                            data[f'{cam}_features'].pupilDiameter_smooth))):
                break
            fail = True
        if not fail:
            panels.append((plot_pupil_diameter_hist, {
                'pupil_diameter': data[f'{cam}_features'].pupilDiameter_smooth,
                'cam_times': data[f'{cam}_times'],
                'trials_df': data['trials'],
                'cam': cam
            }))
        else:
            panels.append((None, 'Data missing or corrupt\nPupil diameter'))

    # Plotting
    plt.rcParams.update({'font.size': 10})
    fig = plt.figure(figsize=(17, 10))
    for i, panel in enumerate(panels):
        ax = plt.subplot(2, 5, i + 1)
        ax.text(-0.1,
                1.15,
                ascii_uppercase[i],
                transform=ax.transAxes,
                fontsize=16,
                fontweight='bold')
        # Check if there was in issue with inputs, if yes, print the respective text
        if panel[0] is None:
            ax.text(.5,
                    .5,
                    panel[1],
                    color='r',
                    fontweight='bold',
                    fontsize=12,
                    horizontalalignment='center',
                    verticalalignment='center',
                    transform=ax.transAxes)
            plt.axis('off')
        else:
            try:
                panel[0](**panel[1])
            except BaseException:
                logger.error(f'Error in {panel[0].__name__}\n' +
                             traceback.format_exc())
                ax.text(.5,
                        .5,
                        f'Error while plotting\n{panel[0].__name__}',
                        color='r',
                        fontweight='bold',
                        fontsize=12,
                        horizontalalignment='center',
                        verticalalignment='center',
                        transform=ax.transAxes)
                plt.axis('off')
    plt.tight_layout(rect=[0, 0.03, 1, 0.95])

    return fig