Exemple #1
0
 def test_add_electrical_series(self):
     fe = FilteredEphys()
     table = make_electrode_table()
     region = DynamicTableRegion('electrodes', [0, 2],
                                 'the first and third electrodes', table)
     eS = ElectricalSeries('test_eS', [0, 1, 2, 3],
                           region,
                           timestamps=[0.1, 0.2, 0.3, 0.4])
     fe.add_electrical_series(eS)
     self.assertEqual(fe.electrical_series.get('test_eS'), eS)
     self.assertEqual(fe['test_eS'], fe.electrical_series.get('test_eS'))
Exemple #2
0
 def test_add_electrical_series(self):
     fe = FilteredEphys()  # noqa: F405
     dev1 = Device('dev1')  # noqa: F405
     group = ElectrodeGroup(  # noqa: F405, F841
         'tetrode1', 'tetrode description', 'tetrode location', dev1)
     table = make_electrode_table()
     region = DynamicTableRegion('electrodes', [0, 2], 'the first and third electrodes', table)
     eS = ElectricalSeries(  # noqa: F405
         'test_eS', [0, 1, 2, 3], region, timestamps=[0.1, 0.2, 0.3, 0.4])
     fe.add_electrical_series(eS)
     self.assertEqual(fe.electrical_series.get('test_eS'), eS)
     self.assertEqual(fe['test_eS'], fe.electrical_series.get('test_eS'))
Exemple #3
0
 def setUpContainer(self):
     """ Return a test FilteredEphys to read/write """
     es = self.setUpTwoElectricalSeries()
     fe = FilteredEphys(es)
     return fe
Exemple #4
0
 def setUpContainer(self):
     es = self.setUpElectricalSeriesContainers()
     ret = FilteredEphys(es)
     return ret
Exemple #5
0
 def setUpContainer(self):
     es = self.setUpElectricalSeriesContainers()
     ret = FilteredEphys('FilteredEphys roundtrip test', es)
     return ret
def main():
    # Get absolute path to file
    filename = Path(sys.argv[1]).resolve().as_posix()

    # TODO use with / as context manager - for handling open/closing files
    # TODO update the plexon API to use exceptions instead of this clunky
    # if result == 0 code
    file_reader = PyPL2FileReader()
    file_handle = file_reader.pl2_open_file(filename)

    if (file_handle == 0):
        print_error(file_reader)

    # create the PL2FileInfo instance containing basic header information
    file_info = PL2FileInfo()
    res = file_reader.pl2_get_file_info(file_handle, file_info)

    if (res == 0):
        print_error(file_reader)

    # USER NEEDS TO INPUT:

    # create the NWBFile instance
    session_description = 'Pulvinar recording from McCartney'
    id = 'M20170127'
    session_start_time = tm_to_datetime(file_info.m_CreatorDateTime)
    timezone = pytz.timezone("America/New_York")
    experimenter = 'Ryan Ly'
    lab = 'Kastner Lab'
    institution = 'Princeton University'
    experiment_description = 'Neural correlates of visual attention across the pulvinar'
    session_id = id
    data_collection = file_info.m_CreatorComment.decode('ascii')

    session_start_time = timezone.localize(session_start_time)
    nwbfile = NWBFile(session_description=session_description,
                      identifier=id,
                      session_start_time=session_start_time,
                      experimenter=experimenter,
                      lab=lab,
                      institution=institution,
                      experiment_description=experiment_description,
                      session_id=session_id,
                      data_collection=data_collection)

    # TODO add in the reprocessor metadata from file_info

    # create a recording device instance
    plexon_device_name = file_info.m_CreatorSoftwareName.decode('ascii') + '_v' + \
                         file_info.m_CreatorSoftwareVersion.decode('ascii')
    plexon_device = nwbfile.create_device(name=plexon_device_name)

    eye_trac_device_name = 'ASL_Eye-trac_6_via_' + plexon_device_name
    eye_trac_device = nwbfile.create_device(name=eye_trac_device_name)

    lever_device_name = 'Manual_lever_via_' + plexon_device_name
    lever_device = nwbfile.create_device(name=lever_device_name)
    # TODO update Device to take in metadata information about the Device

    # create electrode groups representing single shanks or other kinds of data
    # create list of metadata for adding into electrode groups. importantly, sasdasdsads
    # these metadata
    electrode_group_metadata = []
    electrode_group_metadata.append({'name': '32ch-array',
                                    'description': '32-channel_array',
                                    'location': 'Pulvinar',
                                    'device': plexon_device,
                                    'channel_ids': range(1, 33)})
    electrode_group_metadata.append({'name': 'test_electrode_1',
                                    'description': 'test_electrode_1',
                                    'location': 'unknown',
                                    'device': plexon_device,
                                    'channel_ids': [97]})
    electrode_group_metadata.append({'name': 'test_electrode_2',
                                    'description': 'test_electrode_2',
                                    'location': 'unknown',
                                    'device': plexon_device,
                                    'channel_ids': [98]})
    electrode_group_metadata.append({'name': 'test_electrode_3',
                                    'description': 'test_electrode_3',
                                    'location': 'unknown',
                                    'device': plexon_device,
                                    'channel_ids': [99]})
    non_electrode_ts_metadata = []
    non_electrode_ts_metadata.append({'name': 'eyetracker_x_voltage',
                                     'description': 'eyetracker_x_voltage',
                                     'location': 'n/a',
                                     'device': eye_trac_device,
                                     'channel_ids': [126]})
    non_electrode_ts_metadata.append({'name': 'eyetracker_y_voltage',
                                     'description': 'eyetracker_y_voltage',
                                     'location': 'n/a',
                                     'device': eye_trac_device,
                                     'channel_ids': [127]})
    non_electrode_ts_metadata.append({'name': 'lever_voltage',
                                     'description': 'lever_voltage',
                                     'location': 'n/a',
                                     'device': lever_device,
                                     'channel_ids': [128]})

    # make an electrode group for every group of channel IDs specified
    electrode_groups = []
    map_electrode_group_to_channel_ids = []
    for egm in electrode_group_metadata:
        print(f'Creating electrode group named "{egm["name"]}"')
        eg = nwbfile.create_electrode_group(name=egm['name'],
                                            description=egm['description'],
                                            location=egm['location'],
                                            device=egm['device'])
        electrode_groups.append(eg)
        map_electrode_group_to_channel_ids.append(egm['channel_ids'])

    # group indices of analog channels in the Plexon file by type, then source
    wb_src_chans = {};
    fp_src_chans = {};
    spkc_src_chans = {};
    ai_src_chans = {};
    aif_src_chans = {};

    for pl2_ind in range(file_info.m_TotalNumberOfAnalogChannels):
        achannel_info = PL2AnalogChannelInfo()
        res = file_reader.pl2_get_analog_channel_info(file_handle, pl2_ind, achannel_info)
        if (res == 0):
            print_error(file_reader)
            break

        if (achannel_info.m_ChannelEnabled and
                achannel_info.m_ChannelRecordingEnabled and
                achannel_info.m_NumberOfValues > 0 and
                achannel_info.m_MaximumNumberOfFragments > 0):
            # store zero-based channel index and electrode channel id
            achan_name = achannel_info.m_Name.decode('ascii');
            if achan_name.startswith('WB'):
                src_chans = wb_src_chans
            elif achan_name.startswith('FP') and get_channel_id(achan_name) <= 125:
                src_chans = fp_src_chans
            elif achan_name.startswith('FP') and get_channel_id(achan_name) > 125:
                src_chans = ai_src_chans
            elif achan_name.startswith('SPKC'):
                src_chans = spkc_src_chans
            elif achan_name.startswith('AI'):
                src_chans = ai_src_chans
            elif achan_name.startswith('AIF'):
                src_chans = aif_src_chans
            else:
                warnings.warn('Unrecognized analog channel: ' + achan_name)
                break

            channel_id = get_channel_id(achan_name)

            # src_chans is a dict {Plexon source ID : list of dict ...
            # {'pl2_ind': analog channel ind, 'channel_id': electrode channel ID}}
            chans = {'pl2_ind': pl2_ind, 'channel_id': channel_id}
            if not achannel_info.m_Source in src_chans:
                src_chans[achannel_info.m_Source] = [chans]
            else:
                src_chans[achannel_info.m_Source].append(chans)

    # create electrodes and create a region of indices in the electrode table
    # corresponding to the electrodes used for this type of analog data (WB, FP,
    # SPKC, AI, AIF)
    if wb_src_chans:
        for src, chans in wb_src_chans.items():
            channel_ids_all = [ch['channel_id'] for ch in chans]
            channel_ids_by_group = get_channel_ids_by_metadata_list(
                    channel_ids_all, electrode_group_metadata)
            for channel_ids, eg in zip(channel_ids_by_group, electrode_groups):
                if channel_ids:
                    # find the mapped pl2 index again
                    group_chans = [c for c in chans if c['channel_id'] in channel_ids]
                    add_electrodes(nwbfile, channel_ids, eg)
                    wb_es = pl2_create_electrode_timeseries(nwbfile,
                                                            file_reader,
                                                            file_handle,
                                                            file_info.m_TimestampFrequency,
                                                            group_chans,
                                                            'Wideband_voltages_' + eg.name,
                                                            'Wideband electrodes, group ' + eg.name)
                    nwbfile.add_acquisition(wb_es)

    if fp_src_chans or spkc_src_chans:
        ecephys_module = nwbfile.create_processing_module(name='ecephys',
                                                          description='Processed extracellular electrophysiology data')

    if fp_src_chans:
        # LFP signal can come from multiple Plexon "sources"
        # for src, chans in fp_src_chans.items():
        #     channel_ids_all = [ch['channel_id'] for ch in chans]
        #     channel_ids_by_group = get_channel_ids_by_metadata_list(
        #             channel_ids_all, electrode_group_metadata)
        #
        #     # channel_ids_by_group is a list that parallels
        #     # electrode_groups. it contains a list of lists of channel_ids that
        #     # are used for this type and source of analog data which are part of
        #     # the corresponding electrode group
        #     # for example, if the ElectrodeGroup at index 2 contains electrodes
        #     # with the channel IDs 1-32, and there is analog channel data for
        #     # channel ID 4-6, then channel_ids_by_group[2] would have
        #     # [4, 5, 6].
        #     d = []
        #     for c in channel_ids_all:
        #         for i in range(len(map_channel_ids_to_electrode_groups)):
        #             if c in get_channel_ids_by_metadata_list[i]:
        #                 d[i].append(c)
        #
        #     for channel_ids, eg in zip(channel_ids_by_group, electrode_groups):
        for src, chans in fp_src_chans.items():
            channel_ids = [ch['channel_id'] for ch in chans]
            electrode_group_to_channel_ids = get_channel_ids_by_metadata_list(
                    channel_ids, electrode_group_metadata)
            for channel_ids, eg, egm in zip(electrode_group_to_channel_ids,
                                                  electrode_groups,
                                                  electrode_group_metadata):
                if channel_ids:
                    group_chans = [c for c in chans if c['channel_id'] in channel_ids]
                    add_electrodes(nwbfile, channel_ids, eg)
                    lfp_es = pl2_create_electrode_timeseries(nwbfile,
                                                             file_reader,
                                                             file_handle,
                                                             file_info.m_TimestampFrequency,
                                                             group_chans,
                                                             'LFP_voltages_' + eg.name,
                                                             ('LFP electrodes, group ' + eg.name +
                                                              '. Low-pass filtering at 200 Hz done online by Plexon data acquisition system.'))

                    print('Adding LFP processing module with electrical series for channel ids [' +
                          ', '.join(str(x) for x in channel_ids) + '] for electrode group ' +
                          eg.name)
                    # TODO add LFP filter properties, though these are not stored in the PL2
                    # file
                    lfp = LFP(lfp_es, name='LFP_' + egm['name'])
                    ecephys_module.add(lfp)

    spkc_es = None
    if spkc_src_chans:
        for src, chans in spkc_src_chans.items():
            channel_ids = [ch['channel_id'] for ch in chans]
            electrode_group_to_channel_ids = get_channel_ids_by_metadata_list(
                    channel_ids, electrode_group_metadata)
            for channel_ids, eg, egm in zip(electrode_group_to_channel_ids,
                                                  electrode_groups,
                                                  electrode_group_metadata):
                if channel_ids:
                    group_chans = [c for c in chans if c['channel_id'] in channel_ids]
                    add_electrodes(nwbfile, channel_ids, eg)
                    spkc_es = pl2_create_electrode_timeseries(nwbfile,
                                                              file_reader,
                                                              file_handle,
                                                              file_info.m_TimestampFrequency,
                                                              group_chans,
                                                              'High-pass_filtered_voltages_' + egm['name'],
                                                              ('High-pass filtered ("SPKC") electrodes, group ' + egm['name'] +
                                                               '. High-pass filtering at 300 Hz done online by Plexon data acquisition system.'))

                    print('Adding SPKC processing module with electrical series for channel ids [' +
                          ', '.join(str(x) for x in channel_ids) + '] for electrode group ' +
                          egm['name'])
                    # TODO add SPKC filter properties, though these are not stored in the PL2
                    # file
                    filt_ephys = FilteredEphys(spkc_es, name='SPKC_' + egm['name'])
                    ecephys_module.add(filt_ephys)

    if ai_src_chans:
        for src, chans in ai_src_chans.items():
            channel_ids_all = [ch['channel_id'] for ch in chans]
            channel_ids_by_group = get_channel_ids_by_metadata_list(
                    channel_ids_all, non_electrode_ts_metadata)
            for channel_ids, gm in zip(channel_ids_by_group, non_electrode_ts_metadata):
                if channel_ids:
                    # find the mapped pl2 index again
                    pl2_inds = [c['pl2_ind'] for c in chans if c['channel_id'] in channel_ids]
                    ai_es = pl2_create_timeseries(nwbfile,
                                                  file_reader,
                                                  file_handle,
                                                  file_info.m_TimestampFrequency,
                                                  pl2_inds,
                                                  ('Auxiliary_input_' + str(src) +
                                                   '_' + gm['name']),
                                                  ('Auxiliary input, source ' + str(src) +
                                                   ', ' + gm['name']))
                    nwbfile.add_acquisition(ai_es)

    if aif_src_chans:
        for src, chans in aif_src_chans.items():
            channel_ids_all = [ch['channel_id'] for ch in chans]
            channel_ids_by_group = get_channel_ids_by_metadata_list(
                    channel_ids_all, non_electrode_ts_metadata)
            for channel_ids, gm in zip(channel_ids_by_group, non_electrode_ts_metadata):
                if channel_ids:
                    # find the mapped pl2 index again
                    pl2_inds = [c['pl2_ind'] for c in chans if c['channel_id'] in channel_ids]
                    aif_es = pl2_create_timeseries(nwbfile,
                                                  file_reader,
                                                  file_handle,
                                                  file_info.m_TimestampFrequency,
                                                  pl2_inds,
                                                  ('Filtered_auxiliary_input_' + str(src) +
                                                   '_' + gm['name']),
                                                  ('Filtered auxiliary input, source ' + str(src) +
                                                   ', ' + gm['name']))
                    nwbfile.add_acquisition(aif_es)

    #### Spikes ####

    # add these columns to unit table
    nwbfile.add_unit_column('pre_threshold_samples', 'number of samples before threshold')
    nwbfile.add_unit_column('num_samples', 'number of samples for each spike waveform')
    nwbfile.add_unit_column('num_spikes', 'number of spikes')
    nwbfile.add_unit_column('Fs', 'sampling frequency')
    nwbfile.add_unit_column('plx_sort_method', 'sorting method encoded by Plexon')
    nwbfile.add_unit_column('plx_sort_range', 'range of sample indices used in Plexon sorting')
    nwbfile.add_unit_column('plx_sort_threshold', 'voltage threshold used by Plexon sorting')
    nwbfile.add_unit_column('is_unsorted', 'whether this unit is the set of unsorted waveforms')
    nwbfile.add_unit_column('channel_id', 'original recording channel ID')

    # since waveforms are not a 1:1 mapping per unit, use table indexing

    nwbfile.add_unit_column('waveforms', 'waveforms for each spike', index=True)

    # add a unit for each spike channel
    for i in range(file_info.m_TotalNumberOfSpikeChannels):
        pl2_add_units(nwbfile, file_reader, file_handle, i)

    # if spkc_series is not None:
    #     # Plexon does not save the indices of the spike times in the
    #     # high-pass filtered data. So work backwards from the spike times
    #     # first convert spike times to sample indices, accounting for imprecision
    #     spike_inds = spike_ts * schannel_info.m_SamplesPerSecond
    #
    #     # TODO this can be SUPER INEFFICIENT
    #     if not all([math.isclose(x, np.round(x)) for x in spike_inds]):
    #         raise InconsistentInputException()
    #
    #     spike_inds = np.round(spike_inds) # need to account for fragments TODO
    #
    #     ed_module = nwbfile.create_processing_module(name='Plexon online sorted units - all',
    #                                                  description='All units detected online')
    #     print('Adding Event Detection processing module for Electrical Series ' +
    #           f'named {spkc_series.name}')
    #     ed = EventDetection(detection_method="xx", # TODO allow user input
    #                         source_electricalseries=spkc_series,
    #                         source_idx=spike_inds,
    #                         times=spike_ts)
    #     ed_module.add(ed)

    # write NWB file to disk
    out_file = './output/nwb_test.nwb'
    with NWBHDF5IO(out_file, 'w') as io:
        print('Writing to file: ' + out_file)
        io.write(nwbfile)

    # Close the PL2 file
    file_reader.pl2_close_file(file_handle)