package edu.cmu.cs.eyetrack.test;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import au.com.bytecode.opencsv.CSVWriter;
import edu.cmu.cs.eyetrack.analysis.io.Loader;
import edu.cmu.cs.eyetrack.analysis.score.FixationPointScorer;
import edu.cmu.cs.eyetrack.analysis.score.Scorer;
import edu.cmu.cs.eyetrack.analysis.score.compare.DeltaOffsetPointComparator;
import edu.cmu.cs.eyetrack.analysis.score.compare.PointComparator;
import edu.cmu.cs.eyetrack.analysis.score.distance.DistanceFunction;
import edu.cmu.cs.eyetrack.analysis.score.distance.EuclideanDistanceFunction;
import edu.cmu.cs.eyetrack.analysis.struct.tobii.TobiiData;
import edu.cmu.cs.eyetrack.analysis.struct.tobii.TobiiEventMap;
import edu.cmu.cs.eyetrack.analysis.struct.trackit.Experiment;
import edu.cmu.cs.eyetrack.helper.Util;
public class AnalysisTester {
public static void main(String args[]) {
//
// Sets paths for our dev systems based on Windows/Linux
boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows");
boolean isMacOsX = System.getProperty("os.name").toLowerCase().contains("mac os x");
String subjRootDir;
if(isWindows) {
subjRootDir = "C:\\sideprojects\\TrackIt\\experiments\\feb2012\\";
Util.dPrintln("Detected Windows-based operating system");
} else if(isMacOsX) {
//subjRootDir = "/Users/spook/code/TrackIt/sample/sample_data_August_2013";
subjRootDir = "/Users/spook/sideprojects/TrackIt/experiments/2014_07_data/2013-2014_participants";
Util.dPrintln("Detected Mac OS X as operating system");
} else {
//subjRootDir = "/home/spook/sideprojects/TrackIt/experiments/feb2012/";
subjRootDir = "/usr0/home/jpdicker/Dropbox/organized_DirectTrackIt Files";
//subjRootDir = "/usr0/home/jpdicker/Dropbox/Gold Standard";
//subjRootDir = "/usr0/home/jpdicker/Dropbox/Moving Eyes 2012-2013 New Numbers";
//subjRootDir = "/usr0/home/jpdicker/code/TrackIt/sample/sample_data_August_2013";
Util.dPrintln("Detected *nix-based operating system");
}
// Start traversal at root. This root is organized as follows:
// root -> {Subject1 -> {tobii1, tobbi2, ..}, Subject2 -> {tobii1, tobii2, ..}, ..}
File rootDir = new File(subjRootDir);
if(!rootDir.isDirectory()) {
Util.dPrintln("Need root directory for files; path not directory: " + subjRootDir);
return;
}
// Lets the user loop through different time offsets
Vector<Long> timeOffsets = new Vector<Long>();
for(Long offset=0L; offset<=0L; offset+=100L) {
timeOffsets.add(offset);
}
// Lets the user loop through different fixation thresholds (for FixationPointScorer)
Vector<Double> fixationThresholds = new Vector<Double>();
fixationThresholds.add(150.0);
fixationThresholds.add(200.0);
fixationThresholds.add(250.0);
fixationThresholds.add(300.0);
for(Double fixationThreshold : fixationThresholds) {
for(Long offset : timeOffsets) {
PilotOutputRecord recorder = new PilotOutputRecord();
for(File subjectDir : rootDir.listFiles()) {
// Skip any non-directory files sitting around at the subject directory level
if(!subjectDir.isDirectory()) {
Util.dPrintln(subjectDir.getAbsolutePath() + " is not a directory; skipping.");
continue;
}
String subjectID = subjectDir.getName();
// First, load the "gold standards" (e.g., the .csv files) representing the actual trajectories of the
// distractors and targets from Track-It
//
// Load the actual trajectories for each object in this task, taken straight from Track-It
Map<String, Experiment> goldStandards = new HashMap<String, Experiment>();
for(String goldPath : subjectDir.list()) {
// Ignore non-Track-It files
if(!goldPath.endsWith(".csv")) {
continue;
}
Experiment goldExperiment = null;
try {
Util.dPrintln("Attempting to load gold standard data from " + goldPath);
goldExperiment = Loader.loadGoldStandard(new File(subjectDir, goldPath));
} catch(IOException e) {
System.err.println("IO Error loading gold standard file " + goldPath);
e.printStackTrace();
return;
}
Util.dPrintln("Loaded " + goldPath);
Util.dPrintln("Found " + goldExperiment.getNumTrials() + " trials total.");
// Map the identifier (filename except .csv) to the Experiment
String goldID = goldPath.substring(0, goldPath.lastIndexOf('.')).toUpperCase();
goldStandards.put(goldID, goldExperiment);
}
// Second, go through each Track-It file in the directory and compare it against the proper gold
// standard that has (hopefully) been loaded and mapped in the goldStandards HashMap. Unlike with the old
// version, we'll need to compare timestamps in Track-It to timestamps in Tobii to synchronize the data
for(String subjEventPath : subjectDir.list()) {
// Ignore non-Tobii files
if(!subjEventPath.endsWith(".tsv")) {
continue;
}
// Ignore non-Tobii Event-Map files
int eventDataIndex = subjEventPath.lastIndexOf("-Event-Data");
if(eventDataIndex < 0) {
continue;
}
// If the file is a Tobii Event Map file, find its matching
// All Data file and move on to loading both
String allDataPrefix = subjEventPath.substring(0, eventDataIndex) + "-All-Data";
String subjDataPath = null;
for(String candSubjDataPath : subjectDir.list()) {
if(candSubjDataPath.startsWith(allDataPrefix)) {
subjDataPath = candSubjDataPath;
break;
}
}
// If we couldn't find a match to this Event Data, tell the user and skip
if(subjDataPath == null) {
Util.dPrintln("Couldn't find a match to expected Event Data file " + subjEventPath);
return;
}
// Now try to match the Tobii data file to the pre-loaded gold standard file from Track-It\
// We assume that a file named 02_MackJ_AllSame_01_02-All-Data.tsv matches with a file named
// 02_MackJ_AllSame_01_02.csv from Track-It.
String goldID = subjEventPath.substring(0, subjEventPath.indexOf('-')).toUpperCase();
if(!goldStandards.containsKey(goldID)) {
Util.dPrintln("Couldn't find a matching Track-It file for Tobii file " + subjEventPath.toUpperCase() + "; we expected " + goldID + ".csv");
return;
}
Experiment goldExperiment = goldStandards.get(goldID);
// Try to figure out what type of trial this is (ALL DIFF A, ALL SAME B, etc)
PilotOutputRecord.TRIAL_TYPE trialType = PilotOutputRecord.TRIAL_TYPE.inferTrialType(subjEventPath);
if(trialType.equals(PilotOutputRecord.TRIAL_TYPE.UNKNOWN)) {
Util.dPrintln("Couldn't discern what type of trial (e.g., ALL DIFF A, ALL SAME B) this was from the Event Data file " + subjEventPath);
return;
} else {
Util.dPrintln("I think I've found trial type " + trialType + " from filename " + subjEventPath);
}
File subjEventFile = new File(subjectDir, subjEventPath);
File subjDataFile = new File(subjectDir, subjDataPath);
//
// Grab the overview event data for a single user
TobiiEventMap eventMap = null;
try {
Util.dPrintln("Attempting to load trial data from subject path " + subjEventPath);
eventMap = Loader.loadTobiiEvent3(subjEventFile);
} catch(IOException e) {
System.err.println("IO Error loading event data file " + subjEventPath);
e.printStackTrace();
return;
}
Util.dPrintln("Loaded event data from file " + subjEventPath);
//
// Grab the overview event data for a single user
TobiiData subjectData = null;
try {
Util.dPrintln("Attempting to load Tobii data chunk from " + subjDataPath);
subjectData = Loader.loadTobiiData3(subjDataFile, eventMap);
} catch(IOException e) {
System.err.println("Error loading Tobii data chunk from " + subjDataPath);
e.printStackTrace();
return;
}
Util.dPrintln("Loaded Tobii data chunk from " + subjDataPath);
//
// Score the subject's curves against the gold standard
Util.dPrintln("Comparing subject's trajectory against gold standard.");
//long offset = 000; // in ms
PointComparator f = new DeltaOffsetPointComparator(offset);
double exponent = 2.0;
DistanceFunction dist = new EuclideanDistanceFunction();
//DistanceFunction dist = new FalloffDistanceFunction(exponent);
Scorer scorer = new FixationPointScorer(f, dist, fixationThreshold);
//Scorer scorer = new PointByPointScorer(f, dist);
// Record ALL score data in separate files, if desired
boolean recordEverything = false;
Map<Integer, List<Double>> recordMap = null;
if(recordEverything) {
recordMap = new HashMap<Integer, List<Double>>();
}
// Requires that a subject's eyes be tracked by Tobii for at least X% of the time; else, score = NaN
double lookThreshold = 0.0;
//if(goldExperiment.getTrials().size() != 3) {
// System.out.println(goldExperiment);
// System.exit(-1);
//}
Map<Integer, SingleRunScore> scores = scorer.scoreSubject(goldExperiment, subjectData, recordMap, lookThreshold);
// Possibly record every single score in a separate file
if(recordEverything) {
try {
for(Integer trialID : scores.keySet()) {
CSVWriter writer = new CSVWriter(new FileWriter(new File("exact_" + trialType + "_" + subjectID + "_" + trialID + "_" + System.currentTimeMillis() + ".csv")));
for(Double score : recordMap.get(trialID)) {
if(score == null) {
// For now, record invalid looks as negative score
//score = -1.0;
Util.dPrintln("Null score for subject " + subjectID + " on trial " + trialID);
continue;
}
writer.writeNext(new String[] {String.valueOf(score)});
}
writer.close();
}
} catch(IOException e) {
e.printStackTrace();
}
}
// Record the subject's score in our map of scores, for charting later
recorder.addHeader("scorer", scorer.getClass().getName());
recorder.addHeader("offset", offset + " ms");
recorder.addHeader("exponent", String.valueOf(exponent));
recorder.addHeader("comparator function", f.getClass().getName());
recorder.addHeader("distance function", dist.getClass().getName());
recorder.addHeader("scorer", scorer.getClass().getName());
recorder.addHeader("fixationThreshold", String.valueOf(fixationThreshold));
recorder.addHeader("lookThreshold", String.valueOf(lookThreshold) + " %");
recorder.addScore(trialType, subjectID, scores);
} // end of individual subject's listing loop
// For printing purposes, make sure we fill in any Tobii files that were missing with NaN scores
recorder.fakeMissedTobiiTests(subjectID);
} // end of root directory listing loop
// Write all our records to a .csv file
try {
recorder.writeToCSV("eyetrack_offset_" + offset.toString() + "_" + System.currentTimeMillis() + ".csv");
} catch(IOException e) {
e.printStackTrace();
}
} // end of time offsets outer loop
} // end of fixation thresholds outer loop
return;
}
}