Skip to content

Commit bd250eb

Browse files
authored
Merge pull request #38 from LegrandNico/master
version 0.4.0
2 parents bc8b21f + 34d40b7 commit bd250eb

30 files changed

+1896
-16164
lines changed

.coverage

48.7 KB
Binary file not shown.

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.ipynb_checkpoints
22
*.pyc
33
.mypy_cache
4-
.pytest_cache
4+
.pytest_cache
5+
/htmlcov

.pre-commit-config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ repos:
77
rev: v4.3.21
88
hooks:
99
- id: isort
10+
exclude: tests/
1011
- repo: https://github.com/ambv/black
1112
rev: stable
1213
hooks:

MANIFEST.in

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ include requirements.txt
77
include cardioception/HRD/Images/*
88
include cardioception/HRD/Sounds/*
99
include cardioception/HBC/Images/*
10-
include cardioception/HBC/Sounds/*
10+
include cardioception/HBC/Sounds/*
11+
include cardioception/notebooks/data/HBC/*
12+
include cardioception/notebooks/data/HRD/*

README.md

+101-47
Large diffs are not rendered by default.

cardioception/HBC/Images/__init__.py

Whitespace-only changes.

cardioception/HBC/Sounds/__init__.py

Whitespace-only changes.

cardioception/HBC/parameters.py

+72-42
Original file line numberDiff line numberDiff line change
@@ -29,72 +29,85 @@ def getParameters(
2929
----------
3030
participant : str
3131
Subject ID. Default is 'exteroStairCase'.
32-
session : int
33-
Session number. Default to '001'.
32+
resultPath : str or None
33+
Where to save the results.
34+
screenNb : int
35+
Screen number. Used to parametrize py:func:`psychopy.visual.Window`.
36+
Default is set to 0.
3437
serialPort: str
3538
The USB port where the pulse oximeter is plugged. Should be written as
3639
a string e.g., 'COM3', 'COM4'. If set to *None*, the pulse oximeter
3740
will be automatically detected. using the
3841
:py:func:`systole.recording.findOximeter()` function.
39-
taskVersion : str or None
40-
Task version to run. Can be 'Garfinkel', 'Shandry', 'test' or None.
41-
resultPath : str or None
42-
Where to save the results.
42+
session : int
43+
Session number. Default to '001'.
4344
setup : str
4445
Context of oximeter recording. Behavioral will record through a Nonin
4546
pulse oximeter, *fMRI* will record through BrainVision amplifier
4647
through TCP/IP conneciton. *test* will use pre-recorded pulse time
4748
series (for testing only).
4849
systole_kw : dict
4950
Additional keyword arguments for :py:class:`systole.recorder.Oxmeter`.
51+
taskVersion : str or None
52+
Task version to run. Can be 'Garfinkel', 'Shandry', 'test' or None.
5053
5154
Attributes
5255
----------
53-
restPeriod : bool
54-
If *True*, a resting period will be proposed before the task.
55-
restLength : int
56-
The length of the resting period (seconds). Default is 300 seconds.
57-
screenNb : int
58-
The screen number (Psychopy parameter). Default set to 0.
59-
randomize : bool
60-
If `True` (default), will randomize the order of the conditions. If
61-
taskVersion is not None, will use the default task parameter instead.
62-
startKey : str
63-
The key to press to start the task and go to next steps.
64-
rating : bool
65-
If `True` (default), will add a rating scale after the evaluation.
56+
conditions : 1d array-like of str
57+
The conditions. Can be 'Rest', 'Training' or 'Count'.
6658
confScale : list
6759
The range of the confidence rating scale.
60+
heartLogo : `psychopy.visual.ImageStim`
61+
Image presented during resting conditions.
6862
labelsRating : list
6963
The labels of the confidence rating scale.
70-
taskVersion : str or None
71-
Task version to run. Can be 'Garfinkel', 'Shandry' or None.
72-
times : 1d array-like of int
73-
Length of trials, in seconds.
74-
conditions : 1d array-like of str
75-
The conditions. Can be 'Rest', 'Training' or 'Count'.
76-
subjectID : str
77-
Subject identifiant.
78-
subjectNumber : int
79-
Subject reference number.
64+
noteStart : psychopy.sound.Sound instance
65+
The sound that will be played when trial starts.
66+
noteStop : psychopy.sound.Sound instance
67+
The sound that will be played when trial ends.
8068
path : str
8169
The task working directory.
82-
results : str
70+
randomize : bool
71+
If `True` (default), will randomize the order of the conditions. If
72+
taskVersion is not None, will use the default task parameter instead.
73+
rating : bool
74+
If `True` (default), will add a rating scale after the evaluation.
75+
restLength : int
76+
The length of the resting period (seconds). Default is 300 seconds.
77+
restLogo : `psychopy.visual.ImageStim`
78+
Image presented during resting conditions.
79+
restPeriod : bool
80+
If *True*, a resting period will be proposed before the task.
81+
resultPath : str
8382
The subject result directory.
84-
note : `psychopy.sound`
85-
The sound played at trial start and trial end.
86-
win : `psychopy.visual.Window`
87-
Window where to present stimuli.
83+
screenNb : int
84+
The screen number (Psychopy parameter). Default set to 0.
8885
serial : `serial.Serial`
8986
The serial port used to record the PPG activity.
90-
restLogo : `psychopy.visual.ImageStim`
91-
Image presented during resting conditions.
92-
heartLogo : `psychopy.visual.ImageStim`
93-
Image presented during resting conditions.
87+
startKey : str
88+
The key to press to start the task and go to next steps.
89+
taskVersion : str or None
90+
Task version to run. Can be 'Garfinkel', 'Shandry', 'test' or None.
9491
texts : dict
9592
Dictionnary containing the texts to be presented.
9693
textSize : float
9794
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"`
107+
times : 1d array-like of int
108+
Length of trials, in seconds.
109+
win : `psychopy.visual.window`
110+
The window in which to draw objects.
98111
"""
99112
parameters: Dict[str, Any] = {}
100113
parameters["restPeriod"] = True
@@ -106,6 +119,21 @@ def getParameters(
106119
parameters["labelsRating"] = ["Guess", "Certain"]
107120
parameters["taskVersion"] = taskVersion
108121
parameters["results_df"] = pd.DataFrame({})
122+
parameters["setup"] = setup
123+
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+
}
109137

110138
# Experimental design - can choose between a version based on recent
111139
# papers from Sarah Garfinkel's group, or the classic Schandry approach.
@@ -149,30 +177,32 @@ def getParameters(
149177
parameters["noteStart"] = sound.Sound(
150178
pkg_resources.resource_filename("cardioception.HBC", "Sounds/start.wav")
151179
)
152-
parameters["noteEnd"] = sound.Sound(
180+
181+
parameters["noteStop"] = sound.Sound(
153182
pkg_resources.resource_filename("cardioception.HBC", "Sounds/stop.wav")
154183
)
155184

156185
# Open window
186+
if parameters["setup"] == "test":
187+
fullscr = False
157188
parameters["win"] = visual.Window(screen=screenNb, fullscr=fullscr, units="height")
158189
parameters["win"].mouseVisible = False
159190

160191
parameters["restLogo"] = visual.ImageStim(
161192
win=parameters["win"],
162193
units="height",
163-
image=os.path.dirname(__file__) + "/Images/rest.png",
194+
image=pkg_resources.resource_filename(__name__, "Images/rest.png"),
164195
pos=(0.0, -0.2),
165196
)
166197
parameters["restLogo"].size *= 0.15
167198
parameters["heartLogo"] = visual.ImageStim(
168199
win=parameters["win"],
169200
units="height",
170-
image=os.path.dirname(__file__) + "/Images/heartbeat.png",
201+
image=pkg_resources.resource_filename(__name__, "Images/heartbeat.png"),
171202
pos=(0.0, -0.2),
172203
)
173204
parameters["heartLogo"].size *= 0.05
174205

175-
parameters["setup"] = setup
176206
if setup == "behavioral":
177207
# PPG recording
178208
if serialPort is None:

cardioception/HBC/task.py

+29-18
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44

55
import numpy as np
66
import pandas as pd
7-
from psychopy import core, event, visual
7+
from psychopy import core, event, sound, visual
88

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(
@@ -65,7 +66,7 @@ def run(
6566

6667
# Save the results at each iteration
6768
parameters["results_df"].to_csv(
68-
parameters["results"]
69+
parameters["resultPath"]
6970
+ "/"
7071
+ parameters["participant"]
7172
+ parameters["session"]
@@ -75,7 +76,7 @@ def run(
7576

7677
# Save results
7778
parameters["results_df"].to_csv(
78-
parameters["results"]
79+
parameters["resultPath"]
7980
+ "/"
8081
+ parameters["participant"]
8182
+ parameters["session"]
@@ -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
@@ -187,7 +189,8 @@ def trial(
187189
# Add event marker
188190
parameters["oxiTask"].readInWaiting()
189191
parameters["oxiTask"].channels["Channel_0"][-1] = 2
190-
parameters["noteEnd"].play()
192+
parameters["noteStop"].play()
193+
parameters["triggers"]["listeningStop"]
191194
core.wait(3)
192195
parameters["oxiTask"].readInWaiting()
193196

@@ -196,7 +199,7 @@ def trial(
196199

197200
# Save recording
198201
parameters["oxiTask"].save(
199-
parameters["results"]
202+
parameters["resultPath"]
200203
+ "/"
201204
+ parameters["participant"]
202205
+ str(nTrial)
@@ -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"]
@@ -523,5 +534,5 @@ def rest(
523534

524535
# Save recording
525536
parameters["oxiTask"].save(
526-
parameters["results"] + "/" + parameters["participant"] + "_Rest"
537+
parameters["resultPath"] + "/" + parameters["participant"] + "_Rest"
527538
)

0 commit comments

Comments
 (0)