-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathCriticalSpacing.m
5714 lines (5601 loc) · 287 KB
/
CriticalSpacing.m
1
2
3
4
5
6
7
8
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
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
function oo=CriticalSpacing(oIn)
% o=CriticalSpacing(o);
% BUGS: none
% DESCRIPTION: CriticalSpacing measures an observer's crowding distance
% (formerly critical spacing) and acuity (i.e. threshold spacing and size)
% to help characterize the observer's vision. This program takes over your
% screen to measure the observer's size or spacing threshold for letter
% identification. It takes about 5 minutes to measure two thresholds. It's
% meant to be called by a short user-written script, and should work well
% in clinical environments. All results are returned in the "o" struct and
% also saved to disk in two files whose file names include your script
% name, the experimenter and observer names, and the date. One of those
% files has extension .txt and is a plain text that is easy for you to
% read. The other, with extension .mat, is a MATLAB save file and easily
% read by MATLAB programs. It's best to keep both. (It's easy to add code
% to also save a .json file, which is easy to read from other languages,
% like R or python. The filenames are unique and easy to sort, so it's fine
% to let all your data files accumulate in your CriticalSpacing/data/
% folder.
%
% THE "o" ARGUMENT, INPUT AND OUTPUT. You define a condition by creating an
% "o" struct and setting its fields to specify your testing condition. Call
% CriticalSpacing, passing the "o" struct. CriticalSpacing will measure a
% threshold for your condition and return the "o" struct including all the
% results as additional fields. CriticalSpacing may adjust some of your
% parameters to satisfy physical constraints including screen size and
% maximum possible contrast. If you provide several conditions, as an o
% array, then CriticalSpacing runs all the conditions randomly interleaved,
% measuring a threshold for each. I sometimes pass two identical conditions
% to get two thresholds for the same condition.
%
% USER-WRITTEN SCRIPTS. CriticalSpacing.m is meant to be driven by a brief
% user-written script. Your run script is short and very easy to write. It
% just assigns values to the fields of an "o" struct and then calls
% CriticalSpacing to measure a threshold. I have provided
% runCriticalSpacing as an example. You control the behavior of
% CriticalSpacing by setting parameters in the fields the "o" struct. "o"
% defines a condition for which a threshold will be measured. If you
% provide several conditions, as an o array, then CriticalSpacing runs all
% the conditions interleaved, measuring a threshold for each.
% CriticalSpacing initially confirms the viewing distance, asks for the
% experimenter's and observer's names, and presents a page of instructions.
% The rest is just one eye chart after another, each showing one or two
% targets (with or without repetitions). Presentation can be brief or
% static (o.durationSec=inf).
%
% RUN A SCRIPT. To test an observer, double click "runCriticalSpacing.m" or
% your own modified script. They're easy to write. Say "Ok" if MATLAB
% offers to change the current folder. CriticalSpacing automatically saves
% your results to the "CriticalSpacing/data" folder. The data filenames are
% unique and intuitive, so it's ok to let lots of data accumulate in the
% data folder. runCriticalSpacing takes 5 min to test one observer (with 20
% trials per threshold), measuring two thresholds, interleaved.
%
% ESCAPE & RESUME. Every command that accepts keyboard input accepts an
% ESCAPE key. This brings up a dialog offering three choices: to quit the
% whole experiment, to quit the block and begin the next block, to resume
% back where you escaped from. If resuming, the remaining trials are
% shuffled. May 1, 2019.
%
% PUBLICATION. You can read more about this program and its purpose in our
% 2016 article:
%
% Pelli, D. G., Waugh, S. J., Martelli, M., Crutch, S. J., Primativo, S.,
% Yong, K. X., Rhodes, M., Yee, K., Wu, X., Famira, H. F., & Yiltiz, H.
% (2016) A clinical test for visual crowding. F1000Research 5:81 (doi:
% 10.12688/f1000research.7835.1) http://f1000research.com/articles/5-81/v1
% It's open access. Download freely.
%
% INSTALL. To install and run CriticalSpacing on your computer:
% Download the CriticalSpacing software from
% https://github.com/denispelli/CriticalSpacing/archive/master.zip
% Unpack the zip archive, producing a folder called CriticalSpacing. Inside
% the CriticalSpacing folder, open the Word document "Install
% CriticalSpacing.docx" for detailed instructions for installation of
% MATLAB, Psychtoolbox, and CriticalSpacing software. Install. Type "help
% CriticalSpacing" in the MATLAB Command Window.
%
% PRINT THE ALPHABET ON PAPER. Choose a font from those available in the
% CriticalSpacing/pdf/ folder. They are all available when you set
% o.getAlphabetFromDisk=true. We have done most of our work with the
% "Sloan" and "Pelli" fonts. Only Pelli is skinny enough to measure foveal
% crowding. Outside the fovea you can use any font. We recommend "Pelli"
% for threshold spacing (crowding) in the fovea, and Sloan for threshold
% size (acuity) anywhere. Print the PDF for your font, e.g. "Pelli
% alphabet.pdf" or "Sloan alphabet.pdf". Give the printed alphabet page to
% your observer. It shows the possible letters, e.g. "DHKNORSVZ" or
% "1234567889". Most observers will find it helpful to consult this page
% while choosing an answer, especially when they are guessing. And children
% may prefer to respond by pointing at the printed target letters on the
% alphabet page. However, patients who have trouble directing their
% attention may be better off without the paper, to give their undivided
% attention to the display.
%
% DISPLAY ALPHABET ON SCREEN. Anytime you press the "caps lock" key,
% CriticalSpacing will display the alphabet of possible responses in the
% current font. Like the printed version, it shows the nine possible
% letters or digits. This may help observers choose an answer, especially
% when they are guessing.
%
% MATLAB AND PSYCHTOOLBOX. To run this program, you need a computer with
% MATLAB (or Octave) and the Psychtoolbox installed. The computer OS can be
% OS X, Windows, or Linux. MATLAB is commercially available, and many
% universites have site licences. Psychtoolbox is free.
% https://www.mathworks.com/
% http://psychtoolbox.org/
%
% OPTIONAL: SPECIFY YOUR SCREEN SIZE IN CM. Psychtoolbox automatically
% reads your display screen's resolution in pixels and size in cm, and
% reports them in every data file. Alas, the reported size in cm is
% sometimes very wrong in Windows, and is often not available for external
% monitors under any OS. So we allow users to measure the display screen
% size themselves and provide it in their "o" struct as
% o.measuredScreenWidthCm and o.measuredScreenWidthCm. Your measured values
% will override whatever values your display provides. Use a meter stick to
% measure the width and height of your screen's filled rectangle of glowing
% pixels.
%
% macOS: PERMIT MATLAB TO CONTROL YOUR COMPUTER. Open the System
% Preferences: Security and Privacy: Privacy tab. Select Accessibility.
% Click to open the lock in lower left, providing your computer password.
% Click to select MATLAB, allowing it to control your computer. Click the
% lock again to close it.
%
% CONTROL SCREEN. When you run CriticalSpacing, the first screen you see is
% the "control" screen. It tells you several useful things about your
% display and stimuli. Most prominently, it asks you to adjust the actual
% viewing distance to match the nominal value. It also allows you to change
% the nominal value. Further, it tells you the min and max viewing distance
% that will allow you to measure acuity at the specified eccentricity and
% to put both your target and fixation point on the display. (We also
% support off-screen fixation.) Further options include mirroring,
% optimizing resolution,and helping you to attach a wireless keyboard.
%
% A WIRELESS OR LONG-CABLE KEYBOARD is highly desirable because a normally
% sighted observer viewing foveally has excellent vision and must be many
% meters away from the screen in order for us to measure her acuity limit.
% At this distance she can't reach the built-in keyboard attached
% to the screen. If you must use the built-in keyboard, then have the
% experimenter type the observer's verbal answers. Instead, I like the
% Logitech K760 solar-powered wireless keyboard, because its batteries
% never run out. It's no longer made, but still available on Amazon, New
% Egg, and eBay (below). To "pair" the keyboard with your computer's blue
% tooth, press the tiny button on the back of the keyboard.
%
% Logitech Wireless Solar Keyboard K760 for Mac/iPad/iPhone
% http://www.amazon.com/gp/product/B007VL8Y2C
% https://www.newegg.com/logitech-wireless-solar-keyboard-k760-bluetooth-wireless/p/N82E16823126283?Item=9SIA4RE7ZV9401
% https://www.ebay.com/sch/i.html?_from=R40&_trksid=m570.l1313&_nkw=Logitech+K760+Wireless+Solar+Keyboard+&_sacat=0
% https://www.logitech.com/assets/44407/wireless-solar-keyboard-k760-quickstart-guide.pdf
%
% TAPE OR LASER MEASURE FOR VIEWING DISTANCE. The viewing distance will
% typically be several meters, and it's important that you set it
% accurately, within five percent. You can measure it with a $10 tape
% measure marked in centimeters. A fancy $40 alternative is a Bosch laser
% measure, which gives you the answer in two clicks. The laser works even
% with a mirror.
%
% http://www.amazon.com/gp/product/B0016A2UHO
% http://www.amazon.com/gp/product/B00LGANH8K
% https://www.boschtools.com/us/en/boschtools-ocs/laser-measuring-glm-15-0601072810--120449-p/
%
% MIRROR. In a small room, you might need a mirror to achieve a long
% viewing distance. You can request this, in advance, by setting
% o.flipScreenHorizontally=true in your run script. (At run time, when
% CriticalSpacing asks you about viewing distance, you can indicate that
% you're using a mirror by entering the viewing distance as a negative
% number. It will flip the display to be seen in a mirror.) I bought two
% acrylic front surface mirrors for this. 12x24 inches, $46 each from
% inventables. Front surface mirrors preserve image quality, and acrylic is
% hard to break, making it safer than glass. I'm not yet sure how big a
% mirror one needs to accomodate observers of various heights, so I listed
% several of Amazon's offerings, ranging up to 24" by 48". The five-pack is
% a good deal, five 12"x24" mirrors for $67.
%
% http://www.amazon.com/Acrylic-Wall-Mirror-Size-24/dp/B001CWAOJW/ref=sr_1_19
% http://www.amazon.com/Childrens-Factory-Look-At-Mirror/dp/B003BL7TMC/ref=sr_1_14
% https://www.inventables.com/technologies/first-surface-mirror-coated-acrylic
% http://www.amazon.com/12-24-Mirror-Acrylic-Plexiglass/dp/B00IVWQPUI/ref=sr_1_39
% http://www.amazon.com/12-Acrylic-Mirror-Sheet-Pack/dp/B00JPJK3T0/ref=sr_1_13
% http://www.amazon.com/Double-Infant-Mirror-surface-Approved/dp/B0041TABOG/ref=pd_sim_sbs_468_9
%
% READ ALPHABET FROM DISK. If you set o.getAlphabetFromDisk=true in your
% script you can use any of the "fonts" inside the
% CriticalSpacing/alphabets/ folder, which you can best see by looking at
% the alphabet files in CriticalSpacing/pdf/. You can easily create and add
% a new "font" to the alphabets folder. Name the folder after your "font",
% and put one image file per letter inside the folder, named for the
% letter. That's it. You can now specify your new "font" as the
% o.targetFont and CriticalSpacing will use it. You can make the drawings
% yourself, or you can run CriticalSpacing/lib/SaveAlphabetToDisk.m to
% create a new folder based on a computer font that you already own. This
% scheme makes it easy to develop a new font, and also makes it easy to
% share font images without violating a font's commercial distribution
% license. (US Copyright law does not cover fonts. Adobe patents the font
% program, but the images are public domain.) You can also ask
% CriticalSpacing to use any font that's installed in your computer OS by
% setting o.getAlphabetFromDisk=false. The Pelli and Sloan fonts are
% provided in the CriticalSpacing/fonts/ folder, and you can install them
% in your computer OS. On a Mac, you can just double-click the font file
% and say "yes" when your computer offers to install it for you. Once
% you've installed a font, you must quit and restart MATLAB to use the
% newly available font.
%
% OPTIONAL: ADD A NEW FONT. Running the program SaveAlphabetToDisk in
% the CriticalSpacing/lib/ folder, after you edit it to specify the font,
% alphabet, and borderCharacter you want, will add a snapshot of your
% font's alphabet to the pdf folder and add a new folder, named for your
% font, to the CriticalSpacing/alphabets/ folder.
%
% OPTIONAL: USE YOUR COMPUTER'S FONTS, LIVE. Set
% o.getAlphabetFromDisk=false. You may wish to install Pelli or Sloan from
% the CriticalSpacing/fonts/ folder into your computer's OS. Restart MATLAB
% after installing a new font. To render fonts well, Psychtoolbox needs to
% load the FTGL DrawText dropin. It typically takes some fiddling with
% dynamic libraries to make sure the right library is available and that
% access to it is not blocked by the presence of an obsolete version. For
% explanation see "help drawtextplugin". You need this only if you want to
% set o.getAlphabetFromDisk=false.
%
% CHILDREN. Adults and children seem to find CriticalSpacing easy and
% intuitive. Sarah Waugh (Dept. of Vision and Hearing Sciences, Anglia
% Ruskin University) has tested 200 children of ages 3 to 16(?). To test
% children, Sarah used a fictional astronaut adventure story about this
% test. The game, designed by Aenne Brielmann, in my lab here at NYU,
% includes an illustrated story book and alien dolls.
%
% CHOOSE A VIEWING DISTANCE. You can provide a default in your script, e.g.
% o.viewingDistanceCm=400. CriticalSpacing invites you to modify the
% viewing distance (or declare that you're using a mirror) at the beginning
% of each block. You need a long distance to display tiny letters, and, if
% fixation is on-screen, you need short viewing distance to display
% peripheral letters. (We also support off-screen fixation.)
% When viewing foveally, please err on the side of making the viewing
% distance longer than necessary. Observers don't like being closer than 40
% cm. Also, if you use too short a viewing distance then the minimum size
% and spacing may be bigger than the threshold you want to measure. At the
% end of the block, CriticalSpacing.m warns you if the estimated threshold
% is smaller than the minimum possible size or spacing at the current
% distance, and suggests that you increase the viewing distance in
% subsequent blocks. The minimum viewing distance depends on the smallest
% letter size you want to show with 8 pixels and the resolution (pixels per
% centimeter) of your display. This is Eq. 4 in the Pelli et al. (2016)
% paper cited at the beginning,
%
% minRecommendedViewingDistanceCm=57*(minimumTargetPix/letterDeg)/(screenWidthPix/screenWidthCm);
%
% where minimumTargetPix=8 and letterDeg=0.02 for the healthy adult fovea.
%
% PERSPECTIVE DISTORTION IS MINIMIZED BY PLACING THE TARGET AT THE NEAR
% POINT (new in June 2017). At the beginning of each block, the
% CriticalSpacing program gives directions to arrange the display so that
% its "near point" (i.e. point closest to the observer's eye) is orthogonal
% to the observer's line of sight. This guarantees minimal effects of
% perspective distortaion on any target placed there.
%
% CHOOSE VIEWING DISTANCE TO ALLOW ON-SCREEN FIXATION. The big control page
% (the "splash screen") now (June 2017) gives advice about whether the
% fixation will fit on-screen, and tells you the maximum viewing distance
% that would allow that.
%
% ECCENTRICITY OF THE TARGET. Eccentricity of the target is achieved by
% appropriate placement of the target and the fixation mark. Modest
% eccentricities, up to perhaps 30 deg, are achieved with on-screen
% fixation. If the eccentricity is too large for on-screen fixation, then
% we help you set up off-screen fixation.
% 1. Set nearPointXYPix according to o.nearPointXYInUnitSquare, whose
% default is 0.5 0.5.
% 2. If setNearPointEccentricityTo==
% 'target', then set nearPointXYDeg=eccentricityXYDeg.
% 'fixation', then set nearPointXYDeg=[0 0].
% 'value', then assume nearPointXYDeg is already set by user script.
% 3. Ask viewer to adjust display so desired near point is at desired
% viewing distance and orthogonal to line of sight from eye.
% 4. If using off-screen fixation, put fixation at same distance from eye
% as the near point, and compute its position relative to near point.
% HORIZONAL OFF-SCREEN FIXATION. To achieve a large horizontaol
% eccentricity, pick a stable object that has roughly the same height as
% your display, perhaps a can or a cardboard box. Place the sturdy object
% next to your display. (Alternatively, a goose neck clamp, described
% below, could be clamped to a side edge of your display.) Follow the
% instructions of CriticalSpacing to draw a fixation mark, e.g. with a
% black sharpee, on your object at the same height as the cross on the
% screen. CriticalSpacing will tell you how far it should be from the
% on-screen cross (the target location) and how far it shold be from the
% observer's eye. In addition to the sturdy object and marker, you'll need
% a meter stick.
%
% VERTICAL OFF-SCREEN FIXATION. To achieve a large vertical eccentricity, I
% recommend a gooseneck clamp, like the Wimberley PP-200 Plamp II used by
% photographers to hold things.
% https://www.amazon.com/gp/product/B00SCXUZM0/ref=oh_aui_detailpage_o01_s00
% For a negative vertical eccentricity, clamp one end on the top edge of
% your display, and CriticalSpacing will tell you how to position the other
% end, which acts as your fixation mark. For a positive eccentricity, bring
% your display forward to the edge of your table, and clamp the bottom edge
% of the display. You need just the gooseneck clamp and a meter stick.
%
% NAME THE EXPERIMENTER & OBSERVER. If it doesn't already know,
% CriticalSpacing asks for the name of the experimenter and observer. These
% names are included in the data files, and incorporated into the data file
% names. If your know the experimenter or observer name in advance you can
% specify it in your script, e.g. o.experimenter='Denis' or
% o.observer='JohnK', and CriticalSpacing will skip that question.
%
% CAPS-LOCK KEY: DISPLAY THE ALPHABET. Anytime that CriticalSpacing is
% running trials, pressing the caps lock key will display the font's
% alphabet at a large size, filling the screen. (The shift key works too,
% but it's dangerous on Windows. On Windows, pressing the shift key five
% times provokes a "sticky keys" dialog that you won't see because it's
% hidden behind the CriticalSpacing window, so you'll be stuck. The caps
% lock key is always safe.)
%
% ESCAPE KEY: QUIT. You can always terminate the current block by hitting
% the escape key on your keyboard (typically in upper left, labeled "esc").
% Because at least one computer (e.g. the 2017 MacBook Pro with track bar)
% lacks an ESCAPE key, we always accept the GRAVE ACCENT key (also in upper
% left of keyboard) as equivalent. CriticalSpacing will then print out (and
% save to disk) results so far, and ask whether you're quitting the whole
% session or proceeding to the next block. Quitting this block sets the
% flag o.quitBlock, and quitting the experiment also sets the flag
% o.quitExperiment (and o.isLastBlock=true and isLastBlock=true).
% If o.quitExperiment is already set when you call CriticalSpacing, the
% CriticalSpacing returns immediately after processing arguments.
% (CriticalSpacing ignores o.quitBlock on input.)
%
% SPACE KEY: SKIP THIS TRIAL. To make it easier to test children, we've
% softened the "forced" in forced choice. If you (the experimenter) think
% the observer is overwhelmed by this trial, you can press the spacebar
% instead of a letter and the program will immediately go to the next
% trial, which will be easier. If you skip that trial too, the next will be
% even easier, and so on. However, as soon as a trial gets a normal
% response then Quest will resume presenting trials near threshold. We hope
% skipping will make the initial experience easier. Eventually the child
% must still do trials near threshold, because threshold estimation
% requires it. Skipping is always available. If you type one letter and
% then skip, the typed letter still counts. There's an invisible timer.
% If you hit space (to skip) less than 8 s after the chart appeared, then
% the program says "Skip", and any responses not yet taken do not count. If
% you wait at least 8 s before hitting space, then the program says
% "Space" and, supposing that the child felt too unsure to choose
% knowledgeably, the program helps out by providing a random guess. By
% chance, that guess will occasionally be right. Please do not tell the
% observer about this option to skip. Use this only rarely, when you need
% it to avoid a crisis. In general it's important to set up the right
% expectation at the outset. Warn the observer that this is a game and
% nobody gets them all right: "Just try to get as many as you can."
%
% THRESHOLD. CriticalSpacing measures threshold spacing or size (i.e.
% acuity). This program measures threshold spacing in either of two
% directions, selected by the variable o.targetSizeIsHeight, true for
% vertically, and false for horizontally. Target size can be made
% proportional to spacing, allowing measurement of critical spacing without
% knowing the acuity, because we use the largest possible letter for each
% spacing. The ratio SpacingOverSize is computed for spacing and size along
% the axis specified by o.flankingDegVector, a unit length vector. You can
% directly specify o.flankingDegVector, or you can leave it as [] and set
% o.flankingDirection: 'radial', 'tangential', 'horizontal', or 'vertical',
% which will be used to compute o.flankingDegVector. The final report by
% CriticalSpacing includes the aspect ratio of your font:
% o.heightOverWidth.
%
% ECCENTRICITY. Set o.eccentricityXYDeg=[x y] in your script. For
% peripheral testing, it's usually best to set o.durationSec=0.2 to exclude
% eye movements during the brief target presentation. When the flankers are
% radial, the specified spacing refers to the inner flanker, between target
% and fixation. We define scaling eccentricity as eccentricity plus 0.015
% deg. The critical spacing of crowding is proportional to the scaling
% eccentricity The outer flanker is at the scaling eccentricity that has
% the same ratio to the target scaling eccentricity, as the target scaling
% eccentricity does to the inner-flanker scaling eccentricity.
%
% SAVING LETTER-CONFUSION DATA
% For each condition we keep track of which letters the observer has
% trouble with (time and accuracy). This might lead us to adjust or drop
% troublesome letters. We save the letter-confusion and reaction-time
% results of every presentation in a trialData array struct that is a field
% of each condition. The index "i" counts presentations. There may be 1 or
% 2 targets per presentation. If there are two targets (characters), then
% there are two targetScores, responses (characters), responseScores, and
% reactionTimes. targetScores and responseScores are 1 or 0 for each item.
% The responses are in the order typed, at times (since Flip) in
% reactionTimes. reactionTime is nan after the observer views the alphabet
% screen.
% oo(oi).trialData(i).targetDeg
% oo(oi).trialData(i).spacingDeg
% oo(oi).trialData(i).targets
% oo(oi).trialData(i).targetScores
% oo(oi).trialData(i).responses
% oo(oi).trialData(i).responseScores
% oo(oi).trialData(i).reactionTimes
% The other relevant parameters of the condition do not change from trial
% to trial: age, font, thresholdParameter, repeatedTargets.
%
% You can use o.stimulusMarginFraction to shrink stimulusRect e.g. 10% so
% that letters have white above and below.
%
% Copyright (c) 2016, 2017, 2018, 2019 Denis Pelli, [email protected]
% HISTORY:
% August 12, 2019. o.askExperimenterToSetDistance=true asks observer to get
% the experimenter to set the viewing distance each time it changes. The
% experiment types a "signature key" (first letter of the previously given
% o.experimenter name) to continue. This responds to a tendency for
% observers to just click past even bright red screens asking then to set a
% new viewing distance.
%
% March 21, 2020. CriticalSpacing was incorrectly estimating the native
% resolution as the maximum offered by Screen('resolutions'). My fix is to
% create a table lookup for native screen resolution of 15" and 16" MacBook
% Pro computers with Retina displays. CriticalSpacing was estimating the
% wrong "native" resolution at the beginning of each block and setting the
% display to that resolution, which was then overriden when the window
% opened in true native resolution. This is thanks to
% PsychImaging('AddTask','General','UseRetinaResolution') before asking
% PsychImaging to open the window. PsychImaging changes the resolution to
% native as it opens the window. This true native resolution, being still
% below my erroneously high estimate provoked another resolution change at
% the beginning of the next block. The fact that calling Screen with a
% request for native resolution worked suggests that it should be easy to
% find that resoltuion in software, simply by making that call and noting
% what resolution we get. This suggests that I shouldn't bother to set
% resolution to native, since Screen is already doing that for me.
%
% March 30, 2020. DGP. Added work around for bug in Screen Resolution.
% Repeating the call to set, nominally of no effect, is a work around for a
% bug in Screen. Otherwise after we change resolution, subsequent calls to
% OpenWindow put the window in the wrong place. With the extra call, window
% placement is correct. Bug reported March 28, 2020.
%% PLANS
%
% When the alphabet is large (eg. 26) it currently falls off the right
% edge when we display it at the end of a trial. I think we should break it
% into two lines.
%
% I'd like the viewing-distance page to respond to a new command: "o" to
% set up offscreen fixation.
%
% Add switch to use only border characters as flankers.
%
% In repetition mode, don't assume one centered target and an even number
% of spaces. We might want to center between two targets to show an odd
% number of spaces.
%
%% HELPFUL PROGRAMMING ADVICE FOR KEYBOARD INPUT IN PSYCHTOOLBOX
% [PPT]Introduction to PsychToolbox in MATLAB - Jonas Kaplan
% www.jonaskaplan.com/files/psych599/Week6.pptx
% UNICODE
% str = unicode2native('?','utf-8');
% Screen('Preference', 'TextEncodingLocale', 'en_US.UTF-8');
% EXPLANATION FROM MARIO KLEINER (2/9/16) ON RESTORING RESOLUTION
% "The current behavior is something like this:
%
% "* On OSX it tries to restore the video mode that was present at the time
% when the user changed video mode the first time in a session via
% Screen('Resolution'), whenever a window gets closed by code or due to
% error. The OS may restore video mode to whatever it thinks makes sense
% also if Matlab/Octave exits or crashes.
%
% "* On Windows some kind of approximation of the above, at the discretion
% of the OS. I don't know if different recent Windows versions could behave
% differently. We tell the OS that the mode we set is dynamic/temporary and
% the OS restores to something meaningful (to it) at error/window close
% time, or probably also at Matlab exit/crash time.
%
% "* Linux X11 approximation of OSX, except in certain multi-display
% configurations where it doesn't auto-restore anything. And a crash/exit
% of Matlab doesn't auto-restore either. Linux with a future Wayland
% display system will likely have a different behavior again, due to
% ongoing design decisions wrt. desktop security.
%
% "It's one of these areas where true cross-platform portability is not
% really possible.
%
% "In my git repo i have a Screen mex file which no longer triggers errors
% during error handling, but just prints an error message if OSX screws up
% in doing its job as an OS:
%
% https://github.com/kleinerm/Psychtoolbox-3/raw/master/Psychtoolbox/PsychBasic/Screen.mexmaci64
%
% "Running latest PsychToolbox on MATLAB 2015b on latest El Capitan on
% MacBook Air with attached Dell display."
%
% -mario
Screen('Preference','SkipSyncTests',1);
[~,v]=PsychtoolboxVersion;
if v.major*10000 + v.minor*100 + v.point < 30012
error('CriticalSpacing: Your Psychtoolbox is too old. Please run "UpdatePsychtoolbox".');
end
if verLessThan('matlab','9.1')
% https://en.wikipedia.org/wiki/MATLAB#Release_history
error('%s requires MATLAB 9.1 (R2016b) or later, for "split" function.',mfilename);
end
if nargin<1 || ~exist('oIn','var')
oIn.script=[];
end
mainFolder=fileparts(mfilename('fullpath'));
if ~exist('TextSizeToFit','file') && ...
~exist([mainFolder filesep 'lib' filesep 'TextSizeToFit.m'],'file')
error(['Please run me from inside the "CriticalSpacing" folder. '...
'That allows me to find needed subroutines.']);
end
addpath(fullfile(mainFolder,'lib'));
addpath(fullfile(mainFolder,'utilities'));
QuestPlusPath=fullfile(fileparts(mainFolder),'mQUESTPlus');
addpath(fullfile(QuestPlusPath,'contributed'));
addpath(fullfile(QuestPlusPath,'dataproc'));
addpath(fullfile(QuestPlusPath,'demos'));
addpath(fullfile(QuestPlusPath,'mathworkscentral'));
addpath(fullfile(QuestPlusPath,'printplot'));
addpath(fullfile(QuestPlusPath,'psifunctions'));
addpath(fullfile(QuestPlusPath,'questplus'));
addpath(fullfile(QuestPlusPath,'utilities'));
plusMinus=char(177);
% Once we call onCleanup, until CriticalSpacing ends,
% CloseWindowsAndCleanup will run (closing any open windows) when this
% function terminates for any reason, whether by reaching the end, throwing
% an error here (or in any function called from here), or the user hitting
% control-C.
cleanup=onCleanup(@() CloseWindowsAndCleanup);
global skipScreenCalibration ff
global window keepWindowOpen % Keep window open until end of last block.
global isSoundOpen oldDisplaySettings
global scratchWindow
global screenRect % For ProcessEscape
persistent drawTextWarning
global corpusFilename corpusText corpusFrequency corpusWord corpusSentenceBegins corpusLineEndings
persistent oldViewingDistanceCm
global blockTrial blockTrials % used in DrawCounter.
persistent isMicrophonePermissionGranted
keepWindowOpen=false; % Enable only in normal return.
rotate90=[cosd(90) -sind(90); sind(90) cosd(90)];
% THESE STATEMENTS PROVIDE DEFAULT VALUES FOR ALL THE "o" parameters.
% They are overridden by what you provide in the argument struct oIn.
% PROCEDURE
o.procedure='Quest'; % 'Constant stimuli'
o.easyBoost=0.3; % On easy trials, boost log threshold parameter by this.
o.experimenter=''; % Assign your name to skip the runtime question.
o.flipScreenHorizontally=false; % Set to true when using a mirror.
o.fractionEasyTrials=0;
o.observer=''; % Assign the name to skip the runtime question.
o.permissionToChangeResolution=false; % Works for main screen only, due to Psychtoolbox limitations.
o.getAlphabetFromDisk=true; % true makes the program more portable.
o.secsBeforeSkipCausesGuess=8;
o.takeSnapshot=false; % To illustrate your talk or paper.
o.task='identify'; % identify, read, readAloud
o.textFont='Verdana';
o.textSizeDeg=0.4;
o.thresholdParameter='spacing'; % 'spacing' or 'size'
o.trialsDesired=20; % Number of trials (i.e. responses) for the threshold estimate.
o.viewingDistanceCm=400; % Default.
o.maxViewingDistanceCm=0; % Max over remaining blocks in experiment.
% THIS SEEMS TO BE A CLUMSY ANTECEDENT TO THE NEARPOINT IDEA. DGP
o.measureViewingDistanceToTargetNotFixation=true;
o.endsAtMin=[];
o.askExperimenterToSetDistance=true;
o.experiment=''; % Name of this experiment. Used to select files for analysis.
o.block=1; % Each block may contain more than one condition.
o.blocksDesired=1;
o.condition=1; % Integer count of the condition within each block, starting at 1.
o.conditionName='';
o.quitBlock=false;
o.quitExperiment=false;
o.trialsSkipped=0;
o.askForPartingComments=true;
o.partingComments='';
o.spacings=[]; % For o.procedure='Constant stimuli'.
o.fixationCheck=false; % True designates condition as a fixation check.
o.fixationCheckMakeupPresentations=1; % After a mistake, how many correct presentation to require.
% SOUND & FEEDBACK
o.beepNegativeFeedback=false;
o.beepPositiveFeedback=true;
o.showProgressBar=true;
o.speakEachLetter=true;
o.speakEncouragement=false;
o.speakViewingDistance=false;
o.usePurring=true;
o.useSpeech=false;
o.showCounter=true;
o.soundVolume=0.25;
% VISUAL STIMULUS
o.contrast=1; % Nominal contrast, not calibrated.
o.eccentricityXYDeg = [0 0]; % eccentricity of target center re fixation, + for right & up.
o.nearPointXYInUnitSquare=[0.5 0.5]; % location on screen. [0 0] lower right, [1 1] upper right.
o.durationSec=inf; % Duration of display of target and flankers
% o.fixedSpacingOverSize=0; % Disconnect size & spacing.
o.fixedSpacingOverSize=1.4; % Requests size proportional to spacing, horizontally and vertically.
o.relationOfSpacingToSize='fixed ratio'; % 'none' 'fixed by font'
o.fourFlankers=0;
o.oneFlanker=0;
o.targetSizeIsHeight=nan; % false, true (or nan to depend on o.thresholdParameter)
o.minimumTargetPix=6; % Minimum viewing distance depends soley on this & pixPerCm.
o.flankingDirection='radial'; % 'radial' or 'tangential' or 'horizontal' or 'vertical'.
o.flankingDegVector=[]; % Specify x,y vector, or [] to specify named o.flankingDirection.
o.repeatedTargets=false;
o.maxLines=inf; % When repeatedTargets==true, max number of lines, including borders. Must be 1,3,4, ... inf.
o.maxFixationErrorXYDeg=[3 3]; % Repeat targets enough to cope with errors up to this size.
o.practicePresentations=0; % 0 for none. Adds easy trials at the beginning that are not recorded.
o.setTargetHeightOverWidth=false; % Stretch font to achieve a particular aspect ratio.
o.spacingDeg=nan;
o.spacingGuessDeg=nan;
o.targetDeg=nan;
o.targetGuessDeg=nan;
o.stimulusMarginFraction=0.0; % White margin around stimulusRect.
o.targetMargin = 0.25; % Minimum from edge of target to edge of o.stimulusRect, as fraction of targetDeg height.
o.measuredScreenWidthCm = []; % Allow users to provide their own measurement when the OS gives wrong value.
o.measuredScreenHeightCm = [];% Allow users to provide their own measurement when the OS gives wrong value.
o.isolatedTarget=false; % Set to true when measuring acuity for a single isolated letter. Not yet fully supported.
o.brightnessSetting=0.87; % Default. Roughly half luminance. Some observers find 1.0 painfully bright.
% READ TEXT
o.readLines=12;
o.readCharPerLine=50;
o.readSpacingDeg=0.3;
o.readString={}; % The string of text to be read.
o.readSecs=[]; % Time spent reading (from press to release of space bar).
o.readChars=[]; % Length of string.
o.readWords=[]; % Words in string.
o.readCharPerSec=[]; % Speed.
o.readWordPerMin=[]; % Speed.
o.readMethod='';
o.readError='';
o.readQuestions=3;
% o.readFilename='MHC928.txt';
o.readFilename='the phantom tollbooth.txt'; % Norton Juster (1961). The Phantom Tollbooth. Random House.
o.readNumberOfResponses=[];
o.readNumberCorrect=[];
% TARGET FONT
% o.targetFont='Sloan';
% o.alphabet='DHKNORSVZ'; % Sloan alphabet, excluding C
% o.borderLetter='X';
% o.alphabet='HOTVX'; % alphabet of Cambridge Crowding Cards
% o.borderLetter='$';
o.targetFont='Pelli';
o.alphabet='123456789';
o.borderLetter='$';
o.flankerLetter='';
% o.targetFont='ClearviewText';
% o.targetFont='Gotham Cond SSm XLight';
% o.targetFont='Gotham Cond SSm Light';
% o.targetFont='Gotham Cond SSm Medium';
% o.targetFont='Gotham Cond SSm Book';
% o.targetFont='Gotham Cond SSm Bold';
% o.targetFont='Gotham Cond SSm Black';
% o.targetFont='Arouet';
% o.targetFont='Pelli';
% o.targetFont='Retina Micro';
% GEOMETRY
% Default to external monitor if there is one.
o.screen=max(Screen('screens'));
o.nearPointXYDeg=[0 0]; % Set this explicitly if you set setNearPointEccentricityTo='value'.
o.setNearPointEccentricityTo='fixation'; % 'target' or 'fixation' or 'value'
% FIXATION
o.useFixation=true;
o.blankingClipRectInUnitSquare=[0.1 0.1 0.9 0.9];
o.isFixationClippedToStimulusRect=false;
o.isTargetLocationMarked=false; % true to mark target location
o.fixationThicknessDeg=0.03; % Typically 0.03. Make it much thicker for scotopic testing.
o.fixationMarkDeg=1; % Diameter of fixation mark +
o.targetMarkDeg=0.5; % Diameter of target mark X
o.isFixationBlankedNearTarget=true;
o.fixationBlankingRadiusReTargetHeight=2;
o.fixationBlankingRadiusReEccentricity=0.5;
o.blankingClipRectInUnitSquare=[0.1 0.1 0.9 0.9];
o.fixationOffsetBeforeTargetOnsetSecs=0;
o.fixationOnsetAfterTargetOffsetSecs=0.2; % Pause after stimulus before display of fixation.
o.forceFixationOffScreen=false;
% CriticalSpacing requires that the on-screen fixation mark be at least
% o.fixationCoreSizeDeg/2 away from edge of clipping rect. (Implemented in
% CriticalSpacing.m, not in ComputeFixationLines3.m.)
o.fixationCoreSizeDeg=1; % Fixation-centered diameter cannot be clipped.
o.recordGaze=false;
o.useFixationDots=false;
o.fixationDotsNumber=100;
o.fixationDotsWithinRadiusDeg=2;
o.fixationDotsColor=0;
o.fixationDotsWeightDeg=0.05;
% RESPONSE
o.labelAnswers=false; % Useful for non-Roman fonts, like Chinese and Animals.
o.responseLabels='abcdefghijklmnopqrstuvwxyz123456789';
o.alphabetPlacement='bottom'; % 'top' placement currently collides with instructions.
o.instructionPlacement='topLeft'; % 'topLeft' 'bottomLeft'. NOT YET IMPLEMENTED.
o.counterPlacement='bottomRight'; % 'bottomLeft' 'bottomCenter' 'bottomRight'
% GROUP
o.group=''; % A "group" of conditions is defined by having the same
% nonempty string in o.group. This designation requests that all conditions
% in the group be presented with the same fixation and noise, which
% considers all possible target positions. Empty o.group requests that the
% condition be presented independently of the rest. All conditions were
% independent in CriticalSpacing until June 21, 2020.
% SIMULATE OBSERVER
o.simulateObserver=false;
o.simulatedLogThreshold=0;
% o.dontWait must be double 0 or 1. Screen gives a fatal error if it's logical
% boolean.
o.dontWait=0;
% QUEST threshold estimation
% QUEST INPUT
o.beta=2.3; % Mean for Gus & Ashley for radial crowding distance.
o.delta=0.01;
o.measureBeta=false;
o.pThreshold=nan;
o.tGuess=nan;
o.tGuessSd=nan;
o.useQuest=true; % true or false
o.spacingGuessDeg = 1;
% QUEST OUTPUT
o.q=struct([]); % Quest data structure. Use Quest routines to access it.
o.questMean=[]; % Mean of posterior pdf. The threshold estimate. Log10 scale.
o.questSD=[]; % SD of posteriod pdf. 95% confidence is +/-2 SD. Log10 scale.
% READ ALOUD
o.wordLength=3;
o.batch = 1; % Which batch of data to use
% PARTIAL REPORT
o.PRlength=8; % number of letters in string
o.PRIntervalSec=1; % the time of white screen after displaying string
% DEBUGGING AIDS
o.frameTheTarget=false;
o.printScreenResolution=false;
o.printSizeAndSpacing=false;
o.showAlphabet=false;
o.showBounds=false;
o.showLineOfLetters=false;
o.speakSizeAndSpacing=false;
o.useFractionOfScreenToDebug=0;
o.endsAtMin=0;
% BLOCKS AND BRIGHTNESS
% To save time, we set brightness only before the first block, and restore
% it only after the last block. Each call to AutoBrightness seems to take
% about 30 s on an iMac under Mojave. CriticalSpacing doesn't know the
% block number, so we provide two flags to designate the first and last
% blocks. If you provide o.lastBlock=true then brightness
% will be restored at the end of the block (or when CriticalSpacing
% terminates). Otherwise the brightness will remain ready for the next
% block. I think this code eliminates an annoying dead time of 30 to 60 s
% per block.
o.isFirstBlock=true;
o.pleaseReopenWindow=false;
o.isLastBlock=true;
o.skipScreenCalibration=false;
skipScreenCalibration=o.skipScreenCalibration; % Global for CloseWindowsAndCleanup.
% TO MEASURE BETA
% o.measureBeta=false;
% o.offsetToMeasureBeta=-0.4:0.1:0.2; % offset of t, i.e. log signal intensity
% o.trialsDesired=200;
% TO HELP CHILDREN
% o.fractionEasyTrials=0.2; % 0.2 adds 20% easy trials. 0 adds none.
% o.speakEncouragement=true; % true to say "good," "very good," or "nice" after every trial.
% o.practicePresentations=3; % 0 for none. Ignored unless repeatedTargets==true.
% Provides easy practice presentations, ramping up
% the number of targets after each correct report
% of both letters in a presentation, until the
% observer gets three presentations right. Then we
% seamlessly begin the official block.
% PRACTICE PRESENTATIONS.
% In several instances, very young children (4 years old) refused to even
% try to guess the letters when the screen is covered by letters in the
% repeated-letters condition. 8 year olds and adults are unphased. Sarah
% Waugh found that the 4 years olds were willing to identify one or two
% target letters, and we speculated that once they succeeded at that, they
% might be willing to try the repeated-letters condition, with many more
% letters.
%
% You can now request this by setting o.practicePresentations=3. My hope is
% that children will be emboldened by their success on the first three
% trials to succeed on the repeated condition, in which letters cover most
% of the screen.
%
% o.practicePresentations=3 only affects the repeated-targets condition,
% i.e. when o.repeatedTargets=true. This new options adds 3 practice
% presentations at the beginning of every repeatedTargets block. The first
% presentation has only a few target letters (two unique) in a single row.
% Subsequent presentations are similar, until the observer gets both
% targets right. Then it doubles the number of targets. Again it waits for
% the observer to get both targets right, and then doubles the number of
% targets. After 3 successful practice presentations, the official block
% begins. The practice presentation responses are discarded and not passed
% to Quest.
%
% You can restore the old behavior by setting o.practicePresentations=0.
% After the practice, the block estimates threshold by the same procedure
% whether o.practicePresentation is 0 or 3.
% NOT SET BY USER
o.deviceIndex=-3; % all keyboard and keypad devices
o.easyCount=0; % Number of easy presentations
o.guessCount=0; % Number of artificial guess responses
o.quitBlock=false;
o.quitExperiment=false;
o.script='';
o.scriptFullFileName='';
o.scriptName='';
o.targetFontNumber=[];
o.targetHeightOverWidth=nan;
o.actualDurationSec=[];
o.actualDurationGetSecsSec=[];
o.actualDurationVBLSec=[];
%% PROCESS INPUT.
% o is a single struct, and oIn may be an array of structs.
% Create oo, which replicates o for each condition.
conditions=length(oIn);
clear oo
oo(1:conditions)=o;
inputFields=fieldnames(o);
clear o; % Thus MATLAB will flag an error if we accidentally try to use "o".
% For each condition, all fields in the user-supplied "oIn" overwrite
% corresponding fields in "o". We ignore any field in oIn that is not
% already defined in o. We warn of any unknown field that we ignore, unless
% it's a known output field. It might be a typo for an input fields, or an
% obsolete parameter.
outputFields={'beginSecs' 'beginningTime' 'cal' 'dataFilename' ...
'dataFolder' 'eccentricityXYPix' 'fix' 'functionNames' ...
'keyboardNameAndTransport' 'minimumSizeDeg' 'minimumSpacingDeg' ...
'minimumViewingDistanceCm' 'normalAcuityDeg' ...
'normalCrowdingDistanceDeg' 'presentations' 'q' 'responseCount' ...
'responseKeyCodes' 'results' 'screen' 'snapshotsFolder' 'spacings' ...
'spacingsSequence' 'targetFontHeightOverNominal' 'targetPix' ...
'textSize' 'totalSecs' 'unknownFields' 'validKeyNames' ...
'nativeHeight' 'nativeWidth' 'resolution' 'maximumViewingDistanceCm' ...
'minimumScreenSizeXYDeg' 'typicalThesholdSizeDeg' ...
'computer' 'matlab' 'psychtoolbox' 'trialData' 'hasWirelessKeyboard' ...
'standardDrawTextPlugin' 'drawTextWarning' 'oldResolution' ...
'targetSizeIsHeight' ...
'maxRepetition' 'practiceCountdown' 'flankerLetter' 'row' ...
'fixationOnScreen' 'fixationXYPix' 'nearPointXYPix' ...
'pixPerCm' 'pixPerDeg' 'stimulusRect' 'targetXYPix' 'textLineLength' ...
'speakInstructions' 'fixationOnScreen' 'fixationIsOffscreen' ...
'okToShiftCoordinates' 'responseTextWidth' ...
'eccentricityDegVector' 'flankingDegVector' 'minRecommendedViewingDistanceCm' 'screenSizeCm' 'screenSizeDeg' ...
};
unknownFields=cell(0);
for oi=1:conditions
fields=fieldnames(oIn(oi));
oo(oi).unknownFields=cell(0);
for i=1:length(fields)
if ismember(fields{i},inputFields)
oo(oi).(fields{i})=oIn(oi).(fields{i});
elseif ~ismember(fields{i},outputFields)
unknownFields{end+1}=fields{i};
oo(oi).unknownFields{end+1}=fields{i};
end
end
oo(oi).unknownFields=unique(oo(oi).unknownFields);
end % for oi=1:conditions
unknownFields=unique(unknownFields);
if ~isempty(unknownFields)
error(['Unknown o fields:' sprintf(' %s',unknownFields{:}) '.']);
end
if oo(1).quitExperiment
% Quick return. We're skipping every block in the session.
keepWindowOpen=false;
oo=SortFields(oo);
return
end
skipScreenCalibration=oo(1).skipScreenCalibration; % Global used by CloseWindowsAndCleanup.
% clear Screen % might help get appropriate restore after using Screen('Resolution');
Screen('Preference','SuppressAllWarnings',1);
Screen('Preference','VisualDebugLevel',0);
Screen('Preference','Verbosity',0); % Mute Psychtoolbox's INFOs and WARNINGs
% Screen('Preference','SkipSyncTests',1);
%% Set up defaults. Clumsy.
for oi=1:conditions
if ~ismember(oo(oi).thresholdParameter,{'spacing' 'size'})
error('Illegal value ''%s'' of o.thresholdParameter.',oo(oi).thresholdParameter);
end
end
for oi=1:conditions
switch oo(oi).task
case {'read' 'readAloud' 'partialReport'}
oo(oi).relationOfSpacingToSize='fixed by font';
end
if ~isfinite(oo(oi).targetSizeIsHeight)
switch oo(oi).thresholdParameter
case 'size'
oo(oi).targetSizeIsHeight=true;
case 'spacing'
oo(oi).targetSizeIsHeight=false;
end
end
end % for oi=1:conditions
for oi=1:conditions
if oo(oi).practicePresentations>0
if oo(oi).repeatedTargets
oo(oi).maxRepetition=1;
else
oo(oi).maxRepetition=0;
end
oo(oi).practiceCountdown=oo(oi).practicePresentations;
else
oo(oi).practiceCountdown=0;
oo(oi).maxRepetition=inf;
end
end % for oi=1:conditions
for oi=1:conditions
if ~(oo(oi).maxLines>=1 && round(oo(oi).maxLines)==oo(oi).maxLines && oo(oi).maxLines~=2 )
error('%d: o.maxLines==%.1f, but should be an integer 1,3,4,... inf.',oi,oo(oi).maxLines);
end
end
Screen('Preference','TextAntiAliasing',1);
SoundVolume(oo(1).soundVolume);
soundMuting=SoundMuting;
if oo(1).soundVolume>0 && ~isempty(soundMuting) && soundMuting
warning('SoundVolume %.2f is greater than zero, but SoundMuting is true.',oo(1).soundVolume);
end
% Unfortunately Screen requires that this true/false argument be cast as
% double.
for oi=1:length(oo)
if islogical(oo(oi).dontWait)
oo(oi).dontWait=double(oo(oi).dontWait);
end
end
%% Set up KbCheck.
% Calling ListenChar(2) tells the MATLAB console/editor to ignore what we
% type, so we don't inadvertently echo observer responses into the Command
% window or any open file. We use this mode while use the KbCheck family of
% functions to collect keyboard responses. When we exit, we must reenable
% the keyboard by calling ListenChar() or hitting Control-C.
ListenChar(2); % no echo
KbName('UnifyKeyNames');
RestrictKeysForKbCheck([]);
escapeKeyCode=KbName('ESCAPE');
spaceKeyCode=KbName('space');
returnKeyCode=KbName('Return');
graveAccentKeyCode=KbName('`~');
escapeChar=char(27);
graveAccentChar='`';
for oi=1:conditions
if oo(oi).labelAnswers
if length(oo(oi).alphabet)>length(oo(oi).responseLabels)
error('o.labelAnswers is true, but o.alphabet is longer than o.responseLabels: %d > %d.',...
length(oo(oi).alphabet),length(oo(oi).responseLabels));
end
oo(oi).validResponseLabels=oo(oi).responseLabels(1:length(oo(oi).alphabet));
oo(oi).validKeyNames=KeyNamesOfCharacters(oo(oi).validResponseLabels);
else
oo(oi).validKeyNames=KeyNamesOfCharacters(oo(oi).alphabet);
end
for i=1:length(oo(oi).validKeyNames)
oo(oi).responseKeyCodes(i)=KbName(oo(oi).validKeyNames{i}); % This returns keyCode as integer.
end
if isempty(oo(oi).responseKeyCodes)
error('No valid o.responseKeyCodes for responding.');
end
end % for oi=1:conditions
%% SET UP SCREEN
% The screen size in cm is valuable when the OS provides it, via the
% Psychtoolbox, but it's sometimes wrong on Windows computers, and may not
% always be available for external monitors. So we allow users to measure
% it themselves and provide it in the o struct as o.measuredScreenWidthCm
% and o.measuredScreenHeightCm.
[screenWidthMm,screenHeightMm]=Screen('DisplaySize',oo(1).screen);
if isfinite(oo(1).measuredScreenWidthCm)
screenWidthCm=oo(1).measuredScreenWidthCm;
else
screenWidthCm=screenWidthMm/10;
end
if isfinite(oo(1).measuredScreenHeightCm)
screenHeightCm=oo(1).measuredScreenHeightCm;
else
screenHeightCm=screenHeightMm/10;
end
% I don't understand why this works correctly without 1 at the end of the
% call to Screen 'Rect'. DGP March 23, 2020.
screenRect=Screen('Rect',oo(1).screen);
if oo(1).useFractionOfScreenToDebug
% We want to simulate the full screen, and what we would normally see
% in it, shrunken into a tiny fraction. So we use a reduced number of
% pixels, but we pretend to retain the same screen size in cm, and
% angular subtense.
r=round(oo(oi).useFractionOfScreenToDebug*screenRect);
screenRect=AlignRect(r,screenRect,'bottom','right');