def resample(timeData: TimeData, resampFreq: float, inplace: bool = True) -> TimeData: """Resample time data Parameters ---------- timeData : TimeData timeData to filter resampFreq : float The frequency to resample to inplace : bool, optional Whether to manipulate the data inplace Returns ------- TimeData Filtered time data """ origFreq = timeData.sampleFreq if not inplace: timeData = timeData.copy() timeData.data = resampleData(timeData.data, timeData.sampleFreq, resampFreq) # update the time info timeData.sampleFreq = resampFreq timeData.numSamples = timeData.data[timeData.chans[0]].size timeData.stopTime = timeData.startTime + timedelta( seconds=(1.0 / timeData.sampleFreq) * (timeData.numSamples - 1) ) timeData.addComment("Time data resampled from {:.6f} Hz to {:.6f} Hz".format(origFreq, resampFreq)) return timeData
def bandPass(timeData: TimeData, cutoffLow: float, cutoffHigh: float, inplace: bool = True) -> TimeData: """Bandpass butterworth filter for time data Parameters ---------- timeData : TimeData timeData to filter cutoff : float Cutoff frequency in Hz inplace : bool, optional Whether to manipulate the data inplace Returns ------- TimeData Filtered time data """ if not inplace: timeData = timeData.copy() timeData.data = bandPassData( timeData.data, timeData.sampleFreq, cutoffLow, cutoffHigh ) timeData.addComment( "Band pass filter applied with cutoffs {} Hz and {} Hz".format( cutoffLow, cutoffHigh ) ) return timeData
def notchFilter(timeData: TimeData, notch: float, inplace: bool = True) -> TimeData: """Bandpass butterworth filter for time data Parameters ---------- timeData : TimeData timeData to filter notch : float Frequency to notch filter in Hz inplace : bool, optional Whether to manipulate the data inplace Returns ------- TimeData Filtered time data """ if not inplace: timeData = timeData.copy() timeData.data = notchFilterData( timeData.data, timeData.sampleFreq, notch, notch / 5.0 ) timeData.addComment("Notch filter applied at {} Hz".format(notch)) return timeData
def interpolateToSecond(timeData: TimeData, inplace: bool = True) -> TimeData: """Interpolate data to be on the second Some formats of time data (e.g. SPAM) do not start on the second with their sampling. This method interpolates so that sampling starts on the second and improves interoperability with other recording formats. Parameters ---------- timeData : TimeData Time data to interpolate onto the second inplace : bool, optional Whether to do the interpolation inplace or not. Default is True. Returns ------- TimeData Time data interpolated to start on the second """ startTimeInterp, numSamplesInterp, dataInterp = interpolateToSecondData( timeData.data, timeData.sampleFreq, timeData.startTime) if not inplace: timeData = timeData.copy() timeData.numSamples = numSamplesInterp timeData.startTime = startTimeInterp # calculate end timeEnd timeData.stopTime = timeData.startTime + timedelta( seconds=(1.0 / timeData.sampleFreq) * (timeData.numSamples - 1)) timeData.data = dataInterp timeData.addComment( "Time data interpolated to nearest second. New start time {}, new end time {}, new number of samples {} " .format(timeData.startTime, timeData.stopTime, timeData.numSamples)) return timeData
def polarityReversal(timeData: TimeData, reversal: Dict[str, bool], inplace: bool = True) -> TimeData: """Multiply the data by -1 (polarity reversal) Parameters ---------- timeData : TimeData timeData to normalise reversal : Dict[str, bool] Keys are channels and values are boolean flags for reversing inplace : bool, optional Whether to manipulate the data inplace Returns ------- TimeData Normalised time data """ if not inplace: timeData = timeData.copy() timeData.data = polarityReversalData(timeData.data, reversal) timeData.addComment( "Polarity reversal with parameters: {}".format(reversal)) return timeData
def scale(timeData: TimeData, scalars: Dict[str, bool], inplace: bool = True) -> TimeData: """Scale the data by an arbitrary amount Parameters ---------- timeData : TimeData timeData to normalise scalars : Dict[str, float] Keys are channels and values are boolean flags for reversing inplace : bool, optional Whether to manipulate the data inplace Returns ------- TimeData Normalised time data """ if not inplace: timeData = timeData.copy() timeData.data = scaleData(timeData.data, scalars) timeData.addComment("Time data scaled with scalars: {}".format(scalars)) return timeData
def normalise(timeData: TimeData, inplace: bool = True) -> TimeData: """Normalise time data Parameters ---------- timeData : TimeData timeData to normalise inplace : bool, optional Whether to manipulate the data inplace Returns ------- TimeData Normalised time data """ if not inplace: timeData = timeData.copy() timeData.data = normaliseData(timeData.data) timeData.addComment("Data normalised") return timeData
def calibrate( self, timeData: TimeData, sensor: Dict[str, str], serial: Dict[str, int], chopper: Dict[str, bool], ) -> TimeData: """Calibrate time data For each channel in timeData, searches for a matching calibration file based on sensor type, serial number and chopper. If a calibration file is found, the channel is calibrated using the data in the file. If useTheoretical is False and no file is found, the data is not calibrated todo: If no calibration file is found and the channel is a magnetic data channel, a theoretical function can be used Parameters ---------- timeData : TimeData TimeData object sensor : Dict Dictionary of sensor information with channels as the key and sensor as the value (sensor is a string) serial : Dictionary of serial information with channels as the key and sensor as the value (serial is a number) chopper : Dictionary of chopper information with channels as the key and sensor as the value (chopper is a bool) Returns ------- timeData : TimeData Calibration TimeData object """ calIO = CalibrationIO() # iterate over data for chan in timeData.chans: # output some info self.printText("Calibrating channel {}".format(chan)) # try and find calibration file calFile, calFormat = self.getCalFile( sensor[chan], serial[chan], chopper[chan] ) if calFile == "": # no file found if self.useTheoretical and isMagnetic(chan): # use theoretical calData = self.getTheoreticalCalData(sensor[chan]) timeData.data[chan] = self.calibrateChan( timeData.data[chan], timeData.sampleFreq, calData ) timeData.addComment( "Channel {} calibrated with theoretical calibration function".format( chan ) ) continue else: self.printText( "No Calibration data found - channel will not be calibrated" ) timeData.addComment("Channel {} not calibrated".format(chan)) continue # nothing to do # else file found # no need to separately apply static gain, already included in cal data calIO.refresh(calFile, calFormat, chopper=chopper[chan], extend=self.extend) calData = calIO.read() self.printText( "Calibration file found for sensor {}, serial number {}, chopper {}: {}".format( sensor[chan], serial[chan], chopper[chan], calFile ) ) self.printText("Format: {}".format(calFormat)) self.printText( "Static gain correction of {} applied to calibration data".format( calData.staticGain ) ) # calibrate time data timeData.data[chan] = self.calibrateChan( timeData.data[chan], timeData.sampleFreq, calData ) timeData.addComment( "Channel {} calibrated with calibration data from file {}".format( chan, calFile ) ) # return calibrated time data return timeData