diff --git a/modules/nf-core/hdl/h2/environment.yml b/modules/nf-core/hdl/h2/environment.yml new file mode 100644 index 000000000000..cdc13789af23 --- /dev/null +++ b/modules/nf-core/hdl/h2/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda + - dnachun +dependencies: + - conda-forge::r-base=4.3.1 + - conda-forge::r-data.table=1.15.4 + - dnachun::r-hdl=1.4.0 diff --git a/modules/nf-core/hdl/h2/main.nf b/modules/nf-core/hdl/h2/main.nf new file mode 100644 index 000000000000..457072bec68a --- /dev/null +++ b/modules/nf-core/hdl/h2/main.nf @@ -0,0 +1,35 @@ +process HDL_H2 { + tag "${meta.id}" + label 'process_medium' + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'oras://community.wave.seqera.io/library/r-base_r-data.table_r-hdl:a2553504418194cc' + : 'community.wave.seqera.io/library/r-base_r-data.table_r-hdl:cb9d70356e12d034'}" + + input: + tuple val(meta), path(sumstats) + tuple val(meta2), path(hdl_ref_panel_dir) + + output: + tuple val(meta), path("${meta.id}.h2.tsv"), emit: heritability_results + tuple val(meta), path("${meta.id}.hdl.log"), emit: h2_log + tuple val("${task.process}"), val("hdl"), val("1.4.0"), emit: versions_hdl, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + template('hdl_h2.R') + + stub: + """ + cat < ${meta.id}.h2.tsv +trait\th2\tse\tp\teigen_use +${meta.id}\t0.2100\t0.0300\t0.00010\tautomatic +EOF_STUB + + cat < ${meta.id}.hdl.log +Stub HDL h2 run for ${meta.id} +EOF_STUB + """ +} diff --git a/modules/nf-core/hdl/h2/meta.yml b/modules/nf-core/hdl/h2/meta.yml new file mode 100644 index 000000000000..4c4a7533fcf1 --- /dev/null +++ b/modules/nf-core/hdl/h2/meta.yml @@ -0,0 +1,64 @@ +name: hdl_h2 +description: Estimate SNP heritability from GWAS summary statistics using HDL.h2 +keywords: + - hdl + - heritability + - gwas +tools: + - hdl: + description: High-definition likelihood method for genetic correlation and heritability from summary statistics + homepage: https://github.com/zhenin/HDL + documentation: https://github.com/zhenin/HDL/wiki +input: + - - meta: + type: map + description: Metadata map for the trait input. The `meta.id` value is used to label output files. + - sumstats: + type: file + description: Canonicalized GWAS summary statistics for the trait in `meta.id`. + - - meta2: + type: map + description: Metadata map for the HDL reference panel directory. + - hdl_ref_panel_dir: + type: directory + description: Directory containing HDL LD reference panel files. +output: + heritability_results: + - - meta: + type: map + description: Propagated trait metadata map. + - ${meta.id}.h2.tsv: + type: file + description: Tab-delimited HDL heritability summary output. + h2_log: + - - meta: + type: map + description: Propagated trait metadata map. + - ${meta.id}.hdl.log: + type: file + description: HDL heritability run log. + versions_hdl: + - - ${task.process}: + type: string + description: Name of the process + - hdl: + type: string + description: Name of the tool + - 1.4.0: + type: string + description: Version of the HDL R package bundled for the module +topics: + versions: + - - ${task.process}: + type: string + description: Name of the process + - hdl: + type: string + description: Name of the tool + - 1.4.0: + type: string + description: Version of the HDL R package bundled for the module +authors: + - "@lyh970817" +maintainers: + - "@lyh970817" diff --git a/modules/nf-core/hdl/h2/templates/hdl_h2.R b/modules/nf-core/hdl/h2/templates/hdl_h2.R new file mode 100644 index 000000000000..05f46887925a --- /dev/null +++ b/modules/nf-core/hdl/h2/templates/hdl_h2.R @@ -0,0 +1,44 @@ +#!/usr/bin/env Rscript + +suppressPackageStartupMessages({ + library(data.table) +}) + +parse_hdl_ext_args <- function(raw_ext_args = "${task.ext.args ?: ''}") { + if (raw_ext_args %in% c("", "[]")) { + return(list()) + } + tryCatch( + eval(parse(text = sprintf("list(%s)", raw_ext_args))), + error = function(e) stop(sprintf("Failed to parse task.ext.args as R named arguments: %s", conditionMessage(e))) + ) +} + +if (!requireNamespace("HDL", quietly = TRUE)) { + stop("R package 'HDL' is not installed. Install HDL in the runtime container/environment.") +} + +gwas <- fread("${sumstats}", data.table = FALSE) +hdl_ext_args <- parse_hdl_ext_args() + +call_args <- c( + list( + gwas.df = gwas, + LD.path = "${hdl_ref_panel_dir}", + output.file = "${meta.id}.hdl.log" + ), + hdl_ext_args +) + +result <- do.call(HDL::HDL.h2, call_args) + +out <- data.frame( + trait = "${meta.id}", + h2 = as.numeric(result[["h2"]]), + se = as.numeric(result[["h2.se"]]), + p = as.numeric(result[["P"]]), + eigen_use = as.character(result[["eigen.use"]]), + stringsAsFactors = FALSE +) + +fwrite(out, "${meta.id}.h2.tsv", sep = "\t", quote = FALSE, na = "NA") diff --git a/modules/nf-core/hdl/h2/tests/main.nf.test b/modules/nf-core/hdl/h2/tests/main.nf.test new file mode 100644 index 000000000000..43c2bcd99da4 --- /dev/null +++ b/modules/nf-core/hdl/h2/tests/main.nf.test @@ -0,0 +1,81 @@ +nextflow_process { + + name "Test Process HDL_H2" + script "../main.nf" + process "HDL_H2" + tag "modules" + tag "modules_nfcore" + tag "hdl" + tag "hdl/h2" + + test("trait1 - real") { + when { + process { + """ + input[0] = tuple( + [id: "trait1"], + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/sumstats/trait1_canonical.tsv", checkIfExists: true) + ) + input[1] = Channel.of([ + [id: "hdl_reference"], + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/chr1.1_toy.bim", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/chr1.1_toy.rda", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/chr1.2_toy.bim", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/chr1.2_toy.rda", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/toy_snp_counter.RData", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/toy_snp_list.RData", checkIfExists: true) + ]) + .map { meta, files -> + def reference_dir = file("${workDir}/hdl_reference") + reference_dir.mkdirs() + files.each { it.copyTo(file("${reference_dir}/${it.name}")) } + [meta, reference_dir] + } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["h2_log"])).match() } + ) + } + } + + test("trait1 - stub") { + options "-stub" + when { + process { + """ + input[0] = tuple( + [id: "trait1"], + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/sumstats/trait1_canonical.tsv", checkIfExists: true) + ) + input[1] = Channel.of([ + [id: "hdl_reference"], + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/chr1.1_toy.bim", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/chr1.1_toy.rda", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/chr1.2_toy.bim", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/chr1.2_toy.rda", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/toy_snp_counter.RData", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/homo_sapiens/popgen/hdl/reference/toy_snp_list.RData", checkIfExists: true) + ]) + .map { meta, files -> + def reference_dir = file("${workDir}/hdl_reference") + reference_dir.mkdirs() + files.each { it.copyTo(file("${reference_dir}/${it.name}")) } + [meta, reference_dir] + } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/modules/nf-core/hdl/h2/tests/main.nf.test.snap b/modules/nf-core/hdl/h2/tests/main.nf.test.snap new file mode 100644 index 000000000000..6c3577054fe4 --- /dev/null +++ b/modules/nf-core/hdl/h2/tests/main.nf.test.snap @@ -0,0 +1,70 @@ +{ + "trait1 - stub": { + "content": [ + { + "h2_log": [ + [ + { + "id": "trait1" + }, + "trait1.hdl.log:md5,a13a7344736e93264a761c4ee98c7ede" + ] + ], + "heritability_results": [ + [ + { + "id": "trait1" + }, + "trait1.h2.tsv:md5,ce00dd50cc23b53da1e226dcffcd55e4" + ] + ], + "versions_hdl": [ + [ + "HDL_H2", + "hdl", + "1.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-06-11T01:01:10.97011229" + }, + "trait1 - real": { + "content": [ + { + "h2_log": [ + [ + { + "id": "trait1" + }, + "trait1.hdl.log" + ] + ], + "heritability_results": [ + [ + { + "id": "trait1" + }, + "trait1.h2.tsv:md5,55e85c234116e695451a39de1f55ecd2" + ] + ], + "versions_hdl": [ + [ + "HDL_H2", + "hdl", + "1.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-06-11T01:01:06.2985062" + } +} \ No newline at end of file