diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 264a0c6b..dd0b890e 100755 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -139,13 +139,13 @@ jobs: fail-fast: false matrix: path: [ "${{ fromJson(needs.nf-test-changes.outputs.paths) }}" ] - profile: [ "docker" ] - exclude: - - path: subworkflows/nf-scil/load_test_data include: + - profile: docker - runner: scilus-nf-scil-runners - runner: scilus-nf-scil-bigmem-runners path: modules/nf-scil/registration/easyreg + exclude: + - path: subworkflows/nf-scil/load_test_data uses: ./.github/workflows/nf-test_module.yml with: profile: ${{ matrix.profile }} diff --git a/modules/nf-scil/registration/easyreg/environment.yml b/modules/nf-scil/registration/easyreg/environment.yml new file mode 100644 index 00000000..6b1ae190 --- /dev/null +++ b/modules/nf-scil/registration/easyreg/environment.yml @@ -0,0 +1,6 @@ +name: "registration_easyreg" +channels: + - Docker + - Apptainer +dependencies: + - "Freesurfer" diff --git a/modules/nf-scil/registration/easyreg/main.nf b/modules/nf-scil/registration/easyreg/main.nf new file mode 100644 index 00000000..66dcde02 --- /dev/null +++ b/modules/nf-scil/registration/easyreg/main.nf @@ -0,0 +1,82 @@ + + +process REGISTRATION_EASYREG { + tag "$meta.id" + label 'process_single' + label 'process_high' + + container "freesurfer/freesurfer:7.4.1" + + input: + tuple val(meta), path(reference), path(floating), path(ref_segmentation), path(flo_segmentation) + + output: + tuple val(meta), path("*_reference_segmentation.nii.gz") , emit: ref_seg + tuple val(meta), path("*_floating_segmentation.nii.gz") , emit: flo_seg + tuple val(meta), path("*_reference_registered.nii.gz") , emit: ref_reg + tuple val(meta), path("*_floating_registered.nii.gz") , emit: flo_reg + tuple val(meta), path("*_forward_field.nii.gz") , emit: fwd_field, optional: true + tuple val(meta), path("*_backward_field.nii.gz") , emit: bak_field, optional: true + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def field = task.ext.field ? "--fwd_field ${prefix}_forward_field.nii.gz --bak_field ${prefix}_backward_field.nii.gz " : "" + def threads = task.ext.threads ? "--threads " + task.ext.threads : "" + def affine = task.ext.affine ? "--affine_only " : "" + + """ + export ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1 + export OMP_NUM_THREADS=1 + export OPENBLAS_NUM_THREADS=1 + + if [[ -f "$ref_segmentation" ]]; + then + reference_segmentation=$ref_segmentation + else + reference_segmentation="${prefix}_reference_segmentation.nii.gz" + fi + + if [[ -f "$flo_segmentation" ]]; + then + floating_segmentation=$flo_segmentation + else + floating_segmentation="${prefix}_floating_segmentation.nii.gz" + fi + + mri_easyreg \ + --ref $reference --flo $floating \ + --ref_seg \${reference_segmentation} \ + --flo_seg \${floating_segmentation} \ + --flo_reg ${prefix}_floating_registered.nii.gz \ + --ref_reg ${prefix}_reference_registered.nii.gz \ + $field $threads $affine + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + freesurfer: 7.4.1 + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + mri_easyreg -h + + touch ${prefix}_reference_segmentation.nii.gz + touch ${prefix}_floating_segmentation.nii.gz + touch ${prefix}_reference_registered.nii.gz + touch ${prefix}_floating_registered.nii.gz + touch ${prefix}_forward_field.nii.gz + touch ${prefix}_backward_field.nii.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + freesurfer: 7.4.1 + END_VERSIONS + """ +} diff --git a/modules/nf-scil/registration/easyreg/meta.yml b/modules/nf-scil/registration/easyreg/meta.yml new file mode 100644 index 00000000..950a770a --- /dev/null +++ b/modules/nf-scil/registration/easyreg/meta.yml @@ -0,0 +1,91 @@ +--- +name: "registration_easyreg" +description: Image registration and SynthSeg v2 segmentation with easyreg from freesurfer +keywords: + - nifti + - registration + - segmentation + - easyreg + - freesurfer +tools: + - "Freesurfer": + description: "Software package for the analysis and visualization of structural and functional neuroimaging data." + homepage: "https://surfer.nmr.mgh.harvard.edu/fswiki" + +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + + - reference: + type: file + description: the reference image in .nii(.gz) or .mgz format (note that, since the method is symmetric, the choice of reference vs floating is arbitrary). + pattern: "*.{nii,nii.gz,mgz}" + + - floating: + type: file + description: the floating image in .nii(.gz) or .mgz format. + pattern: "*.{nii,nii.gz,mgz}" + + - ref_segmentation: + type: file + description: file with the SynthSeg v2 (non-robust) segmentation + parcellation of the reference image. + If it does not exist, EasyReg will create it. If it already exists (e.g., from a previous EasyReg run), then EasyReg will read it from disk (which is faster than segmenting). + pattern: "*.{nii,nii.gz}" + + - flo_segmentation: + type: file + description: file with the SynthSeg v2 (non-robust) segmentation + parcellation of the floating image. + If it does not exist, EasyReg will create it. If it already exists (e.g., from a previous EasyReg run), then EasyReg will read it from disk (which is faster than segmenting). + pattern: "*.{nii,nii.gz}" + +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + + - ref_seg: + type: file + description: file with the SynthSeg v2 (non-robust) segmentation + parcellation of the reference image. + pattern: "*_reference_segmentation.nii.gz" + + - flo_seg: + type: file + description: file with the SynthSeg v2 (non-robust) segmentation + parcellation of the floating image. + pattern: "*_floating_segmentation.nii.gz" + + - ref_reg: + type: file + description: this is the file where the deformed (registered) reference image is written. + pattern: "*_reference_registered.nii.gz" + + - flo_reg: + type: file + description: this is the file where the deformed (registered) floating image is written. + pattern: "*_floating_registered.nii.gz" + + - fwd_field: + type: file + description: + this is the file where the forward deformation field is written. The deformation includes both the affine and nonlinear components. + Must be a nifti (.nii/.nii.gz) or .mgz file; it is encoded as the real world (RAS) coordinates of the target location for each voxel. + pattern: "*_forward_field.nii.gz" + + - bak_field: + type: file + description: this is the file where the backward deformation field is written. It must also be a nifty or mgz file. + pattern: "*_backward_field.nii.gz" + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@ThoumyreStanislas" +maintainers: + - "@ThoumyreStanislas" diff --git a/modules/nf-scil/registration/easyreg/tests/main.nf.test b/modules/nf-scil/registration/easyreg/tests/main.nf.test new file mode 100644 index 00000000..0d7e0fb9 --- /dev/null +++ b/modules/nf-scil/registration/easyreg/tests/main.nf.test @@ -0,0 +1,72 @@ +nextflow_process { + + name "Test Process REGISTRATION_EASYREG" + script "../main.nf" + process "REGISTRATION_EASYREG" + + tag "modules" + tag "modules_nfcore" + tag "registration" + tag "registration/easyreg" + + tag "subworkflows" + tag "subworkflows/load_test_data" + + + setup { + run("LOAD_TEST_DATA", alias: "LOAD_DATA") { + script "../../../../../subworkflows/nf-scil/load_test_data/main.nf" + process { + """ + input[0] = Channel.from( [ "freesurfer.zip", "registration.zip" ] ) + input[1] = "test.load-test-data" + """ + } + } + } + + test("registration - easyreg") { + config "./nextflow.config" + when { + process { + """ + ch_split_test_data = LOAD_DATA.out.test_data_directory + .branch{ + t1: it.simpleName == "freesurfer" + b0: it.simpleName == "registration" + } + ch_t1 = ch_split_test_data.t1.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/t1.nii.gz") + ] + } + ch_b0 = ch_split_test_data.b0.map{ + test_data_directory -> [ + [ id:'test' ], + file("\${test_data_directory}/b0.nii.gz"), + [], + [] + ] + } + input[0] = ch_t1 + .join(ch_b0) + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.ref_seg.get(0).get(1)).name, + file(process.out.flo_seg.get(0).get(1)).name, + niftiMD5SUM(process.out.ref_reg.get(0).get(1), 6), + niftiMD5SUM(process.out.flo_reg.get(0).get(1), 6), + niftiMD5SUM(process.out.fwd_field.get(0).get(1), 6), + niftiMD5SUM(process.out.bak_field.get(0).get(1), 6), + process.out.versions + ).match() } + ) + } + } +} diff --git a/modules/nf-scil/registration/easyreg/tests/main.nf.test.snap b/modules/nf-scil/registration/easyreg/tests/main.nf.test.snap new file mode 100644 index 00000000..abb85387 --- /dev/null +++ b/modules/nf-scil/registration/easyreg/tests/main.nf.test.snap @@ -0,0 +1,20 @@ +{ + "registration - easyreg": { + "content": [ + "test_reference_segmentation.nii.gz", + "test_floating_segmentation.nii.gz", + "test_reference_registered.nii.gz:md5:header,c5e41f89848f91c53a9a7be44970d4b1,data,1501221fe23cd62bfdafb33367cadf4d", + "test_floating_registered.nii.gz:md5:header,ec5893cd9ea024e630c4444bd914c331,data,d75ae3fc935cd5cc70ea6797a4c70775", + "test_forward_field.nii.gz:md5:header,0db0a80786ff39864cc17506ed1a0146,data,e4fa6c626729cbf236c34680e5c76df4", + "test_backward_field.nii.gz:md5:header,74c92ee4cd3c4abfecf3da6cdb1d4650,data,8f26afa5c3de21469466213498542486", + [ + "versions.yml:md5,4661210880c42a986923e8257f64c760" + ] + ], + "meta": { + "nf-test": "0.9.0-rc1", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-12T16:01:24.00559" + } +} \ No newline at end of file diff --git a/modules/nf-scil/registration/easyreg/tests/nextflow.config b/modules/nf-scil/registration/easyreg/tests/nextflow.config new file mode 100644 index 00000000..71494b4d --- /dev/null +++ b/modules/nf-scil/registration/easyreg/tests/nextflow.config @@ -0,0 +1,9 @@ +process { + memory = '10G' + withName: "REGISTRATION_EASYREG" { + publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } + ext.field = true + ext.affine = true + ext.threads = 1 + } +} diff --git a/modules/nf-scil/registration/easyreg/tests/tags.yml b/modules/nf-scil/registration/easyreg/tests/tags.yml new file mode 100644 index 00000000..14d95af4 --- /dev/null +++ b/modules/nf-scil/registration/easyreg/tests/tags.yml @@ -0,0 +1,2 @@ +registration/easyreg: + - "modules/nf-scil/registration/easyreg/**"