diff --git a/Task/Classes/al_cannon.m b/Task/Classes/al_cannon.m index f9063b9..ec5b433 100644 --- a/Task/Classes/al_cannon.m +++ b/Task/Classes/al_cannon.m @@ -58,7 +58,8 @@ if self.defaultParticles == false self.dotCol = nan; else - self.dotCol = load('dotColDefault.mat').dotColDefault; +% self.dotCol = load('dotColDefault.mat').dotColDefault; + self.dotCol = getfield(load('dotColDefault.mat','dotColDefault'),'dotColDefault'); end end diff --git a/Task/Classes/al_eyeTracker.m b/Task/Classes/al_eyeTracker.m index afc25b7..42a257e 100644 --- a/Task/Classes/al_eyeTracker.m +++ b/Task/Classes/al_eyeTracker.m @@ -18,6 +18,7 @@ ppd % estimated pixels per degree resolutionX % x resolution (in pixels) saccThres % threshold value + el %eyetracker instance end @@ -45,7 +46,7 @@ self.saccThres = 1; end - function self = initializeEyeLink(self, taskParam, et_file_name_suffix) +function self = initializeEyeLink(self, taskParam, et_file_name_suffix) % INITIALIZEEYELINK This function initialzes the eye-tracker % % Input @@ -55,24 +56,61 @@ % Output: % el: Eye-link object - self.et_file_name = sprintf('%s%s', taskParam.subject.ID, et_file_name_suffix); self.et_file_name = [self.et_file_name]; % todo: check if this is really necessary - % Todo test if we can also pass object instead instead of new structure - options.dist = self.dist; - options.width = self.width; - options.height = self.height; - options.window_rect = taskParam.display.windowRect; - options.frameDur = self.frameDur; - options.frameRate = self.frameRate; - [el, ~] = ELconfig(taskParam.display.window.onScreen, self.et_file_name, options); - - % Calibrate the eye tracker - EyelinkDoTrackerSetup(el); - + if isequal(taskParam.gParam.trackerVersion, 'eyelink') + % Todo test if we can also pass object instead instead of new structure + options.dist = self.dist; + options.width = self.width; + options.height = self.height; + options.window_rect = taskParam.display.windowRect; + options.frameDur = self.frameDur; + options.frameRate = self.frameRate; + [el, ~] = ELconfig(taskParam.display.window.onScreen, self.et_file_name, options); + + % Calibrate the eye tracker + EyelinkDoTrackerSetup(el); + + elseif isequal(taskParam.gParam.trackerVersion, 'SMI') + settings = SMITE.getDefaults('HiSpeed'); + settings.connectInfo = {'192.168.1.1',4444,'192.168.1.2',5555}; + settings.doAverageEyes = false; + settings.cal.bgColor = taskParam.colors.background; + settings.freq = 500; + %settings.trackMode = 'MONOCULAR'; + settings.trackEye = 'EYE_RIGHT'; + settings.logFileName = 'test_log.txt'; + settings.save.allowFileTransfer = false; + + % initialize SMI + self.el = SMITE(settings); + %self.el = self.el.setDummyMode(); + %try a few times in case connection breaks + tries = 0; + while tries <= 6 + try + self.el.init(); + tries + self.el.calibrate(taskParam.display.window.onScreen, false); % was calibrate(taskParam.display.window.onScreen, false) + break + catch ME + WaitSecs(7); + tries = tries + 1; + if tries <= 6 + continue + else + rethrow(ME); + end + end + end + else + + error('Please specifiy tracker version as eyelink or SMI') + end end + function self = estimatePixelsPerDegree(self) % ESTIMATEPIXELSPERDEGREE This function estimates the number of % pixels per degree for online saccade detection @@ -102,29 +140,35 @@ % sacc: Detected saccades % % Credit: Donner lab - - % Short break - pause(0.002) - - % Extract samples from eye-link - [samples, ~, ~] = Eyelink('GetQueuedData'); - % Extract relevant samples depending on tracked eye - if eye==0 - x = (samples(14,:)-zero(1))/self.ppd; - y = (samples(16,:)-zero(2))/self.ppd; - else - x = (samples(15,:)-zero(1))/self.ppd; - y = (samples(17,:)-zero(2))/self.ppd; - end + if isequal(taskParam.gParam.trackerVersion, 'eyelink') - % Compute deviation from fixation spot and categorize saccades - d = (x.^2 + y.^2).^.5; - a = d(2:length(d)); - if any(a>self.saccThres) - sacc = 1; - else - sacc = 0; + % Short break + pause(0.002) + + % Extract samples from eye-link + [samples, ~, ~] = Eyelink('GetQueuedData'); + + % Extract relevant samples depending on tracked eye + if eye==0 + x = (samples(14,:)-zero(1))/self.ppd; + y = (samples(16,:)-zero(2))/self.ppd; + else + x = (samples(15,:)-zero(1))/self.ppd; + y = (samples(17,:)-zero(2))/self.ppd; + end + + % Compute deviation from fixation spot and categorize saccades + d = (x.^2 + y.^2).^.5; + a = d(2:length(d)); + if any(a>self.saccThres) + sacc = 1; + else + sacc = 0; + end + + else + error('online saccades only implemented for eyelink') end end end @@ -139,14 +183,23 @@ % % Output % taskParam: Task-parameter-object instance - - - Eyelink('StartRecording'); - WaitSecs(0.1); - Eyelink('message', 'Start recording Eyelink'); - - % Reference time stamp - taskParam.timingParam.ref = GetSecs(); + if isequal(taskParam.gParam.trackerVersion, 'eyelink') + Eyelink('StartRecording'); + WaitSecs(0.1); + Eyelink('message', 'Start recording Eyelink'); + + % Reference time stamp + taskParam.timingParam.ref = GetSecs(); + + elseif isequal(taskParam.gParam.trackerVersion, 'SMI') + taskParam.eyeTracker.el.startRecording() + WaitSecs(0.1); + taskParam.eyeTracker.el.sendMessage('Start recording SMI'); + + % Reference time stamp + taskParam.timingParam.ref = GetSecs(); + end end + end end diff --git a/Task/Classes/al_gparam.m b/Task/Classes/al_gparam.m index 1587c04..044cd7e 100644 --- a/Task/Classes/al_gparam.m +++ b/Task/Classes/al_gparam.m @@ -65,6 +65,7 @@ scanner % indicates if experiment takes place in scanner meg % indicates if experiment takes place with MEG eyeTracker % indicates if experiment takes place with eyeTracker + trackerVersion % selects whether we want eyelink or SMI version onlineSaccades % indicates if we track saccades during task uke % indicates uke fMRI scanner joy % potentially temporary joystick variable @@ -136,6 +137,7 @@ self.scanner = false; self.meg = false; self.eyeTracker = false; + self.trackerVersion = 'eyelink'; self.uke = false; self.joy = nan; self.useResponseThreshold = false; diff --git a/Task/Classes/al_keys.m b/Task/Classes/al_keys.m index cc5a797..ecf87e9 100644 --- a/Task/Classes/al_keys.m +++ b/Task/Classes/al_keys.m @@ -107,11 +107,20 @@ et_path = pwd; et_file_name = [et_file_name, '.edf']; - al_saveEyelinkData(et_path, et_file_name) - Eyelink('StopRecording'); + if isequal(taskParam.gParam.trackerVersion, 'eyelink') + al_saveEyelinkData(et_path, et_file_name) + Eyelink('StopRecording'); + + elseif isequal(taskParam.gParam.trackerVersion, 'SMI') + + al_saveSMIData(taskParam.eyeTracker.el, et_path, et_file_name) + taskParam.eyeTracker.el.stopRecording(); + end + end + % Behavioral if isequal(taskParam.trialflow.saveData, 'true') && exist('taskData', 'var') == true al_saveData(taskData) diff --git a/Task/Functions/al_baselineArousal.m b/Task/Functions/al_baselineArousal.m index bb03ad9..697953f 100644 --- a/Task/Functions/al_baselineArousal.m +++ b/Task/Functions/al_baselineArousal.m @@ -37,8 +37,12 @@ function al_baselineArousal(taskParam, file_name_suffix) % Presenting trial number at the bottom of the eyetracker display - optional if taskParam.gParam.eyeTracker && isequal(taskParam.trialflow.exp, 'exp') - Eyelink('command', 'record_status_message "TRIAL %d/%d"', i, 3); - Eyelink('message', 'TRIALID %d', i); + if isequal(taskParam.gParam.trackerVersion, 'eyelink') + Eyelink('command', 'record_status_message "TRIAL %d/%d"', i, 3); + Eyelink('message', 'TRIALID %d', i); + elseif isequal(taskParam.gParam.trackerVersion, 'SMI') + taskParam.eyeTracker.el.sendMessage(sprintf('TRIALID BaseAr %d', i)); + end end % Only send trigger on first interation @@ -75,8 +79,15 @@ function al_baselineArousal(taskParam, file_name_suffix) % ----------------- if taskParam.gParam.eyeTracker - et_path = pwd; - et_file_name=[taskParam.eyeTracker.et_file_name, '.edf']; - al_saveEyelinkData(et_path, et_file_name) - Eyelink('StopRecording'); + if isequal(taskParam.gParam.trackerVersion, 'eyelink') + et_path = pwd; + et_file_name=[taskParam.eyeTracker.et_file_name, '.edf']; + al_saveEyelinkData(et_path, et_file_name) + Eyelink('StopRecording'); + elseif isequal(taskParam.gParam.trackerVersion, 'SMI') + et_path = pwd; + et_file_name=[taskParam.eyeTracker.et_file_name, '.edf']; + al_saveSMIData(taskParam.eyeTracker.el, et_path, et_file_name) + taskParam.eyeTracker.el.stopRecording(); + end end diff --git a/Task/Functions/al_sendTrigger.m b/Task/Functions/al_sendTrigger.m index 6ba465c..b9df01e 100644 --- a/Task/Functions/al_sendTrigger.m +++ b/Task/Functions/al_sendTrigger.m @@ -264,7 +264,11 @@ % Send the pupil trigger if taskParam.gParam.eyeTracker && isequal(taskParam.trialflow.exp, 'exp') || taskParam.gParam.eyeTracker && isequal(taskParam.trialflow.exp, 'passive') - Eyelink('message', num2str(triggerID)); + if isequal(taskParam.gParam.trackerVersion, 'eyelink') + Eyelink('message', num2str(triggerID)); + elseif isequal(taskParam.gParam.trackerVersion, 'SMI') + taskParam.eyeTracker.el.sendMessage(num2str(triggerID)); + end end % Send the EEG trigger diff --git a/Task/TaskVersions/ResearchUnit/JenaSMI/al_commonConfettiConfigJenaSMI.m b/Task/TaskVersions/ResearchUnit/JenaSMI/al_commonConfettiConfigJenaSMI.m new file mode 100644 index 0000000..a151564 --- /dev/null +++ b/Task/TaskVersions/ResearchUnit/JenaSMI/al_commonConfettiConfigJenaSMI.m @@ -0,0 +1,70 @@ +% Common Confetti Version Configuration Example +% +% Example of how to add local parameter settings as config input to the +% function that runs the task. +% +% It is recommended that you create your own script with the local +% parameter settings so that you can re-use your settings. + +%PsychDebugWindowConfiguration(0,0.5); +% Create config structure +config = struct(); + +% Add desired parameters +config.trialsExp = 47; %47; %default for experiment is 100 for 4 blocks (400), trying 38 trials for 8 blocks (304) +config.nBlocks = 3; %blocks per noise condition, default is 2 +config.practTrialsVis = 10; %10 +config.practTrialsHid = 20; %20 +config.cannonPractCriterion = 4; % criterion cannon practice +config.cannonPractNumOutcomes = 5; % number of trials cannon practice +config.cannonPractFailCrit = 3; +config.passiveViewing = false; +config.passiveViewingPractTrials = 10; +config.baselineFixLength = 0.25; +config.blockIndices = [1 999 999 999]; % we don't have breaks within each block +config.runIntro = true; % true +config.baselineArousal = true; % true; +config.language = 'German'; % 'English'; +config.sentenceLength = 80; +config.textSize = 32; +config.vSpacing = 1; +config.headerSize = 50; +config.screenSize = [0 0 1680 1050]*1; % get(0,'MonitorPositions')*1.0; +config.screenNumber = 1; +config.s = 83; +config.enter = 13; +config.five = 15; +config.defaultParticles = true; +config.debug = false; +config.showConfettiThreshold = false; +config.printTiming = true; +config.hidePtbCursor = true; +config.dataDirectory = 'C://Users//Matlab-User//Documents//AdaptiveLearning//DataDirectory'; %'C://Users//Matlab-User//Documents//AdaptiveLearning//DataDirectory' +config.meg = false; +config.scanner = false; +config.eyeTracker = true; %true; +config.trackerVersion = 'SMI'; %set 'eyelink' or 'SMI' +config.onlineSaccades = false; +config.saccThres = 1; +config.useDegreesVisualAngle = true; +config.distance2screen = 740; %700; % defined in mm (for degrees visual angle) and eT +config.screenWidthInMM = 580; % for degrees visual angle and ET +config.screenHeightInMM = 295; %210; % for ET +config.sendTrigger = false; +config.sampleRate = 500; % Sampling rate for EEG +config.port = hex2dec('E050'); +config.rotationRadPixel = 140; % 170 +config.rotationRadDeg = 3.16; % 2.5 +config.customInstructions = true; +config.instructionText = al_commonConfettiInstructionsJena(config.language); +config.noPtbWarnings = false; +config.predSpotCircleTolerance = 2; + +if config.sendTrigger + [config.session, ~] = IOPort( 'OpenSerialPort', 'COM3' ); +else + config.session = nan; +end + +% Run task with config input +RunCommonConfettiVersion(config); \ No newline at end of file diff --git a/Task/TaskVersions/ResearchUnit/JenaSMI/al_commonConfettiInstructionsJena.m b/Task/TaskVersions/ResearchUnit/JenaSMI/al_commonConfettiInstructionsJena.m new file mode 100644 index 0000000..315adc7 --- /dev/null +++ b/Task/TaskVersions/ResearchUnit/JenaSMI/al_commonConfettiInstructionsJena.m @@ -0,0 +1,530 @@ +classdef al_commonConfettiInstructionsJena + %AL_COMMONCONFETTIINSTRUCTIONSDEFAULTTEXT This class-definition file + % specifiec the properties of the instruction text. + % + % The advantage of this kind of text file is that most text is in one + % place and a local file can replace (specified in the config file) the + % default file so that local differences are not tracked on GitHub. + + properties + + language + welcomeText + introduceCannon + introduceConfetti + introduceSpot + introduceShield + introduceMiss + introduceMissBucket + introducePracticeSession + firstPracticeHeader + firstPractice + reduceShieldHeader + reduceShield + secondPracticeHeader + secondPractice + thirdPracticeHeader + thirdPractice + fourthPracticeHeader + fourthPractice + startTaskHeader + startTask + noCatchHeader + noCatch + accidentalCatchHeader + accidentalCatch + showCannonText + addCannonText + cannonFeedbackText + practiceBlockFailHeader + practiceBlockFail + cannonPracticeFail + firstPupilBaselineHeader + firstPupilBaseline + secondPupilBaselineHeader + secondPupilBaseline + introduceLowNoiseHeader + introduceLowNoise + introduceHighNoiseHeader + introduceHighNoise + dynamicFeedbackTxt + dynamicFeedbackHeader + introducePassiveViewingHeader + introducePassiveViewing + dynamicBlockTxt + + end + + methods + + function self = al_commonConfettiInstructionsJena(language) + % This function creates an object of + % class al_commonConfettiInstructionsDefaultText + % + % Input + % language: Optional parameter specifying language (default German) + % + % Output + % None + + + % Check if language parameter is provided + if ~exist('language', 'var') || isempty(language) + self.language = 'German'; + else + self.language = language; + end + + % First message when starting task + if isequal(self.language, 'German') + self.welcomeText = 'Herzlich Willkommen zur Konfetti-Kanonen-Aufgabe!'; + elseif isequal(self.language, 'English') + self.welcomeText = 'Welcome to the confetti-cannon task!'; + else + error('language parameter unknown') + end + + % Introduce cannon + if isequal(self.language, 'German') + self.introduceCannon = ['Du blicktst von oben auf eine Konfetti-Kanone, die in der Mitte eines Kreises ist. Deine Aufgabe ist es, das Konfetti mit einem Eimer zu fangen. Mit dem rosafarbenen '... + 'Punkt kannst du anklicken, wo auf dem Kreis du deinen Eimer platzieren möchtest, um das Konfetti zu fangen. Du kannst den Punkt mit der '... + 'Maus steuern.']; + elseif isequal(self.language, 'English') + self.introduceCannon = ['You are looking from above at a confetti cannon placed in the center of a circle. Your task is to catch the confetti with a bucket. Use the pink dot '... + 'to indicate where you would like to place your bucket to catch the confetti. '... + 'You can move the pink dot using the mouse.']; + else + error('language parameter unknown') + end + + % Introduce confetti + if isequal(self.language, 'German') + self.introduceConfetti = 'Das Ziel der Konfetti-Kanone wird mit der schwarzen Linie angezeigt. Steuere den rosafarbenen Punkt auf den Kreis und drücke die linke Maustaste, damit die Konfetti-Kanone schießt.'; + elseif isequal(self.language, 'English') + self.introduceConfetti = 'The aim of the cannon is indicated by the black line. Hit the left mouse button to fire the cannon.'; + else + error('language parameter unknown') + end + + % Introduce spot + if isequal(self.language, 'German') + self.introduceSpot = ['Der schwarze Strich zeigt dir die mittlere Position der letzten Konfettiwolke. Der rosafarbene Strich zeigt dir die '... + 'letzte Position deines Eimers. Steuere den rosafarbenen Punkt jetzt bitte auf das Ziel der Konfetti-Kanone und drücke die linke Maustaste.']; + elseif isequal(self.language, 'English') + self.introduceSpot = ['The black line shows the central position of the last confetti burst. The pink line shows the '... + 'last position of your bucket. Now move the pink dot to the aim of the confetti cannon and press the left mouse button.']; + else + error('language parameter unknown') + end + + % Introduce shield + if isequal(self.language, 'German') + self.introduceShield = 'Nach dem Kanonenschuss siehst du den Eimer. Wenn du mindestens die Hälfte des Konfettis im Eimer fangst, zählt es als Treffer und du erhältst einen Punkt.'; + elseif isequal(self.language, 'English') + self.introduceShield = ['After the cannon is shot you will see the bucket. '... + 'If you catch at least half of the confetti with the bucket, it is considered a "catch" and you get a point. ']; + else + error('language parameter unknown') + end + + % Introduce miss + if isequal(self.language, 'German') + self.introduceMiss = 'Versuche nun, deinen Eimer so zu plazieren, dass du das Konfetti NICHT fängst. Drücke dann die linke Maustaste.'; + elseif isequal(self.language, 'English') + self.introduceMiss = ['Now try to place the bucket so that you miss the confetti. Then press '... + 'the left mouse button. ']; + elseneedle + error('language parameter unknown') + end + + % Introduce miss with bucket + if isequal(self.language, 'German') + self.introduceMissBucket = 'In diesem Fall hast du Konfetti verfehlt.'; + elseif isequal(self.language, 'English') + self.introduceMissBucket = 'In this case you missed the confetti.'; + else + error('language parameter unknown') + end + + % Introduce practice session + if isequal(self.language, 'German') + self.introducePracticeSession = 'Im Folgenden machst du ein paar Übungsdurchgänge\nund im Anschluss zwei Durchgänge des Experiments.'; + elseif isequal(self.language, 'English') + self.introducePracticeSession = 'In the following, you will go through a few practice runs\nand then two blocks of the experiment.'; + else + error('language parameter unknown') + end + + % First practice header + if isequal(self.language, 'German') + self.firstPracticeHeader = 'Erster Übungsdurchgang'; + elseif isequal(self.language, 'English') + self.firstPracticeHeader = 'First Practice Run'; + else + error('language parameter unknown') + end + + % First practice + if isequal(self.language, 'German') + self.firstPractice = ['In diesem Durchgang ist die Konfetti-Kanone schon sehr alt und die Schüsse sind daher ziemlich ungenau. Das heißt, auch wenn '... + 'du den Eimer genau auf das Ziel der Konfetti-Kanone plazierst, kannst du das Konfetti verfehlen. Die Ungenauigkeit ist zufällig. '... + 'Dennoch fangst du am meisten Konfetti, wenn du den rosafarbenen Punkt genau auf die Stelle '... + 'steuerst, auf die die Konfetti-Kanone zielt.\n\nIn dieser Übung sollst du mit der Ungenauigkeit '... + 'der Konfetti-Kanone erst mal vertraut werden. Steuere Sie den rosafarbenen Punkt bitte immer auf die anvisierte '... + 'Stelle. \n\nAchte bitte auf Augenbewegungen und Blinzeln wie von der Versuchsleitung erklärt.']; + elseif isequal(self.language, 'English') + self.firstPractice = ['In this block, the confetti cannon is very old '... + 'and its aim therefore pretty inaccurate. Even if you move the bucket to the exact aim of the confetti cannon, '... + 'you might miss the confetti. This inaccuracy is random. '... + 'Still, your best strategy is to place the '... + 'bucket in the location where the cannon is '... + 'aimed.\n\nThe purpose of this practice session is to familiarize yourself with the inaccuracy '... + 'of the confetti cannon. Please always aim the pink dot at the '... + 'aim of the cannon.']; + else + error('language parameter unknown') + end + + % Reduced shield header + if isequal(self.language, 'German') + self.reduceShieldHeader = 'Illustration Ihres Eimers'; + elseif isequal(self.language, 'English') + self.reduceShieldHeader = 'Demonstration of your bucket'; + else + error('language parameter unknown') + end + + % Reduced + if isequal(self.language, 'German') + self.reduceShield = ['Ab jetzt sehst du den Eimer nur noch als zwei Striche. Außerdem siehst du die Aufgabe in weniger Farben. ' ... + 'Dies ist notwendig, damit wir deine Pupillengröße gut messen können. Achte daher bitte besonders darauf, '... + 'möglichst auf den Punkt in der Mitte des Kreises zu schauen. Bitte versuche Augenbewegungen und blinzeln '... + 'so gut es geht zu vermeiden.\n\n'... + 'Jetzt folgt zunächst eine kurze Demonstration, wie der Eimer mit Strichen im Vergleich zum Eimer der vorherigen Übung aussieht.']; + elseif isequal(self.language, 'English') + self.reduceShield = 'Please update if you plan to use this.'; + else + error('language parameter unknown') + end + + % Second practice header + if isequal(self.language, 'German') + self.secondPracticeHeader = 'Zweiter Übungsdurchgang'; + elseif isequal(self.language, 'English') + self.secondPracticeHeader = 'Second Practice Run'; + else + error('language parameter unknown') + end + + % Second practice + if isequal(self.language, 'German') + self.secondPractice = ['Um sicherzugehen, dass du die Aufgabe verstanden hast, machen wir jetzt eine kurze Übung:\n\n'... + 'Du wirst hintereinander fünf Schüsse der Konfetti-Kanone sehen. Danach gibst du bitte an, wo du das Ziel der Konfetti-Kanone vermutest.\n\n'... + 'Die beste Strategie ist, die mittlere Position der Schüsse anzugeben. Diese Position ist die beste Vohersage, um in der Aufgabe am meisten Konfetti zu fangen.']; + elseif isequal(self.language, 'English') + self.secondPractice = ['Add instructions please']; % update few things if planning to use this + else + error('language parameter unknown') + end + + % Third practice header + if isequal(self.language, 'German') + self.thirdPracticeHeader = 'Dritter Übungsdurchgang'; + elseif isequal(self.language, 'English') + self.thirdPracticeHeader = 'Third Practice Run'; + else + error('language parameter unknown') + end + + % Third practice + if isequal(self.language, 'German') + self.thirdPractice = ['In dieser Übung siehst du nur noch einen Schuss der Konfetti-Kanone. '... + 'Bitte gib wieder an, wo du die Konfetti-Kanone vermutest.\n\nBitte beachte, dass das Ziel der Kanone meistens gleich bleibt. Manchmal richtet sich die Kanone allerdings neu aus. '... + 'Wenn du denkst, dass die Konfetti-Kanone ihre Richtung geändert hat, solltest du auch den Eimer '... + 'dorthin bewegen.\n\nBeachte, dass du das Konfetti trotz guter Vorhersagen auch häufig nicht fangen kannst.']; + elseif isequal(self.language, 'English') + self.thirdPractice = ['Add instructions please']; % update few things if planning to use this + else + error('language parameter unknown') + end + + % Fourth practice header + if isequal(self.language, 'German') + self.fourthPracticeHeader = 'Vierter Übungsdurchgang'; + elseif isequal(self.language, 'English') + self.fourthPracticeHeader = 'Fourth Practice Run'; + else + error('language parameter unknown') + end + + % Fourth practice + if isequal(self.language, 'German') + self.fourthPractice = ['Jetzt kommen wir zur letzten Übung.\n\nDiesmal musst du mit dem rosafarbenen Punkt deinen Schild platzieren und siehst dabei die Kanone nicht mehr. Außerdem wirst du es sowohl mit einer relativ genauen '... + 'als auch einer eher ungenauen versteckten Konfetti-Kanone zu tun haben.\n\n'... + 'Achte bitte auf Augenbewegungen und Blinzeln wie von der Versuchsleitung erklärt.'... + '\n\nBeachte bitte auch, dass das Ziel der Konfetti-Kanone in manchen Fällen sichtbar sein wird. In diesen Fällen ist die beste Strategie, zum Ziel der Kanone zu gehen.']; + elseif isequal(self.language, 'English') + self.fourthPractice = ['Add instructions please']; % update few things if planning to use this + else + error('language parameter unknown') + end + + % Start task header + if isequal(self.language, 'German') + self.startTaskHeader = 'Start des Experiments'; + elseif isequal(self.language, 'English') + self.startTaskHeader = 'Beginning of the Experiment'; + else + error('language parameter unknown') + end + + % Start task + if isequal(self.language, 'German') + self.startTask = ['Toll, du hast die Übungen geschafft! Kurz zusammengefasst fängst du also das meiste Konfetti, '... + 'wenn du den Eimer (rosafarbener Punkt) auf die Stelle bewegst, auf die die Konfetti-Kanone zielt. Weil du die Konfetti-Kanone meistens nicht mehr '... + 'sehen kannst, musst du diese Stelle aufgrund der Position der letzten Konfettiwolken einschätzen. Beachte, dass du das Konfetti trotz '... + 'guter Vorhersagen auch oft nicht fangen kannst. \n\nIn wenigen Fällen wirst du die Konfetti-Kanone zu sehen bekommen und kannst deine Leistung '... + 'verbessern, indem du den Eimer genau auf das Ziel steuerst.\n\n'... + 'Achte bitte auf Augenbewegungen und Blinzeln wie von der Versuchsleitung erklärt.\n\nViel Erfolg!']; + elseif isequal(self.language, 'English') + self.startTask = ['You have completed the practice phase. To summarize, you catch the most confetti, '... + 'when you move the bucket (pink dot) to the aim of the confetti cannon. Because you can usually no longer see the cannon, '... + 'you will have to estimate the aim based on the last confetti bursts. Please note that despite '... + 'good predictions, you often wont be able to catch it. \n\nIn a few cases, you will see the confetti cannon and can improve your performance '... + 'by moving the bucket to its aim.\n\n'... + 'Please avoid eye movements and blinking during a trial. If the dot in the middle is light grey at the end of a trial, you may blink.\n\nGood luck!']; else + error('language parameter unknown') + end + + % No catch header + if isequal(self.language, 'German') + self.noCatchHeader = 'Leider nicht gefangen!'; + elseif isequal(self.language, 'English') + self.noCatchHeader = 'Unfortunately no catch!'; + else + error('language parameter unknown') + end + + % No catch + if isequal(self.language, 'German') + self.noCatch = 'Du hast leider zu wenig Konfetti gefangen. Versuche es noch mal!'; + elseif isequal(self.language, 'English') + self.noCatch = 'Unfortunately, you did not catch enough confetti. Try again!'; + else + error('language parameter unknown') + end + + % Accidental catch header + if isequal(self.language, 'German') + self.accidentalCatchHeader = 'Leider gefangen!'; + elseif isequal(self.language, 'English') + self.accidentalCatchHeader = 'A catch, unfortunately!'; + else + error('language parameter unknown') + end + + % Accidental catch + if isequal(self.language, 'German') + self.accidentalCatch = 'Du hast zu viel Konfetti gefangen. Versuche bitte, das Konfetti zu verfehlen!'; + elseif isequal(self.language, 'English') + self.accidentalCatch = 'You have caught too much confetti. Please try to miss the confetti!'; + else + error('language parameter unknown') + end + + % Show cannon + if isequal(self.language, 'German') + self.showCannonText = 'Bitte gib an, wo du die Kanone vermutest.'; + elseif isequal(self.language, 'English') + self.showCannonText = 'Please add instructions'; + else + error('language parameter unknown') + end + + % Additional show cannon text + if isequal(self.language, 'German') + self.addCannonText = ['\n\nDie grauen Striche zeigen wo das Konfetti als letztes gelandet ist.\n'... + 'Mit der Maus kannst du angeben, wo du die Kanone vermutest.']; + elseif isequal(self.language, 'English') + self.addCannonText = 'Please add instructions'; + else + error('language parameter unknown') + end + + % Cannon feedback text + if isequal(self.language, 'German') + self.cannonFeedbackText = '\n\nHier kannst du deine Angabe und die echte Konfetti-Kanone vergleichen.'; + elseif isequal(self.language, 'English') + self.cannonFeedbackText = 'Please add instructions'; + else + error('language parameter unknown') + end + + % Practice block fail header + if isequal(self.language, 'German') + self.practiceBlockFailHeader = 'Bitte noch mal probieren!'; + elseif isequal(self.language, 'English') + self.practiceBlockFailHeader = 'Please try again!'; + else + error('language parameter unknown') + end + + % Practice block fail + if isequal(self.language, 'German') + self.practiceBlockFail = ['Du hast deinen Eimer oft neben dem Ziel der Kanone platziert. Versuche im nächsten '... + 'Durchgang bitte, den Eimer direkt auf das Ziel zu steuern. Das Ziel wird durch die schwarzen Linie angezeigt.']; + elseif isequal(self.language, 'English') + self.practiceBlockFail = ['You have often placed your bucket next to the aim of the cannon. In the next '... + 'phase, please try to aim the bucket directly at the target. The aim is indicated by the black line.']; + else + error('language parameter unknown') + end + + % Practice block fail + if isequal(self.language, 'German') + self.cannonPracticeFail = ['Du hast die Konfetti-Kanone nicht genau genug eingeschätzt. Versuche im nächsten '... + 'Durchgang bitte, den Mittelpunkt der einzelnen Schüsse auszuwählen. Wenn du Fragen hast, wende dich an die Versuchsleitung.']; + elseif isequal(self.language, 'English') + self.cannonPracticeFail = ['Please add instructions']; + else + error('language parameter unknown') + end + + % First pupil baseline + if isequal(self.language, 'German') + self.firstPupilBaselineHeader = 'Erste Pupillenmessung'; + self.firstPupilBaseline = ['Du wirst jetzt für drei Minuten verschiedene Farben auf dem Bildschirm sehen. '... + 'Bitte fixiere deinen Blick währenddessen auf den kleinen Punkt in der Mitte des Bildschirms.']; + elseif isequal(self.language, 'English') + self.firstPupilBaselineHeader = 'First Pupil Assessment'; + self.firstPupilBaseline = ['Include correct instructions here']; + else + error('language parameter unknown') + end + + % Second pupil baseline + if isequal(self.language, 'German') + self.secondPupilBaselineHeader = 'Zweite Pupillenmessung'; + self.secondPupilBaseline = ['Du wirst jetzt noch mal für drei Minuten verschiedene Farben auf dem Bildschirm sehen. '... + 'Bitte fixiere deinen Blick währenddessen auf den kleinen Punkt in der Mitte des Bildschirms.']; + elseif isequal(self.language, 'English') + self.secondPupilBaselineHeader = 'Second Pupil Assessment'; + self.secondPupilBaseline = ['Include correct instructions here']; + else + error('language parameter unknown') + end + + % Indicate low noise + if isequal(self.language, 'German') + self.introduceLowNoiseHeader = 'Genauere Konfetti-Kanone'; + self.introduceLowNoise = ['Im folgenden Block wird die Konfetti-Kanone relativ genau sein.\n\n'... + 'Die Größe des Eimers kann sich von Durchgang '... + 'zu Durchgang ändern. Diese Veränderung kannst du nicht beeinflussen '... + 'und auch nicht vorhersagen. Daher ist es immer die beste Strategie, '... + 'den Eimer genau dorthin zu stellen, wo du das Ziel der Konfetti-Kanone vermutest.\n\n'... + 'Zur Erinnerung: Der rosafarbene Strich zeigt deine letzte Vorhersage. Der schwarze '... + 'Strich zeigt die Position der letzten Konfetti-Wolke.\n\n'... + 'Achte bitte auf Augenbewegungen und Blinzeln wie von der Versuchsleitung erklärt.']; + elseif isequal(self.language, 'English') + self.introduceLowNoiseHeader = 'More accurate confetti cannon'; + self.introduceLowNoise = ['In the following block, the confetti cannon will be relatively accurate.\n\n'... + 'The size of the bucket can change from trial '... + 'to trial. You cannot influence this change '... + 'nor can you predict it. Therefore, the best strategy is always to '... + 'place the bucket exactly where you think the confetti cannon will be aimed.']; + else + error('language parameter unknown') + end + + % Indicate high noise + if isequal(self.language, 'German') + self.introduceHighNoiseHeader = 'Ungenauere Konfetti-Kanone'; + self.introduceHighNoise = ['Im folgenden Block wird die Konfetti-Kanone relativ ungenau sein.\n\n'... + 'Die Größe des Eimers kann sich von Durchgang '... + 'zu Durchgang ändern. Diese Veränderung kannst du nicht beeinflussen '... + 'und auch nicht vorhersagen. Daher ist es immer die beste Strategie, '... + 'den Eimer genau dorthin zu stellen, wo du das Ziel der Konfetti-Kanone vermutest.\n\n'... + 'Zur Erinnerung: Der rosafarbene Strich zeigt deine letzte Vorhersage. Der schwarze '... + 'Strich zeigt die Position der letzten Konfetti-Wolke.\n\n'... + 'Achte bitte auf Augenbewegungen und Blinzeln wie von der Versuchsleitung erklärt.']; + elseif isequal(self.language, 'English') + self.introduceHighNoiseHeader = 'Less accurate confetti cannon'; + self.introduceHighNoise = ['In the following block, the confetti cannon will be relatively inaccurate.\n\n'... + 'The size of the bucket can change from trial '... + 'to trial. You cannot influence this change '... + 'nor can you predict it. Therefore, the best strategy is always to '... + 'place the bucket exactly where you think the confetti cannon will be aimed.']; + else + error('language parameter unknown') + end + + self.introducePassiveViewingHeader = 'Beobachtungsaufgabe'; + self.introducePassiveViewing = ['Versuche bitte in dieser Aufgabe die Mitte '... + 'des Bildschirms zu fixieren. Es ist wichtig, dass du deine Augen nicht bewegst!\n\n'... + 'Versuche nur zu blinzeln, wenn der weiße Punkt erscheint.']; + + end + + + function self = giveFeedback(self, currPoints, type) + %GIVEFEEDBACK This function displays feedback after a block or + %the task + % + % Input + % self: Instructions-text-object instance + % currPoints: Number of points + % type: single-block vs. whole task feedback + % + % Output + % self: Instructions-text-object instance + + + if isequal(type, 'block') + if isequal(self.language, 'German') + self.dynamicFeedbackTxt = sprintf('In diesem Block hast du %.0f Punkte verdient.', currPoints); + elseif isequal(self.language, 'English') + self.dynamicFeedbackTxt = sprintf('You have earned %.0f points in this block.', currPoints); + else + error('language parameter unknown') + end + + elseif isequal(type, 'task') + if isequal(self.language, 'German') + self.dynamicFeedbackHeader = 'Ende des Versuchs!'; + self.dynamicFeedbackTxt = sprintf('Vielen Dank für deine Teilnahme!\n\n\nDu hast insgesamt %i Punkte gewonnen!', currPoints); + elseif isequal(self.language, 'English') + self.dynamicFeedbackHeader = 'End of the Experiment!'; + self.dynamicFeedbackTxt = sprintf('Thank you for taking part!\n\n\nYou have won a total of %i points!', currPoints); + else + error('language parameter unknown') + end + else + error('type parameter unknown') + end + end + + + function self = giveBlockFeedback(self, nBlocks, half, currBlock) + %GIVEBLOCKFEEDBACK This function displays how many blocks have + %been completed so far + % + % Input + % self: Instructions-text-object instance + % nBlocks: Number of blocks + % half: Number of points + % currBlock: Indicates if we are in passive-viewing condition + % Output + % self: Instructions-text-object instance + + + if half == 1 + self.dynamicBlockTxt = sprintf('Kurze Pause!\n\nDu hast bereits %i von insgesamt %i Durchgängen geschafft.', currBlock, nBlocks*2); + elseif half == 2 + self.dynamicBlockTxt = sprintf('Kurze Pause!\n\nDu hast bereits %i von insgesamt %i Durchgängen geschafft.', currBlock+nBlocks, nBlocks*2); + else + error('half parameter undefined') + end + end + + end +end \ No newline at end of file diff --git a/Task/TaskVersions/ResearchUnit/JenaSMI/al_saveSMIData.m b/Task/TaskVersions/ResearchUnit/JenaSMI/al_saveSMIData.m new file mode 100644 index 0000000..d5853d6 --- /dev/null +++ b/Task/TaskVersions/ResearchUnit/JenaSMI/al_saveSMIData.m @@ -0,0 +1,17 @@ +function al_saveSMIData(tracker_instance, et_path, et_file_name) +%AL_SAVEEYELINKDATA This function saves the eye-tracking data +% +% Input +% et_path: Data directory path +% et_file_name: File name +% +% Output +% None + +fprintf('Saving SMI data to %s\n', et_path) +eyefilename = fullfile(et_path,et_file_name); + +tracker_instance.stopRecording(); +tracker_instance.saveData(eyefilename); + +end diff --git a/Task/TaskVersions/ResearchUnit/commonConfetti/RunCommonConfettiVersion.m b/Task/TaskVersions/ResearchUnit/commonConfetti/RunCommonConfettiVersion.m index 6724dcc..289db15 100644 --- a/Task/TaskVersions/ResearchUnit/commonConfetti/RunCommonConfettiVersion.m +++ b/Task/TaskVersions/ResearchUnit/commonConfetti/RunCommonConfettiVersion.m @@ -64,6 +64,7 @@ config.meg = false; config.scanner = false; config.eyeTracker = false; + config.trackerVersion = 'eyelink'; config.onlineSaccades = true; config.saccThres = 0.7; config.useDegreesVisualAngle = true; @@ -149,6 +150,7 @@ hidePtbCursor = config.hidePtbCursor; % hide cursor dataDirectory = config.dataDirectory; eyeTracker = config.eyeTracker; % doing eye-tracking? +trackerVersion = config.trackerVersion; % select whether eyelink or SMI onlineSaccades = config.onlineSaccades; % online saccades tracking? saccThresh = config.saccThres; useDegreesVisualAngle = config.useDegreesVisualAngle; % Define stimuli in degrees of visual angle @@ -285,6 +287,7 @@ gParam.dataDirectory = dataDirectory; gParam.meg = meg; gParam.eyeTracker = eyeTracker; +gParam.trackerVersion = trackerVersion; gParam.onlineSaccades = onlineSaccades; gParam.sendTrigger = sendTrigger; gParam.scanner = scanner; diff --git a/Task/TaskVersions/ResearchUnit/commonConfetti/al_commonConfettiConfigExample.m b/Task/TaskVersions/ResearchUnit/commonConfetti/al_commonConfettiConfigExample.m index 61a566c..f93c7b6 100644 --- a/Task/TaskVersions/ResearchUnit/commonConfetti/al_commonConfettiConfigExample.m +++ b/Task/TaskVersions/ResearchUnit/commonConfetti/al_commonConfettiConfigExample.m @@ -44,6 +44,7 @@ config.meg = false; config.scanner = false; config.eyeTracker = false; %true; +config.trackerVersion = 'eyelink'; %set 'eyelink' or 'SMI' config.onlineSaccades = true; config.saccThres = 1; config.useDegreesVisualAngle = true; diff --git a/Task/TaskVersions/ResearchUnit/commonConfetti/al_confettiLoop.m b/Task/TaskVersions/ResearchUnit/commonConfetti/al_confettiLoop.m index 6fd0222..10ab56b 100644 --- a/Task/TaskVersions/ResearchUnit/commonConfetti/al_confettiLoop.m +++ b/Task/TaskVersions/ResearchUnit/commonConfetti/al_confettiLoop.m @@ -80,8 +80,12 @@ % Presenting trial number at the bottom of the eyetracker display - optional if taskParam.gParam.eyeTracker - Eyelink('command', 'record_status_message "TRIAL %d/%d"', i, trial); - Eyelink('message', 'TRIALID %d', i); + if isequal(taskParam.gParam.trackerVersion, 'eyelink') + Eyelink('command', 'record_status_message "TRIAL %d/%d"', i, trial); + Eyelink('message', 'TRIALID %d', i); + elseif isequal(taskParam.gParam.trackerVersion, 'SMI') + taskParam.eyeTracker.el.sendMessage(sprintf('TRIALID %d', i)); + end end % Save constant variables on each trial @@ -435,9 +439,19 @@ if taskParam.gParam.eyeTracker && isequal(taskParam.trialflow.saveEtData, 'true') et_path = pwd; - et_file_name=[taskParam.eyeTracker.et_file_name, '.edf']; - al_saveEyelinkData(et_path, et_file_name) - Eyelink('StopRecording'); + + if isequal(taskParam.gParam.trackerVersion, 'eyelink') + et_file_name=[taskParam.eyeTracker.et_file_name, '.edf']; + al_saveEyelinkData(et_path, et_file_name) + Eyelink('StopRecording'); + + elseif isequal(taskParam.gParam.trackerVersion, 'SMI') + et_file_name=[taskParam.eyeTracker.et_file_name]; + al_saveSMIData(taskParam.eyeTracker.el, et_path, et_file_name) + taskParam.eyeTracker.el.stopRecording(); + + end + end % Save behavioral data