ZEST: ZEST In OPI: Open Perimetry Interface

Description

An implementation of the Bayesian test procedures of King-Smith et al. and Watson and Pelli. Note that we use the term `pdf` throughout as in the original paper, even though they are discrete probability functions in this implementation.

Usage

 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25``` ```ZEST(domain=0:40, prior=rep(1/length(domain),length(domain)), likelihood=sapply(domain,function(tt) {0.03+(1-0.03-0.03)*(1-pnorm(domain,tt,1))}), stopType="S", stopValue=1.5, minStimulus=head(domain,1), maxStimulus=tail(domain,1), maxSeenLimit=2, minNotSeenLimit=2, maxPresentations=100, verbose=0, makeStim, stimChoice="mean", ...) ZEST.start(domain=0:40, prior=rep(1/length(domain),length(domain)), likelihood=sapply(domain,function(tt) {0.03+(1-0.03-0.03)*(1-pnorm(domain,tt,1))}), stopType="S", stopValue=1.5, minStimulus=head(domain,1), maxStimulus=tail(domain,1), maxSeenLimit=2, minNotSeenLimit=2, maxPresentations=100, makeStim, stimChoice="mean", ...) ZEST.step(state, nextStim=NULL) ZEST.stop(state) ZEST.final(state) ```

Arguments

 `domain` Vector of values over which pdf is kept. `prior` Starting probability distribution over domain. Same length as `domain`. `likelihood` Matrix where `likelihood[s,t]` is likelihood of seeing `s` given `t` is the true threshold. That is, Pr(s|t) where `s` and `t` are indexes into `domain`. `stopType` `N`, for number of presentations; `S`, for standard deviation of the pdf; and `H`, for the entropy of the pdf. `stopValue` Value for number of presentations (`stopType=N`), standard deviation (`stopType=S)` or Entropy (`stopType=H`). `minStimulus` The smallest stimuli that will be presented. Could be different from `domain[1]`. `maxStimulus` The largest stimuli that will be presented. Could be different from `tail(domain,1)`. `minNotSeenLimit` Will terminate if `minStimulus` value is not seen this many times. `maxSeenLimit` Will terminate if `maxStimulus` value is seen this many times. `maxPresentations` Maximum number of presentations regarless of `stopType`. `verbose` `verbose=0` does nothing, `verbose=1` stores pdfs for returning, and `verbose=2` stores pdfs and also prints each presentaion. `makeStim` A function that takes a dB value and numPresentations and returns an OPI datatype ready for passing to opiPresent. See examples. `stimChoice` A true ZEST procedure uses the `"mean"` of the current pdf as the stimulus, but `"median"` and `"mode"` (as used in a QUEST procedure) are provided for your enjoyment. `...` Extra parameters to pass to the opiPresent function `state` Current state of the ZEST returned by `ZEST.start` and `ZEST.step`. `nextStim` A valid object for `opiPresent` to use as its `nextStim`.

Details

This is an implementation of King-Smith et al.'s ZEST procedure and Watson and Pelli's QUEST procedure. All presentaions are rounded to an element of the supplied domain.

Note this function will repeatedly call `opiPresent` for a stimulus until `opiPresent` returns `NULL` (ie no error occured).

If more than one ZEST is to be interleaved (for example, testing multiple locations), then the `ZEST.start`, `ZEST.step`, `ZEST.stop` and `ZEST.final` calls can maintain the state of the ZEST after each presentation, and should be used. If only a single ZEST is required, then the simpler `ZEST` can be used. See examples below.

Value

Single location

`ZEST` returns a list containing

• npres: Total number of presentations used.

• respSeq:Response sequence stored as a matrix: row 1 is dB values, row 2 is 1/0 for seen/not-seen.

• pdfs: If `verbose` is bigger than 0, then this is a list of the pdfs used for each presentation, otherwise NULL.

• final The mean/median/mode of the final pdf, depending on `stimChoice`, which is the determined threshold.

Multilple locations

`ZEST.start` returns a list that can be passed to `ZEST.step`, `ZEST.stop` and `ZEST.final`. It represents the state of a ZEST at a single location at a point in time and contains the following.

• name: `ZEST`

• A copy of all of the parameters supplied to ZEST.start: `domain` `likelihood`, `stopType`, `stopValue`, `minStimulus`, `maxStimulus`, `maxSeenLimit`, `minNotSeenLimit`, `maxPresentations`, `makeStim`, `stimChoice`, `currSeenLimit`, `currNotSeenLimit`, and `opiParams`.

• pdf: Current pdf: vector of probabilities the same length as `domain`.

• numPresentations: The number of times `ZEST.step` has been called on this state.

• stimuli: A vector containing the stimuli used at each call of `ZEST.step`.

• responses: A vector containing the responses received at each call of `ZEST.step`.

• responseTimes: A vector containing the response times received at each call of `ZEST.step`.

`ZEST.step` returns a list containing

• state: The new state after presenting a stimuli and getting a response.

• resp: The return from the `opiPresent` call that was made.

`ZEST.stop` returns `TRUE` if the ZEST has reached its stopping criteria, and `FALSE` otherwise.

`ZEST.final` returns an estimate of threshold based on state. If `state\$stimChoice` is `mean` then the mean is returned. If `state\$stimChoice` is `mode` then the mode is returned. If `state\$stimChoice` is `median` then the median is returned.

Author(s)

Andrew Turpin <[email protected]>

References

P.E. King-Smith, S.S. Grigsny, A.J. Vingrys, S.C. Benes, and A. Supowit. "Efficient and Unbiased Modifications of the QUEST Threshold Method: Theory, Simulations, Experimental Evaluation and Practical Implementation", Vision Research 34(7) 1994. Pages 885-912.

A.B. Watson and D.G. Pelli. "QUEST: A Bayesian adaptive psychophysical method", Perception and Psychophysics 33 1983. Pages 113-l20.

Please cite: A. Turpin, P.H. Artes and A.M. McKendrick "The Open Perimetry Interface: An enabling tool for clinical visual psychophysics", Journal of Vision 12(11) 2012.

http://perimetry.org/OPI

`dbTocd`, `opiPresent`
 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76``` ```chooseOpi("SimHenson") if (!is.null(opiInitialize(type="C", cap=6))) stop("opiInitialize failed") ############################################## # This section is for single location ZESTs ############################################## # Stimulus is Size III white-on-white as in the HFA makeStim <- function(db, n) { s <- list(x=9, y=9, level=dbTocd(db), size=0.43, color="white", duration=200, responseWindow=1500) class(s) <- "opiStaticStimulus" return(s) } repp <- function(...) sapply(1:50, function(i) ZEST(makeStim=makeStim, ...)) a <- repp(stopType="H", stopValue= 3, verbose=0, tt=30, fpr=0.03) b <- repp(stopType="S", stopValue=1.5, verbose=0, tt=30, fpr=0.03) c <- repp(stopType="S", stopValue=2.0, verbose=0, tt=30, fpr=0.03) d <- repp(stopType="N", stopValue= 50, verbose=0, tt=30, fpr=0.03) e <- repp(prior=dnorm(0:40,m=0,s=5), tt=30, fpr=0.03) f <- repp(prior=dnorm(0:40,m=10,s=5), tt=30, fpr=0.03) g <- repp(prior=dnorm(0:40,m=20,s=5), tt=30, fpr=0.03) h <- repp(prior=dnorm(0:40,m=30,s=5), tt=30, fpr=0.03) layout(matrix(1:2,1,2)) boxplot(lapply(list(a,b,c,d,e,f,g,h), function(x) unlist(x["final",]))) boxplot(lapply(list(a,b,c,d,e,f,g,h), function(x) unlist(x["npres",]))) ############################################## # This section is for multiple ZESTs ############################################## makeStimHelper <- function(db,n, x, y) { # returns a function of (db,n) ff <- function(db, n) db+n body(ff) <- substitute( {s <- list(x=x, y=y, level=dbTocd(db), size=0.43, color="white", duration=200, responseWindow=1500) class(s) <- "opiStaticStimulus" return(s) } , list(x=x,y=y)) return(ff) } # List of (x, y, true threshold) triples locations <- list(c(9,9,30), c(-9,-9,32), c(9,-9,31), c(-9,9,33)) # Setup starting states for each location states <- lapply(locations, function(loc) { ZEST.start( domain=-5:45, minStimulus=0, maxStimulus=40, makeStim=makeStimHelper(db,n,loc[1],loc[2]), stopType="S", stopValue= 1.5, tt=loc[3], fpr=0.03, fnr=0.01) }) # Loop through until all states are "stop" while(!all(st <- unlist(lapply(states, ZEST.stop)))) { i <- which(!st) # choose a random, i <- i[runif(1, min=1, max=length(i))] # unstopped state r <- ZEST.step(states[[i]]) # step it states[[i]] <- r\$state # update the states } finals <- lapply(states, ZEST.final) # get final estimates of threshold for(i in 1:length(locations)) { #cat(sprintf("Location (%+2d,%+2d) ",locations[[i]][1], locations[[i]][2])) #cat(sprintf("has threshold %4.2f\n", finals[[i]])) } if (!is.null(opiClose())) warning("opiClose() failed") ```