Initiation Matlab 3 : introduction `a la Psychtoolbox
Transcription
Initiation Matlab 3 : introduction `a la Psychtoolbox
SCALab Initiation Matlab 3 Laurent Ott Initiation Matlab 3 : introduction à la Psychtoolbox La Psychtoolbox est un ensemble de fonctions et d’exécutables Matlab (fichier .mex) écrits en C/C++ dédiés à la réalisation d’expériences en psychologie. Cette boı̂te à outil n’est pas développée par The Mathworks (l’éditeur de Matlab) mais par des chercheurs en psychologie. Elle est disponible à l’adresse suivante http://psychtoolbox.org/PsychtoolboxDownload. Elle offre une interface de contrôle aisée de la couche graphique et permet la maı̂trise des aspects temporels quant à la mise à jour de l’affichage. De même, elle facilite la collecte précise des réponses du sujet par le biais de différents matériels (clavier, souris, eye tracker, ...). De nombreux scripts de démonstration sont fournis avec la toolbox. Ces scripts sont accessibles dans le répertoire PsychDemos situé dans le répertoire d’installation de la Psychtoolbox. Pour y accéder sous Matlab : >> cd([PsychtoolboxRoot,’/PsychDemos’]) Ces scripts de démonstration sont une bonne base de départ pour la mise en place d’expériences personnalisées. Ils montrent la mise en oeuvre des fonctionnalités de la Psychtoolbox. La psychtoolbox n’intègre pas dans l’environnement Matlab une documentation aussi facilement accessible et fournie que pour les fonctions Matlab de base. Si une connexion internet est disponible à l’utilisateur, il est conseillé d’utiliser l’aide en ligne à l’adresse suivante : → http ://docs.psychtoolbox.org/Psychtoolbox Cependant, si aucune connexion internet n’est disponible, il est possible d’obtenir de l’aide pour les fonctions de la Psychtoolbox en ligne de commande (par ex : >>help Screen pour une description générale de la fonction Screen, >>Screen(’Flip?’) pour une aide détaillée de l’option ’Flip’). 1 La fonction Screen La fonction Screen est à la base de la Psychtoolbox. C’est la fonction qui permet la gestion de l’affichage des stimuli. Dans cette introduction, nous passerons en revue quelques options de cette fonction. la syntaxe de cette fonction est toujours de la forme : Screen(’fonction’,pointeur_fenetre,options obligatoires et facultatives) 1.1 Ouverture / Fermeture d’un ’window’ Exécuter le script first screen.m décrit ci-dessous. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% first_screen.m clear all close all %initialise some variables that will be used to call ’OpenWindow’ whichScreen = 0; %allow to choose the display if there’s more than one wPtr = Screen( ’OpenWindow’, whichScreen); %Open a window and %returns a pointer to the window white = 255; %pixel value for white black = 0; %pixel value for black 1 SCALab Initiation Matlab 3 Laurent Ott gray = (white+black) / 2; %pixel value for middle gray Screen(’FillRect’, wPtr , gray); %fill the buffer image in the %graphic card memory with grey Screen(’Flip’, wPtr ); %displays the content of the buffer %in the window WaitSecs(1); %pause for 1 sec Screen(’FillRect’, wPtr , white); Screen(’Flip’, wPtr ); WaitSecs(1); Screen(’FillRect’, wPtr , black); Screen(’Flip’, wPtr ); WaitSecs(1); Screen(’CloseAll’); %closes the window %%%%%%%%%%%%%%%%%%%%%%%%%%%% On fait appel en début de script à la fonction ’OpenWindow’, cette fonction permet à la Psychtoolbox de prendre la main sur la gestion d’un écran. Pour que le système d’exploitation reprenne la main sur l’affichage, il faudra faire appel à la fonction Screen(’CloseAll’), comme dans l’exemple en fin de script. 1.2 Reprendre la main en cas de blocage Il peut arriver lors de la phase d’écriture/test d’un programme de perdre la main alors que la Psychtoolbox est en cours d’utilisation et recouvre la barre des tâches et la fenêtre de commandes de Matlab. Ceci peut se produire si votre programme s’arrête (certainement à cause d’une erreur dans votre programme) avant de fermer le window avec Screen(’CloseAll’). Le clavier semblera ne plus fonctionner car il sera vu uniquement par la fonction Screen et non plus par Matlab. Pour reprendre la main, appuyez simultanément sur Ctrl+C. Ceci aura pour effet de stopper votre programme si ce n’est pas déjà le cas. Utiliser ensuite la combinaison Alt+TAB pour ramener la fenêtre de commande Matlab au premier plan. Ceci devrait permettre l’utilisation du clavier. Tapez ensuite sca puis appuyer sur la touche ENTER. sca est un raccourci vers la fonction Screen(’CloseAll’). La fenêtre de la Psychtoolbox devrait alors se fermer. Faites le test avec le script suivant. %%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% test_erreur.m clear all close all whichScreen = 0; wPtr = Screen( ’OpenWindow’, whichScreen ); a = b %cette instruction va générer une erreur %car b n’a pas été défini %L’instruction suivante ne sera jamais exécutée Screen(’CloseAll’); %fermeture du window %%%%%%%%%%%%%%%%%%%%%%%%%%%% Pour se prémunir de ces blocages pendant la phase d’écriture et de test d’un programme avec la psychtoolbox. Il est possible d’utiliser les instructions try, catch de la manière suivante. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% test_try_catch.m clear all close all try whichScreen = 0; 2 SCALab Initiation Matlab 3 Laurent Ott wPtr = Screen( ’OpenWindow’, whichScreen ); a = b %this statement yields an error %indeed, b is undefined Screen(’CloseAll’); %close the window catch ERR %if an error occurs between the try / catch statements %the error informations are stored in the structure ERR %(ERR is an arbritary variable name), and the following code %is then executed Screen(’CloseAll’); rethrow( ERR ); %now that the window is closed, %we propagate the error so that %the message error is nicely displayed %at the prompt end %%%%%%%%%%%%%%%%%%%%%%%%%%%% Si une erreur se produit entre les instructions try et catch, plutôt que d’arrêter immédiatement l’exécution du script, Matlab va exécuter les instructions situées entre le catch et le end suivant. Nous pouvons donc nous assurer de l’exécution du Screen(’CloseAll’) pour récupérer la main sur le clavier et la command window. L’instruction rethrow( ERR ) va propager l’erreur ayant eu lieu entre le try et catch, nous retrouverons donc la trace de cette éventuelle erreur sur la command window, ce qui facilitera son débuggage. 1.3 Stimuli à partir de primitives visuelles simples Screen(’DrawLine’, windowPtr [,color], fromH, fromV, toH, toV [,penWidth]); Screen(’DrawArc’,windowPtr,[color],[rect],startAngle,arcAngle) Screen(’FrameArc’,windowPtr,[color],[rect],startAngle,arcAngle[,penWidth] [,penHeight] [,penMode]) Screen(’FillArc’,windowPtr,[color],[rect],startAngle,arcAngle) Screen(’FillRect’, windowPtr [,color] [,rect] ); Screen(’FrameRect’, windowPtr [,color] [,rect] [,penWidth]); Screen(’FillOval’, windowPtr [,color] [,rect] [,perfectUpToMaxDiameter]); Screen(’FrameOval’, windowPtr [,color] [,rect] [,penWidth] [,penHeight] [,penMode]); Screen(’FramePoly’, windowPtr [,color], pointList [,penWidth]); Screen(’FillPoly’, windowPtr [,color], pointList [, isConvex]); Voici la liste des options à la fonction Screen pour l’affichage de primitves simples. Pour rappel, vous pouvez accéder à l’aide de chacune de ces options depuis la ligne de commande (par ex : Screen(’DrawLine ?’). L’unité de base de l’écran est le pixel. Un point sur l’écran est définie par ses coordonnées (x,y) en pixels sur un repère à 2 dimensions. L’origine (0,0) de ce repère se trouve dans le coin en haut à gauche de l’écran, l’axe horizontal X est dirigé vers la droite et l’axe vertical Y vers le bas (voir Fig. 1-(a) . (a) (b) (c) (d) Figure 1 – (a) système de coordonnée de écran. (b) un point (2 coordonnées - horizontal, vertical). (c) une ligne (2 points : début, fin). (d) un rectangle (2 points : coin supérieur gauche, coin inférieur droit) Le paramètre rect, utile pour la majorité des primitives visuelles, permet de définir un rectangle sur l’écran dans lequel on veut placer notre primitive. C’est un vecteur ligne de 4 éléments. Les 1er et 2ème éléments 3 SCALab Initiation Matlab 3 Laurent Ott sont les coordonnées du coin supérieur gauche. Les 3ème et 4ème les coordonnées du coin inférieur droit. Par exemple, le rectangle de la Figure 1-(d) serait rect_1d = [2,3,7,7]. Le paramètre color permet de spécifier la couleur souhaitée de la primitive visuelle. On la définie par un vecteur ligne des 3 composantes RGB (pour Red Green Blue) avec R,G,B codés entre 0 et 255 (par ex bleu = [0,0,255]) . On trouvera facilement sur internet les correspondances entre couleur et composantes RGB (recherche google ’RGB color picker’). Il existe un cas spécial pour définir les couleurs en niveau de gris. Dans ce cas, il suffit de définir la couleur comme un entier compris entre 0 (du noir) et 255 (au blanc). Préparation d’un stimulus complexe et raffraichissement de l’écran : L’appel au fonction ci-dessous pour dessiner des primitves ne met pas à jour l’affichage présent à l’écran. Ces fonction vont modifier une image présente dans la mémoire de la carte graphique. On peut ainsi combiner plusieurs primitves avant d’en afficher le résultat à l’écran. Pour mettre à jour l’écran avec l’image présente dans la mémoire de la carte graphique, on fait appel à la fonction Screen(’Flip’, wPtr);. Exercice : Créer un script ouvrant un window pour y afficher sur fond blanc les primitives suivantes : — un rectangle plein de couleur jaune avec son coin supérieur gauche en (30,50) et son coin inférieur droit en (130,150) — un cercle de couleur verte placé au milieu de l’écran de diamètre 100 pixels. La taille de l’écran en pixels peut être obtenue à l’aide de la 2ème valeur de retour de la fonction Screen(’OpenWindow’,...) (pour voir l’aide >>Screen(’OpenWindow?’)) — une ligne reliant le centre du rectangle au centre du cercle Laisser s’écouler 2 secondes avant de fermer le window. Puis réafficher les même primitives mais sur fond noir cette fois-ci. 1.4 Affichage d’images (MakeTexture et DrawTexture) La fonction Screen(’MakeTexture’’) permet de convertir une matrice d’image 2D ou 3D en une texture OpenGL et retourne un pointeur vers cette texture. Ce pointeur sera utilisé pour mettre à jour le buffer avant affichage à l’aide de la fonction ’DrawTexture’. texturePointer=Screen(’MakeTexture’, windowPointer, imageMatrix, ...) Screen(’DrawTexture’, windowPointer, texturePointer [,sourceRect] [,destinationRect],...) sourceRect est un paramètre optionel, un rect définissant une partie de l’image à présenter, si il n’est pas spécifié ou laissé vide ( [] ), par défaut toute l’image est utilisée. destinationRect est un rect pouvant être utilisé pour spécifier une partie de l’écran où l’on souhaite présenter l’image (par défaut l’image sera présenté au centre de l’écran sans mise à l’échelle), si le ratio entre la source et la destination n’est pas le même, l’image sera distordue pour épouser le rect de destination. La fonction imread issue de la bibliothèque de fonctions de Matlab permet de créer une matrice d’image à partir d’un fichier image (ex : BMP, JPEG). Le script d’exemple ci-dessous montre l’utilisation de 4 textures pour afficher des images. Nous utilisons ici des images se trouvant incluses dans la distribution de la Psychtoolbox dans le sous-répertoire PsychHardware/EyelinkToo ResearchDemo/AntiSaccade. %%%%%%%%%%%%%%%%%%%%%%%%%%% % ex_texture.m clear all close all basedir = fullfile( PsychtoolboxRoot , ’PsychHardware’ , ’EyelinkToolbox’ , ’EyelinkDemos’ , ’SR-ResearchDemo’ , ’AntiSaccade’ ); imMatrix_V1 imMatrix_V2 imMatrix_R1 imMatrix_R2 = = = = imread(fullfile( imread(fullfile( imread(fullfile( imread(fullfile( basedir basedir basedir basedir , , , , ’img51.jpg’ )); ’img52.jpg’ )); ’img101.jpg’ )); ’img102.jpg’ )); 4 SCALab Initiation Matlab 3 Laurent Ott %initialise some variables that will be used to call ’OpenWindow’ whichScreen = 0; %allow to choose the display if there’s more than one try wPtr = Screen( ’OpenWindow’, whichScreen); %Open a window and %returns a pointer to the window white = 255; %pixel value for white black = 0; %pixel value for black gray = (white+black) / 2; %pixel value for middle gray %create a openGL texture for each image imTexture_V1 = Screen(’MakeTexture’,wPtr,imMatrix_V1); imTexture_V2 = Screen(’MakeTexture’,wPtr,imMatrix_V2); imTexture_R1 = Screen(’MakeTexture’,wPtr,imMatrix_R1); imTexture_R2 = Screen(’MakeTexture’,wPtr,imMatrix_R2); Screen(’DrawTexture’, wPtr , imTexture_V1); %Fill the buffer with the first texture Screen(’Flip’, wPtr ); %update the display with the buffer content WaitSecs(1); Screen(’DrawTexture’, wPtr , imTexture_V2); Screen(’Flip’, wPtr ); WaitSecs(1); Screen(’DrawTexture’, wPtr , imTexture_R1); Screen(’Flip’, wPtr ); WaitSecs(1); Screen(’DrawTexture’, wPtr , imTexture_R2); Screen(’Flip’, wPtr ); WaitSecs(1); Screen(’CloseAll’); %close the window catch ERR Screen(’CloseAll’); rethrow(ERR); end %%%%%%%%%%%%%%%%%%%%%%%%%% Exercice : Compléter le script suivant dont le but est d’afficher 3 images (konijntjes1024x768.jpg, konijntjes1024x768blur.jpg, konijntjes1024x768gray.jpg) sur le même écran comme sur la figure Fig. 2. La taille de l’écran en pixels peut être obtenue à l’aide de la 2ème valeur de retour de la fonction Screen(’OpenWindow’,...) (pour voir l’aide >>Screen(’OpenWindow?’)). De même, la taille d’une image peut être obtenue en utilisant la fonction size() sur la matrice d’image. Par exemple, pour une matrice d’image IM, la taille horizontale est donnée par size(IM,2) et la taille verticale par size(IM,1). %%%%%%%%%%%%%%%%%%%%%%%%%%% % ex_texture_placement.m clear all close all basedir = fullfile( PsychtoolboxRoot , ’PsychDemos’ ); imMatrix_sharp = imread(fullfile( basedir , ’konijntjes1024x768.jpg’ )); imMatrix_blur = imread(fullfile( basedir , ’konijntjes1024x768blur.jpg’ )); imMatrix_gray = imread(fullfile( basedir , ’konijntjes1024x768gray.jpg’ )); %initialise some variables that will be used to call ’OpenWindow’ whichScreen = 0; %allow to choose the display if there’s more than one try [wPtr , wRect] = Screen( ’OpenWindow’, whichScreen ); %Open a window and %returns a pointer to the window white = 255; %pixel value for white black = 0; %pixel value for black gray = (white+black) / 2; %pixel value for middle gray 5 SCALab Initiation Matlab 3 Laurent Ott %create a openGL texture for each image imTexture_sharp = Screen(’MakeTexture’,wPtr,imMatrix_sharp); imTexture_blur = Screen(’MakeTexture’,wPtr,imMatrix_blur); imTexture_gray = Screen(’MakeTexture’,wPtr,imMatrix_gray); %%% ADD YOUR CODE HERE %%% %wait for a key stroke while ~KbCheck end Screen(’CloseAll’); catch ERR Screen(’CloseAll’); rethrow(ERR); end %%%%%%%%%%%%%%%%%%%%%%%%%% Figure 2 – Placement souhaité des 3 images. 1.5 Gestion précise de la mise à jour de l’affichage Comme vous avez pu le constater dans les exemples précédents, la mise à jour effective de l’affichage se fait au moyen de la fonction ’Flip’. Cette fonction peut être appelée avec différentes options et peut renvoyer des informations importantes quant à la gestion du temps. [VBLTimestamp StimulusOnsetTime] = Screen(’Flip’, windowPtr [, when] ) l’argument d’entrée optionnel ’when’ permet de spécifier le temps machine auquel on souhaite mettre à jour l’affichage. La fonction ’Flip’ bloquera l’exécution du programe jusqu’à la mise à jour de l’affichage. La valeur de retour ’StimulusOnsetTime’ permet d’obtenir une estimation précise du temps auquel a effectivement eu lieu l’affichage. Il faut cependant être conscient des limites de l’écran en terme de fréquence de raffraichissment. Un écran ayant une fréquence de raffraichissement de 100Hz permet d’afficher une image toutes les 10ms. Pour un écran à 60Hz, on tombe à une image toutes les 16,67ms. %%%%%%%%%%%%%%%%%%%%%%%%%%% % synchro_flip.m clear all close all duree_stim = 0.5; %variable to specify the stim presentation duration (here 500ms) basedir = fullfile( PsychtoolboxRoot , ’PsychHardware’ , ’EyelinkToolbox’ , ’EyelinkDemos’ , ’SR-ResearchDemo’ , ’AntiSaccade’ ); imMatrix_V1 imMatrix_V2 imMatrix_R1 imMatrix_R2 = = = = imread(fullfile( imread(fullfile( imread(fullfile( imread(fullfile( basedir basedir basedir basedir , , , , ’img51.jpg’ )); ’img52.jpg’ )); ’img101.jpg’ )); ’img102.jpg’ )); %initialise some variables that will be used to call ’OpenWindow’ 6 SCALab Initiation Matlab 3 Laurent Ott whichScreen = 0; %allow to choose the display if there’s more than one try wPtr = Screen( ’OpenWindow’, whichScreen); %Open a window and %returns a pointer to the window white = 255; %pixel value for white black = 0; %pixel value for black gray = (white+black) / 2; %pixel value for middle gray %create a openGL texture for each image imTexture_V1 = Screen(’MakeTexture’,wPtr,imMatrix_V1); imTexture_V2 = Screen(’MakeTexture’,wPtr,imMatrix_V2); imTexture_R1 = Screen(’MakeTexture’,wPtr,imMatrix_R1); imTexture_R2 = Screen(’MakeTexture’,wPtr,imMatrix_R2); Screen(’FillRect’, wPtr , gray); %Fill the buffer in grey [VBL , stimOnsetTime] = Screen(’Flip’, wPtr ); %immediately update the display and store the timestamp of %the effective update in stimOnsetTime Screen(’DrawTexture’, wPtr , imTexture_V1); %Fill the buffer with the first texture [VBL , stimOnsetTime] = Screen(’Flip’, wPtr, stimOnsetTime+duree_stim ); %update the display 500 ms after the last ’Flip’ Screen(’DrawTexture’, wPtr , imTexture_V2); [VBL , stimOnsetTime] = Screen(’Flip’, wPtr , stimOnsetTime+duree_stim ); Screen(’DrawTexture’, wPtr , imTexture_R1); [VBL , stimOnsetTime] = Screen(’Flip’, wPtr , stimOnsetTime+duree_stim ); Screen(’DrawTexture’, wPtr , imTexture_R2); Screen(’Flip’, wPtr , stimOnsetTime+duree_stim ); WaitSecs(0.5); Screen(’CloseAll’); %close the windows catch ERR Screen(’CloseAll’); rethrow(ERR); end %%%%%%%%%%%%%%%%%%%%%%%%%% 2 Interaction avec le sujet Dans la plupart des expériences de psychologie, les réponses du sujet sont obtenues par le biais du clavier ou de la souris. En plus de la touche ou de la position de la souris, le temps de réponse (temps écoulé entre l’apparition du stimulus et la réponse du sujet) est bien souvent une variable que les chercheurs en psychologie souhaitent mesurer de manière précise. Les fonctions d’accès au clavier et à la souris offertes par la Psychtoolbox permettent d’obtenir une précision temporelle de l’ordre de la milliseconde. 2.1 Evènement clavier La réponse d’un sujet au clavier est obtenue par le biais de la fonction KbCheck. Cette fonction n’est pas bloquante (elle ne bloque pas l’exécution du programme en attente d’un évènement au clavier). Elle renvoie ”instantanément” l’état des touches du clavier de la manière suivante : [keyIsDown, secs, keyCode] = KbCheck La variable de retour ’KeyIsDown’ est un booléen qui passe à 1 lorsque n’importe quelle touche est enfoncée. ’secs’ renvoie le temps machine de l’appui et ’keyCode’ est un tableau de 256 booléens. Chaque booléen réprésente l’état d’une touche du clavier. 7 SCALab Initiation Matlab 3 Laurent Ott Pour trouver quel booléen du tableau ’keyCode’ correspond à une touche, vous pouvez exécuter l’instruction suivante ds la Command window en appuyant sur la touche désirée pendant son exécution : >>for i = 1:10, WaitSecs(0.5); [keyIsDown, secs, keyCode] = KbCheck; find(keyCode), end Pour observer et attendre un évènement sur la souris ou le clavier, il faut mettre en place une boucle de scrutation. Par ex, pour attendre un appui quelconque au clavier : while ~KbCheck end Pour attendre un appui sur la touche Ctrl gauche : [keyIsDown, secs, keyCode] = KbCheck; while ~keyCode( 38 ) % le élément 38 de keyCode correspond à la touche Ctrl sur mon système mais peut varier % d’un système à l’autre [keyIsDown, secs, keyCode] = KbCheck; end 2.2 Evènement souris La position de la souris et l’état de ses boutons peuvent être obtenus à l’aide de la fonction : [x,y,buttons] = GetMouse Les valeurs de retour ’x’ et ’y’ sont la position du curseur en coordonnées ecran (pixels). ’buttons’ est un tableau de N booléens où N est le nombre de boutons de la souris. Chaque élément du tableau représente un bouton de la souris. Un booléen à (1) signifie que le bouton correspond est appuyé et vaut (0) sinon. Le script suivant donne un exemple d’utilisation des fonctions KbCheck et GetMouse. %%%%%%%%%%%%%%%%%%%%%%%%%%% % mesure_clavier_souris.m clear all close all try whichScreen = 0; wPtr = Screen( ’OpenWindow’, whichScreen ); white = 255; %pixel value for white black = 0; %pixel value for black gray = (white+black) / 2; %pixel value for middle gray nb_trial = 5; for i = 1:nb_trial Screen(’FillRect’, wPtr , gray); DrawFormattedText(wPtr, ... ’Measure of the time difference between \na key stroke and a mouse click\n\nStart by hitting a key’ ... ,’center’ , ’center’, black); Screen(’Flip’, wPtr ); %wait for a key stroke while ~KbCheck end tkeyboard = GetSecs; %we store the timestamp of the key stroke event Screen(’FillRect’, wPtr , white); DrawFormattedText(wPtr, ’Waiting for a mouse click’ ,’center’ , ’center’, black); Screen(’Flip’, wPtr ); %wait for a mouse click [x,y,buttons] = GetMouse; while ~sum(buttons) [x,y,buttons] = GetMouse; end 8 SCALab Initiation Matlab 3 Laurent Ott tmouse = GetSecs; %we store the timestamp of the mouse event Screen(’FillRect’, wPtr , black); DrawFormattedText(wPtr, ’Please relase the keyboard and the mouse’ ,’center’ , ’center’, white); Screen(’Flip’, wPtr ); %wait for the subject to release the mouse and the keyboard [x,y,buttons] = GetMouse; while KbCheck | sum(buttons) [x,y,buttons] = GetMouse; end deltaT(i) = tmouse-tkeyboard; Screen(’FillRect’, wPtr , gray); DrawFormattedText(wPtr, [’deltaT = ’,num2str(deltaT(i),’%.3f’), ’ s’] ,’center’ , ’center’, black); Screen(’Flip’, wPtr ); WaitSecs(0.5); end Screen(’CloseAll’); %close the window %graph the measure figure, plot(deltaT,’*’), xlim([0 length(deltaT)+1]), ylim([0 max(deltaT)]); xlabel(’Trial num’), ylabel(’deltaT (s)’); catch ERR Screen(’CloseAll’); rethrow(ERR); end %%%%%%%%%%%%%%%%%%%%%%%%%% Exercice : Programmer une tâche pour évaluer l’effet de l’illusion de Muller-Lyer L’illusion de Muller-Lyer est une illusion d’optique. Une de ces manifestations les plus connues est pour un segment de droite d’apparaı̂tre comme plus ou moins long selon qu’il ait attaché à ses extrémités des flèches pointant vers l’intérieur ou vers l’extérieur (voir Fig 3). On peut imaginer la tâche d’évaluation suivante : - Afficher un segment de droite horizontale de longueur fixe (par exemple 300 px) avec des flêches pointant vers l’extérieur placé au centre de l’écran. - Afficher un second segment de droite horizontale de longueur aléatoire (variant par exemple de 260 px à 340 px) avec des flêches pointant vers l’intérieur placé sous la première ligne. - En utilisant 2 touches du clavier de votre choix pour récupérer les réponses du sujet, demander au sujet de choisir si la ligne du dessous paraı̂t comme étant plus grande ou plus petite que la ligne du dessus. Réaliser le programme de cette tâche pour 50 essais en faisant variant la longueur de la ligne du bas de -40 à +40 pixels par rapport à la longueur de la ligne du dessus. Pour chaque essai, on souhaite garder un historique des réponses du sujet associés à la différence de taille des lignes. 3 Animations L’illusion du mouvement est obtenue en changeant rapidement la position ou la forme de ce qui est affiché à l’écran. Typiquement, la fréquence de raffraı̂chissement d’un écran est de 60 Hz (i.e. 60 fois par seconde, certains écrans peuvent avoir des fréquences supérieures), nous pouvons donc afficher une nouvelle image ou changer la position d’un stimulus toutes les 16.67 ms Plus la distance entre les positions d’un stimulus dans 2 images successives est grande, plus le mouvement apparent sera important. %%%%%%%%%%%%%%%%%%%%%%%%%%% % animation1.m 9 SCALab Initiation Matlab 3 Laurent Ott Figure 3 – Illustration of the Muller-Lyer illusion. The line in the bottom arrow seems to be longer than the one from the top arrow. clear all close all try whichScreen = 0; [wPtr , wRect] = Screen( ’OpenWindow’, whichScreen ); white = 255; %pixel value for white black = 0; %pixel value for black gray = (white+black) / 2; %pixel value for middle gray red = [255,0,0]; Vx = 5; % velocity on X axis in pixel / frame Px = wRect(3)/2; %initial position of the circle on X axis Py = wRect(4)/2; %initial position of the circle on Y axis R = 50; %radius of the circle while ~KbCheck % compute new position Px=Px+Vx; % if the circle goes outside the right side of the screen % we change its position in order to make it appear on % the left side if Px-R > wRect(3) Px = -R; end % draw circle in new position Screen(’FillOval’,wPtr, red ,[Px-R,Py-R, Px+R, Py+R]); Screen(’Flip’,wPtr); end Screen(’CloseAll’); catch ERR Screen(’CloseAll’); rethrow(ERR); end %%%%%%%%%%%%%%%%%%%%%%%%%% 10 SCALab Initiation Matlab 3 Laurent Ott Exercices : - Observer l’effet de la valeur de la vitesse Vx en l’augmentant ou en la diminuant. Modifiez la vitesse Vx pour une valeur negative. Que ce passe-t-il ? Résolvez le bug. - modifez le code pour ajouter une composante verticale au mouvement. - Modifiez le code pour contrôler le mouvement du cercle à partir du clavier. - Créez un script pour que le cercle suive le pointeur de la souris - Créez un script affichant et mettant à jour un montre analogique simplifiée (voir Fig. 4), utilisez les lignes de code suivantes pour récupérer l’heure en temps réel time = now; hour = str2num( datestr( time , ’HH’ ) ); minute = str2num( datestr( time , ’MM’ ) ); second = str2num( datestr( time , ’SS’ ) ); Figure 4 – Montre analogique simplifiée à 13h58m35s. 4 Programmation d’un clone du jeu Snake/Nibbles Vous avez sûrement tous déjà eu l’occasion de jouer à une variante du jeu Snake. Ce jeu consiste à piloter les déplacements d’un serpent pour collecter des objets et gagner ainsi des points. Le serpent se déplace en continu à vitesse constante, on ne peut que changer sa direction. A chaque objet collecté, le serpent s’agrandit un peu. On perd la partie dès lors que le serpent sort de l’écran ou qu’il rencontre un obstacle (un mur ou lui même en se mangeant la queue). Je vous propose de programmer une version simplifiée de ce jeu culte, sans obstacle et avec des graphismes très limités (voir Fig 5 ). 4.1 Modélisation du jeu Nous allons subdiviser l’écran en une matrice de blocs décrivant les positions possibles des éléments du jeu (objet à collecter, la tête du serpent et sa queue). Le taille horizontale et verticale de la matrice de bloc sont des paramètres que nous fixerons dans le code (par ex Nx = 100 et Ny=75). Comme le montre la Figure 6, nous pouvons calculer le rectangle d’un bloc situé à la colonne i et à la ligne j dans la matrice de blocs à partir de la formule suivante. rectBloc=[ (i-1)*(scrSizeX/Nx) , i*(scrSizeX/Nx) , (j-1)*(scrSizeY/Ny) , j*(scrSizeY/Ny) ]; Exercice : - Ecrivez le script qui ouvre un windows en prenant soin de récupérer la taille de l’écran, puis sur une matrice de blocs de taille Nx=100, Ny=75 remplie les blocs (i=2,j=2) en noir, (i=50,j=37) en rouge 11 SCALab Initiation Matlab 3 Laurent Ott Figure 5 – Version du jeu Snake à programmer. scrSizeX 1 1 ΔY 2 3 Nx-1 Nx ΔX ( j−1)Δ Y =2 Δ Y j ΔY =3 Δ Y 2 3 (i−1) Δ X =1 Δ X i=2 j=3 i ΔX =2 Δ X scrSizeY Ny-1 Ny with Δ X = scrSizeX scrSizeY , Δ Y= NX NY Figure 6 – Discretisation de l’espace en blocs. Calcul des rectangles des blocs. et (i=99,j=74) en bleu 4.1.1 Direction de déplacement du serpent Le serpent peut suivre 4 directions (haut, droite, bas, gauche). Nous représenterons la direction du serpent à l’aide d’une variable direction pouvant prendre les valeurs 1,2,3 ou 4 pour représenter respectivement les directions précédemment mentionnées. Au début du jeu, on choisira aléatoirement la direction initiale. direction = randi(4); Le joueur pourra par la suite modifier la direction à l’aide des flèches du clavier. Voici par exemple comment on pourrait scruter le clavier pendant 0.5 secondes. tStart = GetSecs; while GetSecs - tStart < 0.5 12 SCALab Initiation Matlab 3 Laurent Ott [keyIsDown, secs, keyCode] = KbCheck; if keyCode( UpArrowCode ) direction = 1; elsif keyCode( RightArrowCode ) direction = 2; elseif keyCode( DownArrowCode ) direction = 3; elseif keyCode( LeftArrowCode ) direction = 4; end end Pour utiliser ce bout de code, il vous faut d’abord initialiser les variables *ArrowCode avec les codes correspondant aux flèches du clavier. 4.1.2 Cible Les position des objets et du serpent peuvent à présent simplement être décrit par les positions i,j des blocs associés. Nous définissons donc une variable target vecteur ligne de 2 éléments pour représenter la position de la cible. On initalisera la position de la cible aléatoirement et lorsque la cible sera mangé par le serpent on la replacera à nouveau aléatoirement à une nouvelle position. target = [ randi(Nx) , randi(Ny) ]; 4.1.3 Serpent Le serpent a quant à lui une longueur variable et peut occuper plusieurs blocs. Il sera représenté par une matrice snake de taille (Nsx2) où Ns est la longueur du serpent. Chaque ligne de snake contient les coordonnées i,j d’un bloc constituant le serpent. Au début du jeu Ns=1, et augmentera à chaque objet collecté. On fera en sorte de garder la tête du serpent à la première ligne de snake On initialisera au début du jeu le serpent au centre de l’écran. snake = [ round(Nx/2) , round(Ny/2) ]; Pour faire se déplacer le serpent, il faudra dans un premier temps agrandir la matrice snake en rajoutant la nouvelle position de la tête du serpent en première ligne de la matrice. Pour ce faire, nous utilisons la concaténation de matrices. Par exemple pour placer la tête du serpent à la gauche de sa position précédente, cela donne : snake = [ [ snake(1,1)-1 , snake(1,2) ] ; snake]; %deplacement de la t^ ete vers la gauche En l’état, le serpent a grandi d’un bloc, si le serpent vient de manger une cible tout est pour le mieux, sinon il faut retirer le dernier bloc pour simuler la traine du serpent. On peut faire cela en affectant la matrice vide [] à la dernière ligne de la variable snake comme suit snake(end,:) = []; 4.2 Boucle du jeu Contrairement à un programme ou script simple qui effectue son traitement puis s’arrête. Un jeu video peut fonctionner indéfiniment. Et contrairement à un programme avec une interface graphique qui attend une intéraction de l’utilisateur pour réaliser l’action demandée, l’environnement du jeu continue souvent d’évoluer même sans intéraction de l’utilisateur. Nous allons donc utiliser une boucle while dont on ne sortira que si le joueur souhaite quitter la partie (par exemple en appuyant sur Echap) ou si il a perdu la partie (le serpent sort de l’écran ou se mord la queue pour notre jeu). Dans cette boucle, nous allons à chaque itération regarder si le joueur souhaite changer de direction, calculer les nouvelles positions de nos objets dans l’environnment et mettre jour l’affichage de l’environnement. Dans notre jeu, le mouvement apparent du serpent est réalisé en décalant periodiquement la 13 SCALab Initiation Matlab 3 Laurent Ott position du serpent d’un block dans la direction souhaitée. Comme nous l’avons vu précédemment dans la partie concernant les animations, la vitesse du serpent dépend de l’intervalle de temps entre 2 positions consecutives. Nous spécifirons cet intervalle de temps dans la variable updateInterval que nous utiliserons pour limiter la cadence des itérations de notre boucle de jeu. Ce paramètre permettra également de modifier la difficulté du jeu. Description du programme du jeu : — initialiser les paramètres du jeu (interval de raffraichissement par ex updateInterval=0.1, taille de la matrice de blocs Nx, Ny) — Initialiser les variables d’état du jeu (position de la cible, du serpent, direction, score) — Présenter un écran de bienvenue, attendre du joueur un appui sur une touche pour signaler qu’il est prêt — tant que le jeu n’est pas fini (boucle de jeu) — Afficher la cible et le serpent à leur position courante. — Scruter l’état du clavier pendant updateInterval, mettre à jour la variable direction si une flèche du clavier a été enfoncée. — Mettre à jour la variable snake en fonction de la direction (déplacement d’un bloc), i.e. agrandir la matrice pour rajouter en première position la nouvelle position de la tête du serpent — Vérifier si la cible a été mangée. — Si oui, on calcule une nouvelle position de cible et on incrémente le score — Si non, on raccourci le serpent en supprimant la dernière ligne de la matrice snake — Vérifier que le serpent ne rencontre pas d’obstacle à sa nouvelle position. Sinon, le joueur a perdu — Présenter un écran de fin du jeu Il ne vous reste plus qu’à implémenter tout ça. 5 Pour aller plus loin La Psychtoolbox comprend également des abstractions facilitant la présentation de stimuli sonores ainsi que des vidéos. Nous ne détaillerons pas ici ces aspects. Mais j’invite les curieux à parcourir les scripts de démonstrations fournies avec la Psychtoolbox. Pour l’aspect audio, la librairie portaudio est utilisée via la fonction d’abstraction PsychPortAudio(), les démonstrations intéressantes sont les suivantes : — BasicSoundOutputDemo - Demonstrate basic usage of PsychPortAudio() for sound playback. — BasicSoundInputDemo - Demonstrate basic usage of PsychPortAudio() for sound capture. — SimpleSoundScheduleDemo - Simple demo for basic use of sound schedules with PsychPortAudio. — SimpleVoiceTriggerDemo - Demo of a simple voice trigger with PsychPortAudio. — BasicSoundFeedbackDemo - Demonstrates a audio feedback loop via PsychPortAudio(). See DelayedSoundFeedbackDemo for a more research grade approach. — BasicSoundScheduleDemo - Demonstrate basic usage of sound schedules and buffers with PsychPortAudio(). — DelayedSoundFeedbackDemo - Demonstrates a audio feedback loop via PsychPortAudio() with exactly controlled latency. Pour l’aspect lecture de videos, la librairie Gstreamer est utilisée via la fonction d’abstraction Screen(), les démonstrations intéressantes sont les suivantes : — SimpleMovieDemo - Most simplistic demo on how to play a movie. — PlayMoviesDemo - Show simple playback of one movie with sound at a time. — PlayDualMoviesDemo - Same as PlayMoviesDemo, but play two movies in parallel. — PlayMoviesWithoutGapDemo2 - Play one movie while opening another one to reduce gaps between movies. — DetectionRTInVideoDemo - How to collect reaction times in response to detection of some event in a presented movie file. Takes care to get timing right. 14 SCALab Initiation Matlab 3 Laurent Ott — LoadMovieIntoTexturesDemo - Quickly load a movie into a stack of textures for quick playback with arbitrary speed and order. Pour voir le code source des démos, entrez >>edit DemoName à l’invite de commmande. 15