___________________________________________________________________________________________________________________ DIFFERENCE THRESHOLD: Interweaved Staircase Method (Example: determine difference threshold for a red) ___________________________________________________________________________________________________________________ Script Author: Katja Borchert, Ph.D. (katjab@millisecond.com) for Millisecond Software, LLC Date: 03-26-2014 last updated: 03-06-2020 by K. Borchert (katjab@millisecond.com) for Millisecond Software, LLC Script Copyright © 03-06-2020 Millisecond Software ___________________________________________________________________________________________________________________ BACKGROUND INFO ___________________________________________________________________________________________________________________ This script implements the interweaved STAIRCASE METHOD to estimate the difference threshold for a particular color red Reference: Ehrenstein, W.H. & and Addie Ehrenstein, A. (1999). Psychophysical Methods. In U. Windhorst & H. Johansson, Hakan (Eds.), Modern Techniques in Neuroscience Research (pp.1211-1241). Heidelberg: Springer. (->http://uni-leipzig.de/~isp/isp/history/texts/PSYPHY-M.PDF) ___________________________________________________________________________________________________________________ TASK DESCRIPTION ___________________________________________________________________________________________________________________ In the staircase procedure, participants are asked repeatedly whether a red Target color is lighter or darker than a red base color. For example: the red component of the target color is lighter. As long as participants say the target red is lighter than the base red, the target red gets adjusted down by a preestablished step size (editable value). Once participants reverse their answer (and say it's darker), the adjustment reverses and the the target color is turned up lighter by the preestablished step size. The reversal threshold = average of the last two target red values before reversal (e.g. one perceived lighter and one perceived darker than baseline) gets noted. Once a predetermined number of reversals has taken place (parameters.max_reversals) the difference threshold is calculated as the mean of all the obtained reversal thresholds. Interweaved staircase method: two staircases are run at the same time. One staircase starts with a lighter red target (= staircase DOWN), the other staircase starts with a darker red target (= staircase UP) The individual trials of each staircase are either called in a random or in an alternate fashion (-> parameters.staircase_order). The purpose of the interweaved staircase method is to somewhat obscure the nature of the procedure. In general, the staircase procedure is considered a variant of the Method of Limits with the difference that once a participant changes the response (e.g. from "it's lighter" to "it's darker") the threshold estimation isn't over but the adjustments are reversed and a predetermined number of reveral thresholds are noted => the threshold gets crossed several times. By default, this script runs one cycle: one up and one down staircase interweaved ___________________________________________________________________________________________________________________ DURATION ___________________________________________________________________________________________________________________ the default set-up of the script takes appr. 2 minutes to complete ___________________________________________________________________________________________________________________ DATA FILE INFORMATION ___________________________________________________________________________________________________________________ The default data stored in the data files are: (1) Raw data file: 'staircasemethod_raw*.iqdat' (a separate file for each participant) build: The specific Inquisit version used (the 'build') that was run computer.platform: the platform the script was run on (win/mac/ios/android) date, time, date and time script was run subject, group, with the current subject/groupnumber script.sessionid: with the current session id blockcode, blocknum: the name and number of the current block (built-in Inquisit variable) trialcode, trialnum: the name and number of the currently recorded trial (built-in Inquisit variable) Note: trialnum is a built-in Inquisit variable; it counts all trials run; even those that do not store data to the data file. staircase: 1 = staircase DOWN; 2 = staircase UP direction_down Direction_ DOWN: 1 = target lighter; 2 = target darker for staircase DOWN direction_up: 1 = target lighter; 2 = target darker for staircase UP baseline_value: stores the red color target tone of the current base targetvalue_down: stores the red color target tone of the current target Color in the DOWN staircase targetvalue_up: stores the red color target tone of the current target Color in the UP response: the participant's response (scancode of response) latency: the response latency countreversals_down: count number of direction reversal for staircase DOWN countreversals_up: count number of direction reversal for staircase UP ReversalThreshold: estimated Threshold at Reversal point (midpoint between last two target values) diffthreshold_down: mean reversal thresholds for current UP Staircase (thresholds: midpoint between the last two target values before reversal response) diffthreshold_up: mean reversal thresholds for current DOWN Staircase (thresholds: midpoint between the last two target values before reversal response) diffthreshold: mean threshold (based on current DiffThreshold_up and DiffThreshold_Down) for the current cycle (2) Summary data file: 'staircasemethod_summary*.iqdat' (a separate file for each participant) computer.platform: the platform the script was run on (win/mac/ios/android) script.startdate: date script was run script.starttime: time script was started script.subjectid: assigned subject id number script.groupid: assigned group id number script.sessionid: assigned session id number script.elapsedtime: time it took to run script (in ms); measured from onset to offset of script script.completed: 0 = script was not completed (prematurely aborted); 1 = script was completed (all conditions run) cycles: stores the number of staircase cycles run staircase_order: select from "alternate" (default) or "random" alternate: staircase up and staircase alternate (until one of the staircases has reached the max. number of reversals) random: staircases are selected randomly (until one of the staircases has reached the max. number of reversals) step: the stepsize with which the target color increases/decreases (default: 2) parameters.initialdifference: initial difference in the red component of target and base (default: 50+1 = 51) Note: in this script the initial difference is selected in such a way that the adjusted targetvalues are always either above or below baseline. parameters.baselinevalue1: the baseline_value of the first "limits" cycle diffthreshold1: stores the Difference Threshold of the first cycle Note: by design, this script only runs one cycle ___________________________________________________________________________________________________________________ INSTRUCTIONS ___________________________________________________________________________________________________________________ see section Editable Instructions ___________________________________________________________________________________________________________________ EDITABLE CODE ___________________________________________________________________________________________________________________ check below for (relatively) easily editable parameters, stimuli, instructions etc. Keep in mind that you can use this script as a template and therefore always "mess" with the entire code to further customize your experiment. The parameters you can change are: /responsekey_right - /responsekey_left: the response key assignments (scancodes and labels) /baselinevalue1: the baseline value (0 -> black, 255 -> brightest red) Note: to add further staircase cycles with different basevalues add a value for each new baseline value you want to test and go to BLOCKS for further instructions. /step: the stepsize with which the target color increases/decreases (default: 2) /initialdifference: initial difference in the red component of target and base (default: 50+1 = 51) Note: in this script the initial difference is selected in such a way that the targetvalues are always either above or below baseline. /max_reversals: the maximum number of reversals per staircase /staircase_order: select from "alternate" (default) or "random" alternate: staircase up and staircase alternate (until one of the staircases has reached the max. number of reversals) random: staircases are selected randomly (until one of the staircases has reached the max. number of reversals) ************************************************************************************************************** ************************************************************************************************************** EDITABLE PARAMETERS: change editable parameters here ************************************************************************************************************** ************************************************************************************************************** /responsekey_right = "K" /responsekey_left = "D" /baselinevalue1 = 45 /step = 8 /initialdifference = 20 + 0.5*parameters.step /max_reversals = 3 /staircase_order = "alternate" ************************************************************************************************************** ************************************************************************************************************** EDITABLE STIMULI: change editable stimuli here ************************************************************************************************************** ************************************************************************************************************** /1 = "BASE SHAPE" /2 = "TARGET SHAPE" ************************************************************************************************************** ************************************************************************************************************** EDITABLE INSTRUCTIONS: change instructions here ************************************************************************************************************** ************************************************************************************************************** /1 = "Is the Target Shape (right) tiled CLOCKWISE (rightward) or COUNTER-CLOCKWISE (leftward) than the Base Shape (left)?" /2 = "Rightward -> Press the '<%parameters.responsekey_right%>' <%expressions.buttoninstruct1%> Leftward -> Press the '<%parameters.responsekey_left%>' <%expressions.buttoninstruct1%>" / fontstyle = ("Arial", 2.80%, false, false, false, false, 5, 1) ^For the following task you will be presented two circles of different shades of red. ^^The BASE color stays constant but the TARGET color will get lighter or darker. ^^Your task is to decide whether the current red color of the TARGET is lighter or darker than the red color of the base. ^^If it's lighter -> Press the <%parameters.responsekey_right%> <%expressions.buttoninstruct1%> ^If it's darker -> Press the <%parameters.responsekey_left%> <%expressions.buttoninstruct1%> ^^^The closer the TARGET color is to the BASE color the more difficult the task. You have reached the end of the task. ^^Thank you! **************************************************************************************************** general instruction expressions: adjust the instruction text depending on device used to run script **************************************************************************************************** /buttoninstruct1 = if (computer.touch && !computer.haskeyboard) {"button";} else {"key on your keyboard";} ************************************************************************************************************** !!!REMAINING CODE: Customize after careful consideration only!!! ************************************************************************************************************** ************************************************************************************************************** ************************************************************************************************************** DEFAULTS ************************************************************************************************************** ************************************************************************************************************** script requires Inquisit 6.1.0.0 or higher /canvasaspectratio = (4,3) /minimumversion = "6.1.0.0" / fontstyle = ("Arial", 3%, false, false, false, false, 5, 1) / txbgcolor = white / txcolor = black ******************************************************************************************************************* ******************************************************************************************************************* DATA: this section contains data file information ******************************************************************************************************************* ******************************************************************************************************************* ******************** raw data ******************** / columns = (build, computer.platform, date, time, subject, group, script.sessionid, blockcode, blocknum, trialcode, trialnum, values.staircase, values.direction_down, values.direction_up, values.baseline_value, values.targetvalue_down, values.targetvalue_up, shape.target.colorgreen, shape.base.colorgreen, response, latency, values.countreversals_down, values.countreversals_up, values.ReversalThreshold, expressions.diffthreshold_down, expressions.diffthreshold_up, expressions.diffthreshold) ******************** summary data ******************** / columns = (computer.platform, script.startdate, script.starttime, script.subjectid, script.groupid, script.sessionid, script.elapsedtime, script.completed, values.cycles, parameters.staircase_order, parameters.step, parameters.initialdifference, parameters.baselinevalue1, values.diffthreshold1) ******************************************************************************************************************* ******************************************************************************************************************* VALUES: automatically updated ******************************************************************************************************************* ******************************************************************************************************************* /cycles: stores the number of staircase cycles run /baseline_value: stores the red color target tone of the current base /targetvalue_down: stores the red color target tone of the current target Color in the DOWN staircase /targetvalue_up: stores the red color target tone of the current target Color in the UP staircase /change_up: stores the adjustment change (based on parameters.step) for the UP staircase /change_down: stores the adjustment change (based on parameters.step) for the DOWN staircase /staircase: 1 = staircase DOWN; 2 = staircase UP /nextstaircase: 1 = staircase DOWN; 2 = staircase UP /run_up- /run_down: checks wether any trials have been run in the up/down staircase of the given cycle /direction_down: 1 = target lighter; 2 = target darker for staircase DOWN /direction_up: 1 = target lighter; 2 = target darker for staircase UP /direction_helper: direction helper variable /countreversals_down: count number of direction reversal for staircase DOWN /countreversals_up: count number of direction reversal for staircase UP /helper: helper variable /ReversalThreshold: estimated Threshold at Reversal point (midpoint between last two target values) /sum_DiffThreshold_down: adds the difference thresholds for staircase DOWN /sum_DiffThreshold_up: adds the difference thresholds for staircase UP /DiffThreshold1: stores the Difference Threshold of the first staircase cycle /cycles = 0 /baseline_value = 0 /targetvalue_down = 0 /targetvalue_up = 0 /change_up = 0 /change_down = 0 /staircase = 0 /nextstaircase = 0 /run_up = 0 /run_down = 0 /direction_down = 0 /direction_up = 0 /direction_helper = 0 /countreversals_down = 0 /countreversals_up = 0 /helper = 0 /ReversalThreshold = 0 /sum_DiffThreshold_down= 0 /sum_DiffThreshold_up = 0 /DiffThreshold1 = 0 ******************************************************************************************************************* ******************************************************************************************************************* EXPRESSIONS ******************************************************************************************************************* ******************************************************************************************************************* /DiffThreshold_up: mean reversal thresholds for UP Staircase (thresholds: midpoint between the last two target values before reversal response) /DiffThreshold_down: mean reversal thresholds for DOWN Staircase (thresholds: midpoint between the last two target values before reversal response) /DiffThreshold: mean threshold based on DiffThreshold_up and DiffThreshold_Down /DiffThreshold_up = values.sum_DiffThreshold_up/parameters.max_reversals /DiffThreshold_down = values.sum_diffthreshold_down/parameters.max_reversals /DiffThreshold = (expressions.diffthreshold_down + expressions.diffthreshold_up)/2 ******************************************************************************************************************* ******************************************************************************************************************* INSTRUCTIONS ******************************************************************************************************************* ******************************************************************************************************************* /items = instructions /select = 1 /position = (50%, 10%) / fontstyle = ("Arial", 3%, true, false, false, false, 5, 1) / txcolor = black /erase = false /items = instructions /select = 2 /position = (50%, 20%) / fontstyle = ("Arial", 2%, false, false, false, false, 5, 1) / txcolor = black /erase = false ******************************************************************************************************************* ******************************************************************************************************************* STIMULI ******************************************************************************************************************* ******************************************************************************************************************* / shape = triangle / color = (0, 135, 10) / size = (30%*2/4, 30%) / position = (30%, 50%) / rotation = 0 / shape = triangle / color = (0, 135, 10) / size = (30%*2/4, 30%) / position = (70%, 50%) / rotation = 0 /items = labels /select = 1 / fontstyle = ("Arial", 3%, false, false, false, false, 5, 1) / txcolor = black /hposition = shape.base.hposition /vposition = shape.base.vposition + shape.base.height + 10% /erase = false /items = labels /select = 2 / fontstyle = ("Arial", 3%, false, false, false, false, 5, 1) / txcolor = black /hposition = shape.target.hposition /vposition = shape.target.vposition + shape.target.height + 10% /erase = false /shape = rectangle /color = white /size = (100%, 100%) /position = (50%, 50%) ******************************************************************************************************************* ******************************************************************************************************************* LISTS ******************************************************************************************************************* ******************************************************************************************************************* Note: list.randomstaircase randomly selects the next staircase 1 = DOWN, 2 = UP /items = (1, 2) /replace = true ******************************************************************************************************************* ******************************************************************************************************************* TRIALS: run one interweaved staircase cycle ******************************************************************************************************************* ******************************************************************************************************************* Sequence: trial.staircaseselection - > trial.staircase_down/trial.staircase_up as long as number of reversals in each of the staircases < parameters.max_reversals Note: trial.staircaseselection * selects the next staircase (up or down) either randomly or in alternate fashion (depending on parameters.staircase_order) * if the number of reversals of one of the staircases has reached the predetermined number (parameters.max_reversals) only the second staircase is run until the number of reversals for that staircase also reaches the predetermined number * if both staircases have reached the predetermined number of reversals trial.thresholds is called /ontrialbegin = [ if (values.nextstaircase == 1) { values.helper = 2; } else { values.helper = 1; }; if (parameters.staircase_order == "random") { values.helper = list.randomstaircase.nextvalue; }; ] /trialduration = 0 /ontrialend = [ values.nextstaircase = values.helper; if (values.countreversals_down >= parameters.max_reversals && values.countreversals_up < parameters.max_reversals && values.helper == 1) { values.nextstaircase = 2; } else if (values.countreversals_down < parameters.max_reversals && values.countreversals_up >= parameters.max_reversals && values.helper == 2) { values.nextstaircase = 1; }; ] /branch = [ if (values.nextstaircase == 1 && values.run_down == 0) { trial.start_staircasedown; } else if (values.nextstaircase == 2 && values.run_up == 0) { trial.start_staircaseup; } else if (values.countreversals_down >= parameters.max_reversals && values.countreversals_up >= parameters.max_reversals) { trial.thresholds; } else if (values.nextstaircase == 1) { trial.staircase_down; } else { trial.staircase_up; }; ] /recorddata = false ************************************************* Staircase DOWN: target value starts rightward (lighter == rightward) ************************************************* Note: trial.start_staircasedown * is called before the very first staircase down trial is called (trial.staircase_down) * it sets the startsvalues for the down staircase /ontrialbegin = [ values.run_down = 1; values.targetvalue_down = values.baseline_value + (parameters.initialdifference); shape.base.rotation = values.baseline_value; shape.target.rotation= values.targetvalue_down; values.change_down = 0; values.direction_down = 1; ] /timeout = 0 /branch = [return trial.staircase_down;] Note: - staircase for targets that start out lighter than the base - decreases the red color target tone of the target Color until a reversal then the red color target tone of the target color increases again - stores threshold values = reversal values (midpoint between the last two targetvalues) in list.thresholds_down - tracks number of reversals for staircase_down /ontrialbegin = [ values.staircase = 1; if (values.direction_down == 1) { values.targetvalue_down -= values.change_down; } else { values.targetvalue_down += values.change_down; }; shape.target.rotation = values.targetvalue_down; ] / stimulusframes = [1 = taskinstruct1, taskinstruct2, baselabel, targetlabel, base, target] / validresponse = (parameters.responsekey_left, parameters.responsekey_right) / monkeyresponse = (parameters.responsekey_left, parameters.responsekey_right) / ontrialend = [ values.change_down = parameters.step; values.direction_helper = values.direction_down; if (values.direction_down == 1 && trial.staircase_down.responsetext == parameters.responsekey_left) { values.direction_helper = 2; values.countreversals_down += 1; values.reversalthreshold = values.targetvalue_down + 0.5*parameters.step; values.sum_DiffThreshold_down += values.reversalthreshold; } else if (values.direction_down == 2 && trial.staircase_down.responsetext == parameters.responsekey_right) { values.direction_helper = 1; values.countreversals_down += 1; values.reversalthreshold = values.targetvalue_down - 0.5*parameters.step; values.sum_DiffThreshold_down += values.reversalthreshold; }; ] /branch = [ values.direction_down = values.direction_helper; return trial.staircaseselection; ] ************************************************* Staircase UP: target value starts darker ************************************************* Note: trial.start_staircaseup * is called before the very first staircase up trial is called (trial.staircase_up) * it sets the startsvalues for the up staircase /ontrialbegin = [ values.run_up = 1; values.targetvalue_up = values.baseline_value - (parameters.initialdifference); shape.base.rotation = values.baseline_value; shape.target.rotation = values.targetvalue_up; values.change_up = 0; values.direction_up = 2; ] /timeout = 0 /branch = [ return trial.staircase_up; ] Note: - staircase for targets that start out darker than the base - increase the red color target tone of the target Color until a reversal then the red color target tone of the target color decreases again - stores threshold values = reversal values (midpoint between the last two targetvalues) in list.thresholds_up - tracks number of reversals for staircase_up /ontrialbegin = [ values.staircase = 2; if (values.direction_up == 1) { values.targetvalue_up -= values.change_up; } else { values.targetvalue_up += values.change_up; }; shape.target.rotation = values.targetvalue_up; ] / stimulusframes = [1 = taskinstruct1, taskinstruct2, baselabel, targetlabel, base, target] / validresponse = (parameters.responsekey_left, parameters.responsekey_right) / monkeyresponse = (parameters.responsekey_left, parameters.responsekey_right) / ontrialend = [ values.change_up = parameters.step; if (values.targetvalue_up == 360 && trial.staircase_up.responsetext == parameters.responsekey_right) { values.targetvalue_up -=1; } else if (values.targetvalue_up == 180 && trial.staircase_up.responsetext == parameters.responsekey_left) { values.targetvalue_up +=1; }; values.direction_helper = values.direction_up; if (values.direction_up == 2 && trial.staircase_up.responsetext == parameters.responsekey_right) { values.direction_helper = 1; values.countreversals_up += 1; values.reversalthreshold = values.targetvalue_up - 0.5*parameters.step; values.sum_DiffThreshold_up += values.reversalthreshold; } else if (values.direction_up == 1 && trial.staircase_up.responsetext == parameters.responsekey_left) { values.direction_helper = 2; values.countreversals_up += 1; values.reversalthreshold = values.targetvalue_up + 0.5*parameters.step; values.sum_DiffThreshold_up += values.reversalthreshold; }; ] /branch = [ values.direction_up = values.direction_helper; return trial.staircaseselection; ] Notes: stores thresholds into data file /trialduration = 0 /recorddata = true ******************************************************************************************************************* ******************************************************************************************************************* BLOCKS ******************************************************************************************************************* ******************************************************************************************************************* Note: block.staircases1 runs one cycle of two interweaved staircases (one starting above and one starting below basevalue) with parameters.baselinevalue1 To run further cycles, create separate blocks for each of the baselines. change -> /onblockbegin = [values.baseline_value = values.baselinevalue2] Go to EXPERIMENT and add the new cycles /onblockbegin = [ values.cycles += 1; values.sum_diffthreshold_down = 0; values.sum_diffthreshold_up = 0; values.countreversals_down = 0; values.countreversals_up = 0; values.run_down = 0; values.run_up = 0; values.baseline_value = parameters.baselinevalue1; values.nextstaircase = list.randomstaircase.nextvalue; ] /trials = [1=staircaseselection] /onblockend = [ values.diffthreshold1 = expressions.diffthreshold; ] ******************************************************************************************************************* ******************************************************************************************************************* EXPERIMENT ******************************************************************************************************************* ******************************************************************************************************************* Note: to run more cycles add the newly created staircases blocks /blocks = [1 = staircases1] => /blocks = [1 = staircases1; 2 = staircases2....] /postinstructions = (end) /blocks = [1 = staircases1] ******************************************************************************************************************* End of File *******************************************************************************************************************