|
||||||||
| PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES | |||||||
See:
Description
| Interface Summary | |
| QcConfig | Gives controlets access to their context and their initialization parameters. |
| QcContext | Provides interfaces that allow QcControlets to interact with their container. |
| QcMetaData | Encapsulates seismic waveform meta data. |
| QcRequest | Encapsulates a measurement request. |
| QcResponse | Provides methods for recording QC measurements. |
| QcWaveform | QcWaveform encapsulates a seismic waveform signal from one channel for the time interval given by the request object passed to a controlet in its measureQuality() method. |
| Class Summary | |
| QcControlet | The abstract base class for all classes which measure waveform quality. |
| Exception Summary | |
| QcException | Thrown by controlets when the unexpected happens. |
The controlet package is exposed to Quality Control Applications so that they may interact with the Quack program.
Definition: a controlet is a class that subclasses
QcControlet. Controlets must, at a minimum,
implement the
measureQuality() method. Quack calls the
measureQuality() method when it needs to have a controlet
measure the quality of seismic waveform data.
Definition: a Quality Control Application - QCA is a jar file containing one or more controlets, their support classes and a meta-data file named qcapp.xml. The information contained in qcapp.xml exposes the controlet classes to Quack (simply extending QcControlet is not enough to expose a controlet to Quack). The Quack program can load multiple QCAs, each of which may contain multiple controlets. Each QCA is loaded by a seperate class loader. This allows Quack to dynamically load and unload QCAs at runtime and it also prevents class naming conflicts between QCAs.
Architecture
Quack is patterned after architectures such as
Java Web Server and Enterprise Application Server.
Java Web Server/Quack analogies:
Java Web Server |
Quack |
| WAR file | QCA file |
| Servlet | Controlet |
| web.xml | qcapp.xml |
At regular time intervals (for example once an hour), Quack invokes the
measureQuality() method of configured controlets. In the
measureQuality() method, controlets are passed
request and
response objects.
The request object contains information about which seismic channels need
to be sampled, over what time interval, and where the data can be found along
with meta information such as network/station/location/channel names, and
instrument responses.
The response object allows a controlet to record zero or more values for each measurement. For example, a single controlet could record the values of both mean and rms.
The response object has two methods:
The first method allows a measurement to be associated with an individual waveform, while the second method allows a measurement to be associated with multiple waveforms.
Note:Please do not confuse the response object with the seismic instrument response which maybe retrieved from QcWaveform.getRespData().
[Up]
[Up]
jar -tf sample_qca.jar META-INF/ META-INF/MANIFEST.MF edu/ edu/whatsamatau/ edu/whatsamatau/geophys/ edu/whatsamatau/geophys/util/ edu/whatsamatau/geophys/RmsControlet.class edu/whatsamatau/geophys/util/GMTDateFormater.class edu/whatsamatau/geophys/util/DataBuffer.class [More support classes....] xml/ xml/qcapp.xml
[Up]
<qc-app>
<qc-app-name>Sample QCA</qc-app-name>
<qc-app-majorversion>1</qc-app-majorversion>
<qc-app-minorversion>0</qc-app-minorversion>
<qc-app-author>Rocket J Squirrel</qc-app-author>
<qc-app-institution>Buena Vista</qc-app-institution>
<controlet>
<controlet-name>RMS</controlet-name>
<controlet-class>edu.whatsamatau.geophys.RmsControlet</controlet-class>
<controlet-type>1</controlet-type>
<init-param>
<!-- custom initialization parameter used by RmsControlet -->
<!-- see the init() method in RmsControlet.java -->
<param-name>VerboseDebug</param-name>
<param-value>false</param-value>
</init-param>
</controlet>
</qc-app>
[Up]
package edu.whatsamatau.geophys;
import edu.iris.dmc.qc.controlet.*;
import java.util.*;
import java.net.*;
import java.io.*;
// DataBuffer is used for parsing miniSEED data.
// The controlet API does no parsing of miniSEED data.
// For maximum flexibility, this task is left to the controlet implementation.
import edu.whatsamatau.geophys.util.DataBuffer;
// used for verbose debug message logging
import edu.whatsamatau.geophys.util.GMTDateFormater;
/**
* A controlet for calculating Mean and Variance of data.
* Data is returned in the floats hashtable:
*
* <table border="1">
*
* <tr> <td>Key</td> <td>Value </td> </tr>
* <tr> <td>1 </td> <td>Number of samples (N) </td> </tr>
* <tr> <td>2 </td> <td>Average </td> </tr>
* <tr> <td>3 </td> <td>Variance </td> </tr>
*
* </table>
*
*/
public class RmsControlet extends QcControlet {
// set to true to log verbose debug messages
private boolean verboseDebug = false;
// ---------------------------------------------------
// Optional method specified by base class QcControlet
// This method is called once before the controlet goes
// into service.
//
// In this example we use init() to turn logging on/off.
// The config object is inherited from QcControlet.
// We get VerboseDebug from the qcapp.xml file.
// ...
// <init-param>
// <param-name>VerboseDebug</param-name>
// <param-value>true</param-value>
// </init-param>
// ...
// ---------------------------------------------------
public void init() {
String s = config.getInitParameter("VerboseDebug");
if( s != null && s.equalsIgnoreCase("true") ) {
verboseDebug = true;
log("init(): verboseDebug enabled");
} else {
verboseDebug = false;
}
}
// --------------------------------------------
// This method must be implemented.
// measureQuality is specified by the abstract
// base class QcControlet
// --------------------------------------------
public void measureQuality(QcRequest request, QcResponse response) throws QcException {
// log a message indicating that we were called
if( verboseDebug )
log("RmsControlet.measureQuality()");
// loop over all of the waveforms. There will be one waveform for each
// channel that needs to be measured.
//
QcWaveform waveform[] = request.getWaveforms();
for( int i = 0; i < waveform.length; i++ ) {
//
// Call the base classe's stopMeasurement() method to see if we should
// stop. This allows the controlet to die gracefully.
// This is especially useful in the developement process;
// Quack cannot dynamically reload a QCA jar file if any of its
// controlets are busy.
//
// Depending on the controlet, you might want to
// make this call at more than one point in your code.
//
if( this.stopMeasurement() ) {
return;
}
if( verboseDebug ) {
log( "working on waveform[" + i + "]");
}
try {
//
// given the waveform, and the start and end times of the sample
// interval, estimate the avg and variance by calling our custom
// private class computeAvgRMS...
//
float[] est = computeAvgRMS(waveform[i], request.getStartTime(), request.getEndTime());
//
// Results are stored in this format:
//
// est[0] = number of samples
// est[1] = average
// est[2] = variance
//
// est == null if not data was found
//
if( est == null ) {
continue;
}
if( verboseDebug ) {
log( "N/avg/RMS=" + est[0] + "/" + est[1] + "/" + est[2] );
}
// Only floatValues will be populated....
Hashtable intValues = null;
Hashtable floatValues = new Hashtable();
Hashtable stringValues = null;
Hashtable booleanValues = null;
floatValues.put( new Integer(0), new Float(est[0]) ); // the number of samples
floatValues.put( new Integer(1), new Float(est[1]) ); // the avg
floatValues.put( new Integer(2), new Float(est[2]) ); // the rms
// pull the start and stop times out of the request
Date startTime = request.getStartTime();
Date stopTime = request.getEndTime();
//
// Have the response object store the results. This is where the
// results are sent to the data base
//
try {
response.setValues( waveform[i],
intValues, // empty
floatValues, // not empty
stringValues, // empty
booleanValues, // empty
startTime,
stopTime );
} catch( IllegalArgumentException e ) {
log("unexpected exception:" + e );
throw new QcException( "Unexpected IllegalArgumentException: " + e );
}
} catch( IOException e ) {
log("unexpected exception:" + e );
throw new QcException( "Unexpected IOException: " + e );
}
}
}
//------------------------------------------------------------
// compute the variance and Avg for the time range
// Note: for simplicity all times are rounded to milliseconds.
// MiniSEED time resolution is 0.1 millisecond.
//------------------------------------------------------------
private float[] computeAvgRMS(
QcWaveform waveform,
Date startTime,
Date stopTime
)
throws IOException, QcException {
//-----------------------------------------------------
//
// sum{ (xj - avg)^2 }
// Variance = -------------------
// (n - 1)
//
// sum{ xj^2 } - 2*avg*sum{ xj } + n*avg^2
// = ----------------------------------------
// n - 1
//
// Let sumA = sum{ xj^2 }
// Let sumB = sum{ xj } --> avg = sumB/n
//
// sumA - 2*sumB^2/n + n*sumB^2/(n*n)
// = ----------------------------------
// n - 1
//
// sumA - sumB^2/n
// = ---------------
// n - 1
//
// rms = squrt( variance )
//
//------------------------------------------------------
double rms = 0.0;
double avg = 0.0;
int n = 0;
double sumA = 0.0;
double sumB = 0.0;
//
// the url array contains, in sequence, the urls of all of the miniseed
// data that needs to be analyzed. The mini seed data is only for one
// channel. Note: The url's may point to none-existent data! This can
// happen if the url is generated, and then url file is deleted.
// A well written controlet should be able to handle this.
//
URL url[] = waveform.getMiniSeedURLs();
//
// if the url array is empty, return null
//
if( url.length == 0 ) {
log("no data to process, getMiniSeedURLs() returned empty array");
return null;
}
//
// The DataBuffer class is used to parse the miniseed data.
//
DataBuffer db = new DataBuffer(); // miniseed class
//
// Get the sample window in milliseconds since GMT 0:0:0 jan 1, 1970
//
long s1 = startTime.getTime(); // start of the sample interval
long s2 = stopTime.getTime(); // end of the sample interval
//
// Loop on all of the urls.
//
for( int i = 0; i < url.length; i++ ) {
if( verboseDebug ) {
log("working on url[" + i + "]=" + url[i]);
}
URLConnection uc;
InputStream in;
byte buffer[];
//---------------------------------------------
// get the record length and close the stream
//---------------------------------------------
uc = url[i].openConnection();
uc.connect();
in = uc.getInputStream();
buffer = new byte[512];
int readLength = in.read(buffer);
if( readLength != buffer.length ) {
log( "unable to read input stream, readLength=" + readLength);
in.close();
continue;
}
db.loadBuffer(buffer);
int buffsize = db.getDataRecLen();
in.close();
//--------------------------------------------
// Open the stream again and process
//--------------------------------------------
buffer = new byte[buffsize];
uc = url[i].openConnection();
uc.connect();
in = uc.getInputStream();
//--------------------------------------------------
// MiniSEED data is made up of records. Each record
// contains a variable number of sequential samples
// loop over records.
//--------------------------------------------------
while( in.read(buffer) == buffer.length ) {
db.loadBuffer(buffer);
if( db.getDataRecLen() != buffer.length ) {
log("bad record length!");
throw new QcException("bad record length != " + buffer.length );
}
//
// determine if the interval is within startTime and stopTime
//
//----------------------------------------------
// r1 - starttime of the record interval
// milliseconds since gmt 0:0:0 01/01/70
//----------------------------------------------
long r1 = db.getJavaDate().getTime();
//---------------------------------------------
// r2 - stoptime of the record interval
// milliseconds since gmt 0:0:0 01/01/70
//
// Note the divide by 10.
// This is to convert 0.1 msecs to msecs
//---------------------------------------------
double dRecLength = db.getRecInterval()/10.0;
long r2 = r1 + (long)dRecLength;
//-------------------------------------------------------
// Determine if the record interval (r1,r2) is touched by
// the sample interval (s1,s2)
//-------------------------------------------------------
boolean recordInSampleInterval =
// start of the sample interval is in the record interval
( r1 <= s1 && s1 <= r2 ) ||
// end of the sample interval is in the record interval
( r1 <= s2 && s2 <= r2 ) ||
// sample interval surrounds the record interval
( s1 <= r1 && r2 <= s2 ) ;
if( verboseDebug ) {
// verbose debug message
// diagnoistic debug stuff
String header;
if( recordInSampleInterval ) {
header = "\n-----record in sample interval------\n";
} else {
header = "\n-----record NOT in sample interval--\n";
}
String tmp =
header +
"s/c/n/l=" +
db.getStn() + "/" + db.getChn() + "/" + db.getNet() + "/" + db.getLoc() + "\n" +
" " + db.getYYYYDDD() + "\n" +
" Date= " + db.getDate() + "\n" +
" JavaDate=" + GMTDateFormater.format(db.getJavaDate()) + "\n" +
" samples=" + db.getNumSamples() +"\n" +
" RecLen=" + db.getDataRecLen() +"\n" +
" interval=" + db.getRecInterval() + "\n" +
" rate=" + db.getSampleRate() + "\n" +
" s1 = " + s1 + " " + GMTDateFormater.format(new Date(s1)) + "\n" +
" s2 = " + s2 + " " + GMTDateFormater.format(new Date(s2)) + "\n" +
" r1 = " + r1 + " " + GMTDateFormater.format(new Date(r1)) + "\n" +
" r2 = " + r2 + " " + GMTDateFormater.format(new Date(r2));
log(tmp);
}
if( recordInSampleInterval ) {
//
// get the DataBuffer code to decode the miniseed record
//
int x[] = new int[db.getNumSamples()];
db.setDecoder(db.getBlk_1000().getDecodeString());
db.decodeBuffer();
db.packTrace(x);
float sampRate = db.getSampleRate();
//
// loop over the array of data. Only anlayze data that is
// within the sample interval (s1,s2)
//
for( int j = 0; j < x.length; j++ ) {
//
// rj - sample time
//
long rj = r1 + (long)(1000.0f * ((float)j)/sampRate);
if( s1 <= rj && rj <= s2 ) {
sumA += x[j]*x[j];
sumB += x[j];
n++;
}
} // end loop sample array
} // end if in interval
} // end while read
in.close();
} //end for url's
avg = sumB/(double)n;
//-----------------------------
// sumA - sumB^2/n
// Variance = ---------------
// n - 1
//-----------------------------
double variance = (sumA - sumB*sumB/(double)n)/((double)n - 1.0);
rms = Math.sqrt( variance );
float ret[] = new float[3];
ret[0] = (float)n;
ret[1] = (float)avg;
ret[2] = (float)rms;
return ret;
}
}
[Up]
Meta information is accessed through the QcWaveform interface. QcWaveform has two methods for accessing meta information getMetaData() and getRespData().
QcWaveform.getMetaData()
QcWaveform.getMetaData() returns an array of objects implementing the QcMetaData interface. QcMetaData encapsulates information about:
See:
QcWaveform.getMetaData()
QcMetaData.getStartTime() / QcMetaData.getEndTime()
QcRequest.getStartTime() / QcRequest.getEndTime()
QcWaveform.getRespData()
QcWaveform.getRespData() returns a String containing instrument response information in the form of a RESP file. This information will cover the request's sample time interval. The controlet is responsible for parsing this string. See QcWaveform.getRespData() for more details.
|
||||||||
| PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES | |||||||