October 1, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

An Adaptive Whitening Filter in Java

  • November 1, 2005
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Discussion and Sample Code

The class named Adapt02

The beginning of the class named Adapt02 and the beginning of the main method is shown in Listing 1.

class Adapt02{
  public static void main(String[] args){
    //Default parameter values
    double feedbackGain = 0.00001;
    int numberIterations = 500;
    int predictionFilterLength = 26;
    double signalScale = 20;
    double noiseScale = 20;
    int numberNoiseSources = 1;

Listing 1

The code in Listing 1 establishes default values for six program parameters.  These default values are used if the user doesn't provide six parameters on the command line.  These default values were used to produce the program outputs shown in Figure 1 through Figure 3.

Dealing with the command-line parameters

The code in Listing 2 deal with the command-line parameters as described above.

    if(args.length != 6){
      System.out.println(
                   "Usage with all parameters following " +
                               "program name:n" +
                               "java Adapt02n" +
                               "feedbackGainn" + 
                               "numberIterationsn" + 
                               "predictionFilterLengthn" +
                               "signalScalen" +
                               "noiseScalen" +
                               "numberNoiseSourcesn");
      System.out.println(
            "Using following values by default:n" +
            "feedbackGain: " + feedbackGain +
            "nnumberIterations: " + numberIterations +
            "npredictionFilterLength: " + 
                                   predictionFilterLength +
            "nsignalScale: " + signalScale +
            "nnoiseScale: " + noiseScale +
            "nnumberNoiseSources: " + numberNoiseSources);
    }else{//Command line params were provided.
      feedbackGain = Double.parseDouble(args[0]);
      numberIterations = Integer.parseInt(args[1]);
      predictionFilterLength = Integer.parseInt(args[2]);
      signalScale = Double.parseDouble(args[3]);
      noiseScale = Double.parseDouble(args[4]);
      numberNoiseSources = Integer.parseInt(args[5]);
    
      System.out.println(
            "Using following values from input:n" +
            "feedbackGain: " + feedbackGain +
            "nnumberIterations: " + numberIterations +
            "npredictionFilterLength: " + 
                                   predictionFilterLength +
            "nsignalScale: " + signalScale +
            "nnoiseScale: " + noiseScale +
            "nnumberNoiseSources: " + numberNoiseSources);
    }//end else

Listing 2

The code in Listing 2 also displays the parameters that are used for each run of the program on the command-line screen.

The code in Listing 2 is straightforward and shouldn't require further explanation.

Invoke the method named process

The code in Listing 3 instantiates a new object of the Adapt02 class and invokes the method named process on that object.  The values of each of the six command-line parameters described above are passed to the process method.  These values have already been converted from command-line String objects to values of type double and type int.

    new Adapt02().process(feedbackGain,
                          numberIterations,
                          predictionFilterLength,
                          signalScale,
                          noiseScale,
                          numberNoiseSources);
  }//end main

Listing 3

Listing 3 also signals the end of the main method.  When the process method returns, the program terminates.

The process method

Listing 4 shows the beginning of the method named process.  This is the primary adaptive processing and plotting method for the program.

  void process(double feedbackGain,
               int numberIterations,
               int predictionFilterLength,
               double signalScale,
               double noiseScale,
               int numberNoiseSources){

Listing 4

The initial prediction filter

Listing 5 creates the initial prediction filter with a value of zero for every coefficient.  The coefficient values are stored as values of type double in an array object referred to by predictionFilter.  Recall that array elements of type double are automatically initialized to a value of zero in Java.

    double[] predictionFilter = 
                        new double[predictionFilterLength];

Listing 5

You could easily initialize the coefficient values in the prediction filter to values other than zero if you had a reason to do so.

The initial whitening filter

The code in Listing 6 creates the initial whitening filter and initializes it for spectrum analysis and plotting by:

  • Creating an array object of type double to contain the whitening filter coefficients.
  • Copying the initial prediction filter coefficients into the lower elements of the whitening filter array.
  • Setting the topmost value in the whitening filter array to a value of -1.  (Recall that the whitening filter is created by concatenating a value of -1 onto the end of the prediction filter as described earlier.)

    double[] whiteningFilter = 
                   new double[predictionFilter.length + 1];
    System.arraycopy(predictionFilter,
                     0,
                     whiteningFilter,
                     0,
                     predictionFilter.length);
    //Set the final value in the whitening filter to -1.
    whiteningFilter[whiteningFilter.length - 1] = -1;

Listing 6

Create two delay lines

The code in Listing 7 creates two array objects to serve as delay lines.  The previous lesson taught you about delay lines, so I won't repeat that material here.

    //Create an array to serve as a two-sample delay line
    // for the raw data.
    double[] rawData = new double[2];
    //Create an array to serve as a processing delay line
    // for the data being processed.
    double[] chanA = new double[predictionFilter.length];

Listing 7

Plotting objects

Listing 8 instantiates an object of the PlotALot07 class, which is used later to plot the time series data shown in Figure 1 and Figure 2.

    PlotALot07 timePlotObj = 
                  new PlotALot07("Time",468,200,25,10,4,4);
    PlotALot03 freqPlotObj = 
                   new PlotALot03("Freq",264,487,35,2,0,0);
    
    PlotALot01 filterPlotObj = new PlotALot01("Filter",
            (whiteningFilter.length * 4) + 8,487,70,4,0,0);

Listing 8

Then Listing 8 instantiates an object of the PlotALot03 class, which is used later for plotting two channels of frequency response data at specific time intervals during the adaptive process as shown in the right panel of Figure 3.  One channel is for the amplitude response and the other channel is for the phase response.

Finally, Listing 8 instantiates an object of the PlotALot01 class, which is used later to display the whitening filter at specific time intervals during the adaptive process as shown in the left panel of Figure 3.

If you have been following along and reading my previous lessons, the use of objects from the PlotALot family of classes should be very familiar to you by now.  Therefore, I won't discuss this topic further in this lesson.

A possible point of confusion

There is one possible point of confusion, however, that is worth noting in this lesson (although it was explained fully in the previous lesson).

The minimum allowable width for a Java Frame object is 112 pixels when Java is running under Windows XP.  Therefore, the display of the impulse responses of the whitening filters won't synchronize properly and show one filter on each line for whitening filter lengths less than 26 coefficients.  To compensate for this problem, the code that feeds data to the plotting object later in the program extends the length of the filter to cause it to synchronize and to plot one set of filter coefficients on each line.

The value of the extension coefficients

When the filter is artificially extended (for plotting purposes only), it is extended with artificial filter coefficients having a value of 2.5.  This was done to make it obvious which part of the plot shows the actual filter coefficients and which part shows the artificial extension.

Figure 10 shows the display of a six whitening filters based on a prediction filter length of only fifteen coefficients.

Figure 10

The flat raised portion on the right side of each individual impulse response is not part of the actual filter, but rather is the artificial extension that was necessary to force this plot to synchronize properly under Windows XP.  The result of proper synchronization is that the impulse responses are plotted one above the other as shown.  (If this seems confusing, I recommend that you read more about it in the previous lesson.)

Working variables

Listing 9 declares and initializes several working variables.

    //Declare and initialize working variables.
    double output = 0;
    double err = 0;
    double target = 0;
    double input = 0;
    double signal = 0;
    double sineNoise = 0;

Listing 9

Display the frequency response

Recall that the adaptive process hasn't begun at this point in the program.  Listing 10 invokes the method named displayFreqResponse to display the frequency response of the initial whitening filter in the top of the right panel in Figure 3.

    displayFreqResponse(whiteningFilter,freqPlotObj,128,
                               whiteningFilter.length - 1);

Listing 10

At this point, the whitening convolution filter consists of a single coefficient with a value of -1.  All other coefficients have a value of zero.  As would be expected, therefore, the amplitude response is flat across the entire frequency spectrum.  The phase response is also flat across the entire spectrum with a value of 180 degrees.

(For the record, the frequency response is computed at 128 points between zero and the Nyquist folding frequency.)

The method named displayFreqResponse was explained in detail in the previous lesson, so I won't repeat that material here.

Display the initial whitening filter

Listing 11 displays the initial whitening filter at the top of the left panel in Figure 3 by feeding the filter coefficients to the plotting object instantiated earlier and referred to by filterPlotObj.

    for(int cnt = 0;cnt < whiteningFilter.length;cnt++){
      filterPlotObj.feedData(40*whiteningFilter[cnt]);
    }//end for loop
    //Extend the whitening filter with a value of 2.5 for
    // display purposes only if it is too short to
    // synchronize properly with the plotting software.
    // This value of 2.5 is easily recognizable in the 
    // plot as artificial extended data.  See earlier
    // comment on this topic.  
    //Note that this approach to forcing synchronization
    // will not cause the plot to synchronize properly on
    // an operating system for which the sum of the left
    // and right insets on a Frame object are different
    // from 8 pixels.  The same approach to synchronization
    // could be used but the minimum synchronizable filter
    // length would probably be different.
    if(whiteningFilter.length <= 26){
      for(int cnt = 0;cnt < (26 - whiteningFilter.length);
                                                    cnt++){
        filterPlotObj.feedData(2.5);
      }//end for loop
    }//end if

Listing 11

If the length of the whitening filter is less than or equal to 26 coefficients, the code in Listing 11 extends the filter for plotting purposes as described earlier.

(If you are running this program under some operating system other than Windows XP, the plot may not synchronize properly under your operating system.  In that case, you should pay particular attention to the comments in Listing 11.)

Create the test data

We are now ready to execute the for loop that is used to implement the iterative adaptive process.

During each iteration of the for loop, the code in Listing 12 generates one sample of wide-band signal by getting a value from a random number generator.  Then it creates a sample of narrow-band noise by getting and adding one, two, or three values from the sin method of the Math class.  The signal sample and the noise sample are both scaled by factors provided by the user.

    for(int cnt = 0;cnt < numberIterations;cnt++){
      //Get the next sample of wideband signal.
      signal = signalScale*(Math.random() - 0.5);
      
      //Get the next sample of sinusoidal noise containing
      // three, two, or one sinusoid.
      if(numberNoiseSources == 3){
        sineNoise = noiseScale*(Math.sin(2*cnt*PI/8) +
                                Math.sin(2*cnt*PI/5) +
                                Math.sin(2*cnt*PI/3));
      }else if(numberNoiseSources == 2){
        sineNoise = noiseScale*(Math.sin(2*cnt*PI/8) + 
                                Math.sin(2*cnt*PI/5));
      }else if(numberNoiseSources == 1){
        sineNoise = noiseScale*(Math.sin(2*cnt*PI/8));
      }else{
        System.out.println(
            "Incorrect number noise sources, terminating");
        System.exit(0);
      }//end else

Listing 12

Add the signal to the noise

Listing 13 adds the signal to the noise and passes the sum to the method named flowLine for insertion into the delay line referred to by rawData.

      flowLine(rawData,signal + sineNoise);

Listing 13

The method named flowLine was explained in detail in the previous lesson, so I won't repeat that explanation here.

Populate the chanA delay line

Listing 14 populates the chanA delay line with the next to the last value in the rawData delay line.  The last sample value in the rawData delay line will be the adaptive target.

      flowLine(chanA,rawData[rawData.length - 2]);

Listing 14

Get and save data for plotting

Listing 15 gets the most recent sample that was put into the chanA delay line and saves it for plotting.

      input = chanA[chanA.length -1];

Listing 15

Apply the prediction filter

Listing 16 invokes the dotProduct method to apply the coefficients belonging to the prediction filter to the data samples contained in the chanA delay line.

(Click here to read a description of a vector dot product from Mathworld.  The vector dot product is a central element in the computational process involved in convolution.)

      output = dotProduct(predictionFilter,chanA);

Listing 16

I explained the dotProduct method in detail in the previous lesson and won't repeat that explanation here.

Compute the prediction error

Listing 17 computes the prediction error by:

  • Getting the value of the signal plus noise sample from the end of the rawData delay line to be used as the prediction target.
  • Subtracting the target value that was returned by the dotProduct method.

      //Get the signal plus noise sample from the end of
      // the raw data delay line for an adaptive target.
      target = rawData[rawData.length - 1];
      
      //Compute the error between the current filter output
      // and the target.
      err = output - target;

Listing 17

(Note:  If it weren't for the fact that I wrote this program to save and display various computational results, I could have written this code in a much more compact form involving the dot product of the whitening filter, instead of the prediction filter, and the raw data.)

Update the prediction filter coefficients

Listing 18 uses the value of the error along with the value of feedbackGain to update each of the coefficient values in the prediction filter.  This is an implementation of a least mean square (LMS) adaptive algorithm.

      for(int ctr = 0;ctr < predictionFilter.length;ctr++){
        predictionFilter[ctr] -= 
                               err*chanA[ctr]*feedbackGain;
      }//end for loop.

Listing 18

As I mentioned in the previous lesson, I'm not going to attempt to justify this adaptive algorithm theoretically.  There are hundreds of articles on the web that provide such justification.  If you are interested in a justification, I recommend that you use Google to search them out and read them.  For example, you might search for the keywords LMS Adaptive Algorithm or for the keywords Steepest Descent.

The code in Listing 18 signals the end of the adaptive process.  Some of the code prior to this point, and most of the code following this point exists for display purposes only.

Plotting code

Listing 19 contains all of the remaining code in the for loop that began in Listing 12.

      //Feed the time series data to the plotting object.
      timePlotObj.feedData(
                -err,signal,sineNoise,input,output,target);
      
      //Compute and plot the frequency response and plot
      // the whitening filter every 100 iterations.
      if(cnt%100 == 0){
        //Create a whitening filter from the data in the
        // prediction filter.  Begin by copying the
        // prediction filter into the bottom elements of
        // the whitening filter.
        System.arraycopy(predictionFilter,
                         0,
                         whiteningFilter,
                         0,
                         predictionFilter.length);
        //Now set the final value in the whitening filter
        // to -1. A whitening filter is a prediction filter
        // with a -1 appended to its end.
        whiteningFilter[whiteningFilter.length - 1] = -1;
        displayFreqResponse(whiteningFilter,freqPlotObj,
                           128,whiteningFilter.length - 1);
        //Display the whitening filter coefficient values.
        for(int ctr = 0;ctr < whiteningFilter.length;
                                                    ctr++){
          filterPlotObj.feedData(40*whiteningFilter[ctr]);
        }//end for loop
        //Extend the whitening filter with a value of 2.5
        // for plotting if necessary to cause it to
        // synchronize with one filter on each axis.
        // See explanatory comment earlier.
        if(whiteningFilter.length <= 26){
          for(int count = 0;
              count < (26-whiteningFilter.length);count++){
            filterPlotObj.feedData(2.5);
          }//End for loop
        }//End if statement
      }//End display of frequency response and whitening
       // filter
    }//End for loop, End adaptive process

Listing 19

As mentioned earlier, the code in Listing 19 is mainly used for display purposes.  This code is straightforward and shouldn't require further explanation.

Cause the data to be plotted

The code in Listing 20 causes all of the data that has been fed to the plotting objects during the running of the program to actually be plotted on the screen.

    timePlotObj.plotData();
    freqPlotObj.plotData(0,201);
    filterPlotObj.plotData(265,201);
    
  }//end process method

Listing 20

Listing 20 also signals the end of the method named process.

The adaptive process

The actual adaptive process is mainly executed in Listing 16, Listing 17, and Listing 18, plus those listings that involve feeding signal plus noise data into the delay lines.  Thus, the bulk of the code in this program is used for the following purposes having little or nothing to do with the adaptive process:

  • Generate synthetic data that can be used to illustrate the use of an adaptive whitening filter.
  • Display various data elements for use in explaining the adaptive process.

If that code were to be eliminated from the program, leaving only the code required by the adaptive process, this would be a rather short and compact program.

Run the Program

I encourage you to copy the code from Listing 21 and Listing 22 into your text editor, compile it, and execute it. 

Recall that you will also need to create class files for the following classes:

  • PlotALot01
  • PlotALot03
  • ForwardRealToComplex01

Earlier in this lesson, I provided links to the previously-published lessons where you can get the source code for those classes.

Experiment with the code in the class named Adapt02, making changes, and observing the results of your changes.  For example, much of the code in this lesson is superfluous to the actual adaptive process, but instead is used to display data that helps to explain the adaptive process.  See how much of that code you can eliminate and still have a viable program.

Separate the remaining code into two major sections.  Include the code that is required to create the synthetic data for test purposes in one section.  Include only the code that is required to adaptively process the data in the other section.  Compare the size of the two sections in order to get a feel for the amount of code that is actually required to implement adaptive whitening filters.

Summary

In this lesson, I showed you how to write an adaptive whitening filter program in Java.  I also showed you how to use the whitening filter to extract wide-band signal that is corrupted by one or more components of narrow-band noise.

What's Next?

The next lesson in this series will teach you how to write an adaptive line tracking program in Java.





Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel