diff --git a/.github/actions/nf-test/action.yml b/.github/actions/nf-test/action.yml index c5a256ef..5db43459 100644 --- a/.github/actions/nf-test/action.yml +++ b/.github/actions/nf-test/action.yml @@ -71,8 +71,10 @@ runs: PROFILE=${{ inputs.profile }} fi + # Increase the java memory + export _JAVA_OPTIONS="-Xmx4g" nf-test test \ - --profile=${PROFILE},test \ + --profile=+${PROFILE} \ $(if [ -n "${{ inputs.tags }}" ]; then echo "--tag ${{ inputs.tags }}"; fi) \ --ci \ --changed-since HEAD^ \ diff --git a/.nf-core.yml b/.nf-core.yml index 3011d963..213c021c 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,5 +1,4 @@ lint: - container_configs: false files_exist: - CODE_OF_CONDUCT.md - assets/nf-core-sf_pediatric_logo_light.png @@ -37,6 +36,9 @@ lint: nextflow_config: false schema_params: false template_strings: false + container_configs: false + nf_test_content: + - tests/gpu.nf.test nf_core_version: 4.0.3 repository_type: pipeline template: diff --git a/CHANGELOG.md b/CHANGELOG.md index ad3e4431..b895b899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### `Added` +- Changed segmentation in young participants to use BIBSnet and infant freesurfer ([#128](https://github.com/scilus/sf-pediatric/pull/128)) - Now use `nf-bids` plugin to read BIDS dataset for faster and more efficient data collection ([#124](https://github.com/scilus/sf-pediatric/pull/124)) - Add GPU support via a config profile for module `eddy` and `localtracking` ([#121](https://github.com/scilus/sf-pediatric/pull/121)) - Add support for user-defined external atlases in ‘fsLR‘ 32k space and simply mapping from template to subject space ([#120](https://github.com/scilus/sf-pediatric/pull/120)) @@ -28,6 +29,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Move to parameter typing and bump minimal nextflow version to `26.04.0` ([#122](https://github.com/scilus/sf-pediatric/pull/122)) - Update to nf-core template `4.0.2` ([#117](https://github.com/scilus/sf-pediatric/pull/117)) +### `Removed` + +- Remove M-CRIB-S as a segmentation method for younger participants in favor of BIBSnet and infantfs ([#128](https://github.com/scilus/sf-pediatric/pull/128)) + ## [0.2.2] - [2026-02-09] ### `Added` diff --git a/assets/Docker/infantfs/fsl.yml b/assets/Docker/infantfs/fsl.yml new file mode 100644 index 00000000..83af2711 --- /dev/null +++ b/assets/Docker/infantfs/fsl.yml @@ -0,0 +1,6 @@ +name: fsl +channels: + - https://fsl.fmrib.ox.ac.uk/fsldownloads/fslconda/public/ + - conda-forge +dependencies: + - fsl-avwutils diff --git a/assets/Docker/infantfs/sf-pediatric-infantfs.Dockerfile b/assets/Docker/infantfs/sf-pediatric-infantfs.Dockerfile new file mode 100644 index 00000000..08694f0b --- /dev/null +++ b/assets/Docker/infantfs/sf-pediatric-infantfs.Dockerfile @@ -0,0 +1,121 @@ +FROM rockylinux:8.9 AS build-fsl + +RUN dnf -y update && \ + dnf -y install --nogpgcheck \ + curl \ + bzip2 \ + && dnf clean all + +WORKDIR / +RUN echo "2024.04.25" && curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj +ENV MAMBA_ROOT_PREFIX="/opt/conda" + +# Fetch the FSL environment +COPY fsl.yml fsl.yml +RUN micromamba create -y -f fsl.yml && \ + micromamba clean --all --yes +ENV PATH="/opt/conda/envs/fsl/bin:$PATH" + +FROM freesurfer/freesurfer:8.2.0 AS build_freesurfer +# Bring over freesurfer from official release +RUN rm -rf \ + /usr/local/freesurfer/8.2.0-1/average/AAN \ + /usr/local/freesurfer/8.2.0-1/average/3T18yoSchwartz* \ + /usr/local/freesurfer/8.2.0-1/average/711-2B* \ + /usr/local/freesurfer/8.2.0-1/average/SVIP* \ + /usr/local/freesurfer/8.2.0-1/average/RLB700* \ + /usr/local/freesurfer/8.2.0-1/average/RB_all* \ + /usr/local/freesurfer/8.2.0-1/average/aseg+spmhead* \ + /usr/local/freesurfer/8.2.0-1/average/face.gca \ + /usr/local/freesurfer/8.2.0-1/average/label_scales.dat \ + /usr/local/freesurfer/8.2.0-1/average/mideface-atlas \ + /usr/local/freesurfer/8.2.0-1/average/wmsa_new_eesmith.gca \ + /usr/local/freesurfer/8.2.0-1/average/talairach_mixed_with_skull.gca \ + /usr/local/freesurfer/8.2.0-1/average/BrainstemSS \ + /usr/local/freesurfer/8.2.0-1/average/HippoSF \ + /usr/local/freesurfer/8.2.0-1/average/PALS_B12.readme \ + /usr/local/freesurfer/8.2.0-1/average/ThalamicNuclei \ + /usr/local/freesurfer/8.2.0-1/average/samseg \ + /usr/local/freesurfer/8.2.0-1/average/mni_icbm152_nlin_asym_09c \ + /usr/local/freesurfer/8.2.0-1/average/mult-comp-cor \ + /usr/local/freesurfer/8.2.0-1/average/Choi_JNeurophysiol12_MNI152 \ + /usr/local/freesurfer/8.2.0-1/average/Buckner_JNeurophysiol11_MNI152 \ + /usr/local/freesurfer/8.2.0-1/average/Yeo_Brainmap_MNI152 \ + /usr/local/freesurfer/8.2.0-1/average/Yeo_JNeurophysiol11_MNI152 \ + /usr/local/freesurfer/8.2.0-1/bin/freeview \ + /usr/local/freesurfer/8.2.0-1/diffusion \ + /usr/local/freesurfer/8.2.0-1/matlab \ + /usr/local/freesurfer/8.2.0-1/models/easyreg* \ + /usr/local/freesurfer/8.2.0-1/sessions \ + /usr/local/freesurfer/8.2.0-1/fsfast \ + /usr/local/freesurfer/8.2.0-1/fsafd \ + /usr/local/freesurfer/8.2.0-1/subjects/bert \ + /usr/local/freesurfer/8.2.0-1/subjects/V1_average \ + /usr/local/freesurfer/8.2.0-1/subjects/cvs_avg35 \ + /usr/local/freesurfer/8.2.0-1/subjects/cvs_avg35_inMNI152 \ + /usr/local/freesurfer/8.2.0-1/subjects/fsaverage3 \ + /usr/local/freesurfer/8.2.0-1/subjects/fsaverage4 \ + /usr/local/freesurfer/8.2.0-1/subjects/fsaverage5 \ + /usr/local/freesurfer/8.2.0-1/subjects/fsaverage6 \ + /usr/local/freesurfer/8.2.0-1/subjects/fsaverage_sym \ + /usr/local/freesurfer/8.2.0-1/subjects/lh.EC_average \ + /usr/local/freesurfer/8.2.0-1/subjects/rh.EC_average \ + /usr/local/freesurfer/8.2.0-1/subjects/sample* \ + /usr/local/freesurfer/8.2.0-1/trctrain \ + /usr/local/freesurfer/8.2.0-1/MCRv97 \ + /usr/local/freesurfer/8.2.0-1/models \ + /usr/local/freesurfer/8.2.0-1/python/lib/python3.8/site-packages/tensorflow* + +# Set the runtime stage +FROM rockylinux:8.9 AS runtime + +# Install freesurfer dependencies +RUN dnf -y update && \ + dnf -y install --nogpgcheck \ + libgomp \ + procps-ng \ + libXt \ + libXext \ + libtiff \ + libpng15 \ + tcsh \ + bc \ + ncurses-compat-libs \ + unzip && \ + dnf clean all + +COPY --from=build-fsl /bin/micromamba /bin/micromamba +COPY --from=build-fsl /opt/conda/envs/fsl /opt/conda/envs/fsl +COPY --from=build_freesurfer /usr/local/freesurfer/8.2.0* /opt/freesurfer + +# Set environment variables for FreeSurfer and FSL +ENV MAMBA_ROOT_PREFIX="/opt/conda" \ + PATH="${PATH}:/opt/conda/envs/fsl/bin" \ + CPATH="/opt/conda/envs/fsl/include:${CPATH}" \ + LD_LIBRARY_PATH="/opt/conda/envs/fsl/lib:${LD_LIBRARY_PATH}" \ + CONDA_PYTHON="/opt/conda/envs/fsl/bin/python" +ENV LANG="C.UTF-8" \ + LC_ALL="C.UTF-8" \ + PYTHONNOUSERSITE=1 \ + FSLDIR="/opt/conda/envs/fsl" \ + FSLOUTPUTTYPE="NIFTI_GZ" \ + FSLMULTIFILEQUIT="TRUE" \ + FSLLOCKDIR="" \ + FSLMACHINELIST="" \ + FSLREMOTECALL="" \ + FSLGECUDAQ="cuda.q" +ENV FREESURFER_HOME="/opt/freesurfer" +ENV SUBJECTS_DIR="$FREESURFER_HOME/subjects" \ + FUNCTIONALS_DIR="$FREESURFER_HOME/sessions" \ + MNI_DIR="$FREESURFER_HOME/mni" \ + LOCAL_DIR="$FREESURFER_HOME/local" \ + MINC_BIN_DIR="$FREESURFER_HOME/mni/bin" \ + MINC_LIB_DIR="$FREESURFER_HOME/mni/lib" \ + MNI_DATAPATH="$FREESURFER_HOME/mni/data" \ + FSL_DIR=${FSLDIR} \ + FREESURFER="/opt/freesurfer" +ENV PERL5LIB="$MINC_LIB_DIR/perl5/5.8.5" \ + MNI_PERL5LIB="$MINC_LIB_DIR/perl5/5.8.5" \ + PATH="$FREESURFER_HOME/bin:$FREESURFER_HOME/tktools:$MINC_BIN_DIR:$PATH" +ENV USER=freesurfer \ + LOGNAME=freesurfer diff --git a/assets/Docker/mcribs/fsl.yml b/assets/Docker/mcribs/fsl.yml deleted file mode 100644 index 5da2acd8..00000000 --- a/assets/Docker/mcribs/fsl.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: fsl -channels: - - https://fsl.fmrib.ox.ac.uk/fsldownloads/fslconda/public/ - - conda-forge -dependencies: - - python =3.11 - - mkl=2023.2.0 - - mkl-service=2.4.0 - # Base scientific python stack; required by FSL, so pinned here - - numpy=1.26 - - scipy=1.11 - - matplotlib=3.8 - - pandas=2.2 - - h5py=3.10 - - nitime=0.10 - - scikit-image=0.22 - - scikit-learn=1.4 - # FSL dependencies. - - fsl-bet2=2111.4 - - fsl-flirt=2111.2 - - fsl-fast4=2111.3 - - fsl-fugue=2201.4 - - fsl-mcflirt=2111.0 - - fsl-miscmaths=2203.2 - - fsl-topup=2203.2 diff --git a/assets/Docker/mcribs/nf-pediatric_mcribs.Dockerfile b/assets/Docker/mcribs/nf-pediatric_mcribs.Dockerfile deleted file mode 100644 index efc72bb1..00000000 --- a/assets/Docker/mcribs/nf-pediatric_mcribs.Dockerfile +++ /dev/null @@ -1,122 +0,0 @@ -ARG BASE_IMAGE=ubuntu:jammy-20240405 - -# Older Python to support legacy MCRIBS (taken from nibabies Dockerfile) -FROM python:3.6.15-slim AS pyenv -RUN pip install --no-cache-dir numpy nibabel scipy pandas numexpr contextlib2 imageio \ - && cp /usr/lib/x86_64-linux-gnu/libffi.so.7* /usr/local/lib - -# Download FSL. -FROM ${BASE_IMAGE} AS build-fsl - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - build-essential \ - binutils \ - bzip2 \ - ca-certificates \ - curl \ - unzip \ - && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -WORKDIR / -RUN echo "2024.04.25" && curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj -ENV MAMBA_ROOT_PREFIX="/opt/conda" - -# Fetch the FSL environment -COPY fsl.yml fsl.yml -RUN micromamba create -y -f fsl.yml && \ - micromamba clean --all --yes -ENV PATH="/opt/conda/envs/fsl/bin:$PATH" - -# Set a stage for the build -FROM ${BASE_IMAGE} AS runtime - -ENV DEBIAN_FRONTEND="noninteractive" \ - LANG="en_US.UTF-8" \ - LC_ALL="en_US.UTF-8" - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - apt-utils \ - bc \ - build-essential \ - ca-certificates \ - curl \ - git \ - gnupg \ - libtool \ - locales \ - lsb-release \ - tcsh \ - netbase \ - unzip \ - xvfb \ - # MCRIBS-required - libboost-dev \ - libeigen3-dev \ - libflann-dev \ - libgl1-mesa-dev \ - libglu1-mesa-dev \ - libssl-dev \ - libxt-dev \ - zlib1g-dev && \ - locale-gen en_US.UTF-8 && \ - apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Configure PPAs for libpng12 and libxp6 -RUN GNUPGHOME=/tmp gpg --keyserver hkps://keyserver.ubuntu.com --no-default-keyring --keyring /usr/share/keyrings/linuxuprising.gpg --recv 0xEA8CACC073C3DB2A \ - && GNUPGHOME=/tmp gpg --keyserver hkps://keyserver.ubuntu.com --no-default-keyring --keyring /usr/share/keyrings/zeehio.gpg --recv 0xA1301338A3A48C4A \ - && echo "deb [signed-by=/usr/share/keyrings/linuxuprising.gpg] https://ppa.launchpadcontent.net/linuxuprising/libpng12/ubuntu jammy main" > /etc/apt/sources.list.d/linuxuprising.list \ - && echo "deb [signed-by=/usr/share/keyrings/zeehio.gpg] https://ppa.launchpadcontent.net/zeehio/libxp/ubuntu jammy main" > /etc/apt/sources.list.d/zeehio.list - -# Install ANTs -COPY --from=gagnonanthony/ants@sha256:0abd03bc59c10ea4a93e399d07c5e4019cf8eb89963a54e050f209ed73722dce /opt/ants /opt/ants -ENV PATH="/opt/ants/bin:$PATH" \ - ANTSPATH="/opt/ants" \ - LD_LIBRARY_PATH="/opt/ants/lib:$LD_LIBRARY_PATH" - -# Install FreeSurfer (with Infant Module) -COPY --from=nipreps/freesurfer@sha256:3b895fc732a7080374a15c4f976510f39c0c48dc76c030ab27316febd5e419ee /opt/freesurfer /opt/freesurfer -ENV FREESURFER_HOME="/opt/freesurfer" -ENV SUBJECTS_DIR="$FREESURFER_HOME/subjects" \ - FUNCTIONALS_DIR="$FREESURFER_HOME/sessions" \ - MNI_DIR="$FREESURFER_HOME/mni" \ - LOCAL_DIR="$FREESURFER_HOME/local" \ - MINC_BIN_DIR="$FREESURFER_HOME/mni/bin" \ - MINC_LIB_DIR="$FREESURFER_HOME/mni/lib" \ - MNI_DATAPATH="$FREESURFER_HOME/mni/data" \ - FSL_DIR=${FSLDIR} \ - FREESURFER="/opt/freesurfer" -ENV PERL5LIB="$MINC_LIB_DIR/perl5/5.8.5" \ - MNI_PERL5LIB="$MINC_LIB_DIR/perl5/5.8.5" \ - PATH="$FREESURFER_HOME/bin:$FREESURFER_HOME/tktools:$MINC_BIN_DIR:$PATH" - -# Install MCRIBS from nipreps (with legacy Python) -COPY --from=gagnonanthony/mcribs@sha256:12ddd74a0d47f055bea4bb19cc0df5b39d0b3ee2aa76d4276d4bfea6f169b2b5 /opt/MCRIBS/ /opt/MCRIBS -COPY --from=pyenv /usr/local/lib/ /usr/local/lib/ -COPY --from=pyenv /usr/local/bin/python3 /opt/MCRIBS/bin/python -ENV PATH="/opt/MCRIBS/bin:/opt/MCRIBS/MIRTK/MIRTK-install/bin:/opt/MCRIBS/MIRTK/MIRTK-install/lib/tools:${PATH}" \ - LD_LIBRARY_PATH="/opt/MCRIBS/lib:/opt/MCRIBS/ITK/ITK-install/lib:/opt/MCRIBS/VTK/VTK-install/lib:/opt/MCRIBS/MIRTK/MIRTK-install/lib:/usr/local/lib:${LD_LIBRARY_PATH}" \ - MCRIBS_HOME="/opt/MCRIBS" \ - PYTHONPATH="/opt/MCRIBS/lib/python:/usr/local/lib/python3.6/site-packages/:${PYTHONPATH}" - -# Install FSL -COPY --from=build-fsl /bin/micromamba /bin/micromamba -COPY --from=build-fsl /opt/conda/envs/fsl /opt/conda/envs/fsl -ENV MAMBA_ROOT_PREFIX="/opt/conda" \ - PATH="${PATH}:/opt/conda/envs/fsl/bin" \ - CPATH="/opt/conda/envs/fsl/include:${CPATH}" \ - LD_LIBRARY_PATH="/opt/conda/envs/fsl/lib:${LD_LIBRARY_PATH}" \ - CONDA_PYTHON="/opt/conda/envs/fsl/bin/python" - -# FSL environment. -ENV LANG="C.UTF-8" \ - LC_ALL="C.UTF-8" \ - PYTHONNOUSERSITE=1 \ - FSLDIR="/opt/conda/envs/fsl" \ - FSLOUTPUTTYPE="NIFTI_GZ" \ - FSLMULTIFILEQUIT="TRUE" \ - FSLLOCKDIR="" \ - FSLMACHINELIST="" \ - FSLREMOTECALL="" \ - FSLGECUDAQ="cuda.q" diff --git a/assets/sf-pediatric-schema.png b/assets/sf-pediatric-schema.png index 91e86474..18707ef8 100644 Binary files a/assets/sf-pediatric-schema.png and b/assets/sf-pediatric-schema.png differ diff --git a/assets/sf-pediatric-schema.svg b/assets/sf-pediatric-schema.svg index 9b91a8f4..d7f19111 100644 --- a/assets/sf-pediatric-schema.svg +++ b/assets/sf-pediatric-schema.svg @@ -8,9 +8,35 @@ version="1.1" id="svg1" xml:space="preserve" + sodipodi:docname="sf-pediatric-schema.svg" + inkscape:export-filename="sf-pediatric-schema.png" + inkscape:export-xdpi="300" + inkscape:export-ydpi="300" + inkscape:version="1.3.2 (091e20e, 2023-11-25)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg">M-CRIB-S (< 3 mo.) BIBSnet (< 1 year)Recon-all(Infant) Recon-allFastSurferRecon-all-clinical (default)FastSurferRecon-all-clinical (default)DK atlas (< 3 mo.)Brainnetome Child (default)Brainnetome Child (> 3 mo.)External atlases (user-supplied) - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - def mov = filename.contains("T1w") ? "T1w" : filename.contains("T2w") ? "T2w" : null - def fix = mov == "T1w" ? "T2w" : mov == "T2w" ? "T1w" : null - if (filename.contains("warped.nii.gz")) { "${meta.id}_${ses}${run}space-${fix}_desc-preproc_${mov}.nii.gz" } - else if (filename.contains("versions.yml")) { null } - else { params.lean_output ? null : filename } - } - ], - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/xfm/" : "${params.outdir}/${meta.id}/xfm/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - def mov = filename.contains("T1w") ? "T1w" : filename.contains("T2w") ? "T2w" : null - def fix = mov == "T1w" ? "T2w" : mov == "T2w" ? "T1w" : null - if (filename.contains("forward1_affine.mat")) { "${meta.id}_${ses}${run}from-${mov}_to-${fix}_mode-image_desc-affine_xfm.mat" } - else if (filename.contains("backward0_affine.mat")) { "${meta.id}_${ses}${run}from-${fix}_to-${mov}_mode-image_desc-affine_xfm.mat" } - else if (filename.contains("versions.yml")) { null } - else { params.lean_output ? null : filename } - }, - ] + path: { "${params.outdir}" }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + def ses = meta.session ? "${meta.session}_" : "" + def run = meta.run ? "run-${meta.run}_" : "" + def anat_dir = meta.session ? "${meta.id}/${meta.session}/anat" : "${params.outdir}/${meta.id}/anat" + def xfm_dir = meta.session ? "${meta.id}/${meta.session}/xfm" : "${params.outdir}/${meta.id}/xfm" + def mov = filename.contains("T1w") ? "T1w" : filename.contains("T2w") ? "T2w" : null + def fix = mov == "T1w" ? "T2w" : mov == "T2w" ? "T1w" : null + + // Files going to the anat directory + if (filename.contains("warped.nii.gz")) { "${anat_dir}/${meta.id}_${ses}${run}space-${fix}_desc-preproc_${mov}.nii.gz" } + // Files going to the xfm directory + else if (filename.contains("forward1_affine.mat")) { "${xfm_dir}/${meta.id}_${ses}${run}from-${mov}_to-${fix}_mode-image_desc-affine_xfm.mat" } + else if (filename.contains("backward0_affine.mat")) { "${xfm_dir}/${meta.id}_${ses}${run}from-${fix}_to-${mov}_mode-image_desc-affine_xfm.mat" } + else if (filename.contains("versions.yml")) { null } + else { params.lean_output ? null : filename } + } ] } + withName: '.*:ANATTODWI' { ext.run_qc = true ext.suffix_qc = "registration_anattodwi" publishDir = [ - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/anat/" : "${params.outdir}/${meta.id}/anat/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - if ( filename.contains("T1w_warped") ) { "${meta.id}_${ses}${run}space-DWI_desc-preproc_T1w.nii.gz" } - else if ( filename.contains("T2w_warped") ) { "${meta.id}_${ses}${run}space-DWI_desc-preproc_T2w.nii.gz" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ], - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/xfm/" : "${params.outdir}/${meta.id}/xfm/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - if (filename.contains("T1w_forward1_affine.mat")) { "${meta.id}_${ses}${run}from-T1w_to-dwi_mode-image_desc-affine_xfm.mat" } - else if (filename.contains("T1w_forward0_warp.nii.gz")) { "${meta.id}_${ses}${run}from-T1w_to-dwi_mode-image_desc-warp_xfm.nii.gz" } - else if (filename.contains("T1w_backward0_affine.mat")) { "${meta.id}_${ses}${run}from-dwi_to-T1w_mode-image_desc-affine_xfm.mat" } - else if (filename.contains("T1w_backward1_warp.nii.gz")) { "${meta.id}_${ses}${run}from-dwi_to-T1w_mode-image_desc-warp_xfm.nii.gz" } - else if (filename.contains("T2w_forward1_affine.mat")) { "${meta.id}_${ses}${run}from-T2w_to-dwi_mode-image_desc-affine_xfm.mat" } - else if (filename.contains("T2w_forward0_warp.nii.gz")) { "${meta.id}_${ses}${run}from-T2w_to-dwi_mode-image_desc-warp_xfm.nii.gz" } - else if (filename.contains("T2w_backward0_affine.mat")) { "${meta.id}_${ses}${run}from-dwi_to-T2w_mode-image_desc-affine_xfm.mat" } - else if (filename.contains("T2w_backward1_warp.nii.gz")) { "${meta.id}_${ses}${run}from-dwi_to-T2w_mode-image_desc-warp_xfm.nii.gz" } - else if (filename.contains("versions.yml")) { null } - else { params.lean_output ? null : filename } - } - ] + path: { "${params.outdir}" }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + def ses = meta.session ? "${meta.session}_" : "" + def run = meta.run ? "run-${meta.run}_" : "" + def anat_dir = meta.session ? "${meta.id}/${meta.session}/anat" : "${params.outdir}/${meta.id}/anat" + def xfm_dir = meta.session ? "${meta.id}/${meta.session}/xfm" : "${params.outdir}/${meta.id}/xfm" + + // Files going to the anat directory + if ( filename.contains("T1w_warped") ) { "${anat_dir}/${meta.id}_${ses}${run}space-DWI_desc-preproc_T1w.nii.gz" } + else if ( filename.contains("T2w_warped") ) { "${anat_dir}/${meta.id}_${ses}${run}space-DWI_desc-preproc_T2w.nii.gz" } + // Files going to the xfm directory + else if (filename.contains("T1w_forward1_affine.mat")) { "${xfm_dir}/${meta.id}_${ses}${run}from-T1w_to-dwi_mode-image_desc-affine_xfm.mat" } + else if (filename.contains("T1w_forward0_warp.nii.gz")) { "${xfm_dir}/${meta.id}_${ses}${run}from-T1w_to-dwi_mode-image_desc-warp_xfm.nii.gz" } + else if (filename.contains("T1w_backward0_affine.mat")) { "${xfm_dir}/${meta.id}_${ses}${run}from-dwi_to-T1w_mode-image_desc-affine_xfm.mat" } + else if (filename.contains("T1w_backward1_warp.nii.gz")) { "${xfm_dir}/${meta.id}_${ses}${run}from-dwi_to-T1w_mode-image_desc-warp_xfm.nii.gz" } + else if (filename.contains("T2w_forward1_affine.mat")) { "${xfm_dir}/${meta.id}_${ses}${run}from-T2w_to-dwi_mode-image_desc-affine_xfm.mat" } + else if (filename.contains("T2w_forward0_warp.nii.gz")) { "${xfm_dir}/${meta.id}_${ses}${run}from-T2w_to-dwi_mode-image_desc-warp_xfm.nii.gz" } + else if (filename.contains("T2w_backward0_affine.mat")) { "${xfm_dir}/${meta.id}_${ses}${run}from-dwi_to-T2w_mode-image_desc-affine_xfm.mat" } + else if (filename.contains("T2w_backward1_warp.nii.gz")) { "${xfm_dir}/${meta.id}_${ses}${run}from-dwi_to-T2w_mode-image_desc-warp_xfm.nii.gz" } + else if (filename.contains("versions.yml")) { null } + else { params.lean_output ? null : filename } + } ] } withName: '.*:TEMPLATETODWI' { diff --git a/conf/segmentation.config b/conf/segmentation.config index 80270ab4..c1b0524c 100644 --- a/conf/segmentation.config +++ b/conf/segmentation.config @@ -18,49 +18,29 @@ process { ext.cerebnet = params.cerebnet ext.hypvinn = params.hypvinn ext.seg_only = false - publishDir = [ - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/anat/" : "${params.outdir}/${meta.id}/anat/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - if ( filename.contains("__final_t1.nii.gz") ) { "${meta.id}_${ses}desc-preproc_T1w.nii.gz" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ] - ] } // ** Nothing to set here ** // // withName: '.*:SEGMENTATION:RECONALLCLINICAL' { // } - withName: '.*:SEGMENTATION:RECONALL' { - publishDir = [ - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/anat/" : "${params.outdir}/${meta.id}/anat/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - if ( filename.contains("__final_t1.nii.gz") ) { "${meta.id}_${ses}desc-preproc_T1w.nii.gz" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ] - ] - } - withName: '.*:SEGMENTATION:MCRIBS' { - ext.surf = true - ext.jointhresh = params.mcribs_jointhresh - ext.fastcollision = params.mcribs_fastcollision - ext.nopialoutside = params.mcribs_nopialoutside - ext.cortical = true - ext.aparcaseg = true - ext.stats = true - ext.seed = params.mcribs_seed + // ** Nothing to set here ** // + // withName: '.*:SEGMENTATION:RECONALL' { + // } + + // ** Nothing to set here ** // + // withName: '.*:SEGMENTATION:BIBSNET' { + // } + + withName: '.*:SEGMENTATION:INFANTFS' { + ext.newborn = false + ext.oneyear = false + ext.forceskullstrip = false + ext.mi = false + ext.gmwm = false + ext.ccseg = false + ext.nostats = false + ext.intnormFSL = false } withName: '.*:SEGMENTATION:REGISTRATION_ANTSREGISTRATION' { @@ -82,38 +62,28 @@ process { withName: '.*:SEGMENTATION:ATLASES_FSAVERAGE2SUBJECT' { publishDir = [ - [ - path: { params.fs_output_dir ?: "${params.outdir}/../freesurfer/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "_${meta.session}" : "" - def run = meta.run ? "_run-${meta.run}" : "" - if ( filename.contains("_fs") ) { "${meta.id}${ses}${run}" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ], - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/anat/" : "${params.outdir}/${meta.id}/anat/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "_${meta.session}" : "" - def run = meta.run ? "_run-${meta.run}" : "" - if ( filename.contains("dseg.nii.gz") ) { "${meta.id}${ses}${run}_seg-${params.atlas_name}_dseg.nii.gz" } - else if ( filename.contains("volume_subcortical") ) { "${meta.id}${ses}${run}_seg-${params.atlas_name}_stat-subcortical.tsv" } - else if ( filename.contains("volume_lh") ) { "${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-left_stat-volume.tsv" } - else if ( filename.contains("volume_rh") ) { "${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-right_stat-volume.tsv" } - else if ( filename.contains("area_lh") ) { "${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-left_stat-surfacearea.tsv" } - else if ( filename.contains("area_rh") ) { "${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-right_stat-surfacearea.tsv" } - else if ( filename.contains("thickness_lh") ) { "${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-left_stat-thickness.tsv" } - else if ( filename.contains("thickness_rh") ) { "${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-right_stat-thickness.tsv" } - else if ( filename.contains("dseg.tsv") ) { "${meta.id}${ses}${run}_seg-${params.atlas_name}_desc-labels_dseg.tsv" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ] + path: { params.outdir }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + def ses = meta.session ? "_${meta.session}" : "" + def run = meta.run ? "_run-${meta.run}" : "" + def anat_dir = meta.session ? "${meta.id}/${meta.session}/anat" : "${meta.id}/anat" + def fs_dir = params.fs_output_dir ?: "../freesurfer" + + if ( filename.contains("_fs") ) { "${fs_dir}/${meta.id}${ses}${run}" } + else if ( filename.contains("dseg.nii.gz") ) { "${anat_dir}/${meta.id}${ses}${run}_seg-${params.atlas_name}_dseg.nii.gz" } + else if ( filename.contains("volume_subcortical") ) { "${anat_dir}/${meta.id}${ses}${run}_seg-${params.atlas_name}_stat-subcortical.tsv" } + else if ( filename.contains("volume_lh") ) { "${anat_dir}/${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-left_stat-volume.tsv" } + else if ( filename.contains("volume_rh") ) { "${anat_dir}/${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-right_stat-volume.tsv" } + else if ( filename.contains("area_lh") ) { "${anat_dir}/${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-left_stat-surfacearea.tsv" } + else if ( filename.contains("area_rh") ) { "${anat_dir}/${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-right_stat-surfacearea.tsv" } + else if ( filename.contains("thickness_lh") ) { "${anat_dir}/${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-left_stat-thickness.tsv" } + else if ( filename.contains("thickness_rh") ) { "${anat_dir}/${meta.id}${ses}${run}_seg-${params.atlas_name}_hemi-right_stat-thickness.tsv" } + else if ( filename.contains("dseg.tsv") ) { "${anat_dir}/${meta.id}${ses}${run}_seg-${params.atlas_name}_desc-labels_dseg.tsv" } + else if ( filename.contains("versions.yml") ) { null } + else { params.lean_output ? null : filename } + } ] } } diff --git a/conf/template.config b/conf/template.config index 5e8f874c..1e14c8b3 100644 --- a/conf/template.config +++ b/conf/template.config @@ -63,34 +63,26 @@ process { ext.transform = 's' ext.quick = false publishDir = [ - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/anat/" : "${params.outdir}/${meta.id}/anat/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - def mov = filename.contains("T1w") ? "T1w" : filename.contains("T2w") ? "T2w" : null - if ( filename.contains("warped.nii.gz") ) { "${meta.id}_${ses}${run}space-${params.template}_desc-preproc_${mov}.nii.gz" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ], - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/xfm/" : "${params.outdir}/${meta.id}/xfm/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - if ( filename.contains("forward0_warp.nii.gz") ) { "${meta.id}_${ses}${run}from-dwi_to-${params.template}_mode-image_desc-warp_xfm.nii.gz" } - else if ( filename.contains("forward1_affine.mat") ) { "${meta.id}_${ses}${run}from-dwi_to-${params.template}_mode-image_desc-affine_xfm.mat" } - else if ( filename.contains("backward1_warp.nii.gz") ) { "${meta.id}_${ses}${run}from-${params.template}_to-dwi_mode-image_desc-warp_xfm.nii.gz" } - else if ( filename.contains("backward0_affine.mat") ) { "${meta.id}_${ses}${run}from-${params.template}_to-dwi_mode-image_desc-affine_xfm.mat" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ] + path: { "${params.outdir}" }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + def ses = meta.session ? "${meta.session}_" : "" + def run = meta.run ? "run-${meta.run}_" : "" + def anat_dir = meta.session ? "${meta.id}/${meta.session}/anat" : "${params.outdir}/${meta.id}/anat" + def xfm_dir = meta.session ? "${meta.id}/${meta.session}/xfm" : "${params.outdir}/${meta.id}/xfm" + def mov = filename.contains("T1w") ? "T1w" : filename.contains("T2w") ? "T2w" : null + + // Files going to the anat directory + if ( filename.contains("warped.nii.gz") ) { "${anat_dir}/${meta.id}_${ses}${run}space-${params.template}_desc-preproc_${mov}.nii.gz" } + // Files going to the xfm directory + else if ( filename.contains("forward0_warp.nii.gz") ) { "${xfm_dir}/${meta.id}_${ses}${run}from-dwi_to-${params.template}_mode-image_desc-warp_xfm.nii.gz" } + else if ( filename.contains("forward1_affine.mat") ) { "${xfm_dir}/${meta.id}_${ses}${run}from-dwi_to-${params.template}_mode-image_desc-affine_xfm.mat" } + else if ( filename.contains("backward1_warp.nii.gz") ) { "${xfm_dir}/${meta.id}_${ses}${run}from-${params.template}_to-dwi_mode-image_desc-warp_xfm.nii.gz" } + else if ( filename.contains("backward0_affine.mat") ) { "${xfm_dir}/${meta.id}_${ses}${run}from-${params.template}_to-dwi_mode-image_desc-affine_xfm.mat" } + else if ( filename.contains("versions.yml") ) { null } + else { params.lean_output ? null : filename } + } ] } withName: '.*:OUTPUT_TEMPLATE_SPACE:WARPIMAGES' { @@ -100,44 +92,36 @@ process { ext.output_dtype = "float" ext.default_val = 0 publishDir = [ - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/anat/" : "${params.outdir}/${meta.id}/anat/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - if ( filename.contains("WM_probseg") ) { "${meta.id}_${ses}${run}space-${params.template}_label-WM_probseg.nii.gz" } - else if ( filename.contains("GM_probseg") ) { "${meta.id}_${ses}${run}space-${params.template}_label-GM_probseg.nii.gz" } - else if ( filename.contains("CSF_probseg") ) { "${meta.id}_${ses}${run}space-${params.template}_label-CSF_probseg.nii.gz" } - else if ( filename.contains("map_wm") ) { "${meta.id}_${ses}${run}space-${params.template}_label-WM_probseg.nii.gz" } - else if ( filename.contains("map_gm") ) { "${meta.id}_${ses}${run}space-${params.template}_label-GM_probseg.nii.gz" } - else if ( filename.contains("map_csf") ) { "${meta.id}_${ses}${run}space-${params.template}_label-CSF_probseg.nii.gz" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ], - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/dwi/" : "${params.outdir}/${meta.id}/dwi/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - if ( filename.contains("ad") ) { "${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-ad_dwimap.nii.gz" } - else if ( filename.contains("_fa_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-fa_dwimap.nii.gz" } - else if ( filename.contains("_rd_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-rd_dwimap.nii.gz" } - else if ( filename.contains("_md_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-md_dwimap.nii.gz" } - else if ( filename.contains("_mode_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-mode_dwimap.nii.gz" } - else if ( filename.contains("_ga_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-ga_dwimap.nii.gz" } - else if ( filename.contains("_afd_total_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-csd_param-afdtotal_dwimap.nii.gz" } - else if ( filename.contains("_nufo_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-csd_param-nufo_dwimap.nii.gz" } - else if ( filename.contains("_afd_max_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-csd_param-afdmax_dwimap.nii.gz" } - else if ( filename.contains("_afd_sum_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-csd_param-afdsum_dwimap.nii.gz" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ] + path: { "${params.outdir}" }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + def ses = meta.session ? "${meta.session}_" : "" + def run = meta.run ? "run-${meta.run}_" : "" + def anat_dir = meta.session ? "${meta.id}/${meta.session}/anat" : "${params.outdir}/${meta.id}/anat" + def dwi_dir = meta.session ? "${meta.id}/${meta.session}/dwi" : "${params.outdir}/${meta.id}/dwi" + + // Files going to the anat directory + if ( filename.contains("WM_probseg") ) { "${anat_dir}/${meta.id}_${ses}${run}space-${params.template}_label-WM_probseg.nii.gz" } + else if ( filename.contains("GM_probseg") ) { "${anat_dir}/${meta.id}_${ses}${run}space-${params.template}_label-GM_probseg.nii.gz" } + else if ( filename.contains("CSF_probseg") ) { "${anat_dir}/${meta.id}_${ses}${run}space-${params.template}_label-CSF_probseg.nii.gz" } + else if ( filename.contains("map_wm") ) { "${anat_dir}/${meta.id}_${ses}${run}space-${params.template}_label-WM_probseg.nii.gz" } + else if ( filename.contains("map_gm") ) { "${anat_dir}/${meta.id}_${ses}${run}space-${params.template}_label-GM_probseg.nii.gz" } + else if ( filename.contains("map_csf") ) { "${anat_dir}/${meta.id}_${ses}${run}space-${params.template}_label-CSF_probseg.nii.gz" } + // Files going to the dwi directory + else if ( filename.contains("ad") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-ad_dwimap.nii.gz" } + else if ( filename.contains("_fa_") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-fa_dwimap.nii.gz" } + else if ( filename.contains("_rd_") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-rd_dwimap.nii.gz" } + else if ( filename.contains("_md_") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-md_dwimap.nii.gz" } + else if ( filename.contains("_mode_") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-mode_dwimap.nii.gz" } + else if ( filename.contains("_ga_") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-ga_dwimap.nii.gz" } + else if ( filename.contains("_afd_total_") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_model-csd_param-afdtotal_dwimap.nii.gz" } + else if ( filename.contains("_nufo_") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_model-csd_param-nufo_dwimap.nii.gz" } + else if ( filename.contains("_afd_max_") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_model-csd_param-afdmax_dwimap.nii.gz" } + else if ( filename.contains("_afd_sum_") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_model-csd_param-afdsum_dwimap.nii.gz" } + else if ( filename.contains("versions.yml") ) { null } + else { params.lean_output ? null : filename } + } ] } withName: '.*:OUTPUT_TEMPLATE_SPACE:WARPRGB' { @@ -147,18 +131,16 @@ process { ext.output_dtype = "uchar" ext.default_val = 0 publishDir = [ - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/dwi/" : "${params.outdir}/${meta.id}/dwi/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - if ( filename.contains("_rgb_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-rgb_dwimap.nii.gz" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ] + path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/dwi/" : "${params.outdir}/${meta.id}/dwi/" }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + def ses = meta.session ? "${meta.session}_" : "" + def run = meta.run ? "run-${meta.run}_" : "" + if ( filename.contains("_rgb_") ) { "${meta.id}_${ses}${run}space-${params.template}_model-tensor_param-rgb_dwimap.nii.gz" } + else if ( filename.contains("versions.yml") ) { null } + else { params.lean_output ? null : filename } + } ] } withName: '.*:OUTPUT_TEMPLATE_SPACE:WARPMASK' { @@ -168,23 +150,21 @@ process { ext.output_dtype = "int" ext.default_val = 0 publishDir = [ - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/anat/" : "${params.outdir}/${meta.id}/anat/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - if ( filename.contains("mask_wm") ) { "${meta.id}_${ses}${run}space-${params.template}_label-WM_mask.nii.gz" } - else if ( filename.contains("mask_gm") ) { "${meta.id}_${ses}${run}space-${params.template}_label-GM_mask.nii.gz" } - else if ( filename.contains("mask_csf") ) { "${meta.id}_${ses}${run}space-${params.template}_label-CSF_mask.nii.gz" } - else if ( filename.contains("wm_mask") ) { "${meta.id}_${ses}${run}space-${params.template}_label-WM_mask.nii.gz" } - else if ( filename.contains("gm_mask") ) { "${meta.id}_${ses}${run}space-${params.template}_label-GM_mask.nii.gz" } - else if ( filename.contains("csf_mask") ) { "${meta.id}_${ses}${run}space-${params.template}_label-CSF_mask.nii.gz" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ], + path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/anat/" : "${params.outdir}/${meta.id}/anat/" }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + def ses = meta.session ? "${meta.session}_" : "" + def run = meta.run ? "run-${meta.run}_" : "" + if ( filename.contains("mask_wm") ) { "${meta.id}_${ses}${run}space-${params.template}_label-WM_mask.nii.gz" } + else if ( filename.contains("mask_gm") ) { "${meta.id}_${ses}${run}space-${params.template}_label-GM_mask.nii.gz" } + else if ( filename.contains("mask_csf") ) { "${meta.id}_${ses}${run}space-${params.template}_label-CSF_mask.nii.gz" } + else if ( filename.contains("wm_mask") ) { "${meta.id}_${ses}${run}space-${params.template}_label-WM_mask.nii.gz" } + else if ( filename.contains("gm_mask") ) { "${meta.id}_${ses}${run}space-${params.template}_label-GM_mask.nii.gz" } + else if ( filename.contains("csf_mask") ) { "${meta.id}_${ses}${run}space-${params.template}_label-CSF_mask.nii.gz" } + else if ( filename.contains("versions.yml") ) { null } + else { params.lean_output ? null : filename } + } ] } withName: '.*:OUTPUT_TEMPLATE_SPACE:WARPLABELS' { @@ -195,18 +175,16 @@ process { ext.default_val = 0 ext.suffix = "labels_warped" publishDir = [ - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/anat/" : "${params.outdir}/${meta.id}/anat/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - if ( filename.contains("labels") ) { "${meta.id}_${ses}${run}space-${params.template}_seg-${params.atlas_name}_dseg.nii.gz" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ], + path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/anat/" : "${params.outdir}/${meta.id}/anat/" }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + def ses = meta.session ? "${meta.session}_" : "" + def run = meta.run ? "run-${meta.run}_" : "" + if ( filename.contains("labels") ) { "${meta.id}_${ses}${run}space-${params.template}_seg-${params.atlas_name}_dseg.nii.gz" } + else if ( filename.contains("versions.yml") ) { null } + else { params.lean_output ? null : filename } + } ] } @@ -219,84 +197,76 @@ process { ext.threshold = 0.001 ext.no_empty = true publishDir = [ - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/dwi/" : "${params.outdir}/${meta.id}/dwi/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - if ( filename.contains("pft_tracking") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-wholebrain_track-pft_tractogram.trk" } - else if ( filename.contains("local_tracking") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-wholebrain_track-sdstream_tractogram.trk" } - else if ( filename.contains("concatenated_tractogram") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-wholebrain_track-sdstream_desc-ensemble_tractogram.trk" } - else if ( filename.contains("commit") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-wholebrain_seg-${params.atlas_name}_desc-filtered_tractogram.h5" } - else if ( filename.contains("afd_fixel.h5") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-wholebrain_seg-${params.atlas_name}_desc-preproc_tractogram.h5" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ], - [ - path: { meta.session ? "${params.outdir}/${meta.id}/${meta.session}/dwi/bundles/" : "${params.outdir}/${meta.id}/dwi/bundles/" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - def ses = meta.session ? "${meta.session}_" : "" - def run = meta.run ? "run-${meta.run}_" : "" - if ( filename.contains("AC-labels") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-AnteriorCommisure_track-sdstream_tractogram.trk" } - else if ( filename.contains("AF-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-ArcuateFasciculus_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("AF-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-ArcuateFasciculus_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("CC-Fr-1") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumFrontal1_track-sdstream_tractogram.trk" } - else if ( filename.contains("CC-Fr-2") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumFrontal2_track-sdstream_tractogram.trk" } - else if ( filename.contains("CC-Oc") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumOccipital_track-sdstream_tractogram.trk" } - else if ( filename.contains("CC-Pa") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumParietal_track-sdstream_tractogram.trk" } - else if ( filename.contains("CC-Pr-Po") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumPrecentralPostcentral_track-sdstream_tractogram.trk" } - else if ( filename.contains("CC-Te") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumTemporal_track-sdstream_tractogram.trk" } - else if ( filename.contains("CG-L-An") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CingulumAnterior_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("CG-L-curve") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CingulumCurve_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("CG-L-Po") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CingulumPosterior_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("CG-L-labels") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-Cingulum_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("CG-R-An") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CingulumAnterior_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("CG-R-curve") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CingulumCurve_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("CG-R-Po") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-CingulumPosterior_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("CG-R-labels") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-Cingulum_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("FAT-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-FrontalAslantTract_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("FAT-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-FrontalAslantTract_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("FPT-L-Brainstem") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-FrontoPontineTractBrainstem_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("FPT-L-labels") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-FrontoPontineTract_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("FPT-R-Brainstem") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-FrontoPontineTractBrainstem_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("FPT-R-labels") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-FrontoPontineTract_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("FX-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-Fornix_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("FX-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-Fornix_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("ICP-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-InferiorCerebellarPeduncle_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("ICP-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-InferiorCerebellarPeduncle_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("IFOF-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-InferiorFrontoOccipitalFasciculus_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("IFOF-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-InferiorFrontoOccipitalFasciculus_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("ILF-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-InferiorLongitudinalFasciculus_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("ILF-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-InferiorLongitudinalFasciculus_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("MCP") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-MiddleCerebellarPeduncle_track-sdstream_tractogram.trk" } - else if ( filename.contains("MdLF-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-MiddleLongitudinalFascicle_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("MdLF-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-MiddleLongitudinalFascicle_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("OR-ML-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-OpticRadiationMeyersLoop_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("OR-ML-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-OpticRadiationMeyersLoop_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("PC-labels") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-PosteriorCommisure_track-sdstream_tractogram.trk" } - else if ( filename.contains("POPT-L-Brainstem") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-ParietoOccipitoPontineTractBrainstem_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("POPT-R-Brainstem") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-ParietoOccipitoPontineTractBrainstem_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("POPT-L-labels") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-ParietoOccipitoPontineTract_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("POPT-R-labels") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-ParietoOccipitoPontineTract_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("PYT-L-Brainstem") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-PyramidalTractBrainstem_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("PYT-R-Brainstem") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-PyramidalTractBrainstem_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("PYT-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-PyramidalTract_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("PYT-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-PyramidalTract_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("SCP-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-SuperiorCerebellarPeduncle_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("SCP-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-SuperiorCerebellarPeduncle_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("SLF-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-SuperiorLongitudinalFasciculus_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("SLF-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-SuperiorLongitudinalFasciculus_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("UF-L") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-UncinateFasciculus_hemi-left_track-sdstream_tractogram.trk" } - else if ( filename.contains("UF-R") ) { "${meta.id}_${ses}${run}space-${params.template}_tract-UncinateFasciculus_hemi-right_track-sdstream_tractogram.trk" } - else if ( filename.contains("versions.yml") ) { null } - else { params.lean_output ? null : filename } - } - ] + path: { "${params.outdir}" }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + def ses = meta.session ? "${meta.session}_" : "" + def run = meta.run ? "run-${meta.run}_" : "" + def dwi_dir = meta.session ? "${meta.id}/${meta.session}/dwi" : "${params.outdir}/${meta.id}/dwi" + def bundles_dir = meta.session ? "${meta.id}/${meta.session}/dwi/bundles" : "${params.outdir}/${meta.id}/dwi/bundles" + + // Files going to the dwi directory + if ( filename.contains("pft_tracking") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-wholebrain_track-pft_tractogram.trk" } + else if ( filename.contains("local_tracking") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-wholebrain_track-sdstream_tractogram.trk" } + else if ( filename.contains("concatenated_tractogram") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-wholebrain_track-sdstream_desc-ensemble_tractogram.trk" } + else if ( filename.contains("commit") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-wholebrain_seg-${params.atlas_name}_desc-filtered_tractogram.h5" } + else if ( filename.contains("afd_fixel.h5") ) { "${dwi_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-wholebrain_seg-${params.atlas_name}_desc-preproc_tractogram.h5" } + // Files going to the dwi/bundles directory + else if ( filename.contains("AC-labels") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-AnteriorCommisure_track-sdstream_tractogram.trk" } + else if ( filename.contains("AF-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-ArcuateFasciculus_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("AF-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-ArcuateFasciculus_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("CC-Fr-1") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumFrontal1_track-sdstream_tractogram.trk" } + else if ( filename.contains("CC-Fr-2") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumFrontal2_track-sdstream_tractogram.trk" } + else if ( filename.contains("CC-Oc") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumOccipital_track-sdstream_tractogram.trk" } + else if ( filename.contains("CC-Pa") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumParietal_track-sdstream_tractogram.trk" } + else if ( filename.contains("CC-Pr-Po") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumPrecentralPostcentral_track-sdstream_tractogram.trk" } + else if ( filename.contains("CC-Te") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CorpusCallosumTemporal_track-sdstream_tractogram.trk" } + else if ( filename.contains("CG-L-An") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CingulumAnterior_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("CG-L-curve") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CingulumCurve_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("CG-L-Po") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CingulumPosterior_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("CG-L-labels") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-Cingulum_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("CG-R-An") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CingulumAnterior_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("CG-R-curve") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CingulumCurve_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("CG-R-Po") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-CingulumPosterior_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("CG-R-labels") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-Cingulum_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("FAT-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-FrontalAslantTract_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("FAT-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-FrontalAslantTract_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("FPT-L-Brainstem") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-FrontoPontineTractBrainstem_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("FPT-L-labels") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-FrontoPontineTract_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("FPT-R-Brainstem") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-FrontoPontineTractBrainstem_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("FPT-R-labels") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-FrontoPontineTract_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("FX-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-Fornix_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("FX-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-Fornix_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("ICP-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-InferiorCerebellarPeduncle_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("ICP-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-InferiorCerebellarPeduncle_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("IFOF-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-InferiorFrontoOccipitalFasciculus_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("IFOF-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-InferiorFrontoOccipitalFasciculus_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("ILF-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-InferiorLongitudinalFasciculus_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("ILF-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-InferiorLongitudinalFasciculus_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("MCP") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-MiddleCerebellarPeduncle_track-sdstream_tractogram.trk" } + else if ( filename.contains("MdLF-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-MiddleLongitudinalFascicle_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("MdLF-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-MiddleLongitudinalFascicle_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("OR-ML-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-OpticRadiationMeyersLoop_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("OR-ML-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-OpticRadiationMeyersLoop_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("PC-labels") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-PosteriorCommisure_track-sdstream_tractogram.trk" } + else if ( filename.contains("POPT-L-Brainstem") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-ParietoOccipitoPontineTractBrainstem_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("POPT-R-Brainstem") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-ParietoOccipitoPontineTractBrainstem_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("POPT-L-labels") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-ParietoOccipitoPontineTract_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("POPT-R-labels") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-ParietoOccipitoPontineTract_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("PYT-L-Brainstem") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-PyramidalTractBrainstem_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("PYT-R-Brainstem") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-PyramidalTractBrainstem_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("PYT-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-PyramidalTract_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("PYT-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-PyramidalTract_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("SCP-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-SuperiorCerebellarPeduncle_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("SCP-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-SuperiorCerebellarPeduncle_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("SLF-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-SuperiorLongitudinalFasciculus_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("SLF-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-SuperiorLongitudinalFasciculus_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("UF-L") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-UncinateFasciculus_hemi-left_track-sdstream_tractogram.trk" } + else if ( filename.contains("UF-R") ) { "${bundles_dir}/${meta.id}_${ses}${run}space-${params.template}_tract-UncinateFasciculus_hemi-right_track-sdstream_tractogram.trk" } + else if ( filename.contains("versions.yml") ) { null } + else { params.lean_output ? null : filename } + } ] } } diff --git a/main.nf b/main.nf index b55ecee1..c6852e6c 100644 --- a/main.nf +++ b/main.nf @@ -61,7 +61,7 @@ params { outdir: String // URL to download templates from. - templates_url: String = 'https://osf.io/Kd8h7/download' + templates_url: String = 'https://osf.io/Bqw6v/download' // Path to the directory where templates will be downloaded and cached. templates_download_path: String = "${env('HOME')}/.cache/sf-pediatric" diff --git a/modules/local/atlases/brainnetomechild.nf b/modules/local/atlases/brainnetomechild.nf deleted file mode 100644 index b42d5112..00000000 --- a/modules/local/atlases/brainnetomechild.nf +++ /dev/null @@ -1,239 +0,0 @@ -process ATLASES_BRAINNETOMECHILD { - tag "$meta.id" - label 'process_medium' - - container "gagnonanthony/nf-pediatric-atlases:1.1.0" - - input: - tuple val(meta), path(folder), path(utils), path(fs_license) - - output: - tuple val(meta), path("*brainnetome_child_v1.nii.gz") , emit: labels - tuple val(meta), path("*brainnetome_child_v1_dilated.nii.gz") , emit: labels_dilate - tuple val(meta), path("*[brainnetome_child]*.txt") , emit: labels_txt - tuple val(meta), path("*[brainnetome_child]*.json") , emit: labels_json - tuple val(meta), path("*BN_Child.stats") , emit: stats_files - tuple val(meta), path("*BN_Child.annot") , emit: annot_files - path("*.tsv") , emit: stats - path "versions.yml" , emit: versions - - when: - task.ext.when == null || task.ext.when - - script: - def prefix = task.ext.prefix ?: "${meta.id}" - def ses = meta.session ? "_${meta.session}" : "" - - """ - # Exporting the FS license and setting up the environment - export FS_LICENSE=./license.txt - #export PYTHONPATH=/opt/freesurfer/python/packages:\$PYTHONPATH - - # Setting the logging configs. - BLUE='\\033[0;34m' - LRED='\\033[1;31m' - NC='\\033[0m' - ColorPrint () { echo -e \${BLUE}\${1}\${NC}; } - - # Copy the input folder (this enables the resume feature of nextflow). - cp -rL $folder ${prefix}__folder - - # If there already is an annot file in the label folder, remove it. - rm -f ${prefix}__folder/$prefix/label/lh.BN_Child.annot ${prefix}__folder/$prefix/label/rh.BN_Child.annot - - # Symlink the fsaverage folder if it is not already there. - if [ ! -d ${prefix}__folder/fsaverage ]; then - # Sometimes, when using freesurfer, there is an empty fsaverage file. - if [ -f ${prefix}__folder/fsaverage ]; then - rm ${prefix}__folder/fsaverage - fi - ln -s \$(readlink -e $utils/fsaverage) ${prefix}__folder/ - fi - - # Fetching the required variables. - export SUBJECTS_DIR=\$(readlink -e ${prefix}__folder) - export SUBJID=$prefix - export NBR_PROCESSES=$task.cpus - export OUT_DIR="Brainnetome_Child" - export UTILS_DIR=\$(readlink -e $utils/freesurfer_utils/) - export FS_ID_FOLDER=\${SUBJECTS_DIR}/\${SUBJID}/ - - # Make tmp folder because of the LUT - mkdir -p BN_child_atlas/tmp - mkdir FS_atlas - mkdir \${OUT_DIR} - - # ================================================================================== - # Generate transformation from Freesurfer - echo "Generate Freesurfer transformation file" - tkregister2 --mov \${FS_ID_FOLDER}/mri/brain.mgz --noedit --s \${SUBJID} --regheader --reg BN_child_atlas/register.dat >> logfile.txt - - # ================================================================================== - # Create the Brainnetomme Child cortical parcellation from Freesurfer data - echo -e "\${BLUE}Create the Brainnetome Child cortical parcellation from Freesurfer data (slow)\${NC}" - # Compared to the adult version, the BN Child atlas comes in fsaverage space in an annotation format. - mri_annotation2label --subject fsaverage --hemi lh --outdir BN_child_atlas/tmp/ --annotation \${UTILS_DIR}/lh.BN_child_fsaverage.annot >> logfile.txt &>> logfile.txt - mri_annotation2label --subject fsaverage --hemi rh --outdir BN_child_atlas/tmp/ --annotation \${UTILS_DIR}/rh.BN_child_fsaverage.annot >> logfile.txt &>> logfile.txt - rm BN_child_atlas/tmp/*\\?* - - # Then do a surface-based registration to move it to the subject space. - for i in BN_child_atlas/tmp/l*.*.label; - do echo mri_label2label --srcsubject fsaverage --srclabel \${i} --trgsubject \${SUBJID} --regmethod surface --hemi lh --trglabel BN_child_atlas/\$(basename \${i}) ' >> logfile.txt' >> cmd.sh; done - for i in BN_child_atlas/tmp/r*.*.label; - do echo mri_label2label --srcsubject fsaverage --srclabel \${i} --trgsubject \${SUBJID} --regmethod surface --hemi rh --trglabel BN_child_atlas/\$(basename \${i}) ' >> logfile.txt' >> cmd.sh; done - # Slow operation, multiprocessing it! - parallel --will-cite -P \${NBR_PROCESSES} < cmd.sh; rm cmd.sh BN_child_atlas/tmp/* - - # ================================================================================== - # Merge it into a single .annot file for statistics (the LUT file has to be copied in the tmp folder each time, since freesurfer is modifying it). - echo -e "\${BLUE}Exporting cortical statistics from the Brainnetome Child atlas\${NC}" - cp \${UTILS_DIR}/atlas_brainnetome_child_v1_LUT.txt BN_child_atlas/tmp/ - mris_label2annot --s \${SUBJID} --h lh --ctab BN_child_atlas/tmp/atlas_brainnetome_child_v1_LUT.txt --a BN_Child --ldir BN_child_atlas/ >> logfile.txt &>> logfile.txt - mris_anatomical_stats -mgz -cortex \${FS_ID_FOLDER}/label/lh.cortex.label -f \${FS_ID_FOLDER}/stats/lh.BN_Child.stats -b -a \${FS_ID_FOLDER}/label/lh.BN_Child.annot -c BN_child_atlas/tmp/atlas_brainnetome_child_v1_LUT.txt \${SUBJID} lh white >> logfile.txt &>> logfile.txt - cp \${UTILS_DIR}/atlas_brainnetome_child_v1_LUT.txt BN_child_atlas/tmp/ - mris_label2annot --s \${SUBJID} --h rh --ctab BN_child_atlas/tmp/atlas_brainnetome_child_v1_LUT.txt --a BN_Child --ldir BN_child_atlas/ >> logfile.txt &>> logfile.txt - mris_anatomical_stats -mgz -cortex \${FS_ID_FOLDER}/label/rh.cortex.label -f \${FS_ID_FOLDER}/stats/rh.BN_Child.stats -b -a \${FS_ID_FOLDER}/label/rh.BN_Child.annot -c BN_child_atlas/tmp/atlas_brainnetome_child_v1_LUT.txt \${SUBJID} rh white >> logfile.txt &>> logfile.txt - - # Extracting the stats into a tsv file. - aparcstats2table --subjects \${SUBJID} --hemi=lh -m volume -p BN_Child --tablefile=\${OUT_DIR}/\${SUBJID}${ses}__volume_lh.BN_Child.tsv >> logfile.txt &>> logfile.txt - aparcstats2table --subjects \${SUBJID} --hemi=rh -m volume -p BN_Child --tablefile=\${OUT_DIR}/\${SUBJID}${ses}__volume_rh.BN_Child.tsv >> logfile.txt &>> logfile.txt - aparcstats2table --subjects \${SUBJID} --hemi=lh -m thickness -p BN_Child --tablefile=\${OUT_DIR}/\${SUBJID}${ses}__thickness_lh.BN_Child.tsv >> logfile.txt &>> logfile.txt - aparcstats2table --subjects \${SUBJID} --hemi=rh -m thickness -p BN_Child --tablefile=\${OUT_DIR}/\${SUBJID}${ses}__thickness_rh.BN_Child.tsv >> logfile.txt &>> logfile.txt - aparcstats2table --subjects \${SUBJID} --hemi=lh -m area -p BN_Child --tablefile=\${OUT_DIR}/\${SUBJID}${ses}__area_lh.BN_Child.tsv >> logfile.txt &>> logfile.txt - aparcstats2table --subjects \${SUBJID} --hemi=rh -m area -p BN_Child --tablefile=\${OUT_DIR}/\${SUBJID}${ses}__area_rh.BN_Child.tsv >> logfile.txt &>> logfile.txt - - # ================================================================================== - # Create the Brainnetomme subcortical parcellation from Freesurfer data - echo -e "\${BLUE}Create the Brainnetomme subcortical parcellation (adult is identical to child version) from Freesurfer data (slow)\${NC}" - mri_ca_label \${FS_ID_FOLDER}/mri/brain.mgz \${FS_ID_FOLDER}/mri/transforms/talairach.m3z \${UTILS_DIR}/subcortex_BN_atlas.gca BN_child_atlas/BN_atlas_subcortex.nii.gz >> logfile.txt - - # ================================================================================== - # Convert the *.label to *.nii.gz, very slow because of the cortical ribbon (filling) - echo -e "\${BLUE}Creating the Brainnetome Child parcellation ROIs by filling up the cortical ribbon (very slow)\${NC}" - for i in BN_child_atlas/lh*.label; - do echo mri_label2vol --label \${i} --temp \${FS_ID_FOLDER}/mri/brain.mgz --o \${i/.label/.nii.gz} --subject \${SUBJID} --hemi lh --proj frac 0 1 .1 --fillthresh .3 --reg BN_child_atlas/register.dat --fill-ribbon ' >> logfile.txt' >> cmd.sh - done - for i in BN_child_atlas/rh*.label; - do echo mri_label2vol --label \${i} --temp \${FS_ID_FOLDER}/mri/brain.mgz --o \${i/.label/.nii.gz} --subject \${SUBJID} --hemi rh --proj frac 0 1 .1 --fillthresh .3 --reg BN_child_atlas/register.dat --fill-ribbon ' >> logfile.txt' >> cmd.sh - done - # Slow operation, multiprocessing it ! - parallel --will-cite -P \${NBR_PROCESSES} < cmd.sh; rm cmd.sh - - # ================================================================================== - # Rename and multiply all binary masks so they follow the same convention as split_label_by_ids - echo "Rename the BN Child cortical ROIs to a simple ids convention" - python3 \${UTILS_DIR}/utils_rename_parcellation.py BN_child_atlas/*h.*.nii.gz \${UTILS_DIR}/parcellation_names_BN_child.json BN_child_atlas/split_rename/ - - # ================================================================================== - echo -e "\${BLUE}Rename the BN subcortical ROIs to a simple ids convention\${NC}" - # Split subcortical ids and mix them with the cortical - mkdir BN_child_atlas/subcortical/ - scil_split_volume_by_ids.py BN_child_atlas/BN_atlas_subcortex.nii.gz --out_dir BN_child_atlas/subcortical/ - for i in BN_child_atlas/subcortical/*.nii.gz; - do base_name=\$(basename \$i .nii.gz); add_name=\$((\${base_name}-22)); - scil_image_math.py --data_type uint16 --exclude_background subtraction \${i} 22 BN_child_atlas/split_rename/\${add_name}.nii.gz - done - rm -r BN_child_atlas/subcortical/ - - # ================================================================================== - echo -e "\${BLUE}Rename the FS wmparc ROIs to a simple ids convention\${NC}" - mri_convert \${FS_ID_FOLDER}/mri/wmparc.mgz BN_child_atlas/wmparc.nii.gz - scil_image_math.py convert BN_child_atlas/wmparc.nii.gz BN_child_atlas/wmparc.nii.gz --data_type uint16 -f - mkdir FS_atlas/split_rename/ - scil_split_volume_by_ids.py BN_child_atlas/wmparc.nii.gz --out_dir FS_atlas/split_rename/ - - # ================================================================================== - echo "Transfert the FS brainstem and cerebellum to BN Child" - # Add the brainstem to BN (225) - for i in FS_atlas/split_rename/16.nii.gz; - do base_name=\$(basename \$i .nii.gz); add_name=\$((\${base_name}+209)); scil_image_math.py --data_type uint16 --exclude_background addition \${i} 209 BN_child_atlas/split_rename/\${add_name}.nii.gz - done - - # Add the cerebellum to BN (226,227) - scil_image_math.py --data_type uint16 --exclude_background addition FS_atlas/split_rename/8.nii.gz 218 BN_child_atlas/split_rename/226.nii.gz - scil_image_math.py --data_type uint16 --exclude_background addition FS_atlas/split_rename/47.nii.gz 180 BN_child_atlas/split_rename/227.nii.gz - - # ================================================================================== - echo -e "\${BLUE}Combine the labels into a clean Brainnetome atlas\${NC}" - a='' - for i in BN_child_atlas/split_rename/*.nii.gz; do a="\${a} --volume_ids \${i} \$(basename \${i} .nii.gz)"; scil_image_math.py convert \${i} \${i} --data_type uint16 -f; done - scil_combine_labels.py BN_child_atlas/atlas_brainnetome_child.nii.gz \${a} - - # ================================================================================== - # Since Freesurfer is all in 1x1x1mm and a 256x256x256 array, our atlases must be resampled/reshaped - echo -e "\${BLUE}Reshape as the original input and convert the final atlases into uint16\${NC}" - mri_convert \${FS_ID_FOLDER}/mri/native.mgz BN_child_atlas/native.nii.gz - scil_reshape_to_reference.py BN_child_atlas/atlas_brainnetome_child.nii.gz BN_child_atlas/native.nii.gz BN_child_atlas/atlas_brainnetome_child.nii.gz --interpolation nearest -f - - # ================================================================================== - # Safer for most script, thats our label data type - echo -e "\${BLUE}Finished creating the atlas by dilating the label\${NC}" - scil_image_math.py convert BN_child_atlas/atlas_brainnetome_child.nii.gz \${OUT_DIR}/atlas_brainnetome_child_v1.nii.gz --data_type uint16 -f - cp \${UTILS_DIR}/atlas_brainnetome_child_v1_*.* \${OUT_DIR}/ - - # Compute statistics on subcortical regions. - mri_convert \${FS_ID_FOLDER}/mri/synthSR.norm.mgz BN_child_atlas/synthSR.norm.nii.gz - scil_reshape_to_reference.py \${OUT_DIR}/atlas_brainnetome_child_v1.nii.gz BN_child_atlas/synthSR.norm.nii.gz BN_child_atlas/atlas_brainnetome_child_reshaped.nii.gz --interpolation nearest --keep_dtype -f - mri_segstats --seg BN_child_atlas/atlas_brainnetome_child_reshaped.nii.gz --ctab \${OUT_DIR}/atlas_brainnetome_child_v1_LUT.txt --excludeid 0 \ - --o \${FS_ID_FOLDER}/stats/subcortical.BN_Child.stats --pv \${FS_ID_FOLDER}/mri/synthSR.norm.mgz \ - --id 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 - asegstats2table --subjects \${SUBJID} --meas=volume \ - --tablefile=\${OUT_DIR}/\${SUBJID}${ses}__volume_BN_Child_subcortical.tsv --all-segs --stats=subcortical.BN_Child.stats - - # Dilating the atlas - mri_convert \${FS_ID_FOLDER}/mri/brainmask.mgz BN_child_atlas/brain_mask.nii.gz - scil_image_math.py lower_threshold BN_child_atlas/brain_mask.nii.gz 0.001 BN_child_atlas/brain_mask.nii.gz --data_type uint8 -f - scil_image_math.py dilation BN_child_atlas/brain_mask.nii.gz 1 BN_child_atlas/brain_mask.nii.gz -f - scil_reshape_to_reference.py BN_child_atlas/brain_mask.nii.gz BN_child_atlas/native.nii.gz BN_child_atlas/brain_mask.nii.gz --interpolation nearest -f - scil_image_math.py convert BN_child_atlas/brain_mask.nii.gz BN_child_atlas/brain_mask.nii.gz --data_type uint8 -f - - scil_dilate_labels.py \${OUT_DIR}/atlas_brainnetome_child_v1.nii.gz \${OUT_DIR}/atlas_brainnetome_child_v1_dilated.nii.gz --distance 2 --labels_to_dilate {1..188} {225..227} --mask BN_child_atlas/brain_mask.nii.gz - - echo -e "\${BLUE}Finished creating the atlas.\${NC}" - - # ================================================================================== - # Copy the results to the output folder - cp Brainnetome_Child/* ./ - - # Fetch .annot files and .stats files, to place within the FastSurfer/FreeSurfer folder. - mv \${FS_ID_FOLDER}/label/*BN_Child.annot ./ - mv \${FS_ID_FOLDER}/stats/*BN_Child.stats ./ - - # Clean up the remaining folders. - rm -rf BN_child_atlas Brainnetome_Child FS_atlas ${prefix}__folder - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - scilpy: \$(pip list | grep scilpy | tr -s ' ' | cut -d' ' -f2) - freesurfer: \$(mri_convert -version | grep "freesurfer" | sed -E 's/.* ([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/') - END_VERSIONS - """ - - stub: - def prefix = task.ext.prefix ?: "${meta.id}" - def ses = meta.session ? "_${meta.session}" : "" - - """ - touch ${prefix}__brainnetome_child_v1.nii.gz - touch ${prefix}__brainnetome_child_v1_dilated.nii.gz - touch ${prefix}__brainnetome_child_v1_LUT.txt - touch ${prefix}__brainnetome_child_v1_LUT.json - touch ${prefix}${ses}__volume_BN_Child_subcortical.tsv - touch ${prefix}${ses}__volume_lh.BN_Child.tsv - touch ${prefix}${ses}__volume_rh.BN_Child.tsv - touch ${prefix}${ses}__area_lh.BN_Child.tsv - touch ${prefix}${ses}__area_rh.BN_Child.tsv - touch ${prefix}${ses}__thickness_lh.BN_Child.tsv - touch ${prefix}${ses}__thickness_rh.BN_Child.tsv - touch lh.BN_Child.annot - touch rh.BN_Child.annot - touch lh.BN_Child.stats - touch rh.BN_Child.stats - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - scilpy: \$(pip list | grep scilpy | tr -s ' ' | cut -d' ' -f2) - freesurfer: \$(mri_convert -version | grep "freesurfer" | sed -E 's/.* ([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/') - END_VERSIONS - """ -} diff --git a/modules/local/atlases/concatenatestats.nf b/modules/local/atlases/concatenatestats.nf deleted file mode 100644 index 891ae1d5..00000000 --- a/modules/local/atlases/concatenatestats.nf +++ /dev/null @@ -1,42 +0,0 @@ -process ATLASES_CONCATENATESTATS { - tag "global" - label 'process_single' - - container "${ 'gagnonanthony/neurostatx:0.1.0' }" - - input: - tuple val(meta), path(tsv) - - output: - path("*_volumes.tsv") , emit: subcortical - path("*_volume_lh.tsv") , emit: volume_lh - path("*_volume_rh.tsv") , emit: volume_rh - path("*_area_lh.tsv") , emit: area_lh - path("*_area_rh.tsv") , emit: area_rh - path("*_thickness_lh.tsv") , emit: thickness_lh - path("*_thickness_rh.tsv") , emit: thickness_rh - path "versions.yml" , emit: versions - - when: - task.ext.when == null || task.ext.when - - script: - template 'concatenatestats.py' - - stub: - """ - touch subcortical_${meta.agegroup}_volumes.tsv - touch cortical_${meta.agegroup}_volume_lh.tsv - touch cortical_${meta.agegroup}_volume_rh.tsv - touch cortical_${meta.agegroup}_area_lh.tsv - touch cortical_${meta.agegroup}_area_rh.tsv - touch cortical_${meta.agegroup}_thickness_lh.tsv - touch cortical_${meta.agegroup}_thickness_rh.tsv - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - python: \$(python3 -c 'import platform; print(platform.python_version())') - pandas: \$(python3 -c 'import pandas; print(pandas.__version__)') - END_VERSIONS - """ -} diff --git a/modules/local/atlases/formatlabels.nf b/modules/local/atlases/formatlabels.nf deleted file mode 100644 index b9f105e7..00000000 --- a/modules/local/atlases/formatlabels.nf +++ /dev/null @@ -1,121 +0,0 @@ -process ATLASES_FORMATLABELS { - tag "$meta.id" - label 'process_single' - - container "gagnonanthony/nf-pediatric-atlases:1.1.0" - - input: - tuple val(meta), path(folder), path(utils), path(fs_license) - - output: - tuple val(meta), path("*labels.nii.gz") , emit: labels - tuple val(meta), path("*DK_v1_LUT.txt") , emit: labels_txt - tuple val(meta), path("*DK_v1_LUT.json") , emit: labels_json - path("*.tsv") , emit: stats - path "versions.yml" , emit: versions - - when: - task.ext.when == null || task.ext.when - - script: - def prefix = task.ext.prefix ?: "${meta.id}" - def ses = meta.session ? "_${meta.session}" : "" - - """ - # Exporting the FS license and setting up the environment - export FS_LICENSE=./license.txt - export SUBJECTS_DIR=\$(readlink -e ./) - export UTILS_DIR=\$(readlink -e $utils/freesurfer_utils/) - - mv $folder ${prefix} - - # Creating the label file. - scil_remove_labels.py ${prefix}/mri/aparc+aseg.nii.gz ${prefix}${ses}__labels.nii.gz \ - -i 4 24 43 159 160 161 162 253 -f - - # By default, labels are in range 1000-2000, let's reformat them. - scil_combine_labels.py \ - --volume_ids ${prefix}${ses}__labels.nii.gz 1001 2001 1002 2002 1003 2003 \ - 1005 2005 1006 2006 1007 2007 1008 2008 1009 2009 1010 2010 \ - 1011 2011 1012 2012 1013 2013 1014 2014 1015 2015 1016 2016 1017 2017 \ - 1018 2018 1019 2019 1020 2020 1021 2021 1022 2022 1023 2023 1024 2024 \ - 1025 2025 1026 2026 1027 2027 1028 2028 1029 2029 1030 2030 1031 2031 \ - 1032 2032 1033 2033 1034 2034 1035 2035 18 54 17 53 11 50 13 52 12 51 \ - 9 48 16 8 47 \ - --unique ${prefix}${ses}__labels.nii.gz -f - - # Extracting subcortical volume. - asegstats2table --subjects ${prefix} --meas volume \ - --tablefile ${prefix}${ses}__volume_aseg_subcortical.tsv --no-vol-extras - - # Extracting cortical statistics. - python3 $utils/convert_fs_stats.py \ - -i ${prefix}/stats/lh.aparc.stats \ - -o ${prefix}${ses}__volume_lh.aparc.tsv \ - -m GrayVol \ - -s ${prefix} - python3 $utils/convert_fs_stats.py \ - -i ${prefix}/stats/rh.aparc.stats \ - -o ${prefix}${ses}__volume_rh.aparc.tsv \ - -m GrayVol \ - -s ${prefix} - python3 $utils/convert_fs_stats.py \ - -i ${prefix}/stats/lh.aparc.stats \ - -o ${prefix}${ses}__area_lh.aparc.tsv \ - -m SurfArea \ - -s ${prefix} - python3 $utils/convert_fs_stats.py \ - -i ${prefix}/stats/rh.aparc.stats \ - -o ${prefix}${ses}__area_rh.aparc.tsv \ - -m SurfArea \ - -s ${prefix} - python3 $utils/convert_fs_stats.py \ - -i ${prefix}/stats/lh.aparc.stats \ - -o ${prefix}${ses}__thickness_lh.aparc.tsv \ - -m ThickAvg \ - -s ${prefix} - python3 $utils/convert_fs_stats.py \ - -i ${prefix}/stats/rh.aparc.stats \ - -o ${prefix}${ses}__thickness_rh.aparc.tsv \ - -m ThickAvg \ - -s ${prefix} - - # Copy LUT files - cp \${UTILS_DIR}/atlas_DK_v1_LUT.json \${UTILS_DIR}/atlas_DK_v1_LUT.txt ./ - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - scilpy: \$(pip list | grep scilpy | tr -s ' ' | cut -d' ' -f2) - freesurfer: \$(mri_convert -version | grep "freesurfer" | sed -E 's/.* ([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/') - END_VERSIONS - """ - - stub: - def prefix = task.ext.prefix ?: "${meta.id}" - def ses = meta.session ? "_${meta.session}" : "" - """ - export PYTHONPATH=/opt/freesurfer/python/packages:\$PYTHONPATH - - touch ${prefix}${ses}__labels.nii.gz - touch ${prefix}${ses}__volume_aseg_subcortical.tsv - touch ${prefix}${ses}__volume_lh.aparc.tsv - touch ${prefix}${ses}__volume_rh.aparc.tsv - touch ${prefix}${ses}__area_lh.aparc.tsv - touch ${prefix}${ses}__area_rh.aparc.tsv - touch ${prefix}${ses}__thickness_lh.aparc.tsv - touch ${prefix}${ses}__thickness_rh.aparc.tsv - touch ${prefix}__DK_v1_LUT.txt - touch ${prefix}__DK_v1_LUT.json - - scil_remove_labels.py -h - scil_combine_labels.py -h - python3 $utils/convert_fs_stats.py -h - asegstats2table --help - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - scilpy: \$(pip list | grep scilpy | tr -s ' ' | cut -d' ' -f2) - freesurfer: \$(mri_convert -version | grep "freesurfer" | sed -E 's/.* ([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/') - END_VERSIONS - """ -} diff --git a/modules/local/atlases/fsaverage2subject.nf b/modules/local/atlases/fsaverage2subject.nf index 205395ae..9691d8a2 100644 --- a/modules/local/atlases/fsaverage2subject.nf +++ b/modules/local/atlases/fsaverage2subject.nf @@ -36,40 +36,18 @@ process ATLASES_FSAVERAGE2SUBJECT { export FS_LICENSE=${fs_license} export SUBJECTS_DIR=\$(pwd) - # Check if we got infant data, if so, we need to recompute the sphere registration - if [ -f ${prefix}_fs/surf/lh.sphere.reg2 ]; then - # Mapping directly subject -> fsaverage yields better results than going through the template - mris_register -multi_scale 3 -max_degrees 135 -1 -inflated ${prefix}_fs/surf/lh.sphere \ - ${fsaverage}/surf/lh.sphere ${prefix}_fs/surf/lh.sphere.fsaverage.reg --threads $task.cpus - mris_register -multi_scale 3 -max_degrees 135 -1 -inflated ${prefix}_fs/surf/rh.sphere \ - ${fsaverage}/surf/rh.sphere ${prefix}_fs/surf/rh.sphere.fsaverage.reg --threads $task.cpus - args="--srcsurfreg sphere --trgsurfreg sphere.fsaverage.reg --cortex" - else - # surf to surf works well with adult data, so nothing to add here. - args="" - fi - # Surface-to-surface mapping mri_surf2surf --srcsubject \$(basename $fsaverage) --trgsubject ${prefix}_fs \ --hemi lh --sval-annot ${atlas_name}.annot \ - --o ${atlas_name}.annot \$args + --o ${atlas_name}.annot mri_surf2surf --srcsubject \$(basename $fsaverage) --trgsubject ${prefix}_fs \ --hemi rh --sval-annot ${atlas_name}.annot \ - --o ${atlas_name}.annot \$args + --o ${atlas_name}.annot # Convert to uint16 scil_volume_math convert ${subcortical} \ ${atlas_name}_subcortical_warped.nii.gz --data_type uint16 -f - # Assess if there is a talairach.xfm file, if not, generate an identity transform. - # TODO: Check if this does not introduce aberrant statistics (by comparing to a subject with a talairach.xfm file). - if [ ! -f ${prefix}_fs/mri/transforms/talairach.xfm ] - then - echo "No talairach.xfm file found, generating an identity transform." - mkdir ${prefix}_fs/mri/transforms - printf "MNI Transform File\\n%% Transform from orig to talairach\\n\\nTransform_type = Linear;\\nLinear_Transform =\\n 1.000000 0.000000 0.000000 0.000000 ;\\n 0.000000 1.000000 0.000000 0.000000 ;\\n 0.000000 0.000000 1.000000 0.000000 ;\\n" > ${prefix}_fs/mri/transforms/talairach.xfm - fi - # Compute stats for cortical regions mris_anatomical_stats -mgz -cortex ${prefix}_fs/label/lh.cortex.label \ -f ${prefix}_fs/stats/lh.${atlas_name}.stats \ @@ -95,7 +73,7 @@ process ATLASES_FSAVERAGE2SUBJECT { --tablefile=${prefix}__area_rh.${atlas_name}.tsv # Compute stats for subcortical regions - mri_segstats --seg $subcortical \ + mri_segstats --seg ${atlas_name}_subcortical_warped.nii.gz \ --ctab ${atlas_name}_LUT.txt \ --excludeid 0 \ --o ${prefix}_fs/stats/subcortical.${atlas_name}.stats \ diff --git a/modules/local/atlases/fslr2fsaverage.nf b/modules/local/atlases/fslr2fsaverage.nf index 59060b88..0af4a318 100644 --- a/modules/local/atlases/fslr2fsaverage.nf +++ b/modules/local/atlases/fslr2fsaverage.nf @@ -27,6 +27,7 @@ process ATLASES_FSLR2FSAVERAGE { # Set some env variables. export FS_LICENSE=${fs_license} + export SUBJECTS_DIR=\$(pwd) # Set some temp folders mkdir -p ./tmp/ @@ -38,33 +39,23 @@ process ATLASES_FSLR2FSAVERAGE { -volume-all ${atlas_name}_subcortical.nii.gz \ -label ${atlas_name}_subcortical_labels.nii.gz - # Convert transformation sphere if we can find one tagged with "fslr32k" - # Mostly found in alternative fsaverage atlases, such as the one used by MCRIBS - if [[ -f ./fsaverage_folder/surf/lh.sphere.reg.fslr32k ]]; then - mris_convert ./fsaverage_folder/surf/lh.*.fslr32k ./fsaverage_folder/surf/lh.sphere.reg.fslr32k.surf.gii - mris_convert ./fsaverage_folder/surf/rh.*.fslr32k ./fsaverage_folder/surf/rh.sphere.reg.fslr32k.surf.gii - lh_sphere=./fsaverage_folder/surf/lh.sphere.reg.fslr32k.surf.gii - rh_sphere=./fsaverage_folder/surf/rh.sphere.reg.fslr32k.surf.gii - else - # Convert the freesurfer sphere to gifti - mris_convert ./fsaverage_folder/surf/lh.sphere ./fsaverage_folder/surf/lh.sphere.surf.gii - mris_convert ./fsaverage_folder/surf/rh.sphere ./fsaverage_folder/surf/rh.sphere.surf.gii - - # We need to invert the fsLR -> fsaverage transform - wb_command -surface-sphere-project-unproject \ - ./fsaverage_folder/surf/lh.sphere.surf.gii \ - $fslr/fs_LR-deformed_to-fsaverage.L.sphere.32k_fs_LR.surf.gii \ - $fslr/fsaverage.L.sphere.32k_fs_LR.surf.gii \ - ./fsaverage_folder/surf/lh.sphere.reg.fslr32k.surf.gii - wb_command -surface-sphere-project-unproject \ - ./fsaverage_folder/surf/rh.sphere.surf.gii \ - $fslr/fs_LR-deformed_to-fsaverage.R.sphere.32k_fs_LR.surf.gii \ - $fslr/fsaverage.R.sphere.32k_fs_LR.surf.gii \ - ./fsaverage_folder/surf/rh.sphere.reg.fslr32k.surf.gii - - lh_sphere=./fsaverage_folder/surf/lh.sphere.reg.fslr32k.surf.gii - rh_sphere=./fsaverage_folder/surf/rh.sphere.reg.fslr32k.surf.gii - fi + mris_convert ./fsaverage_folder/surf/lh.sphere ./fsaverage_folder/surf/lh.sphere.surf.gii + mris_convert ./fsaverage_folder/surf/rh.sphere ./fsaverage_folder/surf/rh.sphere.surf.gii + + # We need to invert the fsLR -> fsaverage transform + wb_command -surface-sphere-project-unproject \ + ./fsaverage_folder/surf/lh.sphere.surf.gii \ + $fslr/fs_LR-deformed_to-fsaverage.L.sphere.32k_fs_LR.surf.gii \ + $fslr/fsaverage.L.sphere.32k_fs_LR.surf.gii \ + ./fsaverage_folder/surf/lh.sphere.reg.fslr32k.surf.gii + wb_command -surface-sphere-project-unproject \ + ./fsaverage_folder/surf/rh.sphere.surf.gii \ + $fslr/fs_LR-deformed_to-fsaverage.R.sphere.32k_fs_LR.surf.gii \ + $fslr/fsaverage.R.sphere.32k_fs_LR.surf.gii \ + ./fsaverage_folder/surf/rh.sphere.reg.fslr32k.surf.gii + + lh_sphere=./fsaverage_folder/surf/lh.sphere.reg.fslr32k.surf.gii + rh_sphere=./fsaverage_folder/surf/rh.sphere.reg.fslr32k.surf.gii # Create a midthickness surface for the fsaverage atlas, needed to map the labels using wb_command wb_shortcuts -freesurfer-resample-prep ./fsaverage_folder/surf/lh.white \ diff --git a/modules/local/atlases/templates/concatenatestats.py b/modules/local/atlases/templates/concatenatestats.py deleted file mode 100644 index 8bf76908..00000000 --- a/modules/local/atlases/templates/concatenatestats.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 - -import glob -import platform - -import pandas as pd - - -def format_yaml_like(data: dict, indent: int = 0) -> str: - """Formats a dictionary to a YAML-like string. - Pulled from the scvitools/solo nf-core modules. - https://github.com/nf-core/modules/nf-core/scvitools/solo/templates/solo.py - - Args: - data (dict): The dictionary to format. - indent (int): The current indentation level. - - Returns: - str: A string formatted as YAML. - """ - yaml_str = "" - for key, value in data.items(): - spaces = " " * indent - if isinstance(value, dict): - yaml_str += f"{spaces}{key}:\\n{format_yaml_like(value, indent + 1)}" - else: - yaml_str += f"{spaces}{key}: {value}\\n" - return yaml_str - - -for stat in ['volume', 'area', 'thickness', 'subcortical']: - group = "$meta.agegroup" - if stat == 'subcortical': - files = glob.glob("*subcortical*") - df = pd.concat([pd.read_csv(f, sep='\\t') for f in files], ignore_index=True) - df.rename(columns={df.columns[0]: "Sample"}, inplace=True) - df.to_csv(f"{group}_{stat}_volumes.tsv", sep='\\t', index=False) - else: - for hemi in ['lh', 'rh']: - files = glob.glob(f"*{stat}_{hemi}*") - df = pd.concat([pd.read_csv(f, sep='\\t') for f in files], ignore_index=True) - df.rename(columns={df.columns[0]: "Sample"}, inplace=True) - if stat == "thickness": - # Specific to the Brainnetome atlas, drop some columns if they exist. - if "BrainSegVolNotVent" in df.columns: - df.drop(columns=["BrainSegVolNotVent", "eTIV", f"{hemi}_cluster94_{stat}", - f"{hemi}_MeanThickness_{stat}"], inplace=True) - elif stat == "area": - # Specific to the Brainnetome atlas, drop some columns if they exist. - if "BrainSegVolNotVent" in df.columns: - df.drop(columns=["BrainSegVolNotVent", "eTIV", f"{hemi}_cluster94_{stat}", - f"{hemi}_WhiteSurfArea_{stat}"], inplace=True) - else: - # Specific to the Brainnetome atlas, drop some columns if they exist. - if "BrainSegVolNotVent" in df.columns: - df.drop(columns=["BrainSegVolNotVent", "eTIV", f"{hemi}_cluster94_{stat}"], inplace=True) - df.columns = [col.replace(f"_{stat}", "") for col in df.columns] - df.to_csv(f"cortical_{group}_{stat}_{hemi}.tsv", sep='\\t', index=False) - -versions = { - "${task.process}": { - "python": platform.python_version(), - "pandas": pd.__version__, - } -} - -with open("versions.yml", "w") as f: - f.write(format_yaml_like(versions)) diff --git a/modules/local/segmentation/bibsnet/environment.yml b/modules/local/segmentation/bibsnet/environment.yml new file mode 100644 index 00000000..8abd12c0 --- /dev/null +++ b/modules/local/segmentation/bibsnet/environment.yml @@ -0,0 +1,4 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: [] +dependencies: [] diff --git a/modules/local/segmentation/bibsnet/main.nf b/modules/local/segmentation/bibsnet/main.nf new file mode 100644 index 00000000..fd1c91a8 --- /dev/null +++ b/modules/local/segmentation/bibsnet/main.nf @@ -0,0 +1,64 @@ +process SEGMENTATION_BIBSNET { + tag "$meta.id" + label 'process_bibsnet' + + conda "${moduleDir}/environment.yml" + container "gagnonanthony/sf-pediatric-bibsnet:3.7.0" + containerOptions { + (workflow.containerEngine == 'docker') ? '--entrypoint ""' : '' + } + + input: + tuple val(meta), path(t1), path(t2) + + output: + tuple val(meta), path("*space-T1w_desc-aseg_dseg.nii.gz") , emit: t1_dseg, optional: true + tuple val(meta), path("*space-T1w_desc-brain_mask.nii.gz") , emit: t1_brain_mask, optional: true + tuple val(meta), path("*space-T2w_desc-aseg_dseg.nii.gz") , emit: t2_dseg, optional: true + tuple val(meta), path("*space-T2w_desc-brain_mask.nii.gz") , emit: t2_brain_mask, optional: true + tuple val("${task.process}"), val('bibsnet'), eval('echo $BIBSNET_VERSION'), topic: versions, emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def fsl_bin_path = task.ext.fsl_bin_path ?: "/opt/conda/envs/fsl/bin/" + def nnunet_path = task.ext.nnunet_path ?: "/opt/venv/bin/nnUNet_predict" + def cmd_t1 = t1 ? "cp -rL ${t1} ./bids/${prefix}/anat/${prefix}_T1w.nii.gz && touch bids/${prefix}/anat/${prefix}_T1w.json" : "" + def cmd_t2 = t2 ? "cp -rL ${t2} ./bids/${prefix}/anat/${prefix}_T2w.nii.gz && touch bids/${prefix}/anat/${prefix}_T2w.json" : "" + + """ + # We need to mimick a bids structure for BIBSNet + mkdir -p bids/${prefix}/anat + ${cmd_t1} + ${cmd_t2} + + # Launch BIBSNet + bibsnet \ + -v \ + bids \ + ./ \ + participant \ + --fsl-bin-path ${fsl_bin_path} \ + --nnUNet ${nnunet_path} \ + --work-dir ./work + + # Copy the output to work dir. + mv bibsnet/${prefix}/anat/*aseg_dseg.nii.gz ./ + mv bibsnet/${prefix}/anat/*brain_mask.nii.gz ./ + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def cmd_t1 = t1 ? "touch ${prefix}_space-T1w_desc-aseg_dseg.nii.gz && touch ${prefix}_space-T1w_desc-brain_mask.nii.gz" : "" + def cmd_t2 = t2 ? "touch ${prefix}_space-T2w_desc-aseg_dseg.nii.gz && touch ${prefix}_space-T2w_desc-brain_mask.nii.gz" : "" + + """ + bibsnet -h + + # Create empty output files + ${cmd_t1} + ${cmd_t2} + """ +} diff --git a/modules/local/segmentation/bibsnet/meta.yml b/modules/local/segmentation/bibsnet/meta.yml new file mode 100644 index 00000000..03885899 --- /dev/null +++ b/modules/local/segmentation/bibsnet/meta.yml @@ -0,0 +1,89 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "segmentation_bibsnet" +description: | + Module for segmenting neonatal brain MRI anatomical images using the nnU-Net based BIBSNet. + Designed to be used with either a T1w, T2w, or both T1w and T2w images for + subjects aged 0-8 months. +keywords: + - segmentation + - neonatal + - anatomical + - freesurfer +tools: + - "bibsnet": + description: | + This BIDS App provides the utility of creating a nnU-Net anatomical MRI segmentation + and mask with a infant brain trained model. It can easily be included in other + processing pipelines and for circumventing JLF within Nibabies. + homepage: "https://github.com/DCAN-Labs/BIBSnet" + documentation: "https://bibsnet.readthedocs.io/en/latest/" + doi: "https://doi.org/10.1016/j.dcn.2026.101706" + licence: Apache-2.0 +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - t1: + type: file + description: T1-weighted MRI image + pattern: "*.{nii.gz,nii}" + ontologies: + - edam: "http://edamontology.org/format_4001" # NIfTI + - t2: + type: file + description: T2-weighted MRI image + pattern: "*.{nii.gz,nii}" + ontologies: + - edam: "http://edamontology.org/format_4001" # NIfTI +output: + aseg: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.nii.gz": + type: file + description: Segmented anatomical image + pattern: "*.{nii.gz,nii}" + ontologies: + - edam: "http://edamontology.org/format_4001" # NIfTI + mask: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.nii.gz": + type: file + description: Brain mask image + pattern: "*.{nii.gz,nii}" + ontologies: + - edam: "http://edamontology.org/format_4001" # NIfTI + versions: + - - "${task.process}": + type: string + description: The name of the process + - "bibsnet": + type: string + description: The name of the tool + - "echo $BIBSNET_VERSION": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - "bibsnet": + type: string + description: The name of the tool + - "echo $BIBSNET_VERSION": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@gagnonanthony" +maintainers: + - "@gagnonanthony" diff --git a/modules/local/segmentation/bibsnet/tests/main.nf.test b/modules/local/segmentation/bibsnet/tests/main.nf.test new file mode 100644 index 00000000..d89292f0 --- /dev/null +++ b/modules/local/segmentation/bibsnet/tests/main.nf.test @@ -0,0 +1,80 @@ +// TODO nf-core: Once you have added the required tests, please run the following command to build this file: +// nf-core modules test segmentation/bibsnet +nextflow_process { + + name "Test Process SEGMENTATION_BIBSNET" + script "../main.nf" + process "SEGMENTATION_BIBSNET" + + tag "modules" + tag "modules_nfneuro" + tag "segmentation" + tag "segmentation/bibsnet" + + setup { + run("LOAD_TEST_DATA", alias: "LOAD_DATA") { + script "../../../../../subworkflows/nf-neuro/load_test_data/main.nf" + process { + """ + input[0] = channel.from( [ "segmentation_bibsnet.zip" ] ) + input[1] = "test.load-test-data" + """ + } + } + } + + test("sarscov2 - bam") { + + when { + process { + """ + // TODO nf-core: define inputs of the process here. Example: + + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + process.out, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + //TODO nf-core: Add all required assertions to verify the test output. + // See https://nf-co.re/docs/developing/testing/assertions for more information and examples. + ) + } + + } + + // TODO nf-core: Change the test name preferably indicating the test-data and file-format used but keep the " - stub" suffix. + test("sarscov2 - bam - stub") { + + options "-stub" + + when { + process { + """ + // TODO nf-core: define inputs of the process here. Example: + + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/local/segmentation/infantfs/environment.yml b/modules/local/segmentation/infantfs/environment.yml new file mode 100644 index 00000000..8abd12c0 --- /dev/null +++ b/modules/local/segmentation/infantfs/environment.yml @@ -0,0 +1,4 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: [] +dependencies: [] diff --git a/modules/local/segmentation/infantfs/main.nf b/modules/local/segmentation/infantfs/main.nf new file mode 100644 index 00000000..ec6075d0 --- /dev/null +++ b/modules/local/segmentation/infantfs/main.nf @@ -0,0 +1,86 @@ +process SEGMENTATION_INFANTFS { + tag "$meta.id" + label 'process_low' + label 'process_high_memory' + + conda "${moduleDir}/environment.yml" + container "gagnonanthony/sf-pediatric-infantfs:8.2.0" + + input: + tuple val(meta), path(anat), path(aseg), path(mask), path(license) + + output: + tuple val(meta), path("*_infantfs") , emit: folder + tuple val(meta), path("*_final_image.nii.gz") , emit: image + tuple val("${task.process}"), val('freesurfer'), eval("mri_convert -version | grep 'freesurfer' | sed -E 's/.* ([0-9.]+).*/\\1/'"), topic: versions, emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def ageMonthsInt = meta.age ? (meta.age.toFloat() / 12.0).toInteger() : null + def age = meta.age ? "--age ${ageMonthsInt}" : task.ext.age ? "--age ${task.ext.age}" : "" + def newborn = task.ext.newborn ? "--newborn" : "" + def oneyear = task.ext.oneyear ? "--oneyear" : "" + def forceskullstrip = task.ext.forceskullstrip ? "--forceskullstrip" : "" + def mi = task.ext.mi ? "--MI" : "" + def gmwm = task.ext.gmwm ? "--gmwm" : "" + def ccseg = task.ext.ccseg ? "--ccseg" : "" + def nostats = task.ext.nostats ? "--no-stats" : "" + def intnormFSL = task.ext.intnormFSL ? "--intnormFSL" : "" + + // Mask is needed if aseg is provided, throw an error if not provided + if (aseg && !mask) { + throw new Exception("Mask is required if aseg is provided") + } + """ + # Setting vars + export FS_LICENSE=\$(realpath $license) + export SUBJECTS_DIR="./" + export USER=freesurfer + export LOGNAME=freesurfer + + # If aseg is provided, mask the anatomical image with the provided mask + if [ -f ${aseg} ]; then + mri_mask $anat $mask ${prefix}_masked.nii.gz + args="--masked ${prefix}_masked.nii.gz --segfile ${aseg}" + else + args="" + fi + + infant_recon_all \ + -s ${prefix} \ + -i ${anat} \ + -o ${prefix}_infantfs \ + ${age} \ + ${newborn} \ + ${oneyear} \ + ${forceskullstrip} \ + ${mi} \ + ${gmwm} \ + ${ccseg} \ + ${nostats} \ + ${intnormFSL} \ + \$args + + # Move the brain.mgz file to current dir + mri_convert ${prefix}_infantfs/mri/brain.mgz ${prefix}_final_image.nii.gz + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + infant_recon_all -h + + mkdir -p ${prefix}_infantfs/mri/transforms \ + ${prefix}_infantfs/label/ \ + ${prefix}_infantfs/surf/ \ + ${prefix}_infantfs/stats/ \ + ${prefix}_infantfs/scripts/ \ + ${prefix}_infantfs/tmp/ \ + ${prefix}_infantfs/touch/ + touch ${prefix}_final_image.nii.gz + """ +} diff --git a/modules/local/segmentation/infantfs/meta.yml b/modules/local/segmentation/infantfs/meta.yml new file mode 100644 index 00000000..03d41db2 --- /dev/null +++ b/modules/local/segmentation/infantfs/meta.yml @@ -0,0 +1,99 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "segmentation_infantfs" +description: | + This module performs segmentation of infant brain MRI images using the infant_recon_all tool. + When providing only raw images, the module is designed to work with only T1w images. However, + it also accepts pre-segmented images (e.g., using BIBSNet) and will reuse this segmentation + to generate surfaces and other outputs. When using pre-segmented images, the module can work with + both T1w and T2w images. Pre-segmented images should be supplied with a brain mask, otherwise an + error will be thrown. +keywords: + - infant + - segmentation + - infant_recon_all + - surfaces + - cortical +tools: + - "freesurfer": + description: | + Open source neuroimaging toolkit for processing, analyzing, and visualizing human brain MR images. + homepage: https://surfer.nmr.mgh.harvard.edu/ + doi: 10.1016/j.neuroimage.2012.01.021 + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - anat: + type: file + description: Anatomical image (T1w or T2w) in NIfTI format. + pattern: "*.{nii,nii.gz}" + ontologies: + - edam: http://edamontology.org/format_4001 # NIFTI format + - aseg: + type: file + description: Pre-segmented anatomical image (e.g., from BIBSNet) in NIfTI format. + pattern: "*.{nii,nii.gz}" + ontologies: + - edam: http://edamontology.org/format_4001 # NIFTI format + - mask: + type: file + description: Brain mask to be used with theanatomical image. + pattern: "*.{nii,nii.gz}" + ontologies: + - edam: http://edamontology.org/format_4001 # NIFTI format + - license: + type: file + description: FreeSurfer license file. + pattern: "*.txt" + ontologies: [] +output: + folder: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_infantfs": + type: folder + description: FreeSurfer output folder. + pattern: "*_infantfs" + image: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_final_image.nii.gz": + type: file + description: Final processed anatomical image. + pattern: "*_final_image.nii.gz" + ontologies: + - edam: http://edamontology.org/format_4001 # NIFTI format + versions_segmentation: + - - "${task.process}": + type: string + description: The name of the process + - "freesurfer": + type: string + description: The name of the tool + - "mri_convert -version | grep 'freesurfer' | sed -E 's/.* ([0-9.]+).*/\\1/'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - "freesurfer": + type: string + description: The name of the tool + - "mri_convert -version | grep 'freesurfer' | sed -E 's/.* ([0-9.]+).*/\\1/'": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@gagnonanthony" +maintainers: + - "@gagnonanthony" diff --git a/modules/local/segmentation/infantfs/tests/main.nf.test b/modules/local/segmentation/infantfs/tests/main.nf.test new file mode 100644 index 00000000..9d55ae8e --- /dev/null +++ b/modules/local/segmentation/infantfs/tests/main.nf.test @@ -0,0 +1,76 @@ +// TODO nf-core: Once you have added the required tests, please run the following command to build this file: +// nf-core modules test segmentation/infantfs +nextflow_process { + + name "Test Process SEGMENTATION_INFANTFS" + script "../main.nf" + process "SEGMENTATION_INFANTFS" + + tag "modules" + tag "modules_" + tag "segmentation" + tag "segmentation/infantfs" + + // TODO nf-core: Change the test name preferably indicating the test-data and file-format used + test("sarscov2 - bam") { + + // TODO nf-core: If you are created a test for a chained module + // (the module requires running more than one process to generate the required output) + // add the 'setup' method here. + // You can find more information about how to use a 'setup' method in the nf-test docs (https://www.nf-test.com/docs/testcases/setup/). + + when { + process { + """ + // TODO nf-core: define inputs of the process here. Example: + + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + process.out, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + //TODO nf-core: Add all required assertions to verify the test output. + // See https://nf-co.re/docs/developing/testing/assertions for more information and examples. + ) + } + + } + + // TODO nf-core: Change the test name preferably indicating the test-data and file-format used but keep the " - stub" suffix. + test("sarscov2 - bam - stub") { + + options "-stub" + + when { + process { + """ + // TODO nf-core: define inputs of the process here. Example: + + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/local/segmentation/mcribs.nf b/modules/local/segmentation/mcribs.nf deleted file mode 100644 index 63f4dc6e..00000000 --- a/modules/local/segmentation/mcribs.nf +++ /dev/null @@ -1,96 +0,0 @@ -process SEGMENTATION_MCRIBS { - tag "$meta.id" - label 'process_high' - - container 'gagnonanthony/nf-pediatric-mcribs:2.1.0' - - input: - tuple val(meta), path(t2), path(fs_license), path(t1) // ** T1 is optional ** // - - output: - tuple val(meta), path("*_mcribs") , emit: folder - tuple val(meta), path("*_preprocessed_T2w.nii.gz") , emit: anat - path "versions.yml" , emit: versions - - when: - task.ext.when == null || task.ext.when - - script: - def prefix = task.ext.prefix ?: "${meta.id}" - def validage = (meta.age > 27 && meta.age < 44) ? true : false - def age = validage ? "--subjectage ${Math.round(meta.age)}": "--subjectage 44" - def useT1 = t1 ? "--useT1" : "" - def surf = task.ext.surf ? "--surfrecon --inflatesphere --surfreg --surfvol" : "" - def jointhresh = task.ext.jointhresh ? task.ext.surf ? "--deformablejointhresh ${task.ext.jointhresh}" : "" : "" - def fastcollision = task.ext.fastcollision ? task.ext.surf ? "--deformablefastcollision" : "" : "" - def nopialoutside = task.ext.nopialoutside ? task.ext.surf ? "--deformablenoensurepialoutside" : "" : "" - def cortical = task.ext.cortical ? "--cortribbon --cortparc --cortthickness" : "" - def aparcaseg = task.ext.aparcaseg? "--aparc2aseg --apas2aseg" : "" - def stats = task.ext.stats ? "--segstats --parcstats" : "" - def seed = task.ext.seed ?: "1234" - - """ - # Set freesurfer seed for reproducibility and bind license. - # export FREESURFER_SEED=$seed - export FS_LICENSE=\$(realpath $fs_license) - - # MCRIBS requires a specific file structure; creating it. - mkdir -p RawT2 - cp -rL $t2 RawT2/${prefix}.nii.gz - - # If T1 is provided, copy it to the correct location. - if [ -n "$t1" ]; then - mkdir -p RawT1RadiologicalIsotropic - cp -rL $t1 RawT1RadiologicalIsotropic/${prefix}.nii.gz - fi - - MCRIBReconAll -nthreads $task.cpus $useT1 \ - --conform \ - --voxelsize "volumepreserve" \ - --tissueseg \ - $age \ - $surf \ - $jointhresh \ - $fastcollision \ - $nopialoutside \ - $cortical \ - $aparcaseg \ - $stats \ - ${prefix} - - # Move the output to the expected location. - mv freesurfer/${prefix} ${prefix}_mcribs - mri_convert ${prefix}_mcribs/mri/brain.mgz ${prefix}_preprocessed_T2w.nii.gz - - # If aparc+aseg.mgz exists, convert it to nii.gz. - if [ -f "${prefix}_mcribs/mri/aparc+aseg.mgz" ]; then - mri_convert ${prefix}_mcribs/mri/aparc+aseg.mgz ${prefix}_mcribs/mri/aparc+aseg.nii.gz - fi - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - mcribs: 2.1.0 - ants: \$(antsRegistration --version |& sed '1!d ; s/ANTs Version: //') - fsl: \$(flirt -version 2>&1 | sed -n 's/FLIRT version \\([0-9.]\\+\\)/\\1/p') - freesurfer: \$(recon-all --version | grep -oP '\\d+\\.\\d+\\.\\d+') - END_VERSIONS - """ - - stub: - def prefix = task.ext.prefix ?: "${meta.id}" - - """ - mkdir ${prefix}_mcribs - touch ${prefix}_preprocessed_T2w.nii.gz - - MCRIBReconAll -h - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - mcribs: 2.1.0 - ants: \$(antsRegistration --version |& sed '1!d ; s/ANTs Version: //') - fsl: \$(flirt -version 2>&1 | sed -n 's/FLIRT version \\([0-9.]\\+\\)/\\1/p') - freesurfer: \$(recon-all --version | grep -oP '\\d+\\.\\d+\\.\\d+') - END_VERSIONS - """ -} diff --git a/nextflow.config b/nextflow.config index c6e3331a..0d179240 100644 --- a/nextflow.config +++ b/nextflow.config @@ -23,12 +23,6 @@ params { atlas_name = "BrainnetomeChild" subcortical_rois = "189:224" - // MCRIBS options - mcribs_jointhresh = null - mcribs_fastcollision = false - mcribs_nopialoutside = false - mcribs_seed = 1234 - //** T1 Preprocessing options **// // Denoising t1_denoise_number_of_coils = 1 @@ -376,8 +370,6 @@ profiles { } gpu { docker.runOptions = '-u $(id -u):$(id -g) --gpus all' - // ** Setting a default GPU type if not already defined ** // - params.gpu_type = params.gpu_type ?: 'h100' // ** GPU config ** // includeConfig 'conf/gpu.config' diff --git a/nextflow_schema.json b/nextflow_schema.json index fc3f60af..7467ba7f 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -87,12 +87,12 @@ "title": "Segmentation Options", "type": "object", "fa_icon": "fas fa-brain", - "description": "Options for FreeSurfer, FastSurfer, and/or M-CRIB-S processing. Only relevant if you select the segmentation profile.", - "help_text": "These options are used to configure the FreeSurfer, FastSurfer and/or M-CRIB-S processing steps in the pipeline.", + "description": "Options for FreeSurfer or FastSurfer processing. Only relevant if you select the segmentation profile.", + "help_text": "These options are used to configure the FreeSurfer or FastSurfer processing steps in the pipeline.", "properties": { "method": { "type": "string", - "description": "Method to use to perform surface reconstruction and cortical/subcortical segmentation. Will only affect subjects > 3 months of age. Recon-all-clinical is highly recommended for subjects between 3 months and 5 years old. Options include recon-all, recon-all-clinical, or FastSurfer.", + "description": "Method to use to perform surface reconstruction and cortical/subcortical segmentation. Will only affect subjects > 1 year of age. Recon-all-clinical is highly recommended for subjects between 1 year and 5 years old (and above). Options include recon-all, recon-all-clinical, or FastSurfer.", "fa_icon": "fas fa-brain", "default": "recon-all-clinical", "enum": ["recon-all-clinical", "recon-all", "fastsurfer"], @@ -130,38 +130,11 @@ }, "fs_output_dir": { "type": "string", - "description": "Path to FreeSurfer/FastSurfer/M-CRIB-S output directory.", + "description": "Path to FreeSurfer/FastSurfer output directory.", "fa_icon": "fas fa-folder-open", "help_text": "Default location will be alongside `--outdir`. For more information, please see the [documentation](https://scilus.github.io/sf-pediatric/guides/outputs/)", "format": "directory-path", "hidden": false - }, - "mcribs_jointhresh": { - "type": "number", - "description": "Join threshold used in the MCRIBS surface reconstruction step.", - "fa_icon": "fas fa-brain", - "hidden": true - }, - "mcribs_fastcollision": { - "type": "boolean", - "description": "Use deformable fast collision test in the MCRIBS surface reconstruction step.", - "fa_icon": "fas fa-brain", - "default": false, - "hidden": true - }, - "mcribs_nopialoutside": { - "type": "boolean", - "description": "Do not ensure pial is outside of WM in the MCRIBS surface reconstruction step.", - "fa_icon": "fas fa-brain", - "default": false, - "hidden": true - }, - "mcribs_seed": { - "type": "number", - "description": "Seed used in the MCRIBS surface reconstruction step.", - "fa_icon": "fas fa-brain", - "default": 1234, - "hidden": true } } }, diff --git a/subworkflows/local/segmentation/segmentation.nf b/subworkflows/local/segmentation/segmentation.nf index f8c6d1ae..9a15690b 100644 --- a/subworkflows/local/segmentation/segmentation.nf +++ b/subworkflows/local/segmentation/segmentation.nf @@ -2,7 +2,8 @@ include { SEGMENTATION_FASTSURFER as FASTSURFER } from '../../../modules/nf-neuro/segmentation/fastsurfer/main' include { SEGMENTATION_FSRECONALL as RECONALL } from '../../../modules/nf-neuro/segmentation/fsreconall/main' include { SEGMENTATION_RECONALLCLINICAL as RECONALLCLINICAL } from '../../../modules/local/segmentation/reconallclinical/main' -include { SEGMENTATION_MCRIBS as MCRIBS } from '../../../modules/local/segmentation/mcribs' +include { SEGMENTATION_BIBSNET as BIBSNET } from '../../../modules/local/segmentation/bibsnet' +include { SEGMENTATION_INFANTFS as INFANTFS } from '../../../modules/local/segmentation/infantfs' include { ATLASES_FSLR2FSAVERAGE as ATLASES_FSLR2FSAVERAGE } from '../../../modules/local/atlases/fslr2fsaverage' include { ATLASES_FSAVERAGE2SUBJECT as ATLASES_FSAVERAGE2SUBJECT } from '../../../modules/local/atlases/fsaverage2subject' @@ -14,14 +15,12 @@ workflow SEGMENTATION { take: ch_t1 // channel: [ val(meta), [ t1 ] ] ch_t2 // channel: [ val(meta), [ t2 ] ] - ch_coreg // channel: [ val(meta), [ anat ] ] ch_atlas // channel: [ atlas ] ch_fs_license // channel: [ fs_license ] ch_mni152 // channel: [ mni152 ] ch_intermediate_template // channel: [ val(meta), [ intermediate_template ], [ transformations ] ] ch_fslr // channel: [ fslr ] ch_fsaverage // channel: [ fsaverage ] - ch_fsaverage_alt // channel: [ fsaverage_alt ] main: @@ -32,17 +31,16 @@ workflow SEGMENTATION { // ch_seg = ch_t1 .join(ch_t2, remainder: true) - .join(ch_coreg, remainder: true) .combine(ch_fs_license) .branch { it -> fastsurfer: it[0].age >= 5 && it[0].age <= 18 && params.method == 'fastsurfer' - return [it[0], it[1], it[4]] + return [it[0], it[1], it[3]] freesurfer: it[0].age >= 5 && it[0].age <= 18 && params.method == "recon-all" - return [it[0], it[1], it[4]] - clinical: it[0].age >= 0.25 && it[0].age <= 18 && params.method == "recon-all-clinical" - return [it[0], it[1] ?: it[2], it[4]] + return [it[0], it[1], it[3]] + clinical: it[0].age >= 1 && it[0].age <= 18 && params.method == "recon-all-clinical" + return [it[0], it[1] ?: it[2], it[3]] infant: true - return [it[0], it[2], it[4], it[3] ?: []] + return [it[0], it[1] ?: [], it[2] ?: []] } // ** FastSurfer ** // @@ -57,20 +55,39 @@ workflow SEGMENTATION { RECONALLCLINICAL ( ch_seg.clinical ) ch_versions = ch_versions.mix(RECONALLCLINICAL.out.versions) - // ** For infant, it's a bit trickier, as MCRIBS do not ** // - // ** perform preprocessing, so we need to do it (done in pediatric.nf). ** // - // ** Run MCRIBS ** // - MCRIBS ( ch_seg.infant ) - ch_versions = ch_versions.mix(MCRIBS.out.versions) - // ch_multiqc_files = ch_multiqc_files.mix(MCRIBS.out.zip.collect{it[1]}) + // ** output the final t1w image ** // + ch_t1w = channel.empty() + .mix(FASTSURFER.out.final_t1) + .mix(RECONALL.out.final_t1) + .mix(RECONALLCLINICAL.out.final_t1) + .join(ch_t1, remainder: true) + .map{ meta, final_t1, t1 -> + return [meta, final_t1 ?: t1] + } + + // ** Use BIBSNet for infant segmentation as a prior for infant freesurfer ** // + BIBSNET ( ch_seg.infant ) + + // ** Run infant freesurfer using segmentation from BIBSNet ** // + ch_infantfs = ch_seg.infant + .join(BIBSNET.out.t1_dseg, remainder: true) + .join(BIBSNET.out.t1_brain_mask, remainder: true) + .join(BIBSNET.out.t2_dseg, remainder: true) + .join(BIBSNET.out.t2_brain_mask, remainder: true) + .combine(ch_fs_license) + .map{ meta, t1, t2, t1_dseg, t1_brain_mask, t2_dseg, t2_brain_mask, license -> + return [meta, t2 ?: t1, t2_dseg ?: t1_dseg, t2_brain_mask ?: t1_brain_mask, license] + } + + INFANTFS ( ch_infantfs ) // ** T2w outputs ** // - // ** Keeping the MCRIBS output if available, otherwise mix in the ch_t2 ** // + // ** Keeping the INFANTFS output if available, otherwise mix in the ch_t2 ** // ch_t2w = ch_t2 - .join(MCRIBS.out.anat, remainder: true) + .join(INFANTFS.out.image, remainder: true) .map{ - meta, t2, mcribs -> - return [meta, mcribs ?: t2] + meta, t2, image -> + return [meta, image ?: t2] } // @@ -81,7 +98,7 @@ workflow SEGMENTATION { ch_register = FASTSURFER.out.final_t1 .mix(RECONALL.out.final_t1) .mix(RECONALLCLINICAL.out.final_t1) - .mix(MCRIBS.out.anat) + .mix(INFANTFS.out.image) .combine(ch_mni152) .join(ch_intermediate_template, remainder: true) .branch { meta, anat, mni152, int_template, _int_transfo -> @@ -103,15 +120,9 @@ workflow SEGMENTATION { .combine(ch_fslr) .combine(ch_fsaverage) .combine(ch_fs_license) - .map{ atlas, fslr, fsaverage, license -> [[id: "fsaverage"], atlas, fslr, fsaverage, license] } - ch_fslr2fsaverage_alt = ch_atlas - .combine(ch_fslr) - .combine(ch_fsaverage_alt) - .combine(ch_fs_license) - .map{ atlas, fslr, fsaverage, license -> [[id: "fsaverage_alt"], atlas, fslr, fsaverage, license] } - ch_fslr2fsaverage = ch_fslr2fsaverage - .mix(ch_fslr2fsaverage_alt) - .filter{ it -> it[3] } // Filter out if alternative fsaverage is not provided + .map{ atlas, fslr, fsaverage, license -> + [[id: "fsaverage"], atlas, fslr, fsaverage, license] + } ATLASES_FSLR2FSAVERAGE ( ch_fslr2fsaverage ) ch_versions = ch_versions.mix(ATLASES_FSLR2FSAVERAGE.out.versions) @@ -122,7 +133,7 @@ workflow SEGMENTATION { ch_temp_transforms = FASTSURFER.out.final_t1 .mix(RECONALL.out.final_t1) .mix(RECONALLCLINICAL.out.final_t1) - .mix(MCRIBS.out.anat) + .mix(INFANTFS.out.image) .join(REGISTRATION_ANTS.out.backward_image_transform) .combine(ATLASES_FSLR2FSAVERAGE.out.subcortical.map{ it -> it[1] }.first()) // extract the path from the emitted tuple .join(ch_intermediate_template, remainder: true) @@ -141,29 +152,22 @@ workflow SEGMENTATION { // // MODULE: Run FSAVERAGE2SUBJECT mapping // - ch_fsaverage2subject_fs = RECONALLCLINICAL.out.folder + ch_fsaverage2subject = channel.empty() + .mix(RECONALLCLINICAL.out.folder) .mix(RECONALL.out.recon_all_out_folder) .mix(FASTSURFER.out.fastsurferdirectory) + .mix(INFANTFS.out.folder) .combine(ATLASES_FSLR2FSAVERAGE.out.fsaverage - .filter{ meta, _fsaverage -> meta.id == "fsaverage" } - .map{ it -> it[1] }) // extract the path from the emitted tuple - .join(REGISTRATION_ANTSAPPLYTRANSFORMS.out.warped_image) - .combine(ch_fs_license) - ch_fsaverage2subject_alt = MCRIBS.out.folder - .combine(ATLASES_FSLR2FSAVERAGE.out.fsaverage - .filter{ meta, _fsaverage -> meta.id == "fsaverage_alt" } .map{ it -> it[1] }) // extract the path from the emitted tuple .join(REGISTRATION_ANTSAPPLYTRANSFORMS.out.warped_image) .combine(ch_fs_license) - ch_fsaverage2subject = ch_fsaverage2subject_fs - .mix(ch_fsaverage2subject_alt) ATLASES_FSAVERAGE2SUBJECT ( ch_fsaverage2subject, ATLASES_FSLR2FSAVERAGE.out.atlas_name.first() ) ch_versions = ch_versions.mix(ATLASES_FSAVERAGE2SUBJECT.out.versions) emit: // ** Processed anatomical image ** // - t1 = ch_t1 // channel: [ val(meta), [ t1 ] ] + t1 = ch_t1w // channel: [ val(meta), [ t1 ] ] t2 = ch_t2w // channel: [ val(meta), [ t2 ] ] // ** Segmentation ** // diff --git a/subworkflows/local/templates/main.nf b/subworkflows/local/templates/main.nf index 974036f4..26e8b440 100644 --- a/subworkflows/local/templates/main.nf +++ b/subworkflows/local/templates/main.nf @@ -14,16 +14,46 @@ def createCohortChannel(channel, cohort) { } } +// Function to compute md5 checksum of a folder // +def computeDirMd5(dirPath) { + def md = java.security.MessageDigest.getInstance('MD5') + def baseDir = file(dirPath) + def all_files = [] + baseDir.eachFileRecurse { f -> + if (f.isFile()) { all_files << f } + } + // Sort by path relative to the base directory — filesystem-location-independent + all_files.sort { a, b -> + baseDir.relativize(a).toString() <=> baseDir.relativize(b).toString() + } + all_files.each { f -> + md.update(f.bytes) + } + return md.digest().encodeHex().toString() +} + workflow TEMPLATES { main: ch_versions = channel.empty() def templates_path = "${params.templates_download_path}/templates" + def expected_md5 = "8c74f1635d69969a6b67f098d569a0b7" + + // Compute a deterministic md5 checksum of the templates folder to check if it has changed + def needs_download = true + if (file(templates_path).exists() && file(templates_path).isDirectory()) { + def local_md5 = computeDirMd5(templates_path) + if (local_md5 == expected_md5) { + log.info "Templates already exist at ${templates_path} and are up-to-date, skipping download." + needs_download = false + } else { + log.info "Templates already exist at ${templates_path} but are outdated, re-downloading." + file(templates_path).deleteDir() + } + } - if ( file(templates_path).exists() ) { - log.info "Templates already exist at ${templates_path}, skipping download." - } else { + if ( needs_download ) { log.info "Downloading templates..." try { "mkdir -p ${params.templates_download_path}".execute().waitFor() diff --git a/subworkflows/local/utils/fetch_derivatives.nf b/subworkflows/local/utils/fetch_derivatives.nf index 10caaf76..692000db 100644 --- a/subworkflows/local/utils/fetch_derivatives.nf +++ b/subworkflows/local/utils/fetch_derivatives.nf @@ -65,8 +65,8 @@ workflow FETCH_DERIVATIVES { def session = parts.find { it.startsWith('ses-') } ?: "" def run = parts.find { it.startsWith('run-') } ?: "" def age = getAge(id, participantData, session) - def tempAge = age.toFloat() > 25 ? Math.abs((age.toFloat() - 35) / 52) : age.toFloat() - def priors = fetchPriors(tempAge) + age = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() + def priors = fetchPriors(age) def metadata = [id: id, session: session, run: run, age: age, fa: priors.fa, ad: priors.ad, rd: priors.rd, md: priors.md, rd_min: priors.rd_min, rd_max: priors.rd_max] return [metadata, file] @@ -99,8 +99,8 @@ workflow FETCH_DERIVATIVES { def session = parts.find { it.startsWith('ses-') } ?: "" def run = parts.find { it.startsWith('run-') } ?: "" def age = getAge(id, participantData, session) - def tempAge = age.toFloat() > 25 ? Math.abs((age.toFloat() - 35) / 52) : age.toFloat() - def priors = fetchPriors(tempAge) + age = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() + def priors = fetchPriors(age) def metadata = [id: id, session: session, run: run, age: age, fa: priors.fa, ad: priors.ad, rd: priors.rd, md: priors.md, rd_min: priors.rd_min, rd_max: priors.rd_max] def type = file.name.contains('T1w') ? 'T1w' : 'T2w' @@ -124,8 +124,8 @@ workflow FETCH_DERIVATIVES { def session = parts.find { it.startsWith('ses-') } ?: "" def run = parts.find { it.startsWith('run-') } ?: "" def age = getAge(id, participantData, session) - def tempAge = age.toFloat() > 25 ? Math.abs((age.toFloat() - 35) / 52) : age.toFloat() - def priors = fetchPriors(tempAge) + age = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() + def priors = fetchPriors(age) def metadata = [id: id, session: session, run: run, age: age, fa: priors.fa, ad: priors.ad, rd: priors.rd, md: priors.md, rd_min: priors.rd_min, rd_max: priors.rd_max] def type = file.name.contains('warp') ? 'warp' : 'affine' @@ -157,8 +157,8 @@ workflow FETCH_DERIVATIVES { def session = parts.find { it.startsWith('ses-') } ?: "" def run = parts.find { it.startsWith('run-') } ?: "" def age = getAge(id, participantData, session) - def tempAge = age.toFloat() > 25 ? Math.abs((age.toFloat() - 35) / 52) : age.toFloat() - def priors = fetchPriors(tempAge) + age = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() + def priors = fetchPriors(age) def metadata = [id: id, session: session, run: run, age: age, fa: priors.fa, ad: priors.ad, rd: priors.rd, md: priors.md, rd_min: priors.rd_min, rd_max: priors.rd_max] return [metadata, file] @@ -173,8 +173,8 @@ workflow FETCH_DERIVATIVES { def session = parts.find { it.startsWith('ses-') } ?: "" def run = parts.find { it.startsWith('run-') } ?: "" def age = getAge(id, participantData, session) - def tempAge = age.toFloat() > 25 ? Math.abs((age.toFloat() - 35) / 52) : age.toFloat() - def priors = fetchPriors(tempAge) + age = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() + def priors = fetchPriors(age) def metadata = [id: id, session: session, run: run, age: age, fa: priors.fa, ad: priors.ad, rd: priors.rd, md: priors.md, rd_min: priors.rd_min, rd_max: priors.rd_max] return [metadata, file] @@ -192,8 +192,8 @@ workflow FETCH_DERIVATIVES { def session = parts.find { it.startsWith('ses-') } ?: "" def run = parts.find { it.startsWith('run-') } ?: "" def age = getAge(id, participantData, session) - def tempAge = age.toFloat() > 25 ? Math.abs((age.toFloat() - 35) / 52) : age.toFloat() - def priors = fetchPriors(tempAge) + age = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() + def priors = fetchPriors(age) def metadata = [id: id, session: session, run: run, age: age, fa: priors.fa, ad: priors.ad, rd: priors.rd, md: priors.md, rd_min: priors.rd_min, rd_max: priors.rd_max] return [metadata, file] @@ -227,8 +227,8 @@ workflow FETCH_DERIVATIVES { def session = parts.find { it.startsWith('ses-') } ?: "" def run = parts.find { it.startsWith('run-') } ?: "" def age = getAge(id, participantData, session) - def tempAge = age.toFloat() > 25 ? Math.abs((age.toFloat() - 35) / 52) : age.toFloat() - def priors = fetchPriors(tempAge) + age = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() + def priors = fetchPriors(age) def metadata = [id: id, session: session, run: run, age: age, fa: priors.fa, ad: priors.ad, rd: priors.rd, md: priors.md, rd_min: priors.rd_min, rd_max: priors.rd_max] return [metadata, file] @@ -256,8 +256,8 @@ workflow FETCH_DERIVATIVES { def session = parts.find { it.startsWith('ses-') } ?: "" def run = parts.find { it.startsWith('run-') } ?: "" def age = getAge(id, participantData, session) - def tempAge = age.toFloat() > 25 ? Math.abs((age.toFloat() - 35) / 52) : age.toFloat() - def priors = fetchPriors(tempAge) + age = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() + def priors = fetchPriors(age) def metadata = [id: id, session: session, run: run, age: age, fa: priors.fa, ad: priors.ad, rd: priors.rd, md: priors.md, rd_min: priors.rd_min, rd_max: priors.rd_max] return [metadata, file] @@ -276,8 +276,8 @@ workflow FETCH_DERIVATIVES { def session = parts.find { it -> it.startsWith('ses-') } ?: "" def run = parts.find { it -> it.startsWith('run-') } ?: "" def age = getAge(id, participantData, session) - def tempAge = age.toFloat() > 25 ? Math.abs((age.toFloat() - 35) / 52) : age.toFloat() - def priors = fetchPriors(tempAge) + age = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() + def priors = fetchPriors(age) def metadata = [id: id, session: session, run: run, age: age, fa: priors.fa, ad: priors.ad, rd: priors.rd, md: priors.md, rd_min: priors.rd_min, rd_max: priors.rd_max] return [metadata, file] diff --git a/subworkflows/local/utils_nfcore_sf_pediatric_pipeline/main.nf b/subworkflows/local/utils_nfcore_sf_pediatric_pipeline/main.nf index 0a238fdf..e8bc99d6 100644 --- a/subworkflows/local/utils_nfcore_sf_pediatric_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_sf_pediatric_pipeline/main.nf @@ -41,6 +41,13 @@ workflow PIPELINE_INITIALISATION { ch_versions = channel.empty() + // + // Check if the gpu profile is used and validate if a gpu type is provided. + // + if ( workflow.profile.contains('gpu') && !params.gpu_type ) { + error "ERROR: The 'gpu' profile is used but no GPU type is provided. Please provide a GPU type using --gpu_type." + } + // // Print version and exit if required and dump pipeline parameters to JSON file // @@ -131,8 +138,8 @@ workflow PIPELINE_INITIALISATION { } // Temp age in years for priors prediction (only if data is over 25, as we assume it is gestational age). // ** Setting to a minimum of 0.04 (~ 2 weeks) to avoid negative values for the priors prediction ** // - def tempAge = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() - def priors = fetchPriors(tempAge) + age = age.toFloat() > 25 ? Math.max(Math.abs((age.toFloat() - 40) / 52), 0.04) : age.toFloat() + def priors = fetchPriors(age) // ** Instantiate a variable that will collect prints related to BIDS file matching ** // // ** to be printed in a log file later ** // @@ -700,7 +707,7 @@ def matchFilesToDWI(Map dwiJson, dwiFilename, Map revJson, revFilename, Map asso } // If no B0Field* are present, check for IntendedFor - def intendedFor = assocJson?.IntendedFor + def intendedFor = normalizeToList(assocJson?.IntendedFor) if (intendedFor) { def matches = intendedFor.any { target -> def targetName = target.toString().replaceAll("^bids::", "").split('/')[-1] @@ -1454,8 +1461,8 @@ def buildMethodsDescription() { if ( !enabled('segmentation') ) return "" def parts = [] parts << """
Cortical and sub-cortical segmentation
""" - parts << "Cortical and subcortical segmentation was performed using ${params.method == "fastsurfer" ? "FastSurfer (Henschel et al., 2020)" : params.method == "recon-all" ? " recon-all from FreeSurfer (Fischl, 2012)" : "recon-all-clinical from Freesurfer (Fischl, 2012; Billot et al., 2023; Iglesias et al., 2023)"} on the T1w anatomical images." - parts << "For younger participants (< 3 months old), cortical and subcortical segmentation was performed using the M-CRIB-S pipeline (Adamson et al., 2020)." + parts << "Cortical and subcortical segmentation and surface reconstruction was performed using ${params.method == "fastsurfer" ? "FastSurfer (Henschel et al., 2020)" : params.method == "recon-all" ? " recon-all from FreeSurfer (Fischl, 2012)" : "recon-all-clinical from Freesurfer (Fischl, 2012; Billot et al., 2023; Iglesias et al., 2023)"} on the T1w anatomical images." + parts << "For younger participants (< 1 year old), cortical and subcortical segmentation was performed initially using BIBSnet (Hendrickson et al., 2026). Then, the BIBSnet segmentation was added as input to infant-recon-all to generate the final surfaces and cortical segmentation (Zöllei et al., 2020)." parts << "Following segmentation, the ${params.atlas_name == "BrainnetomeChild" ? "Brainnetome atlas for preadolescents (Li et al., 2023)" : "${params.atlas_name} atlas"} was mapped from fsLR-32k space to subject-space using surface-based registration methods from FreeSurfer (Fischl, 2012) and then converted into voxel labels." parts << "Volume, surface area, and cortical thickness were measured for each parcel and outputted in tab-separated value files." @@ -1563,6 +1570,8 @@ def toolBibliographyText() { "Zhang et al., 2012" : "
  • Zhang, H., Schneider, T., Wheeler-Kingshott, C. A., & Alexander, D. C. (2012). NODDI: Practical in vivo neurite orientation dispersion and density imaging of the human brain. NeuroImage, 61(4), 1000–1016. https://doi.org/10.1016/j.neuroimage.2012.03.072
  • ", "Pasternak et al., 2009" : "
  • Pasternak, O., Sochen, N., Gur, Y., Intrator, N., & Assaf, Y. (2009). Free water elimination and mapping from diffusion MRI. Magnetic Resonance in Medicine, 62(3), 717–730. https://doi.org/10.1002/mrm.22055
  • ", "Renauld et al., 2026" : "
  • Renauld, E., Boré, A., Poirier, C., Valcourt-Caron, A., Karan, P., Théberge, A., Théaud, G., Edde, M., Poulin, P., Girard, G., Houde, J.-C., Gagnon, A., St-Onge, E., Little, G., Legarreta, J. H., Thoumyre, S., Grenier, G., El Yamani, Z., Ocampo Pineda, M., … Descoteaux, M. (2026). Tractography analysis with the scilpy toolbox. Aperture Neuro, 6. https://doi.org/10.52294/001c.154022
  • ", + "Hendrickson et al., 2026" : "
  • Hendrickson, T. J., Reiners, P., Moore, L. A., Lundquist, J. T., Fayzullobekova, B., Perrone, A. J., Lee, E. G., Moser, J., Day, T. K. M., Alexopoulos, D., Styner, M., Kardan, O., Chamberlain, T. A., Mummaneni, A., Caldas, H. A., Bower, B., Stoyell, S., Martin, T., Sung, S., … Feczko, E. (2026). BIBSNet: A deep learning baby image brain segmentation network for MRI scans. Developmental Cognitive Neuroscience, 79, 101706. https://doi.org/10.1016/j.dcn.2026.101706
  • ", + "Zöllei et al., 2020" : "
  • Zöllei, L., Iglesias, J. E., Ou, Y., Grant, P. E., & Fischl, B. (2020). Infant FreeSurfer: An automated segmentation and surface extraction pipeline for T1-weighted neuroimaging data of infants 0–2 years. NeuroImage, 218, 116946. https://doi.org/10.1016/j.neuroimage.2020.116946
  • ", // Preprints and in-preparation works "Gagnon et al., 2026" : "
  • Gagnon, A., Boré, A., Valcourt Caron, A., Edde, M., Thoumyre, S., Lepage, J.-F., Talati, A., Posner, J., Ouellet, A., Brunet, M. A., Takser, L., Rheault, F., & Descoteaux, M. (2026). sf-pediatric: A robust and age-adaptable end-to-end pipeline for pediatric diffusion MRI. bioRxiv. https://doi.org/10.64898/2026.01.19.700454
  • ", ] diff --git a/tests/chained.nf.test b/tests/chained.nf.test index 649f385c..0eb4e074 100644 --- a/tests/chained.nf.test +++ b/tests/chained.nf.test @@ -4,7 +4,7 @@ nextflow_pipeline { name "Test sf-pediatric all profiles chained" script "../main.nf" -/* + test("Segmentation + connectomics profiles - should run successfully") { when { @@ -42,7 +42,7 @@ nextflow_pipeline { } } } -*/ + test("Connectomics + tracking profiles - should run successfully") { when { diff --git a/tests/chained.nf.test.snap b/tests/chained.nf.test.snap index 72a12385..10070556 100644 --- a/tests/chained.nf.test.snap +++ b/tests/chained.nf.test.snap @@ -34,6 +34,9 @@ "mrtrix": "3.0.7", "fsl": 6.0 }, + "BIBSNET": { + "bibsnet": "3.7.0" + }, "CONNECTIVITY_AFDFIXEL": { "scilpy": "2.3.0" }, @@ -82,11 +85,8 @@ "IMAGE_POWDERAVERAGE": { "scilpy": "2.2.2" }, - "MCRIBS": { - "mcribs": "2.1.0", - "ants": "2.5.3-g98bf76d", - "fsl": 6.0, - "freesurfer": "7.1.1" + "INFANTFS": { + "freesurfer": "8.2.0" }, "NORMALIZE_DWI": { "scilpy": "2.2.2", @@ -266,7 +266,6 @@ "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T2w_desc-preproc_T2w.json", "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T2w_desc-preproc_T2w.nii.gz", "sub-test1/ses-baseline/dwi", - "sub-test1/ses-baseline/dwi/bundles", "sub-test1/ses-baseline/dwi/sub-test1_ses-baseline_desc-brain_mask.json", "sub-test1/ses-baseline/dwi/sub-test1_ses-baseline_desc-brain_mask.nii.gz", "sub-test1/ses-baseline/dwi/sub-test1_ses-baseline_desc-fwc_dwi.json", @@ -476,7 +475,6 @@ "sub-test1/ses-time1/anat/sub-test1_ses-time1_space-T2w_desc-preproc_T2w.json", "sub-test1/ses-time1/anat/sub-test1_ses-time1_space-T2w_desc-preproc_T2w.nii.gz", "sub-test1/ses-time1/dwi", - "sub-test1/ses-time1/dwi/bundles", "sub-test1/ses-time1/dwi/sub-test1_ses-time1_desc-brain_mask.json", "sub-test1/ses-time1/dwi/sub-test1_ses-time1_desc-brain_mask.nii.gz", "sub-test1/ses-time1/dwi/sub-test1_ses-time1_desc-fwc_dwi.json", @@ -726,7 +724,6 @@ "sub-test2/anat/sub-test2_run-02_space-T2w_desc-preproc_T2w.json", "sub-test2/anat/sub-test2_run-02_space-T2w_desc-preproc_T2w.nii.gz", "sub-test2/dwi", - "sub-test2/dwi/bundles", "sub-test2/dwi/sub-test2_run-01_desc-brain_mask.json", "sub-test2/dwi/sub-test2_run-01_desc-brain_mask.nii.gz", "sub-test2/dwi/sub-test2_run-01_desc-fwc_dwi.json", @@ -1074,7 +1071,6 @@ "sub-test3/anat/sub-test3_space-T2w_desc-preproc_T2w.json", "sub-test3/anat/sub-test3_space-T2w_desc-preproc_T2w.nii.gz", "sub-test3/dwi", - "sub-test3/dwi/bundles", "sub-test3/dwi/sub-test3_desc-brain_mask.json", "sub-test3/dwi/sub-test3_desc-brain_mask.nii.gz", "sub-test3/dwi/sub-test3_desc-fwc_dwi.json", @@ -1308,10 +1304,10 @@ "tpl-MNIInfant_metadata.json" ] ], - "timestamp": "2026-07-13T15:44:28.623925", + "timestamp": "2026-08-15T09:45:13.833584", "meta": { "nf-test": "0.9.5", - "nextflow": "26.04.4" + "nextflow": "26.04.6" } }, "Bundling + tracking profiles - should run successfully": { @@ -2593,5 +2589,199 @@ "nf-test": "0.9.5", "nextflow": "26.04.4" } + }, + "Segmentation + connectomics profiles - should run successfully": { + "content": [ + 31, + { + "ATLASES_FSAVERAGE2SUBJECT": { + "freesurfer": "8.2.0", + "scilpy": "2.2.2", + "workbench": "2.1.0" + }, + "ATLASES_FSLR2FSAVERAGE": { + "freesurfer": "8.2.0", + "workbench": "2.1.0" + }, + "BIBSNET": { + "bibsnet": "3.7.0" + }, + "INFANTFS": { + "freesurfer": "8.2.0" + }, + "RECONALLCLINICAL": { + "freesurfer": "8.0.0" + }, + "REGISTRATION_ANTS": { + "ants": "2.6.2", + "imagemagick": "6.9.12-98", + "mrtrix": "3.0.7", + "scilpy": "2.2.2" + }, + "REGISTRATION_ANTSAPPLYTRANSFORMS": { + "ants": "2.6.2", + "imagemagick": "6.9.12-98", + "mrtrix": "3.0.7", + "scilpy": "2.2.2" + }, + "Workflow": { + "scilus/sf_pediatric": "v0.3.0" + } + }, + [ + "BrainnetomeChild_area_lh_stats.tsv", + "BrainnetomeChild_area_rh_stats.tsv", + "BrainnetomeChild_thickness_lh_stats.tsv", + "BrainnetomeChild_thickness_rh_stats.tsv", + "BrainnetomeChild_volume_lh_stats.tsv", + "BrainnetomeChild_volume_rh_stats.tsv", + "BrainnetomeChild_volume_subcortical_stats.tsv", + "dataset_description.json", + "multiqc", + "multiqc/global_multiqc_data", + "multiqc/global_multiqc_plots", + "multiqc/global_multiqc_report.html", + "participants.tsv", + "pipeline_info", + "pipeline_info/sf_pediatric_software_mqc_versions.yml", + "sub-test1", + "sub-test1/ses-baseline", + "sub-test1/ses-baseline/anat", + "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_desc-labels_dseg.tsv", + "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_dseg.json", + "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_dseg.nii.gz", + "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_hemi-left_stat-surfacearea.tsv", + "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_hemi-left_stat-thickness.tsv", + "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_hemi-left_stat-volume.tsv", + "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_hemi-right_stat-surfacearea.tsv", + "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_hemi-right_stat-thickness.tsv", + "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_hemi-right_stat-volume.tsv", + "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_stat-subcortical.tsv", + "sub-test1/ses-baseline/figures", + "sub-test1/ses-baseline/figures/sub-test1_ses-baseline_gradients_mqc.png", + "sub-test1/ses-baseline/multiqc", + "sub-test1/ses-baseline/multiqc/sub-test1_multiqc_data", + "sub-test1/ses-baseline/multiqc/sub-test1_multiqc_plots", + "sub-test1/ses-baseline/multiqc/sub-test1_multiqc_report.html", + "sub-test1/ses-time1", + "sub-test1/ses-time1/anat", + "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_desc-labels_dseg.tsv", + "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_dseg.json", + "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_dseg.nii.gz", + "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_hemi-left_stat-surfacearea.tsv", + "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_hemi-left_stat-thickness.tsv", + "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_hemi-left_stat-volume.tsv", + "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_hemi-right_stat-surfacearea.tsv", + "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_hemi-right_stat-thickness.tsv", + "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_hemi-right_stat-volume.tsv", + "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_stat-subcortical.tsv", + "sub-test1/ses-time1/figures", + "sub-test1/ses-time1/figures/sub-test1_ses-time1_gradients_mqc.png", + "sub-test1/ses-time1/multiqc", + "sub-test1/ses-time1/multiqc/sub-test1_multiqc_data", + "sub-test1/ses-time1/multiqc/sub-test1_multiqc_plots", + "sub-test1/ses-time1/multiqc/sub-test1_multiqc_report.html", + "sub-test3", + "sub-test3/anat", + "sub-test3/anat/sub-test3_seg-BrainnetomeChild_desc-labels_dseg.tsv", + "sub-test3/anat/sub-test3_seg-BrainnetomeChild_dseg.json", + "sub-test3/anat/sub-test3_seg-BrainnetomeChild_dseg.nii.gz", + "sub-test3/anat/sub-test3_seg-BrainnetomeChild_hemi-left_stat-surfacearea.tsv", + "sub-test3/anat/sub-test3_seg-BrainnetomeChild_hemi-left_stat-thickness.tsv", + "sub-test3/anat/sub-test3_seg-BrainnetomeChild_hemi-left_stat-volume.tsv", + "sub-test3/anat/sub-test3_seg-BrainnetomeChild_hemi-right_stat-surfacearea.tsv", + "sub-test3/anat/sub-test3_seg-BrainnetomeChild_hemi-right_stat-thickness.tsv", + "sub-test3/anat/sub-test3_seg-BrainnetomeChild_hemi-right_stat-volume.tsv", + "sub-test3/anat/sub-test3_seg-BrainnetomeChild_stat-subcortical.tsv", + "sub-test3/figures", + "sub-test3/figures/sub-test3_gradients_mqc.png", + "sub-test3/multiqc", + "sub-test3/multiqc/sub-test3_multiqc_data", + "sub-test3/multiqc/sub-test3_multiqc_plots", + "sub-test3/multiqc/sub-test3_multiqc_report.html", + "sub-test4", + "sub-test4/anat", + "sub-test4/anat/sub-test4_seg-BrainnetomeChild_desc-labels_dseg.tsv", + "sub-test4/anat/sub-test4_seg-BrainnetomeChild_dseg.json", + "sub-test4/anat/sub-test4_seg-BrainnetomeChild_dseg.nii.gz", + "sub-test4/anat/sub-test4_seg-BrainnetomeChild_hemi-left_stat-surfacearea.tsv", + "sub-test4/anat/sub-test4_seg-BrainnetomeChild_hemi-left_stat-thickness.tsv", + "sub-test4/anat/sub-test4_seg-BrainnetomeChild_hemi-left_stat-volume.tsv", + "sub-test4/anat/sub-test4_seg-BrainnetomeChild_hemi-right_stat-surfacearea.tsv", + "sub-test4/anat/sub-test4_seg-BrainnetomeChild_hemi-right_stat-thickness.tsv", + "sub-test4/anat/sub-test4_seg-BrainnetomeChild_hemi-right_stat-volume.tsv", + "sub-test4/anat/sub-test4_seg-BrainnetomeChild_stat-subcortical.tsv", + "sub-test4/figures", + "sub-test4/figures/sub-test4_gradients_mqc.png", + "sub-test4/multiqc", + "sub-test4/multiqc/sub-test4_multiqc_data", + "sub-test4/multiqc/sub-test4_multiqc_plots", + "sub-test4/multiqc/sub-test4_multiqc_report.html" + ], + [ + "", + "sub-test1_ses-baseline", + "sub-test1_ses-baseline/label", + "sub-test1_ses-baseline/label/lh.BrainnetomeChild.annot", + "sub-test1_ses-baseline/label/rh.BrainnetomeChild.annot", + "sub-test1_ses-baseline/mri", + "sub-test1_ses-baseline/mri/transforms", + "sub-test1_ses-baseline/scripts", + "sub-test1_ses-baseline/stats", + "sub-test1_ses-baseline/stats/lh.BrainnetomeChild.stats", + "sub-test1_ses-baseline/stats/rh.BrainnetomeChild.stats", + "sub-test1_ses-baseline/stats/subcortical.BrainnetomeChild.stats", + "sub-test1_ses-baseline/surf", + "sub-test1_ses-baseline/tmp", + "sub-test1_ses-baseline/touch", + "sub-test1_ses-time1", + "sub-test1_ses-time1/label", + "sub-test1_ses-time1/label/lh.BrainnetomeChild.annot", + "sub-test1_ses-time1/label/rh.BrainnetomeChild.annot", + "sub-test1_ses-time1/mri", + "sub-test1_ses-time1/mri/transforms", + "sub-test1_ses-time1/scripts", + "sub-test1_ses-time1/stats", + "sub-test1_ses-time1/stats/lh.BrainnetomeChild.stats", + "sub-test1_ses-time1/stats/rh.BrainnetomeChild.stats", + "sub-test1_ses-time1/stats/subcortical.BrainnetomeChild.stats", + "sub-test1_ses-time1/surf", + "sub-test1_ses-time1/tmp", + "sub-test1_ses-time1/touch", + "sub-test3", + "sub-test3/label", + "sub-test3/label/lh.BrainnetomeChild.annot", + "sub-test3/label/rh.BrainnetomeChild.annot", + "sub-test3/mri", + "sub-test3/mri/transforms", + "sub-test3/scripts", + "sub-test3/stats", + "sub-test3/stats/lh.BrainnetomeChild.stats", + "sub-test3/stats/rh.BrainnetomeChild.stats", + "sub-test3/stats/subcortical.BrainnetomeChild.stats", + "sub-test3/surf", + "sub-test3/tmp", + "sub-test3/touch", + "sub-test4", + "sub-test4/label", + "sub-test4/label/lh.BrainnetomeChild.annot", + "sub-test4/label/rh.BrainnetomeChild.annot", + "sub-test4/mri", + "sub-test4/mri/transforms", + "sub-test4/scripts", + "sub-test4/stats", + "sub-test4/stats/lh.BrainnetomeChild.stats", + "sub-test4/stats/rh.BrainnetomeChild.stats", + "sub-test4/stats/subcortical.BrainnetomeChild.stats", + "sub-test4/surf", + "sub-test4/tmp", + "sub-test4/touch" + ] + ], + "timestamp": "2026-08-15T09:50:25.504892", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } } } \ No newline at end of file diff --git a/tests/data/bids/sub-test3/fmap/sub-test3_acq-dwi_dir-AP_run-01_epi.json b/tests/data/bids/sub-test3/fmap/sub-test3_acq-dwi_dir-AP_run-01_epi.json index d36a3dd4..bfd451bd 100644 --- a/tests/data/bids/sub-test3/fmap/sub-test3_acq-dwi_dir-AP_run-01_epi.json +++ b/tests/data/bids/sub-test3/fmap/sub-test3_acq-dwi_dir-AP_run-01_epi.json @@ -3,7 +3,5 @@ "MagneticFieldStrength": 3, "PhaseEncodingDirection": "j-", "TotalReadoutTime": 0.062, - "IntendedFor": [ - "dwi/sub-test3_dwi.nii.gz" - ] + "IntendedFor": "dwi/sub-test3_dwi.nii.gz" } diff --git a/tests/data/bids/sub-test4/dwi/sub-test4_dwi.json b/tests/data/bids/sub-test4/dwi/sub-test4_dwi.json index a5266e0c..8d4a81d6 100644 --- a/tests/data/bids/sub-test4/dwi/sub-test4_dwi.json +++ b/tests/data/bids/sub-test4/dwi/sub-test4_dwi.json @@ -2,5 +2,6 @@ "Modality": "MR", "MagneticFieldStrength": 3, "PhaseEncodingDirection": "j", - "TotalReadoutTime": 0.062 + "TotalReadoutTime": 0.062, + "B0FieldSource": "dwi" } diff --git a/tests/data/bids/sub-test4/fmap/sub-test4_acq-dwi_dir-AP_epi.json b/tests/data/bids/sub-test4/fmap/sub-test4_acq-dwi_dir-AP_epi.json index e47c7f7e..86015622 100644 --- a/tests/data/bids/sub-test4/fmap/sub-test4_acq-dwi_dir-AP_epi.json +++ b/tests/data/bids/sub-test4/fmap/sub-test4_acq-dwi_dir-AP_epi.json @@ -3,7 +3,5 @@ "MagneticFieldStrength": 3, "PhaseEncodingDirection": "j-", "TotalReadoutTime": 0.062, - "IntendedFor": [ - "dwi/sub-test4_dwi.nii.gz" - ] + "B0FieldIdentifier": "dwi" } diff --git a/tests/gpu.nf.test b/tests/gpu.nf.test new file mode 100644 index 00000000..3725383d --- /dev/null +++ b/tests/gpu.nf.test @@ -0,0 +1,26 @@ +nextflow_pipeline { + + options "-stub-run" + + name "Test sf-pediatric profile tracking" + script "../main.nf" + + profile "gpu" + + test("Tracking + gpu profile without gpu_type - should fail") { + when { + params { + + params.input = "$projectDir/tests/data/bids/" + params.outdir = "$outputDir" + + params.tracking = true + } + } + + then { + assert workflow.failed + assert workflow.stdout.contains("ERROR: The 'gpu' profile is used but no GPU type is provided. Please provide a GPU type using --gpu_type.") + } + } +} diff --git a/tests/segmentation.nf.test.snap b/tests/segmentation.nf.test.snap index b6c86413..9d214f19 100644 --- a/tests/segmentation.nf.test.snap +++ b/tests/segmentation.nf.test.snap @@ -1,7 +1,7 @@ { "Segmentation profile - should run successfully": { "content": [ - 31, + 18, { "ATLASES_FSAVERAGE2SUBJECT": { "freesurfer": "8.2.0", @@ -12,20 +12,11 @@ "freesurfer": "8.2.0", "workbench": "2.1.0" }, - "COREG": { - "ants": "2.6.2", - "imagemagick": "6.9.12-98", - "mrtrix": "3.0.7", - "scilpy": "2.2.2" - }, - "DENOISING_NLMEANS": { - "scilpy": "2.2.2" + "BIBSNET": { + "bibsnet": "3.7.0" }, - "MCRIBS": { - "mcribs": "2.1.0", - "ants": "2.5.3-g98bf76d", - "fsl": 6.0, - "freesurfer": "7.1.1" + "INFANTFS": { + "freesurfer": "8.2.0" }, "RECONALLCLINICAL": { "freesurfer": "8.0.0" @@ -75,23 +66,10 @@ "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_hemi-right_stat-thickness.tsv", "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_hemi-right_stat-volume.tsv", "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_seg-BrainnetomeChild_stat-subcortical.tsv", - "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T1w_desc-brain_mask.json", - "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T1w_desc-brain_mask.nii.gz", - "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T1w_desc-preproc_T1w.json", - "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T1w_desc-preproc_T1w.nii.gz", - "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T2w_desc-brain_mask.json", - "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T2w_desc-brain_mask.nii.gz", - "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T2w_desc-preproc_T1w.json", - "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T2w_desc-preproc_T1w.nii.gz", - "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T2w_desc-preproc_T2w.json", - "sub-test1/ses-baseline/anat/sub-test1_ses-baseline_space-T2w_desc-preproc_T2w.nii.gz", "sub-test1/ses-baseline/multiqc", "sub-test1/ses-baseline/multiqc/sub-test1_multiqc_data", "sub-test1/ses-baseline/multiqc/sub-test1_multiqc_plots", "sub-test1/ses-baseline/multiqc/sub-test1_multiqc_report.html", - "sub-test1/ses-baseline/xfm", - "sub-test1/ses-baseline/xfm/sub-test1_ses-baseline_from-T1w_to-T2w_mode-image_desc-affine_xfm.mat", - "sub-test1/ses-baseline/xfm/sub-test1_ses-baseline_from-T2w_to-T1w_mode-image_desc-affine_xfm.mat", "sub-test1/ses-time1", "sub-test1/ses-time1/anat", "sub-test1/ses-time1/anat/sub-test1_ses-time1_seg-BrainnetomeChild_desc-labels_dseg.tsv", @@ -171,10 +149,10 @@ "sub-test3/touch" ] ], - "timestamp": "2026-07-13T16:03:41.251369", + "timestamp": "2026-08-13T08:47:51.197943", "meta": { "nf-test": "0.9.5", - "nextflow": "26.04.4" + "nextflow": "26.04.6" } } } \ No newline at end of file diff --git a/workflows/sf_pediatric.nf b/workflows/sf_pediatric.nf index c145e476..d61fb361 100644 --- a/workflows/sf_pediatric.nf +++ b/workflows/sf_pediatric.nf @@ -124,24 +124,15 @@ workflow SF_PEDIATRIC { .filter { _meta, t2 -> t2 != null && (!(t2 instanceof List) || !t2.isEmpty()) } .map { meta, t2 -> [meta, t2] } - // Create specific channel for infant subjects, useful for segmentation subworkflow - ch_infant_t1 = ch_t1.filter { meta, _t1 -> meta.age < 0.25 || meta.age > 18 } - ch_infant_t2 = ch_t2.filter { meta, _t2 -> meta.age < 0.25 || meta.age > 18 } - // Fetch infant synthstrip weights ch_synthstrip_weights_infant = channel.fromPath( "$projectDir/assets/synthstrip.infant.1.pt", checkIfExists: true ) - // Condition representing which channel to pass to PREPROC T1w/T2w depending - // on selected profiles - ch_t1_input = !params.tracking ? ch_infant_t1 : ch_t1 - ch_t2_input = !params.tracking ? ch_infant_t2 : ch_t2 - // Create per subject channels for synthstrip weights to ensure infant weights // are matched with infant subjects, else, use default weights (automatically handled in module) - ch_t1_sample_weights = ch_t1_input + ch_t1_sample_weights = ch_t1 .combine(ch_synthstrip_weights_infant) .map { item -> def tuple = (item instanceof List) ? item : [item] @@ -150,7 +141,7 @@ workflow SF_PEDIATRIC { [meta, (meta.age < 2.5 || meta.age > 18) ? weight : []] } - ch_t2_sample_weights = ch_t2_input + ch_t2_sample_weights = ch_t2 .combine(ch_synthstrip_weights_infant) .map { item -> def tuple = (item instanceof List) ? item : [item] @@ -171,12 +162,10 @@ workflow SF_PEDIATRIC { // // SUBWORKFLOW: Run preprocessing on anatomical images. // - reg_t1 = channel.empty() - - if ( params.tracking || params.segmentation ) { + if ( params.tracking ) { // ** Run T1 preprocessing ** // PREPROC_T1W ( - ch_t1_input, + ch_t1, channel.empty(), channel.empty(), channel.empty(), @@ -198,7 +187,7 @@ workflow SF_PEDIATRIC { // ** T2 Preprocessing ** // PREPROC_T2W ( - ch_t2_input, + ch_t2, channel.empty(), channel.empty(), channel.empty(), @@ -239,13 +228,12 @@ workflow SF_PEDIATRIC { COREG ( ch_coreg_input ) ch_versions = ch_versions.mix(COREG.out.versions) // ch_multiqc_files = ch_multiqc_files.mix(COREG.out.zip.collect{it[1]}) - reg_t1 = COREG.out.image_warped ?: channel.empty() } // // SUBWORKFLOW: Run FastSurfer, recon-all, or recon-all-clinical T1 reconstruction with BrainnetomeChild atlas - // Additionally, if infant data is provided, run MCRIBS segmentation. + // Additionally, if infant data is provided, run BIBSNET + infant freesurfer. // if ( params.segmentation ) { @@ -254,32 +242,6 @@ workflow SF_PEDIATRIC { ? channel.fromPath(params.fs_license, checkIfExists: true, followLinks: true) : channel.empty().ifEmpty { error "No license file path provided. Please specify the path using --fs_license parameter." } - // ** Assemble T1w/T2w channels using derivatives for < 0.25 years, ** // - // ** otherwise, raw images. ** // - ch_t1_seg = ch_t1 - .branch { tuple -> - fs: tuple[0].age >= 0.25 && tuple[0].age <= 18 - return tuple - } - ch_t1_seg_proc = PREPROC_T1W.out.t1_final - .branch { tuple -> - mcribs: tuple[0].age < 0.25 || tuple[0].age > 18 - return tuple - } - ch_t1_seg = ch_t1_seg.fs.mix(ch_t1_seg_proc.mcribs) - - ch_t2_seg = ch_t2 - .branch { tuple -> - fs: tuple[0].age >= 0.25 && tuple[0].age <= 18 - return tuple - } - ch_t2_seg_proc = PREPROC_T2W.out.t1_final - .branch { tuple -> - mcribs: tuple[0].age < 0.25 || tuple[0].age > 18 - return tuple - } - ch_t2_seg = ch_t2_seg.fs.mix(ch_t2_seg_proc.mcribs) - // ** Set up the intermediate template based on the subject's age ** ch_tpl0 = TEMPLATES.out.UNCBCPInfant0.map{ tuple -> tuple[1..2, 6] } ch_tpl3 = TEMPLATES.out.UNCBCPInfant3.map{ tuple -> tuple[1..2, 6] } @@ -287,8 +249,8 @@ workflow SF_PEDIATRIC { ch_tpl12 = TEMPLATES.out.UNCBCPInfant12.map{ tuple -> tuple[1..2, 6] } ch_tpl24 = TEMPLATES.out.UNCBCPInfant24.map{ tuple -> tuple[1..2, 6] } - ch_intermediate_template = ch_t1_seg - .join(ch_t2_seg, remainder: true) + ch_intermediate_template = ch_t1 + .join(ch_t2, remainder: true) .combine(ch_tpl0) .combine(ch_tpl3) .combine(ch_tpl6) @@ -338,9 +300,8 @@ workflow SF_PEDIATRIC { SEGMENTATION ( - ch_t1_seg, - ch_t2_seg, - reg_t1, + ch_t1, + ch_t2, channel.fromPath("${params.atlas_folder}/${params.atlas_name}", checkIfExists: true), ch_fs_license, channel.fromPath("$params.templates_download_path/templates/tpl-fsLR/tpl-MNI152NLin6Asym_res-01_desc-brain_T1w.nii.gz", checkIfExists: true), @@ -352,14 +313,14 @@ workflow SF_PEDIATRIC { .mix(ch_intermediate_template.infant24) .mix(ch_intermediate_template.other), channel.fromPath("$params.templates_download_path/templates/tpl-fsLR", checkIfExists: true), - channel.fromPath("$params.templates_download_path/templates/fsaverage", checkIfExists: true), - channel.fromPath("$params.templates_download_path/templates/fsaverage_alt", checkIfExists: true) + channel.fromPath("$params.templates_download_path/templates/fsaverage", checkIfExists: true) ) ch_versions = ch_versions.mix(SEGMENTATION.out.versions) // Collate all segmentation tsv files and output them. // We need to filter files to collate based on metric and hemisphere // Iterate over the expected metrics and hemispheres, and collate files matching those patterns + def expectedHeaders = [:] ch_merged_tsvs = SEGMENTATION.out.tsv .map { _meta, tsvs -> tsvs } .flatten() @@ -375,6 +336,17 @@ workflow SF_PEDIATRIC { // Find columns to keep (not containing "???") def keepIdx = header.indices.findAll { i -> !header[i].contains('???') } + def filteredHeader = keepIdx.collect { i -> header[i] } + + if (!expectedHeaders.containsKey(match)) { + expectedHeaders[match] = filteredHeader + } else { + // Validate that the current header matches the expected header for this metric/hemisphere + // Let's throw a warning instead of an error to allow the workflow to continue, but log the issue + if (filteredHeader != expectedHeaders[match]) { + log.warn "Header mismatch for ${tsv.name}: expected ${expectedHeaders[match]}, found ${filteredHeader}. This may indicate missing cortical/subcortical ROIs for some subjects." + } + } // Rename first column to "sample", filter columns def newHeader = keepIdx.collect { i ->