def test_url_from_eid(self): actual = video.url_from_eid(self.eid, 'left', self.one) self.assertEqual(self.url, actual) actual = video.url_from_eid(self.eid, one=self.one) expected = {'left': self.url} self.assertEqual(expected, actual) actual = video.url_from_eid(self.eid, label=('left', 'right'), one=self.one) expected = {'left': self.url, 'right': None} self.assertEqual(expected, actual)
def test_url_from_eid(self): assert self.one.mode != 'remote' actual = video.url_from_eid(self.eid, 'left', self.one) self.assertEqual(self.url, actual) actual = video.url_from_eid(self.eid, one=self.one) expected = {'left': self.url} self.assertEqual(expected, actual) actual = video.url_from_eid(self.eid, label=('left', 'right'), one=self.one) expected = {'left': self.url, 'right': None} self.assertEqual(expected, actual) # Test remote mode old_mode = self.one.mode self.one.mode = 'remote' actual = video.url_from_eid(self.eid, label='left', one=self.one) self.assertEqual(self.url, actual) self.one.mode = old_mode # Test arg checks with self.assertRaises(ValueError): video.url_from_eid(self.eid, 'back')
Get video frames and metadata =================================== Video frames and meta data can be loaded using the ibllib.io.video module, which contains functions for loading individual or groups of frames efficiently. The video may be streamed remotely or loaded from a local file. In these examples a remote URL is used. """ import numpy as np import ibllib.io.video as vidio from oneibl.one import ONE 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,
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
def _get_video_lengths(eid): urls = vidio.url_from_eid(eid) return {k: camio.get_video_length(v) for k, v in urls.items()}