Skip to content

Commit 34d40b7

Browse files
author
LegrandNico
committed
Add support for triggers (HBC and HRD)
1 parent b0c11df commit 34d40b7

File tree

7 files changed

+155
-38
lines changed

7 files changed

+155
-38
lines changed

.coverage

0 Bytes
Binary file not shown.

README.md

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The Cardioception Python Package - Measuring Interoception with Psychopy - imple
1212

1313
These tasks can run using minimal experimental settings: a computer and a recording device to monitor the heart rate of the participant. The default version of the task use the [Nonin 3012LP Xpod USB pulse oximeter](https://www.nonin.com/products/xpod/) together with [Nonin 8000SM 'soft-clip' fingertip sensors](https://www.nonin.com/products/8000s/). This sensor can be plugged directly into the stim PC via USB and will work with Cardioception without any additional coding required.
1414

15-
The tasks can also integrate easily with other recording devices and experimental settings (ECG, M/EEG, fMRI...). See the *Send triggers* session for details.
15+
The tasks can also integrate easily with other recording devices and experimental settings (ECG, M/EEG, fMRI...). See *Sending triggers* below for details.
1616

1717
##How to cite?
1818

@@ -26,19 +26,21 @@ If you are using [systole](https://systole-docs.github.io/) to interact with you
2626
2727

2828
# Installation
29-
## Using the Python Package Index
29+
**Using the Python Package Index**
3030
* The most recent version can be installed uing:
3131
`pip install cardioception`
3232
* The current development branch can be installed using
3333
`pip install git+https://github.com/embodied-computation-group/Cardioception.git`
3434

35-
## Downloading the ZIP file
35+
**Downloading the ZIP file**
3636

3737
<img src="/images/downloadZIP.png" align="left" alt="metadPy" height="200" HSPACE=30>
38+
3839
You can also download the most recent version by downloading the repository as a .zip file.
3940

4041
After extracting the content of the file, the package can be installed via the command line by running `pip install .`. Note that this command should be executed when your terminal run inside the extracted folder. You can navigate through your local folder using the command `cd [path to your folder]`.
4142

43+
<br clear="left"/>
4244

4345
## Dependencies
4446

@@ -70,6 +72,7 @@ The version provided here are the ones used when testing and runing cardioceptio
7072

7173
Cardioception will automatically copy the images and sound files necessary to run the task correctly (~ 160 Mo). These files will be removed if you uninstall the package using `pip uninstall cardioception`.
7274

75+
# Package modularity
7376
## Physiological recording
7477

7578
Both the Heartbeat counting task (HBC) and the heart rate discrimination task (HRD) require access to physiological recording device during the task to estimate the heart rate or count the number of heartbeats in a given time window. Cardioception natively supports:
@@ -78,6 +81,45 @@ Both the Heartbeat counting task (HBC) and the heart rate discrimination task (H
7881

7982
The package can easily be extended and integrate other recording devices by providing another recording class that will interface with your own devices (ECG, pulse oximeters, or any king of recording that will offer precise estimation of the cardiac frequency).
8083

84+
## Sending triggers
85+
86+
The package includes function calls at the beginning and the end of each meaningful trial phase (trial start/end, listening start/end, decision start/end, and confidence rating start/end). These function calls are embedded in the `"triggers"` parameter dictionary.
87+
88+
```python
89+
from cardioception.HRD.parameters import getParameters
90+
91+
parameters = getParameters()
92+
parameters["triggers"]
93+
```
94+
`
95+
{'trialStart': None,
96+
'trialStop': None,
97+
'listeningStart': None,
98+
'listeningStop': None,
99+
'decisionStart': None,
100+
'decisionStop': None,
101+
'confidenceStart': None,
102+
'confidenceStop': None}
103+
`
104+
105+
By default, keys are initialized with `None` (no triggers sent). But these values can be overwritten with callable implementing the proper trigger message fitting your setup. This can be done by creating a new function and applying it to the relevant dictionary key.
106+
107+
```python
108+
def sendTrigger(x):
109+
# Function connecting to your device and sending the
110+
# required value. For illustration, we are using a simple print call.
111+
print(f'Send a trigger with value {x}')
112+
113+
114+
# Here we use this function to send triggers with value 1
115+
# at the begining of the listening phase
116+
# and with value 2 at the end of the listening phase
117+
parameters["triggers"]["listeningStart"] = sendTrigger("1")
118+
parameters["triggers"]["listeningStop"] = sendTrigger("2")
119+
```
120+
121+
Note that we are using the function call (`sendTrigger("1")`) and not the function itself (`sendTrigger`) here.
122+
81123
# Run the tasks
82124

83125
Each task contains a `parameters` and a `task` submodule describing the experimental parameters and the Psychopy script respectively. Several changes and adaptation can be parametrized just by passing arguments to the parameters functions. Please refer to the API documentation for details.
@@ -90,7 +132,8 @@ Each task contains a `parameters` and a `task` submodule describing the experime
90132
Once the package has been installed, you can run the task (e.g. here the Heart rate Discrimination task) using the following code snippet:
91133

92134
```python
93-
from cardioception.HRD import parameters, task
135+
from cardioception.HRD.parameters import getParameters
136+
from cardioception.HRD import task
94137

95138
# Set global task parameters
96139
parameters = parameters.getParameters(

cardioception/HBC/parameters.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,22 @@ def getParameters(
9292
Dictionnary containing the texts to be presented.
9393
textSize : float
9494
Text size.
95+
triggers : dict
96+
Dictionary {str, callable or None}. The function will be executed
97+
before the corresponding trial sequence. The default values are
98+
`None` (no trigger sent).
99+
* `"trialStart"`
100+
* `"trialStop"`
101+
* `"listeningStart"`
102+
* `"listeningStop"`
103+
* `"decisionStart"`
104+
* `"decisionStop"`
105+
* `"confidenceStart"`
106+
* `"confidenceStop"`
95107
times : 1d array-like of int
96108
Length of trials, in seconds.
97-
win : `psychopy.visual.Window`
98-
Window where to present stimuli.
99-
109+
win : `psychopy.visual.window`
110+
The window in which to draw objects.
100111
"""
101112
parameters: Dict[str, Any] = {}
102113
parameters["restPeriod"] = True
@@ -110,6 +121,20 @@ def getParameters(
110121
parameters["results_df"] = pd.DataFrame({})
111122
parameters["setup"] = setup
112123

124+
# Initialize triggers dictionary with None
125+
# Some or all can later be overwrited with callable
126+
# sending the information needed.
127+
parameters["triggers"] = {
128+
"trialStart": None,
129+
"trialStop": None,
130+
"listeningStart": None,
131+
"listeningStop": None,
132+
"decisionStart": None,
133+
"decisionStop": None,
134+
"confidenceStart": None,
135+
"confidenceStop": None,
136+
}
137+
113138
# Experimental design - can choose between a version based on recent
114139
# papers from Sarah Garfinkel's group, or the classic Schandry approach.
115140
# The primary difference ebtween the two is the order of trials and the

cardioception/HBC/task.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
def run(
1111
parameters: dict,
12-
confidenceRating: bool = True,
1312
runTutorial: bool = True,
1413
win: Optional[visual.Window] = None,
1514
):
@@ -19,13 +18,11 @@ def run(
1918
----------
2019
parameters : dict
2120
Task parameters.
22-
confidenceRating : bool
23-
Whether the trial show include a confidence rating scale.
2421
tutorial : bool
25-
If *True*, will present a tutorial with 10 training trial with feedback
22+
If `True`, will present a tutorial with 10 training trial with feedback
2623
and 5 trials with confidence rating.
27-
win : `psychopy.visual.Window`
28-
Window where to present stimuli.
24+
win : `psychopy.visual.window` or None
25+
The window in which to draw objects.
2926
"""
3027
if win is None:
3128
win = parameters["win"]
@@ -44,10 +41,14 @@ def run(
4441
range(0, len(parameters["conditions"])),
4542
):
4643

44+
parameters["triggers"]["trialStart"] # Send trigger or None
45+
4746
nCount, confidence, confidenceRT = trial(
4847
condition, duration, nTrial, parameters, win
4948
)
5049

50+
parameters["triggers"]["trialStop"] # Send trigger or None
51+
5152
# Store results in a DataFrame
5253
parameters["results_df"] = parameters["results_df"].append(
5354
pd.DataFrame(
@@ -114,8 +115,8 @@ def trial(
114115
Trial number.
115116
parameters : dict
116117
Task parameters.
117-
win : `psychopy.visual.Window`
118-
Window where to present stimuli.
118+
win : `psychopy.visual.window` or None
119+
The window in which to draw objects.
119120
120121
Returns
121122
-------
@@ -177,6 +178,7 @@ def trial(
177178
# Add event marker
178179
parameters["oxiTask"].channels["Channel_0"][-1] = 1
179180
parameters["noteStart"].play()
181+
parameters["triggers"]["listeningStart"]
180182
core.wait(1)
181183

182184
# Record for a desired time length
@@ -188,6 +190,7 @@ def trial(
188190
parameters["oxiTask"].readInWaiting()
189191
parameters["oxiTask"].channels["Channel_0"][-1] = 2
190192
parameters["noteStop"].play()
193+
parameters["triggers"]["listeningStop"]
191194
core.wait(3)
192195
parameters["oxiTask"].readInWaiting()
193196

@@ -218,6 +221,8 @@ def trial(
218221
messageCount.draw()
219222
win.flip()
220223

224+
parameters["triggers"]["decisionStart"] # Send trigger or None
225+
221226
nCounts = ""
222227
while True:
223228

@@ -295,6 +300,8 @@ def trial(
295300
messageCount.draw()
296301
win.flip()
297302

303+
parameters["triggers"]["decisionStop"] # Send trigger or None
304+
298305
##############
299306
# Rating scale
300307
##############
@@ -316,12 +323,14 @@ def trial(
316323
text=parameters["texts"]["confidence"],
317324
height=parameters["textSize"],
318325
)
326+
parameters["triggers"]["confidenceStart"]
319327
while ratingScale.noResponse:
320328
message.draw()
321329
ratingScale.draw()
322330
win.flip()
323331
confidence = ratingScale.getRating()
324332
confidenceRT = ratingScale.getRT()
333+
parameters["triggers"]["confidenceStop"]
325334

326335
finalCount = int(nCounts) if nCounts else None
327336

@@ -335,8 +344,8 @@ def tutorial(parameters: dict, win: Optional[visual.Window] = None):
335344
----------
336345
parameters : dict
337346
Task parameters.
338-
win : `psychopy.visual.Window`
339-
Window where to present stimuli.
347+
win : `psychopy.visual.window` or None
348+
The window in which to draw objects.
340349
"""
341350
if win is None:
342351
win = parameters["win"]
@@ -500,8 +509,10 @@ def rest(
500509
----------
501510
parameters : dict
502511
Task parameters.
503-
win : `psychopy.visual.Window`
504-
Window where to present stimuli.
512+
duration : float
513+
Duration or the recording (seconds).
514+
win : `psychopy.visual.window` or None
515+
The window in which to draw objects.
505516
"""
506517
if win is None:
507518
win = parameters["win"]

cardioception/HRD/parameters.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ def getParameters(
133133
The task working directory.
134134
referenceTone : callable
135135
Function selecting the reference tones for the exteroceptive condition.
136-
The output should be a single float matching the name of the `.wav` files
137-
(ending with `.0` or `.5`). Default is uniform between 40.0 and 100.0 BPM
138-
(`np.random.choice(np.arange(40, 100, 0.5))`).
136+
The output should be a single float matching the name of the `.wav`
137+
files (ending with `.0` or `.5`). Default is uniform between 40.0 and
138+
100.0 BPM (`np.random.choice(np.arange(40, 100, 0.5))`).
139139
resultPath : str or None
140140
Where to save the results.
141141
serial : PySerial instance
@@ -164,8 +164,20 @@ def getParameters(
164164
Long text elements.
165165
textSize : float
166166
Scalling parameter for text size.
167-
win : Psychopy window instance
168-
The window where to run the task.
167+
triggers : dict
168+
Dictionary {str, callable or None}. The function will be executed
169+
before the corresponding trial sequence. The default values are
170+
`None` (no trigger sent).
171+
* `"trialStart"`
172+
* `"trialStop"`
173+
* `"listeningStart"`
174+
* `"listeningStop"`
175+
* `"decisionStart"`
176+
* `"decisionStop"`
177+
* `"confidenceStart"`
178+
* `"confidenceStop"`
179+
win : `psychopy.visual.window`
180+
The window in which to draw objects.
169181
"""
170182
parameters: Dict[str, Any] = {}
171183
parameters["ExteroCondition"] = exteroception
@@ -191,6 +203,20 @@ def getParameters(
191203
parameters["signal_df"] = pd.DataFrame([]) # Physiological recording
192204
parameters["results_df"] = pd.DataFrame([]) # Behavioral results
193205

206+
# Initialize triggers dictionary with None
207+
# Some or all can later be overwrited with callable
208+
# sending the information needed.
209+
parameters["triggers"] = {
210+
"trialStart": None,
211+
"trialStop": None,
212+
"listeningStart": None,
213+
"listeningStop": None,
214+
"decisionStart": None,
215+
"decisionStop": None,
216+
"confidenceStart": None,
217+
"confidenceStop": None,
218+
}
219+
194220
# Set default path /Results/ 'Subject ID' /
195221
parameters["participant"] = participant
196222
parameters["session"] = session

0 commit comments

Comments
 (0)