TOUCHPixx Demo 1 – Creating a touchscreen whac-a-mole with the TOUCHPixx¶
In this demo, we simulate a whack-a-mole game to demonstrate how to use the TOUCHPixx data.
To use the TOUCHPixx, a calibration must be done to convert from TOUCHPixx coordinates to screen coordinates. Since we are working on a simple 2-dimension space, two points (two corners) is enough to offer a linear mapping to the entire touch screen.
To start using the TOUCHPixx, we must set the digital inputs properly. This is done with Datapixx('EnableTouchpixx');
.
As the RESPONSEPixx, we must stabilize inputs on the TOUCHPixx Datapixx('SetTouchpixxStabilizeDuration', 0.01)
, where 0.01 means a press
must last 10 ms before it is considered an input.
When there is no press, Datapixx('GetTouchpixxCoordinates');
returns zero for both x and y.
This function returns an array which includes the x coordinate in the first position and y coordinate in the second.
These are raw coordinates and must be calibrated. A linear mapping must be created with the two
known coordinates by simply creating an equation . Since we
took two points, we can find the unknown .
To log all touches, we start a logger Datapixx('SetTouchpixxLog');
and we set the mode
to be continuous Datapixx('EnableTouchpixxLogContinuousMode');
so that we can sweep and
still have a recorded input.
To get the coordinates, we first ask for a status to know if any input occurred: status = Datapixx('GetTouchpixxStatus');
and
check the newLogFrames
arguments of status
. Once we know there are new frames, we can read the presses and
timetags: [touches timetags] = Datapixx('ReadTouchpixxLog', status.newLogFrames);
.
Remember that these are raw coordinates and must be converted.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | function TouchpixxWhacAMoleDemo()
% TouchpixxWhacAMoleDemo()
%
% Whack-A-Mole game using TOUCHPixx touch panel.
%
% History:
%
% Nov 23, 2012 paa Written
% Nov 3, 2014 dml Revised
AssertOpenGL;
try
% We are assuming that the DATAPixx is connected to the highest number screen.
% If it isn't, then assign screenNumber explicitly here.
screenNumber=max(Screen('Screens'));
% We use the imaging pipeline to open a window so that we can get microsecond accurate stimulus onset timetags
PsychImaging('PrepareConfiguration');
PsychImaging('AddTask', 'General', 'UseDataPixx');
[w, wRect] = PsychImaging('OpenWindow', screenNumber, 0);
winWidth = wRect(3) - wRect(1);
winHeight = wRect(4) - wRect(2);
% After OpenWindow so it's under the text generated by Screen
fprintf('\nTOUCHPixx Whack-A-Mole Demo\n');
% Configure DATAPixx/TOUCHPixx
Datapixx('SetVideoMode', 0); % Normal passthrough
Datapixx('EnableTouchpixx'); % Turn on TOUCHPixx hardware driver
Datapixx('SetTouchpixxStabilizeDuration', 0.01); % Stabilize inputs for calibration
Datapixx('RegWrRd');
% Put up first touch calibration target near top-left corner, and acquire TOUCHPixx coordinates
calDispX1 = 100;
calDispY1 = 100;
backCol = [128 128 128];
Screen('FillRect', w, backCol, wRect);
calCol = [255 255 255];
Screen('FillRect', w, calCol, [calDispX1-50 calDispY1-50 calDispX1+50 calDispY1+50]);
textCol = [0 0 0];
Screen('TextFont',w, 'Courier New');
Screen('TextSize',w, floor(50 * winWidth/1920));
DrawFormattedText(w, 'Touch center of first calibration square', 'center', 'center', textCol);
Screen('Flip', w);
touchPt = [0 0]; % Wait for press
while touchPt == [0 0]
Datapixx('RegWrRd');
touchPt = Datapixx('GetTouchpixxCoordinates');
end;
calTouchX1 = touchPt(1);
calTouchY1 = touchPt(2);
Screen('FillRect', w, backCol, wRect); % Erase calibration square
Screen('Flip', w);
isPressed = 1; % Wait until panel release
while isPressed
Datapixx('RegWrRd');
status = Datapixx('GetTouchpixxStatus');
isPressed = status.isPressed;
end;
% Do same for a second calibration target near bottom-right corner of display
calDispX2 = winWidth - 100;
calDispY2 = winHeight - 100;
Screen('FillRect', w, backCol, wRect);
Screen('FillRect', w, calCol, [calDispX2-50 calDispY2-50 calDispX2+50 calDispY2+50]);
DrawFormattedText(w, 'Touch center of second calibration square', 'center', 'center', textCol);
Screen('Flip', w);
touchPt = [0 0]; % Wait for press
while touchPt == [0 0]
Datapixx('RegWrRd');
touchPt = Datapixx('GetTouchpixxCoordinates');
end;
calTouchX2 = touchPt(1);
calTouchY2 = touchPt(2);
Screen('FillRect', w, backCol, wRect); % Erase calibration square
Screen('Flip', w);
isPressed = 1; % Wait until panel release
while isPressed
Datapixx('RegWrRd');
status = Datapixx('GetTouchpixxStatus');
isPressed = status.isPressed;
end;
% Calculate linear mapping between touch coordinates and display coordinates
mx = (calDispX2 - calDispX1) / (calTouchX2 - calTouchX1);
my = (calDispY2 - calDispY1) / (calTouchY2 - calTouchY1);
bx = (calTouchX1 * calDispX2 - calTouchX2 * calDispX1) / (calTouchX1 - calTouchX2);
by = (calTouchY1 * calDispY2 - calTouchY2 * calDispY1) / (calTouchY1 - calTouchY2);
% Whacking instructions
Screen('FillRect', w, backCol, wRect);
DrawFormattedText(w, 'Whack moles when they appear\nTouch screen to start', 'center', 'center', textCol);
Screen('Flip', w);
isPressed = 0; % Wait until panel release
while ~isPressed
Datapixx('RegWrRd');
status = Datapixx('GetTouchpixxStatus');
isPressed = status.isPressed;
end;
while isPressed % Wait until panel release
Datapixx('RegWrRd');
status = Datapixx('GetTouchpixxStatus');
isPressed = status.isPressed;
end;
% Loop for each mole to whack
for i = 1:4
% Wait for a random 1-2 second mole target onset
status = Datapixx('GetVideoStatus');
refreshRate = status.verticalFrequency;
onsetDelay = floor((1 + rand(1)) * refreshRate);
Screen('FillRect', w, backCol, wRect);
for onsetFrame = 1:onsetDelay;
Screen('Flip', w);
end
% Draw mole
Screen('FillRect', w, backCol, wRect);
moleX = floor(wRect(1) + winWidth/8 + winWidth * 0.75 * rand(1));
moleY = floor(wRect(2) + winHeight/8 + winHeight * 0.75 * rand(1));
moleSize = 150;
moleCol = [100 100 0];
Screen('FillOval', w, moleCol, [moleX-moleSize/2 moleY-moleSize/2 moleX+moleSize/2 moleY+moleSize/2]);
Screen('FillOval', w, [0 0 0], [moleX-moleSize/4 moleY-moleSize/4 moleX-moleSize/8 moleY-moleSize/8]);
Screen('FillOval', w, [0 0 0], [moleX+moleSize/8 moleY-moleSize/4 moleX+moleSize/4 moleY-moleSize/8]);
Screen('FrameArc', w, [0 0 0], [moleX-moleSize/3 moleY-moleSize/3 moleX+moleSize/3 moleY+moleSize/3], 135, 90, 20);
PsychDataPixx('LogOnsetTimestamps', 1); % Tells imaging pipeline to capture DATAPixx microsecond-accurate hardware stimulus onset timestamp
Screen('Flip', w);
moleTimetag = PsychDataPixx('GetLastOnsetTimestamp');
% Start TOUCHPixx event logging
Datapixx('SetTouchpixxLog'); % Configure TOUCHPixx logging with default buffer
Datapixx('EnableTouchpixxLogContinuousMode'); % Continuous logging during a touch, so we recognize a sweep-a-mole
Datapixx('StartTouchpixxLog');
Datapixx('RegWrRd');
% Wait around until mole gets whack'd
whacked = 0;
while ~whacked
% Escape if key pressed
if KbCheck
break;
end
% How much TOUCHPixx data is available to read?
Datapixx('RegWrRd'); % Update registers for GetTouchpixxStatus
status = Datapixx('GetTouchpixxStatus');
if status.newLogFrames % We have new TOUCHPixx logged data to read?
[touches timetags] = Datapixx('ReadTouchpixxLog', status.newLogFrames);
for iTouch = 1:status.newLogFrames % Examine each logged TOUCHPixx datum
touchX = touches(1,iTouch);
touchY = touches(2,iTouch);
if (touchX ~= 0 & touchY ~= 0) % Confirm datum is not a panel release
% Convert touch panel coordinates to pixel coordinates
whackX = mx * touchX + bx;
whackY = my * touchY + by;
% Detect a winning whac
if (abs(whackX - moleX) < moleSize/2 & abs(whackY - moleY) < moleSize/2)
whacked = 1;
responseTime = timetags(iTouch) - moleTimetag;
disp(sprintf('Mole at (%d,%d) whacked in %d milliseconds', moleX, moleY, floor(responseTime*1000)));
break;
end
end
end
end
end
Datapixx('StopTouchpixxLog');
Datapixx('RegWrRd');
end
Datapixx('DisableTouchpixx'); % Turn off TOUCHPixx hardware driver
Datapixx('RegWrRd');
Screen('CloseAll');
ShowCursor;
return;
catch
Screen('CloseAll');
ShowCursor;
psychrethrow(psychlasterror);
end
|