Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
716a6b9
Fix help strings for GE-related parameters.
tsalo Apr 24, 2025
e8d64a8
Try disabling HMC for GE data.
tsalo Apr 24, 2025
4061b07
Update confounds.py
tsalo Apr 24, 2025
a69104e
Whoops!
tsalo Apr 24, 2025
88ecb70
Add boilerplate to HTML reports.
tsalo Apr 24, 2025
32d38e0
Fix.
tsalo Apr 24, 2025
670ffa4
Keep working.
tsalo Apr 25, 2025
ed0199b
Update hmc.py
tsalo Apr 25, 2025
084bcbe
Update hmc.py
tsalo Apr 25, 2025
8b82dfd
Update confounds.py
tsalo Apr 25, 2025
9c2036f
Update confounds.py
tsalo Apr 25, 2025
5148047
Update hmc.py
tsalo Apr 25, 2025
c059203
Work on QSIPrep-style HMC.
tsalo Apr 28, 2025
b9c7b9a
Update fit.py
tsalo May 5, 2025
fa08708
Update.
tsalo May 5, 2025
658e51e
Update hmc.py
tsalo May 5, 2025
77e32cc
Update hmc.py
tsalo May 5, 2025
76b897f
Update utility.py
tsalo May 5, 2025
f90bcc4
Update hmc.py
tsalo May 5, 2025
f757475
Update hmc.py
tsalo May 5, 2025
ef754f7
Update hmc.py
tsalo May 5, 2025
d56a186
Try this.
tsalo May 5, 2025
175979a
Update utility.py
tsalo May 5, 2025
e4bd5ce
Update utility.py
tsalo May 5, 2025
5aaa068
Update utility.py
tsalo May 5, 2025
5a6f545
Update.
tsalo May 5, 2025
54806d6
Update utility.py
tsalo May 5, 2025
0841d5b
update
tsalo May 5, 2025
bb25306
Update hmc.py
tsalo May 5, 2025
3ab57dc
Update utility.py
tsalo May 5, 2025
0903cd8
Merge remote-tracking branch 'upstream/main' into ge-disable-hmc
tsalo May 6, 2025
ecd0a8d
Update hmc.py
tsalo May 6, 2025
42c9eed
Update hmc.py
tsalo May 6, 2025
6505ca7
Try fake rmsd.
tsalo May 6, 2025
9024b30
whoops
tsalo May 6, 2025
37a1dc1
Merge remote-tracking branch 'upstream/main' into ge-disable-hmc
tsalo May 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions aslprep/data/reports-spec-anat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,20 @@ sections:
- name: About
reportlets:
- bids: {datatype: figures, desc: about, suffix: T1w, extension: [.html]}
- custom: boilerplate
path: '{out_dir}/logs'
bibfile: ['aslprep', 'data/boilerplate.bib']
caption: |
<p>We kindly ask to report results preprocessed with this tool using the following boilerplate.</p>
<p class="alert alert-info" role="alert">
<strong>Copyright Waiver</strong>.
The boilerplate text was automatically generated by <em>NiReports</em> with the
express intention that users should copy and paste this text into their manuscripts <em>unchanged</em>.
It is released under the
<a href="https://creativecommons.org/publicdomain/zero/1.0/" target="_blank">CC0 license</a>.
</p>
title: Methods
- custom: errors
path: '{out_dir}/sub-{subject}/log/{run_uuid}'
captions: <em>NiReports</em> may have recorded failure conditions.
title: Errors
17 changes: 17 additions & 0 deletions aslprep/data/reports-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,20 @@ sections:
- name: About
reportlets:
- bids: {datatype: figures, desc: about, suffix: T1w, extension: [.html]}
- custom: boilerplate
path: '{out_dir}/logs'
bibfile: ['aslprep', 'data/boilerplate.bib']
caption: |
<p>We kindly ask to report results preprocessed with this tool using the following boilerplate.</p>
<p class="alert alert-info" role="alert">
<strong>Copyright Waiver</strong>.
The boilerplate text was automatically generated by <em>NiReports</em> with the
express intention that users should copy and paste this text into their manuscripts <em>unchanged</em>.
It is released under the
<a href="https://creativecommons.org/publicdomain/zero/1.0/" target="_blank">CC0 license</a>.
</p>
title: Methods
- custom: errors
path: '{out_dir}/sub-{subject}/log/{run_uuid}'
captions: <em>NiReports</em> may have recorded failure conditions.
title: Errors
32 changes: 32 additions & 0 deletions aslprep/interfaces/confounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,3 +549,35 @@ def _run_interface(self, runtime):
json.dump(qc_metadata, fobj, indent=4, sort_keys=True)

return runtime


class _CreateFakeMotionOutputsInputSpec(BaseInterfaceInputSpec):
asl_file = File(exists=True, mandatory=True, desc='the input NIfTI file')


class _CreateFakeMotionOutputsOutputSpec(TraitedSpec):
rmsd_file = File(exists=True, desc='RMSD TSV file. All zeros.')


class CreateFakeMotionOutputs(SimpleInterface):
"""Create outputs expected by HMC workflow without any motion."""

input_spec = _CreateFakeMotionOutputsInputSpec
output_spec = _CreateFakeMotionOutputsOutputSpec

def _run_interface(self, runtime):
import nibabel as nb

img = nb.load(self.inputs.asl_file)
if img.ndim == 4:
n_volumes = img.shape[3]
else:
# Support single-volume images
n_volumes = 1

rmsd = np.zeros((n_volumes,))

self._results['rmsd_file'] = os.path.join(runtime.cwd, 'rmsd.txt')
np.savetxt(self._results['rmsd_file'], rmsd)

return runtime
225 changes: 225 additions & 0 deletions aslprep/interfaces/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
from nipype.interfaces.base import (
BaseInterfaceInputSpec,
File,
InputMultiObject,
SimpleInterface,
TraitedSpec,
traits,
)
from nipype.interfaces.c3 import C3dAffineTool, C3dAffineToolInputSpec, C3dAffineToolOutputSpec
from nipype.interfaces.fsl.base import FSLCommand, FSLCommandInputSpec
from nipype.interfaces.nilearn import NilearnBaseInterface
from nipype.utils.filemanip import fname_presuffix, load_json, save_json
Expand Down Expand Up @@ -258,6 +260,161 @@ def _run_interface(self, runtime):
return runtime


class CombineMotionsInputSpec(BaseInterfaceInputSpec):
transform_files = InputMultiObject(
File(exists=True),
mandatory=True,
desc='FSL-format transform files',
)
ref_file = File(exists=True, mandatory=True, desc='Fixed Image')


class CombineMotionsOututSpec(TraitedSpec):
full_motion_file = File(exists=True)
confounds_file = File(exists=True)
xforms = File(exists=True)


class CombineMotions(SimpleInterface):
"""Combine motion parameters from multiple transform files, from QSIPrep."""

input_spec = CombineMotionsInputSpec
output_spec = CombineMotionsOututSpec

def _run_interface(self, runtime):
import numpy as np

collected_motion = []
full_motion_file = os.path.join(runtime.cwd, 'motion_params.csv')
confounds_file = os.path.join(runtime.cwd, 'confounds.tsv')
ref_file = self.inputs.ref_file
for xform in self.inputs.transform_files:
collected_motion.append(get_fsl_motion_params(xform, ref_file))

final_motion = np.row_stack(collected_motion)
cols = [
'scaleX',
'scaleY',
'scaleZ',
'shearXY',
'shearXZ',
'shearYZ',
'rot_x',
'rot_y',
'rot_z',
'trans_x',
'trans_y',
'trans_z',
]
full_motion_df = pd.DataFrame(data=final_motion, columns=cols)
full_motion_df.to_csv(full_motion_file, index=False)
self._results['full_motion_file'] = full_motion_file

confounds_df = full_motion_df[['trans_x', 'trans_y', 'trans_z', 'rot_x', 'rot_y', 'rot_z']]
confounds_df.to_csv(confounds_file, sep='\t', index=False)
self._results['motion_file'] = confounds_file

return runtime


class _ConcatITKInputSpec(BaseInterfaceInputSpec):
inlist = traits.List(
File(exists=True),
mandatory=True,
desc='ITK-format transform files',
)


class _ConcatITKOututSpec(TraitedSpec):
xforms = File(exists=True)


class ConcatITK(SimpleInterface):
"""Combine motion parameters from multiple transform files, from QSIPrep."""

input_spec = _ConcatITKInputSpec
output_spec = _ConcatITKOututSpec

def _run_interface(self, runtime):
from nitransforms.linear import Affine

inlist = self.inputs.inlist
# Create concatenated itk transform file
xform = Affine.from_filename(inlist[0])
temp_file = os.path.join(runtime.cwd, 'itk.txt')
xform.to_filename(temp_file, fmt='itk')
with open(temp_file) as fobj:
xform_strs = fobj.readlines()

for i_vol, xform_file in enumerate(inlist[1:]):
xform = Affine.from_filename(xform_file)
temp_file = os.path.join(runtime.cwd, f'itk_{i_vol}.txt')
xform.to_filename(temp_file, fmt='itk')
with open(temp_file) as fobj:
temp_strs = fobj.readlines()

xform_strs.append('\n')
xform_strs += temp_strs[1:]

self._results['xforms'] = os.path.join(runtime.cwd, 'itk.txt')
with open(self._results['xforms'], 'w') as fobj:
fobj.write('\n'.join(xform_strs))

return runtime


def get_fsl_motion_params(xform, src_file):
import numpy as np
from scipy.spatial.transform import Rotation as R
from transforms3d.affines import decompose44

def get_measures(line):
line = line.strip().split()
return np.array([float(num) for num in line[-3:]])

def get_image_center(src_fname):
# returns image center in mm
src_img = nb.load(src_fname)
src_aff = src_img.affine
src_center = (np.array(src_img.shape) - 1) / 2
src_center_mm = nb.affines.apply_affine(src_aff, src_center)
src_offsets = src_aff[0:3, 3]
src_center_mm -= src_offsets
return src_center_mm

def get_trans_from_offset(image_center, rotmat):
# offset[0] = trans[0] + center[0] - [rot[0,0]*center[0]
# +rot[0,1]*center[1] + rot[0,2]*center[2]]
trans = np.zeros((3,))
offsets = rotmat[0:3, 3]
for i in range(3):
offpart = offsets[i] - image_center[i]
rotpart = (
rotmat[i, 0] * image_center[0]
+ rotmat[i, 1] * image_center[1]
+ rotmat[i, 2] * image_center[2]
)
trans[i] = offpart + rotpart
return trans

img_center = get_image_center(src_file)
c3d_out_xfm = np.loadtxt(fname=xform, dtype='float')
[T, Rotmat, Z, S] = decompose44(c3d_out_xfm)
T = get_trans_from_offset(img_center, c3d_out_xfm)

flip = np.array([1, -1, -1])
negflip = np.array([-1, 1, 1])
Rotmat_to_convert = R.from_matrix(Rotmat)
Rotvec = Rotmat_to_convert.as_rotvec()

rotation = Rotvec * negflip
translation = T * flip
scale = Z
shear = S

return np.concatenate([scale, shear, rotation, translation])


class _SplitByVolumeTypeInputSpec(BaseInterfaceInputSpec):
aslcontext = File(exists=True)
asl_file = File(exists=True)
Expand Down Expand Up @@ -298,6 +455,46 @@ def _run_interface(self, runtime):
return runtime


class _TSplitInputSpec(BaseInterfaceInputSpec):
in_file = File(exists=True)


class _TSplitOutputSpec(TraitedSpec):
out_files = traits.List(File(exists=True))


class TSplit(SimpleInterface):
"""Split out a specific volume type from the ASL file."""

input_spec = _TSplitInputSpec
output_spec = _TSplitOutputSpec

def _run_interface(self, runtime):
out_files = []
asl_img = nb.load(self.inputs.in_file)
if asl_img.ndim == 4:
n_volumes = asl_img.shape[3]
else:
n_volumes = 1

digits = len(str(n_volumes)) + 1

for i_vol in range(n_volumes):
out_file = fname_presuffix(
self.inputs.in_file,
suffix=f'_{i_vol:0{digits}d}',
newpath=runtime.cwd,
use_ext=True,
)
out_img = image.index_img(asl_img, i_vol)
out_img.to_filename(out_file)
out_files.append(out_file)

self._results['out_files'] = out_files

return runtime


class _SmoothInputSpec(BaseInterfaceInputSpec):
in_file = File(
exists=True,
Expand Down Expand Up @@ -350,3 +547,31 @@ def _run_interface(self, runtime):
img_smoothed.to_filename(self._results['out_file'])

return runtime


class _C3dAffineToolInputSpec(C3dAffineToolInputSpec):
in_itk = File(
exists=True,
mandatory=False,
argstr='-itk %s',
desc='Input ITK transform file to convert',
)
ras2fsl = traits.Bool(argstr='-ras2fsl', position=4)
out_file = File(
exists=False,
usedefault=True,
desc='Output file',
)


class _C3dAffineToolOutputSpec(C3dAffineToolOutputSpec):
out_file = File(exists=True, desc='Output file')


class C3dAffineToolFix(C3dAffineTool):
"""C3dAffineTool."""

input_spec = _C3dAffineToolInputSpec
output_spec = _C3dAffineToolOutputSpec

_outputs_filenames = {'out_file': 'affine.xfm'}
4 changes: 2 additions & 2 deletions aslprep/workflows/asl/fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
from aslprep.interfaces.reports import FunctionalSummary
from aslprep.interfaces.utility import ReduceASLFiles
from aslprep.utils.asl import select_processing_target
from aslprep.workflows.asl.hmc import init_asl_hmc_wf
from aslprep.workflows.asl.hmc import init_linear_alignment_wf
from aslprep.workflows.asl.outputs import init_asl_fit_reports_wf, init_ds_aslref_wf
from aslprep.workflows.asl.reference import init_raw_aslref_wf

Expand Down Expand Up @@ -457,7 +457,7 @@ def init_asl_fit_wf(
# Stage 2: Estimate head motion
if not hmc_xforms:
config.loggers.workflow.info('Stage 2: Adding motion correction workflow')
asl_hmc_wf = init_asl_hmc_wf(
asl_hmc_wf = init_linear_alignment_wf(
name='asl_hmc_wf',
mem_gb=mem_gb['filesize'],
omp_nthreads=omp_nthreads,
Expand Down
Loading
Loading