% QuestDemo.m
%
% One of the great contributions of psychophysics to psychology is the
% notion of measuring threshold, i.e. the signal strength required for a
% criterion level of response by the observer (Pelli & Farell, 1994;
% Farell & Pelli, 1999). Watson and Pelli (1983) described a maximum
% likelihood procedure, which they called QUEST, for estimating threshold.
% The Quest toolbox in the Psychtoolbox is a set of MATLAB functions
% that implement all the original QUEST functions, plus several others.
% You can think of it as a Bayesian toolbox for testing observers and
% estimating their thresholds. This QUEST toolbox is self-contained,
% and runs on any computer with MATLAB 5 or better.
% web http://psychtoolbox.org/
% web http://psych.nyu.edu/pelli/software.html#quest
%
% By commenting and uncommenting five lines below, you can use this file
% to implement three QUEST-related procedures for measuring threshold.
%
% QuestMode: In the original algorithm of Watson & Pelli (1983), each
% trial is at the MODE of the posterior pdf. Their final estimate is
% maximum likelihood, which is the MODE of the posterior pdf after
% dividing out the prior pdf. (Subsequent experience has shown that it's
% better not to divide out the prior, simply using MODE of posterior pdf
% throughout.)
%
% QuestMean: In the improved algorithm of King-Smith et al. (1994), each
% trial and the final estimate are at the MEAN of the posterior pdf.
%
% QuestQuantile & QuestMean: In the ideal algorithm of Pelli (1987), each
% trial is at the best QUANTILE, and the final estimate is at the MEAN of
% the posterior pdf.
%
% You begin by calling QuestCreate, telling Quest what is your prior
% knowledge, i.e. a guess and associated sd for threshold. Then you run
% some number of trials, typically 40. For each trial you ask Quest to
% recommend a test intensity. Then you actually test the observer at some
% intensity, not necessarily what Quest recommended, and then you call
% QuestUpdate to report to Quest the actual intensity used and whether the
% observer got it right. Quest saves this information in your Quest struct,
% which we usually call "q". This cycle is repeated for each trial. Finally,
% at the end, when you're done, you ask Quest to provide a final threshold
% estimate, usually the mean and sd (of the posterior pdf).
%
% It is important to realize that Quest is merely a friendly adviser,
% cataloging your data in your q structure, and making statistical
% analyses of it, but never giving you orders. You're still in charge. On
% each trial, you ask Quest (by calling QuestMode, or QuestMean, or
% QuestQuantile) to suggest the best intensity for the next trial. Taking
% that as advice, in your experiment you should then select the intensity
% yourself for the next trial, taking into account the limitations of your
% equipment and experiment. Typically you'll impose a maximum and a
% minimum, but your equipment may also restrict you to particular discrete
% values, and you might have some reason for not repeating a value.
% Typically you'll choose the available intensity closest to what Quest
% recommended. In some cases the process of producing the stimulus is so
% involved that the exact stimulus intensity is known only after it's been
% shown. Having run the trial, you then report the new datum,
% the actual intensity tested and the observer's response, asking Quest to
% add it to the database in q.
%
% To use Quest you must provide an estimated value for beta. Beta
% controls the steepness of the Weibull function. Many vision studies use
% Michelson contrast to control the visibility of the stimulus. It turns
% out that psychometric functions for 2afc detection as a function of
% contrast have a beta of roughly 3 for a remarkably wide range of targets
% and conditions (Nachmias, 1981). However, you may want to estimate beta
% for the particular conditions of your experiment. QuestBetaAnalysis is
% provided for that purpose, but please think of it as a limited optional
% feature. It allows only two free parameters, threshold and beta. You may
% prefer to use a general-purpose maximum likelihood fitting program to
% allow more degrees of freedom in fitting a Weibull function to your
% psychometric data. However, once you've done that it's likely that
% you'll settle on fixed values for all but threshold and use Quest to
% estimate that.
%
% Note that data collected to estimate threshold usually are not
% good for estimating beta. The psychometric function is sigmoidal, with a
% flat floor, a rise, and a flat ceiling. To estimate threshold you want
% all your trials near the steepest (roughly speaking) part of the rise.
% To estimate beta, the steepness of the rise, you want to have most of
% your trials at the corners, where the rise begins and where it ends. The
% usual way to achieve this is to first estimate threshold and then to
% collect a large number of trials (e.g. 100) at each of several
% intensities chosen to span the domain of the rise. These data can
% be plotted, making a nice graph of the psychometric function and
% they can be fed to QuestBetaAnalysis, to estimate threshold and beta.
%
% References
%
% Farell, B., & Pelli, D. G. (1999). Psychophysical methods, or how to
% measure threshold, and why. In J. G. Robson & R. H. S. Carpenter (Eds.),
% A Practical Guide to Vision Research (pp. 129-136). New York: Oxford
% University Press.
%
% King-Smith, P. E., Grigsby, S. S., Vingrys, A. J., Benes, S. C., and
% Supowit, A. (1994) Efficient and unbiased modifications of the QUEST
% threshold method: theory, simulations, experimental evaluation and
% practical implementation. Vision Res, 34 (7), 885-912.
%
% Nachmias, J. (1981). On the psychometric function for contrast detection.
% Vision Res, 21(2), 215-223.
%
% Pelli, D. G. (1987) The ideal psychometric procedure. Investigative
% Ophthalmology & Visual Science, 28 (Suppl), 366.
%
% Pelli, D. G., & Farell, B. (1994). Psychophysical methods. In M. Bass,
% E. W. Van Stryland, D. R. Williams & W. L. Wolfe (Eds.), Handbook of
% Optics, 2nd ed. (Vol. I, pp. 29.21-29.13). New York: McGraw-Hill.
%
% Watson, A. B. and Pelli, D. G. (1983) QUEST: a Bayesian adaptive
% psychometric method. Percept Psychophys, 33 (2), 113-20.
%
% All the papers of which I'm an author can be downloaded as PDF files
% from my web site:
% web http://psych.nyu.edu/pelli/
%
% Try "help Quest".

% Copyright (c) 1996-2004 Denis G. Pelli
%
% 3/3/97  dhb  Cosmetic editing.
% 10/13/04 dgp Added paragraph noting that Quest's suggestion for next trial
%              is merely a suggestion.
% 12/18/04 dgp Made it independent of the Psychtoolbox and greatly expanded the help text above.
% 12/20/04 dgp Added explanation to the print outs.
% 3/11/05  dgp Added one more decimal place to log C for each trial, as suggested by David Jones.
%              Loop INPUT until observer gives a value.
% 3/10/18  dgp Add optional animated plots of trial placement and the shrinking posterior %				pdf.

% GetSecs is part of the Psychophysics Toolbox.  If you are running
% QuestDemo without the Psychtoolbox, we use CPUTIME instead of GetSecs.
if exist('GetSecs')
	getSecsFunction='GetSecs';
else
	getSecsFunction='cputime';
end

fprintf('Welcome to QuestDemo. Quest will now estimate an observer''s threshold.\n');
fprintf('The intensity scale is abstract, but usually we think of it as representing\n');
fprintf('log contrast. \n');
animate=input('Do you want so see a live animated plot of the shrinking pdf? (y/n) [y]:','s');
animate=~streq(lower(animate),'n');
% We'll need this for the simulation.
fprintf('\nQuest won''t know, but in this demo we''re testing a simulated observer. \n');
tActual=[];
while isempty(tActual)
	tActual=input('Please specify the true threshold of the simulated observer (e.g. -2): ');
end
fprintf('Thank you. We won''t tell Quest.\n');
fprintf('\nWhen you test a real human observer, instead of a simulated observer, \n');
fprintf('you won''t know the true threshold. However you can still guess. You \n');
fprintf('must provide Quest with an initial threshold estimate as a mean and \n');
fprintf('standard deviation, which we call your "guess" and "sd". Be generous \n');
fprintf('with the sd, as Quest will have trouble finding threshold if it''s more\n');
fprintf('than one sd from your guess.\n');

% Provide our prior knowledge to QuestCreate, and receive the data struct "q".
tGuess=[];
while isempty(tGuess)
	tGuess=input('Estimate threshold (e.g. -1): ');
end
tGuessSd=[];
while isempty(tGuessSd)
	tGuessSd=input('Estimate the standard deviation of your guess, above, (e.g. 2): ');
end
pThreshold=0.82;
beta=3.5;delta=0.01;gamma=0.5;
q=QuestCreate(tGuess,tGuessSd,pThreshold,beta,delta,gamma);
q.normalizePdf=1; % This adds a few ms per call to QuestUpdate, but otherwise the pdf will underflow after about 1000 trials.

% fprintf('Your initial guess was %g +- %g\n',tGuess,tGuessSd);
% fprintf('Quest''s initial threshold estimate is %g +- %g\n',QuestMean(q),QuestSd(q));

% Simulate a series of trials.
% On each trial we ask Quest to recommend an intensity and we call QuestUpdate to save the result in q.
trialsDesired=40;
wrongRight={'wrong','right'};
timeZero=eval(getSecsFunction);
for k=1:trialsDesired
	% Get recommended level.  Choose your favorite algorithm.
	tTest=QuestQuantile(q);	% Recommended by Pelli (1987), and still our favorite.
	% 	tTest=QuestMean(q);		% Recommended by King-Smith et al. (1994)
	% 	tTest=QuestMode(q);		% Recommended by Watson & Pelli (1983)
	
	% We are free to test any intensity we like, not necessarily what Quest suggested.
	% 	tTest=min(-0.05,max(-3,tTest)); % Restrict to range of log contrasts that our equipment can produce.
	
	% Simulate a trial
	timeSplit=eval(getSecsFunction); % Omit simulation and printing from the timing measurements.
   if animate
      figure(1);
      response=QuestSimulate(q,tTest,tActual,2);
      title('Actual psychometric function, and the points tested.')
      xl=xlim;
   else
      response=QuestSimulate(q,tTest,tActual);
   end
 	fprintf('Trial %3d at %5.2f is %s\n',k,tTest,char(wrongRight(response+1)));
	timeZero=timeZero+eval(getSecsFunction)-timeSplit;
	
	% Update the pdf
	q=QuestUpdate(q,tTest,response); % Add the new datum (actual test intensity and observer response) to the database.
   if animate
      figure(2)
      plot(q.x+q.tGuess,q.pdf)
      xlim(xl);
      title('Posterior PDF');
      hold on
   end
end

% Print results of timing.
fprintf('%.0f ms/trial\n',1000*(eval(getSecsFunction)-timeZero)/trialsDesired);

% Ask Quest for the final estimate of threshold.
t=QuestMean(q);		% Recommended by Pelli (1989) and King-Smith et al. (1994). Still our favorite.
sd=QuestSd(q);
fprintf('Final threshold estimate (mean+-sd) is %.2f +- %.2f\n',t,sd);
% t=QuestMode(q);	% Similar and preferable to the maximum likelihood recommended by Watson & Pelli (1983).
% fprintf('Mode threshold estimate is %4.2f\n',t);
fprintf('\nYou set the true threshold to %.2f.\n',tActual);
fprintf('Quest knew only your guess: %.2f +- %.2f.\n',tGuess,tGuessSd);

% Optionally, reanalyze the data with beta as a free parameter.
fprintf('\nBETA. Many people ask, so here''s how to analyze the data with beta as a free\n');
fprintf('parameter. However, we don''t recommend it as a daily practice. The data\n');
fprintf('collected to estimate threshold are typically concentrated at one\n');
fprintf('contrast and don''t constrain beta. To estimate beta, it is better to use\n');
fprintf('100 trials per intensity (typically log contrast) at several uniformly\n');
fprintf('spaced intensities. We recommend using such data to estimate beta once,\n');
fprintf('and then using that beta in your daily threshold measurements. With\n');
fprintf('that disclaimer, here''s the analysis with beta as a free parameter.\n');
QuestBetaAnalysis(q); % optional
fprintf('Actual parameters of simulated observer:\n');
fprintf('logC	beta	gamma\n');
fprintf('%5.2f	%4.1f	%5.2f\n',tActual,q.beta,q.gamma);

if animate
   figure(1)
   hold off
   figure(2)
   hold off
end
