Skip to content
Merged
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
Binary file modified .coverage
Binary file not shown.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.ipynb_checkpoints
*.pyc
.mypy_cache
.pytest_cache
.pytest_cache
/htmlcov
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ repos:
rev: v4.3.21
hooks:
- id: isort
exclude: tests/
- repo: https://github.com/ambv/black
rev: stable
hooks:
Expand Down
4 changes: 3 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ include requirements.txt
include cardioception/HRD/Images/*
include cardioception/HRD/Sounds/*
include cardioception/HBC/Images/*
include cardioception/HBC/Sounds/*
include cardioception/HBC/Sounds/*
include cardioception/notebooks/data/HBC/*
include cardioception/notebooks/data/HRD/*
148 changes: 101 additions & 47 deletions README.md

Large diffs are not rendered by default.

Empty file.
Empty file.
114 changes: 72 additions & 42 deletions cardioception/HBC/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,72 +29,85 @@ def getParameters(
----------
participant : str
Subject ID. Default is 'exteroStairCase'.
session : int
Session number. Default to '001'.
resultPath : str or None
Where to save the results.
screenNb : int
Screen number. Used to parametrize py:func:`psychopy.visual.Window`.
Default is set to 0.
serialPort: str
The USB port where the pulse oximeter is plugged. Should be written as
a string e.g., 'COM3', 'COM4'. If set to *None*, the pulse oximeter
will be automatically detected. using the
:py:func:`systole.recording.findOximeter()` function.
taskVersion : str or None
Task version to run. Can be 'Garfinkel', 'Shandry', 'test' or None.
resultPath : str or None
Where to save the results.
session : int
Session number. Default to '001'.
setup : str
Context of oximeter recording. Behavioral will record through a Nonin
pulse oximeter, *fMRI* will record through BrainVision amplifier
through TCP/IP conneciton. *test* will use pre-recorded pulse time
series (for testing only).
systole_kw : dict
Additional keyword arguments for :py:class:`systole.recorder.Oxmeter`.
taskVersion : str or None
Task version to run. Can be 'Garfinkel', 'Shandry', 'test' or None.

Attributes
----------
restPeriod : bool
If *True*, a resting period will be proposed before the task.
restLength : int
The length of the resting period (seconds). Default is 300 seconds.
screenNb : int
The screen number (Psychopy parameter). Default set to 0.
randomize : bool
If `True` (default), will randomize the order of the conditions. If
taskVersion is not None, will use the default task parameter instead.
startKey : str
The key to press to start the task and go to next steps.
rating : bool
If `True` (default), will add a rating scale after the evaluation.
conditions : 1d array-like of str
The conditions. Can be 'Rest', 'Training' or 'Count'.
confScale : list
The range of the confidence rating scale.
heartLogo : `psychopy.visual.ImageStim`
Image presented during resting conditions.
labelsRating : list
The labels of the confidence rating scale.
taskVersion : str or None
Task version to run. Can be 'Garfinkel', 'Shandry' or None.
times : 1d array-like of int
Length of trials, in seconds.
conditions : 1d array-like of str
The conditions. Can be 'Rest', 'Training' or 'Count'.
subjectID : str
Subject identifiant.
subjectNumber : int
Subject reference number.
noteStart : psychopy.sound.Sound instance
The sound that will be played when trial starts.
noteStop : psychopy.sound.Sound instance
The sound that will be played when trial ends.
path : str
The task working directory.
results : str
randomize : bool
If `True` (default), will randomize the order of the conditions. If
taskVersion is not None, will use the default task parameter instead.
rating : bool
If `True` (default), will add a rating scale after the evaluation.
restLength : int
The length of the resting period (seconds). Default is 300 seconds.
restLogo : `psychopy.visual.ImageStim`
Image presented during resting conditions.
restPeriod : bool
If *True*, a resting period will be proposed before the task.
resultPath : str
The subject result directory.
note : `psychopy.sound`
The sound played at trial start and trial end.
win : `psychopy.visual.Window`
Window where to present stimuli.
screenNb : int
The screen number (Psychopy parameter). Default set to 0.
serial : `serial.Serial`
The serial port used to record the PPG activity.
restLogo : `psychopy.visual.ImageStim`
Image presented during resting conditions.
heartLogo : `psychopy.visual.ImageStim`
Image presented during resting conditions.
startKey : str
The key to press to start the task and go to next steps.
taskVersion : str or None
Task version to run. Can be 'Garfinkel', 'Shandry', 'test' or None.
texts : dict
Dictionnary containing the texts to be presented.
textSize : float
Text size.
triggers : dict
Dictionary {str, callable or None}. The function will be executed
before the corresponding trial sequence. The default values are
`None` (no trigger sent).
* `"trialStart"`
* `"trialStop"`
* `"listeningStart"`
* `"listeningStop"`
* `"decisionStart"`
* `"decisionStop"`
* `"confidenceStart"`
* `"confidenceStop"`
times : 1d array-like of int
Length of trials, in seconds.
win : `psychopy.visual.window`
The window in which to draw objects.
"""
parameters: Dict[str, Any] = {}
parameters["restPeriod"] = True
Expand All @@ -106,6 +119,21 @@ def getParameters(
parameters["labelsRating"] = ["Guess", "Certain"]
parameters["taskVersion"] = taskVersion
parameters["results_df"] = pd.DataFrame({})
parameters["setup"] = setup

# Initialize triggers dictionary with None
# Some or all can later be overwrited with callable
# sending the information needed.
parameters["triggers"] = {
"trialStart": None,
"trialStop": None,
"listeningStart": None,
"listeningStop": None,
"decisionStart": None,
"decisionStop": None,
"confidenceStart": None,
"confidenceStop": None,
}

# Experimental design - can choose between a version based on recent
# papers from Sarah Garfinkel's group, or the classic Schandry approach.
Expand Down Expand Up @@ -149,30 +177,32 @@ def getParameters(
parameters["noteStart"] = sound.Sound(
pkg_resources.resource_filename("cardioception.HBC", "Sounds/start.wav")
)
parameters["noteEnd"] = sound.Sound(

parameters["noteStop"] = sound.Sound(
pkg_resources.resource_filename("cardioception.HBC", "Sounds/stop.wav")
)

# Open window
if parameters["setup"] == "test":
fullscr = False
parameters["win"] = visual.Window(screen=screenNb, fullscr=fullscr, units="height")
parameters["win"].mouseVisible = False

parameters["restLogo"] = visual.ImageStim(
win=parameters["win"],
units="height",
image=os.path.dirname(__file__) + "/Images/rest.png",
image=pkg_resources.resource_filename(__name__, "Images/rest.png"),
pos=(0.0, -0.2),
)
parameters["restLogo"].size *= 0.15
parameters["heartLogo"] = visual.ImageStim(
win=parameters["win"],
units="height",
image=os.path.dirname(__file__) + "/Images/heartbeat.png",
image=pkg_resources.resource_filename(__name__, "Images/heartbeat.png"),
pos=(0.0, -0.2),
)
parameters["heartLogo"].size *= 0.05

parameters["setup"] = setup
if setup == "behavioral":
# PPG recording
if serialPort is None:
Expand Down
47 changes: 29 additions & 18 deletions cardioception/HBC/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

import numpy as np
import pandas as pd
from psychopy import core, event, visual
from psychopy import core, event, sound, visual


def run(
parameters: dict,
confidenceRating: bool = True,
runTutorial: bool = True,
win: Optional[visual.Window] = None,
):
Expand All @@ -19,13 +18,11 @@ def run(
----------
parameters : dict
Task parameters.
confidenceRating : bool
Whether the trial show include a confidence rating scale.
tutorial : bool
If *True*, will present a tutorial with 10 training trial with feedback
If `True`, will present a tutorial with 10 training trial with feedback
and 5 trials with confidence rating.
win : `psychopy.visual.Window`
Window where to present stimuli.
win : `psychopy.visual.window` or None
The window in which to draw objects.
"""
if win is None:
win = parameters["win"]
Expand All @@ -44,10 +41,14 @@ def run(
range(0, len(parameters["conditions"])),
):

parameters["triggers"]["trialStart"] # Send trigger or None

nCount, confidence, confidenceRT = trial(
condition, duration, nTrial, parameters, win
)

parameters["triggers"]["trialStop"] # Send trigger or None

# Store results in a DataFrame
parameters["results_df"] = parameters["results_df"].append(
pd.DataFrame(
Expand All @@ -65,7 +66,7 @@ def run(

# Save the results at each iteration
parameters["results_df"].to_csv(
parameters["results"]
parameters["resultPath"]
+ "/"
+ parameters["participant"]
+ parameters["session"]
Expand All @@ -75,7 +76,7 @@ def run(

# Save results
parameters["results_df"].to_csv(
parameters["results"]
parameters["resultPath"]
+ "/"
+ parameters["participant"]
+ parameters["session"]
Expand Down Expand Up @@ -114,8 +115,8 @@ def trial(
Trial number.
parameters : dict
Task parameters.
win : `psychopy.visual.Window`
Window where to present stimuli.
win : `psychopy.visual.window` or None
The window in which to draw objects.

Returns
-------
Expand Down Expand Up @@ -177,6 +178,7 @@ def trial(
# Add event marker
parameters["oxiTask"].channels["Channel_0"][-1] = 1
parameters["noteStart"].play()
parameters["triggers"]["listeningStart"]
core.wait(1)

# Record for a desired time length
Expand All @@ -187,7 +189,8 @@ def trial(
# Add event marker
parameters["oxiTask"].readInWaiting()
parameters["oxiTask"].channels["Channel_0"][-1] = 2
parameters["noteEnd"].play()
parameters["noteStop"].play()
parameters["triggers"]["listeningStop"]
core.wait(3)
parameters["oxiTask"].readInWaiting()

Expand All @@ -196,7 +199,7 @@ def trial(

# Save recording
parameters["oxiTask"].save(
parameters["results"]
parameters["resultPath"]
+ "/"
+ parameters["participant"]
+ str(nTrial)
Expand All @@ -218,6 +221,8 @@ def trial(
messageCount.draw()
win.flip()

parameters["triggers"]["decisionStart"] # Send trigger or None

nCounts = ""
while True:

Expand Down Expand Up @@ -295,6 +300,8 @@ def trial(
messageCount.draw()
win.flip()

parameters["triggers"]["decisionStop"] # Send trigger or None

##############
# Rating scale
##############
Expand All @@ -316,12 +323,14 @@ def trial(
text=parameters["texts"]["confidence"],
height=parameters["textSize"],
)
parameters["triggers"]["confidenceStart"]
while ratingScale.noResponse:
message.draw()
ratingScale.draw()
win.flip()
confidence = ratingScale.getRating()
confidenceRT = ratingScale.getRT()
parameters["triggers"]["confidenceStop"]

finalCount = int(nCounts) if nCounts else None

Expand All @@ -335,8 +344,8 @@ def tutorial(parameters: dict, win: Optional[visual.Window] = None):
----------
parameters : dict
Task parameters.
win : `psychopy.visual.Window`
Window where to present stimuli.
win : `psychopy.visual.window` or None
The window in which to draw objects.
"""
if win is None:
win = parameters["win"]
Expand Down Expand Up @@ -500,8 +509,10 @@ def rest(
----------
parameters : dict
Task parameters.
win : `psychopy.visual.Window`
Window where to present stimuli.
duration : float
Duration or the recording (seconds).
win : `psychopy.visual.window` or None
The window in which to draw objects.
"""
if win is None:
win = parameters["win"]
Expand All @@ -523,5 +534,5 @@ def rest(

# Save recording
parameters["oxiTask"].save(
parameters["results"] + "/" + parameters["participant"] + "_Rest"
parameters["resultPath"] + "/" + parameters["participant"] + "_Rest"
)
Loading