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 correctly.
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.
I strongly recommend that everyone adds a JavaScript only code component to their first routine as follows
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 UsagetotalScore = 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 | 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 functionsMaths 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) | 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 | math.std(A, 'uncorrected') to divide by n |
Fixed in 2020.2.5 | |
round(a,b)a = number to roundb = number of digits | round = function(num, n=0) { return +(Math.round(num + ("e+" + n)) + ("e-" + n)); } |
Fixed in 2020.2 by the addition of the line | |
abs() | abs = Math.abs; |
sin() | sin = Math.sin; |
cos() | cos = Math.cos; |
pi | pi=Math.PI; |
sqrt() | sqrt = Math.sqrt; |
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 …
The change permissions: “view code”, then “settings -> general -> permissions”
To copy someone else’s experiment:#
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]
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
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. |
Use “radio” response options in Forms. | Use “choice” response options for the same functionality locally and online. |
Create slider components in code. | 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. | 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. 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 | 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 | 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 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. | 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... 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. |
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 |
.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(); |
core.Clock() | new util.Clock(); |
.upper() | .toUpperCase() I haven’t yet successfully used: String.prototype.upper = ““.toUpperCase; in code_JS |
kb = keyboard.Keyboard() | 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 |
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); |
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. 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. |
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:
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
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.
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.
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().
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
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;} );
continueRoutine = continue_routine;
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;
$.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)
});
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
quitPsychoJS('Mobile device detected. Goodbye!', false)
}
document.body.style.cursor='none';
You need to add the following code when you want to un-hide it:
document.body.style.cursor='auto';
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
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
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)
Tech Support & Bug Report Guidelines
This manual contains technical support and bug report guidelines for studies that are conducted online
How to daisy chain a PsychoJS/Pavlovia experiment with another website (i.e. sending participants from another website to Pavlovia and back again).