/* * Fourier Transform * * This class contains the method for performing a * Fast Fourier Transform (FFT) and associated methods * e.g. for estimation of a power spectrum, for windowing data, * obtaining a time-frequency representation. * Basic FFT method is adapted from the Numerical Recipes * methods written in the C language: * Numerical Recipes in C, The Art of Scientific Computing, * W.H. Press, S.A. Teukolsky, W.T. Vetterling & B.P. Flannery, * Cambridge University Press, 2nd Edition (1992) pp 496 - 558. * (http://www.nr.com/). * * AUTHOR: Dr Michael Thomas Flanagan * DATE: 20 December 2003 * UPDATES: 26 July 2004, 31 August 2004, 15 June 2005, 27 January 2006 * UPDATES: 18 February 2006 method correlation correction (thanks to Daniel Mader, Universtät Freiburg -- IMTEK) 7 July 2008 * * DOCUMENTATION: * See Michael Thomas Flanagan's Java library on-line web page: * http://www.ee.ucl.ac.uk/~mflanaga/java/FourierTranasform.html * http://www.ee.ucl.ac.uk/~mflanaga/java/ * * * Copyright (c) 2003 - 2008 Michael Thomas Flanagan * * PERMISSION TO COPY: * Permission to use, copy and modify this software and its documentation for * NON-COMMERCIAL purposes is granted, without fee, provided that an acknowledgement * to the author, Michael Thomas Flanagan at www.ee.ucl.ac.uk/~mflanaga, appears in all copies. * * Dr Michael Thomas Flanagan makes no representations about the suitability * or fitness of the software for any or for a particular purpose. * Michael Thomas Flanagan shall not be liable for any damages suffered * as a result of using, modifying or distributing this software or its derivatives. * ***************************************************************************************/ package flanagan.math; import flanagan.math.*; import flanagan.io.*; import java.io.Serializable; import flanagan.complex.*; import flanagan.plot.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.JFrame; public class FourierTransform extends Canvas implements Serializable{ private static final long serialVersionUID = 1L; // serial version unique identifier private Complex[] complexData = null; // array to hold the input data as a set of Complex numbers private Complex[] complexCorr = null; // corresponding array to hold the data to be correlated with first data set private boolean complexDataSet = false; // if true - the complex data input array has been filled, if false - it has not. private int originalDataLength = 0; // original data length value; the working data length may be altered by deletion or padding private int fftDataLength = 0; // working data length - usually the smallest power of two that is either equal to originalDataLength or larger than originalDataLength private boolean dataAltered = false; // set to true if originalDataLength altered, e.g. by point deletion or padding. private double[] fftData = null; // array to hold a data set of complex numbers arranged as alternating // real and imaginary parts, e.g. real_0 imag_0, real_1 imag_1, for the fast Fourier Transform method private double[] fftCorr = null; // corresponding array to hold the data to be correlated with first data set private double[] fftResp = null; // corresponding array to hold the response to be convolved with first data set private boolean fftDataSet = false; // if true - the fftData array has been filled, if false - it has not. private double[] fftDataWindow = null; // array holding fftData array elements multiplied by the windowing weights private double[] fftCorrWindow = null; // corresponding array to hold the data to be correlated with first data set private int windowOption = 0; // Window Option // = 0; no windowing applied (default) - equivalent to option = 1 // = 1; Rectangular (square, box-car) // = 2; Bartlett (triangular) // = 3; Welch // = 4; Hann (Hanning) // = 5; Hamming // = 6; Kaiser // = 7; Gaussian // all window names private String[] windowNames = {"no windowing applied", "Rectangular (square, box-car)", "Bartlett (triangular)", "Welch", "Hann (Hanning)", "Hamming", "Kaiser", "Gaussian"}; private String windowName = windowNames[0]; // current window name private double kaiserAlpha = 2.0D; // Kaiser window constant, alpha private double gaussianAlpha = 2.5D; // Gaussian window constant, alpha private double[] weights = null; // windowing weights private boolean windowSet = false; // = true when a windowing option has been chosen, otherwise = false private boolean windowApplied = false; // = true when data has been multiplied by windowing weights, otherwise = false private double sumOfSquaredWeights = 0.0D; // Sum of the windowing weights private Complex[] transformedDataComplex = null; // transformed data set of Complex numbers private double[] transformedDataFft = null; // transformed data set of double adjacent real and imaginary parts private boolean fftDone = false; // = false - basicFft has not been called // = true - basicFft has been called private double[][] powerSpectrumEstimate = null; // first row - array to hold frequencies // second row - array to hold estimated power density (psd) spectrum private boolean powSpecDone = false; // = false - PowerSpectrum has not been called // = true - PowerSpectrum has been called private int psdNumberOfPoints = 0; // Number of points in the estimated power spectrum private int segmentNumber = 1; // Number of segments into which the data has been split private int segmentLength = 0; // Number of of data points in a segment private boolean overlap = false; // Data segment overlap option // = true; overlap by half segment length - smallest spectral variance per data point // good where data already recorded and data reduction is after the process // = false; no overlap - smallest spectral variance per conputer operation // good for real time data collection where data reduction is computer limited private boolean segNumSet = false; // true if segment number has been set private boolean segLenSet = false; // true of segment length has been set private double deltaT = 1.0D; // Sampling period (needed only for true graphical output) private boolean deltaTset = false; // true if sampling period has been set private double[][] correlationArray = null; // first row - array to hold time lags // second row - correlation between fftDataWindow and fftCorrWindow private boolean correlateDone = false; // = false - correlation has not been called // = true - correlation has been called private int numberOfWarnings = 9; // Number of warnings private boolean[] warning = new boolean[numberOfWarnings]; // warnings - if warning[x] = true warningText[x] is printed private int plotLineOption = 0; // PlotPowerSpectrum line option // = 0 points linked by straight line [default option] // = 1 cubic spline interpolation // = 2 no line - only points private int plotPointOption = 0; // PlotPowerSpectrum point option // = 0 no point symbols [default option] // = 1 filled circles private double[][] timeFrequency = null; // matrix of time against frequency mean square powers from shoert time FT // first row = blank cell followed by time vector // first column = blank cell followed by frequency vector // each cell is then the mean square amplitude at that frequency and time private boolean shortTimeDone = false; // = true when short time Fourier Transform has been performed private int numShortFreq = 0; // number of frequency points in short time Fourier transform private int numShortTimes = 0; // number of time points in short time Fourier transform private String shortTitle = " "; // Short Time Fourier Transform graph title // constructors // No initialisation of the data variables public FourierTransform(){ for(int i=0; isegLno){ segL = segLov; segN = segNov; ovrlp = true; } else{ segL = segLno; segN = segNno; ovrlp = false; } } else{ segL = segLno; segN = segNno; ovrlp = false; } } else{ if(test2){ segL = segLov; segN = segNov; ovrlp = true; } else{ setSegment = false; } } // compare segmentation and zero padding if(setSegment && (this.originalDataLength-segL <= this.fftDataLength - this.originalDataLength)){ System.out.println("Data length is not an integer power of two"); System.out.println("Data cannot be transformed as a single segment"); System.out.print("The data has been split into " + segN+ " segments of length " + segL); if(ovrlp){ System.out.println(" with 50% overlap"); } else{ System.out.println(" with no overlap"); } this.segmentLength = segL; this.segmentNumber = segN; this.overlap = ovrlp; this.warning[7] = true; } else{ System.out.println("Data length is not an integer power of two"); if(this.dataAltered){ System.out.println("Deleted point has been restored and the data has been padded with zeros to give a power of two length"); this.warning[0] = false; } else{ System.out.println("Data has been padded with zeros to give a power of two length"); } this.fftDataLength = this.fftDataLength; this.warning[8] = true; } } } } private void printWarnings(FileOutput fout){ if(warning[0]){ fout.println("WARNING!"); fout.println("Number of data points must be an even number"); fout.println("The last point was deleted"); fout.println(); } if(warning[1]){ fout.println("WARNING!"); fout.println("Segment length was not an integer power of two"); fout.println("Segment length was reset to total data length, i.e. no segmentation"); fout.println(); } if(warning[2]){ fout.println("WARNING!"); fout.println("Total data length divided by the number of segments was not an integer"); fout.println("Segment length was reset to total data length, i.e. no segmentation"); fout.println(); } if(warning[3]){ fout.println("WARNING!"); fout.println("Total data length divided by the segment length was not an integer"); fout.println("Segment length was reset to total data length, i.e. no segmentation"); fout.println(); } if(warning[4]){ fout.println("WARNING!"); fout.println("Total data length divided by the number of segments plus one was not an integer"); fout.println("Segment length was reset to total data length, i.e. no segmentation"); fout.println(); } if(warning[5]){ fout.println("WARNING!"); fout.println("Twice the total data length divided by the segment length was not an integer"); fout.println("Segment length was reset to total data length, i.e. no segmentation"); fout.println(); } if(warning[6]){ fout.println("WARNING!"); fout.println("Overlap is not possible with less than two segments"); fout.println("Overlap option has been reset to 'no overlap' i.e. to false"); fout.println(); } if(warning[7]){ fout.println("WARNING!"); fout.println("Data length was not an integer power of two"); fout.println("The data could not be transformed as a single segment"); fout.print("The data has been split into " + this.segmentNumber+ " segment/s of length " + this.segmentLength); if(this.overlap){ fout.println(" with 50% overlap"); } else{ fout.println(" with no overlap"); } fout.println(); } if(warning[8]){ fout.println("WARNING!"); fout.println("Data length was not an integer power of two"); fout.println("Data has been padded with " + (this.fftDataLength-this.originalDataLength) + " zeros to give an integer power of two length"); fout.println(); } } // get the number of segments public int getSegmentNumber(){ return this.segmentNumber; } // get the segment length public int getSegmentLength(){ return this.segmentLength; } // set overlap option - see above (head of program comment lines) for option description public void setOverlapOption(boolean overlapOpt){ boolean old = this.overlap; this.overlap = overlapOpt; if(old != this.overlap){ if(this.fftDataSet){ this.setSegmentNumber(this.segmentNumber); } } } // get overlap option - see above for options public boolean getOverlapOption(){ return this.overlap; } // calculate the number of data points given the: // segment length (segLen), number of segments (segNum) // and the overlap option (overlap: true - overlap, false - no overlap) public static int calcDataLength(boolean overlap, int segLen, int segNum){ if(overlap){ return (segNum+1)*segLen/2; } else{ return segNum*segLen; } } // Method for performing a Fast Fourier Transform public void transform(){ // set up data array int isign = 1; if(!this.fftDataSet)throw new IllegalArgumentException("No data has been entered for the Fast Fourier Transform"); if(this.originalDataLength!=this.fftDataLength){ System.out.println("Fast Fourier Transform data length ," + this.originalDataLength + ", is not an integer power of two"); System.out.println("WARNING!!! Data has been padded with zeros to fill to nearest integer power of two length " + this.fftDataLength); } // Perform fft double[] hold = new double[this.fftDataLength*2]; for(int i=0; i i) { int ii = i-1; dtemp = data[jj]; data[jj] = data[ii]; data[ii] = dtemp; dtemp = data[jj+1]; data[jj+1] = data[ii+1]; data[ii+1] = dtemp; } m = n >> 1; while (m >= 2 && j > m) { j -= m; m >>= 1; } j += m; } mmax=2; while (n > mmax) { istep=mmax << 1; theta=isign*(6.28318530717959D/mmax); wtemp=Math.sin(0.5D*theta); wpr = -2.0D*wtemp*wtemp; wpi=Math.sin(theta); wr=1.0D; wi=0.0D; for (m=1;m=n)lowPoint=0; if(highPoint<0 || highPoint>n)highPoint=n; this.plotPowerSpectrumLinear(lowPoint, highPoint, graphTitle); } } // Display a plot of the power spectrum from a given frequency // no graph title provided public void plotPowerSpectrum(double lowFreq){ String graphTitle = "Estimation of Power Spectrum Density"; this.plotPowerSpectrum(lowFreq, graphTitle); } // Display a plot of the power spectrum from a given frequency // graph title provided public void plotPowerSpectrum(double lowFreq, String graphTitle){ if(!this.powSpecDone)this.powerSpectrum(); double highFreq = this.powerSpectrumEstimate[1][this.powerSpectrumEstimate[0].length-1]; this.plotPowerSpectrum(lowFreq, highFreq, graphTitle); } // Display a plot of the power spectrum within a defined frequency window // no graph title provided public void plotPowerSpectrum(double lowFreq, double highFreq){ if(!this.powSpecDone){ System.out.println("plotPowerSpectrum - powerSpectrum has not been called - no plot displayed"); } else{ String graphTitle = "Estimation of Power Spectrum Density"; this.plotPowerSpectrum(lowFreq, highFreq, graphTitle); } } // Display a plot of the power spectrum within a defined frequency window // graph title provided public void plotPowerSpectrum(double lowFreq, double highFreq, String graphTitle){ if(!this.powSpecDone){ System.out.println("plotPowerSpectrum - powerSpectrum has not been called - no plot displayed"); } else{ int low = 0; int high = 0; if(!this.deltaTset){ System.out.println("plotPowerSpectrum - deltaT has not been set"); System.out.println("full spectrum plotted"); } else{ int ii = 0; int n = this.powerSpectrumEstimate[0].length - 1; boolean test = true; if(lowFreq==-1.0D){ low = 1; } else{ while(test){ if(this.powerSpectrumEstimate[0][ii]>lowFreq){ low=ii-1; if(low<0)low=0; test = false; } else{ ii++; if(ii>=n){ low = 0; System.out.println("plotPowerSpectrum - lowFreq out of range - reset to zero"); test = false; } } } } test = true; ii = 0; while(test){ if(this.powerSpectrumEstimate[0][ii]>highFreq){ high=ii-1; if(high<0){ System.out.println("plotPowerSpectrum - highFreq out of range - reset to highest value"); high = n; } test = false; } else{ ii++; if(ii>=n){ high = n; System.out.println("plotPowerSpectrum - highFreq out of range - reset to highest value"); test = false; } } } this.plotPowerSpectrumLinear(low, high, graphTitle); } } } // Display a plot of the power spectrum // no graph title provided public void plotPowerSpectrum(){ if(!this.powSpecDone)this.powerSpectrum(); String graphTitle = "Estimation of Power Spectrum Density"; this.plotPowerSpectrumLinear(0, this.powerSpectrumEstimate[0].length-1, graphTitle); } // Display a plot of the power spectrum public void plotPowerSpectrum(String graphTitle){ if(!this.powSpecDone)this.powerSpectrum(); this.plotPowerSpectrumLinear(0, this.powerSpectrumEstimate[0].length-1, graphTitle); } // Prepare a plot of the power spectrum (linear) private void plotPowerSpectrumLinear(int low, int high, String graphTitle){ int nData = this.powerSpectrumEstimate[0].length; int nNew = high - low + 1; double[][] spectrum = new double[2][nNew]; for(int i=0; i=n)lowPoint=0; if(highPoint<0 || highPoint>n)highPoint=n; this.plotPowerSpectrumLog(lowPoint, highPoint, graphTitle); } // Display a plot of the power spectrum from a given frequency // no graph title provided public void plotPowerLog(double lowFreq){ String graphTitle = "Estimation of Power Spectrum Density"; this.plotPowerLog(lowFreq, graphTitle); } // Display a log plot of the power spectrum from a given frequency // graph title provided public void plotPowerLog(double lowFreq, String graphTitle){ if(!this.powSpecDone)this.powerSpectrum(); double highFreq = this.powerSpectrumEstimate[1][this.powerSpectrumEstimate[0].length-1]; this.plotPowerLog(lowFreq, highFreq, graphTitle); } // Display a plot of the power spectrum within a defined frequency window // no graph title provided public void plotPowerLog(double lowFreq, double highFreq){ if(!this.powSpecDone)this.powerSpectrum(); String graphTitle = "Estimation of Power Spectrum Density"; this.plotPowerLog(lowFreq, highFreq, graphTitle); } // Display a log plot of the power spectrum within a defined frequency window // graph title provided public void plotPowerLog(double lowFreq, double highFreq, String graphTitle){ if(!this.powSpecDone)this.powerSpectrum(); int low = 0; int high = 0; if(!this.deltaTset){ System.out.println("plotPowerLog - deltaT has not been set"); System.out.println("full spectrum plotted"); } else{ int ii = 0; int n = this.powerSpectrumEstimate[0].length - 1; boolean test = true; if(lowFreq==-1.0D){ low = 1; } else{ while(test){ if(this.powerSpectrumEstimate[0][ii]>lowFreq){ low=ii-1; if(low<0)low=0; test = false; } else{ ii++; if(ii>=n){ low = 0; System.out.println("plotPowerLog - lowFreq out of range - reset to zero"); test = false; } } } } test = true; ii = 0; while(test){ if(this.powerSpectrumEstimate[0][ii]>highFreq){ high=ii-1; if(high<0){ System.out.println("plotPowerLog - highFreq out of range - reset to highest value"); high = n; } test = false; } else{ ii++; if(ii>=n){ high = n; System.out.println("plotPowerSpectrum - highFreq out of range - reset to highest value"); test = false; } } } this.plotPowerSpectrumLog(low, high, graphTitle); } } // Display a log plot of the power spectrum // no graph title provided public void plotPowerLog(){ if(!this.powSpecDone)this.powerSpectrum(); String graphTitle = "Estimation of Power Spectrum Density"; this.plotPowerSpectrumLog(0, this.powerSpectrumEstimate[0].length-1, graphTitle); } // Display a log plot of the power spectrum public void plotPowerLog(String graphTitle){ if(!this.powSpecDone)this.powerSpectrum(); this.plotPowerSpectrumLog(0, this.powerSpectrumEstimate[0].length-1, graphTitle); } // Prepare a plot of the power spectrum (log) private void plotPowerSpectrumLog(int low, int high, String graphTitle){ int nData = this.powerSpectrumEstimate[0].length; int nNew = high - low + 1; double[][] spectrum = new double[2][nNew]; for(int i=0; i0.0D){ minimum = spectrum[1][ii]; test = false; } else{ ii++; if(ii>=nNew){ test = false; System.out.println("plotPowerSpectrumLog: no non-zero amplitudes"); System.exit(0); } } } // Find minimum for(int i=ii+1; i1){ if(this.overlap){ graphTitle2 += ", segments overlap by 50%"; } else{ graphTitle2 += ", segments do not overlap"; } } pg.setGraphTitle2(graphTitle2); pg.setXaxisLegend("Frequency"); if(this.deltaTset){ pg.setXaxisUnitsName("cycles per unit time"); } else{ pg.setXaxisUnitsName("cycles per grid point"); } pg.setYaxisLegend(yLegend); switch(this.plotLineOption){ case 0: pg.setLine(3); break; case 1: pg.setLine(1); break; case 2: pg.setLine(2); break; default: pg.setLine(3); } switch(this.plotPointOption){ case 0: pg.setPoint(0); break; case 1: pg.setPoint(4); break; default: pg.setPoint(0); } pg.plot(); } // Set the line option in plotting the power spectrum or correlation // = 0 join points with straight lines // = 1 cubic spline interpolation // = 3 no line - only points public void setPlotLineOption(int lineOpt){ this.plotLineOption = lineOpt; } // Get the line option in ploting the power spectrum or correlation // = 0 join points with straight lines // = 1 cubic spline interpolation // = 3 no line - only points public int getPlotLineOption(){ return this.plotLineOption; } // Set the point option in plotting the power spectrum or correlation // = 0 no point symbol // = 1 filled circles public void setPlotPointOption(int pointOpt){ this.plotPointOption = pointOpt; } // Get the point option in plotting the power spectrum or correlation // = 0 no point symbol // = 1 filled circles public int getPlotPointOption(){ return this.plotPointOption; } // Return correlation of data already entered with data passed as this method's argument // data must be real public double[][] correlate(double[] data){ int nLen = data.length; if(!this.fftDataSet)throw new IllegalArgumentException("No data has been previously entered"); if(nLen!=this.originalDataLength)throw new IllegalArgumentException("The two data sets to be correlated are of different length"); if(!FourierTransform.checkPowerOfTwo(nLen))throw new IllegalArgumentException("The length of the correlation data sets is not equal to an integer power of two"); this.complexCorr = Complex.oneDarray(nLen); for(int i=0; i correlation isign = -1; basicFft(hold3, nLen, isign); // fill correlation array for(int i=0; i<2*nLen; i++)this.transformedDataFft[i]=hold3[i]; this.correlationArray = new double[2][nLen]; j=0; int k=nLen; for(int i=nLen/2+1; ithis.originalDataLength)throw new IllegalArgumentException("The window length, " + windowLength + ", is greater than the data length, " + this.originalDataLength + "."); // if no window option has been set - default = Gaussian with alpha = 2.5 if(this.windowOption==0)this.setGaussian(); // set up time-frequency matrix // first row = blank cell followed by time vector // first column = blank cell followed by frequency vector // each cell is then the mean square amplitude at that frequency and time this.numShortTimes = this.originalDataLength - windowLength + 1; this.numShortFreq = windowLength/2; this.timeFrequency = new double[this.numShortFreq+1][this.numShortTimes+1]; this.timeFrequency[0][0]=0.0D; this.timeFrequency[0][1]=(double)(windowLength-1)*this.deltaT/2.0D; for(int i=2;i<=this.numShortTimes;i++){ this.timeFrequency[0][i] = this.timeFrequency[0][i-1] + this.deltaT; } for(int i=0;i0){ xAve += 1; xTp = this.numShortTimes/xAve+1; xLast = this.numShortTimes - xAve*(xTp-1); } else{ xTp = this.numShortTimes/xAve; xLast = xAve; } xLen = xTp; } // Check and set parameters in case need to average or expand to match fixed y-axis pixels int pixelsPerYpoint = 0; int yTp = 0; int yAve = 0; int yLast = 0; boolean yCheck = true; if(this.numShortFreq <= yLen){ pixelsPerYpoint = yLen/this.numShortFreq; yLen = pixelsPerYpoint*this.numShortFreq; yTp = this.numShortFreq; } else{ yCheck = false; pixelsPerYpoint = 1; yTp = this.numShortFreq; yAve = this.numShortFreq/yLen; yLast = this.numShortFreq % yLen; if(yLast>0){ yAve += 1; yTp = this.numShortFreq/yAve+1; yLast = this.numShortFreq - yAve*(yTp-1); } else{ yTp = this.numShortFreq/yAve; yLast = yAve; } yLen = yTp; } // Complete axes positions int yBot = yTop + yLen; int xTop = xBot + xLen; // declare contour map arrays double[][] averages = new double[yTp][xTp]; int[][] pixels = new int[yTp][xTp]; double[] times = new double[xTp]; int[] timesPixels = new int[xTp]; double[] freqs = new double[yTp]; int[] freqPixels = new int[yTp]; double[][] hold = new double[this.numShortFreq][xTp]; // If necessary average or expand to match fixed y-axis pixels if(xCheck){ for(int i=0; i<=this.numShortFreq; i++){ for(int j=1; j<=this.numShortTimes; j++){ if(i==0){ times[j-1] = this.timeFrequency[0][j]; } else{ hold[i-1][j-1] = this.timeFrequency[i][j]; } } } } else{ double sum = 0.0D; int start = 1; int workingAve = xAve; for(int i=0; i<=this.numShortFreq;i++){ start = 1; for(int j=1; j<=xTp; j++){ workingAve = xAve; if(j==xTp)workingAve = xLast; sum=0.0D; for(int k=start; k<=(start+workingAve-1); k++){ sum += this.timeFrequency[i][k]; } if(i==0){ times[j-1] = sum/workingAve; } else{ hold[i-1][j-1] = sum/workingAve; } start += workingAve; } } } // If necessary average or expand to match fixed x-axis pixels if(yCheck){ for(int i=0; imax)max = averages[i][j]; if(averages[i][j]0.1D*max)bandZero = 0.99D*min; double bandWidth = (1.01D*max - 0.99D*min)/numBands; double[] band = new double[numBands]; band[0]=bandZero + bandWidth; for(int i=1; i1){ if((m % 2)!=0){ test = false; } else{ m /= 2; } } return test; } // Checks whether the argument n is an integer times a integer power of 2 // returns integer multiplier if true // returns zero if false public static int checkIntegerTimesPowerOfTwo(int n){ boolean testOuter1 = true; boolean testInner1 = true; boolean testInner2 = true; boolean testReturn = true; int m = n; int j = 1; int mult = 0; while(testOuter1){ testInner1 = FourierTransform.checkPowerOfTwo(m); if(testInner1){ testReturn = true; testOuter1 = false; } else{ testInner2 = true; while(testInner2){ m /= ++j; if(m < 1){ testInner2 = false; testInner1 = false; testOuter1 = false; testReturn = false; } else{ if((m % 2)==0)testInner2 = false; } } } } if(testReturn)mult = j; return mult; } // Return the serial version unique identifier public static long getSerialVersionUID(){ return FourierTransform.serialVersionUID; } }