1
0
mirror of https://github.com/psychopy/psychojs.git synced 2025-05-10 18:50:54 +00:00

data: enforce formatting rules

This commit is contained in:
Sotiri Bakagiannis 2021-07-09 14:08:08 +01:00
parent 5468898716
commit 27d08ba42f
3 changed files with 108 additions and 157 deletions

View File

@ -7,12 +7,10 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import * as XLSX from "xlsx";
import * as XLSX from 'xlsx'; import { MonotonicClock } from "../util/Clock.js";
import {PsychObject} from '../util/PsychObject.js'; import { PsychObject } from "../util/PsychObject.js";
import {MonotonicClock} from '../util/Clock.js'; import * as util from "../util/Util.js";
import * as util from '../util/Util.js';
/** /**
* <p>An ExperimentHandler keeps track of multiple loops and handlers. It is particularly useful * <p>An ExperimentHandler keeps track of multiple loops and handlers. It is particularly useful
@ -29,7 +27,6 @@ import * as util from '../util/Util.js';
*/ */
export class ExperimentHandler extends PsychObject export class ExperimentHandler extends PsychObject
{ {
/** /**
* Getter for experimentEnded. * Getter for experimentEnded.
* *
@ -54,7 +51,6 @@ export class ExperimentHandler extends PsychObject
this._experimentEnded = ended; this._experimentEnded = ended;
} }
/** /**
* Legacy experiment getters. * Legacy experiment getters.
*/ */
@ -68,16 +64,15 @@ export class ExperimentHandler extends PsychObject
return this._trialsData; return this._trialsData;
} }
constructor({ constructor({
psychoJS, psychoJS,
name, name,
extraInfo extraInfo,
} = {}) } = {})
{ {
super(psychoJS, name); super(psychoJS, name);
this._addAttribute('extraInfo', extraInfo); this._addAttribute("extraInfo", extraInfo);
// loop handlers: // loop handlers:
this._loops = []; this._loops = [];
@ -91,7 +86,6 @@ export class ExperimentHandler extends PsychObject
this._experimentEnded = false; this._experimentEnded = false;
} }
/** /**
* Whether or not the current entry (i.e. trial data) is empty. * Whether or not the current entry (i.e. trial data) is empty.
* <p>Note: this is mostly useful at the end of an experiment, in order to ensure that the last entry is saved.</p> * <p>Note: this is mostly useful at the end of an experiment, in order to ensure that the last entry is saved.</p>
@ -106,7 +100,6 @@ export class ExperimentHandler extends PsychObject
return (Object.keys(this._currentTrialData).length > 0); return (Object.keys(this._currentTrialData).length > 0);
} }
/** /**
* Add a loop. * Add a loop.
* *
@ -125,7 +118,6 @@ export class ExperimentHandler extends PsychObject
loop.experimentHandler = this; loop.experimentHandler = this;
} }
/** /**
* Remove the given loop from the list of unfinished loops, e.g. when it has completed. * Remove the given loop from the list of unfinished loops, e.g. when it has completed.
* *
@ -143,7 +135,6 @@ export class ExperimentHandler extends PsychObject
} }
} }
/** /**
* Add the key/value pair. * Add the key/value pair.
* *
@ -172,7 +163,6 @@ export class ExperimentHandler extends PsychObject
this._currentTrialData[key] = value; this._currentTrialData[key] = value;
} }
/** /**
* Inform this ExperimentHandler that the current trial has ended. Further calls to {@link addData} * Inform this ExperimentHandler that the current trial has ended. Further calls to {@link addData}
* will be associated with the next trial. * will be associated with the next trial.
@ -184,7 +174,7 @@ export class ExperimentHandler extends PsychObject
*/ */
nextEntry(snapshots) nextEntry(snapshots)
{ {
if (typeof snapshots !== 'undefined') if (typeof snapshots !== "undefined")
{ {
// turn single snapshot into a one-element array: // turn single snapshot into a one-element array:
if (!Array.isArray(snapshots)) if (!Array.isArray(snapshots))
@ -203,7 +193,6 @@ export class ExperimentHandler extends PsychObject
} }
} }
} }
} }
// this is to support legacy generated JavaScript code and does not properly handle // this is to support legacy generated JavaScript code and does not properly handle
// loops within loops: // loops within loops:
@ -236,7 +225,6 @@ export class ExperimentHandler extends PsychObject
this._currentTrialData = {}; this._currentTrialData = {};
} }
/** /**
* Save the results of the experiment. * Save the results of the experiment.
* *
@ -254,11 +242,11 @@ export class ExperimentHandler extends PsychObject
* @param {Array.<Object>} [options.sync] - whether or not to communicate with the server in a synchronous manner * @param {Array.<Object>} [options.sync] - whether or not to communicate with the server in a synchronous manner
*/ */
async save({ async save({
attributes = [], attributes = [],
sync = false sync = false,
} = {}) } = {})
{ {
this._psychoJS.logger.info('[PsychoJS] Save experiment results.'); this._psychoJS.logger.info("[PsychoJS] Save experiment results.");
// (*) get attributes: // (*) get attributes:
if (attributes.length === 0) if (attributes.length === 0)
@ -286,16 +274,14 @@ export class ExperimentHandler extends PsychObject
} }
} }
// (*) get various experiment info: // (*) get various experiment info:
const info = this.extraInfo; const info = this.extraInfo;
const __experimentName = (typeof info.expName !== 'undefined') ? info.expName : this.psychoJS.config.experiment.name; const __experimentName = (typeof info.expName !== "undefined") ? info.expName : this.psychoJS.config.experiment.name;
const __participant = ((typeof info.participant === 'string' && info.participant.length > 0) ? info.participant : 'PARTICIPANT'); const __participant = ((typeof info.participant === "string" && info.participant.length > 0) ? info.participant : "PARTICIPANT");
const __session = ((typeof info.session === 'string' && info.session.length > 0) ? info.session : 'SESSION'); const __session = ((typeof info.session === "string" && info.session.length > 0) ? info.session : "SESSION");
const __datetime = ((typeof info.date !== 'undefined') ? info.date : MonotonicClock.getDateStr()); const __datetime = ((typeof info.date !== "undefined") ? info.date : MonotonicClock.getDateStr());
const gitlabConfig = this._psychoJS.config.gitlab; const gitlabConfig = this._psychoJS.config.gitlab;
const __projectId = (typeof gitlabConfig !== 'undefined' && typeof gitlabConfig.projectId !== 'undefined') ? gitlabConfig.projectId : undefined; const __projectId = (typeof gitlabConfig !== "undefined" && typeof gitlabConfig.projectId !== "undefined") ? gitlabConfig.projectId : undefined;
// (*) save to a .csv file: // (*) save to a .csv file:
if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.CSV) if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.CSV)
@ -304,23 +290,23 @@ export class ExperimentHandler extends PsychObject
// newlines, etc. // newlines, etc.
const worksheet = XLSX.utils.json_to_sheet(this._trialsData); const worksheet = XLSX.utils.json_to_sheet(this._trialsData);
// prepend BOM // prepend BOM
const csv = '\ufeff' + XLSX.utils.sheet_to_csv(worksheet); const csv = "\ufeff" + XLSX.utils.sheet_to_csv(worksheet);
// upload data to the pavlovia server or offer them for download: // upload data to the pavlovia server or offer them for download:
const key = __participant + '_' + __experimentName + '_' + __datetime + '.csv'; const key = __participant + "_" + __experimentName + "_" + __datetime + ".csv";
if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER && if (
this._psychoJS.config.experiment.status === 'RUNNING' && this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER
!this._psychoJS._serverMsg.has('__pilotToken')) && this._psychoJS.config.experiment.status === "RUNNING"
&& !this._psychoJS._serverMsg.has("__pilotToken")
)
{ {
return /*await*/ this._psychoJS.serverManager.uploadData(key, csv, sync); return /*await*/ this._psychoJS.serverManager.uploadData(key, csv, sync);
} }
else else
{ {
util.offerDataForDownload(key, csv, 'text/csv'); util.offerDataForDownload(key, csv, "text/csv");
} }
} }
// (*) save in the database on the remote server: // (*) save in the database on the remote server:
else if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.DATABASE) else if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.DATABASE)
{ {
@ -328,7 +314,7 @@ export class ExperimentHandler extends PsychObject
for (let r = 0; r < this._trialsData.length; r++) for (let r = 0; r < this._trialsData.length; r++)
{ {
let doc = {__projectId, __experimentName, __participant, __session, __datetime}; let doc = { __projectId, __experimentName, __participant, __session, __datetime };
for (let h = 0; h < attributes.length; h++) for (let h = 0; h < attributes.length; h++)
{ {
doc[attributes[h]] = this._trialsData[r][attributes[h]]; doc[attributes[h]] = this._trialsData[r][attributes[h]];
@ -338,22 +324,22 @@ export class ExperimentHandler extends PsychObject
} }
// upload data to the pavlovia server or offer them for download: // upload data to the pavlovia server or offer them for download:
if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER && if (
this._psychoJS.config.experiment.status === 'RUNNING' && this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER
!this._psychoJS._serverMsg.has('__pilotToken')) && this._psychoJS.config.experiment.status === "RUNNING"
&& !this._psychoJS._serverMsg.has("__pilotToken")
)
{ {
const key = 'results'; // name of the mongoDB collection const key = "results"; // name of the mongoDB collection
return /*await*/ this._psychoJS.serverManager.uploadData(key, JSON.stringify(documents), sync); return /*await*/ this._psychoJS.serverManager.uploadData(key, JSON.stringify(documents), sync);
} }
else else
{ {
util.offerDataForDownload('results.json', JSON.stringify(documents), 'application/json'); util.offerDataForDownload("results.json", JSON.stringify(documents), "application/json");
} }
} }
} }
/** /**
* Get the attribute names and values for the current trial of a given loop. * Get the attribute names and values for the current trial of a given loop.
* <p> Only info relating to the trial execution are returned.</p> * <p> Only info relating to the trial execution are returned.</p>
@ -367,20 +353,20 @@ export class ExperimentHandler extends PsychObject
static _getLoopAttributes(loop) static _getLoopAttributes(loop)
{ {
// standard trial attributes: // standard trial attributes:
const properties = ['thisRepN', 'thisTrialN', 'thisN', 'thisIndex', 'stepSizeCurrent', 'ran', 'order']; const properties = ["thisRepN", "thisTrialN", "thisN", "thisIndex", "stepSizeCurrent", "ran", "order"];
let attributes = {}; let attributes = {};
const loopName = loop.name; const loopName = loop.name;
for (const loopProperty in loop) for (const loopProperty in loop)
{ {
if (properties.includes(loopProperty)) if (properties.includes(loopProperty))
{ {
const key = (loopProperty === 'stepSizeCurrent') ? loopName + '.stepSize' : loopName + '.' + loopProperty; const key = (loopProperty === "stepSizeCurrent") ? loopName + ".stepSize" : loopName + "." + loopProperty;
attributes[key] = loop[loopProperty]; attributes[key] = loop[loopProperty];
} }
} }
// specific trial attributes: // specific trial attributes:
if (typeof loop.getCurrentTrial === 'function') if (typeof loop.getCurrentTrial === "function")
{ {
const currentTrial = loop.getCurrentTrial(); const currentTrial = loop.getCurrentTrial();
for (const trialProperty in currentTrial) for (const trialProperty in currentTrial)
@ -404,7 +390,7 @@ export class ExperimentHandler extends PsychObject
else: else:
names.append(loopName+'.thisTrial') names.append(loopName+'.thisTrial')
vals.append(trial) vals.append(trial)
// single StairHandler // single StairHandler
elif hasattr(loop, 'intensities'): elif hasattr(loop, 'intensities'):
names.append(loopName+'.intensity') names.append(loopName+'.intensity')
@ -415,10 +401,8 @@ export class ExperimentHandler extends PsychObject
return attributes; return attributes;
} }
} }
/** /**
* Experiment result format * Experiment result format
* *
@ -431,15 +415,14 @@ ExperimentHandler.SaveFormat = {
/** /**
* Results are saved to a .csv file * Results are saved to a .csv file
*/ */
CSV: Symbol.for('CSV'), CSV: Symbol.for("CSV"),
/** /**
* Results are saved to a database * Results are saved to a database
*/ */
DATABASE: Symbol.for('DATABASE') DATABASE: Symbol.for("DATABASE"),
}; };
/** /**
* Experiment environment. * Experiment environment.
* *
@ -448,6 +431,6 @@ ExperimentHandler.SaveFormat = {
* @public * @public
*/ */
ExperimentHandler.Environment = { ExperimentHandler.Environment = {
SERVER: Symbol.for('SERVER'), SERVER: Symbol.for("SERVER"),
LOCAL: Symbol.for('LOCAL') LOCAL: Symbol.for("LOCAL"),
}; };

View File

@ -9,11 +9,10 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import seedrandom from "seedrandom";
import seedrandom from 'seedrandom'; import * as XLSX from "xlsx";
import * as XLSX from 'xlsx'; import { PsychObject } from "../util/PsychObject.js";
import {PsychObject} from '../util/PsychObject.js'; import * as util from "../util/Util.js";
import * as util from '../util/Util.js';
/** /**
* <p>A Trial Handler handles the importing and sequencing of conditions.</p> * <p>A Trial Handler handles the importing and sequencing of conditions.</p>
@ -31,7 +30,6 @@ import * as util from '../util/Util.js';
*/ */
export class TrialHandler extends PsychObject export class TrialHandler extends PsychObject
{ {
/** /**
* Getter for experimentHandler. * Getter for experimentHandler.
* *
@ -56,7 +54,6 @@ export class TrialHandler extends PsychObject
this._experimentHandler = exp; this._experimentHandler = exp;
} }
/** /**
* @constructor * @constructor
* @public * @public
@ -64,27 +61,27 @@ export class TrialHandler extends PsychObject
* @todo extraInfo is not taken into account, we use the expInfo of the ExperimentHandler instead * @todo extraInfo is not taken into account, we use the expInfo of the ExperimentHandler instead
*/ */
constructor({ constructor({
psychoJS, psychoJS,
trialList = [undefined], trialList = [undefined],
nReps, nReps,
method = TrialHandler.Method.RANDOM, method = TrialHandler.Method.RANDOM,
extraInfo = [], extraInfo = [],
seed, seed,
name, name,
autoLog = true autoLog = true,
} = {}) } = {})
{ {
super(psychoJS); super(psychoJS);
this._addAttribute('trialList', trialList); this._addAttribute("trialList", trialList);
this._addAttribute('nReps', nReps); this._addAttribute("nReps", nReps);
this._addAttribute('method', method); this._addAttribute("method", method);
this._addAttribute('extraInfo', extraInfo); this._addAttribute("extraInfo", extraInfo);
this._addAttribute('name', name); this._addAttribute("name", name);
this._addAttribute('autoLog', autoLog); this._addAttribute("autoLog", autoLog);
this._addAttribute('seed', seed); this._addAttribute("seed", seed);
this._prepareTrialList(trialList); this._prepareTrialList(trialList);
// number of stimuli // number of stimuli
this.nStim = this.trialList.length; this.nStim = this.trialList.length;
@ -112,7 +109,6 @@ export class TrialHandler extends PsychObject
// array of current snapshots: // array of current snapshots:
this._snapshots = []; this._snapshots = [];
// setup the trial sequence: // setup the trial sequence:
this._prepareSequence(); this._prepareSequence();
@ -121,18 +117,17 @@ export class TrialHandler extends PsychObject
this._finished = false; this._finished = false;
} }
/** /**
* Helps go through each trial in the sequence one by one, mirrors PsychoPy. * Helps go through each trial in the sequence one by one, mirrors PsychoPy.
*/ */
next() { next()
{
const trialIterator = this[Symbol.iterator](); const trialIterator = this[Symbol.iterator]();
const { value } = trialIterator.next(); const { value } = trialIterator.next();
return value; return value;
} }
/** /**
* Iterator over the trial sequence. * Iterator over the trial sequence.
* *
@ -168,7 +163,7 @@ export class TrialHandler extends PsychObject
if (this.thisRepN >= this.nReps) if (this.thisRepN >= this.nReps)
{ {
this.thisTrial = null; this.thisTrial = null;
return {done: true}; return { done: true };
} }
this.thisIndex = this._trialSequence[this.thisRepN][this.thisTrialN]; this.thisIndex = this._trialSequence[this.thisRepN][this.thisTrialN];
@ -181,12 +176,11 @@ export class TrialHandler extends PsychObject
vals = (self.thisRepN, self.thisTrialN, self.thisTrial) vals = (self.thisRepN, self.thisTrialN, self.thisTrial)
logging.exp(msg % vals, obj=self.thisTrial)*/ logging.exp(msg % vals, obj=self.thisTrial)*/
return {value: this.thisTrial, done: false}; return { value: this.thisTrial, done: false };
} },
}; };
} }
/** /**
* Execute the callback for each trial in the sequence. * Execute the callback for each trial in the sequence.
* *
@ -208,7 +202,6 @@ export class TrialHandler extends PsychObject
} }
} }
/** /**
* @typedef {Object} Snapshot * @typedef {Object} Snapshot
* @property {TrialHandler} handler - the trialHandler * @property {TrialHandler} handler - the trialHandler
@ -253,12 +246,12 @@ export class TrialHandler extends PsychObject
getCurrentTrial: () => this.getTrial(currentIndex), getCurrentTrial: () => this.getTrial(currentIndex),
getTrial: (index = 0) => this.getTrial(index), getTrial: (index = 0) => this.getTrial(index),
addData: (key, value) => this.addData(key, value) addData: (key, value) => this.addData(key, value),
}; };
// add to the snapshots the current trial's attributes: // add to the snapshots the current trial's attributes:
const currentTrial = this.getCurrentTrial(); const currentTrial = this.getCurrentTrial();
const excludedAttributes = ['handler', 'name', 'nStim', 'nRemaining', 'thisRepN', 'thisTrialN', 'thisN', 'thisIndex', 'ran', 'finished']; const excludedAttributes = ["handler", "name", "nStim", "nRemaining", "thisRepN", "thisTrialN", "thisN", "thisIndex", "ran", "finished"];
const trialAttributes = []; const trialAttributes = [];
for (const attribute in currentTrial) for (const attribute in currentTrial)
{ {
@ -280,7 +273,6 @@ export class TrialHandler extends PsychObject
return snapshot; return snapshot;
} }
/** /**
* Setter for the seed attribute. * Setter for the seed attribute.
* *
@ -289,9 +281,9 @@ export class TrialHandler extends PsychObject
*/ */
setSeed(seed, log) setSeed(seed, log)
{ {
this._setAttribute('seed', seed, log); this._setAttribute("seed", seed, log);
if (typeof seed !== 'undefined') if (typeof seed !== "undefined")
{ {
this._randomNumberGenerator = seedrandom(seed); this._randomNumberGenerator = seedrandom(seed);
} }
@ -301,7 +293,6 @@ export class TrialHandler extends PsychObject
} }
} }
/** /**
* Set the internal state of this trial handler from the given snapshot. * Set the internal state of this trial handler from the given snapshot.
* *
@ -312,12 +303,11 @@ export class TrialHandler extends PsychObject
static fromSnapshot(snapshot) static fromSnapshot(snapshot)
{ {
// if snapshot is undefined, do nothing: // if snapshot is undefined, do nothing:
if (typeof snapshot === 'undefined') if (typeof snapshot === "undefined")
{ {
return; return;
} }
snapshot.handler.nStim = snapshot.nStim; snapshot.handler.nStim = snapshot.nStim;
snapshot.handler.nTotal = snapshot.nTotal; snapshot.handler.nTotal = snapshot.nTotal;
snapshot.handler.nRemaining = snapshot.nRemaining; snapshot.handler.nRemaining = snapshot.nRemaining;
@ -330,13 +320,12 @@ export class TrialHandler extends PsychObject
snapshot.handler.thisTrial = snapshot.handler.getCurrentTrial(); snapshot.handler.thisTrial = snapshot.handler.getCurrentTrial();
// add the snapshot's trial attributes to a global variable, whose name is derived from // add the snapshot's trial attributes to a global variable, whose name is derived from
// that of the handler: loops -> thisLoop (note the dropped s): // that of the handler: loops -> thisLoop (note the dropped s):
let name = snapshot.name; let name = snapshot.name;
if (name[name.length-1] === 's') if (name[name.length - 1] === "s")
{ {
name = name.substr(0, name.length-1); name = name.substr(0, name.length - 1);
} }
name = `this${name[0].toUpperCase()}${name.substr(1)}`; name = `this${name[0].toUpperCase()}${name.substr(1)}`;
@ -348,7 +337,6 @@ export class TrialHandler extends PsychObject
window[name] = value; window[name] = value;
} }
/** /**
* Getter for the finished attribute. * Getter for the finished attribute.
* *
@ -359,7 +347,6 @@ export class TrialHandler extends PsychObject
return this._finished; return this._finished;
} }
/** /**
* Setter for the finished attribute. * Setter for the finished attribute.
* *
@ -368,14 +355,13 @@ export class TrialHandler extends PsychObject
set finished(isFinished) set finished(isFinished)
{ {
this._finished = isFinished; this._finished = isFinished;
this._snapshots.forEach( snapshot => this._snapshots.forEach((snapshot) =>
{ {
snapshot.finished = isFinished; snapshot.finished = isFinished;
}); });
} }
/** /**
* Get the trial index. * Get the trial index.
* *
@ -387,7 +373,6 @@ export class TrialHandler extends PsychObject
return this.thisIndex; return this.thisIndex;
} }
/** /**
* Set the trial index. * Set the trial index.
* *
@ -398,7 +383,6 @@ export class TrialHandler extends PsychObject
this.thisIndex = index; this.thisIndex = index;
} }
/** /**
* Get the attributes of the trials. * Get the attributes of the trials.
* *
@ -424,7 +408,6 @@ export class TrialHandler extends PsychObject
return Object.keys(this.trialList[0]); return Object.keys(this.trialList[0]);
} }
/** /**
* Get the current trial. * Get the current trial.
* *
@ -436,7 +419,6 @@ export class TrialHandler extends PsychObject
return this.trialList[this.thisIndex]; return this.trialList[this.thisIndex];
} }
/** /**
* Get the nth trial. * Get the nth trial.
* *
@ -453,7 +435,6 @@ export class TrialHandler extends PsychObject
return this.trialList[index]; return this.trialList[index];
} }
/** /**
* Get the nth future or past trial, without advancing through the trial list. * Get the nth future or past trial, without advancing through the trial list.
* *
@ -472,7 +453,6 @@ export class TrialHandler extends PsychObject
return this.trialList[this.thisIndex + n]; return this.trialList[this.thisIndex + n];
} }
/** /**
* Get the nth previous trial. * Get the nth previous trial.
* <p> Note: this is useful for comparisons in n-back tasks.</p> * <p> Note: this is useful for comparisons in n-back tasks.</p>
@ -486,7 +466,6 @@ export class TrialHandler extends PsychObject
return getFutureTrial(-abs(n)); return getFutureTrial(-abs(n));
} }
/** /**
* Add a key/value pair to data about the current trial held by the experiment handler * Add a key/value pair to data about the current trial held by the experiment handler
* *
@ -502,7 +481,6 @@ export class TrialHandler extends PsychObject
} }
} }
/** /**
* Import a list of conditions from a .xls, .xlsx, .odp, or .csv resource. * Import a list of conditions from a .xls, .xlsx, .odp, or .csv resource.
* *
@ -542,8 +520,8 @@ export class TrialHandler extends PsychObject
{ {
try try
{ {
let resourceExtension = resourceName.split('.').pop(); let resourceExtension = resourceName.split(".").pop();
if (['csv', 'odp', 'xls', 'xlsx'].indexOf(resourceExtension) > -1) if (["csv", "odp", "xls", "xlsx"].indexOf(resourceExtension) > -1)
{ {
// (*) read conditions from resource: // (*) read conditions from resource:
const resourceValue = serverManager.getResource(resourceName, true); const resourceValue = serverManager.getResource(resourceName, true);
@ -552,20 +530,20 @@ export class TrialHandler extends PsychObject
// which is then read in as a string // which is then read in as a string
const decodedResourceMaybe = new Uint8Array(resourceValue); const decodedResourceMaybe = new Uint8Array(resourceValue);
// Could be set to 'buffer' for ASCII .csv // Could be set to 'buffer' for ASCII .csv
const type = resourceExtension === 'csv' ? 'string' : 'array'; const type = resourceExtension === "csv" ? "string" : "array";
const decodedResource = type === 'string' ? (new TextDecoder()).decode(decodedResourceMaybe) : decodedResourceMaybe; const decodedResource = type === "string" ? (new TextDecoder()).decode(decodedResourceMaybe) : decodedResourceMaybe;
const workbook = XLSX.read(decodedResource, { type }); const workbook = XLSX.read(decodedResource, { type });
// we consider only the first worksheet: // we consider only the first worksheet:
if (workbook.SheetNames.length === 0) if (workbook.SheetNames.length === 0)
{ {
throw 'workbook should contain at least one worksheet'; throw "workbook should contain at least one worksheet";
} }
const sheetName = workbook.SheetNames[0]; const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName]; const worksheet = workbook.Sheets[sheetName];
// worksheet to array of arrays (the first array contains the fields): // worksheet to array of arrays (the first array contains the fields):
const sheet = XLSX.utils.sheet_to_json(worksheet, {header: 1, blankrows: false}); const sheet = XLSX.utils.sheet_to_json(worksheet, { header: 1, blankrows: false });
const fields = sheet.shift(); const fields = sheet.shift();
// (*) select conditions: // (*) select conditions:
@ -574,8 +552,8 @@ export class TrialHandler extends PsychObject
// (*) return the selected conditions as an array of 'object as map': // (*) return the selected conditions as an array of 'object as map':
// [ // [
// {field0: value0-0, field1: value0-1, ...} // {field0: value0-0, field1: value0-1, ...}
// {field0: value1-0, field1: value1-1, ...} // {field0: value1-0, field1: value1-1, ...}
// ... // ...
// ] // ]
let trialList = new Array(selectedRows.length - 1); let trialList = new Array(selectedRows.length - 1);
for (let r = 0; r < selectedRows.length; ++r) for (let r = 0; r < selectedRows.length; ++r)
@ -598,7 +576,7 @@ export class TrialHandler extends PsychObject
value = arrayMaybe; value = arrayMaybe;
} }
if (typeof value === 'string') if (typeof value === "string")
{ {
const numberMaybe = Number.parseFloat(value); const numberMaybe = Number.parseFloat(value);
@ -610,7 +588,7 @@ export class TrialHandler extends PsychObject
else else
{ {
// Parse doubly escaped line feeds // Parse doubly escaped line feeds
value = value.replace(/(\n)/g, '\n'); value = value.replace(/(\n)/g, "\n");
} }
} }
@ -621,23 +599,21 @@ export class TrialHandler extends PsychObject
return trialList; return trialList;
} }
else else
{ {
throw 'extension: ' + resourceExtension + ' currently not supported.'; throw "extension: " + resourceExtension + " currently not supported.";
} }
} }
catch (error) catch (error)
{ {
throw { throw {
origin: 'TrialHandler.importConditions', origin: "TrialHandler.importConditions",
context: `when importing condition: ${resourceName}`, context: `when importing condition: ${resourceName}`,
error error,
}; };
} }
} }
/** /**
* Prepare the trial list. * Prepare the trial list.
* *
@ -647,16 +623,15 @@ export class TrialHandler extends PsychObject
_prepareTrialList(trialList) _prepareTrialList(trialList)
{ {
const response = { const response = {
origin: 'TrialHandler._prepareTrialList', origin: "TrialHandler._prepareTrialList",
context: 'when preparing the trial list' context: "when preparing the trial list",
}; };
// we treat undefined trialList as a list with a single empty entry: // we treat undefined trialList as a list with a single empty entry:
if (typeof trialList === 'undefined') if (typeof trialList === "undefined")
{ {
this.trialList = [undefined]; this.trialList = [undefined];
} }
// if trialList is an array, we make sure it is not empty: // if trialList is an array, we make sure it is not empty:
else if (Array.isArray(trialList)) else if (Array.isArray(trialList))
{ {
@ -665,30 +640,27 @@ export class TrialHandler extends PsychObject
this.trialList = [undefined]; this.trialList = [undefined];
} }
} }
// if trialList is a string, we treat it as the name of the condition resource: // if trialList is a string, we treat it as the name of the condition resource:
else if (typeof trialList === 'string') else if (typeof trialList === "string")
{ {
this.trialList = TrialHandler.importConditions(this.psychoJS.serverManager, trialList); this.trialList = TrialHandler.importConditions(this.psychoJS.serverManager, trialList);
} }
// unknown type: // unknown type:
else else
{ {
throw Object.assign(response, { throw Object.assign(response, {
error: 'unable to prepare trial list: unknown type: ' + (typeof trialList) error: "unable to prepare trial list: unknown type: " + (typeof trialList),
}); });
} }
} }
/* /*
* Prepare the sequence of trials. * Prepare the sequence of trials.
* *
* <p>The returned sequence is a matrix (an array of arrays) of trial indices * <p>The returned sequence is a matrix (an array of arrays) of trial indices
* with nStim columns and nReps rows. Note that this is the transpose of the * with nStim columns and nReps rows. Note that this is the transpose of the
* matrix return by PsychoPY. * matrix return by PsychoPY.
* *
* Example: with 3 trial and 5 repetitions, we get: * Example: with 3 trial and 5 repetitions, we get:
* - sequential: * - sequential:
* [[0 1 2] * [[0 1 2]
@ -711,8 +683,8 @@ export class TrialHandler extends PsychObject
_prepareSequence() _prepareSequence()
{ {
const response = { const response = {
origin: 'TrialHandler._prepareSequence', origin: "TrialHandler._prepareSequence",
context: 'when preparing a sequence of trials' context: "when preparing a sequence of trials",
}; };
// get an array of the indices of the elements of trialList : // get an array of the indices of the elements of trialList :
@ -722,9 +694,8 @@ export class TrialHandler extends PsychObject
{ {
this._trialSequence = Array(this.nReps).fill(indices); this._trialSequence = Array(this.nReps).fill(indices);
// transposed version: // transposed version:
//this._trialSequence = indices.reduce( (seq, e) => { seq.push( Array(this.nReps).fill(e) ); return seq; }, [] ); // this._trialSequence = indices.reduce( (seq, e) => { seq.push( Array(this.nReps).fill(e) ); return seq; }, [] );
} }
else if (this.method === TrialHandler.Method.RANDOM) else if (this.method === TrialHandler.Method.RANDOM)
{ {
this._trialSequence = []; this._trialSequence = [];
@ -733,7 +704,6 @@ export class TrialHandler extends PsychObject
this._trialSequence.push(util.shuffle(indices.slice(), this._randomNumberGenerator)); this._trialSequence.push(util.shuffle(indices.slice(), this._randomNumberGenerator));
} }
} }
else if (this.method === TrialHandler.Method.FULL_RANDOM) else if (this.method === TrialHandler.Method.FULL_RANDOM)
{ {
// create a flat sequence with nReps repeats of indices: // create a flat sequence with nReps repeats of indices:
@ -755,15 +725,13 @@ export class TrialHandler extends PsychObject
} }
else else
{ {
throw Object.assign(response, {error: 'unknown method'}); throw Object.assign(response, { error: "unknown method" });
} }
return this._trialSequence; return this._trialSequence;
} }
} }
/** /**
* TrialHandler method * TrialHandler method
* *
@ -775,20 +743,20 @@ TrialHandler.Method = {
/** /**
* Conditions are presented in the order they are given. * Conditions are presented in the order they are given.
*/ */
SEQUENTIAL: Symbol.for('SEQUENTIAL'), SEQUENTIAL: Symbol.for("SEQUENTIAL"),
/** /**
* Conditions are shuffled within each repeat. * Conditions are shuffled within each repeat.
*/ */
RANDOM: Symbol.for('RANDOM'), RANDOM: Symbol.for("RANDOM"),
/** /**
* Conditions are fully randomised across all repeats. * Conditions are fully randomised across all repeats.
*/ */
FULL_RANDOM: Symbol.for('FULL_RANDOM'), FULL_RANDOM: Symbol.for("FULL_RANDOM"),
/** /**
* Same as above, but named to reflect PsychoPy boileplate. * Same as above, but named to reflect PsychoPy boileplate.
*/ */
FULLRANDOM: Symbol.for('FULL_RANDOM') FULLRANDOM: Symbol.for("FULL_RANDOM"),
}; };

View File

@ -1,3 +1,3 @@
export * from './ExperimentHandler.js'; export * from "./ExperimentHandler.js";
export * from './TrialHandler.js'; export * from "./TrialHandler.js";
//export * from './Shelf.js'; // export * from './Shelf.js';