SourceForge.net Logo timeSeries: timeSeries Class Library

campAnalysis.cpp

The following example is an actual analysis program used to process 4025 data files generated by the National Advanced Driving Simulator. The program listing is cross-referenced to the timeSeries library documentation and provides a non-trivial example of how the classes can be used to perform an analysis.

Copyright Jeff Greenberg.

This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

#include <iostream>
#include <fstream>
#include <list>
#include <string>
#include <vector>

using namespace std;
#include "dataFrame.h"
#include "dataFile.h"
#include "timeSeries.h"
#include "timeSeriesArray.h"
#include "lookupTable.h"
#include "TTC2.h"
#include "filter.h"
#include "measures.h"

const double MPHPERFPS=0.68182;
const double FPSPERMPS=3.28084;

string getLeafName(string&);
bool isBrakingTrial(string&);
measures  process_file(string dataFileName, 
                                  dataFrame& df, 
                                  lookupTable& fileTable, 
                                  lookupTable& brakeTable,
                                  lookupTable& steeringTable);
iir_t *init_lowpass();
iir_t *init_cheby1();


template <class T>
void filtfilt(iir_t *lpf,timeSeries<T>& POVspeed_filt);

template <class T>
int findBrakingReactionFrame(timeSeries<T>& SVaccel,
                             timeSeries<T>& SVspeed, 
                             timeSeries<T>& throttle, 
                             timeSeries<T>& brake);

template <class T>
int findSteeringReactionFrame(timeSeries<T>& SVaccel,
                              timeSeries<T>& swavel, 
                              timeSeries<T>& swa,
                              timeSeries<T>& lanePos, 
                              timeSeries<T>& SVYawRate);

void outputMeasures(const string& measuresFile,
                    const measures& dependentMeasures,
                    const int filenum);
#ifdef WIN32
string convertLink(const char *fname);
int analysisMain (int argc, char * const argv[])
#else
int main (int argc, char * const argv[])
#endif
{   using namespace std;
    
    // there are 3 lookup tables that we'll need access to:
    //
    // fileTable, which associates files with misc metadata (subj #, run name, etc)
    // brakingTable, which associates the run name with the braking onset frame #
    // steeringTable, which associates the run name with the steering onset frame #
    //
    // these lookup tables are initialized here, before we begin processing data files
    //
#ifdef WIN32    
    string fileTablePath=
        "\\\\Osprey\\CAMP\\NADS non-data deliverables\\CAMP LookupTables.txt";
    string brakeTablePath=
        "\\\\Osprey\\CAMP\\NADS non-data deliverables\\AllBrakeWithFrame_new.txt";
    string steeringTablePath=
        "\\\\Osprey\\CAMP\\NADS non-data deliverables\\AllSteeringWithFrame.txt";
#else
    string fileTablePath=
        "/Volumes/CAMP/NADS non-data deliverables/CAMP LookupTables.txt";
    string brakeTablePath=
        "/Volumes/CAMP/NADS non-data deliverables/AllBrakeWithFrame_new.txt";
    string steeringTablePath=
        "/Volumes/CAMP/NADS non-data deliverables/AllSteeringWithFrame.txt";
#endif

    try {
        static lookupTable fileTable(fileTablePath,"File Name");
        static lookupTable brakeTable(brakeTablePath,"Run");
        static lookupTable steeringTable(steeringTablePath,"Run");

        // All of the data files are assumed to have the same format so
        // parse the data definition file once before we begin
#ifdef WIN32
        string framePath="\\\\Osprey\\CAMP\\NADS non-data deliverables\\nadsCampFrame.dfm";
#else
        string framePath="/Users/jgreenb2/campAnalysis/analysis/nadsCampFrame.dfm";
#endif

        dataFrame nadsCampFrame(framePath);
        
        for (int filenum=1;filenum<argc;++filenum) {
            string dataFileName(argv[filenum]);
            cout << "reading file " << dataFileName << " " << filenum << " of ";
            cout << argc-1 << "..." << flush;
            try {
                measures dependentMeasures=process_file(dataFileName, 
                                                        nadsCampFrame, 
                                                        fileTable, 
                                                        brakeTable, 
                                                        steeringTable);
                outputMeasures(dataFileName,dependentMeasures,filenum);
            }
            // these are all errors that cause us to skip a data file
            // but not abort the run
            catch (tsException& tse) {
                tse.debug_print();
                cerr << "skipping to next file..." << endl;
            }
            catch (dataFile::dataInputExceptions& die) {
                die.debug_print();
                cerr << "skipping to next file..." << endl;
            }
            catch (lookupTable::lookupExceptions& lookupError) {
                lookupError.debug_print();
                cerr << "skipping to next file..." << endl;
            }
        }
    }
    catch (lookupTable::lookupExceptions& lookupError) {
        // if we can't read a lookup file we have to just exit
        lookupError.debug_print();
        exit(1);
    }       
    
}

measures process_file(string dataFileName, 
                              dataFrame& nadsCampFrame, 
                              lookupTable& fileTable, 
                              lookupTable& brakeTable,
                              lookupTable& steeringTable)
{   const double Ts=1.0/120.0;
#ifdef WIN32
            dataFileName=convertLink(dataFileName.c_str());
#endif
            
    // load the entire data file into memory

    dataFile data(dataFileName,nadsCampFrame,Ts,dataFile::LittleEndian);
    cout << "done" << endl;
    
    // get the variables we're interested in
    
    timeSeries<float> 
        throttle(nadsCampFrame.findName("CFS_Accelerator_Pedal_Position"),data);
    timeSeries<short> 
        prndl(nadsCampFrame.findName("CFS_Auto_Transmission_Mode"),data);
    timeSeries<float> 
        brake(nadsCampFrame.findName("CFS_Brake_Pedal_Force"),data);
    timeSeries<float> 
        swavel(nadsCampFrame.findName("CFS_Steering_Wheel_Angle_Rate"),data);
    timeSeries<float> 
        swa(nadsCampFrame.findName("CFS_Steering_Wheel_Angle"),data);   
    timeSeries<float> 
        range(nadsCampFrame.findName("SCC_OwnVehToLeadObjDist"),data);
    timeSeriesArray<float> 
        omega(nadsCampFrame.findName("VDS_Chassis_CG_Ang_Vel"),data);
    timeSeriesArray<float> 
        sforces(nadsCampFrame.findName("MIF_Head_Point_Specific_Forces"),data);
    timeSeriesArray<float> 
        lanepos(nadsCampFrame.findName("VDS_Chassis_CG_Position"),data);
    timeSeriesArray<float> 
        cgaccel(nadsCampFrame.findName("VDS_Chassis_CG_Accel"),data);
    timeSeriesArray<float> 
        scenario_vels(nadsCampFrame.findName("SCC_DynObj_Vel"),data);
    
    // the SVaccel is in ft/s, since this is (mostly) what NADS uses. We'll keep to ft 
    // and s for the kinematics here to be consistent
    timeSeries<float>SVaccel = cgaccel.element(0);
    
    // believe it or not, we didn't actually get vehicle speed from NADS so we have to 
    // compute it here
    timeSeries<float>SVspeed = SVaccel.integrate();
    
    // POV speed is buried in the SCC_DynObj_Vel structure
    // and it's in m/s instead of ft/s. We convert it here.
    timeSeries<float>POVspeed = scenario_vels.element(0)*FPSPERMPS;
    
    // POV accel is not directly available, so once again, we calculate it.
    // Use a 10Hz, acausal, phaseless filter to smooth POVspeed first, since the NADS 
    // SCC objects are updated at a much lower sample frequency 
    timeSeries<float>POVspeed_filt = POVspeed;
    iir_t *lpf = init_lowpass();
    filtfilt(lpf,POVspeed_filt);
    timeSeries<float>POVaccel = POVspeed_filt.differentiate();
    free_iir_filter(lpf);
    
    // extract the lateral lane position
    timeSeries<float>SVLatLanePos = lanepos.element(0);

    // and extract the SV yaw rate
    timeSeries<float> SVYawRate = omega.element(2);
    
    // find the frame number at which the steering or braking reaction
    // begins
    
    // start by finding out the name of this run
    string fileName = getLeafName(dataFileName);
    string runName = fileTable.find<string>(fileName,"Run Folder");
    
    cout << runName << endl;
    
    // run names ending in NB or HB are braking while runs ending in NS or HS 
    // are steering trials
    
    bool brakingTrial = isBrakingTrial(runName);
    
    // get the relevant reaction frame from the lookup file
    
    int NADSreactionFrame;
    if (brakingTrial) {
        NADSreactionFrame = brakeTable.find<int>(runName,"FrameOnSVBrakeOnset");
    } else {
        NADSreactionFrame = steeringTable.find<int>(runName,"OwnOnsetFrame");
    }
    
    // since we cannot trust the NADS braking reaction frames we calculate our own
    // and compare the two
    //
        
    int reactionFrame;
    if (brakingTrial) {
        reactionFrame = findBrakingReactionFrame(SVaccel,SVspeed,throttle,brake);
    } else {
        timeSeries<float> SVLatAccel = cgaccel.element(1);
        reactionFrame = findSteeringReactionFrame(SVLatAccel,
                                                  swavel,
                                                  swa,
                                                  SVLatLanePos, 
                                                  SVYawRate);
    }
    
    string alert="";
    if (abs(NADSreactionFrame-reactionFrame) > 30) alert="***";
    cout << (brakingTrial ? "braking " : "steering ") << "nads onset frame= ";
    cout << NADSreactionFrame << alert << endl;
    cout << (brakingTrial ? "braking " : "steering ") << "camp onset frame= ";
    cout << reactionFrame << alert << endl;

    double ttc;
    try {
        ttc = TTC2(SVspeed[reactionFrame],
                  POVspeed[reactionFrame],
                  SVaccel[reactionFrame],
                  POVaccel[reactionFrame],
                  range[reactionFrame]);
    }
    catch (TTC::TTCExceptions& ttce) {
        ttc = -999.0;
    }
    double NADSttc;
    if (brakingTrial) {
        NADSttc = brakeTable.find<double>(runName,"TTC2SVBrakeOnset");
    } else {
        NADSttc = steeringTable.find<double>(runName,"TTC2OnSteerOnset");
    }
    
    const double SystemDelay=0.0;
    double reqDec;
    try {
        reqDec= requiredDecel(SystemDelay,
                              SVspeed[reactionFrame],
                              POVspeed[reactionFrame],
                              SVaccel[reactionFrame],
                              POVaccel[reactionFrame],
                              range[reactionFrame]);
    }
    catch (TTC::TTCExceptions& ttce) {
        reqDec = -999.0;
    }
    
    double NADSreqDec;  
    if (brakingTrial) {
        NADSreqDec = brakeTable.find<double>(runName,"PredictDecelSVBrakeOnset");
    } else {
        NADSreqDec = steeringTable.find<double>(runName,"PreDecelOnSteerOnset");
    }
    
    
    cout << "Camp TTC2= " << ttc << " NADS TTC2= " << NADSttc << endl;
    cout << "Camp rDec=" << reqDec/32 << " g's NADS rDec= " << NADSreqDec/32;
    cout << " g's" << endl;
    cout << endl;
    
    // create the measures
    measures meas;
    
    meas.entry("subno", fileTable.find<int>(fileName,"Subject Number"));
    meas.entry("filename",fileName);
    meas.entry("condition", fileTable.find<string>(fileName,"Condition"));
    meas.entry("runName", runName);
    meas.entry("instruction", fileTable.find<string>(fileName,"Instruction"));
    meas.entry("frameno",reactionFrame);
    meas.entry("NADSframeno", NADSreactionFrame);
    meas.entry("reqdec",reqDec);
    meas.entry("NADSreqdec",NADSreqDec);
    meas.entry("TTC2",ttc);
    meas.entry("NADSTTC2",NADSttc);
    meas.entry("SVspeed",SVspeed[reactionFrame]);
    meas.entry("POVspeed",POVspeed[reactionFrame]);
    meas.entry("SVaccel",SVaccel[reactionFrame]);
    meas.entry("POVaccel",POVaccel[reactionFrame]);
    meas.entry("Range", range[reactionFrame]);
                   
    return meas;
}

/* lowpass is a 3rd order butterworth filter with a break
   frequency at 10 Hz, sampling freq. at 120 Hz
*/

iir_t *init_lowpass()
{   double coeff[][6]= {
                    {1, 1.00000959440837, 0, 1, -0.577350269189628, 0},
                    {1, 1.99999040559162, 0.999990405683675, 1, -1.3856406460551, 0.6}
           };
    double gain=0.0113248654051871;
    iir_t *f;

    f=init_iir_filter(coeff, gain, 2);

    return f;
}

/* cheby1 is a 3rd order low pass Chebychev1 filter with a break frequency of
   5 hz and 1db of passband ripple
*/


iir_t *init_cheby1()
{   double coeff[][6]= {
    {1, 1.00000959440837, 0, 1, -0.877830604810662, 0},
    {1, 1.99999040559162, 0.999990405683675, 1, -1.81608886485109, 0.879775751929623}
    };
    double gain=0.000972573559484491;
    iir_t *f;

    f=init_iir_filter(coeff, gain, 2);

    return f;
}

template <class T>
void filtfilt(iir_t *lpf,timeSeries<T>& ts) {
    // filter in place....
    
    // forward...
    for ( int i=0;i<ts.length();i++) {
        ts[i] = iir_filter(lpf,ts[i]);
    }
    
    // then backward...
    for ( int i=ts.length()-1;i>=0;i--) {
        ts[i] = iir_filter(lpf,ts[i]);
    }
}

string getLeafName(string& file) {
#ifdef WIN32
    size_t beg=file.find_last_of("\\")+1;
#else
    size_t beg=file.find_last_of("/")+1;

#endif
    size_t end=file.find_first_of(".",beg)-1;
    
    return file.substr(beg,end-beg+1);
}

bool isBrakingTrial(string& runName) {
    bool brakingTrial;
    char lastChar = runName[runName.length()-1];
    if (lastChar == 'B') brakingTrial=true;
    else if (lastChar == 'S') brakingTrial=false;
    else {
        cerr<< "bad run name! " << runName << endl; 
        exit(1);
    }
    return brakingTrial;
}

/*
    The brake reaction time is defined as 20 samples prior to the
    point at which the SVaccel dips below 0.1g
 */

template <class T>
int findBrakingReactionFrame(timeSeries<T>& SVaccel,
                             timeSeries<T>& SVspeed,
                             timeSeries<T>& throttle,
                             timeSeries<T>& brake) 
{   
    vector<int> bindx;
    
    // there can be big accel/decel spikes at the prndl transition so
    // start the search for the peak decel after the SV is safely in
    // motion
    bindx = find(SVspeed > 20.0/MPHPERFPS);
    int moving = bindx[0];
    int peak = SVaccel.range(moving,SVaccel.getMaxIndex()).min();

    // find the last throttle release prior to the peak decel
    bindx = find((throttle.range(moving,peak) > 0.0));
    int beg = bindx.back();
    
    // if this is a left-footed braker and still has the right foot on
    // the gas, give up and just set the window back an arbitrary 5s
    const float bthresh=2.0;
    if (brake[beg] > bthresh) {
        beg = SVaccel.index(SVaccel.time(beg) - 5.0);
    }
    // now find the first decel of more than 0.1 g's
    const double decelThresh = -0.1*32.0;
    bindx = find(SVaccel.range(beg,peak) < decelThresh);
    int onsetFrame = bindx[0] - 20;

    return onsetFrame;
}   

template <class T>
int findSteeringReactionFrame(timeSeries<T>& SVLatAccel,
                              timeSeries<T>& swavel, 
                              timeSeries<T>& swa,
                              timeSeries<T>& SVLatLanePos,
                              timeSeries<T>& SVYawRate)
{   const double endingLanePos = -4250;
    const double startingLanePos = -4237;
    
    
    // find the point at which the SV is 60% of the way between lanes
    const double Pt60 = (0.4*startingLanePos+0.6*endingLanePos);
    vector<int> indx;
    indx = find(SVLatLanePos <= Pt60);
    int lane60 = indx.front();
    double t60 = SVLatLanePos.time(lane60);
    const double TswaRange=7.0;
    double t0 = t60 - TswaRange;
    
    // search in a time range beginning TswaRange seconds before
    // the 60% lane change point
    int minaccel = SVLatAccel.range(t0,t60).min();
    double Tminaccel = SVLatAccel.time(minaccel);
    
    // smooth out the swa velocity using a 5Hz acausal filter
    timeSeries<T> swav_f = swavel;
    iir_t *lpf = init_cheby1();
    filtfilt(lpf,swav_f);
    free_iir_filter(lpf);

    const double swaThresh = -2.5;
    timeSeries<T> sv = swav_f.range(t0-10.0,Tminaccel);
    timeSeries<T> sa = swa.range(t0-10.0,Tminaccel);
    vector<int> zerocross =  find((((sv>>1)*sv)<=0.0) && 
                                  (sv<0.0) &&
                                  (sa>swaThresh));
    return zerocross.back();
}

void outputMeasures(const string& dataFileName,
                    const measures& meas,
                    const int filenum)
{   
    // write the header file out the first time only
    if (filenum == 1) {
        ofstream headerFile("campmeas.hdr", ios::out);
        print_labels(headerFile,meas);
        headerFile.close();
    }
    
    int extpos = dataFileName.find(".bin");
    string measFileName = dataFileName.substr(0,extpos) + ".dat";
    ofstream mFile(measFileName.c_str(), ios::out);
    mFile << meas;
    mFile.close();
    
    return;
}

Generated on Tue Mar 16 15:10:49 2010 for timeSeries by  doxygen 1.6.3