scripts/epochClasses/ppi/Standard.java

/////////////////////////////////////////////////////////////////
// Standard epoch-definition script for PPI data
/////////////////////////////////////////////////////////////////
package ppi;

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 PPI data.
 * This script generates a list of epoch times; and for each 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.2 or -0.3 is common for ERPs.</li>
 *     <li><tt>defaultEpochDur</tt>: the duration of epochs, in seconds.
 *     A value of 1.0 is common for ERPs.</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.2f;
    private static float defaultEpochDur = 1.0f;
    private float rt0 = 0.1f;           // minimum accpetable RT, in sec
    private float rt1 = 1.0f;           // maximum acceptable RT, in sec
    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>RT, RTclass</tt>: values that are derived from stimulus and 
     *     button press events, and incorporates some allowed range.  The 
     *     latter is a String value, which simplifies creation of subsets
     *     of epochs based on RT speed.</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);

        // Time series are available via the inherited method getSeries()
        // Events are available via the inherited method getEvents()
        // (Equally, they could be obtained directly from rec.)
        runtimeEvents = getEvents();

        // Generate meta information about attribute table
        String[] names = {
            "Time", "Stim",
            "AlphaPow", "AlphaPha", "AlphaQ", 
            "ScalpSD", "ScalpPP"};
        String[] types = {
            "FLOAT", "VARCHAR10",
            "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
        if(runtimeEvents==null || runtimeEvents.size()==0) return;
        td = new Object[runtimeEvents.size()][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
        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<runtimeEvents.size())?
            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<runtimeEvents.size(); row++) {
            Event e = runtimeEvents.get(row);
            td[row][colTime] = new Float((float)e.getTime());
        }
    } // updateTime

    ////////////////////////////////////////////////////////////////////
    /** Initializes td[*][colStim] and td[*][colStimResp].
     * 
     * This version is specific to ppi paradigms.
     */
    private void updateStim(int colStim) {
        for(int row=0; row<runtimeEvents.size(); row++) {
            Event e = runtimeEvents.get(row);

            if(e.getDesc().matches("Startle")) {
                td[row][colStim] = "Startle";
            } else if(e.getDesc().matches("PPStartle")) {
                td[row][colStim] = "PPStartle";
            } else if(e.getDesc().matches("Prepulse")) {
                td[row][colStim] = "Prepulse";
            } else {
                td[row][colStim] = null;
            }
        }
    } // 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;

        // Estimate auxiliary values
        int nEvents = runtimeEvents.size();
        float[] times = new float[nEvents];
        for(int row=0; row<nEvents; row++) {
            Event e = runtimeEvents.get(row);
            times[row] = (float)e.getTime();
            //System.out.print(times[row]+" ");  // DEBUG
        }
        // 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[nEvents][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<nEvents; 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<runtimeEvents.size(); row++) {
            Event e = runtimeEvents.get(row);
            // Calculate SD and P-P measures by finding max in EEG chans only
            float maxSd = 0;
            float maxPP = 0;
            float tStart = (float)e.getTime()+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

}

 


Validate HTML CSS Generated 2011-08-12T10:32:18+1000 Chris Rennie