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
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
|
|
4
|
-
// Define interfaces for our test data types
|
|
5
|
-
export interface TestDataFiles {
|
|
6
|
-
spatialCognition: any[];
|
|
7
|
-
nback: any[];
|
|
8
|
-
participant: any[];
|
|
9
|
-
gonogo: any[];
|
|
10
|
-
emotions: any[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Function to load all test data
|
|
14
|
-
export function loadTestData(type: string): TestDataFiles {
|
|
15
|
-
const dataDir = path.join(process.cwd(), 'example-data');
|
|
16
|
-
|
|
17
|
-
if (type === 'spatialCognition') {
|
|
18
|
-
return {
|
|
19
|
-
spatialCognition: JSON.parse(fs.readFileSync(path.join(dataDir, 'spatial-cognition.json'), 'utf-8')),
|
|
20
|
-
nback: [],
|
|
21
|
-
participant: [],
|
|
22
|
-
gonogo: [],
|
|
23
|
-
emotions: []
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
if (type === 'nback') {
|
|
27
|
-
return {
|
|
28
|
-
spatialCognition: [],
|
|
29
|
-
nback: JSON.parse(fs.readFileSync(path.join(dataDir, 'nback.json'), 'utf-8')),
|
|
30
|
-
participant: [],
|
|
31
|
-
gonogo: [],
|
|
32
|
-
emotions: []
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
if (type === 'participant') {
|
|
36
|
-
return {
|
|
37
|
-
spatialCognition: [],
|
|
38
|
-
nback: [],
|
|
39
|
-
participant: JSON.parse(fs.readFileSync(path.join(dataDir, 'participant.json'), 'utf-8')),
|
|
40
|
-
gonogo: [],
|
|
41
|
-
emotions: []
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
if (type === 'gonogo') {
|
|
45
|
-
return {
|
|
46
|
-
spatialCognition: [],
|
|
47
|
-
nback: [],
|
|
48
|
-
participant: [],
|
|
49
|
-
gonogo: JSON.parse(fs.readFileSync(path.join(dataDir, 'gonogo.json'), 'utf-8')),
|
|
50
|
-
emotions: []
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (type === 'emotions') {
|
|
54
|
-
return {
|
|
55
|
-
spatialCognition: [],
|
|
56
|
-
nback: [],
|
|
57
|
-
participant: [],
|
|
58
|
-
gonogo: [],
|
|
59
|
-
emotions: JSON.parse(fs.readFileSync(path.join(dataDir, 'emotions.json'), 'utf-8'))
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
if (type === 'all') {
|
|
63
|
-
return {
|
|
64
|
-
spatialCognition: JSON.parse(fs.readFileSync(path.join(dataDir, 'spatial-cognition.json'), 'utf-8')),
|
|
65
|
-
nback: JSON.parse(fs.readFileSync(path.join(dataDir, 'nback.json'), 'utf-8')),
|
|
66
|
-
participant: JSON.parse(fs.readFileSync(path.join(dataDir, 'participant.json'), 'utf-8')),
|
|
67
|
-
gonogo: JSON.parse(fs.readFileSync(path.join(dataDir, 'gonogo.json'), 'utf-8')),
|
|
68
|
-
emotions: JSON.parse(fs.readFileSync(path.join(dataDir, 'emotions.json'), 'utf-8'))
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Default return for unknown types
|
|
73
|
-
return {
|
|
74
|
-
spatialCognition: [],
|
|
75
|
-
nback: [],
|
|
76
|
-
participant: [],
|
|
77
|
-
gonogo: [],
|
|
78
|
-
emotions: []
|
|
79
|
-
};
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
// Define interfaces for our test data types
|
|
5
|
+
export interface TestDataFiles {
|
|
6
|
+
spatialCognition: any[];
|
|
7
|
+
nback: any[];
|
|
8
|
+
participant: any[];
|
|
9
|
+
gonogo: any[];
|
|
10
|
+
emotions: any[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Function to load all test data
|
|
14
|
+
export function loadTestData(type: string): TestDataFiles {
|
|
15
|
+
const dataDir = path.join(process.cwd(), 'example-data');
|
|
16
|
+
|
|
17
|
+
if (type === 'spatialCognition') {
|
|
18
|
+
return {
|
|
19
|
+
spatialCognition: JSON.parse(fs.readFileSync(path.join(dataDir, 'spatial-cognition.json'), 'utf-8')),
|
|
20
|
+
nback: [],
|
|
21
|
+
participant: [],
|
|
22
|
+
gonogo: [],
|
|
23
|
+
emotions: []
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (type === 'nback') {
|
|
27
|
+
return {
|
|
28
|
+
spatialCognition: [],
|
|
29
|
+
nback: JSON.parse(fs.readFileSync(path.join(dataDir, 'nback.json'), 'utf-8')),
|
|
30
|
+
participant: [],
|
|
31
|
+
gonogo: [],
|
|
32
|
+
emotions: []
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (type === 'participant') {
|
|
36
|
+
return {
|
|
37
|
+
spatialCognition: [],
|
|
38
|
+
nback: [],
|
|
39
|
+
participant: JSON.parse(fs.readFileSync(path.join(dataDir, 'participant.json'), 'utf-8')),
|
|
40
|
+
gonogo: [],
|
|
41
|
+
emotions: []
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (type === 'gonogo') {
|
|
45
|
+
return {
|
|
46
|
+
spatialCognition: [],
|
|
47
|
+
nback: [],
|
|
48
|
+
participant: [],
|
|
49
|
+
gonogo: JSON.parse(fs.readFileSync(path.join(dataDir, 'gonogo.json'), 'utf-8')),
|
|
50
|
+
emotions: []
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (type === 'emotions') {
|
|
54
|
+
return {
|
|
55
|
+
spatialCognition: [],
|
|
56
|
+
nback: [],
|
|
57
|
+
participant: [],
|
|
58
|
+
gonogo: [],
|
|
59
|
+
emotions: JSON.parse(fs.readFileSync(path.join(dataDir, 'emotions.json'), 'utf-8'))
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (type === 'all') {
|
|
63
|
+
return {
|
|
64
|
+
spatialCognition: JSON.parse(fs.readFileSync(path.join(dataDir, 'spatial-cognition.json'), 'utf-8')),
|
|
65
|
+
nback: JSON.parse(fs.readFileSync(path.join(dataDir, 'nback.json'), 'utf-8')),
|
|
66
|
+
participant: JSON.parse(fs.readFileSync(path.join(dataDir, 'participant.json'), 'utf-8')),
|
|
67
|
+
gonogo: JSON.parse(fs.readFileSync(path.join(dataDir, 'gonogo.json'), 'utf-8')),
|
|
68
|
+
emotions: JSON.parse(fs.readFileSync(path.join(dataDir, 'emotions.json'), 'utf-8'))
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Default return for unknown types
|
|
73
|
+
return {
|
|
74
|
+
spatialCognition: [],
|
|
75
|
+
nback: [],
|
|
76
|
+
participant: [],
|
|
77
|
+
gonogo: [],
|
|
78
|
+
emotions: []
|
|
79
|
+
};
|
|
80
80
|
}
|
package/src/common-interfaces.ts
CHANGED
|
@@ -1,79 +1,125 @@
|
|
|
1
|
-
interface CommonResults {
|
|
2
|
-
n_trials: number;
|
|
3
|
-
n_correct_trials: number;
|
|
4
|
-
n_incorrect_trials: number;
|
|
5
|
-
avg_rt_total: number;
|
|
6
|
-
avg_rt_correct: number;
|
|
7
|
-
avg_rt_incorrect: number;
|
|
8
|
-
performance_score: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
1
|
+
interface CommonResults {
|
|
2
|
+
n_trials: number;
|
|
3
|
+
n_correct_trials: number;
|
|
4
|
+
n_incorrect_trials: number;
|
|
5
|
+
avg_rt_total: number;
|
|
6
|
+
avg_rt_correct: number;
|
|
7
|
+
avg_rt_incorrect: number;
|
|
8
|
+
performance_score: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Accuracy, signal detection and reaction times for one group of n-back trials
|
|
12
|
+
interface NbackGroupResults {
|
|
13
|
+
n_trials: number;
|
|
14
|
+
n_correct_trials: number;
|
|
15
|
+
performance_score: number;
|
|
16
|
+
n_targets: number;
|
|
17
|
+
n_non_targets: number;
|
|
18
|
+
hits: number;
|
|
19
|
+
misses: number;
|
|
20
|
+
false_alarms: number;
|
|
21
|
+
correct_rejections: number;
|
|
22
|
+
hit_rate: number | null;
|
|
23
|
+
false_alarm_rate: number | null;
|
|
24
|
+
d_prime: number | null;
|
|
25
|
+
// Lures: non-targets repeating an image from a distance other than N. Their false alarms are
|
|
26
|
+
// also counted in false_alarms; a high lure_false_alarm_rate means responding to familiarity.
|
|
27
|
+
n_lures: number;
|
|
28
|
+
lure_false_alarms: number;
|
|
29
|
+
lure_false_alarm_rate: number | null;
|
|
30
|
+
avg_rt_hits: number | null;
|
|
31
|
+
avg_rt_false_alarms: number | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface NbackBlockResults extends NbackGroupResults {
|
|
35
|
+
block: string;
|
|
36
|
+
block_index: number;
|
|
37
|
+
n_back: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface NbackLevelResults extends NbackGroupResults {
|
|
41
|
+
n_back: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface NbackSpecialResults {
|
|
45
|
+
target_trials: {
|
|
46
|
+
n: number;
|
|
47
|
+
avg_rt: number;
|
|
48
|
+
avg_acc: number;
|
|
49
|
+
};
|
|
50
|
+
non_target_trials: {
|
|
51
|
+
n: number;
|
|
52
|
+
avg_rt: number;
|
|
53
|
+
avg_acc: number;
|
|
54
|
+
};
|
|
55
|
+
// Only for test version 2+ data, whose trials carry n_back, block and block_index
|
|
56
|
+
by_block?: NbackBlockResults[];
|
|
57
|
+
by_nback?: NbackLevelResults[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface SpatialSpecialResults {
|
|
61
|
+
by_distance: any[];
|
|
62
|
+
by_angle: any[];
|
|
63
|
+
by_rotation: any[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface GonogoSpecialResults {
|
|
67
|
+
// Mean of go_trials.avg_acc (hit rate) and nogo_trials.avg_acc (correct-rejection rate).
|
|
68
|
+
// This is the accuracy the performance score uses, so withholding counts as much as responding.
|
|
69
|
+
balanced_acc: number;
|
|
70
|
+
// Per block: avg_rt over hits only, avg_acc = proportion correct, plus hit_rate,
|
|
71
|
+
// correct_rejection_rate and balanced_acc.
|
|
72
|
+
by_block: any[];
|
|
73
|
+
go_trials: {
|
|
74
|
+
n: number;
|
|
75
|
+
avg_rt: number; // mean RT of hits (go trials with a response); 0 when there are none
|
|
76
|
+
avg_acc: number; // hit rate
|
|
77
|
+
n_omissions: number; // go trials without a response
|
|
78
|
+
};
|
|
79
|
+
nogo_trials: {
|
|
80
|
+
n: number;
|
|
81
|
+
avg_rt: number; // mean RT of false alarms (no-go trials with a response); 0 when there are none
|
|
82
|
+
avg_acc: number; // correct-rejection rate
|
|
83
|
+
n_commissions: number; // no-go trials with a response (false alarms)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface EmotionSpecialResults {
|
|
88
|
+
by_emotion: any[];
|
|
89
|
+
confusion_matrix: any;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface NbackResultsStructure {
|
|
93
|
+
common: CommonResults;
|
|
94
|
+
special: NbackSpecialResults;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface SpatialResultsStructure {
|
|
98
|
+
common: CommonResults;
|
|
99
|
+
special: SpatialSpecialResults;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface GonogoResultsStructure {
|
|
103
|
+
common: CommonResults;
|
|
104
|
+
special: GonogoSpecialResults;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface EmotionResultsStructure {
|
|
108
|
+
common: CommonResults;
|
|
109
|
+
special: EmotionSpecialResults;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export {
|
|
113
|
+
CommonResults,
|
|
114
|
+
NbackResultsStructure,
|
|
115
|
+
SpatialResultsStructure,
|
|
116
|
+
GonogoResultsStructure,
|
|
117
|
+
EmotionResultsStructure,
|
|
118
|
+
NbackSpecialResults,
|
|
119
|
+
NbackGroupResults,
|
|
120
|
+
NbackBlockResults,
|
|
121
|
+
NbackLevelResults,
|
|
122
|
+
SpatialSpecialResults,
|
|
123
|
+
GonogoSpecialResults,
|
|
124
|
+
EmotionSpecialResults
|
|
79
125
|
};
|
package/src/emotions.ts
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import { EmotionResultsStructure, CommonResults, EmotionSpecialResults } from './common-interfaces';
|
|
2
|
-
|
|
3
|
-
const EMOTION_MAP = ["neutral", "sad", "happy", "surprised", "angry"];
|
|
4
|
-
|
|
5
|
-
const emotion_calculate_results = (obj: any[]): EmotionResultsStructure => {
|
|
6
|
-
const trials = emotion_extract_trials(obj);
|
|
7
|
-
if (trials.length === 0) {
|
|
8
|
-
throw new Error("No trials found");
|
|
9
|
-
}
|
|
10
|
-
return emotion_results(trials);
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const emotion_extract_trials = (obj: any[]) => {
|
|
14
|
-
// filter trials where response and rt are defined and not null
|
|
15
|
-
let trials = obj.filter((o: any) => 'response' in o && 'rt' in o && 'emotion' in o);
|
|
16
|
-
trials = trials.filter((o: any) => o.response !== null && o.rt !== null);
|
|
17
|
-
trials.forEach((o: any) => {
|
|
18
|
-
const match = o.emotion.match(/\/([^/]+)_([0-9]+)\.png/);
|
|
19
|
-
if (match) {
|
|
20
|
-
o.stimulus = match[0];
|
|
21
|
-
o.emotion = match[1];
|
|
22
|
-
o.image_number = match[2];
|
|
23
|
-
//this is <emotion>_<number>.png
|
|
24
|
-
o.image = `${match[1]}_${match[2]}.png`;
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
trials.forEach((o: any) => {
|
|
28
|
-
o.emotion_response = EMOTION_MAP[o.response] || "unknown";
|
|
29
|
-
o.correct = o.emotion === o.emotion_response;
|
|
30
|
-
// in the older ones there were 15 trials per rotation
|
|
31
|
-
});
|
|
32
|
-
return trials;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const emotion_results = (obj: any[]): EmotionResultsStructure => {
|
|
36
|
-
if (obj.length === 0) {
|
|
37
|
-
throw new Error("No trials found");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const correct_trials = obj.filter((o: any) => o.correct === true);
|
|
41
|
-
const incorrect_trials = obj.filter((o: any) => o.correct === false);
|
|
42
|
-
|
|
43
|
-
const total_rt = obj.reduce((a, b) => a + (b.rt || 0), 0);
|
|
44
|
-
const correct_rt = correct_trials.reduce((a, b) => a + (b.rt || 0), 0);
|
|
45
|
-
const incorrect_rt = incorrect_trials.reduce((a, b) => a + (b.rt || 0), 0);
|
|
46
|
-
|
|
47
|
-
const avg_rt = total_rt / obj.length;
|
|
48
|
-
const avg_acc = correct_trials.length / obj.length;
|
|
49
|
-
|
|
50
|
-
// Calculate performance score using the existing formula
|
|
51
|
-
const best_score = 1000;
|
|
52
|
-
const worst_score = 10000;
|
|
53
|
-
let performance = avg_rt / (Math.pow(avg_acc, 5) + 0.001);
|
|
54
|
-
performance = Math.min(Math.max(performance, best_score), worst_score);
|
|
55
|
-
const performance_score = (1 - ((performance - best_score) / (worst_score - best_score))) * 100;
|
|
56
|
-
|
|
57
|
-
const common: CommonResults = {
|
|
58
|
-
n_trials: obj.length,
|
|
59
|
-
n_correct_trials: correct_trials.length,
|
|
60
|
-
n_incorrect_trials: incorrect_trials.length,
|
|
61
|
-
avg_rt_total: avg_rt,
|
|
62
|
-
avg_rt_correct: correct_trials.length > 0 ? correct_rt / correct_trials.length : 0,
|
|
63
|
-
avg_rt_incorrect: incorrect_trials.length > 0 ? incorrect_rt / incorrect_trials.length : 0,
|
|
64
|
-
performance_score
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Group by emotion for special results
|
|
68
|
-
const by_emotion = EMOTION_MAP.map(emotion => {
|
|
69
|
-
const emotion_trials = obj.filter((o: any) => o.emotion === emotion);
|
|
70
|
-
return {
|
|
71
|
-
emotion,
|
|
72
|
-
n: emotion_trials.length,
|
|
73
|
-
avg_rt: emotion_trials.length > 0 ? emotion_trials.reduce((a, b) => a + (b.rt || 0), 0) / emotion_trials.length : 0,
|
|
74
|
-
avg_acc: emotion_trials.length > 0 ? emotion_trials.reduce((a, b) => a + (b.correct ? 1 : 0), 0) / emotion_trials.length : 0,
|
|
75
|
-
};
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Create confusion matrix
|
|
79
|
-
const confusion_matrix = EMOTION_MAP.reduce((matrix: any, actual: string) => {
|
|
80
|
-
matrix[actual] = EMOTION_MAP.reduce((row: any, predicted: string) => {
|
|
81
|
-
row[predicted] = obj.filter((o: any) => o.emotion === actual && o.emotion_response === predicted).length;
|
|
82
|
-
return row;
|
|
83
|
-
}, {});
|
|
84
|
-
return matrix;
|
|
85
|
-
}, {});
|
|
86
|
-
|
|
87
|
-
const special: EmotionSpecialResults = {
|
|
88
|
-
by_emotion,
|
|
89
|
-
confusion_matrix
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
common,
|
|
94
|
-
special
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export {EmotionResultsStructure, emotion_calculate_results, emotion_extract_trials, emotion_results};
|
|
1
|
+
import { EmotionResultsStructure, CommonResults, EmotionSpecialResults } from './common-interfaces';
|
|
2
|
+
|
|
3
|
+
const EMOTION_MAP = ["neutral", "sad", "happy", "surprised", "angry"];
|
|
4
|
+
|
|
5
|
+
const emotion_calculate_results = (obj: any[]): EmotionResultsStructure => {
|
|
6
|
+
const trials = emotion_extract_trials(obj);
|
|
7
|
+
if (trials.length === 0) {
|
|
8
|
+
throw new Error("No trials found");
|
|
9
|
+
}
|
|
10
|
+
return emotion_results(trials);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const emotion_extract_trials = (obj: any[]) => {
|
|
14
|
+
// filter trials where response and rt are defined and not null
|
|
15
|
+
let trials = obj.filter((o: any) => 'response' in o && 'rt' in o && 'emotion' in o);
|
|
16
|
+
trials = trials.filter((o: any) => o.response !== null && o.rt !== null);
|
|
17
|
+
trials.forEach((o: any) => {
|
|
18
|
+
const match = o.emotion.match(/\/([^/]+)_([0-9]+)\.png/);
|
|
19
|
+
if (match) {
|
|
20
|
+
o.stimulus = match[0];
|
|
21
|
+
o.emotion = match[1];
|
|
22
|
+
o.image_number = match[2];
|
|
23
|
+
//this is <emotion>_<number>.png
|
|
24
|
+
o.image = `${match[1]}_${match[2]}.png`;
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
trials.forEach((o: any) => {
|
|
28
|
+
o.emotion_response = EMOTION_MAP[o.response] || "unknown";
|
|
29
|
+
o.correct = o.emotion === o.emotion_response;
|
|
30
|
+
// in the older ones there were 15 trials per rotation
|
|
31
|
+
});
|
|
32
|
+
return trials;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const emotion_results = (obj: any[]): EmotionResultsStructure => {
|
|
36
|
+
if (obj.length === 0) {
|
|
37
|
+
throw new Error("No trials found");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const correct_trials = obj.filter((o: any) => o.correct === true);
|
|
41
|
+
const incorrect_trials = obj.filter((o: any) => o.correct === false);
|
|
42
|
+
|
|
43
|
+
const total_rt = obj.reduce((a, b) => a + (b.rt || 0), 0);
|
|
44
|
+
const correct_rt = correct_trials.reduce((a, b) => a + (b.rt || 0), 0);
|
|
45
|
+
const incorrect_rt = incorrect_trials.reduce((a, b) => a + (b.rt || 0), 0);
|
|
46
|
+
|
|
47
|
+
const avg_rt = total_rt / obj.length;
|
|
48
|
+
const avg_acc = correct_trials.length / obj.length;
|
|
49
|
+
|
|
50
|
+
// Calculate performance score using the existing formula
|
|
51
|
+
const best_score = 1000;
|
|
52
|
+
const worst_score = 10000;
|
|
53
|
+
let performance = avg_rt / (Math.pow(avg_acc, 5) + 0.001);
|
|
54
|
+
performance = Math.min(Math.max(performance, best_score), worst_score);
|
|
55
|
+
const performance_score = (1 - ((performance - best_score) / (worst_score - best_score))) * 100;
|
|
56
|
+
|
|
57
|
+
const common: CommonResults = {
|
|
58
|
+
n_trials: obj.length,
|
|
59
|
+
n_correct_trials: correct_trials.length,
|
|
60
|
+
n_incorrect_trials: incorrect_trials.length,
|
|
61
|
+
avg_rt_total: avg_rt,
|
|
62
|
+
avg_rt_correct: correct_trials.length > 0 ? correct_rt / correct_trials.length : 0,
|
|
63
|
+
avg_rt_incorrect: incorrect_trials.length > 0 ? incorrect_rt / incorrect_trials.length : 0,
|
|
64
|
+
performance_score
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Group by emotion for special results
|
|
68
|
+
const by_emotion = EMOTION_MAP.map(emotion => {
|
|
69
|
+
const emotion_trials = obj.filter((o: any) => o.emotion === emotion);
|
|
70
|
+
return {
|
|
71
|
+
emotion,
|
|
72
|
+
n: emotion_trials.length,
|
|
73
|
+
avg_rt: emotion_trials.length > 0 ? emotion_trials.reduce((a, b) => a + (b.rt || 0), 0) / emotion_trials.length : 0,
|
|
74
|
+
avg_acc: emotion_trials.length > 0 ? emotion_trials.reduce((a, b) => a + (b.correct ? 1 : 0), 0) / emotion_trials.length : 0,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Create confusion matrix
|
|
79
|
+
const confusion_matrix = EMOTION_MAP.reduce((matrix: any, actual: string) => {
|
|
80
|
+
matrix[actual] = EMOTION_MAP.reduce((row: any, predicted: string) => {
|
|
81
|
+
row[predicted] = obj.filter((o: any) => o.emotion === actual && o.emotion_response === predicted).length;
|
|
82
|
+
return row;
|
|
83
|
+
}, {});
|
|
84
|
+
return matrix;
|
|
85
|
+
}, {});
|
|
86
|
+
|
|
87
|
+
const special: EmotionSpecialResults = {
|
|
88
|
+
by_emotion,
|
|
89
|
+
confusion_matrix
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
common,
|
|
94
|
+
special
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export {EmotionResultsStructure, emotion_calculate_results, emotion_extract_trials, emotion_results};
|
package/src/getters.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
function getBrowserInformation(data: any[]): any {
|
|
2
|
-
const browserInfo = data.find(row => row.trial_type === "browser-check");
|
|
3
|
-
if (!browserInfo) {
|
|
4
|
-
return null;
|
|
5
|
-
}
|
|
6
|
-
return {
|
|
7
|
-
os: browserInfo.os,
|
|
8
|
-
mobile: browserInfo.mobile,
|
|
9
|
-
fullscreen: browserInfo.fullscreen,
|
|
10
|
-
vsync_rate: browserInfo.vsync_rate,
|
|
11
|
-
webcam: browserInfo.webcam,
|
|
12
|
-
microphone: browserInfo.microphone,
|
|
13
|
-
browser: browserInfo.browser,
|
|
14
|
-
browser_version: browserInfo.browser_version,
|
|
15
|
-
webaudio: browserInfo.webaudio,
|
|
16
|
-
width: browserInfo.width,
|
|
17
|
-
height: browserInfo.height
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export { getBrowserInformation };
|
|
1
|
+
function getBrowserInformation(data: any[]): any {
|
|
2
|
+
const browserInfo = data.find(row => row.trial_type === "browser-check");
|
|
3
|
+
if (!browserInfo) {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
return {
|
|
7
|
+
os: browserInfo.os,
|
|
8
|
+
mobile: browserInfo.mobile,
|
|
9
|
+
fullscreen: browserInfo.fullscreen,
|
|
10
|
+
vsync_rate: browserInfo.vsync_rate,
|
|
11
|
+
webcam: browserInfo.webcam,
|
|
12
|
+
microphone: browserInfo.microphone,
|
|
13
|
+
browser: browserInfo.browser,
|
|
14
|
+
browser_version: browserInfo.browser_version,
|
|
15
|
+
webaudio: browserInfo.webaudio,
|
|
16
|
+
width: browserInfo.width,
|
|
17
|
+
height: browserInfo.height
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { getBrowserInformation };
|