An easy-to-use platform for EEG experimentation in the classroom
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

new faces/houses images

authored by

Yury-Shevchenko and committed by
Teon L Brooks
047cebc1 0e22195b

+596 -160
app/assets/face_house/faces/Annie_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Blake_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Don_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Estelle_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face1.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face10.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face11.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face12.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face13.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face14.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face15.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face16.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face17.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face18.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face19.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face2.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face20.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face21.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face22.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face23.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face24.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face25.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face26.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face27.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face28.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face29.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face30.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face4.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face5.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face6.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face7.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face8.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Face9.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Frank_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Janie_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Joan_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Jodi_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Joe_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Tim_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Tom_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/faces/Wallace_3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House1.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House10.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House11.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House12.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House13.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House14.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House15.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House16.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House17.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House18.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House19.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House2.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House20.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House21.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House22.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House23.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House24.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House25.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House26.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House27.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House28.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House29.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House30.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House4.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House5.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House6.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House7.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House8.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/House9.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house1.3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house10.4.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house11.2.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house12.1.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house2.2.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house3.1.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house4.3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house5.2.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house6.3.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house7.1.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house8.4.jpg

This is a binary file and will not be displayed.

app/assets/face_house/houses/house9.2.jpg

This is a binary file and will not be displayed.

-1
app/components/AnalyzeComponent.js
··· 305 305 } else { 306 306 colors = ['red', 'green', 'teal', 'orange']; 307 307 } 308 - console.log('numberConditions', numberConditions); 309 308 return ( 310 309 <div> 311 310 {this.props.epochsInfo
+1
app/components/DesignComponent/CustomDesignComponent.js
··· 237 237 Select the folder with images for each condition and choose the correct response. 238 238 You can upload image files with the following extensions: ".png", ".jpg", ".jpeg". 239 239 Make sure when you preview your experiment that the resolution is high enough. 240 + You can resize or compress your images in an image editing program or on one of the websites online. 240 241 </p> 241 242 </Segment> 242 243
+2 -2
app/components/DesignComponent/index.js
··· 25 25 // conditions images 26 26 import multiConditionShape from '../../assets/multi/multiConditionShape.png'; 27 27 import multiConditionDots from '../../assets/multi/multiConditionDots.png'; 28 - import conditionFace from '../../assets/face_house/faces/Annie_3.jpg'; 29 - import conditionHouse from '../../assets/face_house/houses/house1.3.jpg'; 28 + import conditionFace from '../../assets/face_house/faces/Face1.jpg'; 29 + import conditionHouse from '../../assets/face_house/houses/House1.jpg'; 30 30 import conditionOrangeT from '../../assets/search/conditionOrangeT.png'; 31 31 import conditionNoOrangeT from '../../assets/search/conditionNoOrangeT.png'; 32 32 import conditionCongruent from '../../assets/stroop/match_g.png';
+2 -1
app/components/HomeComponent/index.js
··· 134 134 this.props.experimentActions.createNewWorkspace({ 135 135 title, 136 136 type: EXPERIMENTS.CUSTOM, 137 + paradigm: EXPERIMENTS.CUSTOM, 137 138 }); 138 139 this.props.history.push(SCREENS.DESIGN.route); 139 140 } ··· 380 381 </Header> 381 382 <div className={styles.experimentCardDescription}> 382 383 <p> 383 - Design your own EEG experiment! 384 + Design your own experiment! 384 385 </p> 385 386 </div> 386 387 </Grid.Column>
+15 -6
app/components/InputCollect.js
··· 16 16 group: string; 17 17 session: number; 18 18 isError: boolean; 19 + isSubjectError: boolean; 20 + isSessionError: boolean; 19 21 } 20 22 21 23 export default class InputCollect extends Component<Props, State> { ··· 33 35 group: this.props.data && this.props.data.group, 34 36 session: this.props.data && this.props.data.session, 35 37 isError: false, 38 + isSubjectError: false, 39 + isSessionError: false, 36 40 }; 37 41 this.handleTextEntry = this.handleTextEntry.bind(this); 38 42 this.handleClose = this.handleClose.bind(this); ··· 50 54 } 51 55 52 56 handleClose() { 53 - if (this.state.subject.length >= 1) { 57 + if (this.state.subject.length >= 1 && this.state.session) { 54 58 this.props.onClose( 55 59 this.sanitizeTextInput(this.state.subject), 56 60 this.sanitizeTextInput(this.state.group), 57 61 this.state.session 58 62 ); 59 63 } else { 60 - this.setState({ isError: true }); 64 + if(this.state.subject.length < 1) { 65 + this.setState({ isSubjectError: true }); 66 + } 67 + if(!this.state.session) { 68 + this.setState({ isSessionError: true }); 69 + } 61 70 } 62 71 } 63 72 ··· 85 94 <Input 86 95 focus 87 96 fluid 88 - error={this.state.isError} 97 + error={this.state.isSubjectError} 89 98 onChange={(object, data) => this.handleTextEntry(object, data, 'subject')} 90 99 onKeyDown={this.handleEnterSubmit} 91 100 value={this.state.subject} ··· 93 102 /> 94 103 </Modal.Content> 95 104 <Modal.Content> 96 - Enter group name 105 + Enter group name (optional) 97 106 <Input 98 107 focus 99 108 fluid 100 - error={this.state.isError} 101 109 onChange={(object, data) => this.handleTextEntry(object, data, 'group')} 102 110 onKeyDown={this.handleEnterSubmit} 103 111 value={this.state.group} ··· 108 116 <Input 109 117 focus 110 118 fluid 111 - error={this.state.isError} 119 + error={this.state.isSessionError} 112 120 onChange={(object, data) => this.handleTextEntry(object, data, 'session')} 113 121 onKeyDown={this.handleEnterSubmit} 114 122 value={this.state.session} 123 + type="number" 115 124 /> 116 125 </Modal.Content> 117 126 <Modal.Actions>
+37 -22
app/components/TopNavComponent/index.js
··· 4 4 import { EXPERIMENTS, SCREENS } from '../../constants/constants'; 5 5 import styles from '../styles/topnavbar.css'; 6 6 import PrimaryNavSegment from './PrimaryNavSegment'; 7 - import { openWorkspaceDir } from '../../utils/filesystem/storage'; 7 + import { readAndParseState, readWorkspaces } from '../../utils/filesystem/storage'; 8 8 import BrainwavesIcon from '../../assets/common/Brainwaves_Icon_big.png'; 9 + import { isNil } from 'lodash'; 9 10 10 11 interface Props { 11 12 title: ?string; ··· 15 16 type: EXPERIMENTS; 16 17 } 17 18 19 + interface State { 20 + recentWorkspaces: Array<string>; 21 + } 22 + 18 23 export default class TopNavComponent extends Component<Props> { 19 24 props: Props; 20 25 26 + state = { 27 + recentWorkspaces: [], 28 + }; 29 + 21 30 getStyleForScreen(navSegmentScreen: SCREENS) { 22 31 if (navSegmentScreen.route === this.props.location.pathname) { 23 32 return styles.activeNavColumn; ··· 34 43 return styles.initialNavColumn; 35 44 } 36 45 46 + handleLoadRecentWorkspace(dir: string) { 47 + const recentWorkspaceState = readAndParseState(dir); 48 + if (!isNil(recentWorkspaceState)) { 49 + this.props.experimentActions.setState(recentWorkspaceState); 50 + } 51 + } 52 + 53 + updateWorkspaces = () => { 54 + this.setState({ recentWorkspaces: readWorkspaces() }); 55 + }; 56 + 37 57 render() { 38 58 if ( 39 59 this.props.location.pathname === SCREENS.HOME.route || ··· 44 64 return null; 45 65 } 46 66 return ( 47 - <Grid className={styles.navContainer} verticalAlign='middle' columns='16'> 48 - <Grid.Column width={1} className={styles.experimentTitleGridColumn}> 49 - <Segment basic> 67 + <Grid className={styles.navContainer} verticalAlign='middle'> 68 + <Grid.Column className={styles.experimentTitleGridColumn}> 69 + <Segment basic className={styles.homeButton}> 50 70 <NavLink to={SCREENS.HOME.route}> 51 71 <Image centered className={styles.exitWorkspaceBtn} src={BrainwavesIcon} /> 72 + Home 52 73 </NavLink> 53 74 </Segment> 54 75 </Grid.Column> 55 76 56 - <Grid.Column width={1} className={styles.experimentTitleGridColumn}> 57 - <Segment basic> 58 - <NavLink to={SCREENS.HOME.route}>Home</NavLink> 59 - </Segment> 60 - </Grid.Column> 61 - 62 - <Grid.Column width={2} className={styles.experimentTitleGridColumn}> 77 + <Grid.Column width={3} className={styles.experimentTitleGridColumn}> 63 78 <Segment basic> 64 79 <Dropdown 65 80 text={this.props.title ? this.props.title : 'Untitled'} 66 81 direction='right' 67 - simple 82 + onClick={() => { 83 + this.updateWorkspaces(); 84 + }} 68 85 > 69 86 <Dropdown.Menu> 70 - <Dropdown.Item> 71 - <NavLink to={SCREENS.BANK.route}> 72 - <p>Experiment Bank</p> 73 - </NavLink> 74 - </Dropdown.Item> 75 - <Dropdown.Item> 76 - <NavLink to={SCREENS.HOME.route}> 77 - <p>My Experiments</p> 78 - </NavLink> 79 - </Dropdown.Item> 87 + {this.state.recentWorkspaces.map((workspace) => ( 88 + <Dropdown.Item 89 + key={workspace} 90 + onClick={() => this.handleLoadRecentWorkspace(workspace)} 91 + > 92 + <p>{workspace}</p> 93 + </Dropdown.Item> 94 + ))} 80 95 </Dropdown.Menu> 81 96 </Dropdown> 82 97 </Segment>
+8 -2
app/components/styles/topnavbar.css
··· 93 93 width: inherit; 94 94 } 95 95 96 + .homeButton { 97 + display: grid; 98 + justify-content: center; 99 + margin-left: 20px !important; 100 + } 101 + 96 102 .exitWorkspaceBtn{ 97 103 font-family: Gothic A1 !important; 98 104 font-style: normal !important; ··· 101 107 line-height: 30px !important; 102 108 color: #007C70 !important; 103 109 border: none !important; 104 - height: 35px; 105 - min-width: 35px; 110 + height: 30px; 111 + min-width: 30px; 106 112 }
+13 -1
app/utils/labjs/index.js
··· 6 6 import stroop from './scripts/stroop'; 7 7 import multitasking from './scripts/multitasking'; 8 8 import faceshouses from './scripts/faceshouses'; 9 + import custom from './scripts/custom'; 9 10 10 11 class ExperimentWindow extends Component { 11 12 constructor(props) { ··· 28 29 this.study = lab.util.fromObject(clonedeep(stroop), lab); 29 30 break; 30 31 case 'Faces and Houses': 31 - default: 32 32 faceshouses.parameters = props.settings.params; 33 33 faceshouses.parameters.title = props.settings.title; 34 34 faceshouses.files = props.settings.params.stimuli ··· 40 40 return obj; 41 41 }, {}); 42 42 this.study = lab.util.fromObject(clonedeep(faceshouses), lab); 43 + break; 44 + case 'Custom': 45 + default: 46 + custom.parameters = props.settings.params; 47 + custom.parameters.title = props.settings.title; 48 + custom.files = props.settings.params.stimuli.map(image => ( 49 + { [`${image.dir}/${image.filename}`] : `${image.dir}/${image.filename}`} 50 + )).reduce((obj, item) => { 51 + obj[Object.keys(item)[0]] = Object.values(item)[0] 52 + return obj; 53 + }, {}); 54 + this.study = lab.util.fromObject(clonedeep(custom), lab); 43 55 break; 44 56 } 45 57 this.study.run();
+20 -94
app/utils/labjs/protocols/faceshouses.js
··· 8 8 const housesDir = path.join(rootFolder, 'assets', 'face_house', 'houses'); 9 9 const fixation = path.join(rootFolder, 'assets', 'common', 'fixationcross.png'); 10 10 11 + const stimuli = ['Face1', 'Face2', 'Face3', 'Face4', 'Face5', 'Face6', 'Face7', 'Face8', 'Face9', 'Face10', 12 + 'Face11', 'Face12', 'Face13', 'Face14', 'Face15', 'Face16', 'Face17', 'Face18', 'Face19', 'Face20', 13 + 'Face21', 'Face22', 'Face23', 'Face24', 'Face25', 'Face26', 'Face27', 'Face28', 'Face29', 'Face30', 14 + 'House1', 'House2', 'House3', 'House4', 'House5', 'House6', 'House7', 'House8', 'House9', 'House10', 15 + 'House11', 'House12', 'House13', 'House14', 'House15', 'House16', 'House17', 'House18', 'House19', 'House20', 16 + 'House21', 'House22', 'House23', 'House24', 'House25', 'House26', 'House27', 'House28', 'House29', 'House30'].map(s => ({ 17 + condition: s.startsWith('Face') ? 'Face' : 'House', 18 + dir: s.startsWith('Face') ? facesDir : housesDir, 19 + filename: `${s}.jpg`, 20 + name: s, 21 + response: s.startsWith('Face') ? '1' : '9', 22 + phase: 'main', 23 + type: s.startsWith('Face') ? EVENTS.STIMULUS_1 : EVENTS.STIMULUS_2, 24 + })) 25 + 26 + // phase: ['Face1', 'House1'].includes(s) ? 'practice' : 'main', 27 + 11 28 export const buildN170Timeline = () => ({ 12 29 overview_title: `Faces Houses Experiment`, 13 30 overview: `When you scroll through your social media feed, you may find that you are more likely to pause when a picture ··· 44 61 randomize: 'random', 45 62 includePractice: true, 46 63 trialDuration: 1000, 47 - nbTrials: 8, 48 - nbPracticeTrials: 2, 64 + nbTrials: 120, 65 + nbPracticeTrials: 6, 49 66 iti: 500, 50 67 presentationTime: 1000, 51 68 selfPaced: true, ··· 79 96 type: 4, 80 97 response: '', 81 98 }, 82 - stimuli: [ 83 - { 84 - condition: 'Face', 85 - dir: facesDir, 86 - filename: 'Annie_3.jpg', 87 - name: 'Annie_3', 88 - response: '1', 89 - phase: 'practice', 90 - type: EVENTS.STIMULUS_1, 91 - }, 92 - { 93 - condition: 'Face', 94 - dir: facesDir, 95 - filename: 'Blake_3.jpg', 96 - name: 'Blake_3', 97 - response: '1', 98 - phase: 'main', 99 - type: EVENTS.STIMULUS_1, 100 - }, 101 - { 102 - condition: 'Face', 103 - dir: facesDir, 104 - filename: 'Don_3.jpg', 105 - name: 'Don_3', 106 - response: '1', 107 - phase: 'main', 108 - type: EVENTS.STIMULUS_1, 109 - }, 110 - { 111 - condition: 'Face', 112 - dir: facesDir, 113 - filename: 'Estelle_3.jpg', 114 - name: 'Estelle_3', 115 - response: '1', 116 - phase: 'main', 117 - type: EVENTS.STIMULUS_1, 118 - }, 119 - { 120 - condition: 'Face', 121 - dir: facesDir, 122 - filename: 'Frank_3.jpg', 123 - name: 'Frank_3', 124 - response: '1', 125 - phase: 'main', 126 - type: EVENTS.STIMULUS_1, 127 - }, 128 - { 129 - condition: 'House', 130 - dir: housesDir, 131 - filename: 'house1.3.jpg', 132 - name: 'house1.3', 133 - response: '9', 134 - phase: 'practice', 135 - type: EVENTS.STIMULUS_2, 136 - }, 137 - { 138 - condition: 'House', 139 - dir: housesDir, 140 - filename: 'house2.2.jpg', 141 - name: 'house2.2', 142 - response: '9', 143 - phase: 'main', 144 - type: EVENTS.STIMULUS_2, 145 - }, 146 - { 147 - condition: 'House', 148 - dir: housesDir, 149 - filename: 'house3.1.jpg', 150 - name: 'house3.1', 151 - response: '9', 152 - phase: 'main', 153 - type: EVENTS.STIMULUS_2, 154 - }, 155 - { 156 - condition: 'House', 157 - dir: housesDir, 158 - filename: 'house4.3.jpg', 159 - name: 'house4.3', 160 - response: '9', 161 - phase: 'main', 162 - type: EVENTS.STIMULUS_2, 163 - }, 164 - { 165 - condition: 'House', 166 - dir: housesDir, 167 - filename: 'house5.2.jpg', 168 - name: 'house5.3', 169 - response: '9', 170 - phase: 'main', 171 - type: EVENTS.STIMULUS_2, 172 - }, 173 - ], 99 + stimuli, 174 100 }, 175 101 mainTimeline: ['intro', 'faceHouseTimeline', 'end'], // array of trial and timeline ids 176 102 trials: {
+479
app/utils/labjs/scripts/custom.js
··· 1 + // Define study 2 + const studyObject = { 3 + "title": "root", 4 + "type": "lab.flow.Sequence", 5 + "parameters": {}, 6 + "plugins": [], 7 + "metadata": {}, 8 + "files": {}, 9 + "responses": {}, 10 + "content": [ 11 + { 12 + "type": "lab.flow.Sequence", 13 + "files": {}, 14 + "parameters": {}, 15 + "responses": {}, 16 + "messageHandlers": {}, 17 + "title": "The face-house task", 18 + "content": [ 19 + { 20 + "type": "lab.html.Screen", 21 + "files": {}, 22 + "parameters": {}, 23 + "responses": { 24 + "keypress(Space)": "continue", 25 + "keypress(q)": "skipPractice" 26 + }, 27 + "messageHandlers": {}, 28 + "title": "Instruction", 29 + "content": "\u003Cheader class=\"content-vertical-center content-horizontal-center\"\u003E\n \u003Ch1\u003E${this.parameters.title || \"The face-house task\"}\u003C\u002Fh1\u003E\n\u003C\u002Fheader\u003E\n\n\u003Cmain\u003E\n\n \u003Cp\u003E\n ${this.parameters.intro}\n \u003C\u002Fp\u003E\n \n\u003C\u002Fmain\u003E\n\n\u003Cfooter class=\"content-vertical-center content-horizontal-center\"\u003E\n \n\u003C\u002Ffooter\u003E" 30 + }, 31 + { 32 + "type": "lab.flow.Loop", 33 + "files": {}, 34 + "parameters": {}, 35 + "templateParameters": [], 36 + "sample": { 37 + "mode": "draw-shuffle", 38 + "n": "" 39 + }, 40 + "responses": {}, 41 + "messageHandlers": { 42 + "before:prepare": function anonymous( 43 + ) { 44 + let initParameters = [...this.parameters.stimuli] || []; 45 + initParameters = initParameters.filter(t => t.phase === 'practice') || []; 46 + let numberTrials = this.parameters.nbPracticeTrials; 47 + if(initParameters.length === 0){ 48 + numberTrials = 0; 49 + } 50 + const randomize = this.parameters.randomize; 51 + const trialsLength = initParameters.length; 52 + if(numberTrials > trialsLength){ 53 + const append = [...initParameters] 54 + const multiply = Math.ceil(numberTrials / trialsLength); 55 + for (let i = 0; i < multiply; i++){ 56 + initParameters = initParameters.concat(append) 57 + } 58 + } 59 + 60 + function shuffle(a) { 61 + let j, x, i 62 + for (i = a.length - 1; i > 0; i--) { 63 + j = Math.floor(Math.random() * (i + 1)) 64 + x = a[i] 65 + a[i] = a[j] 66 + a[j] = x 67 + } 68 + return a 69 + } 70 + 71 + if(randomize === 'random'){ 72 + shuffle(initParameters); 73 + } 74 + 75 + const trialConstructor = ( file ) => ({ 76 + 'condition': file.condition, 77 + 'image': `${file.dir}/${file.filename}`, 78 + 'correctResponse': file.response, 79 + 'phase': 'practice', 80 + 'name': file.name, 81 + 'type': file.type, 82 + }) 83 + 84 + // balance design across conditions 85 + const conditions = Array.from(new Set(initParameters.map(p => p.condition))); 86 + const conditionsParameters = {}; 87 + for (const c of conditions) { 88 + conditionsParameters[c] = initParameters.filter(p => p.condition == c) 89 + } 90 + const numberConditionsTrials = Math.ceil(numberTrials / conditions.length); 91 + let balancedParameters = []; 92 + for (let i = 0; i < numberConditionsTrials; i++){ 93 + for (const c of conditions) { 94 + balancedParameters = balancedParameters.concat(conditionsParameters[c][i % conditionsParameters[c].length]) 95 + } 96 + } 97 + initParameters = [...balancedParameters.slice(0, numberTrials)]; 98 + 99 + let practiceParameters = []; 100 + for (let i = 0; i < numberTrials; i++){ 101 + practiceParameters = practiceParameters.concat(trialConstructor(initParameters[i])) 102 + } 103 + 104 + // assign options values to parameters of this task 105 + this.options.templateParameters = practiceParameters; 106 + if(randomize === 'random'){ 107 + this.options.shuffle = true; 108 + } else { 109 + this.options.shuffle = false; 110 + } 111 + } 112 + }, 113 + "title": "Practice loop", 114 + "shuffleGroups": [], 115 + "template": { 116 + "type": "lab.flow.Sequence", 117 + "files": {}, 118 + "parameters": {}, 119 + "responses": {}, 120 + "messageHandlers": {}, 121 + "title": "Trial", 122 + "content": [ 123 + { 124 + "type": "lab.canvas.Screen", 125 + "content": [ 126 + { 127 + "type": "rect", 128 + "left": 0, 129 + "top": 0, 130 + "angle": 0, 131 + "width": 10, 132 + "height": "50", 133 + "stroke": null, 134 + "strokeWidth": 1, 135 + "fill": "black" 136 + }, 137 + { 138 + "type": "rect", 139 + "left": 0, 140 + "top": 0, 141 + "angle": 90, 142 + "width": 10, 143 + "height": "50", 144 + "stroke": null, 145 + "strokeWidth": 1, 146 + "fill": "black" 147 + } 148 + ], 149 + "files": {}, 150 + "parameters": {}, 151 + "responses": {}, 152 + "messageHandlers": {}, 153 + "viewport": [ 154 + 800, 155 + 600 156 + ], 157 + "title": "Fixation cross", 158 + "timeout": "${parameters.iti}" 159 + }, 160 + { 161 + "type": "lab.html.Screen", 162 + "files": {}, 163 + "responses": {}, 164 + "parameters": {}, 165 + "messageHandlers": { 166 + "before:prepare": function anonymous( 167 + ) { 168 + // This code registers an event listener for this screen. 169 + // We have a timeout for this screen, but we also want to record responses. 170 + // On a keydown event, we record the key and the time of response. 171 + // We also record whether the response was correct (by comparing the pressed key with the correct response which is defined inside the Experiment loop). 172 + // "this" in the code means the lab.js experiment. 173 + const responses = [... new Set(this.parameters.stimuli.map( e => (e.response)))]; 174 + this.data.trial_number = 1 + parseInt(this.options.id.split('_')[this.options.id.split('_').length-2]); 175 + this.data.response_given = 'no'; 176 + 177 + this.options.events = { 178 + 'keydown': (event) => { 179 + if(responses.includes(event.key)){ 180 + this.data.reaction_time = this.timer; 181 + if(this.parameters.phase === 'task') this.data.response_given = 'yes'; 182 + this.data.response = event.key; 183 + if(this.data.response == this.parameters.correctResponse){ 184 + this.data.correct_response = true; 185 + } else { 186 + this.data.correct_response = false; 187 + } 188 + this.end(); 189 + } 190 + } 191 + } 192 + }, 193 + "run": function anonymous( 194 + ) { 195 + this.parameters.callbackForEEG(this.parameters.type); 196 + } 197 + }, 198 + "title": "Stimulus", 199 + "timeout": "${parameters.selfPaced ? '3600000' : parameters.presentationTime}", 200 + "content": "\u003Cmain class=\"content-horizontal-center content-vertical-center\"\u003E\n \u003Cdiv\u003E\n \u003Cimg src=${ this.files[this.parameters.image] } height=${ this.parameters.imageHeight } \u002F\u003E\n \u003C\u002Fdiv\u003E\n\u003C\u002Fmain\u003E\n\n\u003Cfooter class=\"content-vertical-center content-horizontal-center\"\u003E\n \u003Cp\u003E\n ${this.parameters.taskHelp} \n \u003C\u002Fp\u003E\n\u003C\u002Ffooter\u003E" 201 + }, 202 + { 203 + "type": "lab.canvas.Screen", 204 + "content": [ 205 + { 206 + "type": "i-text", 207 + "left": 0, 208 + "top": 0, 209 + "angle": 0, 210 + "width": 895.3, 211 + "height": 36.16, 212 + "stroke": null, 213 + "strokeWidth": 1, 214 + "fill": "${ state.correct_response ? 'green' : 'red' }", 215 + "text": "${ state.correct_response ? 'Well done!' : 'Please respond accurately' }", 216 + "fontStyle": "normal", 217 + "fontWeight": "bold", 218 + "fontSize": "52", 219 + "fontFamily": "sans-serif", 220 + "lineHeight": 1.16, 221 + "textAlign": "center" 222 + } 223 + ], 224 + "files": {}, 225 + "parameters": {}, 226 + "responses": {}, 227 + "messageHandlers": { 228 + "end": function anonymous() { 229 + this.data.correct_response = false; 230 + }}, 231 + "viewport": [ 232 + 800, 233 + 600 234 + ], 235 + "title": "Feedback", 236 + "tardy": true, 237 + "timeout": "1000", 238 + "skip": "${ parameters.phase === 'task' }" 239 + } 240 + ] 241 + } 242 + }, 243 + { 244 + "type": "lab.html.Screen", 245 + "files": {}, 246 + "parameters": {}, 247 + "responses": { 248 + "keypress(Space)": "continue" 249 + }, 250 + "messageHandlers": {}, 251 + "title": "Main task", 252 + "content": "\u003Cheader class=\"content-vertical-center content-horizontal-center\"\u003E\n \u003Ch1\u003EReady for the real data collection?\u003C\u002Fh1\u003E\n\u003C\u002Fheader\u003E\n\u003Cmain\u003E\n\n \u003Cp\u003E\n Press the the space bar to start the main task.\n \u003C\u002Fp\u003E\n\n\u003C\u002Fmain\u003E\n\u003Cfooter class=\"content-vertical-center content-horizontal-center\"\u003E\n \n\u003C\u002Ffooter\u003E" 253 + }, 254 + { 255 + "type": "lab.flow.Loop", 256 + "files": {}, 257 + "parameters": {}, 258 + "templateParameters": [], 259 + "sample": { 260 + "mode": "draw-shuffle", 261 + "n": "" 262 + }, 263 + "responses": {}, 264 + "messageHandlers": { 265 + "before:prepare": function anonymous( 266 + ) { 267 + let initialParameters = [...this.parameters.stimuli] || []; 268 + initialParameters = initialParameters.filter(t => t.phase === 'main') || []; 269 + let numberTrials = this.parameters.nbTrials; 270 + if(initialParameters.length === 0){ 271 + numberTrials = 0; 272 + } 273 + const randomize = this.parameters.randomize; 274 + const trialsLength = initialParameters.length; 275 + if(numberTrials > trialsLength){ 276 + const append = [...initialParameters] 277 + const multiply = Math.ceil(numberTrials / trialsLength); 278 + for (let i = 0; i < multiply; i++){ 279 + initialParameters = initialParameters.concat(append) 280 + } 281 + } 282 + 283 + function shuffle(a) { 284 + let j, x, i 285 + for (i = a.length - 1; i > 0; i--) { 286 + j = Math.floor(Math.random() * (i + 1)) 287 + x = a[i] 288 + a[i] = a[j] 289 + a[j] = x 290 + } 291 + return a 292 + } 293 + 294 + if(randomize === 'random'){ 295 + shuffle(initialParameters); 296 + } 297 + 298 + const trialConstructor = ( file ) => ({ 299 + 'condition': file.condition, 300 + 'image': `${file.dir}/${file.filename}`, 301 + 'correctResponse': file.response, 302 + 'phase': 'task', 303 + 'name': file.name, 304 + 'type': file.type, 305 + }) 306 + // balance design across conditions 307 + const conditions = Array.from(new Set(initialParameters.map(p => p.condition))); 308 + const conditionsParameters = {}; 309 + for (const c of conditions) { 310 + conditionsParameters[c] = initialParameters.filter(p => p.condition == c) 311 + } 312 + const numberConditionsTrials = Math.ceil(numberTrials / conditions.length); 313 + let balancedParameters = []; 314 + for (let i = 0; i < numberConditionsTrials; i++){ 315 + for (const c of conditions) { 316 + balancedParameters = balancedParameters.concat(conditionsParameters[c][i % conditionsParameters[c].length]) 317 + } 318 + } 319 + initialParameters = [...balancedParameters.slice(0, numberTrials)]; 320 + 321 + let trialParameters = []; 322 + for (let i = 0; i < numberTrials; i++){ 323 + trialParameters = [...trialParameters.concat(trialConstructor(initialParameters[i]))] 324 + } 325 + // assign options values to parameters of this task 326 + this.options.templateParameters = trialParameters; 327 + if(randomize === 'random'){ 328 + this.options.shuffle = true; 329 + } else { 330 + this.options.shuffle = false; 331 + } 332 + } 333 + }, 334 + "title": "Experiment loop", 335 + "shuffleGroups": [], 336 + "template": { 337 + "type": "lab.flow.Sequence", 338 + "files": {}, 339 + "parameters": {}, 340 + "responses": {}, 341 + "messageHandlers": {}, 342 + "title": "Trial", 343 + "content": [ 344 + { 345 + "type": "lab.canvas.Screen", 346 + "content": [ 347 + { 348 + "type": "rect", 349 + "left": 0, 350 + "top": 0, 351 + "angle": 0, 352 + "width": 10, 353 + "height": "50", 354 + "stroke": null, 355 + "strokeWidth": 1, 356 + "fill": "black" 357 + }, 358 + { 359 + "type": "rect", 360 + "left": 0, 361 + "top": 0, 362 + "angle": 90, 363 + "width": 10, 364 + "height": "50", 365 + "stroke": null, 366 + "strokeWidth": 1, 367 + "fill": "black" 368 + } 369 + ], 370 + "files": {}, 371 + "parameters": {}, 372 + "responses": {}, 373 + "messageHandlers": {}, 374 + "viewport": [ 375 + 800, 376 + 600 377 + ], 378 + "title": "Fixation cross", 379 + "timeout": "${parameters.iti}" 380 + }, 381 + { 382 + "type": "lab.html.Screen", 383 + "files": {}, 384 + "responses": {}, 385 + "parameters": {}, 386 + "messageHandlers": { 387 + "before:prepare": function anonymous( 388 + ) { 389 + // This code registers an event listener for this screen. 390 + // We have a timeout for this screen, but we also want to record responses. 391 + // On a keydown event, we record the key and the time of response. 392 + // We also record whether the response was correct (by comparing the pressed key with the correct response which is defined inside the Experiment loop). 393 + // "this" in the code means the lab.js experiment. 394 + const responses = [... new Set(this.parameters.stimuli.map( e => (e.response)))]; 395 + this.data.trial_number = 1 + parseInt(this.options.id.split('_')[this.options.id.split('_').length-2]); 396 + this.data.response_given = 'no'; 397 + 398 + this.options.events = { 399 + 'keydown': (event) => { 400 + if(responses.includes(event.key)){ 401 + this.data.reaction_time = this.timer; 402 + if(this.parameters.phase === 'task') this.data.response_given = 'yes'; 403 + this.data.response = event.key; 404 + if(this.data.response == this.parameters.correctResponse){ 405 + this.data.correct_response = true; 406 + } else { 407 + this.data.correct_response = false; 408 + } 409 + this.end(); 410 + } 411 + } 412 + } 413 + }, 414 + "run": function anonymous( 415 + ) { 416 + this.parameters.callbackForEEG(this.parameters.type); 417 + } 418 + }, 419 + "title": "Stimulus", 420 + "timeout": "${parameters.selfPaced ? '3600000' : parameters.presentationTime}", 421 + "timeout": "${parameters.selfPaced ? '3600000' : parameters.presentationTime}", 422 + "content": "\u003Cmain class=\"content-horizontal-center content-vertical-center\"\u003E\n \u003Cdiv\u003E\n \u003Cimg src=${ this.files[this.parameters.image] } height=${ this.parameters.imageHeight } \u002F\u003E\n \u003C\u002Fdiv\u003E\n\u003C\u002Fmain\u003E\n\n\u003Cfooter class=\"content-vertical-center content-horizontal-center\"\u003E\n \u003Cp\u003E\n ${this.parameters.taskHelp} \n \u003C\u002Fp\u003E\n\u003C\u002Ffooter\u003E" 423 + }, 424 + { 425 + "type": "lab.canvas.Screen", 426 + "content": [ 427 + { 428 + "type": "i-text", 429 + "left": 0, 430 + "top": 0, 431 + "angle": 0, 432 + "width": 895.3, 433 + "height": 36.16, 434 + "stroke": null, 435 + "strokeWidth": 1, 436 + "fill": "${ state.correct_response ? 'green' : 'red' }", 437 + "text": "${ state.correct_response ? 'Well done!' : 'Please respond accurately' }", 438 + "fontStyle": "normal", 439 + "fontWeight": "bold", 440 + "fontSize": "52", 441 + "fontFamily": "sans-serif", 442 + "lineHeight": 1.16, 443 + "textAlign": "center" 444 + } 445 + ], 446 + "files": {}, 447 + "parameters": {}, 448 + "responses": {}, 449 + "messageHandlers": {}, 450 + "viewport": [ 451 + 800, 452 + 600 453 + ], 454 + "title": "Feedback", 455 + "tardy": true, 456 + "timeout": "1000", 457 + "skip": "${ parameters.phase === 'task' }" 458 + } 459 + ] 460 + } 461 + }, 462 + { 463 + "type": "lab.html.Screen", 464 + "files": {}, 465 + "parameters": {}, 466 + "responses": { 467 + "keypress(Space)": "end" 468 + }, 469 + "messageHandlers": {}, 470 + "title": "End", 471 + "content": "\u003Cheader class=\"content-vertical-center content-horizontal-center\"\u003E\n \n\u003C\u002Fheader\u003E\n\n\u003Cmain\u003E\n \u003Ch1\u003E\n Thank you!\n \u003C\u002Fh1\u003E\n \u003Ch1\u003E\n Press the space bar to finish the task.\n \u003C\u002Fh1\u003E\n\u003C\u002Fmain\u003E\n\n" 472 + } 473 + ] 474 + } 475 + ] 476 + } 477 + 478 + // export 479 + export default studyObject;
+16 -10
app/utils/labjs/scripts/faceshouses.js
··· 4 4 type: 'lab.flow.Sequence', 5 5 parameters: {}, 6 6 plugins: [], 7 - metadata: { 8 - title: 'The face-house task', 9 - description: 10 - "Faces contain a lot of information that is relevant to our survival. It's important to be able to quickly recognize people you can trust and read emotions in both strangers and people you know. ", 11 - repository: '', 12 - contributors: 'Yury Shevchenko \u003Cyury.shevchenko@uni-konstanz.de\u003E', 13 - }, 7 + metadata: {}, 14 8 files: {}, 15 9 responses: {}, 16 10 content: [ ··· 48 42 messageHandlers: { 49 43 'before:prepare': function anonymous() { 50 44 let initParameters = [...this.parameters.stimuli] || []; 51 - initParameters = initParameters.filter((t) => t.phase === 'practice') || []; 45 + // initParameters = initParameters.filter(t => t.phase === 'practice') || []; 52 46 let numberTrials = this.parameters.nbPracticeTrials; 53 47 if (initParameters.length === 0) { 54 48 numberTrials = 0; ··· 201 195 }, 202 196 }, 203 197 title: 'Stimulus', 198 + timeout: "${parameters.selfPaced ? '3600000' : parameters.presentationTime}", 204 199 content: 205 - '\u003Cmain class="content-horizontal-center content-vertical-center"\u003E\n \u003Cdiv\u003E\n \u003Cimg src=${ this.files[this.parameters.image] } height=${ this.parameters.imageHeight } \u002F\u003E\n \u003C\u002Fdiv\u003E\n\u003C\u002Fmain\u003E', 200 + '\u003Cmain class="content-horizontal-center content-vertical-center"\u003E\n \u003Cdiv\u003E\n \u003Cimg src=${ this.files[this.parameters.image] } height=${ this.parameters.imageHeight } \u002F\u003E\n \u003C\u002Fdiv\u003E\n\u003C\u002Fmain\u003E\n\n\u003Cfooter class="content-vertical-center content-horizontal-center"\u003E\n \u003Cp\u003E\n ${this.parameters.taskHelp} \n \u003C\u002Fp\u003E\n\u003C\u002Ffooter\u003E', 206 201 }, 207 202 { 208 203 type: 'lab.canvas.Screen', ··· 230 225 files: {}, 231 226 parameters: {}, 232 227 responses: {}, 228 + messageHandlers: { 229 + end: function anonymous() { 230 + this.data.correct_response = false; 231 + }, 232 + }, 233 + viewport: [800, 600], 234 + files: {}, 235 + parameters: {}, 236 + responses: {}, 233 237 messageHandlers: {}, 234 238 viewport: [800, 600], 235 239 title: 'Feedback', ··· 418 422 }, 419 423 }, 420 424 title: 'Stimulus', 425 + timeout: "${parameters.selfPaced ? '3600000' : parameters.presentationTime}", 426 + timeout: "${parameters.selfPaced ? '3600000' : parameters.presentationTime}", 421 427 content: 422 - '\u003Cmain class="content-horizontal-center content-vertical-center"\u003E\n \u003Cdiv\u003E\n \u003Cimg src=${ this.files[this.parameters.image] } height=${ this.parameters.imageHeight } \u002F\u003E\n \u003C\u002Fdiv\u003E\n\u003C\u002Fmain\u003E', 428 + '\u003Cmain class="content-horizontal-center content-vertical-center"\u003E\n \u003Cdiv\u003E\n \u003Cimg src=${ this.files[this.parameters.image] } height=${ this.parameters.imageHeight } \u002F\u003E\n \u003C\u002Fdiv\u003E\n\u003C\u002Fmain\u003E\n\n\u003Cfooter class="content-vertical-center content-horizontal-center"\u003E\n \u003Cp\u003E\n ${this.parameters.taskHelp} \n \u003C\u002Fp\u003E\n\u003C\u002Ffooter\u003E', 423 429 }, 424 430 { 425 431 type: 'lab.canvas.Screen',
+1 -6
app/utils/labjs/scripts/multitasking.js
··· 9 9 type: 'lab.flow.Sequence', 10 10 parameters: {}, 11 11 plugins: [], 12 - metadata: { 13 - title: 'Multitasking', 14 - description: '', 15 - repository: '', 16 - contributors: '', 17 - }, 12 + metadata: {}, 18 13 files: {}, 19 14 responses: {}, 20 15 content: [
+1 -8
app/utils/labjs/scripts/stroop.js
··· 4 4 title: 'root', 5 5 type: 'lab.flow.Sequence', 6 6 plugins: [], 7 - metadata: { 8 - title: 'Stroop task', 9 - description: 'An implementation of the classic paradigm introduced by Stroop (1935).', 10 - repository: 11 - 'https:\u002F\u002Fgithub.com\u002FFelixHenninger\u002Flab.js\u002Ftree\u002Fmaster\u002Ftasks', 12 - contributors: 13 - 'Felix Henninger \u003Cmailbox@felixhenninger.com\u003E (http:\u002F\u002Ffelixhenninger.com)', 14 - }, 7 + metadata: {}, 15 8 parameters: {}, 16 9 files: {}, 17 10 responses: {},
+1 -7
app/utils/labjs/scripts/visualsearch.js
··· 4 4 type: 'lab.flow.Sequence', 5 5 parameters: {}, 6 6 plugins: [], 7 - metadata: { 8 - title: 'Visual search', 9 - description: 10 - 'The visual search task is made according to specifications described here\nhttps:\u002F\u002Fwww.psytoolkit.org\u002Fexperiment-library\u002Fsearch.html', 11 - repository: '', 12 - contributors: 'Yury Shevchenko \u003Cyury.shevchenko@uni-konstanz.de\u003E', 13 - }, 7 + metadata: {}, 14 8 files: {}, 15 9 responses: {}, 16 10 content: [