Implementation

Download as pdf or txt
Download as pdf or txt
You are on page 1of 60

Sep 01 2009 10:39 aoi.

cpp Page 1/1


#include <iostream>
#include <iomanip>
#include <string>
#include <math.h>

#ifdef ANM_OSX
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <OpenGL/glext.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glext.h>
#endif

using namespace std;

#include "aoi.h"

// aoi class implementation


// (c) Andrew T. Duchowski

/////////////////////////// friends ////////////////////////////////////////////


istream& operator>>(istream& s,Aoi& rhs)
{
int i=0;
char c,n;
double x,y;

// assume we’re reading in Tobii’s AOI_ (AOI Data) data file


// Fix number,Timestamp,Duration,GazepointX,GazepointY
// name tl_x,tl_y tr_x,tr_y br_x,br_y bl_x,bl_y

// read in name
s >> rhs.name;

do {
// read in AOI coordinates
s >> x; s >> c; s >> y;
rhs.coord[i][0] = x;
rhs.coord[i][1] = y;
i++;
} while( s.get(c) && (c != ’\n’) && ((n = s.peek()) != ’\n’) && (i<4) );

return(s);
}

ostream& operator<<(ostream& s,const Aoi& rhs)


{
s.setf(ios::fixed,ios::floatfield);
s.precision(2);
s << rhs.name;
for(int i=0;i<4;i++)
s << "\t" << rhs.coord[i][0] << "," << rhs.coord[i][1];
s << endl;
return s;
}
Sep 01 2009 10:39 aoievent.cpp Page 1/1
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <string>
#include <algorithm>

using namespace std;

#include "aoievent.h"

// scanpath implementation
// (c) Andrew T. Duchowski

istream& operator>>(istream& s, AOIEvent& rhs)


{
s >> rhs.time;
s >> rhs.duration;
s >> rhs.name;
return s;
}

ostream& operator<<(ostream& s, const AOIEvent& rhs)


{
s << rhs.time << "\t";
s << rhs.duration << "\t";
s << rhs.name << "\t";

return s;
}
Sep 01 2009 10:39 cluster.cpp Page 1/3
#include <iostream> for(int i=0; i<2; i++) { mean[i] = x(i); mean(i) = x(i); };
#include <iomanip>
#include <sstream> // if we don’t initialize major, minor axes, cluster rendering bombs
#include <string> major.push_back(0.0);
#include <cmath> major.push_back(0.0);
#include <cstdlib> minor.push_back(0.0);
minor.push_back(0.0);
#ifdef ANM_OSX
#include <OpenGL/gl.h> // set centroid time to incoming point
#include <OpenGL/glu.h> time = x.gettimestamp();
#include <OpenGL/glext.h>
#include <GLUT/glut.h> t_in = t_out = time;
#else }
#include <GL/gl.h>
#include <GL/glu.h> /////////////////////////// operators: copy assignment /////////////////////////
//#include <GL/glext.h> Cluster Cluster::operator=(const Cluster& rhs)
#include <GL/glut.h> {
#endif if(this != &rhs) {
mean = rhs.mean;
#ifndef INFINITY time = rhs.time;
#define INFINITY MAXFLOAT t_in = rhs.t_in;
#endif t_out = rhs.t_out;

using namespace std; point.clear();


for(int i=0; i<(int)rhs.point.size(); i++)
#include <vector.h> point.push_back(rhs.point[i]);
#include <matrix.h>
#include <quaternion.h> for(int i=0; i<(int)rhs.major.size(); i++)
major.push_back(rhs.major[i]);
#include "jacobi.h"
#include "polynomial.h" for(int i=0; i<(int)rhs.minor.size(); i++)
#include "ellipse.h" minor.push_back(rhs.minor[i]);
#include "point.h"
#include "cluster.h" if(ellipse) delete ellipse; ellipse = NULL;
if(rhs.ellipse) ellipse = new Ellipse(rhs.ellipse);
// cluster implementation }
// (c) Andrew T. Duchowski return *this;
}
ostream& operator<<(ostream& s, const Cluster& rhs)
{ /////////////////////////// members ////////////////////////////////////////////
s << rhs.mean; bool Cluster::insert(Point& x)
{
return s; // if the incoming point’s mean is close enough to current
} // mean, accept the point, otherwise reject it
if(!x.closeto(mean,time)) return false; // spatiotemporal clustering
Cluster::Cluster(Point& x) : //if(!x.closeto(mean)) return false; // spatial clustering only
letter(0),
time(0.0), // update (running) mean
t_in(0.0), // from Brown, Robert Grover, "Introduction to Random Signal
t_out(0.0), // Analysis and Kalman Filtering", John Wiley \& Sons, New York, NY, 1983
ellipse(NULL) // [p.182]
{ // TK5102.5 .B696
// this routine usually gets called with the first incoming point for(int i=0; i<2; i++) {
point.push_back(new Point(x)); // note the Point.operator() -- we’re getting the mean from point x,
// it’s a crappy way to do it...
// note the Point.operator() -- we’re getting the mean from point x, mean[i] = (float)point.size()/(float)(point.size()+1) * mean[i] +
// it’s a crappy way to do it... 1.0/(float)(point.size()+1) * x(i);
// I’m confusing a point’s coordinates ([0],[1]) with its // I’m confusing a point’s coordinates ([0],[1]) with its
// mean ((0),(1))... // mean ((0),(1))...
Sep 01 2009 10:39 cluster.cpp Page 2/3
mean(i) = (float)point.size()/(float)(point.size()+1) * mean(i) + for(int i=0; i<(*point[k]).dim(); i++) {
1.0/(float)(point.size()+1) * x(i); if((*point[k])[i] < min[i]) min[i] = (*point[k])[i];
} if((*point[k])[i] > max[i]) max[i] = (*point[k])[i];
}
// calculate "average timestamp" for the cluster }
time = (float)point.size()/(float)(point.size()+1) * time + double width = (max[0] - min[0])/2.0;
1.0/(float)(point.size()+1) * x.gettimestamp(); double height = (max[1] - min[1])/2.0;
double major_len = width > height ? width : height;
if(x.gettimestamp() < t_in) t_in = x.gettimestamp(); double minor_len = width < height ? width : height;
if(x.gettimestamp() > t_out) t_out = x.gettimestamp(); //std::cerr << "done: " << width << " x " << height << "." << std::endl;

point.push_back(new Point(x)); // normalize then scale the major/minor axes


major = norm(major); minor = norm(minor);
return true; major *= major_len; minor *= minor_len;
}
// find the angle of the ellipse’s semi-major axis w.r.t. x-axis (horizontal)
void Cluster::pca(void) double angle = atan2(major[1],major[0])*180.0/M_PI;
{ //std::cerr << "ellipse anlge: " << angle << std::endl;
matrix<double> cov(2,2);
vector<double> eval(2,0.0); //std::cerr << "creating ellipse with";
matrix<double> evec(2,2); //std::cerr << setprecision(2);
//std::cerr << " (c,d) = (" << mean[0] << "," << mean[1] << ")";
vector<double> min(2, INFINITY); //std::cerr << " (a,b) = (" << major_len << "," << minor_len << ")";
vector<double> max(2,-INFINITY); //std::cerr << "...";
ellipse = new Ellipse(mean[0],mean[1],major_len,minor_len,angle);
if(point.size() < 2) return; //std::cerr << "done creating ellipse." << std::endl;
}
// calculate covariance matrix (should be 2x2 symmetrical)
//std::cerr << "points: " << point.size() << ", setting cov..."; double Cluster::distance(Cluster& rhs)
for(int k=0; k<(int)point.size(); k++) {
for(int i=0; i<2; i++) return(mean.distance(rhs.mean));
for(int j=0; j<2; j++) }
cov[i][j] += ((*point[k])[i] - mean[i]) * ((*point[k])[j] - mean[j]);
//std::cerr << "done." << std::endl; bool Cluster::intersects(Cluster& rhs)
{
// find eigenvalues/eigenvectors of covariance matrix bool intersection = false;
jacobi(cov,eval,evec); bool space = false;
bool spacetime = false;
// sort eigenvalues/eigenvectors in descending order (column-major!) vector<Point* > points;
eigsrt(eval,evec);
// check mean proximity
// ellipse’s major axis is the max eigenvector (largest eigenvalue); if(mean.closeto(rhs.mean)) space = true;
// semi-major axis is on the plane, orthogonal to major axis; else {
// don’t forget to transpose evec since eigenvectors are in columns // find ellipse-ellipse intersection points; make sure both clusters
evec = evec.transpose(); // have ellipses defines (one or both may be just a point)
major = evec[0]; minor = evec[1]; if(ellipse && rhs.ellipse) {
points = ellipse->intersect((*rhs.ellipse));
// lengths for the ellipse axes
/* // if ellipses intersect at 2+ points, consider that an intersection
double major_len = sqrt(eval[0]); if(points.size() >= 2) space = true;
double minor_len = sqrt(eval[1]); }
double major_len = sqrt(eval[0])/((double)point.size()-1); }
double minor_len = sqrt(eval[1])/((double)point.size()-1);
double major_len = sqrt(eval[0]/((double)point.size()-1)); // spatiotemporal proximity: need to check for time overlap
double minor_len = sqrt(eval[1]/((double)point.size()-1)); //if(mean.closeto(rhs.mean,time)) spacetime = true;
*/ // if not considering temporal overlap, just set to true,
// find point set’s bounding box // DOES THIS NEGATE SPATIOTEMPORAL CLUSTERING?!
//std::cerr << "points: " << point.size() << ", bounding box..."; spacetime = true;
for(int k=0; k<(int)point.size(); k++) {
Sep 01 2009 10:39 cluster.cpp Page 3/3
if(space && spacetime) intersection = true; // glutStrokeCharacter(GLUT_STROKE_ROMAN,letter);
// glutStrokeCharacter(GLUT_STROKE_ROMAN,letter % 94 + 33);
// avoid memory leak: proper way to delete off iterated list (STL book, p.205) // glutStrokeCharacter(GLUT_STROKE_ROMAN,letter % 59 + 65);
for(vector<Point* >::iterator it = points.begin(); it!= points.end();) glutStrokeCharacter(GLUT_STROKE_ROMAN,(letter - 1) % 26 + 65);
points.erase(it++); glPopMatrix();
}
return intersection;
} // render ellipse
glEnable(GL_POINT_SMOOTH);
void Cluster::render(float width,float height,vector<float>& pointColor, bool bbox) glPointSize(1.0);
{ if(ellipse) ellipse->render(width,height,bbox);
GLfloat clw;
// reset color
// get current line width glColor4f(pointColor[0],pointColor[1],pointColor[2],pointColor[3]);
glGetFloatv(GL_LINE_WIDTH,&clw);
// render axes
//if(point.size() < 2) return; if( ((int)major.size() >= 2) && ((int)minor.size() >= 2) ) {
glBegin(GL_LINES);
// set color to whatever we’re using glVertex2f(mean[0]*width,mean[1]*height);
glColor4f(pointColor[0],pointColor[1],pointColor[2],pointColor[3]); glVertex2f(mean[0]*width+major[0]*width,mean[1]*height+major[1]*height);
glEnd();
// render points making up cluster glBegin(GL_LINES);
for(int i=0; i < (int)point.size(); i++) { glVertex2f(mean[0]*width,mean[1]*height);
// point[i]->render(); glVertex2f(mean[0]*width+minor[0]*width,mean[1]*height+minor[1]*height);
glBegin(GL_QUADS); glEnd();
glVertex2f((*point[i])[0]*width-5.0, (*point[i])[1]*height-5.0); }
glVertex2f((*point[i])[0]*width+5.0, (*point[i])[1]*height-5.0);
glVertex2f((*point[i])[0]*width+5.0, (*point[i])[1]*height+5.0); // reset line width
glVertex2f((*point[i])[0]*width-5.0, (*point[i])[1]*height+5.0); glLineWidth(clw);
glEnd(); }
}

// render mean point


//mean.render();
glBegin(GL_QUADS);
glVertex2f(mean[0]*width-5.0, mean[1]*height-5.0);
glVertex2f(mean[0]*width+5.0, mean[1]*height-5.0);
glVertex2f(mean[0]*width+5.0, mean[1]*height+5.0);
glVertex2f(mean[0]*width-5.0, mean[1]*height+5.0);
glEnd();

// set thicker line width


glLineWidth(2);
// set color more opaque
glColor4f(pointColor[0],pointColor[1],pointColor[2],0.7);

// render labels
if(letter>0) {
double offset = sqrt(width*width + height*height)/30.0;
glPushMatrix();
glBegin(GL_LINES);
glVertex2f(mean[0]*width,mean[1]*height);
glVertex2f(mean[0]*width+minor[0]*width+offset,
mean[1]*height+minor[1]*height+offset);
glEnd();
glTranslatef(mean[0]*width+minor[0]*width+offset,
mean[1]*height+minor[1]*height+offset,0.0);
glScalef(0.1,0.1,1.0);
// glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12,letter);
Sep 01 2009 10:39 crand.cpp Page 1/1
#include <cstdlib> *idum=IA*(*idum-k*IQ)-IR*k;
#include <cmath> if(*idum < 0) *idum += IM;
#include <unistd.h> if(j<NTAB) iv[j] = *idum;
#include <sys/types.h> }
#include <sys/time.h> iy = iv[0];
}
#include "crand.h" k=(*idum)/IQ; // start here when not initializing.
*idum = IA*(*idum-k*IQ)-IR*k; // compute idm=(IA*idum) % IM without
double set_range(double rnum, double lo, double hi) if(*idum < 0) *idum += IM; // overflows by Schrage’s method.
{ j = iy/NDIV; // will be in the range 0..NTAB-1.
// returns random number in range [lo,hi] iy = iv[j]; // output previously stored valued
return(rnum*(hi-lo) + lo); iv[j] = *idum; // and refill the shuffle table.
} if((temp=AM*iy) > RNMX) return RNMX; // because users don’t expect
else return temp; // endpoint values.
float crand(int init) }
{
static long seed;
long pid;
struct timeval tp;
struct timezone tzp;

// interface to ran1: call with init=1 to init, init=0 for pseudo-random no.

if(init) {
// initialize generator with current time of day, in microseconds
pid = (long)getpid();
gettimeofday(&tp,&tzp);
seed = -( (tp.tv_sec*1000000 + tp.tv_usec) & pid );
ran1(&seed);
return(0.);
}
else
return(ran1(&seed));
}

float ran1(long *idum)


{
int j;
long k;
static long iy=0;
static long iv[NTAB];
float temp;

// ‘‘minimal’’ random number generator of Park and Miller with Bays-Durham


// shuffle and added safeguards. returns a uniform deviate between 0.0 and
// 1.0 (exclusive of the endpoint values). Call with idum a negative integer
// to initialize; thereafter, do not alter idum between successive deviates
// in a sequence. RNMX should approximate the largest floating value that
// is less than 1.
//
// From: Press, William H., Teukolsky, Saul A., Vetterling, William T., and
// Flannery, Brian P., ‘‘Numerical Recipes in C: The Art of Scientific
// Computing’’, Cambridge University Press, (Cambridge:1992), 2nd ed.

if(*idum <= 0 || !iy) { // initialize.


if(-(*idum) < 1) *idum = 1; // be sure to prevent idum = 0.
else *idum = -(*idum);
for(j=NTAB+7;j>=0;j--) { // load the shuffle table
k=(*idum)/IQ; // (after 8 warm-ups).
Sep 01 2009 10:39 distn.cpp Page 1/2
#include <cstdlib> if(ran < ratio) return(lower + sqrt(c*ran));
#include <cmath> else return(upper - sqrt(upper*upper + d - e*ran));
#include <unistd.h> }
#include <sys/types.h>
#include <sys/time.h> float weibull(float scale, float shape)
{
#include "crand.h" float precision=.00001;
#include "distn.h"
// weibull distn. of shape: 1 - exp[ -(x/scale)^shape]; for x > 0.
float cuniform(float a, float b) if(fabsf(scale - 0.) < precision) return(0.); // error
{ if(fabsf(shape - 0.) < precision) return(0.); // error
// sets crand()’s output to [a,b] range assuming uniform random generator return( scale * exp( (1./shape)*log( -log(crand(0)) ) ) );
if(b <= a) return( b + (a-b)*crand(0) ); }
else return( a + (b-a)*crand(0) );
} float erlang(float mean, float typee)
{
float normal(float mean, float sdev) int i,k;
{ float rnd;
float r1,r2,s; float precision=.00001;

do { if(fabsf(mean - 0.) < precision) return(0.); // error


r1 = (2.*crand(0)) - 1.; k = (int)ceilf(typee);
r2 = (2.*crand(0)) - 1.; if( (fabsf(typee - k) >= 0.01) || (k <= 0) ) return(0.); // error
s = r1*r1 + r2*r2; rnd = 0.;
} while( s >= 1. );
return( mean + sdev*(r1*sqrt(-2.*log(s)/s)) ); for(i=1;i<=k;i++) rnd += expd(mean/k);
}
return(rnd);
float lognormal(float mean, float sdev) }
{
float normmean, normvar, mu2, sigma2; float beta(float shape1, float shape2)
float precision=.00001; {
int k1,k2,count,maxcount=500;
if(fabsf(mean - 0.) < precision) return(0.); // error float x,y;
if(fabsf(sdev - 0.) < precision) return(0.); // error float precision=.00001;
mu2 = mean*mean;
sigma2 = sdev*sdev; // pdf(t) = [*t(shape1-1) * (1 - t)^(shape2-1) ] / betaFunction(.,.)
normmean = .5*log((mu2*mu2)/(sigma2 + mu2));
normvar = log((mu2 + sigma2)/mu2); if( (fabsf(shape1 - 0.) < precision) ||
return(exp((double)normal(normmean,sqrt((double)normvar)))); (fabsf(shape2 - 0.) < precision) ) return(0.); // error
} count = 1;
k1 = (int)ceilf(shape1);
float expd(float sdev) k2 = (int)ceilf(shape2);
{ if( ((k1*k2) > 0) && (fabsf(k1 - shape1)<.01) && (fabsf(k2 - shape2)<.01) ) {
return( -sdev * log(crand(0)) ); x = erlang(k1,k1);
} y = erlang(k2,k2);
return(x/(x + y));
float triangular(float lower, float mode, float upper) }
{
float ran,ratio,c,d,e; do {
x = exp( (1./shape1)*log(crand(0)) );
if(upper <= lower) return(0.); // error y = exp( (1./shape2)*log(crand(0)) );
if((mode <= lower) || count++;
(mode >= upper)) return(0.); // error if(count > maxcount) return(0.); // error
ran = crand(0); } while((x+y) <= 1.0);
ratio = (mode - lower)/(upper - lower);
c = (upper - lower)*(mode - lower); return( x / (x + y) );
d = (mode - lower)*(upper - mode) - 2.*upper*mode + mode*mode; }
e = (upper - lower)*(upper - mode);
Sep 01 2009 10:39 distn.cpp Page 2/2
float gama(float scale,float shape) }
{ return(k-1);
int k; }
float a,b,z,q,d,u1,u2,y,w,v;
float precision=.00001; float binomial(float number,float prob)
{
if( (scale <= precision) || (shape <= precision) ) return(0.); // error int i, n, k;
k = (int)ceilf(shape-0.001);
if(prob < 0.00001 || prob > 0.99999) return(0.); // error
if(fabsf(shape - k) < 0.01) n = (int)ceilf(number);
return(erlang(scale*shape,k)); if( (fabsf((float)n - number) > 0.001) || (n <= 1.0) ) return(0.); // error

if(shape > 1) { if(prob > 0.5)


a = 1.0/sqrt(2*shape-1); return(n - binomial(n,1 - prob));
b = shape-1.386294361; // log(4) = 1.386294361
q = shape + 1/a; if(n < 30) {
d = 2.504077397; // 1+log(4.5) = 2.504077397 k = 0;
for(i=1;i<=n;i++) if(crand(0) <= prob) k++;
for(k=1;k<=500;++k) { return(k);
u1 = crand(0); }
u2 = crand(0);
v = a*log(u1/(1.-u1)); if(prob < 0.1)
y = shape*exp(v); return(poisson(n * prob));
z = u1*u1*u2;
w = b + q*v - y; return((int)ceilf(normal(n*prob,sqrt(n*prob*(1-prob)))));
if((w+d-4.5*z) >= 0) return(scale*y); }
if( w >= log(z)) return(scale*y);
}
return(0.); // error, could not find gamma random variate
}

b = (2.718281828+shape)/2.718281828; // e = 2.7818281828
for(k=1;k<=500;++k) {
u1 = crand(0);
u2 = crand(0);
q = b*u1;
if(q > 1) {
y = -log((b-q)/shape);
if( u2 <= exp((shape-1)*log(y)) ) return(scale*y);
}
else {
y = exp( log(q)/shape );
if(u2 <= exp(-y)) return(scale*y);
}
}
return(0.); // error, could not find gamma random variate
}

float poisson(float mean)


{
int k=0;
float sum=0.0;
float precision=.00001;

if(fabsf(mean - 0.) < precision) return(0.); // error


if(mean > 25) return(ceilf(normal(mean,sqrt(mean))));
while(sum < mean) {
sum -= log(crand(0));
k++;
Sep 01 2009 10:39 ellipse.cpp Page 1/4
#include <iostream> h = rhs->h; k = rhs->k; rx = rhs->rx; ry = rhs->ry;
#include <iomanip> angle = rhs->angle; M = rhs->M; N = rhs->N;
#include <string> A = rhs->A; B = rhs->B; C = rhs->C; D = rhs->D; E = rhs->E; F = rhs->F;
#include <math.h> xmin = rhs->xmin; xmax = rhs->xmax; ymin = rhs->ymin; ymax = rhs->ymax;
}
#ifdef ANM_OSX
#include <OpenGL/gl.h> void Ellipse::render_polynomial(float width,float height)
#include <OpenGL/glu.h> {
#include <OpenGL/glext.h> // polynomial method
#else // from: <http://www.wol.net.pk/mtshome/cppComputerGraphics.html#Ellipse>
#include <GL/gl.h> float x=rx;
#include <GL/glu.h> float y=0;
//#include <GL/glext.h> float range=0;
#endif
glBegin(GL_POINTS);
using namespace std; do {
y=fabsf(ry*sqrt(1-((x*x)/(rx*rx))));
#include "ellipse.h"
#include "point.h" glVertex2i((int)(h*width+x*width+0.5),(int)(k*height+y*height+0.5));
#include "polynomial.h" glVertex2i((int)(h*width+x*width+0.5),(int)(k*height-y*height+0.5));
glVertex2i((int)(h*width-x*width+0.5),(int)(k*height-y*height+0.5));
// ellipse implementation; some of the code obtained off the web glVertex2i((int)(h*width-x*width+0.5),(int)(k*height+y*height+0.5));
// (c) Andrew T. Duchowski
// x-=0.01;
/////////////////////////// friends //////////////////////////////////////////// x-=0.00001562500000000000;
ostream& operator<<(ostream& s,Ellipse& rhs) } while(x>=range);
{ glEnd();
s.setf(ios::fixed,ios::floatfield); }
s.precision(2);
s << "(x - " << rhs.h << ")^2 (y - " << rhs.k << ")^2" << endl; void Ellipse::render_bresenham(float width, float height)
s << "---------- + --------- = 1 " << endl; {
s << " " << rhs.rx << "^2" << " " << rhs.ry << "^2" << endl; // Bresenham’s method
s << endl; // from: <http://www.j3d.org/matrix_faq/curvfaq_latest.html>
s << rhs.getQuadratic();
return s; #define PUTPIXEL(x,y) {\
} glBegin(GL_POINTS); \
glVertex2f((x*width+0.5),(y*height+0.5)); \
Ellipse Ellipse::operator=(const Ellipse& rhs) glVertex2f((x*width+0.5),(y*height+0.5)); \
{ glVertex2f((x*width+0.5),(y*height+0.5)); \
if(this != &rhs) { glVertex2f((x*width+0.5),(y*height+0.5)); \
h = rhs.h; k = rhs.k; rx = rhs.rx; ry = rhs.ry; glEnd(); \
angle = rhs.angle; M = rhs.M; N = rhs.N; }
A = rhs.A; B = rhs.B; C = rhs.C; D = rhs.D; E = rhs.E; F = rhs.F;
xmin = rhs.xmin; xmax = rhs.xmax; ymin = rhs.ymin; ymax = rhs.ymax; float yp = ymin;
quadratic = rhs.quadratic; float xp = xmin;
} float d = A*xmin*xmin + B*ymin*ymin + C*xmin + D*ymin + E*xmin*ymin + F;
return *this; float dx = 2.0*A*xmin + C + E*ymin;
} float ddxx = 2.0*A;
float dy = 2.0*B*ymin + D + E*ymin;
Ellipse::Ellipse(const Ellipse& rhs) : quadratic(rhs.quadratic) float ddyy = 2.0*B;
{ float ddxy = 2.0*E;
h = rhs.h; k = rhs.k; rx = rhs.rx; ry = rhs.ry; float ddyx = 2.0*E;
angle = rhs.angle; M = rhs.M; N = rhs.N;
A = rhs.A; B = rhs.B; C = rhs.C; D = rhs.D; E = rhs.E; F = rhs.F; #define INCREMENT_X() {\
xmin = rhs.xmin; xmax = rhs.xmax; ymin = rhs.ymin; ymax = rhs.ymax; xp += 1;\
} d += dx;\
dx += ddxx;\
Ellipse::Ellipse(Ellipse* rhs) : quadratic(rhs->quadratic) dy += ddxy;\
{ }
Sep 01 2009 10:39 ellipse.cpp Page 2/4
#define INCREMENT_Y() {\ }
yp += 1;\
d += dx;\ while ( dx < 0 ) { // Octant 7 (10:30 - 12:00)
dx += ddyx;\ PUTPIXEL(xp, yp);
dy += ddyy;\ INCREMENT_X();
} if ( d < 0 ) DECREMENT_Y();
#define DECREMENT_X() {\ }
xp -= 1;\ }
dx -= ddxx;\
dy -= ddxy;\ void Ellipse::render(float width, float height, bool box)
d -= dx;\ {
} glPushMatrix();
#define DECREMENT_Y() {\ glLoadIdentity();
yp -= 1;\
dx -= ddyx;\ // use this for ellipse rotated about origin
dy -= ddyy;\ //glRotatef(-angle,0,0,1);
d -= dy;\ // should be using this for ellipse rotated about own center point
} glTranslatef(h*width,k*height,0);
glRotatef(angle,0,0,1);
while ( dx < -dy ) { // Octant 0 (12:00 - 01:30) glTranslatef(-h*width,-k*height,0);
PUTPIXEL(xp, yp);
INCREMENT_X(); render_polynomial(width,height);
if ( d > 0 ) INCREMENT_Y(); //render_bresenham(width,height);
}
glPopMatrix();
while ( dy < 0 ) { // Octant 1 (01:30 - 03:00)
PUTPIXEL(xp, yp); // draw the bounding rectangles
INCREMENT_Y(); if(box) {
if ( d < 0 ) INCREMENT_X(); glBegin(GL_LINES);
} glVertex2f(xmin*width,ymin*height);
glVertex2f(xmax*width,ymin*height);
while ( dy < dx ) { // Octant 2 (03:00 - 04:30) glEnd();
PUTPIXEL(xp, yp); glBegin(GL_LINES);
INCREMENT_Y(); glVertex2f(xmax*width,ymin*height);
if ( d > 0 ) DECREMENT_X(); glVertex2f(xmax*width,ymax*height);
} glEnd();
glBegin(GL_LINES);
while ( dx > 0 ) { // Octant 3 (04:30 - 06:00) glVertex2f(xmax*width,ymax*height);
PUTPIXEL(xp, yp); glVertex2f(xmin*width,ymax*height);
DECREMENT_X(); glEnd();
if ( d < 0 ) INCREMENT_Y(); glBegin(GL_LINES);
} glVertex2f(xmin*width,ymax*height);
glVertex2f(xmin*width,ymin*height);
while ( -dx < dy ) { // Octant 4 (06:00 - 07:30) glEnd();
PUTPIXEL(xp, yp); }
DECREMENT_X(); }
if ( d > 0 ) DECREMENT_Y();
} void Ellipse::updateQuadratic()
{
while ( dy > 0 ) { // Octant 5 (07:30 - 09:00) /*
PUTPIXEL(xp, yp); From: <http://www.j3d.org/matrix_faq/curvfaq_latest.html>
DECREMENT_Y();
if ( d < 0 ) DECREMENT_X(); Given the algebraic quadratic expression of an ellipse:
} Ax^2 + By^2 + Cx + Dy + Exy + F = 0

while ( dx < dy ) { // Octant 6 (09:00 - 10:30) with center (c,d) and axes (r,s) [this class uses (h,k) (rx,ry) instead]
PUTPIXEL(xp, yp);
DECREMENT_Y(); coefficients for axis-aligned ellipse:
if ( d > 0 ) INCREMENT_X(); A = s^2
Sep 01 2009 10:39 ellipse.cpp Page 3/4
B = r^2 N2*(r2*c2 + s2*d2) +
C = -2s^2c 2*M*N*c*d*(s2 - r2) +
D = -2r^2d -r2*s2;
E = 0.0; /*
F = s^2c^2 + r^2d^2 - r^2s^2 // translated, no rotation, axis-aligned
A = s2;
coefficient for ellipse rotated by arbitrary angle about origin B = r2;
with M = cos(angle), N = sin(angle): C = -2.0*s2*c;
A = s^2M^2 + r^2N^2 D = -2.0*r2*d;
B = s^2N^2 + r^2M^2 E = 0.0;
C = -2(s^2Mc + r^2Nd) F = s2*c2 + r2*d2 - r2*s2;
D = 2(s^2Nc - r^2Md) */
E = 2MN(r^2 - s^2) /*
F = s^2c^2 + r^2d^2 - r^2s^2 // rotated about origin
A = s2*M2 + r2*N2;
(note that the above rotates ellipse about z-axis, hence not "in place", B = s2*N2 + r2*M2;
or in other words, assuming ellipse is at the origin; thus to draw it, C = -2.0*(s2*M*c + r2*N*d);
don’t shift to origin and then back) D = 2.0*(s2*N*c - r2*M*d);
*/ E = 2.0*M*N*(r2 - s2);
F = s2*c2 + r2*d2 - r2*s2;
double c=h, d=k; */
double r=rx, s=ry; /*
double r2=r*r, s2=s*s; // translated to (c,d), rotated (about origin), then shifted back to origin
double c2=c*c, d2=d*d; // (useless! ellipses are stuck at the origin)
double M2 = M*M, N2 = N*N; A = s2*M2 + r2*N2;
double P,Q,R; B = s2*N2 + r2*M2;
// double one_M = (1.0-M); C = 2.0*(s2*M*c - r2*N*d) - 2.0*c*(r2*N2 + s2*M2) + 2.0*M*N*d*(r2 - s2);
// double one_M2 = one_M*one_M; D = 2.0*(s2*N*c + r2*M*d) - 2.0*d*(r2*M2 + s2*N2) + 2.0*M*N*c*(r2 - s2);
E = 2.0*M*N*(s2 - r2);
/* F = N2*(s2*d2 + r2*c2) + one_M2*(s2*c2 + r2*d2) +
// axis-aligned at origin 2.0*M*N*d*c*(s2 - r2) + 2.0*N*d*c*(r2 - s2) +
A = s2; -r2*s2;
B = r2; */
C = 0.0; // We use
D = 0.0; // Ax^2 + By^2 + Cx + Dy + Exy + F = 0
E = 0.0; // but polynomial used in intersection code assumes
F = -r2*s2; // Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0
*/ //std::cerr << "creating polynomial with";
/* //std::cerr << " A = " << A;
// rotated at origin //std::cerr << " B = " << B;
A = s2*M2 + r2*N2; //std::cerr << " C = " << C;
B = s2*N2 + r2*M2; //std::cerr << " D = " << D;
C = 0.0; //std::cerr << " E = " << E;
D = 0.0; //std::cerr << " F = " << F;
E = 2.0*M*N*(s2 - r2); //std::cerr << std::endl;
F = -r2*s2; quadratic = Polynomial(F,D,C,B,E,A);
*/
// rotated at origin, translated to (c,d) // determine limits, xmin, xmax, ymin, ymax by solving
// this is what we want! I got some inspiration for this approach // parametric Pt^2 + Qt + R = 0
// from <http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/> P = 4.0*A*B - E*E;
// which said that you have to evaluate the quadratic at each Q = 4.0*B*C - 2.0*D*E;
// stage of the transformation, hence, start with the above simple R = 4.0*B*F - D*D;
// ellipse, then, rotate, then translate. //std::cerr << "calculating roots of polynomial with";
A = s2*M2 + r2*N2; //std::cerr << " P = " << P;
B = s2*N2 + r2*M2; //std::cerr << " Q = " << Q;
C = -2.0*c*(s2*M2 + r2*N2) - 2.0*M*N*d*(s2 - r2); //std::cerr << " R = " << R;
D = -2.0*d*(s2*N2 + r2*M2) - 2.0*M*N*c*(s2 - r2); //std::cerr << std::endl;
E = 2.0*M*N*(s2 - r2); vector<double> xaxis = Polynomial(P,Q,R).roots();
F = M2*(s2*c2 + r2*d2) + switch((int)xaxis.size()) {
Sep 01 2009 10:39 ellipse.cpp Page 4/4
case 0: xmin = xmax = 0.0; break;
case 1: xmin = xmax = xaxis[0]; break;
case 2: xmin = xaxis[1]; xmax = xaxis[0]; break;
}

P = 4.0*A*B - E*E;
Q = 4.0*A*D - 2.0*C*E;
R = 4.0*A*F - C*C;
//std::cerr << "calculating roots of polynomial with";
//std::cerr << " P = " << P;
//std::cerr << " Q = " << Q;
//std::cerr << " R = " << R;
//std::cerr << std::endl;
vector<double> yaxis = Polynomial(P,Q,R).roots();
switch((int)yaxis.size()) {
case 0: ymin = ymax = 0.0; break;
case 1: ymin = ymax = yaxis[0]; break;
case 2: ymin = yaxis[1]; ymax = yaxis[0]; break;
}
}

vector<Point* > Ellipse::intersect(Ellipse& rhs)


{
Polynomial a(quadratic), b(rhs.quadratic);
Polynomial yPoly = bezout(a,b);
vector<double> yRoots = yPoly.roots();
double epsilon = 0.001;
vector<Point* > result;

double norm0 = (a[0]*a[0] + 2.0*a[1]*a[1] + a[2]*a[2]) * epsilon;


double norm1 = (b[0]*b[0] + 2.0*b[1]*b[1] + b[2]*b[2]) * epsilon;

// code for this routine originated from:


// <http://www.kevlindev.com/gui/math/intersection/Intersection.js>

for(int y = 0; y < (int)yRoots.size(); y++) {


Polynomial xPoly(a[0],
a[3] + yRoots[y] * a[1],
a[5] + yRoots[y] * (a[4] + yRoots[y]*a[2]));
vector<double> xRoots = xPoly.roots();

for(int x = 0; x < (int)xRoots.size(); x++) {


double test = (a[0]*xRoots[x] + a[1]*yRoots[y] + a[3]) * xRoots[x] +
(a[2]*yRoots[y] + a[4]) * yRoots[y] +
a[5];
if(fabsf(test) < norm0) {
test = (b[0]*xRoots[x] + b[1]*yRoots[y] + b[3]) * xRoots[x] +
(b[2]*yRoots[y] + b[4]) * yRoots[y] +
b[5];
if(fabsf(test) < norm1) {
result.push_back(new Point(xRoots[x], yRoots[y]));
}
}
}
}
//std::cerr << "got " << result.size() << " intersections" << std::endl;
return result;
}
Sep 01 2009 10:39 event.cpp Page 1/1
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <string>
#include <algorithm>

using namespace std;

#include "event.h"

// scanpath implementation
// (c) Andrew T. Duchowski

istream& operator>>(istream& s, Event& rhs)


{
int key;

s >> rhs.t;
s >> rhs.name;
s >> key;
s >> rhs.x;
s >> rhs.y;

return s;
}

ostream& operator<<(ostream& s, const Event& rhs)


{
s << rhs.t << "\t"; // Timestamp
s << rhs.name << "\t"; // Event name
s << 1 << "\t"; // EventKey = 1
s << rhs.x << "\t"; // x coord
s << rhs.y; // y coord

return s;
}
Sep 01 2009 10:39 filter.cpp Page 1/1
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <string>
#include <algorithm>

using namespace std;

#include "filter.h"

// scanpath implementation
// (c) Andrew T. Duchowski

istream& operator>>(istream& s, Filter& rhs)


{
string str;

s >> str; s >> str; // "Filter settings:"


s >> str; // "Eye:"
s >> rhs.eye;
s >> str; s >> str; // "Validity level:"
s >> rhs.validity;
s >> str; s >> str; // "Fixation radius:"
s >> rhs.r;
s >> str; s >> str; // "Min duration:"
s >> rhs.d;

return s;
}

ostream& operator<<(ostream& s, const Filter& rhs)


{
s << "Filter settings:" << std::endl; s << std::endl;
s << "Eye: ";
s << rhs.eye << std::endl;
s << "Validity level: ";
s << rhs.validity << std::endl;
s << "Fixation radius: ";
s << rhs.r << std::endl;
s << "Min duration: ";
s << rhs.d << std::endl;

return s;
}
Sep 01 2009 10:39 fixation.cpp Page 1/1
#include <iostream> return s;
#include <iomanip> }
#include <string>
#include <math.h> Fixation::Fixation(const Fixation& rhs) : Point(rhs)
{
#ifdef ANM_OSX number = rhs.number;
#include <OpenGL/gl.h> duration = rhs.duration;
#include <OpenGL/glu.h> amplitude = rhs.amplitude;
#include <OpenGL/glext.h> radius = rhs.radius;
#else type = rhs.type;
#include <GL/gl.h> }
#include <GL/glu.h>
//#include <GL/glext.h> Fixation::Fixation(Fixation* rhs) : Point(rhs)
#endif {
number = rhs->number;
using namespace std; duration = rhs->duration;
amplitude = rhs->amplitude;
#include "point.h" radius = rhs->radius;
#include "fixation.h" type = rhs->type;
}
// fixation class implementation
// (c) Andrew T. Duchowski Fixation Fixation::operator=(const Fixation& rhs)
{
/////////////////////////// friends //////////////////////////////////////////// if(this != &rhs) {
istream& operator>>(istream& s,Fixation& rhs)
{ // copy base class data members (must use base class cast)
int i=0; ((Point &)*this) = rhs;
char c,n;
double f; // copy local data members
number = rhs.number;
// assume we’re reading in Tobii’s FXD (Fixation Data) data file duration = rhs.duration;
// Fix number,Timestamp,Duration,GazepointX,GazepointY amplitude = rhs.amplitude;
radius = rhs.radius;
// read in Fix number type = rhs.type;
s >> rhs.number; }
return *this;
// read timestamp }
s >> rhs.timestamp;

// read duration
s >> rhs.duration;

do {
// read in fixation coordinate
s >> f; rhs[i] = f; i++;
} while( s.get(c) && (c != ’\n’) && ((n = s.peek()) != ’\n’) && (i<2) );

return(s);
}

ostream& operator<<(ostream& s,const Fixation& rhs)


{
s.setf(ios::fixed,ios::floatfield);
s.precision(8);
s << rhs.number;
s << "\t" << rhs.timestamp;
s << "\t" << rhs.duration;
s << "\t" << rhs.coord[0] << "\t" << rhs.coord[1];
s << endl;
Sep 01 2009 10:39 fixationfilter.cpp Page 1/5
#include <iostream> // fixation, the fixation is extended to include the new gazepoint.
#include <cmath> // (The radius of the acceptance circle is user specified by setting the
#include <sys/time.h> // value of the function argument gaze_deviation_threshold.)
// To accommodate noisy eyegaze measurements, a gazepoint that exceeds
#include "fixationfilter.h" // the deviation threshold is included in an on-going fixation if the
// subsequent gazepoint returns to a position within the threshold.
// Nearly verbatim from LC Technologies’ fixfunc.c // If a gazepoint is not found, during a blink for example, a fixation
// Eye Fixation Analysis Functions // is extended if a) the next legitimate gazepoint measurement falls within
// LC Technologies, Inc. // the acceptance circle, and b) there are less than minimum_fix_samples
// 9455 Silver King Court // of successive missed gazepoints. Otherwise, the previous fixation
// Fairfax, VA 22031 // is considered to end at the last good gazepoint measurement.
// (703) 385-7133 //
// Available on the web, somewhere in the vicinity of // UNITS OF MEASURE
// http://www.eyegaze.com //
// The gaze position/direction may be expressed in any units (e.g.
// some additions made by ARF: Andrew R. Freed // millimeters, pixels, or radians), but the filter threshold must be
// original fixfunc.c found at: // expressed in the same units.
// http://www.freedville.com/professional/thesis/eyetrack/source-code/fixfunc.c //
// (last accessed 2/7/2009) // INITIALIZING THE FUNCTION
//
// bug fix by ATD: Andrew T. Duchowski // Prior to analyzing a sequence of gazepoint data, the initFixation
// function should be called to clear any previous, present and new
// orig. documentation follows, modified to reflect current C++ implementation // fixations and to initialize the ring buffers of prior gazepoint data.
//
// RETURN VALUES - Eye Motion State: // PROGRAM NOTES
// //
// MOVING 0 The eye was in motion min_fix_samples ago // For purposes of describing an ongoing sequence of fixations, fixations
// FIXATING 1 The eye was fixating min_fix_samples ago // in this program are referred to as "previous", "present", and "new".
// FIXATION_COMPLETED 2 A completed fixation has just been detected; // The present fixation is the one that is going on right now, or, if a
// the fixation ended min_fix_samples ago // new fixation has just started, the present fixation is the one that
// // just finished. The previous fixation is the one immediatly preceeding
// Include fixationfilter.h for class def. and above constant definitions. // the present one, and a new fixation is the one immediately following
// // the present one. Once the present fixation is declared to be completed,
// SUMMARY // the present fixation becomes the previous one, the new fixation becomes
// // the present one, and there is not yet a new fixation.
// This function converts a series of uniformly-sampled (raw) gaze
// points into a series of variable-duration saccades and fixations. FixationFilter::FixationFilter(float gaze_dev_threshold,
// Fixation analysis may be performed in real time or after the fact. To int minimum_fix_samples)
// allow eye fixation analysis during real-time eyegaze data collection, {
// the function is designed to be called once per sample. When the eye gaze_deviation_threshold = gaze_dev_threshold;
// is in motion, ie during saccades, the function returns 0 (MOVING). initFixation(minimum_fix_samples);
// When the eye is still, ie during fixations, the function returns 1 }
// (FIXATING). Upon the detected completion of a fixation, the function
// returns 2 (FIXATION_COMPLETED) and produces: void FixationFilter::initFixation(int minimum_fix_samples)
// a) the time duration of the saccade between the last and present {
// eye fixation (eyegaze samples) // this function clears any previous, present and new fixations, and it
// b) the time duration of the present, just completed fixation // initializes detectFixation()’s internal ring buffers of prior
// (eyegaze samples) // gazepoint data. initFixation() should be called prior to a sequence
// c) the average x and y coordinates of the eye fixation // of calls to detectFixation().
// (in user defined units of x_gaze and y_gaze)
// Note: Although this function is intended to work in "real time", there // minimum number of gaze samples that can be considered a fixation
// is a delay of minimum_fix_samples in the filter which detects the // note: if the input value is less than 3, the function sets it to 3
// motion/fixation condition of the eye. minimum_fixation_samples = minimum_fix_samples;
//
// PRINCIPLE OF OPERATION // make sure the minimum fix time is at least 3 samples
// if(minimum_fixation_samples < 3) minimum_fixation_samples = 3;
// This function detects fixations by looking for sequences of gaze-
// point measurements that remain relatively constant. If a new gazepoint if(minimum_fixation_samples >= RING_SIZE) {
// lies within a circular region around the running average of an on-going std::cerr << "Warning: minimum_fixation_samples ";
Sep 01 2009 10:39 fixationfilter.cpp Page 2/5
std::cerr << minimum_fixation_samples; // PROCESS TRACKED EYE
std::cerr << " >= RING_SIZE " << RING_SIZE << std::endl; // A1: if the eye’s gazepoint was successfully measured this sample
} if(gazepoint_found) {
nNoEyeFound = 0; // number of successive no-tracks is zero
// initialize the internal ring buffer // B1: if there is a present fixation
for(ringIndex = 0; ringIndex < RING_SIZE; ringIndex++) { if(nPresFixSamples > 0) {
x_gaze_ring[ringIndex] = 0.0F; // compute the deviation of the gaze point from the present fixation
y_gaze_ring[ringIndex] = 0.0F; calculateGazeDeviationFromPresentFixation(x_gaze, y_gaze);
gaze_found_ring[ringIndex] = false; // C1: if the gaze point is within the present fixation region
eye_motion_state[ringIndex] = MOVING; if(presDr <= gaze_deviation_threshold) {
x_fix_ring[ringIndex] = 0.0F; // restore any previous gazepoints that were temporarily left
y_fix_ring[ringIndex] = 0.0F; // out of the fixation
gaze_deviation_ring[ringIndex] = -0.1F; restoreOutPoints();
sac_duration_ring[ringIndex] = 0; // update the present fixation hypothesis and check if there
fix_duration_ring[ringIndex] = 0; // are enough samples to declare that the eye is fixating
} updatePresentFixation(x_gaze, y_gaze);
ringIndex = 0; }
ringIndexDelay = RING_SIZE - minimum_fixation_samples; // C2: otherwise (gaze point outside present fixation region)
else { // presDr > gaze_deviation_threshold
// set the call count to 0, init the previous fixation end count // increment the number of gazepoint samples outside the present fix.
// so the first saccade duration is a legitimate count nPresOut++;
callCount = 0; // ATD: I think nPresOut has to be (range) limited to RING_SIZE
prevFixEndCount = 0; if(nPresOut >= RING_SIZE) nPresOut = 0;
// D1: if the present fixation is finished, i.e., if there have
// reset the present fixation data // been minimum_fixation_samples since the gazepoint last matched
resetPresentFixation(); // the present fixation, and the present fixation is long
// enough to count as a real fixation,
// reset the new fixation data if(((int)(callCount - presFixEndCount) >= minimum_fixation_samples) &&
resetNewFixation(); (nPresFixSamples >= minimum_fixation_samples)) {
// declare the present fixation to be completed, move the
// initialize the number of successive samples with no eye found // present fixation to the prior, move the new fixation to
nNoEyeFound = 0; // the present, and check if the new (now present) fixation
} // has enough points for the eye to be declared to be fixating
declareCompletedFixation();
int FixationFilter::detectFixation(bool gazepoint_found, // compute the deviation of the gazepoint from the now present fix.
float x_gaze, float y_gaze) calculateGazeDeviationFromPresentFixation(x_gaze, y_gaze);
{ // E1: if the gazepoint is within the now present fixation region
// increment the call count, the ring index, and the delayed ring index if(presDr <= gaze_deviation_threshold) {
callCount++; // update the present fixation data and check if there
ringIndex++; // are enough samples to declare that the eye is fixating
if(ringIndex >= RING_SIZE) ringIndex = 0; updatePresentFixation(x_gaze, y_gaze);
ringIndexDelay = ringIndex - minimum_fixation_samples; }
if(ringIndexDelay < 0) ringIndexDelay += RING_SIZE; // E2: otherwise (the gazepoint is outside the present fix. region)
else {
// update the storage rings // start a new fixation at the gazepoint
x_gaze_ring[ringIndex] = x_gaze; startNewFixationAtGazepoint(x_gaze, y_gaze);
y_gaze_ring[ringIndex] = y_gaze; }
gaze_found_ring[ringIndex] = gazepoint_found; }
// D2: otherwise (the present fixation is not finished)
// initially assume the eye is moving else {
// note: these values are updated during the processing of this and // F1: if there is a new fixation hypothesis
// subsequent gazepoints if(n_new_fix_samples > 0) {
eye_motion_state[ringIndex] = MOVING; // compute the deviation of the gazepoint from the new fixation
x_fix_ring[ringIndex] = -0.0F; calculateGazeDeviationFromNewFixation(x_gaze, y_gaze);
y_fix_ring[ringIndex] = -0.0F; // G1: if the new point falls within the new fix
gaze_deviation_ring[ringIndex] = -0.1F; if(new_dr <= gaze_deviation_threshold) {
sac_duration_ring[ringIndex] = 0; // update the new fixation hypothesis
fix_duration_ring[ringIndex] = 0; updateNewFixation(x_gaze, y_gaze);
// H: if there are now enough points in the new fix
Sep 01 2009 10:39 fixationfilter.cpp Page 3/5
// to declare it a real fix
if(n_new_fix_samples == minimum_fixation_samples) { // return the eye motion/fixation state for the delayed point
// drop the present fixation data, move the new return(eye_motion_state[ringIndexDelay]);
// fixation into the present fixation and see }
// if the new (now present) fixation has enough
// points to declare the eye to be fixating void FixationFilter::resetPresentFixation(void)
moveNewFixationToPresentFixation(); {
} // reset the present fixation, i.e., declare it nonexistent
} presFixStartCount = 0;
// G2: otherwise (the point is outside the new fixation) presFixEndCount = 0;
else { nPresFixSamples = 0;
// start the new fixation at the new gazepoint xPresFixSum = 0.0F;
startNewFixationAtGazepoint(x_gaze, y_gaze); yPresFixSum = 0.0F;
} xPresFix = 0.0F;
} yPresFix = 0.0F;
// F2: otherwise (there is not a new fixation) nPresOut = 0;
else { }
// start the new fixation at the gazepoint
startNewFixationAtGazepoint(x_gaze, y_gaze); void FixationFilter::resetNewFixation(void)
} {
} // reset new fixation, i.e., declare it nonexistent
} new_fix_start_count = 0;
} new_fix_end_count = 0;
// B2: otherwise (ther is not a present fixation) n_new_fix_samples = 0;
else { x_new_fix_sum = 0.0F;
// start the present fixation at the gazepoint and reset the new fixation y_new_fix_sum = 0.0F;
startPresentFixationAtGazepoint(x_gaze, y_gaze); x_new_fix = 0.0F;
} y_new_fix = 0.0F;
} }
// PROCESS THE UNTRACKED EYE
// A2: otherwise (the gazepoint was not successfully measured this sample) void FixationFilter::startPresentFixationAtGazepoint(float x_gaze, float y_gaze)
else { {
// increment the number of successive samples with no eye found // starts the present fixation at the argument gazepoint
nNoEyeFound++; // and makes sure there is no new fixation hypothesis
// I: if it has been minimum_fixation_samples since the last sample nPresFixSamples = 1;
// in the present fixation xPresFixSum = x_gaze;
if((int)(callCount-presFixEndCount) >= minimum_fixation_samples) { yPresFixSum = y_gaze;
// J: if there had been a fixation prior to losing track of the eye xPresFix = x_gaze;
if(nPresFixSamples >= minimum_fixation_samples) { yPresFix = y_gaze;
// declare the present fixation to be completed, move the presFixStartCount = callCount;
// present fixation to the prior, move the new fixation to presFixEndCount = callCount;
// the present, and check if the new (now present) fixation nPresOut = 0;
// has enough points for the eye to be declared to be fixating
declareCompletedFixation(); // make sure there is no new fixation
} resetNewFixation();
// reset the present fixation data }
resetPresentFixation();
} void FixationFilter::startNewFixationAtGazepoint(float x_gaze, float y_gaze)
} {
// originally, this code chunk would pass the data back to calling function, // start new fixation at argument gazepoint
// passing the delayed gazepoint data, with relevant saccade/fixation data n_new_fix_samples = 1;
x_gaze_delayed = x_gaze_ring[ringIndexDelay]; x_new_fix_sum = x_gaze;
y_gaze_delayed = y_gaze_ring[ringIndexDelay]; y_new_fix_sum = y_gaze;
gazepoint_found_delayed = gaze_found_ring[ringIndexDelay]; x_new_fix = x_gaze;
x_fix_delayed = x_fix_ring[ringIndexDelay]; y_new_fix = y_gaze;
y_fix_delayed = y_fix_ring[ringIndexDelay]; new_fix_start_count = callCount;
gaze_deviation_delayed = gaze_deviation_ring[ringIndexDelay]; new_fix_end_count = callCount;
saccade_duration_delayed = sac_duration_ring[ringIndexDelay]; }
fix_duration_delayed = fix_duration_ring[ringIndexDelay];
Sep 01 2009 10:39 fixationfilter.cpp Page 4/5
void FixationFilter::updatePresentFixation(float x_gaze, float y_gaze)
{ // checks to see whether there are enough samples in the presently
// update present fixation with the argument gazepoint, // hypothesized fixation to declare that the eye is fixating yet,
// checks if there are enough samples to declare that the eye is now // and if there is a true fixation going on, it updates the ring
// fixating, and makes sure there is no hypothesis for a new fixation // buffers to reflect the fixation
nPresFixSamples++;
xPresFixSum += x_gaze; // if there are enough samples for a fixation
yPresFixSum += y_gaze; if(nPresFixSamples >= minimum_fixation_samples) {
xPresFix = xPresFixSum / nPresFixSamples; // declare the eye to be fixating; go back through the last
yPresFix = yPresFixSum / nPresFixSamples; // minimum_fixation_samples entries of the ring buffer making sure that all
presFixEndCount = callCount; // samples from the present fixation are marked as fixating, and set
nPresOut = 0; // the entries with the newest estimate of the fixation location
for(i=0; i < minimum_fixation_samples; i++) {
// check if there are enough samples in the present fixation hypothesis
// to declare that the eye is fixating ii = ringIndex - i;
checkIfFixating(); if(ii < 0) ii += RING_SIZE;

// there is no hypothesis for a new fixation eye_motion_state[ii] = FIXATING;


resetNewFixation(); x_fix_ring[ii] = xPresFix;
} y_fix_ring[ii] = yPresFix;

void FixationFilter::updateNewFixation(float x_gaze, float y_gaze) sac_duration_ring[ii] = (int)(presFixStartCount - prevFixEndCount-1);


{ fix_duration_ring[ii] = (int)(presFixEndCount - presFixStartCount+1-i);
// updates the new fixation with the argument gazepoint }
n_new_fix_samples++; }
x_new_fix_sum += x_gaze; }
y_new_fix_sum += y_gaze;
x_new_fix = x_new_fix_sum / n_new_fix_samples; void FixationFilter::moveNewFixationToPresentFixation(void)
y_new_fix = y_new_fix_sum / n_new_fix_samples; {
new_fix_end_count = callCount; // copies the new fixation data into the present fixation
} // and resets the new fixation
nPresFixSamples = n_new_fix_samples;
void FixationFilter::calculateGazeDeviationFromPresentFixation(float x_gaze, float y_g xPresFixSum = x_new_fix_sum;
aze) yPresFixSum = y_new_fix_sum;
{ xPresFix = x_new_fix;
float dx, dy; // horiz. and vert. deviations yPresFix = y_new_fix;
presFixStartCount = new_fix_start_count;
// calculate the deviation of the gazepoint from the present fixation location presFixEndCount = new_fix_end_count;
dx = x_gaze - xPresFix; nPresOut = 0;
dy = y_gaze - yPresFix;
presDr = (float)sqrt(dx*dx + dy*dy); // reset new fixation
resetNewFixation();
// put the deviation in the ring buffer for future reference
gaze_deviation_ring[ringIndex] = presDr; // check if there are enough samples in the new (now present) fixation to
} // declare that the eye is fixating
checkIfFixating();
void FixationFilter::calculateGazeDeviationFromNewFixation(float x_gaze, float y_gaze) }
{
float dx, dy; // horiz. and vert. deviations void FixationFilter::declareCompletedFixation(void)
{
// calculate the deviation of the gazepoint from the new fixation location // declare the present fixation to be completed
dx = x_gaze - x_new_fix; eye_motion_state[ringIndexDelay] = FIXATION_COMPLETED;
dy = y_gaze - y_new_fix;
new_dr = (float)sqrt(dx*dx + dy*dy); // move the present fixation to the previous fixation; this saves the
} // end time of the present fixation for later computation of the saccade
// period between this and the next fixation
void FixationFilter::checkIfFixating(void) prevFixEndCount = presFixEndCount;
{
int i, ii; // dummy ring indices // move the new fixation data, if any, to the present fixation, reset
Sep 01 2009 10:39 fixationfilter.cpp Page 5/5
// the new fixation, and check if there are enough samples in the new
// (now present) fixation to declare that the eye is fixating
moveNewFixationToPresentFixation();
}

void FixationFilter::restoreOutPoints(void)
{
int i, ii; // dummy ring indices

// restores any previous gazepoints that were left out of


// the fixation and are now known to be part of the present fixation

// if there were some points that temporarily went out of the fixation region
if(nPresOut > 0) {

// undo the hypothesis that they were outside the fixation and declare
// them now to be part of the fixation
for(i = 1; i <= nPresOut; i++) {
ii = ringIndex - i;
if(ii < 0) ii += RING_SIZE;
// ATD: I think if nPresOut is not (range) limited to RING_SIZE
// BUG! ii goes -ve (way -ve, out to -184)
// std::cerr << "ii = " << ii << std::endl;
if(gaze_found_ring[ii]) {
nPresFixSamples++;
xPresFixSum += x_gaze_ring[ii];
yPresFixSum += y_gaze_ring[ii];
eye_motion_state[ii] = FIXATING;
}
}
// set the number of "out" points to be zero
nPresOut = 0;
}
}
Sep 01 2009 10:39 gltexobj.cpp Page 1/13
/**************************************************************************** #include <matrix.h>
** $Id: qt/gltexobj.cpp 3.1.2 edited Nov 8 2002 $ #include <quaternion.h>
**
** Copyright (C) 1992-2002 Trolltech AS. All rights reserved. #include "gltexobj.h"
** #include "crand.h"
** This file is part of an example program for Qt. This example #include "distn.h"
** program may be used, distributed and modified without limitation.
** const int textures=2;
*****************************************************************************/ ulong texID[] = {GL_TEXTURE0, GL_TEXTURE1};

/**************************************************************************** //const int redrawWait = 50;


** const int redrawWait = 0;
** This is a simple QGLWidget demonstrating the use of QImages for textures.
** /*!
** Much of the GL code is inspired by the ’spectex’ and ’texcyl’ Create a GLTexobj widget
** public domain demo programs by Brian Paul. */
**
****************************************************************************/ GLTexobj::GLTexobj( QWidget* parent, const char* name ) :
QGLWidget( parent, name ),
#include <iostream> img(width(),height(),32),
#include <iomanip> stimulus(width(),height(),32),
#include <fstream> textfont(),
#include <sstream> letter(0),
#include <vector> sequence(NULL), position(NULL),
Ra_sequence(NULL), Ra_position(NULL),
#include <string> simDuration(10000.0), simPeriod(20.0),
#include <list> simSamples(500), simRunning(false),
#include <map> showScanpaths(true),
#include <algorithm> heatmap(NULL)
#include <cmath> {
#include <cstdlib> time_t tloc;
#include <time.h>
#include <sys/time.h> timer = new QTimer( this );
#include <sys/types.h> connect( timer, SIGNAL(timeout()), SLOT(update()) );
#include <sys/stat.h> //timer->start( redrawWait, FALSE );

#ifdef ANM_OSX // looks like font selection under X11 may be limited...
#include <OpenGL/gl.h> textfont.setFamily("Fixed");
#include <OpenGL/glu.h> textfont.setRawMode(true);
#include <OpenGL/glext.h> textfont.setStyleStrategy(QFont::OpenGLCompatible);
#else //textfont.setPixelSize(10);
#include <GL/gl.h> textfont.setPointSize(10);
#include <GL/glu.h> textfont.setFixedPitch(true);
#include <GL/glext.h> textfont.setStyleHint(QFont::AnyStyle,QFont::PreferBitmap);
#endif
QFontInfo textfontinfo(textfont);
#include <qgl.h>
#include <qtimer.h> std::cout << "family: " << textfontinfo.family() << " ";
#include <qevent.h> std::cout << "pointsize: " << textfontinfo.pointSize() << " ";
#include <qstring.h> std::cout << "weight: " << textfontinfo.weight() << " ";
#include <qfont.h> std::cout << "italic: " << textfontinfo.italic() << " ";
#include <qfontinfo.h> std::cout << std::endl;
#include <qimage.h>
#include <qfiledialog.h> srand((unsigned int)time(&tloc));
#include <qmessagebox.h> }

using namespace std;


/*!
#include <vector.h> Release allocated resources
Sep 01 2009 10:39 gltexobj.cpp Page 2/13
*/ performed here.
*/
GLTexobj::˜GLTexobj()
{ void GLTexobj::paintGL()
if(sequence) delete sequence; {
if(position) delete position; ostringstream os;
if(Ra_sequence) delete Ra_sequence; vector<float> c(4,0.0); c[2] = 1.0; c[3] = 1.0;
if(Ra_position) delete Ra_position;
// clear back buffer
if(heatmap) delete heatmap; glDrawBuffer(GL_BACK);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
makeCurrent();
} // set up texture transform matrix (no transformation)
glEnable(GL_TEXTURE_2D);
glMatrixMode(GL_TEXTURE);
/*! glLoadIdentity();
Set up the OpenGL rendering state, and define display list
*/ // set up modelview matrix
glMatrixMode(GL_MODELVIEW);
void GLTexobj::initializeGL() glLoadIdentity();
{
std::cerr << "GL: " << glGetString(GL_VERSION) << std::endl; // render texture (stimulus)
glActiveTexture(texID[0]);
// specify clear color; (0.3,0.4,0.6,1.0) is a nice "slate blue" glBindTexture(GL_TEXTURE_2D,texNames[0]);
glClearColor(1.0, 1.0, 1.0, 0.0); glBegin(GL_QUADS);
{
glGenTextures(2,texNames); glTexCoord2f(0,0); glVertex2i(0,0);
imgBind(); glTexCoord2f(1,0); glVertex2i(width(),0);
glTexCoord2f(1,1); glVertex2i(width(),height());
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexCoord2f(0,1); glVertex2i(0,height());
glEnable(GL_BLEND); }
} glEnd();

// render texture (heatmap)


/*! glActiveTexture(texID[0]);
Set up the OpenGL view port, matrix mode, etc. glBindTexture(GL_TEXTURE_2D,texNames[1]);
*/ glBegin(GL_QUADS);
{
void GLTexobj::resizeGL( int w, int h ) glTexCoord2f(0,0); glVertex2i(0,0);
{ glTexCoord2f(1,0); glVertex2i(width(),0);
glViewport(0,0,w,h); glTexCoord2f(1,1); glVertex2i(width(),height());
glTexCoord2f(0,1); glVertex2i(0,height());
glMatrixMode(GL_PROJECTION); }
glLoadIdentity(); glEnd();
// origin at display center, pixel range ([-w,w],[-h,h])
//gluOrtho2D(-w,w,-h,h); // disable TU0
// origin at bottom left, pixel range ([0,w],[0,h]) glDisable(GL_TEXTURE_2D);
gluOrtho2D(0.0,w,0.0,h);
// perspective projection (for 3D) // render here
//gluPerspective(90,1.0,1.0,1000.0);
//glFrustum(-1.0,1.0,-1.0,1.0,1.0,1000.0); /*
// render text "(x,y)" at cursor
glMatrixMode(GL_MODELVIEW); //std::cerr << "(" << x << "," << y << ")" << std::endl;
glLoadIdentity(); glColor4f(0.0,0.0,0.0,0.5);
} os.setf(ios::fixed,ios::floatfield);
os.precision(2);
os << "(" << x << "," << 1.0-y << ")";
/*! if(textfont.handle())
Paint the texobj. The actual openGL commands for drawing the texobj are renderText(x*(float)width()-15,y*(float)height()+15,0.0,
Sep 01 2009 10:39 gltexobj.cpp Page 3/13
QString(os.str()),textfont); }

// render black box at (moving) mouse coordinates map<string,Scanpath* >::iterator pos;


glColor4f(0.0,0.0,0.0,0.5); for(pos=scanpath.begin(); pos != scanpath.end(); pos++) {
glBegin(GL_QUADS); std::cerr << "fixations: " << pos->second->fixations() << std::endl;
glVertex2f(x*(float)width()-5,y*(float)height()-5); for(int f=0; f<pos->second->fixations(); f++) {
glVertex2f(x*(float)width()+5,y*(float)height()-5); duration = (float)pos->second->ithfixation(f)->getDuration();
glVertex2f(x*(float)width()+5,y*(float)height()+5);
glVertex2f(x*(float)width()-5,y*(float)height()+5); x = (float)((*pos->second->ithfixation(f))[0] * width());
glEnd(); y = (float)((*pos->second->ithfixation(f))[1] * height());
*/
// std::cerr << "fixation: " << f;
// render all defined AOIs // std::cerr << " (" << x << "," << y << ")";
map<string,Aoi* >::iterator aoip; // std::cerr << std::endl;
for(aoip=aoi.begin(); aoip != aoi.end(); aoip++) {
glBegin(GL_LINE_LOOP); // approximating to 2*sigma idea came from:
for(int i=0;i<4;i++) { // http://people.csail.mit.edu/sparis/bf/#code
x = aoip->second->coord[i][0] * (float)width(); for(int i=(int)(y-2.0*sigma); i<(int)(y+2.0*sigma); i++) {
y = aoip->second->coord[i][1] * (float)height(); for(int j=(int)(x-2.0*sigma); j<(int)(x+2.0*sigma); j++) {
glVertex2f(x,y); sx = j - x; sy = i - y;
} heat = exp((sx*sx + sy*sy)/(-2.0*sigma*sigma));
glEnd(); if( (0 <= i) && (i < height()) && (0 <= j) && (j < width()) ) {
} r[i][j] += (float)heat;
g[i][j] += (float)heat;
// render scanpaths (unless simulation running; takes too long to draw) b[i][j] += (float)heat;
if(!simRunning && showScanpaths) { }
map<string,Scanpath* >::iterator pos; }
for(pos=scanpath.begin(); pos != scanpath.end(); pos++) }
pos->second->render(aoi,(float)width(),(float)height());
}
vector<Scanpath* >::iterator Ra_pos; }
for(Ra_pos=Ra_scanpath.begin(); Ra_pos != Ra_scanpath.end(); Ra_pos++)
(*Ra_pos)->render(aoi,(float)width(),(float)height()); // get max values
} for(int i=0; i<height(); i++) {
for(int j=0; j<width(); j++) {
// swapbuffers if(r[i][j] > maxr) maxr = r[i][j];
swapBuffers(); if(g[i][j] > maxg) maxg = g[i][j];
} if(b[i][j] > maxb) maxb = b[i][j];
}
void GLTexobj::mapheat() }
{
int idx; // normalize
float heat,sigma=25.0; for(int i=0; i<height(); i++) {
float duration; for(int j=0; j<width(); j++) {
float x,y; r[i][j] /= maxr;
float sx,sy; g[i][j] /= maxg;
uchar red,green,blue,alpha=0xcc; b[i][j] /= maxb;
matrix<float> r(height(),width()); }
matrix<float> g(height(),width()); }
matrix<float> b(height(),width());
float maxr=-1.0,maxg=-1.0,maxb=-1.0; // convert float to image
for(int i=0; i<height(); i++) {
//std::cerr << "w x h: "; for(int j=0; j<width(); j++) {
//std::cerr << " (" << width() << "," << height() << ")"; red = static_cast<uchar>(r[i][j] * 255.0);
//std::cerr << std::endl; green = static_cast<uchar>(g[i][j] * 255.0);
blue = static_cast<uchar>(b[i][j] * 255.0);
if(!heatmap) { // store in RGBA format
heatmap = new GLubyte[4*width()*height()]; idx = 4*(i*width() + j);
bzero(heatmap,4*width()*height()); heatmap[idx + 0] = red;
Sep 01 2009 10:39 gltexobj.cpp Page 4/13
heatmap[idx + 1] = green; if(scanpaths < 1 && Ra_scanpaths == 1)
heatmap[idx + 2] = blue; for(Ra_pos=Ra_scanpath.begin(); Ra_pos != Ra_scanpath.end(); Ra_pos++)
heatmap[idx + 3] = alpha; (*Ra_pos)->label(&letter);
}
} // if there’s only one scanpath, label it sequentially
if(scanpaths == 1)
unsigned char p; for(pos=scanpath.begin(); pos != scanpath.end(); pos++, scanpaths++)
FILE *ostream; pos->second->label(&letter);
ostream = fopen("heatmap.ppm","w");
fprintf(ostream,"P6\n%d %d\n%d\n",width(),height(),255); // multiple scanpaths, compare each against all others for elliptical overlap
for(int i=0; i<height(); i++) { if(scanpaths > 1)
for(int j=0; j<width(); j++) { for(pos=scanpath.begin(); pos != scanpath.end(); pos++)
idx = 4*(i*width() + j); for(npos=scanpath.begin(); npos != scanpath.end(); npos++)
p = heatmap[idx + 0]; fwrite(&p,sizeof(uchar),1,ostream); if(pos!=npos) pos->second->matchlabels(npos->second,&letter);
p = heatmap[idx + 1]; fwrite(&p,sizeof(uchar),1,ostream);
p = heatmap[idx + 2]; fwrite(&p,sizeof(uchar),1,ostream); // compare each random scanpath against subject scanpaths for ell. overlap
p = heatmap[idx + 3]; // don’t write alpha for(Ra_pos=Ra_scanpath.begin(); Ra_pos != Ra_scanpath.end(); Ra_pos++)
} for(pos=scanpath.begin(); pos != scanpath.end(); pos++)
} (*Ra_pos)->matchlabels(pos->second,&letter);
fflush(ostream);
fclose(ostream); // compare each random scanpath against random scanpaths for ell. overlap
for(Ra_pos=Ra_scanpath.begin(); Ra_pos != Ra_scanpath.end(); Ra_pos++)
// texture 1 is the ’heatmap’ for(Ra_npos=Ra_scanpath.begin(); Ra_npos != Ra_scanpath.end(); Ra_npos++)
glActiveTexture(texID[0]); if(Ra_pos!=Ra_npos) (*Ra_pos)->matchlabels((*Ra_npos),&letter);
glBindTexture(GL_TEXTURE_2D,texNames[1]);
// set up texture env // print out sequence strings
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); std::cerr << "sequences:" << std::endl;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); for(pos=scanpath.begin(); pos != scanpath.end(); pos++)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); std::cerr << pos->second->printString() << std::endl;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, // target // print out random sequence strings
0, // level std::cerr << "Ra sequences:" << std::endl;
GL_RGBA, // internalFormat for(Ra_pos=Ra_scanpath.begin(); Ra_pos != Ra_scanpath.end(); Ra_pos++)
width(), height(), std::cerr << (*Ra_pos)->printString() << std::endl;
0, // border
GL_RGBA, // format // print out unique sequence strings (for position similiarity)
GL_UNSIGNED_BYTE, // type std::cerr << "unique sequences:" << std::endl;
heatmap); for(pos=scanpath.begin(); pos != scanpath.end(); pos++)
} std::cerr << pos->second->printUniqueString() << std::endl;

void GLTexobj::label() // print out unique random sequence strings (for position similiarity)
{ std::cerr << "unique random sequences:" << std::endl;
int scanpaths=0; for(Ra_pos=Ra_scanpath.begin(); Ra_pos != Ra_scanpath.end(); Ra_pos++)
int Ra_scanpaths=Ra_scanpath.size(); std::cerr << (*Ra_pos)->printUniqueString() << std::endl;
// now part of GLTexobj
// static int letter(0); // pairwise similarity
set<string,longer> ulcsset; S__S(scanpaths); // between subject scanpaths only
map<string,Scanpath* >::iterator pos,npos; Ra_S(scanpaths); // between subject and random scanpaths
vector<Scanpath* >::iterator Ra_pos, Ra_npos; std::cerr << "sequence similarity (S_s):" << std::endl;
parsingDiagram(sequence,Ra_sequence);
// get each scanpath to grow its kd-tree std::cerr << "position similarity (S_p):" << std::endl;
for(pos=scanpath.begin(); pos != scanpath.end(); pos++, scanpaths++) parsingDiagram(position,Ra_position);
pos->second->growkdtree((float)width(),(float)height());
// same for all random scanpaths // QFileDialog* fd;
for(Ra_pos=Ra_scanpath.begin(); Ra_pos != Ra_scanpath.end(); Ra_pos++) QString qfilename;
(*Ra_pos)->growkdtree((float)width(),(float)height()); std::string strpath,strfull;
std::ofstream ofs;
// if there are no scanpaths and only 1 Random scanpath, std::ostringstream os;
// label random scanpath sequentially
Sep 01 2009 10:39 gltexobj.cpp Page 5/13
/* --- this makes more sense by batch processor, not GUI // if(i!=j) (*position)[i][j] = pos->second->p_similarity(npos->second);
// get directory where to dump stats (*sequence)[i][j] = pos->second->s_similarity(npos->second);
#ifdef ANM_OSX (*position)[i][j] = pos->second->p_similarity(npos->second);
fd = new QFileDialog("../../.././","Choose directory",this); }
#else }
fd = new QFileDialog("./","Choose directory",this); // print lower diagonal (matrix is symmetrical, only need half of it)
#endif std::cerr << "sequence similarity (S_s):" << std::endl << sequence;
fd->setMode(QFileDialog::Directory); std::cerr << "position similarity (S_p):" << std::endl << position;
//fd->setMode(QFileDialog::DirectoryOnly); }
fd->setViewMode(QFileDialog::Detail); // alternative is List }
fd->setFilter("CSV files (*.csv)");
if(fd->exec() == QDialog::Accepted) { void GLTexobj::Ra_S(int n)
qfilename = fd->selectedFile(); {
strpath = qfilename.ascii(); int i,j,r=(int)Ra_scanpath.size();
map<string,Scanpath* >::iterator pos;
os << strpath << "/" << "G_s.csv"; // compose filename vector<Scanpath* >::iterator Ra_pos;
ofs.open(os.str().c_str()); // open file
stats(ofs,sequence,Ra_sequence,G); // pairwise Ra_sequence similarity (Levenshtein)
ofs.close(); // close file if(r > 0 && n > 0) {
os.str(""); // clear out filename // clear out current random position and sequence Y-matrices and resize
// here we make an n x r matrix since we favor the lower-diagonal form
os << strpath << "/" << "L_s.csv"; // compose filename if(Ra_sequence) delete Ra_sequence; Ra_sequence = new YMatrix(n,r);
ofs.open(os.str().c_str()); // open file if(Ra_position) delete Ra_position; Ra_position = new YMatrix(n,r);
stats(ofs,sequence,Ra_sequence,L);
ofs.close(); // close file // compare with random sequences
os.str(""); // clear out filename for(pos = scanpath.begin(), i=0; pos != scanpath.end(); i++, pos++) {
for(Ra_pos = Ra_scanpath.begin(), j=0;
os << strpath << "/" << "G_p.csv"; // compose filename Ra_pos != Ra_scanpath.end(); j++, Ra_pos++) {
ofs.open(os.str().c_str()); // open file (*Ra_sequence)[i][j] = pos->second->s_similarity(*Ra_pos);
stats(ofs,position,Ra_position,G); (*Ra_position)[i][j] = pos->second->p_similarity(*Ra_pos);
ofs.close(); // close file }
os.str(""); // clear out filename }
// print lower diagonal (matrix is symmetrical, only need half of it)
os << strpath << "/" << "L_p.csv"; // compose filename std::cerr << "Ra sequence similarity (S_s):" << std::endl << Ra_sequence;
ofs.open(os.str().c_str()); // open file std::cerr << "Ra position similarity (S_p):" << std::endl << Ra_position;
stats(ofs,position,Ra_position,L); }
ofs.close(); // close file }
os.str(""); // clear out filename
} void GLTexobj::parsingDiagram(YMatrix *m, YMatrix *Ra_m)
*/ {
int n_Ra=0, n_R=0, n_L=0, n_I=0, n_G=0;
updateGL(); float sum_Ra=0.0, sum_R=0.0, sum_L=0.0, sum_I=0.0, sum_G=0.0;
} float rs_Ra=0.0, rs_R=0.0, rs_L=0.0, rs_I=0.0, rs_G=0.0;
float d;
void GLTexobj::S__S(int n)
{ // given d, the difference in rank, Spearman’s rank-order coefficient
int i,j; // is given as
map<string,Scanpath* >::iterator pos,npos; //
// r_s = 1 - \frac{6 \sum{d^2}}{n (n^2 - 1)}
// pairwise sequence similarity (Levenshtein) //
if(n > 1) { // where the resulting correlation is interpreted as:
// clear out current position and sequence Y-matrices and resize //
if(sequence) delete sequence; sequence = new YMatrix(n,n); // very strong, if 0.9 \leq r_s \leq 1.0,
if(position) delete position; position = new YMatrix(n,n); // strong, if 0.7 \leq r_s \leq 0.9,
// moderate, if 0.5 \leq r_s \leq 0.7,
// compare pos with all other subject sequences
for(pos = scanpath.begin(), i=0; pos != scanpath.end(); i++, pos++) { // parsing diagrams---process subject-subject pairings
for(npos = scanpath.begin(), j=0; npos != scanpath.end(); j++, npos++) { for(int i=0;i<m->rows();i++) {
// if(i!=j) (*sequence)[i][j] = pos->second->s_similarity(npos->second); for(int j=0;j<m->cols();j++) {
Sep 01 2009 10:39 gltexobj.cpp Page 6/13
// don’t count entries on the diagonal! }
// (they are trivially 1.00 and skew the mean)
// if(i>=j) { void GLTexobj::stats(ostream& s, YMatrix *m, YMatrix *Ra_m, cmp_t C)
if(i>j) { {
d = (*m)[i][j].cmp.first; vector<double> similarity, Ra_similarity;
switch((*m)[i][j].cmp.second) {
case N: break; // should have no N comparisons // collect up corresponding comparison entries from m Y-matrix
case Ra: break; // should have no random comparisons if(m) {
case R: sum_R += d; n_R++; break; for(int i=0;i<m->rows();i++)
case L: sum_L += d; n_L++; break; for(int j=0;j<m->cols();j++)
case I: sum_I += d; n_I++; break; if(i>=j && (*m)[i][j].cmp.second == C)
case G: sum_G += d; n_G++; break; similarity.push_back((*m)[i][j].cmp.first);
} }
} // collect up all comparison entries from Ra_m Y-matrix
} if(Ra_m) {
} for(int i=0;i<Ra_m->rows();i++)
// parsing diagrams---process subject-random pairings for(int j=0;j<Ra_m->cols();j++)
if(Ra_m) { if(i>=j && i<m->rows() && j<m->cols() && (*m)[i][j].cmp.second == C)
for(int i=0;i<Ra_m->rows();i++) { Ra_similarity.push_back((*Ra_m)[i][j].cmp.first);
for(int j=0;j<Ra_m->cols();j++) { }
d = (*Ra_m)[i][j].cmp.first;
if(i>=j) { // dump out .csv file for subsequent processing via R
switch((*Ra_m)[i][j].cmp.second) { s << "comparison,similarity" << std::endl;
case N: break; // should have no N comparisons for(int i=0;i<(int)similarity.size();i++) {
case Ra: sum_Ra += d; n_Ra++; break; switch(C) {
case R: break; // should have no R comparisons case N: break; // should not be N comparisons
case L: break; // should have no L comparisons case Ra: break; // should not be random comps
case I: break; // should have no I comparisons case R: s << "R,"; break;
case G: break; // should have no G comparisons case L: s << "L,"; break;
} case I: s << "I,"; break;
} case G: s << "G,"; break;
} }
} s << similarity[i] << std::endl;
} }
for(int i=0;i<(int)Ra_similarity.size();i++)
//rs_Ra = n_Ra > 0 ? 1.0 - 6.0 * sum_Ra/(powf((float)n_Ra,3.0) - n_Ra) : 0.0; s << "Ra," << Ra_similarity[i] << std::endl;
//rs_R = n_R > 0 ? 1.0 - 6.0 * sum_R/(powf((float)n_R,3.0) - n_R) : 0.0; }
//rs_L = n_L > 0 ? 1.0 - 6.0 * sum_L/(powf((float)n_L,3.0) - n_L) : 0.0;
//rs_I = n_I > 0 ? 1.0 - 6.0 * sum_I/(powf((float)n_I,3.0) - n_I) : 0.0; void GLTexobj::mousePressEvent(QMouseEvent* e)
{
rs_Ra = n_Ra > 0 ? sum_Ra/(float)n_Ra : 0.0; vector<float> color(4,0.0);
rs_R = n_R > 0 ? sum_R/(float)n_R : 0.0;
rs_L = n_L > 0 ? sum_L/(float)n_L : 0.0; mouseMapCoordinates(e->x(),e->y());
rs_I = n_I > 0 ? sum_I/(float)n_I : 0.0;
rs_G = n_G > 0 ? sum_G/(float)n_G : 0.0; if(e->button() & LeftButton) {
// do nothing
std::cerr << setw(15) << "Reptitive"; } else if(e->button() & RightButton) {
std::cerr << setw(15) << "Local" << "\n"; // do nothing
std::cerr << setw(15) << rs_R; }
std::cerr << setw(15) << rs_L << std::endl;
std::cerr << setw(15) << "Idiosyncratic"; e->accept();
std::cerr << setw(15) << "Global" << "\n"; updateGL();
std::cerr << setw(15) << rs_I; }
std::cerr << setw(15) << rs_G << std::endl;
void GLTexobj::mouseReleaseEvent(QMouseEvent* e)
if(Ra_m) { {
std::cerr << setw(30) << "Random" << "\n"; mouseMapCoordinates(e->x(),e->y());
std::cerr << setw(30) << rs_Ra << std::endl;
} if(e->button() & MidButton) {
Sep 01 2009 10:39 gltexobj.cpp Page 7/13
// std::cerr << "MidButton + ";
if(e->state() & ControlButton) {
// std::cerr << "ControlButton" << std::endl; void GLTexobj::fileGenerateRa()
// on the Mac: press mouse button first, then press CTL button {
} else { Header *h=NULL;
// std::cerr << std::endl; vector<float> color(4,0.3); // set transparency
} Scanpath *scp;
} QString status;
std::ostringstream os;
if(e->button() & RightButton) {
} // reset simulation elapsed time (no. samples)
simSamples = 0;
e->accept();
updateGL(); // set timestamp
} simStart = timestamp();

void GLTexobj::mouseDoubleClickEvent(QMouseEvent* e) // create new scanpath, with new id "R01-R01-" for the Random recording
{ os << "R" << setw(2) << setfill(’0’) << (int)Ra_scanpath.size()+1;
e->accept(); h = new Header("random",os.str().c_str(),"R01");
updateGL();
} // get scanpath id (associative map key)
id = h->id();
void GLTexobj::wheelEvent(QWheelEvent* e) // get random color for the scanpath
{ for(int i=0; i<3; i++) color[i] = (float)rand()/(float)RAND_MAX;
float degrees = ((float)e->delta() / 360.0); // should be WHEEL_DELTA // random scanpaths get stored separately from subject scanpaths
// float radians = degrees * M_PI / 180.0; scp = new Scanpath(h,width(),height(),color);
scp->setRandom();
if(e->state() & ControlButton) { Ra_scanpath.push_back(scp);
std::cerr << "wheelEvent:";
std::cerr << " delta: " << e->delta(); status = QString("Generating random scanpath (%1 s)")
std::cerr << " degrees: " << degrees << std::endl; .arg(simDuration/1000.0,0,’f’,0); // duration in seconds
}
e->accept(); emit messageStatus(status);
updateGL(); emit setProgress(0,(int)(simDuration/simPeriod));
}
simRunning = true;
void GLTexobj::mouseMoveEvent(QMouseEvent* e)
{ // start timer; idea is for timer to call update()
mouseMapCoordinates(e->x(),e->y()); // to simulate random eye movements
timer->start((int)simPeriod, FALSE);
e->accept(); }
updateGL();
} void GLTexobj::update()
{
void GLTexobj::mouseMapCoordinates(int ix, int iy) static double fixation_dt=poisson(500.0);
{ static double fixation_ts=timestamp();
//std::cerr << "(" << ix << "," << iy << ")" << std::endl; static double fixation_te=0.0;
static double fixation_tt=0.0;
// flip y-coordinate to put into GL coordinates static bool saccade=true;
x = ix; y = height() - iy;
static float fx, fy;
// normalize coordinates static Point *pp;
x = x/width(); y = y/height();
// this is the slot called by Timer whenever its timeout period expires
/* // and is meant to be re-entrant; it simulates random eye movements:
// scale coordinates to ([-w,w], [-h,h]) // - whenever this routine is called, it will either re-fixate the
x = width()*(2.0*x - 1.0); y = height()*(2.0*y - 1.0); // gaze point (fx,fy), or will saccade to some new random location
*/ // - the decision to saccade depends on the time since the last fixation,
} // the period being governed by a normal distribution with variance
Sep 01 2009 10:39 gltexobj.cpp Page 8/13
// characteristic of true eye movements, e.g., [150,650] ms
// - the gaze point is repositioned to a new location, governed //std::cerr << pp;
// by saccade amplitude characteristic of true saccades, e.g., ??
// add current point to scanpath
fixation_te = timestamp(); if(!Ra_scanpath.empty())
fixation_tt = fixation_te - fixation_ts; Ra_scanpath[(int)Ra_scanpath.size()-1]->insertPoint(pp);
if(fixation_tt >= fixation_dt) saccade = true;
emit setProgress(simSamples);
if(saccade) {
// simulate saccade (working in display coordinates) // div simDuration by period (in ms) to get number of samples
if(++simSamples > (int)(simDuration/simPeriod)) {
// using built-in random number generator, no model of eye movements,
// just random location based on built-in rand() // analyze eye movements (e.g., via LC Tech’s position-variance scheme)
// fx = (float)rand()/(float)RAND_MAX * (float)width(); Ra_scanpath[(int)Ra_scanpath.size()-1]->position_variance();
// fy = (float)rand()/(float)RAND_MAX * (float)height();
// use k-means as dispersion-based data reduction (clustering) method
// uniform distribution (min, max), better random generator than rand() Ra_scanpath[(int)Ra_scanpath.size()-1]->fixation_mean_shift();
// but still no model of eye movements
// fx = cuniform(0.,(float)width()); // classify fixations
// fy = cuniform(0.,(float)height()); Ra_scanpath[(int)Ra_scanpath.size()-1]->classifyFixations();

// normal distribution (mean, sdev) instead of uniform, here it is // reset duration (how long to simulate for, in ms)
// weighted to the screen center, but is still not a good model of simDuration = 10000.0;
// saccadic amplitude
fx = normal((float)width()/2.0,(float)width()/6.0); emit clearStatus();
fy = normal((float)height()/2.0,(float)height()/6.0);
simRunning = false;
// get new fixation duration, should be [150,650] ms range
// fixation_dt = cuniform(150.0,600); // stop timer
// fixation_dt = normal(375.0,56.25); timer->stop();
// fixation_dt = poisson(500.0); }
fixation_dt = poisson(1300.0);
// draw current point
// get fixaton start timestamp mouseMapCoordinates((int)fx,(int)fy);
fixation_ts = timestamp();
updateGL();
// reset saccade flag }
saccade = false;
} else { void GLTexobj::fileOpen()
// no saccade, maintain fixation, simulating random re-fixation, {
// with (dx,dy) (arbitrarily) close to current location QFileDialog* fd;
QString qfilename;
// uniform distribution in range [-.45,.45] --- what units?
// fx += cuniform((float)-.45,(float).45); #ifdef ANM_OSX
// fy += cuniform((float)-.45,(float).45); fd = new QFileDialog("../../.././",QString::null,this);
#else
// normal distribution in range with mean=0, sdev=0.91 visual angle (30 px) fd = new QFileDialog("./",QString::null,this);
fx += normal(0.,0.91); #endif
fy += normal(0.,0.91); fd->setMode(QFileDialog::ExistingFile); // use AnyFile for writing
} fd->setViewMode(QFileDialog::Detail); // alternative is List
fd->setFilter("Text files (*.txt)");
// generate new point
pp = new Point(fx,fy,timestamp() - simStart); // Tobii ClearView 2.7.1 recording file types (uncomment as implemented)
fd->addFilter("AOI (*AOI.txt)");
// normalize fd->addFilter("AOIL (*AOIL.txt)");
pp->scale(1.0/(float)width(),1.0/(float)height()); //fd->addFilter("CMD (*CMD.txt)");
//fd->addFilter("EVD (*EVD.txt)");
// flip y-coordinate to put into GL coordinates fd->addFilter("FXD (*FXD.txt)");
pp->flip(); fd->addFilter("EFD (*EFD.txt)");
Sep 01 2009 10:39 gltexobj.cpp Page 9/13
//fd->addFilter("GZD (*GZD.txt)");
//fd->addFilter("AOI_ (*AOI_.txt)"); // all file types except AOIL have headers (AOIL file depends on AOI file)
if( (r.type() != AOIL) ) {
// get selected file // read header info
if(fd->exec() == QDialog::Accepted) qfilename = fd->selectedFile(); ifs >> (h = new Header());
// construct id: assumed to be "<Subject>-<Recording>-" which should
// process file if not empty // be a unique identifier for the recording (if that’s how the files
if(!qfilename.isEmpty()) { // are named)
fileRead(qfilename); id = h->id();
updateGL(); // get random color for the scanpath
} for(int i=0; i<3; i++) color[i] = (float)rand()/(float)RAND_MAX;
}
if(h->sd() == "random") {
void GLTexobj::fileOpenAll() // random scanpath, added to array of random scanpaths, not to map
{ Scanpath *scp = new Scanpath(h,width(),height(),color);
QFileDialog* fd; scp->setRandom();
QStringList qfilenames; scp->input(r.type()); // note which file is to be read in
QStringList::iterator it; ifs >> scp; // read in the given file
Ra_scanpath.push_back(scp); // add to list
#ifdef ANM_OSX } else {
fd = new QFileDialog("../../.././",QString::null,this); // scanpath is a map (associative array), with id acting as key
#else if(!scanpath[id]) // new scanpath
fd = new QFileDialog("./",QString::null,this); scanpath[id] = new Scanpath(h,width(),height(),color);
#endif else // already have header, free memory
fd->setMode(QFileDialog::ExistingFiles); delete h;
fd->setViewMode(QFileDialog::Detail); // alternative is List scanpath[id]->input(r.type()); // note which file is to be read in
fd->setFilter("Text files (*.txt)"); ifs >> scanpath[id]; // read in the given file

// Tobii ClearView 2.7.1 recording file types (uncomment as implemented) // set simDuration to duration of scanpath just read in
//fd->addFilter("AOI (*AOI.txt)"); // if user now generates random scanpath, it will match this duration
//fd->addFilter("AOIL (*AOIL.txt)"); // DO NOT call fileGenerateRa() here though, IT WILL NOT WORK because
//fd->addFilter("CMD (*CMD.txt)"); // that routine starts the timer, which is non-blocking
//fd->addFilter("EVD (*EVD.txt)"); if((simDuration = scanpath[id]->duration()) < 1.0) simDuration = 10000;
fd->addFilter("FXD (*FXD.txt)"); }
fd->addFilter("EFD (*EFD.txt)");
//fd->addFilter("GZD (*GZD.txt)"); // automatically search for and read associated AOIL file if it exists
// (so the stimulus info will be known for scanpath just read in)
// get list of selected files std::ifstream aoilifs;
if(fd->exec() == QDialog::Accepted) qfilenames = fd->selectedFiles(); std::ifstream aoiifs;
std::ifstream evdifs;
// process each one std::ostringstream os;
for(it=qfilenames.begin(); it != qfilenames.end(); it++) { std::string strfull,strpath;
fileRead(*it); struct stat buf;
updateGL();
} // begin read EVD
}
// construct filename to open (qfilename.ascii() includes full path)
void GLTexobj::fileRead(QString qfilename) strfull = qfilename.ascii();
{
Header* h=NULL; // strip leading dir name (could be very long, e.g., /home/<user>/.../
QStringList::iterator it; strpath = strfull.substr(0,strfull.rfind(’/’));
vector<float> color(4,0.3); // set transparency // construct "*EVD.txt" filename
os << strpath << "/" << id << "EVD" << ".txt";
std::ifstream ifs(qfilename.ascii()); // std::cerr << "full = " << strfull << std::endl;
if(!ifs) // std::cerr << "path = " << strpath << std::endl;
std::cerr << "Warning: can’t open " << qfilename << std::endl; // std::cerr << "file = " << os.str().c_str() << std::endl;
else {
// establish file type (the Recording obj just looks for EFD, GZD, etc.) // if file exists, open it, read it in, close it, clearing bits
Recording r(qfilename.ascii()); if(!stat(os.str().c_str(),&buf)) {
Sep 01 2009 10:39 gltexobj.cpp Page 10/13
// open EVD file // open AOIL file
evdifs.open(os.str().c_str(),std::ifstream::in); aoiifs.open(os.str().c_str(),std::ifstream::in);
if(evdifs.good()) { if(aoiifs.good()) {

// eat header // eat header


evdifs >> (h = new Header()); aoiifs >> (h = new Header());
// can check here whether header info matches current scanpath // can check here whether header info matches current scanpath
// header info // header info
delete h; delete h;

scanpath[id]->input(EVD); // note which file is to be read in scanpath[id]->input(AOI); // note which file is to be read in
evdifs >> scanpath[id]; // read in the given file aoiifs >> scanpath[id]; // read in the given file
} }
evdifs.close(); // close file aoiifs.close(); // close file
evdifs.clear(); // reset status bits aoiifs.clear(); // reset status bits
os.str(""); // clear out filename os.str(""); // clear out filename
} }
// end read EVD // end read AOI file

// begin read AOIL } else if(r.type() == AOIL) {


// read in AOIL file
// construct filename to open (qfilename.ascii() includes full path) // it has no header, hence no id and so impossible to tell which
strfull = qfilename.ascii(); // scanpath it’s supposed to belong to---use last filename as index
// strip leading dir name (could be very long, e.g., /home/<user>/.../ id = qfilename.ascii(); // this has entire path
strpath = strfull.substr(0,strfull.rfind(’/’)); id = id.substr(id.rfind(’/’)+1,id.length()); // like ’rend’
// construct "*AOIL.txt" filename id = id.substr(0,id.length() - 8);
os << strpath << "/" << id << "AOIL" << ".txt"; std::cerr << "id = " << id << std::endl;
// std::cerr << "full = " << strfull << std::endl; if(!scanpath.empty() && scanpath.find(id) != scanpath.end()) {
// std::cerr << "path = " << strpath << std::endl; std::cerr << "AOIL file for id = " << id << std::endl;
// std::cerr << "file = " << os.str().c_str() << std::endl; scanpath[id]->input(r.type());
ifs >> scanpath[id];
// if file exists, open it, read it in, close it, clearing bits cerr << "AOIL READ2" << endl;
if(!stat(os.str().c_str(),&buf)) { } else {
// open AOIL file std::cerr << "Warning: either no scanpaths read in yet or";
aoilifs.open(os.str().c_str(),std::ifstream::in); std::cerr << " one with " << id << " not found" << std::endl;
if(aoilifs.good()) { // throw up message box with predefined "Ok" and "Abort" buttons
scanpath[id]->input(AOIL); // note which file is to be read in switch(QMessageBox::warning(this,"AOIL Open",
aoilifs >> scanpath[id]; // read in the given file "No scanpaths read in yet or\n"
} "scanpath with given id not found.\n"
aoilifs.close(); // close file "Read in at least one recording.\n\n",
aoilifs.clear(); // reset status bits QMessageBox::Ok,
os.str(""); // clear out filename QMessageBox::Abort)) {
} case QMessageBox::Ok:
// end read AOIL return;
case QMessageBox::Abort:
// begin read AOI exit(1);
}
// construct filename to open (qfilename.ascii() includes full path) }
strfull = qfilename.ascii(); }
// strip leading dir name (could be very long, e.g., /home/<user>/.../ }
strpath = strfull.substr(0,strfull.rfind(’/’)); ifs.close();
// construct "*AOI.txt" filename }
os << strpath << "/" << id << "AOI" << ".txt";
std::cerr << "full = " << strfull << std::endl; void GLTexobj::fileSaveAs()
std::cerr << "path = " << strpath << std::endl; {
std::cerr << "file = " << os.str().c_str() << std::endl; QFileDialog* fd;
QString hint;
// if file exists, open it, read it in, close it, clearing bits QString qfilename;
if(!stat(os.str().c_str(),&buf)) { std::ostringstream os;
Sep 01 2009 10:39 gltexobj.cpp Page 11/13
*/
if(!Ra_scanpath.empty()) { }

// construct "*EFD.txt" filename void GLTexobj::fileClear()


os << Ra_scanpath[Ra_scanpath.size()-1]->header->id() << "EFD" << ".txt"; {
// std::cerr << "file = " << os.str().c_str() << std::endl; // reset letter
letter = 0;
hint = os.str().c_str();
// clear all scanpaths
#ifdef ANM_OSX map<string,Scanpath* >::iterator pos=scanpath.begin();
fd = new QFileDialog("../../.././",QString::null,this); while(pos != scanpath.end()) {
#else if(pos->second) delete pos->second;
fd = new QFileDialog("./",QString::null,this); scanpath.erase(pos++);
#endif }
fd->setMode(QFileDialog::AnyFile); // use ExistingFile for reading
fd->setViewMode(QFileDialog::Detail); // alternative is List // clear all random scanpaths
fd->setFilter("Text files (*.txt)"); vector<Scanpath* >::iterator Ra_pos=Ra_scanpath.begin();
fd->setSelection(hint); while(Ra_pos != Ra_scanpath.end()) {
if(*Ra_pos) delete *Ra_pos;
// Tobii ClearView 2.7.1 recording file types (uncomment as implemented) Ra_scanpath.erase(Ra_pos++);
// fd->addFilter("AOI (*AOI.txt)"); }
// fd->addFilter("AOIL (*AOIL.txt)");
// fd->addFilter("CMD (*CMD.txt)"); updateGL();
fd->addFilter("EFD (*EFD.txt)"); }
// fd->addFilter("EVD (*EVD.txt)");
// fd->addFilter("FXD (*FXD.txt)"); void GLTexobj::aoiOpen()
// fd->addFilter("GZD (*GZD.txt)"); {
string str;
// get selected file QFileDialog* fd;
if(fd->exec() == QDialog::Accepted) qfilename = fd->selectedFile(); QString qfilename;
Aoi* aoip;
// process file if not empty double w,h;
if(!qfilename.isEmpty()) {
std::ofstream ofs(qfilename.ascii()); if(scanpath.empty()) {
// save the most recently created/read in random scanpath std::cerr << "Warning: no scanpaths read in yet." << std::endl;
ofs << Ra_scanpath[Ra_scanpath.size()-1]; // throw up message box with predefined "Ok" and "Abort" buttons
ofs.close(); switch(QMessageBox::warning(this,"AOI Open",
} "No scanpaths read in yet.\n"
} "Read in at least one recording.\n\n",
} QMessageBox::Ok,
QMessageBox::Abort)) {
void GLTexobj::fileSaveAll() case QMessageBox::Ok:
{ return;
std::ostringstream os; case QMessageBox::Abort:
std::ofstream ofs; exit(1);
QString qfilename = }
QFileDialog::getSaveFileName("./",QString::null,this); /* alternate way to do it, with own button0 and button1 labels
switch(QMessageBox::warning(this,"AOI Open",
/* "No scanpaths read in yet.\n"
if(!qfilename.isEmpty()) { "Read in at least one recording.\n\n",
// save all scanpaths "Ok",
for(int s=0; s < (int)scanpath.size(); s++) { "Quit",
os << qfilename.ascii() << "." << setw(4) << setfill(’0’) << s << ".sp"; 0)) {
ofs.open(os.str().c_str()); case 0: // Ok
ofs << scanpath[s]; return;
ofs.close(); case 1:
os.str(""); exit(1);
} }
} */
Sep 01 2009 10:39 gltexobj.cpp Page 12/13
} aoi[aoip->name] = aoip;
else // already have AOI, free up memory
// get w,h stimulus dimensions from first available scanpath delete aoip;
w = scanpath.begin()->second->w(); h = scanpath.begin()->second->h();
// read in file
// display file dialog, set up file display filter // Name Top Left Top Right Bottom Right Bottom Left
#ifdef ANM_OSX ifs >> str >> str >> str >> str >> str >> str >> str >> str >> str;
fd = new QFileDialog("../../.././",QString::null,this); ifs >> std::ws;
#else
fd = new QFileDialog("./",QString::null,this); while(!ifs.eof()) {
#endif // read in aoi info and eat whitespace at EOL
fd->setMode(QFileDialog::ExistingFile); // use AnyFile for writing ifs >> (aoip = new Aoi()) >> std::ws;
fd->setViewMode(QFileDialog::Detail); // alternative is List
fd->setFilter("Text files (*.txt)"); // normalize
aoip->scale(1.0/w,1.0/h);
// Tobii ClearView 2.7.1 AOI file type
fd->addFilter("AOI (AOI*.txt)"); // flip y-coordinate to put into GL reference frame
if(fd->exec() == QDialog::Accepted) qfilename = fd->selectedFile(); aoip->flip();

/* reading in AOI and adding into vector<Aoi* > // add to map


if(!aoi[aoip->name]) // new AOI
if(!qfilename.isEmpty()) { aoi[aoip->name] = aoip;
std::ifstream ifs(qfilename.ascii()); else // already have AOI, free up memory
if(!ifs) delete aoip;
std::cerr << "Warning: can’t open " << qfilename << std::endl; }
else { }
while(!ifs.eof()) { ifs.close();
// read in aoi info and eat whitespace at EOL // debug
ifs >> (aoip = new Aoi()) >> std::ws; // std::cerr << s;
}
// normalize }
aoip->scale(1.0/w,1.0/h);
void GLTexobj::imgOpen()
// flip y-coordinate to put into GL reference frame {
aoip->flip(); QFileDialog* fd;
QString qfilename;
aoi.push_back(aoip);
} #ifdef ANM_OSX
} fd = new QFileDialog("../../.././",QString::null,this);
ifs.close(); #else
} fd = new QFileDialog("./",QString::null,this);
*/ #endif
fd->setMode(QFileDialog::ExistingFile); // use AnyFile for writing
// reading AOI and inserting into map<string,Aoi* > fd->setViewMode(QFileDialog::Detail); // alternative is List
if(!qfilename.isEmpty()) { fd->setFilter("Images (*.jpg *.bmp *.ppm *.png)");
std::ifstream ifs(qfilename.ascii());
if(!ifs) if(fd->exec() == QDialog::Accepted) qfilename = fd->selectedFile();
std::cerr << "Warning: can’t open " << qfilename << std::endl;
else { if(!qfilename.isEmpty()) {
// add in 0th AOI as "Content" (entire viewing surface) stimulus.load(qfilename);
aoip = new Aoi("Content", 0,0, w,0, w,h, 0,h); stimulus = QGLWidget::convertToGLFormat(stimulus);
// normalize
aoip->scale(1.0/w,1.0/h); imgBind();
}
// flip y-coordinate to put into GL reference frame
aoip->flip(); updateGL();
}
// add to map
if(!aoi[aoip->name]) // new AOI void GLTexobj::imgBind()
Sep 01 2009 10:39 gltexobj.cpp Page 13/13
{
// texture 0 is the ’image’
glActiveTexture(texID[0]);
glBindTexture(GL_TEXTURE_2D,texNames[0]);

// set up texture env


glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, // target
0, // level
GL_RGBA, // internalFormat
stimulus.width(),stimulus.height(), // width,height
0, // border
GL_RGBA, // format
GL_UNSIGNED_BYTE, // type
stimulus.bits()); // texels
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}

void GLTexobj::imgSaveAs()
{
glReadBuffer(GL_BACK);
img = QGLWidget::grabFrameBuffer(true);

#ifdef ANM_OSX
QString qfilename = QFileDialog::getSaveFileName("../../.././",QString::null,this);
#else
QString qfilename = QFileDialog::getSaveFileName("./",QString::null,this);
#endif

if(!qfilename.isEmpty()) img.save(qfilename,"PNG");
}

void GLTexobj::afPlot()
{
map<string,Scanpath* >::iterator pos;
for(pos=scanpath.begin(); pos != scanpath.end(); pos++)
pos->second->afplot();

double GLTexobj::timestamp()
{
double s,us,tod;
struct timeval tp;

// get time of day (tod), return in milliseconds

gettimeofday(&tp,NULL);
s = static_cast<double>(tp.tv_sec);
us = static_cast<double>(tp.tv_usec);
tod = s*1000000.0 + us;
return(tod/1000.0);
}
Sep 01 2009 10:39 glwinobj.cpp Page 1/1
/**************************************************************************** file->insertItem("Save All...",texwin,SLOT(fileSaveAll()),CTRL+Key_L);
** $Id: qt/glwinobj.cpp 3.1.2 edited Nov 8 2002 $ file->insertSeparator();
** file->insertItem("Clear",texwin,SLOT(fileClear()),CTRL+Key_C);
** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. file->insertSeparator();
** file->insertItem("Quit",qApp,SLOT(quit()),CTRL+Key_Q);
** This file is part of an example program for Qt. This example
** program may be used, distributed and modified without limitation. // create the edit menu
** edit = new QPopupMenu(menubar);
*****************************************************************************/ menubar->insertItem("Edit",edit);
view_scanpaths = new QCheckBox("scanpaths",edit,0);
#include <iostream> view_scanpaths->setChecked(true);
#include <vector> edit->insertItem(view_scanpaths);
#include <string> connect(view_scanpaths,SIGNAL(toggled(bool)),texwin,SLOT(viewScanpaths(bool)));
#include <list>
#include <math.h> // create the aoi menu
aoi = new QPopupMenu(menubar);
#include <qpushbutton.h> menubar->insertItem("AOI",aoi);
#include <qslider.h> aoi->insertItem("Open...",texwin,SLOT(aoiOpen()));
#include <qlayout.h>
#include <qframe.h> // create the image menu
#include <qmenubar.h> image = new QPopupMenu(menubar);
#include <qpopupmenu.h> menubar->insertItem("Image",image);
#include <qcheckbox.h> image->insertItem("Open...",texwin,SLOT(imgOpen()));
#include <qprogressbar.h> image->insertItem("Save As...",texwin,SLOT(imgSaveAs()));
#include <qstatusbar.h>
#include <qapplication.h> // create the tools menu
#include <qkeycode.h> tools = new QPopupMenu(menubar);
menubar->insertItem("Tools",tools);
using namespace std; tools->insertItem("AF plot output...",texwin,SLOT(afPlot()));
tools->insertItem("Label",texwin,SLOT(label()));
#include "glwinobj.h" tools->insertItem("Heatmap",texwin,SLOT(mapheat()));
#include "gltexobj.h"
// status and progress bar
GLObjectWindow::GLObjectWindow( QWidget* parent, const char* name ) : statusBar = new QStatusBar(this);
QWidget( parent, name ) connect(texwin,SIGNAL(messageStatus(const QString&)),
{ statusBar,SLOT(message(const QString&)));
GLTexobj* texwin=NULL; connect(texwin,SIGNAL(clearStatus()),
statusBar,SLOT(clear()));
// Create an OpenGL widget: (doubleBuffer | rgba | depth) set globally // progress bar
texwin = new GLTexobj(this,"glarea"); progressBar = new QProgressBar(statusBar);
std::cout << "doubleBuffer: " << texwin->format().doubleBuffer() << " " connect(texwin,SIGNAL(setProgress(int)),
<< "rgba: " << texwin->format().rgba() << " " progressBar,SLOT(setProgress(int)));
<< "depth: " << texwin->format().depth() << " " connect(texwin,SIGNAL(setProgress(int,int)),
<< std::endl; progressBar,SLOT(setProgress(int,int)));
texwin->setMouseTracking(true); connect(texwin,SIGNAL(resetProgress()),
progressBar,SLOT(reset()));
// create a menu bar connect(texwin,SIGNAL(clearStatus()),
menubar = new QMenuBar(this); progressBar,SLOT(reset()));
menubar->setSeparator(QMenuBar::InWindowsStyle); statusBar->addWidget(progressBar,0,true); // must be permanent (,0,true)

// create the file menu // Top level layout (with 0,0 border)
file = new QPopupMenu(menubar); vlayout = new QVBoxLayout(this, 0, 0, "vlayout");
menubar->insertItem("Recording",file); vlayout->setMenuBar(menubar);
file->insertItem("Open",texwin,SLOT(fileOpen()),CTRL+Key_O); vlayout->addWidget(texwin,1);
file->insertItem("Open All..",texwin,SLOT(fileOpenAll()),CTRL+Key_A); vlayout->addWidget(statusBar);
file->insertSeparator(); }
file->insertItem("Generate Random",texwin,SLOT(fileGenerateRa()),CTRL+Key_R);
file->insertSeparator();
file->insertItem("Save As...",texwin,SLOT(fileSaveAs()),CTRL+Key_W);
Sep 01 2009 10:39 header.cpp Page 1/1
#include <iostream> s >> str; s >> str; // "Coordinate unit:"
#include <iomanip> s >> rhs.unit;
#include <fstream>
#include <sstream> return s;
#include <string> }
#include <algorithm>
#include <time.h> ostream& operator<<(ostream& s, const Header& rhs)
#include <sys/time.h> {
s << "Data properties:" << std::endl; s << std::endl;
using namespace std; s << "Recording date: ";
s << rhs.month << "/" << rhs.day << "/" << rhs.year << std::endl;
#include "header.h" s << "Recording time : ";
s << rhs.hour << ":" << rhs.minute << ":" << rhs.second << ":";
// scanpath implementation s << setw(3) << setfill(’0’) << rhs.ms;
// (c) Andrew T. Duchowski s << " (corresponds to time 0)" << std::endl;
Header::Header(string sty, string subj, string rec) : s << "Study: ";
study(sty), s << rhs.study << std::endl;
subject(subj), s << "Subject: ";
recording(rec), s << rhs.subject << std::endl;
w(1280), h(1024), s << "Recording: ";
unit("Pixels") s << rhs.recording << std::endl;
{ s << "Screen resolution: ";
time_t tloc; s << rhs.w << " x " << rhs.h << std::endl;
struct tm *tod=NULL; s << "Coordinate unit: ";
s << rhs.unit << std::endl;
time(&tloc);
tod = localtime(&tloc); return s;
}
month = tod->tm_mon + 1;
day = tod->tm_mday;
year = tod->tm_year + 1900;
hour = tod->tm_hour;
minute = tod->tm_min;
second = tod->tm_sec;
ms = 0;
}

istream& operator>>(istream& s, Header& rhs)


{
char c;
string str;

s >> str; s >> str; // "Data properties:"


s >> str; s >> str; // "Recording date:"
s >> rhs.month >> c >> rhs.day >> c >> rhs.year;
s >> str; s >> str; s >> str; // "Recording time :"
s >> rhs.hour >> c >> rhs.minute >> c >> rhs.second >> c >> rhs.ms;
s >> str; s >> str; s >> str; s >> str; // "(corresponds to time 0)"
s >> str >> std::ws; // "Study:"
//s >> rhs.study;
std::getline(s,rhs.study);
s >> str >> std::ws; // "Subject:"
//s >> rhs.subject;
std::getline(s,rhs.subject);
s >> str >> std::ws; // "Recording:"
//s >> rhs.recording;
std::getline(s,rhs.recording);
s >> str; s >> str >> std::ws; // "Screen resolution:"
s >> rhs.w >> str >> rhs.h >> std::ws;
Sep 01 2009 10:39 jacobi.cpp Page 1/2
#include <vector> a[ip][iq]=0.0;
#include <cmath> else if (fabs(a[ip][iq]) > tresh) {
h=d[iq]-d[ip];
#include <matrix.h> if (fabs(h)+g == fabs(h))
t=(a[ip][iq])/h;
#include "jacobi.h" else {
theta=0.5*h/(a[ip][iq]);
#define ROTATE(a,i,j,k,l) g=a[i][j];h=a[k][l];a[i][j]=g-s*(h+g*tau);\ t=1.0/(fabs(theta)+sqrt(1.0+theta*theta));
a[k][l]=h+s*(g-h*tau); if (theta < 0.0) t = -t;
}
int jacobi(matrix<double> a,std::vector<double> &d,matrix<double> &v) c=1.0/sqrt(1+t*t);
{ s=t*c;
// Computes all eigenvalues and eigenvectors of a real symmteric matrix tau=s/(1.0+c);
// a[1..n][1..n]. On output, elements of a above the diagonal are destroyed. h=t*a[ip][iq];
// d[1..n] returns the eigenvalues of a. v[1..n][1..n] is a matrix whose z[ip] -= h;
// columns contain, on ouput, the normalized eigenvectors of a. nrot z[iq] += h;
// returns the number Jacobi rotations that were required. d[ip] -= h;
// d[iq] += h;
// From: Press, William H., Teukolsky, Saul A., Vetterling, William T., and a[ip][iq]=0.0;
// Flannery, Brian P., ‘‘Numerical Recipes in C: The Art of Scientific for (j=0;j<ip-1;j++) {
// Computing’’, Cambridge University Press, (Cambridge:1992), 2nd ed. ROTATE(a,j,ip,j,iq)
// (p.467) }
for (j=ip+1;j<iq-1;j++) {
int n = a.rows(); ROTATE(a,ip,j,j,iq)
int nrot=0; }
int j,iq,ip,i; for (j=iq+1;j<n;j++) {
double tresh,theta,tau,t,sm,s,h,g,c; ROTATE(a,ip,j,iq,j)
}
std::vector<double> b(n); for (j=0;j<n;j++) {
std::vector<double> z(n); ROTATE(v,j,ip,j,iq)
}
// initialize eigenvalue and eigenvector arrays ++nrot;
d.clear(); d.resize(n,0.0); }
for(int ip=0; ip<n; ip++) { }
for(int iq=0; iq<n; iq++) }
v[ip][iq]=0.0; for (ip=0;ip<n;ip++) {
v[ip][ip]=1.0; b[ip] += z[ip];
} d[ip]=b[ip];
for (int ip=0; ip<n; ip++) { z[ip]=0.0;
b[ip] = d[ip] = a[ip][ip]; }
z[ip] = 0.0; }
} std::cerr << "Too many iterations in routine JACOBI" << std::endl;
nrot=0; return nrot;
for (i=0;i<50;i++) { }
sm=0.0;
for (ip=0;ip<n-1;ip++) { #undef ROTATE
for (iq=ip+1;iq<n;iq++)
sm += fabs(a[ip][iq]); void eigsrt(std::vector<double> &d,matrix<double> &v)
} {
if (sm == 0.0) return nrot; // Given the eigenvalues d[1..n] and eigenvectors v[1..n][1..n] as output
if (i < 4) // from jacobi() or tqli(), this routine sorts the eigenvalues into
tresh=0.2*sm/(n*n); // descending order, and rearranges the columns of v correspondingly.
else // The method is straight insertion.
tresh=0.0; //
for (ip=0;ip<n-1;ip++) { // From: Press, William H., Teukolsky, Saul A., Vetterling, William T., and
for (iq=ip+1;iq<n;iq++) { // Flannery, Brian P., ‘‘Numerical Recipes in C: The Art of Scientific
g=100.0*fabs(a[ip][iq]); // Computing’’, Cambridge University Press, (Cambridge:1992), 2nd ed.
if(i > 4 && fabs(d[ip])+g == fabs(d[ip]) // (p.468)
&& fabs(d[iq])+g == fabs(d[iq]) )
Sep 01 2009 10:39 jacobi.cpp Page 2/2
int k,n = d.size();
float p;

for(int i=0; i<n-1; i++) {


p = d[k=i];
for(int j=i+1; j<n; j++)
if(d[j] >= p) p = d[k=j];
if(k != i) {
d[k] = d[i];
d[i] = p;
for(int j=0; j<n; j++) {
p = v[j][i];
v[j][i] = v[j][k];
v[j][k] = p;
}
}
}
}
Sep 01 2009 10:39 kdtree.cpp Page 1/4
#include <iostream> // remove tree post-order
#include <iomanip> remove(node->left);
#include <vector> remove(node->right);
#include <string>
#include <algorithm> delete node; node = NULL;
#include <cmath> }
}
#ifdef ANM_OSX
#include <OpenGL/gl.h> KDTree::KDTree(vector<Cluster* > els, Point min, Point max)
#include <OpenGL/glu.h> {
#include <OpenGL/glext.h> int depth=0;
#else
#include <GL/gl.h> if(els.empty()) {
#include <GL/glu.h> root = NULL;
//#include <GL/glext.h> return;
#endif }
root = insert(NULL,els,depth,min,max);
#ifndef INFINITY }
#define INFINITY MAXFLOAT
#endif KDNode* KDTree::insert(KDNode* parent, vector<Cluster* > els, int depth, Point min, Po
int max)
using namespace std; {
if(els.empty()) return NULL;
#include "ellipse.h"
#include "point.h" // select axis based on depth so that axis cycles through all valid values
#include "cluster.h" int axis = depth % els[0]->dim();
#include "kdtree.h"
if(els.size() == 1)
// kd-tree implementation (based on various sources, include wikipedia) return(new KDNode(axis,els[0],min,max,NULL,NULL,parent));
// (c) Andrew T. Duchowski
// sort element list and choose median as pivot element
/////////////////////////// friends //////////////////////////////////////////// sort(els.begin(),els.end(),ClusterAxisCompare(axis));
ostream& operator<<(ostream& s,const KDNode& rhs)
{ // create node and construct subtrees
s.setf(ios::fixed,ios::floatfield); int median = els.size()/2;
s.precision(2);
s << rhs.left; // create the left and right sublists
if(!rhs.parent) { s << " [root] "; } Cluster *mel = els[median];
s << (*rhs.element); vector<Cluster* > lel, rel;
s << rhs.right; for(int i=0; i<median; i++) lel.push_back(els[i]);
return s; for(int i=median+1; i<(int)els.size(); i++) rel.push_back(els[i]);
}
// create new node, initializing its axis, value, and parent pointer
ostream& operator<<(ostream& s,const KDTree& rhs) KDNode* node = new KDNode(axis,mel,min,max,NULL,NULL,parent);
{ // recursively create left and right subtrees; attach to current node
s.setf(ios::fixed,ios::floatfield); Point _min, _max;
s.precision(2); _min = min;
s << rhs.root; _max = max;
return s; _max[axis] = (*mel)[axis]; // splitting on axis-value (e.g., x,y)
} node->left = insert(node,lel,depth+1,_min,_max);
_min = min;
KDTree::˜KDTree() _max = max;
{ _min[axis] = (*mel)[axis]; // splitting on axis-value (e.g., x,y)
remove(root); node->right = insert(node,rel,depth+1,_min,_max);
}
return node;
void KDTree::remove(KDNode* node) }
{
if(node) { void KDTree::knn_query(Cluster* q,vector<Cluster* > *p,double *r,int k)
Sep 01 2009 10:39 kdtree.cpp Page 2/4
{ // against those points that are already on the list; do this only if
*r = INFINITY; // the distance is smaller than the last point on the list, otherwise
if(k==1) { // safe to ignore this point
Cluster* c = NULL; if(dist < (*q).distance((*p).back())) {
nn(root,q,&c,r); for(curpp = (*p).begin(); curpp != (*p).end(); curpp++) {
(*p).push_back(c); if(dist < (*q).distance(*curpp)) {
} (*p).insert(curpp,1,node->element);
else knn(root,q,p,r,k); break;
} }
}
void KDTree::knn(KDNode* node,Cluster* q,vector<Cluster* > *p,double *r,int k) // since we know we have k nodes already and we just added one,
{ // pop one off the end
int axis; if((int)(*p).size() > k) (*p).pop_back();
double dist;
vector<Cluster* >::iterator curpp; // find largest distance in list
*r = (*q).distance((*p).back());
if(!node) return; }
}
// compute node’s distance to query point
dist = (*q).distance((*node->element)); // find largest distance in list
//*r = (*q).distance((*p).back());
// as we descend the tree to find the closest leaf (the first approximation
// is initially found at the leaf node which contains the target point), // traverse down the "closer" side of the tree -- note that
// but along the way test against each node that we touch -- it may be that // these recursive calls may (will) override the p,r arguments
// one of the nodes on the way down is closer // if a closer node than the current node is found
// //
// for k-nearest neighbors, maintain a sorted list s.t. the point // the first approximation (e.g., leaf node) is not necessarily the
// that is furthest away can be deleted if the list contains k points // closest; but from this descent-only search we know that any potential
// and a new closer point is found -- to do so, use a list that is // nearer neighbor must lie closer and so must lie within the circle
// sorted as points are added (e.g., via insertion sort basically), // defined by center q and radius r
// this way if we add a closer point than what is on the list, we can //
// safely remove the last point on the list // as we return we need to check to see whether the current closest
// // circle (defined by center q and radius r) intersects the "farther"
// for the actual search, instead of searching within a radius that is // side of the tree -- if it does, we must search that subtree
// the closest distance yet found, search within the area (volume) whose axis = node->axis;
// radius is the k-th closest yet found--until k points have been found, if((*q)[axis] <= (*node->element)[axis]) {
// this distance is infinity // check the circle (q,r) intersection with node->left (extraneous)
if((int)(*p).size() < k) { if((*q)[axis] - (*r) <= (*node->element)[axis]) knn(node->left,q,p,r,k);
// if we don’t yet have the requested number of points, insert current // check the circle (q,r) intersection with node->right
// node’s distance into (sorted) knn list without bothering to check if((*q)[axis] + (*r) > (*node->element)[axis]) knn(node->right,q,p,r,k);
// distance: } else {
// if list is empty or distance is larger than last node // check the circle (q,r) intersection with node->right (extraneous)
// (and there are fewer of them than required k) if((*q)[axis] + (*r) > (*node->element)[axis]) knn(node->right,q,p,r,k);
// just add the node to the back // check the circle (q,r) intersection with node->left
if( (*p).empty() || (dist > (*q).distance((*p).back())) ) if((*q)[axis] - (*r) <= (*node->element)[axis]) knn(node->left,q,p,r,k);
(*p).push_back(node->element); }
// otherwise }
// iterate through the list to find proper place to insert --
// it won’t be at the back since we already checked that void KDTree::nn_query(Cluster* q,Cluster* *p,double *r)
else { {
for(curpp = (*p).begin(); curpp != (*p).end(); curpp++) { *r = INFINITY;
if(dist < (*q).distance(*curpp)) { nn(root,q,p,r);
(*p).insert(curpp,1,node->element); }
break;
} void KDTree::nn(KDNode* node,Cluster* q,Cluster* *p,double *r)
} {
} int axis;
} else { double dist;
// insert current node into (sorted) knn list, checking its distance
Sep 01 2009 10:39 kdtree.cpp Page 3/4
if(!node) return;
// if element is within range query, add to list
// compute node’s distance to query point if( (min[0] <= (*node->element)[0]) && ((*node->element)[0] <= max[0]) &&
dist = (*q).distance(node->element); (min[1] <= (*node->element)[1]) && ((*node->element)[1] <= max[1]) ) {
// std::cerr << "Got point: " << (*node->element) << std::endl;
// as we descend the tree to find the closest leaf (the first (*p).push_back(node->element);
// approximation is initially found at the leaf node which contains }
// the target point), but along the way test against each node that we
// touch -- it may be that one of the nodes on the way down is closer // figure out which side of tree to go into
if(dist < *r) { if( (min[node->axis] <= (*node->element)[node->axis]) &&
*r = dist; ((*node->element)[node->axis] <= max[node->axis]) ) {
*p = node->element; // range box intersects split plane (straddles split axis),
} // need to traverse both sides of tree
range(node->left,p,min,max);
// traverse down the "closer" side of the tree -- note that range(node->right,p,min,max);
// these recursive calls may (will) override the p,r arguments }
// if a closer node than the current node is found else if(max[node->axis] <= (*node->element)[node->axis])
// // range box fully contained on smaller side of space,
// the first approximation (e.g., leaf node) is not necessarily the // need only to traverse left side of tree
// closest; but from this descent-only search we know that any potential range(node->left,p,min,max);
// nearer neighbor must lie closer and so must lie within the circle else if(min[node->axis] >= (*node->element)[node->axis])
// defined by center q and radius r // range box fully contained on larger side of space,
// // need only to traverse left side of tree
// as we return we need to check to see whether the current closest range(node->right,p,min,max);
// circle (defined by center q and radius r) intersects the "farther" }
// side of the tree -- if it does, we must search that subtree
axis = node->axis; void KDTree::render(float width,float height)
if((*q)[axis] <= (*node->element)[axis]) { {
// check the circle (q,r) intersection with node->left (extraneous) if(root) render(root,width,height);
if((*q)[axis] - (*r) <= (*node->element)[axis]) nn(node->left,q,p,r); }
// check the circle (q,r) intersection with node->right
if((*q)[axis] + (*r) > (*node->element)[axis]) nn(node->right,q,p,r); void KDTree::render(KDNode *node,float width,float height)
} else { {
// check the circle (q,r) intersection with node->right (extraneous)
if((*q)[axis] + (*r) > (*node->element)[axis]) nn(node->right,q,p,r); if(!node) return;
// check the circle (q,r) intersection with node->left
if((*q)[axis] - (*r) <= (*node->element)[axis]) nn(node->left,q,p,r); render(node->left,width,height);
}
} if(!node->axis) {
glColor4f(1.0,0.0,0.0,0.5);
void KDTree::range_query(vector<Cluster* > *p,double x1,double y1,double x2,double y2) glBegin(GL_LINES);
{ // draw y-axis aligned line extending to ymin
Point min, max; glVertex2f((*node->element)[0]*width,(*node->element)[1]*height);
glVertex2f((*node->element)[0]*width,node->min[1]*height);
// incoming points may be disorganized, i.e., user may have started out glEnd();
// at bottom-right, top-right, whatever; sort out into minx, maxx, miny, maxy glBegin(GL_LINES);
// bounding box s.t. top-left is (minx,miny) // draw y-axis aligned line extending to ymax
min[0] = x1 < x2 ? x1 : x2; glVertex2f((*node->element)[0]*width,(*node->element)[1]*height);
min[1] = y1 < y2 ? y1 : y2; glVertex2f((*node->element)[0]*width,node->max[1]*height);
max[0] = x1 > x2 ? x1 : x2; glEnd();
max[1] = y1 > y2 ? y1 : y2; } else {
glColor4f(0.0,1.0,0.0,0.5);
// call recursive query, filling in point vector with points within range glBegin(GL_LINES);
range(root,p,min,max); // draw x-axis aligned line extending to xmin
} glVertex2f((*node->element)[0]*width,(*node->element)[1]*height);
glVertex2f(node->min[0]*width,(*node->element)[1]*height);
void KDTree::range(KDNode* node, vector<Cluster* > *p, Point min, Point max) glEnd();
{ glBegin(GL_LINES);
if(!node) return; // draw x-axis aligned line extending to xmax
Sep 01 2009 10:39 kdtree.cpp Page 4/4
glVertex2f((*node->element)[0]*width,(*node->element)[1]*height);
glVertex2f(node->max[0]*width,(*node->element)[1]*height);
glEnd();
}

render(node->right,width,height);
}
Sep 01 2009 10:39 main.cpp Page 1/1
/**************************************************************************** GLObjectWindow* w = new GLObjectWindow;
** $Id: qt/main.cpp 3.1.2 edited Nov 8 2002 $
** // set size...
** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. w->resize( 640, 480 );
** //w->resize( 1280, 1024 );
** This file is part of an example program for Qt. This example app.setMainWidget( w );
** program may be used, distributed and modified without limitation. w->show();
** // ... or go full screen
*****************************************************************************/ //w->showFullScreen();
//
// Qt OpenGL example: Texture int result = app.exec();
// delete w;
// File: main.cpp return result;
// }
// The main() function
//

#ifdef ANM_OSX
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <OpenGL/glext.h>
#include <GLUT/glut.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glext.h>
#include <GL/glut.h>
#endif

#include <qapplication.h>
#include <qgl.h>
#include "glwinobj.h"
#include "crand.h"

int main(int argc, char **argv)


{
QApplication::setColorSpec(QApplication::CustomColor);
QApplication app(argc,argv);

if(!QGLFormat::hasOpenGL()) {
qWarning( "This system has no OpenGL support. Exiting." );
return -1;
}

// Create OpenGL format


QGLFormat f;
f.setDoubleBuffer(TRUE);
f.setRgba(TRUE);
f.setDepth(TRUE);
//f.setStencil(TRUE);
QGLFormat::setDefaultFormat(f);

// What’s this doing here?


glutInit(&argc,argv);

// initialize random number generator


crand(1);

// Create window
Sep 01 2009 10:39 point.cpp Page 1/3
#include <iostream> coord = c;
#include <iomanip> mean = c;
#include <string> }
#include <cmath>
Point::Point(const Point& rhs)
#ifdef ANM_OSX {
#include <OpenGL/gl.h> for(int i=0; i<(int)rhs.coord.size(); i++) {
#include <OpenGL/glu.h> coord.push_back(rhs.coord[i]);
#include <OpenGL/glext.h> mean.push_back(rhs.mean[i]);
#else mn.push_back(rhs.mn[i]);
#include <GL/gl.h> }
#include <GL/glu.h> for(int i=0; i<(int)rhs.sigma.size(); i++)
//#include <GL/glext.h> sigma.push_back(rhs.sigma[i]);
#endif timestamp = rhs.timestamp;
md = rhs.md;
using namespace std; }

#include "point.h" Point::Point(Point* rhs)


{
// point class implementation for(int i=0; i<(int)rhs->coord.size(); i++) {
// (c) Andrew T. Duchowski coord.push_back(rhs->coord[i]);
mean.push_back(rhs->mean[i]);
/////////////////////////// friends //////////////////////////////////////////// mn.push_back(rhs->mn[i]);
istream& operator>>(istream& s,Point& rhs) }
{ for(int i=0; i<(int)rhs->sigma.size(); i++)
int i=0; sigma.push_back(rhs->sigma[i]);
char c,n; timestamp = rhs->timestamp;
double f; md = rhs->md;
}
// assume we’re reading in Tobii’s EFD (Raw Data) data file
// Timestamp,Found,GazepointX,GazepointY Point Point::operator=(const Point& rhs)
{
// read timestamp if(this != &rhs) {
s >> rhs.timestamp; coord.clear();
mean.clear();
// read found string ("None", "Both", "RightOnly", "LeftOnly", and ??) mn.clear();
s >> rhs.found; for(int i=0; i<(int)rhs.coord.size(); i++) {
coord.push_back(rhs.coord[i]);
do { mean.push_back(rhs.mean[i]);
// read in gaze point coordinate mn.push_back(rhs.mn[i]);
s >> f; rhs[i] = f; i++; }
} while( s.get(c) && (c != ’\n’) && ((n = s.peek()) != ’\n’) && (i<2) ); timestamp = rhs.timestamp;
sigma.clear();
return(s); for(int i=0; i<(int)rhs.sigma.size(); i++)
} sigma.push_back(rhs.sigma[i]);
md = rhs.md;
ostream& operator<<(ostream& s,const Point& rhs) }
{ return *this;
s.setf(ios::fixed,ios::floatfield); }
s.precision(8);
s << rhs.timestamp; Point Point::operator*(Point& rhs)
s << "\t" << rhs.found; {
s << "\t" << rhs.coord[0] << "\t" << rhs.coord[1]; Point result(coord[0] * rhs.coord[0], coord[1] * rhs.coord[1]);
s << endl;
return s; return result;
} }

Point::Point(vector<double> c) Point Point::operator*(double scalar)


{ {
Sep 01 2009 10:39 point.cpp Page 2/3
for(int i=0; i<(int)coord.size(); i++) }
coord[i] *= scalar;
bool Point::closeto(Point& rhs)
return *this; {
} double dist=0.0,sdist=0.0;

Point Point::operator/(double scalar) if(mean.size() != rhs.mean.size())


{ std::cerr << "Warning: mean dimensions unequal!" << std::endl;
for(int i=0; i<(int)coord.size(); i++)
coord[i] /= scalar; for(int i=0; i<(int)mean.size(); i++) {
dist += pow(mean[i] - rhs.mean[i],2.0);
return *this; sdist += pow(sigma[i],2.0);
} }

Point Point::operator+(Point& rhs) if(sqrt(dist) < sqrt(sdist)) return true;


{ else return false;
Point result(coord[0] + rhs.coord[0], coord[1] + rhs.coord[1]); }

return result; bool Point::closeto(Point& rhs, double rhstime)


} {
double dist=0.0,sdist=0.0,dt;
Point Point::operator-(Point& rhs)
{ if(mean.size() != rhs.mean.size())
Point result(coord[0] - rhs.coord[0], coord[1] - rhs.coord[1]); std::cerr << "Warning: mean dimensions unequal!" << std::endl;

return result; for(int i=0; i<(int)mean.size(); i++) {


} dist += pow(mean[i] - rhs.mean[i],2.0);
sdist += pow(sigma[i],2.0);
bool Point::valid(void) }
{
//if( (found != "None") && (coord[0] > 0) && (coord[1] > 0) ) // the timestamp stored with the point is its creation time since
//if( (found == "Both") && (coord[0] > 0) && (coord[1] > 0) ) // the beginning of the session, in milliseconds, e.g., a point created
if( (found == "Both") && // 2 seconds after the session starts would have timestamp = 2000.0
(coord[0] > 0) && (coord[1] > 0) && dt = fabs(rhstime - timestamp);
(coord[0] < 1) && (coord[1] < 1)
) // Note from Anthony Santella: can use time here to split point out from
return true; // cluster, i.e., can use time to check connected components here. An
else // idea of a temporal mean would be useful here.
return false; if( (sqrt(dist) < sqrt(sdist)) && (dt < sigma[2]) )
} return true;
else
bool Point::duplicate(Point& rhs) return false;
{ }
if(this == &rhs) return false; // don’t delete yourself
double Point::mean_dist(Point& rhs)
if(distance(rhs) < 0.0001) return true; {
else return false; double dx = rhs.mean[0] - mean[0];
} double dy = rhs.mean[1] - mean[1];

double Point::distance(Point& rhs) // to use time or not?


{ double dt = rhs.timestamp - timestamp;
double dist=0.0; double k = K(dx,dy,dt); // spatiotemporal clustering
// double k = K(dx,dy); // spatial clustering only
if((*this).dim() != rhs.dim())
std::cerr << "Warning: coord dimensions unequal!" << std::endl; //std::cerr << "k = " << k << std::endl;

for(int i=0; i<(*this).dim(); i++) for(int i=0; i<2; i++) mn[i] += (k * rhs.mean[i]);
dist += pow(coord[i] - rhs.coord[i],2.0);
return(sqrt(dist)); md += k;
Sep 01 2009 10:39 point.cpp Page 3/3

return md;
}

double Point::mean_shift()
{
double dx,dy;
double delta = 0.0;
vector<double> oldmean(2,0.0); // keep old mean

for(int i=0; i<2; i++) oldmean[i] = mean[i];

// shift the mean


for(int i=0; i<2; i++) mean[i] = mn[i] / md;

// check how far mean has moved


dx = oldmean[0] - mean[0]; dy = oldmean[1] - mean[1];
delta = sqrt(dx*dx + dy*dy);
return delta;
}

double Point::K(double dx, double dy, double dt)


{
return gauss(dx,dy,dt);
}

double Point::K(double dx, double dy)


{
return gauss(dx,dy);
}

double Point::gauss(double dx, double dy, double dt)


{
double g = (dx*dx)/(sigma[0]*sigma[0]) +
(dy*dy)/(sigma[1]*sigma[1]) +
(dt*dt)/(sigma[2]*sigma[2]) ;

return(exp(-g));
}

double Point::gauss(double dx, double dy)


{
double g = (dx*dx)/(sigma[0]*sigma[0]) +
(dy*dy)/(sigma[1]*sigma[1]);

return(exp(-g));
}
Sep 01 2009 10:39 polynomial.cpp Page 1/4
#include <iostream> Polynomial result(degree() + rhs.degree() + 1);
#include <iomanip>
#include <string> for(int i=0; i<=degree() + rhs.degree(); i++)
#include <math.h> result.coefs.push_back(0.0);
for(int i=0; i<=degree(); i++)
using namespace std; for(int j=0; j<=rhs.degree(); j++)
result.coefs[i+j] += coefs[i]*rhs.coefs[j];
#include "polynomial.h"
return result;
// polynomial implementation (based on java implementation I found on the web) }
// (c) Andrew T. Duchowski
Polynomial Polynomial::operator/(double scalar)
/////////////////////////// friends //////////////////////////////////////////// {
ostream& operator<<(ostream& s,Polynomial& rhs) for(int i=0; i<(int)coefs.size(); i++)
{ coefs[i] /= scalar;
s.setf(ios::fixed,ios::floatfield);
s.precision(2); return *this;
for(int i=rhs.degree(); i>=0; --i) { }
if(rhs[i] < 0.0)
s << " - "; Polynomial Polynomial::simplify()
else {
s << " + "; for(int i=degree(); i>=0; --i) {
s << fabsf(rhs[i]); if(fabsf(coefs[i]) <= TOLERANCE) {
if(i > 1) coefs.pop_back();
s << " x^" << i; }
else if(i==1) else break;
s << " x"; }
}
s << endl; return *this;
return s; }
}
double Polynomial::eval(double x)
Polynomial::Polynomial(vector<double> c) {
{ double result=0;
coefs.clear();
for(int i=c.size()-1; i>=0; --i) coefs.push_back(c[i]); for(int i=coefs.size()-1; i>=0; --i)
} result = result*x + coefs[i];
return result;
Polynomial::Polynomial(const Polynomial& rhs) }
{
coefs.clear(); bool Polynomial::bisection(double min,double max,double *root)
for(int i=0; i<(int)rhs.coefs.size(); i++) coefs.push_back(rhs.coefs[i]); {
} double minValue = eval(min);
double maxValue = eval(max);
Polynomial::Polynomial(Polynomial* rhs) double result = 0.0;
{ bool haveRoot = false;
coefs.clear();
for(int i=0; i<(int)rhs->coefs.size(); i++) coefs.push_back(rhs->coefs[i]); if(fabsf(minValue) <= TOLERANCE) {
} result = min;
haveRoot = true;
Polynomial Polynomial::operator=(const Polynomial& rhs) *root = result;
{ }
coefs.clear(); else if(fabsf(maxValue) <= TOLERANCE) {
for(int i=0; i<(int)rhs.coefs.size(); i++) coefs.push_back(rhs.coefs[i]); result = max;
return *this; haveRoot = true;
} *root = result;
}
Polynomial Polynomial::operator*(Polynomial& rhs) else if(minValue*maxValue <= 0.0) {
{ double tmp1 = log(max-min);
Sep 01 2009 10:39 polynomial.cpp Page 2/4
double tmp2 = log(10.0) * ACCURACY; vector<double> Polynomial::rootsInInterval(double min,double max)
int iters = (int)ceilf((tmp1+tmp2)/log(2.0)); {
for(int i=0; i<iters; i++) { double root;
result = 0.5*(min+max); vector<double> roots;
double value = eval(result);
if(fabsf(value) <= TOLERANCE) if(degree() == 1) {
break; if(bisection(min,max,&root))
if(value*minValue < 0.0) { roots.push_back(root);
max = result; } else {
maxValue = value; Polynomial deriv = derivative();
} else { Polynomial droots = deriv.rootsInInterval(min,max);
min = result; if(droots.coefs.size() > 0) {
minValue = value; if(bisection(min,droots[0],&root))
} roots.push_back(root);
} for(int i=0; i<=(int)droots.coefs.size()-2; i++) {
haveRoot = true; if(bisection(droots[i],droots[i+1],&root))
*root = result; roots.push_back(root);
} }
return(haveRoot); if(bisection(droots[droots.coefs.size()-1],max,&root))
} roots.push_back(root);
} else {
Polynomial Polynomial::derivative() if(bisection(min,max,&root))
{ roots.push_back(root);
Polynomial derivative(degree()-1); }
}
derivative.coefs.clear(); return(roots);
for(int i=1; i<(int)coefs.size(); i++) }
derivative.coefs.push_back(i*coefs[i]);
vector<double> Polynomial::linearRoot()
return derivative; {
} vector<double> result;
double a = coefs[1];
vector<double> Polynomial::roots()
{ if(fabsf(a) > 0.000001)
vector<double> result; result.push_back(-coefs[0]/a);

simplify(); return(result);
}
switch(degree()) {
case 0: vector<double> Polynomial::quadraticRoots()
break; {
case 1: vector<double> results;
result = linearRoot();
break; if(degree()==2) {
case 2: double a = coefs[2];
result = quadraticRoots(); double b = coefs[1]/a;
break; double c = coefs[0]/a;
case 3: double d = b*b-4.0*c;
result = cubicRoots(); if(d > 0.0) {
break; double e = sqrt(d);
case 4: results.push_back(0.5*(-b+e));
result = quarticRoots(); results.push_back(0.5*(-b-e));
break; } else if(fabsf(d) < 0.000001) {
default: results.push_back(0.5*-b);
break; }
} }
return(result); return(results);
} }
Sep 01 2009 10:39 polynomial.cpp Page 3/4
vector<double> Polynomial::cubicRoots() double c2=coefs[2]/c4;
{ double c1=coefs[1]/c4;
vector<double> results; double c0=coefs[0]/c4;
Polynomial resolveRoots = Polynomial(1.0,
if(degree()==3) { -c2,
double c3=coefs[3]; c3*c1-4.0*c0,
double c2=coefs[2]/c3; -c3*c3*c0+4.0*c2*c0-c1*c1).cubicRoots();
double c1=coefs[1]/c3; double y=resolveRoots[0];
double c0=coefs[0]/c3; double discrim=c3*c3/4.0-c2+y;
double a=(3.0*c1-c2*c2)/3.0; if(fabsf(discrim) <= TOLERANCE) {
double b=(2.0*c2*c2*c2-9.0*c1*c2+27.0*c0)/27.0; discrim=0.0;
double offset=c2/3.0; double t2 = y*y-4.0*c0;
double discrim=b*b/4.0 + a*a*a/27.0; if(t2 >= -TOLERANCE) {
double halfB=b/2.0; if(t2 < 0.0)
if(fabsf(discrim) <= TOLERANCE) { t2 = 0.0;
discrim = 0.0; t2 = 2.0*sqrt(t2);
double tmp; double t1 = 3.0*c3*c3/4.0-2.0*c2;
if(halfB >= 0.0) if(t1+t2 >= TOLERANCE) {
tmp=-pow(halfB,1.0/3.0); double d=sqrt(t1+t2);
else results.push_back(-c3/4.0 + d/2.0);
tmp=pow(-halfB,1.0/3.0); results.push_back(-c3/4.0 - d/2.0);
results.push_back(2.0*tmp-offset); }
results.push_back( -tmp-offset); if(t1-t2 >= TOLERANCE) {
} else if(discrim > 0.0) { double d = sqrt(t1-t2);
double e=sqrt(discrim); results.push_back(-c3/4.0 + d/2.0);
double tmp; results.push_back(-c3/4.0 - d/2.0);
double root; }
tmp=-halfB+e; }
if(tmp >= 0.0) } else if(discrim > 0.0) {
root=pow(tmp,1.0/3.0); double e=sqrt(discrim);
else double t1=3.0*c3*c3/4.0-e*e-2.0*c2;
root=-pow(-tmp,1.0/3.0); double t2=(4.0*c3*c2-8.0*c1-c3*c3*c3)/(4.0*e);
tmp=-halfB-e; double plus=t1+t2;
if(tmp >= 0.0) double minus=t1-t2;
root+=pow(tmp,1.0/3.0); if(fabsf(plus) <= TOLERANCE)
else plus = 0.0;
root-=pow(-tmp,1.0/3.0); if(fabsf(minus) <= TOLERANCE)
results.push_back(root-offset); minus = 0.0;
} else if(discrim < 0.0) { if(plus >= 0.0) {
double distance=sqrt(-a/3.0); double f = sqrt(plus);
double angle=atan2(sqrt(-discrim),-halfB)/3.0; results.push_back(-c3/4.0 + (e+f)/2.0);
double kos=cos(angle); results.push_back(-c3/4.0 + (e-f)/2.0);
double syn=sin(angle); }
double sqrt3=sqrt(3.0); if(minus >= 0.0){
results.push_back(2.0*distance*kos-offset); double f = sqrt(minus);
results.push_back(-distance*(kos+sqrt3*syn)-offset); results.push_back(-c3/4.0 + (f-e)/2.0);
results.push_back(-distance*(kos-sqrt3*syn)-offset); results.push_back(-c3/4.0 - (f+e)/2.0);
} }
} } else if(discrim < 0.0) { }
return(results); }
} return(results);
}
vector<double> Polynomial::quarticRoots()
{ Polynomial bezout(Polynomial e1,Polynomial e2)
vector<double> results; {

if(degree()==4) { // from <http://www.kevlindev.com/gui/math/intersection/index.htm>


double c4=coefs[4]; //
double c3=coefs[3]/c4; // bezout() calculates the Bezout determinant for the two specified
Sep 01 2009 10:39 polynomial.cpp Page 4/4
// ellipses. The resulting quadratic polynomial is returned. This
// method is used by intersect().
//
// e1 is an array of the coefficients of an ellipse in general quadratic
// form: ax^2 + bxy + cy^2 + dx + ey + f = 0.
// The coefficients are in a,b,c,d,e,f order.
//
// e2 is an array of the coefficients of an ellipse in general quadratic
// form: ax^2 + bxy + cy^2 + dx + ey + f = 0.
// The coefficients are in a,b,c,d,e,f order.’

double AB = e1[0]*e2[1] - e2[0]*e1[1];


double AC = e1[0]*e2[2] - e2[0]*e1[2];
double AD = e1[0]*e2[3] - e2[0]*e1[3];
double AE = e1[0]*e2[4] - e2[0]*e1[4];
double AF = e1[0]*e2[5] - e2[0]*e1[5];

double BC = e1[1]*e2[2] - e2[1]*e1[2];


double BE = e1[1]*e2[4] - e2[1]*e1[4];
double BF = e1[1]*e2[5] - e2[1]*e1[5];

double CD = e1[2]*e2[3] - e2[2]*e1[3];

double DE = e1[3]*e2[4] - e2[3]*e1[4];


double DF = e1[3]*e2[5] - e2[3]*e1[5];

double BFpDE = BF + DE;


double BEmCD = BE - CD;

double e = AB*BC - AC*AC;


double d = AB*BEmCD + AD*BC - 2.0*AC*AE;
double c = AB*BFpDE + AD*BEmCD - AE*AE - 2.0*AC*AF;
double b = AB*DF + AD*BFpDE - 2.0*AE*AF;
double a = AD*DF - AF*AF;

return(Polynomial(e,d,c,b,a));
}
Sep 01 2009 10:39 scanpath.cpp Page 1/10
#include <iostream> string eventName="blank";
#include <iomanip>
#include <fstream> // need to determine scaling factor
#include <sstream> if(rhs.header) {
#include <vector> // stimulus width, height (not display screen!)
#include <string> w = rhs.header->width(); h = rhs.header->height();
#include <list> } else {
#include <algorithm> w = 1280.0; h = 1024.0; // default (HACK!)
#include <utility> }
#include <cmath>
//std::cerr << rhs.header;
#ifdef ANM_OSX
#include <OpenGL/gl.h> // figure out what it is user is trying to read in
#include <OpenGL/glu.h> if(!rhs.formats.empty()) file = rhs.formats[rhs.formats.size()-1];
#include <OpenGL/glext.h> switch(file) {
#else case EFD:
#include <GL/gl.h> // Timestamp,Found,GazepointX,GazepointY
#include <GL/glu.h> s >> str >> str >> str >> str;
//#include <GL/glext.h>
#endif while(!s.eof()) {
// read in gaze point info and eat whitespace at EOL
using namespace std; s >> (pp = new Point()) >> std::ws;

#include <vector.h> // normalize


#include <matrix.h> pp->scale(1.0/w,1.0/h);
#include <quaternion.h>
// flip y-coordinate to put into GL coordinates
#include "polynomial.h" pp->flip();
#include "ellipse.h"
#include "point.h" if(pp->valid()) rhs.insertPoint(pp);
#include "cluster.h" else delete pp;
#include "fixation.h" }
#include "scanpath.h"
#include "aoi.h" // analyze eye movements (e.g., via LC Tech’s position-variance scheme)
#include "aoievent.h" rhs.position_variance();

// scanpath implementation // use k-means as dispersion-based data reduction (clustering) method


// (c) Andrew T. Duchowski rhs.fixation_mean_shift();

/*! // classify fixations


input routine: handles Tobii files: rhs.classifyFixations();
EFD (gaze points) -- applies mean shift as a means of testing k-means
for eye movement analysis break;
AOIL -- reads AOIL file to get at stimulus name case AOI:
FXD (fixations) -- applies mean shift as a means for data reduction // Time Duration Name
*/ s >> str >> str >> str >> std::ws;
istream& operator>>(istream& s, Scanpath& rhs)
{ while(!s.eof()) {
int i; s >> (ap = new AOIEvent()) >> std::ws;
file_t file; //Name of file to read
string str; // std::cerr << ap << std::endl;
string name, image_url;
double w,h; rhs.insertAOI(ap);
Point *pp; }
Fixation *fp; break;
AOIEvent *ap;
int time=0; case AOIL:
int eventKey=0; // Stimuli: <stimulus> - AOI List
int data1=0,data2=0; s >> str >> rhs.stimulus >> str >> str >> str >> std::ws;
Sep 01 2009 10:39 scanpath.cpp Page 2/10
std::cerr << "Not implemented yet" << std::endl;
// AOI ID AOI Name Image/URL break;
s >> str >> str >> str >> str >> str >> std::ws; case UNK:
default:
while(!s.eof()) { std::cerr << "Unknown file type" << std::endl;
// read in name image/url and eat whitespace at EOL break;
s >> i >> name >> image_url >> std::ws; }
return s;
rhs.insertAoiL(make_pair(name,image_url)); }
}
/*!
// debugging output routine (for debugging mainly)
// rhs.printAoiL(); */
break; ostream& operator<<(ostream& s, Scanpath& rhs)
case CMD: {
std::cerr << "Not implemented yet" << std::endl; float x,y,t;
break; double w,h;

case EVD: // need to determine scaling factor


// Time Eventname Eventkey Data1 Data2 if(rhs.header) {
s >> str >> str >> str >> str >> str >> std::ws; // stimulus width, height (not display screen!)
w = rhs.header->width(); h = rhs.header->height();
while(!s.eof()) { } else {
// Read in timestamp event name w = 1280.0; h = 1024.0; // default (HACK!)
s >> time >> eventName >> eventKey >> data1 >> data2 >> std::ws; }
rhs.insertEVD(make_pair((float)data1/w,(float)data2/h));
} if(rhs.header) s << rhs.header;
break;
case FXD: if(rhs.fixation.empty()) {
s >> (rhs.filter = new Filter()); // dump out all gaze points
s << std::endl;
// Fix number,Timestamp,Duration,GazepointX,GazepointY s << "Timestamp" << "\t";
s >> str >> str >> str >> str >> str >> str; s << "Found" << "\t";
s << "GazepointX" << "\t";
while(!s.eof()) { s << "GazepointY" << std::endl;
// read in fixation info and eat whitespace at EOL s << std::endl;
s >> (fp = new Fixation()) >> std::ws; for(int i=0; i < (int)rhs.point.size(); i++) {
t = rhs.point[i]->gettimestamp();
// normalize x = (*rhs.point[i])[0];
fp->scale(1.0/w,1.0/h); y = (*rhs.point[i])[1];
// flip y-coordinate to put into image coordinates
// flip y-coordinate to put into GL coordinates y = 1.0 - y;
fp->flip(); // scale to image dimensions
x = x * w;
// for display purposes mainly, to match Tobii circles y = y * h;
fp->setRadius(rhs.filter->radius());
s << (int)t << "\t" << rhs.point[i]->getStatus() << "\t";
rhs.insertFixation(fp); s << (int)x << "\t" << (int)y;
} s << std::endl;
std::cerr << "read " << rhs.fixation.size() << " fixations" << std::endl; }
} else {
// use k-means as dispersion-based data reduction method // dump out filter settings
rhs.fixation_mean_shift(); s << std::endl;
if(rhs.filter) s << rhs.filter;
// classify fixations
rhs.classifyFixations(); // dump out all fixation points
s << std::endl;
break; s << "Fix number" << "\t";
case GZD: s << "Timestamp" << "\t";
Sep 01 2009 10:39 scanpath.cpp Page 3/10
s << "Duration" << "\t"; // ’-’
s << "GazepointX" << "\t"; // glVertex2f(x*width+x_len, (height - (y*height)));
s << "GazepointY" << std::endl; // glVertex2f(x*width-x_len, (height - (y*height)));
s << std::endl;
for(int i=0; i < (int)rhs.fixation.size(); i++) { // ’\’
t = rhs.fixation[i]->gettimestamp(); glVertex2f(x*width-x_len, (height - (y*height-x_len)));
x = (*rhs.fixation[i])[0]; glVertex2f(x*width+x_len, (height - (y*height+x_len)));
y = (*rhs.fixation[i])[1];
// flip y-coordinate to put into image coordinates glEnd();
y = 1.0 - y; }
// scale to image dimensions }
x = x * w;
y = y * h; // reset line width
glLineWidth(1);
s << (int)rhs.fixation[i]->getNumber() << "\t";
s << (int)t << "\t"; // use scanpath color for all elements
s << (int)rhs.fixation[i]->getDuration() << "\t"; glColor4f(color[0],color[1],color[2],color[3]);
s << (int)x << "\t" << (int)y;
s << std::endl; // render gaze points
} if(!point.empty()) {
} for(int i=0; i < (int)point.size(); i++) {
x = (*point[i])[0] * width; y = (*point[i])[1] * height;
return s; glPushMatrix();
} glTranslatef(x,y,0.0);
gluDisk(circ,0,3,360,1);
/*! glPopMatrix();
main drawing routine, draws: }
gaze points (if EFD file was read in) // render raw scanpath joining gaze points
clusters (whether they were made from gaze points or fixations) glBegin(GL_LINE_STRIP);
kd-tree (if available) for(int i=0; i < (int)point.size(); i++) {
fixations (if FXD file read in) x = (*point[i])[0] * width; y = (*point[i])[1] * height;
*/ glVertex2f(x, y);
void Scanpath::render(map<string,Aoi* > AOI,float width,float height) }
{ glEnd();
int x_len=8; }
float x,y;
double r; // render clusters
Aoi *aoip; bool bbox = true;
GLUquadricObj* circ = gluNewQuadric(); for(int r=0; r<(int)cluster.size(); r++)
cluster[r]->render(width,height,color,bbox);
// render mouse click events // reset line width
glColor4f(1.0,0.0,0.0,1.0); glLineWidth(8);
glLineWidth(5); glBegin(GL_LINE_STRIP);
glEnable (GL_LINE_SMOOTH); for(int r=0; r<(int)cluster.size(); r++)
if(!evd.empty()) { glVertex2f((*cluster[r])[0]*width,(*cluster[r])[1]*height);
for(int i=0; i < (int)evd.size(); i++){ glEnd();

x = evd[i].first; y = evd[i].second; glLineWidth(1);


//if(kdtree) kdtree->render(width,height);
glBegin(GL_LINES);
// render fixations
// ’/’ if(!fixation.empty()) {
glVertex2f(x*width-x_len, (height - (y*height+x_len))); for(int i=0; i < (int)fixation.size(); i++) {
glVertex2f(x*width+x_len, (height - (y*height-x_len))); /*
switch(fixation[i]->getFixation()) {
// ’|’ case AMBIENT:
// glVertex2f(x*width, (height - (y*height+x_len))); glColor4f(0.0,1.0,0.0,color[3]);
// glVertex2f(x*width, (height - (y*height-x_len))); break;
case FOCAL:
Sep 01 2009 10:39 scanpath.cpp Page 4/10
glColor4f(1.0,0.0,0.0,color[3]); return;
break; }
case UNCLASSIFIED:
glColor4f(0.0,0.0,0.0,color[3]); if(header) {
break; // stimulus width, height (not display screen!)
} w = header->width(); h = header->height();
*/ } else {
x = (*fixation[i])[0] * width; y = (*fixation[i])[1] * height; w = 1280.0; h = 1024.0; // default (HACK!)
r = fixation[i]->getRadius(); }
glPushMatrix();
glTranslatef(x,y,0.0); //std::cerr << "point size = " << (int)point.size() << std::endl;
// disk, for circle, use gluDisk(circ,r-2.0,r,360,1); for(int k=0; k<(int)point.size(); k++) {
gluDisk(circ,r-2.0,r,360,1); // scale point coordinates to Tobii display screen dimensions
// gluDisk(circ,0,r,360,1); x = (float)((*point[k])[0] * w); y = (float)((*point[k])[1] * h);
glPopMatrix(); // std::cerr << "point [" << k << "] (" << x << "," << y << ")\n";
} switch(ffilter.detectFixation(point[k]->valid(),x,y)) {
} case FIXATING:
// std::cerr << "FIXATING" << std::endl;
glColor4f(0.0,0.0,1.0,0.05); if(!started) {
// render AOI fixations (from aoi vector) spent in AOIs (from AOI map) ts = point[k]->gettimestamp();
if(!aoi.empty()) { started=true;
for(int i=0; i < (int)aoi.size(); i++) { pointcount=0;
// check to see if we have AOI list }
if(aoi[i]->getName() != "Content" && (aoip = AOI[aoi[i]->getName()])) { // update (running) mean (centroid)
glBegin(GL_QUADS); // from Brown, Robert Grover, "Introduction to Random Signal
for(int k=0;k<4;k++) { // Analysis and Kalman Filtering", John Wiley \& Sons, New York, NY,
x = aoip->coord[k][0] * width; // 1983 [p.182]
y = aoip->coord[k][1] * height; // TK5102.5 .B696
glVertex2f(x,y); for(int i=0; i<2; i++)
} mean[i] = (float)pointcount/(float)(pointcount+1) * mean[i] +
glEnd(); 1.0/(float)(pointcount+1) * (*point[k])[i];
} pointcount++;
} break;
} case MOVING:
// std::cerr << "MOVING" << std::endl;
gluDeleteQuadric(circ); break;
} case FIXATION_COMPLETED:
// std::cerr << "FIXATION_COMPLETED" << std::endl;
/*! if(!filter) filter = new Filter();
use LC Tech’s fixation detection to classify points
*/ te = point[k]->gettimestamp();
void Scanpath::position_variance(void) tt = te - ts;
{ started = false;
Fixation *fp=NULL;
std::vector<float> mean(2,0.0); // obtain fixation attributes from filter, normalizing coords
int pointcount=0,n=0; fp = new Fixation(ffilter.getFixation_x() / w,
float x,y; ffilter.getFixation_y() / h,ts);
double ts=0.0,te=0.0,tt=0.0; // samples x sampling rate (ms)
double w,h; fp->setDuration(ffilter.getFixationDuration() * 20.0);
bool started=false; // samples x sampling rate (ms)
fp->setPrevSaccDuration(ffilter.getPrevSaccadeDuration() * 20.0);
// remove consecutive duplicates
//std::cerr << "Before point.erase: " << point.size() << " points\n"; // my own (manual) way of calculating centroid, duration
point.erase(unique(point.begin(),point.end(),PointDuplicate()),point.end()); // fp = new Fixation(mean[0],mean[1],ts);
//std::cerr << "After point.erase: " << point.size() << " points\n"; // fp->setDuration(tt);

// check for sufficient number of points (can’t cluster 1 point) // radius (from filter)
if(point.size() < 2) { fp->setRadius(filter->radius());
std::cerr << "Not enough points!" << std::endl;
Sep 01 2009 10:39 scanpath.cpp Page 5/10
// number (which fixation; for i/o purposes) maxdm = -HUGE;
fp->setNumber(++n);
// for each point, get it to calculate the weighted sum of distances
insertFixation(fp); // of other means
for(int k=0; k<(int)point.size(); k++)
// debug info; comparing my code with LC Tech’s for(int j=0; j<(int)point.size(); j++)
// std::cerr << " my mean: ("; dm = point[k]->mean_dist(*point[j]);
// std::cerr << mean[0] << ",";
// std::cerr << mean[1] << ")"; // for each point, get it to shift its mean by evaluating num/denom
// std::cerr << " filter: ("; for(int k=0; k<(int)point.size(); k++)
// std::cerr << ffilter.getFixation_x() / w << ","; if((dm = point[k]->mean_shift()) > maxdm) maxdm = dm;
// std::cerr << ffilter.getFixation_y() / h << ")";
// std::cerr << std::endl; } while(maxdm > 0.1); // iterative stopping criterion
// std::cerr << " my duration: "; //std::cerr << "done." << std::endl;
// std::cerr << tt << ",";
// std::cerr << " filter: "; // result above is that each point has shifted its mean to the local
// std::cerr << ffilter.getFixationDuration() * 20.0; // cluster center; hence new clusters are defined by point whose
// std::cerr << std::endl; // means are very close together (less than sigma)
// std::cerr << "prev saccade duration: "; //
// std::cerr << ffilter.getPrevSaccadeDuration() * 20.0; // note that there may be clusters that only contain one point -- these
// std::cerr << std::endl; // should be either considered outliers or should be made into very small
// clusters
// resetting mean for next fixation
mean[0] = 0.0; mean[1] = 0.0; // create clusters s.t. all the points with very close (distance wise)
break; // means get lumped into same cluster clusters should then perform PCA
} // to find ellipse parameters
}
std::cerr << "detected " << n << " fixations." << std::endl; std::cerr << "inserting " << point.size() << " points...";
} for(int k=0; k<(int)point.size(); k++) {
// if no clusters exist yet, make a new one
/*! if(cluster.empty()) cluster.push_back(new Cluster(*point[k]));
mean shift applied to gaze points else {
(as means for position-variance, or dispersion-based, eye movement analysis) // check to see if this point belongs in any cluster
*/ bool accepted = false;
void Scanpath::point_mean_shift(void) for(int r=0; r<(int)cluster.size() && !accepted; r++)
{ accepted = (*cluster[r]).insert(*point[k]);
matrix<double> cov(2,2); // if not, make a new cluster
double maxdm, dm; if(!accepted) cluster.push_back(new Cluster(*point[k]));
}
// remove consecutive duplicates }
point.erase(unique(point.begin(),point.end(),PointDuplicate()),point.end()); std::cerr << "created " << cluster.size() << " clusters." << std::endl;

// check for sufficient number of points (can’t cluster 1 point) // perform PCA on each cluster: this will set up ellipses for each as well
if(point.size() < 2) { //std::cerr << "doing pca...";
std::cerr << "Not enough points!" << std::endl; for(int r=0; r < (int)cluster.size(); r++) cluster[r]->pca();
return; //std::cerr << "pca done." << std::endl;
} }

//std::cerr << "mean shift..."; /*!


// implement Santella and DeCarlo’s iterative mean shift algorithm mean shift applied to fixations
// alg done in two steps: (as means for data reduction)
// step 1: */
// (1) calculate numerator: \sum_{j} k(x - x_j) x_j void Scanpath::fixation_mean_shift(void)
// (2) calculate denominator: \sum_{j} k(x - x_j) {
// step 2: matrix<double> cov(2,2);
// (1) shift the mean by dividing numerator by denominator: double maxdm, dm;
// s(x) = \frac{\sum_{j} k(x - x_j) x_j}{\sum_{j} k(x - x_j)}
do { // remove consecutive duplicates
// reset max delta mean value fixation.erase(unique(fixation.begin(),fixation.end(),PointDuplicate()),
Sep 01 2009 10:39 scanpath.cpp Page 6/10
fixation.end()); std::cerr << "created " << cluster.size() << " clusters." << std::endl;

// check for sufficient number of fixations (can’t cluster 1 fixation) // perform PCA on each cluster: this will set up ellipses for each as well
if(fixation.size() < 2) { //std::cerr << "doing pca...";
std::cerr << "Not enough fixations!" << std::endl; for(int r=0; r < (int)cluster.size(); r++) cluster[r]->pca();
return; //std::cerr << "pca done." << std::endl;
} }

//std::cerr << "mean shift..."; /*!


// implement Santella and DeCarlo’s iterative mean shift algorithm build kd-tree for clsuters (cluster is a vector of clusters)
// alg done in two steps: */
// step 1: void Scanpath::growkdtree(float w, float h)
// (1) calculate numerator: \sum_{j} k(x - x_j) x_j {
// (2) calculate denominator: \sum_{j} k(x - x_j) Point min(-w, -h, 0.0);
// step 2: Point max( w, h, 0.0);
// (1) shift the mean by dividing numerator by denominator:
// s(x) = \frac{\sum_{j} k(x - x_j) x_j}{\sum_{j} k(x - x_j)} // create kd-tree
do { kdtree = new KDTree(cluster,min,max);
// reset max delta mean value
maxdm = -HUGE; //std::cerr << "Tree (inorder): " << std::endl;
//std::cerr << kdtree << std::endl;
// for each fixation, get it to calculate the weighted sum of distances }
// of other means
for(int k=0; k<(int)fixation.size(); k++) /*!
for(int j=0; j<(int)fixation.size(); j++) find (k) nearest clusters closet to current cluster
dm = fixation[k]->mean_dist(*fixation[j]); */
void Scanpath::knn_query(Cluster* q,vector<Cluster* > *p, double *r,int k)
// for each fixation, get it to shift its mean by evaluating num/denom {
for(int k=0; k<(int)fixation.size(); k++) if(kdtree) kdtree->knn_query(q,p,r,k);
if((dm = fixation[k]->mean_shift()) > maxdm) maxdm = dm; }

} while(maxdm > 0.1); // iterative stopping criterion /*!


//std::cerr << "done." << std::endl; find (single) nearest cluster closet to current cluster
*/
// result above is that each fixation has shifted its mean to the local void Scanpath::nn_query(Cluster* q,Cluster* *nn,double *r)
// cluster center; hence new clusters are defined by point whose {
// means are very close together (less than sigma) if(kdtree) kdtree->nn_query(q,nn,r);
// }
// note that there may be clusters that only contain one point -- these
// should be either considered outliers or should be made into very small /*!
// clusters find nearest clusters closest to current cluster within range
*/
// create clusters s.t. all the fixations with very close (distance wise) void Scanpath::range_query(vector<Cluster* > *q,double x1,double y1,
// means get lumped into same cluster; clusters should then perform PCA double x2,double y2)
// to find ellipse parameters {
if(kdtree) kdtree->range_query(q,x1,y1,x2,y2);
std::cerr << "inserting " << fixation.size() << " fixations..."; }
for(int k=0; k<(int)fixation.size(); k++) {
// if no clusters exist yet, make a new one /*!
if(cluster.empty()) cluster.push_back(new Cluster(*fixation[k])); label scanpath from 0 onwards (output will be faked to show range [33,126]
else { */
// check to see if this fixation belongs in any cluster void Scanpath::label(int *letter)
bool accepted = false; {
for(int r=0; r<(int)cluster.size() && !accepted; r++) //std::cerr << cluster.size() << " clusters: " << std::endl;
accepted = (*cluster[r]).insert(*fixation[k]); for(int r=0; r<(int)cluster.size(); r++) {
// if not, make a new cluster //std::cerr << "cluster: " << r << ";
if(!accepted) cluster.push_back(new Cluster(*fixation[k])); //std::cerr << "letter: " << setw(3) << *letter << std::endl;
} //std::cerr << "letter: " << (char)((*letter)%94+33) << std::endl;
} //std::cerr << "letter: " << (char)((*letter)%26+65) << std::endl;
Sep 01 2009 10:39 scanpath.cpp Page 7/10
//std::cerr << "letter: " << (char)((*letter - 1)%26+65) << std::endl;
cluster[r]->letter = ++(*letter); // concatenate cluster letters into scanpath string
} for(int r=0; r<(int)cluster.size(); r++)
} istring.push_back(cluster[r]->letter);

/*! return istring;


this is the main ’’scanpath intersection’’ routine, i.e., how to }
generate cluster labels, based on ellipse-ellipse intersections
*/ /*!
void Scanpath::matchlabels(Scanpath* that,int *letter) produce scanpath string
{ */
double radius; string Scanpath::printString() const
vector<Cluster* > range; {
ostringstream os;
// debug: print out the next letter
//std::cerr << "letter: " << (*letter + 1) << ": "; // concatenate cluster letters into scanpath string
//std::cerr << "letter: " << (char)((*letter + 1)%26+64) << std::endl; for(int r=0; r<(int)cluster.size(); r++)
//std::cerr << "letter: " << ((*letter + 1)%26 == 0 ? (char)((*letter + 2)%26+64) : (c // os << (char)(cluster[r]->letter % 94 + 33);
har)((*letter + 1)%26+64)) << std::endl; // os << (char)(cluster[r]->letter % 26 + 65);
os << (char)((cluster[r]->letter - 1) % 26 + 65);
// for each of this scanpath’s clusters,
// find that scanpath’s ‘‘sufficiently close’’ clusters and assign letters return os.str();
for(int r=0; r<(int)this->cluster.size(); r++) { }
// iteratively perform the knn-query on ‘that’ scanpath, starting with
// with the nearest neighbor until intersections are no longer found /*!
// (i.e., closest cluster from ‘that’ scanpath does not intersect, produce unique scanpath string
// so no other clusters can either since they are farther away */
for(int k=1; k<(int)that->cluster.size(); k++) { std::vector<int> Scanpath::getUniqueString()
range.clear(); {
that->knn_query(cluster[r],&range,&radius,k); std::vector<int> istring;
if(!range.empty() && cluster[r]->intersects(range.back())) {
// concatenate cluster letters into scanpath string
// if neither cluster has a label assign the next letter to both for(int r=0; r<(int)cluster.size(); r++)
if(!cluster[r]->letter && !range.back()->letter) { istring.push_back(cluster[r]->letter);
cluster[r]->letter = range.back()->letter = ++(*letter);
//std::cerr << "istring = ";
// if this cluster has no label take intersecting cluster’s //for(int i=0; i<(int)istring.size(); i++)
} else if(!cluster[r]->letter && range.back()->letter) // std::cerr << (char)(istring[i] % 94 + 33);
cluster[r]->letter = range.back()->letter; //std::cerr << std::endl;

// if intersecting cluster has no label, take this cluster’s // remove duplicates


else if(cluster[r]->letter && !range.back()->letter) for(int i=0; i<(int)istring.size(); i++)
range.back()->letter = cluster[r]->letter; for(int j=i+1; j<(int)istring.size();)
} if(istring[i] == istring[j])
else { istring.erase(istring.begin()+j);
// no intersection; if cluster has no letter, assign next available else j++;
if(!cluster[r]->letter) cluster[r]->letter = ++(*letter);
break; //std::cerr << "ustring = ";
} //for(int i=0; i<(int)istring.size(); i++)
} // std::cerr << (char)(istring[i] % 94 + 33);
} //std::cerr << std::endl;
}
return istring;
/*! }
produce scanpath string
*/ /*!
std::vector<int> Scanpath::getString() produce unique scanpath string
{ */
std::vector<int> istring; string Scanpath::printUniqueString() const
Sep 01 2009 10:39 scanpath.cpp Page 8/10
{ }
ostringstream os;
string us; /*!
Needleman-Wunsch
// concatenate cluster letters into scanpath string */
for(int r=0; r<(int)cluster.size(); r++) int Scanpath::s_difference(Scanpath& rhs)
// os << (char)(cluster[r]->letter % 94 + 33); {
// os << (char)(cluster[r]->letter % 26 + 65); return(n_w(getString(),rhs.getString()));
os << (char)((cluster[r]->letter - 1) % 26 + 65); }

// copy assembled string /*!


us = os.str(); Levenshtein
*/
// cull non-unique characters Comparison Scanpath::s_similarity(Scanpath& rhs)
for(int i=0; i<(int)us.length(); i++) {
for(int j=i+1; j<(int)us.length(); ) // calculate sequence similarity (S_s) as Levenshtein distance
if(us[i] == us[j]) us.erase(j,1); // (symmetric) normalized over length of the longer of the two strings
else j++;
int dist = lev(getString(),rhs.getString());
return us; int maxlen = max(getString().size(),rhs.getString().size());
} float s_s = (maxlen ? 1.0 - (float)dist/(float)maxlen : 0.0);
// Spearman’s rank-order coefficient (?) doesn’t seem right...
/*! // float d = (float)dist, n = (float)maxlen;
position similarity // float s_s = (maxlen ? 1.0 - 6.0*d*d/(n*(n*n - 1.0)) : 0.0);
*/
Comparison Scanpath::p_similarity(Scanpath& rhs) if(random || rhs.random)
{ return(Comparison(s_s,Ra)); // Random: (one scanpath is random)
int count=0; else if( (subject() == rhs.subject()) && (stimulus == rhs.stimulus) )
float p_s; return(Comparison(s_s,R)); // Repetitive: same subj, same scene
std::vector<int> s1(getUniqueString()); else if( (subject() != rhs.subject()) && (stimulus == rhs.stimulus) )
std::vector<int> s2(rhs.getUniqueString()); return(Comparison(s_s,L)); // Local: diff subj, same scene
else if( (subject() == rhs.subject()) && (stimulus != rhs.stimulus) )
// compute position similarity as the percentage of characters return(Comparison(s_s,I)); // Idiosyncratic: same subj, diff scene
// of the shorter string within the longer one else if( (subject() != rhs.subject()) && (stimulus != rhs.stimulus) )
if(s1.size() < s2.size()) { return(Comparison(s_s,G)); // Global: diff subj, diff scene
for(int i=0; i<(int)s1.size(); i++)
for(int j=0; j<(int)s2.size(); j++) // shouldn’t get here!
if(s1[i] == s2[j]) count++; return(Comparison(0.0,N));
p_s = (s1.size() ? (float)count/(float)s1.size() : 0.0); }
} else {
for(int i=0; i<(int)s2.size(); i++) /*!
for(int j=0; j<(int)s1.size(); j++) Levenshtein costs
if(s2[i] == s1[j]) count++; */
p_s = (s2.size() ? (float)count/(float)s1.size() : 0.0); int Scanpath::S_lev(int c1, int c2)
} {
if(c1 == c2) return 0; // identitiy (match)
if(random || rhs.random) else return 1; // substitution cost
return(Comparison(p_s,Ra)); // Random: (one scanpath is random) }
else if( (subject() == rhs.subject()) && (stimulus == rhs.stimulus) )
return(Comparison(p_s,R)); // Repetitive: same subj, same scene /*!
else if( (subject() != rhs.subject()) && (stimulus == rhs.stimulus) ) Needleman-Wunsch costs
return(Comparison(p_s,L)); // Local: diff subj, same scene */
else if( (subject() == rhs.subject()) && (stimulus != rhs.stimulus) ) int Scanpath::S_n_w(int c1, int c2)
return(Comparison(p_s,I)); // Idiosyncratic: same subj, diff scene {
else if( (subject() != rhs.subject()) && (stimulus != rhs.stimulus) ) if(c1 == c2) return 1; // identitiy (match)
return(Comparison(p_s,G)); // Global: diff subj, diff scene else return -1; // substitution cost
}
// shouldn’t get here!
return(Comparison(0.0,N)); /*!
Sep 01 2009 10:39 scanpath.cpp Page 9/10
Needleman-Wunsch // process each fixation
*/ for(int i=0; i<(int)fixation.size(); i++) {
int Scanpath::n_w(const std::vector<int>& s1, const std::vector<int>& s2)
{ // obtain saccadic amplitude of incoming and outgoing saccades
int d=-2; // indel (gap) cost // this is calculated in normalized pixel coordinates
int c[3]; i-1 >= 0 ?
matrix<int> F(s1.size(),s2.size()); // score i_sacc = fixation[i]->distance(fixation[i-1]) :
i_sacc = fixation[i]->distance(fixation[i ]);
for(int i=0; i<(int)s1.size(); i++) F[i][0] = i*d; i+1 < (int)fixation.size() ?
for(int j=0; j<(int)s2.size(); j++) F[0][j] = j*d; o_sacc = fixation[i]->distance(fixation[i+1]) :
o_sacc = fixation[i]->distance(fixation[i ]);
for(int i=1; i<(int)s1.size(); i++) {
for(int j=1; j<(int)s2.size(); j++) { // calculate saccaddic amplitude as average of incoming and outgoing
c[0] = F[i-1][j-1] + S_n_w(s1[i], s2[j]); // substitution sacc = (visualAngle(i_sacc) + visualAngle(o_sacc))/2.0;
c[1] = F[i-1][j ] + d; // deletion fixation[i]->setAmplitude(sacc);
c[2] = F[i ][j-1] + d; // insertion
F[i][j] = max(c[0],c[1],c[2]); // obtain this fixation’s duration (assume in ms)
} duration = fixation[i]->getDuration();
}
// declare fixation as either focal or ambient
return(F[F.rows()-1][F.cols()-1]); if( (duration > dur_threshold) && (sacc < amp_threshold) )
} fixation[i]->setFixation(FOCAL);
else
/*! fixation[i]->setFixation(AMBIENT);
Levenshtein }
*/ }
int Scanpath::lev(const std::vector<int>& s1, const std::vector<int>& s2)
{ /*!
int d=1; // gap cost routine to return duration of this scanpath (in ms)
int c[3]; */
matrix<int> F(s1.size(),s2.size()); // score double Scanpath::duration(void)
{
if(!s1.size() || !s2.size()) return 0; if(!point.empty())
return(point[point.size()-1]->gettimestamp() -
for(int i=0; i<(int)s1.size(); i++) F[i][0] = i; point[0]->gettimestamp());
for(int j=0; j<(int)s2.size(); j++) F[0][j] = j; else if(!fixation.empty())
return(fixation[fixation.size()-1]->gettimestamp() -
for(int i=1; i<(int)s1.size(); i++) { fixation[0]->gettimestamp());
for(int j=1; j<(int)s2.size(); j++) { else
c[0] = F[i-1][j-1] + S_lev(s1[i], s2[j]); // substitution return(0.0); // error: whoever called this ftn shouldn’t have
c[1] = F[i-1][j ] + d; // deletion }
c[2] = F[i ][j-1] + d; // insertion
F[i][j] = min(c[0],c[1],c[2]); /*!
} support function to convert pixel distance to vsiual angle based on
} viewing dist
*/
return(F[F.rows()-1][F.cols()-1]); double Scanpath::visualAngle(double pixels)
} {
double w=1280.0,h=1024.0; // assumed dim, header will override
/*! double diagonal; // display diagonal (pixels)
routine to classify fixations (from FXD data) as ambient or focal double screensize=17.0; // 17" display
*/ double dpi; // dots per inch
void Scanpath::classifyFixations(void) double r,D=19.68,vangle; // assume 50 cm viewing dist (19.68")
{
double i_sacc, o_sacc, sacc; // get Tobii display resolution
double duration; if(header) {
double amp_threshold = 5.0; // sacc. amp. (degrees visual angle) w = (double)header->width();
double dur_threshold = 250.0; // fixation duration (ms) h = (double)header->height();
}
Sep 01 2009 10:39 scanpath.cpp Page 10/10
ofs << "#" << std::endl;
// calculate dpi ofs << "set title \"Focal/Ambient Fixation Classification\"" << std::endl;
diagonal = sqrt((w*w)+(h*h)); ofs << "set xlabel \"Fixation duration (ms)\"" << std::endl;
dpi = diagonal/screensize; ofs << "set ylabel \"Saccade amplitude (degrees visual angle)\"" << std::endl;
ofs << "set output ’" << os_eps.str().c_str() << "’" << std::endl;
// assume distance coming in is normalized, scale to diagonal image res ofs << "set multiplot" << std::endl;
pixels = pixels * diagonal; ofs << "set parametric" << std::endl;
ofs << "const = 250" << std::endl;
// convert pixel distance to inches ofs << "#set trange [1:33]" << std::endl;
r = pixels/dpi; ofs << "set trange [0:35]" << std::endl;
ofs << "set xrange [100:700]" << std::endl;
// convert to visual angle ofs << "set yrange [0:35]" << std::endl;
vangle = 2.0*atan(r/(2.0*D)); // atan returns in range [-pi/2,pi/2] ofs << "#set key top right" << std::endl;
vangle *= (180.0/M_PI); // convert to degrees ofs << "set key 685,34 " << std::endl;
ofs << "plot const,t title \"Duration threshold\"" << std::endl;
return vangle; ofs << "unset parametric" << std::endl;
} ofs << "set key 685,31.5" << std::endl;
ofs << "amp(x) = 5" << std::endl;
/*! ofs << "plot \\" << std::endl;
dump out gnuplot file for plotting fixation types ofs << " amp(x) title \"Amplitude threshold\" ";
*/ ofs << "with lines lt 1 lw 2 lc rgb \"#000000\", \\" << std::endl;
void Scanpath::afplot(void) ofs << " ’" << os_dat.str().c_str() << "’ using 1:2 title \"Fixations\" \\";
{ ofs << std::endl;
std::ostringstream os_plt; // str for composition ofs << " with linespoints pt 4 ps 2 lt 1 lw 3 lc rgb \"#330066\"";
std::ostringstream os_dat; // str for composition ofs << std::endl;
std::ostringstream os_eps; // str for composition ofs << "unset multiplot" << std::endl;
std::ofstream ofs; ofs.close();
vector<Fixation* > sorted; // sorted fixation (temporary)
Fixation* fixp; // write out data file
ofs.open(os_dat.str().c_str());
// sort fixation vector (create temporary copy first) for(int i=0; i<(int)sorted.size(); i++) {
for(int i=0;i<(int)fixation.size();i++) { //std::cerr << "writing (";
fixp = new Fixation(fixation[i]); //std::cerr << sorted[i]->getDuration();
sorted.push_back(fixp); //std::cerr << ",";
} //std::cerr << sorted[i]->getAmplitude();
sort(sorted.begin(),sorted.end(),FixationCompare()); //std::cerr << ")" << std::endl;
ofs << sorted[i]->getDuration() << " " << sorted[i]->getAmplitude();
// set up file names ofs << std::endl;
os_plt << header->recording << "AFP.plt"; }
os_eps << header->recording << "AFP.eps"; ofs.close();
os_dat << header->recording << "AFP.dat";
// proper way to delete off iterated list (STL book, p.205)?
// write out gnuplot commands file for(vector<Fixation* >::iterator it=sorted.begin(); it!=sorted.end();)
ofs.open(os_plt.str().c_str()); sorted.erase(it++);
ofs << "set fontpath "; }
ofs << "\"/sw/share/texmf-dist/fonts/type1!\" ";
ofs << "\"/usr/share/texmf/fonts/type1!\" "; /*!
ofs << "\"/usr/local/teTeX/share/texmf-dist/fonts/type1!\" "; print out AOIL file (testing parsing)
ofs << std::endl; */
ofs << "set term postscript eps enhanced \"NimbusSanL-Regu\" 22 "; void Scanpath::printAoiL(void)
ofs << "fontfile \"uhvr8a.pfb\" "; {
ofs << "fontfile \"usyr.pfb\" "; int i=1;
ofs << "fontfile \"utmri8a.pfb\" "; vector<pair<string,string> >::iterator pos;
ofs << std::endl;
ofs << "reset" << std::endl; std::cerr << "Stimuli: " << stimulus << " - AOI List" << "\n\n";
ofs << "#" << std::endl; std::cerr << "AOI ID AOI Name Image/URL" << "\n\n";
ofs << "# clemson purple: R:51 G:00 B:102 #330066" << std::endl; for(pos=aoil.begin(); pos != aoil.end(); pos++)
ofs << "# clemson orange: R:255 G:99 B:00 #FF6300" << std::endl; std::cerr << i++ << " " << pos->first << "\t" << pos->second << "\n";
ofs << "# clemson blue: R:03 G:76 B:132 #034c84" << std::endl; }
Sep 01 2009 10:39 ymatrix.cpp Page 1/1
#include <iostream>
#include <iomanip> /////////////////////////// public member functions ////////////////////////////
#include <fstream> void YMatrix::zero(void)
#include <sstream> {
#include <vector> for(int i=0; i<rows(); i++)
#include <string> for(int j=0; j<cols(); j++)
#include <list> (*this)[i][j] = Comparison(0.0,N);
#include <algorithm> }
#include <utility>
#include <cmath>

#ifdef ANM_OSX
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <OpenGL/glext.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
//#include <GL/glext.h>
#endif

using namespace std;

#include "ymatrix.h"

/////////////////////////// friends ////////////////////////////////////////////


ostream& operator<<(ostream& s,const Comparison& rhs)
{
s.setf(ios::fixed,ios::floatfield);
s.precision(2);

switch(rhs.cmp.second) {
case N: s << " ("; break;
case Ra: s << "Ra("; break;
case R: s << " R("; break;
case L: s << " L("; break;
case I: s << " I("; break;
case G: s << " G("; break;
}

if(rhs.cmp.second == N)
s << setw(4) << " -- ) ";
else
s << setw(4) << rhs.cmp.first << ") ";

return s;
}

std::ostream& operator<<(std::ostream& s,const YMatrix& rhs)


{
// print lower diagonal (matrix is symmetrical, only need half of it)
for(int i=0; i<rhs.rows(); i++) {
for(int j=0; j<rhs.cols(); j++) {
if(i>=j) s << rhs[i][j];
}
if(i>=0) s << std::endl;
}
return s;
}

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy