rmeddis@0
|
1 % ------------------------------------------------------- bestFitPsychometicFunctions
|
rmeddis@0
|
2 function [psy, levelsPhaseTwoBinVector, logistic, rareEvent]= bestFitPsychometicFunctions (levelsPhaseTwo, responsesPhaseTwo)
|
rmeddis@0
|
3 % bestFitPsychometicFunctions computes a psychometric function from a matrix of levels and responses
|
rmeddis@0
|
4 %output
|
rmeddis@0
|
5 % psy is the empirical probabbility of a detection associated with levels in levelsPhaseTwoBinVector
|
rmeddis@0
|
6 % logistic is a structure with results for fitting the logistic function
|
rmeddis@0
|
7 % the logistic fit depends on whether maximum likelihood is used or least squares
|
rmeddis@0
|
8 % rareEvent is a structure with results for fitting the rareEvent function
|
rmeddis@0
|
9 % this is always calculated and lest squares is always used
|
rmeddis@0
|
10
|
rmeddis@0
|
11 global experiment stimulusParameters binFrequencies
|
rmeddis@0
|
12
|
rmeddis@0
|
13 % Generate a psychometic function by binning the levelsPhaseTwo
|
rmeddis@0
|
14 % x is a vector of [levelsPhaseTwo; responsesPhaseTwo]
|
rmeddis@0
|
15 % experiment.psyBinWidth defines the width of the bin
|
rmeddis@0
|
16 [psy, levelsPhaseTwoBinVector, binFrequencies,yesResponses, noResponses]= ...
|
rmeddis@0
|
17 psychometricFunction(levelsPhaseTwo, responsesPhaseTwo, experiment.psyBinWidth);
|
rmeddis@0
|
18 % [levelsPhaseTwoBinVector; binFrequencies; psy];
|
rmeddis@0
|
19
|
rmeddis@0
|
20 % undefined slope - return
|
rmeddis@0
|
21 if isempty(psy)
|
rmeddis@0
|
22 % slope is undefined
|
rmeddis@0
|
23 psy=[]; levelsPhaseTwoBinVector=[];
|
rmeddis@0
|
24 logistic.bestThreshold=NaN; logistic.bestK=NaN; logistic.predictionsLOG=[]; logistic.predictionLevels=[];
|
rmeddis@0
|
25 rareEvent.bestGain=NaN; rareEvent.bestVMin=NaN; rareEvent.predictionsRE=[]; rareEvent.predictionLevels=[];
|
rmeddis@0
|
26 rareEvent.thresholddB= NaN; rareEvent.bestPaMindB=NaN;
|
rmeddis@0
|
27 return
|
rmeddis@0
|
28 end
|
rmeddis@0
|
29
|
rmeddis@0
|
30 % find best fit rare event function
|
rmeddis@0
|
31 switch experiment.paradigm
|
rmeddis@0
|
32 case 'gapDuration'
|
rmeddis@0
|
33 % i.e. don't attempt this but visit the function to set null results
|
rmeddis@0
|
34 rareEvent=fitRareEvent([], responsesPhaseTwo, stimulusParameters.targetDuration);
|
rmeddis@0
|
35 otherwise
|
rmeddis@0
|
36 rareEvent=fitRareEvent(levelsPhaseTwo, responsesPhaseTwo, stimulusParameters.targetDuration);
|
rmeddis@0
|
37 end
|
rmeddis@0
|
38
|
rmeddis@0
|
39 % find best logistic fit
|
rmeddis@0
|
40 logisticFunc=' 1./(1+exp(-a2.*(x-a1)));';
|
rmeddis@0
|
41 switch experiment.functionEstMethod
|
rmeddis@0
|
42 % least squares estimate
|
rmeddis@0
|
43 case {'logisticLS', 'rareEvent','peaksAndTroughs'}
|
rmeddis@0
|
44 [a1, a2, Euclid]=fitFunctionUsingLeastSquares(levelsPhaseTwo, responsesPhaseTwo, ...
|
rmeddis@0
|
45 logisticFunc, experiment.possLogSlopes, experiment.meanSearchStep);
|
rmeddis@0
|
46 % [a1, a2, Euclid]=fitLogisticUsingLS(psy, levelsPhaseTwoBinVector); %! does not give same result
|
rmeddis@0
|
47 logistic.bestThreshold=a1;
|
rmeddis@0
|
48 logistic.bestK=a2;
|
rmeddis@0
|
49 logistic.predictionsLOG= ...
|
rmeddis@0
|
50 1./(1+exp(-logistic.bestK.*(experiment.predictionLevels-logistic.bestThreshold)));
|
rmeddis@0
|
51 logistic.predictionLevels=experiment.predictionLevels;
|
rmeddis@0
|
52 logistic.Euclid = Euclid;
|
rmeddis@0
|
53 % predResponses=1./(1+exp(-logistic.bestK.*(levelsPhaseTwo-logistic.bestThreshold)));
|
rmeddis@0
|
54 % [levelsPhaseTwo' responsesPhaseTwo' predResponses' responsesPhaseTwo'-predResponses' ]
|
rmeddis@0
|
55
|
rmeddis@0
|
56 % maximum likelihood fitting
|
rmeddis@0
|
57 case 'logisticML'
|
rmeddis@0
|
58 [a1, a2, Euclid]=fitFunctionUsingMaxLikelihood (levelsPhaseTwoBinVector,...
|
rmeddis@0
|
59 psy, yesResponses, noResponses, logisticFunc, experiment.possLogSlopes,experiment.meanSearchStep);
|
rmeddis@0
|
60 logistic.bestThreshold=a1;
|
rmeddis@0
|
61 logistic.bestK=a2;
|
rmeddis@0
|
62 logistic.predictionsLOG= ...
|
rmeddis@0
|
63 1./(1+exp(-logistic.bestK.*(experiment.predictionLevels-logistic.bestThreshold)));
|
rmeddis@0
|
64 logistic.predictionLevels=experiment.predictionLevels;
|
rmeddis@0
|
65 logistic.Euclid = Euclid;
|
rmeddis@0
|
66 end
|
rmeddis@0
|
67
|
rmeddis@0
|
68 % disp(num2str([logistic.bestThreshold logistic.bestK logistic.Euclid]))
|
rmeddis@0
|
69 % disp(num2str([ rareEvent.thresholddB rareEvent.bestGain rareEvent.bestVMin rareEvent.Euclid]))
|
rmeddis@0
|
70
|
rmeddis@0
|
71 % --------------------------------------------- fitLogisticUsingLS
|
rmeddis@0
|
72 function [mean, slope, Euclid]=fitLogisticUsingLS(p,L)
|
rmeddis@0
|
73
|
rmeddis@0
|
74 %!! each bin needs to be weighted by its size
|
rmeddis@0
|
75
|
rmeddis@0
|
76 idx1=find(p>0);
|
rmeddis@0
|
77 p=p(idx1);
|
rmeddis@0
|
78 L=L(idx1);
|
rmeddis@0
|
79 idx1=find(p<1);
|
rmeddis@0
|
80 p=p(idx1);
|
rmeddis@0
|
81 L=L(idx1);
|
rmeddis@0
|
82
|
rmeddis@0
|
83 if length(L)<2
|
rmeddis@0
|
84 mean=NaN;
|
rmeddis@0
|
85 slope=NaN;
|
rmeddis@0
|
86 Euclid=NaN;
|
rmeddis@0
|
87 return
|
rmeddis@0
|
88 end
|
rmeddis@0
|
89
|
rmeddis@0
|
90 y=L;
|
rmeddis@0
|
91 x=log(1./p -1);
|
rmeddis@0
|
92 a =polyfit(x, y, 1);
|
rmeddis@0
|
93 mean=a(2);
|
rmeddis@0
|
94 slope=-1/a(1);
|
rmeddis@0
|
95
|
rmeddis@0
|
96 % euclid
|
rmeddis@0
|
97 predy=polyval(a,x);
|
rmeddis@0
|
98 Euclid=sum((predy-y).^2);
|
rmeddis@0
|
99 [mean slope Euclid];
|
rmeddis@0
|
100
|
rmeddis@0
|
101 % --------------------------------------------- fitFunctionUsingLeastSquares
|
rmeddis@0
|
102 function [a1, a2, Euclid]=fitFunctionUsingLeastSquares(levelsPhaseTwo, responsesPhaseTwo, func, possSlopes, meanSearchStep)
|
rmeddis@0
|
103
|
rmeddis@0
|
104 % nResponses= yesResponses+noResponses;
|
rmeddis@0
|
105 % idx=find(not(nResponses==0));
|
rmeddis@0
|
106 % levelsPhaseTwo=levelsPhaseTwo(idx);
|
rmeddis@0
|
107 % yesResponses=yesResponses(idx);
|
rmeddis@0
|
108 % noResponses=noResponses(idx);
|
rmeddis@0
|
109 % nResponses=nResponses(idx);
|
rmeddis@0
|
110
|
rmeddis@0
|
111 % psy=yesResponses./nResponses;
|
rmeddis@0
|
112
|
rmeddis@0
|
113 possThresholds=min(levelsPhaseTwo):meanSearchStep:max(levelsPhaseTwo);
|
rmeddis@0
|
114
|
rmeddis@0
|
115
|
rmeddis@0
|
116 % create vectors representing all combinations of a1 and a2
|
rmeddis@0
|
117 nPossThresh=length(possThresholds);
|
rmeddis@0
|
118 nPossSlopes=length(possSlopes);
|
rmeddis@0
|
119 a1=repmat(possThresholds',nPossSlopes,1);
|
rmeddis@0
|
120 a2=repmat(possSlopes,nPossThresh,1);
|
rmeddis@0
|
121 a2=reshape(a2,nPossThresh*nPossSlopes,1);
|
rmeddis@0
|
122
|
rmeddis@0
|
123 % each response is predicted by every possible threshold and slope
|
rmeddis@0
|
124 LS=ones(size(a1));
|
rmeddis@0
|
125 for i=1:length(levelsPhaseTwo)
|
rmeddis@0
|
126 x=levelsPhaseTwo(i);
|
rmeddis@0
|
127 eval(['y=' func]);
|
rmeddis@0
|
128 actual=responsesPhaseTwo(i);
|
rmeddis@0
|
129 error= (actual - y).^2;
|
rmeddis@0
|
130 LS=LS+error;
|
rmeddis@0
|
131 end
|
rmeddis@0
|
132
|
rmeddis@0
|
133 [Euclid, idx]=min(LS);
|
rmeddis@0
|
134
|
rmeddis@0
|
135 a1=a1(idx);
|
rmeddis@0
|
136 a2=a2(idx);
|
rmeddis@0
|
137
|
rmeddis@0
|
138 % x=levelsPhaseTwo;
|
rmeddis@0
|
139 % eval(['y=' func]);
|
rmeddis@0
|
140 % Euclid= sum((psy-y).^2);
|
rmeddis@0
|
141
|
rmeddis@0
|
142 % plot(levelsPhaseTwo,y,'r')
|
rmeddis@0
|
143 % hold on
|
rmeddis@0
|
144 % plot(levelsPhaseTwo,psy,'o')
|
rmeddis@0
|
145
|
rmeddis@0
|
146 % --------------------------------------------- fitFunctionUsingMaxLikelihood
|
rmeddis@0
|
147 function [a1, a2, Euclid]=fitFunctionUsingMaxLikelihood...
|
rmeddis@0
|
148 (levelsPhaseTwo,psy, yesResponses, noResponses, func, possible_a2, meanSearchStep)
|
rmeddis@0
|
149
|
rmeddis@0
|
150 % fitFunctionUsingMaxLikelihood fits the function in 'func' to binned yes-no data.
|
rmeddis@0
|
151 % levelsPhaseTwo specifies the bin centers
|
rmeddis@0
|
152 % yesResponses and noResponses are the niumber of yes and no responsesPhaseTwo in each bin
|
rmeddis@0
|
153 % 'func' is a function of a1, b1, x; e.g. func='1./(1+exp(-a2.*(x-a1)));';
|
rmeddis@0
|
154 % and a2, a1 are parameters to be discovered.
|
rmeddis@0
|
155 % If bins are empty (i.e. neither yes or no), these are eliminated
|
rmeddis@0
|
156 %
|
rmeddis@0
|
157
|
rmeddis@0
|
158 nResponses=yesResponses+noResponses;
|
rmeddis@0
|
159 possible_a1=min(levelsPhaseTwo):meanSearchStep:max(levelsPhaseTwo);
|
rmeddis@0
|
160
|
rmeddis@0
|
161 % if nargin<6
|
rmeddis@0
|
162 % possible_a2=-2:.05:4;
|
rmeddis@0
|
163 % end
|
rmeddis@0
|
164
|
rmeddis@0
|
165 % create vectors representing all combinations of a1 and a2
|
rmeddis@0
|
166 nPossThresh=length(possible_a1);
|
rmeddis@0
|
167 nPossSlopes=length(possible_a2);
|
rmeddis@0
|
168 a1=repmat(possible_a1',nPossSlopes,1);
|
rmeddis@0
|
169 a2=repmat(possible_a2,nPossThresh,1);
|
rmeddis@0
|
170 a2=reshape(a2,nPossThresh*nPossSlopes,1);
|
rmeddis@0
|
171
|
rmeddis@0
|
172 cumulativeProbability=ones(1, length(a2));
|
rmeddis@0
|
173 cumulativeProbability=cumulativeProbability';
|
rmeddis@0
|
174
|
rmeddis@0
|
175 for i=1:length(levelsPhaseTwo)
|
rmeddis@0
|
176 x=levelsPhaseTwo(i);
|
rmeddis@0
|
177 eval(['y=' func]);
|
rmeddis@0
|
178 funcVal=(1-y).^yesResponses(i);
|
rmeddis@0
|
179 cumulativeProbability= cumulativeProbability.*funcVal;
|
rmeddis@0
|
180 funcVal=y.^noResponses(i);
|
rmeddis@0
|
181 cumulativeProbability= cumulativeProbability.*funcVal;
|
rmeddis@0
|
182 end
|
rmeddis@0
|
183
|
rmeddis@0
|
184 [maxProb idx]=max(cumulativeProbability);
|
rmeddis@0
|
185 a1=a1(idx);
|
rmeddis@0
|
186 a2=a2(idx);
|
rmeddis@0
|
187
|
rmeddis@0
|
188 % x=levelsPhaseTwo;
|
rmeddis@0
|
189 eval(['y=' func]);
|
rmeddis@0
|
190 Euclid= sum(nResponses.*(psy-y).^2);
|
rmeddis@0
|
191
|
rmeddis@0
|
192 % figure(1), clf
|
rmeddis@0
|
193 % plot(levelsPhaseTwo,y)
|
rmeddis@0
|
194 % hold on
|
rmeddis@0
|
195 % plot(levelsPhaseTwo,psy,'o')
|
rmeddis@0
|
196
|
rmeddis@0
|
197
|
rmeddis@0
|
198
|
rmeddis@0
|
199 % --------------------------------------------------- fitRareEvent
|
rmeddis@0
|
200 function rareEvent=fitRareEvent(stimulusLevels, responsesPhaseTwo, duration, gains, Vmins)
|
rmeddis@0
|
201 % least squares estimate of *rare event* function
|
rmeddis@0
|
202 % model is: r = g P – A ... g=events/s/Pa, A= events/s, P= pressure (Pa)
|
rmeddis@0
|
203 % psy=1- exp(d(gP –A )) ... d=duration
|
rmeddis@0
|
204 % p(event)=gain*levelmPa -Vmin
|
rmeddis@0
|
205 % 'responsesPhaseTwo' is a binary vector of subject's decision.
|
rmeddis@0
|
206 % 'stimulusLevels' are the corresponding signal levesl (values)
|
rmeddis@0
|
207 % duration is required to compute the expectation of an event occurring
|
rmeddis@0
|
208 % gains is an optional list of gains to be tried
|
rmeddis@0
|
209 % Vmins is an optional list of Vmins to be tried
|
rmeddis@0
|
210
|
rmeddis@0
|
211 global experiment
|
rmeddis@0
|
212 if nargin<5
|
rmeddis@0
|
213 minVmin=.1; maxVmin=10; nVmins=100;
|
rmeddis@0
|
214 Vmins=[0 logspace(log10(minVmin),log10(maxVmin),nVmins)];
|
rmeddis@0
|
215 % disp(Vmins)
|
rmeddis@0
|
216 gainMin=0.0001; gainMax=1; nGains=100;
|
rmeddis@0
|
217 gains=[1e-5 logspace(log10(gainMin),log10(gainMax),nGains)];
|
rmeddis@0
|
218 end
|
rmeddis@0
|
219
|
rmeddis@0
|
220 rareEvent.bestGain=NaN;
|
rmeddis@0
|
221 rareEvent.bestVMin=NaN;
|
rmeddis@0
|
222 rareEvent.thresholddB=0;
|
rmeddis@0
|
223 rareEvent.bestPaMindB=NaN;
|
rmeddis@0
|
224 rareEvent.predictionLevels=[];
|
rmeddis@0
|
225 rareEvent.predictionsRE=[];
|
rmeddis@0
|
226 rareEvent.Euclid=NaN;
|
rmeddis@0
|
227
|
rmeddis@0
|
228 if isempty(stimulusLevels), return, end
|
rmeddis@0
|
229
|
rmeddis@0
|
230 % expected slope is negative, rareEvent can not be used
|
rmeddis@0
|
231 if experiment.psyFunSlope<0
|
rmeddis@0
|
232 return
|
rmeddis@0
|
233 end
|
rmeddis@0
|
234
|
rmeddis@0
|
235 % NB calculations in microPascals!
|
rmeddis@0
|
236 stimulusLevelsAsPressure=28 * 10.^(stimulusLevels/20);
|
rmeddis@0
|
237
|
rmeddis@0
|
238 % allGains=reshape(repmat(gains,nVmins,1), 1, nVmins*nGains);
|
rmeddis@0
|
239 % allVmins=repmat(Vmins, 1, nGains);
|
rmeddis@0
|
240
|
rmeddis@0
|
241 predictions=NaN*zeros(1,length(stimulusLevels));
|
rmeddis@0
|
242 gainCount=0;
|
rmeddis@0
|
243 Euclid=inf; bestVmin=0; bestGain=0;
|
rmeddis@0
|
244 for gain= gains
|
rmeddis@0
|
245 gainCount=gainCount+1;
|
rmeddis@0
|
246 VminCount=0;
|
rmeddis@0
|
247
|
rmeddis@0
|
248 % 1 – exp(-d (g P – A))
|
rmeddis@0
|
249 for Vmin=Vmins
|
rmeddis@0
|
250 VminCount=VminCount+1;
|
rmeddis@0
|
251 % all levelsPhaseTwo are simultaneously assessed
|
rmeddis@0
|
252
|
rmeddis@0
|
253 % original linear function
|
rmeddis@0
|
254 % gP_Vmin=gain*stimulusLevelsAsPressure-Vmin;
|
rmeddis@0
|
255 % idx=(gP_Vmin>0);
|
rmeddis@0
|
256 % predictions(idx)= 1-exp(-duration*(gP_Vmin(idx)));
|
rmeddis@0
|
257 % new log function log(r) =gP-A
|
rmeddis@0
|
258
|
rmeddis@0
|
259 % log function
|
rmeddis@0
|
260 gP_Vmin=gain*stimulusLevelsAsPressure-Vmin;
|
rmeddis@0
|
261 idx=(gP_Vmin>0);
|
rmeddis@0
|
262 predictions(idx)= 1-exp(-duration*exp(gP_Vmin(idx)));
|
rmeddis@0
|
263 predictions(~idx)=0;
|
rmeddis@0
|
264
|
rmeddis@0
|
265 % % square function
|
rmeddis@0
|
266 % P_Vmin=stimulusLevelsAsPressure-Vmin;
|
rmeddis@0
|
267 % idx=(P_Vmin)>0;
|
rmeddis@0
|
268 % predictions(idx)= 1-exp(-duration*gain*(P_Vmin(idx)).^2);
|
rmeddis@0
|
269 % predictions(~idx)=0;
|
rmeddis@0
|
270
|
rmeddis@0
|
271
|
rmeddis@0
|
272 % NB the error is equally weighted for each stimulus/response pair
|
rmeddis@0
|
273 % this is not the same as equal weighting for each distinct level
|
rmeddis@0
|
274 error=(predictions - responsesPhaseTwo).^2;
|
rmeddis@0
|
275 error=mean(error(~isnan(error)));
|
rmeddis@0
|
276 if error<Euclid
|
rmeddis@0
|
277 Euclid=error;
|
rmeddis@0
|
278 bestVmin=Vmin;
|
rmeddis@0
|
279 bestVminCount=VminCount;
|
rmeddis@0
|
280 % bestGainCount=gainCount;
|
rmeddis@0
|
281 bestGain=gain;
|
rmeddis@0
|
282 end
|
rmeddis@0
|
283 end
|
rmeddis@0
|
284 end
|
rmeddis@0
|
285 % disp(Vmins)
|
rmeddis@0
|
286
|
rmeddis@0
|
287 % if bestGainCount==1 | bestGainCount==nGains
|
rmeddis@0
|
288 % disp(['gain estimate ' num2str(gains(bestGainCount)) ' may be out of range'])
|
rmeddis@0
|
289 % end
|
rmeddis@0
|
290
|
rmeddis@0
|
291 [rareEvent.Euclid idx]=min(Euclid);
|
rmeddis@0
|
292 rareEvent.bestGain=bestGain;
|
rmeddis@0
|
293 rareEvent.bestVMin=bestVmin;
|
rmeddis@0
|
294 rareEvent.thresholdPa=(-log(0.5)/duration + rareEvent.bestVMin)/rareEvent.bestGain;
|
rmeddis@0
|
295 rareEvent.thresholddB=20*log10(rareEvent.thresholdPa/28);
|
rmeddis@0
|
296 rareEvent.bestPaMindB=20*log10((rareEvent.bestVMin/rareEvent.bestGain)/28);
|
rmeddis@0
|
297
|
rmeddis@0
|
298 predictionLevels= -50:1:120;
|
rmeddis@0
|
299 rareEvent.predictionLevels=predictionLevels;
|
rmeddis@0
|
300 stimulusLevelsAsPressure=28*10.^(predictionLevels/20);
|
rmeddis@0
|
301
|
rmeddis@0
|
302 % gP_Vmin=rareEvent.bestGain*stimulusLevelsAsPressure-rareEvent.bestVMin;
|
rmeddis@0
|
303 % rareEvent.predictionsRE= 1-exp(-duration*(gP_Vmin));
|
rmeddis@0
|
304
|
rmeddis@0
|
305 gP_Vmin=rareEvent.bestGain*stimulusLevelsAsPressure-rareEvent.bestVMin;
|
rmeddis@0
|
306 rareEvent.predictionsRE= 1-exp(-duration*exp(gP_Vmin));
|
rmeddis@0
|
307
|
rmeddis@0
|
308 % P_Vmin=stimulusLevelsAsPressure-Vmin;
|
rmeddis@0
|
309 % predictions= 1-exp(-duration*gain*P_Vmin.^2);
|
rmeddis@0
|
310 % predictions(predictions<0)=0;
|
rmeddis@0
|
311 % rareEvent.predictionsRE=predictions;
|
rmeddis@0
|
312
|
rmeddis@0
|
313 % rareEvent
|
rmeddis@0
|
314
|