|
14 | 14 | class VisualPatternReversalVEP(BlockExperiment): |
15 | 15 |
|
16 | 16 | def __init__(self, display_refresh_rate: int, eeg: Optional[EEG] = None, save_fn=None, |
17 | | - block_duration_seconds=50, block_trial_size: int=100, n_blocks: int=4, use_vr=False, use_fullscr=True): |
| 17 | + block_duration_seconds=50, block_trial_size: int=100, n_blocks: int=4, use_vr=False, use_fullscr=True, |
| 18 | + use_optode=False): |
18 | 19 |
|
19 | 20 | self.display_refresh_rate = display_refresh_rate |
| 21 | + self.use_optode = use_optode |
20 | 22 | soa=0.5 |
21 | 23 | iti=0 |
22 | 24 | jitter=0 |
@@ -100,6 +102,21 @@ def load_stimulus(self) -> Dict[str, Any]: |
100 | 102 | create_checkerboard = self.create_monitor_checkerboard |
101 | 103 | size = (self.window_size[1], self.window_size[1]) |
102 | 104 |
|
| 105 | + # Optode sync patch: small white/black square in the bottom-left corner. |
| 106 | + # Alternates polarity with each checkerboard reversal so a photodiode |
| 107 | + # taped to this corner produces a TTL pulse on every stimulus onset. |
| 108 | + # Only created for the monitor path — VR uses compositor timestamps instead. |
| 109 | + if self.use_optode and not self.use_vr: |
| 110 | + patch_size = 50 # pixels |
| 111 | + x = -self.window.size[0] / 2 + patch_size / 2 |
| 112 | + y = -self.window.size[1] / 2 + patch_size / 2 |
| 113 | + self.optode_patch = visual.Rect( |
| 114 | + self.window, width=patch_size, height=patch_size, |
| 115 | + pos=(x, y), units='pix', fillColor='white' |
| 116 | + ) |
| 117 | + else: |
| 118 | + self.optode_patch = None |
| 119 | + |
103 | 120 | # The surrounding / periphery needs to be dark when not using vr. |
104 | 121 | # Also used for covering eye which is not being stimulated. |
105 | 122 | self.black_background = visual.Rect(self.window, |
@@ -215,6 +232,13 @@ def present_stimulus(self, idx: int): |
215 | 232 | if self.use_vr: |
216 | 233 | self.window.setBuffer(closed_eye) |
217 | 234 | self.black_background.draw() |
| 235 | + |
| 236 | + # Alternate sync patch polarity with each reversal so the photodiode |
| 237 | + # fires on every checkerboard flip, not just odd or even frames. |
| 238 | + if self.optode_patch is not None: |
| 239 | + self.optode_patch.fillColor = 'white' if checkerboard_frame == 0 else 'black' |
| 240 | + self.optode_patch.draw() |
| 241 | + |
218 | 242 | self.window.flip() |
219 | 243 |
|
220 | 244 | # Use compositor-reported predicted display time when available (VR path). |
|
0 commit comments