diff --git a/src/main/java/com/github/thed2lab/analysis/Analysis.java b/src/main/java/com/github/thed2lab/analysis/Analysis.java index 5839ed5..8817b72 100644 --- a/src/main/java/com/github/thed2lab/analysis/Analysis.java +++ b/src/main/java/com/github/thed2lab/analysis/Analysis.java @@ -187,9 +187,9 @@ private static List> generateResultsHelper(DataEntry allGaze, DataE resultsMap.putAll(Angles.analyze(validAreaFixations)); resultsMap.putAll(ConvexHull.analyze(validAreaFixations)); resultsMap.putAll(GazeEntropy.analyze(validAreaFixations)); - resultsMap.putAll(Blinks.analyze(areaGaze)); + resultsMap.putAll(Blinks.analyze(areaGaze)); // unfiltered resultsMap.putAll(Gaze.analyze(validAreaGaze)); - resultsMap.putAll(Event.analyze(validAreaGaze)); + resultsMap.putAll(Event.analyze(areaGaze)); // unfiltered var resultsList = new ArrayList>(2); resultsList.add(new ArrayList<>(resultsMap.keySet())); diff --git a/src/main/java/com/github/thed2lab/analysis/AreaOfInterests.java b/src/main/java/com/github/thed2lab/analysis/AreaOfInterests.java index 4a5aacd..fad70b4 100644 --- a/src/main/java/com/github/thed2lab/analysis/AreaOfInterests.java +++ b/src/main/java/com/github/thed2lab/analysis/AreaOfInterests.java @@ -3,8 +3,6 @@ import static com.github.thed2lab.analysis.Constants.AOI_LABEL; import static com.github.thed2lab.analysis.Constants.FIXATION_DURATION; import static com.github.thed2lab.analysis.Constants.FIXATION_ID; -import static com.github.thed2lab.analysis.Constants.SCREEN_HEIGHT; -import static com.github.thed2lab.analysis.Constants.SCREEN_WIDTH; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/com/github/thed2lab/analysis/ConvexHull.java b/src/main/java/com/github/thed2lab/analysis/ConvexHull.java index 8dbb912..f81653c 100644 --- a/src/main/java/com/github/thed2lab/analysis/ConvexHull.java +++ b/src/main/java/com/github/thed2lab/analysis/ConvexHull.java @@ -158,7 +158,7 @@ protected static Point2D.Double getLowestPoint(List points) { /** * Returns a sorted set of points from the list points. The * set of points are sorted in increasing order of the angle they and the - * lowest point P make with the x-axis. If tow (or more) points + * lowest point P make with the x-axis. If two (or more) points * form the same angle towards P, the one closest to P * comes first. * diff --git a/src/main/java/com/github/thed2lab/analysis/DataEntry.java b/src/main/java/com/github/thed2lab/analysis/DataEntry.java index 70baa0b..c1b3e9c 100644 --- a/src/main/java/com/github/thed2lab/analysis/DataEntry.java +++ b/src/main/java/com/github/thed2lab/analysis/DataEntry.java @@ -1,7 +1,7 @@ package com.github.thed2lab.analysis; -import java.util.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; diff --git a/src/main/java/com/github/thed2lab/analysis/GazeEntropy.java b/src/main/java/com/github/thed2lab/analysis/GazeEntropy.java index bc90ee1..7fd572f 100644 --- a/src/main/java/com/github/thed2lab/analysis/GazeEntropy.java +++ b/src/main/java/com/github/thed2lab/analysis/GazeEntropy.java @@ -1,6 +1,7 @@ package com.github.thed2lab.analysis; import static com.github.thed2lab.analysis.Constants.AOI_LABEL; +import static com.github.thed2lab.analysis.Constants.FIXATION_ID; import java.util.ArrayList; import java.util.HashMap; @@ -22,20 +23,23 @@ static public LinkedHashMap analyze(DataEntry fixations) { var transitionProbability = new HashMap>(); var aoiSequence = new ArrayList(); String lastAoi = null; + int lastId = -1; // arbitrary number that will never be the ID int fixationCount = fixations.rowCount(); for (int row = 0; row < fixations.rowCount(); row++) { String aoi = fixations.getValue(AOI_LABEL, row); + int curId = Integer.valueOf(fixations.getValue(FIXATION_ID, row)); aoiSequence.add(aoi); aoiProbability.put(aoi, aoiProbability.getOrDefault(aoi, 0.0) + 1); - if (lastAoi != null) { // skips the first loop + if (lastAoi != null && curId == lastId + 1) { // skips the first loop and non-consecutive fixations Map relationMatrix = transitionProbability.getOrDefault(lastAoi, new HashMap()); double count = relationMatrix.getOrDefault(aoi, 0.0); relationMatrix.put(aoi, count + 1); transitionProbability.put(lastAoi, relationMatrix); } lastAoi = aoi; + lastId = curId; } for (Map.Entry entry : aoiProbability.entrySet()) { diff --git a/src/main/java/com/github/thed2lab/analysis/Windows.java b/src/main/java/com/github/thed2lab/analysis/Windows.java index cb90b4f..8e5e053 100644 --- a/src/main/java/com/github/thed2lab/analysis/Windows.java +++ b/src/main/java/com/github/thed2lab/analysis/Windows.java @@ -257,6 +257,11 @@ static void outputWindowFiles(List windows, double time0, String outp headers.add("window_duration"); headers.add("initial_seconds_elapsed_since_start"); headers.add("final_seconds_elapsed_since_start"); + + // In the combined window folder, add headers if there are none + if (allWindowDGMs.size() == 0) { + allWindowDGMs.add(headers); + } List dgms = results.get(1); dgms.add(String.valueOf(time1)); @@ -265,11 +270,6 @@ static void outputWindowFiles(List windows, double time0, String outp dgms.add(String.valueOf(initialDuration)); dgms.add(String.valueOf(finalDuration)); allWindowDGMs.add(dgms); - - // In the combined window folder, add headers if there are none - if (allWindowDGMs.size() == 0) { - allWindowDGMs.add(headers); - } FileHandler.writeToCSV(results, windowDirectory, fileName + "_DGMs"); AreaOfInterests.generateAOIs(windowGaze, windowFixations, windowDirectory, fileName); diff --git a/src/test/java/com/github/thed2lab/analysis/AnalysisTest.java b/src/test/java/com/github/thed2lab/analysis/AnalysisTest.java new file mode 100644 index 0000000..07db720 --- /dev/null +++ b/src/test/java/com/github/thed2lab/analysis/AnalysisTest.java @@ -0,0 +1,65 @@ +package com.github.thed2lab.analysis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileReader; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.junit.Test; + +import com.opencsv.CSVReader; + +public class AnalysisTest { + + private final double PRECISION = 0.000000001; // allowable floating point error + private final String ALL_GAZE_PATH = "./src/test/resources/test_all_gaze.csv"; + private final DataEntry ALL_GAZE = FileHandler.buildDataEntry(new File(ALL_GAZE_PATH)); + private final String ALL_FIXATION_PATH = "./src/test/resources/filtered_by_fixation.csv"; + private final DataEntry ALL_FIXATION = FileHandler.buildDataEntry(new File(ALL_FIXATION_PATH)); + + @Test + public void testGenerateResults_wholeScreenData() { + List expected = new LinkedList<>(); + try ( + FileReader fileReader = new FileReader("./src/test/resources/test_DGMs.csv"); + CSVReader csvReader = new CSVReader(fileReader); + ) { + expected.add(csvReader.readNext()); + expected.add(csvReader.readNext()); + } catch (Exception e) { + fail("Could not read file"); + } + + List> actual = Analysis.generateResults(ALL_GAZE, ALL_FIXATION); + assertEquals(2, actual.size()); // headers and 1 row of data + + // check headers + Iterator actualIter = actual.get(0).iterator(); + for (String expValue : expected.get(0)) { + assertEquals(expValue.trim(), actualIter.next()); + } + + // check values + actualIter = actual.get(1).iterator(); + for (String expValue : expected.get(1)) { + var bloop = actualIter.next(); + if (!isEqual(expValue, bloop)) { + fail(); + } + } + } + + /** + * Checks if two numbers saved as strings are equal, within an allowable amount of floating point error. + * @param double1 first number to compare + * @param double2 second number to compare + * @return if the two numbers are equal + */ + private boolean isEqual(String double1, String double2) { + return Math.abs(Double.parseDouble(double1) - Double.parseDouble(double2)) < PRECISION; + } +} diff --git a/src/test/java/com/github/thed2lab/analysis/GazeEntropyTest.java b/src/test/java/com/github/thed2lab/analysis/GazeEntropyTest.java index e58db8d..00f4573 100644 --- a/src/test/java/com/github/thed2lab/analysis/GazeEntropyTest.java +++ b/src/test/java/com/github/thed2lab/analysis/GazeEntropyTest.java @@ -16,11 +16,11 @@ public class GazeEntropyTest { public void testGazeEntropyAnalyze_singleAoi_0entropy() { final double EXPECTED_STATIONARY = 0.0; final double EXPECTED_TRANSITION = 0.0; - DataEntry data = new DataEntry(Arrays.asList("AOI")) {{ - process(Arrays.asList("A")); - process(Arrays.asList("A")); - process(Arrays.asList("A")); - process(Arrays.asList("A")); + DataEntry data = new DataEntry(Arrays.asList("FPOGID","AOI")) {{ + process(Arrays.asList("0", "A")); + process(Arrays.asList("1", "A")); + process(Arrays.asList("1", "A")); + process(Arrays.asList("1", "A")); }}; var results = GazeEntropy.analyze(data); assertEquals( @@ -40,15 +40,15 @@ public void testGazeEntropyAnalyze_singleAoi_0entropy() { public void testGazeEntropyAnalyze_threeAoi() { final double EXPECTED_STATIONARY = 0.4699915470362; final double EXPECTED_TRANSITION = 0.282583442123752; - DataEntry data = new DataEntry(Arrays.asList("AOI")) {{ - process(Arrays.asList("A")); - process(Arrays.asList("B")); - process(Arrays.asList("B")); - process(Arrays.asList("A")); - process(Arrays.asList("A")); - process(Arrays.asList("B")); - process(Arrays.asList("C")); - process(Arrays.asList("C")); + DataEntry data = new DataEntry(Arrays.asList("FPOGID", "AOI")) {{ + process(Arrays.asList("1", "A")); + process(Arrays.asList("2", "B")); + process(Arrays.asList("3", "B")); + process(Arrays.asList("4", "A")); + process(Arrays.asList("5", "A")); + process(Arrays.asList("6", "B")); + process(Arrays.asList("7", "C")); + process(Arrays.asList("8", "C")); }}; var results = GazeEntropy.analyze(data); assertEquals( @@ -68,13 +68,39 @@ public void testGazeEntropyAnalyze_threeAoi() { public void testGazeEntropyAnalyze_undefinedAoi() { final double EXPECTED_STATIONARY = 0.301029995663981; final double EXPECTED_TRANSITION = 0.288732293303828; - DataEntry data = new DataEntry(Arrays.asList("AOI")) {{ - process(Arrays.asList("A")); - process(Arrays.asList("")); - process(Arrays.asList("")); - process(Arrays.asList("A")); - process(Arrays.asList("A")); - process(Arrays.asList("")); + DataEntry data = new DataEntry(Arrays.asList("FPOGID", "AOI")) {{ + process(Arrays.asList("0", "A")); + process(Arrays.asList("1", "")); + process(Arrays.asList("2", "")); + process(Arrays.asList("3", "A")); + process(Arrays.asList("4", "A")); + process(Arrays.asList("5", "")); + }}; + var results = GazeEntropy.analyze(data); + assertEquals( + "Unexpected stationary entropy.", + EXPECTED_STATIONARY, + Double.parseDouble(results.get(STATIONARY_ENTROPY)), + PRECISION + ); + assertEquals( + "Unexpected transition entropy.", + EXPECTED_TRANSITION, Double.parseDouble(results.get(TRANSITION_ENTROPY)), + PRECISION + ); + } + + @Test + public void testGazeEntropyAnalyze_nonConsecutiveFixations() { + final double EXPECTED_STATIONARY = 0.301029995663981; + final double EXPECTED_TRANSITION = 0.138217295471837; + DataEntry data = new DataEntry(Arrays.asList("FPOGID", "AOI")) {{ + process(Arrays.asList("0", "A")); + process(Arrays.asList("1", "")); + process(Arrays.asList("2", "")); + process(Arrays.asList("4", "A")); + process(Arrays.asList("5", "A")); + process(Arrays.asList("6", "")); }}; var results = GazeEntropy.analyze(data); assertEquals( diff --git a/src/test/resources/test_DGMs.csv b/src/test/resources/test_DGMs.csv new file mode 100644 index 0000000..ed5d6dd --- /dev/null +++ b/src/test/resources/test_DGMs.csv @@ -0,0 +1,2 @@ +total_number_of_fixations,sum_of_all_fixation_duration_s,mean_fixation_duration_s,median_fixation_duration_s,stdev_of_fixation_durations_s,min_fixation_duration_s,max_fixation_duration_s,total_number_of_saccades,sum_of_all_saccade_lengths,mean_saccade_length,median_saccade_length,stdev_of_saccade_lengths,min_saccade_length,max_saccade_length,sum_of_all_saccade_durations,mean_saccade_duration,median_saccade_duration,stdev_of_saccade_durations,min_saccade_duration,max_saccade_duration,scanpath_duration,fixation_to_saccade_ratio,average_peak_saccade_velocity,sum_of_all_absolute_degrees,mean_absolute_degree,median_absolute_degree,stdev_of_absolute_degrees,min_absolute_degree,max_absolute_degree,sum_of_all_relative_degrees,mean_relative_degree,median_relative_degree,stdev_of_relative_degrees,min_relative_degree,max_relative_degree,convex_hull_area,stationary_entropy,transition_entropy,total_number_of_blinks,average_blink_rate_per_minute,total_number_of_valid_recordings,average_pupil_size_of_left_eye,average_pupil_size_of_right_eye,average_pupil_size_of_both_eyes,total_number_of_l_mouse_clicks +20,4.76282,0.238141,0.16748,0.152435548482145,0.08044,0.66125,16,2530.86608142828,158.179130089267,119.346748844631,106.3517507880460,20.7083517064975,349.414837212159,0.19091,0.011931875,0.00671,0.0094964149156,0.00598,0.03028,4.95373,24.9479859619718,179.969192732729,395.580356733446,24.7237722958404,15.4757613889113,22.7925364835493,2.9677710724739,81.4803527675405,1618.85106640278,124.527005107906,123.297847178851,26.0218488445123,79.5855258680609,163.151086040417,168928.54539264,0.3482253728572,0.2295311788752,3,27.5902238640109,822,4.5235607299270,4.3886963746959,4.4561285523114,2 \ No newline at end of file