/* * This file is part of Alida, a Java library for * Advanced Library for Integrated Development of Data Analysis Applications. * * Copyright (C) 2010 - @YEAR@ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Fore more information on Alida, visit * * http://www.informatik.uni-halle.de/alida/ * */ /* * Most recent change(s): * * $Rev$ * $Date$ * $Author$ * */ package de.unihalle.informatik.Alida.demo; import java.util.ArrayList; import java.util.Collections; import de.unihalle.informatik.Alida.exceptions.ALDOperatorException; import de.unihalle.informatik.Alida.exceptions.ALDOperatorException.OperatorExceptionType; import de.unihalle.informatik.Alida.operator.ALDOperator; import de.unihalle.informatik.Alida.operator.events.ALDOperatorExecutionProgressEvent; import de.unihalle.informatik.Alida.annotations.Parameter; import de.unihalle.informatik.Alida.annotations.ALDAOperator; import de.unihalle.informatik.Alida.annotations.ALDDerivedClass; import de.unihalle.informatik.Alida.annotations.Parameter.ParameterModificationMode; /** * Operator to smooth the data of an {@link ExperimentalData1D}. * * @author posch */ @ALDDerivedClass @ALDAOperator(genericExecutionMode=ALDAOperator.ExecutionMode.ALL, level=ALDAOperator.Level.APPLICATION) public class SmoothData1D extends ALDOperator { public enum SmoothingMethod { MEDIAN, MEAN, GAUSSIAN } /** 1D Experiment */ @Parameter( label= "1D Experiment", required = true, direction = Parameter.Direction.IN, description = "1D Experiment", dataIOOrder = 1) protected ExperimentalData1D experiment; /** Smoothing method */ @Parameter( label = "Smoothing method", required = true, direction = Parameter.Direction.IN, callback = "smoothingMethodChanged", description = "Smoothing method", paramModificationMode = ParameterModificationMode.MODIFIES_INTERFACE, dataIOOrder = 2) SmoothingMethod smoothingMethod = SmoothingMethod.MEDIAN; /** Window width */ @Parameter( label = "Window width", required = true, direction = Parameter.Direction.IN, description = "Window width (should be uneven)", dataIOOrder = 3) Integer width = 3; /** Standard deviation of Gaussian */ @Parameter( label = "Standdard deviation of Gaussian", required = true, direction = Parameter.Direction.IN, description = "Standdard deviation of Gaussian", dataIOOrder = 3) Float sigma = 1.0F; /** Smoothed 1D Experiment */ @Parameter( label= "Smothed 1D Experiment", direction = Parameter.Direction.OUT, description = "Smothed1D Experiment", dataIOOrder = 1) protected ExperimentalData1D smoothedExperiment; /** * Default constructor. * @throws ALDOperatorException */ public SmoothData1D() throws ALDOperatorException { // necessary handle dynamic parameters correctly smoothingMethodChanged(); } @Override protected void operate() { this.fireOperatorExecutionProgressEvent( new ALDOperatorExecutionProgressEvent(this, "Starting to smooth 1D Data...")); Double[] smoothedData; if ( smoothingMethod == SmoothingMethod.MEDIAN) { smoothedData = median( experiment.getData(), this.width); } else { smoothedData = smoothByConvolution(); } smoothedExperiment = new ExperimentalData1D( experiment.getDescription() + " (smoothed)", smoothedData, experiment.isBaselineCorrected(), experiment.getTimeResolution()); } /** Compute the median of {@code data} within a window of width {@code width}. Values outside the range * of {@code data} are ignored. * <p> * Ties and windows with even width are handled simplistic. * * @param data * @param width * @return */ private Double[] median(Double[] data, Integer width) { Double[] smoothedData = new Double[ data.length]; ArrayList<Double> window = new ArrayList<Double>(width); int lowerHalfWidth = (int) Math.floor( width/2.0); int upperHalfWidth = width-1-lowerHalfWidth; for ( int i = 0 ; i < lowerHalfWidth ; i++ ) { window.clear(); for ( int l = 0 ; l <= i + upperHalfWidth ; l++) { // if window exceeds end of data, ignore position if (l >= data.length) continue; window.add( data[l]); } Collections.sort(window); smoothedData[i] = window.get( window.size()/2); } for ( int i = lowerHalfWidth ; i < data.length - upperHalfWidth ; i++) { window.clear(); for ( int l = i - lowerHalfWidth ; l <= i + upperHalfWidth ; l++) { // if window exceeds end of data, ignore position if (l >= data.length) continue; window.add( data[l]); } Collections.sort(window); smoothedData[i] = window.get(window.size()/2); } for ( int i =data.length - upperHalfWidth ; i < data.length ; i++) { window.clear(); for ( int l = i - lowerHalfWidth ; l < data.length; l++) { // if window exceeds end of data, ignore position if (l < 0) continue; window.add( data[l]); } Collections.sort(window); smoothedData[i] = window.get(window.size()/2); } return smoothedData; } /** Smooth the data by convolution, either with a gaussian or a mean filter * @return */ private Double[] smoothByConvolution() { Double[] kernel; if ( smoothingMethod == SmoothingMethod.MEAN ) { kernel = new Double[width]; for ( int i = 0 ; i < width ; i++) kernel[i] = 1.0/width; } else { // GAUSSIAN Float floatWidth = 5.0F*sigma; int width = floatWidth.intValue(); if ( width % 2 != 1) width++; int halfWidth = (int)(Math.floor(width/2.0)); kernel = new Double[ width]; kernel[halfWidth] = 1.0; Double sum = 1.0; for ( int i = 1 ; i <= halfWidth ; i++ ) { Double val = Math.exp( -(i*i)/(sigma*sigma)); kernel[halfWidth+i] = val; kernel[halfWidth-i] = val; sum += (val+val); } for ( int i = 0 ; i < width ; i++) kernel[i] /= sum; } if ( verbose ) { System.out.println("Kernel"); for ( int i = 0 ; i < kernel.length ; i++) System.out.println( kernel[i]); } return convolve( experiment.getData(), kernel); } /** Convolve {@code data} with the {@code kernel}. Values outside the range * of {@code data} are ignored. * * @param data * @param kernel * @return */ private Double[] convolve(Double[] data, Double[] kernel) { Double[] smoothedData = new Double[ data.length]; int lowerHalfWidth = (int) Math.floor( kernel.length/2.0); int upperHalfWidth = kernel.length-1-lowerHalfWidth; for ( int i = 0 ; i < lowerHalfWidth ; i++ ) { double sum = 0; double sumWeights = 0.0; for ( int l = 0 ; l <= i + upperHalfWidth ; l++) { //System.out.println(" " + (l + kernel.length -1 - (i+upperHalfWidth))); sum += data[l] * kernel[ l + kernel.length -1 - (i+upperHalfWidth) ]; sumWeights += kernel[l + kernel.length -1 - (i+upperHalfWidth)]; } smoothedData[i] = sum/sumWeights; } double sumWeights = 0.0; for ( int l = 0 ; l < kernel.length ; l++) sumWeights += kernel[l]; for ( int i = lowerHalfWidth ; i < data.length - upperHalfWidth ; i++) { double sum = 0; for ( int l = i - lowerHalfWidth ; l <= i + upperHalfWidth ; l++) sum += data[l] * kernel[l-(i-lowerHalfWidth)]; smoothedData[i] = sum/sumWeights; } for ( int i =data.length - upperHalfWidth ; i < data.length ; i++) { double sum = 0; sumWeights = 0.0; for ( int l = i - lowerHalfWidth ; l < data.length; l++) { //System.out.println(l- (i-lowerHalfWidth)); sum += data[l] * kernel[l- (i-lowerHalfWidth)]; sumWeights += kernel[l- (i-lowerHalfWidth)]; } smoothedData[i] = sum/sumWeights; } return smoothedData; } /** * Callback routine to change parameters on change of smoothing method. * @throws ALDOperatorException */ @SuppressWarnings("unused") private void smoothingMethodChanged() throws ALDOperatorException { try { if (this.smoothingMethod == null ) { if (this.hasParameter("width")) { this.removeParameter("width"); } if (this.hasParameter("sigma")) { this.removeParameter("sigma"); } } else if (this.smoothingMethod == SmoothingMethod.GAUSSIAN) { if (this.hasParameter("width")) { this.removeParameter("width"); } if (!this.hasParameter("sigma")) { this.addParameter("sigma"); } } else { if (this.hasParameter("sigma")) { this.removeParameter("sigma"); } if (!this.hasParameter("width")) { this.addParameter("width"); } } } catch (SecurityException e) { throw new ALDOperatorException(OperatorExceptionType.OPERATE_FAILED, "[SmoothData1D::smoothingMethodChanged()] failedl!"); } catch (ALDOperatorException e) { throw new ALDOperatorException(OperatorExceptionType.OPERATE_FAILED, "[SmoothData1D::smoothingMethodChanged()] failedl!"); } } /** * @return the experiment */ public ExperimentalData1D getExperiment() { return experiment; } /** * @param experiment the experiment to set */ public void setExperiment(ExperimentalData1D experiment) { this.experiment = experiment; } /** * @return the smoothingMethod */ public SmoothingMethod getSmoothingMethod() { return smoothingMethod; } /** * @param smoothingMethod the smoothingMethod to set */ public void setSmoothingMethod(SmoothingMethod smoothingMethod) { this.smoothingMethod = smoothingMethod; } /** * @return the width */ public Integer getWidth() { return width; } /** * @param width the width to set */ public void setWidth(Integer width) { this.width = width; } /** * @return the sigma */ public Float getSigma() { return sigma; } /** * @param sigma the sigma to set */ public void setSigma(Float sigma) { this.sigma = sigma; } /** * @return the smoothedExperiment */ public ExperimentalData1D getSmoothedExperiment() { return smoothedExperiment; } }