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'))
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'))
def setUpContainer(self): """ Return a test FilteredEphys to read/write """ es = self.setUpTwoElectricalSeries() fe = FilteredEphys(es) return fe
def setUpContainer(self): es = self.setUpElectricalSeriesContainers() ret = FilteredEphys(es) return ret
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)