diff --git a/.circleci/config.yml b/.circleci/config.yml index 85cdeb7c78..d3ff6bb2d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,88 +1,64 @@ -version: 2 -jobs: - pytest-docker: - machine: true +version: 2.1 + +commands: + check-for-built-images: steps: - - attach_workspace: - # Must be absolute path or relative path from working_directory - at: /home/circleci/ - run: - name: "Set Python Version" + name: "Checking for locally built images" command: | - pyenv install 3.6.3 - pyenv global 3.6.3 - - run: - name: Get Sample BIDS Data - command: git clone https://github.com/bids-standard/bids-examples.git - - run: - name: pytest - command: | - docker load < cpac-docker-image.tar.gz - docker run -dit -P -v /home/circleci/project/test-results:/code/test-results -v /home/circleci/project/htmlcov:/code/htmlcov --entrypoint=/bin/bash --name docker_test fcpindi/c-pac:${CIRCLE_BRANCH//\//_} - docker exec docker_test /bin/bash ./code/dev/circleci_data/test_in_image.sh - - store_test_results: - path: test-results - - store_artifacts: - path: test-results - - store_artifacts: - path: htmlcov - no_output_timeout: 5h - pytest-singularity: - machine: true + if [[ ! -f cpac-docker-image.tar.gz || ! -f cpac-singularity-image.simg ]] + then + circleci step halt + fi + set-python-version: steps: - - attach_workspace: - # Must be absolute path or relative path from working_directory - at: /home/circleci/ - run: - name: "Set Python Version" + name: "Setting Python Version" command: | pyenv install 3.6.3 pyenv global 3.6.3 + install-singularity-requirements: + steps: - run: - name: test_install + name: "Installing Singularity requirements" command: | - sudo apt-get update && sudo apt-get install flawfinder squashfs-tools uuid-dev libuuid1 libffi-dev libssl-dev libssl1.0.0 libarchive-dev libgpgme11-dev libseccomp-dev -y - cd singularity - ./autogen.sh - ./configure --prefix=/usr/local --sysconfdir=/etc - make - sudo make install - cd .. - pip install -r dev/circleci_data/requirements.txt - coverage run -m pytest --junitxml=test-results/junit.xml --continue-on-collection-errors dev/circleci_data/test_install.py - build: - machine: true + sudo apt-get update && sudo apt-get install flawfinder squashfs-tools uuid-dev libuuid1 libffi-dev libssl-dev libssl1.0.0 libarchive-dev libgpgme11-dev libseccomp-dev -y + set-up-singularity: steps: - - checkout - run: - name: "Set Python Version" - command: | - pyenv install 3.6.3 - pyenv global 3.6.3 + name: "Setting up Singularity" + command: | + cd singularity + ./autogen.sh + ./configure --prefix=/usr/local --sysconfdir=/etc + make + sudo make install + cd .. + build-images: + steps: - run: - name: build-docker + name: "Building Docker image" command: | docker build -t fcpindi/c-pac:${CIRCLE_BRANCH//\//_} . docker save fcpindi/c-pac:${CIRCLE_BRANCH//\//_} | gzip > cpac-docker-image.tar.gz + no_output_timeout: 5h # Persist the specified paths (workspace/echo-output) into the workspace for use in downstream job. - run: - name: local-registry + name: "Starting local registry" command: docker run -d -p 5000:5000 --restart=always --name registry registry:2 + - install-singularity-requirements + - run: + name: "Cloning Singularity 2.5.2" + command: git clone -b 2.5.2 https://github.com/sylabs/singularity + - set-up-singularity - run: - name: build-singularity + name: "Building Singularity image from Docker image" command: | - sudo apt-get update && sudo apt-get install flawfinder squashfs-tools uuid-dev libuuid1 libffi-dev libssl-dev libssl1.0.0 libarchive-dev libgpgme11-dev libseccomp-dev -y - git clone -b 2.5.2 https://github.com/sylabs/singularity - cd singularity - ./autogen.sh - ./configure --prefix=/usr/local --sysconfdir=/etc - make - sudo make install - cd .. docker load < cpac-docker-image.tar.gz docker tag fcpindi/c-pac:${CIRCLE_BRANCH//\//_} localhost:5000/fcpindi/c-pac:${CIRCLE_BRANCH//\//_} docker push localhost:5000/fcpindi/c-pac:${CIRCLE_BRANCH//\//_} - SINGULARITY_NOHTTPS=1 singularity build C-PAC-CI.simg docker://localhost:5000/fcpindi/c-pac:${CIRCLE_BRANCH//\//_} + SINGULARITY_NOHTTPS=1 singularity build C-PAC-CI.simg docker://localhost:5000/fcpindi/c-pac:${CIRCLE_BRANCH//\//_} + no_output_timeout: 5h - store_artifacts: path: cpac-docker-image.tar.gz - store_artifacts: @@ -94,63 +70,90 @@ jobs: root: /home/circleci/ # Must be relative path from root paths: project - heatmaps: + configure-git-user: + steps: + - add_ssh_keys: + fingerprints: + - "12:bc:f2:e4:31:cc:72:54:54:bc:f5:5b:89:e6:d8:ee" + - run: + name: "Configuring git user" + command: | + git config --global user.email "${CIRCLE_USERNAME}@users.noreply.github.com" + git config --global user.name "${CIRCLE_USERNAME} @ CircleCI" + +jobs: + pytest-docker: machine: true steps: - attach_workspace: # Must be absolute path or relative path from working_directory at: /home/circleci/ + - check-for-built-images + - set-python-version - run: - name: "Set Python Version" - command: | - pyenv install 3.6.3 - pyenv global 3.6.3 + name: Getting Sample BIDS Data + command: git clone https://github.com/bids-standard/bids-examples.git - run: - name: ≟ - command: python dev/circleci_data/scripts/compare.py - test: + name: Running pytest on Docker image + command: | + docker load < cpac-docker-image.tar.gz + docker run -dit -P -v /home/circleci/project/test-results:/code/test-results -v /home/circleci/project/htmlcov:/code/htmlcov --entrypoint=/bin/bash --name docker_test fcpindi/c-pac:${CIRCLE_BRANCH//\//_} + docker exec docker_test /bin/bash ./code/dev/circleci_data/test_in_image.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test-results + - store_artifacts: + path: htmlcov + no_output_timeout: 5h + pytest-singularity: machine: true steps: - attach_workspace: # Must be absolute path or relative path from working_directory at: /home/circleci/ + - check-for-built-images + - set-python-version + - install-singularity-requirements + - set-up-singularity - run: - name: "Set Python Version" + name: Testing Singularity installation command: | - pyenv install 3.6.3 - pyenv global 3.6.3 + pip install -r dev/circleci_data/requirements.txt + coverage run -m pytest --junitxml=test-results/junit.xml --continue-on-collection-errors dev/circleci_data/test_install.py + build: + machine: true + steps: + - checkout + - set-python-version + - configure-git-user - run: - name: Run C-PAC + name: "Checking if version needs updated" + # update version if version needs updated, otherwise just move on command: | - docker load < cpac-docker-image.tar.gz - docker run -i --rm -v $HOME:/output -v /tmp:/tmp cpac:${CIRCLE_BRANCH//\//_} s3://fcp-indi/data/Projects/RocklandSample/RawDataBIDS /output participant --participant_label A00028185 --pipeline_file /cpac_resources/pipe-test_ci.yml - no_output_timeout: 3h + if [[ ! $(git log -1 --pretty=%B) == *"Update version to"* ]] + then + cd $HOME/project/CPAC + VERSION=$(python -c "from info import __version__; print(__version__)") + cd .. + echo "v${VERSION}" > version + find ./CPAC/resources/configs -name "*.yml" -exec sed -i -r "s/^(# [Vv]ersion ).*$/# Version ${VERSION}/g" {} \; + git add version CPAC/resources/configs + if [[ ! -z $(git diff origin/${CIRCLE_BRANCH}) ]] + then + git commit -m ":bookmark: Update version to ${VERSION}" + git push origin HEAD:${CIRCLE_BRANCH} || true + circleci step halt + fi + cd .. + fi + - build-images workflows: version: 2 - build_and_test: + build-and-test: jobs: - - build: - filters: - branches: - ignore: - - feature/auto-heatmaps - - config/test_config - - test: - requires: - - build - filters: - branches: - only: - - develop - - master - - heatmaps: - requires: - - build - - test - filters: - branches: - only: feature/auto-heatmaps + - build - pytest-docker: requires: - build diff --git a/CPAC/__main__.py b/CPAC/__main__.py index ee5f88404e..82f25bf00d 100644 --- a/CPAC/__main__.py +++ b/CPAC/__main__.py @@ -321,7 +321,8 @@ def accept_all(object, name, value): @utils.group() def data_config(): - pass + from CPAC.utils.ga import track_config + track_config('cli') @data_config.command() diff --git a/CPAC/anat_preproc/anat_preproc.py b/CPAC/anat_preproc/anat_preproc.py index 71687e1465..586acdc820 100644 --- a/CPAC/anat_preproc/anat_preproc.py +++ b/CPAC/anat_preproc/anat_preproc.py @@ -3,10 +3,13 @@ from nipype.interfaces import afni from nipype.interfaces import ants from nipype.interfaces import fsl +from nipype.interfaces.fsl import utils as fsl_utils +from nipype.interfaces.fsl import preprocess as fsl_preproc import nipype.pipeline.engine as pe import nipype.interfaces.utility as util from CPAC.anat_preproc.ants import init_brain_extraction_wf -from CPAC.anat_preproc.utils import create_3dskullstrip_arg_string +from CPAC.anat_preproc.utils import create_3dskullstrip_arg_string, \ + fsl_aff_to_rigid from CPAC.utils.datasource import create_check_for_s3_node from CPAC.unet.function import predict_volumes @@ -28,10 +31,501 @@ def patch_cmass_output(lst, index=0): raise IndexError("lst index out of range") return lst[index] -def create_anat_preproc(method='afni', already_skullstripped=False, config=None, wf_name='anat_preproc'): - """The main purpose of this workflow is to process T1 scans. Raw mprage file is deobliqued, reoriented - into RPI and skullstripped. Also, a whole brain only mask is generated from the skull stripped image - for later use in registration. + +def acpc_alignment(skullstrip_tool='afni', config=None, acpc_target='whole-head', wf_name='acpc_align'): + + preproc = pe.Workflow(name=wf_name) + + inputnode = pe.Node(util.IdentityInterface(fields=['anat_leaf', + 'brain_mask', + 'template_head', + 'template_brain']), + name='inputspec') + + output_node = pe.Node(util.IdentityInterface(fields=['acpc_aligned_head', + 'acpc_brain_mask']), + name='outputspec') + + robust_fov = pe.Node(interface=fsl_utils.RobustFOV(), + name='anat_acpc_1_robustfov') + robust_fov.inputs.brainsize = config.acpc_brainsize + robust_fov.inputs.out_transform = 'fov_xfm.mat' + + # align head-to-head to get acpc.mat (for human) + if acpc_target == 'whole-head': + preproc.connect(inputnode, 'anat_leaf', robust_fov, 'in_file') + + # align brain-to-brain to get acpc.mat (for monkey) + if acpc_target == 'brain': + initial_skullstrip = skullstrip_anatomical(method=skullstrip_tool, config=config, + wf_name="anat_acpc_0_pre") + preproc.connect(inputnode, 'anat_leaf', + initial_skullstrip, 'inputspec.anat_data') + if skullstrip_tool == 'mask': + preproc.connect(inputnode, 'brain_mask', + initial_skullstrip, 'inputspec.brain_mask') + preproc.connect(initial_skullstrip, 'outputspec.brain', + robust_fov, 'in_file') + + convert_fov_xfm = pe.Node(interface=fsl_utils.ConvertXFM(), + name='anat_acpc_2_fov_convertxfm') + convert_fov_xfm.inputs.invert_xfm = True + + preproc.connect(robust_fov, 'out_transform', + convert_fov_xfm, 'in_file') + + align = pe.Node(interface=fsl.FLIRT(), + name='anat_acpc_3_flirt') + align.inputs.interp = 'spline' + align.inputs.searchr_x = [30, 30] + align.inputs.searchr_y = [30, 30] + align.inputs.searchr_z = [30, 30] + + preproc.connect(robust_fov, 'out_roi', align, 'in_file') + + # align head-to-head to get acpc.mat (for human) + if acpc_target == 'whole-head': + preproc.connect(inputnode, 'template_head', align, 'reference') + + # align brain-to-brain to get acpc.mat (for monkey) + if acpc_target=='brain': + preproc.connect(inputnode, 'template_brain', align, 'reference') + + concat_xfm = pe.Node(interface=fsl_utils.ConvertXFM(), + name='anat_acpc_4_concatxfm') + concat_xfm.inputs.concat_xfm = True + + preproc.connect(convert_fov_xfm, 'out_file', concat_xfm, 'in_file') + preproc.connect(align, 'out_matrix_file', concat_xfm, 'in_file2') + + aff_to_rig_imports = ['import os', 'from numpy import *'] + aff_to_rig = pe.Node(util.Function(input_names=['in_xfm', 'out_name'], + output_names=['out_mat'], + function=fsl_aff_to_rigid, + imports=aff_to_rig_imports), + name='anat_acpc_5_aff2rigid') + aff_to_rig.inputs.out_name = 'acpc.mat' + + preproc.connect(concat_xfm, 'out_file', aff_to_rig, 'in_xfm') + + apply_xfm = pe.Node(interface=fsl.ApplyWarp(), + name='anat_acpc_6_applywarp') + apply_xfm.inputs.interp = 'spline' + apply_xfm.inputs.relwarp = True + + preproc.connect(inputnode, 'anat_leaf', apply_xfm, 'in_file') + preproc.connect(inputnode, 'template_head', apply_xfm, 'ref_file') + preproc.connect(aff_to_rig, 'out_mat', apply_xfm, 'premat') + preproc.connect(apply_xfm, 'out_file', output_node, 'acpc_aligned_head') + + if skullstrip_tool == 'mask': + apply_xfm_mask = pe.Node(interface=fsl.ApplyWarp(), + name='anat_mask_acpc_7_applywarp') + apply_xfm_mask.inputs.interp = 'nn' + apply_xfm_mask.inputs.relwarp = True + + preproc.connect(inputnode, 'brain_mask', apply_xfm_mask, 'in_file') + preproc.connect(inputnode, 'template_brain', apply_xfm_mask, 'ref_file') + preproc.connect(aff_to_rig, 'out_mat', apply_xfm_mask, 'premat') + preproc.connect(apply_xfm_mask, 'out_file', output_node, 'acpc_brain_mask') + + + return preproc + +def skullstrip_anatomical(method='afni', config=None, wf_name='skullstrip_anatomical'): + + method = method.lower() + + preproc = pe.Workflow(name=wf_name) + + inputnode = pe.Node(util.IdentityInterface(fields=['anat_data', + 'brain_mask', + 'template_brain_only_for_anat', + 'template_skull_for_anat']), + name='inputspec') + outputnode = pe.Node(util.IdentityInterface(fields=['skullstrip', + 'brain', + 'brain_mask']), + name='outputspec') + + if method == 'afni': + # Skull-stripping using AFNI 3dSkullStrip + inputnode_afni = pe.Node( + util.IdentityInterface(fields=['mask_vol', + 'shrink_factor', + 'var_shrink_fac', + 'shrink_fac_bot_lim', + 'avoid_vent', + 'niter', + 'pushout', + 'touchup', + 'fill_hole', + 'avoid_eyes', + 'use_edge', + 'exp_frac', + 'smooth_final', + 'push_to_edge', + 'use_skull', + 'perc_int', + 'max_inter_iter', + 'blur_fwhm', + 'fac', + 'monkey']), + name='AFNI_options') + + skullstrip_args = pe.Node(util.Function(input_names=['spat_norm', + 'spat_norm_dxyz', + 'mask_vol', + 'shrink_fac', + 'var_shrink_fac', + 'shrink_fac_bot_lim', + 'avoid_vent', + 'niter', + 'pushout', + 'touchup', + 'fill_hole', + 'avoid_eyes', + 'use_edge', + 'exp_frac', + 'smooth_final', + 'push_to_edge', + 'use_skull', + 'perc_int', + 'max_inter_iter', + 'blur_fwhm', + 'fac', + 'monkey'], + output_names=['expr'], + function=create_3dskullstrip_arg_string), + name='anat_skullstrip_args') + + inputnode_afni.inputs.set( + mask_vol=config.skullstrip_mask_vol, + shrink_factor=config.skullstrip_shrink_factor, + var_shrink_fac=config.skullstrip_var_shrink_fac, + shrink_fac_bot_lim=config.skullstrip_shrink_factor_bot_lim, + avoid_vent=config.skullstrip_avoid_vent, + niter=config.skullstrip_n_iterations, + pushout=config.skullstrip_pushout, + touchup=config.skullstrip_touchup, + fill_hole=config.skullstrip_fill_hole, + avoid_eyes=config.skullstrip_avoid_eyes, + use_edge=config.skullstrip_use_edge, + exp_frac=config.skullstrip_exp_frac, + smooth_final=config.skullstrip_smooth_final, + push_to_edge=config.skullstrip_push_to_edge, + use_skull=config.skullstrip_use_skull, + perc_int=config.skullstrip_perc_int, + max_inter_iter=config.skullstrip_max_inter_iter, + blur_fwhm=config.skullstrip_blur_fwhm, + fac=config.skullstrip_fac, + monkey=config.skullstrip_monkey, + ) + + preproc.connect([ + (inputnode_afni, skullstrip_args, [ + ('mask_vol', 'mask_vol'), + ('shrink_factor', 'shrink_fac'), + ('var_shrink_fac', 'var_shrink_fac'), + ('shrink_fac_bot_lim', 'shrink_fac_bot_lim'), + ('avoid_vent', 'avoid_vent'), + ('niter', 'niter'), + ('pushout', 'pushout'), + ('touchup', 'touchup'), + ('fill_hole', 'fill_hole'), + ('avoid_eyes', 'avoid_eyes'), + ('use_edge', 'use_edge'), + ('exp_frac', 'exp_frac'), + ('smooth_final', 'smooth_final'), + ('push_to_edge', 'push_to_edge'), + ('use_skull', 'use_skull'), + ('perc_int', 'perc_int'), + ('max_inter_iter', 'max_inter_iter'), + ('blur_fwhm', 'blur_fwhm'), + ('fac', 'fac'), + ('monkey','monkey') + ]) + ]) + + anat_skullstrip = pe.Node(interface=afni.SkullStrip(), + name='anat_skullstrip') + + anat_skullstrip.inputs.outputtype = 'NIFTI_GZ' + + preproc.connect(inputnode, 'anat_data', + anat_skullstrip, 'in_file') + + preproc.connect(skullstrip_args, 'expr', + anat_skullstrip, 'args') + + # Generate anatomical brain mask + anat_brain_mask = pe.Node(interface=afni.Calc(), + name='anat_brain_mask') + + anat_brain_mask.inputs.expr = 'step(a)' + anat_brain_mask.inputs.outputtype = 'NIFTI_GZ' + + preproc.connect(anat_skullstrip, 'out_file', + anat_brain_mask, 'in_file_a') + + # Apply skull-stripping step mask to original volume + anat_skullstrip_orig_vol = pe.Node(interface=afni.Calc(), + name='anat_skullstrip_orig_vol') + + anat_skullstrip_orig_vol.inputs.expr = 'a*step(b)' + anat_skullstrip_orig_vol.inputs.outputtype = 'NIFTI_GZ' + + preproc.connect(inputnode, 'anat_data', + anat_skullstrip_orig_vol, 'in_file_a') + + preproc.connect(anat_brain_mask, 'out_file', + anat_skullstrip_orig_vol, 'in_file_b') + + preproc.connect(anat_brain_mask, 'out_file', + outputnode, 'brain_mask') + + preproc.connect(anat_skullstrip_orig_vol, 'out_file', + outputnode, 'brain') + + elif method == 'fsl': + # Skull-stripping using FSL BET + inputnode_bet = pe.Node( + util.IdentityInterface(fields=['frac', + 'mask_boolean', + 'mesh_boolean', + 'outline', + 'padding', + 'radius', + 'reduce_bias', + 'remove_eyes', + 'robust', + 'skull', + 'surfaces', + 'threshold', + 'vertical_gradient']), + name='BET_options') + + anat_skullstrip = pe.Node( + interface=fsl.BET(), name='anat_skullstrip') + anat_skullstrip.inputs.output_type = 'NIFTI_GZ' + + inputnode_bet.inputs.set( + frac=config.bet_frac, + mask_boolean=config.bet_mask_boolean, + mesh_boolean=config.bet_mesh_boolean, + outline=config.bet_outline, + padding=config.bet_padding, + radius=config.bet_radius, + reduce_bias=config.bet_reduce_bias, + remove_eyes=config.bet_remove_eyes, + robust=config.bet_robust, + skull=config.bet_skull, + surfaces=config.bet_surfaces, + threshold=config.bet_threshold, + vertical_gradient=config.bet_vertical_gradient, + ) + + preproc.connect(inputnode, 'anat_data', + anat_skullstrip, 'in_file') + + preproc.connect([ + (inputnode_bet, anat_skullstrip, [ + ('frac', 'frac'), + ('mask_boolean', 'mask'), + ('mesh_boolean', 'mesh'), + ('outline', 'outline'), + ('padding', 'padding'), + ('radius', 'radius'), + ('reduce_bias', 'reduce_bias'), + ('remove_eyes', 'remove_eyes'), + ('robust', 'robust'), + ('skull', 'skull'), + ('surfaces', 'surfaces'), + ('threshold', 'threshold'), + ('vertical_gradient', 'vertical_gradient'), + ]) + ]) + + preproc.connect(anat_skullstrip, 'out_file', + outputnode, 'skullstrip') + + # Apply skull-stripping step mask to original volume + anat_skullstrip_orig_vol = pe.Node(interface=afni.Calc(), + name='anat_skullstrip_orig_vol') + + anat_skullstrip_orig_vol.inputs.expr = 'a*step(b)' + anat_skullstrip_orig_vol.inputs.outputtype = 'NIFTI_GZ' + + preproc.connect(inputnode, 'anat_data', + anat_skullstrip_orig_vol, 'in_file_a') + + preproc.connect(anat_skullstrip, 'out_file', + anat_skullstrip_orig_vol, 'in_file_b') + + preproc.connect(anat_skullstrip, 'mask_file', + outputnode, 'brain_mask') + + preproc.connect(anat_skullstrip_orig_vol, 'out_file', + outputnode, 'brain') + + elif method == 'niworkflows-ants': + # Skull-stripping using niworkflows-ants + anat_skullstrip_ants = init_brain_extraction_wf(tpl_target_path=config.niworkflows_ants_template_path, + tpl_mask_path=config.niworkflows_ants_mask_path, + tpl_regmask_path=config.niworkflows_ants_regmask_path, + name='anat_skullstrip_ants') + + preproc.connect(inputnode, 'anat_data', + anat_skullstrip_ants, 'inputnode.in_files') + + preproc.connect(anat_skullstrip_ants, 'copy_xform.out_file', + outputnode, 'skullstrip') + + preproc.connect(anat_skullstrip_ants, 'copy_xform.out_file', + outputnode, 'brain') + + preproc.connect(anat_skullstrip_ants, 'atropos_wf.copy_xform.out_mask', + outputnode, 'brain_mask') + + elif method == 'mask': + + brain_mask_deoblique = pe.Node(interface=afni.Refit(), + name='brain_mask_deoblique') + brain_mask_deoblique.inputs.deoblique = True + preproc.connect(inputnode, 'brain_mask', + brain_mask_deoblique, 'in_file') + + brain_mask_reorient = pe.Node(interface=afni.Resample(), + name='brain_mask_reorient') + brain_mask_reorient.inputs.orientation = 'RPI' + brain_mask_reorient.inputs.outputtype = 'NIFTI_GZ' + preproc.connect(brain_mask_deoblique, 'out_file', + brain_mask_reorient, 'in_file') + + + anat_skullstrip_orig_vol = pe.Node(interface=afni.Calc(), + name='anat_skullstrip_orig_vol') + anat_skullstrip_orig_vol.inputs.expr = 'a*step(b)' + anat_skullstrip_orig_vol.inputs.outputtype = 'NIFTI_GZ' + + preproc.connect(inputnode, 'anat_data', + anat_skullstrip_orig_vol, 'in_file_a') + + preproc.connect(brain_mask_reorient, 'out_file', + anat_skullstrip_orig_vol, 'in_file_b') + + preproc.connect(brain_mask_reorient, 'out_file', + outputnode, 'brain_mask') + + preproc.connect(anat_skullstrip_orig_vol, 'out_file', + outputnode, 'brain') + + elif method == 'unet': + """ + UNet + options (following numbers are default): + input_slice: 3 + conv_block: 5 + kernel_root: 16 + rescale_dim: 256 + """ + # TODO: add options to pipeline_config + unet_check_for_s3 = create_check_for_s3_node('unet', config.unet_model) + unet_mask = pe.Node(util.Function(input_names=['model_path', 'cimg_in'], + output_names=['out_path'], + function=predict_volumes), + name='unet_mask') + + preproc.connect(unet_check_for_s3, 'local_path', unet_mask, 'model_path') + preproc.connect(inputnode, 'anat_data', unet_mask, 'cimg_in') + + """ + Revised mask with ANTs + """ + # fslmaths -mul brain.nii.gz + unet_masked_brain = pe.Node(interface=fsl.MultiImageMaths(), name='unet_masked_brain') + unet_masked_brain.inputs.op_string = "-mul %s" + preproc.connect(inputnode, 'anat_data', unet_masked_brain, 'in_file') + preproc.connect(unet_mask, 'out_path', unet_masked_brain, 'operand_files') + + # flirt -v -dof 6 -in brain.nii.gz -ref NMT_SS_0.5mm.nii.gz -o brain_rot2atl -omat brain_rot2atl.mat -interp sinc + # TODO: antsRegistration -z 0 -d 3 -r [NMT_SS_0.5mm.nii.gz,brain.nii.gz,0] -o [transform,brain_rot2atl.nii.gz,brain_inv_rot2atl.nii.gz] -t Rigid[0.1] -m MI[NMT_SS_0.5mm.nii.gz,brain.nii.gz,1,32,Regular,0.25] -c [1000x500x250x100,1e-08,10] -s 3.0x2.0x1.0x0.0 -f 8x4x2x1 -u 1 -t Affine[0.1] -m MI[NMT_SS_0.5mm.nii.gz,brain.nii.gz,1,32,Regular,0.25] -c [1000x500x250x100,1e-08,10] -s 3.0x2.0x1.0x0.0 -f 8x4x2x1 -u 1 + native_brain_to_template_brain = pe.Node(interface=fsl.FLIRT(), name='native_brain_to_template_brain') + native_brain_to_template_brain.inputs.dof = 6 + native_brain_to_template_brain.inputs.interp = 'sinc' + preproc.connect(unet_masked_brain, 'out_file', native_brain_to_template_brain, 'in_file') + preproc.connect(inputnode, 'template_brain_only_for_anat', native_brain_to_template_brain, 'reference') + + # flirt -in head.nii.gz -ref NMT_0.5mm.nii.gz -o head_rot2atl -applyxfm -init brain_rot2atl.mat + # TODO: antsApplyTransforms -d 3 -i head.nii.gz -r NMT_0.5mm.nii.gz -n Linear -o head_rot2atl.nii.gz -v -t transform1Rigid.mat -t transform2Affine.mat -t transform0DerivedInitialMovingTranslation.mat + native_head_to_template_head = pe.Node(interface=fsl.FLIRT(), name='native_head_to_template_head') + native_head_to_template_head.inputs.apply_xfm = True + preproc.connect(inputnode, 'anat_data', native_head_to_template_head, 'in_file') + preproc.connect(native_brain_to_template_brain, 'out_matrix_file', native_head_to_template_head, 'in_matrix_file') + preproc.connect(inputnode, 'template_skull_for_anat', native_head_to_template_head, 'reference') + + # fslmaths NMT_SS_0.5mm.nii.gz -bin templateMask.nii.gz + template_brain_mask = pe.Node(interface=fsl.maths.MathsCommand(), name='template_brain_mask') + template_brain_mask.inputs.args = '-bin' + preproc.connect(inputnode, 'template_brain_only_for_anat', template_brain_mask, 'in_file') + + # ANTS 3 -m CC[head_rot2atl.nii.gz,NMT_0.5mm.nii.gz,1,5] -t SyN[0.25] -r Gauss[3,0] -o atl2T1rot -i 60x50x20 --use-Histogram-Matching --number-of-affine-iterations 10000x10000x10000x10000x10000 --MI-option 32x16000 + ants_template_head_to_template = pe.Node(interface=ants.Registration(), name='template_head_to_template') + ants_template_head_to_template.inputs.metric = ['CC'] + ants_template_head_to_template.inputs.metric_weight = [1,5] + ants_template_head_to_template.inputs.transforms = ['SyN'] + ants_template_head_to_template.inputs.transform_parameters = [(0.25,)] + ants_template_head_to_template.inputs.interpolation = 'NearestNeighbor' + ants_template_head_to_template.inputs.number_of_iterations = [[60,50,20]] + ants_template_head_to_template.inputs.smoothing_sigmas = [[0.6,0.2,0.0]] + ants_template_head_to_template.inputs.shrink_factors = [[4,2,1]] + ants_template_head_to_template.inputs.convergence_threshold = [1.e-8] + preproc.connect(native_head_to_template_head, 'out_file', ants_template_head_to_template, 'fixed_image') + preproc.connect(inputnode, 'template_skull_for_anat', ants_template_head_to_template, 'moving_image') + # antsApplyTransforms -d 3 -i templateMask.nii.gz -t atl2T1rotWarp.nii.gz atl2T1rotAffine.txt -r brain_rot2atl.nii.gz -o brain_rot2atl_mask.nii.gz + template_head_transform_to_template = pe.Node(interface=ants.ApplyTransforms(), name='template_head_transform_to_template') + template_head_transform_to_template.inputs.dimension = 3 + preproc.connect(template_brain_mask, 'out_file', template_head_transform_to_template, 'input_image') + preproc.connect(native_brain_to_template_brain, 'out_file', template_head_transform_to_template, 'reference_image') + preproc.connect(ants_template_head_to_template, 'forward_transforms', template_head_transform_to_template, 'transforms') + + # TODO: replace convert_xfm and flirt with: + # antsApplyTransforms -d 3 -i brain_rot2atl_mask.nii.gz -r brain.nii.gz -n linear -o brain_mask.nii.gz -t [transform0DerivedInitialMovingTranslation.mat,1] -t [transform2Affine.mat,1] -t [transform1Rigid.mat,1] + # convert_xfm -omat brain_rot2native.mat -inverse brain_rot2atl.mat  + invt = pe.Node(interface=fsl.ConvertXFM(), name='convert_xfm') + invt.inputs.invert_xfm = True + preproc.connect(native_brain_to_template_brain, 'out_matrix_file', invt, 'in_file') + + # flirt -in brain_rot2atl_mask.nii.gz -ref brain.nii.gz -o brain_mask.nii.gz -applyxfm -init brain_rot2native.mat + template_brain_to_native_brain = pe.Node(interface=fsl.FLIRT(), name='template_brain_to_native_brain') + template_brain_to_native_brain.inputs.apply_xfm = True + preproc.connect(template_head_transform_to_template, 'output_image', template_brain_to_native_brain, 'in_file') + preproc.connect(unet_masked_brain, 'out_file', template_brain_to_native_brain, 'reference') + preproc.connect(invt, 'out_file', template_brain_to_native_brain, 'in_matrix_file') + + # fslmaths brain_mask.nii.gz -thr .5 -bin brain_mask_thr.nii.gz + refined_mask = pe.Node(interface=fsl.Threshold(), name='refined_mask') + refined_mask.inputs.thresh = 0.5 + refined_mask.inputs.args = '-bin' + preproc.connect(template_brain_to_native_brain, 'out_file', refined_mask, 'in_file') + + # get a new brain with mask + refined_brain = pe.Node(interface=fsl.MultiImageMaths(), name='refined_brain') + refined_brain.inputs.op_string = "-mul %s" + preproc.connect(inputnode, 'anat_data', refined_brain, 'in_file') + preproc.connect(refined_mask, 'out_file', refined_brain, 'operand_files') + + preproc.connect(refined_mask, 'out_file', outputnode, 'brain_mask') + preproc.connect(refined_brain, 'out_file', outputnode, 'brain') + + return preproc + +def create_anat_preproc(method='afni', already_skullstripped=False, + config=None, acpc_target='whole-head', wf_name='anat_preproc'): + """The main purpose of this workflow is to process T1 scans. Raw mprage + file is deobliqued, reoriented into RPI and skullstripped. Also, a whole + brain only mask is generated from the skull stripped image for later use + in registration. Returns ------- @@ -55,16 +549,19 @@ def create_anat_preproc(method='afni', already_skullstripped=False, config=None, Path to RPI oriented anatomical image outputspec.skullstrip : string - Path to skull stripped RPI oriented mprage file with normalized intensities. + Path to skull stripped RPI oriented mprage file with normalized + intensities. outputspec.brain : string - Path to skull stripped RPI brain image with original intensity values and not normalized or scaled. + Path to skull stripped RPI brain image with original intensity + values and not normalized or scaled. Order of commands: - Deobliqing the scans. :: 3drefit -deoblique mprage.nii.gz - - Re-orienting the Image into Right-to-Left Posterior-to-Anterior Inferior-to-Superior (RPI) orientation :: + - Re-orienting the Image into Right-to-Left Posterior-to-Anterior + Inferior-to-Superior (RPI) orientation :: 3dresample -orient RPI -prefix mprage_RPI.nii.gz -inset mprage.nii.gz @@ -76,7 +573,9 @@ def create_anat_preproc(method='afni', already_skullstripped=False, config=None, or using BET :: bet mprage_RPI.nii.gz - - The skull-stripping step modifies the intensity values. To get back the original intensity values, we do an element wise product of RPI data with step function of skull-stripped data :: + - The skull-stripping step modifies the intensity values. To get back the + original intensity values, we do an element wise product of RPI data + with step function of skull-stripped data :: 3dcalc -a mprage_RPI.nii.gz -b mprage_RPI_3dT.nii.gz -expr 'a*step(b)' @@ -112,55 +611,79 @@ def create_anat_preproc(method='afni', already_skullstripped=False, config=None, 'skullstrip', 'brain', 'brain_mask', + 'anat_skull_leaf', 'center_of_mass']), name='outputspec') anat_deoblique = pe.Node(interface=afni.Refit(), name='anat_deoblique') anat_deoblique.inputs.deoblique = True + preproc.connect(inputnode, 'anat', anat_deoblique, 'in_file') + preproc.connect(anat_deoblique, 'out_file', outputnode, 'refit') + + # Anatomical reorientation + anat_reorient = pe.Node(interface=afni.Resample(), + name='anat_reorient') + anat_reorient.inputs.orientation = 'RPI' + anat_reorient.inputs.outputtype = 'NIFTI_GZ' + + preproc.connect(anat_deoblique, 'out_file', anat_reorient, 'in_file') + preproc.connect(anat_reorient, 'out_file', outputnode, 'reorient') + + anat_leaf = pe.Node(util.IdentityInterface(fields=['anat_data']), + name='anat_leaf') + + if not config.acpc_align: + preproc.connect(anat_reorient, 'out_file', anat_leaf, 'anat_data') + + # ACPC alignment + if config.acpc_align: + acpc_align = acpc_alignment(skullstrip_tool=method, config=config, acpc_target=acpc_target, wf_name='acpc_align') + + preproc.connect(anat_reorient, 'out_file', acpc_align, 'inputspec.anat_leaf') + preproc.connect(inputnode, 'brain_mask', acpc_align, 'inputspec.brain_mask') + preproc.connect(inputnode, 'template_brain_only_for_anat', acpc_align, 'inputspec.template_brain') + preproc.connect(inputnode, 'template_skull_for_anat', acpc_align, 'inputspec.template_head') + preproc.connect(acpc_align, 'outputspec.acpc_aligned_head', anat_leaf, 'anat_data') - preproc.connect(anat_deoblique, 'out_file', outputnode, 'refit') # Disable non_local_means_filtering and n4_bias_field_correction when run niworkflows-ants if method == 'niworkflows-ants': - config.non_local_means_filtering = False + config.non_local_means_filtering = False config.n4_bias_field_correction = False if config.non_local_means_filtering and config.n4_bias_field_correction: denoise = pe.Node(interface = ants.DenoiseImage(), name = 'anat_denoise') - preproc.connect(anat_deoblique, 'out_file', denoise, 'input_image') + preproc.connect(anat_leaf, 'anat_data', denoise, 'input_image') + n4 = pe.Node(interface = ants.N4BiasFieldCorrection(dimension=3, shrink_factor=2, copy_header=True), name='anat_n4') preproc.connect(denoise, 'output_image', n4, 'input_image') + elif config.non_local_means_filtering and not config.n4_bias_field_correction: denoise = pe.Node(interface = ants.DenoiseImage(), name = 'anat_denoise') - preproc.connect(anat_deoblique, 'out_file', denoise, 'input_image') + preproc.connect(anat_leaf, 'anat_data', denoise, 'input_image') + elif not config.non_local_means_filtering and config.n4_bias_field_correction: n4 = pe.Node(interface = ants.N4BiasFieldCorrection(dimension=3, shrink_factor=2, copy_header=True), name='anat_n4') - preproc.connect(anat_deoblique, 'out_file', n4, 'input_image') + preproc.connect(anat_leaf, 'anat_data', n4, 'input_image') - # Anatomical reorientation - anat_reorient = pe.Node(interface=afni.Resample(), - name='anat_reorient') - anat_reorient.inputs.orientation = 'RPI' - anat_reorient.inputs.outputtype = 'NIFTI_GZ' + anat_leaf2 = pe.Node(util.IdentityInterface(fields=['anat_data']), + name='anat_leaf2') if config.n4_bias_field_correction: - preproc.connect(n4, 'output_image', anat_reorient, 'in_file') + preproc.connect(n4, 'output_image', anat_leaf2, 'anat_data') elif config.non_local_means_filtering and not config.n4_bias_field_correction: - preproc.connect(denoise, 'output_image', anat_reorient, 'in_file') + preproc.connect(denoise, 'output_image', anat_leaf2, 'anat_data') else: - preproc.connect(anat_deoblique, 'out_file', anat_reorient, 'in_file') - - preproc.connect(anat_reorient, 'out_file', outputnode, 'reorient') + preproc.connect(anat_leaf, 'anat_data', anat_leaf2, 'anat_data') if already_skullstripped: - anat_skullstrip = pe.Node(interface=util.IdentityInterface(fields=['out_file']), name='anat_skullstrip') - preproc.connect(anat_reorient, 'out_file', + preproc.connect(anat_leaf2, 'anat_data', anat_skullstrip, 'out_file') preproc.connect(anat_skullstrip, 'out_file', @@ -181,337 +704,23 @@ def create_anat_preproc(method='afni', already_skullstripped=False, config=None, else: - if method == 'afni': - # Skull-stripping using AFNI 3dSkullStrip - inputnode_afni = pe.Node( - util.IdentityInterface(fields=['mask_vol', - 'shrink_factor', - 'var_shrink_fac', - 'shrink_fac_bot_lim', - 'avoid_vent', - 'niter', - 'pushout', - 'touchup', - 'fill_hole', - 'avoid_eyes', - 'use_edge', - 'exp_frac', - 'smooth_final', - 'push_to_edge', - 'use_skull', - 'perc_int', - 'max_inter_iter', - 'blur_fwhm', - 'fac', - 'monkey']), - name='AFNI_options') - - skullstrip_args = pe.Node(util.Function(input_names=['spat_norm', - 'spat_norm_dxyz', - 'mask_vol', - 'shrink_fac', - 'var_shrink_fac', - 'shrink_fac_bot_lim', - 'avoid_vent', - 'niter', - 'pushout', - 'touchup', - 'fill_hole', - 'avoid_eyes', - 'use_edge', - 'exp_frac', - 'smooth_final', - 'push_to_edge', - 'use_skull', - 'perc_int', - 'max_inter_iter', - 'blur_fwhm', - 'fac', - 'monkey'], - output_names=['expr'], - function=create_3dskullstrip_arg_string), - name='anat_skullstrip_args') - - preproc.connect([ - (inputnode_afni, skullstrip_args, [ - ('mask_vol', 'mask_vol'), - ('shrink_factor', 'shrink_fac'), - ('var_shrink_fac', 'var_shrink_fac'), - ('shrink_fac_bot_lim', 'shrink_fac_bot_lim'), - ('avoid_vent', 'avoid_vent'), - ('niter', 'niter'), - ('pushout', 'pushout'), - ('touchup', 'touchup'), - ('fill_hole', 'fill_hole'), - ('avoid_eyes', 'avoid_eyes'), - ('use_edge', 'use_edge'), - ('exp_frac', 'exp_frac'), - ('smooth_final', 'smooth_final'), - ('push_to_edge', 'push_to_edge'), - ('use_skull', 'use_skull'), - ('perc_int', 'perc_int'), - ('max_inter_iter', 'max_inter_iter'), - ('blur_fwhm', 'blur_fwhm'), - ('fac', 'fac'), - ('monkey','monkey') - ]) - ]) - - anat_skullstrip = pe.Node(interface=afni.SkullStrip(), - name='anat_skullstrip') - - anat_skullstrip.inputs.outputtype = 'NIFTI_GZ' - - preproc.connect(anat_reorient, 'out_file', - anat_skullstrip, 'in_file') - - preproc.connect(skullstrip_args, 'expr', - anat_skullstrip, 'args') - - # Generate anatomical brain mask - - anat_brain_mask = pe.Node(interface=afni.Calc(), - name='anat_brain_mask') - - anat_brain_mask.inputs.expr = 'step(a)' - anat_brain_mask.inputs.outputtype = 'NIFTI_GZ' - - preproc.connect(anat_skullstrip, 'out_file', - anat_brain_mask, 'in_file_a') - - # Apply skull-stripping step mask to original volume - anat_skullstrip_orig_vol = pe.Node(interface=afni.Calc(), - name='anat_skullstrip_orig_vol') - - anat_skullstrip_orig_vol.inputs.expr = 'a*step(b)' - anat_skullstrip_orig_vol.inputs.outputtype = 'NIFTI_GZ' - - preproc.connect(anat_reorient, 'out_file', - anat_skullstrip_orig_vol, 'in_file_a') - - preproc.connect(anat_brain_mask, 'out_file', - anat_skullstrip_orig_vol, 'in_file_b') - - preproc.connect(anat_brain_mask, 'out_file', - outputnode, 'brain_mask') - - preproc.connect(anat_skullstrip_orig_vol, 'out_file', - outputnode, 'brain') - - elif method == 'fsl': - # Skull-stripping using FSL BET - inputnode_bet = pe.Node( - util.IdentityInterface(fields=['frac', - 'mask_boolean', - 'mesh_boolean', - 'outline', - 'padding', - 'radius', - 'reduce_bias', - 'remove_eyes', - 'robust', - 'skull', - 'surfaces', - 'threshold', - 'vertical_gradient']), - name='BET_options') - - anat_skullstrip = pe.Node( - interface=fsl.BET(), name='anat_skullstrip') - anat_skullstrip.inputs.output_type = 'NIFTI_GZ' - - preproc.connect(anat_reorient, 'out_file', - anat_skullstrip, 'in_file') - - preproc.connect([ - (inputnode_bet, anat_skullstrip, [ - ('frac', 'frac'), - ('mask_boolean', 'mask'), - ('mesh_boolean', 'mesh'), - ('outline', 'outline'), - ('padding', 'padding'), - ('radius', 'radius'), - ('reduce_bias', 'reduce_bias'), - ('remove_eyes', 'remove_eyes'), - ('robust', 'robust'), - ('skull', 'skull'), - ('surfaces', 'surfaces'), - ('threshold', 'threshold'), - ('vertical_gradient', 'vertical_gradient'), - ]) - ]) - - preproc.connect(anat_skullstrip, 'out_file', - outputnode, 'skullstrip') - - # Apply skull-stripping step mask to original volume - anat_skullstrip_orig_vol = pe.Node(interface=afni.Calc(), - name='anat_skullstrip_orig_vol') - - anat_skullstrip_orig_vol.inputs.expr = 'a*step(b)' - anat_skullstrip_orig_vol.inputs.outputtype = 'NIFTI_GZ' - - preproc.connect(anat_reorient, 'out_file', - anat_skullstrip_orig_vol, 'in_file_a') - - preproc.connect(anat_skullstrip, 'out_file', - anat_skullstrip_orig_vol, 'in_file_b') - - preproc.connect(anat_skullstrip, 'mask_file', - outputnode, 'brain_mask') - - preproc.connect(anat_skullstrip_orig_vol, 'out_file', - outputnode, 'brain') - - elif method == 'niworkflows-ants': - # Skull-stripping using niworkflows-ants - anat_skullstrip_ants = init_brain_extraction_wf(tpl_target_path=config.niworkflows_ants_template_path, - tpl_mask_path=config.niworkflows_ants_mask_path, - tpl_regmask_path=config.niworkflows_ants_regmask_path, - name='anat_skullstrip_ants') - - preproc.connect(anat_reorient, 'out_file', - anat_skullstrip_ants, 'inputnode.in_files') - - preproc.connect(anat_skullstrip_ants, 'copy_xform.out_file', - outputnode, 'skullstrip') - - preproc.connect(anat_skullstrip_ants, 'copy_xform.out_file', - outputnode, 'brain') - - preproc.connect(anat_skullstrip_ants, 'atropos_wf.copy_xform.out_mask', - outputnode, 'brain_mask') - - elif method == 'mask': - - brain_mask_deoblique = pe.Node(interface=afni.Refit(), - name='brain_mask_deoblique') - brain_mask_deoblique.inputs.deoblique = True + anat_skullstrip = skullstrip_anatomical(method=method, config=config, + wf_name="{0}_skullstrip".format(wf_name)) + preproc.connect(anat_leaf2, 'anat_data', + anat_skullstrip, 'inputspec.anat_data') + if method == 'mask' and config.acpc_align: + preproc.connect(acpc_align, 'outputspec.acpc_brain_mask', + anat_skullstrip, 'inputspec.brain_mask') + else: preproc.connect(inputnode, 'brain_mask', - brain_mask_deoblique, 'in_file') - - brain_mask_reorient = pe.Node(interface=afni.Resample(), - name='brain_mask_reorient') - brain_mask_reorient.inputs.orientation = 'RPI' - brain_mask_reorient.inputs.outputtype = 'NIFTI_GZ' - preproc.connect(brain_mask_deoblique, 'out_file', - brain_mask_reorient, 'in_file') - - - anat_skullstrip_orig_vol = pe.Node(interface=afni.Calc(), - name='anat_skullstrip_orig_vol') - anat_skullstrip_orig_vol.inputs.expr = 'a*step(b)' - anat_skullstrip_orig_vol.inputs.outputtype = 'NIFTI_GZ' - - preproc.connect(anat_reorient, 'out_file', - anat_skullstrip_orig_vol, 'in_file_a') - - preproc.connect(brain_mask_reorient, 'out_file', - anat_skullstrip_orig_vol, 'in_file_b') - - preproc.connect(brain_mask_reorient, 'out_file', - outputnode, 'brain_mask') - - preproc.connect(anat_skullstrip_orig_vol, 'out_file', - outputnode, 'brain') - - elif method == 'unet': - """ - UNet - options (following numbers are default): - input_slice: 3 - conv_block: 5 - kernel_root: 16 - rescale_dim: 256 - """ - # TODO: add options to pipeline_config - unet_check_for_s3 = create_check_for_s3_node('unet', config.unet_model) - unet_mask = pe.Node(util.Function(input_names=['model_path', 'cimg_in'], - output_names=['out_path'], - function=predict_volumes), - name='unet_mask') - - preproc.connect(unet_check_for_s3, 'local_path', unet_mask, 'model_path') - preproc.connect(anat_reorient, 'out_file', unet_mask, 'cimg_in') - - """ - Revised mask with ANTs - """ - # fslmaths -mul brain.nii.gz - unet_masked_brain = pe.Node(interface=fsl.MultiImageMaths(), name='unet_masked_brain') - unet_masked_brain.inputs.op_string = "-mul %s" - preproc.connect(anat_reorient, 'out_file', unet_masked_brain, 'in_file') - preproc.connect(unet_mask, 'out_path', unet_masked_brain, 'operand_files') - - # flirt -v -dof 6 -in brain.nii.gz -ref NMT_SS_0.5mm.nii.gz -o brain_rot2atl -omat brain_rot2atl.mat -interp sinc - # TODO: antsRegistration -z 0 -d 3 -r [NMT_SS_0.5mm.nii.gz,brain.nii.gz,0] -o [transform,brain_rot2atl.nii.gz,brain_inv_rot2atl.nii.gz] -t Rigid[0.1] -m MI[NMT_SS_0.5mm.nii.gz,brain.nii.gz,1,32,Regular,0.25] -c [1000x500x250x100,1e-08,10] -s 3.0x2.0x1.0x0.0 -f 8x4x2x1 -u 1 -t Affine[0.1] -m MI[NMT_SS_0.5mm.nii.gz,brain.nii.gz,1,32,Regular,0.25] -c [1000x500x250x100,1e-08,10] -s 3.0x2.0x1.0x0.0 -f 8x4x2x1 -u 1 - native_brain_to_template_brain = pe.Node(interface=fsl.FLIRT(), name='native_brain_to_template_brain') - native_brain_to_template_brain.inputs.dof = 6 - native_brain_to_template_brain.inputs.interp = 'sinc' - preproc.connect(unet_masked_brain, 'out_file', native_brain_to_template_brain, 'in_file') - preproc.connect(inputnode, 'template_brain_only_for_anat', native_brain_to_template_brain, 'reference') - - # flirt -in head.nii.gz -ref NMT_0.5mm.nii.gz -o head_rot2atl -applyxfm -init brain_rot2atl.mat - # TODO: antsApplyTransforms -d 3 -i head.nii.gz -r NMT_0.5mm.nii.gz -n Linear -o head_rot2atl.nii.gz -v -t transform1Rigid.mat -t transform2Affine.mat -t transform0DerivedInitialMovingTranslation.mat - native_head_to_template_head = pe.Node(interface=fsl.FLIRT(), name='native_head_to_template_head') - native_head_to_template_head.inputs.apply_xfm = True - preproc.connect(anat_reorient, 'out_file', native_head_to_template_head, 'in_file') - preproc.connect(native_brain_to_template_brain, 'out_matrix_file', native_head_to_template_head, 'in_matrix_file') - preproc.connect(inputnode, 'template_skull_for_anat', native_head_to_template_head, 'reference') - - # fslmaths NMT_SS_0.5mm.nii.gz -bin templateMask.nii.gz - template_brain_mask = pe.Node(interface=fsl.maths.MathsCommand(), name='template_brain_mask') - template_brain_mask.inputs.args = '-bin' - preproc.connect(inputnode, 'template_brain_only_for_anat', template_brain_mask, 'in_file') - - # ANTS 3 -m CC[head_rot2atl.nii.gz,NMT_0.5mm.nii.gz,1,5] -t SyN[0.25] -r Gauss[3,0] -o atl2T1rot -i 60x50x20 --use-Histogram-Matching --number-of-affine-iterations 10000x10000x10000x10000x10000 --MI-option 32x16000 - ants_template_head_to_template = pe.Node(interface=ants.Registration(), name='template_head_to_template') - ants_template_head_to_template.inputs.metric = ['CC'] - ants_template_head_to_template.inputs.metric_weight = [1,5] - ants_template_head_to_template.inputs.transforms = ['SyN'] - ants_template_head_to_template.inputs.transform_parameters = [(0.25,)] - ants_template_head_to_template.inputs.interpolation = 'NearestNeighbor' - ants_template_head_to_template.inputs.number_of_iterations = [[60,50,20]] - ants_template_head_to_template.inputs.smoothing_sigmas = [[0.6,0.2,0.0]] - ants_template_head_to_template.inputs.shrink_factors = [[4,2,1]] - ants_template_head_to_template.inputs.convergence_threshold = [1.e-8] - preproc.connect(native_head_to_template_head, 'out_file', ants_template_head_to_template, 'fixed_image') - preproc.connect(inputnode, 'template_skull_for_anat', ants_template_head_to_template, 'moving_image') - # antsApplyTransforms -d 3 -i templateMask.nii.gz -t atl2T1rotWarp.nii.gz atl2T1rotAffine.txt -r brain_rot2atl.nii.gz -o brain_rot2atl_mask.nii.gz - template_head_transform_to_template = pe.Node(interface=ants.ApplyTransforms(), name='template_head_transform_to_template') - template_head_transform_to_template.inputs.dimension = 3 - preproc.connect(template_brain_mask, 'out_file', template_head_transform_to_template, 'input_image') - preproc.connect(native_brain_to_template_brain, 'out_file', template_head_transform_to_template, 'reference_image') - preproc.connect(ants_template_head_to_template, 'forward_transforms', template_head_transform_to_template, 'transforms') - - # TODO: replace convert_xfm and flirt with: - # antsApplyTransforms -d 3 -i brain_rot2atl_mask.nii.gz -r brain.nii.gz -n linear -o brain_mask.nii.gz -t [transform0DerivedInitialMovingTranslation.mat,1] -t [transform2Affine.mat,1] -t [transform1Rigid.mat,1] - # convert_xfm -omat brain_rot2native.mat -inverse brain_rot2atl.mat  - invt = pe.Node(interface=fsl.ConvertXFM(), name='convert_xfm') - invt.inputs.invert_xfm = True - preproc.connect(native_brain_to_template_brain, 'out_matrix_file', invt, 'in_file') - - # flirt -in brain_rot2atl_mask.nii.gz -ref brain.nii.gz -o brain_mask.nii.gz -applyxfm -init brain_rot2native.mat - template_brain_to_native_brain = pe.Node(interface=fsl.FLIRT(), name='template_brain_to_native_brain') - template_brain_to_native_brain.inputs.apply_xfm = True - preproc.connect(template_head_transform_to_template, 'output_image', template_brain_to_native_brain, 'in_file') - preproc.connect(unet_masked_brain, 'out_file', template_brain_to_native_brain, 'reference') - preproc.connect(invt, 'out_file', template_brain_to_native_brain, 'in_matrix_file') - - # fslmaths brain_mask.nii.gz -thr .5 -bin brain_mask_thr.nii.gz - refined_mask = pe.Node(interface=fsl.Threshold(), name='refined_mask') - refined_mask.inputs.thresh = 0.5 - refined_mask.inputs.args = '-bin' - preproc.connect(template_brain_to_native_brain, 'out_file', refined_mask, 'in_file') - - # get a new brain with mask - refined_brain = pe.Node(interface=fsl.MultiImageMaths(), name='refined_brain') - refined_brain.inputs.op_string = "-mul %s" - preproc.connect(anat_reorient, 'out_file', refined_brain, 'in_file') - preproc.connect(refined_mask, 'out_file', refined_brain, 'operand_files') - - preproc.connect(refined_mask, 'out_file', outputnode, 'brain_mask') - preproc.connect(refined_brain, 'out_file', outputnode, 'brain') + anat_skullstrip, 'inputspec.brain_mask') + preproc.connect(anat_skullstrip, 'outputspec.brain_mask', + outputnode, 'brain_mask') + preproc.connect(anat_skullstrip, 'outputspec.brain', + outputnode, 'brain') + + preproc.connect(anat_leaf2, 'anat_data', outputnode, 'anat_skull_leaf') return preproc + diff --git a/CPAC/anat_preproc/utils.py b/CPAC/anat_preproc/utils.py index 4795527fb6..84b05ce4c4 100644 --- a/CPAC/anat_preproc/utils.py +++ b/CPAC/anat_preproc/utils.py @@ -1,6 +1,132 @@ # -*- coding: utf-8 -*- +def fsl_aff_to_rigid(in_xfm, out_name): + + out_mat = os.path.join(os.getcwd(), out_name) + + # Script for getting a 6 DOF approx to a 12 DOF standard transformation + # + # Mark Jenkinson + # FMRIB Image Analysis Group + # + # Copyright (C) 2012 University of Oxford + # + # Part of FSL - FMRIB's Software Library + # http://www.fmrib.ox.ac.uk/fsl + # fsl@fmrib.ox.ac.uk + # + # Developed at FMRIB (Oxford Centre for Functional Magnetic Resonance + # Imaging of the Brain), Department of Clinical Neurology, Oxford + # University, Oxford, UK + # + # + # LICENCE + # + # FMRIB Software Library, Release 5.0 (c) 2012, The University of + # Oxford (the "Software") + # + # The Software remains the property of the University of Oxford ("the + # University"). + # + # The Software is distributed "AS IS" under this Licence solely for + # non-commercial use in the hope that it will be useful, but in order + # that the University as a charitable foundation protects its assets for + # the benefit of its educational and research purposes, the University + # makes clear that no condition is made or to be implied, nor is any + # warranty given or to be implied, as to the accuracy of the Software, + # or that it will be suitable for any particular purpose or for use + # under any specific conditions. Furthermore, the University disclaims + # all responsibility for the use which is made of the Software. It + # further disclaims any liability for the outcomes arising from using + # the Software. + # + # The Licensee agrees to indemnify the University and hold the + # University harmless from and against any and all claims, damages and + # liabilities asserted by third parties (including claims for + # negligence) which arise directly or indirectly from the use of the + # Software or the sale of any products based on the Software. + # + # No part of the Software may be reproduced, modified, transmitted or + # transferred in any form or by any means, electronic or mechanical, + # without the express permission of the University. The permission of + # the University is not required if the said reproduction, modification, + # transmission or transference is done without financial return, the + # conditions of this Licence are imposed upon the receiver of the + # product, and all original and amended source code is included in any + # transmitted product. You may be held legally responsible for any + # copyright infringement that is caused or encouraged by your failure to + # abide by these terms and conditions. + # + # You are not permitted under this Licence to use this Software + # commercially. Use for which any financial return is received shall be + # defined as commercial use, and includes (1) integration of all or part + # of the source code or the Software into a product for sale or license + # by or on behalf of Licensee to third parties or (2) use of the + # Software or any derivative of it for research with the final aim of + # developing software products for sale or license to a third party or + # (3) use of the Software or any derivative of it for research with the + # final aim of developing non-software products for sale or license to a + # third party, or (4) use of the Software to provide any service to an + # external organisation for which payment is received. If you are + # interested in using the Software commercially, please contact Isis + # Innovation Limited ("Isis"), the technology transfer company of the + # University, to negotiate a licence. Contact details are: + # innovation@isis.ox.ac.uk quoting reference DE/9564. + + # Load in the necessary info + a = loadtxt(in_xfm) + # set specific AC and PC coordinates in FLIRT convention (x1=AC, x2=PC, x3=point above x1 in the mid-sag plane) + x1 = matrix([[91], [129], [67], [1]]) + x2 = matrix([[91], [100], [70], [1]]) + x3 = matrix([[91], [129], [117], [1]]) + + ainv = linalg.inv(a) + + # vectors v are in MNI space, vectors w are in native space + v21 = (x2 - x1) + v31 = (x3 - x1) + # normalise and force orthogonality + v21 = v21 / linalg.norm(v21) + v31 = v31 - multiply(v31.T * v21, v21) + v31 = v31 / linalg.norm(v31) + tmp = cross(v21[0:3, 0].T, v31[0:3, 0].T).T + v41 = mat(zeros((4, 1))) + v41[0:3, 0] = tmp + # Map vectors to native space + w21 = ainv * (v21) + w31 = ainv * (v31) + # normalise and force orthogonality + w21 = w21 / linalg.norm(w21) + w31 = w31 - multiply(w31.T * w21, w21) + w31 = w31 / linalg.norm(w31) + tmp = cross(w21[0:3, 0].T, w31[0:3, 0].T).T + w41 = mat(zeros((4, 1))) + w41[0:3, 0] = tmp + + # setup matrix: native to MNI space + r1 = matrix(eye(4)) + r1[0:4, 0] = w21 + r1[0:4, 1] = w31 + r1[0:4, 2] = w41 + r2 = matrix(eye(4)) + r2[0, 0:4] = v21.T + r2[1, 0:4] = v31.T + r2[2, 0:4] = v41.T + r = r2.T * r1.T + + # Fix the translation (keep AC=x1 in the same place) + ACmni = x1 + ACnat = ainv * x1 + trans = ACmni - r * ACnat + r[0:3, 3] = trans[0:3] + + # Save out the result + savetxt(out_mat, r, fmt='%14.10f') + + return out_mat + + def create_3dskullstrip_arg_string(shrink_fac, var_shrink_fac, shrink_fac_bot_lim, avoid_vent, niter, pushout, touchup, fill_hole, avoid_eyes, diff --git a/CPAC/distortion_correction/distortion_correction.py b/CPAC/distortion_correction/distortion_correction.py index 8617566dd5..3588be66f1 100644 --- a/CPAC/distortion_correction/distortion_correction.py +++ b/CPAC/distortion_correction/distortion_correction.py @@ -400,7 +400,7 @@ def connect_distortion_correction(workflow, strat_list, c, diff, blip, epi_distcorr.inputs.afni_threshold_input.afni_threshold = \ c.fmap_distcorr_threshold - node, out_file = strat['anatomical_reorient'] + node, out_file = strat['anatomical_skull_leaf'] workflow.connect(node, out_file, epi_distcorr, 'inputspec.anat_file') diff --git a/CPAC/func_preproc/func_preproc.py b/CPAC/func_preproc/func_preproc.py index f50f9a7c19..b888b9147f 100644 --- a/CPAC/func_preproc/func_preproc.py +++ b/CPAC/func_preproc/func_preproc.py @@ -801,7 +801,9 @@ def create_func_preproc(skullstrip_tool, motion_correct_tool, 'preprocessed_mask', 'slice_time_corrected', 'transform_matrices', - 'center_of_mass']), + 'center_of_mass', + 'motion_filter_info', + 'motion_filter_plot']), name='outputspec') func_deoblique = pe.Node(interface=afni_utils.Refit(), @@ -1124,22 +1126,40 @@ def create_func_preproc(skullstrip_tool, motion_correct_tool, out_oned_matrix, 'out_file') if config: - if config.notch_filter_motion_estimates: + if config.motion_estimate_filter['run']: notch_imports = ['import os', 'import numpy as np', - 'from scipy.signal import iirnotch, filtfilt'] + 'from scipy.signal import iirnotch, lfilter, firwin, freqz', + 'from matplotlib import pyplot as plt', + 'from CPAC.func_preproc.utils import degrees_to_mm, mm_to_degrees'] notch = pe.Node(Function(input_names=['motion_params', - 'fc_RR_min', - 'fc_RR_max', + 'filter_type', 'TR', + 'fc_RR_min', + 'fc_RR_max', + 'center_freq', + 'freq_bw', + 'lowpass_cutoff', 'filter_order'], - output_names=['filtered_motion_params'], + output_names=['filtered_motion_params', + 'filter_info', + 'filter_plot'], function=notch_filter_motion, imports=notch_imports), - name='notch_filter_motion_params') + name='filter_motion_params') + + notch.inputs.filter_type = config.motion_estimate_filter['filter_type'] + notch.inputs.fc_RR_min = config.motion_estimate_filter['breathing_rate_min'] + notch.inputs.fc_RR_max = config.motion_estimate_filter['breathing_rate_max'] + notch.inputs.center_freq = config.motion_estimate_filter['center_frequency'] + notch.inputs.freq_bw = config.motion_estimate_filter['filter_bandwidth'] + notch.inputs.lowpass_cutoff = config.motion_estimate_filter['lowpass_cutoff'] + notch.inputs.filter_order = config.motion_estimate_filter['filter_order'] + + preproc.connect(notch, 'filter_info', + output_node, 'motion_filter_info') - notch.inputs.fc_RR_min = config.notch_filter_breathing_rate_min - notch.inputs.fc_RR_max = config.notch_filter_breathing_rate_max - notch.inputs.filter_order = config.notch_filter_order + preproc.connect(notch, 'filter_plot', + output_node, 'motion_filter_plot') preproc.connect(out_oned, 'out_file', notch, 'motion_params') preproc.connect(input_node, 'TR', notch, 'TR') @@ -1197,22 +1217,40 @@ def create_func_preproc(skullstrip_tool, motion_correct_tool, normalize_motion_params, 'in_file') if config: - if config.notch_filter_motion_estimates: + if config.motion_estimate_filter['run']: notch_imports = ['import os', 'import numpy as np', - 'from scipy.signal import iirnotch, filtfilt'] + 'from scipy.signal import iirnotch, lfilter, firwin, freqz', + 'from matplotlib import pyplot as plt', + 'from CPAC.func_preproc.utils import degrees_to_mm, mm_to_degrees'] notch = pe.Node(Function(input_names=['motion_params', - 'fc_RR_min', - 'fc_RR_max', + 'filter_type', 'TR', + 'fc_RR_min', + 'fc_RR_max', + 'center_freq', + 'freq_bw', + 'lowpass_cutoff', 'filter_order'], - output_names=['filtered_motion_params'], + output_names=['filtered_motion_params', + 'filter_info', + 'filter_plot'], function=notch_filter_motion, imports=notch_imports), - name='notch_filter_motion_params') + name='filter_motion_params') + + notch.inputs.filter_type = config.motion_estimate_filter['filter_type'] + notch.inputs.fc_RR_min = config.motion_estimate_filter['breathing_rate_min'] + notch.inputs.fc_RR_max = config.motion_estimate_filter['breathing_rate_max'] + notch.inputs.center_freq = config.motion_estimate_filter['center_frequency'] + notch.inputs.freq_bw = config.motion_estimate_filter['filter_bandwidth'] + notch.inputs.lowpass_cutoff = config.motion_estimate_filter['lowpass_cutoff'] + notch.inputs.filter_order = config.motion_estimate_filter['filter_order'] + + preproc.connect(notch, 'filter_info', + output_node, 'motion_filter_info') - notch.inputs.fc_RR_min = config.notch_filter_breathing_rate_min - notch.inputs.fc_RR_max = config.notch_filter_breathing_rate_max - notch.inputs.filter_order = config.notch_filter_order + preproc.connect(notch, 'filter_plot', + output_node, 'motion_filter_plot') preproc.connect(normalize_motion_params, 'out_file', notch, 'motion_params') @@ -1772,7 +1810,9 @@ def connect_func_preproc(workflow, strat_list, c, unique_id=None): 'functional_preprocessed_mask': (func_preproc, 'outputspec.preprocessed_mask'), 'functional_preprocessed': (func_preproc, 'outputspec.preprocessed'), 'functional_brain_mask': (func_preproc, 'outputspec.mask'), - 'motion_correct': (func_preproc, 'outputspec.motion_correct'), + 'motion_correct': (func_preproc, 'outputspec.motion_correct'), + 'motion_estimate_filter_info_design': (func_preproc, 'outputspec.motion_filter_info'), + 'motion_estimate_filter_info_plot': (func_preproc, 'outputspec.motion_filter_plot') }) if 'func' in c.run_longitudinal: @@ -1855,6 +1895,8 @@ def connect_func_preproc(workflow, strat_list, c, unique_id=None): 'functional_brain_mask': (func_preproc, 'outputspec.mask'), 'motion_correct': (func_preproc, 'outputspec.motion_correct'), 'coordinate_transformation': (func_preproc, 'outputspec.transform_matrices'), + 'motion_estimate_filter_info_design': (func_preproc, 'outputspec.motion_filter_info'), + 'motion_estimate_filter_info_plot': (func_preproc, 'outputspec.motion_filter_plot') }) if 'func' in c.run_longitudinal: diff --git a/CPAC/func_preproc/utils.py b/CPAC/func_preproc/utils.py index 187803d49c..0b919b0687 100644 --- a/CPAC/func_preproc/utils.py +++ b/CPAC/func_preproc/utils.py @@ -1,8 +1,10 @@ import numpy as np -from scipy.signal import iirnotch, filtfilt +from scipy.signal import iirnotch, firwin, filtfilt, lfilter, freqz +from matplotlib import pyplot as plt import nibabel as nb import subprocess +import math def add_afni_prefix(tpattern): if tpattern: @@ -76,44 +78,125 @@ def oned_text_concat(in_files): return out_file -def notch_filter_motion(motion_params, fc_RR_min, fc_RR_max, TR, - filter_order=4): +def degrees_to_mm(degrees, head_radius): + # function to convert degrees of motion to mm + mm = 2*math.pi*head_radius*(degrees/360) + return mm + +def mm_to_degrees(mm, head_radius): + # function to convert mm of motion to degrees + degrees = 360*mm/(2*math.pi*head_radius) + return degrees + + +def notch_filter_motion(motion_params, filter_type, TR, fc_RR_min=None, + fc_RR_max=None, center_freq=None, freq_bw=None, + lowpass_cutoff=None, filter_order=4): # Adapted from DCAN Labs: # https://github.com/DCAN-Labs/dcan_bold_processing/blob/master/ # ...matlab_code/filtered_movement_regressors.m - TR = float(TR.replace("s", "")) - params_data = np.loadtxt(motion_params) - - fc_RR_bw = [fc_RR_min, fc_RR_max] + if "ms" in TR: + TR = float(TR.replace("ms", ""))/1000 + elif "ms" not in TR and "s" in TR: + TR = float(TR.replace("s", "")) - # Respiratory Rate - rr = [float(fc_RR_min) / float(60), - float(fc_RR_max) / float(60)] + params_data = np.loadtxt(motion_params) # Sampling frequency fs = 1 / TR # Nyquist frequency - fNy = fs / 2; + fNy = fs / 2 - rr_fNy = [rr[0] + fNy, rr[1] + fNy] + if filter_type == "notch": - fa = abs(rr - np.floor(np.divide(rr_fNy, fs)) * fs) + # Respiratory Rate + if fc_RR_min and fc_RR_max: + rr = [float(fc_RR_min) / float(60), + float(fc_RR_max) / float(60)] - W_notch = np.divide(fa, fNy) - Wn = np.mean(W_notch) - bw = np.diff(W_notch) - [b_filt, a_filt] = iirnotch(Wn, bw) - num_f_apply = np.floor(filter_order / 2) + rr_fNy = [rr[0] + fNy, rr[1] + fNy] + fa = abs(rr - np.floor(np.divide(rr_fNy, fs)) * fs) - filtered_params = filtfilt(b_filt, a_filt, params_data.T) + elif center_freq and freq_bw: + tail = float(freq_bw)/float(2) + fa = [center_freq-tail, center_freq+tail] - filtered_motion_params = os.path.join(os.getcwd(), - "{0}_notch-filtered.1D".format(os.path.basename(motion_params))) - np.savetxt(filtered_motion_params, filtered_params.T, fmt='%f') + W_notch = np.divide(fa, fNy) + + Wn = np.mean(W_notch) + bw = np.diff(W_notch) + + # for filter info + center_freq = Wn * fNy + bandwidth = fa[1] - fa[0] + + Q = Wn/bw + [b_filt, a_filt] = iirnotch(Wn, Q) + num_f_apply = np.floor(filter_order / 2) + + filter_info = f"Motion estimate filter information\n\nType: Notch\n" \ + f"\nCenter freq: {center_freq}\nBandwidth: {bandwidth}\n\n" \ + f"Wn: {Wn}\nQ: {Q}\n\n" \ + f"Based on:\nSampling freq: {fs}\nNyquist freq: {fNy}" + + elif filter_type == "lowpass": + + if fc_RR_min: + rr = float(fc_RR_min) / float(60) + rr_fNy = rr + fNy + fa = abs(rr - np.floor(np.divide(rr_fNy, fs)) * fs) + + elif lowpass_cutoff: + fa = lowpass_cutoff - return filtered_motion_params + Wn = fa/fNy + if filter_order: + b_filt = firwin(filter_order+1, Wn) + a_filt = 1 + + num_f_apply = 0 + + filter_info = f"Motion estimate filter information\n\nType: Lowpass" \ + f"\n\nCutoff freq: {fa}\nWn: {Wn}\n\n" \ + f"Based on:\nSampling freq: {fs}\nNyquist freq: {fNy}" + + filter_design = os.path.join(os.getcwd(), + "motion_estimate_filter_design.txt") + filter_plot = os.path.join(os.getcwd(), + "motion_estimate_filter_freq-response.png") + + # plot frequency response for user info + w, h = freqz(b_filt, a_filt, fs=fs) + + fig, ax1 = plt.subplots() + ax1.set_title('Motion estimate filter frequency response') + + ax1.plot(w, 20 * np.log10(abs(h)), 'b') + ax1.set_ylabel('Amplitude [dB]', color='b') + ax1.set_xlabel('Frequency [Hz]') + + plt.savefig(filter_plot) + + with open(filter_design, 'wt') as f: + f.write(filter_info) + + # convert rotation params from degrees to mm + params_data[:, 0:3] = degrees_to_mm(params_data[:, 0:3], head_radius=50) + + filtered_params = lfilter(b_filt, a_filt, params_data.T, zi=None) + + for i in range(0, int(num_f_apply) - 1): + filtered_params = lfilter(b_filt, a_filt, filtered_params, zi=None) + + # back rotation params to degrees + filtered_params[0:3,:] = mm_to_degrees(filtered_params[0:3,:], head_radius = 50) + + filtered_motion_params = os.path.join(os.getcwd(), + "{0}_filtered.1D".format(os.path.basename(motion_params))) + np.savetxt(filtered_motion_params, filtered_params.T, fmt='%f') + return (filtered_motion_params, filter_design, filter_plot) diff --git a/CPAC/info.py b/CPAC/info.py index bd729c2a98..68602da7d4 100644 --- a/CPAC/info.py +++ b/CPAC/info.py @@ -10,8 +10,8 @@ # version _version_major = 1 _version_minor = 7 -_version_micro = 0 -_version_extra = '' +_version_micro = 1 +_version_extra = 'dev' def get_cpac_gitversion(): @@ -20,7 +20,8 @@ def get_cpac_gitversion(): Returns ------- None or str - Version of Nipype according to git. + + Version of C-PAC according to git. """ import os import subprocess @@ -35,15 +36,17 @@ def get_cpac_gitversion(): ver = None try: - o, _ = subprocess.Popen('git describe --always', shell=True, cwd=gitpath, - stdout=subprocess.PIPE).communicate() + o, _ = subprocess.Popen('git describe --always', shell=True, + cwd=gitpath, stdout=subprocess.PIPE + ).communicate() except Exception: pass else: - ver = o.strip().split('-')[-1] + ver = o.decode().strip().split('-')[-1] return ver + if 'dev' in _version_extra: gitversion = get_cpac_gitversion() if gitversion: @@ -159,4 +162,5 @@ def get_cpac_gitversion(): "traits==4.6.0", "PyBASC==0.4.5", "pathlib==1.0.1", + "websockets==8.1", ] diff --git a/CPAC/longitudinal_pipeline/longitudinal_workflow.py b/CPAC/longitudinal_pipeline/longitudinal_workflow.py index e0350ce843..66676b7be6 100644 --- a/CPAC/longitudinal_pipeline/longitudinal_workflow.py +++ b/CPAC/longitudinal_pipeline/longitudinal_workflow.py @@ -95,7 +95,7 @@ def register_anat_longitudinal_template_to_standard(longitudinal_template_node, strat_init_new.update_resource_pool({ 'anatomical_brain': (longitudinal_template_node, 'brain_template'), - 'anatomical_reorient': (longitudinal_template_node, 'skull_template'), + 'anatomical_skull_leaf': (longitudinal_template_node, 'skull_template'), 'anatomical_brain_mask': (brain_mask, 'out_file') }) @@ -200,7 +200,7 @@ def register_anat_longitudinal_template_to_standard(longitudinal_template_node, fnirt_reg_anat_mni, 'inputspec.reference_brain') # skull input - node, out_file = strat['anatomical_reorient'] + node, out_file = strat['anatomical_skull_leaf'] workflow.connect(node, out_file, fnirt_reg_anat_mni, 'inputspec.input_skull') @@ -287,7 +287,7 @@ def register_anat_longitudinal_template_to_standard(longitudinal_template_node, ants_reg_anat_mni, 'inputspec.moving_brain') # get the reorient skull-on anatomical from resource pool - node, out_file = strat['anatomical_reorient'] + node, out_file = strat['anatomical_skull_leaf'] # pass the anatomical to the workflow workflow.connect(node, out_file, @@ -412,7 +412,7 @@ def register_anat_longitudinal_template_to_standard(longitudinal_template_node, fnirt_reg_anat_symm_mni, 'inputspec.input_brain') - node, out_file = strat['anatomical_reorient'] + node, out_file = strat['anatomical_skull_leaf'] workflow.connect(node, out_file, fnirt_reg_anat_symm_mni, 'inputspec.input_skull') @@ -493,7 +493,7 @@ def register_anat_longitudinal_template_to_standard(longitudinal_template_node, ants_reg_anat_symm_mni, 'inputspec.reference_brain') # get the reorient skull-on anatomical from resource pool - node, out_file = strat['anatomical_reorient'] + node, out_file = strat['anatomical_skull_leaf'] # pass the anatomical to the workflow workflow.connect(node, out_file, @@ -645,7 +645,7 @@ def connect_anat_preproc_inputs(strat, anat_preproc, strat_name, strat_nodes_lis new_strat.update_resource_pool({ 'anatomical_brain': ( anat_preproc, 'outputspec.brain'), - 'anatomical_reorient': ( + 'anatomical_skull_leaf': ( anat_preproc, 'outputspec.reorient'), 'anatomical_brain_mask': ( anat_preproc, 'outputspec.brain_mask'), @@ -1213,7 +1213,7 @@ def seg_apply_warp(strat_name, resource, type='str', file_type=None): rsc_name, brain_merge_node, 'in{}'.format(i + 1)) # the in{}.format take i+1 because the Merge nodes inputs starts at 1 - rsc_key = 'anatomical_reorient' + rsc_key = 'anatomical_skull_leaf' anat_preproc_node, rsc_name = strat_nodes_list[i][rsc_key] workflow.connect(anat_preproc_node, rsc_name, skull_merge_node, diff --git a/CPAC/nuisance/bandpass.py b/CPAC/nuisance/bandpass.py index 65cc7f091d..ba707f9982 100644 --- a/CPAC/nuisance/bandpass.py +++ b/CPAC/nuisance/bandpass.py @@ -87,45 +87,50 @@ def bandpass_voxels(realigned_file, regressor_file, bandpass_freqs, bandpassed_file = os.path.join(os.getcwd(), 'bandpassed_demeaned_filtered.nii.gz') img.to_filename(bandpassed_file) - if regressor_file.endswith('.nii.gz') or regressor_file.endswith('.nii'): - nii = nb.load(regressor_file) - data = nii.get_data().astype('float64') - mask = (data != 0).sum(-1) != 0 - Y = data[mask].T - Yc = Y - np.tile(Y.mean(0), (Y.shape[0], 1)) - Y_bp = np.zeros_like(Y) - for j in range(Y.shape[1]): - Y_bp[:, j] = ideal_bandpass(Yc[:, j], sample_period, bandpass_freqs) - data[mask] = Y_bp.T - - img = nb.Nifti1Image(data, header=nii.get_header(), - affine=nii.get_affine()) - regressor_bandpassed_file = os.path.join(os.getcwd(), - 'regressor_bandpassed_demeaned_filtered.nii.gz') - img.to_filename(regressor_bandpassed_file) - else: - with open(regressor_file, 'r') as f: - header = [f.readline() for x in range(0,3)] - - regressor = np.loadtxt(regressor_file) - Yc = regressor - np.tile(regressor.mean(0), (regressor.shape[0], 1)) - Y_bp = np.zeros_like(Yc) - for j in range(regressor.shape[1]): - Y_bp[:, j] = ideal_bandpass(Yc[:, j], sample_period, - bandpass_freqs) - - regressor_bandpassed_file = os.path.join(os.getcwd(), - 'regressor_bandpassed_demeaned_filtered.1D') - - with open(regressor_bandpassed_file, "w") as ofd: - # write out the header information - for line in header: - ofd.write(line) - - nuisance_regressors = np.array(Y_bp) - np.savetxt(ofd, nuisance_regressors, fmt='%.18f', - delimiter='\t') + regressor_bandpassed_file = None + + if regressor_file is not None: + + if regressor_file.endswith('.nii.gz') or regressor_file.endswith('.nii'): + nii = nb.load(regressor_file) + data = nii.get_data().astype('float64') + mask = (data != 0).sum(-1) != 0 + Y = data[mask].T + Yc = Y - np.tile(Y.mean(0), (Y.shape[0], 1)) + Y_bp = np.zeros_like(Y) + for j in range(Y.shape[1]): + Y_bp[:, j] = ideal_bandpass(Yc[:, j], sample_period, bandpass_freqs) + data[mask] = Y_bp.T + + img = nb.Nifti1Image(data, header=nii.get_header(), + affine=nii.get_affine()) + regressor_bandpassed_file = os.path.join(os.getcwd(), + 'regressor_bandpassed_demeaned_filtered.nii.gz') + img.to_filename(regressor_bandpassed_file) + + else: + with open(regressor_file, 'r') as f: + header = [f.readline() for x in range(0,3)] + + regressor = np.loadtxt(regressor_file) + Yc = regressor - np.tile(regressor.mean(0), (regressor.shape[0], 1)) + Y_bp = np.zeros_like(Yc) + for j in range(regressor.shape[1]): + Y_bp[:, j] = ideal_bandpass(Yc[:, j], sample_period, + bandpass_freqs) + + regressor_bandpassed_file = os.path.join(os.getcwd(), + 'regressor_bandpassed_demeaned_filtered.1D') + + with open(regressor_bandpassed_file, "w") as ofd: + # write out the header information + for line in header: + ofd.write(line) + + nuisance_regressors = np.array(Y_bp) + np.savetxt(ofd, nuisance_regressors, fmt='%.18f', + delimiter='\t') return bandpassed_file, regressor_bandpassed_file diff --git a/CPAC/nuisance/nuisance.py b/CPAC/nuisance/nuisance.py index 548db5521f..4ae311fa0a 100644 --- a/CPAC/nuisance/nuisance.py +++ b/CPAC/nuisance/nuisance.py @@ -1503,9 +1503,10 @@ def create_nuisance_regression_workflow(nuisance_selectors, nuisance_wf.connect(inputspec, 'regressor_file', nuisance_regression, 'ort') else: - nuisance_wf.connect(inputspec, 'regressor_file', - nuisance_regression, 'ort') - + # there's no regressor file generated if only Bandpass in nuisance_selectors + if not ('Bandpass' in nuisance_selectors and len(nuisance_selectors.selector.keys()) == 1): + nuisance_wf.connect(inputspec, 'regressor_file', + nuisance_regression, 'ort') nuisance_wf.connect(nuisance_regression, 'out_file', outputspec, 'residual_file_path') diff --git a/CPAC/pipeline/cpac_pipeline.py b/CPAC/pipeline/cpac_pipeline.py index d63dad2bbc..ee12f4fdc2 100644 --- a/CPAC/pipeline/cpac_pipeline.py +++ b/CPAC/pipeline/cpac_pipeline.py @@ -17,7 +17,6 @@ import nipype import nipype.pipeline.engine as pe import nipype.interfaces.fsl as fsl -import nipype.interfaces.freesurfer as freesurfer import nipype.interfaces.io as nio import nipype.interfaces.utility as util from nipype.interfaces.afni import preprocess @@ -700,6 +699,20 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 else: ventricle_mask_exist = True + # check acpc alignment target + if c.acpc_align and str(c.acpc_template_skull).lower() in ['none', 'false']: + err = "\n\n[!] C-PAC says: You have choosed ACPC alignment, " \ + "but you did not provide ACPC alignment template. " \ + "Options you provided:\nacpc_template_skull: {0}" \ + '\n\n'.format(str(c.acpc_template_skull)) + raise Exception(err) + elif c.acpc_align and str(c.acpc_template_skull).lower() not in ['none', 'false', ''] and str(c.acpc_template_brain).lower() in ['none', 'false', '']: + acpc_target = 'whole-head' + elif c.acpc_align and str(c.acpc_template_skull).lower() not in ['none', 'false', ''] and str(c.acpc_template_brain).lower() not in ['none', 'false', '']: + acpc_target = 'brain' + else: + acpc_target = None + # TODO ASH normalize file paths with schema validator template_keys = [ ("anat", "templateSpecificationFile"), @@ -711,6 +724,9 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 ("anat", "template_based_segmentation_CSF"), ("anat", "template_based_segmentation_GRAY"), ("anat", "template_based_segmentation_WHITE"), + ("anat", "template_based_segmentation_WHITE"), + ("anat", "acpc_template_skull"), + ("anat", "acpc_template_brain"), ] for key_type, key in template_keys: @@ -928,199 +944,172 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 for num_strat, strat in enumerate(strat_list): - if 1 in c.runFreeSurfer: + if 'anatomical_brain_mask' in strat: - reconall = pe.Node(interface=freesurfer.ReconAll(), - name=f'reconall_{num_strat}') - - reconall.inputs.subject_id = subject_id - reconall.inputs.directive = 'all' - reconall.inputs.subjects_dir = '.' - - node, out_file = strat['anatomical'] + anat_preproc = create_anat_preproc(method='mask', + config=c, + acpc_target=acpc_target, + wf_name='anat_preproc_mask_%d' % num_strat) + + new_strat = strat.fork() + + node, out_file = new_strat['anatomical'] + workflow.connect(node, out_file, + anat_preproc, 'inputspec.anat') + node, out_file = new_strat['anatomical_brain_mask'] workflow.connect(node, out_file, - reconall, 'T1_files') + anat_preproc, 'inputspec.brain_mask') + workflow.connect(c.acpc_template_skull, 'local_path', + anat_preproc, 'inputspec.template_skull_for_anat') + workflow.connect(c.acpc_template_brain, 'local_path', + anat_preproc, 'inputspec.template_brain_only_for_anat') + + new_strat.append_name(anat_preproc.name) + new_strat.set_leaf_properties(anat_preproc, 'outputspec.brain') + new_strat.update_resource_pool({ + 'anatomical_brain': (anat_preproc, 'outputspec.brain'), + 'anatomical_skull_leaf': (anat_preproc, 'outputspec.anat_skull_leaf'), + }) + new_strat.update_resource_pool({ + 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask') + }, override=True) + + new_strat_list += [new_strat] + + continue + + if already_skullstripped: + + anat_preproc = create_anat_preproc(method=None, + already_skullstripped=True, + config=c, + acpc_target=acpc_target, + wf_name='anat_preproc_already_%d' % num_strat) + + new_strat = strat.fork() + + node, out_file = new_strat['anatomical'] + workflow.connect(node, out_file, + anat_preproc, 'inputspec.anat') + workflow.connect(c.acpc_template_skull, 'local_path', + anat_preproc, 'inputspec.template_skull_for_anat') + workflow.connect(c.acpc_template_brain, 'local_path', + anat_preproc, 'inputspec.template_brain_only_for_anat') + + new_strat.append_name(anat_preproc.name) + new_strat.set_leaf_properties(anat_preproc, 'outputspec.brain') + new_strat.update_resource_pool({ + 'anatomical_brain': (anat_preproc, 'outputspec.brain'), + 'anatomical_skull_leaf': (anat_preproc, 'outputspec.anat_skull_leaf'), + 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask'), + }) + + new_strat_list += [new_strat] else: - if 'anatomical_brain_mask' in strat: + if not any(o in c.skullstrip_option for o in ["AFNI", "FSL", "niworkflows-ants", "unet"]): + err = '\n\n[!] C-PAC says: Your skull-stripping method options ' \ + 'setting does not include either \'AFNI\' or \'FSL\' or \'niworkflows-ants\'.\n\n' \ + 'Options you provided:\nskullstrip_option: {0}' \ + '\n\n'.format(str(c.skullstrip_option)) + raise Exception(err) + + if "AFNI" in c.skullstrip_option: - anat_preproc = create_anat_preproc(method='mask', + anat_preproc = create_anat_preproc(method='afni', config=c, - wf_name='anat_preproc_mask_%d' % num_strat) + acpc_target=acpc_target, + wf_name='anat_preproc_afni_%d' % num_strat) new_strat = strat.fork() node, out_file = new_strat['anatomical'] workflow.connect(node, out_file, anat_preproc, 'inputspec.anat') - - node, out_file = strat['anatomical_brain_mask'] - workflow.connect(node, out_file, - anat_preproc, 'inputspec.brain_mask') - + workflow.connect(c.acpc_template_skull, 'local_path', + anat_preproc, 'inputspec.template_skull_for_anat') + workflow.connect(c.acpc_template_brain, 'local_path', + anat_preproc, 'inputspec.template_brain_only_for_anat') new_strat.append_name(anat_preproc.name) new_strat.set_leaf_properties(anat_preproc, 'outputspec.brain') new_strat.update_resource_pool({ 'anatomical_brain': (anat_preproc, 'outputspec.brain'), - 'anatomical_reorient': (anat_preproc, 'outputspec.reorient'), + 'anatomical_skull_leaf': (anat_preproc, 'outputspec.anat_skull_leaf'), + 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask'), }) - new_strat.update_resource_pool({ - 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask') - }, override=True) new_strat_list += [new_strat] - continue - - if already_skullstripped: - - anat_preproc = create_anat_preproc(method=None, - already_skullstripped=True, + if "FSL" in c.skullstrip_option: + anat_preproc = create_anat_preproc(method='fsl', config=c, - wf_name='anat_preproc_already_%d' % num_strat) + acpc_target=acpc_target, + wf_name='anat_preproc_bet_%d' % num_strat) new_strat = strat.fork() node, out_file = new_strat['anatomical'] workflow.connect(node, out_file, anat_preproc, 'inputspec.anat') + workflow.connect(c.acpc_template_skull, 'local_path', + anat_preproc, 'inputspec.template_skull_for_anat') + workflow.connect(c.acpc_template_brain, 'local_path', + anat_preproc, 'inputspec.template_brain_only_for_anat') new_strat.append_name(anat_preproc.name) new_strat.set_leaf_properties(anat_preproc, 'outputspec.brain') new_strat.update_resource_pool({ 'anatomical_brain': (anat_preproc, 'outputspec.brain'), - 'anatomical_reorient': (anat_preproc, 'outputspec.reorient'), + 'anatomical_skull_leaf': (anat_preproc, 'outputspec.anat_skull_leaf'), 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask'), }) new_strat_list += [new_strat] - else: - if not any(o in c.skullstrip_option for o in ["AFNI", "FSL", "niworkflows-ants", "unet"]): - err = '\n\n[!] C-PAC says: Your skull-stripping method options ' \ - 'setting does not include either \'AFNI\' or \'FSL\' or \'niworkflows-ants\'.\n\n' \ - 'Options you provided:\nskullstrip_option: {0}' \ - '\n\n'.format(str(c.skullstrip_option)) - raise Exception(err) - - if "AFNI" in c.skullstrip_option: - - anat_preproc = create_anat_preproc(method='afni', - config=c, - wf_name='anat_preproc_afni_%d' % num_strat) - - anat_preproc.inputs.AFNI_options.set( - mask_vol=c.skullstrip_mask_vol, - shrink_factor=c.skullstrip_shrink_factor, - var_shrink_fac=c.skullstrip_var_shrink_fac, - shrink_fac_bot_lim=c.skullstrip_shrink_factor_bot_lim, - avoid_vent=c.skullstrip_avoid_vent, - niter=c.skullstrip_n_iterations, - pushout=c.skullstrip_pushout, - touchup=c.skullstrip_touchup, - fill_hole=c.skullstrip_fill_hole, - avoid_eyes=c.skullstrip_avoid_eyes, - use_edge=c.skullstrip_use_edge, - exp_frac=c.skullstrip_exp_frac, - smooth_final=c.skullstrip_smooth_final, - push_to_edge=c.skullstrip_push_to_edge, - use_skull=c.skullstrip_use_skull, - perc_int=c.skullstrip_perc_int, - max_inter_iter=c.skullstrip_max_inter_iter, - blur_fwhm=c.skullstrip_blur_fwhm, - fac=c.skullstrip_fac, - monkey=c.skullstrip_monkey, - ) - - new_strat = strat.fork() - node, out_file = new_strat['anatomical'] - workflow.connect(node, out_file, - anat_preproc, 'inputspec.anat') - new_strat.append_name(anat_preproc.name) - new_strat.set_leaf_properties(anat_preproc, 'outputspec.brain') - new_strat.update_resource_pool({ - 'anatomical_brain': (anat_preproc, 'outputspec.brain'), - 'anatomical_reorient': (anat_preproc, 'outputspec.reorient'), - 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask'), - }) - - new_strat_list += [new_strat] - - if "FSL" in c.skullstrip_option: - anat_preproc = create_anat_preproc(method='fsl', - config=c, - wf_name='anat_preproc_bet_%d' % num_strat) - - anat_preproc.inputs.BET_options.set( - frac=c.bet_frac, - mask_boolean=c.bet_mask_boolean, - mesh_boolean=c.bet_mesh_boolean, - outline=c.bet_outline, - padding=c.bet_padding, - radius=c.bet_radius, - reduce_bias=c.bet_reduce_bias, - remove_eyes=c.bet_remove_eyes, - robust=c.bet_robust, - skull=c.bet_skull, - surfaces=c.bet_surfaces, - threshold=c.bet_threshold, - vertical_gradient=c.bet_vertical_gradient, - ) - - new_strat = strat.fork() - node, out_file = new_strat['anatomical'] - workflow.connect(node, out_file, - anat_preproc, 'inputspec.anat') - new_strat.append_name(anat_preproc.name) - new_strat.set_leaf_properties(anat_preproc, 'outputspec.brain') - new_strat.update_resource_pool({ - 'anatomical_brain': (anat_preproc, 'outputspec.brain'), - 'anatomical_reorient': (anat_preproc, 'outputspec.reorient'), - 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask'), - }) - - new_strat_list += [new_strat] - - if "niworkflows-ants" in c.skullstrip_option: - anat_preproc = create_anat_preproc(method='niworkflows-ants', - config=c, - wf_name='anat_preproc_niworkflows_ants_%d' % num_strat) + if "niworkflows-ants" in c.skullstrip_option: + anat_preproc = create_anat_preproc(method='niworkflows-ants', + config=c, + acpc_target=acpc_target, + wf_name='anat_preproc_niworkflows_ants_%d' % num_strat) - new_strat = strat.fork() - node, out_file = new_strat['anatomical'] - workflow.connect(node, out_file, - anat_preproc, 'inputspec.anat') - new_strat.append_name(anat_preproc.name) - new_strat.set_leaf_properties(anat_preproc, 'outputspec.brain') - new_strat.update_resource_pool({ - 'anatomical_brain': (anat_preproc, 'outputspec.brain'), - 'anatomical_reorient': (anat_preproc, 'outputspec.reorient'), - 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask'), - }) + new_strat = strat.fork() + node, out_file = new_strat['anatomical'] + workflow.connect(node, out_file, + anat_preproc, 'inputspec.anat') + workflow.connect(c.acpc_template_skull, 'local_path', + anat_preproc, 'inputspec.template_skull_for_anat') + workflow.connect(c.acpc_template_brain, 'local_path', + anat_preproc, 'inputspec.template_brain_only_for_anat') + new_strat.append_name(anat_preproc.name) + new_strat.set_leaf_properties(anat_preproc, 'outputspec.brain') + new_strat.update_resource_pool({ + 'anatomical_brain': (anat_preproc, 'outputspec.brain'), + 'anatomical_skull_leaf': (anat_preproc, 'outputspec.anat_skull_leaf'), + 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask'), + }) - new_strat_list += [new_strat] + new_strat_list += [new_strat] - if "unet" in c.skullstrip_option: - anat_preproc = create_anat_preproc(method='unet', - config=c, - wf_name='anat_preproc_unet_%d' % num_strat) + if "unet" in c.skullstrip_option: + anat_preproc = create_anat_preproc(method='unet', + config=c, + acpc_target=acpc_target, + wf_name='anat_preproc_unet_%d' % num_strat) - new_strat = strat.fork() - node, out_file = new_strat['anatomical'] - workflow.connect(node, out_file, - anat_preproc, 'inputspec.anat') - node, out_file = new_strat['template_brain_for_anat'] - workflow.connect(node, out_file, - anat_preproc, 'inputspec.template_brain_only_for_anat') - node, out_file = new_strat['template_skull_for_anat'] - workflow.connect(node, out_file, - anat_preproc, 'inputspec.template_skull_for_anat') - new_strat.append_name(anat_preproc.name) - new_strat.set_leaf_properties(anat_preproc, 'outputspec.brain') - new_strat.update_resource_pool({ - 'anatomical_brain': (anat_preproc, 'outputspec.brain'), - 'anatomical_reorient': (anat_preproc, 'outputspec.reorient'), - 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask'), - }) + new_strat = strat.fork() + node, out_file = new_strat['anatomical'] + workflow.connect(node, out_file, + anat_preproc, 'inputspec.anat') + workflow.connect(c.acpc_template_skull, 'local_path', + anat_preproc, 'inputspec.template_skull_for_anat') + workflow.connect(c.acpc_template_brain, 'local_path', + anat_preproc, 'inputspec.template_brain_only_for_anat') + new_strat.append_name(anat_preproc.name) + new_strat.set_leaf_properties(anat_preproc, 'outputspec.brain') + new_strat.update_resource_pool({ + 'anatomical_brain': (anat_preproc, 'outputspec.brain'), + 'anatomical_skull_leaf': (anat_preproc, 'outputspec.anat_skull_leaf'), + 'anatomical_brain_mask': (anat_preproc, 'outputspec.brain_mask'), + }) - new_strat_list += [new_strat] + new_strat_list += [new_strat] strat_list = new_strat_list @@ -1205,7 +1194,7 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 workflow.connect(node, out_file, fnirt_reg_anat_mni, 'inputspec.reference_brain') - node, out_file = strat['anatomical_reorient'] + node, out_file = strat['anatomical_skull_leaf'] workflow.connect(node, out_file, fnirt_reg_anat_mni, 'inputspec.input_skull') @@ -1301,7 +1290,7 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 ants_reg_anat_mni, 'inputspec.reference_brain') # get the reorient skull-on anatomical from resource pool - node, out_file = strat['anatomical_reorient'] + node, out_file = strat['anatomical_skull_leaf'] # pass the anatomical to the workflow workflow.connect(node, out_file, @@ -1458,7 +1447,7 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 workflow.connect(node, out_file, fnirt_reg_anat_symm_mni, 'inputspec.reference_brain') - node, out_file = strat['anatomical_reorient'] + node, out_file = strat['anatomical_skull_leaf'] workflow.connect(node, out_file, fnirt_reg_anat_symm_mni, 'inputspec.input_skull') @@ -1542,7 +1531,7 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 # get the reorient skull-on anatomical from resource # pool - node, out_file = strat['anatomical_reorient'] + node, out_file = strat['anatomical_skull_leaf'] # pass the anatomical to the workflow workflow.connect(node, out_file, @@ -1959,6 +1948,13 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 for num_strat, strat in enumerate(strat_list): + node, out_file = strat.get_leaf_properties() + strat.update_resource_pool({ + 'functional_freq_unfiltered': ( + node, out_file + ), + }) + # for each strategy, create a new one without nuisance if 0 in c.runNuisance or 1 in c.run_pypeer: new_strat_list.append(strat.fork()) @@ -2074,12 +2070,6 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 'inputspec.functional_file_path' ) - new_strat.update_resource_pool({ - 'functional_freq_unfiltered': ( - node, out_file - ), - }) - node, out_file = new_strat['frame_wise_displacement_jenkinson'] workflow.connect( node, out_file, @@ -3311,7 +3301,6 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 scan_ids += ['scan_' + str(scan_id) for scan_id in sub_dict['rest']] - for num_strat, strat in enumerate(strat_list): if pipeline_name is None or pipeline_name == 'None': @@ -3364,7 +3353,7 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 if ndmg_out: ds = pe.Node(DataSink(), name='sinker_{}_{}'.format(num_strat, - resource_i)) + resource_i)) ds.inputs.base_directory = c.outputDirectory ds.inputs.creds_path = creds_path ds.inputs.encrypt_bucket_keys = encrypt_data @@ -3529,6 +3518,13 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 output_sink_nodes = [] node, out_file = rp[resource] + if "info" in resource: + ds.inputs.base_directory = c.logDirectory + ds.inputs.container = os.path.join('pipeline_info', + 'pipeline_{0}'.format(pipeline_id), subject_id) + resource = '{0}.@{1}'.format(resource.split('_info_')[0], + resource.split('_info_')[1]) + # exclue Nonetype transforms if resource == 'ants_initial_xfm' or resource == 'ants_rigid_xfm' or resource == 'ants_affine_xfm' \ or resource == 'ants_symmetric_initial_xfm' or resource == 'ants_symmetric_rigid_xfm' or resource == 'ants_symmetric_affine_xfm': @@ -3561,6 +3557,7 @@ def build_workflow(subject_id, sub_dict, c, pipeline_name=None, num_ants_cores=1 workflow.connect(node, out_file, ds, resource) if trans_type == 'Affine' and resource == 'func_to_epi_ants_affine_xfm': workflow.connect(node, out_file, ds, resource) + if resource not in ['ants_initial_xfm', 'ants_rigid_xfm', 'ants_affine_xfm', 'func_to_epi_ants_initial_xfm', 'func_to_epi_ants_rigid_xfm', 'func_to_epi_ants_affine_xfm',\ 'ants_symmetric_initial_xfm','ants_symmetric_rigid_xfm','ants_symmetric_affine_xfm']: workflow.connect(node, out_file, ds, resource) diff --git a/CPAC/pipeline/cpac_runner.py b/CPAC/pipeline/cpac_runner.py index 696c5b8aa4..2fb37c0f1e 100644 --- a/CPAC/pipeline/cpac_runner.py +++ b/CPAC/pipeline/cpac_runner.py @@ -308,9 +308,12 @@ def run(subject_list_file, config_file=None, p_name=None, plugin=None, if tracking: try: - track_run(level='participant', participants=len(sublist)) + track_run( + level='participant' if not test_config else 'test', + participants=len(sublist) + ) except: - pass + print("Usage tracking failed for this run.") # If we're running on cluster, execute job scheduler if c.runOnGrid: @@ -453,7 +456,7 @@ def run(subject_list_file, config_file=None, p_name=None, plugin=None, } }) else: - if keys[-2] == 'anatomical_brain' or keys[-2] == 'anatomical_brain_mask' or keys[-2] == 'anatomical_reorient': + if keys[-2] == 'anatomical_brain' or keys[-2] == 'anatomical_brain_mask' or keys[-2] == 'anatomical_skull_leaf': pass elif 'apply_warp_anat_longitudinal_to_standard' in keys[-2] or 'fsl_apply_xfm_longitudinal' in keys[-2]: # TODO update!!! diff --git a/CPAC/qc/pipeline.py b/CPAC/qc/pipeline.py index 9ab1c2c13c..40bcc7f22f 100644 --- a/CPAC/qc/pipeline.py +++ b/CPAC/qc/pipeline.py @@ -114,7 +114,7 @@ def create_qc_workflow(workflow, c, strategies, qc_outputs): # make QC montages for Skull Stripping Visualization anat_underlay, out_file = strat['anatomical_brain'] - skull, out_file_s = strat['anatomical_reorient'] + skull, out_file_s = strat['anatomical_skull_leaf'] qc_workflow = create_qc_skullstrip( 'qc_skullstrip_{0}'.format(num_strat) diff --git a/CPAC/registration/output_func_to_standard.py b/CPAC/registration/output_func_to_standard.py index a499072c85..c64934ebae 100644 --- a/CPAC/registration/output_func_to_standard.py +++ b/CPAC/registration/output_func_to_standard.py @@ -4,7 +4,14 @@ import nipype.interfaces.ants as ants import nipype.interfaces.c3 as c3 from CPAC.registration.utils import change_itk_transform_type, check_transforms, generate_inverse_transform_flags + +from nipype.interfaces.afni import utils as afni_utils +from CPAC.func_preproc.utils import chunk_ts, split_ts_chunks +from CPAC.utils.interfaces.function import Function + # Todo: CC distcor is not implement for fsl apply xform func to mni, why ?? + + def fsl_apply_transform_func_to_mni( workflow, output_name, @@ -14,8 +21,55 @@ def fsl_apply_transform_func_to_mni( strat, interpolation_method, distcor=False, - map_node=False + map_node=False, + func_ts=False, + num_cpus=1 ): + """ + Applies previously calculated FSL registration transforms to input + images. This workflow employs the FSL applywarp tool: + + https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/registration/index.html + + Parameters + ---------- + workflow: Nipype workflow object + the workflow containing the resources involved + output_name: str + what the name of the warped functional should be when written to the + resource pool + func_key: string + resource pool key correspoding to the node containing the 3D or 4D + functional file to be written into MNI space, use 'leaf' for a + leaf node + ref_key: string + resource pool key correspoding to the file path to the template brain + used for functional-to-template registration + num_strat: int + the number of strategy objects + strat: C-PAC Strategy object + a strategy with one or more resource pools + interpolation_method: str + which interpolation to use when applying the warps + distcor: boolean + indicates whether a distortion correction transformation should be + added to the transforms, this of course requires that a distortion + correction map exist in the resource pool + map_node: boolean + indicates whether a mapnode should be used, if TRUE func_key is + expected to correspond to a list of resources that should each + be written into standard space with the other parameters + func_ts: boolean + indicates whether the input image is a 4D time series + num_cpus: int + the number of CPUs dedicated to each participant workflow - this + is used to determine how to parallelize the warp application step + + Returns + ------- + workflow : nipype.pipeline.engine.Workflow + + """ strat_nodes = strat.get_nodes_names() @@ -35,6 +89,10 @@ def fsl_apply_transform_func_to_mni( elif isinstance(ref_key, tuple): ref_node, ref_out_file = ref_key + if int(num_cpus) > 1 and func_ts: + # parallelize time series warp application + map_node = True + if map_node == True: # func_mni_warp func_mni_warp = pe.MapNode(interface=fsl.ApplyWarp(), @@ -46,11 +104,52 @@ def fsl_apply_transform_func_to_mni( func_mni_warp = pe.Node(interface=fsl.ApplyWarp(), name='func_mni_fsl_warp_{0}_{1:d}'.format(output_name, num_strat)) - func_mni_warp.inputs.interp = interpolation_method - workflow.connect(func_node, func_file, - func_mni_warp, 'in_file') + # parallelize the apply warp, if multiple CPUs, and it's a time series! + if int(num_cpus) > 1 and func_ts: + + node_id = '{0}_{1:d}'.format(output_name, num_strat) + + chunk_imports = ['import nibabel as nb'] + chunk = pe.Node(Function(input_names=['func_file', + 'n_cpus'], + output_names=['TR_ranges'], + function=chunk_ts, + imports=chunk_imports), + name=f'chunk_{node_id}') + + chunk.inputs.n_cpus = int(num_cpus) + workflow.connect(func_node, func_file, chunk, 'func_file') + + split_imports = ['import os', 'import subprocess'] + split = pe.Node(Function(input_names=['func_file', + 'tr_ranges'], + output_names=['split_funcs'], + function=split_ts_chunks, + imports=split_imports), + name=f'split_{node_id}') + + workflow.connect(func_node, func_file, split, 'func_file') + workflow.connect(chunk, 'TR_ranges', split, 'tr_ranges') + + workflow.connect(split, 'split_funcs', func_mni_warp, 'in_file') + + func_concat = pe.Node(interface=afni_utils.TCat(), + name=f'func_concat{node_id}') + func_concat.inputs.outputtype = 'NIFTI_GZ' + + workflow.connect(func_mni_warp, 'out_file', + func_concat, 'in_files') + + strat.update_resource_pool({ + output_name: (func_concat, 'out_file') + }) + + else: + workflow.connect(func_node, func_file, + func_mni_warp, 'in_file') + strat.update_resource_pool({output_name: (func_mni_warp, 'out_file')}) workflow.connect(ref_node, ref_out_file, func_mni_warp, 'ref_file') @@ -65,6 +164,26 @@ def fsl_apply_transform_func_to_mni( workflow.connect(node, out_file, func_mni_warp, 'field_file') + if output_name == "functional_to_standard": + write_composite_xfm = pe.Node(interface=fsl.ConvertWarp(), + name='combine_fsl_warps_{0}_{1:d}'.format(output_name,\ + num_strat)) + + workflow.connect(ref_node, ref_out_file, + write_composite_xfm, 'reference') + + node, out_file = strat['functional_to_anat_linear_xfm'] + workflow.connect(node, out_file, + write_composite_xfm, 'premat') + + node, out_file = strat['anatomical_to_mni_nonlinear_xfm'] + workflow.connect(node, out_file, + write_composite_xfm, 'warp1') + + strat.update_resource_pool( + {"functional_to_standard_xfm": (write_composite_xfm, + 'out_file')}) + elif 'anat_mni_flirt_register' in strat_nodes: if 'functional_to_mni_linear_xfm' not in strat: @@ -83,7 +202,7 @@ def fsl_apply_transform_func_to_mni( workflow.connect(node, out_file, combine_transforms, 'in_file') - strat.update_resource_pool({ 'functional_to_mni_linear_xfm': + strat.update_resource_pool({'functional_to_mni_linear_xfm': (combine_transforms, 'out_file')}) strat.append_name(combine_transforms.name) @@ -96,7 +215,6 @@ def fsl_apply_transform_func_to_mni( raise ValueError( 'Could not find flirt or fnirt registration in nodes') - strat.update_resource_pool({ output_name: (func_mni_warp, 'out_file')}) strat.append_name(func_mni_warp.name) return workflow @@ -117,7 +235,8 @@ def ants_apply_warps_func_mni( input_image_type=0, num_ants_cores=1, registration_template='t1', - func_type='non-ica-aroma' + func_type='non-ica-aroma', + num_cpus=1 ): """ @@ -177,6 +296,16 @@ def ants_apply_warps_func_mni( num_ants_cores: int the number of CPU cores dedicated to ANTS anatomical-to-standard registration + registration_template: str + which template to use as a target for the apply warps ('t1' or 'epi'), + should be the same as the target used in the warp calculation + (registration) + func_type: str + 'non-ica-aroma' or 'ica-aroma' - how to handle the functional time series + based on the particular demands of ICA-AROMA processed time series + num_cpus: int + the number of CPUs dedicated to each participant workflow - this is + used to determine how to parallelize the warp application step Workflow Outputs:: @@ -361,48 +490,46 @@ def ants_apply_warps_func_mni( if distcor is True and func_type not in 'ica-aroma': # Field file from anatomical nonlinear registration transforms_to_combine = [\ - ('epi_to_func_nonlinear_xfm', 'in5'), - ('func_to_epi_ants_affine_xfm', 'in4'), - ('func_to_epi_ants_rigid_xfm', 'in3'), - ('func_to_epi_ants_initial_xfm', 'in2'), - # ('fsl_mat_as_itk', 'in2'), - ('blip_warp_inverse', 'in1')] + ('epi_to_func_nonlinear_xfm', 'in4'), + ('func_to_epi_ants_affine_xfm', 'in3'), + ('func_to_epi_ants_rigid_xfm', 'in2'), + ('func_to_epi_ants_initial_xfm', 'in1')] else: transforms_to_combine = [\ - ('epi_to_func_nonlinear_xfm', 'in5'), - ('func_to_epi_ants_affine_xfm', 'in4'), - ('func_to_epi_ants_rigid_xfm', 'in3'), - ('func_to_epi_ants_initial_xfm', 'in2')] - # ('fsl_mat_as_itk', 'in1')] + ('epi_to_func_nonlinear_xfm', 'in4'), + ('func_to_epi_ants_affine_xfm', 'in3'), + ('func_to_epi_ants_rigid_xfm', 'in2'), + ('func_to_epi_ants_initial_xfm', 'in1')] else: transforms_to_combine = [\ ('func_to_epi_nonlinear_xfm', 'in1'), ('func_to_epi_ants_affine_xfm', 'in2'), ('func_to_epi_ants_rigid_xfm', 'in3'), ('func_to_epi_ants_initial_xfm', 'in4')] - # ('fsl_mat_as_itk', 'in5')] - - if distcor is True and func_type not in 'ica-aroma': - transforms_to_combine.append(('blip_warp', 'in5')) # define the node collect_transforms = pe.Node(util.Merge(num_transforms), - name='collect_transforms{0}_{1}_{2}'.format(inverse_string, - registration_template, - num_strat)) + name='collect_transforms_{0}_{1}_{2}_{3}'.format(output_name, + inverse_string, + registration_template, + num_strat)) - # wire in the various tranformations + # wire in the various transformations for transform_key, input_port in transforms_to_combine: - node, out_file = strat[ants_transformation_dict[symmetry][transform_key]] + try: + node, out_file = strat[ants_transformation_dict[symmetry][transform_key]] + except KeyError: + raise Exception(locals()) workflow.connect(node, out_file, collect_transforms, input_port) # check transform list (if missing any init/rig/affine) and exclude Nonetype check_transform = pe.Node(util.Function(input_names=['transform_list'], output_names=['checked_transform_list', 'list_length'], function=check_transforms), - name='check_transforms{0}_{1}_{2}'.format(inverse_string, - registration_template, - num_strat)) + name='check_transforms{0}_{1}_{2}_{3}'.format(output_name, + inverse_string, + registration_template, + num_strat)) workflow.connect(collect_transforms, 'out', check_transform, 'transform_list') @@ -410,9 +537,10 @@ def ants_apply_warps_func_mni( inverse_transform_flags = pe.Node(util.Function(input_names=['transform_list'], output_names=['inverse_transform_flags'], function=generate_inverse_transform_flags), - name='inverse_transform_flags{0}_{1}_{2}'.format(inverse_string, - registration_template, - num_strat)) + name='inverse_transform_flags_{0}_{1}_{2}_{3}'.format(output_name, + inverse_string, + registration_template, + num_strat)) workflow.connect(check_transform, 'checked_transform_list', inverse_transform_flags, 'transform_list') @@ -426,16 +554,20 @@ def ants_apply_warps_func_mni( strat.append_name(inverse_transform_flags.name) #### now we add in the apply ants warps node + if int(num_cpus) > 1 and input_image_type == 3: + # parallelize time series warp application + map_node = True + if map_node: apply_ants_warp = pe.MapNode( interface=ants.ApplyTransforms(), - name='apply_ants_warp_{0}_mapnode{1}_{2}_{3}'.format(output_name, + name='apply_ants_warp_{0}_mapnode_{1}_{2}_{3}'.format(output_name, inverse_string, registration_template, num_strat), iterfield=['input_image'], mem_gb=1.5) else: apply_ants_warp = pe.Node( interface=ants.ApplyTransforms(), - name='apply_ants_warp_{0}{1}_{2}_{3}'.format(output_name, + name='apply_ants_warp_{0}_{1}_{2}_{3}'.format(output_name, inverse_string, registration_template, num_strat), mem_gb=1.5) apply_ants_warp.inputs.out_postfix = '_antswarp' @@ -460,12 +592,109 @@ def ants_apply_warps_func_mni( workflow.connect(collect_node, collect_out, apply_ants_warp, 'transforms') - workflow.connect(input_node, input_out, - apply_ants_warp, 'input_image') + if output_name == "functional_to_standard": + # write out the composite functional to standard transforms + write_composite_xfm = pe.Node( + interface=ants.ApplyTransforms(), + name='write_composite_xfm_{0}_{1}_{2}_{3}'.format(output_name, + inverse_string, registration_template, num_strat), mem_gb=1.5) + write_composite_xfm.inputs.print_out_composite_warp_file = True + write_composite_xfm.inputs.output_image = "func_to_standard_xfm.nii.gz" + + workflow.connect(input_node, input_out, + write_composite_xfm, 'input_image') + + write_composite_xfm.inputs.input_image_type = input_image_type + write_composite_xfm.inputs.dimension = 3 + write_composite_xfm.inputs.interpolation = interpolation_method + + node, out_file = strat[ref_key] + workflow.connect(node, out_file, + write_composite_xfm, 'reference_image') + + collect_node, collect_out = strat[collect_transforms_key] + workflow.connect(collect_node, collect_out, + write_composite_xfm, 'transforms') + + # write_composite_inv_xfm = pe.Node( + # interface=ants.ApplyTransforms(), + # name='write_composite_xfm_{0}_{1}_{2}_{3}'.format(output_name, + # '_inverse', registration_template, num_strat), mem_gb=1.5) + # write_composite_inv_xfm.inputs.print_out_composite_warp_file = True + # write_composite_inv_xfm.inputs.output_image = "func_to_standard_inverse-xfm.nii.gz" + # + # workflow.connect(input_node, input_out, + # write_composite_inv_xfm, 'input_image') + # + # workflow.connect(inverse_transform_flags, 'inverse_transform_flags', + # write_composite_inv_xfm, 'invert_transform_flags') + # + # + # write_composite_inv_xfm.inputs.input_image_type = input_image_type + # write_composite_inv_xfm.inputs.dimension = 3 + # write_composite_inv_xfm.inputs.interpolation = interpolation_method + # + # node, out_file = strat[ref_key] + # workflow.connect(node, out_file, + # write_composite_inv_xfm, 'reference_image') + # + # collect_node, collect_out = strat[collect_transforms_key] + # workflow.connect(collect_node, collect_out, + # write_composite_inv_xfm, 'transforms') + + strat.update_resource_pool({ + "functional_to_standard_xfm": (write_composite_xfm, 'output_image') + }) + #"functional_to_standard_inverse-xfm": (write_composite_inv_xfm, 'output_image') + #}) + + # parallelize the apply warp, if multiple CPUs, and it's a time series! + if int(num_cpus) > 1 and input_image_type == 3: - strat.update_resource_pool({ - output_name: (apply_ants_warp, 'output_image') - }) + node_id = f'_{output_name}_{inverse_string}_{registration_template}_{num_strat}' + + chunk_imports = ['import nibabel as nb'] + chunk = pe.Node(Function(input_names=['func_file', + 'n_cpus'], + output_names=['TR_ranges'], + function=chunk_ts, + imports=chunk_imports), + name=f'chunk_{node_id}') + + chunk.inputs.n_cpus = int(num_cpus) + workflow.connect(input_node, input_out, chunk, 'func_file') + + split_imports = ['import os', 'import subprocess'] + split = pe.Node(Function(input_names=['func_file', + 'tr_ranges'], + output_names=['split_funcs'], + function=split_ts_chunks, + imports=split_imports), + name=f'split_{node_id}') + + workflow.connect(input_node, input_out, split, 'func_file') + workflow.connect(chunk, 'TR_ranges', split, 'tr_ranges') + + workflow.connect(split, 'split_funcs', apply_ants_warp, 'input_image') + + func_concat = pe.Node(interface=afni_utils.TCat(), + name=f'func_concat_{node_id}') + func_concat.inputs.outputtype = 'NIFTI_GZ' + + workflow.connect(apply_ants_warp, 'output_image', + func_concat, 'in_files') + + strat.update_resource_pool({ + output_name: (func_concat, 'out_file') + }) + + else: + workflow.connect(input_node, input_out, + apply_ants_warp, 'input_image') + + strat.update_resource_pool({ + output_name: (apply_ants_warp, 'output_image') + }) strat.append_name(apply_ants_warp.name) @@ -474,10 +703,11 @@ def ants_apply_warps_func_mni( def output_func_to_standard(workflow, func_key, ref_key, output_name, strat, num_strat, pipeline_config_obj, input_image_type='func_derivative', - symmetry='asymmetric', inverse=False, registration_template='t1', func_type='non-ica-aroma'): + symmetry='asymmetric', inverse=False, registration_template='t1', + func_type='non-ica-aroma'): image_types = ['func_derivative', 'func_derivative_multi', - 'func_4d', 'func_mask'] + 'func_4d', 'func_mask'] if input_image_type not in image_types: raise ValueError('Input image type {0} should be one of {1}'.format(\ @@ -490,6 +720,8 @@ def output_func_to_standard(workflow, func_key, ref_key, output_name, distcor = True if 'epi_distcorr' in nodes or \ 'blip_correct' in nodes else False + num_cpus = pipeline_config_obj.maxCoresPerParticipant + if 'anat_mni_fnirt_register' in nodes or \ 'anat_mni_flirt_register' in nodes or \ 'func_to_epi_fsl' in nodes: @@ -499,9 +731,11 @@ def output_func_to_standard(workflow, func_key, ref_key, output_name, else: interp = pipeline_config_obj.funcRegFSLinterpolation + func_ts = True if input_image_type == 'func_4d' else False + fsl_apply_transform_func_to_mni(workflow, output_name, func_key, ref_key, num_strat, strat, interp, distcor=distcor, - map_node=map_node) + map_node=map_node, func_ts=func_ts, num_cpus=num_cpus) elif 'ANTS' in pipeline_config_obj.regOption: @@ -516,9 +750,9 @@ def output_func_to_standard(workflow, func_key, ref_key, output_name, num_strat, strat, interpolation_method=interp, distcor=distcor, map_node=map_node, inverse=inverse, symmetry=symmetry, input_image_type=image_type, - num_ants_cores=pipeline_config_obj.num_ants_threads, - registration_template=registration_template, - func_type=func_type) + num_ants_cores=pipeline_config_obj.num_ants_threads, + registration_template=registration_template, + func_type=func_type, num_cpus=num_cpus) else: raise ValueError('Cannot determine whether a ANTS or FSL registration' \ diff --git a/CPAC/registration/registration.py b/CPAC/registration/registration.py index 9d512324ac..2dcbbcd18c 100644 --- a/CPAC/registration/registration.py +++ b/CPAC/registration/registration.py @@ -1059,7 +1059,7 @@ def connect_func_to_anat_bbreg(workflow, strat_list, c, diff_complete): func_to_anat_bbreg, 'inputspec.func') # Input anatomical whole-head image (reoriented) - node, out_file = strat['anatomical_reorient'] + node, out_file = strat['anatomical_skull_leaf'] workflow.connect(node, out_file, func_to_anat_bbreg, 'inputspec.anat_skull') @@ -1164,17 +1164,18 @@ def connect_func_to_template_reg(workflow, strat_list, c): ) # Input registration parameters - if c.ANTs_para_EPI_registration is None: + if reg.lower() == 'ants' and c.ANTs_para_EPI_registration is None: err_msg = '\n\n[!] C-PAC says: \n'\ "You have selected \'regOption: [{0}]\' and \'runRegisterFuncToTemplate : ['{1}']\'. \n"\ 'However, no EPI-to-template ANTs parameters were specified. ' \ 'Please specify ANTs parameters properly and try again'.format(str(c.regOption), - str(c.runRegisterFuncToTemplate)) + str(c.runRegisterFuncToTemplate)) raise Exception(err_msg) - else: + elif reg.lower() == 'ants': func_to_epi.inputs.inputspec.ants_para = c.ANTs_para_EPI_registration - - func_to_epi.inputs.inputspec.interp = c.funcRegANTSinterpolation + func_to_epi.inputs.inputspec.interp = c.funcRegANTSinterpolation + else: + func_to_epi.inputs.inputspec.interp = c.funcRegFSLinterpolation node, out_file = strat.get_leaf_properties() workflow.connect(node, out_file, func_to_epi, 'inputspec.func_4d') @@ -1198,7 +1199,7 @@ def connect_func_to_template_reg(workflow, strat_list, c): 'functional_to_epi-standard': (func_to_epi, 'outputspec.func_in_epi'), }) - if reg == 'FSL': + if reg.lower() == 'fsl': strat.update_resource_pool({ 'epi_registration_method': 'FSL', 'func_to_epi_linear_xfm': (func_to_epi, 'outputspec.fsl_flirt_xfm'), @@ -1206,7 +1207,7 @@ def connect_func_to_template_reg(workflow, strat_list, c): 'epi_to_func_linear_xfm': (func_to_epi, 'outputspec.invlinear_xfm'), }) - elif reg == 'ANTS': + elif reg.lower() == 'ants': strat.update_resource_pool({ 'epi_registration_method': 'ANTS', 'func_to_epi_ants_initial_xfm': (func_to_epi, 'outputspec.ants_initial_xfm'), @@ -1256,4 +1257,4 @@ def connect_func_to_template_reg(workflow, strat_list, c): registration_template='t1', func_type='non-ica-aroma') - return workflow, strat_list \ No newline at end of file + return workflow, strat_list diff --git a/CPAC/registration/utils.py b/CPAC/registration/utils.py index 42d0d83d3e..a1b03ed5ba 100644 --- a/CPAC/registration/utils.py +++ b/CPAC/registration/utils.py @@ -15,17 +15,18 @@ def seperate_warps_list(warp_list, selection): selected_warp = warp return selected_warp + def check_transforms(transform_list): transform_number = list(filter(None, transform_list)) return[(transform_number[index]) for index in range(len(transform_number))], len(transform_number) + def generate_inverse_transform_flags(transform_list): inverse_transform_flags=[] for transform in transform_list: # check `blip_warp_inverse` file name and rename it if 'WARPINV' in transform: inverse_transform_flags.append(False) - if 'updated_affine' in transform: inverse_transform_flags.append(True) if 'Initial' in transform: @@ -38,6 +39,7 @@ def generate_inverse_transform_flags(transform_list): inverse_transform_flags.append(False) return inverse_transform_flags + def hardcoded_reg(moving_brain, reference_brain, moving_skull, reference_skull, ants_para, fixed_image_mask=None, interp=None): diff --git a/CPAC/resources/configs/data_config_S3-BIDS-ABIDE.yml b/CPAC/resources/configs/data_config_S3-BIDS-ABIDE.yml index f8e33cb544..49611a5e45 100644 --- a/CPAC/resources/configs/data_config_S3-BIDS-ABIDE.yml +++ b/CPAC/resources/configs/data_config_S3-BIDS-ABIDE.yml @@ -1,5 +1,4 @@ # CPAC Data Configuration File -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/data_config_S3-BIDS-ADHD200.yml b/CPAC/resources/configs/data_config_S3-BIDS-ADHD200.yml index dac9054c4f..3e0677c6a9 100644 --- a/CPAC/resources/configs/data_config_S3-BIDS-ADHD200.yml +++ b/CPAC/resources/configs/data_config_S3-BIDS-ADHD200.yml @@ -1,5 +1,4 @@ # CPAC Data Configuration File -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/data_config_S3-BIDS-ADHD200_only2.yml b/CPAC/resources/configs/data_config_S3-BIDS-ADHD200_only2.yml index be9eab2436..1ab5fe7de0 100644 --- a/CPAC/resources/configs/data_config_S3-BIDS-ADHD200_only2.yml +++ b/CPAC/resources/configs/data_config_S3-BIDS-ADHD200_only2.yml @@ -1,5 +1,4 @@ # CPAC Data Configuration File -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/data_config_S3-BIDS-NKI-RocklandSample.yml b/CPAC/resources/configs/data_config_S3-BIDS-NKI-RocklandSample.yml index 0ac1a2c82a..5ad419ca52 100644 --- a/CPAC/resources/configs/data_config_S3-BIDS-NKI-RocklandSample.yml +++ b/CPAC/resources/configs/data_config_S3-BIDS-NKI-RocklandSample.yml @@ -1,5 +1,4 @@ # CPAC Data Configuration File -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/data_config_cpac_benchmark.yml b/CPAC/resources/configs/data_config_cpac_benchmark.yml index ff10b6bc44..c85fb55723 100644 --- a/CPAC/resources/configs/data_config_cpac_benchmark.yml +++ b/CPAC/resources/configs/data_config_cpac_benchmark.yml @@ -1,5 +1,4 @@ # CPAC Data Configuration File -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/data_settings_template.yml b/CPAC/resources/configs/data_settings_template.yml index 4d4bdf4710..2fe6c794ec 100644 --- a/CPAC/resources/configs/data_settings_template.yml +++ b/CPAC/resources/configs/data_settings_template.yml @@ -1,5 +1,4 @@ # CPAC Data Settings File -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/group_config_template.yml b/CPAC/resources/configs/group_config_template.yml index e2464ac90d..4d4fdc84a2 100644 --- a/CPAC/resources/configs/group_config_template.yml +++ b/CPAC/resources/configs/group_config_template.yml @@ -1,5 +1,4 @@ # CPAC Group-Level Analysis Configuration YAML file -# Version 1.4.3 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_anat-only.yml b/CPAC/resources/configs/pipeline_config_anat-only.yml index 92b7c798e7..2e1c8e4424 100644 --- a/CPAC/resources/configs/pipeline_config_anat-only.yml +++ b/CPAC/resources/configs/pipeline_config_anat-only.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_benchmark-ANTS.yml b/CPAC/resources/configs/pipeline_config_benchmark-ANTS.yml index 390c6bc6fe..ca09b59e7b 100644 --- a/CPAC/resources/configs/pipeline_config_benchmark-ANTS.yml +++ b/CPAC/resources/configs/pipeline_config_benchmark-ANTS.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_benchmark-FNIRT.yml b/CPAC/resources/configs/pipeline_config_benchmark-FNIRT.yml index 3a88434f18..599b4ce3af 100644 --- a/CPAC/resources/configs/pipeline_config_benchmark-FNIRT.yml +++ b/CPAC/resources/configs/pipeline_config_benchmark-FNIRT.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_fmriprep-options.yml b/CPAC/resources/configs/pipeline_config_fmriprep-options.yml index 475f2e8d2d..ab718afd80 100644 --- a/CPAC/resources/configs/pipeline_config_fmriprep-options.yml +++ b/CPAC/resources/configs/pipeline_config_fmriprep-options.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_monkey.yml b/CPAC/resources/configs/pipeline_config_monkey.yml index 0c10365d2d..dbd77886aa 100644 --- a/CPAC/resources/configs/pipeline_config_monkey.yml +++ b/CPAC/resources/configs/pipeline_config_monkey.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # @@ -117,6 +116,20 @@ non_local_means_filtering: True n4_bias_field_correction: True +# ACPC Alignment +acpc_align: True + + +# ACPC size of brain in z-dimension in mm. +# Default: 150mm for human data. +acpc_brainsize: 70 + + +# ACPC aligned template +acpc_template_skull: s3://fcp-indi/resources/cpac/resources/MacaqueYerkes19_T1w_0.5mm.nii.gz +acpc_template_brain: s3://fcp-indi/resources/cpac/resources/MacaqueYerkes19_T1w_0.5mm_brain.nii.gz + + # Disables skull-stripping on the anatomical inputs if they are already skull-stripped outside of C-PAC. # Set this to 1 if your input images are already skull-stripped. already_skullstripped : [0] diff --git a/CPAC/resources/configs/pipeline_config_ndmg.yml b/CPAC/resources/configs/pipeline_config_ndmg.yml index 9c3ba1b754..47022ad9a0 100644 --- a/CPAC/resources/configs/pipeline_config_ndmg.yml +++ b/CPAC/resources/configs/pipeline_config_ndmg.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_nhp-macaque.yml b/CPAC/resources/configs/pipeline_config_nhp-macaque.yml index a57807fa8e..ef60342707 100644 --- a/CPAC/resources/configs/pipeline_config_nhp-macaque.yml +++ b/CPAC/resources/configs/pipeline_config_nhp-macaque.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # @@ -117,6 +116,20 @@ non_local_means_filtering: True n4_bias_field_correction: True +# ACPC Alignment +acpc_align: True + + +# ACPC size of brain in z-dimension in mm. +# Default: 150mm for human data. +acpc_brainsize: 70 + + +# ACPC aligned template +acpc_template_skull: s3://fcp-indi/resources/cpac/resources/MacaqueYerkes19_T1w_0.5mm.nii.gz +acpc_template_brain: s3://fcp-indi/resources/cpac/resources/MacaqueYerkes19_T1w_0.5mm_brain.nii.gz + + # Disables skull-stripping on the anatomical inputs if they are already skull-stripped outside of C-PAC. # Set this to 1 if your input images are already skull-stripped. already_skullstripped : [0] diff --git a/CPAC/resources/configs/pipeline_config_preproc.yml b/CPAC/resources/configs/pipeline_config_preproc.yml index cc84f15a5a..121061bb87 100644 --- a/CPAC/resources/configs/pipeline_config_preproc.yml +++ b/CPAC/resources/configs/pipeline_config_preproc.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_regtest-1.yml b/CPAC/resources/configs/pipeline_config_regtest-1.yml index c88ec26faa..6403e8760d 100644 --- a/CPAC/resources/configs/pipeline_config_regtest-1.yml +++ b/CPAC/resources/configs/pipeline_config_regtest-1.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_regtest-2.yml b/CPAC/resources/configs/pipeline_config_regtest-2.yml index 1516b82217..efd3eaeab4 100644 --- a/CPAC/resources/configs/pipeline_config_regtest-2.yml +++ b/CPAC/resources/configs/pipeline_config_regtest-2.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_regtest-3.yml b/CPAC/resources/configs/pipeline_config_regtest-3.yml index ef9378ffa4..6c968ed99f 100644 --- a/CPAC/resources/configs/pipeline_config_regtest-3.yml +++ b/CPAC/resources/configs/pipeline_config_regtest-3.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_regtest-4.yml b/CPAC/resources/configs/pipeline_config_regtest-4.yml index dbc602d604..af51ae1adb 100644 --- a/CPAC/resources/configs/pipeline_config_regtest-4.yml +++ b/CPAC/resources/configs/pipeline_config_regtest-4.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/pipeline_config_rodent.yml b/CPAC/resources/configs/pipeline_config_rodent.yml index e6f1cc36d9..5b9003ab89 100644 --- a/CPAC/resources/configs/pipeline_config_rodent.yml +++ b/CPAC/resources/configs/pipeline_config_rodent.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.6.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/data-test_S3-ADHD200_1.yml b/CPAC/resources/configs/test_configs/data-test_S3-ADHD200_1.yml index 90062a98ad..602367a63a 100644 --- a/CPAC/resources/configs/test_configs/data-test_S3-ADHD200_1.yml +++ b/CPAC/resources/configs/test_configs/data-test_S3-ADHD200_1.yml @@ -1,5 +1,4 @@ # CPAC Data Configuration File -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/data-test_S3-ADHD200_no-params.yml b/CPAC/resources/configs/test_configs/data-test_S3-ADHD200_no-params.yml index 36586a8c11..f7ecd8f742 100644 --- a/CPAC/resources/configs/test_configs/data-test_S3-ADHD200_no-params.yml +++ b/CPAC/resources/configs/test_configs/data-test_S3-ADHD200_no-params.yml @@ -1,5 +1,4 @@ # CPAC Data Configuration File -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/data-test_S3-NKI-RS_fmap.yml b/CPAC/resources/configs/test_configs/data-test_S3-NKI-RS_fmap.yml index 521a137e71..ec5a98daf4 100644 --- a/CPAC/resources/configs/test_configs/data-test_S3-NKI-RS_fmap.yml +++ b/CPAC/resources/configs/test_configs/data-test_S3-NKI-RS_fmap.yml @@ -1,5 +1,4 @@ # CPAC Data Configuration File -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/data_config_S3_CoRR_5only_mult-scan.yml b/CPAC/resources/configs/test_configs/data_config_S3_CoRR_5only_mult-scan.yml index 27d51208e8..86f3938095 100644 --- a/CPAC/resources/configs/test_configs/data_config_S3_CoRR_5only_mult-scan.yml +++ b/CPAC/resources/configs/test_configs/data_config_S3_CoRR_5only_mult-scan.yml @@ -1,5 +1,5 @@ # CPAC Data Configuration File -# Version 1.4.2 +# # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/data_config_S3_CoRR_5only_mult-sess.yml b/CPAC/resources/configs/test_configs/data_config_S3_CoRR_5only_mult-sess.yml index d0bf697aa3..7cbea91fc1 100644 --- a/CPAC/resources/configs/test_configs/data_config_S3_CoRR_5only_mult-sess.yml +++ b/CPAC/resources/configs/test_configs/data_config_S3_CoRR_5only_mult-sess.yml @@ -1,5 +1,5 @@ # CPAC Data Configuration File -# Version 1.4.2 +# # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-AllNuis.yml b/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-AllNuis.yml index 6e37c37c83..fd28c625b8 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-AllNuis.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-AllNuis.yml @@ -1,5 +1,5 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 +# # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-DistCorr3dSk.yml b/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-DistCorr3dSk.yml index 6a5ce051f0..489cb48396 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-DistCorr3dSk.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-DistCorr3dSk.yml @@ -1,5 +1,5 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 +# # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-DistCorrBET.yml b/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-DistCorrBET.yml index e910d4e657..8c44769a89 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-DistCorrBET.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_ANTs-3dSk-DistCorrBET.yml @@ -1,5 +1,5 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 +# # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_ANTs-BET-AllNuis.yml b/CPAC/resources/configs/test_configs/pipe-test_ANTs-BET-AllNuis.yml index 13400c40de..b02ef91b42 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_ANTs-BET-AllNuis.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_ANTs-BET-AllNuis.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-3dSk-AllNuis.yml b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-3dSk-AllNuis.yml index 073bc3b633..23c68820d0 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-3dSk-AllNuis.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-3dSk-AllNuis.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-BASC.yml b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-BASC.yml index a3830ec8e5..978ae7be97 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-BASC.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-BASC.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-ISC-voxel.yml b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-ISC-voxel.yml index bdef4626fc..dc397178df 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-ISC-voxel.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-ISC-voxel.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-ISC.yml b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-ISC.yml index 5bdb988dbc..00e7e22e4c 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-ISC.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-ISC.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-MDMR.yml b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-MDMR.yml index 6957d0bc91..b47599f7c0 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-MDMR.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis-MDMR.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis.yml b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis.yml index 459eb7bb51..c6561f4a96 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_FNIRT-BET-AllNuis.yml @@ -1,5 +1,4 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/configs/test_configs/pipe-test_all.yml b/CPAC/resources/configs/test_configs/pipe-test_all.yml index 269ade4537..4b4a68adb8 100644 --- a/CPAC/resources/configs/test_configs/pipe-test_all.yml +++ b/CPAC/resources/configs/test_configs/pipe-test_all.yml @@ -1,5 +1,5 @@ # CPAC Pipeline Configuration YAML file -# Version 1.4.2 +# # # http://fcp-indi.github.io for more info. # diff --git a/CPAC/resources/cpac_outputs.csv b/CPAC/resources/cpac_outputs.csv index 49f7c61aa9..c61fca573d 100644 --- a/CPAC/resources/cpac_outputs.csv +++ b/CPAC/resources/cpac_outputs.csv @@ -66,7 +66,9 @@ functional_to_anat_linear_xfm,,transform,,,,,,,,,,,, functional_to_epi-standard,template,raw,,yes,,,,,,,,yes,,yes functional_to_epi-standard_smooth,template,raw,,yes,,,,,yes,,,,,yes functional_to_standard,template,raw,,yes,,,,,,,,yes,,yes +functional_to_standard_inverse-xfm,,transform,,,,,,,,,,,, functional_to_standard_smooth,template,raw,,yes,,,,,yes,,,,,yes +functional_to_standard_xfm,,transform,,,,,,,,,,,, ica_aroma_denoised_functional,functional,raw,,,,,,,yes,,,,, max_displacement,,,,,,,,,,,,,yes, mean_functional,functional,raw,,,,,,,yes,,,,, @@ -77,6 +79,9 @@ motion_correct,functional,raw,,,,yes,,,,,,yes,, motion_correct_smooth,functional,raw,,yes,,,,,yes,,,,, motion_correct_to_standard,template,raw,,yes,,,,,yes,,,yes,, motion_correct_to_standard_smooth,template,raw,,yes,,,,,yes,,,,, +motion_estimate_filter_info_design,,,,,,,,,,,,,, +motion_estimate_filter_info_plot,,,,,,,,,,,,,, +motion_estimate_filter_info_plot-norm,,,,,,,,,,,,,, motion_params,,,,,,,,,,,,,, movement_parameters,,,,,,,,,,,,,yes, ndmg_graph,,,,,,,,,,,,,, @@ -116,4 +121,4 @@ symmetric_anatomical_to_standard,template,raw,,,,,,,,,,,, symmetric_mni_to_anatomical_nonlinear_xfm,,transform,,,,,,,,,,,, vmhc_fisher_zstd,template,r-to-z,,,yes,,,,,,,,yes, vmhc_fisher_zstd_zstat_map,template,z-stat,,,yes,,,,,,,,, -vmhc_raw_score,template,Pearson's r,,,yes,,yes,,,,,,yes, \ No newline at end of file +vmhc_raw_score,template,Pearson's r,,,yes,,yes,,,,,,yes, diff --git a/CPAC/resources/templates/ndmg_atlases.csv b/CPAC/resources/templates/ndmg_atlases.csv new file mode 100644 index 0000000000..15ac6b0ba6 --- /dev/null +++ b/CPAC/resources/templates/ndmg_atlases.csv @@ -0,0 +1,22 @@ +"aal_space-MNI152NLin6_res-1x1x1.nii.gz","AAL_space-MNI152NLin6_res-1x1x1.nii.gz" +"aal_space-MNI152NLin6_res-2x2x2.nii.gz","AAL_space-MNI152NLin6_res-2x2x2.nii.gz" +"AAL2zourioMazoyer2002.nii.gz","AAL_space-MNI152NLin6_res-1x1x1.nii.gz" +"brodmann_space-MNI152NLin6_res-1x1x1.nii.gz","Brodmann_space-MNI152NLin6_res-1x1x1.nii.gz" +"brodmann_space-MNI152NLin6_res-2x2x2.nii.gz","Brodmann_space-MNI152NLin6_res-2x2x2.nii.gz" +"CorticalAreaParcellationfromRestingStateCorrelationsGordon2014.nii.gz","CAPRSC_space-MNI152NLin6_res-1x1x1.nii.gz" +"desikan_space-MNI152NLin6_res-1x1x1.nii.gz","Desikan_space-MNI152NLin6_res-1x1x1.nii.gz" +"desikan_space-MNI152NLin6_res-2x2x2.nii.gz","Desikan_space-MNI152NLin6_res-2x2x2.nii.gz" +"DesikanKlein2012.nii.gz","DesikanKlein_space-MNI152NLin6_res-1x1x1.nii.gz" +"glasser_space-MNI152NLin6_res-1x1x1.nii.gz","Glasser_space-MNI152NLin6_res-1x1x1.nii.gz" +"Juelichgmthr252mmEickhoff2005.nii.gz","Juelich_space-MNI152NLin6_res-1x1x1.nii.gz" +"MICCAI2012MultiAtlasLabelingWorkshopandChallengeNeuromorphometrics.nii.gz","MICCAI_space-MNI152NLin6_res-1x1x1.nii.gz" +"princetonvisual-top_space-MNI152NLin6_res-2x2x2.nii.gz","Princetonvisual-top_space-MNI152NLin6_res-2x2x2.nii.gz" +"Schaefer2018-200-node_space-MNI152NLin6_res-1x1x1.nii.gz","Schaefer200_space-MNI152NLin6_res-1x1x1.nii.gz" +"Schaefer2018-300-node_space-MNI152NLin6_res-1x1x1.nii.gz","Schaefer300_space-MNI152NLin6_res-1x1x1.nii.gz" +"Schaefer2018-400-node_space-MNI152NLin6_res-1x1x1.nii.gz","Schaefer400_space-MNI152NLin6_res-1x1x1.nii.gz" +"Schaefer2018-1000-node_space-MNI152NLin6_res-1x1x1.nii.gz","Schaefer1000_space-MNI152NLin6_res-1x1x1.nii.gz" +"slab907_space-MNI152NLin6_res-1x1x1.nii.gz","Slab907_space-MNI152NLin6_res-1x1x1.nii.gz" +"yeo-7_space-MNI152NLin6_res-1x1x1.nii.gz","Yeo-7_space-MNI152NLin6_res-1x1x1.nii.gz" +"yeo-7-liberal_space-MNI152NLin6_res-1x1x1.nii.gz","Yeo-7-liberal_space-MNI152NLin6_res-1x1x1.nii.gz" +"yeo-17_space-MNI152NLin6_res-1x1x1.nii.gz","Yeo-17_space-MNI152NLin6_res-1x1x1.nii.gz" +"yeo-17-liberal_space-MNI152NLin6_res-1x1x1.nii.gz","Yeo-17-liberal_space-MNI152NLin6_res-1x1x1.nii.gz" \ No newline at end of file diff --git a/CPAC/sca/utils.py b/CPAC/sca/utils.py index cee9a7645b..b968e8f901 100644 --- a/CPAC/sca/utils.py +++ b/CPAC/sca/utils.py @@ -69,6 +69,7 @@ def compute_fisher_z_score(correlation_file, timeseries_one_d): def check_ts(in_file): + import os import numpy as np if '.txt' in in_file: @@ -77,12 +78,16 @@ def check_ts(in_file): except ValueError: timepoints = np.loadtxt(in_file).shape[0] rois = 1 + out_file = in_file elif '.csv' in in_file: csv_array = np.genfromtxt(in_file, delimiter=',') + if np.isnan(csv_array[0][0]): + csv_array = csv_array[1:] timepoints, rois = csv_array.shape # multiple regression (fsl_glm) needs this format for -design input - in_file = in_file.replace('.csv', '.txt') - np.savetxt(in_file, csv_array, delimiter='\t') + out_file = os.path.join(os.getcwd(), + os.path.basename(in_file).replace('.csv', '.txt')) + np.savetxt(out_file, csv_array, delimiter='\t') if rois > timepoints: message = ('\n\n\n****The number of timepoints (' + str(timepoints) + ') is smaller than the number of ROIs to run (' @@ -91,7 +96,7 @@ def check_ts(in_file): print(message) raise Exception(message) else: - return in_file + return out_file def map_to_roi(timeseries, maps): @@ -99,39 +104,32 @@ def map_to_roi(timeseries, maps): Renames the outputs of the temporal multiple regression workflow for sca according to the header information of the timeseries.txt file that was passed - NOTE: This is only run if the temporal regression is run as part of sca (which = 'RT') when calling the temporal regression workflow. If you run the temporal regression workflow manually, don\'t set (which = 'RT') unless you provide a timeseries.txt file with a header containing the names of the timeseries - - Parameters ---------- - timeseries: string Input timeseries.txt file - maps: List (nifti files) List of output files generated by the temporal regression workflow if (which == 'RT') - - Returns ------- - labels : List (strings) List of names that the output files should be renamed to - maps: List (nifti files) List of output files generated by the temporal regression workflow if (which == 'RT') """ import numpy as np + import pandas as pd from nipype import logging logger = logging.getLogger('nipype.workflow') - testMat = np.loadtxt(timeseries, delimiter=',') + + testMat = pd.read_csv(timeseries) timepoints, rois = testMat.shape if rois > timepoints: @@ -179,4 +177,4 @@ def map_to_roi(timeseries, maps): maps = maps[:rois] - return roi_list, maps + return roi_list, maps \ No newline at end of file diff --git a/CPAC/timeseries/timeseries_analysis.py b/CPAC/timeseries/timeseries_analysis.py index 347964b130..b1fcad1db6 100644 --- a/CPAC/timeseries/timeseries_analysis.py +++ b/CPAC/timeseries/timeseries_analysis.py @@ -133,6 +133,9 @@ def clean_roi_csv(roi_csv): if '/' in line and '.' in line: modified = True continue + if 'Sub-brick' in line: + modified = True + continue edited_lines.append(line) if modified: diff --git a/CPAC/utils/configuration.py b/CPAC/utils/configuration.py index 2fe8fb439c..9f6634df54 100644 --- a/CPAC/utils/configuration.py +++ b/CPAC/utils/configuration.py @@ -10,6 +10,10 @@ def __init__(self, config_map): for key in config_map: if config_map[key] == 'None': config_map[key] = None + if isinstance(config_map[key], dict): + for subkey in config_map[key]: + if config_map[key][subkey] == 'None': + config_map[key][subkey] = None # set FSLDIR to the environment $FSLDIR if the user sets it to # 'FSLDIR' in the pipeline config file if key == 'FSLDIR': diff --git a/CPAC/utils/datasource.py b/CPAC/utils/datasource.py index 59ba30d0b9..5f2482761b 100644 --- a/CPAC/utils/datasource.py +++ b/CPAC/utils/datasource.py @@ -1,3 +1,4 @@ +import csv import json import nipype.pipeline.engine as pe import nipype.interfaces.utility as util @@ -487,7 +488,35 @@ def check_for_s3(file_path, creds_path=None, dl_dir=None, img_type='other', # Check if it exists or it is successfully downloaded if not os.path.exists(local_path): - raise IOError('File {0} does not exist!'.format(local_path)) + # alert users to 2020-07-20 Neuroparc atlas update (v0 to v1) + ndmg_atlases = {} + with open( + os.path.join( + os.path.dirname(os.path.dirname(__file__)), + 'resources/templates/ndmg_atlases.csv' + ) + ) as ndmg_atlases_file: + ndmg_atlases['v0'], ndmg_atlases['v1'] = zip(*[( + f'/ndmg_atlases/label/Human/{atlas[0]}', + f'/ndmg_atlases/label/Human/{atlas[1]}' + ) for atlas in csv.reader(ndmg_atlases_file)]) + if local_path in ndmg_atlases['v0']: + raise FileNotFoundError( + ''.join([ + 'Neuroparc atlas paths were updated on July 20, 2020. ' + 'C-PAC configuration files using Neuroparc v0 atlas paths ' + '(including C-PAC default and preconfigured pipeline ' + 'configurations from v1.6.2a and earlier) need to be ' + 'updated to use Neuroparc atlases. Your current ' + 'configuration includes the Neuroparc v0 path ' + f'{local_path} which needs to be updated to ', + ndmg_atlases['v1'][ndmg_atlases['v0'].index(local_path)], + '. For a full list such paths, see https://fcp-indi.' + 'github.io/docs/nightly/user/ndmg_atlases' + ]) + ) + else: + raise FileNotFoundError(f'File {local_path} does not exist!') if verbose: print("Downloaded file:\n{0}\n".format(local_path)) diff --git a/CPAC/utils/ga.py b/CPAC/utils/ga.py index 300db44e81..d62b77751a 100644 --- a/CPAC/utils/ga.py +++ b/CPAC/utils/ga.py @@ -1,15 +1,19 @@ +import configparser import os import os.path as op import requests -import uuid -import configparser -import traceback +import tempfile import threading +import traceback +import uuid from CPAC.info import __version__, ga_tracker - -tracking_path = op.join(op.expanduser('~'), '.cpac') +udir = op.expanduser('~') +if udir=='/': + udir = tempfile.mkdtemp() + temp_dir = True +tracking_path = op.join(udir, '.cpac') def get_or_create_config(): @@ -17,7 +21,7 @@ def get_or_create_config(): parser = configparser.ConfigParser() parser.read_dict(dict(user=dict(uid=uuid.uuid1().hex, track=True))) - with open(tracking_path, 'w') as fhandle: + with open(tracking_path, 'w+') as fhandle: parser.write(fhandle) else: parser = configparser.ConfigParser() @@ -27,7 +31,12 @@ def get_or_create_config(): def get_uid(): - if os.environ.get('CPAC_TRACKING', '').lower() not in ['', '0', 'false', 'off']: + if os.environ.get('CPAC_TRACKING', '').lower() not in [ + '', + '0', + 'false', + 'off' + ]: return os.environ.get('CPAC_TRACKING') parser = get_or_create_config() @@ -39,11 +48,27 @@ def get_uid(): def do_it(data, timeout): try: - headers = { 'User-Agent': 'C-PAC/{} (https://fcp-indi.github.io)'.format(__version__) } - response = requests.post('https://www.google-analytics.com/collect', data=data, timeout=timeout, headers=headers) + headers = { + 'User-Agent': 'C-PAC/{} (https://fcp-indi.github.io)'.format( + __version__ + ) + } + response = requests.post( + 'https://www.google-analytics.com/collect', + data=data, + timeout=timeout, + headers=headers + ) return response except: return False + if temp_dir: + try: + os.remove(tracking_path) + os.rmdir(udir) + temp_dir = False + except: + print("Unable to delete temporary tracking path.") def track_event(category, action, uid=None, label=None, value=0, @@ -108,9 +133,11 @@ def track_event(category, action, uid=None, label=None, value=0, 'aid': "CPAC", 'an': "CPAC", 'av': __version__, - 'aip': 1, # anonymize IP by removing last octet, slightly worse geolocation + 'aip': 1, # anonymize IP by removing last octet, slightly worse + # geolocation } - + + if thread: t = threading.Thread(target=do_it, args=(data, timeout)) t.start() @@ -118,6 +145,30 @@ def track_event(category, action, uid=None, label=None, value=0, do_it(data, timeout) +def track_config(cpac_interface): + track_event( + 'config', + cpac_interface, + label=None, + value=None, + thread=False + ) + + def track_run(level='participant', participants=0): - assert level in ['participant', 'group'] - track_event('run', level, label='participants', value=participants, thread=False) + if level in ['participant', 'group']: + track_event( + 'run', + level, + label='participants', + value=participants, + thread=False + ) + else: + track_event( + 'config', + 'test', + label='participants', + value=participants, + thread=False + ) diff --git a/CPAC/utils/interfaces/datasink.py b/CPAC/utils/interfaces/datasink.py index d2f535b2d4..c2f8dd8dd0 100644 --- a/CPAC/utils/interfaces/datasink.py +++ b/CPAC/utils/interfaces/datasink.py @@ -538,7 +538,7 @@ def _list_outputs(self): s3tempoutdir = os.path.join(s3tempoutdir, d) # flattening list - if isinstance(files, list): + if files and isinstance(files, list): if isinstance(files[0], list): files = [item for sublist in files for item in sublist] diff --git a/CPAC/utils/monitoring.py b/CPAC/utils/monitoring.py index 98f713d48e..31a0e61a63 100644 --- a/CPAC/utils/monitoring.py +++ b/CPAC/utils/monitoring.py @@ -1,17 +1,37 @@ import os import glob import json +import time import logging import datetime import threading -import socketserver +import queue +import asyncio +import websockets import networkx as nx import nipype.pipeline.engine as pe +import nipype.pipeline.engine.nodes as nodes +is_monitoring = threading.Lock() +monitor_nodes_queue = queue.Queue() +monitor_wait_lock = threading.Lock() + +monitor_message = lambda data: f'{{"time": {time.time()}, "message": {data}}}' -# Log initial information from all the nodes def recurse_nodes(workflow, prefix=''): + """ + Function to traverse the workflow, yielding its nodes + + Parameters + ---------- + workflow : nipype.pipeline.engine.Workflow + the workflow + + prefix : string + custom prefix to prepend to the node name + + """ for node in nx.topological_sort(workflow._graph): if isinstance(node, pe.Workflow): for subnode in recurse_nodes(node, prefix + workflow.name + '.'): @@ -24,9 +44,21 @@ def recurse_nodes(workflow, prefix=''): def log_nodes_initial(workflow): + """ + Function to record all the existing nodes in the workflow + + Parameters + ---------- + workflow : nipype.pipeline.engine.Workflow + the workflow + + """ logger = logging.getLogger('callback') for node in recurse_nodes(workflow): - logger.debug(json.dumps(node)) + data = json.dumps(node) + logger.debug(data) + if is_monitoring.locked(): + monitor_nodes_queue.put(monitor_message(data)) def log_nodes_cb(node, status): @@ -51,107 +83,117 @@ def log_nodes_cb(node, status): if status != 'end': return - import nipype.pipeline.engine.nodes as nodes - - logger = logging.getLogger('callback') - if isinstance(node, nodes.MapNode): return - runtime = node.result.runtime + logger = logging.getLogger('callback') + + try: + runtime = node.result.runtime + except: + runtime = None status_dict = { 'id': str(node), 'hash': node.inputs.get_hashval()[1], - 'start': getattr(runtime, 'startTime'), - 'finish': getattr(runtime, 'endTime'), - 'runtime_threads': getattr(runtime, 'cpu_percent', 'N/A'), - 'runtime_memory_gb': getattr(runtime, 'mem_peak_gb', 'N/A'), + 'start': getattr(runtime, 'startTime') if runtime else None, + 'end': getattr(runtime, 'endTime') if runtime else None, + 'runtime_threads': getattr(runtime, 'cpu_percent', 'N/A') if runtime else 'N/A', + 'runtime_memory_gb': getattr(runtime, 'mem_peak_gb', 'N/A') if runtime else 'N/A', 'estimated_memory_gb': node.mem_gb, 'num_threads': node.n_procs, } - if status_dict['start'] is None or status_dict['finish'] is None: + if status_dict['start'] is None or status_dict['end'] is None: status_dict['error'] = True - logger.debug(json.dumps(status_dict)) - - -class LoggingRequestHandler(socketserver.BaseRequestHandler): + data = json.dumps(status_dict) + logger.debug(data) - def handle(self): + if is_monitoring.locked(): + monitor_nodes_queue.put(monitor_message(data)) - tree = {} - logs = glob.glob( - os.path.join( - self.server.logging_dir, - "pipeline_" + self.server.pipeline_name, - "*" - ) - ) - - for log in logs: - subject = log.split('/')[-1] - tree[subject] = {} - - callback_file = os.path.join(log, "callback.log") - - if not os.path.exists(callback_file): - continue - - with open(callback_file, 'rb') as lf: - for l in lf.readlines(): - l = l.strip() - try: - node = json.loads(l) - if node["id"] not in tree[subject]: - tree[subject][node["id"]] = { - "hash": node["hash"] - } - if "start" in node and "finish" in node: - tree[subject][node["id"]]["start"] = node["start"] - tree[subject][node["id"]]["finish"] = node["finish"] - - else: - if "start" in node and "finish" in node: - if tree[subject][node["id"]]["hash"] == node["hash"]: - tree[subject][node["id"]]["cached"] = { - "start": node["start"], - "finish": node["finish"], - } +async def send_logs(websocket, path): + """ + Function executed when a new websocket connection is opened. - # pipeline was changed, and we have a new hash - else: - tree[subject][node["id"]]["start"] = node["start"] - tree[subject][node["id"]]["finish"] = node["finish"] + It will iterate through the nodes reporting queue, and send it to + the websocked. + + Parameters + ---------- + websocket : Websocket + The websocket object - except: - break + path : string + Path for the websocket (the accepted is ws://0.0.0.0:8080/log) + + """ + if path != '/log': + return - tree = {s: t for s, t in tree.items() if t} + monitor_wait_lock.release() + while True: + item = monitor_nodes_queue.get() + await websocket.send(item) + monitor_nodes_queue.task_done() - headers = 'HTTP/1.1 200 OK\nConnection: close\n\n' - self.request.sendall(headers + json.dumps(tree) + "\n") +def ws(loop, host='0.0.0.0', port=8080): + """ + Function to start the monitoring server. + + Parameters + ---------- + loop : asyncio.Loop + The loop used by the server + host : string + Address or IP that the websocket server binds to. -class LoggingHTTPServer(socketserver.ThreadingTCPServer, object): + port : integer + Port for the websocket server. + + """ + asyncio.set_event_loop(loop) + start_server = websockets.serve(send_logs, "0.0.0.0", port) + loop.run_until_complete(start_server) + loop.run_forever() - def __init__(self, pipeline_name, logging_dir='', host='', port=8080, request=LoggingRequestHandler): - super(LoggingHTTPServer, self).__init__((host, port), request) - if not logging_dir: - logging_dir = os.getcwd() +def monitor_server(host='0.0.0.0', port=8080, wait=False): + """ + Function to start the monitoring server thread. + It sets the `monitor_wait_lock` so the pipeline waits for the websocket + to connect. This way, no reported data is lost. + + Parameters + ---------- + host : string + Address or IP that the websocket server binds to. - self.logging_dir = logging_dir - self.pipeline_name = pipeline_name + port : integer + Port for the websocket server. + wait : boolean + Only sets the lock if required. + + Returns + ------- + server_thread : threading.Thread + The thread the server is running -def monitor_server(pipeline_name, logging_dir, host='0.0.0.0', port=8080): - httpd = LoggingHTTPServer(pipeline_name, logging_dir, host, port, LoggingRequestHandler) + loop: asyncio.Loop + The loop used by the server + + """ + if wait: + monitor_wait_lock.acquire() + + is_monitoring.acquire() - server_thread = threading.Thread(target=httpd.serve_forever) + loop = asyncio.new_event_loop() + server_thread = threading.Thread(target=ws, args=[loop, host, port]) server_thread.isDaemon = True server_thread.start() - - return server_thread + return server_thread, loop diff --git a/CPAC/utils/ndmg_utils.py b/CPAC/utils/ndmg_utils.py index 1effb2d57c..234699c687 100644 --- a/CPAC/utils/ndmg_utils.py +++ b/CPAC/utils/ndmg_utils.py @@ -235,4 +235,4 @@ def ndmg_create_graphs(ts, labels): connectome = graph(ts.shape[0], labels, sens="func") conn = connectome.cor_graph(ts) connectome.save_graph(out_file) - return out_file \ No newline at end of file + return out_file diff --git a/CPAC/utils/symlinks.py b/CPAC/utils/symlinks.py index ace8a30316..2e382e35b5 100644 --- a/CPAC/utils/symlinks.py +++ b/CPAC/utils/symlinks.py @@ -6,7 +6,7 @@ 'anatomical_brain': 'anat', 'anatomical_brain_mask': 'anat', 'qc': 'qc', - 'anatomical_reorient': 'anat', + 'anatomical_skull_leaf': 'anat', 'anatomical_to_mni_linear_xfm': 'anat', 'mni_to_anatomical_linear_xfm': 'anat', 'mni_to_anatomical_nonlinear_xfm': 'anat', diff --git a/CPAC/utils/utils.py b/CPAC/utils/utils.py index 271fb46cfd..b451a385fa 100644 --- a/CPAC/utils/utils.py +++ b/CPAC/utils/utils.py @@ -1196,7 +1196,7 @@ def check_config_resources(c): if total_user_cores > num_cores: err_msg = 'Config file specifies more subjects running in ' \ 'parallel than number of threads available. Change ' \ - 'this and try again' + f'this and try again ({total_user_cores} cores asked, {num_cores} cores available)' raise Exception(err_msg) else: num_cores_per_sub = c.maxCoresPerParticipant diff --git a/Dockerfile b/Dockerfile index f64dce65e1..46b7333362 100644 --- a/Dockerfile +++ b/Dockerfile @@ -78,16 +78,13 @@ RUN curl -sLo /tmp/libpng12.deb http://mirrors.kernel.org/ubuntu/pool/main/libp/ dpkg -i /tmp/libpng12.deb && \ rm /tmp/libpng12.deb -# Compiles libxp- this is necessary for some newer versions of Ubuntu -# where the is no Debian package available. -RUN git clone git://anongit.freedesktop.org/xorg/lib/libXp /tmp/libXp && \ - cd /tmp/libXp && \ - ./autogen.sh && \ - ./configure && \ - make && \ - make install && \ - cd - && \ - rm -rf /tmp/libXp +# Install libxp from third-party repository +RUN apt-get update && \ + apt-get install -y software-properties-common && \ + add-apt-repository --yes ppa:zeehio/libxp && \ + apt-get update && apt-get install libxp6 libxp-dev && \ + add-apt-repository --remove --yes ppa:zeehio/libxp && \ + apt-get update # Installing and setting up c3d RUN mkdir -p /opt/c3d && \ diff --git a/README.md b/README.md index 9e95aa4720..afc3be6937 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,16 @@ The C-PAC website is located here: http://fcp-indi.github.com/ How to Run ---------- -Instructions can be found within our quick-start guide: http://fcp-indi.github.io/docs/user/quick.html +Instructions can be found within our quick-start guide: https://fcp-indi.github.io/docs/latest/user/quick Documentation ------------- -User documentation can be found here: http://fcp-indi.github.com/docs/user/index.html +User documentation can be found here: https://fcp-indi.github.io/docs/latest/user -Developer documentation can be found here: http://fcp-indi.github.com/docs/developer/index.html +Developer documentation can be found here: https://fcp-indi.github.io/docs/latest/developer and here: https://github.com/FCP-INDI/.github/blob/primary/CONTRIBUTING.md -Documentation pertaining to this latest release can be found here: https://github.com/FCP-INDI/C-PAC/releases/tag/v1.6.0 +Documentation pertaining to this latest release can be found here: https://fcp-indi.github.io/docs/latest/user/release_notes/latest Discussion Forum diff --git a/dev/docker_data/cpac_templates.tar.gz b/dev/docker_data/cpac_templates.tar.gz index 0f7de59be5..7ff72a2caa 100644 Binary files a/dev/docker_data/cpac_templates.tar.gz and b/dev/docker_data/cpac_templates.tar.gz differ diff --git a/dev/docker_data/default_pipeline.yml b/dev/docker_data/default_pipeline.yml index 190ca1e73d..930d98851f 100644 --- a/dev/docker_data/default_pipeline.yml +++ b/dev/docker_data/default_pipeline.yml @@ -109,15 +109,10 @@ reGenerateOutputs : False # Anatomical preprocessing options. # --------------------------------- - # Run anatomical preproceessing runAnatomical: [1] -# Run FreeSurfer -runFreeSurfer: [0] - - # Non-local means filtering via ANTs DenoiseImage non_local_means_filtering: False @@ -126,13 +121,27 @@ non_local_means_filtering: False n4_bias_field_correction: False +# ACPC Alignment +acpc_align: False + + +# ACPC size of brain in z-dimension in mm. +# Default: 150mm for human data. +acpc_brainsize: 150 + + +# ACPC aligned template +acpc_template_skull: /usr/share/fsl/5.0/data/standard/MNI152_T1_1mm.nii.gz +acpc_template_brain: None + + # Disables skull-stripping on the anatomical inputs if they are already skull-stripped outside of C-PAC. # Set this to 1 if your input images are already skull-stripped. already_skullstripped : [0] # Choice of using AFNI or FSL-BET or niworkflows-ants or UNet for monkey data to perform SkullStripping -# Options: ['AFNI', 'BET', 'niworkflows-ants', 'unet'] +# Options: ['AFNI', 'FSL', 'niworkflows-ants', 'unet'] skullstrip_option : ['AFNI'] @@ -662,13 +671,45 @@ motion_correction_reference_volume : 0 functional_volreg_twopass : On -notch_filter_motion_estimates: False +# Filter physiological (respiration) artifacts from the head motion estimates. +# Adapted from DCAN Labs filter. +# https://www.ohsu.edu/school-of-medicine/developmental-cognition-and-neuroimaging-lab +# https://www.biorxiv.org/content/10.1101/337360v1.full.pdf +motion_estimate_filter: + + run: False + + # options: "notch", "lowpass" + filter_type: "notch" + + # Number of filter coefficients. + filter_order: 4 + + # Dataset-wide respiratory rate data from breathing belt required. + # mutually exclusive with cutoff, center frequency, and bandwidth options further below + + # Lowest Breaths-Per-Minute in dataset. + # Required for both notch and lowpass filters. + breathing_rate_min: + + # Highest Breaths-Per-Minute in dataset. + # Required for the notch filter. + breathing_rate_max: + + # notch filter direct customization parameters + # mutually exclusive with breathing_rate options above + + # the center frequency of the notch filter + center_frequency: -notch_filter_breathing_rate_min: None + # the width of the notch filter + filter_bandwidth: -notch_filter_breathing_rate_max: None + # lowpass filter direct customization parameters + # mutually exclusive with breathing_rate options above -notch_filter_order: 4 + # the frequency cutoff of the filter + lowpass_cutoff: # Run AFNI 3dDespike diff --git a/dev/docker_data/run.py b/dev/docker_data/run.py index 9c2828bb24..278d521963 100755 --- a/dev/docker_data/run.py +++ b/dev/docker_data/run.py @@ -578,17 +578,20 @@ def resolve_aws_credential(source): if args.analysis_level in ["participant", "test_config"]: # build pipeline easy way - from CPAC.utils.monitoring import monitor_server + from CPAC.utils.monitoring import monitor_wait_lock, monitor_server import CPAC.pipeline.cpac_runner monitoring = None if args.monitoring: try: - monitoring = monitor_server( - c['pipelineName'], - c['logDirectory'] + monitoring, _ = monitor_server( + wait=True, ) - except: + + print("[Waiting for monitoring websocket to connect]") + while monitor_wait_lock.locked(): + time.sleep(1) + except Exception as e: pass plugin_args = { diff --git a/requirements.txt b/requirements.txt index 3a32e5d398..6a09c6efed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,4 +25,5 @@ simplejson==3.15.0 scikit-learn==0.22.1 traits==4.6.0 PyBASC==0.4.5 -pathlib==1.0.1 \ No newline at end of file +pathlib==1.0.1 +websockets==8.1 \ No newline at end of file diff --git a/version b/version index d4f6e2c5d4..ca64acb348 100644 --- a/version +++ b/version @@ -1 +1 @@ -v1.6.2 +v1.7.1.g2a1530a5-dev