diff --git a/modules/nf-neuro/registration/cobralab_ants/environment.yml b/modules/nf-neuro/registration/cobralab_ants/environment.yml new file mode 100644 index 00000000..2c00919a --- /dev/null +++ b/modules/nf-neuro/registration/cobralab_ants/environment.yml @@ -0,0 +1,3 @@ +channels: [] +dependencies: [] +name: registration_ants diff --git a/modules/nf-neuro/registration/cobralab_ants/main.nf b/modules/nf-neuro/registration/cobralab_ants/main.nf new file mode 100644 index 00000000..46bb9ef1 --- /dev/null +++ b/modules/nf-neuro/registration/cobralab_ants/main.nf @@ -0,0 +1,188 @@ + +process REGISTRATION_COBRALABANTS { + tag "$meta.id" + label 'process_medium' + + container "scilus/scilus:2.2.1" + + input: + tuple val(meta), path(fixed_image), path(moving_image), path(mask) + + output: + tuple val(meta), path("*_warped.nii.gz") , emit: image_warped + tuple val(meta), path("*_forward1_affine.mat") , emit: forward_affine, optional: true + tuple val(meta), path("*_forward0_warp.nii.gz") , emit: forward_warp, optional: true + tuple val(meta), path("*_backward1_warp.nii.gz") , emit: backward_warp, optional: true + tuple val(meta), path("*_backward0_affine.mat") , emit: backward_affine, optional: true + tuple val(meta), path("*_forward*.{nii.gz,mat}", arity: '1..2') , emit: forward_image_transform + tuple val(meta), path("*_backward*.{nii.gz,mat}", arity: '1..2') , emit: backward_image_transform + tuple val(meta), path("*_backward*.{nii.gz,mat}", arity: '1..2') , emit: forward_tractogram_transform + tuple val(meta), path("*_forward*.{nii.gz,mat}", arity: '1..2') , emit: backward_tractogram_transform + tuple val(meta), path("*_registration_ants_mqc.gif") , emit: mqc, optional: true + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def suffix_qc = task.ext.suffix_qc ?: "" + def run_qc = task.ext.run_qc as Boolean || false + + if ( mask ) args += " --fixed-mask $mask" + // if ( moving_mask ) args += " --moving-mask $moving_mask" + if ( task.ext.initial_transform ) args += " --initial-transform $task.ext.initial_transform" + if ( task.ext.float ) args += " --float" + if ( task.ext.histogram_matching ) args += " --histogram-matching" + if ( task.ext.rough ) args += " --rough" + if ( task.ext.fast ) args += " --fast" + if ( task.ext.mask_extract ) args += " --mask-extract" + if ( task.ext.keep_mask_after_extract ) args += " --keep-mask-after-extract" + if ( task.ext.resampled_linear_output ) args += " --resampled-linear-output $task.ext.resampled_linear_output" + if ( task.ext.linear_type ) args += " --linear-type $task.ext.linear_type" + if ( task.ext.close ) args += " --close" + if ( task.ext.convergence != '1e-6' ) args += " --convergence $task.ext.convergence" + if ( task.ext.skip_linear ) args += " --skip-linear" + if ( task.ext.linear_metric != 'Mattes' ) args += " --linear-metric $task.ext.linear_metric" + if ( task.ext.linear_shrink_factors ) args += " --linear-shrink-factors $task.ext.linear_shrink_factors" + if ( task.ext.linear_smoothing_sigmas ) args += " --linear-smoothing-sigmas $task.ext.linear_smoothing_sigmas" + if ( task.ext.linear_convergence ) args += " --linear-convergence $task.ext.linear_convergence" + if ( task.ext.final_iterations_linear != '50' ) args += " --final-iterations-linear $task.ext.final_iterations_linear" + if ( task.ext.kmeans_transformed_linear ) args += " --kmeans-transformed-linear" + if ( task.ext.skip_nonlinear ) args += " --skip-nonlinear" + if ( task.ext.syn_control != '0.4,4,0' ) args += " --syn-control $task.ext.syn_control" + if ( task.ext.syn_metric != 'CC[4]' ) args += " --syn-metric $task.ext.syn_metric" + if ( task.ext.syn_shrink_factors ) args += " --syn-shrink-factors $task.ext.syn_shrink_factors" + if ( task.ext.syn_smoothing_sigmas ) args += " --syn-smoothing-sigmas $task.ext.syn_smoothing_sigmas" + if ( task.ext.syn_convergence ) args += " --syn-convergence $task.ext.syn_convergence" + if ( task.ext.final_iterations_nonlinear != '20' ) args += " --final-iterations-nonlinear $task.ext.final_iterations_nonlinear" + if ( task.ext.winsorize_image_intensities ) args += " --winsorize-image-intensities $task.ext.winsorize_image_intensities" + if ( task.ext.clobber ) args += " --clobber" + if ( task.ext.verbose == false ) args += " --no-verbose" + if ( task.ext.debug ) args += " --debug" + + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=$task.cpus + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + + moving_id=\$(basename $moving_image .nii.gz) + moving_id=\${moving_id#${meta.id}_*} + + antsRegistration_affine_SyN.sh $args --resampled-output ${prefix}_\${moving_id}_warped.nii.gz $moving_image $fixed_image output + + if [ ${task.ext.skip_linear} == false ]; then + mv output0GenericAffine.mat ${prefix}_forward1_affine.mat + fi + + if [ ${task.ext.skip_nonlinear} == false ]; then + mv output1InverseWarp.nii.gz ${prefix}_backward1_warp.nii.gz + mv output1Warp.nii.gz ${prefix}_forward0_warp.nii.gz + fi + + antsApplyTransforms -d 3 -t [${prefix}_forward1_affine.mat,1] \ + -o Linear[${prefix}_backward0_affine.mat] + + ### ** QC ** ### + if $run_qc; then + mv $fixed_image fixed_image.nii.gz + extract_dim=\$(mrinfo fixed_image.nii.gz -size) + read sagittal_dim coronal_dim axial_dim <<< "\${extract_dim}" + + # Get the middle slice + coronal_dim=\$((\$coronal_dim / 2)) + axial_dim=\$((\$axial_dim / 2)) + sagittal_dim=\$((\$sagittal_dim / 2)) + + # Get fixed ID, moving ID already computed + fixed_id=\$(basename $fixed_image .nii.gz) + fixed_id=\${fixed_id#${meta.id}_*} + + # Set viz params. + viz_params="--display_slice_number --display_lr --size 256 256" + # Iterate over images. + for image in fixed_image warped; do + mrconvert *\${image}.nii.gz *\${image}_viz.nii.gz -stride -1,2,3 + scil_viz_volume_screenshot *\${image}_viz.nii.gz \${image}_coronal.png \ + --slices \$coronal_dim --axis coronal \$viz_params + scil_viz_volume_screenshot *\${image}_viz.nii.gz \${image}_sagittal.png \ + --slices \$sagittal_dim --axis sagittal \$viz_params + scil_viz_volume_screenshot *\${image}_viz.nii.gz \${image}_axial.png \ + --slices \$axial_dim --axis axial \$viz_params + + if [ \$image != fixed_image ]; then + title="Warped \${moving_id^^}" + else + title="Reference \${fixed_id^^}" + fi + + convert +append \${image}_coronal*.png \${image}_axial*.png \ + \${image}_sagittal*.png \${image}_mosaic.png + convert -annotate +20+230 "\${title}" -fill white -pointsize 30 \ + \${image}_mosaic.png \${image}_mosaic.png + + # Clean up. + rm \${image}_coronal*.png \${image}_sagittal*.png \${image}_axial*.png + done + + # Create GIF. + convert -delay 10 -loop 0 -morph 10 \ + warped_mosaic.png fixed_image_mosaic.png warped_mosaic.png \ + ${prefix}_${suffix_qc}_registration_ants_mqc.gif + + # Clean up. + rm *_mosaic.png + fi + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + ants: \$(antsRegistration --version | grep "Version" | sed -E 's/.*: v?([0-9.a-zA-Z-]+).*/\\1/') + imagemagick: \$(convert -version | grep "Version:" | sed -E 's/.*ImageMagick ([0-9.-]+).*/\\1/') + mrtrix: \$(mrinfo -version 2>&1 | grep "== mrinfo" | sed -E 's/== mrinfo ([0-9.]+).*/\\1/') + scilpy: \$(uv pip -q -n list | grep scilpy | tr -s ' ' | cut -d' ' -f2) + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def suffix_qc = task.ext.suffix_qc ?: "" + def run_qc = task.ext.run_qc as Boolean || false + + """ + set +e + function handle_code () { + local code=\$? + ignore=( 1 ) + [[ " \${ignore[@]} " =~ " \$code " ]] || exit \$code + } + + # Local trap to ignore awaited non-zero exit codes + { + trap 'handle_code' ERR + antsRegistrationSyNQuick.sh -h + } + + antsApplyTransforms -h + convert -help . + scil_viz_volume_screenshot -h + + touch ${prefix}_t1_warped.nii.gz + touch ${prefix}_forward1_affine.mat + touch ${prefix}_forward0_warp.nii.gz + touch ${prefix}_backward1_warp.nii.gz + touch ${prefix}_backward0_affine.mat + + if $run_qc; then + touch ${prefix}_${suffix_qc}_registration_ants_mqc.gif + fi + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + ants: \$(antsRegistration --version | grep "Version" | sed -E 's/.*: v?([0-9.a-zA-Z-]+).*/\\1/') + imagemagick: \$(convert -version | grep "Version:" | sed -E 's/.*ImageMagick ([0-9.-]+).*/\\1/') + mrtrix: \$(mrinfo -version 2>&1 | grep "== mrinfo" | sed -E 's/== mrinfo ([0-9.]+).*/\\1/') + scilpy: \$(uv pip -q -n list | grep scilpy | tr -s ' ' | cut -d' ' -f2) + END_VERSIONS + """ +} diff --git a/modules/nf-neuro/registration/cobralab_ants/meta.yml b/modules/nf-neuro/registration/cobralab_ants/meta.yml new file mode 100644 index 00000000..4245c6ee --- /dev/null +++ b/modules/nf-neuro/registration/cobralab_ants/meta.yml @@ -0,0 +1,354 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-neuro/modules/main/modules/meta-schema.json +name: "registration_ants" +description: | + Image registration with antsRegistration_affine_SyN.sh from CoBrALab with automatic optimal pyramid generation. + + Defaults: 3D images and multi-stage registration (rigid + similarity + affine + deformable) + + Main features: + (1) Supports multiple transformation types (see `transform` argument). + (2) Supports initial transformations (see `initial_transform` argument). + (3) Automatic creation of backward (inverse) transformations matrices. + (4) Curated combined output transformations for REGISTRATION_ANTSAPPLYTRANSFORMS and REGISTRATION_TRANSFORMTRACTOGRAM + processes : `forward_image_transform`, `forward_tractogram_transform`, and their `backward` versions. + (5) Quality control (QC) image generation for MultiQC reports. +keywords: + - nifti + - registration + - antsRegistrationSyN + - antsRegistrationSyNQuick + - antsRegistration_affine_SyN +tools: + - ANTs: + description: "Advanced Normalization Tools." + homepage: "https://github.com/ANTsX/ANTs" + documentation: "http://stnava.github.io/ANTsDoc/" + doi: "10.1038/s41598-021-87564-6" + - antsRegistration_affine_SyN.sh: + description: "Advanced Normalization Tools." + homepage: "https://github.com/CoBrALab/antsRegistration_affine_SyN" + documentation: "https://github.com/CoBrALab/antsRegistration_affine_SyN/blob/master/README.md" + - ImageMagick: + description: "ImageMagick is a software suite to create, edit, compose, or convert bitmap images." + homepage: "https://imagemagick.org/" + documentation: "https://imagemagick.org/script/command-line-processing.php" + - MRtrix3: + description: "MRtrix3 is a software package for processing diffusion MRI data." + homepage: "https://www.mrtrix3.org/" + documentation: "https://mrtrix.readthedocs.io/en/latest/" + doi: "10.1016/j.neuroimage.2019.116137" + - Scilpy: + description: "Scilpy is a Python library for processing diffusion MRI data." + homepage: "https://github.com/scilus/scilpy" + documentation: "https://scilpy.readthedocs.io/en/latest/" +args: + - fast: + type: boolean + description: "Use Mattes for deformation registration stages" + default: false + - histogram_matching: + type: boolean + description: "Perform histogram matching between images before registration." + default: false + - initial_transform: + type: string + description: Algorithmic initialization by geometric center, intensities, or origin or nothing + default: "com-masks" + choices: + - com-masks + - com + - cov + - origin + - none + - run_qc: + type: boolean + description: "Run quality control (QC) to generate a MultiQC report." + default: false + - suffix_qc: + type: string + description: "Suffix for the QC image file." + default: "" + - mask_extract: + type: boolean + description: "Use masks to extract input images, only works with both images masked" + default: false + - keep_mask_after_extract: + type: boolean + description: "Keep using masks for metric after extraction" + default: false + - resampled_linear_output: + type: string + description: "Output resampled file(s) with only linear transform, repeat for resampling multispectral outputs" + default: "" + - linear_type: + type: string + description: "Type of linear transform" + default: "affine" + choices: + - "rigid" + - "lsq6" + - "similarity" + - "lsq9" + - "affine" + - "lsq12" + - close: + type: boolean + description: "Images are close in space and similarity, skip large scale pyramid search" + default: false + - weights: + type: string + description: "A single value, which disables weighting, or a comma separated list of weights, ordered primary pair, followed by multispectral pairs" + default: "1" + - convergence: + type: string + description: "Convergence stopping value for registration" + default: "1e-6" + - skip_linear: + type: boolean + description: "Skip the linear registration stages" + default: false + - linear_metric: + type: string + description: "Linear metric" + default: "Mattes" + - linear_shrink_factors: + type: string + description: "Override shrink factors for linear stage, provide to override automatic generation, must be provided with sigmas and convergence" + default: "" + - linear_smoothing_sigmas: + type: string + description: "Override smoothing sigmas for linear stage, provide to override automatic generation, must be provided with shrinks and convergence" + default: "" + - linear_convergence: + type: string + description: "Override convergence levels for linear stage, provide to override automatic generation, must be provided with shrinks and sigmas" + default: "" + - final_iterations_linear: + type: string + description: "Maximum iterations at finest scale for linear automatic generation" + default: "50" + - kmeans_transformed_linear: + type: boolean + description: "KMeans cluster image intensities for linear registration" + default: false + - skip_nonlinear: + type: boolean + description: "Skip the nonlinear stage" + default: false + - syn_control: + type: string + description: "Non-linear (SyN) gradient and regularization parameters, not checked for correctness" + default: "0.4,4,0" + - syn_metric: + type: string + description: "Non-linear (SyN) metric and radius or bins, choose Mattes[32] for faster registrations" + default: "CC[4]" + - syn_shrink_factors: + type: string + description: "Shrink factors for Non-linear (SyN) stage, provide to override automatic generation, must be provided with sigmas and convergence" + default: "" + - syn_smoothing_sigmas: + type: string + description: "Smoothing sigmas for Non-linear (SyN) stage, provide to override automatic generation, must be provided with shrinks and convergence" + default: "" + - syn_convergence: + type: string + description: "Convergence levels for Non-linear (SyN) stage, provide to override automatic generation, must be provided with shrinks and sigmas" + default: "" + - final_iterations_nonlinear: + type: string + description: "Maximum iterations at finest scale for non-linear automatic generation" + default: "20" + - winsorize_image_intensities: + type: string + description: "Winsorize data based on specified quantiles, comma separated lower,upper" + default: "" + - float: + type: boolean + description: "Calculate registration using float instead of double" + default: false + - clobber: + type: boolean + description: "Overwrite files that already exist" + default: false + - verbose: + type: boolean + description: "Run commands verbosely" + default: true + - debug: + type: boolean + description: "Show all internal commands and logic for debug" + default: false +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - fixed_image: + type: file + description: Fixed image(s) or source image(s) or reference image(s) + pattern: "*.{nii,nii.gz}" + mandatory: true + ontologies: + - edam: http://edamontology.org/format_4001 # NIFTI format + - moving_image: + type: file + description: Moving image(s) or target image(s) + pattern: "*.{nii,nii.gz}" + mandatory: true + ontologies: + - edam: http://edamontology.org/format_4001 # NIFTI format + - mask: + type: file + description: Mask for the fixed image + pattern: "*.{nii,nii.gz}" + mandatory: false + ontologies: + - edam: http://edamontology.org/format_4001 # NIFTI format + - moving_mask: + type: file + description: Mask for the moving image + pattern: "*.{nii,nii.gz}" + mandatory: false + ontologies: + - edam: http://edamontology.org/format_4001 # NIFTI format +output: + image_warped: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - "*_warped.nii.gz": + type: file + description: Nifti volume after registration. + pattern: "*_warped.nii.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + forward_affine: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - "*_forward1_affine.mat": + type: file + description: Affine transformation from moving to fixed + pattern: "*_forward1_affine.mat" + mandatory: false + ontologies: [] + forward_warp: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - "*_forward0_warp.nii.gz": + type: file + description: Nifti volume containing warp field from moving to fixed + pattern: "*_forward0_warp.nii.gz" + mandatory: false + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + backward_warp: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - "*_backward1_warp.nii.gz": + type: file + description: Nifti volume containing warp field from fixed to moving + pattern: "*_backward1_warp.nii.gz" + mandatory: false + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + backward_affine: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - "*_backward0_affine.mat": + type: file + description: Affine transformation from fixed to moving + pattern: "*_backward0_affine.mat" + mandatory: false + ontologies: [] + forward_image_transform: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - "*_forward*.{nii.gz,mat}": + type: list + description: | + Tuple, Transformation files to warp images in fixed space, in the correct order + for REGISTRATION_TRANSFORMTRACTOGRAM : [ meta, [ forward_warp, forward_affine ] ]. + pattern: "*_forward*.{nii.gz,mat}" + ontologies: [] + backward_image_transform: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - "*_backward*.{nii.gz,mat}": + type: list + description: | + Tuple, transformation files to warp images in moving space, in the correct order + for REGISTRATION_TRANSFORMTRACTOGRAM : [ meta, [ backward_affine, backward_warp ] ]. + pattern: "*_backward*.{nii.gz,mat}" + ontologies: [] + forward_tractogram_transform: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - "*_backward*.{nii.gz,mat}": + type: list + description: | + Tuple, transformation files to warp tractograms into fixed space, in the correct order + for REGISTRATION_TRANSFORMTRACTOGRAM : [ meta, [ backward_affine, backward_warp ] ]. + pattern: "*_backward*.{nii.gz,mat}" + ontologies: [] + backward_tractogram_transform: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - "*_forward*.{nii.gz,mat}": + type: list + description: | + Tuple, transformation files to warp tractograms into moving space, in the correct order + for REGISTRATION_TRANSFORMTRACTOGRAM : [ meta, [ forward_affine, forward_warp ] ]. + pattern: "*_forward*.{nii.gz,mat}" + ontologies: [] + mqc: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test', single_end:false ]` + - "*_registration_ants_mqc.gif": + type: file + description: .gif file containing quality control image for the registration + process. Made for use in MultiQC report. + pattern: "*_registration_ants_mqc.gif" + mandatory: false + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@ThoumyreStanislas" + - "@AlexVCaron" diff --git a/modules/nf-neuro/registration/cobralab_ants/tests/main.nf.test b/modules/nf-neuro/registration/cobralab_ants/tests/main.nf.test new file mode 100644 index 00000000..858003d4 --- /dev/null +++ b/modules/nf-neuro/registration/cobralab_ants/tests/main.nf.test @@ -0,0 +1,195 @@ +nextflow_process { + + name "Test Process REGISTRATION_COBRALABANTS" + script "../main.nf" + process "REGISTRATION_COBRALABANTS" + config "./nextflow.config" + + tag "modules" + tag "modules_nfneuro" + tag "registration" + tag "registration/ants" + + tag "subworkflows" + tag "subworkflows/load_test_data" + + setup { + run("LOAD_TEST_DATA", alias: "LOAD_DATA") { + script "../../../../../subworkflows/nf-neuro/load_test_data/main.nf" + process { + """ + input[0] = Channel.from( [ "T1w.zip", "others.zip" ] ) + input[1] = "test.load-test-data" + """ + } + } + } + + test("registration - ants - SyN") { + when { + process { + """ + ch_split_test_data = LOAD_DATA.out.test_data_directory + .branch{ + T1w: it.simpleName == "T1w" + moving: it.simpleName == "others" + } + ch_T1w = ch_split_test_data.T1w.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/T1w.nii.gz") + ] + } + ch_moving = ch_split_test_data.moving.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/t1.nii.gz") + ] + } + ch_T1w_mask = ch_split_test_data.T1w.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/T1w_mask.nii.gz") + ] + } + input[0] = ch_T1w + .join(ch_moving) + .join(ch_T1w_mask) + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("registration - ants - SyN quick") { + config "./nextflow_quick.config" + when { + process { + """ + ch_split_test_data = LOAD_DATA.out.test_data_directory + .branch{ + T1w: it.simpleName == "T1w" + moving: it.simpleName == "others" + } + ch_T1w = ch_split_test_data.T1w.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/T1w.nii.gz") + ] + } + ch_moving = ch_split_test_data.moving.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/t1.nii.gz") + ] + } + ch_T1w_mask = ch_split_test_data.T1w.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/T1w_mask.nii.gz") + ] + } + input[0] = ch_T1w + .join(ch_moving) + .join(ch_T1w_mask) + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("registration - ants - no warps") { + config "./nextflow_no_warp.config" + when { + process { + """ + ch_split_test_data = LOAD_DATA.out.test_data_directory + .branch{ + T1w: it.simpleName == "T1w" + moving: it.simpleName == "others" + } + ch_T1w = ch_split_test_data.T1w.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/T1w.nii.gz") + ] + } + ch_moving = ch_split_test_data.moving.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/t1.nii.gz") + ] + } + ch_T1w_mask = ch_split_test_data.T1w.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/T1w_mask.nii.gz") + ] + } + input[0] = ch_T1w + .join(ch_moving) + .join(ch_T1w_mask) + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("registration - ants - stub") { + tag "stub" + options "-stub-run" + when { + process { + """ + ch_split_test_data = LOAD_DATA.out.test_data_directory + .branch{ + T1w: it.simpleName == "T1w" + moving: it.simpleName == "others" + } + ch_T1w = ch_split_test_data.T1w.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/T1w.nii.gz") + ] + } + ch_moving = ch_split_test_data.moving.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/t1.nii.gz") + ] + } + ch_T1w_mask = ch_split_test_data.T1w.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/T1w_mask.nii.gz") + ] + } + input[0] = ch_T1w + .join(ch_moving) + .join(ch_T1w_mask) + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match() } + ) + } + } +} diff --git a/modules/nf-neuro/registration/cobralab_ants/tests/main.nf.test.snap b/modules/nf-neuro/registration/cobralab_ants/tests/main.nf.test.snap new file mode 100644 index 00000000..75d3eb00 --- /dev/null +++ b/modules/nf-neuro/registration/cobralab_ants/tests/main.nf.test.snap @@ -0,0 +1,569 @@ +{ + "registration - ants - stub": { + "content": [ + [ + "versions.yml:md5,78610891ed0adf1a5fe5f0de034d7555" + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T13:00:52.757761907" + }, + "registration - ants - SyN quick": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_t1_warped.nii.gz:md5,5534f8cb21fcc44f577d520e6083bb83" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_forward1_affine.mat:md5,dde4de8a1ca4ff1647e55dc1fece402c" + ] + ], + "10": [ + "versions.yml:md5,78610891ed0adf1a5fe5f0de034d7555" + ], + "2": [ + [ + { + "id": "test" + }, + "test_forward0_warp.nii.gz:md5,96b2d18c7537db76366e4557b44f24fe" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test_backward1_warp.nii.gz:md5,28f58437d878a6ea0c81c0e780e2333f" + ] + ], + "4": [ + [ + { + "id": "test" + }, + "test_backward0_affine.mat:md5,8cc894275b42e3b78efca499caf22e30" + ] + ], + "5": [ + [ + { + "id": "test" + }, + [ + "test_forward0_warp.nii.gz:md5,96b2d18c7537db76366e4557b44f24fe", + "test_forward1_affine.mat:md5,dde4de8a1ca4ff1647e55dc1fece402c" + ] + ] + ], + "6": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,8cc894275b42e3b78efca499caf22e30", + "test_backward1_warp.nii.gz:md5,28f58437d878a6ea0c81c0e780e2333f" + ] + ] + ], + "7": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,8cc894275b42e3b78efca499caf22e30", + "test_backward1_warp.nii.gz:md5,28f58437d878a6ea0c81c0e780e2333f" + ] + ] + ], + "8": [ + [ + { + "id": "test" + }, + [ + "test_forward0_warp.nii.gz:md5,96b2d18c7537db76366e4557b44f24fe", + "test_forward1_affine.mat:md5,dde4de8a1ca4ff1647e55dc1fece402c" + ] + ] + ], + "9": [ + [ + { + "id": "test" + }, + "test_T1_to_T1_slab_registration_ants_mqc.gif:md5,e5730d99d11edfc9802776c782edbf17" + ] + ], + "backward_affine": [ + [ + { + "id": "test" + }, + "test_backward0_affine.mat:md5,8cc894275b42e3b78efca499caf22e30" + ] + ], + "backward_image_transform": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,8cc894275b42e3b78efca499caf22e30", + "test_backward1_warp.nii.gz:md5,28f58437d878a6ea0c81c0e780e2333f" + ] + ] + ], + "backward_tractogram_transform": [ + [ + { + "id": "test" + }, + [ + "test_forward0_warp.nii.gz:md5,96b2d18c7537db76366e4557b44f24fe", + "test_forward1_affine.mat:md5,dde4de8a1ca4ff1647e55dc1fece402c" + ] + ] + ], + "backward_warp": [ + [ + { + "id": "test" + }, + "test_backward1_warp.nii.gz:md5,28f58437d878a6ea0c81c0e780e2333f" + ] + ], + "forward_affine": [ + [ + { + "id": "test" + }, + "test_forward1_affine.mat:md5,dde4de8a1ca4ff1647e55dc1fece402c" + ] + ], + "forward_image_transform": [ + [ + { + "id": "test" + }, + [ + "test_forward0_warp.nii.gz:md5,96b2d18c7537db76366e4557b44f24fe", + "test_forward1_affine.mat:md5,dde4de8a1ca4ff1647e55dc1fece402c" + ] + ] + ], + "forward_tractogram_transform": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,8cc894275b42e3b78efca499caf22e30", + "test_backward1_warp.nii.gz:md5,28f58437d878a6ea0c81c0e780e2333f" + ] + ] + ], + "forward_warp": [ + [ + { + "id": "test" + }, + "test_forward0_warp.nii.gz:md5,96b2d18c7537db76366e4557b44f24fe" + ] + ], + "image_warped": [ + [ + { + "id": "test" + }, + "test_t1_warped.nii.gz:md5,5534f8cb21fcc44f577d520e6083bb83" + ] + ], + "mqc": [ + [ + { + "id": "test" + }, + "test_T1_to_T1_slab_registration_ants_mqc.gif:md5,e5730d99d11edfc9802776c782edbf17" + ] + ], + "versions": [ + "versions.yml:md5,78610891ed0adf1a5fe5f0de034d7555" + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T13:00:13.19463937" + }, + "registration - ants - SyN": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_t1_warped.nii.gz:md5,8d9c289924c4d2c8edab3feae669d1c3" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_forward1_affine.mat:md5,16a42a74c35c9fda7786250b333bef86" + ] + ], + "10": [ + "versions.yml:md5,78610891ed0adf1a5fe5f0de034d7555" + ], + "2": [ + [ + { + "id": "test" + }, + "test_forward0_warp.nii.gz:md5,9a3b969b74ac82ab0940b8b94677c7b9" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test_backward1_warp.nii.gz:md5,3f2d23cce4fbcf970099eecd629d4019" + ] + ], + "4": [ + [ + { + "id": "test" + }, + "test_backward0_affine.mat:md5,317e067757768c8e75de9d12f84563c9" + ] + ], + "5": [ + [ + { + "id": "test" + }, + [ + "test_forward0_warp.nii.gz:md5,9a3b969b74ac82ab0940b8b94677c7b9", + "test_forward1_affine.mat:md5,16a42a74c35c9fda7786250b333bef86" + ] + ] + ], + "6": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,317e067757768c8e75de9d12f84563c9", + "test_backward1_warp.nii.gz:md5,3f2d23cce4fbcf970099eecd629d4019" + ] + ] + ], + "7": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,317e067757768c8e75de9d12f84563c9", + "test_backward1_warp.nii.gz:md5,3f2d23cce4fbcf970099eecd629d4019" + ] + ] + ], + "8": [ + [ + { + "id": "test" + }, + [ + "test_forward0_warp.nii.gz:md5,9a3b969b74ac82ab0940b8b94677c7b9", + "test_forward1_affine.mat:md5,16a42a74c35c9fda7786250b333bef86" + ] + ] + ], + "9": [ + + ], + "backward_affine": [ + [ + { + "id": "test" + }, + "test_backward0_affine.mat:md5,317e067757768c8e75de9d12f84563c9" + ] + ], + "backward_image_transform": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,317e067757768c8e75de9d12f84563c9", + "test_backward1_warp.nii.gz:md5,3f2d23cce4fbcf970099eecd629d4019" + ] + ] + ], + "backward_tractogram_transform": [ + [ + { + "id": "test" + }, + [ + "test_forward0_warp.nii.gz:md5,9a3b969b74ac82ab0940b8b94677c7b9", + "test_forward1_affine.mat:md5,16a42a74c35c9fda7786250b333bef86" + ] + ] + ], + "backward_warp": [ + [ + { + "id": "test" + }, + "test_backward1_warp.nii.gz:md5,3f2d23cce4fbcf970099eecd629d4019" + ] + ], + "forward_affine": [ + [ + { + "id": "test" + }, + "test_forward1_affine.mat:md5,16a42a74c35c9fda7786250b333bef86" + ] + ], + "forward_image_transform": [ + [ + { + "id": "test" + }, + [ + "test_forward0_warp.nii.gz:md5,9a3b969b74ac82ab0940b8b94677c7b9", + "test_forward1_affine.mat:md5,16a42a74c35c9fda7786250b333bef86" + ] + ] + ], + "forward_tractogram_transform": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,317e067757768c8e75de9d12f84563c9", + "test_backward1_warp.nii.gz:md5,3f2d23cce4fbcf970099eecd629d4019" + ] + ] + ], + "forward_warp": [ + [ + { + "id": "test" + }, + "test_forward0_warp.nii.gz:md5,9a3b969b74ac82ab0940b8b94677c7b9" + ] + ], + "image_warped": [ + [ + { + "id": "test" + }, + "test_t1_warped.nii.gz:md5,8d9c289924c4d2c8edab3feae669d1c3" + ] + ], + "mqc": [ + + ], + "versions": [ + "versions.yml:md5,78610891ed0adf1a5fe5f0de034d7555" + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T12:58:47.595933738" + }, + "registration - ants - no warps": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_t1_warped.nii.gz:md5,c13330e3f71c57f2517dcd6f59f64ff1" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_forward1_affine.mat:md5,7387bc8f93dd29d2a0aadb9d5dc06d5b" + ] + ], + "10": [ + "versions.yml:md5,78610891ed0adf1a5fe5f0de034d7555" + ], + "2": [ + + ], + "3": [ + + ], + "4": [ + [ + { + "id": "test" + }, + "test_backward0_affine.mat:md5,733babe33e1656d0f85064caf24d6f82" + ] + ], + "5": [ + [ + { + "id": "test" + }, + [ + "test_forward1_affine.mat:md5,7387bc8f93dd29d2a0aadb9d5dc06d5b" + ] + ] + ], + "6": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,733babe33e1656d0f85064caf24d6f82" + ] + ] + ], + "7": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,733babe33e1656d0f85064caf24d6f82" + ] + ] + ], + "8": [ + [ + { + "id": "test" + }, + [ + "test_forward1_affine.mat:md5,7387bc8f93dd29d2a0aadb9d5dc06d5b" + ] + ] + ], + "9": [ + + ], + "backward_affine": [ + [ + { + "id": "test" + }, + "test_backward0_affine.mat:md5,733babe33e1656d0f85064caf24d6f82" + ] + ], + "backward_image_transform": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,733babe33e1656d0f85064caf24d6f82" + ] + ] + ], + "backward_tractogram_transform": [ + [ + { + "id": "test" + }, + [ + "test_forward1_affine.mat:md5,7387bc8f93dd29d2a0aadb9d5dc06d5b" + ] + ] + ], + "backward_warp": [ + + ], + "forward_affine": [ + [ + { + "id": "test" + }, + "test_forward1_affine.mat:md5,7387bc8f93dd29d2a0aadb9d5dc06d5b" + ] + ], + "forward_image_transform": [ + [ + { + "id": "test" + }, + [ + "test_forward1_affine.mat:md5,7387bc8f93dd29d2a0aadb9d5dc06d5b" + ] + ] + ], + "forward_tractogram_transform": [ + [ + { + "id": "test" + }, + [ + "test_backward0_affine.mat:md5,733babe33e1656d0f85064caf24d6f82" + ] + ] + ], + "forward_warp": [ + + ], + "image_warped": [ + [ + { + "id": "test" + }, + "test_t1_warped.nii.gz:md5,c13330e3f71c57f2517dcd6f59f64ff1" + ] + ], + "mqc": [ + + ], + "versions": [ + "versions.yml:md5,78610891ed0adf1a5fe5f0de034d7555" + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T13:00:29.996790917" + } +} \ No newline at end of file diff --git a/modules/nf-neuro/registration/cobralab_ants/tests/nextflow.config b/modules/nf-neuro/registration/cobralab_ants/tests/nextflow.config new file mode 100644 index 00000000..4c2ce76e --- /dev/null +++ b/modules/nf-neuro/registration/cobralab_ants/tests/nextflow.config @@ -0,0 +1,7 @@ +process { + withName: "REGISTRATION_COBRALABANTS" { + publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } + ext.repro_mode = 1 + ext.initial_transform = "com" + } +} diff --git a/modules/nf-neuro/registration/cobralab_ants/tests/nextflow_no_warp.config b/modules/nf-neuro/registration/cobralab_ants/tests/nextflow_no_warp.config new file mode 100644 index 00000000..e63042d6 --- /dev/null +++ b/modules/nf-neuro/registration/cobralab_ants/tests/nextflow_no_warp.config @@ -0,0 +1,8 @@ +process { + withName: "REGISTRATION_COBRALABANTS" { + publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } + ext.fast = true + ext.skip_nonlinear = true + ext.ext.linear_type = 'rigid' + } +} diff --git a/modules/nf-neuro/registration/cobralab_ants/tests/nextflow_quick.config b/modules/nf-neuro/registration/cobralab_ants/tests/nextflow_quick.config new file mode 100644 index 00000000..349e46c9 --- /dev/null +++ b/modules/nf-neuro/registration/cobralab_ants/tests/nextflow_quick.config @@ -0,0 +1,8 @@ +process { + withName: "REGISTRATION_COBRALABANTS" { + publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } + ext.fast = true + ext.run_qc = true + ext.suffix_qc = "T1_to_T1_slab" + } +} diff --git a/modules/nf-neuro/registration/cobralab_ants/tests/tags.yml b/modules/nf-neuro/registration/cobralab_ants/tests/tags.yml new file mode 100644 index 00000000..8aa857e3 --- /dev/null +++ b/modules/nf-neuro/registration/cobralab_ants/tests/tags.yml @@ -0,0 +1,2 @@ +registration/ants: + - "modules/nf-neuro/registration/ants/**"