def xdf2fif(filename, interactive=False, outdir=None): """ Convert XDF format """ from pyxdf import pyxdf pyxdf.logger.setLevel('WARNING') fdir, fname, fext = qc.parse_path_list(filename) if outdir is None: outdir = fdir elif outdir[-1] != '/': outdir += '/' fiffile = outdir + fname + '.fif' # channel x times data = pyxdf.load_xdf(filename) raw_data = data[0][0]['time_series'].T if np.max(raw_data[:-1]) < 1: logger.info('Assuming the signal unit is volate (V). Converting to uV') raw_data[:-1] *= 10**6 signals = np.concatenate((raw_data[-1, :].reshape(1, -1), raw_data[:-1, :])) sample_rate = int(data[0][0]['info']['nominal_srate'][0]) # TODO: check the event channel index and move to the 0-th index # in LSL, usually the name is TRIG or STI 014. ch_names = [] for ch in data[0][0]['info']['desc'][0]['channels'][0]['channel']: ch_names.append(ch['label'][0]) trig_ch_guess = pu.find_event_channel(signals, ch_names) if trig_ch_guess is None: trig_ch_guess = 0 ch_names = ['TRIGGER' ] + ch_names[:trig_ch_guess] + ch_names[trig_ch_guess + 1:] ch_info = ['stim'] + ['eeg'] * (len(ch_names) - 1) # fif header creation info = mne.create_info(ch_names, sample_rate, ch_info) raw = mne.io.RawArray(signals, info) #raw.add_events(events_index, stim_channel='TRIGGER') # save and close raw.save(fiffile, verbose=False, overwrite=True, fmt='double') logger.info('Saved to %s' % fiffile) saveChannels2txt(outdir, ch_names)
def xdf2fif(filename, interactive=False, outdir=None): """ Convert XDF format """ from xdf import xdf fdir, fname, fext = qc.parse_path_list(filename) if outdir is None: outdir = fdir elif outdir[-1] != '/': outdir += '/' fiffile = outdir + fname + '.fif' # channel x times data = xdf.load_xdf(filename) raw_data = data[0][0]['time_series'].T signals = np.concatenate((raw_data[-1, :].reshape(1, -1), raw_data[:-1, :])) sample_rate = int(data[0][0]['info']['nominal_srate'][0]) # TODO: check the event channel index and move to the 0-th index # in LSL, usually the name is TRIG or STI 014. ch_names = [] for ch in data[0][0]['info']['desc'][0]['channels'][0]['channel']: ch_names.append(ch['label'][0]) trig_ch_guess = pu.find_event_channel(signals, ch_names) if trig_ch_guess is None: trig_ch_guess = 0 ch_names =[ch_names[trig_ch_guess]] + ch_names[:trig_ch_guess] + ch_names[trig_ch_guess+1:] ch_info = ['stim'] + ['eeg'] * (len(ch_names)-1) # fif header creation info = mne.create_info(ch_names, sample_rate, ch_info) raw = mne.io.RawArray(signals, info) # save and close raw.save(fiffile, verbose=False, overwrite=True, fmt='double') print('Saved to', fiffile)
def pcl2fif(filename, interactive=False, outdir=None, external_event=None, offset=0, overwrite=False, precision='single'): """ PyCNBI Python pickle file Params -------- outdir: If None, it will be the subdirectory of the fif file. external_event: Event file in text format. Each row should be: "SAMPLE_INDEX 0 EVENT_TYPE" precision: Data matrix format. 'single' improves backward compatability. """ fdir, fname, fext = qc.parse_path_list(filename) if outdir is None: outdir = fdir + 'fif/' elif outdir[-1] != '/': outdir += '/' data = qc.load_obj(filename) if type(data['signals']) == list: signals_raw = np.array(data['signals'][0]).T # to channels x samples else: signals_raw = data['signals'].T # to channels x samples sample_rate = data['sample_rate'] if 'ch_names' not in data: ch_names = ['CH%d' % (x + 1) for x in range(signals_raw.shape[0])] else: ch_names = data['ch_names'] # search for event channel trig_ch = pu.find_event_channel(signals_raw, ch_names) ''' TODO: REMOVE # exception if trig_ch is None: logger.warning('Inferred event channel is None.') if interactive: logger.warning('If you are sure everything is alright, press Enter.') input() # fix wrong event channel elif trig_ch_guess != trig_ch: logger.warning('Specified event channel (%d) != inferred event channel (%d).' % (trig_ch, trig_ch_guess)) if interactive: input('Press Enter to fix. Event channel will be set to %d.' % trig_ch_guess) ch_names.insert(trig_ch_guess, ch_names.pop(trig_ch)) trig_ch = trig_ch_guess logger.info('New channel list:') for c in ch_names: logger.info('%s' % c) logger.info('Event channel is now set to %d' % trig_ch) ''' # move trigger channel to index 0 if trig_ch is None: # assuming no event channel exists, add a event channel to index 0 for consistency. logger.warning( 'No event channel was not found. Adding a blank event channel to index 0.' ) eventch = np.zeros([1, signals_raw.shape[1]]) signals = np.concatenate((eventch, signals_raw), axis=0) num_eeg_channels = signals_raw.shape[ 0] # data['channels'] is not reliable any more trig_ch = 0 ch_names = ['TRIGGER' ] + ['CH%d' % (x + 1) for x in range(num_eeg_channels)] elif trig_ch == 0: signals = signals_raw num_eeg_channels = data['channels'] - 1 else: # move event channel to 0 logger.info('Moving event channel %d to 0.' % trig_ch) signals = np.concatenate( (signals_raw[[trig_ch]], signals_raw[:trig_ch], signals_raw[trig_ch + 1:]), axis=0) assert signals_raw.shape == signals.shape num_eeg_channels = data['channels'] - 1 ch_names.pop(trig_ch) trig_ch = 0 ch_names.insert(trig_ch, 'TRIGGER') logger.info('New channel list:') for c in ch_names: logger.info('%s' % c) ch_info = ['stim'] + ['eeg'] * num_eeg_channels info = mne.create_info(ch_names, sample_rate, ch_info) # create Raw object raw = mne.io.RawArray(signals, info) raw._times = data['timestamps'] # seems to have no effect if external_event is not None: raw._data[0] = 0 # erase current events events_index = event_timestamps_to_indices(filename, external_event, offset) if len(events_index) == 0: logger.warning('No events were found in the event file') else: logger.info('Found %d events' % len(events_index)) raw.add_events(events_index, stim_channel='TRIGGER') qc.make_dirs(outdir) fiffile = outdir + fname + '.fif' raw.save(fiffile, verbose=False, overwrite=overwrite, fmt=precision) logger.info('Saved to %s' % fiffile) saveChannels2txt(outdir, ch_names) return True
def connect(self, find_any=True): """ Run in child process """ server_found = False amps = [] channels = 0 while server_found == False: if self.amp_name is None and self.amp_serial is None: logger.info("Looking for a streaming server...") else: logger.info("Looking for %s (Serial %s) ..." % (self.amp_name, self.amp_serial)) streamInfos = pylsl.resolve_streams() if len(streamInfos) > 0: # For now, only 1 amp is supported by a single StreamReceiver object. for si in streamInfos: # is_slave= ('true'==pylsl.StreamInlet(si).info().desc().child('amplifier').child('settings').child('is_slave').first_child().value() ) inlet = pylsl.StreamInlet(si) # LSL XML parser has a bug which crashes so do not use for now #amp_serial = inlet.info().desc().child('acquisition').child_value('serial_number') amp_serial = 'N/A' amp_name = si.name() # connect to a specific amp only? if self.amp_serial is not None and self.amp_serial != amp_serial: continue # connect to a specific amp only? if self.amp_name is not None and self.amp_name != amp_name: continue # EEG streaming server only? if self.eeg_only and si.type() != 'EEG': continue if 'USBamp' in amp_name: logger.info( 'Found USBamp streaming server %s (type %s, amp_serial %s) @ %s.' % (amp_name, si.type(), amp_serial, si.hostname())) self._lsl_tr_channel = 16 channels += si.channel_count() ch_list = pu.lsl_channel_list(inlet) amps.append(si) server_found = True break elif 'BioSemi' in amp_name: logger.info( 'Found BioSemi streaming server %s (type %s, amp_serial %s) @ %s.' % (amp_name, si.type(), amp_serial, si.hostname())) self._lsl_tr_channel = 0 # or subtract -6684927? (value when trigger==0) channels += si.channel_count() ch_list = pu.lsl_channel_list(inlet) amps.append(si) server_found = True break elif 'SmartBCI' in amp_name: logger.info( 'Found SmartBCI streaming server %s (type %s, amp_serial %s) @ %s.' % (amp_name, si.type(), amp_serial, si.hostname())) self._lsl_tr_channel = 23 channels += si.channel_count() ch_list = pu.lsl_channel_list(inlet) amps.append(si) server_found = True break elif 'StreamPlayer' in amp_name: logger.info( 'Found StreamPlayer streaming server %s (type %s, amp_serial %s) @ %s.' % (amp_name, si.type(), amp_serial, si.hostname())) self._lsl_tr_channel = 0 channels += si.channel_count() ch_list = pu.lsl_channel_list(inlet) amps.append(si) server_found = True break elif 'openvibeSignal' in amp_name: logger.info( 'Found an Openvibe signal streaming server %s (type %s, amp_serial %s) @ %s.' % (amp_name, si.type(), amp_serial, si.hostname())) ch_list = pu.lsl_channel_list(inlet) self._lsl_tr_channel = find_event_channel( ch_names=ch_list) channels += si.channel_count() amps.append(si) server_found = True # OpenVibe standard unit is Volts, which is not ideal for some numerical computations self.multiplier = 10**6 # change V -> uV unit for OpenVibe sources break elif 'openvibeMarkers' in amp_name: logger.info( 'Found an Openvibe markers server %s (type %s, amp_serial %s) @ %s.' % (amp_name, si.type(), amp_serial, si.hostname())) ch_list = pu.lsl_channel_list(inlet) self._lsl_tr_channel = find_event_channel( ch_names=ch_list) channels += si.channel_count() amps.append(si) server_found = True break elif find_any: logger.info( 'Found a streaming server %s (type %s, amp_serial %s) @ %s.' % (amp_name, si.type(), amp_serial, si.hostname())) ch_list = pu.lsl_channel_list(inlet) self._lsl_tr_channel = find_event_channel( ch_names=ch_list) channels += si.channel_count() amps.append(si) server_found = True break time.sleep(1) self.amp_name = amp_name # define EEG channel indices self._lsl_eeg_channels = list(range(channels)) if self._lsl_tr_channel is None: logger.warning( 'Trigger channel not fonud. Adding an empty channel 0.') else: if self._lsl_tr_channel != 0: logger.info_yellow( 'Trigger channel found at index %d. Moving to index 0.' % self._lsl_tr_channel) self._lsl_eeg_channels.pop(self._lsl_tr_channel) self._lsl_eeg_channels = np.array(self._lsl_eeg_channels) self.tr_channel = 0 # trigger channel is always set to 0. self.eeg_channels = np.arange( 1, channels) # signal channels start from 1. # create new inlets to read from the stream inlets_master = [] inlets_slaves = [] for amp in amps: # data type of the 2nd argument (max_buflen) is int according to LSL C++ specification! inlet = pylsl.StreamInlet(amp, max_buflen=self.stream_bufsec) inlets_master.append(inlet) self.buffers.append([]) self.timestamps.append([]) inlets = inlets_master + inlets_slaves sample_rate = amps[0].nominal_srate() logger.info('Channels: %d' % channels) logger.info('LSL Protocol version: %s' % amps[0].version()) logger.info('Source sampling rate: %.1f' % sample_rate) logger.info('Unit multiplier: %.1f' % self.multiplier) #self.winsize = int(self.winsec * sample_rate) #self.bufsize = int(self.bufsec * sample_rate) self.winsize = int(round(self.winsec * sample_rate)) self.bufsize = int(round(self.bufsec * sample_rate)) self.stream_bufsize = int(round(self.stream_bufsec * sample_rate)) self.sample_rate = sample_rate self.connected = True self.ch_list = ch_list self.inlets = inlets # Note: not picklable! # TODO: check if there's any problem with multiple inlets if len(self.inlets) > 1: logger.warning( 'Merging of multiple acquisition servers is not supported yet. Only %s will be used.' % amps[0].name()) ''' for i in range(1, len(self.inlets)): chunk, tslist = self.inlets[i].pull_chunk(max_samples=self.stream_bufsize) self.buffers[i].extend(chunk) self.timestamps[i].extend(tslist) if self.bufsize > 0 and len(self.buffers[i]) > self.bufsize: self.buffers[i] = self.buffers[i][-self.bufsize:] ''' # create channel info if self._lsl_tr_channel is None: self.ch_list = ['TRIGGER'] + self.ch_list else: for i, chn in enumerate(self.ch_list): if chn == 'TRIGGER' or chn == 'TRG' or 'STI ' in chn: self.ch_list.pop(i) self.ch_list = ['TRIGGER'] + self.ch_list break logger.info('self.ch_list %s' % self.ch_list) # fill in initial buffer logger.info('Waiting to fill initial buffer of length %d' % (self.winsize)) while len(self.timestamps[0]) < self.winsize: self.acquire() time.sleep(0.1) self.ready = True logger.info('Start receiving stream data.')