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

An Adaptive Line Tracker in Java, Page 3

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

Complete Program Listings

Complete listings of the classes discussed in this lesson are shown in the listings below.

/*File Adapt05.java
Copyright 2005, R.G.Baldwin

The purpose of the program is to use the general purpose 
adaptive engine provided by AdaptEngine01 to implement an 
adaptive spectral line tracker. The line tracker is 
designed to track the frequency of frequency-modulated 
signals buried in wide-band noise. Adaptive processing 
takes place in the time domain.  The signals are tracked 
in the frequency domain. Experimental results produced by
the adaptive line tracker are compared with results 
produced by conventional spectrum analysis.

The program develops a whitening filter and uses the 
notches in the whitening filter as an indication of the 
frequency of the FM signals at equal intervals in time.

Demonstration of the program capability is accomplished by 
processing time series consiting of wide-band noise plus FM
signals.

Three different experimental cases can be specified by the 
user:

1. A single FM sweep from a low frequency to a high 
frequency. The user specifies the rate at which the signal
sweeps.

2. A single FM signal that switches back and forth between 
two frequencies.  The user specifies each of the 
frequencies.

3. An additive combination of the two cases described
above.

User input is provided by way of command-line parameters.
The command-line parameters are:

double feedbackGain: This is the multiplicative factor that
is used in the feedback loop of the LMS adaptive algorithm.

int numberIterations: This is the number of iterations that
the adaptive algorithm is allowed to execute before
terminating and displaying the results.

int filterLength: This is the length of the filter that is 
developed within the adaptive algorithm. Note that this
length is one less than the length of the whitening filter 
mentioned above.  The whitening filter consists of this
filter with a -1 concatenated onto its end.

double wideBandNoiseScale: This is a scale factor that is 
applied to the wide band noise before it is added to the
FM signal.

double fmSignalScale: This is a scale factor that is 
applied to each FM signal before it is added to the 
wide-band noise.

int fmSignalCase: This the test case described above.  Must
be 1, 2, or 3.

double freqSlideConst: This value specifies the rate at 
which the FM sweep signal changes frequency. The higher the
value of this parameter, the faster will be the change in 
frequency.

double freqShiftFactorLow: The base frequency for the 
frequency switching signal is one-fourth of the sampling
frequency. This multiplicative factor is applied to the
base frequency to establish the low frequency for the 
frequency-switching signal.  For example, a value of 0.5 
for this parameter results in a frequency that is 
one-eighth of the sampling frequency.

double freqShiftFactorHigh: This parameter is applied as a 
multiplicative factor to the base frequency to establish 
the upper frequency for the frequency shifting signal.

int lengthMultiplier:  This parameter specifies the length 
of the chunks of data that will be analyzed using
conventional spectral analysis.  This value is a multiple 
of the length of the whitening filter.

If the user doesn't provide ten command-line parameters,
a set of default values is used.  See the default-value
comments in the code for an indication of the approximate 
values that might be appropriate for any particular 
parameter.

The program puts a marker at zero frequency in each 
spectral plot. This makes it possible to visually confirm
that the spectral plots are properly synchronized, with one
spectral plot above the other.

Tested using J2SE 5.0 and WinXP.  J2SE 5.0 or later is
required.
**********************************************************/
import static java.lang.Math.*;//J2SE 5.0 req

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
      
    //Instantiate a new object of the Adapt05 class and
    // invoke the method named process on that object.
    new Adapt05().process(feedbackGain,
                          numberIterations,
                          filterLength,
                          wideBandNoiseScale,
                          fmSignalScale,
                          fmSignalCase,
                          freqSlideConst,
                          freqShiftFactorLow,
                          freqShiftFactorHigh,
                          spectralWidth,
                          lengthMultiplier);
  }//end main
  //-----------------------------------------------------//
  
  //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.
  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;

    //Create an array 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.
    double[] whiteningFilter = 
                              new double[filterLength + 1];
    //Set the last coefficient value in the whitening
    // filter to -1. All other values are initialized to
    // zero. Coefficient values returned by the adaptive
    // process will be copied into the lower elements of
    // thewhitening filter later.
    whiteningFilter[filterLength] = -1;
    
    //Create an array to contain two samples of the data to
    // be adaptively processed.  This array is used as a
    // tapped delay line. The data sample to be filtered is
    // located at index 0.  The value of the adaptive
    // target is located at index 1.
    double[] data = new double[2];
    
    //Create an array to serve as a delay line to contain a
    // chunk of raw data that will be used for conventional
    // spectral analysis. Make the length of the data an
    // integer multiple of the length of the whitening
    // filter.
    double[] rawData = new double[
                  lengthMultiplier*whiteningFilter.length];
    
    //Instantiate a general purpose adaptive processing
    // object. This object provides the adaptive behavior
    // for the entire program.
    AdaptEngine01 theAdapter = 
              new AdaptEngine01(filterLength,feedbackGain);

    //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);
                                   
    //Perform the specified number of iterations
    for(int cnt = 0;cnt < numberIterations;cnt++){
      //Generate the synthetic wideBandNoise and fmSignal
      // data.
      
      //Get the next sample of wideBandNoise with a
      // uniform distribution from -1.0 to +1.0.  Scale the
      // noise by the specified scale factor. Note the use
      // of a static import directive for the Math class,
      // which requires J2SE 5.0 or later.
      wideBandNoise = 
                 wideBandNoiseScale*(2.0*(random() - 0.5));
      
      //Get the next sample of fmSignal data. The contents
      // of the following variable set 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
      
      //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);
      
      //Execute the adaptive whitening process. Pass the
      // two samples to the adapt method, one as the data
      // to be filtered and the other as the predictive
      // target. This one statement is responsible for all
      // of the adaptive behavior of the program.
      AdaptiveResult result = 
                         theAdapter.adapt(data[0],data[1]);

      //Compute and plot the frequency response and the
      // conventional spectrum every 75 iterations.
      
      //Construct the whitening filter by copying the
      // prediction filter that was returned by the
      // adapter 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);      
      
      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
    
    //Cause all the data to be plotted.
    freqPlotObj.plotData(0,0);
    conventionalPlotObj.plotData(232,0);
    
  }//end process method
  //-----------------------------------------------------//

  //This method simulates a tapped delay line. It receives
  // a reference to an array and a value.  It discards the
  // value at index 0 of the array, moves all the other
  // values by one element toward 0, and inserts the new
  // value at the top of the array.
  static void flowLine(double[] line,double val){
    for(int cnt = 0;cnt < (line.length - 1);cnt++){
      line[cnt] = line[cnt+1];
    }//end for loop
    line[line.length - 1] = val;
  }//end flowLine
  //-----------------------------------------------------//
  
  //This method is used to compute and display the
  // amplitude frequency response of an incoming whitening
  // filter.
  void displayFreqResponse(
     double[] filter,PlotALot08 plot,int len,int zeroTime){

    //Create the arrays required by the Fourier Transform.
    double[] timeDataIn = new double[len];
    double[] realSpect = new double[len];
    double[] imagSpect = new double[len];
    double[] angle = new double[len];
    double[] magnitude = new double[len];
    
    //Copy the filter into the timeDataIn array
    System.arraycopy(filter,0,timeDataIn,0,filter.length);

    //Compute DFT of the filter from zero to the folding
    // frequency and save it in the output arrays.
    ForwardRealToComplex01.transform(timeDataIn,
                                     realSpect,
                                     imagSpect,
                                     angle,
                                     magnitude,
                                     zeroTime,
                                     0.0,
                                     0.5);

    //Display the magnitude data. Convert to normalized
    // decibels first.
    //Eliminate or change any values that are incompatible
    // with log10 method.
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      if((magnitude[cnt] == Double.NaN) || 
                                    (magnitude[cnt] <= 0)){
        //Replace the magnitude by a very small positive
        // value.
        magnitude[cnt] = 0.0000001;
      }else if(magnitude[cnt] == Double.POSITIVE_INFINITY){
        //Replace the magnitude by a very large positive
        // value.
        magnitude[cnt] = 9999999999.0;
      }//end else if
    }//end for loop
  
    //Now convert magnitude data to log base 10
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      magnitude[cnt] = log10(magnitude[cnt]);
    }//end for loop
    
    //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.
    
    //Change the algebraic sign on all the values to turn
    // the response curve upside down.  This causes the
    // notches in the response curve to appear as peaks in
    // the display.
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      magnitude[cnt] = -magnitude[cnt];
    }//end for loop

    //The purpose of much of the following code is to
    // normalize the amplitude response such that each
    // response curve will occupy the same size horizontal
    // strip in the plot regardless of the magnitude
    // of the input data.
    
    //Bias the values 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

    //Find the absolute peak value.  Begin with a negative
    // peak 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

    //Now feed the normalized decibel data to the plotting
    // system.
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      plot.feedData(magnitude[cnt]);
    }//end for loop
    
  }//end displayFreqResponse
  //-----------------------------------------------------//
  
  //This method is used to compute and display the
  // conventional amplitude spectrum of a chunk of incoming
  // data.
  //The code in this method is similar to, but not
  // identical to the code in the method named
  // displayFreqResponse.
  void displaySpectrum(
     double[] data,PlotALot08 plot,int len,int zeroTime){

    //Create the arrays required by the Fourier Transform.
    double[] timeDataIn = new double[len];
    double[] realSpect = new double[len];
    double[] imagSpect = new double[len];
    double[] angle = new double[len];
    double[] magnitude = new double[len];
    
    //Copy the data into the timeDataIn array
    System.arraycopy(data,0,timeDataIn,0,data.length);

    //Compute DFT of the data from zero to the folding
    // frequency and save it in the output arrays.
    ForwardRealToComplex01.transform(timeDataIn,
                                     realSpect,
                                     imagSpect,
                                     angle,
                                     magnitude,
                                     zeroTime,
                                     0.0,
                                     0.5);

    //Display the magnitude data. Note that conversion to
    // decibels has been disabled in this version of the
    // method. Enable the following code to re-enable
    // the conversion to decibels.
/*
    //Eliminate or change any values that are incompatible
    // with log10 method.
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      if((magnitude[cnt] == Double.NaN) || 
                                    (magnitude[cnt] <= 0)){
        //Replace the magnitude by a very small positive
        // value.
        magnitude[cnt] = 0.0000001;
      }else if(magnitude[cnt] == Double.POSITIVE_INFINITY){
        //Replace the magnitude by a very large positive
        // value.
        magnitude[cnt] = 9999999999.0;
      }//end else if
    }//end for loop
  
    //Now convert magnitude data to log base 10
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      magnitude[cnt] = log10(magnitude[cnt]);
    }//end for loop
*/

    //These spectral results are normalized the same way
    // the frequency response curves are normalized as
    // described in the method named displayFreqResponse.
    //Bias the values 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

    //Find the absolute peak value.  Begin with a negative
    // peak 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 confirm plot synchronization later.
    magnitude[0] = peak;

    //Normalize to 20 times the peak value.
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      magnitude[cnt] = 20*magnitude[cnt]/peak;
    }//end for loop

    //Now feed the normalized data to the plotting
    // system.
    for(int cnt = 0;cnt < magnitude.length;cnt++){
      plot.feedData(magnitude[cnt]);
    }//end for loop
    
  }//end displaySpectrum
  //-----------------------------------------------------//
}//end class Adapt05
//=======================================================//

Listing 20

 

/*File PlotALot08.java 
Copyright 2005, R.G.Baldwin
This is an update to the program named PlotALot01.
The purpose of this update is to eliminate the
drawing of the horizontal axes on the plot.
Otherwise, it is identical to PlotALot01.

This program is designed to plot large amounts of
time-series data for a single channel.  See
PlotALot02.java for a two-channel program.

Note that by carefully adjusting the plotting
parameters, this program could also be used to
plot large quantities of spectral data in a
waterfall display.

The class provides a main method so that the
class can be run as an application to test
itself.

There are three steps involved in the use of this
class for plotting time series data:
1. Instantiate a plotting object of type 
   PlotALot08 using one of two overloaded 
   constructors.
2. Feed data that is to be plotted to the 
   plotting object by invoking the feedData 
   method once for each data value.
3. Invoke one of two overloaded plotData methods 
   on the plotting object once all of the data 
   has been fed to the object.  This causes all
   of the data to be plotted.
   
A using program can instantiate as many 
plotting objects as are needed to plot all of the
different time series that need to be plotted.
Each plotting object can be used to plot as many
data values as need be plotted until the program
runs out of available memory.

The plotting object of type PlotALot08 owns one 
or more Page objects that extend the Frame class.
The plotting object can own as many Page objects 
as are necessary to plot all of the data that is 
fed to that plotting object.

The program produces a graphic output consisting 
of a stack of Page objects on the screen, with 
the data plotted on a Canvas object contained by 
the Page object.  The Page showing the earliest 
data is on the top of the stack and the Page 
showing the latest data is on the bottom of the 
stack.  The Page objects on the top of the stack 
must be physically moved in order to see the 
Page objects on the bottom of the stack.

Each Page object contains one or more horizontal 
axes on which the data is plotted.  The earliest 
data is plotted on the axis nearest the top of 
the Page moving from left to right across the 
axis.  Positive data values are plotted above
the axis and negative values are plotted below
the axis.  When the right end of an axis is 
reached, the next data value is plotted on the 
left end of the axis immediately below it.  When 
the right end of the last axis on the Page is 
reached, a new Page object is created and the 
next data value is plotted at the left end of the
top axis on that Page object.

A mentioned above, there are two overloaded 
versions of the constructor for the PlotALot08
class. One overloaded version accepts several 
incoming parameters allowing the user to control
various aspects of the plotting format. A second 
overloaded version accepts a title string only 
and sets all of the plotting parameters to 
default values. You can easily modify these
default values and recompile the class if you
prefer different default values.

The parameters for the version of the constructor
that accepts plotting format information are:

String title: Title for the Frame object. This
 title is concatenated with the page number and 
 the result appears in the banner at the top of 
 the Frame.
int frameWidth:The Frame width in pixels.
int frameHeight: The Frame height in pixels.
int traceSpacing: Distance between trace axes in
 pixels.
int sampSpace: Number of pixels dedicated to each
 data sample in pixels per sample.  Must be 1 or
 greater.
int ovalWidth: Width of an oval that is used to 
 mark the sample value on the plot.
int ovalHeight: Height of an oval that is used to
 mark the sample value on the plot.

For test purposes, the main method instantiates 
and feeds two independent plotting objects. 
Plotting parameters are specified for the first 
plotting object. Default plotting parameters are 
accepted for the second plotting object.
 
The data that is fed to each plotting object is 
white random noise. However, for the first
plotting object, fifteen of the data values are 
not random.  Rather, seven of the values are set
to values of 0,0,25,-25,25,0,0 to confirm the 
proper transition from the end of one page to the
beginning of the next page. In addition, eight of
the values are set to 0,0,20,20,-20,-20,0,0 in
order to confirm the proper transition from one 
trace to the next trace on the same page.

These specific values and the locations in the 
data where they are placed provide visible 
confirmation that the transitions mentioned above
are handled correctly. Note, however that these 
are the correct locations for an AWT Frame object
under WinXP. A Frame may have different inset 
values under other operating systems, which may 
cause these specific locations to be incorrect 
for that operating system.  In that case, the 
values will be plotted but they won't confirm 
the proper transition.

The following information about the plotting 
parameters for each plotting object is displayed 
on the command line screen when the class is used
for plotting.  The values shown below result from
the execution of the main method of the class for
test purposes. One of the plotting objects 
instantiated by the main method is entitled "A" 
and the other is entitled "B".

Title: A
Frame width: 158
Frame height: 237
Page width: 150
Page height: 210
Trace spacing: 36
Sample spacing: 5
Traces per page: 5
Samples per page: 150

Title: B
Frame width: 400
Frame height: 410
Page width: 392
Page height: 383
Trace spacing: 50
Sample spacing: 2
Traces per page: 7
Samples per page: 1372

There are two overloaded versions of the plotData
method. One version allows the user to specify 
the location on the screen where the stack of 
plotted pages will appear. This version requires 
two parameters, which are coordinate values in 
pixels.  The first parameter specifies the 
horizontal coordinate of the upper left corner of
the stack of pages relative to the upper left 
corner of the screen.  The second parameter 
specifies the vertical coordinate of the upper 
left corner of the stack of pages relative to the
upper left corner of the screen. Specifying 
coordinate values of 0,0 causes the stack to be 
located in the upper left corner of the screen.  

The other overloaded version of plotData places 
the stack of pages in the upper left corner of 
the screen by default.
 
Each page has a WindowListener that will 
terminate the program if the user clicks the 
close button on the Frame.

The program was tested using J2SE 5.0 and WinXP.
Requires J2SE 5.0 to support generics.
************************************************/

import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class PlotALot08{
  //This main method is provided so that the
  // class can be run as an application to test
  // itself.
  public static void main(String[] args){
    //Instantiate two independent plotting
    // objects.  Control plotting parameters for
    // the first object.  Accept default plotting
    // parameters for the second object.
    PlotALot08 plotObjectA = 
            new PlotALot08("A",158,237,36,5,4,4);
    PlotALot08 plotObjectB = new PlotALot08("B");
    
    //Feed the data to the first plotting object.
    for(int cnt = 0;cnt < 275;cnt++){
      //Plot some white random noise in the first
      // object using specified plotting
      // parameters. Note, that fifteen of the
      // following values are not random.  Seven
      // values are set to 0,0,25,-25,25,0,0
      // specifically to confirm the proper
      // transition from the end of one page to
      // the beginning of the next page.  Eight
      // values are set to 0,0,20,20,-20,-20,0,0
      // to confirm the proper transition from
      // one trace to the next trace on the same
      // page.  Note that these are the correct
      // values for an AWT Frame object under
      // WinXP.  However, a Frame may have 
      // different inset values on other
      // operating systems, which may cause these
      // specific values to be incorrect.
      if(cnt == 147){
        plotObjectA.feedData(0);
      }else if(cnt == 148){
        plotObjectA.feedData(0);
      }else if(cnt == 149){
        plotObjectA.feedData(25);
      }else if(cnt == 150){
        plotObjectA.feedData(-25);
      }else if(cnt == 151){
        plotObjectA.feedData(25);
      }else if(cnt == 152){
        plotObjectA.feedData(0);
      }else if(cnt == 153){
        plotObjectA.feedData(0);
      }else if(cnt == 26){
        plotObjectA.feedData(0);
      }else if(cnt == 27){
        plotObjectA.feedData(0);
      }else if(cnt == 28){
        plotObjectA.feedData(20);
      }else if(cnt == 29){
        plotObjectA.feedData(20);
      }else if(cnt == 30){
        plotObjectA.feedData(-20);
      }else if(cnt == 31){
        plotObjectA.feedData(-20);
      }else if(cnt == 32){
        plotObjectA.feedData(0);
      }else if(cnt == 33){
        plotObjectA.feedData(0);
      }else{
        plotObjectA.feedData(
                       (Math.random() - 0.5)*25);
      }//end else
    }//end for loop
    //Cause the data to be plotted.
    plotObjectA.plotData(401,0);
    
    //Plot white random noise in the second
    // plotting object using default plotting
    // parameters.
    //Feed the data to the second plotting
    // object.
    for(int cnt = 0;cnt < 2600;cnt++){
      plotObjectB.feedData(
                       (Math.random() - 0.5)*25);
    }//end for loop
    //Cause the data to be plotted.
    plotObjectB.plotData();
    
  }//end main
  //-------------------------------------------//

  String title;
  int frameWidth;
  int frameHeight;
  int traceSpacing;//pixels between traces
  int sampSpacing;//pixels between samples
  int ovalWidth;//width of sample marking oval
  int ovalHeight;//height of sample marking oval
  
  int tracesPerPage;
  int samplesPerPage;
  int pageCounter = 0;
  int sampleCounter = 0;
  ArrayList <Page> pageLinks = 
                           new ArrayList<Page>();
  
  //There are two overloaded versions of the
  // constructor for this class.  This
  // overloaded version accepts several incoming
  // parameters allowing the user to control
  // various aspects of the plotting format. A
  // different overloaded version accepts a title
  // string only and sets all of the plotting
  // parameters to default values.
  PlotALot08(String title,//Frame title
             int frameWidth,//in pixels
             int frameHeight,//in pixels
             int traceSpacing,//in pixels
             int sampSpace,//in pixels per sample
             int ovalWidth,//sample marker width
             int ovalHeight)//sample marker hite
  {//constructor
    //Specify sampSpace as pixels per sample.
    // Should never be less than 1.  Convert to
    // pixels between samples for purposes of
    // computation.
    this.title = title;
    this.frameWidth = frameWidth;
    this.frameHeight = frameHeight;
    this.traceSpacing = traceSpacing;
    //Convert to pixels between samples.
    this.sampSpacing = sampSpace - 1;
    this.ovalWidth = ovalWidth;
    this.ovalHeight = ovalHeight;

    //The following object is instantiated solely
    // to provide information about the width and
    // height of the canvas. This information is
    // used to compute a variety of other
    // important values.
    Page tempPage = new Page(title);
    int canvasWidth = tempPage.canvas.getWidth();
    int canvasHeight = 
                     tempPage.canvas.getHeight();
    //Display information about this plotting
    // object.
    System.out.println("nTitle: " + title);
    System.out.println(
          "Frame width: " + tempPage.getWidth());
    System.out.println(
        "Frame height: " + tempPage.getHeight());
    System.out.println(
                   "Page width: " + canvasWidth);
    System.out.println(
                 "Page height: " + canvasHeight);
    System.out.println(
               "Trace spacing: " + traceSpacing);
    System.out.println(
         "Sample spacing: " + (sampSpacing + 1));
    if(sampSpacing < 0){
      System.out.println("Terminating");
      System.exit(0);
    }//end if
    //Get rid of this temporary page.
    tempPage.dispose();
    //Now compute the remaining important values.
    tracesPerPage = 
                 (canvasHeight - traceSpacing/2)/
                                    traceSpacing;
    System.out.println("Traces per page: "
                                + tracesPerPage);
    if(tracesPerPage == 0){
      System.out.println("Terminating program");
      System.exit(0);
    }//end if
    samplesPerPage = canvasWidth * tracesPerPage/
                               (sampSpacing + 1);
    System.out.println("Samples per page: "
                               + samplesPerPage);
    //Now instantiate the first usable Page
    // object and store its reference in the
    // list.
    pageLinks.add(new Page(title));
  }//end constructor
  //-------------------------------------------//
  
  PlotALot08(String title){
    //Invoke the other overloaded constructor
    // passing default values for all but the
    // title.
    this(title,400,410,50,2,2,2);
  }//end overloaded constructor
  //-------------------------------------------//
  
  //Invoke this method for each point to be
  // plotted.
  void feedData(double val){
    if((sampleCounter) == samplesPerPage){
      //if the page is full, increment the page
      // counter, create a new empty page, and
      // reset the sample counter.
      pageCounter++;
      sampleCounter = 0;
      pageLinks.add(new Page(title));
    }//end if
    //Store the sample value in the MyCanvas
    // object to be used later to paint the
    // screen.  Then increment the sample
    // counter.  The sample value passes through
    // the page object into the current MyCanvas
    // object.
    pageLinks.get(pageCounter).putData(
                              val,sampleCounter);
    sampleCounter++;
  }//end feedData
  //-------------------------------------------//
  
  //There are two overloaded versions of the
  // plotData method.  One version allows the
  // user to specify the location on the screen
  // where the stack of plotted pages will
  // appear.  The other version places the stack
  // in the upper left corner of the screen.
  
  //Invoke one of the overloaded versions of
  // this method once when all of the data has
  // been fed to the plotting object in order to
  // rearrange the order of the pages with
  // page 0 at the top of the stack on the
  // screen.
  
  //For this overloaded version, specify xCoor
  // and yCoor to control the location of the
  // stack on the screen.  Values of 0,0 will
  // place the stack at the upper left corner of
  // the screen.  Also see the other overloaded
  // version, which places the stack at the upper
  // left corner of the screen by default.
  void plotData(int xCoor,int yCoor){
    Page lastPage = 
             pageLinks.get(pageLinks.size() - 1);
    //Delay until last page becomes visible.
    while(!lastPage.isVisible()){
      //Loop until last page becomes visible
    }//end while loop
    
    Page tempPage = null;
    //Make all pages invisible
    for(int cnt = 0;cnt < (pageLinks.size());
                                          cnt++){
      tempPage = pageLinks.get(cnt);
      tempPage.setVisible(false);
    }//end for loop
    
    //Now make all pages visible in reverse order
    // so that page 0 will be on top of the
    // stack on the screen.
    for(int cnt = pageLinks.size() - 1;cnt >= 0;
                                          cnt--){
      tempPage = pageLinks.get(cnt);
      tempPage.setLocation(xCoor,yCoor);
      tempPage.setVisible(true);
    }//end for loop

  }//end plotData(int xCoor,int yCoor)
  //-------------------------------------------//
  
  //This overloaded version of the method causes
  // the stack to be located in the upper left
  // corner of the screen by default
  void plotData(){
    plotData(0,0);//invoke overloaded version
  }//end plotData()
  //-------------------------------------------//

  //Inner class.  A PlotALot08 object may
  // have as many Page objects as are required
  // to plot all of the data values.  The 
  // reference to each Page object is stored
  // in an ArrayList object belonging to the
  // PlotALot08 object.
  class Page extends Frame{
    MyCanvas canvas;
    int sampleCounter;

    Page(String title){//constructor
      canvas = new MyCanvas();
      add(canvas);
      setSize(frameWidth,frameHeight);    
      setTitle(title + " Page: " + pageCounter);
      setVisible(true);
      
      //---------------------------------------//
      //Anonymous inner class to terminate the
      // program when the user clicks the close
      // button on the Frame.
      addWindowListener(
        new WindowAdapter(){
          public void windowClosing(
                                  WindowEvent e){
            System.exit(0);//terminate program
          }//end windowClosing()
        }//end WindowAdapter
      );//end addWindowListener
      //---------------------------------------//
    }//end constructor
    //=========================================//
  
    //This method receives a sample value of type
    // double and stores it in an array object
    // belonging to the MyCanvas object.
    void putData(double sampleValue,
                 int sampleCounter){
      canvas.data[sampleCounter] = sampleValue;
      //Save the sample counter in an instance
      // variable to make it available to the
      // overridden paint method. This value is
      // needed by the paint method so it will
      // know how many samples to plot on the
      // final page which probably won't be full.
      this.sampleCounter = sampleCounter;
    }//end putData
    
    //=========================================//
    //Inner class
    class MyCanvas extends Canvas{
      double [] data = 
                      new double[samplesPerPage];
      
      //Override the paint method
      public void paint(Graphics g){
/*
Eliminate the drawing of horizontal axes for this
version of the program.
        //Draw horizontal axes, one for each
        // trace.
        for(int cnt = 0;cnt < tracesPerPage;
                                          cnt++){
          g.drawLine(0,
                     (cnt+1)*traceSpacing,
                     this.getWidth(),
                     (cnt+1)*traceSpacing);
        }//end for loop
*/
        //Plot the points if there are any to be
        // plotted.
        if(sampleCounter > 0){
          for(int cnt = 0;cnt <= sampleCounter;
                                          cnt++){
            //Compute a vertical offset to locate
            // the data on a particular trace.
            int yOffset = 
                   (1 + cnt*(sampSpacing + 1)/
                   this.getWidth())*traceSpacing;
            //Draw an oval centered on the sample
            // value to mark the sample.  It is 
            // best if the dimensions of the oval
            // are evenly divisable by 2 for 
            // centering purposes.
            //Reverse the sign on sample value to
            // cause positive sample values to go
            // up on the screen
            g.drawOval(cnt*(sampSpacing + 1)%
                   this.getWidth() - ovalWidth/2,
              yOffset - (int)data[cnt] 
                                  - ovalHeight/2,
              ovalWidth,
              ovalHeight);
            
            //Connect the sample values with
            // straight lines.  Do not draw a
            // line connecting the last sample in
            // one trace to the first sample in
            // the next trace.
            if(cnt*(sampSpacing + 1)%
                               this.getWidth() >=
                                sampSpacing + 1){
              g.drawLine(
                (cnt - 1)*(sampSpacing + 1)%
                                 this.getWidth(),
                yOffset - (int)data[cnt-1],
                cnt*(sampSpacing + 1)%
                                 this.getWidth(),
                yOffset - (int)data[cnt]);
            }//end if
          }//end for loop
        }//end if for sampleCounter > 0
      }//end overridden paint method
    }//end inner class MyCanvas
  }//end inner class Page
}//end class PlotALot08
//=============================================//


Listing 21

 


Copyright 2005, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP).  His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments.  (TI is still a world leader in DSP.)  In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

Baldwin@DickBaldwin.com





Page 3 of 3



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel