April 16, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

An Adaptive Line Tracker in Java, Page 2

  • January 9, 2006
  • By Richard G. Baldwin, Richard G. Baldwin
  • Send Email »
  • More Articles »

Discussion and Sample Code

Adapt05

The class named Adapt05 and the main method begin in Listing 1.

class Adapt05{

  public static void main(String[] args){
    //Default parameter values.  See a description of each
    // of these parameters in the opening comments above.
    // Note that this set of default values represents a
    // high signal-to-noise ratio.
    double feedbackGain = 0.00001;
    int numberIterations = 3375;
    int filterLength = 15;
    double wideBandNoiseScale = 1.0;
    double fmSignalScale = 20.0;
    int fmSignalCase = 3;
    double freqSlideConst = 0.0004;
    double freqShiftFactorLow = 0.5;
    double freqShiftFactorHigh = 1.5;
    int lengthMultiplier = 2;
    
    int spectralWidth = 222;//Not a user input value
    
    if(args.length != 10){
      System.out.println(
               "Usage with all parameters following the " +
               "program name:n" +
               "java Adapt05n" +
               "feedbackGainn" + 
               "numberIterationsn" + 
               "filterLengthn" +
               "wideBandNoiseScalen" +
               "fmSignalScalen" +
               "fmSignalCasen" +
               "freqSlideConstn" +
               "freqShiftFactorLown" +
               "freqShiftFactorHighn" +
               "lengthMultipliern");
               
      System.out.println(
          "Input values were not provided.n"+
          "Using following values:n" +
          "feedbackGain: " + feedbackGain +
          "nnumberIterations: " + numberIterations +
          "nfilterLength: " + filterLength +
          "nwideBandNoiseScale: " + wideBandNoiseScale +
          "nfmSignalScale: " + fmSignalScale +
          "nfmSignalCase: " + fmSignalCase +
          "nfreqSlideConst: " + freqSlideConst +
          "nfreqShiftFactorLow: " + freqShiftFactorLow +
          "nfreqShiftFactorHigh: " + freqShiftFactorHigh +
          "nspectralWidth: " + spectralWidth +
          "nlengthMultiplier: " + lengthMultiplier +
          "nConventional data length: " + 
                         (lengthMultiplier * filterLength + 
                                        lengthMultiplier));
    }else{//Command line params were provided.
      feedbackGain = Double.parseDouble(args[0]);
      numberIterations = Integer.parseInt(args[1]);
      filterLength = Integer.parseInt(args[2]);
      wideBandNoiseScale = Double.parseDouble(args[3]);
      fmSignalScale = Double.parseDouble(args[4]);
      fmSignalCase = Integer.parseInt(args[5]);
      freqSlideConst = Double.parseDouble(args[6]);
      freqShiftFactorLow = Double.parseDouble(args[7]);
      freqShiftFactorHigh = Double.parseDouble(args[8]);
      lengthMultiplier = Integer.parseInt(args[9]);
    
      System.out.println(
          "Input values were provided.n"+
          "Using following values:n" +
          "feedbackGain: " + feedbackGain +
          "nnumberIterations: " + numberIterations +
          "nfilterLength: " + filterLength +
          "nwideBandNoiseScale: " + wideBandNoiseScale +
          "nfmSignalScale: " + fmSignalScale +
          "nfmSignalCase: " + fmSignalCase +
          "nfreqSlideConst: " + freqSlideConst+
          "nfreqShiftFactorLow: " + freqShiftFactorLow +
          "nfreqShiftFactorHigh: " + freqShiftFactorHigh +
          "nspectralWidth: " + spectralWidth +
          "nlengthMultiplier: " + lengthMultiplier +
          "nConventional data length: " + 
                         (lengthMultiplier * filterLength + 
                                        lengthMultiplier));
    }//end else

Listing 1

The code in Listing 1 is straightforward, and shouldn't require any explanation beyond the comments in the code.

Instantiate an object of the class

Listing 2 instantiates a new object of the Adapt05 class and invokes the method named process on that object.

    new Adapt05().process(feedbackGain,
                          numberIterations,
                          filterLength,
                          wideBandNoiseScale,
                          fmSignalScale,
                          fmSignalCase,
                          freqSlideConst,
                          freqShiftFactorLow,
                          freqShiftFactorHigh,
                          spectralWidth,
                          lengthMultiplier);
  }//end main

Listing 2

Listing 2 also signals the end of the main method.

The method named process

Listing 3 shows the beginning of the method named process.

  void process(double feedbackGain,
               int numberIterations,
               int filterLength,
               double wideBandNoiseScale,
               double fmSignalScale,
               int fmSignalCase,
               double freqSlideConst,
               double freqShiftFactorLow,
               double freqShiftFactorHigh,
               int spectralWidth,
               int lengthMultiplier){

    //Declare and initialize working variables.
    double err = 0;
    double wideBandNoise = 0;
    double fmSignal = 0;
    double freqSlideValue = 0;
    double freqShiftFactor = freqShiftFactorLow;

Listing 3

This is the primary processing and plotting method for the program.  This method uses an object of the AdaptEngine01 class to provide the adaptive behavior.

The code in Listing 3 declares and initializes some working variables.

Array for the whitening filter

Listing 4 creates an array object to contain the whitening filter.  The actual whitening of the data is accomplished within the object of type AdaptEngine01.  That object returns the prediction portion of the whitening filter, but does not return an actual whitening filter.

This copy of the whitening filter is required solely for the purpose of computing and displaying the frequency response of the whitening filter.  Note that the length of the whitening filter is one greater than the length of the filter that is returned by the adaptive object.  The extra coefficient in the whitening filter is set to a value of -1 by the code in Listing 4.

    double[] whiteningFilter = 
                              new double[filterLength + 1];

    whiteningFilter[filterLength] = -1;

Listing 4

All other values in the whitening filter are initialized to zero by the code in Listing 4.  Coefficient values returned by the adaptive process are copied into the lower elements of the whitening filter later.

A data delay line

Listing 5 creates an array to contain two samples of the data that will be adaptively processed. 

    double[] data = new double[2];

Listing 5

This array is used as a tapped delay line.  During each adaptive iteration, the data sample to be filtered is located at index 0.  The value of the adaptive target is located at index 1.  Each new data sample is inserted at index 1, causing the value at that index to be moved to index 0.  This causes the value at index 0 to fall off the end of the delay line.

A delay line for raw data

Listing 6 creates an array object to serve as a delay line to contain a chunk of raw data.  This raw data is used for conventional spectral analysis.

    double[] rawData = new double[
                  lengthMultiplier*whiteningFilter.length];

Listing 6

The length of this delay line is an integer multiple of the length of the whitening filter.  The multiplier is provided as a user input parameter.

A general-purpose adaptive engine object

Listing 7 instantiates a general purpose adaptive processing object of the class AdaptEngine01.  This object is used later to provide the adaptive behavior for the entire program.

    AdaptEngine01 theAdapter = 
              new AdaptEngine01(filterLength,feedbackGain);

Listing 7

Instantiate plotting objects

Listing 8 instantiates two plotting objects of the PlotALot08 class.  The first plotting object is used later to display the frequency response of the whitening filter.  The second plotting object is used later to display the results of the conventional spectrum analysis.

    //Instantiate a plotting object to display whitening
    // filter frequency response data.
    PlotALot08 freqPlotObj = new PlotALot08(
                                         "Adaptive",
                                         spectralWidth + 8,
                                         487,
                                         10,
                                         1,
                                         0,
                                         0);

    //Instantiate a plotting object to display the results
    // of conventional spectrum analysis.
    PlotALot08 conventionalPlotObj = new PlotALot08(
                                         "Conventional",
                                         spectralWidth + 8,
                                         487,
                                         10,
                                         1,
                                         0,
                                         0);
                                   

Listing 8

The class named PlotALot08 can be viewed in Listing 21 near the end of the lesson.  Although this class is new to this lesson, it is very similar to the previously-published classes in the family of PlotALot classes and should not require an explanation.

Start looping and get wide-band noise sample

Listing 9 shows the beginning of a for loop that will execute the specified number of adaptive iterations.

    for(int cnt = 0;cnt < numberIterations;cnt++){
      wideBandNoise = 
                 wideBandNoiseScale*(2.0*(random() - 0.5));

Listing 9

Listing 9 also gets the next sample of wideBandNoise with a uniform distribution from -1.0 to +1.0.  The noise is scaled by the specified scale factor.  (Note the use of a static import directive for the Math class, which requires J2SE 5.0 or later.)

Get the next sample of narrow-band signal

Listing 10 gets the next sample of frequency-modulated sinusoidal (narrow-band) signal.

      //Get the next sample of fmSignal data. The contents
      // of the following variable sets the frequency for
      // the FM sweep signal for this sample. This value
      // increases for each successive iteration.
      freqSlideValue += freqSlideConst;

      //Use the value of fmSignalCase to determine which
      // configuration of FM signal to generate.
      if(fmSignalCase == 1){
        //Single source, sweep frequency
        fmSignal = fmSignalScale*(sin(
                          cnt*(freqSlideValue + 2*PI/32)));
      }else if(fmSignalCase == 2){
        //Single source, shift frequency.  Frequency shifts
        // every 1650 iterations.
        if(cnt%1650 == 0){
          if(freqShiftFactor == freqShiftFactorLow){
            freqShiftFactor = freqShiftFactorHigh;
          }else{
            freqShiftFactor = freqShiftFactorLow;
          }//end else
        }//end if
        fmSignal = fmSignalScale*sin(
                               freqShiftFactor*cnt*2*PI/4);
      }else if(fmSignalCase == 3){
        //Two signal sources with one of each of the above
        // configurations.
        if(cnt%1650 == 0){
          if(freqShiftFactor == freqShiftFactorLow){
            freqShiftFactor = freqShiftFactorHigh;
          }else{
            freqShiftFactor = freqShiftFactorLow;
          }//end else
        }//end if
        fmSignal = fmSignalScale*(sin(
                          cnt*(freqSlideValue + 2*PI/32)) +
                          sin(freqShiftFactor*cnt*2*PI/4));
      }else{
        System.out.println(
            "Incorrect signal case, terminating");
        System.exit(0);
      }//end else

Listing 10

The narrow-band signal consists of:

  • A frequency-modulated sweep as shown in Figure 3, or
  • A frequency-modulated shift as shown in Figure 1, or
  • A combination of the two as shown in Figure 5.

The choice among the three alternatives is provided by the user as an input parameter.

Although the code in Listing 10 is somewhat long, it is straightforward and shouldn't require a detailed explanation.

Insert the raw data into the delay lines

The first statement in Listing 11 inserts the raw signal plus noise data into the two-element delay line that is used to feed the adaptive process.

The second statement in Listing 11 inserts the raw signal plus noise data into the longer delay line that is used to feed the conventional spectrum analysis process.

      //Insert the wideBandNoise plus fmSignal into the
      // two-element delay line.
      flowLine(data,wideBandNoise+fmSignal);
      
      //Insert signal plus noise into delay line used for
      // conventional spectrum analysis.
      flowLine(rawData,wideBandNoise+fmSignal);

Listing 11

Obviously, I could have used the same delay line for both purposes.  However, since each delay line is used to feed an entirely different process, I decided to separate the two for clarity of purpose.

Execute the adaptive process

The single statement in Listing 12 invokes the adapt method on the adaptive engine of type AdaptEngine01, to perform the adaptive process for the entire program.  (All of the other code in the process method is used to manage data and to display results.)

      AdaptiveResult result = 
                         theAdapter.adapt(data[0],data[1]);

Listing 12

Input to the adapt method

The code in Listing 12 passes the two samples from the two-element delay line to the adapt method.  The first parameter is the next sample of raw data that is to be filtered by the adaptive prediction filter.  The second parameter is the sample that is used as the predictive target.

In other words, the adapt method uses the first parameter in conjunction with previous samples that have been saved in an attempt to predict the value of the second parameter.  The prediction error is then used to adjust the coefficients in the prediction filter that is maintained by the adapt method.

Several values are returned by the adapt method

Several important results are returned by the adapt method.  These results are encapsulated in an object of the class AdaptiveResult.  This class is a wrapper class that is designed to encapsulate the adaptive results in a single object.

Frequency response and conventional spectrum

Now that the adaptive process has been completed for this iteration, the next major task is to compute and to display the frequency response of the whitening filter and the conventional spectrum of the raw data at the end of every 75th adaptive iteration.

Construct the whitening filter

As mentioned earlier, the actual whitening process is performed inside the adapt method.  A copy of the whitening filter is not returned by the adapt method.  However, a copy of the prediction filter is returned by the adapt method.  The whitening filter is simply the prediction filter with an extra coefficient having a value of -1 concatenated onto its end.

An array for storage of the whitening filter was constructed in Listing 4.  The value of -1 was also deposited into the last element in that array in Listing 4.  Listing 13 constructs and saves the whitening filter by copying the coefficients from the prediction filter that was returned by the adapt method into the lower elements of the whitening filter array, leaving the value of -1 in the last element.

      System.arraycopy(result.filterArray,
                       0,
                       whiteningFilter,
                       0,
                       filterLength);

Listing 13

(While performing the final edit on this lesson, I realized that I should have moved the statement in Listing 13 inside of the if statement in Listing 14 to conserve computer resources.  The new whitening filter needs to be constructed only when the frequency response of the filter is to be computed and displayed.)

Compute and display frequency data

Listing 14 contains an if statement that causes frequency data to be computed and displayed at the end of every 75th adaptive iteration, as shown in Figure 7.

      if(cnt%75 == 0){
        //Compute and display the frequency response of the
        // whitening filter.
        displayFreqResponse(whiteningFilter,
                            freqPlotObj,
                            spectralWidth,
                            whiteningFilter.length - 1);

        //Compute and display the conventional spectrum of
        // a chunk of raw signal plus noise data.
        displaySpectrum(rawData,
                        conventionalPlotObj,
                        spectralWidth,
                        rawData.length - 1);
      }//End display of frequency data
    }//End for loop, End adaptive process

Listing 14

The invocation of the displayFreqResponse method in Listing 14 computes and displays the amplitude response of the whitening filter as shown in the left panel of Figure 7.

The invocation of the displaySpectrum method in Listing 14 computes and displays the conventional amplitude spectrum for comparison purposes as shown in the right panel of Figure 7.

I will have more to say about these two methods later.

Display the results

Listing 14 also signals the end of the for loop that began in Listing 9.  Once the code in Listing 14 has been executed, all that remains is to cause the graphic data that has been stored in the plotting objects to be displayed on the screen.  This is accomplished by the code in Listing 15.

    freqPlotObj.plotData(0,0);
    conventionalPlotObj.plotData(232,0);
    
  }//end process method

Listing 15

Listing 15 also signals the end of the process method and the end of the program.

Computing and displaying frequency data

Listing 14 above invokes the displayFreqResponse method and the displaySpectrum method to cause the frequency data to be displayed.

The purpose of the displayFreqResponse method is to compute and display the amplitude frequency response of an incoming whitening filter.

Partially explained in an earlier lesson

A method very similar to and having the same name as the displayFreqResponse method was explained in the lesson entitled An Adaptive Whitening Filter in Java.  Therefore, I am going to begin by referring you back to that lesson for an explanation of the overall method.

You can view the new version of displayFreqResponse used in this lesson in Listing 20 near the end of the lesson.  This explanation will pick up at the point in the method where the version of the method used in this lesson differs from the version of the method used in the lesson entitled An Adaptive Whitening Filter in Java.

Primary differences

The primary difference between the two versions of the method has to do with the final formatting of the frequency response curve for display.  If you refer back to Figure 3 in the lesson entitled An Adaptive Whitening Filter in Java, you will see that the locations of spectral lines in the spectrum were indicated by deep notches in the amplitude response curve.  (That lesson also displayed phase response information, which is not of interest in this lesson.)

If you refer back to Figure 1 in this lesson, you will see that the presence of narrow-band signal is indicated by a peak at the signal frequency in the display of the response.  This is accomplished by turning the response curve upside down and then normalizing it.

Code in the displayFreqResponse method

The code in Listing 16 picks up at the point where the code differs from the code in the previously-explained version of the method.

Listing 16 changes the algebraic sign on all the amplitude response values to turn the response curve upside down.  This causes the notches in the response curve to appear as peaks in the display as shown in Figure 1.

    //Note that from this point forward, all references to
    // magnitude are referring to log base 10 data, which
    // can be thought of as scaled decibels.
    
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      magnitude[cnt] = -magnitude[cnt];
    }//end for loop

Listing 16

Was this necessary?

Obviously it wasn't necessary to turn the notches into peaks.  However, this results in a display that is more in line with what we are accustomed to seeing.  We tend to expect the presence of energy to be represented by a peak in the spectrum and that is the information that we are conveying here.

Normalization of the data

The effective display of a large amount of data is not an easy thing to accomplish.  A typical display medium normally provides a fixed amount of space in which to display the data.  If the data values are large, excursions in the display will typically be large, and may even exceed the allowable space.

On the other hand, if the data values are small, excursions in the display will often be very small and possibly not even visible.  A lot of work is usually required to make it possible to display data having a wide variety of values in a limited space in a meaningful way.

The purpose of much of the following code is to normalize the amplitude response such that each response curve will occupy the same size rectangle in the display regardless of the magnitude of the input data.  (See Figure 1 as an example of this normalization.)

Make the smallest value equal to zero

The code in Listing 17 is executed once for each frequency response curve that is to be displayed.  We begin by biasing the values in the frequency response such that the smallest value becomes zero.

    //First find the smallest value.
    double min = 9999999999.0;
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      if(magnitude[cnt] < min){
        min = magnitude[cnt];
      }//end if
    }//end for loop
    //Now apply the bias.
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      magnitude[cnt] -= min;
    }//end for loop

Listing 17

Once you know the purpose of the code in Listing 17, the behavior of the code is straightforward and should not require further explanation.

Normalize the peak value

The code in Listing 18 performs the following steps:

  • Find and save the absolute peak value for the response curve
  • Set the value at zero frequency to the peak value for the response curve.
  • Scale all the values in the response curve to cause the peak value to end up with a vale of 20.  This causes each response curve to occupy overlapping rectangular strips in the final plot (see Figure 1), each of which has a vertical dimension of 20 pixels.

    //Find the absolute peak value.  Begin with a negative
    // trial value with a large magnitude and replace it
    // with the largest magnitude value.
    double peak = -9999999999.0;
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      if(peak < abs(magnitude[cnt])){
        peak = abs(magnitude[cnt]);
      }//end if
    }//end for loop
    
    //Set the zero frequency value to the peak so that it
    // can be used to visually confirm plot synchronization
    // later.
    magnitude[0] = peak;

    //Normalize to 20 times the peak value. Each response
    // curve will now occupy a horizontal strip of the
    // plotting area that is 20 pixels from top to bottom.
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      magnitude[cnt] = 20*magnitude[cnt]/peak;
    }//end for loop

Listing 18

The value at zero frequency

Typically the value of a frequency response curve at a frequency of zero is of little or no interest.

(Signals in the real world don't usually exist at a frequency of zero, unless you are measuring the output from a battery or direct-current generator.  A non-zero value at zero frequency usually indicates an undesirable electronic bias in the acquisition of the sampled data.)

Therefore, the values of the response curves at zero frequency were used to solve a potential plotting-alignment problem.

Alignment can be difficult

It can be a little difficult to get all of the parameters set correctly in the use of the class named PlotALot08 to cause the plots to be properly aligned with each response curve correctly placed above the one below it.  Therefore, the value of the response at zero frequency was artificially set to the peak value to make it visually obvious if the plot is not properly aligned.  When the plot is properly aligned, those values all line up vertically on the left side of the plot as shown in Figure 1.

Even without the markers at zero frequency, it would be rather obvious if the plots were not properly aligned in Figure 1.  However, that would not be the case for the right panel in Figure 8 or the right panel in Figure 10.  Therefore, this marker at zero frequency can be very helpful in providing assurance that the spectral data is being properly displayed.

Feed the plotting object

The code in Listing 19 feeds the normalized frequency response data to the plotting object.  Note that this data was converted to decibels in that portion of the method that was explained in the earlier lesson entitled An Adaptive Whitening Filter in Java.

    for(int cnt = 0;cnt < magnitude.length;cnt++){
      plot.feedData(magnitude[cnt]);
    }//end for loop
    
  }//end displayFreqResponse

Listing 19

Listing 19 also signals the end of the method named displayFreqResponse.

The method named displaySpectrum

The method named displaySpectrum is used to compute and to display the results of the conventional spectrum analysis that is performed on the raw signal plus noise data.  You can view the method in its entirety in Listing 20 near the end of the lesson.

The code in this method is similar to, but is not identical to the code in the method named displayFreqResponse that was explained above.

The major differences between the two methods are:

  • The conversion to decibels is disabled in the method named displaySpectrum.  However, the required code to convert the spectral data to decibels is still there so that you can re-enable it to see the results of conversion of spectral data to decibels if you wish to do so.
  • The spectral data values are not turned upside down in the method named displaySpectrum.  Thus peaks in the spectral energy are displayed as peaks in the plot, as shown in the right panels of Figure 8 and Figure 10.

The method named displaySpectrum shown in Listing 20 is completely documented through the use of in-code comments.  Therefore, further explanation of the method should not be necessary.

Run the Program

I encourage you to copy the code from the classes in the section entitled Complete Program Listings.  Compile and execute the programs.  Experiment with the code.  Make changes to the code, recompile, execute, and observe the results of your changes.

In addition to the classes named Adapt05 and PlotALot08 (for which the source code is provided in this lesson), you will need access to the following classes.  The source code for these classes can be found in the lessons indicated.

Summary

In this lesson, I showed you how to use the general-purpose LMS adaptive engine from a previous lesson to write an adaptive line tracker in Java.

What's Next?

Future lessons in this series will become somewhat more general.  I plan to publish lessons that explain and provide examples of four common scenarios in which adaptive filtering is used:

  • System Identification
  • Inverse System Identification
  • Noise Cancellation
  • Prediction

Somewhere along the way I may also publish a lesson that explains and illustrates the difference between least mean square (LMS) and recursive least squares (RLS) adaptive algorithms.

References

In preparation for understanding the material in this lesson, I recommend that you study the material in the following previously-published lessons:

  • 100   Periodic Motion and Sinusoids
  • 104   Sampled Time Series
  • 108   Averaging Time Series
  • 1478 Fun with Java, How and Why Spectral Analysis Works
  • 1482 Spectrum Analysis using Java, Sampling Frequency, Folding Frequency, and the FFT Algorithm
  • 1483 Spectrum Analysis using Java, Frequency Resolution versus Data Length
  • 1484 Spectrum Analysis using Java, Complex Spectrum and Phase Angle
  • 1485 Spectrum Analysis using Java, Forward and Inverse Transforms, Filtering in the Frequency Domain
  • 1487 Convolution and Frequency Filtering in Java
  • 1488 Convolution and Matched Filtering in Java
  • 1492 Plotting Large Quantities of Data using Java
  • 2350 Adaptive Filtering in Java, Getting Started
  • 2352 An Adaptive Whitening Filter in Java
  • 2354 A General-Purpose LMS Adaptive Engine in Java




Page 2 of 3



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel