Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Topup correction #229

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
174 changes: 173 additions & 1 deletion pypreprocess/nipype_preproc_fsl_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import commands
import nipype.interfaces.fsl as fsl
from nipype.caching import Memory as NipypeMemory
from sklearn.externals.joblib import Memory as JoblibMemory
from joblib import Parallel, delayed
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont need to import this, im taking it out. Also is making the tests fail by introducing a dependency.

from sklearn.externals.joblib import Memory as JoblibMemory

fsl.FSLCommand.set_default_output_type('NIFTI_GZ')
FSL_T1_TEMPLATE = "/usr/share/fsl/data/standard/MNI152_T1_1mm_brain.nii.gz"
Expand Down Expand Up @@ -180,3 +181,174 @@ def do_subject_preproc(subject_id,
output['func'] = applyxfm_results.outputs.out_file

return output


def _do_img_topup_correction(ap_realig_img, pa_realig_img, func_imgs,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose _do_epi_topup_correction for the function name.

total_readout_times=(1., 1.), memory=None,
tmp_dir=None):
"""
Compute and apply topup as implemented in FSL.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it (in the docstring) that the function only do AP / PA (for the moment)


It is crucial to provide the total_readout_times of the ap_img and pa_img
correctly in case they are different. Otherwise a value of 1 can be taken
as default.

More detailed documentation can be found in the example provided in the FSL
webpage. http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/TOPUP/ExampleTopupFollowedByApplytopup.

Parameters
----------
ap_realig_img: string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put ap_ref_img (preferred) or ap_realign_img

path to img using negative phase-encode blips. Anterior --> Posterior
the image should be already realigned

pa_realig_img: string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pa_ref_img etc.

path to img using positive phase-encode blips. Posterior --> Anterior
the image should be already realigned

func_imgs: list of string
functional imgs path, that will be corrected with topup

total_readout_times: tuple of floats, optional (default None)
the first float corresponds to the total_readout_times of the ap img
and the second float to the pa img. Its crucial to provide these if
they are different. The times should be provided in seconds.

memory: Nipype `Memory` object, optional (default None)
if given, then caching will be enabled

tmp_dir: string, optional (default None)
temporary directory to store acquisition parameters configuration file
for topup. Must be provided in case memory is provided.
"""
if total_readout_times is None:
total_readout_times = (1., 1.) # Check FSL documentation explanation

if memory is not None:
if tmp_dir is None:
raise('Temporary directory was not provided with memory object')
# Merge AP and PA images
merge = memory.cache(fsl.Merge)
appa_merged = merge(in_files=[ap_realig_img, pa_realig_img],
dimension='t')

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, you have to deal with a gotcha of FDL topup: it requires the images to have an even number of slices (sounds crazy, but true indeed). So you probably need to add something like:

    odd = (np.mod(nib.load(appa_merged).shape[2], 2) == 1)
    if odd:
        cmd = "fsl5.0-fslroi %s /tmp/pe 0 -1 0 -1 0 1 0 -1" % appa_merged
        commands.getoutput(cmd))
        cmd = "fsl5.0-fslmerge -z %s /tmp/pe %s" % (
            appa_merged, appa_merged)
        print(mem.cache)(commands.getoutput(cmd))

# Compute topup transformation
acq_param_file = os.path.join(tmp_dir, 'acq_param.txt')
if not os.path.isfile(acq_param_file):
with open(acq_param_file, 'w') as acq:
content = '0 -1 0 {0:6.5f} \n0 1 0 {0:6.5f}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this holds only for AP/PA

acq.write(content % total_readout_times)

topup = memory.cache(fsl.TOPUP)
correction = topup(in_file=appa_merged.outputs.merged_file,
encoding_file=acq_param_file,
output_type='NIFTI', out_base='APPA_DefMap',
out_field='sanitycheck_DefMap',
out_corrected='sanitycheck_unwarped_B0')

# Apply topup correction to images
fieldcoef = correction.outputs.out_fieldcoef
movpar = correction.outputs.out_movpar
applytopup = memory.cache(fsl.ApplyTOPUP)
return [applytopup(in_files=img, encoding_file=acq_param_file,
in_index=[2], in_topup_fieldcoef=fieldcoef,
in_topup_movpar=movpar, output_type='NIFTI',
method='jac') for img in func_imgs]

else:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this likely to occur ?

# Merge AP and PA images
appa_merged = fsl.Merge(in_files=[ap_realig_img, pa_realig_img],
dimension='t').run()

# Compute topup transformation
acq_param_file = os.path.join('/tmp', 'pypreprocess_topup',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it okay to create this directory ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Fri, May 27, 2016 at 6:04 PM, mrahim [email protected] wrote:

In pypreprocess/nipype_preproc_fsl_utils.py
#229 (comment):

  •    # Apply topup correction to images
    
  •    fieldcoef = correction.outputs.out_fieldcoef
    
  •    movpar = correction.outputs.out_movpar
    
  •    applytopup = memory.cache(fsl.ApplyTOPUP)
    
  •    return [applytopup(in_files=img, encoding_file=acq_param_file,
    
  •                       in_index=[2], in_topup_fieldcoef=fieldcoef,
    
  •                       in_topup_movpar=movpar, output_type='NIFTI',
    
  •                       method='jac') for img in func_imgs]
    
  • else:
  •    # Merge AP and PA images
    
  •    appa_merged = fsl.Merge(in_files=[ap_realig_img, pa_realig_img],
    
  •                            dimension='t').run()
    
  •    # Compute topup transformation
    
  •    acq_param_file = os.path.join('/tmp', 'pypreprocess_topup',
    

is it okay to create this directory ?

No. We should store the parameters in the subject's output directory
(subject_data.output_dir).


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/neurospin/pypreprocess/pull/229/files/d9eba52dc19c47e59073e19f282f4d83ce9d3631#r64928000,
or mute the thread
https://github.com/notifications/unsubscribe/AAms1BP-syhHiAUENWCPtrxJxOImL1oiks5qFxYRgaJpZM4IooTp
.

Elvis Dohmatob,
sent from my potato...
http://dohmatob.blogspot.fr/, https://team.inria.fr/parietal/elvis/,
https://github.com/dohmatob

'acq_param.txt')
if not os.path.isfile(acq_param_file):
with open(acq_param_file, 'w') as acq:
content = '0 -1 0 {0:6.5f} \n0 1 0 {0:6.5f}'
acq.write(content % total_readout_times)

correction = fsl.TOPUP(in_file=appa_merged.outputs.merged_file,
encoding_file=acq_param_file,
output_type='NIFTI',
out_base='APPA_DefMap',
out_field='sanitycheck_DefMap',
out_corrected='sanitycheck_unwarped_B0').run()

# Apply topup correction to images
fieldcoef = correction.outputs.out_fieldcoef
movpar = correction.outputs.out_movpar
return [fsl.ApplyTOPUP(in_files=img, encoding_file=acq_param_file,
in_index=[2], in_topup_fieldcoef=fieldcoef,
in_topup_movpar=movpar, output_type='NIFTI',
method='jac').run() for img in func_imgs]


def _do_subject_topup_correction(subject_data, caching=True,
hardlink_output=True):
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above, concerning AP / PA; make it explicit.

Apply topup correction to subject functional images as implemented in FSL.

subject_data.topup is expected for any correction to take place. It must
contain a dictionary with imgs in subject_data.func as key and a
tuple of the form (string, string, (float, float)) corresponding to
(ap_img, pa_img, total_readout_times) as value. Its crucial that the ap
and pa imgs have already been realigned.

Parameters
----------
subject_data: `SubjectData` object
subject data whose functional images are to be corrected with topup
(subject_data.func and subject_data.topup)

caching: bool, optional (default True)
if true, then caching will be enabled

**kwargs:
additional parameters to the back-end (SPM, FSL, python)

Returns
-------
subject_data: `SubjectData` object
preprocessed subject_data. The func and anatomical fields
(subject_data.func and subject_data.anat) now contain the
oregistered and anatomical images functional images of the subject
"""
corrected_func = []
subject_data.nipype_results['topup_correction'] = []

imgs_to_topup = [img for img in subject_data.func if img in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line looks weird to me. What's the difference / relation btw subject_data.func and subject_data.topup ?

subject_data.topup]
for img_idx, img in enumerate(imgs_to_topup):
ap_realig_img = subject_data.topup[img][0]
pa_realig_img = subject_data.topup[img][1]
total_readout_times = subject_data.topup[img][2]

if caching:
cache_dir = cache_dir = os.path.join(subject_data.output_dir,
'cache_dir')
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
subject_data.mem = NipypeMemory(base_dir=cache_dir)
memory = subject_data.mem
tmp_dir = os.path.join(subject_data.tmp_output_dir,
'_%d' % img_idx)
top = _do_img_topup_correction(ap_realig_img, pa_realig_img, [img],
total_readout_times, memory,
tmp_dir)
else:
top = _do_img_topup_correction(ap_realig_img, pa_realig_img, [img],
total_readout_times)
if top is None:
subject_data.failed = True
return subject_data
else:
subject_data.nipype_results['topup_correction'].append(top)
corrected_func.append(top.outputs.out_corrected)

# commit output files
if hardlink_output:
subject_data.hardlink_output_files()
subject_data.func = corrected_func

return subject_data.sanitize()
9 changes: 8 additions & 1 deletion pypreprocess/subject_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ class SubjectData(object):
anat: string
path to anatomical image

topup: dict, optional (default None)
dictionary with imgs provided in func as keys and tuples of the form
(string, string, (float, float)) as values. Corresponding to
(ap_img, pa_img, total_readout_times). total_readout_times can be None,
but ap_img and pa_img are mandatory.

subject_id: string, optional (default 'sub001')
subject id

Expand All @@ -122,13 +128,14 @@ class SubjectData(object):

"""

def __init__(self, func=None, anat=None, subject_id="sub001",
def __init__(self, func=None, anat=None, topup=None, subject_id="sub001",
session_ids=None, output_dir=None, session_output_dirs=None,
anat_output_dir=None, scratch=None, warpable=None, **kwargs):
if warpable is None:
warpable = ['anat', 'func']
self.func = func
self.anat = anat
self.topup = topup
self.subject_id = subject_id
self.session_ids = session_ids
self.n_sessions = None
Expand Down