diff --git a/nipype/algorithms/confounds.py b/nipype/algorithms/confounds.py index 4a74ff5cec..dba0545e8c 100644 --- a/nipype/algorithms/confounds.py +++ b/nipype/algorithms/confounds.py @@ -1185,31 +1185,62 @@ def is_outlier(points, thresh=3.5): return timepoints_to_discard -def cosine_filter( - data, timestep, period_cut, remove_mean=True, axis=-1, failure_mode="error" +def _make_cosine_regressors(ntimepoints, timestep, period_cut): + return _full_rank(_cosine_drift(period_cut, timestep * np.arange(ntimepoints)))[0] + + +def _make_legendre_regressors(ntimepoints, degree): + support = np.linspace([-1], [1], ntimepoints) + return np.hstack( + [ + np.ones((ntimepoints, 1)), # Degree 0 is a constant + *(Legendre.basis(i + 1)(support) for i in range(degree)), + ] + ) + + +def _high_pass_filter( + data, *args, filter_type, remove_mean=True, axis=-1, failure_mode="error" ): datashape = data.shape timepoints = datashape[axis] if datashape[0] == 0 and failure_mode != "error": return data, np.array([]) - data = data.reshape((-1, timepoints)) + filters = { + 'cosine': _make_cosine_regressors, + 'legendre': _make_legendre_regressors, + } - frametimes = timestep * np.arange(timepoints) - X = _full_rank(_cosine_drift(period_cut, frametimes))[0] + X = filters[filter_type](timepoints, *args) non_constant_regressors = X[:, :-1] if X.shape[1] > 1 else np.array([]) - betas = np.linalg.lstsq(X, data.T)[0] + data = data.reshape((-1, timepoints)) + + betas = np.linalg.pinv(X) @ data.T if not remove_mean: - X = X[:, :-1] - betas = betas[:-1] - - residuals = data - X.dot(betas).T + X = X[:, 1:] + betas = betas[1:] + residuals = data - (X @ betas).T return residuals.reshape(datashape), non_constant_regressors +def cosine_filter( + data, timestep, period_cut, remove_mean=True, axis=-1, failure_mode="error" +): + return _high_pass_filter( + data, + timestep, + period_cut, + filter_type='cosine', + remove_mean=remove_mean, + axis=axis, + failure_mode=failure_mode, + ) + + def regress_poly(degree, data, remove_mean=True, axis=-1, failure_mode="error"): """ Returns data with degree polynomial regressed out. @@ -1221,36 +1252,14 @@ def regress_poly(degree, data, remove_mean=True, axis=-1, failure_mode="error"): IFLOGGER.debug( "Performing polynomial regression on data of shape %s", str(data.shape) ) - - datashape = data.shape - timepoints = datashape[axis] - if datashape[0] == 0 and failure_mode != "error": - return data, np.array([]) - - # Rearrange all voxel-wise time-series in rows - data = data.reshape((-1, timepoints)) - - # Generate design matrix - X = np.ones((timepoints, 1)) # quick way to calc degree 0 - for i in range(degree): - polynomial_func = Legendre.basis(i + 1) - value_array = np.linspace(-1, 1, timepoints) - X = np.hstack((X, polynomial_func(value_array)[:, np.newaxis])) - - non_constant_regressors = X[:, :-1] if X.shape[1] > 1 else np.array([]) - - # Calculate coefficients - betas = np.linalg.pinv(X).dot(data.T) - - # Estimation - if remove_mean: - datahat = X.dot(betas).T - else: # disregard the first layer of X, which is degree 0 - datahat = X[:, 1:].dot(betas[1:, ...]).T - regressed_data = data - datahat - - # Back to original shape - return regressed_data.reshape(datashape), non_constant_regressors + return _high_pass_filter( + data, + degree, + filter_type='legendre', + remove_mean=remove_mean, + axis=axis, + failure_mode=failure_mode, + ) def combine_mask_files(mask_files, mask_method=None, mask_index=None): @@ -1526,7 +1535,6 @@ def _cosine_drift(period_cut, frametimes): Ref: http://en.wikipedia.org/wiki/Discrete_cosine_transform DCT-II """ len_tim = len(frametimes) - n_times = np.arange(len_tim) hfcut = 1.0 / period_cut # input parameter is the period # frametimes.max() should be (len_tim-1)*dt @@ -1534,13 +1542,15 @@ def _cosine_drift(period_cut, frametimes): # hfcut = 1/(2*dt) yields len_time # If series is too short, return constant regressor order = max(int(np.floor(2 * len_tim * hfcut * dt)), 1) - cdrift = np.zeros((len_tim, order)) - nfct = np.sqrt(2.0 / len_tim) + cdrift = np.ones((len_tim, order)) + + if order > 1: + nfct = np.sqrt(2.0 / len_tim) + support = (np.pi / len_tim) * (np.arange(len_tim) + 0.5) - for k in range(1, order): - cdrift[:, k - 1] = nfct * np.cos((np.pi / len_tim) * (n_times + 0.5) * k) + for k in range(1, order): + cdrift[:, k] = nfct * np.cos(support * k) - cdrift[:, order - 1] = 1.0 # or 1./sqrt(len_tim) to normalize return cdrift