def load_camera_frame_count(session_path, label: str, raw=True): """ Load the embedded frame count for a given session. If the file doesn't exist, or is empty, a None value is returned. :param session_path: Absolute path of session folder :param label: The specific video to load, one of ('left', 'right', 'body') :param raw: If True the raw data are returned without preprocessing, otherwise frame count is returned starting from 0 :return: The frame count """ if session_path is None: return label = assert_valid_label(label) video_path = Path(session_path).joinpath('raw_video_data') if next(video_path.glob(f'_iblrig_{label}Camera.frameData*.bin'), None): df = load_camera_frameData(session_path, camera=label) return df['embeddedFrameCounter'].values # Load frame count glob = video_path.glob(f'_iblrig_{label}Camera.frame_counter*.bin') count_file = next(glob, None) count = np.fromfile(count_file, dtype=np.float64).astype(int) if count_file else [] if len(count) == 0: return if not raw: count -= count[0] # start from zero return count
def __init__(self, label, session_path=None): super().__init__(session_path) self.label = assert_valid_label(label) self.save_names = f'_ibl_{label}Camera.times.npy' self.var_names = f'{label}_camera_timestamps' self._log_level = _logger.level _logger.setLevel(logging.DEBUG)
def load_camera_gpio(session_path, label: str, as_dicts=False): """ Load the GPIO for a given session. If the file doesn't exist, or is empty, a None value is returned. The raw binary file contains uint32 values (saved as doubles) where the first 4 bits represent the state of each of the 4 GPIO pins. The array is expanded to an n x 4 array by shifting each bit to the end and checking whether it is 0 (low state) or 1 (high state). :param session_path: Absolute path of session folder :param label: The specific video to load, one of ('left', 'right', 'body') :param as_dicts: If False the raw data are returned boolean array with shape (n_frames, n_pins) otherwise GPIO is returned as a list of dictionaries with keys ('indices', 'polarities'). :return: An nx4 boolean array where columns represent state of GPIO pins 1-4. If as_dicts is True, a list of dicts is returned with keys ('indices', 'polarities'), or None if the dictionary is empty. """ if session_path is None: return raw_path = Path(session_path).joinpath('raw_video_data') label = assert_valid_label(label) # Load pin state if next(raw_path.glob(f'_iblrig_{label}Camera.frameData*.bin'), False): df = load_camera_frameData(session_path, camera=label, raw=False) gpio = np.array([x for x in df['embeddedGPIOPinState'].values]) if len(gpio) == 0: return [None] * 4 if as_dicts else None else: GPIO_file = next(raw_path.glob(f'_iblrig_{label}Camera.GPIO*.bin'), None) # This deals with missing and empty files the same gpio = np.fromfile(GPIO_file, dtype=np.float64).astype(np.uint32) if GPIO_file else [] # Check values make sense (4 pins = 16 possible values) if not np.isin(gpio, np.left_shift(np.arange(2 ** 4, dtype=np.uint32), 32 - 4)).all(): _logger.warning('Unexpected GPIO values; decoding may fail') if len(gpio) == 0: return [None] * 4 if as_dicts else None # 4 pins represented as uint32 # For each pin, shift its bit to the end and check the bit is set gpio = (np.right_shift(np.tile(gpio, (4, 1)).T, np.arange(31, 27, -1)) & 0x1) == 1 if as_dicts: if not gpio.any(): _logger.error('No GPIO changes') return [None] * 4 # Find state changes for each pin and construct a dict of indices and polarities for each edges = np.vstack((gpio[0, :], np.diff(gpio.astype(int), axis=0))) # gpio = [(ind := np.where(edges[:, i])[0], edges[ind, i]) for i in range(4)] # gpio = [dict(zip(('indices', 'polarities'), x)) for x in gpio_] # py3.8 gpio = [{'indices': np.where(edges[:, i])[0], 'polarities': edges[edges[:, i] != 0, i]} for i in range(4)] # Replace empty dicts with None gpio = [None if x['indices'].size == 0 else x for x in gpio] return gpio
def load_camera_frameData(session_path, camera: str = 'left', raw: bool = False) -> pd.DataFrame: """ Loads binary frame data from Bonsai camera recording workflow. Args: session_path (StrPath): Path to session folder camera (str, optional): Load FramsData for specific camera. Defaults to 'left'. raw (bool, optional): Whether to return raw or parsed data. Defaults to False. Returns: parsed: (raw=False, Default) pandas.DataFrame: 4 int64 columns: { Timestamp, # float64 (seconds from session start) embeddedTimeStamp, # float64 (seconds from session start) embeddedFrameCounter, # int64 (Frame number from session start) embeddedGPIOPinState # object (State of each of the 4 GPIO pins as a # list of numpy boolean arrays # e.g. np.array([True, False, False, False]) } raw: pandas.DataFrame: 4 int64 columns: { Timestamp, # UTC ticks from BehaviorPC # (100's of ns since midnight 1/1/0001) embeddedTimeStamp, # Camera timestamp (Needs unclycling and conversion) embeddedFrameCounter, # Frame counter (int) embeddedGPIOPinState # GPIO pin state integer representation of 4 pins } """ camera = assert_valid_label(camera) fpath = Path(session_path).joinpath("raw_video_data") fpath = next(fpath.glob(f"_iblrig_{camera}Camera.frameData*.bin"), None) assert fpath, f"{fpath}\nFile not Found: Could not find bin file for cam <{camera}>" rdata = np.fromfile(fpath, dtype=np.float64) assert rdata.size % 4 == 0, "Dimension mismatch: bin file length is not mod 4" rows = int(rdata.size / 4) data = np.reshape(rdata.astype(np.int64), (rows, 4)) df_dict = dict.fromkeys( ["Timestamp", "embeddedTimeStamp", "embeddedFrameCounter", "embeddedGPIOPinState"] ) df = pd.DataFrame(data, columns=df_dict.keys()) if raw: return df df_dict["Timestamp"] = (data[:, 0] - data[0, 0]) / 10_000_000 # in seconds from first frame camerats = uncycle_pgts(convert_pgts(data[:, 1])) df_dict["embeddedTimeStamp"] = camerats - camerats[0] # in seconds from first frame df_dict["embeddedFrameCounter"] = data[:, 2] - data[0, 2] # from start gpio = (np.right_shift(np.tile(data[:, 3], (4, 1)).T, np.arange(31, 27, -1)) & 0x1) == 1 df_dict["embeddedGPIOPinState"] = [np.array(x) for x in gpio.tolist()] parsed_df = pd.DataFrame.from_dict(df_dict) return parsed_df
def test_assert_valid_label(self): with self.assertRaises(ValueError): video.assert_valid_label('tail') label = video.assert_valid_label('LEFT') self.assertEqual(label, 'left') # Verify works with lists labels = video.assert_valid_label(['Right', 'body']) self.assertEqual(labels, ('right', 'body')) with self.assertRaises(TypeError): video.assert_valid_label(None)
def __init__(self, session_path_or_eid, camera, **kwargs): """ :param session_path_or_eid: A session id or path :param camera: The camera to run QC on, if None QC is run for all three cameras :param n_samples: The number of frames to sample for the position and brightness QC :param stream: If true and local video files not available, the data are streamed from the remote source. :param log: A logging.Logger instance, if None the 'ibllib' logger is used :param one: An ONE instance for fetching and setting the QC on Alyx """ # When an eid is provided, we will download the required data by default (if necessary) download_data = not is_session_path(session_path_or_eid) self.download_data = kwargs.pop('download_data', download_data) self.stream = kwargs.pop('stream', None) self.n_samples = kwargs.pop('n_samples', 100) super().__init__(session_path_or_eid, **kwargs) # Data self.label = assert_valid_label(camera) filename = f'_iblrig_{self.label}Camera.raw*.mp4' raw_video_path = self.session_path.joinpath('raw_video_data') self.video_path = next(raw_video_path.glob(filename), None) # If local video doesn't exist, change video path to URL if not self.video_path and self.stream is not False and self.one is not None: try: self.stream = True self.video_path = self.one.path2url(raw_video_path / filename.replace('*', '')) except (StopIteration, ALFObjectNotFound): _log.error('No remote or local video file found') self.video_path = None logging.disable(logging.CRITICAL) self._type = get_session_extractor_type(self.session_path) or None logging.disable(logging.NOTSET) keys = ('count', 'pin_state', 'audio', 'fpga_times', 'wheel', 'video', 'frame_samples', 'timestamps', 'camera_times', 'bonsai_times') self.data = Bunch.fromkeys(keys) self.frame_samples_idx = None # QC outcomes map self.metrics = None self.outcome = 'NOT_SET'
def get_DLC(eid, video_type): """load dlc traces load dlc traces for a given session and video type. :param eid: A session eid :param video_type: string in 'left', 'right', body' :return: array of times and dict with dlc points as keys and x,y coordinates as values, for each frame id Example: eid = '6c6983ef-7383-4989-9183-32b1a300d17a' video_type = 'right' Times, XYs = get_DLC(eid, video_type) # get for frame 500 the x coordinate of the nose # and the time stamp: x_frame_500 = XYs['nose_tip'][0][500] t_frame_500 = Times[500] """ one = ONE() video_type = assert_valid_label(video_type) cam = one.load_object(eid, f'{video_type}Camera', collection='alf') points = np.unique(['_'.join(x.split('_')[:-1]) for x in cam.dlc.columns]) XYs = {} for point in points: x = np.ma.masked_where(cam.dlc[point + '_likelihood'] < 0.9, cam.dlc[point + '_x']) x = x.filled(np.nan) y = np.ma.masked_where(cam.dlc[point + '_likelihood'] < 0.9, cam.dlc[point + '_y']) y = y.filled(np.nan) XYs[point] = np.array([x, y]) return cam.times, XYs
def load_camera_ssv_times(session_path, camera: str): """ Load the bonsai frame and camera timestamps from Camera.timestamps.ssv NB: For some sessions the frame times are in the first column, in others the order is reversed. NB: If using the new bin file the bonsai_times is a float in seconds since first frame :param session_path: Absolute path of session folder :param camera: Name of the camera to load, e.g. 'left' :return: array of datetimes, array of frame times in seconds """ camera = assert_valid_label(camera) video_path = Path(session_path).joinpath('raw_video_data') if next(video_path.glob(f'_iblrig_{camera}Camera.frameData*.bin'), None): df = load_camera_frameData(session_path, camera=camera) return df['Timestamp'].values, df['embeddedTimeStamp'].values file = next(video_path.glob(f'_iblrig_{camera.lower()}Camera.timestamps*.ssv'), None) if not file: file = str(video_path.joinpath(f'_iblrig_{camera.lower()}Camera.timestamps.ssv')) raise FileNotFoundError(file + ' not found') # NB: Numpy has deprecated support for non-naive timestamps. # Converting them is extremely slow: 6000 timestamps takes 0.8615s vs 0.0352s. # from datetime import timezone # c = {0: lambda x: datetime.fromisoformat(x).astimezone(timezone.utc).replace(tzinfo=None)} # Determine the order of the columns by reading one line and testing whether the first value # is an integer or not. with open(file, 'r') as f: line = f.readline() type_map = OrderedDict(bonsai='<M8[ns]', camera='<u4') try: int(line.split(' ')[1]) except ValueError: type_map.move_to_end('bonsai') ssv_params = dict(names=type_map.keys(), dtype=','.join(type_map.values()), delimiter=' ') ssv_times = np.genfromtxt(file, **ssv_params) # np.loadtxt is slower for some reason bonsai_times = ssv_times['bonsai'] camera_times = uncycle_pgts(convert_pgts(ssv_times['camera'])) return bonsai_times, camera_times
def extract_all(session_path, session_type=None, save=True, **kwargs): """ For the IBL ephys task, reads ephys binary file and extract: - video time stamps :param session_path: '/path/to/subject/yyyy-mm-dd/001' :param session_type: the session type to extract, i.e. 'ephys', 'training' or 'biased'. If None the session type is inferred from the settings file. :param save: Bool, defaults to False :param kwargs: parameters to pass to the extractor :return: outputs, files """ if session_type is None: session_type = get_session_extractor_type(session_path) if not session_type or session_type not in _get_task_types_json_config( ).values(): raise ValueError( f"Session type {session_type} has no matching extractor") elif 'ephys' in session_type: # assume ephys == FPGA labels = assert_valid_label( kwargs.pop('labels', ('left', 'right', 'body'))) labels = (labels, ) if isinstance(labels, str) else labels # Ensure list/tuple extractor = [partial(CameraTimestampsFPGA, label) for label in labels] if 'sync' not in kwargs: kwargs['sync'], kwargs['chmap'] = \ get_main_probe_sync(session_path, bin_exists=kwargs.pop('bin_exists', False)) else: # assume Bpod otherwise assert kwargs.pop('labels', 'left'), 'only left camera is currently supported' extractor = CameraTimestampsBpod outputs, files = run_extractor_classes(extractor, session_path=session_path, save=save, **kwargs) return outputs, files