From 3ca17f1f829a868f666c614f52fd9a5f5ef595cb Mon Sep 17 00:00:00 2001 From: alejandrogzi Date: Wed, 30 Sep 2026 13:25:41 +0200 Subject: [PATCH] feat(oriongeno): add Nextflow and WDL annotation modules Wrap ghcr.io/hillerlab/oriongeno for single-GPU prediction and multi-GPU annotation. Lineage selects the checkpoint; species name does not. Predict writes GTF by default and GFF3 on request. --- assets/catalog/catalog.md | 2 + modules/nextflow/oriongeno/multi/main.nf | 114 +++++++++++++++++++ modules/nextflow/oriongeno/predict/main.nf | 105 ++++++++++++++++++ modules/wdl/oriongeno/multi/main.wdl | 119 ++++++++++++++++++++ modules/wdl/oriongeno/predict/main.wdl | 122 +++++++++++++++++++++ 5 files changed, 462 insertions(+) create mode 100644 modules/nextflow/oriongeno/multi/main.nf create mode 100644 modules/nextflow/oriongeno/predict/main.nf create mode 100644 modules/wdl/oriongeno/multi/main.wdl create mode 100644 modules/wdl/oriongeno/predict/main.wdl diff --git a/assets/catalog/catalog.md b/assets/catalog/catalog.md index 09dfd85..19ab2b9 100644 --- a/assets/catalog/catalog.md +++ b/assets/catalog/catalog.md @@ -120,6 +120,8 @@ - [tiberius/merge](../../modules/nextflow/tiberius/merge/main.nf) — Merge chunk GTFs with merge_annotations.py, restore FASTA order, emit GTF/GFF3 and optional protein/CDS FASTAs. - [subworkflows/tiberius](../../subworkflows/tiberius/main.nf) — Scatter (none / chromosome / weighted), predict, and merge. - [workflows/tiberius](../../workflows/tiberius/main.nf) — Standalone Tiberius pipeline. +- [oriongeno/predict](../../modules/nextflow/oriongeno/predict/main.nf) — OrionGeno ab initio gene annotation on one GPU (`process_gpu`). No CPU fallback. Default checkpoint: mammals. GTF by default, GFF3 via `meta.oriongeno_format`. Bundled OrionGeno is non-commercial. +- [oriongeno/multi](../../modules/nextflow/oriongeno/multi/main.nf) — OrionGeno annotation across the GPUs assigned to the task (`oriongeno multi`). Writes GTF. `process_gpu` requests one GPU; raise `accelerator` on `ORIONGENO_MULTI` to use more. --- diff --git a/modules/nextflow/oriongeno/multi/main.nf b/modules/nextflow/oriongeno/multi/main.nf new file mode 100644 index 0000000..a3dd0df --- /dev/null +++ b/modules/nextflow/oriongeno/multi/main.nf @@ -0,0 +1,114 @@ +/* +Copyright (c) 2026 The Hiller Lab at the Senckenberg Gessellschaft für Naturforschung +Distributed under the terms of the Apache License, Version 2.0. +*/ + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ORIONGENO_MULTI — Ab initio OrionGeno annotation across one or more GPUs. + + Thin wrapper around `oriongeno multi`. `--devices` is the GPU list + Nextflow assigned in CUDA_VISIBLE_DEVICES, or 0 when that variable is + unset. `label 'process_gpu'` requests one GPU under `-profile gpu`. + To use several GPUs, set accelerator for this process in the config; + that selector overrides the label: + + process { + withName: 'ORIONGENO_MULTI' { + accelerator = 4 + } + } + + `oriongeno multi` writes GTF. meta.oriongeno_format other than gtf is + an error. Lineage, species, repeat output, and extra args match + ORIONGENO_PREDICT. Inference requires an NVIDIA GPU; there is no CPU + fallback. Bundled OrionGeno is non-commercial. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +process ORIONGENO_MULTI { + tag "$meta.id" + label 'process_high' + label 'process_gpu' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + '' : + 'ghcr.io/hillerlab/oriongeno:latest' }" + + input: + tuple val(meta), path(genome) + val lineage + + output: + tuple val(meta), path("annotation/*"), emit: annotation + tuple val(meta), path("repeats/*"), emit: repeats, optional: true + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = [task.ext.args ?: '', meta.oriongeno_args ?: ''].findAll { it }.join(' ') + def prefix = task.ext.prefix ?: "${meta.id}" + def known = ['mammals', 'birds', 'fish', 'other_vertebrates', 'arthropods', 'other_invertebrates', 'plants', 'fungi'] + def lineage_name = lineage.toString() + def checkpoint = (task.ext.checkpoint ?: meta.checkpoint ?: '').toString() + if (!checkpoint) { + if (!known.contains(lineage_name)) { + error "Unknown OrionGeno lineage '${lineage_name}'. Expected one of: ${known.join(', ')}. Or set task.ext.checkpoint / meta.checkpoint to a checkpoint directory." + } + checkpoint = "/opt/oriongeno/checkpoints/oriongeno_${lineage_name}" + } + def format = (meta.oriongeno_format ?: 'gtf').toString().toLowerCase() + if (format != 'gtf') { + error "ORIONGENO_MULTI writes GTF. oriongeno multi does not emit GFF." + } + def species = (task.ext.species_name ?: meta.species_name ?: meta.species ?: '').toString() + def species_arg = species ? "--species-name \"${species}\"" : '' + def emit_repeat = meta.oriongeno_output_repeat == true || meta.oriongeno_output_repeat == 'true' + """ + set -euo pipefail + mkdir -p annotation + + devices="\${CUDA_VISIBLE_DEVICES:-0}" + if [ -z "\$devices" ]; then + devices=0 + fi + + oriongeno multi \\ + --genome "${genome}" \\ + --output "annotation/${prefix}.gtf" \\ + --checkpoint "${checkpoint}" \\ + --devices "\$devices" \\ + --work-dir oriongeno-work \\ + --output-gene true \\ + --output-repeat ${emit_repeat ? 'true' : 'false'} \\ + ${species_arg} \\ + ${args} + + if [ -f "annotation/${prefix}.repeat.gtf" ]; then + mkdir -p repeats + mv "annotation/${prefix}.repeat.gtf" "repeats/${prefix}.repeat.gtf" + fi + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + oriongeno: \$( oriongeno --version 2>/dev/null | sed 's/^oriongeno //' || echo "2026.7.10" ) + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def emit_repeat = meta.oriongeno_output_repeat == true || meta.oriongeno_output_repeat == 'true' + """ + set -euo pipefail + mkdir -p annotation + touch "annotation/${prefix}.gtf" + ${emit_repeat ? "mkdir -p repeats && touch \"repeats/${prefix}.repeat.gtf\"" : ''} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + oriongeno: \$( oriongeno --version 2>/dev/null | sed 's/^oriongeno //' || echo "2026.7.10" ) + END_VERSIONS + """ +} diff --git a/modules/nextflow/oriongeno/predict/main.nf b/modules/nextflow/oriongeno/predict/main.nf new file mode 100644 index 0000000..d28f281 --- /dev/null +++ b/modules/nextflow/oriongeno/predict/main.nf @@ -0,0 +1,105 @@ +/* +Copyright (c) 2026 The Hiller Lab at the Senckenberg Gessellschaft für Naturforschung +Distributed under the terms of the Apache License, Version 2.0. +*/ + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ORIONGENO_PREDICT — Ab initio OrionGeno annotation on one GPU. + + Thin wrapper around `oriongeno`. Inference requires an NVIDIA GPU + (compute capability >= 7.0, driver >= 525.60.13). There is no CPU fallback. + `lineage` selects a checkpoint baked into the image. It does not select + the species. The published image contains mammals unless it was built + with ORIONGENO_MODELS. A directory in task.ext.checkpoint or + meta.checkpoint overrides the lineage. Species conditioning is + meta.species_name (or meta.species, or task.ext.species_name). + meta.oriongeno_format is gtf (default) or gff3. meta.oriongeno_output_repeat + also writes the repeat annotation. Extra flags go in task.ext.args or + meta.oriongeno_args and are not given biological defaults here. + Bundled OrionGeno is non-commercial. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +process ORIONGENO_PREDICT { + tag "$meta.id" + label 'process_high' + label 'process_gpu' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + '' : + 'ghcr.io/hillerlab/oriongeno:latest' }" + + input: + tuple val(meta), path(genome) + val lineage + + output: + tuple val(meta), path("annotation/*"), emit: annotation + tuple val(meta), path("repeats/*"), emit: repeats, optional: true + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = [task.ext.args ?: '', meta.oriongeno_args ?: ''].findAll { it }.join(' ') + def prefix = task.ext.prefix ?: "${meta.id}" + def known = ['mammals', 'birds', 'fish', 'other_vertebrates', 'arthropods', 'other_invertebrates', 'plants', 'fungi'] + def lineage_name = lineage.toString() + def checkpoint = (task.ext.checkpoint ?: meta.checkpoint ?: '').toString() + if (!checkpoint) { + if (!known.contains(lineage_name)) { + error "Unknown OrionGeno lineage '${lineage_name}'. Expected one of: ${known.join(', ')}. Or set task.ext.checkpoint / meta.checkpoint to a checkpoint directory." + } + checkpoint = "/opt/oriongeno/checkpoints/oriongeno_${lineage_name}" + } + def format = (meta.oriongeno_format ?: 'gtf').toString().toLowerCase() + if (!(format in ['gtf', 'gff', 'gff3'])) { + error "Unknown OrionGeno format '${format}'. Use gtf or gff3." + } + def ext = (format == 'gff' || format == 'gff3') ? 'gff3' : 'gtf' + def species = (task.ext.species_name ?: meta.species_name ?: meta.species ?: '').toString() + def species_arg = species ? "--species-name \"${species}\"" : '' + def emit_repeat = meta.oriongeno_output_repeat == true || meta.oriongeno_output_repeat == 'true' + """ + set -euo pipefail + mkdir -p annotation + + oriongeno \\ + --genome "${genome}" \\ + --output "annotation/${prefix}.${ext}" \\ + --checkpoint "${checkpoint}" \\ + --output-gene true \\ + --output-repeat ${emit_repeat ? 'true' : 'false'} \\ + ${species_arg} \\ + ${args} + + if [ -f "annotation/${prefix}.repeat.${ext}" ]; then + mkdir -p repeats + mv "annotation/${prefix}.repeat.${ext}" "repeats/${prefix}.repeat.${ext}" + fi + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + oriongeno: \$( oriongeno --version 2>/dev/null | sed 's/^oriongeno //' || echo "2026.7.10" ) + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def format = (meta.oriongeno_format ?: 'gtf').toString().toLowerCase() + def ext = (format == 'gff' || format == 'gff3') ? 'gff3' : 'gtf' + def emit_repeat = meta.oriongeno_output_repeat == true || meta.oriongeno_output_repeat == 'true' + """ + set -euo pipefail + mkdir -p annotation + touch "annotation/${prefix}.${ext}" + ${emit_repeat ? "mkdir -p repeats && touch \"repeats/${prefix}.repeat.${ext}\"" : ''} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + oriongeno: \$( oriongeno --version 2>/dev/null | sed 's/^oriongeno //' || echo "2026.7.10" ) + END_VERSIONS + """ +} diff --git a/modules/wdl/oriongeno/multi/main.wdl b/modules/wdl/oriongeno/multi/main.wdl new file mode 100644 index 0000000..e29fa17 --- /dev/null +++ b/modules/wdl/oriongeno/multi/main.wdl @@ -0,0 +1,119 @@ +# Copyright (c) 2026 The Hiller Lab at the Senckenberg Gessellschaft für Naturforschung +# Distributed under the terms of the Apache License, Version 2.0. + +# ORIONGENO_MULTI — Ab initio OrionGeno annotation across one or more GPUs. +# Thin wrapper around `oriongeno multi`. devices is a comma-separated list of +# GPU ids ("0,1,2,3"). The caller schedules those devices; this task writes GTF. +# lineage, species_name, output_repeat, checkpoint, and extra_args match predict. +# Inference requires an NVIDIA GPU. There is no CPU fallback. +# Bundled OrionGeno is non-commercial. + +version 1.3 + +task multi { + input { + File genome + String devices + String lineage = "mammals" + String species_name = "" + Boolean output_repeat = false + String checkpoint = "" + String extra_args = "" + String prefix = "" + } + + String stem = sub(sub(basename(genome), "\\.gz$", ""), "\\.(fa|fasta|fna|fas)$", "") + String sample = if prefix != "" then prefix else stem + String annotation_path = "annotation/" + sample + ".gtf" + String repeat_path = "repeats/" + sample + ".repeat.gtf" + + command <<< + set -euo pipefail + + lineage="~{lineage}" + devices="~{devices}" + checkpoint="~{checkpoint}" + species="~{species_name}" + + if [ -z "$devices" ]; then + echo "ORIONGENO_MULTI requires at least one GPU id in devices." >&2 + exit 1 + fi + + if [ -n "$checkpoint" ]; then + ckpt="$checkpoint" + else + case "$lineage" in + mammals|birds|fish|other_vertebrates|arthropods|other_invertebrates|plants|fungi) ;; + *) + echo "Unknown OrionGeno lineage '$lineage'." >&2 + exit 1 + ;; + esac + ckpt="/opt/oriongeno/checkpoints/oriongeno_${lineage}" + fi + + mkdir -p annotation + species_args=() + if [ -n "$species" ]; then + species_args+=(--species-name "$species") + fi + + oriongeno multi \ + --genome "~{genome}" \ + --output "~{annotation_path}" \ + --checkpoint "$ckpt" \ + --devices "$devices" \ + --work-dir oriongeno-work \ + --output-gene true \ + --output-repeat ~{if output_repeat then "true" else "false"} \ + "${species_args[@]}" \ + ~{extra_args} + + gene="~{annotation_path}" + repeat_src="${gene%.*}.repeat.${gene##*.}" + if [ -f "$repeat_src" ]; then + mkdir -p repeats + mv "$repeat_src" "~{repeat_path}" + fi + >>> + + output { + File annotation = annotation_path + File? repeats = repeat_path + } + + requirements { + container: "ghcr.io/hillerlab/oriongeno:latest" + } +} + +workflow run { + input { + File genome + String devices + String lineage = "mammals" + String species_name = "" + Boolean output_repeat = false + String checkpoint = "" + String extra_args = "" + String prefix = "" + } + + call multi { + input: + genome = genome, + devices = devices, + lineage = lineage, + species_name = species_name, + output_repeat = output_repeat, + checkpoint = checkpoint, + extra_args = extra_args, + prefix = prefix + } + + output { + File annotation = multi.annotation + File? repeats = multi.repeats + } +} diff --git a/modules/wdl/oriongeno/predict/main.wdl b/modules/wdl/oriongeno/predict/main.wdl new file mode 100644 index 0000000..76bcbd3 --- /dev/null +++ b/modules/wdl/oriongeno/predict/main.wdl @@ -0,0 +1,122 @@ +# Copyright (c) 2026 The Hiller Lab at the Senckenberg Gessellschaft für Naturforschung +# Distributed under the terms of the Apache License, Version 2.0. + +# ORIONGENO_PREDICT — Ab initio OrionGeno annotation on one GPU. +# Inference requires an NVIDIA GPU (compute capability >= 7.0, driver >= 525.60.13). +# There is no CPU fallback. lineage selects a checkpoint baked into the image +# and does not select the species. The published image contains mammals unless +# it was built with ORIONGENO_MODELS. checkpoint overrides lineage when set. +# format is gtf (default) or gff3. output_repeat also writes the repeat annotation. +# extra_args is appended as-is. Bundled OrionGeno is non-commercial. + +version 1.3 + +task predict { + input { + File genome + String lineage = "mammals" + String species_name = "" + String format = "gtf" + Boolean output_repeat = false + String checkpoint = "" + String extra_args = "" + String prefix = "" + } + + String stem = sub(sub(basename(genome), "\\.gz$", ""), "\\.(fa|fasta|fna|fas)$", "") + String sample = if prefix != "" then prefix else stem + String ext = if format == "gff" || format == "gff3" then "gff3" else "gtf" + String annotation_path = "annotation/" + sample + "." + ext + String repeat_path = "repeats/" + sample + ".repeat." + ext + + command <<< + set -euo pipefail + + lineage="~{lineage}" + format="~{format}" + checkpoint="~{checkpoint}" + species="~{species_name}" + + case "$format" in + gtf|gff|gff3) ;; + *) + echo "Unknown OrionGeno format '$format'. Use gtf or gff3." >&2 + exit 1 + ;; + esac + + if [ -n "$checkpoint" ]; then + ckpt="$checkpoint" + else + case "$lineage" in + mammals|birds|fish|other_vertebrates|arthropods|other_invertebrates|plants|fungi) ;; + *) + echo "Unknown OrionGeno lineage '$lineage'." >&2 + exit 1 + ;; + esac + ckpt="/opt/oriongeno/checkpoints/oriongeno_${lineage}" + fi + + mkdir -p annotation + species_args=() + if [ -n "$species" ]; then + species_args+=(--species-name "$species") + fi + + oriongeno \ + --genome "~{genome}" \ + --output "~{annotation_path}" \ + --checkpoint "$ckpt" \ + --output-gene true \ + --output-repeat ~{if output_repeat then "true" else "false"} \ + "${species_args[@]}" \ + ~{extra_args} + + gene="~{annotation_path}" + repeat_src="${gene%.*}.repeat.${gene##*.}" + if [ -f "$repeat_src" ]; then + mkdir -p repeats + mv "$repeat_src" "~{repeat_path}" + fi + >>> + + output { + File annotation = annotation_path + File? repeats = repeat_path + } + + requirements { + container: "ghcr.io/hillerlab/oriongeno:latest" + } +} + +workflow run { + input { + File genome + String lineage = "mammals" + String species_name = "" + String format = "gtf" + Boolean output_repeat = false + String checkpoint = "" + String extra_args = "" + String prefix = "" + } + + call predict { + input: + genome = genome, + lineage = lineage, + species_name = species_name, + format = format, + output_repeat = output_repeat, + checkpoint = checkpoint, + extra_args = extra_args, + prefix = prefix + } + + output { + File annotation = predict.annotation + File? repeats = predict.repeats + } +}