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
*/
import * as XLSX from 'xlsx';
import {PsychObject} from '../util/PsychObject.js';
import {MonotonicClock} from '../util/Clock.js';
import * as util from '../util/Util.js';
import * as XLSX from "xlsx";
import { MonotonicClock } from "../util/Clock.js";
import { PsychObject } from "../util/PsychObject.js";
import * as util from "../util/Util.js";
/**
* <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
{
/**
* Getter for experimentEnded.
*
@ -54,7 +51,6 @@ export class ExperimentHandler extends PsychObject
this._experimentEnded = ended;
}
/**
* Legacy experiment getters.
*/
@ -68,16 +64,15 @@ export class ExperimentHandler extends PsychObject
return this._trialsData;
}
constructor({
psychoJS,
name,
extraInfo
extraInfo,
} = {})
{
super(psychoJS, name);
this._addAttribute('extraInfo', extraInfo);
this._addAttribute("extraInfo", extraInfo);
// loop handlers:
this._loops = [];
@ -91,7 +86,6 @@ export class ExperimentHandler extends PsychObject
this._experimentEnded = false;
}
/**
* 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>
@ -106,7 +100,6 @@ export class ExperimentHandler extends PsychObject
return (Object.keys(this._currentTrialData).length > 0);
}
/**
* Add a loop.
*
@ -125,7 +118,6 @@ export class ExperimentHandler extends PsychObject
loop.experimentHandler = this;
}
/**
* 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.
*
@ -172,7 +163,6 @@ export class ExperimentHandler extends PsychObject
this._currentTrialData[key] = value;
}
/**
* Inform this ExperimentHandler that the current trial has ended. Further calls to {@link addData}
* will be associated with the next trial.
@ -184,7 +174,7 @@ export class ExperimentHandler extends PsychObject
*/
nextEntry(snapshots)
{
if (typeof snapshots !== 'undefined')
if (typeof snapshots !== "undefined")
{
// turn single snapshot into a one-element array:
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
// loops within loops:
@ -236,7 +225,6 @@ export class ExperimentHandler extends PsychObject
this._currentTrialData = {};
}
/**
* Save the results of the experiment.
*
@ -255,10 +243,10 @@ export class ExperimentHandler extends PsychObject
*/
async save({
attributes = [],
sync = false
sync = false,
} = {})
{
this._psychoJS.logger.info('[PsychoJS] Save experiment results.');
this._psychoJS.logger.info("[PsychoJS] Save experiment results.");
// (*) get attributes:
if (attributes.length === 0)
@ -286,16 +274,14 @@ export class ExperimentHandler extends PsychObject
}
}
// (*) get various experiment info:
const info = this.extraInfo;
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 __session = ((typeof info.session === 'string' && info.session.length > 0) ? info.session : 'SESSION');
const __datetime = ((typeof info.date !== 'undefined') ? info.date : MonotonicClock.getDateStr());
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 __session = ((typeof info.session === "string" && info.session.length > 0) ? info.session : "SESSION");
const __datetime = ((typeof info.date !== "undefined") ? info.date : MonotonicClock.getDateStr());
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:
if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.CSV)
@ -304,23 +290,23 @@ export class ExperimentHandler extends PsychObject
// newlines, etc.
const worksheet = XLSX.utils.json_to_sheet(this._trialsData);
// 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:
const key = __participant + '_' + __experimentName + '_' + __datetime + '.csv';
if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER &&
this._psychoJS.config.experiment.status === 'RUNNING' &&
!this._psychoJS._serverMsg.has('__pilotToken'))
const key = __participant + "_" + __experimentName + "_" + __datetime + ".csv";
if (
this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER
&& this._psychoJS.config.experiment.status === "RUNNING"
&& !this._psychoJS._serverMsg.has("__pilotToken")
)
{
return /*await*/ this._psychoJS.serverManager.uploadData(key, csv, sync);
}
else
{
util.offerDataForDownload(key, csv, 'text/csv');
util.offerDataForDownload(key, csv, "text/csv");
}
}
// (*) save in the database on the remote server:
else if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.DATABASE)
{
@ -338,21 +324,21 @@ export class ExperimentHandler extends PsychObject
}
// upload data to the pavlovia server or offer them for download:
if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER &&
this._psychoJS.config.experiment.status === 'RUNNING' &&
!this._psychoJS._serverMsg.has('__pilotToken'))
if (
this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER
&& 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);
}
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.
@ -367,20 +353,20 @@ export class ExperimentHandler extends PsychObject
static _getLoopAttributes(loop)
{
// standard trial attributes:
const properties = ['thisRepN', 'thisTrialN', 'thisN', 'thisIndex', 'stepSizeCurrent', 'ran', 'order'];
const properties = ["thisRepN", "thisTrialN", "thisN", "thisIndex", "stepSizeCurrent", "ran", "order"];
let attributes = {};
const loopName = loop.name;
for (const loopProperty in loop)
{
if (properties.includes(loopProperty))
{
const key = (loopProperty === 'stepSizeCurrent') ? loopName + '.stepSize' : loopName + '.' + loopProperty;
const key = (loopProperty === "stepSizeCurrent") ? loopName + ".stepSize" : loopName + "." + loopProperty;
attributes[key] = loop[loopProperty];
}
}
// specific trial attributes:
if (typeof loop.getCurrentTrial === 'function')
if (typeof loop.getCurrentTrial === "function")
{
const currentTrial = loop.getCurrentTrial();
for (const trialProperty in currentTrial)
@ -415,10 +401,8 @@ export class ExperimentHandler extends PsychObject
return attributes;
}
}
/**
* Experiment result format
*
@ -431,15 +415,14 @@ ExperimentHandler.SaveFormat = {
/**
* Results are saved to a .csv file
*/
CSV: Symbol.for('CSV'),
CSV: Symbol.for("CSV"),
/**
* Results are saved to a database
*/
DATABASE: Symbol.for('DATABASE')
DATABASE: Symbol.for("DATABASE"),
};
/**
* Experiment environment.
*
@ -448,6 +431,6 @@ ExperimentHandler.SaveFormat = {
* @public
*/
ExperimentHandler.Environment = {
SERVER: Symbol.for('SERVER'),
LOCAL: Symbol.for('LOCAL')
SERVER: Symbol.for("SERVER"),
LOCAL: Symbol.for("LOCAL"),
};

View File

@ -9,11 +9,10 @@
* @license Distributed under the terms of the MIT License
*/
import seedrandom from 'seedrandom';
import * as XLSX from 'xlsx';
import {PsychObject} from '../util/PsychObject.js';
import * as util from '../util/Util.js';
import seedrandom from "seedrandom";
import * as XLSX from "xlsx";
import { PsychObject } from "../util/PsychObject.js";
import * as util from "../util/Util.js";
/**
* <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
{
/**
* Getter for experimentHandler.
*
@ -56,7 +54,6 @@ export class TrialHandler extends PsychObject
this._experimentHandler = exp;
}
/**
* @constructor
* @public
@ -71,18 +68,18 @@ export class TrialHandler extends PsychObject
extraInfo = [],
seed,
name,
autoLog = true
autoLog = true,
} = {})
{
super(psychoJS);
this._addAttribute('trialList', trialList);
this._addAttribute('nReps', nReps);
this._addAttribute('method', method);
this._addAttribute('extraInfo', extraInfo);
this._addAttribute('name', name);
this._addAttribute('autoLog', autoLog);
this._addAttribute('seed', seed);
this._addAttribute("trialList", trialList);
this._addAttribute("nReps", nReps);
this._addAttribute("method", method);
this._addAttribute("extraInfo", extraInfo);
this._addAttribute("name", name);
this._addAttribute("autoLog", autoLog);
this._addAttribute("seed", seed);
this._prepareTrialList(trialList);
// number of stimuli
@ -112,7 +109,6 @@ export class TrialHandler extends PsychObject
// array of current snapshots:
this._snapshots = [];
// setup the trial sequence:
this._prepareSequence();
@ -121,18 +117,17 @@ export class TrialHandler extends PsychObject
this._finished = false;
}
/**
* Helps go through each trial in the sequence one by one, mirrors PsychoPy.
*/
next() {
next()
{
const trialIterator = this[Symbol.iterator]();
const { value } = trialIterator.next();
return value;
}
/**
* Iterator over the trial sequence.
*
@ -182,11 +177,10 @@ export class TrialHandler extends PsychObject
logging.exp(msg % vals, obj=self.thisTrial)*/
return { value: this.thisTrial, done: false };
}
},
};
}
/**
* Execute the callback for each trial in the sequence.
*
@ -208,7 +202,6 @@ export class TrialHandler extends PsychObject
}
}
/**
* @typedef {Object} Snapshot
* @property {TrialHandler} handler - the trialHandler
@ -253,12 +246,12 @@ export class TrialHandler extends PsychObject
getCurrentTrial: () => this.getTrial(currentIndex),
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:
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 = [];
for (const attribute in currentTrial)
{
@ -280,7 +273,6 @@ export class TrialHandler extends PsychObject
return snapshot;
}
/**
* Setter for the seed attribute.
*
@ -289,9 +281,9 @@ export class TrialHandler extends PsychObject
*/
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);
}
@ -301,7 +293,6 @@ export class TrialHandler extends PsychObject
}
}
/**
* Set the internal state of this trial handler from the given snapshot.
*
@ -312,12 +303,11 @@ export class TrialHandler extends PsychObject
static fromSnapshot(snapshot)
{
// if snapshot is undefined, do nothing:
if (typeof snapshot === 'undefined')
if (typeof snapshot === "undefined")
{
return;
}
snapshot.handler.nStim = snapshot.nStim;
snapshot.handler.nTotal = snapshot.nTotal;
snapshot.handler.nRemaining = snapshot.nRemaining;
@ -330,11 +320,10 @@ export class TrialHandler extends PsychObject
snapshot.handler.thisTrial = snapshot.handler.getCurrentTrial();
// 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):
let name = snapshot.name;
if (name[name.length-1] === 's')
if (name[name.length - 1] === "s")
{
name = name.substr(0, name.length - 1);
}
@ -348,7 +337,6 @@ export class TrialHandler extends PsychObject
window[name] = value;
}
/**
* Getter for the finished attribute.
*
@ -359,7 +347,6 @@ export class TrialHandler extends PsychObject
return this._finished;
}
/**
* Setter for the finished attribute.
*
@ -369,13 +356,12 @@ export class TrialHandler extends PsychObject
{
this._finished = isFinished;
this._snapshots.forEach( snapshot =>
this._snapshots.forEach((snapshot) =>
{
snapshot.finished = isFinished;
});
}
/**
* Get the trial index.
*
@ -387,7 +373,6 @@ export class TrialHandler extends PsychObject
return this.thisIndex;
}
/**
* Set the trial index.
*
@ -398,7 +383,6 @@ export class TrialHandler extends PsychObject
this.thisIndex = index;
}
/**
* Get the attributes of the trials.
*
@ -424,7 +408,6 @@ export class TrialHandler extends PsychObject
return Object.keys(this.trialList[0]);
}
/**
* Get the current trial.
*
@ -436,7 +419,6 @@ export class TrialHandler extends PsychObject
return this.trialList[this.thisIndex];
}
/**
* Get the nth trial.
*
@ -453,7 +435,6 @@ export class TrialHandler extends PsychObject
return this.trialList[index];
}
/**
* 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];
}
/**
* Get the nth previous trial.
* <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));
}
/**
* 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.
*
@ -542,8 +520,8 @@ export class TrialHandler extends PsychObject
{
try
{
let resourceExtension = resourceName.split('.').pop();
if (['csv', 'odp', 'xls', 'xlsx'].indexOf(resourceExtension) > -1)
let resourceExtension = resourceName.split(".").pop();
if (["csv", "odp", "xls", "xlsx"].indexOf(resourceExtension) > -1)
{
// (*) read conditions from resource:
const resourceValue = serverManager.getResource(resourceName, true);
@ -552,14 +530,14 @@ export class TrialHandler extends PsychObject
// which is then read in as a string
const decodedResourceMaybe = new Uint8Array(resourceValue);
// Could be set to 'buffer' for ASCII .csv
const type = resourceExtension === 'csv' ? 'string' : 'array';
const decodedResource = type === 'string' ? (new TextDecoder()).decode(decodedResourceMaybe) : decodedResourceMaybe;
const type = resourceExtension === "csv" ? "string" : "array";
const decodedResource = type === "string" ? (new TextDecoder()).decode(decodedResourceMaybe) : decodedResourceMaybe;
const workbook = XLSX.read(decodedResource, { type });
// we consider only the first worksheet:
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 worksheet = workbook.Sheets[sheetName];
@ -598,7 +576,7 @@ export class TrialHandler extends PsychObject
value = arrayMaybe;
}
if (typeof value === 'string')
if (typeof value === "string")
{
const numberMaybe = Number.parseFloat(value);
@ -610,7 +588,7 @@ export class TrialHandler extends PsychObject
else
{
// 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;
}
else
{
throw 'extension: ' + resourceExtension + ' currently not supported.';
throw "extension: " + resourceExtension + " currently not supported.";
}
}
catch (error)
{
throw {
origin: 'TrialHandler.importConditions',
origin: "TrialHandler.importConditions",
context: `when importing condition: ${resourceName}`,
error
error,
};
}
}
/**
* Prepare the trial list.
*
@ -647,16 +623,15 @@ export class TrialHandler extends PsychObject
_prepareTrialList(trialList)
{
const response = {
origin: 'TrialHandler._prepareTrialList',
context: 'when preparing the trial list'
origin: "TrialHandler._prepareTrialList",
context: "when preparing the trial list",
};
// we treat undefined trialList as a list with a single empty entry:
if (typeof trialList === 'undefined')
if (typeof trialList === "undefined")
{
this.trialList = [undefined];
}
// if trialList is an array, we make sure it is not empty:
else if (Array.isArray(trialList))
{
@ -665,23 +640,20 @@ export class TrialHandler extends PsychObject
this.trialList = [undefined];
}
}
// 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);
}
// unknown type:
else
{
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.
*
@ -711,8 +683,8 @@ export class TrialHandler extends PsychObject
_prepareSequence()
{
const response = {
origin: 'TrialHandler._prepareSequence',
context: 'when preparing a sequence of trials'
origin: "TrialHandler._prepareSequence",
context: "when preparing a sequence of trials",
};
// get an array of the indices of the elements of trialList :
@ -724,7 +696,6 @@ export class TrialHandler extends PsychObject
// transposed version:
// this._trialSequence = indices.reduce( (seq, e) => { seq.push( Array(this.nReps).fill(e) ); return seq; }, [] );
}
else if (this.method === TrialHandler.Method.RANDOM)
{
this._trialSequence = [];
@ -733,7 +704,6 @@ export class TrialHandler extends PsychObject
this._trialSequence.push(util.shuffle(indices.slice(), this._randomNumberGenerator));
}
}
else if (this.method === TrialHandler.Method.FULL_RANDOM)
{
// create a flat sequence with nReps repeats of indices:
@ -755,15 +725,13 @@ export class TrialHandler extends PsychObject
}
else
{
throw Object.assign(response, {error: 'unknown method'});
throw Object.assign(response, { error: "unknown method" });
}
return this._trialSequence;
}
}
/**
* TrialHandler method
*
@ -775,20 +743,20 @@ TrialHandler.Method = {
/**
* Conditions are presented in the order they are given.
*/
SEQUENTIAL: Symbol.for('SEQUENTIAL'),
SEQUENTIAL: Symbol.for("SEQUENTIAL"),
/**
* Conditions are shuffled within each repeat.
*/
RANDOM: Symbol.for('RANDOM'),
RANDOM: Symbol.for("RANDOM"),
/**
* 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.
*/
FULLRANDOM: Symbol.for('FULL_RANDOM')
FULLRANDOM: Symbol.for("FULL_RANDOM"),
};

View File

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