Skip to content

Commit b5f0376

Browse files
authored
Merge pull request #3 from SRSteinkamp/peak_metrics
WIP: Peak metrics
2 parents 2bd4821 + 904fabd commit b5f0376

File tree

3 files changed

+220
-30
lines changed

3 files changed

+220
-30
lines changed

physioqc/__init__.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
11
"""Hopefully importing everything."""
22

3-
import pkgutil
4-
53
from ._version import get_versions
64

7-
SKIP_MODULES = ["tests"]
8-
95
__version__ = get_versions()["version"]
106
del get_versions
11-
12-
__all__ = []
13-
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
14-
if "tests" not in module_name:
15-
__all__.append(module_name)
16-
_module = loader.find_module(module_name).load_module(module_name)
17-
globals()[module_name] = _module

physioqc/metrics/multimodal.py

Lines changed: 200 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
"""These functions compute RETROICOR regressors (Glover et al. 2000)."""
2-
# import file
1+
"""These functions compute various non-modality dependent signal processing metrics."""
32
import numpy as np
3+
import peakdet as pk
44

5-
from .. import references
6-
from ..due import due
5+
from .utils import physio_or_numpy
76

87

98
def std(signal):
@@ -12,38 +11,222 @@ def std(signal):
1211
1312
Parameters
1413
----------
15-
std : function
16-
Calculate standard deviation across input channels.
17-
args : signal (X, n) :obj:`numpy.ndarray`
18-
ND array with signal of some human biometric data, hopefully from a living human.
19-
signal, of shape (n_channels, )
14+
signal : np.array
15+
Physiological data
2016
2117
Returns
2218
-------
2319
N-sized array :obj:`numpy.ndarray`
2420
Standard deviation of signal.
2521
"""
22+
signal = physio_or_numpy(signal)
2623
std_val = np.std(signal, axis=0)
2724
return std_val
2825

2926

27+
def mean(signal: np.array):
28+
"""
29+
Calculate mean across input channels of signal.
30+
31+
Parameters
32+
----------
33+
signal : np.array
34+
Physiological data
35+
36+
Returns
37+
-------
38+
N-sized array :obj:`numpy.ndarray`
39+
Mean of signal.
40+
"""
41+
signal = physio_or_numpy(signal)
42+
mean_val = np.mean(signal, axis=0)
43+
return mean_val
44+
45+
3046
def tSNR(signal):
3147
"""
32-
Calculate temporal signal to noise ration of signal.
48+
Calculate temporal signal to noise ratio of signal.
3349
3450
Parameters
3551
----------
36-
tSNR : function
37-
Calculate temporal signal to noise ratio.
38-
args : signal
39-
ND array with signal of some human biometric data, hopefully from a living human.
52+
signal : np.array
53+
Physiological data
4054
4155
Returns
4256
-------
4357
N-sized array :obj:`numpy.ndarray`
4458
Temporal signal to noise ratio of signal.
4559
"""
46-
me = np.mean(signal, axis=0)
47-
std = np.std(signal, axis=0)
48-
tSNR_val = me / std
60+
signal = physio_or_numpy(signal)
61+
tSNR_val = np.mean(signal, axis=0) / np.std(signal, axis=0)
4962
return tSNR_val
63+
64+
65+
def CoV(signal):
66+
"""
67+
Calculate coefficient of variation of signal.
68+
69+
Parameters
70+
----------
71+
signal : np.array
72+
Physiological data
73+
74+
Returns
75+
-------
76+
N-sized array :obj:`numpy.ndarray`
77+
Temporal signal to noise ratio of signal.
78+
"""
79+
signal = physio_or_numpy(signal)
80+
tSNR_val = np.std(signal, axis=0) / np.mean(signal, axis=0)
81+
return tSNR_val
82+
83+
84+
def min(signal: np.array):
85+
"""
86+
Calculate min across input channels of signal.
87+
88+
Parameters
89+
----------
90+
signal : np.array
91+
Physiological data
92+
93+
Returns
94+
-------
95+
N-sized array :obj:`numpy.ndarray`
96+
min of signal.
97+
"""
98+
signal = physio_or_numpy(signal)
99+
min_val = np.min(signal, axis=0)
100+
return min_val
101+
102+
103+
def max(signal: np.array):
104+
"""
105+
Calculate max across input channels of signal.
106+
107+
Parameters
108+
----------
109+
signal : np.array
110+
Physiological data
111+
112+
Returns
113+
-------
114+
N-sized array :obj:`numpy.ndarray`
115+
max of signal.
116+
"""
117+
signal = physio_or_numpy(signal)
118+
max_val = np.max(signal, axis=0)
119+
return max_val
120+
121+
122+
def iqr(signal: np.array, q_high: float = 75, q_low: float = 25):
123+
"""Calculate the Inter Quantile Range (IQR) over the input signal.
124+
125+
Parameters
126+
----------
127+
signal : np.array
128+
Physiological data
129+
q_high : float, optional
130+
higher percentile for IQR, by default 75
131+
q_low : float, optional
132+
lower percentile for IQR, by default 25
133+
134+
Returns
135+
-------
136+
np.array
137+
iqr of the signal
138+
"""
139+
signal = physio_or_numpy(signal)
140+
p_high, p_low = np.percentile(signal, [q_high, q_low], axis=0)
141+
iqr_val = p_high - p_low
142+
143+
return iqr_val
144+
145+
146+
def percentile(signal: np.array, perc: float = 2):
147+
"""Calculate the percentile perc over the signal.
148+
149+
Parameters
150+
----------
151+
signal : np.array
152+
Physiological data
153+
perc : float, optional
154+
percentile for data, by default 2
155+
156+
Returns
157+
-------
158+
np.array
159+
percentile of the signal
160+
"""
161+
signal = physio_or_numpy(signal)
162+
perc_val = np.percentile(signal, axis=0, q=perc)
163+
164+
return perc_val
165+
166+
167+
def peak_detection(
168+
data: pk.Physio,
169+
peak_threshold: float = 0.1,
170+
peak_dist: int = 60,
171+
):
172+
"""
173+
Perform peak detection for further metric extraction and plotting.
174+
175+
Parameters
176+
----------
177+
data : pk.Physio
178+
A peakdet Physio object
179+
peak_threshold : float, optional
180+
Threshold for peak detection, by default 0.1
181+
peak_dist : int, optional
182+
Distance for peak detection, by default 60
183+
184+
Returns
185+
-------
186+
pk.Physio
187+
Updated pk.Physio class with peaks etc.
188+
"""
189+
ph = pk.operations.peakfind_physio(data, thresh=peak_threshold, dist=peak_dist)
190+
191+
return ph
192+
193+
194+
def peak_distance(ph: pk.Physio):
195+
"""Calculate the time between peaks (first derivative of onsets).
196+
197+
Parameters
198+
----------
199+
ph : pk.Physio
200+
A pk.Physio object, that contains peak information.
201+
202+
Returns
203+
-------
204+
np.array
205+
np.array of shape [npeaks, ]
206+
"""
207+
# TODO Check if peaks have been estimated.
208+
diff_peak = np.diff(ph.peaks, axis=0)
209+
210+
return diff_peak
211+
212+
213+
def peak_amplitude(ph: pk.Physio):
214+
"""Return the amplitude for each peak in the ph.Physio object (peak - trough).
215+
216+
Parameters
217+
----------
218+
ph : pk.Physio
219+
pk.Physio object with peak and trough information.
220+
221+
Returns
222+
-------
223+
np.array
224+
np.array of shape [npeaks - 1, ]
225+
"""
226+
# TODO Check if peaks have been estimated.
227+
# Assuming that peaks and troughs are aligned. Last peak has no trough.
228+
peak_amp = ph.data[ph.peaks[:-1]]
229+
trough_amp = ph.data[ph.troughs]
230+
peak_amplitude = peak_amp - trough_amp
231+
232+
return peak_amplitude

physioqc/metrics/utils.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
"""Miscellaneous utility functions for metric calculation."""
22
import logging
33

4-
import numpy as np
5-
64
LGR = logging.getLogger(__name__)
75
LGR.setLevel(logging.INFO)
86

@@ -31,3 +29,23 @@ def print_metric_call(metric, args):
3129
msg = f"{msg}\n"
3230

3331
LGR.info(msg)
32+
33+
34+
def physio_or_numpy(signal):
35+
"""
36+
Return data from a peakdet.physio.Physio object or a np.ndarray-like object.
37+
38+
Parameters
39+
----------
40+
data : peakdet.physio.Physio, np.ndarray, or list
41+
object to get data from
42+
43+
Returns
44+
-------
45+
np.ndarray-like object
46+
Either a np.ndarray or a list
47+
"""
48+
if hasattr(signal, "history"):
49+
signal = signal.data
50+
51+
return signal

0 commit comments

Comments
 (0)