Skip to content

Commit 732a05b

Browse files
authored
Merge pull request #147 from makebrainwaves/dano/experiment-readd
Re-add Stroop, Search, and Multitasking Experiments
2 parents 415aa13 + 8a08194 commit 732a05b

File tree

27 files changed

+871
-928
lines changed

27 files changed

+871
-928
lines changed

app/components/CollectComponent/PreTestComponent.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import PreviewExperimentComponent from '../PreviewExperimentComponent';
1414
import PreviewButton from '../PreviewButtonComponent';
1515
import { HelpSidebar, HelpButton } from './HelpSidebar';
1616
import styles from '../styles/collect.css';
17-
import { loadProtocol } from '../../utils/labjs/functions';
17+
import { getExperimentFromType } from '../../utils/labjs/functions';
1818
import { ExperimentActions, DeviceActions } from '../../actions';
1919
import {
2020
DEVICES,
@@ -23,7 +23,7 @@ import {
2323
PLOTTING_INTERVAL,
2424
CONNECTION_STATUS,
2525
} from '../../constants/constants';
26-
import { ExperimentParameters, Trial } from '../../constants/interfaces';
26+
import { ExperimentParameters } from '../../constants/interfaces';
2727

2828
interface Props {
2929
ExperimentActions: typeof ExperimentActions;
@@ -93,11 +93,11 @@ export default class PreTestComponent extends Component<Props, State> {
9393
if (this.state.isPreviewing) {
9494
return (
9595
<PreviewExperimentComponent
96-
{...loadProtocol(this.props.type)}
96+
{...getExperimentFromType(this.props.type)}
9797
isPreviewing={this.state.isPreviewing}
9898
onEnd={this.endPreview}
9999
type={this.props.type}
100-
previewParams={this.props.params}
100+
params={this.props.params}
101101
title={this.props.title}
102102
/>
103103
);

app/components/DesignComponent/CustomDesignComponent.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,19 @@ import {
1010
Table,
1111
CheckboxProps,
1212
} from 'semantic-ui-react';
13-
import { isNil, isString } from 'lodash';
14-
import { History } from 'history';
13+
import { isString } from 'lodash';
1514

1615
import styles from '../styles/common.css';
17-
import { EXPERIMENTS, SCREENS } from '../../constants/constants';
18-
import {
19-
ExperimentObject,
20-
ExperimentParameters,
21-
} from '../../constants/interfaces';
16+
import { SCREENS } from '../../constants/constants';
17+
import { ExperimentParameters } from '../../constants/interfaces';
2218
import { DesignProps } from './index';
2319
import SecondaryNavComponent from '../SecondaryNavComponent';
2420
import PreviewExperimentComponent from '../PreviewExperimentComponent';
25-
import StimuliDesignColumn from './StimuliDesignColumn';
2621
import { ParamSlider } from './ParamSlider';
2722
import PreviewButton from '../PreviewButtonComponent';
2823
import researchQuestionImage from '../../assets/common/ResearchQuestion2.png';
2924
import methodsImage from '../../assets/common/Methods2.png';
3025
import hypothesisImage from '../../assets/common/Hypothesis2.png';
31-
import { readImages } from '../../utils/filesystem/storage';
32-
import { StimuliRow } from './StimuliRow';
33-
import { ExperimentActions } from '../../actions/experimentActions';
3426

3527
const CUSTOM_STEPS = {
3628
OVERVIEW: 'OVERVIEW',
@@ -112,6 +104,7 @@ export default class CustomDesign extends Component<DesignProps, State> {
112104
}
113105

114106
handleSetText(text: string, section: 'hypothesis' | 'methods' | 'question') {
107+
// @ts-expect-error
115108
this.setState((prevState) => ({
116109
params: {
117110
...prevState.params,
@@ -152,7 +145,7 @@ export default class CustomDesign extends Component<DesignProps, State> {
152145
autoHeight
153146
style={{ minHeight: 100, maxHeight: 400 }}
154147
label={FIELDS.QUESTION}
155-
value={this.state.params.description.question}
148+
value={this.state.params.description?.question}
156149
placeholder="Explain your research question here."
157150
onChange={(event, data) => {
158151
if (!isString(data.value)) {
@@ -176,7 +169,7 @@ export default class CustomDesign extends Component<DesignProps, State> {
176169
autoHeight
177170
style={{ minHeight: 100, maxHeight: 400 }}
178171
label={FIELDS.HYPOTHESIS}
179-
value={this.state.params.description.hypothesis}
172+
value={this.state.params.description?.hypothesis}
180173
placeholder="Describe your hypothesis here."
181174
onChange={(event, data) => {
182175
if (!isString(data.value)) {
@@ -200,7 +193,7 @@ export default class CustomDesign extends Component<DesignProps, State> {
200193
autoHeight
201194
style={{ minHeight: 100, maxHeight: 400 }}
202195
label={FIELDS.METHODS}
203-
value={this.state.params.description.methods}
196+
value={this.state.params.description?.methods}
204197
placeholder="Explain how you will design your experiment to answer the question here."
205198
onChange={(event, data) => {
206199
if (!isString(data.value)) {

app/components/ExperimentWindow.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as lab from 'lab.js/dist/lab.dev';
55
import {
66
ExperimentObject,
77
ExperimentParameters,
8+
Stimulus,
89
} from '../constants/interfaces';
910

1011
export interface ExperimentWindowProps {
@@ -32,9 +33,17 @@ export const ExperimentWindow: React.FC<ExperimentWindowProps> = ({
3233
const experimentToRun = lab.util.fromObject(experimentClone, lab);
3334

3435
experimentToRun.parameters.title = title;
35-
experimentToRun.options.media.images = params.stimuli.map((stimulus) =>
36-
path.join(stimulus.dir, stimulus.filename)
37-
);
36+
if (params.stimuli) {
37+
experimentToRun.options.media.images = params.stimuli?.reduce<string[]>(
38+
(images, stimulus) => {
39+
if (stimulus.dir && stimulus.filename) {
40+
return [...images, path.join(stimulus.dir, stimulus.filename)];
41+
}
42+
return images;
43+
},
44+
[]
45+
);
46+
}
3847

3948
experimentToRun.on('end', () => {
4049
const csv = experimentToRun.options.datastore.exportCsv();

app/components/HomeComponent/index.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ export default class Home extends Component<Props, State> {
302302
/>
303303
</Grid.Column>
304304

305-
{/* <Grid.Column>
305+
<Grid.Column>
306306
<ExperimentCard
307307
onClick={() => this.handleNewExperiment(EXPERIMENTS.STROOP)}
308308
icon={stroopIcon}
@@ -313,7 +313,6 @@ export default class Home extends Component<Props, State> {
313313
/>
314314
</Grid.Column>
315315
</Grid.Row>
316-
317316
<Grid.Row>
318317
<Grid.Column>
319318
<ExperimentCard
@@ -334,9 +333,8 @@ export default class Home extends Component<Props, State> {
334333
messy room.`}
335334
/>
336335
</Grid.Column>
337-
</Grid.Row> */}
338-
339-
{/* <Grid.Row> */}
336+
</Grid.Row>
337+
{/* <Grid.Row>
340338
<Grid.Column>
341339
<ExperimentCard
342340
onClick={() => this.handleNewExperiment(EXPERIMENTS.CUSTOM)}
@@ -347,7 +345,7 @@ export default class Home extends Component<Props, State> {
347345
</Grid.Column>
348346
349347
<Grid.Column />
350-
</Grid.Row>
348+
</Grid.Row> */}
351349
</Grid>
352350
);
353351
case HOME_STEPS.EXPLORE:

app/constants/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
export enum EXPERIMENTS {
22
NONE = 'NONE',
3-
P300 = 'Visual Oddball',
43
N170 = 'Faces and Houses',
5-
SSVEP = 'Steady-state Visual Evoked Potential',
64
STROOP = 'Stroop Task',
75
MULTI = 'Multi-tasking',
86
SEARCH = 'Visual Search',
97
CUSTOM = 'Custom',
8+
// P300 = 'Visual Oddball',
9+
// SSVEP = 'Steady-state Visual Evoked Potential',
1010
}
1111

1212
export const SCREENS = {

app/constants/interfaces.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,31 @@ export interface WorkSpaceInfo {
2020

2121
// All mutable aspects of an experiment that can be updated by the DesignComponent
2222
export type ExperimentParameters = {
23-
trialDuration: number;
24-
nbTrials: number;
23+
// TODO: consider refactoring to expose lab.js sample.mode
24+
description?: ExperimentDescription;
25+
intro: string;
2526
iti: number;
2627
jitter: number;
27-
sampleType: string;
28-
intro: string;
29-
showProgressBar: boolean;
30-
stimuli: Stimulus[];
3128
nbPracticeTrials?: number;
32-
// 'random' | 'sequential';
33-
// TODO: consider refactoring to expose lab.js sample.mode
34-
randomize?: string;
35-
selfPaced?: boolean;
29+
nbTrials: number;
3630
presentationTime?: number;
31+
randomize?: 'random' | 'sequential';
32+
sampleType: string;
33+
selfPaced?: boolean;
34+
showProgressBar: boolean;
35+
stimuli?: Stimulus[];
3736
taskHelp?: string;
38-
description: ExperimentDescription;
37+
trialDuration: number;
3938
};
4039

4140
export interface Stimulus {
4241
condition?: string;
4342
response?: string;
4443
phase?: string;
4544
type: EVENTS;
46-
dir: string;
45+
dir?: string;
4746
title: string;
48-
filename: string;
47+
filename?: string;
4948
}
5049

5150
interface ExperimentDescription {

app/epics/jupyterEpics.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ const loadEpochsEpic: Epic<JupyterActionType, JupyterActionType, RootState> = (
153153
action$,
154154
state$
155155
) =>
156-
// @ts-ignore
156+
// @ts-expect-error
157157
action$.pipe(
158158
filter(isActionOf(JupyterActions.LoadEpochs)),
159159
pluck('payload'),
@@ -166,18 +166,22 @@ const loadEpochsEpic: Epic<JupyterActionType, JupyterActionType, RootState> = (
166166
awaitOkMessage(action$),
167167
execute(filterIIR(1, 30), state$),
168168
awaitOkMessage(action$),
169-
map(() =>
170-
epochEvents(
171-
{
172-
[state$.value.experiment.params!.stimulus1!.title]: EVENTS.STIMULUS_1,
173-
[state$.value.experiment.params!.stimulus2!.title]: EVENTS.STIMULUS_2,
174-
[state$.value.experiment.params!.stimulus3!.title]: EVENTS.STIMULUS_3,
175-
[state$.value.experiment.params!.stimulus4!.title]: EVENTS.STIMULUS_4,
176-
},
169+
map(() => {
170+
if (!state$.value.experiment.params?.stimuli) {
171+
return {};
172+
}
173+
174+
return epochEvents(
175+
Object.fromEntries(
176+
state$.value.experiment.params?.stimuli.map((stimulus, i) => [
177+
stimulus.title,
178+
i,
179+
])
180+
),
177181
-0.1,
178182
0.8
179-
)
180-
),
183+
);
184+
}),
181185
tap((e) => {
182186
console.log('e', e);
183187
}),

app/experiments/faces_houses/experiment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* eslint-disable */
1+
/* eslint-disable no-template-curly-in-string */
22

33
import {
44
initPracticeLoopWithStimuli,

app/experiments/faces_houses/params.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ const stimuli = Array.from({ length: 30 }, (_, i) => `Face${i + 1}`)
2626
type: s.startsWith('Face') ? EVENTS.STIMULUS_1 : EVENTS.STIMULUS_2,
2727
}));
2828

29+
/**
30+
* NOTE: this params object may contain additional parameters in use by the experiment that are not
31+
* explicitly defined in the ExperimentParameters interface, which explicitly defines parameters that can be tweaked
32+
* in the current custom experiment UI.
33+
* This inconsistency will likely be able to be fixed when updating to a lab.js builder-based experiment design flow
34+
* */
2935
export const params = {
3036
imageHeight: '500px',
3137
randomize: 'random',
@@ -62,4 +68,4 @@ export const params = {
6268
// response: '9',
6369
// },
6470
stimuli,
65-
};
71+
} as const;

0 commit comments

Comments
 (0)