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
Beispiel #2
0
 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
Beispiel #5
0
 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)
Beispiel #6
0
    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'
Beispiel #7
0
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
Beispiel #9
0
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