scripts/epochClasses/eyet/Standard.java
/////////////////////////////////////////////////////////////////
// Standard epoch-definition script for EYET data
/////////////////////////////////////////////////////////////////
package eyet;
import java.io.*;
import java.text.*;
import java.util.*;
import epochClasses.*;
import generalClasses.*;
import recordingClasses.Recording;
import seriesClasses.*;
import static epochClasses.EpochSelector.*;
/////////////////////////////////////////////////////////////////
/** Standard epoch-definition script for EC and EO data.
* This script generates a list of containing just one epoch time;
* and for this epoch generates a standard set of attributes. The
* attribute table is a flexible way for attaching all manner of
* information to each epoch.
* This information may be used later when subaveraging epochs.
*
* <p>This script also defines the start and duration of epochs through
* <ul><li><tt>defaultEpochStart</tt>: the offset of epoch beginnings from
* the times listed in the the (obligatory) attribute <tt>Time</tt>.
* A value of 0.0 is common for EEGs.</li>
* <li><tt>defaultEpochDur</tt>: the duration of epochs, in seconds.
* A value of 24.3 is common for spectral analysis; 2.43 for time-locking
* to the stimulus cycle.</li>
* <li><tt>dt</tt>: the increment between successsive epochs, in dec </li>
* </ul>
*
* <p>Attribute values, and epoch start and duration, are reported
* to the calling program via the methods defined in the superclass
* <tt>EpochScript</tt>
*/
public class Standard extends EpochScript
{
private static float defaultEpochStart = 0.0f;
private static float defaultEpochDur = 24.3f; // This is ~10 cycles of stim
private static float dt = 15.0f;
private float[] times = null; // t=0 point of epochs, in lieu of evs
private String[][] th = null; // column header information
private Object[][] td = null; // main results array
private int iteratorEventN = 0; // used only by iterator, next()
private int nAttrib; // number of attribute columns
private ArrayList<Event> runtimeEvents = null; // obtained from Recording
////////////////////////////////////////////////////////////////////
/** This script generates a standard set of attributes:
* <ul><li><tt>Time</tt>: points within the span of the recording. In
* the case of ERP recordings, these times usually coincide with
* significant stimuli. However they can be offset for stimuli, or
* else identify EDA onsets, K-complexes, hypoxic events, etc. <b>This
* attribute is obligatory.</b></li>
* <li><tt>Stim</tt>: a string label that is commonly used for
* identifying epochs. It is here that an event (which contains
* information like Tone, 1000 Hz) is assigned the label "Bg".</li>
* <li><tt>AlphaPow, AlphaPha, AlphaQ</tt>: values that quantify
* the magnitude and phase of alpha.</li>
* <li><tt>ScalpSD, ScalpPP</tt>: maximal SD and peak-to-peak range
* within each epoch. Only scalp channels are considered.</li>
* <li>And so on …</li>
* </ul>
* (Check code for a definitive list.) It also sets the epoch start
* offset and epoch duration.
* @param rec Reference to the data. This is used to retrieve both time
* series and event information.
*/
public Standard(Recording rec) {
super(rec, defaultEpochStart, defaultEpochDur);
// There are no real events during EYET, so create pseudo-events
int nEvents = 0;
try {
float t0 = rec.getX0();
float t1 = rec.getX1();
if(t1-t0-getEpochDur()<0) {
String errMsg = "Illegal values found: \n";
errMsg += "Recording duration is "+(t1-t0)+" seconds, ";
errMsg += "while epoch duration is "+getEpochDur();
CommonLog.get().severe(errMsg);
System.exit(2);
}
nEvents = (int)((t1-t0-getEpochDur())/dt) + 1;
// Times will be in the range [t0,t1)
times = new float[nEvents];
for(int row=0; row<nEvents; row++)
times[row] = t0 + (-getEpochStart()) + row*dt;
} catch(recordingClasses.RecordingException e) {
System.out.println("In eyet/Standard.java: "+e.toString());
System.out.println("This means that the recording contains time"+
" series with unequal t0 or lengths!");
System.exit(2);
}
// Generate meta information about attribute table
String[] names = {
"Time", "Stim",
"AlphaPow", "AlphaPha", "AlphaQ",
"ScalpSD", "ScalpPP"};
String[] types = {
"FLOAT", "VARCHAR6",
"FLOAT", "FLOAT", "VARCHAR3",
"FLOAT", "FLOAT"};
assert names.length==types.length: "Standard.java: length mismatch";
nAttrib = names.length;
th = new String[2][nAttrib];
for(int i=0; i<nAttrib; i++) {
th[0][i] = names[i];
th[1][i] = types[i];
}
// Allocate attribute table
assert (nEvents>=1): "Logic error in eyet/Standard: nEvents="+nEvents;
td = new Object[nEvents][nAttrib];
// Initialize attribute table, one (or a few) columns at a time
updateTime(0);
updateStim(1);
updateSpectralPowerAtStim(2,3,4);
updateArtif(5,6);
} // Standard
////////////////////////////////////////////////////////////////////
/** Dump summary of this object, to be used for diagnostics
* @return String representation of this object
*/
@Override
public String toString() {
String s = super.toString(); // get superclass description
s += "Increment between epochs: "+dt;
if(dt<=getEpochDur()) s += " (overlap = "+(getEpochDur()-dt)+")";
else s += " (gap = "+(dt-getEpochDur())+")";
return s;
} // toString
////////////////////////////////////////////////////////////////////
/** This returns the name and datatype of any number of attributes.
* Each name and datatype characterizes one column of the table
* of attributes.
* @return The name and datatype for each of <tt>nCols</tt> columns
* of the attribute table. The array is dimensioned [2][nCol].
*/
@Override
public String[][] getAttribNameType() {
return th;
} // getAttribNameType
////////////////////////////////////////////////////////////////////
/** The attribute table is returned one row at a time. This checks
* if there are more rows available.
*/
@Override
public boolean hasNext() {
return td!=null && iteratorEventN < td.length;
} // hasNext
////////////////////////////////////////////////////////////////////
/** Returns the next row of attribute table. If there are no more
* rows remaining, then <tt>null</tt> is returned. Note that is
* possible that each element in the returned array is itself
* <tt>null</tt>, so calling code should be prepared to deal with
* such cases too.
*/
@Override
public Object[] next() {
return (td!=null && iteratorEventN<td.length)?
td[iteratorEventN++]: null;
} // next
////////////////////////////////////////////////////////////////////
// private methods //
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
/** Initializes td[*][colTime] with the event times, in seconds.
* NOTE: this is obligatory, unlike all other array columns.
*/
private void updateTime(int colTime) {
for(int row=0; row<td.length; row++) {
td[row][colTime] = new Float(times[row]);
}
} // updateTime
////////////////////////////////////////////////////////////////////
/** Initializes td[*][colStim].
*
* This version is specific to EC/EO paradigms where the 'stimuli'
* are pseudo.
*/
private void updateStim(int colStim) {
for(int row=0; row<td.length; row++)
td[row][colStim] = "Pseudo";
} // updateStim
////////////////////////////////////////////////////////////////////
/** Initializes td[*][colPow] td[*][colPha] and td[*][colPhaseBand],
* which refer to spectral band power, phase, and a categorical version
* of phase. Alpha is quantified for some brief (e.g. 1-sec) period
* following the times listed in <tt>float[] times</tt>.
*
* This version is specific to 'Pz' and to the alpha band.
*/
private void updateSpectralPowerAtStim(int colPow, int colPha,
int colPhaseBand) {
// Which site to look at
String site = "Pz";
// Definition of spectral band: binWidth * binN = 10.0 Hz => alpha
float binWidth = 1.0f;
int binN = 10;
// Calculate alpha power and phase at Pz
ArrayList ss = getSeries();
int chanN = 0;
for(chanN=0; chanN<ss.size(); chanN++) {
Series s = (Series)(ss.get(chanN));
if(s.getPrimaryLabel().equalsIgnoreCase(site)) break;
}
Float[][] alpha = new Float[td.length][2];
if(chanN<ss.size())
getEegAtChan((Series)(ss.get(chanN)), binWidth, binN, times, alpha);
// Update power, phase and categorized phase
for(int row=0; row<td.length; row++) {
td[row][colPow] = alpha[row][0]; // power
td[row][colPha] = alpha[row][1]; // phase
if(alpha[row][1] != null) {
int cat = Math.round(alpha[row][1]/180); if(cat<0) cat+=2;
td[row][colPhaseBand] = (cat==1)? "Out": "In";
} else
td[row][colPhaseBand] = null;
}
} // updateSpectralPowerAtStim
////////////////////////////////////////////////////////////////////
/** Initializes td[*][colScalpSd] and td[*][colScalpPP], which refer
* to the maximal standard deviation and peak-to-peak values, considering
* all scalp signals. These values are suitable for threshold-based
* epoch rejection.
*/
private void updateArtif(int colScalpSd, int colScalpPP) {
ArrayList ss = getSeries();
for(int row=0; row<td.length; row++) {
// Calculate SD and P-P measures by finding max in EEG chans only
float maxSd = 0;
float maxPP = 0;
float tStart = times[row]+getEpochStart();
for(int chanN=0; chanN<ss.size(); chanN++) {
if(ss.get(chanN) instanceof SeriesAnalog) {
SeriesAnalog s = (SeriesAnalog)(ss.get(chanN));
if(s.getMode()==DataMode.EEG &&
s.indexOf(tStart) >= 0 &&
s.indexOf(tStart+getEpochDur()) <= s.getNIndexes()) {
BasicStats bs = s.segment(tStart,getEpochDur(),
getEpochStart())
.getStats();
if(bs.sd > maxSd) maxSd = bs.sd;
if(bs.max-bs.min > maxPP) maxPP = bs.max-bs.min;
}
}
}
// Update SD and P-P measures
td[row][colScalpSd] = maxSd;
td[row][colScalpPP] = maxPP;
}
} // updateArtif
}