Configuring Experiments via Command Line Interfaces
Author
In the previous session, we learned how to use configuration files to make our experiments more reusable. However, this approach still requires us to change something when we want to change the behavior of the experiment, namely the configuration file. What if there are parameters that I want to change regularly, maybe even on every single run of the experiment (e.g. the subject ID). Always changing the config file does not seem like a good strategy for that. This is where command line interfaces (CLIs) come into play. A CLI provides an easy and convenient way to pass parameters to our program by simply typing the name of the program and, next to it, the values we want to put in. In this notebook, we will learn how to build powerful CLIs that make our programs even more reusable!
from argparse import ArgumentParser
from psychopy.event import waitKeys
from psychopy.sound import Sound
from psychopy.visual import Window
Sound(stereo=False) # avoid bug for default `stereo` settingSection 1: Creating an Argument Parser
An argument parser takes in arguments from the command line and feeds them into our Python program.
Python has a builtin module called argparse that does this for us.
Let’s assume we have a CLI program that adds two numbers.
To call this program we would type in our terminal something like that python add.py 3 2, where add.py is the name of the program and 3 and 2 are the values provided to the CLI.
However, it would be annoying to constantly jump between our editor and the terminal.
Fortunately, argparse has a method of directly providing the args as if we were calling the program from the terminal.
This gives us a convenient way for writing and testing our scripts in the same interface.
In this section we are going to write CLI scripts that allow us to play different sounds using PsychoPy’s Sound class.
| Code | Description |
|---|---|
parser = ArgumentParser() |
Create a parser for command line arguments |
parser.add_argument("x", type=int) |
Add an integer argument "x" to the parser |
parser.add_argument("y", type=str) |
Add a string argument "y" to the parser |
parser.print_help() |
Print the help documentation of the parser |
args = parser.parse_args(args=["3", "y"]) |
Parse the arguments and set the "x" to "3" and "y" to "hi" |
args.x |
Access the value passed to the argument "x" |
tone = Sound() |
Create a pure tone |
tone = Sound(value=800) |
Create a pure tone with a frequency of 800 Hz |
tone = Sound(secs=1.5) |
Create a pure tone with a duration of 1.5 seconds |
tone = Sound(hamming=True) |
Create a sound where the onset and offset are smoothly ramped up/down using a hamming window |
tone.play() |
Play the sound |
Exercises
Example: Make an ArgumentParser() that accepts integers for n_trials and print the parser’s help.
parser = ArgumentParser()
parser.add_argument("n_trials", type=int)
parser.print_help()usage: ipykernel_launcher.py [-h] n_trials
positional arguments:
n_trials
options:
-h, --help show this help message and exitExample: Make args from the parser above, with args.n_trials set to 10:
args = parser.parse_args(args=["10"])
args.n_trials10Example: Make args from the parser above, with args.n_trials set to 18:
args = parser.parse_args(args=["18"])
args.n_trials18Exercise: Make an ArgumentParser() that accepts integers for n_blocks and print the parser’s help.
Solution
parser = ArgumentParser()
parser.add_argument('n_blocks', type=int)
parser.print_help()usage: ipykernel_launcher.py [-h] n_blocks
positional arguments:
n_blocks
options:
-h, --help show this help message and exitExercise: Make args from the parser above, with args.n_blocks set to 2:
Solution
args = parser.parse_args(args=['2'])
args.n_blocks2Exercise: Make args from the parser above, with args.n_blocks set to 5:
Solution
args = parser.parse_args(args=['5'])
args.n_blocks5Exercise: Make an ArgumentParser() that accepts strings for subject and print the parser’s help.
Solution
parser = ArgumentParser()
parser.add_argument('subject', type=str)
parser.print_help()usage: ipykernel_launcher.py [-h] subject
positional arguments:
subject
options:
-h, --help show this help message and exitExercise: Make args from the parser above, with args.subject set to Fred.
Solution
args = parser.parse_args(args=['Fred'])
args.subject'Fred'Exercise: Make args from the parser above, with args.subject set to Julia.
Solution
args = parser.parse_args(args=['Julia'])
args.subject'Julia'Exercise: Make an ArgumentParser that accepts integers for freq and print the parser’s help.
Solution
parser = ArgumentParser()
parser.add_argument("freq", type=int)
parser.print_help()usage: ipykernel_launcher.py [-h] freq
positional arguments:
freq
options:
-h, --help show this help message and exitExample: Make PsychoPy play a sound at 800 Hz when args.freq is set to 800:
args = parser.parse_args(args=["800"])
sound = Sound(value=args.freq)
sound.play()
f"playing {args.freq} Hz tone ..."Exercise: Make Psychopy play a sound at 1200 Hz when args.freq is set to 1200.
Solution
args = parser.parse_args(args=["1200"])
sound = Sound(value=args.freq)
sound.play()
f"playing {args.freq} Hz tone ..."Exercise: Make an ArgumentParser that accepts integers for freq and floats for secs and print the parser’s help.
Solution
parser = ArgumentParser()
parser.add_argument('freq', type=int)
parser.add_argument('secs', type=float)
parser.print_help()usage: ipykernel_launcher.py [-h] freq secs
positional arguments:
freq
secs
options:
-h, --help show this help message and exitExercise: Make PsychoPy play a 500 Hz tone with a duration of 1.8 seconds with args from the parser above, when args.freq set to 500 and args.secs set to 1.8.
Solution
args = parser.parse_args(args=["500", "1.8"])
sound = Sound(value=args.freq, secs=args.secs)
sound.play()
f"playing {args.freq} Hz tone for {args.secs} secs..."Exercise: Make PsychoPy play a 4000 Hz tone with a duration of 0.25 seconds with args from the parser above, when args.freq set to 4000 and args.secs set to 0.25:
Solution
args = parser.parse_args(args=["4000", "0.25"])
sound = Sound(value=args.freq, secs=args.secs)
sound.play()
f"playing {args.freq} Hz tone for {args.secs} secs..."Section 2: Documenting your CLI
Adding arguments to a CLI can make a program more versatile but also harder to understand.
To make our program easy to use, we can provide documentation that explains what the program and the individual parameters are doing.
We can also use named arguments where each value is preceded by an identifier like python add.py --num1 3 --num2 5.
This makes it easier to understand what each parameter does, and it also allows us to pass in the parameters in any order.
Its important to keep in mind that named arguments are optional, so our program has to be able to run without them!
In this section, we are exploring some advanced CLI functions together with PsychoPy’s waitKeys functions that allows us to record responses using a keyboard.
Even though we won’t show any images, we’ll still have to open a Window because PsychPy will only record keys from an active window. Everytime, we use PsychoPy’s window without actually displaying anthything, we’ll see this screen:
We can ignore this message - it just means that PsychoPy is automatically measuring the monitor’s frame rate so it can estimate how long an image is displayed.
| Code | Description |
|---|---|
parser = ArgumentParser(description="This program ...") |
Create an argument parser and add a description about the program |
parser.add_argument("n", help="This argument...") |
Add a positional argument "n" and add a help text about the argument |
parser.add_argument("--sub", type=str) |
Add an optional named argument "--sub" of type string |
parser.add_argument("--train", action="store_true") |
Add an optional named argument "--train" that, when included, takes the value True |
args = parser.parse_args(args=["--sub", "Bob", "--train"]) |
Parse the arguments and set args.sub to "Bob" and args.train to True |
with Window() as win: |
Open a Window within a context manager (required for recording key presses) |
keys = waitKeys() |
Wait until any key is pressed |
keys = waitKeys(keyList=["a", "b"]) |
Wait until the keys "a" or "b" are pressed |
keys = waitKeys(maxWait=3) |
Wait until any key is pressed or 3 seconds passed |
keys = waitKeys(timeStamped=True) |
Wait until any key is pressed and return the time of the event |
Exercises
Example: Make an ArgumentParser() that accepts strings for --key1 and --key2 and print the parser’s help.
parser = ArgumentParser()
parser.add_argument("--key1", type=str)
parser.add_argument("--key2", type=str)
parser.print_help()usage: ipykernel_launcher.py [-h] [--key1 KEY1] [--key2 KEY2]
options:
-h, --help show this help message and exit
--key1 KEY1
--key2 KEY2Example: Make PsychoPy wait until the keys "q" or "p" were pressed with args from the parser above, when args.key1 is set to "q" and args.key2 set to "p" and print the recorded key.
args = parser.parse_args(args=["--key1", "q", "--key2", "p"])
with Window() as win:
keys = waitKeys(keyList=[args.key1, args.key2])
keys['q']Example: Make PsychoPy wait until the "return" (or enter) key was pressed with args from the parser above, when args.key1 is set to "return" and args.key2 is not used at all.
args = parser.parse_args(args=["--key1", "return"])
with Window() as win:
keys = waitKeys(keyList=[args.key1, args.key2])
keys['return']Exercise: Make an ArgumentParser() that accepts strings for --yes and --no and print the parser’s help.
Solution
parser = ArgumentParser()
parser.add_argument("--yes", type=str)
parser.add_argument("--no", type=str)
parser.print_help()usage: ipykernel_launcher.py [-h] [--yes YES] [--no NO]
options:
-h, --help show this help message and exit
--yes YES
--no NOExercise: Make PsychoPy wait until the keys "y" or "n" were pressed with args from the parser above, when args.yes is set to "y" and args.no is set to "n".
Solution
args = parser.parse_args(args=["--yes", "y", "--no", "n"])
with Window() as win:
keys = waitKeys(keyList=[args.yes, args.no])
keysSolution
['y']Exercise: Make PsychoPy wait until the key "space" was pressed with args from the parser above, when args.yes is set to "space" and args.no is not used at all.
Solution
args = parser.parse_args(args=["--yes", "space"])
with Window() as win:
keys = waitKeys(keyList=[args.yes, args.no])
keysSolution
['space']Exercise: Make an ArgumentParser() and add the description: "This program waits until 'maxwait' has passed or the 'key' was pressed".
Make the parser accept strings for "--key" and add help="Wait for this key".
Also make the parser accept floats for "--maxwait" and add help="Maximum time to wait in seconds".
Then, print the parser’s help
Solution
parser = ArgumentParser(
description="This program waits until 'maxwait has passed or the `key` was pressed"
)
parser.add_argument("--key", type=str, help="Wait for this key (e.g. 'space')")
parser.add_argument("--maxwait", type=float, help="Maximum time to wait in seconds")
parser.print_help()usage: ipykernel_launcher.py [-h] [--key KEY] [--maxwait MAXWAIT]
This program waits until 'maxwait has passed or the `key` was pressed
options:
-h, --help show this help message and exit
--key KEY Wait for this key (e.g. 'space')
--maxwait MAXWAIT Maximum time to wait in secondsExercise: Make PsychoPy wait until the key "z" was pressed or 2.5 seconds passed with args from the parser above, when args.key is set to "z" and args.maxwait is set to "2.5":
Solution
args = parser.parse_args(args=["--key", "z", "--maxwait", "2.5"])
with Window() as win:
keys = waitKeys(keyList=[args.key], maxWait=args.maxwait)Solution
['z']Exercise: Make PsychoPy wait until the key "g" was pressed or 3.25 seconds passed with args from the parser above, when args.key is set to "g" and args.maxwait is set to "3.25":
Solution
args = parser.parse_args(args=["--key", "g", "--maxwait", "3.25"])
with Window() as win:
keys = waitKeys(keyList=[args.key], maxWait=args.maxwait)Solution
['g']Exercise: Make an ArgumentParser() that accepts strings for "--key" and add a "--timed" flag with action="store_true" and print the parser’s help.
Solution
parser = ArgumentParser()
parser.add_argument("--key", type=str)
parser.add_argument("--timed", action="store_true")
parser.print_help()usage: ipykernel_launcher.py [-h] [--key KEY] [--timed]
options:
-h, --help show this help message and exit
--key KEY
--timedExercise: Make PsychoPy wait until the key "space" was pressed with args from the parser above when args.key is set to "space" and return the time at which the key was pressed when args.timed is True.
Solution
args = parser.parse_args(args=["--key", "space", "--timed"])
with Window() as win:
keys = waitKeys(keyList=[args.key], timeStamped=args.timed)
keysSolution
[['space', 1386.6897800000006]]Exercise: Make PsychoPy wait until the key "space" was pressed with args from the parser above when args.key is set to "space" and don’t return the time at which the key was pressed if the "--timed" flag was not included.
Solution
args = parser.parse_args(args=["--key", "space"])
with Window() as win:
keys = waitKeys(keyList=[args.key], timeStamped=args.timed)
keysSolution
['space']Section 3: Writing a program with a command line interface
Now we know how to write CLIs, play Sounds and record Responses - Let’s create some progamms.
In this section you are going to create new files to write your scripts in.
Make sure that you place the files in the same folder as this notebook (4_command_line_interface), so Python will be able to find them!
To run a program from the terminal, simply write ! before its name and execute the notebook cell.
IMPORTANT: By running this notebook, PsychoPy connected to your audio device. As long as this connection is activte you will not be able to run other PsychoPy programs that also need to communicate with your audio interface. Thus, before you continue, make sure that you click the Restart button at the top your VSCode editor to let go of any existing connection.
| Code | Description |
|---|---|
!python some_program.py -h |
Print the help documentation of some_program’s command line interface |
!python some_program.py hi |
Execute some_program.py with hi as the first positional argument |
!python some_program.py hi --arg1 2 |
Execute some_program.py with hi as the first positional argument and 2 as the value of the optional argument --arg1 |
Exercise: Copy the script below into a new file called say_hi_to.py and store it in the SAME FOLDER as this notebook.
import argparse
# parse command line arguments
parser = argparse.ArgumentParser(description="A script that says hi to someone")
parser.add_argument(
"--first",
type=str,
default="",
help="First name of the person you want to say hi to",
)
parser.add_argument(
"--last", type=str, default="", help="Last name of the person you want to say hi to"
)
parser.add_argument(
"--shout", default=False, action="store_true", help="Use this flag to shout"
)
args = parser.parse_args()
# print message
text = "Hi " + args.first + " " + args.last + "!"
if args.shout:
text = text.upper()
print(text)Exercise: Call the help of the program say_hi_to.py and print its help.
Solution
!python say_hi_to.py -husage: say_hi_to.py [-h] [--first FIRST] [--last LAST] [--shout]
A script that says hi to someone
options:
-h, --help show this help message and exit
--first FIRST First name of the person you want to say hi to
--last LAST Last name of the person you want to say hi to
--shout Use this flag to shoutExercise: Use say_hi_to.py to say hi to "John Doe".
Solution
!python say_hi_to.py --first John --last DoeHi John Doe!Exercise: Use say_hi_to.py to --shout hi to "Bob".
Solution
!python say_hi_to.py --first Bob --shoutHI BOB !Exercise: Create a new file called react_to_tone.py in the SAME FOLDER as this notebook. In this program, create an ArgumentParser that accepts a string argument key, an integer argument freq and a --timed flag. Make the program play a tone at the given frequency, wait fo the key and prints the value returned by waitKeys(). Make the program print the pressed key (and the timestamp if the --timed flag was included). OPTIONAL: add a description to the parser and help to the arguments.
Hint: Use Sound(stereo=False) to avoid an error if you are using an output device with only one channel.
Solution
from argparse import ArgumentParser
from psychopy.event import waitKeys
from psychopy.sound import Sound
from psychopy.visual import Window
parser = ArgumentParser(
description="Play a pure tone and wait for a key press. Optionally return the timestamp"
)
parser.add_argument("freq", type=int, help="Frequency of the tone")
parser.add_argument("key", type=str, help="Key to press")
parser.add_argument("--timed", action="store_true", help="Return a timestamp")
args = parser.parse_args()
sound = Sound(value=args.freq, stereo=False)
sound.play()
key = waitKeys(keyList=[args.key], timeStamped=args.timed)
print(key)Exercise: Make react_to_tone.py play a 600 Hz tone and wait until y was pressed:
Solution
!python react_to_tone.py 600 ySolution
pygame 2.6.1 (SDL 2.28.4, Python 3.10.16)
Hello from the pygame community. https://www.pygame.org/contribute.html
2.4380 WARNING Monitor specification not found. Creating a temporary one...
['y'])Exercise: Make react_to_tone.py play a 750 Hz tone and wait until the key n was pressed and return the timestamp of the key press.
Solution
!python react_to_tone.py 750 n --timedpython: can't open file '/home/olebi/projects/new-learning-platform/notebooks/psychopy/02_making_experiments_configurable/02_command_line_interfaces/react_to_tone.py': [Errno 2] No such file or directorySolution
pygame 2.6.1 (SDL 2.28.4, Python 3.10.16)
Hello from the pygame community. https://www.pygame.org/contribute.html
2.4655 WARNING Monitor specification not found. Creating a temporary one...
[['n', 3.8853570000001127]])Exercise: Add an integer argument n_trials to the ArgumentParser of react_to_tone.py.
Make the program play multiple tones at the given frequency in a loop with for i in range(args.n_trials).
Make the program wait for the key and print out the value returned by Keyboard.waitKeys() after every tone.
Solution
from argparse import ArgumentParser
from psychopy.event import waitKeys
from psychopy.sound import Sound
from psychopy.visual import Window
Sound(stereo=False)
parser = ArgumentParser(
description="Play a pure tone and wait for a key press. Optionally return the timestamp"
)
parser.add_argument("freq", type=int, help="Frequency of the tone in Hz")
parser.add_argument("key", type=str, help="Key to press")
parser.add_argument("n_trials", type=int, help="The number of trials")
parser.add_argument("--timed", action="store_true", help="Return a timestamp")
args = parser.parse_args()
with Window() as win:
for i in range(args.n_trials):
sound = Sound(value=args.freq)
sound.play()
key = waitKeys(keyList=[args.key], timeStamped=args.timed)
print(key)Exercise: Make react_to_tone.py play 3 trials with tones at 1000 Hz and wait for the space key
Solution
!python react_to_tone.py 1000 space 3Solution
pygame 2.6.1 (SDL 2.28.4, Python 3.10.16)
Hello from the pygame community. https://www.pygame.org/contribute.html
['space']
['space']
['space']
2.4967 WARNING Monitor specification not found. Creating a temporary one...Exercise: Make react_to_tone.py play "5" tones at "1600" Hz and wait for the "n" key
Solution
!python react_to_tone.py 1600 n 5Solution
pygame 2.6.1 (SDL 2.28.4, Python 3.10.16)
Hello from the pygame community. https://www.pygame.org/contribute.html
['n']
['n']
['n']
['n']
['n']
2.4661 WARNING Monitor specification not found. Creating a temporary one...Exercise: Create a new file called change_pitch.py in the SAME FOLDER as this notebook. In this program, create an ArgumentParser() that accepts integers for freq, step and n_trials. Make the program play mutliple tones using a loop with for i in range(args.n_trials). After every tone, wait until the "up" or "down" key was pressed. If the "down" key was pressed, decrease the tone frequency by step, if "up" was pressed, increase the tone frequency by step. Start at the frequency passed to the freq parameter. Hint: Keyboard.waitKeys() returns a list of keys — access the name of the pressed key as keys[0].
Solution
from argparse import ArgumentParser
from psychopy.event import waitKeys
from psychopy.sound import Sound
from psychopy.visual import Window
parser = ArgumentParser(
description="Play a pure tone and wait for a key press. Optionally return the timestamp"
)
parser.add_argument("freq", type=int, help="Frequency of the tone in Hz")
parser.add_argument("n_trials", type=int, help="The number of trials")
parser.add_argument("step", type=int, help="Frequency step size in Hz")
args = parser.parse_args()
freq = args.freq
with Window() as win:
for i in range(args.n_trials):
sound = Sound(value=freq, stereo=False)
sound.play()
print(f"playing {freq} Hz tone ...")
keys = waitKeys(keyList=["down", "up"])
if keys[0] == "down":
freq -= args.step # decrease frequency
else:
freq += args.step # increase frequencyExercise: Make change_pitch.py play 10 trials with a starting frequency of 1200 Hz and a step size of 50 Hz.
Solution
!python change_pitch.py 1200 10 50Solution
pygame 2.6.1 (SDL 2.28.4, Python 3.10.16)
Hello from the pygame community. https://www.pygame.org/contribute.html
playing 1200 Hz tone ...
playing 1250 Hz tone ...
playing 1200 Hz tone ...
playing 1150 Hz tone ...
playing 1100 Hz tone ...
playing 1050 Hz tone ...
playing 1000 Hz tone ...
playing 950 Hz tone ...
playing 900 Hz tone ...
playing 950 Hz tone ...
2.6909 WARNING Monitor specification not found. Creating a temporary one...Exercise: Make change_pitch.py` play 5 tones, starting at 2100 Hz with a step size of 100 Hz
Solution
!python change_pitch.py 2100 5 100Solution
pygame 2.6.1 (SDL 2.28.4, Python 3.10.16)
Hello from the pygame community. https://www.pygame.org/contribute.html
playing 2100 Hz tone ...
playing 2200 Hz tone ...
playing 2100 Hz tone ...
playing 2000 Hz tone ...
playing 1900 Hz tone ...
2.2500 WARNING Monitor specification not found. Creating a temporary one...Section 4: Combining CLI and Configuration Files
Now we can combine CLIs and configuration files to get the best of both worlds: We can use CLIs to pass in parameters that we want to change all of the time and configuration files to store the parameters that we don’t want to change as often. The program that is executed below runs a pure tone audiogram to detect the hearing threshold at a given frequency, a perfect use case to combine CLIs and config files!
Exercise: Copy the script below into a new file called audiogram.py and store it in the SAME FOLDER as this notebook. Then, run the audiogram.py.
from psychopy.sound import Sound
from psychopy.visual import Window, TextStim
from psychopy.event import waitKeys
### Define Parameters ####
FREQUENCY = 1000 # frequency of the pure tone
N_REVERSALS = 5 # number of times the staircase reverses
SECS = 0.25 # duration of the tone in seconds
KEY_YES = "y" # key to press if the tone was heard
KEY_NO = "n" # key to press if the tone was not head
START_VOLUME = 0.8 # starting intensity of the tone
STEP_SIZE = 0.1 # step size of the staircase
instructions = (
"Press " + KEY_YES + " if you heard the tone and " + KEY_NO + " if you didn't!"
)
#### Run Experiment ####
with Window() as win:
# show instructions
text = TextStim(win, text=instructions)
text.draw()
win.flip()
# create tone
tone = Sound(value=FREQUENCY, secs=SECS, volume=START_VOLUME, stereo=False)
# start the staircase
reversals = []
direction = -1 # directon of the staircae
while len(reversals) < N_REVERSALS:
tone.play()
keys = waitKeys(keyList=[KEY_NO, KEY_YES])
if keys[0] == KEY_NO: # increase volume if key was not heard
if direction == -1:
direction = 1
reversals.append(tone.volume) # append current level
tone.setVolume(tone.volume + STEP_SIZE)
else: # decrease volume if key was heard
if direction == 1:
direction = -1
reversals.append(tone.volume) # append current level
tone.setVolume(tone.volume - STEP_SIZE)
threshold = round(
sum(reversals) / len(reversals), 3
) # compute threshold by averaging reversals
print("The detection threshold for " + str(FREQUENCY) + " Hz is: " + str(threshold))Solution
!python audiogram.pySolution
Solution
pygame 2.6.1 (SDL 2.28.4, Python 3.10.16)
Hello from the pygame community. https://www.pygame.org/contribute.html
The detection threshold for 1000 Hz is: 0.04
2.3222 WARNING Monitor specification not found. Creating a temporary one...Exercise: In the beginning of the script audiogram.py, multiple parameters are defined (e.g. FREQUENCY). Create a file called audiogram_config.json that stores these parameters. Then, modify audiogram.py so that it loads audiogram_config.json and replace the parameters with the values stored in the file. Try modifying the configuration file an re-running the program!
Solution
import json
from psychopy.sound import Sound
from psychopy.visual import Window, TextStim
from psychopy.event import waitKeys
### Import Parameters ####
with open("audiogram_config.json", "r") as f:
params = json.load(f)
FREQUENCY = params["frequency"] # frequency of the pure tone
N_REVERSALS = params["n_reversals"] # number of times the staircase reverses
SECS = params["secs"] # duration of the tone in seconds
KEY_YES = params["yes"] # key to press if the tone was heard
KEY_NO = params["no"] # key to press if the tone was not head
START_VOLUME = params["start_volume"] # starting intensity of the tone
STEP_SIZE = params["step_size"] # step size of the staircase
...Exercise: Now add add an ArgumentParser to audiogram.py that accepts strings for config_file and use the value of args.config_file as path when loading the configuration file. Run audiogram.py using audiogram_config.json. Then, create a new configuration file and run audiogram.py with the new configuration. OPTIONAL: add a description to the parser and a help to the argument.
Solution
import json
from argparse import ArgumentParser
from psychopy.sound import Sound
from psychopy.visual import Window, TextStim
from psychopy.event import waitKeys
parser = ArgumentParser(description="Run a pure tone audiogram")
parser.add_argument("config_file", type=str, help="Path to the configuration file")
args = parser.parse_args()
### Import Parameters ####
with open(args.config_file, "r") as f:
params = json.load(f)
FREQUENCY = params["frequency"] # frequency of the pure tone
N_REVERSALS = params["n_reversals"] # number of times the staircase reverses
SECS = params["secs"] # duration of the tone in seconds
KEY_YES = params["yes"] # key to press if the tone was heard
KEY_NO = params["no"] # key to press if the tone was not head
START_VOLUME = params["start_volume"] # starting intensity of the tone
STEP_SIZE = params["step_size"] # step size of the staircase
...Exercise: Add an integer "frequency" argument to the parser and use this argument instead of the value stored in the configuration file to set the FREQUENCY of the tone. Then run audiogram.py twice, using the same configuration file but different tone frequencies.
Solution
import json
from argparse import ArgumentParser
from psychopy.sound import Sound
from psychopy.visual import Window, TextStim
from psychopy.event import waitKeys
parser = ArgumentParser(description="Run a pure tone audiogram")
parser.add_argument("config_file", type=str, help="Path to the configuration file")
parser.add_argument("frequency", type=int, help="Frequency of the tone in Hz")
args = parser.parse_args()
### Import Parameters ####
with open(args.config_file, "r") as f:
params = json.load(f)
FREQUENCY = args.frequency # frequency of the pure tone
N_REVERSALS = params["n_reversals"] # number of times the staircase reverses
SECS = params["secs"] # duration of the tone in seconds
KEY_YES = params["yes"] # key to press if the tone was heard
KEY_NO = params["no"] # key to press if the tone was not head
START_VOLUME = params["start_volume"] # starting intensity of the tone
STEP_SIZE = params["step_size"] # step size of the staircase
...