def _parse_stdout(self, stdout):
        import re
        import os
        files = []
        reoriented_files = []
        reoriented_and_cropped_files = []
        bvecs = []
        bvals = []
        skip = False
        last_added_file = None
        for line in stdout.split("\n"):
            if not skip:
                file = None
                if line.startswith("Saving "):
                    file = line[len("Saving "):]
                elif line.startswith("GZip..."):
                    #for gzipped outpus files are not absolute
                    if isdefined(self.inputs.output_dir):
                        output_dir = self.inputs.output_dir
                        print output_dir
                    else:
                        output_dir = self._gen_filename('output_dir')
                    file = os.path.abspath(os.path.join(output_dir,
                                                        line[len("GZip..."):]))
                elif line.startswith("Number of diffusion directions "):
                    if last_added_file:
                        base, filename, ext = split_filename(last_added_file)
                        bvecs.append(os.path.join(base,filename + ".bvec"))
                        bvals.append(os.path.join(base,filename + ".bval"))
                        
                elif re.search('.*->(.*)', line):
                    val = re.search('.*->(.*)', line)
                    val = val.groups()[0]
                    if isdefined(self.inputs.output_dir):
                        output_dir = self.inputs.output_dir
                    else:
                        output_dir = self._gen_filename('output_dir')
                    val = os.path.join(output_dir, val)
                    file = val

                if file:
                    if last_added_file and os.path.exists(file) and not last_added_file in file:
                        files.append(file)
                    last_added_file = file
                    continue

                if line.startswith("Reorienting as "):
                    reoriented_files.append(line[len("Reorienting as "):])
                    skip = True
                    continue
                elif line.startswith("Cropping NIfTI/Analyze image "):
                    base, filename = os.path.split(line[len("Cropping NIfTI/Analyze image "):])
                    filename = "c" + filename
                    reoriented_and_cropped_files.append(os.path.join(base, filename))
                    skip = True
                    continue
            skip = False
        return files, reoriented_files, reoriented_and_cropped_files, bvecs, bvals
Esempio n. 2
0
    def _run_interface(self, runtime):

        motion_mat = np.loadtxt(self.inputs.motion_mat)
        structural_image = self.inputs.structural_image
        if isdefined(self.inputs.structural2ref_regmat):
            structural2ref_regmat = np.loadtxt(
                self.inputs.structural2ref_regmat)
        pet2ref_mat = np.loadtxt(self.inputs.pet2ref_mat)
        pet_image = self.inputs.pet_image
        if isdefined(self.inputs.corr_factor):
            corr_factor = self.inputs.corr_factor
        else:
            corr_factor = 1

        ref2pet_mat = np.linalg.inv(pet2ref_mat)
        if structural_image:
            ref2pet_mat = np.linalg.inv(structural2ref_regmat)
            out_basename = 'al2Struct'
        else:
            out_basename = 'al2Ref'
        basename = pet_image.split('/')[-1].split('.')[0]
        outname = '{0}_{1}'.format(basename, out_basename)
        motion_mat_inv = np.linalg.inv(motion_mat)
        transformation_mat = np.dot(ref2pet_mat,
                                    np.dot(motion_mat_inv, pet2ref_mat))
        np.savetxt('transformation.mat', transformation_mat)

        if structural_image:
            self.applyxfm(pet_image, structural_image, 'transformation.mat',
                          outname + '_mc')
        else:
            self.applyxfm(pet_image, pet_image, 'transformation.mat',
                          outname + '_mc')
        self.apply_temporal_correction(outname + '_mc', corr_factor,
                                       outname + '_mc_corr')
        self.apply_temporal_correction(pet_image, corr_factor,
                                       outname + '_no_mc_corr')
        self.out_basename = out_basename

        return runtime
Esempio n. 3
0
    def get_host_path(self, path):
        """
        Takes a file path that is valid in the container
        and converts it to one valid on the host.
        """
        if not path:
            return

        if isdefined(self.inputs.map_dirs_list):
            mounts = self.inputs.map_dirs_list
            for mount in mounts:
                h_path, c_path = mount.split(':')
                h_path = os.path.abspath(h_path)
                c_path = os.path.abspath(c_path)
                if path.startswith(c_path):
                    rel_path = os.path.relpath(path, c_path)
                    path = os.path.join(h_path, rel_path)
        return (path)
Esempio n. 4
0
def test_bids_infields_outfields(tmpdir):
    tmpdir.chdir()
    infields = ['infield1', 'infield2']
    outfields = ['outfield1', 'outfield2']
    bg = nio.BIDSDataGrabber(infields=infields)
    for outfield in outfields:
        bg.inputs.output_query[outfield] = {'key': 'value'}

    for infield in infields:
        assert (infield in bg.inputs.traits())
        assert (not (isdefined(bg.inputs.get()[infield])))

    for outfield in outfields:
        assert (outfield in bg._outputs().traits())

    # now try without defining outfields, we should get anat and func for free
    bg = nio.BIDSDataGrabber()
    for outfield in ['anat', 'func']:
        assert outfield in bg._outputs().traits()
Esempio n. 5
0
def test_bids_infields_outfields(tmpdir):
    tmpdir.chdir()
    infields = ['infield1', 'infield2']
    outfields = ['outfield1', 'outfield2']
    bg = nio.BIDSDataGrabber(infields=infields)
    for outfield in outfields:
        bg.inputs.output_query[outfield] = {'key': 'value'}

    for infield in infields:
        assert(infield in bg.inputs.traits())
        assert(not(isdefined(bg.inputs.get()[infield])))

    for outfield in outfields:
        assert(outfield in bg._outputs().traits())

    # now try without defining outfields, we should get anat and func for free
    bg = nio.BIDSDataGrabber()
    for outfield in ['anat', 'func']:
        assert outfield in bg._outputs().traits()
Esempio n. 6
0
def test_bids_infields_outfields(tmpdir):
    tmpdir.chdir()
    infields = ["infield1", "infield2"]
    outfields = ["outfield1", "outfield2"]
    bg = nio.BIDSDataGrabber(infields=infields)
    for outfield in outfields:
        bg.inputs.output_query[outfield] = {"key": "value"}

    for infield in infields:
        assert infield in bg.inputs.traits()
        assert not (isdefined(bg.inputs.get()[infield]))

    for outfield in outfields:
        assert outfield in bg._outputs().traits()

    # now try without defining outfields
    bg = nio.BIDSDataGrabber()
    for outfield in ["T1w", "bold"]:
        assert outfield in bg._outputs().traits()
Esempio n. 7
0
    def _run_interface(self, runtime):

        dct = {}
        pet_data = sorted(glob.glob(self.inputs.pet_data + '/*.nii.gz'))
        motion_mats = sorted(glob.glob(self.inputs.motion_mats + '/*.txt'))
        reference = self.inputs.reference
        if isdefined(self.inputs.corr_factors):
            corr_factors = np.loadtxt(self.inputs.corr_factors).tolist()
        else:
            corr_factors = None
        if not pet_data:
            raise Exception('No images found in {}!'.format(
                self.inputs.pet_cropped))
        elif (pet_data and (len(pet_data) != len(motion_mats))):
            raise Exception("The number of the PET images found in {0} is "
                            "different from that of the motion matrices found "
                            "in {1}. Please check.".format(
                                self.inputs.pet_data, self.inputs.motion_mats))
        else:
            pet_qform = self.get_qform(pet_data[0])
            ref_qform = self.get_qform(reference)
            ref_qform_inv = np.linalg.inv(ref_qform)
            pet2ref = np.dot(ref_qform_inv, pet_qform)
            np.savetxt('pet2ref.mat', pet2ref)
            dct['pet_data'] = pet_data
            dct['motion_mats'] = motion_mats
            if (corr_factors is not None
                    and (len(pet_data) == len(corr_factors))):
                dct['corr_factors'] = corr_factors
            elif corr_factors is None:
                dct['corr_factors'] = []
            if (corr_factors is not None
                    and (len(pet_data) != len(corr_factors))):
                raise Exception(
                    "The number of the PET images found in {0} is {1} and it "
                    "is different from that of the PET correction factors"
                    "which is {2}. Please check.".format(
                        self.inputs.pet_data, len(pet_data),
                        len(corr_factors)))
            self.dct = dct

        return runtime
Esempio n. 8
0
    def get_container_path(self, path, mounts):
        """
        Takes a file path that is valid in the host
        and a list of mounts ['host:container']
        changes the file path to the path in the container.
        """
        if not path:
            return

        if isdefined(mounts):
            for mount in mounts:
                h_path, c_path = mount.split(':')
                h_path = os.path.abspath(h_path)
                c_path = os.path.abspath(c_path)

                if path.startswith(h_path):
                    print(path)
                    rel_path = os.path.relpath(path, h_path)
                    path = os.path.join(c_path, rel_path)
                    print(path)
                    print("")
        return (path)
Esempio n. 9
0
        def _connect(hierarchy):
            wf = hierarchy[-1]

            outputnode: Optional[pe.Node] = wf.get_node("outputnode")
            if outputnode is not None:
                outputattrs = set(outputnode.outputs.copyable_trait_names())
                attrs = (inputattrs & outputattrs
                         ) - connected_attrs  # find common attr names

                actually_connected_attrs = set()
                for _, _, datadict in wf._graph.in_edges(outputnode,
                                                         data=True):
                    _, infields = zip(*datadict.get("connect", []))
                    actually_connected_attrs.update(infields)

                for key, value in outputnode.inputs.get().items():
                    if isdefined(value):
                        actually_connected_attrs.add(key)

                attrs &= actually_connected_attrs

                for attr in attrs:
                    self.connect_attr(hierarchy, outputnode, attr,
                                      nodehierarchy, node, attr)
                    connected_attrs.add(attr)

            for attr in list(dsattrs):
                childtpl = _find_child(hierarchy, attr)
                if childtpl is not None:
                    childhierarchy, childnode = childtpl
                    childhierarchy, childnode, childattr = _find_input(
                        childhierarchy, childnode, "in_file")
                    self.connect_attr(childhierarchy, childnode, childattr,
                                      nodehierarchy, node, attr)
                    dsattrs.remove(attr)
                    connected_attrs.add(attr)
Esempio n. 10
0
    def _parse_inputs(self, skip=None):
        # increment the position argument for all child trait objects
        # ensure that container arguments and commands come first
        trait_names = self.inputs.editable_traits()
        local_trait_names = DockerInputSpec().editable_traits()
        # find the current maximum position
        max_position = 0
        for name in local_trait_names:
            t = DockerInputSpec().trait(name)
            if t.position and t.position > max_position:
                max_position = t.position
        # update the passed parameter positions
        new_names = set(trait_names) - set(local_trait_names)
        for name in new_names:
            t = self.inputs.trait(name)
            if t.position and t.position >= 0:
                t.position = t.position + max_position

        # parse any map_dirs_tuples directives, covert them to list strings
        if isdefined(self.inputs.map_dirs_tuples):
            map_strs = [":".join(str) for str in self.inputs.map_dirs_tuples]
            if not isdefined(self.inputs.map_dirs_list):
                self.inputs.map_dirs_list = []
            [self.inputs.map_dirs_list.append(str) for str in map_strs]

        # Mount docker /outputs to host cwd
        if isdefined(self.inputs.map_dirs_tuples):
            self.inputs.map_dirs_list.append(str(os.getcwd()) + ":/outputs")
        else:
            self.inputs.map_dirs_list = [str(os.getcwd()) + ":/outputs"]
        # original parse code here
        all_args = []
        initial_args = {}
        final_args = {}
        metadata = dict(argstr=lambda t: t is not None)

        #Automatic mount of host files and folders
        for name, spec in sorted(self.inputs.traits(**metadata).items()):
            value = getattr(self.inputs, name)
            if spec.is_trait_type(DockerFile):
                h_dir, file = os.path.split(value)
                self.inputs.map_dirs_list.append(h_dir + ":" + "/mounts/" +
                                                 name + "/")
                self.inputs.map_dirs_list = list(set(
                    self.inputs.map_dirs_list))
            elif spec.is_trait_type(DockerDir):
                self.inputs.map_dirs_list.append(value + ":" + "/mounts/" +
                                                 name + "/")

        for name, spec in sorted(self.inputs.traits(**metadata).items()):
            if skip and name in skip:
                continue
            value = getattr(self.inputs, name)
            if spec.name_source:
                value = self._filename_from_source(name)
            elif spec.genfile:
                if not isdefined(value) or value is None:
                    value = self._gen_filename(name)

            if not isdefined(value):
                continue

            # modify DockerFile paths
            if spec.is_trait_type((DockerFile, DockerDir)):
                value = self.get_container_path(value,
                                                self.inputs.map_dirs_list)

            # Add custom parsing for map_dir_list
            if name is 'map_dirs_list':
                arg = "".join(["-v " + str(x) + " " for x in value])
            else:
                arg = self._format_arg(name, spec, value)
            if arg is None:
                continue
            pos = spec.position
            if pos is not None:
                if int(pos) >= 0:
                    initial_args[pos] = arg
                else:
                    final_args[pos] = arg
            else:
                all_args.append(arg)
        first_args = [arg for pos, arg in sorted(initial_args.items())]
        last_args = [arg for pos, arg in sorted(final_args.items())]
        return first_args + all_args + last_args
class NiiWrangler(BaseInterface):
    input_spec = NiiWranglerInputSpec
    output_spec = NiiWranglerOutputSpec

    def __init__(self, *args, **kwargs):
        super(NiiWrangler, self).__init__(*args, **kwargs)
        self.t1_files = []
        self.rsfmri_files = []
        self.dwi_files = []
        self.dwi_ap_files = []
        self.dwi_pa_files = []
        self.flair_files = []
        self.fieldmap_mag = []
        self.fieldmap_ph = []
        self.bval = []
        self.bvec = []
        self.ep_TR= None
        self.fieldmap_mag_delta_te = "NONE"
        self.t1_sample_spacing = 0.
        self.ep_dwi_echo_spacings = None
        self.ep_rsfmri_echo_spacings = None
        self.ep_unwarp_dirs = None

    def _run_interface(self, runtime):
        import re
        import operator
        
        print "starting NII wrangler"
        nii_files = self.inputs.nii_files
        smap = self.inputs.series_map
        dinfo = self.inputs.dicom_info
        #block_averaging = self.inputs.block_struct_averaging
        s_num_reg = re.compile(".*s(\d+)a(?!.*/)") # sux to use filename. make more robust if needed.
        nii_by_series = {}
        fails = []
        extras = []
        for fn in nii_files:
            try:
                # we only want the first nii for each series
                # TODO: find out what those others (A/B) are all about. fix this as needed.
                sn = int(s_num_reg.match(fn).groups()[0])
                if sn in nii_by_series:
                    extras.append(fn)
                    continue
                nii_by_series[sn] = fn
            except Exception, e:
                fails.append(fn)
        if fails:
            raise ValueError("Could not derive series number from file names: %s." % str(fails))
        if extras:
            print >> sys.stderr, "\nWARNING: Ignoring extra niftis: %s\n" % str(extras)
        # add nifti names to the dicts
        m_count = 0
        for sn, fn in nii_by_series.iteritems():
            m = filter(lambda x: x.get("series_num",-1) == sn, dinfo)
            if not m:
                continue
            m_count += 1
            m[0]["nifti_file"] = fn
        if not m_count == len(dinfo):
            raise ValueError("incorrect number of nifti->series matches (%d/%d)" % (m_count, len(dinfo)))
        
        # time for some data wrangling
        nf = "nifti_file"
        sd = "series_desc"
        it = "image_type"
        t1fs = [d for d in filter(lambda x: sd in x and x[sd] in smap.get("t1",[]), dinfo) if nf in d]
        #if block_averaging:
        #    t1fs = [t1fs[0]]
        #    t2fs = [t2fs[0]]
        self.t1_files = [d[nf] for d in t1fs]
        #get rsfmri, if no "resting state return default file"
        bs = [d for d in filter(lambda x: sd in x and x[sd] in smap.get("rsfmri",[]), dinfo) if nf in d]
        self.rsfmri_files = [d[nf] for d in bs]
        #get dwi
        dwi = [d for d in filter(lambda x: sd in x and x[sd] in smap.get("dwi",[]), dinfo) if nf in d]
        
        if len(dwi)==0:
            print "no DTI acquired"
            self.dwi_files=['nothing to proceed']
            self.dwi_pa_files=['nothing to proceed']
            self.dwi_ap_files=['nothing to proceed']        
        else:
            self.dwi_files = [d[nf] for d in dwi]
            dwi_pa = [d for d in filter(lambda x: sd in x and x[sd] in smap.get("fieldmap_pa",[]), dinfo) if nf in d]
            self.dwi_pa_files = [d[nf] for d in dwi_pa]    
            dwi_ap = [d for d in filter(lambda x: sd in x and x[sd] in smap.get("fieldmap_ap",[]), dinfo) if nf in d]
            self.dwi_ap_files = [d[nf] for d in dwi_ap] 
 
        flair = [d for d in filter(lambda x: sd in x and x[sd] in smap.get("flair",[]), dinfo) if nf in d]
        if len(flair)==0:
            print "no FLAIR acquired"
            self.flair_files=['nothing to proceed']
        else:
            self.flair_files = [d[nf] for d in flair]
               
        # we have to do some extra looking at the headers for the mag and phase fieldmaps too
        mag_fs = filter(lambda x: sd in x and
                x[sd] in smap.get("fieldmap_magnitude",[]) and
                it in x and
                isinstance(x[it], list) and
                len(x[it]) > 2 and
                x[it][2].strip().lower() == "m", dinfo) # we want the 3rd field of image type to be 'm'
        phase_fs = filter(lambda x: sd in x and
                x[sd] in smap.get("fieldmap_phase",[]) and
                it in x and
                isinstance(x[it], list) and
                len(x[it]) > 2 and
                x[it][2].strip().lower() == "p", dinfo) # we want the 3rd field of image type to be 'p'
        self.fieldmap_mag = [d[nf] for d in mag_fs if nf in d]
        self.fieldmap_ph = [d[nf] for d in phase_fs if nf in d]
        
        # calculate echo spacing for rsfmri:   
        #if more than one resting state scan was acquired:
        ep_rsfmri_echo_fail = False
        ep_TR_fail=False
        if len(bs)>1:
            print "two or more resting-state scans"
            if isdefined(self.inputs.ep_rsfmri_echo_spacings):
                self.ep_rsfmri_echo_spacings = [self.inputs.ep_rsfmri_echo_spacings for n in self.rsfmri_files]
            elif bs and any(["bw_per_pix_phase_encode" in d and "acq_matrix_n" in d for d in bs]):
                if not all(["bw_per_pix_phase_encode" in d and "acq_matrix_n" in d for d in bs]):
                    ep_rsfmri_echo_fail = True
                else:
                    self.ep_rsfmri_echo_spacings = [1/(d["bw_per_pix_phase_encode"] * d["acq_matrix_n"]) for d in bs]
            else:
                self.ep_rsfmri_echo_spacings = ["NONE" for n in self.rsfmri_files]
            
            if isdefined(self.inputs.ep_TR):
                self.ep_TR=[self.inputs.ep_TR for n in self.rsfmri_files]
            elif bs and any(["TR" in d for d in bs]):
                if not all(["TR" in d for d in bs]):
                    ep_TR_fail = True
                else: 
                    self.ep_TR=[d["TR"] for d in bs]
            else:
                 self.ep_TR= ["NONE" for n in self.rsfmri_files]
        else:
            print "one resting-state scan"
            if isdefined(self.inputs.ep_rsfmri_echo_spacings):
                self.ep_rsfmri_echo_spacings = self.inputs.ep_rsfmri_echo_spacings
            elif bs and "bw_per_pix_phase_encode" in bs[0] and "acq_matrix_n" in bs[0]:
                if not "bw_per_pix_phase_encode" in bs[0] and "acq_matrix_n" in bs[0]:
                    ep_rsfmri_echo_fail = True
                else:
                    self.ep_rsfmri_echo_spacings = 1/(bs[0]["bw_per_pix_phase_encode"] * bs[0]["acq_matrix_n"])
            else:
                self.ep_rsfmri_echo_spacings = "NONE" 
            
            if isdefined(self.inputs.ep_TR):
                self.ep_TR=self.inputs.ep_TR
            elif bs and "TR" in bs[0]:
                if not "TR" in bs[0]:
                    ep_TR_fail = True
                else: 
                    self.ep_TR=bs[0]["TR"]
            else:
                 self.ep_TR= "NONE"

            
        ep_dwi_echo_fail = False
        if len(dwi)>1:
            print "more than one dwi scan"
            if isdefined(self.inputs.ep_dwi_echo_spacings):
                self.ep_dwi_echo_spacings = [self.inputs.ep_dwi_echo_spacings for n in self.dwi_files]
            elif bs and any(["bw_per_pix_phase_encode" in d and "acq_matrix_n" in d for d in dwi]):
                if not all(["bw_per_pix_phase_encode" in d and "acq_matrix_n" in d for d in dwi]):
                    ep_dwi_echo_fail = True
                else:
                    self.ep_dwi_echo_spacings = [1/(d["bw_per_pix_phase_encode"] * d["acq_matrix_n"]) for d in dwi]
            else:
                self.ep_dwi_echo_spacings = ["NONE" for n in self.dwi_files]       
        else:
            print "one dwi scan"
            if isdefined(self.inputs.ep_dwi_echo_spacings):
                self.ep_dwi_echo_spacings = self.inputs.ep_dwi_echo_spacings
            elif bs and "bw_per_pix_phase_encode" in bs[0] and "acq_matrix_n" in bs[0]:
                if not "bw_per_pix_phase_encode" in bs[0] and "acq_matrix_n" in bs[0]:
                    ep_dwi_echo_fail = True
                else:
                    self.ep_dwi_echo_spacings = 1/(bs[0]["bw_per_pix_phase_encode"] * bs[0]["acq_matrix_n"])
            else:
                self.ep_dwi_echo_spacings = "NONE"
         
        # output delta te for magnitude fieldmap if available
        if mag_fs and self.fieldmap_mag and self.fieldmap_ph:
            self.fieldmap_mag_delta_te = mag_fs[0].get("delta_te","NONE")
        else:
            self.fieldmap_mag_delta_te = "NONE"
#        # a BOLD image by any other name...
#        self.bold_names = ["bold_%d" % n for n in xrange(len(self.bolds))]
#        # we'll derive the ep unwarp dir in the future... for now, just set it in config
        if isdefined(self.inputs.ep_unwarp_dir):
            self.ep_unwarp_dirs = [self.inputs.ep_unwarp_dir for n in self.rsfmri_files]
            # if you have any polarity swapped series, apply that now
            pswaps = smap.get("polarity_swapped", [])
            if pswaps:
                for b_idx, uw_dir in enumerate(self.ep_unwarp_dirs):
                    if bs[b_idx].get("series_desc",None) in pswaps:
                        raw_dir = uw_dir.replace("-","")
                        self.ep_unwarp_dirs[b_idx] = "-"+raw_dir if not "-" in uw_dir else raw_dir
        else:
            # fail. we can do better!
            raise ValueError("We can't derive ep_unwarp_dir yet. Please set it in the nii wrangler config section.")
        ### and let's do some sanity checking
        # warn if you're going to ignore field or magnitude phase maps
        if not (len(self.fieldmap_mag) and len(self.fieldmap_ph)):
            print >> sys.stderr, "\nWARNING: found %d magnitude fieldmaps and %d phase fieldmaps.\n" % (len(self.fieldmap_mag), len(self.fieldmap_ph))

        ### derive the derived values
        # t1_sample_spacing
        if t1fs and "RealDwellTime" in t1fs[0].keys():
            self.t1_sample_spacing = t1fs[0]["RealDwellTime"] * math.pow(10,-9)
        else:
            self.t1_sample_spacing = "NONE"
        # don't continue if there was screwiness around calculating ep echo spacing
        if ep_rsfmri_echo_fail:
            raise ValueError("Unabel to calculate fmri ep echo spacing. Try specifying manually in nii wrangler config section.")
        if ep_dwi_echo_fail:
            raise ValueError("Unabel to calculate dwi ep echo spacing. Try specifying manually in nii wrangler config section.")
        if ep_TR_fail:
            raise ValueError("Unabel to derive TR. Try specifying manually in nii wrangler config section.")
        return runtime
Esempio n. 12
0
    def connect(self,
                nodehierarchy,
                node,
                source_file=None,
                subject_id=None,
                **_):
        """
        connect equally names attrs
        preferentially use datasinked outputs
        """

        connected_attrs = set()

        inputattrs = set(node.inputs.copyable_trait_names())
        dsattrs = set(attr for attr in inputattrs if attr.startswith("ds_"))

        for key, value in node.inputs.get().items():
            if isdefined(value):
                inputattrs.remove(key)

        ignore = frozenset(
            ["alt_bold_mask_std", "alt_bold_std", "alt_spatial_reference"])
        inputattrs -= ignore

        def _connect(hierarchy):
            wf = hierarchy[-1]

            outputnode: Optional[pe.Node] = wf.get_node("outputnode")
            if outputnode is not None:
                outputattrs = set(outputnode.outputs.copyable_trait_names())
                attrs = (inputattrs & outputattrs
                         ) - connected_attrs  # find common attr names

                actually_connected_attrs = set()
                for _, _, datadict in wf._graph.in_edges(outputnode,
                                                         data=True):
                    _, infields = zip(*datadict.get("connect", []))
                    actually_connected_attrs.update(infields)

                for key, value in outputnode.inputs.get().items():
                    if isdefined(value):
                        actually_connected_attrs.add(key)

                attrs &= actually_connected_attrs

                for attr in attrs:
                    self.connect_attr(hierarchy, outputnode, attr,
                                      nodehierarchy, node, attr)
                    connected_attrs.add(attr)

            for attr in list(dsattrs):
                childtpl = _find_child(hierarchy, attr)
                if childtpl is not None:
                    childhierarchy, childnode = childtpl
                    childhierarchy, childnode, childattr = _find_input(
                        childhierarchy, childnode, "in_file")
                    self.connect_attr(childhierarchy, childnode, childattr,
                                      nodehierarchy, node, attr)
                    dsattrs.remove(attr)
                    connected_attrs.add(attr)

        hierarchy = self._get_hierarchy("fmriprep_wf",
                                        source_file=source_file,
                                        subject_id=subject_id)

        wf = hierarchy[-1]

        # anat only
        anat_wf = wf.get_node("anat_preproc_wf")

        if anat_wf is None:
            # func first
            _connect(hierarchy)

            if "skip_vols" in inputattrs:
                initial_boldref_wf = wf.get_node("initial_boldref_wf")
                assert isinstance(initial_boldref_wf, pe.Workflow)
                outputnode = initial_boldref_wf.get_node("outputnode")
                self.connect_attr(
                    [*hierarchy, initial_boldref_wf],
                    outputnode,
                    "skip_vols",
                    nodehierarchy,
                    node,
                    "skip_vols",
                )
                connected_attrs.add("skip_vols")

            for name in [
                    "bold_bold_trans_wf",
                    "bold_hmc_wf",
                    "final_boldref_wf",
                    "bold_reg_wf",
                    "sdc_estimate_wf",
                    "sdc_bypass_wf",
                    "sdc_unwarp_report_wf",
                    "bold_std_trans_wf",
                    "bold_surf_wf",
                    "bold_confounds_wf",
            ]:
                bold_wf = wf.get_node(name)
                if bold_wf is not None:
                    _connect([*hierarchy, bold_wf])

            if "bold_split" in inputattrs:
                splitnode = wf.get_node("split_opt_comb")
                if splitnode is None:
                    splitnode = wf.get_node("bold_split")
                self.connect_attr(hierarchy, splitnode, "out_files",
                                  nodehierarchy, node, "bold_split")
                connected_attrs.add("bold_split")

            report_hierarchy = self._get_hierarchy("reports_wf",
                                                   source_file=source_file,
                                                   subject_id=subject_id)
            func_report_wf = report_hierarchy[-1].get_node(
                "func_report_wf")  # this is not part of fmriprep
            if func_report_wf is not None:
                _connect([*report_hierarchy, func_report_wf])

            while wf.get_node("anat_preproc_wf") is None:
                hierarchy.pop()
                wf = hierarchy[-1]

            anat_wf = wf.get_node("anat_preproc_wf")

        assert isinstance(anat_wf, pe.Workflow)
        for name in ["anat_norm_wf", "anat_reports_wf"]:
            wf = anat_wf.get_node(name)
            _connect([*hierarchy, anat_wf, wf])
        _connect([*hierarchy, anat_wf])

        if connected_attrs != inputattrs:
            missing_attrs = sorted(inputattrs - connected_attrs)
            logger.info(
                f"Unable to find fMRIPrep outputs {p.join(missing_attrs)} "
                f"for workflow {nodehierarchy}")

        return