function q=QuestCreate(tGuess,tGuessSd,pThreshold,beta,delta,gamma,grain,range,plotIt) % q=QuestCreate(tGuess,tGuessSd,pThreshold,beta,delta,gamma,[grain],[range],[plotIt]) % % Create a struct q with all the information necessary to measure % threshold. Threshold "t" is measured on an abstract "intensity" % scale, which usually corresponds to log10 contrast. % % QuestCreate saves in struct q the parameters for a Weibull psychometric function: % p2=delta*gamma+(1-delta)*(1-(1-gamma)*exp(-10.^(beta*(x-xThreshold)))); % where x represents log10 contrast relative to threshold. The Weibull % function itself appears only in QuestRecompute, which uses the % specified parameter values in q to compute a psychometric function % and store it in q. All the other Quest functions simply use the % psychometric function stored in "q". QuestRecompute is called solely % by QuestCreate and QuestBetaAnalysis (and possibly by a few user % programs). Thus, if you prefer to use a different kind of % psychometric function, called Foo, you need only create your own % QuestCreateFoo, QuestRecomputeFoo, and (if you need it) % QuestBetaAnalysisFoo, based on QuestCreate, QuestRecompute, and % QuestBetaAnalysis, and you can use them with the rest of the Quest % package unchanged. You would only be changing a few lines of code, % so it would quite easy to do. % % Several users of Quest have asked questions on the Psychtoolbox forum % about how to restrict themselves to a practical testing range. That is % not what tGuessSd and "range" are for; they should be large, e.g. I % typically set tGuessSd=3 and range=5 when intensity represents log % contrast. If necessary, you should restrict the range yourself, outside % of Quest. Here, in QuestCreate, you tell Quest about your prior beliefs, % and you should try to be open-minded, giving Quest a generously large % range to consider as possible values of threshold. For each trial you % will later ask Quest to suggest a test intensity. It is important to % realize that what Quest returns is just what you asked for, a % suggestion. You should then test at whatever intensity you like, taking % into account both the suggestion and any practical constraints (e.g. a % maximum and minimum contrast that you can achieve, and quantization of % contrast). After running the trial you should call QuestUpdate with the % contrast that you actually used and the observer's response to add your % new datum to the database. Don't restrict "tGuessSd" or "range" by the % limitations of what you can display. Keep open the possibility that % threshold may lie outside the range of contrasts that you can produce, % and let Quest consider all possibilities. % % There is one exception to the above advice of always being generous with % tGuessSd. Occasionally we find that we have a working Quest-based % program that measures threshold, and we discover that we need to measure % the proportion correct at a particular intensity. Instead of writing a % new program, or modifying the old one, it is often more convenient to % instead reduce tGuessSd to practically zero, e.g. a value like 0.001, % which has the effect of restricting all threshold estimates to be % practically identical to tGuess, making it easy to run any number of % trials at that intensity. Of course, in this case, the final threshold % estimate from Quest should be ignored, since it is merely parroting back % to you the assertion that threshold is equal to the initial guess % "tGuess". What's of interest is the final proportion correct; at the % end, call QuestTrials or add an FPRINTF statement to report it. % % tGuess is your prior threshold estimate. % tGuessSd is the standard deviation you assign to that guess. Be generous. % pThreshold is your threshold criterion expressed as probability of % response==1. An intensity offset is introduced into the psychometric % function so that threshold (i.e. the midpoint of the table) yields % pThreshold. % beta, delta, and gamma are the parameters of a Weibull psychometric function. % beta controls the steepness of the psychometric function. Typically 3.5. % delta is the fraction of trials on which the observer presses blindly. % Typically 0.01. % gamma is the fraction of trials that will generate response 1 when % intensity==-inf. % grain is the quantization (step size) of the internal table. E.g. 0.01. % range is the intensity difference between the largest and smallest % intensity that the internal table can store. E.g. 5. This interval will % be centered on the initial guess tGuess, i.e. % tGuess+(-range/2:grain:range/2). "range" is used only momentarily here, % to determine "dim", which is retained in the quest struct. "dim" is the % number of distinct intensities that the internal table can store, e.g. % 500. QUEST assumes that intensities outside of this interval have zero % prior probability, i.e. they are impossible values for threshold. The % cost of making "range" too big is some extra storage and computation, % which are usually negligible. The cost of making "range" too small is % that you prejudicially exclude what are actually possible values for % threshold. Getting out-of-range warnings from QuestUpdate is one % possible indication that your stated range is too small. % % See Quest. % 6/8/96 dgp Wrote it. % 6/11/96 dgp Optimized the order of stuffing for faster unstuffing. % 11/10/96 dhb Added warning about correctness after DGP told me. % 3/1/97 dgp Fixed error in sign of xThreshold in formula for p2. % 3/1/97 dgp Updated to use Matlab 5 structs. % 3/3/97 dhb Added missing semicolon to first struct eval. % 3/5/97 dgp Fixed sd: use exp instead of 10^. % 3/5/97 dgp Added some explanation of the psychometric function. % 6/24/97 dgp For simulations, now allow specification of grain and dim. % 9/30/98 dgp Added "dim" fix from Richard Murray. % 4/12/99 dgp dropped support for Matlab 4. % 5/6/99 dgp Simplified "dim" calculation; just round up to even integer. % 8/15/99 dgp Explain how to use other kind of psychometric function. % 2/10/02 dgp Document grain and range. % 9/11/04 dgp Explain why supplied "range" should err on the high side. % 10/13/04 dgp Explain why tGuesSd and range should be large, generous. % 10/13/04 dgp Set q.normalizePdf to 1, to avoid underflow errors that otherwise accur after around 1000 trials. % 8/23/15 dgp Flag error if tGuess is not finite. % % Copyright (c) 1996-2004 Denis Pelli if nargin < 6 || nargin > 9 error('Usage: q=QuestCreate(tGuess,tGuessSd,pThreshold,beta,delta,gamma,[grain],[range])') end if nargin < 7 || isempty(grain) grain=0.01; end if nargin < 8 || isempty(range) dim=500; else if range<=0 error('"range" must be greater than zero.') end dim=range/grain; dim=2*ceil(dim/2); % round up to an even integer end if nargin < 9 || isempty(plotIt) plotIt = 0; end if ~isfinite(tGuess) || ~isreal(tGuess) error('"tGuess" must be real and finite.'); end q.updatePdf=1; % boolean: 0 for no, 1 for yes q.warnPdf=1; % boolean q.normalizePdf=1; % boolean. This adds a few ms per call to QuestUpdate, but otherwise the pdf will underflow after about 1000 trials. q.tGuess=tGuess; q.tGuessSd=tGuessSd; q.pThreshold=pThreshold; q.beta=beta; q.delta=delta; q.gamma=gamma; q.grain=grain; q.dim=dim; q=QuestRecompute(q, plotIt); % THIS CODE WAS IN THE OLD VERSION. I'VE PASTED "q." INTO THE OBVIOUS PLACES. % THIS IS RETAINED SOLELY TO HELP DEBUG ANY BUGS IN THE NEW CODE. % % prepare all the arrays % q.i=-dim/2:dim/2; % q.x=i*grain; % q.pdf=exp(-0.5*(q.x/tGuessSd).^2); % q.pdf=q.pdf/sum(q.pdf); % normalize the pdf % i2=-dim:dim; % q.x2=i2*q.grain; % q.p2=delta*gamma+(1-delta)*(1-(1-gamma)*exp(-10.^(beta*q.x2))); % index=find(diff(q.p2)); % subset that is strictly monotonic % q.xThreshold=interp1(q.p2(index),q.x2(index),q.pThreshold); % q.p2=delta*gamma+(1-delta)*(1-(1-gamma)*exp(-10.^(beta*(q.x2+q.xThreshold)))); % q.s2=fliplr([1-q.p2;q.p2]); % % % Best quantileOrder depends only on min and max of psychometric function. % % For 2-interval forced choice, if pL=0.5 and pH=1 then best quantileOrder=0.60 % % We write x*log(x+eps) in place of x*log(x) to get zero instead of NAN when x is zero. % pL=q.p2(1); % pH=q.p2(end); % pE=pH*log(pH+eps)-pL*log(pL+eps)+(1-pH+eps)*log(1-pH+eps)-(1-pL+eps)*log(1-pL+eps); % pE=1/(1+exp(pE/(pL-pH))); % q.quantileOrder=(pE-pL)/(pH-pL);