From dfc965c9f36100fbac5c75aca133748c8bfcc03d Mon Sep 17 00:00:00 2001 From: Thoumyre Stanislas Date: Thu, 12 Dec 2024 16:54:38 +0000 Subject: [PATCH] make all preproc_t1 modules optional --- subworkflows/nf-neuro/preproc_t1/main.nf | 209 +++++++------ .../nf-neuro/preproc_t1/tests/main.nf.test | 122 ++++++++ .../preproc_t1/tests/main.nf.test.snap | 276 ++++++++++++++++++ .../nf-neuro/preproc_t1/tests/nextflow.config | 6 + .../preproc_t1/tests/nextflow_quick.config | 17 ++ .../tests/nextflow_quick_bis.config | 14 + .../preproc_t1/tests/nextflow_synthbet.config | 5 + 7 files changed, 563 insertions(+), 86 deletions(-) create mode 100644 subworkflows/nf-neuro/preproc_t1/tests/nextflow_quick.config create mode 100644 subworkflows/nf-neuro/preproc_t1/tests/nextflow_quick_bis.config diff --git a/subworkflows/nf-neuro/preproc_t1/main.nf b/subworkflows/nf-neuro/preproc_t1/main.nf index b923495e..11363f54 100644 --- a/subworkflows/nf-neuro/preproc_t1/main.nf +++ b/subworkflows/nf-neuro/preproc_t1/main.nf @@ -24,104 +24,141 @@ workflow PREPROC_T1 { ch_versions = Channel.empty() - // ** Denoising ** // - // Result : [ meta, image, mask | [] ] - // Steps : - // - join [ meta, image, mask | null ] - // - map [ meta, image, mask | [] ] - ch_nlmeans = ch_image - .join(ch_mask_nlmeans, remainder: true) - .map{ it[0..1] + [it[2] ?: []] } - - DENOISING_NLMEANS ( ch_nlmeans ) - ch_versions = ch_versions.mix(DENOISING_NLMEANS.out.versions.first()) - - // ** N4 correction ** // - // Result : [ meta, image, reference | [], mask | [] ] - // Steps : - // - join [ meta, image ] + [ reference, mask ] | [ reference, null ] | [ null ] - // - map [ meta, image, reference | [], mask | [] ] - // - join [ meta, image, reference | [], mask | [], nlmeans-mask | null ] - // - map [ meta, image, reference | [], mask | [] ] - ch_N4 = DENOISING_NLMEANS.out.image - .join(ch_ref_n4, remainder: true) - .map{ it[0..1] + [it[2] ?: [], it[3] ?: []] } - .join(ch_mask_nlmeans, remainder: true) - .map{ it[0..2] + [it[3] ?: it[4] ?: []] } - - PREPROC_N4 ( ch_N4 ) - ch_versions = ch_versions.mix(PREPROC_N4.out.versions.first()) - - // ** Resampling ** // - // Result : [ meta, image, reference | [] ] - // Steps : - // - join [ meta, image, reference | null ] - // - map [ meta, image, reference | [] ] - ch_resampling = PREPROC_N4.out.image - .join(ch_ref_resample, remainder: true) - .map{ it[0..1] + [it[2] ?: []] } - - IMAGE_RESAMPLE ( ch_resampling ) - ch_versions = ch_versions.mix(IMAGE_RESAMPLE.out.versions.first()) - - // ** Brain extraction ** // - if ( params.run_synthbet ) { - // ** SYNTHBET ** // - // Result : [ meta, image, weights | [] ] + if ( params.run_denoising ) { + + // ** Denoising ** // + // Result : [ meta, image, mask | [] ] // Steps : - // - join [ meta, image, weights | null ] - // - map [ meta, image, weights | [] ] - ch_bet = IMAGE_RESAMPLE.out.image - .join(ch_weights, remainder: true) + // - join [ meta, image, mask | null ] + // - map [ meta, image, mask | [] ] + ch_nlmeans = ch_image + .join(ch_mask_nlmeans, remainder: true) .map{ it[0..1] + [it[2] ?: []] } - BETCROP_SYNTHBET ( ch_bet ) - ch_versions = ch_versions.mix(BETCROP_SYNTHBET.out.versions.first()) + DENOISING_NLMEANS ( ch_nlmeans ) + ch_versions = ch_versions.mix(DENOISING_NLMEANS.out.versions.first()) + image_nlmeans = DENOISING_NLMEANS.out.image + } + + else { + image_nlmeans = ch_image + } - // ** Setting BET output ** // - image_bet = BETCROP_SYNTHBET.out.bet_image - mask_bet = BETCROP_SYNTHBET.out.brain_mask + if ( params.run_N4 ) { + // ** N4 correction ** // + // Result : [ meta, image, reference | [], mask | [] ] + // Steps : + // - join [ meta, image ] + [ reference, mask ] | [ reference, null ] | [ null ] + // - map [ meta, image, reference | [], mask | [] ] + // - join [ meta, image, reference | [], mask | [], nlmeans-mask | null ] + // - map [ meta, image, reference | [], mask | [] ] + ch_N4 = image_nlmeans + .join(ch_ref_n4, remainder: true) + .map{ it[0..1] + [it[2] ?: [], it[3] ?: []] } + .join(ch_mask_nlmeans, remainder: true) + .map{ it[0..2] + [it[3] ?: it[4] ?: []] } + + PREPROC_N4 ( ch_N4 ) + ch_versions = ch_versions.mix(PREPROC_N4.out.versions.first()) + image_N4 = PREPROC_N4.out.image + } + + else { + image_N4 = image_nlmeans } + if ( params.run_resampling ) { + // ** Resampling ** // + // Result : [ meta, image, reference | [] ] + // Steps : + // - join [ meta, image, reference | null ] + // - map [ meta, image, reference | [] ] + ch_resampling = image_N4 + .join(ch_ref_resample, remainder: true) + .map{ it[0..1] + [it[2] ?: []] } + + IMAGE_RESAMPLE ( ch_resampling ) + ch_versions = ch_versions.mix(IMAGE_RESAMPLE.out.versions.first()) + image_resample = IMAGE_RESAMPLE.out.image + } else { - // ** ANTSBET ** // - // The template and probability maps are mandatory if running antsBET. Since the - // error message from nextflow when they are absent is either non-informative or - // missing, we use ifEmpty to provide a more informative one. - ch_bet = IMAGE_RESAMPLE.out.image - .join(ch_template.ifEmpty{ error("ANTS BET needs a template") }) - .join(ch_probability_map.ifEmpty{ error("ANTS BET needs a tissue probability map") }) - - BETCROP_ANTSBET ( ch_bet ) - ch_versions = ch_versions.mix(BETCROP_ANTSBET.out.versions.first()) - - // ** Setting BET output ** // - image_bet = BETCROP_ANTSBET.out.t1 - mask_bet = BETCROP_ANTSBET.out.mask + image_resample = image_N4 } - // ** Crop image ** // - ch_crop = image_bet - .map{ it + [[]] } + if ( params.run_bet ) { + // ** Brain extraction ** // + if ( params.run_synthbet ) { + // ** SYNTHBET ** // + // Result : [ meta, image, weights | [] ] + // Steps : + // - join [ meta, image, weights | null ] + // - map [ meta, image, weights | [] ] + ch_bet = image_resample + .join(ch_weights, remainder: true) + .map{ it[0..1] + [it[2] ?: []] } + + BETCROP_SYNTHBET ( ch_bet ) + ch_versions = ch_versions.mix(BETCROP_SYNTHBET.out.versions.first()) + + // ** Setting BET output ** // + image_bet = BETCROP_SYNTHBET.out.bet_image + mask_bet = BETCROP_SYNTHBET.out.brain_mask + } + + else { + // ** ANTSBET ** // + // The template and probability maps are mandatory if running antsBET. Since the + // error message from nextflow when they are absent is either non-informative or + // missing, we use ifEmpty to provide a more informative one. + ch_bet = image_resample + .join(ch_template.ifEmpty{ error("ANTS BET needs a template") }) + .join(ch_probability_map.ifEmpty{ error("ANTS BET needs a tissue probability map") }) + + BETCROP_ANTSBET ( ch_bet ) + ch_versions = ch_versions.mix(BETCROP_ANTSBET.out.versions.first()) + + // ** Setting BET output ** // + image_bet = BETCROP_ANTSBET.out.t1 + mask_bet = BETCROP_ANTSBET.out.mask + } + } + else{ + image_bet = image_resample + mask_bet = Channel.empty() + } + + if ( params.run_crop ) { + // ** Crop image ** // + ch_crop = image_bet + .map{ it + [[]] } - BETCROP_CROPVOLUME_T1 ( ch_crop ) - ch_versions = ch_versions.mix(BETCROP_CROPVOLUME_T1.out.versions.first()) + BETCROP_CROPVOLUME_T1 ( ch_crop ) + ch_versions = ch_versions.mix(BETCROP_CROPVOLUME_T1.out.versions.first()) + image_crop = BETCROP_CROPVOLUME_T1.out.image + bbox = BETCROP_CROPVOLUME_T1.out.bounding_box - // ** Crop mask ** // - ch_crop_mask = mask_bet - .join(BETCROP_CROPVOLUME_T1.out.bounding_box) + // ** Crop mask ** // + ch_crop_mask = mask_bet + .join(BETCROP_CROPVOLUME_T1.out.bounding_box) - BETCROP_CROPVOLUME_MASK ( ch_crop_mask ) - ch_versions = ch_versions.mix(BETCROP_CROPVOLUME_MASK.out.versions.first()) + BETCROP_CROPVOLUME_MASK ( ch_crop_mask ) + ch_versions = ch_versions.mix(BETCROP_CROPVOLUME_MASK.out.versions.first()) + mask_crop = BETCROP_CROPVOLUME_MASK.out.image + } + else{ + image_crop = image_bet + mask_crop = [] + bbox = [] + } emit: - t1_final = BETCROP_CROPVOLUME_T1.out.image // channel: [ val(meta), t1-preprocessed ] - mask_final = BETCROP_CROPVOLUME_MASK.out.image // channel: [ val(meta), t1-mask ] - image_nlmeans = DENOISING_NLMEANS.out.image // channel: [ val(meta), t1-after-denoise ] - image_N4 = PREPROC_N4.out.image // channel: [ val(meta), t1-after-unbias ] - image_resample = IMAGE_RESAMPLE.out.image // channel: [ val(meta), t1-after-resample ] - image_bet = image_bet // channel: [ val(meta), t1-after-bet ] - mask_bet = mask_bet // channel: [ val(meta), intermediary-mask ] - crop_box = BETCROP_CROPVOLUME_T1.out.bounding_box // channel: [ val(meta), bounding-box ] - versions = ch_versions // channel: [ versions.yml ] + t1_final = image_crop // channel: [ val(meta), t1-preprocessed ] + mask_final = mask_crop // channel: [ val(meta), t1-mask ] + image_nlmeans = image_nlmeans // channel: [ val(meta), t1-after-denoise ] + image_N4 = image_N4 // channel: [ val(meta), t1-after-unbias ] + image_resample = image_resample // channel: [ val(meta), t1-after-resample ] + image_bet = image_bet // channel: [ val(meta), t1-after-bet ] + mask_bet = mask_bet // channel: [ val(meta), intermediary-mask ] + crop_box = bbox // channel: [ val(meta), bounding-box ] + versions = ch_versions // channel: [ versions.yml ] } diff --git a/subworkflows/nf-neuro/preproc_t1/tests/main.nf.test b/subworkflows/nf-neuro/preproc_t1/tests/main.nf.test index 44744117..f320af3a 100644 --- a/subworkflows/nf-neuro/preproc_t1/tests/main.nf.test +++ b/subworkflows/nf-neuro/preproc_t1/tests/main.nf.test @@ -216,4 +216,126 @@ nextflow_workflow { ) } } + + test("preproc_t1_quick") { + config "./nextflow_quick.config" + + when { + workflow { + """ + ch_split_test_data = LOAD_DATA.out.test_data_directory + .branch{ + antsbet: it.simpleName == "antsbet" + t1w: it.simpleName == "T1w" + } + input[0] = ch_split_test_data.t1w.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + file("\${test_data_directory}/T1w.nii.gz") + ]} + input[1] = ch_split_test_data.antsbet.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [] + ]} + input[2] = ch_split_test_data.antsbet.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [] + ]} + input[3] = ch_split_test_data.antsbet.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [], + [], + [] + ]} + input[4] = ch_split_test_data.t1w.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [], + [] + ]} + input[5] = ch_split_test_data.antsbet.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [] + ]} + input[6] = ch_split_test_data.antsbet.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [] + ]} + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } + + test("preproc_t1_quick_bis") { + config "./nextflow_quick_bis.config" + + when { + workflow { + """ + ch_split_test_data = LOAD_DATA.out.test_data_directory + .branch{ + antsbet: it.simpleName == "antsbet" + t1w: it.simpleName == "T1w" + } + input[0] = ch_split_test_data.t1w.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + file("\${test_data_directory}/T1w.nii.gz") + ]} + input[1] = ch_split_test_data.antsbet.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [] + ]} + input[2] = ch_split_test_data.antsbet.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [] + ]} + input[3] = ch_split_test_data.antsbet.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [], + [], + [] + ]} + input[4] = ch_split_test_data.t1w.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [], + [] + ]} + input[5] = ch_split_test_data.antsbet.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [] + ]} + input[6] = ch_split_test_data.antsbet.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], + [] + ]} + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } } diff --git a/subworkflows/nf-neuro/preproc_t1/tests/main.nf.test.snap b/subworkflows/nf-neuro/preproc_t1/tests/main.nf.test.snap index 7f595ab8..fee02c96 100644 --- a/subworkflows/nf-neuro/preproc_t1/tests/main.nf.test.snap +++ b/subworkflows/nf-neuro/preproc_t1/tests/main.nf.test.snap @@ -1,4 +1,280 @@ { + "preproc_t1_quick": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test_t1_cropped.nii.gz:md5,c507c9182cc410c298fad4a03540c0c9" + ] + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test__denoised.nii.gz:md5,c507c9182cc410c298fad4a03540c0c9" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test__denoised.nii.gz:md5,c507c9182cc410c298fad4a03540c0c9" + ] + ], + "4": [ + [ + { + "id": "test", + "single_end": false + }, + "test_resampled.nii.gz:md5,c507c9182cc410c298fad4a03540c0c9" + ] + ], + "5": [ + [ + { + "id": "test", + "single_end": false + }, + "test_resampled.nii.gz:md5,c507c9182cc410c298fad4a03540c0c9" + ] + ], + "6": [ + + ], + "7": [ + + ], + "8": [ + "versions.yml:md5,657fbb224f260392e573b8511c4b798d", + "versions.yml:md5,bdd934b4b8456060c36d6d97e4f30740", + "versions.yml:md5,ea32c30f5320f720b2f5dc32ac2535ea" + ], + "crop_box": [ + + ], + "image_N4": [ + [ + { + "id": "test", + "single_end": false + }, + "test__denoised.nii.gz:md5,c507c9182cc410c298fad4a03540c0c9" + ] + ], + "image_bet": [ + [ + { + "id": "test", + "single_end": false + }, + "test_resampled.nii.gz:md5,c507c9182cc410c298fad4a03540c0c9" + ] + ], + "image_nlmeans": [ + [ + { + "id": "test", + "single_end": false + }, + "test__denoised.nii.gz:md5,c507c9182cc410c298fad4a03540c0c9" + ] + ], + "image_resample": [ + [ + { + "id": "test", + "single_end": false + }, + "test_resampled.nii.gz:md5,c507c9182cc410c298fad4a03540c0c9" + ] + ], + "mask_bet": [ + + ], + "mask_final": [ + + ], + "t1_final": [ + [ + { + "id": "test", + "single_end": false + }, + "test_t1_cropped.nii.gz:md5,c507c9182cc410c298fad4a03540c0c9" + ] + ], + "versions": [ + "versions.yml:md5,657fbb224f260392e573b8511c4b798d", + "versions.yml:md5,bdd934b4b8456060c36d6d97e4f30740", + "versions.yml:md5,ea32c30f5320f720b2f5dc32ac2535ea" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.2" + }, + "timestamp": "2024-12-12T16:26:43.374566122" + }, + "preproc_t1_quick_bis": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test__bet_image.nii.gz:md5,4b0f4cb83b5d5aa56a18485bf8b6f18d" + ] + ], + "1": [ + [ + + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "T1w.nii.gz:md5,ce10054d30c0a0753c619d67d811fe32" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test__image_n4.nii.gz:md5,b8fe9df61a5e5e0ff6a6b61a72e44ada" + ] + ], + "4": [ + [ + { + "id": "test", + "single_end": false + }, + "test__image_n4.nii.gz:md5,b8fe9df61a5e5e0ff6a6b61a72e44ada" + ] + ], + "5": [ + [ + { + "id": "test", + "single_end": false + }, + "test__bet_image.nii.gz:md5,4b0f4cb83b5d5aa56a18485bf8b6f18d" + ] + ], + "6": [ + [ + { + "id": "test", + "single_end": false + }, + "test__brain_mask.nii.gz:md5,09a624804577e6e8badccaae18d30007" + ] + ], + "7": [ + [ + + ] + ], + "8": [ + "versions.yml:md5,318cabe934be45528a25f52083d9c90d", + "versions.yml:md5,b979132991d8f72a3585465533bd5730" + ], + "crop_box": [ + [ + + ] + ], + "image_N4": [ + [ + { + "id": "test", + "single_end": false + }, + "test__image_n4.nii.gz:md5,b8fe9df61a5e5e0ff6a6b61a72e44ada" + ] + ], + "image_bet": [ + [ + { + "id": "test", + "single_end": false + }, + "test__bet_image.nii.gz:md5,4b0f4cb83b5d5aa56a18485bf8b6f18d" + ] + ], + "image_nlmeans": [ + [ + { + "id": "test", + "single_end": false + }, + "T1w.nii.gz:md5,ce10054d30c0a0753c619d67d811fe32" + ] + ], + "image_resample": [ + [ + { + "id": "test", + "single_end": false + }, + "test__image_n4.nii.gz:md5,b8fe9df61a5e5e0ff6a6b61a72e44ada" + ] + ], + "mask_bet": [ + [ + { + "id": "test", + "single_end": false + }, + "test__brain_mask.nii.gz:md5,09a624804577e6e8badccaae18d30007" + ] + ], + "mask_final": [ + [ + + ] + ], + "t1_final": [ + [ + { + "id": "test", + "single_end": false + }, + "test__bet_image.nii.gz:md5,4b0f4cb83b5d5aa56a18485bf8b6f18d" + ] + ], + "versions": [ + "versions.yml:md5,318cabe934be45528a25f52083d9c90d", + "versions.yml:md5,b979132991d8f72a3585465533bd5730" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.2" + }, + "timestamp": "2024-12-12T16:39:24.287443521" + }, "preproc_t1_synthbet": { "content": [ "test__denoised.nii.gz:md5:header,a7ee0e819007aba98d14f7a145b550e6,data,2e21433e2bcd4de2a6b6167c6233cd40", diff --git a/subworkflows/nf-neuro/preproc_t1/tests/nextflow.config b/subworkflows/nf-neuro/preproc_t1/tests/nextflow.config index 0cf9bdfd..53b6f8f3 100644 --- a/subworkflows/nf-neuro/preproc_t1/tests/nextflow.config +++ b/subworkflows/nf-neuro/preproc_t1/tests/nextflow.config @@ -9,3 +9,9 @@ process { ext.first_suffix = "t1" } } + +params.run_denoising = true +params.run_N4 = true +params.run_resampling = true +params.run_bet = true +params.run_crop = true diff --git a/subworkflows/nf-neuro/preproc_t1/tests/nextflow_quick.config b/subworkflows/nf-neuro/preproc_t1/tests/nextflow_quick.config new file mode 100644 index 00000000..864e5ead --- /dev/null +++ b/subworkflows/nf-neuro/preproc_t1/tests/nextflow_quick.config @@ -0,0 +1,17 @@ +process { + publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } + withName: "IMAGE_RESAMPLE" { + ext.voxel_size = 1 + ext.interp = "lin" + } + withName: "BETCROP_CROPVOLUME_T1" { + ext.output_bbox = false + ext.first_suffix = "t1" + } +} + +params.run_denoising = true +params.run_N4 = false +params.run_resampling = true +params.run_bet = false +params.run_crop = true diff --git a/subworkflows/nf-neuro/preproc_t1/tests/nextflow_quick_bis.config b/subworkflows/nf-neuro/preproc_t1/tests/nextflow_quick_bis.config new file mode 100644 index 00000000..2aa776bd --- /dev/null +++ b/subworkflows/nf-neuro/preproc_t1/tests/nextflow_quick_bis.config @@ -0,0 +1,14 @@ +process { + publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } + withName: "BETCROP_SYNTHBET" { + memory = "8G" + ext.nocsf = true + } +} + +params.run_denoising = false +params.run_N4 = true +params.run_resampling = false +params.run_bet = true +params.run_synthbet = true +params.run_crop = false diff --git a/subworkflows/nf-neuro/preproc_t1/tests/nextflow_synthbet.config b/subworkflows/nf-neuro/preproc_t1/tests/nextflow_synthbet.config index ce65c778..4a5115aa 100644 --- a/subworkflows/nf-neuro/preproc_t1/tests/nextflow_synthbet.config +++ b/subworkflows/nf-neuro/preproc_t1/tests/nextflow_synthbet.config @@ -14,4 +14,9 @@ process { } } +params.run_denoising = true +params.run_N4 = true +params.run_resampling = true +params.run_bet = true params.run_synthbet = true +params.run_crop = true