def load_events(file_path, channels=None):
    """
    return time stamps in seconds of each digital channel

    :param file_path:
    :param channels: name of channels
    :return: dictionary, {channel: {'rise':[timestamps of rising events in seconds],
                                    'fall':[timestamps of falling events in seconds}}
    """

    print('\n')

    if file_path[-7:] != '.events':
        raise LookupError('The input file: ' + file_path + ' is not a .events file!')

    with open(file_path) as f:
        header = oe.readHeader(f)
    fs = float(header['sampleRate'])

    events = oe.loadEvents(file_path)

    detected_channel_number = int(max(events['channel']) + 1)
    real_channels = ['ch_' + ft.int2str(c, 3) for c in range(detected_channel_number)]

    if channels is not None:
        if detected_channel_number != len(channels):
            warning_msg = '\nThe number of digital channels detected: ' + str(detected_channel_number) + \
                          ' does not match input channel number: ' + str(len(channels))
            warnings.warn(warning_msg)

        if len(channels) <= detected_channel_number:
            real_channels[0:len(channels)] = channels
        else:
            real_channels = channels[0:detected_channel_number]

    output = {}

    for i, ch in enumerate(real_channels):
        output.update({ch : {'rise' : [],
                             'fall' : []}
                       })

        rise = events['timestamps'][np.logical_and(events['channel'] == i, events['eventId'] == 1)]
        output[ch]['rise'] = np.array(rise.astype(np.float32) / fs).astype(np.float32)

        fall = events['timestamps'][np.logical_and(events['channel'] == i, events['eventId'] == 0)]
        output[ch]['fall'] = np.array(fall.astype(np.float32) / fs).astype(np.float32)

    print('events loaded.\n')

    return output
def load_continuous_old(file_path, dtype=np.float32):
    """
    Jun's wrapper to load .continuous data from OpenEphys data files

    :param file_path:
    :param dtype: np.float32 or np.int16
    :return: header: dictionary, standard open ephys header for continuous file
             samples: 1D np.array
    """

    assert dtype in (np.float32, np.int16), \
        'Invalid data type specified for loadContinous, valid types are np.float32 and np.int16'

    print("\nLoading continuous data from " + file_path)

    bytes_per_block = CONTINUOUS_TIMESTAMP_DTYPE.itemsize + CONTINUOUS_SAMPLE_PER_RECORD_DTYPE.itemsize + \
                      CONTINUOUS_RECORDING_NUMBER_DTYPE.itemsize + CONTINUOUS_MARKER_BYTES + \
                      CONTINUOUS_SAMPLE_DTYPE.itemsize * oe.SAMPLES_PER_RECORD

    # read in the data
    f = open(file_path, 'rb')

    file_length = os.fstat(f.fileno()).st_size
    print('total length of the file: ', file_length, 'bytes.')

    print('bytes per record block: ', bytes_per_block)

    block_num = (file_length - oe.NUM_HEADER_BYTES) // bytes_per_block
    print('total number of valid blocks: ', block_num)

    header = oe.readHeader(f)

    samples = np.empty(oe.SAMPLES_PER_RECORD * block_num, dtype)

    is_break = False

    for i in range(block_num):
        if i == 0:
            # to get the timestamp of the very first record (block)
            # for alignment of the digital event
            start_ind = np.fromfile(f, CONTINUOUS_TIMESTAMP_DTYPE, 1)
            start_time = float(start_ind) / float(header['sampleRate'])
        else:
            _ = np.fromfile(f, CONTINUOUS_TIMESTAMP_DTYPE, 1)
        N = np.fromfile(f, CONTINUOUS_SAMPLE_PER_RECORD_DTYPE, 1)[0]

        if N != oe.SAMPLES_PER_RECORD:
            print(('samples per record specified in block ' + str(i) + ' (' + str(N) +
                  ') does not equal to expected value (' + str(oe.SAMPLES_PER_RECORD) + ')!'))
            samples = samples[0 : i * oe.SAMPLES_PER_RECORD]
            is_break = True
            break
            # raise Exception('samples per record specified in block ' + str(i) + ' (' + str(N) + \
            #                 ') does not equal to expected value (' + str(oe.SAMPLES_PER_RECORD) + ')!')

        _ = (np.fromfile(f, CONTINUOUS_RECORDING_NUMBER_DTYPE, 1))

        if dtype == np.float32:
            curr_samples = (np.fromfile(f, CONTINUOUS_SAMPLE_DTYPE, N) * float(header['bitVolts'])).astype(np.float32)
        elif dtype == np.int16:
            curr_samples = np.fromfile(f, CONTINUOUS_SAMPLE_DTYPE, N).astype(np.int16)
        else:
            raise ValueError('Error in reading data of block:' + str(i))

        samples[i*oe.SAMPLES_PER_RECORD : (i+1)*oe.SAMPLES_PER_RECORD] = curr_samples

        record_mark = np.fromfile(f, dtype=np.dtype('<u1'), count=10)
        if not np.array_equal(record_mark, oe.RECORD_MARKER):
            print(('record marker specified in block ' + str(i) + ' (' + str(record_mark) +
                  ') does not equal to expected array (' + str(oe.RECORD_MARKER) + ') !'))
            is_break = True
            break

    if is_break:
        samples = samples[0: oe.SAMPLES_PER_RECORD * (i -1)]

    header.update({'start_time': start_time})

    return header, samples
def load_continuous(file_path, dtype=np.float32, start_ind=0):
    """
    Jun's wrapper to load .continuous data from OpenEphys data files, this will try to load the whole file into memory,
     using np.fromfile. It can also start from any position in the file (defined by the start_ind)

    :param file_path:
    :param dtype: np.float32 or np.int16
    :param start_ind: non-negative int, default 0. start index to extract data.
    :return: header: dictionary, standard open ephys header for continuous file
             samples: 1D np.array
    """

    assert dtype in (np.float32, np.int16), \
        'Invalid data type specified for loadContinous, valid types are np.float32 and np.int16'

    print("\nLoading continuous data from " + file_path)

    bytes_per_block = CONTINUOUS_TIMESTAMP_DTYPE.itemsize + CONTINUOUS_SAMPLE_PER_RECORD_DTYPE.itemsize + \
                      CONTINUOUS_RECORDING_NUMBER_DTYPE.itemsize + CONTINUOUS_MARKER_BYTES + \
                      CONTINUOUS_SAMPLE_DTYPE.itemsize * oe.SAMPLES_PER_RECORD

    input_array = np.fromfile(file_path, dtype='<u1')
    file_length = input_array.shape[0]
    print('total length of the file: ', file_length, 'bytes.')

    valid_block_start = find_next_valid_block(input_array, bytes_per_block=bytes_per_block, start_index=start_ind)
    print('the beginning index of the first valid block after index: ' + str(start_ind) + ' is ' + \
          str(valid_block_start))

    print('bytes per record block: ', bytes_per_block)

    # read in the data
    f = open(file_path, 'rb')

    header = oe.readHeader(f)

    block_num = (file_length - valid_block_start) // bytes_per_block
    print('number of potential valid blocks after index', start_ind, ':', block_num)

    samples = np.empty(oe.SAMPLES_PER_RECORD * block_num, dtype=dtype)

    _ = np.fromfile(f, np.dtype('<u1'), valid_block_start - oe.NUM_HEADER_BYTES)

    for i in range(block_num):
        if i == 0:
            # to get the timestamp of the very first record (block)
            # for alignment of the digital event
            start_ind = np.fromfile(f, CONTINUOUS_TIMESTAMP_DTYPE, 1)
            start_time = float(start_ind) / float(header['sampleRate'])
        else:
            _ = np.fromfile(f, CONTINUOUS_TIMESTAMP_DTYPE, 1)
        N = np.fromfile(f, CONTINUOUS_SAMPLE_PER_RECORD_DTYPE, 1)[0]

        if N != oe.SAMPLES_PER_RECORD:
            print(('samples per record specified in block ' + str(i) + ' (' + str(N) +
                  ') does not equal to expected value (' + str(oe.SAMPLES_PER_RECORD) + ')!'))
            samples = samples[0 : i * oe.SAMPLES_PER_RECORD]
            print('samples per record specified in block ' + str(i) + ' (' + str(N) + \
                  ') does not equal to expected value (' + str(oe.SAMPLES_PER_RECORD) + ')!')
            break

        _ = (np.fromfile(f, CONTINUOUS_RECORDING_NUMBER_DTYPE, 1))

        if dtype == np.float32:
            curr_samples = (np.fromfile(f, CONTINUOUS_SAMPLE_DTYPE, N) * float(header['bitVolts'])).astype(np.float32)
        elif dtype == np.int16:
            curr_samples = np.fromfile(f, CONTINUOUS_SAMPLE_DTYPE, N).astype(np.int16)
        else:
            raise ValueError('Error in reading data of block:' + str(i))

        samples[i*oe.SAMPLES_PER_RECORD : (i+1)*oe.SAMPLES_PER_RECORD] = curr_samples

        record_mark = np.fromfile(f, dtype=np.dtype('<u1'), count=10)
        if not np.array_equal(record_mark, oe.RECORD_MARKER):
            print(('record marker specified in block ' + str(i) + ' (' + str(record_mark) +
                  ') does not equal to expected array (oe.RECORD_MARKER)!'))
            break

    header.update({'start_time': start_time})
    print('continuous channel start time (for aligning digital events): ', start_time)

    return header, samples