Wakefield's Daily Tips

Use the topic template

I’ve just updated the topic template for the Builder category and copied it (with minor edits) to the Online category.

Please answer the questions on the template.

OS (e.g. Win10): Macs can be particularly tricky. Older versions of Windows may need older versions of PsychoPy.

PsychoPy version (e.g. 2024.2.4 Py 3.8): Your issue may have been fixed in a later version, or may work better in an earlier version.

Standard Standalone Installation? (y/n) If not then what?: Custom installations or VPNs may bring their own problems.

Do you want it to also run online? (y/n) Some solutions only work locally (or only work online) so it is useful to know whether you want it to work in PsychoPy and PsychoJS or just one of the two.

What are you trying to achieve?: Be brief.

What did you try to make it work?: Include custom Python code and/or screen shots of your flow and relevant routines/components.

Link to the most relevant existing thread you have found: Search for your error message if you have one, and use the advanced options to find solved topics.

What specifically went wrong when you tried that?: Give the error message and/or clear details of what went wrong.

Use HTTPS to connect to GitLab not SSH

While GitLab gives instructions for using SSH keys, this method does not work with gitlab.pavlovia.org.

Most users should be able to sync directly from PsychoPy Builder without needed to choose between HTTPS and SSH at all.

However, if you do need to make a choice, for example because you are cloning a repository, use HTTPS


If you have already cloned a repository using SSH, open the hidden .git/config file within the project and replace all mentions of git@gitlab.pavlovia.org: with https://gitlab.pavlovia.org/ (or fetch the URL from GitLab site and paste the HTTPS URLs one-by-one.

If you have a reason why you cannot use HTTPS, please give details on the following thread.

Compare elements not lists

Lists can’t be compared directly.

So, while locally if a = [1, 1] and b = [1, 1] then a == b is True, online a == b will be false.

If you want to check that two lists are identical, check a[0] == b[0] and a[1] == b[1].

Alternatively, if your arrays contain string variables you could join the lists and compare the results. Happily, "".join(a) == "".join(b) successfully auto translates to a.join("") === b.join("")

Use relative paths

Locally you can specify locations of your resources using absolute paths so which means that, for example, you can keep your stimulus sets separate from your experiments.

Online, all resources need to be in the experiment folder or one of its subfolders. Even when you test on your own computer you can’t ask the browser to look for images on your desktop.

Sound components should start after the routine has started

If you are playing sounds online, then I would recommend setting the start time of any sound files in loops at .1 seconds or later. Since 2023.2 sounds often fail to play online after the first iteration of a loop. It seems to be that if the sound isn’t ready when it is supposed to start it “misses the boat”.

If your sound component has a start condition then I would recommend adding a time component to it. For example:
Begin Routine

playSound = False

Each Frame

if not playSound and t > .1 and showAudio:
     playSound = True

Then use playSound as the start condition instead of showAudio (in this example).

Larger sound files may need longer to load, in which case it would be worth including an ITI at the start of the trial routine (rather than having it as a separate routine, which is my usual approach).

Spreadsheets

One of the aspects I love about PsychoPy is its use of spreadsheets. Before I switched to PsychoPy I used to user SuperLab 2. At the time, you had to enter all of the trial specifications directly into the software. I used to set up the framework of an experiment and then leave the student (whose project I was working on) to laboriously enter the stimuli and then connect each item to the corresponding trial. With SuperLab 4 this aspect was made much easier, but that effectively meant that I no longer had any aspects to delegate to the student.

On the other hand, in PsychoPy I can show the student a template spreadsheet and then ask them to send me an updated one with their stimuli / conditions / file names, etc.

A PsychoPy spreadsheet needs to have variable names in the first row and then conditions the values for those variables in the following rows. There should not be any empty rows and the variable names must not contain spaces. The format can be CSV or Excel xlsx, but in the latter case only the first sheet is used.

What types of variables go in a PsychoPy spreadsheet?

Information: So long as ‘is trials’ is ticked in the loop then the values of each variable in the spreadsheet will be copied into the data file. This means that you might include variables such as ItemType which have no effect on the experiment, but will be useful at the analysis stage.

Text: You can set the text of a text or textbox component to come from a spreadsheet. This is usually used for text stimuli, but it can also be used for longer text, such as instructions. The cells can contain newlines, but their behaviour can be different online compared with locally and Excel vs CSV.

File names: Media cannot be embedded into a PsychoPy spreadsheet. Instead, you can include the file name, including the relative path and extension, such as images/hat.png.

Parameters: Many numeric parameters can be updated via a spreadsheet. Lists don’t always work so one option is to set the values (for example of positions or colour channels) separately.

Correct Answer: Keyboard components (and mouse components in the most recent versions) can accept a variable as the correct answer. If the response matches the given answer, the .corr value is set to 1, otherwise it is set to 0.

Spreadsheet names: If you set up concentric loops, the spreadsheet for the outer loop can specify which spreadsheet to use in an inner loop. However, in another tip, I recommend setting the selected rows instead.

WordHTML

If you are using an HTML question in Pavlovia Surveys or embedded HTML in PsychoPy then you will need to convert your formatted text into HTML.

Although I can write HTML from scratch, it is much easier to start with a formatted document or use a WYSIWYG editor. The one I now use all the time is WordHTML.com.

  1. Paste formatted text into the Word side.
  2. Press the clean button on the HTML side. If this removes too much formatting, click on the undo icon.
  3. Make any edits you need on the Word side.
  4. Highlight the HTML code.
  5. Copy and paste into the HTML markup of a Pavlovia Survey question.

If you are starting with existing HTML, then you can paste into the HTML side, edit on the Word side and then copy and paste the new HTML.

Saving incomplete results

When I create a new study on Pavlovia, one of the first things I do is turn off “save incomplete results”.

The reason I do this is to avoid having lots of data files getting created while I build and test the experiment. This is especially important if you use credits rather than a licence, since each data file will cost a credit.

Even when turned on, saving incomplete results can be a bit hit and miss. The data is always saved if you press escape to exit full screen and then press escape again. It also seems to work if you press escape and then close the tab, but not if you press escape and then navigate to another web page within the same tab.

The other issue is that if you have saving incomplete results turned on, you will get near empty data files from anyone who exits near the start of the experiment, for example before consenting or while reading the instructions.

My recommendation is to turn on incomplete data saving in code at the point where enough results have been collected that you would consider using them. Be mindful of research ethics. If you have told your participants that they have the right to withdraw at any point in the study and the only way to withdraw is by pressing escape, then ethically you should not use their data.

With this in mind, the code to turn on incomplete data saving is:

psychoJS._config.experiment.saveIncompleteResults = true;

It should appear in a JavaScript code component or on the JS side of a Both code component.

In the example I wrote to test this today, I turn on saving incomplete results on the fourth iteration of the loop with:

if ((trials.thisN === 3)) {
    psychoJS._config.experiment.saveIncompleteResults = true; // enable partial data collection
}

As noted in the linked thread, this code may not work in Firefox, but I don’t know if that means that incomplete data saving doesn’t work at all, whether the value can’t be changed in code, or whether changing the value after the start of the experiment has no affect.

1 Like

Extending lists

In Python you can extend lists using the addition sign or the extend function.

a = [1, 2]
b = [3, 4]
a = a + b
print('a1', a)
a.extend(b)
print('a2', a)
b = b.extend(b)
print('b1', b)

Output
a1 [1, 2, 3, 4]
a2 [1, 2, 3, 4, 3, 4]
b1 None

For PsychoJS this code is auto translated to:

a = [1, 2];
b = [3, 4];
a = (a + b);
console.log("a1", a);
a.concat(b);
console.log("a2", a);
b = b.concat(b);
console.log("b1", b);

Output
a1 1,23,4
a2 1,23,4
b1 [3, 4, 3, 4]

Two things are going wrong here.

  1. a + b is concatenating the two lists as strings.
  2. a.concat(b); doesn’t alter list a

As per Wakefield's Daily Tips - #27 by wakecarter, here is a custom function for joining two lists.

Python

def concat(a, b):
    return a + b

JavaScript

function concat(a, b) {
    return a.concat(b);
}

Then in your code component, write c = concat(a, b) instead of c = a + b.

This could be exended to three (or more) lists using a+b+c+etc. and a.concat(b, c, etc.) in the function. If would also be possible to set up a more complex function which checks the number of arguments, but unless you are regularly concatenating a chain of lists, it would probably be easier to just call the simple function more than once.

Saving screenshots

I haven’t managed to get this working online, but locally you can take a screenshot with win.getMovieFrame() and then save it with the name file name as the rest of your data files using win.saveMovieFrames(thisExp.dataFileName+'.png'). You might want to have the get code in Each Frame but make sure it doesn’t run every frame.

The closest I’ve got with saving a screenshot online is saveScreenshot("screenshot"); where saveScreenshot is defined in Before Experiment as:

function saveScreenshot(filename, scale = 0.5) {
    let canvas = document.querySelector('canvas'); // Get the PsychoJS canvas

    // Create a smaller offscreen canvas
    let scaledCanvas = document.createElement('canvas');
    let ctx = scaledCanvas.getContext('2d');
    
    // Set new dimensions (50% of original size)
    scaledCanvas.width = canvas.width * scale;
    scaledCanvas.height = canvas.height * scale;
    
    // Draw original canvas onto the smaller canvas
    ctx.drawImage(canvas, 0, 0, scaledCanvas.width, scaledCanvas.height);

    // Convert to Base64 PNG
    let imageData = scaledCanvas.toDataURL("image/png");
    let base64Data = imageData.split(',')[1]; // Remove metadata

    // Store Base64 image data in experiment file
    psychoJS.experiment.addData(filename, imageData);

    console.log("Screenshot saved at reduced resolution:", filename);
}

This should save the base64 image data into a cell in the CSV file at 50% resolution to reduce the amount of data. However, when I pasted the resulting code into a web browser, I got a white rectangle. Suggestions welcome.

Random functions

Do not import random in Builder. As mentioned in Wakefield's Daily Tips - #19 by wakecarter Builder scripts already contain the line:

from numpy.random import random, randint, normal, shuffle, choice as randchoice

Their usage follows:

a = random() # for a random number 0 <= a < 1
b = randint(i) # for a random integer 0 <= b < i
c = normal(m, s) # for a random sample from a Gaussian distribution of mean m and standard deviation s
shuffle(d) # to shuffle list d
e = randchoice(d) # for a random item from list c

These auto translate to:

a = Math.random();
b = util.randint(i);
c = normal(m, s);
util.shuffle(d);
e = util.randchoice(d);

However, for them all to work online, you need to define a normal function in Before Experiment in a JavaScript (or Both) code component.

function normal(mean=0, stdev=1) {
    const u = 1 - Math.random(); // Converting [0,1] to [1,0]
    const v = Math.random();
    const z = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v );
    // Transform to the desired mean and standard deviation:
    return z * stdev + mean;
}

This uses the Box-Muller transform and this Python code came from algorithm - JavaScript Math.random Normal distribution (Gaussian bell curve)? - Stack Overflow.

In Python you can add a size parameter to some of these functions to return a list or array of random numbers, but I would advise against using this since it doesn’t translate to PsychoJS.

Pilot mode

Piloting a PsychoPy experiment can mean one of three things.

Locally you can switch between Pilot and Run mode by clicking on the toggle between the two words.


In pilot mode the two run buttons are orange instead of green.

Pressing the main run button will run the experiment in a window (rather than full screen) and include a and orange border and the warning PILOTING: Switch to run mode before testing.

The parameters for Pilot mode can be edited from File / Preferences

If instead of piloting locally, you press the JS run button, the experiment will be run locally in your default browser (in full screen). To do this, a copy of the PsychoJS libraries are downloaded to a lib folder in your experiment folder.

If you switch to Run mode and press the JS then you will instead be sent to the appropriate web link. However, if you haven’t already synced your experiment with Pavlovia then you will first be prompted to create a project and will the get a 404 not found error.

Before you can run an experiment online, you must visit the project page – the link appears in the Stdout and Pavlovia tabs of the Runner every time you sync.

Online, experiments can be in one of three modes: Inactive, Piloting and Running. In this screenshot, the software platform is unknown and the platform version is 0. The experiment will not run online. To pilot the experiment online, switch the mode to Piloting by clicking on the Piloting message.

The experiment now has platform PSYCHOJS and version 2024.2.4 (or whatever version you are using). To pilot online, you press the Pilot button in the top right.

A pilot token will be generated so the experiment can run, e.g.

https://run.pavlovia.org/test-suite/test-simple/?__pilotToken=3c59dc048e8850243be8079a5c74d079&__oauthToken=6204c1a913ae8582005a283175a1aa23cade068d6403dce894a26bb8b2bf5641

When you pilot an experiment, data is downloaded to your computer. You should therefore keep the results format set to CSV so you get spreadsheets rather than JSON data files – though you will get a JSON download after every embedded Pavlovia Survey and a sound file after each microphone component.

If the experiment is in piloting mode online and you try to run it online from Builder, you will now get a different error message.

If you switch the experiment to running mode, then it will now run if you have a licence or have assigned credits to the study. If neither of these are true, the error message will be:

If you are using credits, don’t forget to turn off save incomplete results before you add any.

Note that the lib folder from piloting in the local browser does not get uploaded. If you view code, your Gitlab folder should look something like this.

Submitting slider responses

The easiest way to add a slider which allows the participant to select a response but change it before submitting is to have a button component with a start condition of showButton.

Then create a code component with the following code:
Begin Routine

showButton = False

Each Frame

if not showButton:
    try:
        if isinstance(slider.getRating(), float):
            showButton = True
    except:
        pass

This code is more complex than I would like because slider.getRating() can’t be checked until it exists and Auto code components translate None to null instead of undefined in this instance.

Locally this slider looks like:

Online it is slightly different.

Don’t modify spreadsheet variables

If you have a situation where you sometimes use the value of a variable from a spreadsheet in a component and sometimes want to change the value, copy the value to a different variable. Editing the spreadsheet variable causes an error online.

Locally, something like this will work:

if imageFile == 'x': # where imageFile comes from a spreadsheet
     imageFile = 'transparent.png'

To ensure online compatibility, do this instead:

if imageFile == 'x': # where imageFile comes from a spreadsheet
     thisImage = 'transparent.png'
else:
     thisImage = imageFile 

In this case, you should also put thisImage = 'transparent.png' in Begin Experiment

Custom function: defined

In Wakefield's Daily Tips - #74 by wakecarter I dealt with the fact that None doesn’t get translated in a component condition and gets translated to null instead of undefined in a code component by using a try except clause. Today’s tip is a simpler solution. The start condition of the button component can be set to a function which is defined differently in Python and JavaScript.

Add the following Before Experiment.
Python

def defined(x):
    return x != None

JavaScript

function defined(x) {
    return (x !== undefined);
}

Then set the start condition for the button to defined(slider.rating).

The defined function can be used anywhere where 0 is a valid value of x. In other cases (including where x can equal “0”) you can just use if x:

IrfanView

I have been using IrfanView for image resizing, cropping, etc. since 2001 when I needed to create thumbnails of over 1000 cover images when I was webmaster for the official 2000 AD website. My version of the site, is now called BARNEY and can be found at 2000ad.org.

IrfanView is no good for drawing or layers – the closest I get to this with it is copying some background colour to paste over an unwanted feature.

Where it comes into its own is batch processing.

You can convert file types, rename or both.

These settings were used most recently to reduce the size of a set of face stimuli.

IrfanView is Windows only I’m afraid.

YayText bold and italic text generator

If you want to change an individual word in a text component to bold or italics locally then you can use <b> </b> and <i> </i>html tags in a textbox component. However, the bold doesn’t work very well.

Ironically these don’t work online. There is, however, an alternative to turning your text into an image. YayText is an online tool where you can create bold and italic versions of individual words using unicode. The site also has other options, but these seem the most useful. The unicode versions of the words don’t work locally but can be inserted into text or textbox components online directly, or via a spreadsheet variable.

Custom function: remove

In Python list.remove(element) removes the first appearance of element from list, modifying list in place.

To mimic this behaviour in PsychoPy, add the following function in the Before Experiment of a JavaScript code component (or the JS side of a Both component).

Array.prototype.remove = function(value) {
    let index = this.indexOf(value); // Find the first occurrence
    if (index !== -1) {
        this.splice(index, 1); // Remove it in place
    }
};

Note, that this function isn’t the same as the one I’ve previously recommended but that function doesn’t seem to work any more. Perhaps it never did.

Anonymous Participant IDs

If you would like to match participant data across multiple sessions, but do not want to break anonymity by storing a lookup table of email address to participant ID, feel free to use my VESPR Anonymous Participant IDs tool.

To use it, you would need to send your participants to the site and ask them to enter their email address and request an ID. Their address is converted into a number which is then emailed to them for use in your study. This method does, of course, rely on your participants remembering which email address they used if they go back to the site rather than checking their emails for the follow-up time points.

My site does not keep a record of what email addresses have requested IDs and it would be impossible to convert the ID number back into an email address. In theory there is a small risk that two participants could be allocated the same ID but the algorithm makes this very unlikely.

Please cite this reference if you use the tool in your research.

Morys-Carter, W.L. (2021, November 2). Anonymous participant IDs . VESPR. https://moryscarter.com/vespr/id.php.

PsychoPy Primer

In my role as a psychology demonstrator at Oxford Brookes University, I teach and support research methods and statistics. I tend to make presentations in Google Slides and allow them to be viewed by anyone, not just members of my institution.

Today’s tip is a link to my PsychoPy Primer slides which I overhauled last year for a BPS Cognitive section workshop. The hidden slides are ones I decided to skip when I last presented them to second year undergraduates – though I think I should change my approach for that class next year.

If you are really interested, here are the pre-overhaul slides.