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 Real_x = m_x*TOUCHPixx_x + b_x. Since we took two points, we can find the unknown m_x,~ b_x.

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