mirror of
https://github.com/psychopy/psychojs.git
synced 2025-05-10 10:40:54 +00:00
511 lines
15 KiB
JavaScript
511 lines
15 KiB
JavaScript
/**************
|
|
* Gabor Test *
|
|
**************/
|
|
|
|
// import { core, data, sound, util, visual } from '../../psychojs_experimental/psychojsPR519.js';
|
|
// import { core, data, sound, util, visual } from "../out/psychojs-2024.1.0.js";
|
|
import { core, data, sound, util, visual } from "./index.js";
|
|
|
|
// import {StimInspector} from 'https://run.pavlovia.org/lgtst/stiminspector/StimInspector.js';
|
|
// import {StimInspector} from '../stiminspector/StimInspector.js';
|
|
// import {PsyexpReader} from '../psyexpreader/PsyexpReader.js';
|
|
const { PsychoJS } = core;
|
|
const { TrialHandler } = data;
|
|
const { Scheduler } = util;
|
|
//some handy aliases as in the psychopy scripts;
|
|
const { abs, sin, cos, PI: pi, sqrt } = Math;
|
|
const { round } = util;
|
|
|
|
// store info about the experiment session:
|
|
let expName = 'gabor'; // from the Builder filename that created this script
|
|
let expInfo = {};
|
|
|
|
// Start code blocks for 'Before Experiment'
|
|
// init psychoJS:
|
|
const psychoJS = new PsychoJS({
|
|
debug: true
|
|
});
|
|
window.psychoJS = psychoJS;
|
|
window.util = util;
|
|
|
|
// open window:
|
|
psychoJS.openWindow({
|
|
fullscr: false,
|
|
color: new util.Color("gray"),
|
|
units: 'height',
|
|
waitBlanking: true
|
|
});
|
|
|
|
// new StimInspector(psychoJS.window, { core, data, sound, util, visual });
|
|
|
|
// schedule the experiment:
|
|
psychoJS.schedule(psychoJS.gui.DlgFromDict({
|
|
dictionary: expInfo,
|
|
title: expName
|
|
}));
|
|
|
|
const flowScheduler = new Scheduler(psychoJS);
|
|
const dialogCancelScheduler = new Scheduler(psychoJS);
|
|
psychoJS.scheduleCondition(function() { return (psychoJS.gui.dialogComponent.button === 'OK'); }, flowScheduler, dialogCancelScheduler);
|
|
|
|
// flowScheduler gets run if the participants presses OK
|
|
flowScheduler.add(updateInfo); // add timeStamp
|
|
flowScheduler.add(experimentInit);
|
|
// flowScheduler.add(instructRoutineBegin());
|
|
// flowScheduler.add(instructRoutineEachFrame());
|
|
// flowScheduler.add(instructRoutineEnd());
|
|
flowScheduler.add(gaborRoutineBegin());
|
|
flowScheduler.add(gaborRoutineEachFrame());
|
|
flowScheduler.add(gaborRoutineEnd());
|
|
flowScheduler.add(quitPsychoJS, '', true);
|
|
|
|
// quit if user presses Cancel in dialog box:
|
|
dialogCancelScheduler.add(quitPsychoJS, '', false);
|
|
|
|
psychoJS.start({
|
|
expName: expName,
|
|
expInfo: expInfo,
|
|
configURL: "../config.json",
|
|
resources: [
|
|
{
|
|
name: "starformation.jpg",
|
|
path: "./test_resources/starformation.jpg"
|
|
},
|
|
{
|
|
name: "cool.gif",
|
|
path: "./test_resources/cool.gif"
|
|
},
|
|
{
|
|
name: "delorean.gif",
|
|
path: "./test_resources/delorean.gif"
|
|
},
|
|
{
|
|
name: "silverhand.gif",
|
|
path: "./test_resources/silverhand.gif"
|
|
},
|
|
// {
|
|
// name: "007",
|
|
// path: "007.jpg"
|
|
// },
|
|
]
|
|
});
|
|
|
|
psychoJS.experimentLogger.setLevel(core.Logger.ServerLevel.WARNING);
|
|
|
|
var frameDur;
|
|
async function updateInfo() {
|
|
expInfo['date'] = util.MonotonicClock.getDateStr(); // add a simple timestamp
|
|
expInfo['expName'] = expName;
|
|
expInfo['psychopyVersion'] = '2021.3.0';
|
|
expInfo['OS'] = window.navigator.platform;
|
|
|
|
// store frame rate of monitor if we can measure it successfully
|
|
expInfo['frameRate'] = psychoJS.window.getActualFrameRate();
|
|
if (typeof expInfo['frameRate'] !== 'undefined')
|
|
frameDur = 1.0 / Math.round(expInfo['frameRate']);
|
|
else
|
|
frameDur = 1.0 / 60.0; // couldn't get a reliable measure so guess
|
|
|
|
// add info from the URL:
|
|
util.addInfoFromUrl(expInfo);
|
|
|
|
return Scheduler.Event.NEXT;
|
|
}
|
|
|
|
var instructClock;
|
|
var ready;
|
|
var gaborClock;
|
|
var gabor;
|
|
var stims = [];
|
|
window.grating2BlendMode = 'add';
|
|
var globalClock;
|
|
var routineTimer;
|
|
|
|
function addWheelListener () {
|
|
let v = 1.;
|
|
window.addEventListener('wheel', (e) => {
|
|
if (!psychoJS) {
|
|
return;
|
|
}
|
|
psychoJS._window._stimsContainer.position.y += e.deltaY * v;
|
|
})
|
|
}
|
|
|
|
// var video;
|
|
async function experimentInit() {
|
|
// Initialize components for Routine "instruct"
|
|
instructClock = new util.Clock();
|
|
ready = new core.Keyboard({psychoJS: psychoJS, clock: new util.Clock(), waitForStart: true});
|
|
psychoJS.window.backgroundImage = "starformation.jpg";
|
|
|
|
// Initialize components for Routine "gabor"
|
|
gaborClock = new util.Clock();
|
|
|
|
stims.push(
|
|
// new visual.GratingStim({
|
|
// win : psychoJS.window,
|
|
// name: 'morph',
|
|
// tex: 'sin',
|
|
// mask: undefined,
|
|
// ori: 0,
|
|
// size: [256, 512],
|
|
// pos: [0, 0],
|
|
// units: "pix",
|
|
// depth: 0
|
|
// })
|
|
new visual.GifStim({
|
|
win : psychoJS.window,
|
|
name: 'morph',
|
|
image: "silverhand.gif",
|
|
mask: undefined,
|
|
ori: 0,
|
|
size: [512, 512],
|
|
pos: [0, 0],
|
|
units: "pix",
|
|
depth: 0
|
|
})
|
|
);
|
|
|
|
window.stims = stims;
|
|
// Create some handy timers
|
|
globalClock = new util.Clock(); // to track the time since experiment started
|
|
routineTimer = new util.CountdownTimer(); // to track time remaining of each (non-slip) routine
|
|
addWheelListener();
|
|
|
|
return Scheduler.Event.NEXT;
|
|
}
|
|
|
|
|
|
var t;
|
|
var frameN;
|
|
var continueRoutine;
|
|
var gotValidClick;
|
|
var _ready_allKeys;
|
|
var instructComponents;
|
|
function instructRoutineBegin(snapshot) {
|
|
return async function () {
|
|
TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date
|
|
|
|
//------Prepare to start Routine 'instruct'-------
|
|
t = 0;
|
|
instructClock.reset(); // clock
|
|
frameN = -1;
|
|
continueRoutine = true; // until we're told otherwise
|
|
// update component parameters for each repeat
|
|
ready.keys = undefined;
|
|
ready.rt = undefined;
|
|
_ready_allKeys = [];
|
|
// keep track of which components have finished
|
|
instructComponents = [];
|
|
instructComponents.push(ready);
|
|
|
|
for (const thisComponent of instructComponents)
|
|
if ('status' in thisComponent)
|
|
thisComponent.status = PsychoJS.Status.NOT_STARTED;
|
|
return Scheduler.Event.NEXT;
|
|
}
|
|
}
|
|
|
|
|
|
function instructRoutineEachFrame() {
|
|
return async function () {
|
|
//------Loop for each frame of Routine 'instruct'-------
|
|
// get current time
|
|
t = instructClock.getTime();
|
|
frameN = frameN + 1;// number of completed frames (so 0 is the first frame)
|
|
// update/draw components on each frame
|
|
|
|
// *ready* updates
|
|
if (t >= 0 && ready.status === PsychoJS.Status.NOT_STARTED) {
|
|
// keep track of start time/frame for later
|
|
ready.tStart = t; // (not accounting for frame time here)
|
|
ready.frameNStart = frameN; // exact frame index
|
|
|
|
// keyboard checking is just starting
|
|
psychoJS.window.callOnFlip(function() { ready.clock.reset(); }); // t=0 on next screen flip
|
|
psychoJS.window.callOnFlip(function() { ready.start(); }); // start on screen flip
|
|
psychoJS.window.callOnFlip(function() { ready.clearEvents(); });
|
|
}
|
|
|
|
if (ready.status === PsychoJS.Status.STARTED) {
|
|
let theseKeys = ready.getKeys({keyList: [], waitRelease: false});
|
|
_ready_allKeys = _ready_allKeys.concat(theseKeys);
|
|
if (_ready_allKeys.length > 0) {
|
|
ready.keys = _ready_allKeys[_ready_allKeys.length - 1].name; // just the last key pressed
|
|
ready.rt = _ready_allKeys[_ready_allKeys.length - 1].rt;
|
|
// a response ends the routine
|
|
continueRoutine = false;
|
|
}
|
|
}
|
|
|
|
// check for quit (typically the Esc key)
|
|
if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) {
|
|
return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false);
|
|
}
|
|
|
|
// check if the Routine should terminate
|
|
if (!continueRoutine) { // a component has requested a forced-end of Routine
|
|
return Scheduler.Event.NEXT;
|
|
}
|
|
|
|
continueRoutine = false; // reverts to True if at least one component still running
|
|
for (const thisComponent of instructComponents)
|
|
if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) {
|
|
continueRoutine = true;
|
|
break;
|
|
}
|
|
|
|
// refresh the screen if continuing
|
|
if (continueRoutine) {
|
|
return Scheduler.Event.FLIP_REPEAT;
|
|
} else {
|
|
return Scheduler.Event.NEXT;
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
function instructRoutineEnd() {
|
|
return async function () {
|
|
//------Ending Routine 'instruct'-------
|
|
for (const thisComponent of instructComponents) {
|
|
if (typeof thisComponent.setAutoDraw === 'function') {
|
|
thisComponent.setAutoDraw(false);
|
|
}
|
|
}
|
|
ready.stop();
|
|
// the Routine "instruct" was not non-slip safe, so reset the non-slip timer
|
|
routineTimer.reset();
|
|
|
|
return Scheduler.Event.NEXT;
|
|
};
|
|
}
|
|
|
|
|
|
var gaborComponents;
|
|
function gaborRoutineBegin(snapshot) {
|
|
return async function () {
|
|
TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date
|
|
|
|
//------Prepare to start Routine 'instruct'-------
|
|
t = 0;
|
|
gaborClock.reset(); // clock
|
|
frameN = -1;
|
|
continueRoutine = true; // until we're told otherwise
|
|
// update component parameters for each repeat
|
|
ready.keys = undefined;
|
|
ready.rt = undefined;
|
|
_ready_allKeys = [];
|
|
// keep track of which components have finished
|
|
gaborComponents = [];
|
|
gaborComponents.push(ready);
|
|
gaborComponents = [...gaborComponents, ...stims];
|
|
|
|
|
|
for (const thisComponent of gaborComponents)
|
|
if ('status' in thisComponent)
|
|
thisComponent.status = PsychoJS.Status.NOT_STARTED;
|
|
|
|
return Scheduler.Event.NEXT;
|
|
}
|
|
}
|
|
|
|
var secTimer = 0;
|
|
var prevTime = performance.now();
|
|
var dynamicDimension = 0;
|
|
var newSize = [512, 512];
|
|
var newPos = [0, 0];
|
|
var sizeTests = [-512, -256.1, -128, 128, 256.6, 512];
|
|
var positionTests = [-256, -256.1, 256, 256.1, 0];
|
|
var anchorTests = ["left", "topleft", "top", "topright", "right", "bottomright", "bottom", "bottomleft", "center"];
|
|
var sizeTestsProgress = 0;
|
|
var positionTestsProgress = 0;
|
|
var anchorTestsProgress = 0;
|
|
var continueAutoTest = true;
|
|
window.stopTest = function () {
|
|
continueAutoTest = false;
|
|
};
|
|
window.startTest = function () {
|
|
continueAutoTest = true;
|
|
};
|
|
function gaborRoutineEachFrame() {
|
|
return async function () {
|
|
//------Loop for each frame of Routine 'gabor'-------
|
|
// get current time
|
|
t = gaborClock.getTime();
|
|
frameN = frameN + 1;// number of completed frames (so 0 is the first frame)
|
|
|
|
let i;
|
|
for (i = 0; i < stims.length; i++) {
|
|
if (t >= 0. && stims[i].status === PsychoJS.Status.NOT_STARTED) {
|
|
stims[i].tStart = t;
|
|
stims[i].frameNStart = frameN;
|
|
stims[i].setAutoDraw(true);
|
|
}
|
|
}
|
|
|
|
|
|
// testing code
|
|
secTimer += performance.now() - prevTime;
|
|
prevTime = performance.now();
|
|
if (secTimer >= 1000 && continueAutoTest)
|
|
{
|
|
secTimer = 0;
|
|
|
|
if (sizeTestsProgress < sizeTests.length * 2)
|
|
{
|
|
i = sizeTestsProgress % sizeTests.length;
|
|
newSize[dynamicDimension] = sizeTests[i];
|
|
stims[0].setSize(newSize);
|
|
sizeTestsProgress++;
|
|
console.log("stim size set to", stims[0].getSize());
|
|
if (sizeTestsProgress % sizeTests.length === 0)
|
|
{
|
|
dynamicDimension = (dynamicDimension + 1) % 2;
|
|
}
|
|
}
|
|
else if (sizeTestsProgress < sizeTests.length * 3)
|
|
{
|
|
i = sizeTestsProgress % sizeTests.length;
|
|
newSize[0] = sizeTests[i];
|
|
newSize[1] = sizeTests[i];
|
|
stims[0].setSize(newSize);
|
|
sizeTestsProgress++;
|
|
console.log("stim size set to", stims[0].getSize());
|
|
}
|
|
else if (
|
|
sizeTestsProgress >= sizeTests.length * 3 &&
|
|
positionTestsProgress < positionTests.length * 2)
|
|
{
|
|
i = positionTestsProgress % positionTests.length;
|
|
newPos[dynamicDimension] = positionTests[i];
|
|
stims[0].setPos(newPos);
|
|
positionTestsProgress++;
|
|
console.log("stim pos set to", stims[0].getPos());
|
|
if (positionTestsProgress % positionTests.length === 0)
|
|
{
|
|
newPos[dynamicDimension] = 0;
|
|
dynamicDimension = (dynamicDimension + 1) % 2;
|
|
}
|
|
}
|
|
else if(
|
|
sizeTestsProgress >= sizeTests.length * 3 &&
|
|
positionTestsProgress >= positionTests.length * 2 &&
|
|
anchorTestsProgress < anchorTests.length)
|
|
{
|
|
i = anchorTestsProgress % anchorTests.length;
|
|
stims[0].setAnchor(anchorTests[i]);
|
|
anchorTestsProgress++;
|
|
console.log("anchor set to", anchorTests[i]);
|
|
}
|
|
|
|
if (
|
|
sizeTestsProgress >= sizeTests.length * 3 &&
|
|
positionTestsProgress >= positionTests.length * 2 &&
|
|
anchorTestsProgress >= anchorTests.length)
|
|
{
|
|
sizeTestsProgress = 0;
|
|
positionTestsProgress = 0;
|
|
anchorTestsProgress = 0;
|
|
dynamicDimension = 0;
|
|
newPos[0] = 0;
|
|
newPos[1] = 0;
|
|
newSize[0] = 512;
|
|
newSize[1] = 512;
|
|
console.log("============== full reset ==============");
|
|
stims[0].setPos(newPos);
|
|
stims[0].setSize(newSize);
|
|
stims[0].setAnchor("center");
|
|
}
|
|
}
|
|
|
|
|
|
// check for quit (typically the Esc key)
|
|
if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0)
|
|
{
|
|
continueRoutine = false;
|
|
}
|
|
|
|
// check if the Routine should terminate
|
|
if (!continueRoutine) { // a component has requested a forced-end of Routine
|
|
return Scheduler.Event.NEXT;
|
|
}
|
|
|
|
continueRoutine = false; // reverts to True if at least one component still running
|
|
for (const thisComponent of gaborComponents)
|
|
if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) {
|
|
continueRoutine = true;
|
|
break;
|
|
}
|
|
|
|
// refresh the screen if continuing
|
|
if (continueRoutine) {
|
|
return Scheduler.Event.FLIP_REPEAT;
|
|
} else {
|
|
return Scheduler.Event.NEXT;
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
function gaborRoutineEnd() {
|
|
return async function () {
|
|
//------Ending Routine 'gabor'-------
|
|
for (const thisComponent of gaborComponents) {
|
|
if (typeof thisComponent.setAutoDraw === 'function') {
|
|
thisComponent.setAutoDraw(false);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// the Routine "gabor" was not non-slip safe, so reset the non-slip timer
|
|
routineTimer.reset();
|
|
|
|
return Scheduler.Event.NEXT;
|
|
};
|
|
}
|
|
|
|
|
|
function endLoopIteration(scheduler, snapshot) {
|
|
// ------Prepare for next entry------
|
|
return async function () {
|
|
if (typeof snapshot !== 'undefined') {
|
|
// ------Check if user ended loop early------
|
|
if (snapshot.finished) {
|
|
// Check for and save orphaned data
|
|
if (psychoJS.experiment.isEntryEmpty()) {
|
|
psychoJS.experiment.nextEntry(snapshot);
|
|
}
|
|
scheduler.stop();
|
|
} else {
|
|
const thisTrial = snapshot.getCurrentTrial();
|
|
if (typeof thisTrial === 'undefined' || !('isTrials' in thisTrial) || thisTrial.isTrials) {
|
|
psychoJS.experiment.nextEntry(snapshot);
|
|
}
|
|
}
|
|
return Scheduler.Event.NEXT;
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
function importConditions(currentLoop) {
|
|
return async function () {
|
|
psychoJS.importAttributes(currentLoop.getCurrentTrial());
|
|
return Scheduler.Event.NEXT;
|
|
};
|
|
}
|
|
|
|
|
|
async function quitPsychoJS(message, isCompleted) {
|
|
// Check for and save orphaned data
|
|
if (psychoJS.experiment.isEntryEmpty()) {
|
|
psychoJS.experiment.nextEntry();
|
|
}
|
|
psychoJS.window.close();
|
|
psychoJS.quit({message: message, isCompleted: isCompleted});
|
|
|
|
return Scheduler.Event.QUIT;
|
|
}
|