Created
March 15, 2016 16:50
-
-
Save TreyBastian/dc5d01d141cc7c3acfe0 to your computer and use it in GitHub Desktop.
Calculate Saccades
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func calculateVelocities(data []float64, timeData []int64) []float64 { | |
var result = make([]float64, 0) | |
for d := range data { | |
// first 2 points of data set will always be NAN as +2 and -2 don't exist | |
if d < 2 || d >= len(data)-2 { | |
result = append(result, math.NaN()) | |
} else { | |
//calculate delta time over the range - d+2 - d-2 should cover the whole time range | |
deltaTime := timeData[d+2] - timeData[d-2] | |
x := (data[d+2] + data[d+1] - data[d-1] - data[d-2]) / (6 * float64(deltaTime)) | |
result = append(result, x) | |
} | |
} | |
return result | |
} | |
func DetectSaccades(gazes []database.Gaze, lambda int) ([]Saccade, error) { | |
// reusable variables to setup | |
totalGazesAmmount := len(gazes) | |
gazesX := make([]float64, totalGazesAmmount) | |
gazesY := make([]float64, totalGazesAmmount) | |
gazesTime := make([]int64, totalGazesAmmount) | |
for g := range gazes { | |
gazesX[g] = gazes[g].X | |
gazesY[g] = gazes[g].X | |
gazesTime[g] = gazes[g].Time | |
} | |
// first thing we need to do is get the velocities | |
velocityX := calculateVelocities(gazesX, gazesTime) | |
velocityY := calculateVelocities(gazesY, gazesTime) | |
// Now we get the median velocities for x and y while stripping NAN valus and do some math that I'm not sure what it does, but gives us a number we want | |
// based off hithub.com/tmalsburg/saccades/saccades/R/saccade_recognition.R & Engbert & Kliegl (2003) | |
// sadly we have to do this in more variables than I wanted to | |
medianXSqr, err := stats.Median(sqrSlice(stripNaN(velocityX))) // get median of every element squared | |
if err != nil { | |
return nil, err | |
} | |
medianXN, err := stats.Median(stripNaN(velocityX)) | |
if err != nil { | |
return nil, err | |
} | |
medianX := math.Abs(medianXSqr - math.Pow(medianXN, 2)) | |
medianYSqr, err := stats.Median(sqrSlice(stripNaN(velocityY))) // get median of every element squared | |
if err != nil { | |
return nil, err | |
} | |
medianYN, err := stats.Median(stripNaN(velocityY)) | |
if err != nil { | |
return nil, err | |
} | |
medianY := math.Abs(medianYSqr - math.Pow(medianYN, 2)) | |
// Next we get the threshholds to detect that it is in fact a saccade | |
thresholdX := float64(lambda) * medianX | |
thresholdY := float64(lambda) * medianY | |
// Now we actually detect gazes that are saccades -- these aren't the saccades we return | |
var sacs = make([]int, 0) | |
for i := 0; i < totalGazesAmmount; i++ { | |
if (math.Pow(velocityX[i]/thresholdX, 2) + math.Pow(velocityY[i]/thresholdY, 2)) > 1 { | |
sacs = append(sacs, i) | |
} | |
} | |
// Finally we sort the saccades into actual saccades to get duration / start - end points and return | |
sacsCount := len(sacs) | |
var saccades = make([]Saccade, 0) | |
duration := 0 | |
gazeA := 1 | |
for i := 0; i < sacsCount-1; i++ { | |
if sacs[i+1]-sacs[i] == 1 { | |
duration++ | |
} else { | |
if duration >= 3 { // default samples number for Engbert & Kliegl (2003) | |
var s Saccade | |
s.StartGaze = sacs[gazeA] | |
s.EndGaze = sacs[i] | |
saccades = append(saccades, s) | |
} | |
gazeA = i + 1 | |
duration = 1 | |
} | |
} | |
return saccades, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment