@@ -13,24 +13,28 @@ import { TimerService } from 'src/app/services/timer.service';
1313import { AbstractBaseTaskComponent } from '../base-task' ;
1414import { TaskPlayerState } from '../task-player/task-player.component' ;
1515import { DataGenerationService } from 'src/app/services/data-generation/data-generation.service' ;
16- import { ImageService } from 'src/app/services/image.service ' ;
16+ import { AttentionCheckCache } from '../attention-check/attention-check.component ' ;
1717
1818interface FaceNameAssociationMetadata {
1919 componentName : ComponentName ;
2020 componentConfig : {
2121 isPractice : boolean ;
2222 phase : 'learning-phase' | 'test-phase' ;
2323 maxResponseTime : number ;
24- stimulusSet : number ;
2524 interTrialDelay : number ;
2625 durationStimulusPresented : number ;
26+ blockNum : number ;
2727 stimuliConfig : {
2828 type : StimuliProvidedType ;
2929 stimuli : FaceNameAssociationStimulus [ ] ;
3030 } ;
3131 } ;
3232}
3333
34+ export enum FaceNameAssociationCache {
35+ STIMULI = 'facenameassociation-stimuli' ,
36+ }
37+
3438@Component ( {
3539 selector : 'app-face-name-association' ,
3640 templateUrl : './face-name-association.component.html' ,
@@ -42,7 +46,7 @@ export class FaceNameAssociationComponent extends AbstractBaseTaskComponent {
4246 * This task involves two phases. In the first phase, the participant sees a bunch of images and associated names. This is the learning phase.
4347 * In the second phase, the participant is tested on the images. Half of them are correct, and are half of them are recombined.
4448 *
45- * The stimuli are hard coded .
49+ * The face images are taken from a set number of images, and names are automatically (and randomly) assigned depending on the counterbalance .
4650 */
4751
4852 // config variables
@@ -53,6 +57,8 @@ export class FaceNameAssociationComponent extends AbstractBaseTaskComponent {
5357 private interTrialDelay = 500 ;
5458 private durationStimulusPresented = 3000 ;
5559 private durationOfFeedback = 1000 ;
60+ private counterbalance : 1 | 2 ;
61+ private blockNum : number ;
5662
5763 // high level variables
5864 taskData : FaceNameAssociationTaskData [ ] ;
@@ -62,12 +68,13 @@ export class FaceNameAssociationComponent extends AbstractBaseTaskComponent {
6268 // local state variables
6369 trialNum = 0 ;
6470 currentName = '' ;
71+ stimulusShown = '' ;
6572 showStimulus = false ;
6673 allowResponse = false ;
67- stimulusShown : string | ArrayBuffer = null ;
6874 blobs : { [ key : string ] : Blob } = { } ;
6975 feedback : string = '' ;
7076 showFeedback : boolean = false ;
77+ imagePath : string = '' ;
7178
7279 YES = UserResponse . YES ;
7380 NO = UserResponse . NO ;
@@ -86,8 +93,7 @@ export class FaceNameAssociationComponent extends AbstractBaseTaskComponent {
8693 constructor (
8794 protected timerService : TimerService ,
8895 protected loaderService : LoaderService ,
89- private dataGenService : DataGenerationService ,
90- private imageService : ImageService
96+ private dataGenService : DataGenerationService
9197 ) {
9298 super ( loaderService ) ;
9399 }
@@ -107,44 +113,51 @@ export class FaceNameAssociationComponent extends AbstractBaseTaskComponent {
107113 'duration stimulus presented not defined'
108114 ) ;
109115
116+ this . config = config ;
117+
110118 this . phase = throwErrIfNotDefined ( metadata . componentConfig . phase , 'phase not defined' ) ;
111119 } catch ( error ) {
112120 throw new Error ( 'values not defined, cannot start study' ) ;
113121 }
114122 this . isPractice = metadata . componentConfig . isPractice ;
115- this . stimulusSet = metadata . componentConfig . stimulusSet || 1 ;
116123 this . interTrialDelay = metadata . componentConfig . interTrialDelay || 500 ;
117124 this . durationStimulusPresented = metadata . componentConfig . durationStimulusPresented || 3000 ;
118-
119- if ( metadata . componentConfig . stimuliConfig . type === StimuliProvidedType . HARDCODED )
125+ this . counterbalance = throwErrIfNotDefined (
126+ config . counterBalanceGroups [ config . counterbalanceNumber ] as 1 | 2 ,
127+ 'counterbalance not defined'
128+ ) ;
129+ this . stimulusSet = this . counterbalance ;
130+ this . blockNum = metadata . componentConfig . blockNum || 1 ;
131+
132+ if ( config . getCacheValue ( FaceNameAssociationCache . STIMULI ) ) {
133+ this . stimuli = config . getCacheValue ( FaceNameAssociationCache . STIMULI ) as FaceNameAssociationStimulus [ ] ;
134+ } else if ( metadata . componentConfig . stimuliConfig . type === StimuliProvidedType . HARDCODED ) {
120135 this . stimuli = metadata . componentConfig . stimuliConfig . stimuli ;
136+ }
121137 }
122138
123139 async start ( ) {
124140 this . taskData = [ ] ;
125141 this . currentStimuliIndex = 0 ;
126142 this . trialNum = 0 ;
127143
128- if ( ! this . stimuli ) {
129- this . stimuli = this . dataGenService . generateFaceNameAssociationTaskStimuli ( this . phase ) ;
130- const fileNames = this . stimuli . map ( ( x ) => `/assets/images/stimuli/facenameassociation/${ x . imageName } .png` ) ;
131- this . imageService . loadImagesAsBlobs ( fileNames ) . subscribe ( ( res ) => {
132- res . forEach ( ( blob , index ) => {
133- const imageName = this . stimuli [ index ] . imageName ;
134- this . blobs [ imageName ] = blob ;
135- } ) ;
136- super . start ( ) ;
137- } ) ;
138- } else {
139- super . start ( ) ;
144+ this . stimuli = this . dataGenService . generateFaceNameAssociationTaskStimuli (
145+ this . phase ,
146+ this . counterbalance ,
147+ this . stimuli
148+ ) ;
149+ if ( ! this . config . getCacheValue ( FaceNameAssociationCache . STIMULI ) ) {
150+ // store in cache for next block
151+ this . config . setCacheValue ( FaceNameAssociationCache . STIMULI , this . stimuli ) ;
140152 }
153+ super . start ( ) ;
141154 }
142155
143156 private getActualAnswer ( stimulus : FaceNameAssociationStimulus ) : UserResponse {
144157 if ( this . phase === 'learning-phase' ) {
145158 return UserResponse . NA ;
146159 } else {
147- return stimulus . personName === stimulus . correctPersonName ? UserResponse . YES : UserResponse . NO ;
160+ return stimulus . trialType === FaceNameAssociationTaskTrialtype . INTACT ? UserResponse . YES : UserResponse . NO ;
148161 }
149162 }
150163
@@ -153,7 +166,10 @@ export class FaceNameAssociationComponent extends AbstractBaseTaskComponent {
153166 this . showStimulus = false ;
154167 this . allowResponse = false ;
155168 this . currentName = '' ;
156- this . stimulusShown = null ;
169+
170+ const attentionCheckAnswers : string = (
171+ ( this . config . getCacheValue ( AttentionCheckCache . USER_ANSWERS ) as string [ ] ) || [ ]
172+ ) . reduce ( ( acc , curr , index ) => ( index === 0 ? curr : `${ acc } , ${ curr } ` ) , '' ) ;
157173
158174 this . taskData . push ( {
159175 userID : this . userID ,
@@ -162,19 +178,18 @@ export class FaceNameAssociationComponent extends AbstractBaseTaskComponent {
162178 trial : ++ this . trialNum ,
163179 phase : this . phase ,
164180 imagePresented : this . currentStimulus . imageName ,
165- namePresented : this . currentStimulus . personName ,
166- actualName : this . currentStimulus . correctPersonName ,
181+ namePresented : this . currentStimulus . displayedPersonName ,
182+ actualName : this . currentStimulus . actualPersonName ,
167183 stimulusSet : this . stimulusSet ,
168- maleFemale : this . currentStimulus . isFemale ? 'female' : 'male' ,
169- trialType :
170- this . currentStimulus . personName === this . currentStimulus . correctPersonName
171- ? FaceNameAssociationTaskTrialtype . INTACT
172- : FaceNameAssociationTaskTrialtype . RECOMBINED ,
184+ gender : this . currentStimulus . gender ,
185+ trialType : this . currentStimulus . trialType ,
173186 userAnswer : UserResponse . NA ,
174187 isCorrect : false ,
175188 actualAnswer : this . getActualAnswer ( this . currentStimulus ) ,
176189 responseTime : 0 ,
190+ blockNum : this . blockNum ,
177191 submitted : this . timerService . getCurrentTimestamp ( ) ,
192+ attentionCheck : attentionCheckAnswers ,
178193 } ) ;
179194
180195 if ( this . phase === 'learning-phase' ) {
@@ -196,8 +211,8 @@ export class FaceNameAssociationComponent extends AbstractBaseTaskComponent {
196211 }
197212
198213 private async setStimuliUI ( ) {
199- this . currentName = this . currentStimulus . personName ;
200- await this . showImage ( this . blobs [ this . currentStimulus . imageName ] ) ;
214+ this . currentName = this . currentStimulus . displayedPersonName ;
215+ this . stimulusShown = this . currentStimulus . imagePath ;
201216 }
202217
203218 private setMaxResponseTimer ( delay : number , cbFunc ?: ( ) => void ) {
@@ -279,17 +294,4 @@ export class FaceNameAssociationComponent extends AbstractBaseTaskComponent {
279294 return ;
280295 }
281296 }
282-
283- private showImage ( blob : Blob ) : Promise < void > {
284- return new Promise ( ( resolve ) => {
285- const fr = new FileReader ( ) ;
286- const handler = ( ) => {
287- this . stimulusShown = fr . result ;
288- fr . removeEventListener ( 'load' , handler ) ;
289- resolve ( ) ;
290- } ;
291- fr . addEventListener ( 'load' , handler ) ;
292- fr . readAsDataURL ( blob ) ;
293- } ) ;
294- }
295297}
0 commit comments