Skip to content

Commit fcab9f3

Browse files
author
Stefano Moia
authored
Merge pull request #19 from tsalo/add-metrics
Add initial set of metrics Merged on agreement
2 parents d5f5148 + e729bc3 commit fcab9f3

File tree

8 files changed

+716
-0
lines changed

8 files changed

+716
-0
lines changed

phys2denoise/due.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# emacs: at the end of the file
2+
# ex: set sts=4 ts=4 sw=4 et:
3+
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### #
4+
"""Stub file for a guaranteed safe import of duecredit constructs if duecredit is not available.
5+
6+
To use it, place it into your project codebase to be imported, e.g. copy as
7+
8+
cp stub.py /path/tomodule/module/due.py
9+
10+
Note that it might be better to avoid naming it duecredit.py to avoid shadowing
11+
installed duecredit.
12+
13+
Then use in your code as
14+
15+
from .due import due, Doi, BibTeX, Text
16+
17+
See https://github.com/duecredit/duecredit/blob/master/README.md for examples.
18+
19+
Origin: Originally a part of the duecredit
20+
Copyright: 2015-2019 DueCredit developers
21+
License: BSD-2
22+
"""
23+
24+
__version__ = "0.0.8"
25+
26+
27+
class InactiveDueCreditCollector(object):
28+
"""Just a stub at the Collector which would not do anything."""
29+
30+
def _donothing(self, *args, **kwargs):
31+
"""Perform no good and no bad."""
32+
pass
33+
34+
def dcite(self, *args, **kwargs):
35+
"""If I could cite I would."""
36+
37+
def nondecorating_decorator(func):
38+
return func
39+
40+
return nondecorating_decorator
41+
42+
active = False
43+
activate = add = cite = dump = load = _donothing
44+
45+
def __repr__(self):
46+
"""Perform magic."""
47+
return self.__class__.__name__ + "()"
48+
49+
50+
def _donothing_func(*args, **kwargs):
51+
"""Perform no good and no bad."""
52+
pass
53+
54+
55+
try:
56+
from duecredit import BibTeX, Doi, Text, Url, due
57+
58+
if "due" in locals() and not hasattr(due, "cite"):
59+
raise RuntimeError("Imported due lacks .cite. DueCredit is now disabled")
60+
except Exception as e:
61+
if not isinstance(e, ImportError):
62+
import logging
63+
64+
logging.getLogger("duecredit").error(
65+
"Failed to import duecredit due to %s" % str(e)
66+
)
67+
# Initiate due stub
68+
due = InactiveDueCreditCollector()
69+
BibTeX = Doi = Url = Text = _donothing_func
70+
71+
# Emacs mode definitions
72+
# Local Variables:
73+
# mode: python
74+
# py-indent-offset: 4
75+
# tab-width: 4
76+
# indent-tabs-mode: nil
77+
# End:

phys2denoise/metrics/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Metrics derived from physiological signals."""
2+
from .cardiac import crf, cardiac_phase
3+
from .chest_belt import rpv, env, rv, rrf, respiratory_phase
4+
from .multimodal import retroicor
5+
6+
__all__ = [
7+
"crf",
8+
"cardiac_phase",
9+
"rpv",
10+
"env",
11+
"rv",
12+
"rrf",
13+
"respiratory_phase",
14+
"retroicor",
15+
]

phys2denoise/metrics/cardiac.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""Denoising metrics for cardio recordings."""
2+
import numpy as np
3+
4+
from .. import references
5+
from ..due import due
6+
7+
8+
def iht():
9+
"""Calculate instantaneous heart rate."""
10+
pass
11+
12+
13+
@due.dcite(references.CHANG_GLOVER_2009)
14+
def crf(samplerate, oversampling=50, time_length=32, onset=0.0, tr=2.0):
15+
"""Calculate the cardiac response function using Chang and Glover's definition.
16+
17+
Parameters
18+
----------
19+
samplerate : :obj:`float`
20+
Sampling rate of data, in seconds.
21+
oversampling : :obj:`int`, optional
22+
Temporal oversampling factor, in seconds. Default is 50.
23+
time_length : :obj:`int`, optional
24+
RRF kernel length, in seconds. Default is 32.
25+
onset : :obj:`float`, optional
26+
Onset of the response, in seconds. Default is 0.
27+
28+
Returns
29+
-------
30+
crf : array-like
31+
Cardiac or "heart" response function
32+
33+
Notes
34+
-----
35+
This cardiac response function was defined in [1]_, Appendix A.
36+
37+
The core code for this function comes from metco2, while several of the
38+
parameters, including oversampling, time_length, and onset, are modeled on
39+
nistats' HRF functions.
40+
41+
References
42+
----------
43+
.. [1] C. Chang & G. H. Glover, "Relationship between respiration,
44+
end-tidal CO2, and BOLD signals in resting-state fMRI," Neuroimage,
45+
issue 47, vol. 4, pp. 1381-1393, 2009.
46+
"""
47+
48+
def _crf(t):
49+
rf = 0.6 * t ** 2.7 * np.exp(-t / 1.6) - 16 * (
50+
1 / np.sqrt(2 * np.pi * 9)
51+
) * np.exp(-0.5 * (((t - 12) ** 2) / 9))
52+
return rf
53+
54+
dt = tr / oversampling
55+
time_stamps = np.linspace(
56+
0, time_length, np.rint(float(time_length) / dt).astype(np.int)
57+
)
58+
time_stamps -= onset
59+
crf_arr = _crf(time_stamps)
60+
crf_arr = crf_arr / max(abs(crf_arr))
61+
return crf_arr
62+
63+
64+
def cardiac_phase(peaks, slice_timings, n_scans, t_r):
65+
"""Calculate cardiac phase from cardiac peaks.
66+
67+
Assumes that timing of cardiac events are given in same units
68+
as slice timings, for example seconds.
69+
70+
Parameters
71+
----------
72+
peaks : 1D array_like
73+
Cardiac peak times, in seconds.
74+
slice_timings : 1D array_like
75+
Slice times, in seconds.
76+
n_scans : int
77+
Number of volumes in the imaging run.
78+
t_r : float
79+
Sampling rate of the imaging run, in seconds.
80+
81+
Returns
82+
-------
83+
phase_card : array_like
84+
Cardiac phase signal.
85+
"""
86+
n_slices = np.shape(slice_timings)
87+
phase_card = np.zeros(n_scans)
88+
89+
for i_slice in range(n_slices):
90+
# find previous cardiac peaks
91+
previous_card_peaks = np.asarray(np.nonzero(peaks < slice_timings[i_slice]))
92+
if np.size(previous_card_peaks) == 0:
93+
t1 = 0
94+
else:
95+
last_peak = previous_card_peaks[0][-1]
96+
t1 = peaks[last_peak]
97+
98+
# find posterior cardiac peaks
99+
next_card_peaks = np.asarray(np.nonzero(peaks > slice_timings[i_slice]))
100+
if np.size(next_card_peaks) == 0:
101+
t2 = n_scans * t_r
102+
else:
103+
next_peak = next_card_peaks[0][0]
104+
t2 = peaks[next_peak]
105+
106+
# compute cardiac phase
107+
phase_card[i_slice] = (2 * np.math.pi * (slice_timings[i_slice] - t1)) / (
108+
t2 - t1
109+
)
110+
111+
return phase_card

0 commit comments

Comments
 (0)