Source code for niworkflows.interfaces.bold
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
#
# Copyright 2021 The NiPreps Developers <nipreps@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# We support and encourage derived works from this project, please read
# about our expectations at
#
# https://www.nipreps.org/community/licensing/
#
"""Utilities for BOLD fMRI imaging."""
import nibabel as nb
import numpy as np
from nipype import logging
from nipype.interfaces.base import (
BaseInterfaceInputSpec,
File,
SimpleInterface,
TraitedSpec,
traits,
)
LOGGER = logging.getLogger('nipype.interface')
class _NonsteadyStatesDetectorInputSpec(BaseInterfaceInputSpec):
in_file = File(exists=True, mandatory=True, desc='BOLD fMRI timeseries')
nonnegative = traits.Bool(
True, usedefault=True, desc='whether image voxels must be nonnegative'
)
n_volumes = traits.Range(
value=40,
low=10,
high=200,
usedefault=True,
desc='drop volumes in 4D image beyond this timepoint',
)
zero_dummy_masked = traits.Range(
value=20,
low=2,
high=40,
usedefault=True,
desc='number of timepoints to average when the number of dummies is zero',
)
class _NonsteadyStatesDetectorOutputSpec(TraitedSpec):
t_mask = traits.List(
traits.Bool, desc='list of nonsteady-states (True) and stable (False) volumes'
)
n_dummy = traits.Int(desc='number of volumes identified as nonsteady states')
[docs]
class NonsteadyStatesDetector(SimpleInterface):
"""Detect initial non-steady states in BOLD fMRI timeseries."""
input_spec = _NonsteadyStatesDetectorInputSpec
output_spec = _NonsteadyStatesDetectorOutputSpec
def _run_interface(self, runtime):
img = nb.load(self.inputs.in_file)
ntotal = img.shape[-1] if img.dataobj.ndim == 4 else 1
t_mask = np.zeros((ntotal,), dtype=bool)
if ntotal == 1:
self._results['t_mask'] = [True]
self._results['n_dummy'] = 1
return runtime
from nipype.algorithms.confounds import is_outlier
data = img.get_fdata(dtype='float32')[..., : self.inputs.n_volumes]
# Data can come with outliers showing very high numbers - preemptively prune
data = np.clip(
data,
a_min=0.0 if self.inputs.nonnegative else np.percentile(data, 0.2),
a_max=np.percentile(data, 99.8),
)
self._results['n_dummy'] = is_outlier(np.mean(data, axis=(0, 1, 2)))
start = 0
stop = self._results['n_dummy']
if stop < 2:
stop = min(ntotal, self.inputs.n_volumes)
start = max(0, stop - self.inputs.zero_dummy_masked)
t_mask[start:stop] = True
self._results['t_mask'] = t_mask.tolist()
return runtime