def add_behavior(nwbfile, expt): bd = expt.behaviorData(imageSync=True) fs = 1 / expt.frame_period() behavior_module = nwbfile.create_processing_module( name='Behavior', description='Data relevant to behavior') # Add Normalized Position pos = Position(name='Normalized Position') pos.create_spatial_series(name='Normalized Position', rate=fs, data=bd['treadmillPosition'][:, np.newaxis], reference_frame='0 is belt start', conversion=0.001 * bd['trackLength']) behavior_module.add_container(pos) # Add Licking licking = BehavioralTimeSeries(name='Licking') licking.create_timeseries( name='Licking', data=bd['licking'], rate=fs, unit='na', description='1 if mouse licked during this imaging frame') behavior_module.add_container(licking) # Add Water Reward Delivery water = BehavioralTimeSeries(name='Water') water.create_timeseries( name='Water', data=bd['water'], rate=fs, unit='na', description='1 if water was delivered during this imaging frame') behavior_module.add_container(water) # Add Lap Times laps = BehavioralEvents(name='Lap Starts') # TODO probably not best to have laps as data and timestamps here laps.create_timeseries(name='Lap Starts', data=bd['lap'], timestamps=bd['lap'], description='Frames at which laps began', unit='na') behavior_module.add_container(laps)
def run_conversion(self, nwbfile: NWBFile, metadata: dict = None, overwrite: bool = False): assert isinstance(nwbfile, NWBFile), "'nwbfile' should be of type pynwb.NWBFile" metadata_default = self.get_metadata() metadata = dict_deep_update(metadata_default, metadata) # Subject: if nwbfile.subject is None: nwbfile.subject = Subject(**metadata['Subject']) # adding behavior: start_time = 0.0 rate = 1 / self.data_frame.time.diff().mean() beh_ts = [] for behdict in self.beh_args: if 'cm' in behdict['unit']: conv = 1e-2 behdict.update(unit='m') else: conv = 1 behdict.update(starting_time=start_time, rate=rate, data=self.data_frame[behdict['name']].to_numpy() * conv) beh_ts.append(TimeSeries(**behdict)) if 'behavior' not in nwbfile.processing: beh_mod = nwbfile.create_processing_module( 'behavior', 'Container for behavior time series') beh_mod.add( BehavioralTimeSeries(time_series=beh_ts, name='BehavioralTimeSeries')) else: beh_mod = nwbfile.processing['behavior'] if 'BehavioralTimeSeries' not in beh_mod.data_interfaces: beh_mod.add( BehavioralTimeSeries(time_series=beh_ts, name='BehavioralTimeSeries')) # adding stimulus: for inp_kwargs in self.stimulus_args: if inp_kwargs['name'] not in nwbfile.stimulus: inp_kwargs.update( starting_time=start_time, rate=rate, data=self.data_frame[inp_kwargs['name']].to_numpy()) nwbfile.add_stimulus(TimeSeries(**inp_kwargs))
def test_init(self): ts = TimeSeries('test_ts', np.ones((2, 2)), 'unit', timestamps=[1., 2., 3.]) bts = BehavioralTimeSeries(ts) self.assertEqual(bts.time_series['test_ts'], ts)
def test_init(self): ts = TimeSeries('test_ts', 'a hypothetical source', list(), 'unit', timestamps=list()) bts = BehavioralTimeSeries('test_bts', ts) self.assertEqual(bts.source, 'test_bts') self.assertEqual(bts.time_series, ts)
def face_nwb(): """ Adds Face Energy BehavioralTimeSeries to behavior processing module. Needs data from face.motionEnergy.npy and face.timestamps.npy. """ face_motion_energy = read_npy_file('face.motionEnergy.npy') face_timestamps = read_npy_file('face.timestamps.npy') face_rate = get_rate(face_timestamps) face_energy = TimeSeries( name='face_motion_energy', data=np.ravel(face_motion_energy), unit='arb. unit', starting_time=face_timestamps[0, 1], rate=face_rate, description='Features extracted from the video of the frontal aspect of ' 'the subject, including the subject\'s face and forearms.', comments='The integrated motion energy across the whole frame, i.e. ' 'sum( (thisFrame-lastFrame)^2 ). Some smoothing is applied ' 'before this operation.') face_interface = BehavioralTimeSeries(face_energy) behavior_module.add_data_interface(face_interface)
def test_init(self): ts = TimeSeries('test_ts', list(), 'unit', timestamps=list()) bts = BehavioralTimeSeries(ts) self.assertEqual(bts.time_series['test_ts'], ts)
# NWB provides the concept of a *data interface*--an object for a standard # storage location of specific types of data--through the :py:class:`~pynwb.base.NWBDataInterface` class. # For example, :py:class:`~pynwb.behavior.BehavioralTimeSeries` provides a container for holding one or more # :py:class:`~pynwb.base.TimeSeries` objects that store time series behavioral data. By putting # your behavioral data into a :py:class:`~pynwb.behavior.BehavioralTimeSeries` container, downstream users and # tools know where to look to retrieve behavioral data. For a comprehensive list of available data interfaces, see the # :ref:`overview page <modules_overview>` # # :py:class:`~pynwb.base.NWBDataInterface` objects can be added as acquisition data, or as members # of a :ref:`ProcessingModule <basic_procmod>` # # For the purposes of demonstration, we will use a :py:class:`~pynwb.ecephys.LFP` data interface. from pynwb.behavior import BehavioralTimeSeries bts = BehavioralTimeSeries() nwbfile.add_acquisition(bts) #################### # Each data interface stores its own type of data. We suggest you read the documentation for the # data interface of interest in the :ref:`API documentation <api_docs>` to figure out what data the # data interface allows and/or requires and what methods you will need to call to add this data. #################### # .. _basic_procmod: # # Processing modules # ------------------ # # *Processing modules* are used for storing a set of data interfaces that are related to a particular # processing workflow. For example, if you want to store the intermediate results of a spike sorting workflow,
def AddTDTAnalogDataToNWB(tdt_block_dir, nwb_file_name='', signal_info=None, module_name='behavior', verbose=False): """ Copies analog (continuous) data from the specified Blackrock file to Neurodata Without Borders (NWB) file. This is usually continuous signals about behavior (joystick position, screen refreshes, etc). User should provide information about the signals to help end users understand what is in each signal. Multiple calls can be used to load multiple data signals from multiple files. Typically, the NWB file will already be initialized by calling InitializeNWBFromBlackrock. :param tdt_block_dir: {str} full path of TDT files to convert to NWB. If empty, will open dialog. :param nwb_file_name: [optional] {str} full path of NWB file to export to. Default is to change blackrock extension to 'nwb' :param signal_info: [optional] {list} List of dictionaries with information about the signals to save. :param module_name: [optional] {str} Name of module to store data. Usually 'behavior' but could also be 'ecephys' or 'misc' :param verbose: [optional] {bool} whether to print updates while converting. Default is false. :return: {str} filename of NWB file (empty if error) """ # Check to see if user specified a TDT filename if not tdt_block_dir: # no file name passed # Ask user to specify a file if 'app' not in locals(): app = QApplication([]) tdt_block_dir = QFileDialog.getExistingDirectory( QFileDialog(), 'Select Directory', getcwd()) # Check to see if valid nwb_file_name is passed tdt_tsq_files = [ f for f in listdir(tdt_block_dir) if path.splitext(f)[1] == '.tsq' ] if not nwb_file_name: nwb_file_name = path.splitext(tdt_tsq_files[0])[0] + '.nwb' if verbose: print("Writing to NWB data file %s" % (nwb_file_name)) # Initialize the TDT file try: # Requires the raw data to be imported tdt_header = tdt.read_block(tdt_block_dir, headers=1) except: # catch *all* exceptions e = sys.exc_info()[0] raise FileNotFoundError( "Couldn't open TDT file. Error: {:s}".format(e)) # Initialize the NWB file nwb_io = [] try: if not path.isfile(nwb_file_name): # Initialize NWB file if verbose: print("NWB file doesn't exist. Creating new one: %s..." % (nwb_file_name)) InitializeNWBFromTDT(tdt_block_dir, nwb_file_name, verbose=verbose) # Append to existing file if verbose: print("Opening NWB file %s..." % (nwb_file_name), end='') nwb_file_append = True nwb_io = NWBHDF5IO(nwb_file_name, mode='a') nwb_file = nwb_io.read() if verbose: print("done.") except: # catch *all* exceptions e = sys.exc_info()[0] if nwb_io: nwb_io.close() raise FileExistsError("Couldn't open NWB file. Error: %s" % e) # Make sure module name is either behavior or misc module_name = module_name.lower() if (module_name != 'behavior') and (module_name != 'misc'): raise ValueError("Module type must either be 'behavior' or 'misc'.") # Parse the signal_info list if not signal_info: raise ValueError("Must specify signals to load.") elec_ids = [] for cur_signal_ind, cur_signal_info in enumerate(signal_info): if 'label' not in cur_signal_info.keys(): raise ValueError( "Signal information must have a label for each signal.") if 'name' not in cur_signal_info.keys(): raise ValueError( "Signal information must have a name for each signal. (Should be user-understandable)" ) if 'comments' not in cur_signal_info.keys(): signal_info[cur_signal_ind]['comments'] = '' # Find electrode IDs for this signal if ('elec_id' not in cur_signal_info.keys()) or ( not cur_signal_info['elec_id']): # Loop through and grab all signals of type 'streams' that aren't RAW data signal_info[cur_signal_ind]['elec_id'] = [] for cur_store in tdt_header.stores.keys(): # Grab all 'streams' but ignore RAWs if (tdt_header.stores[cur_store].type_str == 'streams') and (cur_store[0:3] != 'RAW'): signal_info[cur_signal_ind]['elec_id'].append(cur_store) # Create processing module for saving data if module_name not in nwb_file.processing.keys(): if verbose: print( "Specified processing module (%s) does not exist. Creating." % (module_name)) signal_module = ProcessingModule( name=module_name, description="Processing module for continuous signal data from %s." % (path.split(tdt_tsq_files[0])[1])) nwb_file.add_processing_module(signal_module) # Create data interface for the analog signals signal_info_str = signal_info[0]['name'] for i in range(1, len(signal_info)): signal_info_str = signal_info_str + ", " + signal_info[i]['name'] if verbose: print("Creating %s data interface for signals %s." % (module_name, signal_info_str)) if module_name == 'behavior': cur_data_interface = BehavioralTimeSeries(name="Analog signals (" + signal_info_str + ")") elif module_name == 'misc': cur_data_interface = AbstractFeatureSeries(name="Analog signals (" + signal_info_str + ")") else: raise ValueError("Module type must either be 'behavior' or 'misc'.") if verbose: print("Adding signals...") for cur_signal_ind, cur_signal_info in enumerate(signal_info): # Get data from file analog_data = [] analog_fs = [] analog_start_time = [] for cur_elec_id in cur_signal_info['elec_id']: cur_data = tdt.read_block(tdt_block_dir, store=cur_elec_id) cur_analog_data = cur_data.streams[cur_elec_id].data cur_analog_data = np.reshape(cur_analog_data, [len(cur_analog_data), 1]) if len(analog_data) == 0: analog_fs = cur_data.streams[cur_elec_id].fs analog_start_time = cur_data.streams[cur_elec_id].start_time analog_data = cur_analog_data else: analog_fs.append(cur_data.streams[cur_elec_id].fs) analog_start_time.append( cur_data.streams[cur_elec_id].start_time) analog_data = np.concatenate([analog_data, cur_analog_data], axis=1) # Make sure all of the fs and start_times are the same analog_start_time = np.unique(analog_start_time) analog_fs = np.unique(analog_fs) if len(analog_start_time) != 1 or len(analog_fs) != 1: raise ValueError( 'Start time and sampling frequency need to be the same for signals to be combined.' ) # Create time series cur_data_interface.create_timeseries( name=cur_signal_info['name'], data=analog_data, comments=cur_signal_info['comments'], unit="V", #TODO: Check that this is correct for TDT resolution=1.0, #TODO: Can we get this from TDT? conversion= 0.001, #TODO: Check what the correct conversion is for TDT starting_time=analog_start_time[0], rate=analog_fs[0], description="Signal %s from %s." % (cur_signal_info['label'], path.split(tdt_tsq_files[0])[1])) if verbose: print("\tAdded %s." % (cur_signal_info['label'])) # Add data interface to module in NWB file if verbose: print("Adding data interface to module.") nwb_file.processing[module_name].add(cur_data_interface) # Write the file if verbose: print("Writing NWB file and closing.") nwb_io.write(nwb_file) nwb_io.close() return nwb_file_name
def chang2nwb(blockpath, outpath=None, session_start_time=None, session_description=None, identifier=None, anin4=False, ecog_format='auto', external_subject=True, include_pitch=False, include_intensity=False, speakers=True, mic=False, mini=False, hilb=False, verbose=False, imaging_path=None, parse_transcript=False, include_cortical_surfaces=True, include_electrodes=True, include_ekg=True, subject_image_list=None, rest_period=None, load_warped=False, **kwargs): """ Parameters ---------- blockpath: str outpath: None | str if None, output = [blockpath]/[blockname].nwb session_start_time: datetime.datetime default: datetime(1900, 1, 1) session_description: str default: blockname identifier: str default: blockname anin4: False | str Whether or not to convert ANIN4. ANIN4 is used as an extra channel for things like button presses, and is usually unused. If a string is supplied, that is used as the name of the timeseries. ecog_format: str ({'htk'}, 'mat', 'raw') external_subject: bool (optional) True: (default) cortical mesh is saved in an external file and a link is provided to that file. This is useful if you have multiple sessions for a single subject. False: cortical mesh is saved normally include_pitch: bool (optional) add pitch data. Default: False include_intensity: bool (optional) add intensity data. Default: False speakers: bool (optional) Default: False mic: bool (optional) default: False mini: only save data stub. Used for testing hilb: bool include Hilbert Transform data. Default: False verbose: bool (optional) imaging_path: str (optional) None: use IMAGING_DIR 'local': use subject_dir/Imaging/ else: use supplied string parse_transcript: str (optional) include_cortical_surfaces: bool (optional) include_electrodes: bool (optional) include_ekg: bool (optional) subject_image_list: list (optional) List of paths of images to include rest_period: None | array-like kwargs: dict passed to pynwb.NWBFile Returns ------- """ behav_module = None basepath, blockname = os.path.split(blockpath) subject_id = get_subject_id(blockname) if identifier is None: identifier = blockname if session_description is None: session_description = blockname if outpath is None: outpath = blockpath + '.nwb' out_base_path = os.path.split(outpath)[0] if session_start_time is None: session_start_time = datetime(1900, 1, 1).astimezone(timezone('UTC')) if imaging_path is None: subj_imaging_path = path.join(IMAGING_PATH, subject_id) elif imaging_path == 'local': subj_imaging_path = path.join(basepath, 'imaging') else: subj_imaging_path = os.path.join(imaging_path, subject_id) # file paths bad_time_file = path.join(blockpath, 'Artifacts', 'badTimeSegments.mat') ecog_path = path.join(blockpath, 'RawHTK') ecog400_path = path.join(blockpath, 'ecog400', 'ecog.mat') elec_metadata_file = path.join(subj_imaging_path, 'elecs', 'TDT_elecs_all.mat') mesh_path = path.join(subj_imaging_path, 'Meshes') pial_files = glob.glob(path.join(mesh_path, '*pial.mat')) # Create the NWB file object nwbfile = NWBFile(session_description, identifier, session_start_time, datetime.now().astimezone(), session_id=identifier, institution='University of California, San Francisco', lab='Chang Lab', **kwargs) nwbfile.add_electrode_column('bad', 'electrode identified as too noisy') bad_elecs_inds = get_bad_elecs(blockpath) if include_electrodes: add_electrodes(nwbfile, elec_metadata_file, bad_elecs_inds, load_warped=load_warped) else: device = nwbfile.create_device('256Grid') electrode_group = nwbfile.create_electrode_group( name='256Grid electrodes', description='auto_group', location='location', device=device) for elec_counter in range(256): bad = elec_counter in bad_elecs_inds nwbfile.add_electrode(id=elec_counter + 1, x=np.nan, y=np.nan, z=np.nan, imp=np.nan, location=' ', filtering='none', group=electrode_group, bad=bad) ecog_elecs = list(range(len(nwbfile.electrodes))) ecog_elecs_region = nwbfile.create_electrode_table_region( ecog_elecs, 'ECoG electrodes on brain') # Read electrophysiology data from HTK files and add them to NWB file if ecog_format == 'auto': ecog_rate, data, ecog_path = auto_ecog(blockpath, ecog_elecs, verbose=False) elif ecog_format == 'htk': if verbose: print('reading htk acquisition...', flush=True) ecog_rate, data = readhtks(ecog_path, ecog_elecs) data = data.squeeze() if verbose: print('done', flush=True) elif ecog_format == 'mat': with File(ecog400_path, 'r') as f: data = f['ecogDS']['data'][:, ecog_elecs] ecog_rate = f['ecogDS']['sampFreq'][:].ravel()[0] ecog_path = ecog400_path elif ecog_format == 'raw': ecog_path = os.path.join(tdt_data_path, subject_id, blockname, 'raw.mat') ecog_rate, data = load_wavs(ecog_path) else: raise ValueError('unrecognized argument: ecog_format') ts_desc = "all Wav data" if mini: data = data[:2000] ecog_ts = ElectricalSeries(name='ElectricalSeries', data=H5DataIO(data, compression='gzip'), electrodes=ecog_elecs_region, rate=ecog_rate, description=ts_desc, conversion=0.001) nwbfile.add_acquisition(ecog_ts) if include_ekg: ekg_elecs = find_ekg_elecs(elec_metadata_file) if len(ekg_elecs): add_ekg(nwbfile, ecog_path, ekg_elecs) if mic: # Add microphone recording from room fs, data = get_analog(blockpath, 1) nwbfile.add_acquisition( TimeSeries('microphone', data, 'audio unit', rate=fs, description="audio recording from microphone in room")) if speakers: fs, data = get_analog(blockpath, 2) # Add audio stimulus 1 nwbfile.add_stimulus( TimeSeries('speaker 1', data, 'NA', rate=fs, description="audio stimulus 1")) # Add audio stimulus 2 fs, data = get_analog(blockpath, 3) if fs is not None: nwbfile.add_stimulus( TimeSeries('speaker 2', data, 'NA', rate=fs, description='the second stimulus source')) if anin4: fs, data = get_analog(blockpath, 4) nwbfile.add_acquisition( TimeSeries(anin4, data, 'aux unit', rate=fs, description="aux analog recording")) # Add bad time segments if os.path.exists(bad_time_file) and os.stat(bad_time_file).st_size: bad_time = sio.loadmat(bad_time_file)['badTimeSegments'] for row in bad_time: nwbfile.add_invalid_time_interval(start_time=row[0], stop_time=row[1], tags=('ECoG artifact', ), timeseries=ecog_ts) if rest_period is not None: nwbfile.add_epoch_column(name='label', description='label') nwbfile.add_epoch(start_time=rest_period[0], stop_time=rest_period[1], label='rest_period') if hilb: block_hilb_path = os.path.join(hilb_dir, subject_id, blockname, blockname + '_AA.h5') file = File(block_hilb_path, 'r') data = transpose_iter( file['X']) # transposes data during iterative write filter_center = file['filter_center'][:] filter_sigma = file['filter_sigma'][:] data = H5DataIO(DataChunkIterator(tqdm(data, desc='writing hilbert data'), buffer_size=400 * 20), compression='gzip') decomp_series = DecompositionSeries( name='LFPDecompositionSeries', description='Gaussian band Hilbert transform', data=data, rate=400., source_timeseries=ecog_ts, metric='amplitude') for band_mean, band_stdev in zip(filter_center, filter_sigma): decomp_series.add_band(band_mean=band_mean, band_stdev=band_stdev) hilb_mod = nwbfile.create_processing_module( name='ecephys', description='holds hilbert analysis results') hilb_mod.add_container(decomp_series) if include_cortical_surfaces: subject = ECoGSubject(subject_id=subject_id) subject.cortical_surfaces = create_cortical_surfaces( pial_files, subject_id) else: subject = Subject(subject_id=subject_id, species='H**o sapiens') if subject_image_list is not None: subject = add_images_to_subject(subject, subject_image_list) if external_subject: subj_fpath = path.join(out_base_path, subject_id + '.nwb') if not os.path.isfile(subj_fpath): subj_nwbfile = NWBFile(session_description=subject_id, identifier=subject_id, subject=subject, session_start_time=datetime( 1900, 1, 1).astimezone(timezone('UTC'))) with NWBHDF5IO(subj_fpath, manager=manager, mode='w') as subj_io: subj_io.write(subj_nwbfile) subj_read_io = NWBHDF5IO(subj_fpath, manager=manager, mode='r') subj_nwbfile = subj_read_io.read() subject = subj_nwbfile.subject nwbfile.subject = subject if parse_transcript: if parse_transcript == 'CV': parseout = parse(blockpath, blockname) df = make_df(parseout, 0, subject_id, align_pos=1) nwbfile.add_trial_column('cv_transition_time', 'time of CV transition in seconds') nwbfile.add_trial_column( 'speak', 'if True, subject is speaking. If False, subject is listening') nwbfile.add_trial_column('condition', 'syllable spoken') for _, row in df.iterrows(): nwbfile.add_trial(start_time=row['start'], stop_time=row['stop'], cv_transition_time=row['align'], speak=row['mode'] == 'speak', condition=row['label']) elif parse_transcript == 'singing': parseout = parse(blockpath, blockname) df = make_df(parseout, 0, subject_id, align_pos=0) if not len(df): df = pd.DataFrame(parseout) df['mode'] = 'speak' df = df.loc[df['label'].astype('bool'), :] # handle empty labels nwbfile.add_trial_column( 'speak', 'if True, subject is speaking. If False, subject is listening') nwbfile.add_trial_column('condition', 'syllable spoken') for _, row in df.iterrows(): nwbfile.add_trial(start_time=row['start'], stop_time=row['stop'], speak=row['mode'] == 'speak', condition=row['label']) elif parse_transcript == 'emphasis': parseout = parse(blockpath, blockname) try: df = make_df(parseout, 0, subject_id, align_pos=0) except: df = pd.DataFrame(parseout) if not len(df): df = pd.DataFrame(parseout) df = df.loc[df['label'].astype('bool'), :] # handle empty labels nwbfile.add_trial_column('condition', 'word emphasized') nwbfile.add_trial_column( 'speak', 'if True, subject is speaking. If False, subject is listening') for _, row in df.iterrows(): nwbfile.add_trial(start_time=row['start'], stop_time=row['stop'], speak=True, condition=row['label']) elif parse_transcript == 'MOCHA': nwbfile = create_transcription(nwbfile, transcript_path, blockname) # behavior if include_pitch: if behav_module is None: behav_module = nwbfile.create_processing_module( 'behavior', 'processing about behavior') if os.path.isfile( os.path.join(blockpath, 'pitch_' + blockname + '.mat')): fs, data = load_pitch(blockpath) pitch_ts = TimeSeries( data=data, rate=fs, unit='Hz', name='pitch', description= 'Pitch as extracted from Praat. NaNs mark unvoiced regions.') behav_module.add_container( BehavioralTimeSeries(name='pitch', time_series=pitch_ts)) else: print('No pitch file for ' + blockname) if include_intensity: if behav_module is None: behav_module = nwbfile.create_processing_module( 'behavior', 'processing about behavior') if os.path.isfile( os.path.join(blockpath, 'intensity_' + blockname + '.mat')): fs, data = load_pitch(blockpath) intensity_ts = TimeSeries( data=data, rate=fs, unit='dB', name='intensity', description='Intensity of speech in dB extracted from Praat.') behav_module.add_container( BehavioralTimeSeries(name='intensity', time_series=intensity_ts)) else: print('No intensity file for ' + blockname) # Export the NWB file with NWBHDF5IO(outpath, manager=manager, mode='w') as io: io.write(nwbfile) if external_subject: subj_read_io.close() if hilb: file.close() # read check with NWBHDF5IO(outpath, manager=manager, mode='r') as io: io.read()