# -*- 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