Version 2020.1.3

PsychoPy version 2020.1.3-2020.2.10
Python to JavaScript Crib Sheet

Compiled by Wakefield Morys-Carter, Oxford Brookes University
Please share widely and follow
 Psych_Stats on twitter.

PsychoPy 2020 has an automatic Python to Javascript translation tool.
This document contains information about code that is not translated correctl
y.

For version 2021.1.3 onwards, please see my new crib sheet.

This document is primarily aimed at people who have used PsychoPy and have used Python code snippets. For a simpler introduction, please refer to this quick start guide by Anastasia Sares. If you use Sona Systems and/or Qualtrics, please refer to this guide by Lindsay Santacroce on how to transfer ID variables from one platform to another.

If you get stuck on a particular bug you can commission me to solve it for you.

If you use PsychoPy code please cite it as follows (APA 7th):

Author(s). (Date of last substantial commit). Title [Computer software]. Pavlovia. URL.

where the URL is either to the experiment page on pavlovia.org or an OSF/Zenodo DOI.

Contents

  1. code_JS -- an extra code component in the first routine
  1. Maths functions
  1. Common problems -- notes on working with Pavlovia
  1. Gitlab functionality
  2. Browser Compatibility
  1. Coding notes -- dos and don’ts
  1. Visual stimuli
  2. Audio stimuli
  3. Video stimuli
  1. Translations that require manual editing -- when to override auto translation.
  2. Colours -- a tip for working with colours as variable names.
  3. Examples of working Python code
  4. Error messages
  5. Useful scripts
  6. Not yet working
  7. Links

code_JS

I strongly recommend that everyone adds a JavaScript only code component to their first routine as follows

  1. Create a code component in your first routine.
  2. Rename it to code_JS
  3. Switch it from Auto->JS to JS
  4. Copy and paste code from the PsychoJS column below in Begin Experiment (not Before Experiment). The elements in bold are ones I find particularly useful.

Python

PsychoJS

thisExp

thisExp=psychoJS.experiment;

win

win=psychoJS.window;

event, e.g. for event.getKeys() or event.clearEvents()

event=psychoJS.eventManager;

shuffle()

shuffle = util.shuffle;

.append dvbridges

Array.prototype.append = [].push;

N.B. This is incompatible with the editable text box, resulting in  TypeError: state.toLowerCase is not a function. In this case you will need to manually replace each occurrence of .append() with .push().

sort()

sort = function(array) {

    return array.sort();

}

or for a numerical sort

sort = function(array) {

    return array.sort((a, b) => (a - b));

}

.count() Rebecca Hirst 

Usage

totalScore = score.count(1) counts the number of times 1 appears in an array called score.

Array.prototype.count = function(value) {

let count = 0;

this.forEach(item => {

if (item === value) {

count++;

}

});

return count;

}

.index()

Usage
findx =array.index(1) returns the index of the first occurrence of 1 in an array.

Array.prototype.index = [].indexOf;

webbrowser.open(“link”)

This also requires import webbrowser in a Python only code block.

webbrowser=window;

Multi-line text is centre aligned locally and left aligned online. To centre align a component called text_2, add text_2.setAlignHoriz('center'); to a Begin Routine tab of code_JS.

Maths functions

Maths functions sometimes seem to auto translate correctly. For when they don’t, it is possible to define the functions or aliases at the start rather than replacing every time.

random()

random = Math.random;

randint(min,maxplusone)

The auto translate in 2020.1 produces a range which includes the max value. To suppress it write 0+randint(min,maxplusone) in your Python code.

randint = function(min, maxplusone) {

  return Math.floor(Math.random() * (maxplusone - min) ) + min;

}

range() aisa2

range = function (size, startAt = 0) {

    return [...Array(size).keys()].map(i => i + startAt);

}

sum()

Write sum()+0 if you need to suppress the auto translate.

sum = function (arr) {

return arr.reduce((a,b)=>a+b)

}

OR

sum = (arr) => arr.reduce((a,b)=>a+b)

average()

average = (arr) => (arr.reduce((a, b) => (a + b), 0)) / arr.length

np.std(A) to divide by n
np.std(A ddof=1) to divide by n-1

math.std(A, 'uncorrected') to divide by n
math.std(A) to divide by n-1

Fixed in 2020.2.5

round(a,b)

a = number to round

b = number of digits

round = function(num, n=0) {    

    return +(Math.round(num + ("e+" + n))  + ("e-" + n));

}

https://discourse.psychopy.org/t/translation-to-js-problem-in-stroopextended-demo-adding-feedback/13446/4

Fixed in 2020.2 by the addition of the line
const { abs, sin, cos, PI: pi, sqrt } = Math;

abs()

abs = Math.abs;

sin()

sin = Math.sin;

cos()

cos = Math.cos;

pi

pi=Math.PI;

sqrt()

sqrt = Math.sqrt;

Common problems

Mac version 2020.1.3 shows up as 2020.1.2. Don’t worry about this.

If images, etc., aren’t getting uploaded automatically, add them as additional resources using the online tab of the experiment settings. Older versions have html in the output path. If this is the case, copy them to the local html/resources folder and they should get uploaded at the next sync. PsychoPy tries to copy necessary resources to this folder but is unable to detect resources specified in code components. https://discourse.psychopy.org/t/reading-list-index-in-sound-component/13142/7

Use Developer Tools (Ctl-Shift-I in Windows/Chrome, Cmd-Opt-J in Mac/Chrome, F12 in IE/Edge, Ctrl-Shift-J in Windows/Safari, Ctrl-Opt-J in Mac/Safari) to view errors via the browser console if you aren’t getting sufficient information from PsychoPy. You can add print(var) (which translates to console.log(var); ) to check the value of a variable var at a particular point. N.B. If you need to stop your participants doing this and being able to view trial variables you may be loading from Excel, add log4javascript.setEnabled(false); to code_JS [sotiri]. This will prevent cheating on experiments with a performance based reward.

If Pavlovia refuses to run the latest upload of your experiment, disable the cache via Network conditions in the browser console. You may also need to clear the cache when you first set this (using Ctrl-F5, Ctrl-Shift-R or equivalent). When you have developer tools showing you can also press and hold the page refresh icon and select Empty Cache and Hard Reload.

Since Pilot tokens expire, the easiest way to demo your experiment is to set it to RUNNING and allocate it a small number of credits, but edit the final screen so that it can’t finish (possibly unless you press a key such as 0 which isn’t typically used). You can then set your experiment not to save partial data using the Dashboard entry for your project. That way, if the experiment fails to finish, no data is saved and therefore no credit is consumed. If you wish to view pilot data files, the saving format must be CSV not DATABASE and you may need to set the experiment to PILOTING.

If you would like to save partial data, but only if the participant has completed a minimum proportion of the experiment, add the line psychoJS._config.experiment.saveIncompleteResults = true;

to a JS component in the appropriate routine. Tamer_Gezici

If you have access to questionnaire software, I would recommend that you use that for your PI sheet, ID number and demographics. Your final PsychoPy routine should include words along the lines of “Please wait for the message “Thank you for your patience” to appear and then click OK to continue the experiment”. The “Completed URL” can be formatted like a text element to include variables, e.g. $"https://brookeshls.co1.qualtrics.com/surveyname?expname="+expName+"&participant="+expInfo['participant']. In Pavlovia expName is the file name for the PsychoPy file used to create the experiment. So long as you have URLs set in the experiment options, it may be possible to change them using the following command: psychoJS.setRedirectUrls(myCompletedURL, myCancelledURL)
https://discourse.psychopy.org/t/completedurl-incompleteurl/5597/23?u=wakecarter

If you accidentally corrupt your experiment by removing all variables from the expInfo section, try adding <Param name="Experiment info" updates="None" val="{'participant': ''}" valType="code"/> to the Settings section of your Builder .psyexp file in a text editor. 

Add event.clearEvents() to Begin Routine to clear key presses from previous routines. For mouse input, record the location of the mouse and pass if it hasn’t changed, e.g.

Begin Routine
mouserec = mouse.getPos()

Each Frame 
mouseloc = mouse.getPos()
if mouseloc[0]==mouserec[0] and mouseloc[1]==mouserec[1]:
   
Pass
elif …

Gitlab Functionality

The change permissions: “view code”, then “settings -> general -> permissions”

To copy someone else’s experiment:#

  1. Go to their gitlab page.
  2. Click on “fork” and choose your own name.
  3. Open PsychoPy select “find existing project online”.
  4. Select the desired folder.
  5. Select the desired project online and press sync.
  6. Open the newly downloaded psyexp file in PsychoPy.

By default, only the project maintainer can update the code by syncing with PsychoPy. To allow Developers to make changes, the Maintainer should go to Settings - Repository - Protected Branches and allow Developers and Maintainers to push and merge.

If your experiment won’t sync to Gitlab, the safest way to create a new experiment on Gitlab is to duplicate your local experiment folder and then delete the hidden folder called .git from inside it. Then open the duplicate study in PsychoPy and sync as before so that it will ask to create a new project. [jon]

Do not attempt to change the name of a project (for example because you created it by forking someone else's project). This will change the URL and break the connection with your local copy. [jon]

Browser Compatibility

For example, some people have reported keyboard responses causing a beep in Safari on Macs. I’m unsure whether this is for keyboard components, event.getKeys and/or core.Keyboard . https://discourse.psychopy.org/t/safari-annoying-beep/8746?u=wakecarter

Coding notes

Don’t

Do

Have anything showing in “Use PsychoPy Version” unless you have a good reason.

This option should be used if you have a working experiment which you don’t want to accidentally break when you upgrade.

Use import

You cannot use pandas, numpy, etc.

Remove all references to avoid the experiment crashing while “initialising the experiment”.

Change or remove “participant” from the Experiment Info fields.

Add ?participant=x or &participant=x to the URL if you want to bypass the Experiment Info fields, for example if you want to assign a participant number from Qualtrics.

Use simple terms for variables, since they may already be in use by PsychoPy. Variable names to avoid are: core, image, index, Object, Length, Number, round, sound, Symbol, t, thisTrial, trials, util, visual.

Use variable names that make sense.

Manipulate the values of variables set by spreadsheets. rvgart

Copy the value to a different variable name (e.g. thisFileName=fileName) and use/manipulate the new variable.

End a routine using the same keypress or mouse click as the previous routine without doing something to stop the existing response being recorded again.

Add an ISI or record the current mouse location / key press at the start of the routine and then only accept responses if the location changes or there has been a period of no response first.

e.g (for touch screen)

mouseloc = mouse.getPos()

if mouseloc[0]==mouserec[0] and mouseloc[1]==mouserec[1]:

pass

elif t<2:

mouserec = mouseloc

elif rec.contains(mouse):

continueRoutine = False

Have a final routine where you have to press escape to end the experiment.

The final routine either needs components with a duration or a keyboard/mouse component that is set to end the routine on a valid response.

Use $slice(0,10) for Selected rows in a loop.

Use 0:10 instead, for the first 10 items. Note that a:b will use rows (a+2) to (b+1) inclusive.

Use functions for component parameters, including variable components.

Define the value you want to use as a variable in a code component (so it can be auto translated) and then use the variable name in the component itself.

Use Python string formatting “my var = %s” % myVar

Use string concatenation with type conversion “my var = “ + str(myVar)

Use str(int(myVar)) if you are getting .0 added to values that should be integers.

Use RatingScales (or Forms prior to 2020.2)

Use Slider instead, which has simpler code.

https://discourse.psychopy.org/t/forms-on-pavlovia/13512/10

Use “radio” response options in Forms.

Use “choice” response options for the same functionality locally and online.

Create slider components in code.
awong26

Use a slider component in Builder and then modify in code using .size, .labels, .ticks,._onChange(true)() and

._setupSlider() cukelarter

Use Pandas for reading Excel files

Load variable information into arrays or dictionaries via a loop prior to the trials loop. For example:

Initialise a variable in Begin Experiment

Append values to that variable in Begin Routine, e.g. wordList.append([Item,Valence,Answer])

See my template experiment here: Experiment | Wake/brookes-template-2020

myData = data.TrialHandler(nReps=1, method='sequential', extraInfo=expInfo, originPath=-1, trialList=data.importConditions('conditions.xlsx'), seed=None, name='myData')

Access individual values using: aValue = myData.trialList[Idx]['variableName']  

myData = new TrialHandler({

psychoJS: psychoJS,

nReps: 1, method: TrialHandler.Method.SEQUENTIAL,

extraInfo: expInfo, originPath: undefined,

trialList: 'conditions.xlsx',

seed: undefined, name: 'myData'});

Use + for appending arrays.
hellenjingyuan

Use .append(), which will be translated to .push() by code_JS

Use random.seed()

No seed is necessary for pseudo random numbers. However, if you want to use a repeatable set of random numbers or improve on the standard seed, please look at the useful scripts section.

Don’t call psychoJS.downloadResources multiple times to download resources.

Create an array of dictionaries, e.g. stimuli=[];

stimuli.push({name:(filename),path: (filenameWithPath)});

psychoJS.downloadResources(stimuli);

if (mouse.getPressed[0] == 1)

if (mouse.getPressed()[0] == 1) dvbridges

Use \ in file paths.

Use / instead.

Nest more than around 40 elif statements after a single if statement.

Recode your conditional statements to keep them simpler. bahadiroktay

Have a [ ] brackets in your Excel file, unless the field is intended to be an array.

This is a new error in 2020.2.

Use a different symbol, or potentially use a code for the bracket instead (not tested).

Have a single allowed key in a keyboard component, e.g. ‘space’

This is a new error in 2020.2.

Use brackets or a comma to show that the allowed keys are a list, e.g.

[‘space’] or ‘space’,’space’

Slider label positions don't always match up.

Add experimentInit(); to the End Routine tab of code_JS (or possibly later if your routine containing code_JS doesn't have a duration). Sijiazhao

Beware -- this seems to rerun all Begin Experiment code, so any variables changed in the first routine may get reset.

Visual Stimuli

Don’t

Do

Use win.flip()

Just don’t (in Python or JavaScript) if you are using Builder. There is an implicit win.flip() at the end of the Each Frame tab and additional flips or halting the process with waitKeys or while will confuse the Builder code.

Use event.waitKeys()

Use while

Skip the file extension.

Locally PsychoPy will check for known file types, so image01 will find image01.png. Online the file extension must be specified.

Use tiff or bmp file formats for images

Use jpg or png. For png images, do not save a colour profile. kevinhroberts Tutorial on converting to PNG

Use .draw if you want an object to appear for the whole routine.

Create the object in Begin Experiment.
Replace with .setAutoDraw(True) in
Begin Routine and .setAutoDraw(False) in End Routine, which translate to Javascript correctly.

Using .draw in Each Frame is fine if you only want the object to appear during a subset of frames.

Use “Set every frame” in builder components.

Make edits to existing stimuli in Each Frame code components, e.g. ccimage.size=[x_size*x_scale, y_size*y_scale] or

polydart.setPos([dartx,darty])

Use text.setText(msg)

Use text.text=msg

The .set method seems to work in most cases but .setText appears to be an exception.

Use text.setOpacity() by itself

https://github.com/psychopy/psychopy/issues/1045

Add a line text.text= straight after the opacity line or avoid changing the opacity of text stimuli by hiding with a polygon of the background colour instead. This is only needed for text stimuli.

Use continueRoutine = False in Begin Routine

Either move the code to Each Frame or use a different method for skipping the routine such as having a loop around it with nReps = 0 or setting the duration of the components to 0.

Update the display every frame if nothing has changed. This can cause memory errors.

Compare the new frame with the old frame and only change objects if there is a difference.

Use fill colour $None

https://github.com/psychopy/psychojs/issues/72

Fill with background colour and place above anything that should appear inside it. Alternatively, add polygon.setFillColor(undefined) to Begin Routine

Rely on the order of the components to determine order of being drawn. It seems as though when an object gets edited it is moved to the front.

If you are making an edit to an object in the background, use .setAutoDraw(False) followed by .setAutoDraw(True) on the object(s) you want it to remain behind.

Use an array in an Excel file for locations or colours.

Use separate variables for x and y coordinates, e.g. $(x,y) or separate r, g, b values for separate colour values.

Compare arrays.

e.g. if mouseloc == mouserec:

Compare individual elements.

e.g. if mouseloc[0]==mouserec[0] and mouseloc[1]==mouserec[1]:

Use deg or cm for units

Use “height” where possible. Norm and pix also work. If you would like to add a routine to allow the participant to resize an image of a credit card to calculate their screen size, you are welcome to use my ScreenScale experiment.

Use visual.Circle, visual.Line or visual.ButtonStim.

Use visual.Polygon or visual.ShapeStim instead.

Have a text component set to nothing as constant. This is a new error in 2020.2 and gives SyntaxError: Unexpected token ',' in the console. The JavaScript will look like:     text: /*  */

  ,

Set it to a space character instead.

Use black text if you want participants to use a mobile device. The background colour seems to fail on some devices, defaulting to black. Black text will therefore be invisible.

Use light text on a dark background.

Audio Stimuli

Don’t

Do

Use .ogg

Use .mp3 for best cross-browser compatibility and revert to .ogg in Python. For more details of audio format compatibility, see https://en.wikipedia.org/wiki/HTML5_audio#Supported_audio_coding_formats

Tutorial on converting to MP3

If you use .wav files and have issues with Safari, try editing index.html, changing

<script type=“text/javascript” src=“https://cdnjs.cloudflare.com/ajax/libs/howler/2.1.1/howler.min.js”>

to

<script type=“text/javascript” src=“https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.1/howler.min.js”> sotiri

Use alphabetical notes (e.g. “A”).

Numerical frequencies might work.

A = 220, 440, 880

“Middle” C = 262.63

The formula for notes on a piano is:

frequency = 440*2n/12

for n =-21 to 27

Use Stop durations

Sounds should either be edited to correct length or use sound.stop() in a code component.

Try to create sound objects in code

.setSound() does not work

.setVolume() does not work

Create a sound component in an earlier routine and set the start time to after you  want the routine to end. You may therefore need to end the routine using continueRoutine=False

You can then start and stop the sound in code using .play() and .stop()

https://discourse.psychopy.org/t/asynchronous-sound-success-story/13789

Video Stimuli

Don’t

Do

Use MPEG-4 encoding.

Use H.264 encoded MP4 files with AAC audio encoding. d.govan

Use .reset()

Use .stop() followed by .seek(0) wake carter

Start a movie at 0 seconds.
This is a new error in 2020.2.

Set a start time to 1 second if the movie refuses to display in full screen. Becca

Fixed in 2020.2

Don’t

Do

Refer to custom loop names when using methods .thisN,  .finished, etc.

N.B. As of 2020.2 you can also  also use `currentLoop.finished` when you don't know the name of the loop that will be running (e.g. when the Routine is inserted into multiple locations).

N.B. .thisN stopped working in 2020.2. Use a dummy variable instead...
For example, put loopN = -1 in a Begin Experiment code tab and loopN+=1 in a Begin Routine code tab in the first routine within the loop

Use loopN instead of trials.thisN

All loops are referred to as  “trials”.

Use the custom loop name when referring to trialList. Use “trials” for other attributes.

If you only have one loop that you need to refer to, rename it to “trials”. If you have several then don’t worry about renaming them.

For nested loops the inner loop is referenced. It may not be possible to reference the outer loop.

https://discourse.psychopy.org/t/loop-finished-true-no-longer-working/11190

Use a $ when specifying a variable for nReps

If the $ is shown in the PsychoPy dialogue box it probably isn’t needed locally in PsychoPy but causes an error online.

Translations that require manual editing

Set Code Type to Both. N.B. See How to set Default Code Type to avoid frustration. I would also recommend having code that needs to be manually edited in a separate code component so that you can continue to use the auto translate for new code. Default Code Type changes the whole component, not just the current tab.

Python

PsychoJS

None

undefined
(the Auto translate incorrectly translates to null)

.split()

.split(" "); kdoelling

'delimeter'.join(array)

array.join(“delimeter”);

.pop(0) i.e. taking the first item in the list instead of the last.

.shift()

.insert(0,x) i.e. adding x to the start of a list

.unshift(x); JensBoelte

import math

math.floor()

Math.floor();
N.B. Delete import statement

core.Clock()

new util.Clock();

.upper()

.toUpperCase()

I haven’t yet successfully used:

String.prototype.upper = ““.toUpperCase; in code_JS

kb = keyboard.Keyboard()
dvbridges

kb = new core.Keyboard({psychoJS: psychoJS, clock: new util.Clock(), waitForStart: true});

Has anyone got this working for them? I don’t get errors but I don’t get a working keyboard either.

core.quit()

psychoJS.quit({message: 'Your custom message’}); OR

quitPsychoJS('Your custom message', false);

More testing needed.

You should also be able to add to the standard message by setting the variable in PsychoPy, e.g. message = “Please click ‘ok’ to be debriefed.”; in the End Experiment tab of code_JS

Using multiplication to create a larger array, e.g. [0, 1] * 10

Array(10).fill([0,1]).flat(); dvbridges

Counting down, e.g.

for Idx in range(10,0,-1):

for (var Idx = 10, _pj_a = 0; (Idx > _pj_a); Idx += (- 1)) {

Auto->JS fails to switch < to > in the ending condition.

for i, j in enumerate(distList['list']):

for ([i, j] of dictList['list'].entries()) dvbridges

text_component.alignText=‘left’

text_component.setAlignHoriz('left');

thisExp.addData('Typed response',

textbox.text);

When saving responses from editable text boxes. However, editable text boxes are incompatible with code_JS

thisExp.addData('Typed response', textbox._pixi.text); sotiri

slider_1.marker.size=(width,height)

slider_1.markerSize=(width,height); zshawver

Colours

Named colours seem to cause issues with the auto translate. The easiest way to get around this is to define colours as variables in a code_Both component in the first routine. This table contains some examples. The RGB values range from -1 to +1 so divide by 127.5 and subtract 1 if you have 0-255 values (or divide by 50 and subtract 1 if you have percentages).

N.B. This method cannot be used to set text colour within a text component, but can be use for text.color= or rec.setFillColor()

Python

PsychoJS

white=[1,1,1]

grey=[.2,.2,.2]

yellow=[1,1,0]

green=[-1,0,-1]

black=[-1,-1,-1]

red=[1,0,0]

white = new util.Color([1, 1, 1]);

grey = new util.Color([.2, .2, .2]);

yellow = new util.Color([1, 1, 0]);

green = new util.Color([-1, 0, -1]);

black = new util.Color([-1, -1, -1]);

red = new util.Color([1, 0, 0]);

This method can also be used for defining colours from a spreadsheet. I’m not yet sure if the spreadsheet can contain a single column, e.g. Colour, containing arrays such as [1,-1,.2] or whether separate columns for Red, Green and Blue are needed.

thisColour=Colour

thisColour = new util.Color(Colour);

Examples of working Python code

Python

Notes

target = visual.Polygon(

    win=win,

    name="target",

    fillColor=yellow,

    lineColor=white,

    edges=36,

    pos = [0,voffset],

    size=scale*dtarget

    )

This code must be in Begin Experiment.
See above for how to define win and colours.

Display using target.setAutoDraw(True) in Begin Routine.

Hide using target.setAutoDraw(False) in End Routine.

For the loop, edit append to push in the Javascript

To control changes in colour use, e.g.

target.setFillColor(red)

polydart.setLineColor(yellow)

polydart.setLineWidth(5)

polydart=visual.ShapeStim(

    win=win, name="dart",

    fillColor=blue,

    lineColor=white,

    vertices=[[0,.5],[.01,.48],[.01,.28],[.08,.15],[.05,-.25],[0,-.5],[-.05,-.25],[-.08,.15],[-.01,.28],[-.01,.48]],

    pos = dartoldpos

    )

errortext = visual.TextStim(win=win, name='errortext',

    text='Throw',

    font='Arial',

    pos=(0.3, .45), height=0.08, wrapWidth=None, ori=0,

    color=white);

for Idx in range (9):

    bars.append(visual.ImageStim(

    win=win,

    name='ladder',

    image='ladder.png', ori=0, pos=[pointsz[Idx]*scale*dtarget/2,(Idx-4)*scale+voffset], size=[scale*dtarget*28.72,scale*dtarget],

    color=white, colorSpace='rgb', opacity=1,

    flipHoriz=False, flipVert=False,

    texRes=128, interpolate=True)

    )

psychoJS.downloadResources([

{ name: (expInfo["participant"] + "s.jpg"), path: ("../faces/" + expInfo["participant"] + "s.jpg") },

{ name: (expInfo["participant"] + ".jpg"), path: ("../faces/" + expInfo["participant"] + ".jpg") }

]);

This will download custom files based on the participant number.

Make sure there is time for the files to be downloaded before they need to be used.

Address the resources in the experiment without their file path.

Error Messages

Cannot read property '0' of undefined

nReps for all loops should be numbers or a variable without the $.

CONTEXT_LOST_WEBGL: loseContext: context lost

This indicates a memory overload. Ensure that updates don’t happen on frames where nothing has changed. Also, add if (typeof this._pixi !== 'undefined') this._pixi.destroy(true); before the creation of a new stimulus called this. frank.papenmeier

Could not find an appropriate player.

If you are downloading sound files on the fly using psychoJS.downloadResources then this error suggests that you haven’t waited long enough for the file to be downloaded.

Illegal return statement

Unbalanced {} brackets in JS code

No default wrap width for unit: undefined

Safari may fail to register the global units setting. If this is the case, select the units setting manually for each component. https://discourse.psychopy.org/t/safari-text-stim-error-no-default-wrap-width-for-unit-undefined-with-workaround/13482

ReferenceError: Can’t find variable: function_name

Define function_name in your initial JavaScript code block

TypeError: Cannot assign to read only property ‘undefined’ of object '#'

You may have blank columns in one of your conditions files [dvbridges] e.g., https://discourse.psychopy.org/t/unknown-resource-conditions-file/13700/2

This error also occurs when you have a cell highlighted which is outside the range of your conditions. Select cell A1 for example and resave. It can also occur if you use a reserved variable name such as index.

TypeError: x is not a constructor

These methods do not work online. Use an alternative.

ReferenceError: frameDur is not defined

frameDur is a PsychoJS variable used for calculating frame durations. This error tends to occur when the JS has not compiled correctly, and so PsychoPy fails to declare the builtin variables in the JS code. The actual error is usually visible from the Psychopy dialog.
https://discourse.psychopy.org/t/referenceerror-framedur-is-not-defined-problem-converting-from-pyschopy-to-js/13270

ReferenceError: variable_name is not defined

Set a value for variable_name in a Begin Experiment code block.

TypeError: Cannot read property ‘map’ of undefined

If you are checking the mouse location, for example with “if rec.contains(mouse):”, ensure that the component for the object to be checked is above the code component that checks it.

TypeError: Cannot read property x of undefined

The issue is likely to be with the variable or array or variable for which you are trying to find property x, rather than any issue with finding property x in general.

Unknown Resource

when setting the image of ImageStim: [component name]

when getting the value of resource: [resource name including path and extension]

unknown resource

Copy any missing resources from the experiment folder to html/resources and sync again (if you are running your experiment from the html folder).

Use experiment settings (cog image ) > online > +/- keys to add the list of resources your experiment will need (2020.2.6 onwards).

Unable to create link to PsychoJS library folder

Have you edited your .gitignore file? You can retrieve the default contents from https://github.com/psychopy/psychopy/blob/master/psychopy/projects/gitignore.py

lib folders used for local debugging can clash with online libraries.
https://discourse.psychopy.org/t/unable-to-create-link-to-psychojs-library-folder/12074

Unable to get property ‘getKeys’ of undefined or null reference

event needs to be defined for event.getKeys() to work, i.e. event=psychoJS.eventManager;

Unspecified JavaScript error

This error is very difficult to debug, but I have come across a few causes:

  1. a text element with a size that rounds to zero (e.g. .1) when the units are in pixels.
  1. setting an image as unspecified.
  2. Trying to run an experiment straight after syncing changes when there are lots of resources. To resolve this close and reopen the browser.
  3. Setting a property "during: ISI".

If the experiment works on some browsers but not others try editing the index.html file directly to replace <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.3.3/pixi.min.js"></script>

with <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/pixi.js-legacy@5.3.3/dist/pixi-legacy.min.js"></script>

This file doesn’t belong to any existing project

  1. Install Git from https://git-scm.com/download/
  2. Opening a command prompt (‘cmd’).
  3. Navigate to the folder where all of your files are (using ‘cd’), e.g. cd desktop\experiment. Now you can start pushing things to pavlovia manually.
  4. Type ‘git status’ (check to see if files have changed, only looks at local directory)
  5. Type ‘git add -A’ (add changes to staging area, ready to send online)|
  6. Type ‘git commit -m “(insert message here)”’ (commit the changes and add a message detailing the contents of the commit)|
  7. Type ‘git pull’ (you will have to make sure that the any new files (primarily data output files), in the online repository are first added to your local repository, otherwise git will be angry)|
  8. Type ‘git push’ (actually send the commits online)|
  9. Type ‘git log’ (check the log of commits to make sure it worked, press q to exit if necessary)|
  10. Type ‘git status’ (double check to make sure staging area is clear and no further updates are necessary)

Useful scripts

Assign Consecutive Participant IDs

I have written a web app. which can be used for Pavlovia experiments: https://moryscarter.com/vespr/pavlovia.php

The page takes four parameters: folder, experiment, id and researcher.

folder (required) is your Pavlovia username

experiment (required) is your experiment name as formatted for the study link. If your recruitment URL does not contain /html/ then add a / to the end of your experiment name.

id (optional) is a unique code which you could assign, for example as a random number in Qualtrics, so you can link your PsychoPy data back. It is passed to Pavlovia unchanged

researcher (optional) is also passed to Pavlovia unchanged. I am using it to ensure that participants start in Qualtrics where they can view the participant information sheet, not Pavlovia.

participant is assigned by my app. as a consecutive number. You could use a modulus of participant in order to assign participants to conditions. The number increments whether or not the participant finishes. It would be considerably more complicated to re-assign aborted participant numbers.

Example URL: https://moryscarter.com/vespr/pavlovia.php?folder=Wake&experiment=prospective-memory-ldt&researcher=wakecarter&id=test

In PsychoPy if you have condition = int(expInfo['participant'])%3 then condition should be assigned values 0, 1 or 2.

See also https://moryscarter.com/vespr/survey.php which has similar functionality.

This code isn't open source, but anyone can use my app. for their own experiment. When using to redirect there is no branding and I could easily tailor it if requested, including to redirect to somewhere other than Pavlovia.

Seed the Random Number Generator

Solution from http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html

Add the following code to code_JS.

N.B. This code is “minified”. The full version (and any updates of this version) come from https://github.com/davidbau/seedrandom

!function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=s&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=s&f+1],c=c*d+h[s&(h[f]=h[g=s&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:"string"==e?a:a+"\0"}function l(a,b){for(var c,d=a+"",e=0;e<d.length;)b[s&e]=s&(c^=19*b[s&e])+d.charCodeAt(e++);return n(b)}function m(c){try{return o?n(o.randomBytes(d)):(a.crypto.getRandomValues(c=new Uint8Array(d)),n(c))}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o,p=c.pow(d,e),q=c.pow(2,f),r=2*q,s=d-1,t=c["seed"+i]=function(a,f,g){var h=[];f=1==f?{entropy:!0}:f||{};var o=l(k(f.entropy?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(f.pass||g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=p,c=0;q>a;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b},o,"global"in f?f.global:this==c)};if(l(c[i](),b),g&&g.exports){g.exports=t;try{o=require("crypto")}catch(u){}}else h&&h.amd&&h(function(){return t})}(this,[],Math,256,6,52,"object"==typeof module&&module,"function"==typeof define&&define,"random");

You can then seed the random numbers by adding Math.seedrandom("hello."); to code_JS.

If you use the seed “hello.” then the first two numbers generated by random() will be 0.9282578795792454 and then 0.3752569768646784. seedString = Math.seedrandom(); will seed based on current time, dom state, and other accumulated local entropy. The seed generated is saved to seedString.

If you want to create random numbers with a custom seed without affecting Math.random, add var myrng = new Math.seedrandom('hello.'); to code_JS and then retrieve random numbers using myrng().

Embed html content (e.g. forms and media)

Create a JS only code component as follows. The html file referenced in the first line should be uploaded to the same folder as index.html of your experiment. You should also add another element (e.g. text “Loading..”) with no duration if you want the routine to wait for a form in the html file to be submitted. Variables from the form are saved to the data file.

For more details see: Custom web component for online experiments: Forms, surveys, questionnaires, and other web-based content

Begin Routine

let src = 'yourcustomcontent.html';

continue_routine = true; // Routines can't be ended from within Begin Routine

$(document).ready(function() {

    // Add custom contents from html file using an iframe:

    $('body').append('<div id="iframe-o" style="visibility: hidden; position: relative; display: table; margin: auto;"><div id="iframe-m" style="display: table-cell; vertical-align: middle;"><div id="iframe-i" style="display: inline-block; width:100%; overflow-y: auto; overflow-x: hidden;"><iframe id="iframe" src="'+src+'" style="width: 100%"></iframe></div></div></div>');

    $('#iframe').on('load',function(iframe){

        // Auto-adjust iframe size:

        $(this).contents().find('html').css({ 'display': 'table', 'width': '100%', 'overflow-x': 'hidden' });

        $('#iframe-o').height($(window).height()-20, true);

        $('#iframe-m').width($(this).contents().find('html').width()+20);

        $('#iframe-i').height ( Math.min ( $(this).contents().find('html').height()+20, $(window).height()-20 ), true );

        $(this).height($(this).contents().find('html').height());

        $('#iframe-o').css('visibility','visible');

        // If iframe contains a form, then capture its output text:

        $(this).contents().find('form').on('submit',function(e){

            e.preventDefault();

            $.each($(this).serializeArray(),function(i, param){

                params[param.name] = param.value;

                psychoJS.experiment.addData(param.name, param.value);

            });

            console.log ( 'DEBUG:FRM', params );

            // Remove iframe and continue to next routine when done:

            $('#iframe-o').remove();

            continue_routine = false;

        });

    });

});

//$('#iframe').attr( 'src', function(i,val){ return val;} );

Each Frame

continueRoutine = continue_routine;

Save Screen Information

var sUsrAg;

var nIdx;

function getBrowserId () {

        var browsers = ["MSIE", "Firefox", "Safari", "Chrome", "Opera"];

        sUsrAg = window.navigator.userAgent,

        nIdx = browsers.length - 1;

        for (nIdx; nIdx > -1 && sUsrAg.indexOf(browsers [nIdx]) === -1; nIdx--);

 

  return browsers[nIdx];

}

 

expInfo['OS'] = window.navigator.platform;

expInfo['browser'] = getBrowserId();

expInfo['xResolution'] = screen.width;

expInfo['yResolution'] = screen.height;

Save IP address

  $.getJSON('https://api.ipify.org?format=json', function(data){

 

        console.log(data.ip);

        localStorage.setItem('ip',data.ip);

 

        psychoJS.experiment.addData('IP_Addresss', data.ip)

 

  });

Automatically exit if the experiment is running on a mobile device

if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {

  quitPsychoJS('Mobile device detected. Goodbye!', false)

}

Hide the mouse cursor

document.body.style.cursor='none';

You need to add the following code when you want to un-hide it:

document.body.style.cursor='auto';

jonathan.kominsky

Disable default behaviour of right mouse click (so it can be used by PsychoPy)

preventClick = function (event) { event.preventDefault(); }

document.addEventListener("contextmenu", preventClick, event, false);

To restore the default behaviour of the right mouse button:

document.removeEventListener("contextmenu", preventClick, event, false);

Summarise Responses
https://discourse.psychopy.org/t/accessing-experiment-data-via-code-component-for-psychojs/8485/2

// Get JS array of trial objects (i.e., a list of python dicts with response data)

dat = psychoJS.experiment._trialsData

// Filter data to get correct trials

corr = dat.filter((trial) => trial['key_resp.corr'] === 1)

// Get RTs only as an array

rts = corr.map((trial) => trial['key_resp.rt'])

// Reduce RTs to a single number : the mean

meanRT = rts.reduce((a, b) => a + b) / rts.length

Not yet possible

Custom filenames and paths
https://discourse.psychopy.org/t/how-to-change-the-name-of-the-data-file-when-running-online-experiment/5247

Forms and rating scales

https://discourse.psychopy.org/t/forms-on-pavlovia/13512/6?u=wakecarter

text.contains

https://github.com/psychopy/psychojs/issues/55

Put a rectangle round the text and then use rec.contains

Running PsychoJS experiments on a local server

https://github.com/psychopy/psychojs/issues/79

Control mouse cursor position
https://github.com/psychopy/psychojs/issues/35


Links

Vertical Enhancement of Statistics and Psychology Research resources page

Participant IDs for Pavlovia consecutive participant numbers

VESPR Study Portal hosting of PI sheets, consecutive participant numbers, counterbalanced assignment to groups, adjustment based on non-completion

PsychoPy Code Component Snippets 
PsychoPy Online Demos (on discourse)

Github issues
PsychoPy discourse / Online experiments

PsychoPy merger (to put two .psyexp files together)

Wakefield Morys-Carter CV

Public PsychoPy Experiments (fork at will)

Brookes Template 2020

  • Embedded form
  • Seed Random
  • Embedded YouTube video
  • Slider
  • Pre-loading items into an array
  • Repeating incorrect items
  • Inclusive gender options
  • Text entry for age

Music Box

  • Plays music not by note from a spreadsheet
  • Too memory intensive to use on a mobile device

Prospective Memory LDT

  • Pre-loading items into an array
  • Interleaving items and targets
  • Touchscreen + keyboard responses

Screen Scale

  • Asks a participant to resize an image to the size of a physical credit card
  • Use the results as vertical and horizontal scaling factors for norm or height units

Thomas Pronk's Demos and Documents

Tech Support & Bug Report Guidelines

This manual contains technical support and bug report guidelines for studies that are conducted online

Demo_chaining

How to daisy chain a PsychoJS/Pavlovia experiment with another website (i.e. sending participants from another website to Pavlovia and back again).