88import numpy as np
99from nilearn import image as nli
1010from nilearn .image import index_img
11- from nipype .utils .filemanip import fname_presuffix
11+ from nipype .utils .filemanip import fname_presuffix , ensure_list
1212from nipype .interfaces .base import (
1313 traits ,
1414 isdefined ,
@@ -445,59 +445,81 @@ class _EstimateReferenceImageOutputSpec(TraitedSpec):
445445
446446class EstimateReferenceImage (SimpleInterface ):
447447 """
448- Given an 4D EPI file estimate an optimal reference image that could be later
449- used for motion estimation and coregistration purposes. If detected uses
450- T1 saturated volumes (non-steady state) otherwise a median of
448+ Generate a reference 3D map from BOLD and SBRef EPI images for BOLD datasets.
449+
450+ Given a 4D BOLD file or one or more 3/4D SBRefs, estimate a reference
451+ image for subsequent motion estimation and coregistration steps.
452+ For the case of BOLD datasets, it estimates a number of T1w saturated volumes
453+ (non-steady state at the beginning of the scan) and calculates the median
454+ across them.
455+ Otherwise (SBRefs or detected zero non-steady state frames), a median of
451456 of a subset of motion corrected volumes is used.
457+ If the input reference (BOLD or SBRef) is 3D already, it just returns a
458+ copy of the image with the NIfTI header extensions removed.
459+
460+ LIMITATION: If one wants to extract the reference from several SBRefs
461+ with several echoes each, the first echo should be selected elsewhere
462+ and run this interface in ``multiecho = False`` mode.
452463 """
453464
454465 input_spec = _EstimateReferenceImageInputSpec
455466 output_spec = _EstimateReferenceImageOutputSpec
456467
457468 def _run_interface (self , runtime ):
458- # Select first EPI file
459- ref_name = self .inputs .in_file [0 ]
460- ref_nii = nb .load (ref_name )
461- n_volumes_to_discard = _get_vols_to_discard (ref_nii )
462-
463- self ._results ["n_volumes_to_discard" ] = n_volumes_to_discard
464-
465- if self .inputs .multiecho and (len (self .inputs .in_file ) < 2 ):
466- raise ValueError ("Argument 'multiecho' is True but "
467- "'in_file' has only one element" )
469+ is_sbref = isdefined (self .inputs .sbref_file )
470+ ref_input = ensure_list (
471+ self .inputs .sbref_file if is_sbref else self .inputs .in_file
472+ )
468473
469- out_ref_fname = os . path . join ( runtime . cwd , "ref_bold.nii.gz" )
470- if isdefined ( self . inputs . sbref_file ) :
471- if self . inputs . multiecho and ( len ( self . inputs . sbref_file ) < 2 ):
474+ if self . inputs . multiecho :
475+ if len ( ref_input ) < 2 :
476+ input_name = " sbref_file" if is_sbref else "in_file"
472477 raise ValueError ("Argument 'multiecho' is True but "
473- "'sbref_file' has only one element" )
474-
475- # Select first SBRef file
476- ref_name = self .inputs .sbref_file [0 ]
477- ref_nii = nb .squeeze_image (nb .load (ref_name ))
478+ f"'{ input_name } ' has only one element." )
479+ else :
480+ # Select only the first echo (see LIMITATION above for SBRefs)
481+ ref_input = ref_input [:1 ]
482+ elif not is_sbref and len (ref_input ) > 1 :
483+ raise ValueError ("Input 'in_file' cannot point to more than one file "
484+ "for single-echo BOLD datasets." )
485+
486+ # Build the nibabel spatial image we will work with
487+ ref_im = []
488+ for im_i in ref_input :
489+ nib_i = nb .squeeze_image (nb .load (im_i ))
490+ if nib_i .dataobj .ndim == 3 :
491+ ref_im .append (nib_i )
492+ elif nib_i .dataobj .ndim == 4 :
493+ ref_im += nb .four_to_three (nib_i )
494+ ref_im = nb .squeeze_image (nb .concat_images (ref_im ))
495+
496+ # Volumes to discard only makes sense with BOLD inputs.
497+ if not is_sbref :
498+ n_volumes_to_discard = _get_vols_to_discard (ref_im )
499+ out_ref_fname = os .path .join (runtime .cwd , "ref_bold.nii.gz" )
500+ else :
501+ n_volumes_to_discard = 0
478502 out_ref_fname = os .path .join (runtime .cwd , "ref_sbref.nii.gz" )
479503
480- # If reference is only 1 volume, return it directly
481- if len (ref_nii .shape ) == 3 :
482- ref_nii .header .extensions .clear ()
483- ref_nii .to_filename (out_ref_fname )
484- self ._results ["ref_image" ] = out_ref_fname
485- return runtime
486- else :
487- # Reset this variable as it no longer applies
488- # and value for the output is stored in self._results
489- n_volumes_to_discard = 0
504+ # Set interface outputs
505+ self ._results ["n_volumes_to_discard" ] = n_volumes_to_discard
506+ self ._results ["ref_image" ] = out_ref_fname
490507
491508 # Slicing may induce inconsistencies with shape-dependent values in extensions.
492509 # For now, remove all. If this turns out to be a mistake, we can select extensions
493510 # that don't break pipeline stages.
494- ref_nii .header .extensions .clear ()
511+ ref_im .header .extensions .clear ()
512+
513+ # If reference is only 1 volume, return it directly
514+ if ref_im .dataobj .ndim == 3 :
515+ ref_im .to_filename (out_ref_fname )
516+ return runtime
495517
496518 if n_volumes_to_discard == 0 :
497- if ref_nii .shape [- 1 ] > 40 :
519+ if ref_im .shape [- 1 ] > 40 :
498520 ref_name = os .path .join (runtime .cwd , "slice.nii.gz" )
499521 nb .Nifti1Image (
500- ref_nii .dataobj [:, :, :, 20 :40 ], ref_nii .affine , ref_nii .header
522+ ref_im .dataobj [:, :, :, 20 :40 ], ref_im .affine , ref_im .header
501523 ).to_filename (ref_name )
502524
503525 if self .inputs .mc_method == "AFNI" :
@@ -516,14 +538,12 @@ def _run_interface(self, runtime):
516538 median_image_data = np .median (mc_slice_nii .get_fdata (), axis = 3 )
517539 else :
518540 median_image_data = np .median (
519- ref_nii .dataobj [:, :, :, :n_volumes_to_discard ], axis = 3
541+ ref_im .dataobj [:, :, :, :n_volumes_to_discard ], axis = 3
520542 )
521543
522- nb .Nifti1Image (median_image_data , ref_nii .affine , ref_nii .header ).to_filename (
544+ nb .Nifti1Image (median_image_data , ref_im .affine , ref_im .header ).to_filename (
523545 out_ref_fname
524546 )
525-
526- self ._results ["ref_image" ] = out_ref_fname
527547 return runtime
528548
529549
0 commit comments