33import numpy as np
44import pandas as pd
55from scipy .ndimage .filters import convolve1d
6- from scipy .signal import detrend , resample
6+ from scipy .signal import detrend
77from scipy .stats import zscore
88
99mpl .use ("TkAgg" )
1515
1616
1717@due .dcite (references .POWER_2018 )
18- def rpv (belt_ts , window ):
18+ def rpv (resp , window = 6 ):
1919 """Calculate respiratory pattern variability.
2020
2121 Parameters
2222 ----------
23- belt_ts
24- window
23+ resp : 1D array_like
24+ window : int
2525
2626 Returns
2727 -------
28- rpv_arr
28+ rpv_val : float
29+ Respiratory pattern variability value.
2930
3031 Notes
3132 -----
@@ -43,35 +44,29 @@ def rpv(belt_ts, window):
4344 115, pp. 2105-2114, 2018.
4445 """
4546 # First, z-score respiratory traces
46- resp_z = zscore (belt_ts )
47+ resp_z = zscore (resp )
4748
4849 # Collect upper envelope
4950 rpv_upper_env = utils .rms_envelope_1d (resp_z , window )
5051
5152 # Calculate standard deviation
52- rpv_arr = np .std (rpv_upper_env )
53- return rpv_arr
53+ rpv_val = np .std (rpv_upper_env )
54+ return rpv_val
5455
5556
5657@due .dcite (references .POWER_2020 )
57- def env (belt_ts , samplerate , out_samplerate , window = 10 , lags = ( 0 ,) ):
58+ def env (resp , samplerate , window = 10 ):
5859 """Calculate respiratory pattern variability across a sliding window.
5960
6061 Parameters
6162 ----------
62- belt_ts : (X,) :obj:`numpy.ndarray`
63+ resp : (X,) :obj:`numpy.ndarray`
6364 A 1D array with the respiratory belt time series.
6465 samplerate : :obj:`float`
65- Sampling rate for belt_ts, in Hertz.
66- out_samplerate : :obj:`float`
67- Sampling rate for the output time series in seconds.
68- Corresponds to TR in fMRI data.
66+ Sampling rate for resp, in Hertz.
6967 window : :obj:`int`, optional
70- Size of the sliding window, in the same units as out_samplerate.
71- Default is 6.
72- lags : (Y,) :obj:`tuple` of :obj:`int`, optional
73- List of lags to apply to the rv estimate. In the same units as
74- out_samplerate.
68+ Size of the sliding window, in seconds.
69+ Default is 10.
7570
7671 Returns
7772 -------
@@ -92,42 +87,37 @@ def env(belt_ts, samplerate, out_samplerate, window=10, lags=(0,)):
9287 young adults scanned at rest, including systematic changes and 'missed'
9388 deep breaths," Neuroimage, vol. 204, 2020.
9489 """
95- window = window * samplerate / out_samplerate
90+ # Convert window to Hertz
91+ window = int (window * samplerate )
92+
9693 # Calculate RPV across a rolling window
9794 env_arr = (
98- pd .Series (belt_ts ).rolling (window = window , center = True ).apply (rpv , window = window )
95+ pd .Series (resp ).rolling (window = window , center = True ).apply (rpv , args = ( window ,) )
9996 )
10097 env_arr [np .isnan (env_arr )] = 0.0
10198 return env_arr
10299
103100
104101@due .dcite (references .CHANG_GLOVER_2009 )
105- def rv (belt_ts , samplerate , out_samplerate , window = 6 , lags = (0 ,)):
102+ def rv (resp , samplerate , window = 6 , lags = (0 ,)):
106103 """Calculate respiratory variance.
107104
108105 Parameters
109106 ----------
110- belt_ts : (X,) :obj:`numpy.ndarray`
107+ resp : (X,) :obj:`numpy.ndarray`
111108 A 1D array with the respiratory belt time series.
112109 samplerate : :obj:`float`
113- Sampling rate for belt_ts, in Hertz.
114- out_samplerate : :obj:`float`
115- Sampling rate for the output time series in seconds.
116- Corresponds to TR in fMRI data.
110+ Sampling rate for resp, in Hertz.
117111 window : :obj:`int`, optional
118- Size of the sliding window, in the same units as out_samplerate .
112+ Size of the sliding window, in seconds .
119113 Default is 6.
120- lags : (Y,) :obj:`tuple` of :obj:`int`, optional
121- List of lags to apply to the rv estimate. In the same units as
122- out_samplerate.
123114
124115 Returns
125116 -------
126- rv_out : (Z, 2Y) :obj:`numpy.ndarray`
127- Respiratory variance values, with lags applied, downsampled to
128- out_samplerate, convolved with an RRF, and detrended/normalized.
129- The first Y columns are not convolved with the RRF, while the second Y
130- columns are.
117+ rv_out : (X, 2) :obj:`numpy.ndarray`
118+ Respiratory variance values.
119+ The first column is raw RV values, after detrending/normalization.
120+ The second column is RV values convolved with the RRF, after detrending/normalization.
131121
132122 Notes
133123 -----
@@ -145,25 +135,19 @@ def rv(belt_ts, samplerate, out_samplerate, window=6, lags=(0,)):
145135 end-tidal CO2, and BOLD signals in resting-state fMRI," Neuroimage,
146136 issue 4, vol. 47, pp. 1381-1393, 2009.
147137 """
138+ # Convert window to Hertz
139+ window = int (window * samplerate )
140+
148141 # Raw respiratory variance
149- rv_arr = pd .Series (belt_ts ).rolling (window = window , center = True ).std ()
142+ rv_arr = pd .Series (resp ).rolling (window = window , center = True ).std ()
150143 rv_arr [np .isnan (rv_arr )] = 0.0
151144
152- # Apply lags
153- n_out_samples = int ((belt_ts .shape [0 ] / samplerate ) / out_samplerate )
154- # convert lags from out_samplerate to samplerate
155- delays = [abs (int (lag * samplerate )) for lag in lags ]
156- rv_with_lags = utils .apply_lags (rv_arr , lags = delays )
157-
158- # Downsample to out_samplerate
159- rv_with_lags = resample (rv_with_lags , num = n_out_samples , axis = 0 )
160-
161145 # Convolve with rrf
162- rrf_arr = rrf (out_samplerate , oversampling = 1 )
163- rv_convolved = convolve1d (rv_with_lags , rrf_arr , axis = 0 )
146+ rrf_arr = rrf (samplerate , oversampling = 1 )
147+ rv_convolved = convolve1d (rv_arr , rrf_arr , axis = 0 )
164148
165149 # Concatenate the raw and convolved versions
166- rv_combined = np .hstack (( rv_with_lags , rv_convolved ))
150+ rv_combined = np .stack (( rv_arr , rv_convolved ), axis = - 1 )
167151
168152 # Detrend and normalize
169153 rv_combined = rv_combined - np .mean (rv_combined , axis = 0 )
@@ -240,14 +224,15 @@ def respiratory_phase(resp, sample_rate, n_scans, slice_timings, t_r):
240224 Returns
241225 -------
242226 phase_resp : array_like
243- Respiratory phase signal.
227+ Respiratory phase signal, of shape (n_scans, n_slices) .
244228 """
245- n_slices = np .shape (slice_timings )
229+ assert slice_timings .ndim == 1 , "Slice times must be a 1D array"
230+ n_slices = np .size (slice_timings )
246231 phase_resp = np .zeros ((n_scans , n_slices ))
247232
248233 # generate histogram from respiratory signal
249234 # TODO: Replace with numpy.histogram
250- resp_hist , resp_hist_bins = plt .hist (resp , bins = 100 )
235+ resp_hist , resp_hist_bins , _ = plt .hist (resp , bins = 100 )
251236
252237 # first compute derivative of respiration signal
253238 resp_diff = np .diff (resp , n = 1 )
0 commit comments