package edu.colostate.vchill.plot; import edu.colostate.vchill.Config; import edu.colostate.vchill.EstimateParser; import edu.colostate.vchill.ScaleManager; import edu.colostate.vchill.chill.ChillMomentFieldScale; import edu.colostate.vchill.data.Ray; /** * Applies filters to data values for plotting. * Usually, one ViewPlotDataFilter object is created per plot reuqest * (ie. per ray of a data type). * * @author Jochen Deyke * @author jpont * @version 2009-03-19 */ public class ViewPlotDataFilter { protected final static Config config = Config.getInstance(); protected final static ScaleManager sm = ScaleManager.getInstance(); private Ray prevRay; private Ray currRay; private Ray nextRay; private Ray threshRay; private double[] prevData; private double[] currData; private double[] nextData; private double[] threshData; private ChillMomentFieldScale scale; /* The gate number that the unfolding algorithm should start at. */ private int unfoldStartGate; /* The last good velocity estimate. */ private double lastGoodEstimate; /* The distance from the last good estimate. */ private int distFromLastGoodEstimate; /** * Creates a new instance of ViewPlotDataFilter */ public ViewPlotDataFilter(final Ray prevRay, final Ray currRay, final Ray nextRay, final Ray threshRay, final String type) { this.prevRay = prevRay; this.currRay = currRay; this.nextRay = nextRay; this.threshRay = threshRay; prevData = prevRay != null ? prevRay.getData() : null; currData = currRay != null ? currRay.getData() : null; nextData = nextRay != null ? nextRay.getData() : null; threshData = threshRay != null ? threshRay.getData() : null; this.scale = sm.getScale(type); /* Determine the gate number that the unfolding algorithm should start at. */ unfoldStartGate = (int) (config.getUnfoldStartRange() / currRay.getGateWidth()); lastGoodEstimate = Double.NaN; distFromLastGoodEstimate = 0; } public double applyFilters(final double i, final double plotStepSize, final double elevation, final double prevValue) { if (currData == null || this.scale == null) return Double.NaN; //no data int k = (int) i; double currValue = currData[k]; //current data value if (Double.isNaN(currValue)) { distFromLastGoodEstimate++; return Double.NaN; } //missing data if (this.checkThresholdNotOK(k)) return Double.NaN; //threshold filter if (config.isNoiseReductionEnabled()) { //noise filter if (config.isThresholdEnabled()) { if (this.checkThresholdNotOK((int) (i - plotStepSize)) && this.checkThresholdNotOK((int) (i + plotStepSize))) { return Double.NaN; //if both surrounding pixels are bad, so is this one } } else { int prevIndex = (int) (i - plotStepSize); int nextIndex = (int) (i + plotStepSize); if ((prevIndex < 0 || Double.isNaN(this.currData[prevIndex])) && (nextIndex >= currData.length || Double.isNaN(this.currData[nextIndex]))) { return Double.NaN; //if both surrounding pixels are bad, so is this one } } } if (config.isUnfoldingEnabled() && scale.isUnfoldable() && k >= unfoldStartGate) { //velocity unfolding double vEst = config.isUnfoldingAutomatic() ? prevValue : EstimateParser.getInstance().getVEstimate(elevation); if (!Double.isNaN(vEst)) { lastGoodEstimate = vEst; distFromLastGoodEstimate = 0; } else { if (distFromLastGoodEstimate < 5) { //don't let small groupings of NaNs affect unfolding vEst = lastGoodEstimate; } else { distFromLastGoodEstimate++; return currValue / 2; } } return this.getUnfoldedValue(currValue, vEst, currRay.getVelocityRange()); } else if (scale.isGradientable() && config.isGradientEnabled()) { //Z as gradient return this.getGradientValue(k); } if (config.isSmoothingEnabled()) return this.getSmoothedValue(k); return currValue; } /** * Checks the threshold value at a given index. * * @param index desired index * @return true if the data is missing or bad, false if it is OK */ public boolean checkThresholdNotOK(final int index) { if (config.isThresholdEnabled() && threshData != null) { if (-1 < index && index < threshData.length) { double threshVal = threshData[index]; if (Double.isNaN(threshVal)) return true; //"missing" threshold value -> bad if (config.isThresholdAbsoluteValue()) threshVal = Math.abs(threshVal); double cutoff = config.getThresholdFilterCutoff(); if (config.isThresholdGreaterThan()) return threshVal > cutoff; return threshVal < cutoff; } } return false; } /** * @param index index of current gate * @return The smoothed value */ public double getSmoothedValue(final int index) { double translatedValue = Double.NaN; if (currData == null) throw new IllegalArgumentException("Null for curr"); if (prevData != null && prevData.length != currData.length) prevData = null; //different length implies different sweep if (nextData != null && nextData.length != currData.length) nextData = null; //different length implies different sweep double maxJump = (scale.getMax() - scale.getMin()) / 12.0; //the maximum difference between current and surroundings to still smooth //Check to see if the gates all exist. Will not at the beginning and end //rays. double sum = 0; int numpoints = 0; for (int i = -1; i < 2; ++i) { int ii = index + i; if (ii < 0 || ii >= currData.length) continue; //index out of range //the < test returns false for NaN, so no extra check is needed if (prevData != null && Math.abs(currData[index] - prevData[ii]) < maxJump) { //gate valid sum += prevData[ii]; ++numpoints; } if (Math.abs(currData[index] - currData[ii]) < maxJump) { //gate valid sum += currData[ii]; ++numpoints; } if (nextData != null && Math.abs(currData[index] - nextData[ii]) < maxJump) { //gate valid sum += nextData[ii]; ++numpoints; } } if (numpoints > 0) translatedValue = sum / numpoints; return translatedValue; } protected double getUnfoldedValue(double currValue, final double vEst, final double nyquist) { if (Double.isNaN(vEst)) return currValue / 2; /* Make sure the signs are different, otherwise the velocity isn't folded. */ if (Math.signum(currValue) == 1.0 && Math.signum(vEst) != -1.0) return currValue / 2; else if (Math.signum(currValue) == -1.0 && Math.signum(vEst) != 1.0) return currValue / 2; double foldThreshold = nyquist * (config.isUnfoldingAutomatic() ? 0.9 : 0.5); /* Double the vEst because it's halfed and the current value isn't. */ if (Math.abs(currValue - (2 * vEst)) > foldThreshold) { currValue += vEst > 0 ? (2 * nyquist) : -(2 * nyquist); } return currValue / 2; } protected double getGradientValue(final int index) { switch (config.getGradientType()) { case Azimuth: return this.getAzimuthGradientValue(index); case Range: return this.getRangeGradientValue(index); default: return currData[index]; } } protected double getRangeGradientValue(final int index) { if (index - 1 < 0 || index + 1 >= currData.length) return Double.NaN; //goes off edge; can't calculate if (this.checkThresholdNotOK(index - 1)) return Double.NaN; //bad data; can't calculate if (this.checkThresholdNotOK(index + 1)) return Double.NaN; //bad data; can't calculate return (currData[index + 1] - currData[index - 1]) / (2 * currRay.getGateWidth()); } protected double getAzimuthGradientValue(final int index) { if (prevRay == null || nextRay == null) return Double.NaN; //goes off edge; can't calculate return (prevRay.getStartAzimuth() > nextRay.getStartAzimuth() ? (prevRay.getData()[index] - nextRay.getData()[index]) : (nextRay.getData()[index] - prevRay.getData()[index]) ) / (currRay.getGateWidth() * index * Math.abs(Math.toRadians(nextRay.getStartAzimuth() - prevRay.getStartAzimuth()))); } }