diff --git a/modules/nf-neuro/connectivity/decompose/environment.yml b/modules/nf-neuro/connectivity/decompose/environment.yml new file mode 100644 index 0000000..83cf516 --- /dev/null +++ b/modules/nf-neuro/connectivity/decompose/environment.yml @@ -0,0 +1,3 @@ +channels: [] +dependencies: [] +name: connectivity_decompose diff --git a/modules/nf-neuro/connectivity/decompose/main.nf b/modules/nf-neuro/connectivity/decompose/main.nf new file mode 100644 index 0000000..77c9fc2 --- /dev/null +++ b/modules/nf-neuro/connectivity/decompose/main.nf @@ -0,0 +1,63 @@ +process CONNECTIVITY_DECOMPOSE { + tag "$meta.id" + label 'process_high' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'scil.usherbrooke.ca/containers/scilus_2.0.2.sif': + 'scilus/scilus:2.0.2' }" + + input: + tuple val(meta), path(trk), path(labels) + + output: + tuple val(meta), path("*__decomposed.h5") , emit: hdf5 + tuple val(meta), path("*__labels_list.txt") , emit: labels_list + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def no_pruning = task.ext.no_pruning ? "--no_pruning" : "" + def no_remove_loops = task.ext.no_remove_loops ? "--no_remove_loops" : "" + def no_remove_outliers = task.ext.no_remove_outliers ? "--no_remove_outliers" : "" + def no_remove_curv = task.ext.no_remove_curv ? "--no_remove_curv" : "" + + def min_len = task.ext.min_len ? "--min_len " + task.ext.min_len : "" + def max_len = task.ext.max_len ? "--max_len " + task.ext.max_len : "" + def outlier_threshold = task.ext.outlier_threshold ? "--outlier_threshold " + task.ext.outlier_threshold : "" + def max_angle = task.ext.max_angle ? "--loop_max_angle " + task.ext.max_angle : "" + def max_curv = task.ext.max_curv ? "--curv_qb_distance " + task.ext.max_curv : "" + + """ + + scil_decompose_connectivity.py $trk $labels \ + "${prefix}__decomposed.h5" --processes $task.cpus \ + --out_labels_list "${prefix}__labels_list.txt" \ + $no_pruning $no_remove_loops $no_remove_outliers \ + $no_remove_curv $min_len $max_len $outlier_threshold \ + $max_angle $max_curv + + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: \$(pip list | grep scilpy | tr -s ' ' | cut -d' ' -f2) + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + touch ${prefix}__decomposed.h5 + touch ${prefix}__labels_list.txt + + scil_decompose_connectivity.py -h + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + scilpy: \$(pip list | grep scilpy | tr -s ' ' | cut -d' ' -f2) + END_VERSIONS + """ +} diff --git a/modules/nf-neuro/connectivity/decompose/meta.yml b/modules/nf-neuro/connectivity/decompose/meta.yml new file mode 100644 index 0000000..27ca93f --- /dev/null +++ b/modules/nf-neuro/connectivity/decompose/meta.yml @@ -0,0 +1,61 @@ +--- +name: "connectivity_decompose" +description: Divide a tractogram into its various connections using a brain parcellation(labels). + The hdf5 output format allows to store other information required for connectivity, such as the associated labels. +keywords: + - nifti + - connectivity + - decompose + - scilpy +tools: + - "scilpy": + description: "The Sherbrooke Connectivity Imaging Lab (SCIL) Python dMRI processing toolbox." + homepage: "https://github.com/scilus/scilpy.git" + +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + + - trk: + type: file + description: Tractogram to decompose. + pattern: "*.{trk, tck, vtk, fib, dpy}" + + - labels: + type: file + description: brain parcellation. Labels must have 0 as background. Volumes must have isotropic voxels. + pattern: "*.nii.gz" + +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + + - hdf5: + type: file + description: | + Output hdf5 file where each bundles is a group with key'LABEL1_LABEL2'. + The array_sequence format cannot be stored directly in a hdf5, so each + group is composed of 'data', 'offsets' and 'lengths' from the array + sequence. The 'data' is stored in VOX/CORNER for simplicity and efficiency. + pattern: "*__decomposed.h5" + + - labels_list: + type: file + description: Save the labels list as text file. + pattern: "*__labels_list.txt" + + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@ThoumyreStanislas" +maintainers: + - "@ThoumyreStanislas" diff --git a/modules/nf-neuro/connectivity/decompose/tests/main.nf.test b/modules/nf-neuro/connectivity/decompose/tests/main.nf.test new file mode 100644 index 0000000..076ae98 --- /dev/null +++ b/modules/nf-neuro/connectivity/decompose/tests/main.nf.test @@ -0,0 +1,93 @@ +nextflow_process { + + name "Test Process CONNECTIVITY_DECOMPOSE" + script "../main.nf" + process "CONNECTIVITY_DECOMPOSE" + + tag "modules" + tag "modules_nfcore" + tag "connectivity" + tag "connectivity/decompose" + + 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( [ "connectivity.zip" ] ) + input[1] = "test.load-test-data" + """ + } + } + } + + test("connectivity - decompose - without option") { + when { + process { + """ + input[0] = LOAD_DATA.out.test_data_directory.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], // meta map + file("\${test_data_directory}/bundle_all_1mm.trk", checkIfExists: true), + file("\${test_data_directory}/endpoints_atlas.nii.gz", checkIfExists: true) + ] + } + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("connectivity - decompose - with all option") { + config "./nextflow.config" + when { + process { + """ + input[0] = LOAD_DATA.out.test_data_directory.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], // meta map + file("\${test_data_directory}/bundle_all_1mm.trk", checkIfExists: true), + file("\${test_data_directory}/endpoints_atlas.nii.gz", checkIfExists: true) + ] + } + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("connectivity - visualisation - stub-run") { + options "-stub-run" + when { + process { + """ + input[0] = LOAD_DATA.out.test_data_directory.map{ + test_data_directory -> [ + [ id:'test', single_end:false ], // meta map + file("\${test_data_directory}/bundle_all_1mm.trk", checkIfExists: true), + file("\${test_data_directory}/endpoints_atlas.nii.gz", checkIfExists: true) + ] + } + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match() } + ) + } + } +} diff --git a/modules/nf-neuro/connectivity/decompose/tests/main.nf.test.snap b/modules/nf-neuro/connectivity/decompose/tests/main.nf.test.snap new file mode 100644 index 0000000..10832c3 --- /dev/null +++ b/modules/nf-neuro/connectivity/decompose/tests/main.nf.test.snap @@ -0,0 +1,120 @@ +{ + "connectivity - decompose - without option": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test__decomposed.h5:md5,90ff9ae7ceb1371349231f85b616874e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test__labels_list.txt:md5,3b0332e02daabf31651a5a0d81ba830a" + ] + ], + "2": [ + "versions.yml:md5,ca6d20bf86c1e32c1bccea5813810f02" + ], + "hdf5": [ + [ + { + "id": "test", + "single_end": false + }, + "test__decomposed.h5:md5,90ff9ae7ceb1371349231f85b616874e" + ] + ], + "labels_list": [ + [ + { + "id": "test", + "single_end": false + }, + "test__labels_list.txt:md5,3b0332e02daabf31651a5a0d81ba830a" + ] + ], + "versions": [ + "versions.yml:md5,ca6d20bf86c1e32c1bccea5813810f02" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.3" + }, + "timestamp": "2025-01-16T12:03:16.31875384" + }, + "connectivity - visualisation - stub-run": { + "content": [ + [ + "versions.yml:md5,ca6d20bf86c1e32c1bccea5813810f02" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.3" + }, + "timestamp": "2025-01-16T12:03:27.09566413" + }, + "connectivity - decompose - with all option": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test__decomposed.h5:md5,5660bb84f333b6586ed247c6b95f6a98" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test__labels_list.txt:md5,3b0332e02daabf31651a5a0d81ba830a" + ] + ], + "2": [ + "versions.yml:md5,ca6d20bf86c1e32c1bccea5813810f02" + ], + "hdf5": [ + [ + { + "id": "test", + "single_end": false + }, + "test__decomposed.h5:md5,5660bb84f333b6586ed247c6b95f6a98" + ] + ], + "labels_list": [ + [ + { + "id": "test", + "single_end": false + }, + "test__labels_list.txt:md5,3b0332e02daabf31651a5a0d81ba830a" + ] + ], + "versions": [ + "versions.yml:md5,ca6d20bf86c1e32c1bccea5813810f02" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.3" + }, + "timestamp": "2025-01-16T12:03:21.738887954" + } +} \ No newline at end of file diff --git a/modules/nf-neuro/connectivity/decompose/tests/nextflow.config b/modules/nf-neuro/connectivity/decompose/tests/nextflow.config new file mode 100644 index 0000000..6a7a5d4 --- /dev/null +++ b/modules/nf-neuro/connectivity/decompose/tests/nextflow.config @@ -0,0 +1,13 @@ +process { + withName: "CONNECTIVITY_DECOMPOSE" { + ext.no_pruning = true + ext.no_remove_loops = true + ext.no_remove_outliers = true + ext.no_remove_curv = true + ext.min_len = 20.0 + ext.max_len = 200.0 + ext.outlier_threshold = 0.6 + ext.max_angle = 330.0 + ext.max_curv = 10.0 + } + } diff --git a/modules/nf-neuro/connectivity/decompose/tests/tags.yml b/modules/nf-neuro/connectivity/decompose/tests/tags.yml new file mode 100644 index 0000000..7779895 --- /dev/null +++ b/modules/nf-neuro/connectivity/decompose/tests/tags.yml @@ -0,0 +1,2 @@ +connectivity/decompose: + - "modules/nf-neuro/connectivity/decompose/**"