# -*- coding: utf-8 -*-
#
# This file is part of PyGaze - the open-source toolbox for eye tracking
#
# PyGazeAnalyser is a Python module for easily analysing eye-tracking data
# Copyright (C) 2014 Edwin S. Dalmaijer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see
# Gaze Plotter
#
# Produces different kinds of plots that are generally used in eye movement
# research, e.g. heatmaps, scanpaths, and fixation locations as overlays of
# images.
#
# version 2 (02 Jul 2014)
__author__ = "Edwin Dalmaijer"
# native
import os
# external
import numpy
import matplotlib
from matplotlib import pyplot, image
# # # # #
# LOOK
# COLOURS
# all colours are from the Tango colourmap, see:
# http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette
COLS = { "butter": [ '#fce94f',
'#edd400',
'#c4a000'],
"orange": [ '#fcaf3e',
'#f57900',
'#ce5c00'],
"chocolate": [ '#e9b96e',
'#c17d11',
'#8f5902'],
"chameleon": [ '#8ae234',
'#73d216',
'#4e9a06'],
"skyblue": [ '#729fcf',
'#3465a4',
'#204a87'],
"plum": [ '#ad7fa8',
'#75507b',
'#5c3566'],
"scarletred":[ '#ef2929',
'#cc0000',
'#a40000'],
"aluminium": [ '#eeeeec',
'#d3d7cf',
'#babdb6',
'#888a85',
'#555753',
'#2e3436'],
}
# FONT
FONT = { 'family': 'Ubuntu',
'size': 12}
matplotlib.rc('font', **FONT)
# # # # #
# FUNCTIONS
def draw_fixations(fixations, dispsize, imagefile=None, durationsize=True, durationcolour=True, alpha=0.5, savefilename=None):
"""Draws circles on the fixation locations, optionally on top of an image,
with optional weigthing of the duration for circle size and colour
arguments
fixations - a list of fixation ending events from a single trial,
as produced by edfreader.read_edf, e.g.
edfdata[trialnr]['events']['Efix']
dispsize - tuple or list indicating the size of the display,
e.g. (1024,768)
keyword arguments
imagefile - full path to an image file over which the heatmap
is to be laid, or None for no image; NOTE: the image
may be smaller than the display size, the function
assumes that the image was presented at the centre of
the display (default = None)
durationsize - Boolean indicating whether the fixation duration is
to be taken into account as a weight for the circle
size; longer duration = bigger (default = True)
durationcolour - Boolean indicating whether the fixation duration is
to be taken into account as a weight for the circle
colour; longer duration = hotter (default = True)
alpha - float between 0 and 1, indicating the transparancy of
the heatmap, where 0 is completely transparant and 1
is completely untransparant (default = 0.5)
savefilename - full path to the file in which the heatmap should be
saved, or None to not save the file (default = None)
returns
fig - a matplotlib.pyplot Figure instance, containing the
fixations
"""
# FIXATIONS
fix = parse_fixations(fixations)
# IMAGE
fig, ax = draw_display(dispsize, imagefile=imagefile)
# CIRCLES
# duration weigths
if durationsize:
siz = 1 * (fix['dur']/30.0)
else:
siz = 1 * numpy.median(fix['dur']/30.0)
if durationcolour:
col = fix['dur']
else:
col = COLS['chameleon'][2]
# draw circles
ax.scatter(fix['x'],fix['y'], s=siz, c=col, marker='o', cmap='jet', alpha=alpha, edgecolors='none')
# FINISH PLOT
# invert the y axis, as (0,0) is top left on a display
ax.invert_yaxis()
# save the figure if a file name was provided
if savefilename != None:
fig.savefig(savefilename)
return fig
def draw_heatmap(fixations, dispsize, imagefile=None, durationweight=True, alpha=0.5, savefilename=None):
"""Draws a heatmap of the provided fixations, optionally drawn over an
image, and optionally allocating more weight to fixations with a higher
duration.
arguments
fixations - a list of fixation ending events from a single trial,
as produced by edfreader.read_edf, e.g.
edfdata[trialnr]['events']['Efix']
dispsize - tuple or list indicating the size of the display,
e.g. (1024,768)
keyword arguments
imagefile - full path to an image file over which the heatmap
is to be laid, or None for no image; NOTE: the image
may be smaller than the display size, the function
assumes that the image was presented at the centre of
the display (default = None)
durationweight - Boolean indicating whether the fixation duration is
to be taken into account as a weight for the heatmap
intensity; longer duration = hotter (default = True)
alpha - float between 0 and 1, indicating the transparancy of
the heatmap, where 0 is completely transparant and 1
is completely untransparant (default = 0.5)
savefilename - full path to the file in which the heatmap should be
saved, or None to not save the file (default = None)
returns
fig - a matplotlib.pyplot Figure instance, containing the
heatmap
"""
# FIXATIONS
fix = parse_fixations(fixations)
# IMAGE
fig, ax = draw_display(dispsize, imagefile=imagefile)
# HEATMAP
# Gaussian
gwh = 200
gsdwh = gwh/6
gaus = gaussian(gwh,gsdwh)
# matrix of zeroes
strt = gwh/2
heatmapsize = dispsize[1] + 2*strt, dispsize[0] + 2*strt
heatmap = numpy.zeros(heatmapsize, dtype=float)
# create heatmap
for i in range(0,len(fix['dur'])):
# get x and y coordinates
#x and y - indexes of heatmap array. must be integers
x = strt + int(fix['x'][i]) - int(gwh/2)
y = strt + int(fix['y'][i]) - int(gwh/2)
# correct Gaussian size if either coordinate falls outside of
# display boundaries
if (not 0 < x < dispsize[0]) or (not 0 < y < dispsize[1]):
hadj=[0,gwh];vadj=[0,gwh]
if 0 > x:
hadj[0] = abs(x)
x = 0
elif dispsize[0] < x:
hadj[1] = gwh - int(x-dispsize[0])
if 0 > y:
vadj[0] = abs(y)
y = 0
elif dispsize[1] < y:
vadj[1] = gwh - int(y-dispsize[1])
# add adjusted Gaussian to the current heatmap
try:
heatmap[y:y+vadj[1],x:x+hadj[1]] += gaus[vadj[0]:vadj[1],hadj[0]:hadj[1]] * fix['dur'][i]
except:
# fixation was probably outside of display
pass
else:
# add Gaussian to the current heatmap
heatmap[y:y+gwh,x:x+gwh] += gaus * fix['dur'][i]
# resize heatmap
heatmap = heatmap[strt:dispsize[1]+strt,strt:dispsize[0]+strt]
# remove zeros
lowbound = numpy.mean(heatmap[heatmap>0])
heatmap[heatmap