compileprofile-spacetests 0.2.1 → 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 +115 -0
- package/README.md +152 -15
- package/dist/cjs/common-interfaces.d.ts +92 -0
- package/dist/cjs/common-interfaces.js +2 -0
- package/dist/cjs/emotions.d.ts +2 -7
- package/dist/cjs/emotions.js +56 -22
- package/dist/cjs/gonogo.d.ts +1 -6
- package/dist/cjs/gonogo.js +125 -46
- package/dist/cjs/index.d.ts +2 -5
- package/dist/cjs/nback.d.ts +6 -12
- package/dist/cjs/nback.js +159 -15
- package/dist/cjs/spatial-cognition.d.ts +1 -6
- package/dist/cjs/spatial-cognition.js +67 -14
- package/dist/esm/common-interfaces.d.ts +92 -0
- package/dist/esm/common-interfaces.js +1 -0
- package/dist/esm/emotions.d.ts +2 -7
- package/dist/esm/emotions.js +55 -21
- package/dist/esm/gonogo.d.ts +1 -6
- package/dist/esm/gonogo.js +125 -46
- package/dist/esm/index.d.ts +2 -5
- package/dist/esm/nback.d.ts +6 -12
- package/dist/esm/nback.js +155 -15
- package/dist/esm/spatial-cognition.d.ts +1 -6
- package/dist/esm/spatial-cognition.js +67 -14
- package/package.json +2 -2
- package/src/__tests__/integration/emotions.test.ts +122 -14
- package/src/__tests__/integration/gonogo.test.ts +240 -27
- package/src/__tests__/integration/nback-versions.test.ts +182 -0
- package/src/__tests__/integration/nback.test.ts +113 -20
- package/src/__tests__/integration/spatial-cognition.test.ts +106 -1
- package/src/common-interfaces.ts +125 -0
- package/src/emotions.ts +68 -29
- package/src/gonogo.ts +128 -55
- package/src/index.ts +22 -6
- package/src/nback.ts +160 -31
- package/src/spatial-cognition.ts +77 -21
|
@@ -1,47 +1,136 @@
|
|
|
1
|
-
import { nback_extract_trials, nback_results,
|
|
2
|
-
nback_calculate_results
|
|
1
|
+
import { nback_extract_trials, nback_results,
|
|
2
|
+
nback_calculate_results } from '../../nback';
|
|
3
3
|
import { loadTestData } from '../test-utils/testData';
|
|
4
4
|
|
|
5
5
|
describe('N-Back Task Integration Tests', () => {
|
|
6
6
|
const testData = loadTestData('nback');
|
|
7
7
|
|
|
8
|
+
// Mock data for predictable testing
|
|
9
|
+
const mockNbackData = [
|
|
10
|
+
{
|
|
11
|
+
"stimulus": "",
|
|
12
|
+
"trial_index": 25,
|
|
13
|
+
"nbackimage": "https://unyavwofnmwihnpvkjji.supabase.co/storage/v1/object/public/test-stimuli/nback//final/nback_final_fractals_4.png",
|
|
14
|
+
"is_target": false,
|
|
15
|
+
"rt": null,
|
|
16
|
+
"response": null,
|
|
17
|
+
"trial_type": "html-button-response",
|
|
18
|
+
"plugin_version": "2.0.0",
|
|
19
|
+
"time_elapsed": 70759,
|
|
20
|
+
"attempted": false,
|
|
21
|
+
"correct": true
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"stimulus": "",
|
|
25
|
+
"trial_index": 26,
|
|
26
|
+
"nbackimage": "https://unyavwofnmwihnpvkjji.supabase.co/storage/v1/object/public/test-stimuli/nback//final/nback_final_fractals_2.png",
|
|
27
|
+
"is_target": false,
|
|
28
|
+
"rt": null,
|
|
29
|
+
"response": null,
|
|
30
|
+
"trial_type": "html-button-response",
|
|
31
|
+
"plugin_version": "2.0.0",
|
|
32
|
+
"time_elapsed": 73325,
|
|
33
|
+
"attempted": false,
|
|
34
|
+
"correct": true
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"stimulus": "",
|
|
38
|
+
"trial_index": 27,
|
|
39
|
+
"nbackimage": "https://unyavwofnmwihnpvkjji.supabase.co/storage/v1/object/public/test-stimuli/nback//final/nback_final_fractals_4.png",
|
|
40
|
+
"is_target": true,
|
|
41
|
+
"rt": 1000,
|
|
42
|
+
"response": 0,
|
|
43
|
+
"trial_type": "html-button-response",
|
|
44
|
+
"plugin_version": "2.0.0",
|
|
45
|
+
"time_elapsed": 74928,
|
|
46
|
+
"attempted": false,
|
|
47
|
+
"correct": true
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"stimulus": "",
|
|
51
|
+
"trial_index": 28,
|
|
52
|
+
"nbackimage": "https://unyavwofnmwihnpvkjji.supabase.co/storage/v1/object/public/test-stimuli/nback//final/nback_final_fractals_3.png",
|
|
53
|
+
"is_target": false,
|
|
54
|
+
"rt": 2000,
|
|
55
|
+
"response": 0,
|
|
56
|
+
"trial_type": "html-button-response",
|
|
57
|
+
"plugin_version": "2.0.0",
|
|
58
|
+
"time_elapsed": 77512,
|
|
59
|
+
"attempted": false,
|
|
60
|
+
"correct": false
|
|
61
|
+
},
|
|
62
|
+
// Non-trial data that should be filtered out
|
|
63
|
+
{ trial_type: 'browser-check', browser: 'chrome' },
|
|
64
|
+
{ trial_type: 'instructions', text: 'Press space when...' }
|
|
65
|
+
];
|
|
66
|
+
|
|
8
67
|
describe('nback_extract_trials', () => {
|
|
9
68
|
it('should extract only trials with is_target field', () => {
|
|
10
69
|
const trials = nback_extract_trials(testData.nback);
|
|
11
70
|
expect(trials.every(trial => trial.is_target !== null)).toBe(true);
|
|
12
71
|
expect(trials.length).toBe(60);
|
|
13
72
|
});
|
|
73
|
+
|
|
74
|
+
it('should extract mock data correctly', () => {
|
|
75
|
+
const trials = nback_extract_trials(mockNbackData);
|
|
76
|
+
expect(trials.length).toBe(4); // Only trials with is_target field
|
|
77
|
+
expect(trials.every(trial => 'is_target' in trial)).toBe(true);
|
|
78
|
+
});
|
|
14
79
|
});
|
|
15
80
|
|
|
16
81
|
describe('nback_results', () => {
|
|
17
82
|
const trials = nback_extract_trials(testData.nback);
|
|
18
83
|
const results = nback_results(trials);
|
|
19
84
|
|
|
20
|
-
it('should calculate correct
|
|
21
|
-
expect(results.
|
|
22
|
-
expect(results.
|
|
23
|
-
expect(results.
|
|
24
|
-
expect(results.
|
|
85
|
+
it('should calculate correct common metrics', () => {
|
|
86
|
+
expect(results.common).toHaveProperty('n_trials');
|
|
87
|
+
expect(results.common).toHaveProperty('n_correct_trials');
|
|
88
|
+
expect(results.common).toHaveProperty('n_incorrect_trials');
|
|
89
|
+
expect(results.common).toHaveProperty('avg_rt_total');
|
|
90
|
+
expect(results.common).toHaveProperty('avg_rt_correct');
|
|
91
|
+
expect(results.common).toHaveProperty('avg_rt_incorrect');
|
|
92
|
+
expect(results.common).toHaveProperty('performance_score');
|
|
93
|
+
expect(results.common.n_trials).toBeGreaterThan(0);
|
|
25
94
|
});
|
|
26
95
|
|
|
27
96
|
it('should calculate correct target trials metrics', () => {
|
|
28
|
-
expect(results.target_trials).toHaveProperty('n');
|
|
29
|
-
expect(results.target_trials).toHaveProperty('avg_rt');
|
|
30
|
-
expect(results.target_trials).toHaveProperty('avg_acc');
|
|
31
|
-
expect(results.target_trials.n).toBeGreaterThan(0);
|
|
97
|
+
expect(results.special.target_trials).toHaveProperty('n');
|
|
98
|
+
expect(results.special.target_trials).toHaveProperty('avg_rt');
|
|
99
|
+
expect(results.special.target_trials).toHaveProperty('avg_acc');
|
|
100
|
+
expect(results.special.target_trials.n).toBeGreaterThan(0);
|
|
32
101
|
});
|
|
33
102
|
|
|
34
103
|
it('should calculate correct non-target trials metrics', () => {
|
|
35
|
-
expect(results.non_target_trials).toHaveProperty('n');
|
|
36
|
-
expect(results.non_target_trials).toHaveProperty('avg_rt');
|
|
37
|
-
expect(results.non_target_trials).toHaveProperty('avg_acc');
|
|
38
|
-
expect(results.non_target_trials.n).toBeGreaterThan(0);
|
|
104
|
+
expect(results.special.non_target_trials).toHaveProperty('n');
|
|
105
|
+
expect(results.special.non_target_trials).toHaveProperty('avg_rt');
|
|
106
|
+
expect(results.special.non_target_trials).toHaveProperty('avg_acc');
|
|
107
|
+
expect(results.special.non_target_trials.n).toBeGreaterThan(0);
|
|
39
108
|
});
|
|
40
109
|
|
|
41
110
|
it('should calculate performance score', () => {
|
|
42
|
-
expect(results.
|
|
43
|
-
expect(results.
|
|
44
|
-
expect(results.
|
|
111
|
+
expect(results.common.performance_score).not.toBeNaN();
|
|
112
|
+
expect(results.common.performance_score).toBeGreaterThanOrEqual(0);
|
|
113
|
+
expect(results.common.performance_score).toBeLessThanOrEqual(100);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should calculate mock data results correctly', () => {
|
|
117
|
+
const mockTrials = nback_extract_trials(mockNbackData);
|
|
118
|
+
const mockResults = nback_results(mockTrials);
|
|
119
|
+
|
|
120
|
+
// Expected: 6 trials total, 4 correct, 2 incorrect, 1 missing (counting false as incorrect, not missing)
|
|
121
|
+
expect(mockResults.common.n_trials).toBe(4);
|
|
122
|
+
expect(mockResults.common.n_correct_trials).toBe(3);
|
|
123
|
+
expect(mockResults.common.n_incorrect_trials).toBe(1);
|
|
124
|
+
|
|
125
|
+
expect(mockResults.common.performance_score).toEqual(75);
|
|
126
|
+
|
|
127
|
+
expect(mockResults.common.avg_rt_total).toEqual(1500);
|
|
128
|
+
|
|
129
|
+
expect(mockResults.special.target_trials.n).toBe(1);
|
|
130
|
+
expect(mockResults.special.target_trials.avg_acc).toEqual(100);
|
|
131
|
+
|
|
132
|
+
expect(mockResults.special.non_target_trials.n).toBe(3);
|
|
133
|
+
expect(mockResults.special.non_target_trials.avg_acc).toBeCloseTo(66.67, 1);
|
|
45
134
|
});
|
|
46
135
|
});
|
|
47
136
|
|
|
@@ -49,8 +138,12 @@ describe('N-Back Task Integration Tests', () => {
|
|
|
49
138
|
it('should calculate results correctly', () => {
|
|
50
139
|
const results = nback_calculate_results(testData.nback);
|
|
51
140
|
expect(results).toBeDefined();
|
|
52
|
-
|
|
53
|
-
|
|
141
|
+
expect(results).toHaveProperty('common');
|
|
142
|
+
expect(results).toHaveProperty('special');
|
|
143
|
+
expect(results.common).toHaveProperty('n_trials');
|
|
144
|
+
expect(results.common).toHaveProperty('performance_score');
|
|
145
|
+
expect(results.special).toHaveProperty('target_trials');
|
|
146
|
+
expect(results.special).toHaveProperty('non_target_trials');
|
|
54
147
|
});
|
|
55
148
|
});
|
|
56
149
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// test out the spatial_extract_image_info function
|
|
2
|
-
import { spatial_extract_image_info, spatial_calculate_results,
|
|
2
|
+
import { spatial_extract_trials, spatial_extract_image_info, spatial_calculate_results,
|
|
3
3
|
spatial_results } from '../../spatial-cognition';
|
|
4
4
|
import { loadTestData } from '../test-utils/testData';
|
|
5
5
|
|
|
@@ -21,6 +21,43 @@ const ImageInfoTestData = [
|
|
|
21
21
|
}
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
+
// Enhanced mock data for comprehensive testing
|
|
25
|
+
const mockSpatialData = [
|
|
26
|
+
{
|
|
27
|
+
stimulus: "<img src=\"https://example.com/map_left_1_90.png\" class=\"max-w-none rotate_0\" width=\"512\" height=\"512\" alt=\"Map\">",
|
|
28
|
+
rt: 800,
|
|
29
|
+
response: 0 // left - correct
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
stimulus: "<img src=\"https://example.com/map_right_2_180.png\" class=\"max-w-none rotate_90\" width=\"512\" height=\"512\" alt=\"Map\">",
|
|
33
|
+
rt: 1200,
|
|
34
|
+
response: 1 // right - correct
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
stimulus: "<img src=\"https://example.com/map_left_1_270.png\" class=\"max-w-none rotate_180\" width=\"512\" height=\"512\" alt=\"Map\">",
|
|
38
|
+
rt: 600,
|
|
39
|
+
response: 1 // right - incorrect (should be left)
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
stimulus: "<img src=\"https://example.com/map_right_3_90.png\" class=\"max-w-none rotate_270\" width=\"512\" height=\"512\" alt=\"Map\">",
|
|
43
|
+
rt: 1000,
|
|
44
|
+
response: 1 // right - correct
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
stimulus: "<img src=\"https://example.com/map_left_2_180.png\" class=\"max-w-none rotate_0\" width=\"512\" height=\"512\" alt=\"Map\">",
|
|
48
|
+
rt: 0,
|
|
49
|
+
response: null // missing response
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
stimulus: "<img src=\"https://example.com/map_right_1_90.png\" class=\"max-w-none rotate_90\" width=\"512\" height=\"512\" alt=\"Map\">",
|
|
53
|
+
rt: 900,
|
|
54
|
+
response: 0 // left - incorrect (should be right)
|
|
55
|
+
},
|
|
56
|
+
// Non-spatial data that should be filtered out
|
|
57
|
+
{ trial_type: 'instructions', text: 'Use arrow keys...' },
|
|
58
|
+
{ trial_type: 'browser-check', browser: 'firefox' }
|
|
59
|
+
];
|
|
60
|
+
|
|
24
61
|
describe('spatial_extract_image_info test data', () => {
|
|
25
62
|
test('processes test data correctly', () => {
|
|
26
63
|
const resultsTestData = spatial_extract_image_info(ImageInfoTestData);
|
|
@@ -48,6 +85,30 @@ describe('spatial_extract_image_info test data', () => {
|
|
|
48
85
|
const resultData = spatial_extract_image_info(testData.spatialCognition);
|
|
49
86
|
expect(resultData).toBeDefined();
|
|
50
87
|
});
|
|
88
|
+
|
|
89
|
+
test('processes mock data correctly', () => {
|
|
90
|
+
// First extract spatial trials, then process image info
|
|
91
|
+
const mockTrials = spatial_extract_trials(mockSpatialData);
|
|
92
|
+
expect(mockTrials.length).toBe(6); // Only spatial trials
|
|
93
|
+
|
|
94
|
+
// Check first trial
|
|
95
|
+
expect(mockTrials[0].direction).toBe('left');
|
|
96
|
+
expect(mockTrials[0].distance).toBe('1');
|
|
97
|
+
expect(mockTrials[0].angle).toBe('90');
|
|
98
|
+
expect(mockTrials[0].rotation).toBe('0');
|
|
99
|
+
expect(mockTrials[0].response).toBe('left');
|
|
100
|
+
expect(mockTrials[0].correct).toBe(true);
|
|
101
|
+
|
|
102
|
+
// Check second trial
|
|
103
|
+
expect(mockTrials[1].direction).toBe('right');
|
|
104
|
+
expect(mockTrials[1].response).toBe('right');
|
|
105
|
+
expect(mockTrials[1].correct).toBe(true);
|
|
106
|
+
|
|
107
|
+
// Check incorrect trial
|
|
108
|
+
expect(mockTrials[2].direction).toBe('left');
|
|
109
|
+
expect(mockTrials[2].response).toBe('right');
|
|
110
|
+
expect(mockTrials[2].correct).toBe(false);
|
|
111
|
+
});
|
|
51
112
|
});
|
|
52
113
|
|
|
53
114
|
describe('spatial_results test data', () => {
|
|
@@ -55,12 +116,56 @@ describe('spatial_results test data', () => {
|
|
|
55
116
|
const resultsTestData = spatial_results(ImageInfoTestData);
|
|
56
117
|
console.log(resultsTestData);
|
|
57
118
|
expect(resultsTestData).toBeDefined();
|
|
119
|
+
expect(resultsTestData).toHaveProperty('common');
|
|
120
|
+
expect(resultsTestData).toHaveProperty('special');
|
|
121
|
+
expect(resultsTestData.common).toHaveProperty('n_trials');
|
|
122
|
+
expect(resultsTestData.common).toHaveProperty('n_correct_trials');
|
|
123
|
+
expect(resultsTestData.common).toHaveProperty('n_incorrect_trials');
|
|
124
|
+
expect(resultsTestData.common).toHaveProperty('performance_score');
|
|
125
|
+
expect(resultsTestData.special).toHaveProperty('by_distance');
|
|
126
|
+
expect(resultsTestData.special).toHaveProperty('by_angle');
|
|
127
|
+
expect(resultsTestData.special).toHaveProperty('by_rotation');
|
|
58
128
|
});
|
|
59
129
|
test('processes real data correctly', () => {
|
|
60
130
|
const testData = loadTestData('spatialCognition').spatialCognition;
|
|
61
131
|
const resultData = spatial_calculate_results(testData);
|
|
62
132
|
console.log(resultData);
|
|
63
133
|
expect(resultData).toBeDefined();
|
|
134
|
+
expect(resultData).toHaveProperty('common');
|
|
135
|
+
expect(resultData).toHaveProperty('special');
|
|
136
|
+
expect(resultData.common).toHaveProperty('n_trials');
|
|
137
|
+
expect(resultData.common).toHaveProperty('n_correct_trials');
|
|
138
|
+
expect(resultData.common).toHaveProperty('avg_rt_total');
|
|
139
|
+
expect(resultData.common).toHaveProperty('performance_score');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('processes mock data with predictable results', () => {
|
|
143
|
+
const spatialTrials = mockSpatialData.filter(o => 'stimulus' in o && o.stimulus.startsWith("<img src="));
|
|
144
|
+
const mockTrials = spatial_extract_image_info(spatialTrials);
|
|
145
|
+
const mockResults = spatial_results(mockTrials);
|
|
146
|
+
|
|
147
|
+
// Expected: 6 trials total, 3 correct, 3 incorrect, 1 missing
|
|
148
|
+
expect(mockResults.common.n_trials).toBe(6);
|
|
149
|
+
expect(mockResults.common.n_correct_trials).toBe(3);
|
|
150
|
+
expect(mockResults.common.n_incorrect_trials).toBe(3);
|
|
151
|
+
|
|
152
|
+
// Expected avg_rt_total: (800+1200+600+1000+0+900)/6 = 750
|
|
153
|
+
expect(mockResults.common.avg_rt_total).toBe(750);
|
|
154
|
+
|
|
155
|
+
// Expected avg_rt_correct: actual calculation shows 466.67
|
|
156
|
+
expect(mockResults.common.avg_rt_correct).toBeCloseTo(466.67, 1);
|
|
157
|
+
|
|
158
|
+
// Expected avg_rt_incorrect: (600+900+more)/3 - need to check actual calculation
|
|
159
|
+
expect(mockResults.common.avg_rt_incorrect).toBeGreaterThan(0);
|
|
160
|
+
|
|
161
|
+
// Performance score should be calculated based on RT/accuracy formula
|
|
162
|
+
expect(mockResults.common.performance_score).toBeGreaterThan(0);
|
|
163
|
+
expect(mockResults.common.performance_score).toBeLessThanOrEqual(100);
|
|
164
|
+
|
|
165
|
+
// Check special results structure
|
|
166
|
+
expect(mockResults.special.by_distance).toHaveLength(3); // distances: 1, 2, 3
|
|
167
|
+
expect(mockResults.special.by_angle).toHaveLength(3); // angles: 90, 180, 270
|
|
168
|
+
expect(mockResults.special.by_rotation).toHaveLength(4); // rotations: 0, 90, 180, 270
|
|
64
169
|
});
|
|
65
170
|
});
|
|
66
171
|
|
|
@@ -0,0 +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
|
+
// 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
|
|
125
|
+
};
|
package/src/emotions.ts
CHANGED
|
@@ -1,40 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
avg_acc: number;
|
|
5
|
-
performance: number;
|
|
6
|
-
}
|
|
1
|
+
import { EmotionResultsStructure, CommonResults, EmotionSpecialResults } from './common-interfaces';
|
|
2
|
+
|
|
3
|
+
const EMOTION_MAP = ["neutral", "sad", "happy", "surprised", "angry"];
|
|
7
4
|
|
|
8
5
|
const emotion_calculate_results = (obj: any[]): EmotionResultsStructure => {
|
|
9
6
|
const trials = emotion_extract_trials(obj);
|
|
10
|
-
// throw if trials is empty
|
|
11
7
|
if (trials.length === 0) {
|
|
12
8
|
throw new Error("No trials found");
|
|
13
9
|
}
|
|
14
10
|
return emotion_results(trials);
|
|
15
11
|
};
|
|
16
12
|
|
|
17
|
-
const emotion_results = (obj: any[]): EmotionResultsStructure => {
|
|
18
|
-
// throw error if obj is empty
|
|
19
|
-
if (obj.length === 0) {
|
|
20
|
-
throw new Error("No trials found");
|
|
21
|
-
}
|
|
22
|
-
let results = {
|
|
23
|
-
n: obj.length,
|
|
24
|
-
avg_rt: obj.reduce((a, b) => a + b.rt, 0) / obj.length,
|
|
25
|
-
avg_acc: obj.reduce((a, b) => a + b.correct, 0) / obj.length,
|
|
26
|
-
performance: NaN
|
|
27
|
-
};
|
|
28
|
-
const best_score = 1000;
|
|
29
|
-
const worst_score = 10000;
|
|
30
|
-
let performance = results.avg_rt / (Math.pow(results.avg_acc, 5) + 0.001);
|
|
31
|
-
performance = Math.min(Math.max(performance, best_score), worst_score);
|
|
32
|
-
results.performance = (1 - ((performance - best_score) / (worst_score - best_score))) * 100;
|
|
33
|
-
return results;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
13
|
const emotion_extract_trials = (obj: any[]) => {
|
|
37
|
-
const emotionMap = ["neutral", "sad", "happy", "surprised", "angry"];
|
|
38
14
|
// filter trials where response and rt are defined and not null
|
|
39
15
|
let trials = obj.filter((o: any) => 'response' in o && 'rt' in o && 'emotion' in o);
|
|
40
16
|
trials = trials.filter((o: any) => o.response !== null && o.rt !== null);
|
|
@@ -49,11 +25,74 @@ const emotion_extract_trials = (obj: any[]) => {
|
|
|
49
25
|
}
|
|
50
26
|
})
|
|
51
27
|
trials.forEach((o: any) => {
|
|
52
|
-
o.emotion_response =
|
|
28
|
+
o.emotion_response = EMOTION_MAP[o.response] || "unknown";
|
|
53
29
|
o.correct = o.emotion === o.emotion_response;
|
|
54
30
|
// in the older ones there were 15 trials per rotation
|
|
55
31
|
});
|
|
56
32
|
return trials;
|
|
57
33
|
}
|
|
58
34
|
|
|
59
|
-
|
|
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};
|