Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions tools/psychopy/01_fmri_interval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from psychopy_mri_emulator import launchScan
from psychopy import visual, core, event, logging

logging.console.setLevel(logging.INFO)

# Create a window
win = visual.Window([800, 600], fullscr=False)

# Create a global clock to sync
globalClock = core.Clock()

# MRI scanner settings
MR_settings = {
'TR': 0.2, # time between volumes (sec)
'volumes': 5, # number of volumes to simulate
'sync': '5', # key representing scanner pulse
'skip': 0,
'sound': False
}

logging.info("Starting fMRI scan emulator in test interval mode")

seq = 0
# Wait for first sync pulse (Test mode will generate them automatically)
seq += launchScan(win, MR_settings, globalClock=globalClock,
mode='Test', log=True)
logging.info(f"Pulse[0], initial detected at {globalClock.getTime():.3f} sec")

# Main loop to react to pulses
while True:
keys = event.getKeys()
if MR_settings['sync'] in keys:
logging.info(f"Pulse[{seq}] detected at {globalClock.getTime():.3f} sec")
seq += 1
if seq >= MR_settings['volumes']:
logging.info("All volumes acquired.")
break
if 'escape' in keys:
logging.info("Experiment terminated by user (Escape pressed)")
break

logging.info("Done.")
win.close()
core.quit()
106 changes: 106 additions & 0 deletions tools/psychopy/02_fmri_series.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from psychopy_mri_emulator import launchScan
from psychopy import visual, core, event, logging

logging.console.setLevel(logging.INFO)


# common functions
def generate_sim_responses(
n_series: int = 3,
pulses_per_series: int = 5,
intra_pulse_delay: float = 0.2,
inter_series_delay: float = 3.0,
initial_delay: float = 2.0,
key: str = '5' # default MRI trigger key
):
"""
Generate simResponses for PsychoPy's launchScan.

Parameters
----------
n_series : int
Number of series (blocks of pulses).
pulses_per_series : int
Number of pulses in each series.
intra_pulse_delay : float
Delay between pulses within a series (seconds).
inter_series_delay : float
Delay between consecutive series (seconds).
initial_delay : float
Delay before the first series starts (seconds).
key : str
Key name to simulate (default '5', typical for scanner triggers).

Returns
-------
list of (float, str)
A list suitable for simResponses.
"""
sim_responses = [(0.0, "0")] # initial dummy response
t = initial_delay

for s in range(n_series):
for p in range(pulses_per_series):
sim_responses.append((t, key))
t += intra_pulse_delay
t += inter_series_delay # pause before next series

return sim_responses



# Create a window
win = visual.Window([800, 600], fullscr=False)

# Create a global clock to sync
globalClock = core.Clock()

# Generate simulated series parameters
n_series = 3
pulses_per_series = 5
intra_pulse_delay = 0.2
inter_series_delay = 3.0
initial_delay = 2.0
sim_responses = generate_sim_responses(n_series, pulses_per_series,
intra_pulse_delay, inter_series_delay,
initial_delay, key='5')
n_pulses = n_series * pulses_per_series

# MRI scanner settings
MR_settings = {
'TR': 1000.0, # time between volumes (sec)
'volumes': n_pulses, # number of volumes to simulate
'sync': '5', # key representing scanner pulse
'skip': n_pulses,
'sound': False
}

logging.info("Starting fMRI scan emulator in test series mode")
logging.info(f"Simulating {n_series} series of {pulses_per_series} pulses each "
f"({n_pulses} total)")
# logging.debug(f"simResponses: {sim_responses}")


seq = 0
# Wait for first sync pulse (Test mode will generate them automatically)
seq += launchScan(win, MR_settings, globalClock=globalClock,
simResponses=sim_responses,
mode='Test', log=True)
logging.info(f"Pulse[0], initial detected at {globalClock.getTime():.3f} sec")

# Main loop to react to pulses
while True:
keys = event.getKeys()
if MR_settings['sync'] in keys:
logging.info(f"Pulse[{seq}] detected at {globalClock.getTime():.3f} sec")
seq += 1
if seq >= MR_settings['volumes']:
logging.info("All volumes acquired.")
break
if 'escape' in keys:
logging.info("Experiment terminated by user (Escape pressed)")
break

logging.info("Done.")
win.close()
core.quit()
44 changes: 44 additions & 0 deletions tools/psychopy/03_fmri_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from psychopy_mri_emulator import launchScan
from psychopy import visual, core, event, logging

logging.console.setLevel(logging.INFO)

# Create a window
win = visual.Window([800, 600], fullscr=False)

# Create a global clock to sync
globalClock = core.Clock()

# MRI scanner settings
MR_settings = {
'TR': 0.2, # time between volumes (sec)
'volumes': 5, # number of volumes to simulate
'sync': '5', # key representing scanner pulse
'skip': 0,
'sound': False
}

logging.info("Starting fMRI scan emulator in scan event mode")

seq = 0
# Wait for first sync pulse
seq += launchScan(win, MR_settings, globalClock=globalClock,
mode='Scan', log=True)
logging.info(f"Pulse[0], initial detected at {globalClock.getTime():.3f} sec")

# Main loop to react to pulses
while True:
keys = event.getKeys()
if MR_settings['sync'] in keys:
logging.info(f"Pulse[{seq}] detected at {globalClock.getTime():.3f} sec")
seq += 1
if seq >= MR_settings['volumes']:
logging.info("All volumes acquired.")
break
if 'escape' in keys:
logging.info("Experiment terminated by user (Escape pressed)")
break

logging.info("Done.")
win.close()
core.quit()
106 changes: 106 additions & 0 deletions tools/psychopy/psychopy_script_notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@

# Overview

On Linux used `reprostim` dev venv and hatch with PsychoPy v2024.2.5:

```shell
hatch run psychopy
```

To install `MRI emulator` plugin, you can install it via PsychoPy Coder GUI:

- In PsychoPy Coder select "Tools" -> "Plugin/packages manager..." -> "MRI emulator" -> "Install" and then restart PsychoPy.

or install it manually:

```shell
pip install psychopy-mri-emulator
```

# Environments tested


## [1]
Ubuntu 24.04.2 LTS
PsychoPy v2024.2.4
Problem:
```
Welcome to PsychoPy3!
v2024.2.4
## Running: /home/vmelnik/Projects/Dartmouth/branches/reprostim/tools/psychopy/01_fmri_interval.py ##
pygame 2.6.1 (SDL 2.28.4, Python 3.10.18)
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
9.0616 WARNING Monitor specification not found. Creating a temporary one...
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/tools/psychopy/01_fmri_interval.py", line 7, in <module>
win = visual.Window([800, 600], fullscr=False)
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/visual/window.py", line 480, in __init__
self.backend = backends.getBackend(win=self, backendConf=backendConf)
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/visual/backends/__init__.py", line 48, in getBackend
Backend = plugins.resolveObjectFromName(useBackend, __name__)
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/plugins/__init__.py", line 202, in resolveObjectFromName
importlib.import_module(path)
File "/usr/lib/python3.10/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 883, in exec_module
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/visual/backends/pygletbackend.py", line 44, in <module>
if pyglet.version < '1.4':
AttributeError: module 'pyglet' has no attribute 'version'
Exception ignored in: <function Window.__del__ at 0x779800419120>
Traceback (most recent call last):
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/visual/window.py", line 665, in __del__
self.close()
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/visual/window.py", line 2657, in close
self.backend.close() # moved here, dereferencing the window prevents
AttributeError: 'NoneType' object has no attribute 'close'
################# Experiment ended with exit code 1 [pid:6335] #################
```

## [2]

Ubuntu 24.04.2 LTS
PsychoPy v2024.2.5
Works fine under `hatch run psychopy`

## [3]

MacOS 12.7.6
PsychoPy v2024.2.5

In case of standalone PsychoPy e.g. on MacOS, you can install the package via

OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES open -a PsychoPy
- Open PsychoPy coder.
- Go to the Tools menu → Plugin/packages manager…
- In the dialog, type: psychopy-mri-emulator


Failed with error:
```
Welcome to PsychoPy3!
v2024.2.5
87.6741 INFO Investigating repo at /Users/vmelnik/Projects/Dartmouth/branches/reprostim
87.7475 WARNING We found a repository at /Users/vmelnik/Projects/Dartmouth/branches/reprostim but it doesn't point to gitlab.pavlovia.org. You could create that as a remote to sync from PsychoPy.
## Running: /Users/vmelnik/Projects/Dartmouth/branches/reprostim/tools/psychopy/01_fmri_interval.py ##
2025-09-04 17:02:35.163 python[56653:2052484] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'nextEventMatchingMask should only be called from the Main Thread!'
*** First throw call stack:
(
0 CoreFoundation 0x00007ff814afa6e3 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007ff81485a8bb objc_exception_throw + 48
2 AppKit 0x00007ff8174bae0d -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 4633
3 libpython3.10.dylib 0x00000001051c6782 ffi_call_unix64 + 82
4 ??? 0x000070000a15b010 0x0 + 123145471504400
)
libc++abi: terminating with uncaught exception of type NSException
################ Experiment ended with exit code -6 [pid:56653] ################
```





Loading