def filter(self): """Apply the constructed filter. Returns ------- A TimeSeries object. """ channel_axis = self.timeseries[self.channels_dim] ch0 = self.bipolar_pairs[self.chan_names[0]] ch1 = self.bipolar_pairs[self.chan_names[1]] sel0 = channel_axis.loc[ch0] sel1 = channel_axis.loc[ch1] ts0 = self.timeseries.loc[{self.channels_dim: sel0}] ts1 = self.timeseries.loc[{self.channels_dim: sel1}] dims_bp = list(self.timeseries.dims) coords_bp = { coord_name: coord for coord_name, coord in list(self.timeseries.coords.items()) } coords_bp[self.channels_dim] = self.bipolar_pairs ts = TimeSeries(data=ts0.values - ts1.values, dims=dims_bp, coords=coords_bp) ts['samplerate'] = self.timeseries['samplerate'] ts.attrs = self.timeseries.attrs.copy() ts.name = self.timeseries.name return ts
def filter(self): """ Applies Butterwoth filter to input time series and returns filtered TimeSeriesX object Returns ------- filtered: TimeSeries The filtered time series """ time_axis_index = get_axis_index(self.timeseries, axis_name='time') filtered_array = buttfilt(self.timeseries, self.freq_range, float(self.timeseries['samplerate']), self.filt_type, self.order, axis=time_axis_index) coords_dict = { coord_name: DataArray(coord.copy()) for coord_name, coord in list(self.timeseries.coords.items()) } coords_dict['samplerate'] = self.timeseries['samplerate'] dims = [dim_name for dim_name in self.timeseries.dims] filtered_timeseries = TimeSeries(filtered_array, dims=dims, coords=coords_dict) # filtered_timeseries = TimeSeries(filtered_timeseries) filtered_timeseries.attrs = self.timeseries.attrs.copy() return filtered_timeseries
def read_session_data(self): """ Reads entire session worth of data :return: TimeSeries object (channels x events x time) with data for entire session the events dimension has length 1 """ brr = self.READER_FILETYPE_DICT[os.path.splitext(self.session_dataroot)[-1]](dataroot=self.session_dataroot, channels=self.channels) session_array,read_ok_mask = brr.read() self.channel_name = brr.channel_name offsets_axis = session_array['offsets'] number_of_time_points = offsets_axis.shape[0] samplerate = float(session_array['samplerate']) physical_time_array = np.arange(number_of_time_points) * (1.0 / samplerate) # session_array = session_array.rename({'start_offsets': 'events'}) session_time_series = TimeSeries(session_array.values, dims=[self.channel_name, 'start_offsets', 'time'], coords={ self.channel_name: session_array[self.channel_name], 'start_offsets': session_array['start_offsets'], 'time': physical_time_array, 'offsets': ('time', session_array['offsets']), 'samplerate': session_array['samplerate'] } ) session_time_series.attrs = session_array.attrs.copy() session_time_series.attrs['dataroot'] = self.session_dataroot return session_time_series
def test_init(): """Test that everything is initialized properly.""" data = np.random.random((10, 10, 10)) rate = 1000 with pytest.raises(AssertionError): TimeSeries(data, {}) with pytest.raises(AssertionError): TimeSeries.create(data, None, coords={}) assert TimeSeries.create(data, None, coords={ 'samplerate': 1 }).samplerate == 1 ts = TimeSeries(data, dict(samplerate=rate)) assert isinstance(ts, xr.DataArray) assert ts.shape == (10, 10, 10) assert ts['samplerate'] == rate
def hilbert_pow(dat_ts, bands=None, pad_to_pow2=False, verbose=True): """ """ # set default freq bands if bands is None: bands = freq_bands # proc padding taxis = dat_ts.get_axis(dat_ts.tdim) npts_orig = dat_ts.shape[taxis] if pad_to_pow2: npts = 2**next_pow2(npts_orig) else: npts = npts_orig # calc the hilbert power if verbose: sys.stdout.write('Hilbert Bands: ') sys.stdout.flush() pow = None for band in bands: if verbose: sys.stdout.write('%s ' % band[0]) sys.stdout.flush() p = TimeSeries(np.abs( hilbert(dat_ts.filtered(band[1], filt_type='pass'), N=npts, axis=taxis).take(np.arange(npts_orig), axis=taxis)), tdim=dat_ts.tdim, samplerate=dat_ts.samplerate, dims=dat_ts.dims.copy()).add_dim(Dim([band[0]], 'freqs')) if pow is None: pow = p else: pow = pow.extend(p, 'freqs') if verbose: sys.stdout.write('\n') sys.stdout.flush() return pow
def test_wavelets_synthetic_data(self): samplerate = 1000. frequency = 180.0 modulation_frequency = 80.0 duration = 1.0 n_points = int(np.round(duration * samplerate)) x = np.arange(n_points, dtype=np.float) y = np.sin(x * (2 * np.pi * frequency / n_points)) y_mod = np.sin(x * (2 * np.pi * frequency / n_points)) * np.sin( x * (2 * np.pi * modulation_frequency / n_points)) ts = TimeSeries(y, dims=['time'], coords=[x]) ts['samplerate'] = samplerate ts.attrs['samplerate'] = samplerate frequencies = [10.0, 30.0, 50.0, 80., 120., 180., 250.0, 300.0, 500.] for frequency in frequencies: wf = MorletWaveletFilter(timeseries=ts, freqs=np.array([frequency]), output='both', frequency_dim_pos=0, verbose=True) pow_wavelet, phase_wavelet = wf.filter() from ptsa.wavelet import phase_pow_multi pow_wavelet_ptsa_orig = phase_pow_multi(freqs=[frequency], samplerates=samplerate, dat=ts.data, to_return='power') assert_array_almost_equal( (pow_wavelet_ptsa_orig - pow_wavelet) / pow_wavelet_ptsa_orig, np.zeros_like(pow_wavelet), decimal=6)
def compute_power(events, freqs, wave_num, rel_start_ms, rel_stop_ms, buf_ms=1000, elec_scheme=None, noise_freq=[58., 62.], resample_freq=None, mean_over_time=True, log_power=True, loop_over_chans=True, cluster_pool=None, use_mirror_buf=False, time_bins=None, do_average_ref=False): """ Returns a TimeSeries object of power values with dimensions 'events' x 'frequency' x 'bipolar_pairs/channels' x 'time', unless mean_over_time is True, then no 'time' dimenstion. Parameters ---------- events: pandas.DataFrame An events structure that contains eegoffset and eegfile fields freqs: np.array or list A set of frequencies at which to compute power using morlet wavelet transform wave_num: int Width of the wavelet in cycles (I THINK) rel_start_ms: int Initial time (in ms), relative to the onset of each event rel_stop_ms: int End time (in ms), relative to the onset of each event buf_ms: Amount of time (in ms) of buffer to add to both the begining and end of the time interval before power computation. This buffer is automatically removed before returning the power timeseries. elec_scheme: pandas.DataFrame: A dataframe of electrode information, returned by load_elec_info(). If the column 'contact' is in the dataframe, monopolar electrodes will be loads. If the columns 'contact_1' and 'contact_2' are in the df, bipolar will be loaded. You may pass in a subset of rows to only load data for electrodes in those rows. If you do not enter an elec_scheme, all monopolar channels will be loaded (but they will not be labeled with correct channel tags). Entering a scheme is recommended. noise_freq: list Stop filter will be applied to the given range. Default=(58. 62) resample_freq: float Sampling rate to resample to after loading eeg but BEFORE computing power. So be careful. Don't downsample below your nyquist. mean_over_time: bool Whether to mean power over time, and return the power data with no time dimension log_power: bool Whether to log the power values loop_over_chans: bool Whether to process each channel independently, or whether to try to do all channels at once. Default is to loop cluster_pool: None or ipython cluster helper pool If given, will parallelize over channels use_mirror_buf: bool If True, a mirror buffer will be (used see load_eeg) instead of a normal buffer time_bins: list or array pairs of start and stop times in which to bin the data do_average_ref: bool If true, will load eeg and then compute an average reference before computing power. Note: This will load eeg for all channels at once, regardless of loop_over_chans or cluster_pool. Will still loop for power computation. Returns ------- timeseries object of power values """ # warn people if they set the resample_freq too low if (resample_freq is not None) and (resample_freq < (np.max(freqs) * 2.)): print('Resampling EEG below nyquist frequency.') warnings.warn('Resampling EEG below nyquist frequency.') # make freqs a numpy array if it isn't already because PTSA is kind of stupid and can't handle a list of numbers if isinstance(freqs, list): freqs = np.array(freqs) # if doing an average reference, load eeg first if do_average_ref: eeg_all_chans = load_eeg(events, rel_start_ms, rel_stop_ms, buf_ms=buf_ms, elec_scheme=elec_scheme, noise_freq=noise_freq, resample_freq=resample_freq, use_mirror_buf=use_mirror_buf, do_average_ref=do_average_ref) else: eeg_all_chans = None # We will loop over channels if desired or if we are are using a pool to parallelize if cluster_pool or loop_over_chans: # must enter an elec scheme if we want to loop over channels if elec_scheme is None: print( 'elec_scheme must be entered if loop_over_chans is True or using a cluster pool.' ) return # put all the inputs into one list. This is so because it is easier to parallize this way. Parallel functions # accept one input. The pool iterates over this list. arg_list = [ (events, freqs, wave_num, elec_scheme.iloc[r:r + 1], rel_start_ms, rel_stop_ms, buf_ms, noise_freq, resample_freq, mean_over_time, log_power, use_mirror_buf, time_bins, eeg_all_chans[:, r:r + 1] if eeg_all_chans is not None else None) for r in range(elec_scheme.shape[0]) ] # if no pool, just use regular map if cluster_pool is not None: pow_list = cluster_pool.map(_parallel_compute_power, arg_list) else: pow_list = list( map( _parallel_compute_power, tqdm(arg_list, disable=True if len(arg_list) == 1 else False))) # This is the stupidest thing in the world. I should just be able to do concat(pow_list, dim='channels') or # concat(pow_list, dim='bipolar_pairs'), but for some reason it breaks. I don't know. So I'm creating a new # TimeSeries object # concatenate data chan_dim = pow_list[0].get_axis_num('channel') elecs = np.concatenate([x[x.dims[chan_dim]].data for x in pow_list]) pow_cat = np.concatenate([x.data for x in pow_list], axis=chan_dim) # create new coordinates and Timeseries with concatenated data and electrode info new_coords = { x: (pow_list[0].coords[x] if x != 'channel' else elecs) for x in pow_list[0].coords.keys() } wave_pow = TimeSeries(data=pow_cat, coords=new_coords, dims=pow_list[0].dims) # if not looping, sending all the channels at once else: arg_list = [ events, freqs, wave_num, elec_scheme, rel_start_ms, rel_stop_ms, buf_ms, noise_freq, resample_freq, mean_over_time, log_power, use_mirror_buf, time_bins, eeg_all_chans ] wave_pow = _parallel_compute_power(arg_list) # reorder dims to make events first wave_pow = make_events_first_dim(wave_pow) return wave_pow
def filter(self): """resamples time series Returns ------- resampled: TimeSeries resampled time series with sampling frequency set to resamplerate """ samplerate = float(self.timeseries['samplerate']) self.time_axis_index = self.timeseries.get_axis_num( self.time_axis_name) time_axis = self.timeseries.coords[self.timeseries.dims[ self.time_axis_index]] time_axis_length = len(time_axis) new_length = int( np.round(time_axis_length * self.resamplerate / samplerate)) try: # time axis can be recarray with one of the arrays being time time_axis_data = time_axis.data[self.time_axis_name] except (KeyError, IndexError) as excp: # if we get here then most likely time axis is ndarray of floats time_axis_data = time_axis.data time_idx_array = np.arange(len(time_axis)) if self.round_to_original_timepoints: filtered_array, new_time_idx_array = resample( self.timeseries.data, new_length, t=time_idx_array, axis=self.time_axis_index) # print new_time_axis new_time_idx_array = np.rint(new_time_idx_array).astype(np.int) new_time_axis = time_axis[new_time_idx_array] else: filtered_array, new_time_axis = \ resample(self.timeseries.data, new_length, t=time_axis_data, axis=self.time_axis_index) coords = {} for i, dim_name in enumerate(self.timeseries.dims): if i != self.time_axis_index: coords[dim_name] = self.timeseries.coords[dim_name].copy() else: coords[dim_name] = new_time_axis coords['samplerate'] = self.resamplerate filtered_timeseries = TimeSeries(filtered_array, coords=coords, dims=self.timeseries.dims) return filtered_timeseries
def filter(self): """Apply the constructed filter.""" time_axis = self.timeseries['time'] wavelet_dims = self.nontime_sizes + (self.freqs.shape[0], ) powers_reshaped = np.array([[]], dtype=np.float) phases_reshaped = np.array([[]], dtype=np.float) wavelets_complex_reshaped = np.array([[]], dtype=np.complex) if 'power' in self.output: powers_reshaped = np.empty(shape=(np.prod(wavelet_dims), len(self.timeseries['time'])), dtype=np.float) if 'phase' in self.output: phases_reshaped = np.empty(shape=(np.prod(wavelet_dims), len(self.timeseries['time'])), dtype=np.float) if 'complex' in self.output: wavelets_complex_reshaped = np.empty( shape=(np.prod(wavelet_dims), len(self.timeseries['time'])), dtype=np.complex) mt = morlet.MorletWaveletTransformMP(self.cpus) timeseries_reshaped = np.ascontiguousarray( self.timeseries.data.reshape( np.prod(self.nontime_sizes, dtype=int), len(self.timeseries['time'])), self.timeseries.data.dtype) if self.output == ['power']: mt.set_output_type(morlet.POWER) if self.output == ['phase']: mt.set_output_type(morlet.PHASE) if 'power' in self.output and 'phase' in self.output: mt.set_output_type(morlet.BOTH) # TODO: update to allow outputing complex as well as power/phase if self.output == ['complex']: mt.set_output_type(morlet.COMPLEX) mt.set_signal_array(timeseries_reshaped) mt.set_wavelet_pow_array(powers_reshaped) mt.set_wavelet_phase_array(phases_reshaped) mt.set_wavelet_complex_array(wavelets_complex_reshaped) mt.initialize_signal_props(float(self.timeseries['samplerate'])) mt.initialize_wavelet_props(self.width, self.freqs) mt.prepare_run() s = time.time() mt.compute_wavelets_threads() powers_final = None phases_final = None wavelet_complex_final = None if 'power' in self.output: powers_final = powers_reshaped.reshape( wavelet_dims + (len(self.timeseries['time']), )) if 'phase' in self.output: phases_final = phases_reshaped.reshape( wavelet_dims + (len(self.timeseries['time']), )) if 'complex' in self.output: wavelet_complex_final = wavelets_complex_reshaped.reshape( wavelet_dims + (len(self.timeseries['time']), )) coords = {k: v for k, v in list(self.timeseries.coords.items())} coords['frequency'] = self.freqs powers_ts = None phases_ts = None wavelet_complex_ts = None if powers_final is not None: powers_ts = TimeSeries(powers_final, dims=self.nontime_dims + ('frequency', 'time'), coords=coords) final_dims = (powers_ts.dims[-2], ) + powers_ts.dims[:-2] + ( powers_ts.dims[-1], ) powers_ts = powers_ts.transpose(*final_dims) if phases_final is not None: phases_ts = TimeSeries(phases_final, dims=self.nontime_dims + ('frequency', 'time'), coords=coords) final_dims = (phases_ts.dims[-2], ) + phases_ts.dims[:-2] + ( phases_ts.dims[-1], ) phases_ts = phases_ts.transpose(*final_dims) if wavelet_complex_final is not None: wavelet_complex_ts = TimeSeries(wavelet_complex_final, dims=self.nontime_dims + ( 'frequency', 'time', ), coords=coords) final_dims = (wavelet_complex_ts.dims[-2], ) + wavelet_complex_ts.dims[:-2] + ( wavelet_complex_ts.dims[-1], ) wavelet_complex_ts = wavelet_complex_ts.transpose(*final_dims) if self.verbose: print('CPP total time wavelet loop: ', time.time() - s) if wavelet_complex_ts is not None: return wavelet_complex_ts else: if powers_ts is None: return phases_ts elif phases_ts is None: return powers_ts else: return powers_ts.append(phases_ts, dim=self.output_dim).assign_coords( output=['power', 'phase'])
def read_events_data(self): """ Reads eeg data for individual event :return: TimeSeries object (channels x events x time) with data for individual events """ self.event_ok_mask_sorted = None # reset self.event_ok_mask_sorted evs = self.events raw_readers, original_dataroots = self.__create_base_raw_readers() # used for restoring original order of the events ordered_indices = np.arange(len(evs)) event_indices_list = [] events = [] ts_array_list = [] event_ok_mask_list = [] for s, (raw_reader, dataroot) in enumerate(zip(raw_readers, original_dataroots)): ts_array, read_ok_mask = raw_reader.read() event_ok_mask_list.append(np.all(read_ok_mask,axis=0)) ind = np.atleast_1d(evs.eegfile == dataroot) event_indices_list.append(ordered_indices[ind]) events.append(evs[ind]) ts_array_list.append(ts_array) if not all([r.channel_name==raw_readers[0].channel_name for r in raw_readers]): raise IncompatibleDataError('cannot read monopolar and bipolar data together') self.channel_name = raw_readers[0].channel_name event_indices_array = np.hstack(event_indices_list) event_indices_restore_sort_order_array = event_indices_array.argsort() eventdata = xr.concat(ts_array_list, dim='start_offsets') samplerate = float(eventdata['samplerate']) tdim = np.arange(eventdata.shape[-1]) * (1.0 / samplerate) + (self.start_time - self.buffer_time) cdim = eventdata[self.channel_name] edim = np.rec.array(np.concatenate(events)) attrs = eventdata.attrs.copy() eventdata = TimeSeries(eventdata.data, dims=[self.channel_name, 'events', 'time'], coords={self.channel_name: cdim, 'events': edim, 'time': tdim, 'samplerate': samplerate } ) eventdata.attrs = attrs # restoring original order of the events eventdata = eventdata[:, event_indices_restore_sort_order_array, :] event_ok_mask = np.hstack(event_ok_mask_list) event_ok_mask_sorted = event_ok_mask[event_indices_restore_sort_order_array] # removing bad events if self.remove_bad_events: if np.any(~event_ok_mask_sorted): warnings.warn("Found some bad events. Removing!", UserWarning) self.removed_corrupt_events = True self.event_ok_mask_sorted = event_ok_mask_sorted eventdata = eventdata[:, event_ok_mask_sorted, :] return eventdata