mirror of
https://github.com/psychopy/psychojs.git
synced 2025-05-10 10:40:54 +00:00
Merge pull request #478 from apitiot/2022.1.0
FF completed the information available in the experiment data for MultiStairHandler
This commit is contained in:
commit
0b1f318830
@ -176,7 +176,7 @@ export class PsychoJS
|
||||
}
|
||||
|
||||
this.logger.info("[PsychoJS] Initialised.");
|
||||
this.logger.info("[PsychoJS] @version 2021.3.0");
|
||||
this.logger.info("[PsychoJS] @version 2022.1.0");
|
||||
|
||||
// hide the initialisation message:
|
||||
jQuery("#root").addClass("is-ready");
|
||||
@ -312,7 +312,7 @@ export class PsychoJS
|
||||
* @async
|
||||
* @public
|
||||
*/
|
||||
async start({ configURL = "config.json", expName = "UNKNOWN", expInfo = {}, resources = [] } = {})
|
||||
async start({ configURL = "config.json", expName = "UNKNOWN", expInfo = {}, resources = [], dataFileName } = {})
|
||||
{
|
||||
this.logger.debug();
|
||||
|
||||
@ -344,6 +344,7 @@ export class PsychoJS
|
||||
this._experiment = new ExperimentHandler({
|
||||
psychoJS: this,
|
||||
extraInfo: expInfo,
|
||||
dataFileName
|
||||
});
|
||||
|
||||
// setup the logger:
|
||||
|
@ -50,6 +50,9 @@ export class ServerManager extends PsychObject
|
||||
|
||||
// resources is a map of <name: string, { path: string, status: ResourceStatus, data: any }>
|
||||
this._resources = new Map();
|
||||
this._nbLoadedResources = 0;
|
||||
this._setupPreloadQueue();
|
||||
|
||||
|
||||
this._addAttribute("autoLog", autoLog);
|
||||
this._addAttribute("status", ServerManager.Status.READY);
|
||||
@ -138,7 +141,9 @@ export class ServerManager extends PsychObject
|
||||
const self = this;
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
const url = this._psychoJS.config.pavlovia.URL + "/api/v2/experiments/" + encodeURIComponent(self._psychoJS.config.experiment.fullpath) + "/sessions";
|
||||
const url = this._psychoJS.config.pavlovia.URL
|
||||
+ "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId
|
||||
+ "/sessions";
|
||||
jQuery.post(url, data, null, "json")
|
||||
.done((data, textStatus) =>
|
||||
{
|
||||
@ -219,8 +224,9 @@ export class ServerManager extends PsychObject
|
||||
this.setStatus(ServerManager.Status.BUSY);
|
||||
|
||||
// prepare DELETE query:
|
||||
const url = this._psychoJS.config.pavlovia.URL + "/api/v2/experiments/" + encodeURIComponent(this._psychoJS.config.experiment.fullpath) + "/sessions/"
|
||||
+ this._psychoJS.config.session.token;
|
||||
const url = this._psychoJS.config.pavlovia.URL
|
||||
+ "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId
|
||||
+ "/sessions/" + this._psychoJS.config.session.token;
|
||||
|
||||
// synchronous query the pavlovia server:
|
||||
if (sync)
|
||||
@ -231,7 +237,7 @@ export class ServerManager extends PsychObject
|
||||
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
request.send(JSON.stringify(data));
|
||||
*/
|
||||
/* This does not work in Chrome before of a CORS bug
|
||||
/* This does not work in Chrome because of a CORS bug
|
||||
await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
|
||||
@ -318,30 +324,72 @@ export class ServerManager extends PsychObject
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Get the status of a resource.
|
||||
* Get the status of a single resource or the reduced status of an array of resources.
|
||||
*
|
||||
* <p>If an array of resources is given, getResourceStatus returns a single, reduced status
|
||||
* that is the status furthest away from DOWNLOADED, with the status ordered as follow:
|
||||
* ERROR (furthest from DOWNLOADED), REGISTERED, DOWNLOADING, and DOWNLOADED</p>
|
||||
* <p>For example, given three resources:
|
||||
* <ul>
|
||||
* <li>if at least one of the resource status is ERROR, the reduced status is ERROR</li>
|
||||
* <li>if at least one of the resource status is DOWNLOADING, the reduced status is DOWNLOADING</li>
|
||||
* <li>if the status of all three resources is REGISTERED, the reduced status is REGISTERED</li>
|
||||
* <li>if the status of all three resources is DOWNLOADED, the reduced status is DOWNLOADED</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @name module:core.ServerManager#getResourceStatus
|
||||
* @function
|
||||
* @public
|
||||
* @param {string} name of the requested resource
|
||||
* @return {core.ServerManager.ResourceStatus} status of the resource
|
||||
* @throws {Object.<string, *>} exception if no resource with that name has previously been registered
|
||||
* @param {string | string[]} names names of the resources whose statuses are requested
|
||||
* @return {core.ServerManager.ResourceStatus} status of the resource if there is only one, or reduced status otherwise
|
||||
* @throws {Object.<string, *>} if at least one of the names is not that of a previously
|
||||
* registered resource
|
||||
*/
|
||||
getResourceStatus(name)
|
||||
getResourceStatus(names)
|
||||
{
|
||||
const response = {
|
||||
origin: "ServerManager.getResourceStatus",
|
||||
context: "when getting the status of resource: " + name,
|
||||
context: `when getting the status of resource(s): ${JSON.stringify(names)}`,
|
||||
};
|
||||
|
||||
const pathStatusData = this._resources.get(name);
|
||||
if (typeof pathStatusData === "undefined")
|
||||
// sanity checks:
|
||||
if (typeof names === 'string')
|
||||
{
|
||||
// throw { ...response, error: 'unknown resource' };
|
||||
throw Object.assign(response, { error: "unknown resource" });
|
||||
names = [names];
|
||||
}
|
||||
if (!Array.isArray(names))
|
||||
{
|
||||
throw Object.assign(response, { error: "names should be either a string or an array of strings" });
|
||||
}
|
||||
const statusOrder = new Map([
|
||||
[Symbol.keyFor(ServerManager.ResourceStatus.ERROR), 0],
|
||||
[Symbol.keyFor(ServerManager.ResourceStatus.REGISTERED), 1],
|
||||
[Symbol.keyFor(ServerManager.ResourceStatus.DOWNLOADING), 2],
|
||||
[Symbol.keyFor(ServerManager.ResourceStatus.DOWNLOADED), 3]
|
||||
]);
|
||||
let reducedStatus = ServerManager.ResourceStatus.DOWNLOADED;
|
||||
for (const name of names)
|
||||
{
|
||||
const pathStatusData = this._resources.get(name);
|
||||
|
||||
if (typeof pathStatusData === "undefined")
|
||||
{
|
||||
// throw { ...response, error: 'unknown resource' };
|
||||
throw Object.assign(response, {
|
||||
error: `unable to find a previously registered resource with name: ${name}`
|
||||
});
|
||||
}
|
||||
|
||||
// update the reduced status according to the order given by statusOrder:
|
||||
if (statusOrder.get(Symbol.keyFor(pathStatusData.status)) <
|
||||
statusOrder.get(Symbol.keyFor(reducedStatus)))
|
||||
{
|
||||
reducedStatus = pathStatusData.status;
|
||||
}
|
||||
}
|
||||
|
||||
return pathStatusData.status;
|
||||
return reducedStatus;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
@ -404,7 +452,7 @@ export class ServerManager extends PsychObject
|
||||
* </ul>
|
||||
*
|
||||
* @name module:core.ServerManager#prepareResources
|
||||
* @param {Array.<{name: string, path: string, download: boolean} | Symbol>} [resources=[]] - the list of resources
|
||||
* @param {String | Array.<{name: string, path: string, download: boolean} | String | Symbol>} [resources=[]] - the list of resources or a single resource
|
||||
* @function
|
||||
* @public
|
||||
*/
|
||||
@ -424,9 +472,13 @@ export class ServerManager extends PsychObject
|
||||
// register the resources:
|
||||
if (resources !== null)
|
||||
{
|
||||
if (typeof resources === "string")
|
||||
{
|
||||
resources = [resources];
|
||||
}
|
||||
if (!Array.isArray(resources))
|
||||
{
|
||||
throw "resources should be an array of objects";
|
||||
throw "resources should be either (a) a string or (b) an array of string or objects";
|
||||
}
|
||||
|
||||
// whether all resources have been requested:
|
||||
@ -471,6 +523,20 @@ export class ServerManager extends PsychObject
|
||||
throw "resources must be manually specified when the experiment is running locally: ALL_RESOURCES cannot be used";
|
||||
}
|
||||
|
||||
// convert those resources that are only a string to an object with name and path:
|
||||
for (let r = 0; r < resources.length; ++r)
|
||||
{
|
||||
const resource = resources[r];
|
||||
if (typeof resource === "string")
|
||||
{
|
||||
resources[r] = {
|
||||
name: resource,
|
||||
path: resource,
|
||||
download: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let { name, path, download } of resources)
|
||||
{
|
||||
if (!this._resources.has(name))
|
||||
@ -504,19 +570,26 @@ export class ServerManager extends PsychObject
|
||||
|
||||
// download those registered resources for which download = true
|
||||
// note: we return a Promise that will be resolved when all the resources are downloaded
|
||||
return new Promise((resolve, reject) =>
|
||||
if (resourcesToDownload.size === 0)
|
||||
{
|
||||
const uuid = this.on(ServerManager.Event.RESOURCE, (signal) =>
|
||||
return Promise.resolve();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
if (signal.message === ServerManager.Event.DOWNLOAD_COMPLETED)
|
||||
const uuid = this.on(ServerManager.Event.RESOURCE, (signal) =>
|
||||
{
|
||||
this.off(ServerManager.Event.RESOURCE, uuid);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
if (signal.message === ServerManager.Event.DOWNLOAD_COMPLETED)
|
||||
{
|
||||
this.off(ServerManager.Event.RESOURCE, uuid);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
this._downloadResources(resourcesToDownload);
|
||||
});
|
||||
this._downloadResources(resourcesToDownload);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
@ -987,8 +1060,6 @@ export class ServerManager extends PsychObject
|
||||
count: resources.size,
|
||||
});
|
||||
|
||||
this._nbLoadedResources = 0;
|
||||
|
||||
// based on the resource extension either (a) add it to the preload manifest, (b) mark it for
|
||||
// download by howler, or (c) add it to the document fonts
|
||||
const preloadManifest = [];
|
||||
@ -1066,7 +1137,6 @@ export class ServerManager extends PsychObject
|
||||
// start loading resources marked for preload.js:
|
||||
if (preloadManifest.length > 0)
|
||||
{
|
||||
this._setupPreloadQueue(resources);
|
||||
this._preloadQueue.loadManifest(preloadManifest);
|
||||
}
|
||||
else
|
||||
@ -1175,10 +1245,14 @@ export class ServerManager extends PsychObject
|
||||
* @name module:core.ServerManager#_setupPreloadQueue
|
||||
* @function
|
||||
* @protected
|
||||
* @param {Set} resources - a set of names of previously registered resources
|
||||
*/
|
||||
_setupPreloadQueue(resources)
|
||||
_setupPreloadQueue()
|
||||
{
|
||||
const response = {
|
||||
origin: "ServerManager._setupPreloadQueue",
|
||||
context: "when setting up a preload queue"
|
||||
};
|
||||
|
||||
this._preloadQueue = new createjs.LoadQueue(true, "", true);
|
||||
|
||||
const self = this;
|
||||
@ -1213,7 +1287,7 @@ export class ServerManager extends PsychObject
|
||||
this._preloadQueue.addEventListener("complete", (event) =>
|
||||
{
|
||||
self._preloadQueue.close();
|
||||
if (self._nbLoadedResources === resources.size)
|
||||
if (self._nbLoadedResources === self._resources.size)
|
||||
{
|
||||
self.setStatus(ServerManager.Status.READY);
|
||||
self.emit(ServerManager.Event.RESOURCE, {
|
||||
@ -1336,6 +1410,11 @@ ServerManager.Status = {
|
||||
* @public
|
||||
*/
|
||||
ServerManager.ResourceStatus = {
|
||||
/**
|
||||
* There was an error during downloading, or the resource is in an unknown state.
|
||||
*/
|
||||
ERROR: Symbol.for("ERROR"),
|
||||
|
||||
/**
|
||||
* The resource has been registered.
|
||||
*/
|
||||
@ -1350,9 +1429,4 @@ ServerManager.ResourceStatus = {
|
||||
* The resource has been downloaded.
|
||||
*/
|
||||
DOWNLOADED: Symbol.for("DOWNLOADED"),
|
||||
|
||||
/**
|
||||
* There was an error during downloading, or the resource is in an unknown state.
|
||||
*/
|
||||
ERROR: Symbol.for("ERROR"),
|
||||
};
|
||||
|
@ -68,12 +68,33 @@ export class ExperimentHandler extends PsychObject
|
||||
psychoJS,
|
||||
name,
|
||||
extraInfo,
|
||||
dataFileName
|
||||
} = {})
|
||||
{
|
||||
super(psychoJS, name);
|
||||
|
||||
this._addAttribute("extraInfo", extraInfo);
|
||||
|
||||
// process the extra info:
|
||||
this._experimentName = (typeof extraInfo.expName === "string" && extraInfo.expName.length > 0)
|
||||
? extraInfo.expName
|
||||
: this.psychoJS.config.experiment.name;
|
||||
this._participant = (typeof extraInfo.participant === "string" && extraInfo.participant.length > 0)
|
||||
? extraInfo.participant
|
||||
: "PARTICIPANT";
|
||||
this._session = (typeof extraInfo.session === "string" && extraInfo.session.length > 0)
|
||||
? extraInfo.session
|
||||
: "SESSION";
|
||||
this._datetime = (typeof extraInfo.date !== "undefined")
|
||||
? extraInfo.date
|
||||
: MonotonicClock.getDateStr();
|
||||
|
||||
this._addAttribute(
|
||||
"dataFileName",
|
||||
dataFileName,
|
||||
`${this._participant}_${this._experimentName}_${this._datetime}`
|
||||
);
|
||||
|
||||
// loop handlers:
|
||||
this._loops = [];
|
||||
this._unfinishedLoops = [];
|
||||
@ -94,6 +115,7 @@ export class ExperimentHandler extends PsychObject
|
||||
* @function
|
||||
* @public
|
||||
* @returns {boolean} whether or not the current entry is empty
|
||||
* @todo This really should be renamed: IsCurrentEntryNotEmpty
|
||||
*/
|
||||
isEntryEmpty()
|
||||
{
|
||||
@ -278,15 +300,6 @@ 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 gitlabConfig = this._psychoJS.config.gitlab;
|
||||
const __projectId = (typeof gitlabConfig !== "undefined" && typeof gitlabConfig.projectId !== "undefined") ? gitlabConfig.projectId : undefined;
|
||||
|
||||
let data = this._trialsData;
|
||||
// if the experiment data have to be cleared, we first make a copy of them:
|
||||
if (clear)
|
||||
@ -306,7 +319,8 @@ export class ExperimentHandler extends PsychObject
|
||||
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}${tag}.csv`;
|
||||
const filenameWithoutPath = this._dataFileName.split(/[\\/]/).pop();
|
||||
const key = `${filenameWithoutPath}${tag}.csv`;
|
||||
if (
|
||||
this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER
|
||||
&& this._psychoJS.config.experiment.status === "RUNNING"
|
||||
@ -323,11 +337,20 @@ export class ExperimentHandler extends PsychObject
|
||||
// save to the database on the pavlovia server:
|
||||
else if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.DATABASE)
|
||||
{
|
||||
const gitlabConfig = this._psychoJS.config.gitlab;
|
||||
const projectId = (typeof gitlabConfig !== "undefined" && typeof gitlabConfig.projectId !== "undefined") ? gitlabConfig.projectId : undefined;
|
||||
|
||||
let documents = [];
|
||||
|
||||
for (let r = 0; r < data.length; r++)
|
||||
{
|
||||
let doc = { __projectId, __experimentName, __participant, __session, __datetime };
|
||||
let doc = {
|
||||
projectId,
|
||||
__experimentName: this._experimentName,
|
||||
__participant: this._participant,
|
||||
__session: this._session,
|
||||
__datetime: this._datetime
|
||||
};
|
||||
for (let h = 0; h < attributes.length; h++)
|
||||
{
|
||||
doc[attributes[h]] = data[r][attributes[h]];
|
||||
|
@ -111,10 +111,12 @@ export class MultiStairHandler extends TrialHandler
|
||||
};
|
||||
}
|
||||
|
||||
this._psychoJS.experiment.addData(this._name+'.response', response);
|
||||
|
||||
if (!this._finished)
|
||||
{
|
||||
// update the current staircase:
|
||||
this._currentStaircase.addResponse(response, value);
|
||||
// update the current staircase, but do not add the response again:
|
||||
this._currentStaircase.addResponse(response, value, false);
|
||||
|
||||
// move onto the next trial:
|
||||
this._nextTrial();
|
||||
@ -206,6 +208,7 @@ export class MultiStairHandler extends TrialHandler
|
||||
const args = Object.assign({}, condition);
|
||||
args.psychoJS = this._psychoJS;
|
||||
args.varName = this._varName;
|
||||
// label becomes name:
|
||||
args.name = condition.label;
|
||||
args.autoLog = this._autoLog;
|
||||
if (typeof condition.nTrials === "undefined")
|
||||
@ -254,7 +257,7 @@ export class MultiStairHandler extends TrialHandler
|
||||
// if the current pass is empty, get a new one:
|
||||
if (this._currentPass.length === 0)
|
||||
{
|
||||
this._currentPass = this._staircases.filter(handler => !handler.finished);
|
||||
this._currentPass = this._staircases.filter( handler => !handler.finished );
|
||||
|
||||
if (this._multiMethod === TrialHandler.Method.SEQUENTIAL)
|
||||
{
|
||||
@ -322,12 +325,48 @@ export class MultiStairHandler extends TrialHandler
|
||||
{
|
||||
if (typeof this._trialList[t] === "undefined")
|
||||
{
|
||||
this._trialList[t] = {[this._varName]: value};
|
||||
this._trialList[t] = {
|
||||
[this._name+"."+this._varName]: value,
|
||||
[this._name+".intensity"]: value
|
||||
};
|
||||
for (const attribute of this._currentStaircase._userAttributes)
|
||||
{
|
||||
// "name" becomes "label" again:
|
||||
if (attribute === "name")
|
||||
{
|
||||
this._trialList[t][this._name+".label"] = this._currentStaircase["_name"];
|
||||
}
|
||||
else if (attribute !== "trialList" && attribute !== "extraInfo")
|
||||
{
|
||||
this._trialList[t][this._name+"."+attribute] = this._currentStaircase["_" + attribute];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this._snapshots[t] !== "undefined")
|
||||
{
|
||||
this._snapshots[t][this._varName] = value;
|
||||
this._snapshots[t].trialAttributes.push(this._varName);
|
||||
let fieldName = this._name + "." + this._varName;
|
||||
this._snapshots[t][fieldName] = value;
|
||||
this._snapshots[t].trialAttributes.push(fieldName);
|
||||
fieldName = this._name + ".intensity";
|
||||
this._snapshots[t][fieldName] = value;
|
||||
this._snapshots[t].trialAttributes.push(fieldName);
|
||||
|
||||
for (const attribute of this._currentStaircase._userAttributes)
|
||||
{
|
||||
// "name" becomes "label" again:
|
||||
if (attribute === 'name')
|
||||
{
|
||||
fieldName = this._name + ".label";
|
||||
this._snapshots[t][fieldName] = this._currentStaircase["_name"];
|
||||
this._snapshots[t].trialAttributes.push(fieldName);
|
||||
}
|
||||
else if (attribute !== 'trialList' && attribute !== 'extraInfo')
|
||||
{
|
||||
fieldName = this._name+"."+attribute;
|
||||
this._snapshots[t][fieldName] = this._currentStaircase["_" + attribute];
|
||||
this._snapshots[t].trialAttributes.push(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -118,10 +118,12 @@ export class QuestHandler extends TrialHandler
|
||||
* @public
|
||||
* @param{number} response - the response to the trial, must be either 0 (incorrect or
|
||||
* non-detected) or 1 (correct or detected)
|
||||
* @param{number | undefined} [value] - optional intensity / contrast / threshold
|
||||
* @param{number | undefined} value - optional intensity / contrast / threshold
|
||||
* @param{boolean} [doAddData = true] - whether or not to add the response as data to the
|
||||
* experiment
|
||||
* @returns {void}
|
||||
*/
|
||||
addResponse(response, value)
|
||||
addResponse(response, value, doAddData = true)
|
||||
{
|
||||
// check that response is either 0 or 1:
|
||||
if (response !== 0 && response !== 1)
|
||||
@ -133,6 +135,11 @@ export class QuestHandler extends TrialHandler
|
||||
};
|
||||
}
|
||||
|
||||
if (doAddData)
|
||||
{
|
||||
this._psychoJS.experiment.addData(this._name + '.response', response);
|
||||
}
|
||||
|
||||
// update the QUEST pdf:
|
||||
if (typeof value !== "undefined")
|
||||
{
|
||||
@ -145,7 +152,10 @@ export class QuestHandler extends TrialHandler
|
||||
|
||||
if (!this._finished)
|
||||
{
|
||||
// estimate the next value of the QUEST variable (and update the trial list and snapshots):
|
||||
this.next();
|
||||
|
||||
// estimate the next value of the QUEST variable
|
||||
// (and update the trial list and snapshots):
|
||||
this._estimateQuestValue();
|
||||
}
|
||||
}
|
||||
|
@ -688,7 +688,7 @@ export class TrialHandler extends PsychObject
|
||||
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:
|
||||
const indices = Array.from(this.trialList.keys());
|
||||
|
||||
if (this._method === TrialHandler.Method.SEQUENTIAL)
|
||||
|
@ -1433,3 +1433,58 @@ export function extensionFromMimeType(mimeType)
|
||||
|
||||
return '.dat';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an estimate of the download speed, by repeatedly downloading an image file from a distant
|
||||
* server.
|
||||
*
|
||||
* @name module:util.getDownloadSpeed
|
||||
* @function
|
||||
* @public
|
||||
* @param {PsychoJS} psychoJS the instance of PsychoJS
|
||||
* @param {number} [nbDownloads = 1] the number of image downloads over which to average
|
||||
* the download speed
|
||||
* @return {number} the download speed, in megabits per second
|
||||
*/
|
||||
export async function getDownloadSpeed(psychoJS, nbDownloads = 1)
|
||||
{
|
||||
// url of the image to download and size of the image in bits:
|
||||
// TODO use a variety of files, with different sizes
|
||||
const imageUrl = "https://upload.wikimedia.org/wikipedia/commons/a/a6/Brandenburger_Tor_abends.jpg";
|
||||
const imageSize_b = 2707459 * 8;
|
||||
|
||||
return new Promise( (resolve, reject) =>
|
||||
{
|
||||
let downloadTimeAccumulator = 0;
|
||||
let downloadCounter = 0;
|
||||
|
||||
const download = new Image();
|
||||
download.onload = () =>
|
||||
{
|
||||
const toc = performance.now();
|
||||
downloadTimeAccumulator += (toc-tic);
|
||||
++ downloadCounter;
|
||||
|
||||
if (downloadCounter === nbDownloads)
|
||||
{
|
||||
const speed_bps = (imageSize_b * nbDownloads) / (downloadTimeAccumulator / 1000);
|
||||
resolve(speed_bps / 1024 / 1024);
|
||||
}
|
||||
else
|
||||
{
|
||||
tic = performance.now();
|
||||
download.src = `${imageUrl}?salt=${tic}`;
|
||||
}
|
||||
}
|
||||
|
||||
download.onerror = (event) =>
|
||||
{
|
||||
const errorMsg = `unable to estimate the download speed: ${JSON.stringify(event)}`;
|
||||
psychoJS.logger.error(errorMsg);
|
||||
reject(errorMsg);
|
||||
}
|
||||
|
||||
let tic = performance.now();
|
||||
download.src = `${imageUrl}?salt=${tic}`;
|
||||
});
|
||||
}
|
||||
|
@ -145,7 +145,10 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
|
||||
if (hasChanged)
|
||||
{
|
||||
let radians = -ori * 0.017453292519943295;
|
||||
this._rotationMatrix = [[Math.cos(radians), -Math.sin(radians)], [Math.sin(radians), Math.cos(radians)]];
|
||||
this._rotationMatrix = [
|
||||
[Math.cos(radians), -Math.sin(radians)],
|
||||
[Math.sin(radians), Math.cos(radians)]
|
||||
];
|
||||
|
||||
this._onChange(true, true)();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user