compileprofile-spacetests 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +114 -114
- package/README.md +171 -147
- package/dist/cjs/common-interfaces.d.ts +33 -1
- package/dist/cjs/gonogo.js +89 -57
- package/dist/cjs/nback.d.ts +6 -2
- package/dist/cjs/nback.js +132 -2
- package/dist/cjs/spatial-cognition.js +0 -2
- package/dist/esm/common-interfaces.d.ts +33 -1
- package/dist/esm/gonogo.js +89 -57
- package/dist/esm/nback.d.ts +6 -2
- package/dist/esm/nback.js +128 -2
- package/dist/esm/spatial-cognition.js +0 -2
- package/example-data/emotions.json +807 -807
- package/example-data/gonogo.json +5392 -5392
- package/example-data/nback.json +892 -892
- package/example-data/participant.json +6 -6
- package/example-data/spatial-cognition.json +462 -462
- package/jest.config.js +13 -13
- package/original/emotion-recognition.R +39 -39
- package/original/gonogo.R +32 -32
- package/package.json +40 -40
- package/src/__tests__/integration/browserInfo.test.ts +16 -16
- package/src/__tests__/integration/emotions.test.ts +172 -172
- package/src/__tests__/integration/gonogo.test.ts +293 -183
- package/src/__tests__/integration/nback-versions.test.ts +182 -0
- package/src/__tests__/integration/nback.test.ts +148 -148
- package/src/__tests__/integration/spatial-cognition.test.ts +171 -171
- package/src/__tests__/test-utils/testData.ts +79 -79
- package/src/common-interfaces.ts +124 -78
- package/src/emotions.ts +98 -98
- package/src/getters.ts +21 -21
- package/src/gonogo.ts +149 -127
- package/src/index.ts +39 -39
- package/src/nback.ts +182 -61
- package/src/spatial-cognition.ts +110 -112
- package/tsconfig.cjs.json +12 -12
- package/tsconfig.esm.json +14 -14
package/dist/cjs/gonogo.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
2
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
14
|
exports.gonogo_results = exports.gonogo_extract_trials = exports.gonogo_calculate_results = void 0;
|
|
4
15
|
var BEST_SCORE = 300;
|
|
@@ -9,49 +20,69 @@ var gonogo_calculate_results = function (data) {
|
|
|
9
20
|
return results;
|
|
10
21
|
};
|
|
11
22
|
exports.gonogo_calculate_results = gonogo_calculate_results;
|
|
23
|
+
// Which stimulus is "go" in each block of the runner:
|
|
24
|
+
// 1 astronaut, 2 alien, 3 rocket, 4 anything but the rocket (the UFOs).
|
|
25
|
+
var should_go_for = function (block_number, stimulus) {
|
|
26
|
+
switch (block_number) {
|
|
27
|
+
case 1: return stimulus == "astronaut_trans_1.png";
|
|
28
|
+
case 2: return stimulus == "alien_trans_1.png";
|
|
29
|
+
case 3: return stimulus == "rocket_trans_1.png";
|
|
30
|
+
case 4: return stimulus != "rocket_trans_1.png";
|
|
31
|
+
default: return false;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
12
34
|
var gonogo_extract_trials = function (obj) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
o.stimulustype = match[1];
|
|
22
|
-
o.image_number = match[2];
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
35
|
+
var trials = [];
|
|
36
|
+
obj.filter(function (o) { return 'image' in o && o.image !== undefined && o.image !== null; }).forEach(function (o) {
|
|
37
|
+
// image is in format <stimulus>_trans_<number>.png, e.g.
|
|
38
|
+
// ".../test-stimuli/gonogo/aliens/astronaut_trans_1.png"
|
|
39
|
+
var match = String(o.image).match(/([^/]+)_(\d+)\.png$/);
|
|
40
|
+
if (!match) {
|
|
41
|
+
// Without a recognisable stimulus the trial cannot be scored, so leave it out
|
|
42
|
+
// instead of counting it as neither correct nor incorrect.
|
|
25
43
|
console.warn('Image format did not match expected pattern:', o.image);
|
|
26
44
|
return;
|
|
27
45
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
o.should_go = o.stimulus == "rocket_trans_1.png";
|
|
39
|
-
break;
|
|
40
|
-
case 4:
|
|
41
|
-
o.should_go = !(o.stimulus == "rocket_trans_1.png");
|
|
42
|
-
break;
|
|
43
|
-
default:
|
|
44
|
-
o.should_go = false;
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
o.correct = o.should_go == o.did_go;
|
|
46
|
+
// Copy, so the caller's data is not modified.
|
|
47
|
+
var trial = __assign({}, o);
|
|
48
|
+
trial.stimulus = match[0];
|
|
49
|
+
trial.stimulustype = match[1];
|
|
50
|
+
trial.image_number = match[2];
|
|
51
|
+
// Prefer a go/no-go flag recorded by the runner; otherwise derive it from block and image.
|
|
52
|
+
trial.should_go = typeof o.should_go === 'boolean' ? o.should_go : should_go_for(o.block_number, trial.stimulus);
|
|
53
|
+
trial.did_go = o.response !== null && o.response !== undefined;
|
|
54
|
+
trial.correct = trial.should_go == trial.did_go;
|
|
55
|
+
trials.push(trial);
|
|
48
56
|
});
|
|
49
57
|
return trials;
|
|
50
58
|
};
|
|
51
59
|
exports.gonogo_extract_trials = gonogo_extract_trials;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
var has_rt = function (o) { return o.did_go === true && typeof o.rt === 'number' && !isNaN(o.rt); };
|
|
61
|
+
// Mean of the values; 0 for an empty list, so callers always get a number.
|
|
62
|
+
var mean = function (values) { return values.length > 0 ? values.reduce(function (a, b) { return a + b; }, 0) / values.length : 0; };
|
|
63
|
+
// Mean RT over the trials that have a response; trials without one have no RT and are not averaged in as 0.
|
|
64
|
+
var mean_rt = function (trials) { return mean(trials.filter(has_rt).map(function (o) { return o.rt; })); };
|
|
65
|
+
var proportion = function (trials, pred) {
|
|
66
|
+
return trials.length > 0 ? trials.filter(pred).length / trials.length : 0;
|
|
67
|
+
};
|
|
68
|
+
// Hit rate and correct-rejection rate weighted equally. When a set has no go (or no no-go)
|
|
69
|
+
// trials, the other rate is used alone.
|
|
70
|
+
var balanced_accuracy = function (go_trials, nogo_trials) {
|
|
71
|
+
var rates = [];
|
|
72
|
+
if (go_trials.length > 0)
|
|
73
|
+
rates.push(proportion(go_trials, function (o) { return o.correct === true; }));
|
|
74
|
+
if (nogo_trials.length > 0)
|
|
75
|
+
rates.push(proportion(nogo_trials, function (o) { return o.correct === true; }));
|
|
76
|
+
return mean(rates);
|
|
77
|
+
};
|
|
78
|
+
// Score 0-100 from the hit RT and the balanced accuracy: RT / (acc^5), clamped to 300-5000 ms.
|
|
79
|
+
// Without any hit there is no RT to score, so the score is 0. (Previously an RT of 0 was
|
|
80
|
+
// clamped to the best value, so a participant who never responded scored 100.)
|
|
81
|
+
var calculate_performance_score = function (hit_rt, acc) {
|
|
82
|
+
if (!(hit_rt > 0)) {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
var performance = hit_rt / (Math.pow(acc, 5) + 0.001);
|
|
55
86
|
performance = Math.min(Math.max(performance, BEST_SCORE), WORST_SCORE);
|
|
56
87
|
var performance_score = (1 - ((performance - BEST_SCORE) / (WORST_SCORE - BEST_SCORE))) * 100;
|
|
57
88
|
return performance_score;
|
|
@@ -63,51 +94,52 @@ var gonogo_results = function (data) {
|
|
|
63
94
|
}
|
|
64
95
|
var correct_trials = data.filter(function (o) { return o.correct === true; });
|
|
65
96
|
var incorrect_trials = data.filter(function (o) { return o.correct === false; });
|
|
66
|
-
var missing_trials = []; // technically not possible in GNG, as no response is just incorrect
|
|
67
97
|
var go_trials = data.filter(function (o) { return o.should_go === true; });
|
|
68
98
|
var nogo_trials = data.filter(function (o) { return o.should_go === false; });
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
var
|
|
72
|
-
var
|
|
73
|
-
var correct_responded_trials = responded_trials.filter(function (o) { return o.correct === true; });
|
|
74
|
-
var incorrect_responded_trials = responded_trials.filter(function (o) { return o.correct === false; });
|
|
75
|
-
var avg_rt = responded_trials.length > 0 ? total_rt / responded_trials.length : 0;
|
|
76
|
-
var avg_acc = correct_trials.length / data.length;
|
|
77
|
-
// Calculate performance score using the existing formula
|
|
78
|
-
var performance_score = calculate_performance_score(avg_rt, avg_acc);
|
|
99
|
+
// A response is correct on go trials (hit) and incorrect on no-go trials (false alarm).
|
|
100
|
+
// Correct no-go trials and missed go trials have no response, so no RT.
|
|
101
|
+
var hit_rt = mean_rt(go_trials);
|
|
102
|
+
var balanced_acc = balanced_accuracy(go_trials, nogo_trials);
|
|
79
103
|
var common = {
|
|
80
104
|
n_trials: data.length,
|
|
81
105
|
n_correct_trials: correct_trials.length,
|
|
82
106
|
n_incorrect_trials: incorrect_trials.length,
|
|
83
|
-
avg_rt_total:
|
|
84
|
-
avg_rt_correct: correct_trials
|
|
85
|
-
avg_rt_incorrect: incorrect_trials
|
|
86
|
-
performance_score:
|
|
107
|
+
avg_rt_total: mean_rt(data),
|
|
108
|
+
avg_rt_correct: mean_rt(correct_trials),
|
|
109
|
+
avg_rt_incorrect: mean_rt(incorrect_trials),
|
|
110
|
+
performance_score: calculate_performance_score(hit_rt, balanced_acc)
|
|
87
111
|
};
|
|
88
112
|
// Group by block for special results
|
|
89
113
|
var by_block = Object.values(data.reduce(function (acc, trial) {
|
|
90
114
|
var key = trial.block_number || 'unknown';
|
|
91
115
|
if (!acc[key])
|
|
92
|
-
acc[key] = { block: key, trials: []
|
|
116
|
+
acc[key] = { block: key, trials: [] };
|
|
93
117
|
acc[key].trials.push(trial);
|
|
94
118
|
return acc;
|
|
95
119
|
}, {})).map(function (group) {
|
|
96
|
-
|
|
97
|
-
|
|
120
|
+
var block_go = group.trials.filter(function (o) { return o.should_go === true; });
|
|
121
|
+
var block_nogo = group.trials.filter(function (o) { return o.should_go === false; });
|
|
122
|
+
group.avg_rt = mean_rt(block_go);
|
|
123
|
+
group.avg_acc = proportion(group.trials, function (o) { return o.correct === true; });
|
|
124
|
+
group.hit_rate = proportion(block_go, function (o) { return o.correct === true; });
|
|
125
|
+
group.correct_rejection_rate = proportion(block_nogo, function (o) { return o.correct === true; });
|
|
126
|
+
group.balanced_acc = balanced_accuracy(block_go, block_nogo);
|
|
98
127
|
return group;
|
|
99
128
|
});
|
|
100
129
|
var special = {
|
|
130
|
+
balanced_acc: balanced_acc,
|
|
101
131
|
by_block: by_block,
|
|
102
132
|
go_trials: {
|
|
103
133
|
n: go_trials.length,
|
|
104
|
-
avg_rt:
|
|
105
|
-
avg_acc: go_trials
|
|
134
|
+
avg_rt: hit_rt,
|
|
135
|
+
avg_acc: proportion(go_trials, function (o) { return o.correct === true; }),
|
|
136
|
+
n_omissions: go_trials.filter(function (o) { return o.did_go === false; }).length,
|
|
106
137
|
},
|
|
107
138
|
nogo_trials: {
|
|
108
139
|
n: nogo_trials.length,
|
|
109
|
-
avg_rt: nogo_trials
|
|
110
|
-
avg_acc: nogo_trials
|
|
140
|
+
avg_rt: mean_rt(nogo_trials),
|
|
141
|
+
avg_acc: proportion(nogo_trials, function (o) { return o.correct === true; }),
|
|
142
|
+
n_commissions: nogo_trials.filter(function (o) { return o.did_go === true; }).length,
|
|
111
143
|
}
|
|
112
144
|
};
|
|
113
145
|
return {
|
package/dist/cjs/nback.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { NbackResultsStructure } from './common-interfaces';
|
|
1
|
+
import { NbackResultsStructure, NbackGroupResults, NbackBlockResults, NbackLevelResults } from './common-interfaces';
|
|
2
2
|
declare const nback_calculate_results: (obj: any[]) => NbackResultsStructure;
|
|
3
3
|
declare const nback_extract_trials: (obj: any[]) => any[];
|
|
4
4
|
declare const nback_results: (trials: any[]) => NbackResultsStructure;
|
|
5
|
-
|
|
5
|
+
declare const inverse_normal: (p: number) => number;
|
|
6
|
+
declare const nback_group_results: (trials: any[]) => NbackGroupResults;
|
|
7
|
+
declare const nback_results_by_block: (trials: any[]) => NbackBlockResults[];
|
|
8
|
+
declare const nback_results_by_nback: (trials: any[]) => NbackLevelResults[];
|
|
9
|
+
export { nback_extract_trials, nback_results, nback_calculate_results, nback_group_results, nback_results_by_block, nback_results_by_nback, inverse_normal, NbackResultsStructure };
|
package/dist/cjs/nback.js
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
2
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.nback_calculate_results = exports.nback_results = exports.nback_extract_trials = void 0;
|
|
14
|
+
exports.inverse_normal = exports.nback_results_by_nback = exports.nback_results_by_block = exports.nback_group_results = exports.nback_calculate_results = exports.nback_results = exports.nback_extract_trials = void 0;
|
|
4
15
|
// from the passed json extract those objects that have field is_target
|
|
16
|
+
// Version 1 trials have no n_back field and get exactly the original result. Version 2 trials
|
|
17
|
+
// (n_back, block, block_index on each trial) also get special.by_block and special.by_nback.
|
|
5
18
|
var nback_calculate_results = function (obj) {
|
|
6
19
|
var trials = nback_extract_trials(obj);
|
|
7
|
-
|
|
20
|
+
var results = nback_results(trials);
|
|
21
|
+
if (nback_is_versioned(trials)) {
|
|
22
|
+
results.special.by_block = nback_results_by_block(trials);
|
|
23
|
+
results.special.by_nback = nback_results_by_nback(trials);
|
|
24
|
+
}
|
|
25
|
+
return results;
|
|
8
26
|
};
|
|
9
27
|
exports.nback_calculate_results = nback_calculate_results;
|
|
28
|
+
var nback_is_versioned = function (trials) {
|
|
29
|
+
return trials.some(function (t) { return t.n_back !== undefined && t.n_back !== null; });
|
|
30
|
+
};
|
|
10
31
|
var nback_extract_trials = function (obj) {
|
|
11
32
|
var trials = obj.filter(function (o) { return 'is_target' in o; });
|
|
12
33
|
return trials;
|
|
@@ -52,3 +73,112 @@ var nback_results = function (trials) {
|
|
|
52
73
|
return results;
|
|
53
74
|
};
|
|
54
75
|
exports.nback_results = nback_results;
|
|
76
|
+
// Standard normal CDF (Marsaglia 2004, Taylor series; absolute error ~1e-15 for |x| < 8)
|
|
77
|
+
var normal_cdf = function (x) {
|
|
78
|
+
var sum = x, term = x, prev = 0;
|
|
79
|
+
var x2 = x * x;
|
|
80
|
+
for (var i = 3; sum !== prev; i += 2) {
|
|
81
|
+
prev = sum;
|
|
82
|
+
term *= x2 / i;
|
|
83
|
+
sum = prev + term;
|
|
84
|
+
}
|
|
85
|
+
return 0.5 + sum * Math.exp(-0.5 * x2 - 0.91893853320467274178);
|
|
86
|
+
};
|
|
87
|
+
// Inverse of the standard normal CDF: Acklam's rational approximation, polished with one
|
|
88
|
+
// Halley step against normal_cdf, as Acklam recommends. Accurate to ~1e-12 for 1e-10 < p < 1 - 1e-10.
|
|
89
|
+
var inverse_normal = function (p) {
|
|
90
|
+
if (p <= 0)
|
|
91
|
+
return -Infinity;
|
|
92
|
+
if (p >= 1)
|
|
93
|
+
return Infinity;
|
|
94
|
+
var x = acklam_inverse_normal(p);
|
|
95
|
+
var e = normal_cdf(x) - p;
|
|
96
|
+
var u = e * Math.sqrt(2 * Math.PI) * Math.exp(x * x / 2);
|
|
97
|
+
return x - u / (1 + x * u / 2);
|
|
98
|
+
};
|
|
99
|
+
exports.inverse_normal = inverse_normal;
|
|
100
|
+
var acklam_inverse_normal = function (p) {
|
|
101
|
+
var a = [-3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02,
|
|
102
|
+
1.383577518672690e+02, -3.066479806614716e+01, 2.506628277459239e+00];
|
|
103
|
+
var b = [-5.447609879822406e+01, 1.615858368580409e+02, -1.556989798598866e+02,
|
|
104
|
+
6.680131188771972e+01, -1.328068155288572e+01];
|
|
105
|
+
var c = [-7.784894002430293e-03, -3.223964580411365e-01, -2.400758277161838e+00,
|
|
106
|
+
-2.549671010322563e+00, 4.374664141464968e+00, 2.938163982698783e+00];
|
|
107
|
+
var d = [7.784695709041462e-03, 3.224671290700398e-01, 2.445134137142996e+00,
|
|
108
|
+
3.754408661907416e+00];
|
|
109
|
+
var p_low = 0.02425;
|
|
110
|
+
if (p < p_low) {
|
|
111
|
+
var q_1 = Math.sqrt(-2 * Math.log(p));
|
|
112
|
+
return (((((c[0] * q_1 + c[1]) * q_1 + c[2]) * q_1 + c[3]) * q_1 + c[4]) * q_1 + c[5]) /
|
|
113
|
+
((((d[0] * q_1 + d[1]) * q_1 + d[2]) * q_1 + d[3]) * q_1 + 1);
|
|
114
|
+
}
|
|
115
|
+
if (p > 1 - p_low)
|
|
116
|
+
return -acklam_inverse_normal(1 - p);
|
|
117
|
+
var q = p - 0.5;
|
|
118
|
+
var r = q * q;
|
|
119
|
+
return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q /
|
|
120
|
+
(((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
|
|
121
|
+
};
|
|
122
|
+
var mean_or_null = function (xs) {
|
|
123
|
+
return xs.length > 0 ? xs.reduce(function (s, x) { return s + x; }, 0) / xs.length : null;
|
|
124
|
+
};
|
|
125
|
+
var responded = function (t) { return t.response !== null && t.response !== undefined; };
|
|
126
|
+
// d' uses the log-linear correction (Hautus 1995), so it stays finite at 0 % or 100 % rates
|
|
127
|
+
var nback_group_results = function (trials) {
|
|
128
|
+
var targets = trials.filter(function (t) { return t.is_target; });
|
|
129
|
+
var non_targets = trials.filter(function (t) { return !t.is_target; });
|
|
130
|
+
var hits = targets.filter(responded);
|
|
131
|
+
var false_alarms = non_targets.filter(responded);
|
|
132
|
+
var lures = non_targets.filter(function (t) { return t.is_lure === true; });
|
|
133
|
+
var lure_false_alarms = lures.filter(responded).length;
|
|
134
|
+
var n_correct = trials.filter(function (t) { return t.correct === true; }).length;
|
|
135
|
+
var rts = function (ts) { return ts.map(function (t) { return t.rt; }).filter(function (rt) { return typeof rt === 'number'; }); };
|
|
136
|
+
return {
|
|
137
|
+
n_trials: trials.length,
|
|
138
|
+
n_correct_trials: n_correct,
|
|
139
|
+
performance_score: trials.length > 0 ? n_correct * 100 / trials.length : 0,
|
|
140
|
+
n_targets: targets.length,
|
|
141
|
+
n_non_targets: non_targets.length,
|
|
142
|
+
hits: hits.length,
|
|
143
|
+
misses: targets.length - hits.length,
|
|
144
|
+
false_alarms: false_alarms.length,
|
|
145
|
+
correct_rejections: non_targets.length - false_alarms.length,
|
|
146
|
+
hit_rate: targets.length > 0 ? hits.length / targets.length : null,
|
|
147
|
+
false_alarm_rate: non_targets.length > 0 ? false_alarms.length / non_targets.length : null,
|
|
148
|
+
d_prime: targets.length > 0 && non_targets.length > 0
|
|
149
|
+
? inverse_normal((hits.length + 0.5) / (targets.length + 1))
|
|
150
|
+
- inverse_normal((false_alarms.length + 0.5) / (non_targets.length + 1))
|
|
151
|
+
: null,
|
|
152
|
+
n_lures: lures.length,
|
|
153
|
+
lure_false_alarms: lure_false_alarms,
|
|
154
|
+
lure_false_alarm_rate: lures.length > 0 ? lure_false_alarms / lures.length : null,
|
|
155
|
+
avg_rt_hits: mean_or_null(rts(hits)),
|
|
156
|
+
avg_rt_false_alarms: mean_or_null(rts(false_alarms)),
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
exports.nback_group_results = nback_group_results;
|
|
160
|
+
var nback_results_by_block = function (trials) {
|
|
161
|
+
var blocks = new Map();
|
|
162
|
+
for (var _i = 0, trials_1 = trials; _i < trials_1.length; _i++) {
|
|
163
|
+
var t = trials_1[_i];
|
|
164
|
+
if (!blocks.has(t.block_index))
|
|
165
|
+
blocks.set(t.block_index, []);
|
|
166
|
+
blocks.get(t.block_index).push(t);
|
|
167
|
+
}
|
|
168
|
+
return Array.from(blocks.entries())
|
|
169
|
+
.sort(function (_a, _b) {
|
|
170
|
+
var a = _a[0];
|
|
171
|
+
var b = _b[0];
|
|
172
|
+
return a - b;
|
|
173
|
+
})
|
|
174
|
+
.map(function (_a) {
|
|
175
|
+
var block_index = _a[0], ts = _a[1];
|
|
176
|
+
return (__assign({ block: ts[0].block, block_index: block_index, n_back: ts[0].n_back }, nback_group_results(ts)));
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
exports.nback_results_by_block = nback_results_by_block;
|
|
180
|
+
var nback_results_by_nback = function (trials) {
|
|
181
|
+
var levels = Array.from(new Set(trials.map(function (t) { return t.n_back; }))).sort(function (a, b) { return a - b; });
|
|
182
|
+
return levels.map(function (n_back) { return (__assign({ n_back: n_back }, nback_group_results(trials.filter(function (t) { return t.n_back === n_back; })))); });
|
|
183
|
+
};
|
|
184
|
+
exports.nback_results_by_nback = nback_results_by_nback;
|
|
@@ -24,11 +24,9 @@ exports.spatial_extract_trials = spatial_extract_trials;
|
|
|
24
24
|
var spatial_results = function (obj) {
|
|
25
25
|
var correct_trials = obj.filter(function (o) { return o.correct === 1 || o.correct === true; });
|
|
26
26
|
var incorrect_trials = obj.filter(function (o) { return o.correct === 0 || o.correct === false; });
|
|
27
|
-
var missing_trials = obj.filter(function (o) { return o.response === null || o.response === undefined; });
|
|
28
27
|
var total_rt = obj.reduce(function (a, b) { return a + (b.rt || 0); }, 0);
|
|
29
28
|
var correct_rt = correct_trials.reduce(function (a, b) { return a + (b.rt || 0); }, 0);
|
|
30
29
|
var incorrect_rt = incorrect_trials.reduce(function (a, b) { return a + (b.rt || 0); }, 0);
|
|
31
|
-
var missing_rt = missing_trials.reduce(function (a, b) { return a + (b.rt || 0); }, 0);
|
|
32
30
|
var avg_rt = obj.length > 0 ? total_rt / obj.length : 0;
|
|
33
31
|
var avg_acc = obj.length > 0 ? correct_trials.length / obj.length : 0;
|
|
34
32
|
var performance_score = calculate_performance_score(avg_rt, avg_acc);
|
|
@@ -7,6 +7,33 @@ interface CommonResults {
|
|
|
7
7
|
avg_rt_incorrect: number;
|
|
8
8
|
performance_score: number;
|
|
9
9
|
}
|
|
10
|
+
interface NbackGroupResults {
|
|
11
|
+
n_trials: number;
|
|
12
|
+
n_correct_trials: number;
|
|
13
|
+
performance_score: number;
|
|
14
|
+
n_targets: number;
|
|
15
|
+
n_non_targets: number;
|
|
16
|
+
hits: number;
|
|
17
|
+
misses: number;
|
|
18
|
+
false_alarms: number;
|
|
19
|
+
correct_rejections: number;
|
|
20
|
+
hit_rate: number | null;
|
|
21
|
+
false_alarm_rate: number | null;
|
|
22
|
+
d_prime: number | null;
|
|
23
|
+
n_lures: number;
|
|
24
|
+
lure_false_alarms: number;
|
|
25
|
+
lure_false_alarm_rate: number | null;
|
|
26
|
+
avg_rt_hits: number | null;
|
|
27
|
+
avg_rt_false_alarms: number | null;
|
|
28
|
+
}
|
|
29
|
+
interface NbackBlockResults extends NbackGroupResults {
|
|
30
|
+
block: string;
|
|
31
|
+
block_index: number;
|
|
32
|
+
n_back: number;
|
|
33
|
+
}
|
|
34
|
+
interface NbackLevelResults extends NbackGroupResults {
|
|
35
|
+
n_back: number;
|
|
36
|
+
}
|
|
10
37
|
interface NbackSpecialResults {
|
|
11
38
|
target_trials: {
|
|
12
39
|
n: number;
|
|
@@ -18,6 +45,8 @@ interface NbackSpecialResults {
|
|
|
18
45
|
avg_rt: number;
|
|
19
46
|
avg_acc: number;
|
|
20
47
|
};
|
|
48
|
+
by_block?: NbackBlockResults[];
|
|
49
|
+
by_nback?: NbackLevelResults[];
|
|
21
50
|
}
|
|
22
51
|
interface SpatialSpecialResults {
|
|
23
52
|
by_distance: any[];
|
|
@@ -25,16 +54,19 @@ interface SpatialSpecialResults {
|
|
|
25
54
|
by_rotation: any[];
|
|
26
55
|
}
|
|
27
56
|
interface GonogoSpecialResults {
|
|
57
|
+
balanced_acc: number;
|
|
28
58
|
by_block: any[];
|
|
29
59
|
go_trials: {
|
|
30
60
|
n: number;
|
|
31
61
|
avg_rt: number;
|
|
32
62
|
avg_acc: number;
|
|
63
|
+
n_omissions: number;
|
|
33
64
|
};
|
|
34
65
|
nogo_trials: {
|
|
35
66
|
n: number;
|
|
36
67
|
avg_rt: number;
|
|
37
68
|
avg_acc: number;
|
|
69
|
+
n_commissions: number;
|
|
38
70
|
};
|
|
39
71
|
}
|
|
40
72
|
interface EmotionSpecialResults {
|
|
@@ -57,4 +89,4 @@ interface EmotionResultsStructure {
|
|
|
57
89
|
common: CommonResults;
|
|
58
90
|
special: EmotionSpecialResults;
|
|
59
91
|
}
|
|
60
|
-
export { CommonResults, NbackResultsStructure, SpatialResultsStructure, GonogoResultsStructure, EmotionResultsStructure, NbackSpecialResults, SpatialSpecialResults, GonogoSpecialResults, EmotionSpecialResults };
|
|
92
|
+
export { CommonResults, NbackResultsStructure, SpatialResultsStructure, GonogoResultsStructure, EmotionResultsStructure, NbackSpecialResults, NbackGroupResults, NbackBlockResults, NbackLevelResults, SpatialSpecialResults, GonogoSpecialResults, EmotionSpecialResults };
|
package/dist/esm/gonogo.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
1
12
|
var BEST_SCORE = 300;
|
|
2
13
|
var WORST_SCORE = 5000;
|
|
3
14
|
var gonogo_calculate_results = function (data) {
|
|
@@ -5,48 +16,68 @@ var gonogo_calculate_results = function (data) {
|
|
|
5
16
|
var results = gonogo_results(trials);
|
|
6
17
|
return results;
|
|
7
18
|
};
|
|
19
|
+
// Which stimulus is "go" in each block of the runner:
|
|
20
|
+
// 1 astronaut, 2 alien, 3 rocket, 4 anything but the rocket (the UFOs).
|
|
21
|
+
var should_go_for = function (block_number, stimulus) {
|
|
22
|
+
switch (block_number) {
|
|
23
|
+
case 1: return stimulus == "astronaut_trans_1.png";
|
|
24
|
+
case 2: return stimulus == "alien_trans_1.png";
|
|
25
|
+
case 3: return stimulus == "rocket_trans_1.png";
|
|
26
|
+
case 4: return stimulus != "rocket_trans_1.png";
|
|
27
|
+
default: return false;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
8
30
|
var gonogo_extract_trials = function (obj) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
o.stimulustype = match[1];
|
|
18
|
-
o.image_number = match[2];
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
31
|
+
var trials = [];
|
|
32
|
+
obj.filter(function (o) { return 'image' in o && o.image !== undefined && o.image !== null; }).forEach(function (o) {
|
|
33
|
+
// image is in format <stimulus>_trans_<number>.png, e.g.
|
|
34
|
+
// ".../test-stimuli/gonogo/aliens/astronaut_trans_1.png"
|
|
35
|
+
var match = String(o.image).match(/([^/]+)_(\d+)\.png$/);
|
|
36
|
+
if (!match) {
|
|
37
|
+
// Without a recognisable stimulus the trial cannot be scored, so leave it out
|
|
38
|
+
// instead of counting it as neither correct nor incorrect.
|
|
21
39
|
console.warn('Image format did not match expected pattern:', o.image);
|
|
22
40
|
return;
|
|
23
41
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
o.should_go = o.stimulus == "rocket_trans_1.png";
|
|
35
|
-
break;
|
|
36
|
-
case 4:
|
|
37
|
-
o.should_go = !(o.stimulus == "rocket_trans_1.png");
|
|
38
|
-
break;
|
|
39
|
-
default:
|
|
40
|
-
o.should_go = false;
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
o.correct = o.should_go == o.did_go;
|
|
42
|
+
// Copy, so the caller's data is not modified.
|
|
43
|
+
var trial = __assign({}, o);
|
|
44
|
+
trial.stimulus = match[0];
|
|
45
|
+
trial.stimulustype = match[1];
|
|
46
|
+
trial.image_number = match[2];
|
|
47
|
+
// Prefer a go/no-go flag recorded by the runner; otherwise derive it from block and image.
|
|
48
|
+
trial.should_go = typeof o.should_go === 'boolean' ? o.should_go : should_go_for(o.block_number, trial.stimulus);
|
|
49
|
+
trial.did_go = o.response !== null && o.response !== undefined;
|
|
50
|
+
trial.correct = trial.should_go == trial.did_go;
|
|
51
|
+
trials.push(trial);
|
|
44
52
|
});
|
|
45
53
|
return trials;
|
|
46
54
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
var has_rt = function (o) { return o.did_go === true && typeof o.rt === 'number' && !isNaN(o.rt); };
|
|
56
|
+
// Mean of the values; 0 for an empty list, so callers always get a number.
|
|
57
|
+
var mean = function (values) { return values.length > 0 ? values.reduce(function (a, b) { return a + b; }, 0) / values.length : 0; };
|
|
58
|
+
// Mean RT over the trials that have a response; trials without one have no RT and are not averaged in as 0.
|
|
59
|
+
var mean_rt = function (trials) { return mean(trials.filter(has_rt).map(function (o) { return o.rt; })); };
|
|
60
|
+
var proportion = function (trials, pred) {
|
|
61
|
+
return trials.length > 0 ? trials.filter(pred).length / trials.length : 0;
|
|
62
|
+
};
|
|
63
|
+
// Hit rate and correct-rejection rate weighted equally. When a set has no go (or no no-go)
|
|
64
|
+
// trials, the other rate is used alone.
|
|
65
|
+
var balanced_accuracy = function (go_trials, nogo_trials) {
|
|
66
|
+
var rates = [];
|
|
67
|
+
if (go_trials.length > 0)
|
|
68
|
+
rates.push(proportion(go_trials, function (o) { return o.correct === true; }));
|
|
69
|
+
if (nogo_trials.length > 0)
|
|
70
|
+
rates.push(proportion(nogo_trials, function (o) { return o.correct === true; }));
|
|
71
|
+
return mean(rates);
|
|
72
|
+
};
|
|
73
|
+
// Score 0-100 from the hit RT and the balanced accuracy: RT / (acc^5), clamped to 300-5000 ms.
|
|
74
|
+
// Without any hit there is no RT to score, so the score is 0. (Previously an RT of 0 was
|
|
75
|
+
// clamped to the best value, so a participant who never responded scored 100.)
|
|
76
|
+
var calculate_performance_score = function (hit_rt, acc) {
|
|
77
|
+
if (!(hit_rt > 0)) {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
var performance = hit_rt / (Math.pow(acc, 5) + 0.001);
|
|
50
81
|
performance = Math.min(Math.max(performance, BEST_SCORE), WORST_SCORE);
|
|
51
82
|
var performance_score = (1 - ((performance - BEST_SCORE) / (WORST_SCORE - BEST_SCORE))) * 100;
|
|
52
83
|
return performance_score;
|
|
@@ -58,51 +89,52 @@ var gonogo_results = function (data) {
|
|
|
58
89
|
}
|
|
59
90
|
var correct_trials = data.filter(function (o) { return o.correct === true; });
|
|
60
91
|
var incorrect_trials = data.filter(function (o) { return o.correct === false; });
|
|
61
|
-
var missing_trials = []; // technically not possible in GNG, as no response is just incorrect
|
|
62
92
|
var go_trials = data.filter(function (o) { return o.should_go === true; });
|
|
63
93
|
var nogo_trials = data.filter(function (o) { return o.should_go === false; });
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
var
|
|
67
|
-
var
|
|
68
|
-
var correct_responded_trials = responded_trials.filter(function (o) { return o.correct === true; });
|
|
69
|
-
var incorrect_responded_trials = responded_trials.filter(function (o) { return o.correct === false; });
|
|
70
|
-
var avg_rt = responded_trials.length > 0 ? total_rt / responded_trials.length : 0;
|
|
71
|
-
var avg_acc = correct_trials.length / data.length;
|
|
72
|
-
// Calculate performance score using the existing formula
|
|
73
|
-
var performance_score = calculate_performance_score(avg_rt, avg_acc);
|
|
94
|
+
// A response is correct on go trials (hit) and incorrect on no-go trials (false alarm).
|
|
95
|
+
// Correct no-go trials and missed go trials have no response, so no RT.
|
|
96
|
+
var hit_rt = mean_rt(go_trials);
|
|
97
|
+
var balanced_acc = balanced_accuracy(go_trials, nogo_trials);
|
|
74
98
|
var common = {
|
|
75
99
|
n_trials: data.length,
|
|
76
100
|
n_correct_trials: correct_trials.length,
|
|
77
101
|
n_incorrect_trials: incorrect_trials.length,
|
|
78
|
-
avg_rt_total:
|
|
79
|
-
avg_rt_correct: correct_trials
|
|
80
|
-
avg_rt_incorrect: incorrect_trials
|
|
81
|
-
performance_score:
|
|
102
|
+
avg_rt_total: mean_rt(data),
|
|
103
|
+
avg_rt_correct: mean_rt(correct_trials),
|
|
104
|
+
avg_rt_incorrect: mean_rt(incorrect_trials),
|
|
105
|
+
performance_score: calculate_performance_score(hit_rt, balanced_acc)
|
|
82
106
|
};
|
|
83
107
|
// Group by block for special results
|
|
84
108
|
var by_block = Object.values(data.reduce(function (acc, trial) {
|
|
85
109
|
var key = trial.block_number || 'unknown';
|
|
86
110
|
if (!acc[key])
|
|
87
|
-
acc[key] = { block: key, trials: []
|
|
111
|
+
acc[key] = { block: key, trials: [] };
|
|
88
112
|
acc[key].trials.push(trial);
|
|
89
113
|
return acc;
|
|
90
114
|
}, {})).map(function (group) {
|
|
91
|
-
|
|
92
|
-
|
|
115
|
+
var block_go = group.trials.filter(function (o) { return o.should_go === true; });
|
|
116
|
+
var block_nogo = group.trials.filter(function (o) { return o.should_go === false; });
|
|
117
|
+
group.avg_rt = mean_rt(block_go);
|
|
118
|
+
group.avg_acc = proportion(group.trials, function (o) { return o.correct === true; });
|
|
119
|
+
group.hit_rate = proportion(block_go, function (o) { return o.correct === true; });
|
|
120
|
+
group.correct_rejection_rate = proportion(block_nogo, function (o) { return o.correct === true; });
|
|
121
|
+
group.balanced_acc = balanced_accuracy(block_go, block_nogo);
|
|
93
122
|
return group;
|
|
94
123
|
});
|
|
95
124
|
var special = {
|
|
125
|
+
balanced_acc: balanced_acc,
|
|
96
126
|
by_block: by_block,
|
|
97
127
|
go_trials: {
|
|
98
128
|
n: go_trials.length,
|
|
99
|
-
avg_rt:
|
|
100
|
-
avg_acc: go_trials
|
|
129
|
+
avg_rt: hit_rt,
|
|
130
|
+
avg_acc: proportion(go_trials, function (o) { return o.correct === true; }),
|
|
131
|
+
n_omissions: go_trials.filter(function (o) { return o.did_go === false; }).length,
|
|
101
132
|
},
|
|
102
133
|
nogo_trials: {
|
|
103
134
|
n: nogo_trials.length,
|
|
104
|
-
avg_rt: nogo_trials
|
|
105
|
-
avg_acc: nogo_trials
|
|
135
|
+
avg_rt: mean_rt(nogo_trials),
|
|
136
|
+
avg_acc: proportion(nogo_trials, function (o) { return o.correct === true; }),
|
|
137
|
+
n_commissions: nogo_trials.filter(function (o) { return o.did_go === true; }).length,
|
|
106
138
|
}
|
|
107
139
|
};
|
|
108
140
|
return {
|
package/dist/esm/nback.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { NbackResultsStructure } from './common-interfaces';
|
|
1
|
+
import { NbackResultsStructure, NbackGroupResults, NbackBlockResults, NbackLevelResults } from './common-interfaces';
|
|
2
2
|
declare const nback_calculate_results: (obj: any[]) => NbackResultsStructure;
|
|
3
3
|
declare const nback_extract_trials: (obj: any[]) => any[];
|
|
4
4
|
declare const nback_results: (trials: any[]) => NbackResultsStructure;
|
|
5
|
-
|
|
5
|
+
declare const inverse_normal: (p: number) => number;
|
|
6
|
+
declare const nback_group_results: (trials: any[]) => NbackGroupResults;
|
|
7
|
+
declare const nback_results_by_block: (trials: any[]) => NbackBlockResults[];
|
|
8
|
+
declare const nback_results_by_nback: (trials: any[]) => NbackLevelResults[];
|
|
9
|
+
export { nback_extract_trials, nback_results, nback_calculate_results, nback_group_results, nback_results_by_block, nback_results_by_nback, inverse_normal, NbackResultsStructure };
|