/* FFTSampleFilter.java created 2008-02-01 * */ package org.signalml.domain.montage.filter; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import org.signalml.app.config.FFTWindowTypeSettings; import org.signalml.app.config.preset.Preset; import org.signalml.math.fft.WindowType; import org.signalml.util.ResolvableString; import org.springframework.context.MessageSourceResolvable; import com.thoughtworks.xstream.annotations.XStreamAlias; /** * This class holds a representation of parameters of FFT sample filter. * Contains an array of {@link Range frequency ranges} in which samples are multiplied by a given * coefficients. * * @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o. */ @XStreamAlias("fftsamplefilter") public class FFTSampleFilter extends SampleFilterDefinition implements Preset, FFTWindowTypeSettings { private static final long serialVersionUID = 1L; private static final Object[] ARGUMENTS = new Object[0]; private static final String[] CODES = new String[] { "fftFilter" }; private static final String[] EFFECT_CODES = new String[] { "fftFilter.effect" }; /** * the name of this filter */ private String name; /** * an array of frequencies ranges with given coefficients */ private ArrayList<Range> ranges; /** * the {@link WindowType type} of a window */ private WindowType windowType = WindowType.RECTANGULAR; /** * parameter for Gaussian and Kaiser window */ private double windowParameter = 0; /** * the string with the description of filters effect */ private transient String effectString; /** * Constructor. Creates an empty filter. */ protected FFTSampleFilter() { } /** * Constructor. Creates a filter which should initially pass or block * a signal. * @param initiallyPassing true if a signal should be passed, * false otherwise */ public FFTSampleFilter(boolean initiallyPassing) { ranges = new ArrayList<Range>(); if (initiallyPassing) { ranges.add(new Range(0,0,1)); } else { ranges.add(new Range(0,0,0)); } } /** * Copy constructor. * @param filter the filter to be copied */ public FFTSampleFilter(FFTSampleFilter filter) { this(); copyFrom(filter); } /** * Returns the name of this filter. * @return the name of this filter */ @Override public String getName() { return name; } /** * Sets the name of this filter. * @param name the name to be set */ @Override public void setName(String name) { this.name = name; } /** * Returns the number of ranges. * @return the number of ranges */ public int getRangeCount() { return ranges.size(); } /** * Returns a range of a given index. * @param index index of a range * @return a range of a given index */ public Range getRangeAt(int index) { return ranges.get(index); } /** * Returns an iterator over a ranges list. * @return an iterator over a ranges list */ public Iterator<Range> getRangeIterator() { return ranges.iterator(); } /** * Removes a range of a specified index. * Adjusts other ranges to fill entire interval [0,Fn) by expanding * previous range (except removing first range, when second is expanded). * If ranges before and after has the same coefficient they are merged. * @param index index of a range to be removed */ public void removeRange(int index) { int size = ranges.size(); if (size <= 1) { return; } Range range = ranges.get(index); if (index == 0) { // first range being removed Range nextRange = ranges.get(index+1); nextRange.lowFrequency = 0; } else if (index == size - 1) { // last range being removed Range previousRange = ranges.get(index-1); previousRange.highFrequency = 0; } else { // not first, not last, and size > 1 - must be at least 3 Range previousRange = ranges.get(index-1); Range nextRange = ranges.get(index+1); previousRange.highFrequency = range.highFrequency; if (previousRange.highFrequency == nextRange.lowFrequency && previousRange.coefficient == nextRange.coefficient) { previousRange.highFrequency = nextRange.highFrequency; ranges.remove(index+1); } } ranges.remove(index); } /** * Adds a given range to ranges arrays. * It replaces all ranges that are included in a given range and * shortens ranges that intersect with given. * @param range a range to be added */ public void setRange(Range range) { setRange(range, false); } /** * Adds a given range to ranges arrays. * If multiply is not set all ranges that are included in a given range * are replaced and all that intersect with given are shortened. * If multiply is set coefficients of parts of ranges that intersect * with given are multiplied by coefficient of given range. * @param newRange a range to be added * @param multiply true if coefficients should be multiplied, false if * they should be replaced */ public void setRange(Range newRange, boolean multiply) { ArrayList<Range> newRanges = new ArrayList<Range>(); Iterator<Range> it = ranges.iterator(); Range range; double newCoefficient; while (it.hasNext()) { range = it.next(); if (range.intersects(newRange)) { if (multiply) { newCoefficient = range.coefficient * newRange.coefficient; } else { newCoefficient = newRange.coefficient; } if (range.lowFrequency < newRange.lowFrequency) { newRanges.add(new Range(range.lowFrequency, newRange.lowFrequency, range.coefficient)); } if (newRange.isOpenEnded()) { if (!multiply || range.isOpenEnded()) { newRanges.add(new Range(Math.max(newRange.lowFrequency, range.lowFrequency), 0, newCoefficient)); break; } else { newRanges.add(new Range(newRange.lowFrequency, range.highFrequency, newCoefficient)); continue; } } // new is not open ended if (range.isOpenEnded()) { newRanges.add(new Range( Math.max(range.lowFrequency, newRange.lowFrequency), newRange.highFrequency, newCoefficient )); // add the open ended rest and quit range.lowFrequency = newRange.highFrequency; range.highFrequency = 0; newRanges.add(range); break; } else { // current is also not open ended newRanges.add(new Range( Math.max(range.lowFrequency, newRange.lowFrequency), Math.min(range.highFrequency, newRange.highFrequency), newCoefficient )); // add the rest of current if any, and continue if (range.highFrequency > newRange.highFrequency) { newRanges.add(new Range(newRange.highFrequency, range.highFrequency, range.coefficient)); } } } else { newRanges.add(range); } } // now scan for adjacent ranges to merge Range lastRange = null; it = newRanges.iterator(); while (it.hasNext()) { range = it.next(); if (lastRange != null) { if (lastRange.highFrequency == range.lowFrequency && lastRange.coefficient == range.coefficient) { lastRange.highFrequency = range.highFrequency; it.remove(); } else { lastRange = range; } } else { lastRange = range; } } ranges = newRanges; Collections.sort(ranges); effectString = null; } /** * Returns the type of the window. * @return the type of the window */ @Override public WindowType getWindowType() { return windowType; } /** * Sets the type of the window. * @param windowType the type of the window to be set */ @Override public void setWindowType(WindowType windowType) { if (windowType == null) { throw new NullPointerException("No window type"); } this.windowType = windowType; } /** * Returns the parameter of a window. * @return the parameter of a window */ @Override public double getWindowParameter() { return windowParameter; } /** * Sets the parameter of a window. * @param windowParameter the parameter of a window to be set */ @Override public void setWindowParameter(double windowParameter) { this.windowParameter = windowParameter; } /** * Creates a copy of this filter. * @return a copy of this filter. */ @Override public FFTSampleFilter duplicate() { FFTSampleFilter filter = new FFTSampleFilter(); filter.copyFrom(this); return filter; } /** * Sets all parameters of this filter to values of * parameters of a given filter. * @param filter a filter which parameters are to be copied * to this filter */ public void copyFrom(FFTSampleFilter filter) { ranges = new ArrayList<Range>(); for (Range range : filter.ranges) { ranges.add(range.clone()); } name = filter.name; windowType = filter.windowType; windowParameter = filter.windowParameter; description = filter.description; effectString = null; } /** * Creates a string with a description of an effect of this filter. Consists * of a list of ranges intervals and coefficients. * * @return a string with a description of an effect of a filter. */ @Override public String getEffect() { if (effectString == null) { StringBuilder sb = new StringBuilder("FFT: ["); boolean first = true; Iterator<Range> it = ranges.iterator(); Range range; while (it.hasNext()) { if (!first) { sb.append(", "); } range = it.next(); sb.append('(').append(range.lowFrequency).append('-'); if (range.highFrequency <= range.lowFrequency) { sb.append("inf)"); } else { sb.append(range.highFrequency).append(')'); } sb.append('=').append(range.coefficient); first = false; } effectString = sb.append(']').toString(); } return effectString; } @Override public SampleFilterType getType() { return SampleFilterType.FFT; } @Override public String toString() { return name; } /** * This class represents a left-closed interval (range) of frequencies. * Allows to compare ranges (using left end of interval) and * to check whether they intersect. */ @XStreamAlias("range") public class Range implements Comparable<Range>, Cloneable, Serializable { private static final long serialVersionUID = 1L; // note: high <= low denotes "up to sf/2" or "up to inf" /** * left end of frequencies interval - inclusive */ private float lowFrequency; // inclusive /** * right end of frequencies interval - exclusive. * If <= lowFrequency then interval is not right-bounded * (or bounded by sf/2). */ private float highFrequency; // exclusive private double coefficient; /** * Creates a range without any data */ public Range() {}; /** * Creates a range of frequencies with given ends and coefficient. * @param lowFrequency the left end of frequencies interval * @param highFrequency the right end of frequencies interval * @param coefficient the coefficient */ public Range(float lowFrequency, float highFrequency, double coefficient) { this.lowFrequency = lowFrequency; this.highFrequency = highFrequency; this.coefficient = coefficient; } /** * Returns the left end of frequencies interval. * @return the left end of frequencies interval */ public float getLowFrequency() { return lowFrequency; } /** * Returns the right end of frequencies interval. * @return the right end of frequencies interval */ public float getHighFrequency() { return highFrequency; } /** * Returns the coefficient. * @return the coefficient */ public double getCoefficient() { return coefficient; } /** * Returns whether interval (range) is right-bounded. * @return false if interval is right-bounded, true otherwise */ public boolean isOpenEnded() { return(highFrequency <= lowFrequency); } /** * Returns whether this range intersects with given. * @param range range to be intersected with this range * @return true if intersection is nonempty, false otherwise */ public boolean intersects(Range range) { if (lowFrequency <= range.lowFrequency) { if (highFrequency <= lowFrequency) { return true; } else { if (range.lowFrequency < highFrequency) { return true; } } } else { if (range.highFrequency <= range.lowFrequency) { return true; } else { if (lowFrequency < range.highFrequency) { return true; } } } return false; } /** * Compares this range to given. * @param o range to be compared with this range * @return difference between left ends of interval * (current - given) */ @Override public int compareTo(Range o) { return (int)(this.lowFrequency - o.lowFrequency); } /** * Creates a copy of this range. * @return a copy of this range */ @Override protected Range clone() { return new Range(lowFrequency, highFrequency, coefficient); } } /** * Checks if the object o is an instance of FFTSampleFilter and if so * checks if ranges (and appriopriate coefficients) and window types are * equal. * @param o object to compare with the FFTSampleFilter * @return true if the Object o is an FFTSampleFilter with equal ranges * and window types as this FFTSamplesFilter, false otherwise. */ @Override public boolean equals(Object o) { if (!(o instanceof FFTSampleFilter)) return false; FFTSampleFilter fft = (FFTSampleFilter)o; Iterator<Range> it1, it2; Range r1, r2; it1 = fft.getRangeIterator(); it2 = getRangeIterator(); while (it1.hasNext() && it2.hasNext()) { r1 = it1.next(); r2 = it2.next(); if (r1.compareTo(r2) != 0) return false; } if (it1.hasNext() || it2.hasNext()) return false; if (!this.windowType.equals(windowType) || this.windowParameter!=fft.windowParameter) return false; return true; } }