Wakefield's Daily Tips

Use height units

If you are using a head rest or controlled environment where you have set up the monitor details in the PsychoPy monitor centre then feel free to use cm or degrees. This is particularly recommended if you are presenting stimuli to two screens.

On the other hand, if you aren’t controlling the distance between the participant and the computer or are running your study online, I would recommend using height units rather than pix or norm.

In norm units the screen is 2 x 2 (bottom left [-1,-1] to top right [1,1]). I sometimes use norm units for paragraphs of text but they don’t work well with images or polygons because the aspect ratio depends on the aspect ratio of the monitor.

Pixels are fine for aspect ratio but the overall size of the stimulus depends on the display resolution. You can’t assume that every computer is 1920 x 1080. You might be tempted to use pixels so you can match the size of your stimulus to ones in the literature. However, the size of a pixel is not constant and in general older computers will have had larger pixels so a 100 x 100 pixel square from 2010 is probably larger than a 100 x 100 pixel square from 2020.

Height units are an excellent compromise. The shortest dimension of the screen is 1 height unit so a .1 x .1 polygon will look square. Note that in portrait orientation the width of the screen is 1 height unit and the height is more than that so if you want your stimuli to fit equally well on both landscape and portrait format screens, you should restrict yourself to a 1 x 1 square area.

Height units have two other advantages.

  1. In general someone will view a small screen from closer than a larger screen so there will be better visual angle constancy than using pixel units.

  2. They are the default units in PsychoPy so when you create a new visual component it will have a reasonable initial size. If you use pix then you will need to change the default values otherwise the stimuli will be too small to see or give an error message.

1 Like

Avoid multiple spreadsheets

One way of counterbalancing blocks in an experiment is to have an outer loop which points to a spreadsheet which contains the names of different spreadsheets to use in an inner loop. The main issue with this is that if you have multiple spreadsheets with the same columns and you want to make a change, you have to make it to all of them. The other more minor issue is that you may have to specify the spreadsheets as additional resources in Experiment Settings / Online.

Instead, what I recommend is to have the spreadsheet attached to the outer loop contain the rows that you want to use in a single spreadsheet attached to the inner loop and use the Selected Rows parameter.

There are two ways to specify rows. Both use the row index, which is the row number in Excel minus 2 (row 1 contains the column titles, row 2 contains the first condition which is index 0).

I usually use the variable $useRows in Selected Rows (note that a $ is needed in the parameter field).

useRows = '0:10' specifies the first ten conditions, with indices from 0 to 9
useRows = '10:20' specifies the second ten conditions, with indices from 10 to 19
etc.

The alternative method is for useRows to be a list of indices

useRows = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
specifies the first ten conditions, with indices from 0 to 9
useRows = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
specifies the first ten conditions, with indices from 10 to 19

The advantage of the list method is that you can change the order, skip rows or even duplicate them. If you set the loop type itself to sequential then you can randomise the order with constraints before the loop starts. I use this for my Complex Randomisation and Repeat Subset online demos.

Complex Randomisation code | try it | discussion thread

This demo is for anyone who wants to randomise the order of their trials with constraints, and can do so based on the row numbers. In this case the demo produces CVC letter strings by shuffling rows 0 to 4 for the vowels and 5-25 for the consonant, and then adding appropriate items to useRows. The loop itself is sequential. The advantage of this method over preloading stimuli is that it does not require extra loops or lists – the trials loop itself is attached to the conditions spreadsheet as normal.

Repeat Subset code | try it | discussion thread

Adds errors made during the first loop to a list which used by the second loop, so that only incorrect trials are repeated.

1 Like

Pavlovia usernames should contain letters

Pavlovia uses GitLab to host experiments and users have a single username and password which gives access to both sites.

When creating an account, there may be a temptation for students to use their student number as their username. However, usernames that just contain digits cause problems for any hosted experiments.

Use your institutional email address for your account but ensure that your username contains at least one letter.

If you have already created an account with a numeric username please change it and then wait a few days. Alternatively, you could create a new account and fork projects from your old account.

https://psychopy.org/online/sharingExperiments.html

Cite your sources

Publications should refer to PsychoPy in the method section, and also Pavlovia if you ran the experiment online.

For example:

The experiment was created using PsychoPy Builder (Peirce et al., 2019) and hosted on Pavlovia (Open Science Tools Ltd., Nottingham, UK). To ensure consistent stimulus sizes across different devices, participants were asked to place a credit card on the screen and resize an image to match (Morys-Carter, 2023). The task that followed displayed 5cm tall Navon figures with a variable number of local items (Morys-Carter, 2021).

Morys-Carter, W.L. (2021, December 8). Navon figures [Computer software]. Pavlovia. https://pavlovia.org/vespr/navon-figures

Morys-Carter, W. L. (2023, July 4). ScreenScale [Computer software]. Pavlovia. https://doi.org/10.17605/OSF.IO/8FHQK

Peirce, J. W., Gray, J. R., Simpson, S., MacAskill, M. R., Höchenberger, R., Sogo, H., Kastman, E., Lindeløv, J. (2019). PsychoPy2: experiments in behavior made easy. Behavior Research Methods. https://dx.doi.org/10.3758/s13428-018-01193-y

Note that in one case I have included a link to the GitLab repository and in the other I created a DOI by uploading a copy of the code to OSF.

To encourage researchers to reuse my code, I set the visibility of the project on GitLab to public and add a licence (GNU General Public License v3.0). If you want to use software set to public on GitLab which doesn’t have a licence you should try to contact the original author and ask them to add one.

If you are only using someone else’s code to help you write or debug your own code, but do not use a substantive portion of it, then you do not need to add a reference, but you could acknowledge their contribution. People sometimes acknowledge me rather than cite me when incorporating my ScreenScale routine. A citation is definitely preferred. On the other hand, I wouldn’t expect a citation from someone who has been guided to an approach in their own code by looking at, for example, my Independent Randomisation online demo.

Coding is case sensitive

Python and JavaScript are both case sensitive languages. The commands you write in code components must be of the correct case, and the cases of the names of your components and variables must match. For example, you could have three distinct variables called item, Item, and ITEM (don’t do this).

In addition, when running online you also need to use the correct case for your filenames. Locally you can have item.png in your spreadsheet and your experiment will successfully display item.PNG. Online, however, this will fail so you will need to either change the capitalisations of the file names in your spreadsheet/experiment or change the file names themselves.

Define functions to reduce manual editing of JavaScript code

Following on from Wakefield's Daily Tips - #6 by wakecarter, I usually have one code component set to Both in the first routine (which I usually call code_both). This component can be used to define functions in the Before Experiment tab in both Python and JavaScript so that they can be called later in an Auto code component.

For example, here is inverse tan, which doesn’t yet get automatically translated from math.atan() to Math.atan()

Python

import math
def atan(x):
     return math.atan(x)

JavaScript

function atan(x) {
return Math.atan(x);
}

In the experiment itself I can then use atan(x)

Check for blank rows in your spreadsheets

It is all too easy to end up with blank rows or columns in a spreadsheet. If you highlight cells and then press delete, you are removing the contents from the cells, not deleting them altogether. If you experience blank trials or errors at random points in a loop then blank rows are a likely culprit. If you set the loop type to sequential, then the error will probably happen after your expected trials.

You can tell how many rows your spreadsheet has by reattaching it to the loop and seeing whether the number of conditions reported by PsychoPy matches the number you expect.

If you use CSV files instead of Excel then you can easily check for blank rows in GitLab or locally in a text editor. Use UTF-8 encoding if you need your spreadsheet to contain new line or other extended characters.

1 Like

Test the installation

If you have just installed PsychoPy (or have upgraded and you have having issues with experiments that previously worked) I would recommend that you create a very simple experiment from scratch to test your installation.

  1. Add a Text component from Stimuli. Keep the default values.
  2. Add a Keyboard component from Responses. Keep the default values.
  3. Save the file in an empty folder.

|756px;x433px;

  1. Run the test.
  2. Wait for the starting dialogue box to appear. Click OK.
    |378px;x219px;

Text should appear for 1 second. The experiment should end when you press a valid key.

PsychoPy should return to the builder view but also add information to the Runner window which includes warnings (which you can ignore) and errors.
|582px;x251px;
Exit code 0 means no errors (even if you didn’t get what you wanted).

If you are using a Mac then you may need to authorise PsychoPy to monitor your input.

  1. Go to System Preferences (easiest way is to press Cmd+space together, and type System Preferences)
  2. Go to Security & Privacy (top row, the house symbol).
  3. Go to Privacy (top right) and unlock.
  4. Go to Input Monitoring (left hand side)
  5. Add a tick next to PsychoPy
  6. Click Save and Reopen. PsychoPy will need restart.

If this test fails then there is an issue with your installation which you need to solve before working on your own experiment.

PsychoPy files are saved in plain text

When you save your experiment in Builder the saved .psyexp file is a script that can be opened in a text editor. This can be particularly helpful if your .psyexp gets corrupted or if you want to search and replace a variable name. The Python and JavaScript files are also saved in plain text but see Wakefield's Daily Tips - #17 by wakecarter

Here’s an example of a PsychoPy Coder script that you can use to search for a particular word in the psyexp files across all of your experiment folders. [I should use this more often myself].

# https://stackoverflow.com/questions/4205854/recursively-find-and-replace-string-in-text-files
import os, fnmatch
def search(directory, find, filePattern):
    for path, dirs, files in os.walk(os.path.abspath(directory)):
        if path.count(os.path.sep) == directory.count(os.path.sep) + 1: # One more than \\ in directory
            for filename in fnmatch.filter(files, filePattern):
                print(filename)
                filepath = os.path.join(path, filename)
                try:
                    with open(filepath) as f:
                        s = f.read()
                    if s.find(find,0) > -1:
                        print("Found " + find + " at position " + str(s.find(find,0)))
                except:
                    print("Failed to open",filename)

#This allows you to do something like:
search("C:\\Users\\userName\\Documents\\Pavlovia", "searchTerm", "*.psyexp")

Note the double \ between folders in the path

1 Like

Use foreslashes not backslashes in file paths

Windows file paths use backslashes (\) to separate the folders in the path.

This doesn’t work online because Linux-base servers use slashes (/) instead. Worse than that, the backslash character is used to mark the beginning of an “escape sequence” so the following character is interpreted by JavaScript as something else.

Here are some examples:
\n new line
\t tab
\' or \" Literal single or double quotes
\\ Literal \
\b backpace
\xHH character repesented by the hexadecimal number HH in ASCII (Windows-1252)

1 Like

Show your file extensions

By default Windows hides the file extensions of file types it recognises, instead showing an icon to indicate the type of file. In PsychoPy, and especially when running experiments online, you need to be sure that the file extensions you include in your spreadsheets and Builder files exactly match the ones on your computer.

Here is where you turn on viewing file extensions (and hidden files) in Windows Explorer.

On a Mac the instructions from Show or hide filename extensions on Mac – Apple Support (UK) are:

  1. In the Finder on your Mac, choose Finder > Settings, then click Advanced.
  2. Select “Show all filename extensions”.

Use Routine Settings

As of 2024.1.0, each standard routine includes its own settings, accessed by a cog in the top left.

Routine Settings includes five tabs: Basic, Flow, Window, Data and Testing.

So far I have mostly used the Flow tab to either add a duration to the routine or a “skip if” condition.

Routine durations are easier than adding durations to every component in the routine separately and works even when a component hasn’t been started because its start condition hasn’t been met).

Skip if is better than using continueRoutine = False in Begin Routine because the entire routine gets skipped rather than Begin Routine and End Routine code components still getting processed. I also use the Testing tab to disable a routine without having to remove it from the flow.

I haven’t tried Window yet but it also looks useful, allowing routines to have different backgrounds from the experiment settings.

Locate Experiment Settings

In versions prior to 2025.1.0, one of the most important menu items didn’t actually appear in any of the menus. You could only access it with a mouse by clicking on the cog icon.

From 2025.1.0 onwards it is also accessible from the Experiment menu and via a keyboard shortcut (Ctrl-Shift-X)

Experiment Settings itself has seven tabs.

General is used to define expInfo dialogue box variables and set the PsychoPy version (leave blank unless you have a reason to want an earlier version).

Screen is used to show the mouse, define the background colour and select the units.

Online is used to set the completed URL ad End Message and attach resources.

Input is used to set the keyboard backend (if a simple experiment fails try switching between ioHub and PsychToolbox).

Data is used to set the column order (change to column priority or alphabetical if the default doesn’t work for you. Changing the data filename doesn’t work online.

Daisy Chaining

Daisy chaining means going from one web application to another in a single participant session. It normally requires passing the values of one or more variables from one app. to the next so that the data can be matched.

There are two aspects to consider.

The first is sending values TO Pavlovia. I highly recommend that you send a value for participant (without changing the spelling or capitalisation) since this value will start the data file and log file names. A column for session always appears in the data file, so I usually use this too.

To send a value to Pavlovia, it must be included in the URL. For example.

Prolific: https://run.pavlovia.org/user/study/?participant={{PROLIFIC_ID}}

Qualtrics: https://run.pavlovia.org/user/study/?participant={e://Field/ResponseID}

Sona: https://run.pavlovia.org/user/study/?participant=%SURVEY_CODE%

Note that if you add a second variable, you must use & instead of a second ?. If you want to use the variable during the experiment, rather than just have it appear in the data file, add it to the expInfo dialogue box in Experiment Settings.

If you want participants to be sent to another web app. after they complete the experiment on Pavlovia, add a completed URL in Experiment Settings / Online. Variables received by or set in the expInfo dialogue box can be added here directly. For example:

**Prolific: ** $"https://app.prolific.co/submissions/complete?cc=3E6BEA76&PROLIFIC_ID=" + expInfo['participant']

**Qualtrics: ** $"https://brookeshls.co1.qualtrics.com/jfe/for/SV_123456?participant=" + expInfo['participant']

**Sona: ** $"https://yourschool.sona-systems.com/webstudy_credit.aspx?experiment_id=123&credit_token=abcd&survey_code=" + expInfo['participant']

If you want to change the URL after the experiment has started, then you need to add the following JavaScript code where myCompletedURL is a variable containing the new completed URL. You must have something in the completed URL field in Experiment Settings for this to work.

psychoJS.setRedirectUrls(myCompletedURL, myCancelledURL)

In Pavlovia Surveys use the completion URL field on the study overview tab. The syntax is slightly different.

**Prolific: ** "https://app.prolific.co/submissions/complete?cc=3E6BEA76&PROLIFIC_ID=" + {participant}

**Qualtrics: ** "https://brookeshls.co1.qualtrics.com/jfe/for/SV_123456?participant=" + {participant}

**Sona: ** "https://yourschool.sona-systems.com/webstudy_credit.aspx?experiment_id=123&credit_token=abcd&survey_code=" + {participant}

In this case variables can be set during the survey so an equivalent of setRedirectUrls isn’t needed. Do not put anything in the completion URL field of a Pavlovia Survey if the survey is being embedded into an experiment.

1 Like

How to use the Pavlovia Shelf

If you want to store and reuse information during a single session, you should just use a variable (such as a list or dictionary). On the other hand, if you want to store information for future sessions (either by the same participant in a longitudinal study or different participants) then the solution online is to use the Pavlovia Shelf.

The Shelf is not designed for real-time interactions between participants so it should not be used accessed within an Each Frame tab or within a loop.

To use the Shelf, first go to the Shelf tab on Pavlovia and click on Add record.

A new record will appear at the bottom of the list ready to be created

image

The name of the record goes in “key components”. Scope can either be Designer (if you want the entry to be accessible by multiple experiments) or Experiment.

Type can be Integer, Boolean, Dictionary, List, Text or Counterbalance. I’ll come back to Counterbalance in a future tip, but for other uses I find that Dictionary is the most flexible.

Since the Shelf only works online I use JavaScript code components to work with it.

Dictionaries

// Store the field names in the Shelf entry in a list
fieldNames = await psychoJS.shelf.getDictionaryFieldNames({key: ["shelfName"]});
// Retrieve the value of a dictionary field in a Shelf entry
fieldValue = await psychoJS.shelf.getDictionaryFieldValue({key: ["shelfName"], fieldName:"fieldName", defaultValue:"fieldName not found"});
// Update the value of a dictionary field in a Shelf entry
await psychoJS.shelf.setDictionaryFieldValue({key: ["shelfName"], fieldName: "fieldName", fieldValue :newFieldValue});

Lists

// Set the value of a list Shelf entry
list = await psychoJS.shelf.getListValue({key: ["shelfName"]});
// Replace a list Shelf entry
await psychoJS.shelf.setListValue({key: ["shelfName"], value: newList})
// Append to a list Shelf entry
await psychoJS.shelf.appendListValue({key: ["shelfName"], elements: extraListValue})
// Pop from a list Shelf entry
finalListValue = await psychoJS.shelf.popListValue({key: ["shelfName"]})

If you are working with a Shelf entry with designer scope instead of experiment, then the key value should be changed to key: ["shelfName", "@designer"] (use @designer literally, rather than replacing designer with your Pavlovia username).

“fieldName” can be replaced with a string variable containing the field name.

Note that await means that processing will stop until the Shelf operation has finished. You can remove it if you don’t need to use the results AND you aren’t going to access the shelf again before the operation has completed.

For more information, have a look at the documentation. Using the Shelf for Multi-session testing, Counterbalancing and more online — PsychoPy v2024.2.5

Don’t delete your repository

I know it can be frustrating when there are synchronisation errors between your local PsychoPy folder and its GitLab repository, but please resist the temptation to delete the repository on GitLab and start again. The main reasons to avoid this are:

  1. It often doesn’t solve the issue.
  2. You will have to find and delete the local git files and folders.
  3. There is a risk that you will accidentally delete all of your projects or even all of your group’s projects.
  4. You will lose the commit history.

A better approach is to sync the GitLab repository to a new local folder.

  1. Ensure you are logged into Pavlovia in PsychoPy Builder and search for projects.

  1. Search for the project you want to restore
  2. Click on the folder icon at the right of Local root and navigate to a new local folder. If you know your repository is up to date you can delete your old local folder at the same time.
  3. Press Download

  1. Check the Pavlovia tab of the PsychoPy Runner for progress.

  1. Close the “search for projects online” window and open the psyexp file in the new folder.
  2. If you want to replace this psyexp file with one you have been working on locally, open the newer psyexp file from the broken folder, save it into the new folder and then reopen it.
  3. Move any missing/updated resources from the broken folder into the new folder.
  4. Resync.

If these steps fail, check that you can sync a new simple experiment to a new repository. If not, then you might need to edit or remove some of your configuration files, such as:

C:\Users\userName\AppData\Roaming\psychopy3\appData.cfg
or
C:\Users\userName\AppData\Roaming\psychopy3\pavlovia\users.json

How to use the colour picker

image

Once you have selected your colour and pressed Insert, three numbers will be added to the field.

image

Left like this, the colour parameter will fail and default to black. Surround the numbers in square brackets and prepend a $ to turn it into a list.

image

The options none and transparent do not work, and simply add 0, 0, 0 (mid grey). For transparent type None (no $)

image

Use keyboard components instead of event.getKeys()

The keyboard component in PsychoPy Builder uses the Keyboard class, which has better timing than the older event module. If you want to identify whether a key has been pressed in Each Frame then code of the form

keys = event.getKeys()

can be replaced with

keys = []
if key_resp.keys: # Keyboard component should store all keys and not force the end of the routine
    if len(key_resp.keys) > nKeys: # Add nKeys = 0 in Begin Routine
        keys = [key_resp.keys[-1]] # keys must be a list
        nKeys = len(key_resp.keys)

In both cases the code can be followed by:

if len(keys):
    if 'return' in keys and len(displayText)>1: # Ensures something has been typed
        continueRoutine=False
    elif 'space' in keys:
        displayText += ' '
    elif 'backspace' in keys: # Doesn't work online
        displayText=displayText[:-1]
    elif 'N/A' in keys: # Several keys return N/A online
        displayText=displayText[:-1]
    elif 'comma' in keys:
        displayText += ','
    elif 'semicolon' in keys:
        displayText += ';'
    elif 'period' in keys:
        displayText += '.'
    elif 'minus' in keys:
        displayText += '-'
    elif 'equal' in keys:
        displayText += '='
    elif 'bracketleft' in keys:
        displayText += '['
    elif 'bracketright' in keys:
        displayText += ']'
    elif 'lshift' in keys or 'rshift' in keys:
        modify = True # Capitalise the next letter
    elif modify==False:
        displayText += keys[0]
    else:
        if '1' in keys:
            displayText += '!'
        elif '2' in keys:
            displayText += '\"'
        elif '3' in keys:
            displayText += '£'
        elif '4' in keys:
            displayText += '$'
        elif '5' in keys:
            displayText += '%'
        elif '6' in keys:
            displayText += '^'
        elif '7' in keys:
            displayText += '&'
        elif '8' in keys:
            displayText += '*'
        elif '9' in keys:
            displayText += '('
        elif '0' in keys:
            displayText += ')'
        elif 'minus' in keys:
            displayText += '_'
        elif 'equal' in keys:
            displayText += '+'
        else:
            displayText += keys[0].upper() 
        modify = False

This code requires the following in Begin Routine

nKeys = 0
displayText = ''

In cases where the Keyboard class is insufficient for your needs, an editable textbox using the following code might help. However, it fails if participants move their cursor away from the end of the string using the left arrow key.

if len(textbox.text) > nKeys:
    keys = textbox.text[-1] # keys doesn't need to be a list because it can only have one character
elif len(textbox.text) < nKeys:
    pass # Add code here if you want to register that backspace has been pressed
nKeys = len(textbox.text)

One final note. on today’s tip. If you are unable to run a simple experiment that has a keyboard component, try changing the keyboard backend in Experiment Settings / Input. There seem to be some compatibility issues with ioHub which are fixed by switching to PsychToolbox.

Don’t use polygon borders above 20

Even if you use height units, polygon borders are measured in pixels. Artifacts appear locally when larger values are used. In the screenshots below, the circle on the right has a size of .3, a fill colour of None and a line width of lineWidth, the value shown above the circles. The circle on the left has a size of .3, a fill colour of white and a grey circle in front of it with a size of .3 - lineWidth / 1000.

Note that the overall size of the circle on the right gets bigger when the lineWidth increases. I could have emulated this on the left by also changing the white circle but felt that in real experiments, I would probably prefer to keep the external size constant.

These artifacts do not appear online.

However, the resolution is affected by the display scale. The above screenshots were on my laptop at 250%.

This is at 100%

and this is at 350%

Unfortunately there doesn’t seem to be a way for PsychoPy to detect or compensate for the participant’s display scale.