package org.signalml.domain.signal.filter.iir;
import org.apache.log4j.Logger;
import org.signalml.domain.montage.filter.TimeDomainSampleFilter;
import org.signalml.domain.signal.samplesource.RoundBufferSampleSource;
import org.signalml.domain.signal.samplesource.SampleSource;
import org.signalml.math.ArrayOperations;
import org.signalml.math.iirdesigner.FilterCoefficients;
import org.signalml.math.iirdesigner.InitialStateCalculator;
/**
* This class represents a Time Domain (IIR or FIR) engine for filtering the samples.
* Use this for offline signals.
*
* @author Piotr Szachewicz
*/
public class OfflineIIRSinglechannelSampleFilter extends AbstractIIRSinglechannelSampleFilter {
protected static final Logger logger = Logger.getLogger(OfflineIIRSinglechannelSampleFilter.class);
/**
* The signal offset of the first sample stored in the {@link AbstractIIRSinglechannelSampleFilter#filtered}
* sample source. Allows to control the caching of the filtered data.
*/
private Integer filteredSignalOffset = null;
/**
* True if filtfilt filtering should be used while
* filtering offline data.
*/
protected boolean useFiltFilt = false;
public OfflineIIRSinglechannelSampleFilter(SampleSource source, TimeDomainSampleFilter definition) {
super(source, definition);
}
public OfflineIIRSinglechannelSampleFilter(SampleSource source, TimeDomainSampleFilter definition, FilterCoefficients coefficients) {
super(source, definition, coefficients);
}
public OfflineIIRSinglechannelSampleFilter(SampleSource source, FilterCoefficients coefficients) {
super(source, coefficients);
}
/**
* Returns the given number of the filtered samples starting from
* the given position in time. {@link OfflineIIRSinglechannelSampleFilter#updateCache(int)}
* must be run before running this method if new samples were added or
* cache was never updated before.
*
* @param target the array to which results will be written starting
* from position <code>arrayOffset</code>
* @param signalOffset the position (in time) in the signal starting
* from which samples will be returned
* @param count the number of samples to be returned
* @param arrayOffset the offset in <code>target</code> array starting
* from which samples will be written
*/
@Override
public synchronized void getSamples(double[] target, int signalOffset, int count, int arrayOffset) {
if (!isCached(signalOffset, count)) {
filterOffline(signalOffset, count);
filteredSignalOffset = signalOffset;
filtered.getSamples(target, 0, count, arrayOffset);
} else {
filtered.getSamples(target, signalOffset - filteredSignalOffset, count, arrayOffset);
}
}
/**
* Checks whether the signal selection in question was already calculated previously
* and is available in the filtered samples cache.
* @param signalOffset the position (in time) in the signal starting
* from which samples will be returned
* @param count the number of samples to be returned
* @return true if the samples are available in cache, false otherwise
*/
protected boolean isCached(int signalOffset, int count) {
if (filteredSignalOffset != null
&& filteredSignalOffset <= signalOffset
&& filteredSignalOffset + filtered.getSampleCount() >= signalOffset + count) {
return true;
} else {
return false;
}
}
/**
* Filters the data from the source SampleSource. The result of filtering
* is put in the filtered RoundSampleSource.
* @param signalOffset the position (in time) in the signal starting
* from which samples will be returned
* @param count the number of filtered samples to be returned
*/
protected synchronized void filterOffline(int signalOffset, int count) {
//filter signal from prefixCount samples before requested starting point (IIR filters are unstable at the beginning)
int leftPrefixCount = 2048; //1024*2 - 2secs for 1024 samplingFreq
if (signalOffset - leftPrefixCount < 0) //fix prefix if some starting area of the signal is requested
leftPrefixCount = signalOffset;
//right prefix - it is useful only if the signal is filered using filtFilt.
int rightPrefixCount = useFiltFilt ? 2048 : 0;
if (signalOffset + count + rightPrefixCount > source.getSampleCount()) {
rightPrefixCount = source.getSampleCount() - signalOffset - count;
}
int filteredCount = leftPrefixCount + count + rightPrefixCount;//samples to filter
//get data from source and filter it
double[] input = new double[filteredCount];
source.getSamples(input, signalOffset - leftPrefixCount, filteredCount, 0);
double[] newFilteredSamples = calculateInitialConditionsAndFilter(input);
//get last filteredCount - prefixCount = count samples and return it
filtered = new RoundBufferSampleSource(count);
for (int i = 0; i < count; i++) {
filtered.addSample(newFilteredSamples[i + leftPrefixCount]);
}
}
/**
* Calculates initial conditions of the filter delays and filters the data
* assuming the calculated initial conditions.
* @param signal the signal to be filtered
* @return the filtered signal
*/
private double[] calculateInitialConditionsAndFilter(double[] signal) {
InitialStateCalculator initalStateCalculator = new InitialStateCalculator(new FilterCoefficients(bCoefficients, aCoefficients));
double[] initialState = initalStateCalculator.getInitialState();
double[] grownSignal = initalStateCalculator.growSignal(signal);
double[] filteredSamples;
//right-wise
double[] initialStateRightwise = new double[initialState.length];
for (int i = 0; i < initialStateRightwise.length; i++) {
initialStateRightwise[i] = initialState[i] * grownSignal[0];
}
filteredSamples = filter(bCoefficients, aCoefficients, grownSignal, initialStateRightwise);
if (useFiltFilt) {
filteredSamples = ArrayOperations.reverse(filteredSamples);
//left-wise
double[] initialStateLeftwise = new double[initialState.length];
for (int i = 0; i < initialStateLeftwise.length; i++) {
initialStateLeftwise[i] = initialState[i] * filteredSamples[0];
}
filteredSamples = filter(bCoefficients, aCoefficients, filteredSamples, initialStateLeftwise);
filteredSamples = ArrayOperations.reverse(filteredSamples);
}
//shorten
int padding = (grownSignal.length - signal.length) / 2;
double[] result = new double[signal.length];
System.arraycopy(filteredSamples, padding, result, 0, signal.length);
return result;
}
/**
* Sets whether this engine should use filtfilt (forward-backward) filtering
* algorithm.
* @param filtfiltEnabled true if this engine should use filtfilt.
*/
public void setFiltfiltEnabled(boolean filtfiltEnabled) {
this.useFiltFilt = filtfiltEnabled;
}
}