Mercurial > hg > map
comparison Copy_of_multithreshold 1.46/subjGUI_MT.m @ 28:02aa9826efe0
mainly multiThreshold
author | Ray Meddis <rmeddis@essex.ac.uk> |
---|---|
date | Fri, 01 Jul 2011 12:59:47 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
27:d4a7675b0413 | 28:02aa9826efe0 |
---|---|
1 function varargout = subjGUI_MT(varargin) | |
2 | |
3 % Begin initialization code - DO NOT EDIT | |
4 gui_Singleton = 1; | |
5 gui_State = struct('gui_Name', mfilename, ... | |
6 'gui_Singleton', gui_Singleton, ... | |
7 'gui_OpeningFcn', @subjGUI_MT_OpeningFcn, ... | |
8 'gui_OutputFcn', @subjGUI_MT_OutputFcn, ... | |
9 'gui_LayoutFcn', [] , ... | |
10 'gui_Callback', []); | |
11 if nargin && isstr(varargin{1}) | |
12 gui_State.gui_Callback = str2func(varargin{1}); | |
13 end | |
14 | |
15 if nargout | |
16 [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); | |
17 else | |
18 gui_mainfcn(gui_State, varargin{:}); | |
19 end | |
20 % End initialization code - DO NOT EDIT | |
21 | |
22 % --- Executes just before subjGUI_MT is made visible. | |
23 function subjGUI_MT_OpeningFcn(hObject, eventdata, handles, varargin) | |
24 | |
25 % Choose default command line output for subjGUI_MT | |
26 handles.output = hObject; | |
27 initializeGUI(handles) | |
28 guidata(hObject, handles); | |
29 | |
30 function varargout = subjGUI_MT_OutputFcn(hObject, eventdata, handles) | |
31 % Get default command line output from handles structure | |
32 varargout{1} = handles.output; | |
33 | |
34 % -----------------------------------------------------initializeGUI | |
35 function initializeGUI(handles) | |
36 global experiment | |
37 global subjectGUIHandles expGUIhandles | |
38 addpath (['..' filesep 'MAP'], ['..' filesep 'utilities'], ... | |
39 ['..' filesep 'parameterStore'], ['..' filesep 'wavFileStore'],... | |
40 ['..' filesep 'testPrograms']) | |
41 | |
42 dbstop if error | |
43 | |
44 % subjectGUI size and location % [left bottom width height] | |
45 scrnsize=get(0,'screensize'); | |
46 set(0, 'units','pixels') | |
47 switch experiment.ear | |
48 % use default size unless... | |
49 case {'MAPmodel', 'MAPmodelMultich', 'MAPmodelSingleCh', ... | |
50 'statsModelLogistic','statsModelRareEvent'} | |
51 % subjectGUI not needed for modelling so minimize subject GUI | |
52 set(gcf, 'units','pixels') | |
53 y=[0*scrnsize(3) 0.8*scrnsize(4) 0.1*scrnsize(3) 0.2*scrnsize(4)]; | |
54 set(gcf,'position',y, 'color',[.871 .961 .996]) | |
55 | |
56 | |
57 | |
58 case 'MAPmodelListen', | |
59 % subjectGUI is needed for display purposes. Make it large | |
60 set(gcf, 'units','pixels') | |
61 y=[.665*scrnsize(3) 0.02*scrnsize(4) ... | |
62 0.33*scrnsize(3) 0.5*scrnsize(4)]; % alongside | |
63 set(gcf,'position',y, 'color',[.871 .961 .996]) | |
64 end | |
65 | |
66 switch experiment.ear | |
67 case{'left', 'right','diotic', 'dichoticLeft','dichoticRight'} | |
68 % Look to see if the button box exists and, if so, initialise it | |
69 buttonBoxIntitialize % harmless if no button box attached | |
70 end | |
71 | |
72 % function varargout = subjGUI_MT(varargin) | |
73 % | |
74 % % Begin initialization code - DO NOT EDIT | |
75 % gui_Singleton = 1; | |
76 % gui_State = struct('gui_Name', mfilename, ... | |
77 % 'gui_Singleton', gui_Singleton, ... | |
78 % 'gui_OpeningFcn', @subjGUI_MT_OpeningFcn, ... | |
79 % 'gui_OutputFcn', @subjGUI_MT_OutputFcn, ... | |
80 % 'gui_LayoutFcn', [] , ... | |
81 % 'gui_Callback', []); | |
82 % if nargin && isstr(varargin{1}) | |
83 % gui_State.gui_Callback = str2func(varargin{1}); | |
84 % end | |
85 % | |
86 % if nargout | |
87 % [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); | |
88 % else | |
89 % gui_mainfcn(gui_State, varargin{:}); | |
90 % end | |
91 % % End initialization code - DO NOT EDIT | |
92 % | |
93 % % --- Executes just before subjGUI_MT is made visible. | |
94 % function subjGUI_MT_OpeningFcn(hObject, eventdata, handles, varargin) | |
95 % | |
96 % % Choose default command line output for subjGUI_MT | |
97 % handles.output = hObject; | |
98 % initializeGUI(handles) | |
99 % guidata(hObject, handles); | |
100 % | |
101 % function varargout = subjGUI_MT_OutputFcn(hObject, eventdata, handles) | |
102 % % Get default command line output from handles structure | |
103 % varargout{1} = handles.output; | |
104 % | |
105 % % -----------------------------------------------------initializeGUI | |
106 % function initializeGUI(handles) | |
107 % global experiment | |
108 % global subjectGUIHandles expGUIhandles | |
109 % | |
110 % dbstop if error | |
111 % | |
112 % % subjectGUI size and location % [left bottom width height] | |
113 % scrnsize=get(0,'screensize'); | |
114 % set(0, 'units','pixels') | |
115 % switch experiment.ear | |
116 % % use default size unless... | |
117 % case {'MAPmodel', 'MAPmodelMultiCh','MAPmodelSingleCh', ... | |
118 % 'statsModelLogistic','statsModelRareEvent'} | |
119 % % subjectGUI not needed for modelling so minimize subject GUI | |
120 % set(gcf, 'units','pixels') | |
121 % y=[0*scrnsize(3) 0.8*scrnsize(4) 0.1*scrnsize(3) 0.2*scrnsize(4)]; | |
122 % set(gcf,'position',y, 'color',[.871 .961 .996]) | |
123 % | |
124 % case 'MAPmodelListen', | |
125 % % subjectGUI is needed for display purposes. Make it large | |
126 % set(gcf, 'units','pixels') | |
127 % y=[.665*scrnsize(3) 0.02*scrnsize(4) ... | |
128 % 0.33*scrnsize(3) 0.5*scrnsize(4)]; % alongside | |
129 % set(gcf,'position',y, 'color',[.871 .961 .996]) | |
130 % end | |
131 % | |
132 % switch experiment.ear | |
133 % case{'left', 'right','diotic', 'dichoticLeft','dichoticRight'} | |
134 % % Look to see if the button box exists and, if so, initialise it | |
135 % buttonBoxIntitialize % harmless if no button box attached | |
136 % end | |
137 | |
138 % clear display of previous mean values. This is a new measurement series | |
139 axes(expGUIhandles.axes4), cla | |
140 reset (expGUIhandles.axes4) | |
141 | |
142 % handles needed in non-callback routines below | |
143 subjectGUIHandles=handles; | |
144 | |
145 % start immediately | |
146 startNewExperiment(handles, expGUIhandles) | |
147 % This is the end of the experiment. Exit here and return to ExpGUI. | |
148 | |
149 % ----------------------------------------------------- startNewExperiment | |
150 function startNewExperiment(handles, expGUIhandles) | |
151 % An experiment consists of a series of 'runs'. | |
152 % Resets all relevant variables at the beginning of a new experiment. | |
153 global experiment stimulusParameters betweenRuns | |
154 | |
155 % 'start new experiment' button is the only valid action now | |
156 experiment.status='waitingForStart'; | |
157 | |
158 switch experiment.threshEstMethod | |
159 % add appropriate labels to subject GUI buttons | |
160 case {'2I2AFC++', '2I2AFC+++'} | |
161 set(handles.pushbutton3,'string','') | |
162 set(handles.pushbutton2,'string','2') | |
163 set(handles.pushbutton1,'string','1') | |
164 set(handles.pushbutton0,'string','0') | |
165 case {'MaxLikelihood', 'oneIntervalUpDown'} | |
166 if stimulusParameters.includeCue | |
167 set(handles.pushbutton3,'string','') | |
168 set(handles.pushbutton2,'string','2') | |
169 set(handles.pushbutton1,'string','1') | |
170 set(handles.pushbutton0,'string','0') | |
171 else | |
172 set(handles.pushbutton3,'string','') | |
173 set(handles.pushbutton2,'string','YES') | |
174 set(handles.pushbutton1,'string','NO') | |
175 set(handles.pushbutton0,'string','') | |
176 end | |
177 end | |
178 | |
179 switch experiment.paradigm | |
180 case 'discomfort' | |
181 set(handles.pushbutton3,'string','') | |
182 set(handles.pushbutton2,'string','uncomfortable') | |
183 set(handles.pushbutton1,'string','loud') | |
184 set(handles.pushbutton0,'string','comfortable') | |
185 experiment.allowCatchTrials=0; | |
186 end | |
187 | |
188 % experiment.subjGUIfontSize is set on expGUI | |
189 set(handles.pushbutton3,'FontSize',experiment.subjGUIfontSize) | |
190 set(handles.pushbutton2,'FontSize',experiment.subjGUIfontSize) | |
191 set(handles.pushbutton1,'FontSize',experiment.subjGUIfontSize) | |
192 set(handles.pushbutton0,'FontSize',experiment.subjGUIfontSize) | |
193 set(handles.pushbuttoNotSure,'FontSize',experiment.subjGUIfontSize) | |
194 set(handles.pushbuttonGO,'FontSize',experiment.subjGUIfontSize) | |
195 set(handles.textMSG,'FontSize',experiment.subjGUIfontSize) | |
196 | |
197 set(handles.pushbutton19,'visible','off') % unused button | |
198 | |
199 % start command window summary of progress | |
200 fprintf(' \n ----------- NEW MEASUREMENTS\n') | |
201 disp(['paradigm: ' experiment.paradigm]) | |
202 cla(expGUIhandles.axes1) | |
203 cla(expGUIhandles.axes2) | |
204 cla(expGUIhandles.axes4) | |
205 cla(expGUIhandles.axes5) | |
206 | |
207 experiment.stop=0; % status of 'stop' button | |
208 experiment.pleaseRepeat=0; % status of 'repeat' button | |
209 experiment.buttonBoxStatus='not busy'; | |
210 | |
211 % date and time and replace ':' with '_' | |
212 date=datestr(now);idx=findstr(':',date);date(idx)='_'; | |
213 experiment.date=date; | |
214 timeNow=clock; betweenRuns.timeNow= timeNow; | |
215 experiment.timeAtStart=[num2str(timeNow(4)) ':' num2str(timeNow(5))]; | |
216 experiment.minElapsed=0; | |
217 | |
218 % unpack catch trial rates. The rate declines from the start rate | |
219 % to the base rate using a time constant. | |
220 stimulusParameters.catchTrialRate=stimulusParameters.catchTrialRates(1); | |
221 stimulusParameters.catchTrialBaseRate=... | |
222 stimulusParameters.catchTrialRates(2); | |
223 stimulusParameters.catchTrialTimeConstant=... | |
224 stimulusParameters.catchTrialRates(3); | |
225 if stimulusParameters.catchTrialBaseRate==0 | |
226 stimulusParameters.catchTrialRate=0; | |
227 end | |
228 | |
229 % for human measurements only, identify the start catch trial rate | |
230 switch experiment.ear | |
231 case{'left', 'right','diotic', 'dichoticLeft','dichoticRight'} | |
232 fprintf('stimulusParameters.catchTrialRate= %6.3f\n', ... | |
233 stimulusParameters.catchTrialRate) | |
234 end | |
235 | |
236 % Reset betweenRuns parameters. this occurs only at experiment start | |
237 % withinRuns values are reset in 'startNewRun' | |
238 % this approach creates a more readable structure summary printout. | |
239 betweenRuns.thresholds=[]; | |
240 betweenRuns.thresholds_mean=[]; | |
241 betweenRuns.thresholds_median=[]; | |
242 betweenRuns.forceThresholds=[]; | |
243 betweenRuns.observationCount=[]; | |
244 betweenRuns.catchTrials=[]; | |
245 betweenRuns.timesOfFirstReversals=[]; | |
246 betweenRuns.bestThresholdTracks=[]; | |
247 betweenRuns.levelTracks=[]; | |
248 betweenRuns.responseTracks=[]; | |
249 betweenRuns.slopeKTracks=[]; | |
250 betweenRuns.gainTracks=[]; | |
251 betweenRuns.VminTracks=[]; | |
252 betweenRuns.bestGain=[]; | |
253 betweenRuns.bestVMin=[]; | |
254 betweenRuns.bestPaMin=[]; | |
255 betweenRuns.bestLogisticM=[]; | |
256 betweenRuns.bestLogisticK=[]; | |
257 betweenRuns.resets=0; | |
258 betweenRuns.runNumber=0; | |
259 | |
260 % Up to two parameters can be changed between runs | |
261 % Find the variable parameters and randomize them | |
262 % e.g. 'variableList1 = stimulusParameters.targetFrequency;' | |
263 eval(['variableList1=stimulusParameters.' betweenRuns.variableName1 ';']); | |
264 eval(['variableList2=stimulusParameters.' betweenRuns.variableName2 ';']); | |
265 nVar1=length(variableList1); | |
266 nVar2=length(variableList2); | |
267 | |
268 % Create two sequence vectors to represent the sequence of var1 and var2 | |
269 % values. 'var1' changes most rapidly. | |
270 switch betweenRuns.randomizeSequence | |
271 case 'fixed sequence' | |
272 var1Sequence=repmat(betweenRuns.variableList1, 1,nVar2); | |
273 var2Sequence=reshape(repmat(betweenRuns.variableList2, ... | |
274 nVar1,1),1,nVar1*nVar2); | |
275 case 'randomize within blocks' | |
276 % the blocks are not randomized | |
277 var1Sequence=betweenRuns.variableList1; | |
278 ranNums=rand(1, length(var1Sequence)); [x idx]=sort(ranNums); | |
279 var1Sequence=var1Sequence(idx); | |
280 betweenRuns.variableList1=variableList1(idx); | |
281 var1Sequence=repmat(var1Sequence, 1,nVar2); | |
282 var2Sequence=reshape(repmat(betweenRuns.variableList2, nVar1,1)... | |
283 ,1,nVar1*nVar2); | |
284 case 'randomize across blocks' | |
285 var1Sequence=repmat(betweenRuns.variableList1, 1,nVar2); | |
286 var2Sequence=reshape(repmat(betweenRuns.variableList2, nVar1,1),... | |
287 1,nVar1*nVar2); | |
288 ranNums=rand(1, nVar1*nVar2); | |
289 [x idx]=sort(ranNums); | |
290 var1Sequence=var1Sequence(idx); | |
291 var2Sequence=var2Sequence(idx); | |
292 % there should be one start value for every combination | |
293 % of var1/ var2. In principle this allows these values to be | |
294 % programmed. Not currently in use. | |
295 stimulusParameters.WRVstartValues=... | |
296 stimulusParameters.WRVstartValues(idx); | |
297 end | |
298 betweenRuns.var1Sequence=var1Sequence; | |
299 betweenRuns.var2Sequence=var2Sequence; | |
300 | |
301 % caught out vector needs to be linked to the length of the whole sequence | |
302 betweenRuns.caughtOut=zeros(1,length(var1Sequence)); | |
303 | |
304 disp('planned sequence:') | |
305 if min(var1Sequence)>1 | |
306 % use decidaml places only if necessary | |
307 disp([betweenRuns.variableName1 ': ' num2str(var1Sequence,'%6.0f') ]) | |
308 else | |
309 disp([betweenRuns.variableName1 ': ' num2str(var1Sequence,'%8.3f') ]) | |
310 end | |
311 if min(var1Sequence)>1 | |
312 disp([betweenRuns.variableName2 ': ' num2str(var2Sequence,'%6.0f') ]) | |
313 else | |
314 disp([betweenRuns.variableName2 ': ' num2str(var2Sequence,'%8.3f') ]) | |
315 end | |
316 | |
317 fprintf('\nvariable1 \t variable2\t \n') | |
318 fprintf('%s \t %s\t Threshold \n',betweenRuns.variableName1,... | |
319 betweenRuns.variableName2) | |
320 | |
321 % Light up 'GO' on subjGUI and advise. | |
322 set(handles.editdigitInput,'visible','off') | |
323 switch experiment.ear | |
324 case {'statsModelLogistic', 'statsModelRareEvent',... | |
325 'MAPmodel', 'MAPmodelMultiCh','MAPmodelSingleCh'} | |
326 % no changes required if model used | |
327 otherwise | |
328 set(handles.pushbuttonGO,'backgroundcolor','y') | |
329 set(handles.pushbuttonGO,'visible','on') | |
330 set(handles.frame1,'visible','off') | |
331 set(handles.textMSG,'backgroundcolor', 'w') | |
332 msg=[{'Ready to start new Experiment'}, {' '}, {'Please, click on the GO button'}]; | |
333 set(handles.textMSG,'string', msg) | |
334 | |
335 set(handles.pushbuttoNotSure,'visible','off') | |
336 set(handles.pushbuttonWrongButton,'visible','off') | |
337 set(handles.pushbutton3,'visible','off') | |
338 set(handles.pushbutton2,'visible','off') | |
339 set(handles.pushbutton1,'visible','off') | |
340 set(handles.pushbutton0,'visible','off') | |
341 pause(.1) % to allow display to be drawn | |
342 end | |
343 | |
344 % Selecting the 'GO' button is the only valid operation action now | |
345 experiment.status='waitingForGO'; % i.e. waiting for new run | |
346 | |
347 % control is now either manual, model (MAP) or randomization | |
348 switch experiment.ear | |
349 case {'MAPmodel','MAPmodelMultiCh','MAPmodelSingleCh','MAPmodelListen'} % MAP model is now the subject | |
350 stimulusParameters.calibrationdB=0; % Pascals required! | |
351 MAPmodelRunsGUI(handles) | |
352 % model is now the subject | |
353 case {'statsModelLogistic', 'statsModelRareEvent'} | |
354 % no catch trials for the statistical model | |
355 stimulusParameters.catchTrialBaseRate=0; | |
356 stimulusParameters.catchTrialRate=0; | |
357 statsModelRunsGUI(handles) | |
358 otherwise | |
359 %manual operation; wait for user to click on 'GO' | |
360 end | |
361 | |
362 % Experiment complete (after MAP or randomization) | |
363 % return to 'initializeGUI' and then back to expGUI | |
364 % Manual control finds its own way home. Program control assumed when | |
365 % the user hits the GO button | |
366 | |
367 % ----------------------------------------------------------------- startNewRun | |
368 function startNewRun(handles) | |
369 % There are many ways to arrive here. | |
370 % Under manual control this is achieved by hitting the GO button | |
371 % either via the button box or a mouse click | |
372 % MAP and randomization methods call this too | |
373 | |
374 global experiment stimulusParameters betweenRuns withinRuns expGUIhandles | |
375 global LevittControl rareEvent | |
376 | |
377 figure(handles.figure1) % guarantee subject GUI visibility | |
378 | |
379 % ignore call if program is not ready | |
380 if ~strcmp(experiment.status,'waitingForGO'), return, end | |
381 | |
382 set(handles.pushbuttonGO,'visible','off') | |
383 | |
384 % append message to expGUI message box to alert experimenter that the user | |
385 % is active | |
386 addToMsg('Starting new trial',0) | |
387 | |
388 cla(expGUIhandles.axes1), title(''); % stimulus | |
389 cla(expGUIhandles.axes2), title(''); % WRV track | |
390 drawnow | |
391 | |
392 betweenRuns.runNumber=betweenRuns.runNumber + 1; | |
393 | |
394 withinRuns.trialNumber=1; | |
395 withinRuns.variableValue=... | |
396 stimulusParameters.WRVstartValues(betweenRuns.runNumber); | |
397 % add random jitter to start level | |
398 if ~experiment.singleShot | |
399 % SS or single shot allows the user to precisely set the WRV | |
400 withinRuns.variableValue=withinRuns.variableValue +... | |
401 (rand-0.5)*stimulusParameters.jitterStartdB; | |
402 end | |
403 | |
404 withinRuns.peaks=[]; | |
405 withinRuns.troughs=[]; | |
406 withinRuns.levelList=[]; | |
407 withinRuns.meanEstTrack=[]; | |
408 withinRuns.bestSlopeK=[]; | |
409 withinRuns.bestGain=[]; | |
410 withinRuns.bestVMin=[]; | |
411 withinRuns.forceThreshold=NaN; | |
412 withinRuns.responseList=[]; | |
413 withinRuns.caughtOut=0; | |
414 withinRuns.wrongButton=0; | |
415 withinRuns.catchTrialCount=0; | |
416 withinRuns.thresholdEstimateTrack=[]; | |
417 | |
418 withinRuns.beginningOfPhase2=0; | |
419 withinRuns.nowInPhase2=0; | |
420 withinRuns.thisIsRepeatTrial=0; | |
421 | |
422 rareEvent.Euclid=NaN; | |
423 rareEvent.bestGain=NaN; | |
424 rareEvent.bestVMin=NaN; | |
425 rareEvent.thresholddB=0; | |
426 rareEvent.bestPaMindB=NaN; | |
427 rareEvent.predictionLevels=[]; | |
428 rareEvent.predictionsRE=[]; | |
429 | |
430 LevittControl.sequence=[]; | |
431 | |
432 % on-screen count of number of runs still to complete | |
433 trialsToGo=length(betweenRuns.var1Sequence)-betweenRuns.runNumber; | |
434 set(handles.toGoCounter,'string', trialsToGo); | |
435 | |
436 switch experiment.threshEstMethod | |
437 case {'2I2AFC++', '2I2AFC+++'} | |
438 % For 2I2AFC the buttons need to be on the screen ab initio | |
439 Levitt2 % inititalize Levitt2 procedure | |
440 end | |
441 | |
442 switch experiment.ear | |
443 case{'left', 'right','diotic', 'dichoticLeft','dichoticRight'} | |
444 % allow subject time to recover from 'go' press | |
445 pause(experiment.clickToStimulusPause) | |
446 end | |
447 | |
448 errormsg=nextStimulus(handles); % get the show on the road | |
449 | |
450 % switch experiment.paradigm | |
451 % case 'SRT' | |
452 % set(handles.editdigitInput,'visible','on') | |
453 % uicontrol(handles.editdigitInput) | |
454 % end | |
455 | |
456 % terminate if there is any kind of problem | |
457 if ~isempty(errormsg) | |
458 % e.g. limits exceeded, clipping | |
459 disp(errormsg) | |
460 runCompleted(handles) | |
461 return | |
462 end | |
463 | |
464 % return route is variable (see intro to this function) | |
465 | |
466 % -----------------------------------------------------buttonBox_callback | |
467 function buttonBox_callback(obj, info) | |
468 global experiment | |
469 global serobj | |
470 global subjectGUIHandles | |
471 | |
472 % do not accept callback if one is already in process | |
473 if strcmp(experiment.buttonBoxStatus,'busy') | |
474 disp(' ignored button press') | |
475 return | |
476 end | |
477 experiment.buttonBoxStatus='busy'; | |
478 % fclose(serobj) | |
479 | |
480 % identify the code of the button pressed | |
481 buttonPressedNo = fscanf(serobj,'%c',1); | |
482 | |
483 % This is the map from the button to the Cedrus codes | |
484 switch experiment.buttonBoxType | |
485 case 'horizontal' | |
486 pbGo='7'; pb0='1'; | |
487 pb1='2'; pb2='3'; | |
488 pbRepeat='4'; pbWrong='6'; pbBlank='5'; | |
489 case 'square' | |
490 pbGo='7'; pb0='1'; | |
491 pb1='3'; pb2='4'; | |
492 pbRepeat='8'; pbWrong='6'; pbBlank='5'; | |
493 end | |
494 | |
495 % decide what to do | |
496 switch experiment.status | |
497 case {'presentingStimulus', 'waitingForStart', 'trialcompleted', ... | |
498 'endOfExperiment'} | |
499 disp(' ignored button press') | |
500 | |
501 case 'waitingForGO' | |
502 % i.e. waiting for new run | |
503 if strcmp(buttonPressedNo,pbGo) % only GO button accepted | |
504 startNewRun(subjectGUIHandles) | |
505 else | |
506 disp(' ignored button press') | |
507 end | |
508 | |
509 case 'waitingForResponse' | |
510 % response to stimuli | |
511 switch buttonPressedNo | |
512 case pb0 % button 0 (top left) | |
513 switch experiment.threshEstMethod | |
514 case {'2I2AFC++', '2I2AFC+++'} | |
515 disp(' ignored button press') | |
516 otherwise | |
517 set(subjectGUIHandles.pushbutton0,... | |
518 'backgroundcolor','r') | |
519 pause(.1) | |
520 set(subjectGUIHandles.pushbutton0,... | |
521 'backgroundcolor',get(0,... | |
522 'defaultUicontrolBackgroundColor')) | |
523 userSelects0or1(subjectGUIHandles) | |
524 end | |
525 | |
526 case pb1 % button 1 (bottom left) | |
527 switch experiment.threshEstMethod | |
528 case {'2I2AFC++', '2I2AFC+++'} | |
529 userSelects0or1(subjectGUIHandles) | |
530 otherwise | |
531 set(subjectGUIHandles.pushbutton1,... | |
532 'backgroundcolor','r') | |
533 pause(.1) | |
534 set(subjectGUIHandles.pushbutton1,... | |
535 'backgroundcolor',get(0,... | |
536 'defaultUicontrolBackgroundColor')) | |
537 userSelects0or1(subjectGUIHandles) | |
538 end | |
539 | |
540 case pb2 % button 2 (bottom right) | |
541 switch experiment.threshEstMethod | |
542 case {'2I2AFC++', '2I2AFC+++'} | |
543 userSelects2 (subjectGUIHandles) | |
544 otherwise | |
545 set(subjectGUIHandles.pushbutton2,... | |
546 'backgroundcolor','r') | |
547 pause(.1) | |
548 set(subjectGUIHandles.pushbutton2,... | |
549 'backgroundcolor',get(0,... | |
550 'defaultUicontrolBackgroundColor')) | |
551 userSelects2 (subjectGUIHandles) | |
552 end | |
553 | |
554 case pbRepeat % extreme right button | |
555 switch experiment.threshEstMethod | |
556 case {'2I2AFC++', '2I2AFC+++'} | |
557 disp(' ignored button press') | |
558 otherwise | |
559 | |
560 set(subjectGUIHandles.pushbuttoNotSure,... | |
561 'backgroundcolor','r') | |
562 pause(.1) | |
563 set(subjectGUIHandles.pushbuttoNotSure,... | |
564 'backgroundcolor',get(0,... | |
565 'defaultUicontrolBackgroundColor')) | |
566 userSelectsPleaseRepeat (subjectGUIHandles) | |
567 end | |
568 | |
569 case {pbWrong, pbBlank} | |
570 disp(' ignored button press') | |
571 | |
572 otherwise % unrecognised button | |
573 disp('ignored button press') | |
574 end % end (button press number) | |
575 otherwise | |
576 disp('ignored button press') | |
577 end % experiment status | |
578 | |
579 % All processing returns through here. | |
580 % fopen(serobj); % flushes the input buffer | |
581 | |
582 % buttonPressedNo = fscanf(serobj,'%c',1); | |
583 | |
584 % button box remains 'busy' until after the stimulus has been presented | |
585 experiment.buttonBoxStatus='not busy'; | |
586 | |
587 % -------------------------------------------------- pushbuttonGO_Callback | |
588 function pushbuttonGO_Callback(hObject, eventdata, handles) | |
589 % This is a mouse click path | |
590 % GO function is also called directly from button box | |
591 % and from MAP model and stats model | |
592 | |
593 set(handles.pushbuttonGO,'visible','off') | |
594 startNewRun(handles) | |
595 | |
596 % ---------------------------------------------------pushbutton0_Callback | |
597 function pushbutton0_Callback(hObject, eventdata, handles) | |
598 global experiment | |
599 % This is a mouse click path | |
600 | |
601 % ignore 0 button if 2I2AFC used | |
602 if findstr(experiment.threshEstMethod,'2I2AFC'), return, end | |
603 | |
604 % userDoesNotHearTarget(handles) % only possible interpretation | |
605 userDecides(handles, false) | |
606 | |
607 % -------------------------------------------------- pushbutton1_Callback | |
608 function pushbutton1_Callback(hObject, eventdata, handles) | |
609 userSelects0or1(handles) % also called from buttonBox | |
610 | |
611 % ---------------------------------------------------pushbutton2_Callback | |
612 function pushbutton2_Callback(hObject, eventdata, handles) | |
613 userSelects2(handles) % also called from buttonBox | |
614 | |
615 % --------------------------------------------- pushbuttoNotSure_Callback | |
616 function pushbuttoNotSure_Callback(hObject, eventdata, handles) | |
617 userSelectsPleaseRepeat(handles) % also called from buttonBox | |
618 | |
619 % -------------------------------------------------- pushbutton3_Callback | |
620 function pushbutton3_Callback(hObject, eventdata, handles) | |
621 | |
622 % ------------------------------------------------- pushbutton19_Callback | |
623 function pushbutton19_Callback(hObject, eventdata, handles) | |
624 % should be invisible (ignore) | |
625 | |
626 % --------------------------------------- pushbuttonWrongButton_Callback | |
627 function pushbuttonWrongButton_Callback(hObject, eventdata, handles) | |
628 userSelectsWrongButton(handles) | |
629 | |
630 % --------------------------------------- editdigitInput_Callback | |
631 function editdigitInput_Callback(hObject, eventdata, handles) | |
632 userSelects0or1(handles) % after digit string input | |
633 | |
634 | |
635 | |
636 % ----------------------------------------------------- userSelects0or1 | |
637 function userSelects0or1(handles) | |
638 global experiment withinRuns | |
639 | |
640 switch experiment.threshEstMethod | |
641 case {'2I2AFC++', '2I2AFC+++'} | |
642 switch withinRuns.stimulusOrder | |
643 case 'targetFirst'; | |
644 % userHearsTarget(handles) | |
645 userDecides(handles, true) | |
646 otherwise | |
647 % userDoesNotHearTarget(handles) | |
648 userDecides(handles, false) | |
649 end | |
650 otherwise | |
651 % single interval | |
652 % 0 or 1 are treated as equivalent (i.e. target is not heard) | |
653 userDecides(handles, false) | |
654 end | |
655 % return to pushButton1 callback | |
656 | |
657 % ----------------------------------------------------- userSelects2 | |
658 function userSelects2(handles) | |
659 global experiment withinRuns | |
660 switch experiment.threshEstMethod | |
661 case {'2I2AFC++', '2I2AFC+++'} | |
662 switch withinRuns.stimulusOrder | |
663 case 'targetSecond'; | |
664 % userDoesNotHearTarget(handles) | |
665 userDecides(handles, true) | |
666 otherwise | |
667 % userHearsTarget(handles) | |
668 userDecides(handles, false) | |
669 end | |
670 otherwise | |
671 % single interval (2 targets heard) | |
672 userDecides(handles, true) | |
673 end | |
674 % return to pushButton2 callback | |
675 | |
676 % ----------------------------------------------------- ---- userDecides | |
677 function userDecides(handles, saidYes) | |
678 global experiment stimulusParameters betweenRuns withinRuns | |
679 global rareEvent logistic psy levelsBinVector | |
680 | |
681 if experiment.singleShot | |
682 return | |
683 end | |
684 | |
685 % ignore click if not 'waitingForResponse' | |
686 if ~strcmp(experiment.status,'waitingForResponse') | |
687 disp('ignored click') | |
688 return | |
689 end | |
690 | |
691 % speech reception threshold | |
692 if strcmp(stimulusParameters.targetType,'digitStrings') | |
693 digitsInput=get(handles.editdigitInput,'string'); | |
694 % must be three digits | |
695 if ~(length(digitsInput)==3) | |
696 addToMsg(['error message: Wrong no of digits'], 0, 1) | |
697 set(handles.textMSG,'string', 'Wrong no of digits', ... | |
698 'BackgroundColor','r', 'ForegroundColor', 'w') | |
699 set(handles.editdigitInput,'string','') | |
700 | |
701 return | |
702 end | |
703 % obtain correct answer from file name | |
704 x=stimulusParameters.digitString; | |
705 idx=find(x=='O'); x(idx)='0'; % replace 'oh' with zero | |
706 | |
707 disp([x ' ' digitsInput]) | |
708 | |
709 if x==digitsInput | |
710 saidYes=1; | |
711 else | |
712 saidYes=0; | |
713 end | |
714 set(handles.editdigitInput,'string','') | |
715 set(handles.editdigitInput,'visible','off') | |
716 pause(0.1) | |
717 end | |
718 | |
719 | |
720 | |
721 % no button presses accepted while processing | |
722 experiment.status='processingResponse'; | |
723 | |
724 % catch trials. Restart trial if caught | |
725 if withinRuns.catchTrial | |
726 if saidYes | |
727 disp('catch trial - caught out') | |
728 withinRuns.caughtOut=withinRuns.caughtOut+1; | |
729 | |
730 % special: estimate caught out rate by allowing the trial | |
731 % to continue after catch | |
732 if stimulusParameters.catchTrialBaseRate==0.5 | |
733 % To use this facility, set the catchTrialRate and the | |
734 % catchTrialBaseRate both to 0.5 | |
735 % update false positive rate | |
736 betweenRuns.caughtOut(betweenRuns.runNumber)=... | |
737 withinRuns.caughtOut; | |
738 plotProgressThisTrial(handles) | |
739 nextStimulus(handles); | |
740 return | |
741 end | |
742 | |
743 % Punishment: restart the trial | |
744 set(handles.frame1,'backgroundColor','r') | |
745 set(handles.pushbuttonGO, ... | |
746 'visible','on', 'backgroundcolor','y') % and go again | |
747 msg=[{'Start again: catch trial error'}, {' '},... | |
748 {'Please,click on the GO button'}]; | |
749 set(handles.textMSG,'string',msg) | |
750 [y,fs]=wavread('ding.wav'); | |
751 wavplay(y/100,fs) | |
752 | |
753 % raise catch trial rate temporarily. | |
754 % this is normally reduced on each new trial (see GO) | |
755 stimulusParameters.catchTrialRate=... | |
756 stimulusParameters.catchTrialRate+0.1; | |
757 if stimulusParameters.catchTrialRate>0.5 | |
758 stimulusParameters.catchTrialRate=0.5; | |
759 end | |
760 fprintf('stimulusParameters.catchTrialRate= %6.3f\n', ... | |
761 stimulusParameters.catchTrialRate) | |
762 | |
763 betweenRuns.caughtOut(betweenRuns.runNumber)=... | |
764 1+betweenRuns.caughtOut(betweenRuns.runNumber); | |
765 betweenRuns.runNumber=betweenRuns.runNumber-1; | |
766 experiment.status='waitingForGO'; | |
767 return % unwind and wait for button press | |
768 else % (said No) | |
769 % user claims not to have heard target. fortunate as it was not | |
770 % present. So, repeat the stimulus (possibly with target) | |
771 % and behave as if the last trial did not occur | |
772 errormsg=nextStimulus(handles); | |
773 | |
774 % terminate if there is any kind of problem | |
775 if ~isempty(errormsg) | |
776 % e.g. limits exceeded, clipping | |
777 disp(['Error nextStimulus: ' errormsg]) | |
778 runCompleted(handles) | |
779 return | |
780 end | |
781 return % no further action - next trial | |
782 end | |
783 end | |
784 | |
785 % This section analyses the responses, makes tracks and defines next stim. | |
786 | |
787 % Define response and update response list | |
788 if saidYes | |
789 % target was heard, so response=1; | |
790 withinRuns.responseList=[withinRuns.responseList 1]; % 'heard it!' | |
791 else | |
792 % target was not hear heard, so response=0; | |
793 withinRuns.responseList=[withinRuns.responseList 0]; | |
794 end | |
795 withinRuns.levelList=[withinRuns.levelList withinRuns.variableValue]; | |
796 trialNumber=length(withinRuns.responseList); | |
797 | |
798 % keep track of peaks and troughs; | |
799 % identify direction of change during initial period | |
800 if saidYes | |
801 % default step size before first reversal | |
802 WRVinitialStep=-stimulusParameters.WRVinitialStep; | |
803 WRVsmallStep=-stimulusParameters.WRVsmallStep; | |
804 % if the previous direction was 'less difficult', this must be a peak | |
805 if strcmp(withinRuns.direction,'less difficult') ... | |
806 && length(withinRuns.levelList)>1 | |
807 withinRuns.peaks=[withinRuns.peaks withinRuns.variableValue]; | |
808 end | |
809 withinRuns.direction='more difficult'; | |
810 | |
811 else | |
812 % said 'no' | |
813 % default step size before first reversal | |
814 WRVinitialStep=stimulusParameters.WRVinitialStep; | |
815 WRVsmallStep=stimulusParameters.WRVsmallStep; | |
816 | |
817 % if the previous direction was 'up', this must be a peak | |
818 if strcmp(withinRuns.direction,'more difficult') ... | |
819 && length(withinRuns.levelList)>1 | |
820 withinRuns.troughs=[withinRuns.troughs withinRuns.variableValue]; | |
821 end | |
822 withinRuns.direction='less difficult'; | |
823 end | |
824 | |
825 % phase 2 is all the levels after and incuding the first reversal | |
826 % plus the level before that | |
827 if ~withinRuns.nowInPhase2 && length(withinRuns.peaks)+ ... | |
828 length(withinRuns.troughs)>0 | |
829 % if ~withinRuns.nowInPhase2 && (~isempty(withinRuns.peaks) ... | |
830 % || ~isempty(withinRuns.troughs)) | |
831 % define phase 2 | |
832 withinRuns.beginningOfPhase2=trialNumber-1; | |
833 withinRuns.nowInPhase2=1; | |
834 WRVsmallStep=WRVinitialStep/2; | |
835 end | |
836 | |
837 if withinRuns.nowInPhase2 | |
838 % keep a record of all levels and responses in phase 2 only | |
839 withinRuns.levelsPhaseTwo=... | |
840 withinRuns.levelList(withinRuns.beginningOfPhase2:end); | |
841 withinRuns.responsesPhaseTwo=... | |
842 withinRuns.responseList(withinRuns.beginningOfPhase2:end); | |
843 else | |
844 withinRuns.levelsPhaseTwo=[]; | |
845 end | |
846 | |
847 | |
848 % get (or substitute) threshold estimate | |
849 switch experiment.threshEstMethod | |
850 case {'2I2AFC++', '2I2AFC+++'} | |
851 % for plotting psychometric function only | |
852 if withinRuns.beginningOfPhase2>0 | |
853 [psy, levelsBinVector, logistic, rareEvent]= ... | |
854 bestFitPsychometicFunctions... | |
855 (withinRuns.levelsPhaseTwo, withinRuns.responsesPhaseTwo); | |
856 end | |
857 | |
858 if ~isempty(withinRuns.peaks) && ~isempty(withinRuns.troughs) | |
859 thresholdEstimate= ... | |
860 mean([mean(withinRuns.peaks) mean(withinRuns.troughs)]); | |
861 else | |
862 thresholdEstimate=NaN; | |
863 end | |
864 otherwise | |
865 % single interval methods | |
866 try | |
867 % using the s trial after the first reversal | |
868 [psy, levelsBinVector, logistic, rareEvent]= ... | |
869 bestFitPsychometicFunctions(withinRuns.levelsPhaseTwo,... | |
870 withinRuns.responsesPhaseTwo); | |
871 catch | |
872 logistic.bestThreshold=NaN; | |
873 end | |
874 end | |
875 | |
876 if withinRuns.nowInPhase2 | |
877 % save tracks of threshold estimates for plotting andprinting | |
878 switch experiment.functionEstMethod | |
879 case {'logisticLS', 'logisticML'} | |
880 if withinRuns.nowInPhase2 | |
881 withinRuns.meanEstTrack=... | |
882 [withinRuns.meanEstTrack ... | |
883 mean(withinRuns.levelsPhaseTwo)]; | |
884 withinRuns.thresholdEstimateTrack=... | |
885 [withinRuns.thresholdEstimateTrack ... | |
886 logistic.bestThreshold]; | |
887 end | |
888 case 'rareEvent' | |
889 withinRuns.meanEstTrack=... | |
890 [withinRuns.meanEstTrack rareEvent.thresholddB]; | |
891 withinRuns.thresholdEstimateTrack=... | |
892 [withinRuns.thresholdEstimateTrack logistic.bestThreshold]; | |
893 case 'peaksAndTroughs' | |
894 withinRuns.meanEstTrack=... | |
895 [withinRuns.meanEstTrack thresholdEstimate]; | |
896 withinRuns.thresholdEstimateTrack=... | |
897 [withinRuns.thresholdEstimateTrack thresholdEstimate]; | |
898 end | |
899 end | |
900 | |
901 % special discomfort condition | |
902 % run is completed when subject hits '2' button | |
903 switch experiment.paradigm | |
904 case 'discomfort' | |
905 if saidYes | |
906 runCompleted(handles) | |
907 return | |
908 end | |
909 end | |
910 | |
911 % choose the next level for the stimulus | |
912 switch experiment.threshEstMethod | |
913 case {'2I2AFC++', '2I2AFC+++'} | |
914 if saidYes | |
915 [WRVinitialStep, msg]=Levitt2('hit', withinRuns.variableValue); | |
916 else | |
917 [WRVinitialStep, msg]=Levitt2('miss',withinRuns.variableValue); | |
918 end | |
919 | |
920 % empty message means continue as normal | |
921 if ~isempty(msg) | |
922 runCompleted(handles) | |
923 return | |
924 end | |
925 newWRVvalue=withinRuns.variableValue-WRVinitialStep; | |
926 | |
927 case {'MaxLikelihood', 'oneIntervalUpDown'} | |
928 % run completed by virtue of number of trials | |
929 % or restart because listener is in trouble | |
930 if length(withinRuns.levelsPhaseTwo)== experiment.maxTrials | |
931 % Use bonomial test to decide if there is an imbalance in the | |
932 % number of 'yes'es and 'no's | |
933 yesCount=sum(withinRuns.responseList); | |
934 noCount=length(withinRuns.responseList)-yesCount; | |
935 z=abs(yesCount-noCount)/(yesCount+noCount)^0.5; | |
936 if z>1.96 | |
937 betweenRuns.resets=betweenRuns.resets+1; | |
938 disp([ 'reset / z= ' num2str( z) ... | |
939 ' Nresets= ' num2str( betweenRuns.resets) ] ) | |
940 withinRuns.peaks=[]; | |
941 withinRuns.troughs=[]; | |
942 withinRuns.levelList=withinRuns.levelList(end); | |
943 withinRuns.meanEstTrack=withinRuns.meanEstTrack(end); | |
944 withinRuns.forceThreshold=NaN; | |
945 withinRuns.responseList=withinRuns.responseList(end); | |
946 withinRuns.beginningOfPhase2=0; | |
947 withinRuns.nowInPhase2=0; | |
948 withinRuns.thresholdEstimateTrack=... | |
949 withinRuns.thresholdEstimateTrack(end); | |
950 else | |
951 runCompleted(handles) | |
952 return | |
953 end | |
954 end | |
955 | |
956 % set new value for WRV | |
957 if withinRuns.nowInPhase2 | |
958 % phase 2 | |
959 currentMeanEst=withinRuns.thresholdEstimateTrack(end); | |
960 switch experiment.threshEstMethod | |
961 case 'MaxLikelihood' | |
962 newWRVvalue=currentMeanEst; | |
963 case {'oneIntervalUpDown'} | |
964 newWRVvalue=withinRuns.variableValue+WRVsmallStep; | |
965 end | |
966 else | |
967 % phase 1 | |
968 if withinRuns.variableValue+2*WRVinitialStep>... | |
969 stimulusParameters.WRVlimits(2) | |
970 % use smaller steps when close to maximum | |
971 WRVinitialStep=WRVinitialStep/2; | |
972 end | |
973 newWRVvalue=withinRuns.variableValue+WRVinitialStep; | |
974 end | |
975 otherwise | |
976 error( 'assessment method not recognised') | |
977 end | |
978 | |
979 switch experiment.paradigm | |
980 % prevent unrealistic gap durations 'gapDetection' tasks. | |
981 % Note that the gap begins when the ramp ends not when stimulus ends | |
982 case 'gapDetection' | |
983 if newWRVvalue<-2*stimulusParameters.rampDuration | |
984 newWRVvalue=-2*stimulusParameters.rampDuration; | |
985 addToMsg('gap duration fixed at - 2 * ramp!',1, 1) | |
986 end | |
987 end | |
988 | |
989 withinRuns.variableValue=newWRVvalue; | |
990 withinRuns.trialNumber=withinRuns.trialNumber+1; | |
991 | |
992 % Trial continues | |
993 plotProgressThisTrial(handles) | |
994 | |
995 % next stimulus and so the cycle continues | |
996 errormsg=nextStimulus(handles); | |
997 % after the stimulus is presented, control returns here and the system | |
998 % waits for user action. | |
999 | |
1000 % terminate if there is any kind of problem | |
1001 if ~isempty(errormsg) | |
1002 % e.g. limits exceeded, clipping | |
1003 disp(['Error nextStimulus: ' errormsg]) | |
1004 runCompleted(handles) | |
1005 return | |
1006 end | |
1007 | |
1008 % ------------------------------------------------ userSelectsPleaseRepeat | |
1009 function userSelectsPleaseRepeat(handles) | |
1010 global experiment withinRuns | |
1011 % ignore click if not 'waitingForResponse' | |
1012 if ~strcmp(experiment.status,'waitingForResponse') | |
1013 disp('ignored click') | |
1014 return | |
1015 end | |
1016 % Take no action other than to make a | |
1017 % tally of repeat requests | |
1018 experiment.pleaseRepeat=experiment.pleaseRepeat+1; | |
1019 withinRuns.thisIsRepeatTrial=1; | |
1020 nextStimulus(handles); | |
1021 | |
1022 % ------------------------------------------------ userSelectsWrongButton | |
1023 function userSelectsWrongButton(handles) | |
1024 global withinRuns experiment | |
1025 % restart is the simplest solution for a 'wrong button' request | |
1026 withinRuns.wrongButton=withinRuns.wrongButton+1; | |
1027 set(handles.pushbuttonGO, 'visible','on', 'backgroundcolor','y') | |
1028 msg=[{'Start again: wrong button pressed'}, {' '},... | |
1029 {'Please,click on the GO button'}]; | |
1030 set(handles.textMSG,'string',msg) | |
1031 experiment.status='waitingForGO'; | |
1032 | |
1033 % ------------------------------------------------- plotProgressThisTrial | |
1034 function plotProgressThisTrial(handles) | |
1035 | |
1036 % used for all responses | |
1037 global experiment stimulusParameters betweenRuns withinRuns expGUIhandles | |
1038 global psy levelsBinVector binFrequencies rareEvent logistic statsModel | |
1039 | |
1040 | |
1041 % plot the levelTrack and the threshold track | |
1042 | |
1043 % Panel 2 | |
1044 % plot the levelList | |
1045 axes(expGUIhandles.axes2); cla | |
1046 plot( withinRuns.levelList,'o','markerFaceColor','k'), hold on | |
1047 % plot the best threshold estimate tracks | |
1048 if length(withinRuns.meanEstTrack)>=1 | |
1049 % The length of the levelList is 2 greater than number of thresholds | |
1050 ptr=withinRuns.beginningOfPhase2+1; | |
1051 plot(ptr: ptr+length(withinRuns.meanEstTrack)-1, ... | |
1052 withinRuns.meanEstTrack, 'r') | |
1053 plot( ptr: ptr+length(withinRuns.thresholdEstimateTrack)-1, ... | |
1054 withinRuns.thresholdEstimateTrack, 'g') | |
1055 hold off | |
1056 estThresh=withinRuns.thresholdEstimateTrack(end); | |
1057 switch experiment.threshEstMethod | |
1058 % add appropriate labels to subject GUI buttons | |
1059 case {'2I2AFC++', '2I2AFC+++'} | |
1060 title([stimulusParameters.WRVname ' = ' ... | |
1061 num2str(withinRuns.variableValue, '%5.1f')]) | |
1062 otherwise | |
1063 title([stimulusParameters.WRVname ' = ' ... | |
1064 num2str(withinRuns.variableValue, '%5.1f') ... | |
1065 '; TH= ' num2str(estThresh, '%5.1f')]) | |
1066 end | |
1067 end | |
1068 xlim([0 experiment.maxTrials+withinRuns.beginningOfPhase2]); | |
1069 ylim(stimulusParameters.WRVlimits) | |
1070 grid on | |
1071 | |
1072 % Panel 4: Summary of threshold estimates (not used here) | |
1073 % Earlier estimates are set in 'runCompleted' | |
1074 % However, title shows runs/trials remaining | |
1075 | |
1076 axes(expGUIhandles.axes4) | |
1077 runsToGo=length(betweenRuns.var1Sequence)-betweenRuns.runNumber; | |
1078 if withinRuns.beginningOfPhase2>0 | |
1079 trialsToGo= experiment.singleIntervalMaxTrials(1) ... | |
1080 + withinRuns.beginningOfPhase2- withinRuns.trialNumber; | |
1081 title(['trials remaining = ' num2str(trialsToGo) ... | |
1082 ': runs to go= ' num2str(runsToGo)]) | |
1083 end | |
1084 | |
1085 % plot psychometric function - panel 5 | |
1086 axes(expGUIhandles.axes5), cla | |
1087 plot(withinRuns.levelList, withinRuns.responseList,'b.'), hold on | |
1088 ylim([0 1]) | |
1089 title('') | |
1090 | |
1091 switch experiment.threshEstMethod | |
1092 case {'MaxLikelihood', 'oneIntervalUpDown'} | |
1093 if withinRuns.beginningOfPhase2>0 | |
1094 % display only when in phase 2. | |
1095 withinRuns.levelsPhaseTwo=... | |
1096 withinRuns.levelList(withinRuns.beginningOfPhase2:end); | |
1097 withinRuns.responsesPhaseTwo=... | |
1098 withinRuns.responseList(withinRuns.beginningOfPhase2:end); | |
1099 | |
1100 % organise data as psychometric function | |
1101 [psy, levelsBinVector, binFrequencies]= ... | |
1102 psychometricFunction(withinRuns.levelsPhaseTwo,... | |
1103 withinRuns.responsesPhaseTwo, experiment.psyBinWidth); | |
1104 | |
1105 % Plot the function | |
1106 % point by point with circles of appropiate weighted size | |
1107 hold on, | |
1108 for i=1:length(psy) | |
1109 plot(levelsBinVector(i), psy(i), 'ro', ... | |
1110 'markersize', 50*binFrequencies(i)/sum(binFrequencies)) | |
1111 end | |
1112 % save info for later | |
1113 betweenRuns.psychometicFunction{betweenRuns.runNumber}=... | |
1114 [levelsBinVector; psy]; | |
1115 | |
1116 % fitPsychometric functions is computed in 'userDecides' | |
1117 % plot(rareEvent.predictionLevels, rareEvent.predictionsRE,'k') | |
1118 plot(logistic.predictionLevels, logistic.predictionsLOG, 'r') | |
1119 plot(rareEvent.predictionLevels, rareEvent.predictionsRE, 'k') | |
1120 if ~isnan(logistic.bestThreshold ) | |
1121 % xlim([ (logistic.bestThreshold -20) ... | |
1122 % (logistic.bestThreshold +20) ]) | |
1123 xlim([ 0 100 ]) | |
1124 % if logistic.bestK< max(experiment.possLogSlopes) | |
1125 title(['k= ' num2str(logistic.bestK, '%6.2f') ' g= '... | |
1126 num2str(rareEvent.bestGain,'%6.3f') ' A=' ... | |
1127 num2str(rareEvent.bestVMin,'%8.1f')]) | |
1128 % title('') | |
1129 % end | |
1130 else | |
1131 title(' ') | |
1132 end | |
1133 | |
1134 switch experiment.ear | |
1135 %plot green line for statsModel a priori model | |
1136 case 'statsModelLogistic' | |
1137 % plot proTem logistic (green) used by stats model | |
1138 p= 1./(1+exp(-statsModel.logisticSlope... | |
1139 *(levelsBinVector-logistic.bestThreshold))); | |
1140 if experiment.psyFunSlope<0, p=1-p;end | |
1141 titleText=[ ', statsModel: logistic']; | |
1142 hold on, plot(levelsBinVector, p,'g') | |
1143 case 'statsModelRareEvent' | |
1144 pressure=28*10.^(levelsBinVector/20); | |
1145 p=1-exp(-stimulusParameters.targetDuration... | |
1146 *(statsModel.rareEvenGain... | |
1147 * pressure-statsModel.rareEventVmin)); | |
1148 p(p<0)=0; | |
1149 if experiment.psyFunSlope<0, p=1-p;end | |
1150 hold on, plot(levelsBinVector, p,'g') | |
1151 end %(estMethod) | |
1152 end | |
1153 otherwise % 2A2IFC | |
1154 | |
1155 message3= ... | |
1156 ([ 'peaks=' num2str(withinRuns.peaks) ... | |
1157 'troughs=' num2str(withinRuns.troughs)]); | |
1158 ylimRM([-0.1 1.1]) % 0=no / 1=yes | |
1159 set(gca,'ytick',[0 1], 'yTickLabel', {'no';'yes'}) | |
1160 ylabel('psychometric function'), xlabel('target level') | |
1161 if length(levelsBinVector)>1 | |
1162 xlim([ min(levelsBinVector) max(levelsBinVector)]) | |
1163 xlim([ 0 100]) | |
1164 end | |
1165 end | |
1166 | |
1167 % command window summary | |
1168 % Accumulate things to say in the message window | |
1169 message1= (['responses: ' num2str(withinRuns.responseList,'%9.0f')]); | |
1170 switch experiment.paradigm | |
1171 % more decimal places needed on GUI | |
1172 case { 'gapDetection', 'frequencyDiscrimination', 'forwardMaskingD'} | |
1173 message2= ([stimulusParameters.WRVname ... | |
1174 ': ' num2str(withinRuns.levelList,'%7.3f')]); | |
1175 message3= (['Thresh (logistic mean): ' ... | |
1176 num2str(withinRuns.thresholdEstimateTrack,'%7.3f')]); | |
1177 otherwise | |
1178 message2= ([stimulusParameters.WRVname ': ' ... | |
1179 num2str(withinRuns.levelList,'%7.1f')]); | |
1180 message3= (['Thresh (logistic mean): ' ... | |
1181 num2str(withinRuns.thresholdEstimateTrack,'%7.1f')]); | |
1182 end | |
1183 | |
1184 addToMsg(str2mat(message1, message2, message3), 0) | |
1185 | |
1186 % -----------------------------------------------------runCompleted | |
1187 function runCompleted(handles) | |
1188 % Used at the end of each run | |
1189 global experiment stimulusParameters betweenRuns withinRuns | |
1190 global rareEvent expGUIhandles | |
1191 % disp('run completed') | |
1192 | |
1193 experiment.status='runCompleted'; | |
1194 | |
1195 plotProgressThisTrial(handles) | |
1196 | |
1197 switch experiment.ear | |
1198 case {'statsModelLogistic', 'statsModelRareEvent','MAPmodel', ... | |
1199 'MAPmodelMultiCh','MAPmodelSingleCh', 'MAPmodelListen'} | |
1200 % no changes required if model used | |
1201 otherwise | |
1202 set(handles.frame1,'visible','off') | |
1203 set(handles.pushbuttoNotSure,'visible','off') | |
1204 set(handles.pushbuttonWrongButton,'visible','off') | |
1205 set(handles.pushbutton3,'visible','off') | |
1206 set(handles.pushbutton2,'visible','off') | |
1207 set(handles.pushbutton1,'visible','off') | |
1208 set(handles.pushbutton0,'visible','off') | |
1209 set(handles.pushbuttonGO,'visible','off') | |
1210 end | |
1211 | |
1212 if isnan(withinRuns.forceThreshold) | |
1213 % the experiment has been aborted for some reason | |
1214 threshold=withinRuns.forceThreshold; | |
1215 stdev=NaN; | |
1216 logistic.bestK=NaN; | |
1217 logistic.bestThreshold=NaN; | |
1218 medianThreshold=NaN; | |
1219 meanThreshold=NaN; | |
1220 else | |
1221 % use only phase 2 levels and responses for calculating thresholds | |
1222 withinRuns.levelsPhaseTwo=... | |
1223 withinRuns.levelList(withinRuns.beginningOfPhase2:end); | |
1224 withinRuns.responsesPhaseTwo=... | |
1225 withinRuns.responseList(withinRuns.beginningOfPhase2:end); | |
1226 [psy, levelsPhaseTwoBinVector, logistic, rareEvent]= ... | |
1227 bestFitPsychometicFunctions... | |
1228 (withinRuns.levelsPhaseTwo, withinRuns.responsesPhaseTwo); | |
1229 | |
1230 % plot final psychometric function | |
1231 axes(expGUIhandles.axes5),cla | |
1232 hold on, plot(rareEvent.predictionLevels, rareEvent.predictionsRE, 'k') | |
1233 hold on, plot(logistic.predictionLevels, logistic.predictionsLOG, 'r') | |
1234 % organise data as psychometric function | |
1235 [psy, levelsBinVector, binFrequencies]= ... | |
1236 psychometricFunction(withinRuns.levelsPhaseTwo,... | |
1237 withinRuns.responsesPhaseTwo, experiment.psyBinWidth); | |
1238 % point by point with circles of appropiate weighted size | |
1239 hold on, | |
1240 for i=1:length(psy) | |
1241 plot(levelsBinVector(i), psy(i), 'ro', ... | |
1242 'markersize', 50*binFrequencies(i)/sum(binFrequencies)) | |
1243 end | |
1244 | |
1245 | |
1246 % experimental | |
1247 medianThreshold=median(withinRuns.levelsPhaseTwo); | |
1248 warning off | |
1249 meanThreshold=mean(withinRuns.levelsPhaseTwo); | |
1250 | |
1251 % identify the current threshold estimate | |
1252 switch experiment.paradigm | |
1253 case 'discomfort' | |
1254 % most recent value (not truely a mean value) | |
1255 threshold=withinRuns.levelList(end); | |
1256 stdev=NaN; | |
1257 otherwise | |
1258 switch experiment.threshEstMethod | |
1259 case {'MaxLikelihood', 'oneIntervalUpDown'} | |
1260 % last value in the list | |
1261 % threshold=withinRuns.meanEstTrack(end); | |
1262 threshold=withinRuns.thresholdEstimateTrack(end); | |
1263 stdev=NaN; | |
1264 | |
1265 case {'2I2AFC++', '2I2AFC+++'} | |
1266 % use peaks and troughs | |
1267 try % there may not be enough values to use | |
1268 peaksUsed=experiment.peaksUsed; | |
1269 threshold=... | |
1270 mean(... | |
1271 [withinRuns.peaks(end-peaksUsed+1:end) ... | |
1272 withinRuns.troughs(end-peaksUsed+1:end)]); | |
1273 stdev=... | |
1274 std([withinRuns.peaks(end-peaksUsed +1:end) ... | |
1275 withinRuns.troughs(end-peaksUsed:end)]); | |
1276 catch | |
1277 threshold=NaN; | |
1278 stdev=NaN; | |
1279 end | |
1280 end | |
1281 end | |
1282 end | |
1283 | |
1284 % Store thresholds | |
1285 betweenRuns.thresholds=[betweenRuns.thresholds threshold]; | |
1286 betweenRuns.thresholds_mean=[betweenRuns.thresholds_mean meanThreshold]; | |
1287 betweenRuns.thresholds_median=... | |
1288 [betweenRuns.thresholds_median medianThreshold]; | |
1289 betweenRuns.forceThresholds=... | |
1290 [betweenRuns.forceThresholds withinRuns.forceThreshold]; | |
1291 | |
1292 % count observations after the startup phase for record keeping | |
1293 betweenRuns.observationCount=... | |
1294 [betweenRuns.observationCount length(withinRuns.levelList)]; | |
1295 betweenRuns.timesOfFirstReversals=... | |
1296 [betweenRuns.timesOfFirstReversals withinRuns.beginningOfPhase2]; | |
1297 betweenRuns.catchTrials=... | |
1298 [betweenRuns.catchTrials withinRuns.catchTrialCount]; | |
1299 | |
1300 % add variable length tracks to cell arrays | |
1301 if withinRuns.beginningOfPhase2>0 | |
1302 betweenRuns.bestThresholdTracks{length(betweenRuns.thresholds)}=... | |
1303 withinRuns.thresholdEstimateTrack; | |
1304 betweenRuns.levelTracks{length(betweenRuns.thresholds)}=... | |
1305 withinRuns.levelList(withinRuns.beginningOfPhase2:end); | |
1306 betweenRuns.responseTracks{length(betweenRuns.thresholds)}=... | |
1307 withinRuns.responseList(withinRuns.beginningOfPhase2:end); | |
1308 else | |
1309 betweenRuns.bestThresholdTracks{length(betweenRuns.thresholds)}=[]; | |
1310 betweenRuns.levelTracks{length(betweenRuns.thresholds)}=[]; | |
1311 betweenRuns.responseTracks{length(betweenRuns.thresholds)}=[]; | |
1312 end | |
1313 | |
1314 betweenRuns.bestGain=[betweenRuns.bestGain rareEvent.bestGain]; | |
1315 betweenRuns.bestVMin=[betweenRuns.bestVMin rareEvent.bestVMin]; | |
1316 betweenRuns.bestPaMin=[betweenRuns.bestPaMin rareEvent.bestPaMindB]; | |
1317 betweenRuns.bestLogisticM=... | |
1318 [betweenRuns.bestLogisticM logistic.bestThreshold]; | |
1319 betweenRuns.bestLogisticK=[betweenRuns.bestLogisticK logistic.bestK]; | |
1320 | |
1321 resultsSoFar=[betweenRuns.var1Sequence(betweenRuns.runNumber)'... | |
1322 betweenRuns.var2Sequence(betweenRuns.runNumber)'... | |
1323 betweenRuns.thresholds(betweenRuns.runNumber)' | |
1324 ]; | |
1325 | |
1326 fprintf('%10.3f \t%10.3f \t%10.1f \n', resultsSoFar') | |
1327 | |
1328 switch experiment.ear | |
1329 case {'left', 'right', 'diotic', 'dichoticLeft','dichoticRight'} | |
1330 disp(['caught out= ' num2str(betweenRuns.caughtOut)]) | |
1331 end | |
1332 | |
1333 % plot history of thresholds in panel 4 | |
1334 axes(expGUIhandles.axes4), cla | |
1335 plotColors='rgbmckywrgbmckyw'; | |
1336 for i=1:length(betweenRuns.thresholds) | |
1337 faceColor=plotColors(floor(i/length(betweenRuns.variableList1)-.01)+1); | |
1338 switch betweenRuns.variableName1 | |
1339 case {'targetFrequency', 'maskerRelativeFrequency'} | |
1340 if min(betweenRuns.var1Sequence)>0 | |
1341 % semilogx(betweenRuns.var1Sequence(1:betweenRuns.runNumber), ... | |
1342 % betweenRuns.thresholds, 'o', ... | |
1343 % 'markerSize', 5,'markerFaceColor',faceColor) | |
1344 semilogx(betweenRuns.var1Sequence(i), ... | |
1345 betweenRuns.thresholds(i), 'o', ... | |
1346 'markerSize', 5,'markerFaceColor',faceColor) | |
1347 else | |
1348 plot(betweenRuns.var1Sequence(1:betweenRuns.runNumber), ... | |
1349 betweenRuns.thresholds, 'o', ... | |
1350 'markerSize', 5,'markerFaceColor',faceColor) | |
1351 plot(betweenRuns.var1Sequence(i), ... | |
1352 betweenRuns.thresholds(i), 'o', ... | |
1353 'markerSize', 5,'markerFaceColor',faceColor) | |
1354 end | |
1355 otherwise | |
1356 % plot(betweenRuns.var1Sequence(1:betweenRuns.runNumber), ... | |
1357 % betweenRuns.thresholds, 'o', 'markerSize', 5,... | |
1358 % 'markerFaceColor',faceColor) | |
1359 plot(betweenRuns.var1Sequence(i), ... | |
1360 betweenRuns.thresholds(i), 'o', 'markerSize', 5,... | |
1361 'markerFaceColor',faceColor) | |
1362 end | |
1363 hold on | |
1364 end | |
1365 xlimRM([ min(betweenRuns.variableList1) max(betweenRuns.variableList1) ]) | |
1366 ylim(stimulusParameters.WRVlimits) | |
1367 ylabel('thresholds') | |
1368 xlabel(betweenRuns.variableName1) | |
1369 set(gca,'ytick', [0 20 40 60 80 100]) | |
1370 try | |
1371 % problems if only one x value | |
1372 set(gca,'XTick', sort(betweenRuns.variableList1)) | |
1373 catch | |
1374 end | |
1375 grid on, set(gca,'XMinorGrid', 'off') | |
1376 | |
1377 % If comparison data is available in pearmeter file, plot it now | |
1378 if ~isempty (experiment.comparisonData) | |
1379 comparisonData=experiment.comparisonData(:,1:end-1); % ignore final BF | |
1380 [x, ncols]=size(comparisonData); | |
1381 if length(betweenRuns.variableList1)==ncols | |
1382 hold on | |
1383 plot (sort(betweenRuns.variableList1), comparisonData, 'r') | |
1384 hold off | |
1385 end | |
1386 end | |
1387 | |
1388 % End of the Experiment also? | |
1389 if betweenRuns.runNumber==length(betweenRuns.var1Sequence) | |
1390 % yes, end of experiment | |
1391 fileName=['savedData/' experiment.name experiment.date ... | |
1392 experiment.paradigm]; | |
1393 % save (fileName, 'experiment', 'stimulusParameters', 'betweenRuns', 'withinRuns', 'variableNames', 'paradigmNames', 'LevittControl') | |
1394 disp('Experiment completed') | |
1395 | |
1396 % update subject GUI to acknowledge end of run | |
1397 subjGUImsg=[{'Experiment completed'}, {' '}, {'Thank you!'}]; | |
1398 set(handles.textMSG,'string', subjGUImsg ) | |
1399 % play 'Tada' | |
1400 [y,fs,nbits]=wavread('TADA.wav'); | |
1401 musicGain=10^(stimulusParameters.musicLeveldB/20); | |
1402 y=y*musicGain; | |
1403 wavplay(y/100,fs, 'async') | |
1404 | |
1405 % update experimenter GUI | |
1406 addToMsg('Experiment completed.',1) | |
1407 | |
1408 printReport | |
1409 experiment.status='endOfExperiment'; | |
1410 return | |
1411 else | |
1412 % No, hang on. | |
1413 switch experiment.ear | |
1414 case {'statsModelLogistic', 'statsModelRareEvent','MAPmodel', ... | |
1415 'MAPmodelMultiCh','MAPmodelSingleCh', 'MAPmodelListen'} | |
1416 % no changes required if model used | |
1417 otherwise | |
1418 % decrement catchTrialRate towards baseRate | |
1419 stimulusParameters.catchTrialRate=... | |
1420 stimulusParameters.catchTrialBaseRate + ... | |
1421 (stimulusParameters.catchTrialRate... | |
1422 -stimulusParameters.catchTrialBaseRate)... | |
1423 *(1-exp(-stimulusParameters.catchTrialTimeConstant)); | |
1424 fprintf('stimulusParameters.catchTrialRate= %6.3f\n', ... | |
1425 stimulusParameters.catchTrialRate) | |
1426 | |
1427 % and go again | |
1428 set(handles.pushbuttonGO,'backgroundcolor','y') | |
1429 set(handles.frame1,'visible','off') | |
1430 set(handles.pushbuttonGO,'visible','on') | |
1431 msg=[{'Ready to start new trial'}, {' '},... | |
1432 {'Please,click on the GO button'}]; | |
1433 set(handles.textMSG,'string',msg) | |
1434 end | |
1435 experiment.status='waitingForGO'; | |
1436 % fprintf('\n') | |
1437 | |
1438 [y,fs,nbits]=wavread('CHIMES.wav'); | |
1439 musicGain=10^(stimulusParameters.musicLeveldB/20); | |
1440 y=y*musicGain; | |
1441 wavplay(y/100,fs,'async') | |
1442 end | |
1443 | |
1444 % -----------------------------------------------------MAPmodelRunsGUI | |
1445 % The computer presses the buttons | |
1446 function MAPmodelRunsGUI(handles) | |
1447 global experiment stimulusParameters method expGUIhandles | |
1448 global AN_IHCsynapseParams | |
1449 method=[]; | |
1450 | |
1451 while strcmp(experiment.status,'waitingForGO') | |
1452 % no catch trials for MAP model | |
1453 experiment.allowCatchTrials=0; | |
1454 | |
1455 % initiates run and plays first stimulus and it returns | |
1456 % without waiting for button press | |
1457 startNewRun(handles) | |
1458 | |
1459 if sum(strcmp(experiment.ear,... | |
1460 {'MAPmodelMultiCh', 'MAPmodelListen'})) | |
1461 % use BFlist specified in MAPparams file | |
1462 BFlist= -1; | |
1463 else | |
1464 BFlist=stimulusParameters.targetFrequency; | |
1465 end | |
1466 showParams=0; | |
1467 % find model parameters using the 'name' box (e.g. CTa ->MAPparamsCTa) | |
1468 paramFunctionName=['method=MAPparams' experiment.name ... | |
1469 '(BFlist, stimulusParameters.sampleRate, showParams);']; | |
1470 eval(paramFunctionName) % go and fetch the parameters | |
1471 | |
1472 | |
1473 % show sample Rate on GUI; it must be set in MAPparams | |
1474 set(expGUIhandles.textsampleRate,'string',... | |
1475 num2str(stimulusParameters.sampleRate)) | |
1476 | |
1477 if experiment.singleShot | |
1478 % AN_IHCsynapseParams.showSummaryStatistics=1; | |
1479 method.showSummaryStatistics=1; | |
1480 AN_IHCsynapseParams.plotSynapseContents=1; | |
1481 else | |
1482 method.showSummaryStatistics=0; | |
1483 AN_IHCsynapseParams.plotSynapseContents=0; | |
1484 end | |
1485 | |
1486 if strcmp(experiment.ear, 'MAPmodelSingleCh') | |
1487 method.MGmembranePotentialSave=1; | |
1488 end | |
1489 | |
1490 % continuous loop until the program stops itself | |
1491 while strcmp(experiment.status,'waitingForResponse') | |
1492 % NB at this point the stimulus has been played | |
1493 pause(0.1) % to allow interrupt with CTRL/C | |
1494 | |
1495 switch experiment.ear | |
1496 case { 'MAPmodelListen'} | |
1497 set(handles.pushbutton1,'backgroundcolor','y','visible','on') | |
1498 set(handles.pushbutton2,'backgroundcolor','y','visible','on') | |
1499 end | |
1500 | |
1501 AN_IHCsynapseParams.mode= 'spikes'; | |
1502 | |
1503 % Analayse the current stimulus using MAP | |
1504 [modelResponse earObject]= MAPmodel( experiment.MAPplot, method); | |
1505 | |
1506 if experiment.stop || experiment.singleShot | |
1507 % trap for single trial or user interrupt using 'stop' button. | |
1508 experiment.status= 'waitingForStart'; | |
1509 experiment.stop=0; | |
1510 addToMsg('manually stopped',1); | |
1511 return | |
1512 end | |
1513 | |
1514 switch modelResponse | |
1515 case 1 | |
1516 % userDoesNotHearTarget(handles) | |
1517 switch experiment.ear | |
1518 case {'MAPmodelListen'} | |
1519 % illuminate appropriate button | |
1520 set(handles.pushbutton1,... | |
1521 'backgroundcolor','r','visible','on') | |
1522 set(handles.pushbutton2,'backgroundcolor','y') | |
1523 end | |
1524 userDecides(handles, false) | |
1525 if experiment.singleShot, return, end | |
1526 | |
1527 case 2 | |
1528 % userHearsTarget(handles) | |
1529 switch experiment.ear | |
1530 case {'MAPmodelListen'} | |
1531 % illuminate appropriate button (DEMO only) | |
1532 set(handles.pushbutton2,'backgroundcolor',... | |
1533 'r','visible','on') | |
1534 set(handles.pushbutton1,'backgroundcolor','y') | |
1535 end | |
1536 | |
1537 switch experiment.paradigm | |
1538 case 'discomfort' | |
1539 % always treat discomfort as 'not heard' | |
1540 userDecides(handles, false) | |
1541 otherwise | |
1542 userDecides(handles, true) | |
1543 end | |
1544 otherwise | |
1545 % probably an abort | |
1546 return | |
1547 end | |
1548 end | |
1549 end | |
1550 | |
1551 function [modelResponse, MacGregorResponse]=MAPmodel( MAPplot, method) | |
1552 | |
1553 global experiment stimulusParameters audio withinRuns | |
1554 global outerMiddleEarParams DRNLParams AN_IHCsynapseParams | |
1555 | |
1556 savePath=path; | |
1557 addpath(['..' filesep 'MAP'], ['..' filesep 'utilities']) | |
1558 modelResponse=[]; | |
1559 MacGregorResponse=[]; | |
1560 | |
1561 % mono only (column vector) | |
1562 audio=audio(:,1)'; | |
1563 | |
1564 % if stop button pressed earlier | |
1565 if experiment.stop, return, end | |
1566 | |
1567 % -------------------------------------------------------------- run Model | |
1568 MAPparamsName=experiment.name; | |
1569 showPlotsAndDetails=experiment.MAPplot; | |
1570 AN_spikesOrProbability='spikes'; | |
1571 | |
1572 % [response, method]=MAPsequenceSeg(audio, method, 1:8); | |
1573 global ICoutput ANdt | |
1574 MAP1_14(audio, 1/method.dt, method.nonlinCF,... | |
1575 MAPparamsName, AN_spikesOrProbability); | |
1576 | |
1577 if showPlotsAndDetails | |
1578 options.printModelParameters=0; | |
1579 options.showModelOutput=1; | |
1580 options.printFiringRates=1; | |
1581 options.showACF=0; | |
1582 options.showEfferent=1; | |
1583 options.surfProbability=0; | |
1584 showMapOptions.surfSpikes=0; | |
1585 UTIL_showMAP(options) | |
1586 end | |
1587 | |
1588 % No response, probably caused by hitting 'stop' button | |
1589 if isempty(ICoutput), return, end | |
1590 | |
1591 % MacGregor response is the sum total of all final stage spiking | |
1592 MacGregorResponse= sum(ICoutput,1); % use IC | |
1593 | |
1594 % ---------------------------------------------------------- end model run | |
1595 | |
1596 dt=ANdt; | |
1597 time=dt:dt:dt*length(MacGregorResponse); | |
1598 | |
1599 % group delay on unit response | |
1600 MacGonsetDelay= 0.004; | |
1601 MacGoffsetDelay= 0.022; | |
1602 | |
1603 % now find the response of the MacGregor model during the target presentation + group delay | |
1604 switch experiment.threshEstMethod | |
1605 case {'2I2AFC++', '2I2AFC+++'} | |
1606 idx= time>stimulusParameters.testTargetBegins+MacGonsetDelay ... | |
1607 & time<stimulusParameters.testTargetEnds+MacGoffsetDelay; | |
1608 nSpikesTrueWindow=sum(MacGregorResponse(:,idx)); | |
1609 idx=find(time>stimulusParameters.testNonTargetBegins+MacGonsetDelay ... | |
1610 & time<stimulusParameters.testNonTargetEnds+MacGoffsetDelay); | |
1611 nSpikesFalseWindow=sum(MacGregorResponse(:,idx)); | |
1612 % nSpikesDuringTarget is +ve when more spikes are found | |
1613 % in the target window | |
1614 difference= nSpikesTrueWindow-nSpikesFalseWindow; | |
1615 | |
1616 if difference>0 | |
1617 % hit | |
1618 nSpikesDuringTarget=experiment.MacGThreshold+1; | |
1619 elseif difference<0 | |
1620 % miss (wrong choice) | |
1621 nSpikesDuringTarget=experiment.MacGThreshold-1; | |
1622 else | |
1623 if rand>0.5 | |
1624 % hit (random choice) | |
1625 nSpikesDuringTarget=experiment.MacGThreshold+1; | |
1626 else | |
1627 % miss (random choice) | |
1628 nSpikesDuringTarget=experiment.MacGThreshold-1; | |
1629 end | |
1630 end | |
1631 disp(['level target dummy decision: ' ... | |
1632 num2str([withinRuns.variableValue nSpikesTrueWindow ... | |
1633 nSpikesFalseWindow nSpikesDuringTarget], '%4.0f') ] ) | |
1634 | |
1635 otherwise | |
1636 % idx=find(time>stimulusParameters.testTargetBegins+MacGonsetDelay ... | |
1637 % & time<stimulusParameters.testTargetEnds+MacGoffsetDelay); | |
1638 % no delay at onset | |
1639 idx=find(time>stimulusParameters.testTargetBegins +MacGonsetDelay... | |
1640 & time<stimulusParameters.testTargetEnds+MacGoffsetDelay); | |
1641 nSpikesDuringTarget=sum(MacGregorResponse(:,idx)); | |
1642 | |
1643 % find(MacGregorResponse)*dt-stimulusParameters.stimulusDelay | |
1644 timeX=time(idx); | |
1645 end | |
1646 | |
1647 % now find the response of the MacGregor model at the end of the masker | |
1648 idx2=find(time>stimulusParameters.testTargetBegins-0.02 ... | |
1649 & time<stimulusParameters.testTargetBegins); | |
1650 if ~isempty(idx2) | |
1651 maskerRate=mean(mean(MacGregorResponse(idx2))); | |
1652 else | |
1653 %e.g. no masker | |
1654 maskerRate=0; | |
1655 end | |
1656 | |
1657 if experiment.MAPplot | |
1658 % add vertical lines to indicate target region | |
1659 figure(99), subplot(6,1,6) | |
1660 hold on | |
1661 yL=get(gca,'YLim'); | |
1662 plot([stimulusParameters.testTargetBegins + MacGonsetDelay ... | |
1663 stimulusParameters.testTargetBegins + MacGonsetDelay],yL,'r') | |
1664 plot([stimulusParameters.testTargetEnds + MacGoffsetDelay ... | |
1665 stimulusParameters.testTargetEnds + MacGoffsetDelay],yL,'r') | |
1666 end | |
1667 | |
1668 % specify unambiguous response | |
1669 switch experiment.paradigm | |
1670 case 'gapDetection' | |
1671 gapResponse=(maskerRate-nSpikesDuringTarget)/maskerRate; | |
1672 if gapResponse>0.2 | |
1673 modelResponse=2; % gap detected | |
1674 else | |
1675 modelResponse=1; % gap not detected | |
1676 end | |
1677 [nSpikesDuringTarget maskerRate gapResponse modelResponse] | |
1678 figure(22), plot(timeX,earObject(idx)) | |
1679 otherwise | |
1680 if nSpikesDuringTarget>experiment.MacGThreshold | |
1681 modelResponse=2; % stimulus detected | |
1682 else | |
1683 modelResponse=1; % nothing heard (default) | |
1684 end | |
1685 end | |
1686 | |
1687 | |
1688 path(savePath) | |
1689 | |
1690 % -----------------------------------------------------statsModelRunsGUI | |
1691 % The computer presses the buttons | |
1692 function statsModelRunsGUI(handles) | |
1693 % Decision are made at random using a prescribe statistical function | |
1694 % to set probabilities as a function of signal level. | |
1695 global experiment | |
1696 | |
1697 experiment.allowCatchTrials=0; | |
1698 | |
1699 while strcmp(experiment.status,'waitingForGO') | |
1700 % i.e. waiting for new run | |
1701 if experiment.stop | |
1702 % user has requested an abort | |
1703 experiment.status= 'waitingForStart'; | |
1704 addToMsg('manually stopped',1) | |
1705 return | |
1706 end | |
1707 | |
1708 % initiates run and plays first stimulus and it returns | |
1709 % without waiting for button press | |
1710 % NB stimulus is not actually generated (for speed) | |
1711 startNewRun(handles) | |
1712 | |
1713 while strcmp(experiment.status,'waitingForResponse') | |
1714 % create artificial response here | |
1715 modelResponse=statsModelGetResponse; | |
1716 switch modelResponse | |
1717 case 1 | |
1718 % userDoesNotHearTarget(handles) | |
1719 userDecides(handles, false) | |
1720 case 2 | |
1721 % userHearsTarget(handles) | |
1722 userDecides(handles, true) | |
1723 end | |
1724 end | |
1725 end | |
1726 | |
1727 % -----------------------------------------------------statsModelGetResponse | |
1728 function modelResponse=statsModelGetResponse(handles) | |
1729 global experiment withinRuns statsModel stimulusParameters | |
1730 % use the generating function to decide if a detection occurs or not | |
1731 | |
1732 % pause(0.1) % to allow stopping with CTRL/C but slows things down | |
1733 | |
1734 % first compute the probability that a detection occurs | |
1735 switch experiment.ear | |
1736 case {'statsModelLogistic'} | |
1737 prob= 1./(1+exp(-statsModel.logisticSlope.*(withinRuns.variableValue-statsModel.logisticMean))); | |
1738 % if experiment.psyFunSlope<0, | |
1739 % prob=1-prob; | |
1740 % end | |
1741 | |
1742 case 'statsModelRareEvent' | |
1743 if experiment.psyFunSlope<0 | |
1744 addToMsg('statsModelRareEvent cannot be used with negative slope',0) | |
1745 error('statsModelRareEvent cannot be used with negative slope') | |
1746 end | |
1747 | |
1748 % standard formula is prob = 1 – exp(-d (g P – A)) | |
1749 % here A->A; To find Pmin use A/gain | |
1750 pressure=28*10^(withinRuns.variableValue/20); | |
1751 gain=statsModel.rareEvenGain; | |
1752 A=statsModel.rareEventVmin; | |
1753 d=stimulusParameters.targetDuration; | |
1754 gP_Vmin=gain*pressure-A; | |
1755 if gP_Vmin>0 | |
1756 prob=1-exp(-d*(gP_Vmin)); | |
1757 else | |
1758 prob=0; | |
1759 end | |
1760 end | |
1761 | |
1762 % Use the probability to choose whether or not a detection has occurred | |
1763 switch experiment.threshEstMethod | |
1764 case {'MaxLikelihood', 'oneIntervalUpDown'} | |
1765 if rand<prob | |
1766 modelResponse=2; %bingo | |
1767 else | |
1768 modelResponse=1; %nothing heard | |
1769 end | |
1770 | |
1771 case {'2I2AFC++', '2I2AFC+++'} | |
1772 if rand<prob | |
1773 modelResponse=2; %bingo | |
1774 else %if the stimulus is not audible, take a 50:50 chance of getting it right | |
1775 if rand<0.5 | |
1776 modelResponse=2; %bingo | |
1777 else | |
1778 modelResponse=1; %nothing heard | |
1779 end | |
1780 end | |
1781 end | |
1782 | |
1783 | |
1784 % ------------------------------------------------------- printTabTable | |
1785 function printTabTable(M, headers) | |
1786 % printTabTable prints a matrix as a table with tabs | |
1787 %headers are optional | |
1788 %headers=strvcat('firstname', 'secondname') | |
1789 % printTabTable([1 2; 3 4],strvcat('a1','a2')); | |
1790 | |
1791 if nargin>1 | |
1792 [r c]=size(headers); | |
1793 for no=1:r | |
1794 fprintf('%s\t',headers(no,:)) | |
1795 end | |
1796 fprintf('\n') | |
1797 end | |
1798 | |
1799 [r c]=size(M); | |
1800 | |
1801 for row=1:r | |
1802 for col=1:c | |
1803 if row==1 && col==1 && M(1,1)==-1000 | |
1804 % Print nothing (tab follows below) | |
1805 else | |
1806 fprintf('%s',num2str(M(row,col))) | |
1807 end | |
1808 if col<c | |
1809 fprintf('\t') | |
1810 end | |
1811 end | |
1812 fprintf('\n') | |
1813 end | |
1814 | |
1815 % ------------------------------------------------------- xlimRM | |
1816 function xlimRM(x) | |
1817 try | |
1818 xlim([x(1) x(2)]) | |
1819 catch | |
1820 end | |
1821 | |
1822 % ------------------------------------------------------- ylimRM | |
1823 function ylimRM(x) | |
1824 try | |
1825 ylim([x(1) x(2)]) | |
1826 catch | |
1827 end | |
1828 | |
1829 | |
1830 function editdigitInput_CreateFcn(hObject, eventdata, handles) | |
1831 | |
1832 % Hint: edit controls usually have a white background on Windows. | |
1833 % See ISPC and COMPUTER. | |
1834 if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) | |
1835 set(hObject,'BackgroundColor','white'); | |
1836 end | |
1837 | |
1838 | |
1839 % -----------------------------------------------------buttonBoxIntitialize | |
1840 function buttonBoxIntitialize | |
1841 % initialize button box | |
1842 global serobj | |
1843 try | |
1844 fclose(serobj); | |
1845 catch | |
1846 end | |
1847 | |
1848 try | |
1849 serobj = serial('COM4') ; % Creating serial port object now its connected to COM4 !!! button boxes in booths are connected to COM2 | |
1850 serobj.Baudrate = 9600; % Set the baud rate at the specific value | |
1851 set(serobj, 'Parity', 'none') ; % Set parity as none | |
1852 set(serobj, 'Databits', 8) ; % set the number of data bits | |
1853 set(serobj, 'StopBits', 1) ; % set number of stop bits as 1 | |
1854 set(serobj, 'Terminator', 'CR') ; % set the terminator value to carriage return | |
1855 set(serobj, 'InputBufferSize', 512) ; % Buffer for read operation, default it is 512 | |
1856 set(serobj,'timeout',10); % 10 sec timeout on button press | |
1857 set(serobj, 'ReadAsyncMode', 'continuous') | |
1858 set(serobj, 'BytesAvailableFcn', @buttonBox_callback) | |
1859 set(serobj, 'BytesAvailableFcnCount', 1) | |
1860 set(serobj, 'BytesAvailableFcnMode', 'byte') | |
1861 % set(serobj, 'BreakInterruptFcn', '@buttonBox_Calback') | |
1862 | |
1863 fopen(serobj); | |
1864 buttonBoxStatus=get(serobj,'status'); | |
1865 catch | |
1866 disp('** no button box found - use mouse **') | |
1867 end |