From 8cfcb4c7a6e9dc3030ea3ab7c68cd074d6cbd42f Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 30 Jul 2024 17:54:27 +0200 Subject: [PATCH 001/123] Pin zenodo doi in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63a92f26..a8a25a52 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![GitHub Actions CI Status](https://github.com/nf-core/proteinfold/actions/workflows/ci.yml/badge.svg)](https://github.com/nf-core/proteinfold/actions/workflows/ci.yml) -[![GitHub Actions Linting Status](https://github.com/nf-core/proteinfold/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/proteinfold/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/proteinfold/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) +[![GitHub Actions Linting Status](https://github.com/nf-core/proteinfold/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/proteinfold/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/proteinfold/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.13135393-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.13135393) [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) [![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A523.04.0-23aa62.svg)](https://www.nextflow.io/) From 06735054ac6329efaab1bd77c7717081955add64 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 30 Jul 2024 17:54:53 +0200 Subject: [PATCH 002/123] Update changelog for v1.2.0dev --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fbddadb..a1aae7ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [[1.1.1](https://github.com/nf-core/proteinfold/releases/tag/1.1.1)] - 2025-07-30 +## v1.2.0dev - [date] -- Minor patch release to fix multiqc report. +### Enhancements & fixes + +## [[1.1.1](https://github.com/nf-core/proteinfold/releases/tag/1.1.1)] - 2025-07-30 ### Enhancements & fixes +- Minor patch release to fix multiqc report. + ## [[1.1.0](https://github.com/nf-core/proteinfold/releases/tag/1.1.0)] - 2025-06-25 ### Credits From f2247ae064bb3dda3288ea1589881f8d356a399d Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 30 Jul 2024 17:58:45 +0200 Subject: [PATCH 003/123] bump dev version in images --- modules/local/colabfold_batch.nf | 2 +- modules/local/mmseqs_colabfoldsearch.nf | 2 +- modules/local/run_alphafold2.nf | 2 +- modules/local/run_alphafold2_msa.nf | 2 +- modules/local/run_alphafold2_pred.nf | 2 +- modules/local/run_esmfold.nf | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/local/colabfold_batch.nf b/modules/local/colabfold_batch.nf index 5dab51fb..5b1c5467 100644 --- a/modules/local/colabfold_batch.nf +++ b/modules/local/colabfold_batch.nf @@ -7,7 +7,7 @@ process COLABFOLD_BATCH { error("Local COLABFOLD_BATCH module does not support Conda. Please use Docker / Singularity / Podman instead.") } - container "nf-core/proteinfold_colabfold:1.1.1" + container "nf-core/proteinfold_colabfold:dev" input: tuple val(meta), path(fasta) diff --git a/modules/local/mmseqs_colabfoldsearch.nf b/modules/local/mmseqs_colabfoldsearch.nf index c2140c5b..c6a2c9b0 100644 --- a/modules/local/mmseqs_colabfoldsearch.nf +++ b/modules/local/mmseqs_colabfoldsearch.nf @@ -7,7 +7,7 @@ process MMSEQS_COLABFOLDSEARCH { error("Local MMSEQS_COLABFOLDSEARCH module does not support Conda. Please use Docker / Singularity / Podman instead.") } - container "nf-core/proteinfold_colabfold:1.1.1" + container "nf-core/proteinfold_colabfold:dev" input: tuple val(meta), path(fasta) diff --git a/modules/local/run_alphafold2.nf b/modules/local/run_alphafold2.nf index cb3527d3..20cbf9fc 100644 --- a/modules/local/run_alphafold2.nf +++ b/modules/local/run_alphafold2.nf @@ -10,7 +10,7 @@ process RUN_ALPHAFOLD2 { error("Local RUN_ALPHAFOLD2 module does not support Conda. Please use Docker / Singularity / Podman instead.") } - container "nf-core/proteinfold_alphafold2_standard:1.1.1" + container "nf-core/proteinfold_alphafold2_standard:dev" input: tuple val(meta), path(fasta) diff --git a/modules/local/run_alphafold2_msa.nf b/modules/local/run_alphafold2_msa.nf index fdc67e88..85a40676 100644 --- a/modules/local/run_alphafold2_msa.nf +++ b/modules/local/run_alphafold2_msa.nf @@ -10,7 +10,7 @@ process RUN_ALPHAFOLD2_MSA { error("Local RUN_ALPHAFOLD2_MSA module does not support Conda. Please use Docker / Singularity / Podman instead.") } - container "nf-core/proteinfold_alphafold2_msa:1.1.1" + container "nf-core/proteinfold_alphafold2_msa:dev" input: tuple val(meta), path(fasta) diff --git a/modules/local/run_alphafold2_pred.nf b/modules/local/run_alphafold2_pred.nf index 92b5d2a5..ee9983c5 100644 --- a/modules/local/run_alphafold2_pred.nf +++ b/modules/local/run_alphafold2_pred.nf @@ -10,7 +10,7 @@ process RUN_ALPHAFOLD2_PRED { error("Local RUN_ALPHAFOLD2_PRED module does not support Conda. Please use Docker / Singularity / Podman instead.") } - container "nf-core/proteinfold_alphafold2_split:1.1.1" + container "nf-core/proteinfold_alphafold2_split:dev" input: tuple val(meta), path(fasta) diff --git a/modules/local/run_esmfold.nf b/modules/local/run_esmfold.nf index 66c5bbc7..f37c9eb3 100644 --- a/modules/local/run_esmfold.nf +++ b/modules/local/run_esmfold.nf @@ -6,7 +6,7 @@ process RUN_ESMFOLD { error("Local RUN_ESMFOLD module does not support Conda. Please use Docker / Singularity / Podman instead.") } - container "nf-core/proteinfold_esmfold:1.1.1" + container "nf-core/proteinfold_esmfold:dev" input: tuple val(meta), path(fasta) From 3986454a6333e5ca8db325e6b539c6fd6a371b0c Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 30 Jul 2024 17:59:55 +0200 Subject: [PATCH 004/123] bump dev in multiqc_config --- assets/multiqc_config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/multiqc_config.yml b/assets/multiqc_config.yml index 3b58e3d0..f6acb16a 100644 --- a/assets/multiqc_config.yml +++ b/assets/multiqc_config.yml @@ -1,7 +1,7 @@ report_comment: > - This report has been generated by the nf-core/proteinfold + This report has been generated by the nf-core/proteinfold analysis pipeline. For information about how to interpret these results, please see the - documentation. + documentation. report_section_order: "nf-core-proteinfold-methods-description": order: -1000 From bfd3c76b0c585d45297bd54e6374dafb27131fa3 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 6 Aug 2024 16:05:30 +0200 Subject: [PATCH 005/123] Fix uppercase model preset --- README.md | 4 ++-- docs/usage.md | 4 ++-- workflows/colabfold.nf | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a8a25a52..522b590e 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ The pipeline takes care of downloading the databases and parameters required by --colabfold_db \ --num_recycles_colabfold 3 \ --use_amber \ - --colabfold_model_preset "AlphaFold2-ptm" \ + --colabfold_model_preset "alphaFold2-ptm" \ --use_gpu \ --db_load_mode 0 -profile @@ -115,7 +115,7 @@ The pipeline takes care of downloading the databases and parameters required by --colabfold_db \ --num_recycles_colabfold 3 \ --use_amber \ - --colabfold_model_preset "AlphaFold2-ptm" \ + --colabfold_model_preset "alphaFold2-ptm" \ --use_gpu \ -profile ``` diff --git a/docs/usage.md b/docs/usage.md index 12e47552..7c81be35 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -236,7 +236,7 @@ nextflow run nf-core/proteinfold \ --colabfold_db \ --num_recycles_colabfold 3 \ --use_amber \ - --colabfold_model_preset "AlphaFold2-ptm" \ + --colabfold_model_preset "alphaFold2-ptm" \ --use_gpu \ --db_load_mode 0 \ -profile @@ -254,7 +254,7 @@ nextflow run nf-core/proteinfold \ --colabfold_db \ --num_recycles_colabfold 3 \ --use_amber \ - --colabfold_model_preset "AlphaFold2-ptm" \ + --colabfold_model_preset "alphaFold2-ptm" \ --use_gpu \ -profile ``` diff --git a/workflows/colabfold.nf b/workflows/colabfold.nf index 3d2829f3..9184a6ff 100644 --- a/workflows/colabfold.nf +++ b/workflows/colabfold.nf @@ -91,7 +91,7 @@ workflow COLABFOLD { // // MODULE: Run mmseqs // - if (params.colabfold_model_preset != 'AlphaFold2-ptm') { + if (params.colabfold_model_preset != 'alphaFold2-ptm') { MULTIFASTA_TO_CSV( ch_fasta ) From 4769bee1d2949ebdd854e5e6025e2139eb5b5244 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 6 Aug 2024 16:11:00 +0200 Subject: [PATCH 006/123] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1aae7ad..503a48ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Enhancements & fixes +- [[PR #175](https://github.com/nf-core/proteinfold/pull/175)] - Fix typo in some instances of model preset `alphaFold2-ptm`. + ## [[1.1.1](https://github.com/nf-core/proteinfold/releases/tag/1.1.1)] - 2025-07-30 ### Enhancements & fixes From 4ca5fdccb55ed61905d19898a1a3f92d8358beab Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Tue, 6 Aug 2024 17:20:27 +0200 Subject: [PATCH 007/123] Fix instances of alphafold2_ptm --- CHANGELOG.md | 2 +- README.md | 4 ++-- docs/usage.md | 4 ++-- workflows/colabfold.nf | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 503a48ec..93f2d420 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Enhancements & fixes -- [[PR #175](https://github.com/nf-core/proteinfold/pull/175)] - Fix typo in some instances of model preset `alphaFold2-ptm`. +- [[PR #175](https://github.com/nf-core/proteinfold/pull/175)] - Fix typo in some instances of model preset `alphafold2_ptm`. ## [[1.1.1](https://github.com/nf-core/proteinfold/releases/tag/1.1.1)] - 2025-07-30 diff --git a/README.md b/README.md index 522b590e..dec185a9 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ The pipeline takes care of downloading the databases and parameters required by --colabfold_db \ --num_recycles_colabfold 3 \ --use_amber \ - --colabfold_model_preset "alphaFold2-ptm" \ + --colabfold_model_preset "alphafold2_ptm" \ --use_gpu \ --db_load_mode 0 -profile @@ -115,7 +115,7 @@ The pipeline takes care of downloading the databases and parameters required by --colabfold_db \ --num_recycles_colabfold 3 \ --use_amber \ - --colabfold_model_preset "alphaFold2-ptm" \ + --colabfold_model_preset "alphafold2_ptm" \ --use_gpu \ -profile ``` diff --git a/docs/usage.md b/docs/usage.md index 7c81be35..be725651 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -236,7 +236,7 @@ nextflow run nf-core/proteinfold \ --colabfold_db \ --num_recycles_colabfold 3 \ --use_amber \ - --colabfold_model_preset "alphaFold2-ptm" \ + --colabfold_model_preset "alphafold2_ptm" \ --use_gpu \ --db_load_mode 0 \ -profile @@ -254,7 +254,7 @@ nextflow run nf-core/proteinfold \ --colabfold_db \ --num_recycles_colabfold 3 \ --use_amber \ - --colabfold_model_preset "alphaFold2-ptm" \ + --colabfold_model_preset "alphafold2_ptm" \ --use_gpu \ -profile ``` diff --git a/workflows/colabfold.nf b/workflows/colabfold.nf index 9184a6ff..46dc8df4 100644 --- a/workflows/colabfold.nf +++ b/workflows/colabfold.nf @@ -91,7 +91,7 @@ workflow COLABFOLD { // // MODULE: Run mmseqs // - if (params.colabfold_model_preset != 'alphaFold2-ptm') { + if (params.colabfold_model_preset != 'alphafold2_ptm') { MULTIFASTA_TO_CSV( ch_fasta ) From 02babc4a2db822e95f54f5696216bb562d2dea07 Mon Sep 17 00:00:00 2001 From: JoseEspinosa Date: Wed, 7 Aug 2024 15:16:42 +0200 Subject: [PATCH 008/123] Fix condition to run MULTIFASTA_TO_CSV when executing colabfold server local --- workflows/colabfold.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflows/colabfold.nf b/workflows/colabfold.nf index 46dc8df4..eafc222c 100644 --- a/workflows/colabfold.nf +++ b/workflows/colabfold.nf @@ -91,7 +91,7 @@ workflow COLABFOLD { // // MODULE: Run mmseqs // - if (params.colabfold_model_preset != 'alphafold2_ptm') { + if (params.colabfold_model_preset != 'alphafold2_ptm' && params.colabfold_model_preset != 'alphafold2') { MULTIFASTA_TO_CSV( ch_fasta ) From b520b9bd26d0a995a81760f16a74a76ed0cd69c1 Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Sat, 21 Sep 2024 13:03:10 +1000 Subject: [PATCH 009/123] accepting mutltiple models --- conf/modules_alphafold2.config | 15 +++++++++++---- conf/modules_colabfold.config | 14 ++++++++++++-- conf/modules_esmfold.config | 11 ++++++++++- main.nf | 14 ++++++++------ nextflow.config | 8 +++++--- nextflow_schema.json | 3 +-- tower.yml | 8 ++++++-- 7 files changed, 53 insertions(+), 20 deletions(-) diff --git a/conf/modules_alphafold2.config b/conf/modules_alphafold2.config index 4aae2d30..33b04c38 100644 --- a/conf/modules_alphafold2.config +++ b/conf/modules_alphafold2.config @@ -17,11 +17,18 @@ process { withName: 'GUNZIP|COMBINE_UNIPROT|DOWNLOAD_PDBMMCIF|ARIA2_PDB_SEQRES' { publishDir = [ - path: {"${params.outdir}/DBs/${params.mode}/${params.alphafold2_mode}"}, + path: {"${params.outdir}/DBs/alphafold2/${params.alphafold2_mode}"}, mode: 'symlink', saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] } + withName: 'NFCORE_PROTEINFOLD:ALPHAFOLD2:MULTIQC' { + publishDir = [ + path: { "${params.outdir}/multiqc" }, + mode: 'copy', + saveAs: { filename -> filename.equals('versions.yml') ? null : "alphafold2_$filename" } + ] + } } if (params.alphafold2_mode == 'standard') { @@ -33,7 +40,7 @@ if (params.alphafold2_mode == 'standard') { params.max_template_date ? "--max_template_date ${params.max_template_date}" : '' ].join(' ').trim() publishDir = [ - path: { "${params.outdir}/${params.mode}/${params.alphafold2_mode}" }, + path: { "${params.outdir}/alphafold2/${params.alphafold2_mode}" }, mode: 'copy', saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] @@ -47,7 +54,7 @@ if (params.alphafold2_mode == 'split_msa_prediction') { withName: 'RUN_ALPHAFOLD2_MSA' { ext.args = params.max_template_date ? "--max_template_date ${params.max_template_date}" : '' publishDir = [ - path: { "${params.outdir}/${params.mode}/${params.alphafold2_mode}" }, + path: { "${params.outdir}/alphafold2/${params.alphafold2_mode}" }, mode: 'copy', saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] @@ -57,7 +64,7 @@ if (params.alphafold2_mode == 'split_msa_prediction') { if(params.use_gpu) { accelerator = 1 } ext.args = params.use_gpu ? '--use_gpu_relax=true' : '--use_gpu_relax=false' publishDir = [ - path: { "${params.outdir}/${params.mode}/${params.alphafold2_mode}" }, + path: { "${params.outdir}/alphafold2/${params.alphafold2_mode}" }, mode: 'copy', saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] diff --git a/conf/modules_colabfold.config b/conf/modules_colabfold.config index a7a719b0..2efcfa01 100644 --- a/conf/modules_colabfold.config +++ b/conf/modules_colabfold.config @@ -10,6 +10,16 @@ ---------------------------------------------------------------------------------------- */ +process { + withName: 'NFCORE_PROTEINFOLD:COLABFOLD:MULTIQC' { + publishDir = [ + path: { "${params.outdir}/multiqc" }, + mode: 'copy', + saveAs: { filename -> filename.equals('versions.yml') ? null : "colabfold_$filename" } + ] + } +} + if (params.colabfold_server == 'webserver') { process { withName: 'COLABFOLD_BATCH' { @@ -20,7 +30,7 @@ if (params.colabfold_server == 'webserver') { params.host_url ? "--host-url ${params.host_url}" : '' ].join(' ').trim() publishDir = [ - path: { "${params.outdir}/${params.mode}/${params.colabfold_server}" }, + path: { "${params.outdir}/colabfold/${params.colabfold_server}" }, mode: 'copy', saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, pattern: '*.*' @@ -57,7 +67,7 @@ if (params.colabfold_server == 'local') { params.use_templates ? '--templates' : '' ].join(' ').trim() publishDir = [ - path: { "${params.outdir}/${params.mode}/${params.colabfold_server}" }, + path: { "${params.outdir}/colabfold/${params.colabfold_server}" }, mode: 'copy', saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, pattern: '*.*' diff --git a/conf/modules_esmfold.config b/conf/modules_esmfold.config index 81b3048f..5b3113b8 100644 --- a/conf/modules_esmfold.config +++ b/conf/modules_esmfold.config @@ -14,10 +14,19 @@ process { withName: 'RUN_ESMFOLD' { ext.args = {params.use_gpu ? '' : '--cpu-only'} publishDir = [ - path: { "${params.outdir}/${params.mode}" }, + path: { "${params.outdir}/colabfold" }, mode: 'copy', saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, pattern: '*.*' ] } + + withName: 'NFCORE_PROTEINFOLD:ESMFOLD:MULTIQC' { + publishDir = [ + path: { "${params.outdir}/multiqc" }, + mode: 'copy', + saveAs: { filename -> filename.equals('versions.yml') ? null : "esmfold_$filename" } + ] + } + } diff --git a/main.nf b/main.nf index d6da0f09..cdc63d84 100644 --- a/main.nf +++ b/main.nf @@ -17,13 +17,15 @@ nextflow.enable.dsl = 2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -if (params.mode == "alphafold2") { +if (params.mode.toLowerCase().split(",").contains("alphafold2")) { include { PREPARE_ALPHAFOLD2_DBS } from './subworkflows/local/prepare_alphafold2_dbs' include { ALPHAFOLD2 } from './workflows/alphafold2' -} else if (params.mode == "colabfold") { +} +if (params.mode.toLowerCase().split(",").contains("colabfold")) { include { PREPARE_COLABFOLD_DBS } from './subworkflows/local/prepare_colabfold_dbs' include { COLABFOLD } from './workflows/colabfold' -} else if (params.mode == "esmfold") { +} +if (params.mode.toLowerCase().split(",").contains("esmfold")) { include { PREPARE_ESMFOLD_DBS } from './subworkflows/local/prepare_esmfold_dbs' include { ESMFOLD } from './workflows/esmfold' } @@ -60,7 +62,7 @@ workflow NFCORE_PROTEINFOLD { // // WORKFLOW: Run alphafold2 // - if(params.mode == "alphafold2") { + if(params.mode.toLowerCase().split(",").contains("alphafold2")) { // // SUBWORKFLOW: Prepare Alphafold2 DBs // @@ -118,7 +120,7 @@ workflow NFCORE_PROTEINFOLD { // // WORKFLOW: Run colabfold // - else if(params.mode == "colabfold") { + if(params.mode.toLowerCase().split(",").contains("colabfold")) { // // SUBWORKFLOW: Prepare Colabfold DBs // @@ -153,7 +155,7 @@ workflow NFCORE_PROTEINFOLD { // // WORKFLOW: Run esmfold // - else if(params.mode == "esmfold") { + if(params.mode.toLowerCase().split(",").contains("esmfold")) { // // SUBWORKFLOW: Prepare esmfold DBs // diff --git a/nextflow.config b/nextflow.config index 7a0c5c4e..3f7428dd 100644 --- a/nextflow.config +++ b/nextflow.config @@ -330,11 +330,13 @@ manifest { includeConfig 'conf/modules.config' // Load modules config for pipeline specific modes -if (params.mode == 'alphafold2') { +if (params.mode.toLowerCase().split(",").contains("alphafold2")) { includeConfig 'conf/modules_alphafold2.config' -} else if (params.mode == 'colabfold') { +} +if (params.mode.toLowerCase().split(",").contains("colabfold")) { includeConfig 'conf/modules_colabfold.config' -} else if (params.mode == 'esmfold') { +} +if (params.mode.toLowerCase().split(",").contains("esmfold")) { includeConfig 'conf/modules_esmfold.config' } diff --git a/nextflow_schema.json b/nextflow_schema.json index df0bbfe3..2d3ce68e 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -32,8 +32,7 @@ "mode": { "type": "string", "default": "alphafold2", - "description": "Specifies the mode in which the pipeline will be run", - "enum": ["alphafold2", "colabfold", "esmfold"], + "description": "Specifies the mode in which the pipeline will be run. mode can be any combination of ['alphafold2', 'colabfold', 'esmfold'] separated by a comma (',') with no spaces.", "fa_icon": "fas fa-cogs" }, "use_gpu": { diff --git a/tower.yml b/tower.yml index 787aedfe..7fc70c10 100644 --- a/tower.yml +++ b/tower.yml @@ -1,5 +1,9 @@ reports: - multiqc_report.html: - display: "MultiQC HTML report" + esmfold_multiqc_report.html: + display: "ESMFOLD MultiQC HTML report" + alphafold2_multiqc_report.html: + display: "ALPHAFOLD2 MultiQC HTML report" + colabfold_multiqc_report.html: + display: "COLABFOLD MultiQC HTML report" samplesheet.csv: display: "Auto-created samplesheet with collated metadata and FASTQ paths" From 78ba2883c9120deb7925aaa4dcbf8933b3610339 Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Sat, 21 Sep 2024 13:33:05 +1000 Subject: [PATCH 010/123] accepting mutltiple models --- conf/modules_esmfold.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/modules_esmfold.config b/conf/modules_esmfold.config index 5b3113b8..d8356924 100644 --- a/conf/modules_esmfold.config +++ b/conf/modules_esmfold.config @@ -14,7 +14,7 @@ process { withName: 'RUN_ESMFOLD' { ext.args = {params.use_gpu ? '' : '--cpu-only'} publishDir = [ - path: { "${params.outdir}/colabfold" }, + path: { "${params.outdir}/esmfold" }, mode: 'copy', saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, pattern: '*.*' From e7b4f06079680105b573a5b23abdd89837a1cbd6 Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Sat, 21 Sep 2024 13:56:00 +1000 Subject: [PATCH 011/123] update usage --- docs/usage.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index be725651..55120275 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -37,7 +37,9 @@ An [example samplesheet](../assets/samplesheet.csv) has been provided with the p ## Running the pipeline -The typical commands for running the pipeline on AlphaFold2, Colabfold and ESMFold modes are shown below. +The typical commands for running the pipeline on AlphaFold2, Colabfold and ESMFold modes are shown below. + +> You can run any combination of the models by providing them to the `--mode` parameter separated by a comma. For example: `--mode alphafold2,esmfold,colabfold` will run the three models in parallel. AlphaFold2 regular can be run using this command: From 0bb9d9d378170b4b0c60e210d6bd8934d431e6c6 Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Sat, 21 Sep 2024 14:08:02 +1000 Subject: [PATCH 012/123] update docs - usage and output --- docs/output.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/output.md b/docs/output.md index 29d2337c..9b9a8fb8 100644 --- a/docs/output.md +++ b/docs/output.md @@ -183,9 +183,9 @@ Below you can find an indicative example of the TSV file with the pLDDT scores p Output files - `multiqc` - - multiqc_report.html: A standalone HTML file that can be viewed in your web browser. - - multiqc_data/: Directory containing parsed statistics from the different tools used in the pipeline. - - multiqc_plots/: Directory containing static images from the report in various formats. + - `_multiqc_report.html`: A standalone HTML file that can be viewed in your web browser. + - `_multiqc_data/`: Directory containing parsed statistics from the different tools used in the pipeline. + - `_multiqc_plots/`: Directory containing static images from the report in various formats. From 080c16f8aed9c5fdd5585a4d04d9a2203a481877 Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Sat, 21 Sep 2024 14:14:27 +1000 Subject: [PATCH 013/123] update change log --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93f2d420..9ba92e3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [[PR #175](https://github.com/nf-core/proteinfold/pull/175)] - Fix typo in some instances of model preset `alphafold2_ptm`. +- [[PR #178](https://github.com/nf-core/proteinfold/pull/178)] - Enable running multiple modes in parallel. + ## [[1.1.1](https://github.com/nf-core/proteinfold/releases/tag/1.1.1)] - 2025-07-30 ### Enhancements & fixes From fbe52036cc47894c57e8c0f877d2e37fe980e826 Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Sat, 21 Sep 2024 14:30:16 +1000 Subject: [PATCH 014/123] lint minor fixes --- main.nf | 4 ++-- nextflow.config | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/main.nf b/main.nf index cdc63d84..6d762db1 100644 --- a/main.nf +++ b/main.nf @@ -20,11 +20,11 @@ nextflow.enable.dsl = 2 if (params.mode.toLowerCase().split(",").contains("alphafold2")) { include { PREPARE_ALPHAFOLD2_DBS } from './subworkflows/local/prepare_alphafold2_dbs' include { ALPHAFOLD2 } from './workflows/alphafold2' -} +} if (params.mode.toLowerCase().split(",").contains("colabfold")) { include { PREPARE_COLABFOLD_DBS } from './subworkflows/local/prepare_colabfold_dbs' include { COLABFOLD } from './workflows/colabfold' -} +} if (params.mode.toLowerCase().split(",").contains("esmfold")) { include { PREPARE_ESMFOLD_DBS } from './subworkflows/local/prepare_esmfold_dbs' include { ESMFOLD } from './workflows/esmfold' diff --git a/nextflow.config b/nextflow.config index 3f7428dd..2bc5ca0a 100644 --- a/nextflow.config +++ b/nextflow.config @@ -332,10 +332,10 @@ includeConfig 'conf/modules.config' // Load modules config for pipeline specific modes if (params.mode.toLowerCase().split(",").contains("alphafold2")) { includeConfig 'conf/modules_alphafold2.config' -} +} if (params.mode.toLowerCase().split(",").contains("colabfold")) { includeConfig 'conf/modules_colabfold.config' -} +} if (params.mode.toLowerCase().split(",").contains("esmfold")) { includeConfig 'conf/modules_esmfold.config' } From dc3bf4fe90cdebf47a09b8de703c93f1acfce78f Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Sat, 21 Sep 2024 14:44:39 +1000 Subject: [PATCH 015/123] lint fixes --- docs/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index 55120275..ecf813ce 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -37,7 +37,7 @@ An [example samplesheet](../assets/samplesheet.csv) has been provided with the p ## Running the pipeline -The typical commands for running the pipeline on AlphaFold2, Colabfold and ESMFold modes are shown below. +The typical commands for running the pipeline on AlphaFold2, Colabfold and ESMFold modes are shown below. > You can run any combination of the models by providing them to the `--mode` parameter separated by a comma. For example: `--mode alphafold2,esmfold,colabfold` will run the three models in parallel. From 2ae16a3ff93fe3f4dbf3149182274eefd74e7628 Mon Sep 17 00:00:00 2001 From: Ziad Al-Bkhetan Date: Thu, 26 Sep 2024 17:34:10 +1000 Subject: [PATCH 016/123] Update CHANGELOG.md Co-authored-by: Jose Espinosa-Carrasco --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ba92e3b..85ada4e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Enhancements & fixes - [[PR #175](https://github.com/nf-core/proteinfold/pull/175)] - Fix typo in some instances of model preset `alphafold2_ptm`. - - [[PR #178](https://github.com/nf-core/proteinfold/pull/178)] - Enable running multiple modes in parallel. ## [[1.1.1](https://github.com/nf-core/proteinfold/releases/tag/1.1.1)] - 2025-07-30 From 73a75e25e7a9f845a8c06b2c0427248ee37340ae Mon Sep 17 00:00:00 2001 From: Ziad Al-Bkhetan Date: Thu, 26 Sep 2024 17:34:18 +1000 Subject: [PATCH 017/123] Update CHANGELOG.md Co-authored-by: Jose Espinosa-Carrasco --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85ada4e4..44cd1bd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Enhancements & fixes -- [[PR #175](https://github.com/nf-core/proteinfold/pull/175)] - Fix typo in some instances of model preset `alphafold2_ptm`. +- [[#177](https://github.com/nf-core/proteinfold/issues/177)]- Fix typo in some instances of model preset `alphafold2_ptm`. - [[PR #178](https://github.com/nf-core/proteinfold/pull/178)] - Enable running multiple modes in parallel. ## [[1.1.1](https://github.com/nf-core/proteinfold/releases/tag/1.1.1)] - 2025-07-30 From 3d02fc570ab914866a3f7add869b3bb1f9a9f80c Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Tue, 1 Oct 2024 10:55:47 +1000 Subject: [PATCH 018/123] add results visulisation --- assets/NO_FILE | 0 assets/proteinfold_template.html | 908 +++++++++++++++++++++++++++ bin/extract_output.py | 28 + bin/generat_report.py | 386 ++++++++++++ main.nf | 24 +- modules/local/colabfold_batch.nf | 8 +- modules/local/generat_report.nf | 51 ++ modules/local/run_alphafold2.nf | 18 +- modules/local/run_alphafold2_msa.nf | 2 +- modules/local/run_alphafold2_pred.nf | 21 +- modules/local/run_esmfold.nf | 6 +- nextflow.config | 3 +- nextflow_schema.json | 5 + workflows/alphafold2.nf | 21 +- workflows/esmfold.nf | 7 +- 15 files changed, 1461 insertions(+), 27 deletions(-) create mode 100644 assets/NO_FILE create mode 100644 assets/proteinfold_template.html create mode 100755 bin/extract_output.py create mode 100755 bin/generat_report.py create mode 100644 modules/local/generat_report.nf diff --git a/assets/NO_FILE b/assets/NO_FILE new file mode 100644 index 00000000..e69de29b diff --git a/assets/proteinfold_template.html b/assets/proteinfold_template.html new file mode 100644 index 00000000..57cfe3d7 --- /dev/null +++ b/assets/proteinfold_template.html @@ -0,0 +1,908 @@ + + + + + + + Protein structure prediction + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + +
+
+
+
+
+
<50
+
70
+
90+
+
+
+
+ +
+

+ Alphafold produces a + + per-residue confidence score (pLDDT) + + between 0 and 100. Some regions below 50 pLDDT may be unstructured in isolation. +

+
+
+ + + + + + +
+ +
+ +
+ + +
+
+
Information
+ +
+
+
Program: *prog_name*
+
ID: *sample_name*
+
+
Average:
+
+ +
+
Navigation
+ + +
+
+ Scroll up/down + to zoom in and out +
+
+ Click + drag + to rotate the structure +
+
+ CTRL + click + drag + to move the structure +
+
+ Click + an atom to bring it into focus +
+
+
+
+ + +
+
+
Toggle representations
+
+ + + + +
+
+ +
+
+
+
Actions
+
+ + + +
+
+
+
Download
+ +
+ + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ + +
+
+
Sequence Coverage
+
+
+ +
+ +
+
+ +
+
pLDDT
+
+ +
+
+
+
+
+ + +
+ + +
+
+
+ + +
+
+

+ The Australian BioCommons + is supported by + Bioplatforms Australia +

+

+ Bioplatforms Australia + is enabled by + NCRIS +

+
+
+
+ + + + \ No newline at end of file diff --git a/bin/extract_output.py b/bin/extract_output.py new file mode 100755 index 00000000..a43a8a3c --- /dev/null +++ b/bin/extract_output.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +import pickle +import os, sys +import argparse + +def read_pkl(id, pkl_files): + for pkl_file in pkl_files: + dict_data = pickle.load(open(pkl_file,'rb')) + #print(dict_data.keys()) + if pkl_file.endswith("features.pkl"): + with open (f"{id}_msa.tsv", "w") as out_f: + for val in dict_data['msa']: + out_f.write("\t".join([str(x) for x in val]) + "\n") + else: + model_id = os.path.basename(pkl_file).replace("result_model_", "").replace("_pred_0.pkl", "") + with open (f"{id}_lddt_{model_id}.tsv", "w") as out_f: + out_f.write("\t".join([str(x) for x in dict_data['plddt']]) + "\n") + + +parser = argparse.ArgumentParser() +parser.add_argument('--pkls',dest='pkls',required=True, nargs="+") +parser.add_argument('--name',dest='name') +parser.add_argument('--output_dir',dest='output_dir') +parser.set_defaults(output_dir='') +parser.set_defaults(name='') +args = parser.parse_args() + +read_pkl(args.name, args.pkls) diff --git a/bin/generat_report.py b/bin/generat_report.py new file mode 100755 index 00000000..dcb48911 --- /dev/null +++ b/bin/generat_report.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python + +import os +from matplotlib import pyplot as plt +import argparse +from collections import OrderedDict +import base64 +import os +from collections import OrderedDict +import plotly.graph_objects as go +from plotly.subplots import make_subplots +import re +from Bio import PDB + +def generate_output_images(msa_path, plddt_data, name, out_dir, in_type, generate_tsv): + msa = [] + if not msa_path.endswith("NO_FILE"): + with open(msa_path, 'r') as in_file: + for line in in_file: + msa.append([int(x) for x in line.strip().split()]) + + seqid = [] + for sequence in msa: + matches = [1.0 if first == other else 0.0 for first, other in zip(msa[0], sequence)] + seqid.append(sum(matches) / len(matches)) + + seqid_sort = sorted(range(len(seqid)), key=seqid.__getitem__) + + non_gaps = [] + for sequence in msa: + non_gaps.append([float(num != 21) if num != 21 else float('nan') for num in sequence]) + + sorted_non_gaps = [non_gaps[i] for i in seqid_sort] + final = [] + for sorted_seq, identity in zip(sorted_non_gaps, [seqid[i] for i in seqid_sort]): + final.append([value * identity if not isinstance(value, str) else value for value in sorted_seq]) + + # ################################################################## + plt.figure(figsize=(14, 14), dpi=100) + # ################################################################## + plt.title("Sequence coverage", fontsize=30, pad=36) + plt.imshow(final, + interpolation='nearest', aspect='auto', + cmap="rainbow_r", vmin=0, vmax=1, origin='lower') + + column_counts = [0] * len(msa[0]) + for col in range(len(msa[0])): + for row in msa: + if row[col] != 21: + column_counts[col] += 1 + + plt.plot(column_counts, color='black') + plt.xlim(-0.5, len(msa[0]) - 0.5) + plt.ylim(-0.5, len(msa) - 0.5) + + plt.tick_params(axis='both', which='both', labelsize=18) + + cbar = plt.colorbar() + cbar.set_label("Sequence identity to query", fontsize=24, labelpad=24) + cbar.ax.tick_params(labelsize=18) + plt.xlabel("Positions", fontsize=24, labelpad=24) + plt.ylabel("Sequences", fontsize=24, labelpad=36) + plt.savefig(f"{out_dir}/{name+('_' if name else '')}seq_coverage.png") + + # ################################################################## + + plddt_per_model = OrderedDict() + output_data = plddt_data + + if generate_tsv == "y": + for plddt_path in output_data: + with open(plddt_path, 'r') as in_file: + plddt_per_model[os.path.basename(plddt_path)[:-4]] = [float(x) for x in in_file.read().strip().split()] + else: + for i, plddt_values_str in enumerate(output_data): + plddt_per_model[i] = [] + plddt_per_model[i] = [float(x) for x in plddt_values_str.strip().split()] + + # plt.figure(figsize=(14, 14), dpi=100) + # plt.title("Predicted LDDT per position") + # for model_name, value_plddt in plddt_per_model.items(): + # plt.plot(value_plddt, label=model_name) + # plt.ylim(0, 100) + # plt.ylabel("Predicted LDDT") + # plt.xlabel("Positions") + # plt.savefig(f"{out_dir}/{name+('_' if name else '')}coverage_LDDT.png") + + # # split into figures + # i = 0 + # for model_name, value_plddt in plddt_per_model.items(): + # plt.figure(figsize=(14, 14), dpi=100) + # plt.title("Predicted LDDT per position") + # plt.plot(value_plddt, label=model_name) + # plt.ylim(0, 100) + # plt.ylabel("Predicted LDDT") + # plt.xlabel("Positions") + # plt.savefig(f"{out_dir}/{name+('_' if name else '')}coverage_LDDT_{i}.png") + # i += 1 + + fig = go.Figure() + for idx, (model_name, value_plddt) in enumerate(plddt_per_model.items()): + rank_label = f"Ranked {idx}" + fig.add_trace(go.Scatter( + x=list(range(len(value_plddt))), + y=value_plddt, + mode='lines', + name=rank_label, + text=[f"({i}, {value:.2f})" for i, value in enumerate(value_plddt)], + hoverinfo='text' + )) + fig.update_layout( + title=dict( + text='Predicted LDDT per position', + x=0.5, + xanchor='center' + ), + xaxis=dict( + title='Positions', + showline=True, + linecolor='black', + gridcolor='WhiteSmoke' + ), + yaxis=dict( + title='Predicted LDDT', + range=[0, 100], + minallowed=0, + maxallowed=100, + showline=True, + linecolor='black', + gridcolor='WhiteSmoke' + ), + legend=dict( + yanchor="bottom", + y=0, + xanchor="right", + x=1.3 + ), + plot_bgcolor='white', + width=600, + height=600, + modebar_remove=['toImage', 'zoomIn', 'zoomOut'] + ) + html_content = fig.to_html(full_html=False, include_plotlyjs='cdn', config={'displayModeBar': True, 'displaylogo': False, 'scrollZoom': True}) + + with open(f"{out_dir}/{name+('_' if name else '')}coverage_LDDT.html", "w") as out_file: + out_file.write(html_content) + + + ################################################################## + + + ################################################################## + """ + num_models = 5 # columns + num_runs_per_model = math.ceil(len(model_names)/num_models) + fig = plt.figure(figsize=(3 * num_models, 2 * num_runs_per_model), dpi=100) + for n, (model_name, value) in enumerate(pae_plddt_per_model.items()): + plt.subplot(num_runs_per_model, num_models, n + 1) + plt.title(model_name) + plt.imshow(value["pae"], label=model_name, cmap="bwr", vmin=0, vmax=30) + plt.colorbar() + fig.tight_layout() + plt.savefig(f"{out_dir}/{name+('_' if name else '')}PAE.png") + """ + ################################################################## + +def generate_plots(msa_path, plddt_paths, name, out_dir): + msa = [] + with open(msa_path, 'r') as in_file: + for line in in_file: + msa.append([int(x) for x in line.strip().split()]) + + seqid = [] + for sequence in msa: + matches = [1.0 if first == other else 0.0 for first, other in zip(msa[0], sequence)] + seqid.append(sum(matches) / len(matches)) + + seqid_sort = sorted(range(len(seqid)), key=seqid.__getitem__) + + non_gaps = [] + for sequence in msa: + non_gaps.append([float(num != 21) if num != 21 else float('nan') for num in sequence]) + + sorted_non_gaps = [non_gaps[i] for i in seqid_sort] + final = [] + for sorted_seq, identity in zip(sorted_non_gaps, [seqid[i] for i in seqid_sort]): + final.append([value * identity if not isinstance(value, str) else value for value in sorted_seq]) + + # Plotting Sequence Coverage using Plotly + fig = go.Figure() + fig.add_trace(go.Heatmap( + z=final, + colorscale="Rainbow", + zmin=0, + zmax=1, + )) + fig.update_layout( + title="Sequence coverage", + xaxis_title="Positions", + yaxis_title="Sequences" + ) + # Save as interactive HTML instead of an image + fig.savefig(f"{out_dir}/{name+('_' if name else '')}seq_coverage.png") + """ + #fig.to_html(full_html=False).write_html(f"{out_dir}/{name+('_' if name else '')}seq_coverage.html") + with open (f"{out_dir}/{name+('_' if name else '')}seq_coverage.html", "w") as out_plt: + out_plt.write(fig.to_html(full_html=False)) + """ + # Plotting Predicted LDDT per position using Plotly + plddt_per_model = OrderedDict() + plddt_paths.sort() + for plddt_path in plddt_paths: + with open(plddt_path, 'r') as in_file: + plddt_per_model[os.path.basename(plddt_path)[:-4]] = [float(x) for x in in_file.read().strip().split()] + + i = 0 + for model_name, value_plddt in plddt_per_model.items(): + fig = go.Figure() + fig.add_trace(go.Scatter( + x=list(range(len(value_plddt))), + y=value_plddt, + mode='lines', + name=model_name + )) + fig.update_layout(title="Predicted LDDT per Position") + fig.savefig(f"{out_dir}/{name+('_' if name else '')}coverage_LDDT_{i}.png") + """ + with open (f"{out_dir}/{name+('_' if name else '')}coverage_LDDT_{i}.html", "w") as out_plt: + out_plt.write(fig.to_html(full_html=False).replace("\"", "\\\"")) + """ + i += 1 + +def align_structures(structures): + parser = PDB.PDBParser(QUIET=True) + structures = [parser.get_structure(f'Structure_{i}', pdb) for i, pdb in enumerate(structures)] + + ref_structure = structures[0] + ref_atoms = [atom for atom in ref_structure.get_atoms()] + + super_imposer = PDB.Superimposer() + aligned_structures = [structures[0]] # Include the reference structure in the list + + for i, structure in enumerate(structures[1:], start=1): + target_atoms = [atom for atom in structure.get_atoms()] + + super_imposer.set_atoms(ref_atoms, target_atoms) + super_imposer.apply(structure.get_atoms()) + + aligned_structure = f'aligned_structure_{i}.pdb' + io = PDB.PDBIO() + io.set_structure(structure) + io.save(aligned_structure) + aligned_structures.append(aligned_structure) + + return aligned_structures + + +def pdb_to_lddt(pdb_files, generate_tsv): + pdb_files_sorted = pdb_files + pdb_files_sorted.sort() + + output_lddt = [] + averages = [] + + for pdb_file in pdb_files_sorted: + plddt_values = [] + seen_lines = set() + + with open(pdb_file, 'r') as infile: + for line in infile: + columns = line.split() + if len(columns) >= 11: + key = f"{columns[5]}\t{columns[10]}" + if key not in seen_lines: + seen_lines.add(key) + plddt_values.append(float(columns[10])) + + # Calculate the average PLDDT value for the current file + if plddt_values: + avg_plddt = sum(plddt_values) / len(plddt_values) + averages.append(avg_plddt) + else: + averages.append(0.0) + + if generate_tsv == "y": + output_file = f"{pdb_file.replace('.pdb', '')}_plddt.tsv" + with open(output_file, 'w') as outfile: + outfile.write(" ".join(map(str, plddt_values)) + "\n") + output_lddt.append(output_file) + else: + plddt_values_string = " ".join(map(str, plddt_values)) + output_lddt.append(plddt_values_string) + + return output_lddt, averages + +print("Starting...") + +parser = argparse.ArgumentParser() +parser.add_argument('--type', dest='in_type') +parser.add_argument('--generate_tsv', choices=['y', 'n'], default = 'n', dest='generate_tsv') +parser.add_argument('--msa', dest='msa', default='NO_FILE') +parser.add_argument('--pdb', dest='pdb',required=True, nargs="+") +parser.add_argument('--name', dest='name') +parser.add_argument('--output_dir',dest='output_dir') +parser.add_argument('--html_template',dest='html_template') +parser.set_defaults(output_dir='') +parser.set_defaults(in_type='ESM-FOLD') +parser.set_defaults(name='') +args = parser.parse_args() + +lddt_data, lddt_averages = pdb_to_lddt(args.pdb, args.generate_tsv) + +generate_output_images(args.msa, lddt_data, args.name, args.output_dir, args.in_type, args.generate_tsv) +#generate_plots(args.msa, args.plddt, args.name, args.output_dir) + +print("generating html report...") +structures = args.pdb +structures.sort() +aligned_structures = align_structures(structures) + +io = PDB.PDBIO() +ref_structure_path = 'aligned_structure_0.pdb' +io.set_structure(aligned_structures[0]) +io.save(ref_structure_path) +aligned_structures[0] = ref_structure_path + +alphafold_template = open(args.html_template, "r").read() +alphafold_template = alphafold_template.replace(f"*sample_name*", args.name) +alphafold_template = alphafold_template.replace(f"*prog_name*", args.in_type) + +args_pdb_array_js = ",\n".join([f'"{model}"' for model in structures]) +alphafold_template = re.sub( + r'const MODELS = \[.*?\];', # Match the existing MODELS array in HTML template + f'const MODELS = [\n {args_pdb_array_js}\n];', # Replace with the new array + alphafold_template, + flags=re.DOTALL, +) + +averages_js_array = f"const LDDT_AVERAGES = {lddt_averages};" +alphafold_template = alphafold_template.replace("const LDDT_AVERAGES = [];", averages_js_array) + +i = 0 +for structure in aligned_structures: + alphafold_template = alphafold_template.replace(f"*_data_ranked_{i}.pdb*", open(structure, "r").read().replace("\n", "\\n")) + i += 1 + +if True: + if not args.msa.endswith("NO_FILE"): + with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}seq_coverage.png", "rb") as in_file: + alphafold_template = alphafold_template.replace("seq_coverage.png", f"data:image/png;base64,{base64.b64encode(in_file.read()).decode('utf-8')}") + + # with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}seq_coverage.html", "r") as in_file: + # seq_cov_html = in_file.read() + # alphafold_template = alphafold_template.replace("
", seq_cov_html) + + else: + pattern = r'
.*?(.*?)*?
\s*' + alphafold_template = re.sub(pattern, '', alphafold_template, flags=re.DOTALL) + + # alphafold_template = alphafold_template.replace("seq_coverage.png","") + + # for i in range(0, len(args.plddt)): + # with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}coverage_LDDT_{i}.png", "rb") as in_file: + # alphafold_template = alphafold_template.replace(f"coverage_LDDT_{i}.png", f"data:image/png;base64,{base64.b64encode(in_file.read()).decode('utf-8')}") + + # for i in range(0, len(args.plddt)): + # with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}coverage_LDDT_{i}.html", "r") as in_file: + # lddt_html = in_file.read() + # alphafold_template = alphafold_template.replace("
", lddt_html) + + with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}coverage_LDDT.html", "r") as in_file: + lddt_html = in_file.read() + alphafold_template = alphafold_template.replace("
", lddt_html) + +""" +with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}seq_coverage.html", "r") as in_file: + alphafold_template = alphafold_template.replace(f"seq_coverage.png", f"{in_file.read()}") + +for i in range(0, 5): + with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}coverage_LDDT_{i}.html", "r") as in_file: + alphafold_template = alphafold_template.replace(f"coverage_LDDT_{i}.png", f"{in_file.read()}") + +""" + +with open(f"{args.output_dir}/{args.name}_{args.in_type}_report.html", "w") as out_file: + out_file.write(alphafold_template) diff --git a/main.nf b/main.nf index 6d762db1..e41745fa 100644 --- a/main.nf +++ b/main.nf @@ -35,6 +35,7 @@ include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nf include { getColabfoldAlphafold2Params } from './subworkflows/local/utils_nfcore_proteinfold_pipeline' include { getColabfoldAlphafold2ParamsPath } from './subworkflows/local/utils_nfcore_proteinfold_pipeline' +include { GENERATE_REPORT } from './modules/local/generat_report' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ COLABFOLD PARAMETER VALUES @@ -58,7 +59,7 @@ workflow NFCORE_PROTEINFOLD { main: ch_multiqc = Channel.empty() ch_versions = Channel.empty() - + ch_report_input = Channel.empty() // // WORKFLOW: Run alphafold2 // @@ -115,6 +116,10 @@ workflow NFCORE_PROTEINFOLD { ) ch_multiqc = ALPHAFOLD2.out.multiqc_report ch_versions = ch_versions.mix(ALPHAFOLD2.out.versions) + ch_report_input = ch_report_input.mix( + ALPHAFOLD2.out.pdb.join(ALPHAFOLD2.out.msa).map{it[0]["model"] = "ALPHAFOLD2"; it} + ) + } // @@ -178,7 +183,24 @@ workflow NFCORE_PROTEINFOLD { ) ch_multiqc = ESMFOLD.out.multiqc_report ch_versions = ch_versions.mix(ESMFOLD.out.versions) + ch_report_input = ch_report_input.mix( + ESMFOLD.out.pdb.combine(Channel.fromPath("$projectDir/assets/NO_FILE")).map{it[0]["model"] = "ESMFOLD"; it} + ) } + + // + // POST PROCESSING: generate visulaisation reports + // + if (!params.skip_visualisation){ + GENERATE_REPORT( + ch_report_input.map{[it[0], it[1]]}, + ch_report_input.map{[it[0], it[2]]}, + ch_report_input.map{it[0].model}, + Channel.fromPath("$projectDir/assets/proteinfold_template.html").first() + ) + } + + emit: multiqc_report = ch_multiqc // channel: /path/to/multiqc_report.html versions = ch_versions // channel: [version1, version2, ...] diff --git a/modules/local/colabfold_batch.nf b/modules/local/colabfold_batch.nf index 5b1c5467..691e4bd8 100644 --- a/modules/local/colabfold_batch.nf +++ b/modules/local/colabfold_batch.nf @@ -18,9 +18,11 @@ process COLABFOLD_BATCH { val numRec output: - path ("*") , emit: pdb - path ("*_mqc.png") , emit: multiqc - path "versions.yml", emit: versions + tuple val(meta), path ("*_relaxed_rank_*.pdb") , emit: pdb + tuple val(meta), path ("*_coverage.png") , emit: msa + tuple val(meta), path ("*_scores_rank.json") , emit: scores + path ("*_mqc.png") , emit: multiqc + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when diff --git a/modules/local/generat_report.nf b/modules/local/generat_report.nf new file mode 100644 index 00000000..77ded432 --- /dev/null +++ b/modules/local/generat_report.nf @@ -0,0 +1,51 @@ +process GENERATE_REPORT { + tag "$meta.id-$meta.model" + label 'process_single' + + conda "bioconda::multiqc:1.21" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'oras://community.wave.seqera.io/library/pip_biopython_matplotlib_plotly:e865101a15ad0014' : + 'community.wave.seqera.io/library/pip_biopython_matplotlib_plotly:4d51afeb4bb75495' }" + + input: + tuple val(meta), path(pdb) + tuple val(meta_msa), path(msa) + val(output_type) + path(template) + + output: + tuple val(meta), path ("*report.html"), emit: report + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + + """ + generat_report.py --type ${output_type} \\ + --msa ${msa} \\ + --pdb ${pdb.join(' ')} \\ + --html_template ${template} \\ + --output_dir ./ \\ + --name ${meta.id} \\ + $args \\ + + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + python: \$(python3 --version | sed 's/Python //g') + END_VERSIONS + """ + + stub: + """ + touch test_alphafold2_report.html + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + python: \$(python3 --version | sed 's/Python //g') + END_VERSIONS + """ +} diff --git a/modules/local/run_alphafold2.nf b/modules/local/run_alphafold2.nf index 20cbf9fc..11fe9eed 100644 --- a/modules/local/run_alphafold2.nf +++ b/modules/local/run_alphafold2.nf @@ -29,7 +29,9 @@ process RUN_ALPHAFOLD2 { output: path ("${fasta.baseName}*") - path "*_mqc.tsv", emit: multiqc + tuple val(meta), path ("${fasta.baseName}/ranked*pdb"), emit: pdb + tuple val(meta), path ("${fasta.baseName}/*_msa.tsv"), emit: msa + tuple val(meta), path ("*_mqc.tsv"), emit: multiqc path "versions.yml", emit: versions when: @@ -72,6 +74,9 @@ process RUN_ALPHAFOLD2 { paste ranked_0_plddt.tsv ranked_1_plddt.tsv ranked_2_plddt.tsv ranked_3_plddt.tsv ranked_4_plddt.tsv > plddt.tsv echo -e Positions"\\t"rank_0"\\t"rank_1"\\t"rank_2"\\t"rank_3"\\t"rank_4 > header.tsv cat header.tsv plddt.tsv > ../"${fasta.baseName}"_plddt_mqc.tsv + + extract_output.py --name ${fasta.baseName} \\ + --pkls features.pkl cd .. cat <<-END_VERSIONS > versions.yml @@ -84,10 +89,17 @@ process RUN_ALPHAFOLD2 { """ touch ./"${fasta.baseName}".alphafold.pdb touch ./"${fasta.baseName}"_mqc.tsv - + mkdir "${fasta.baseName}" + touch "${fasta.baseName}/ranked_0.pdb" + touch "${fasta.baseName}/ranked_1.pdb" + touch "${fasta.baseName}/ranked_2.pdb" + touch "${fasta.baseName}/ranked_3.pdb" + touch "${fasta.baseName}/ranked_4.pdb" + touch "${fasta.baseName}/${fasta.baseName}_msa.tsv + cat <<-END_VERSIONS > versions.yml "${task.process}": - awk: \$(gawk --version| head -1 | sed 's/GNU Awk //; s/, API:.*//') + python: \$(python3 --version | sed 's/Python //g') END_VERSIONS """ } diff --git a/modules/local/run_alphafold2_msa.nf b/modules/local/run_alphafold2_msa.nf index 85a40676..a4f00676 100644 --- a/modules/local/run_alphafold2_msa.nf +++ b/modules/local/run_alphafold2_msa.nf @@ -29,7 +29,7 @@ process RUN_ALPHAFOLD2_MSA { output: path ("${fasta.baseName}*") - path ("${fasta.baseName}.features.pkl"), emit: features + tuple val(meta), path ("${fasta.baseName}.features.pkl"), emit: features path "versions.yml" , emit: versions when: diff --git a/modules/local/run_alphafold2_pred.nf b/modules/local/run_alphafold2_pred.nf index ee9983c5..4f5ac62c 100644 --- a/modules/local/run_alphafold2_pred.nf +++ b/modules/local/run_alphafold2_pred.nf @@ -26,11 +26,13 @@ process RUN_ALPHAFOLD2_PRED { path ('uniref90/*') path ('pdb_seqres/*') path ('uniprot/*') - path msa + tuple val(meta), path(msa) output: path ("${fasta.baseName}*") - path "*_mqc.tsv", emit: multiqc + tuple val(meta), path ("${fasta.baseName}/ranked*pdb"), emit: pdb + tuple val(meta), path ("*_msa.tsv"), emit: msa + tuple val(meta), path ("*_mqc.tsv"), emit: multiqc path "versions.yml", emit: versions when: @@ -58,8 +60,10 @@ process RUN_ALPHAFOLD2_PRED { paste ranked_0_plddt.tsv ranked_1_plddt.tsv ranked_2_plddt.tsv ranked_3_plddt.tsv ranked_4_plddt.tsv > plddt.tsv echo -e Positions"\\t"rank_0"\\t"rank_1"\\t"rank_2"\\t"rank_3"\\t"rank_4 > header.tsv cat header.tsv plddt.tsv > ../"${fasta.baseName}"_plddt_mqc.tsv + cd .. - + extract_output.py --name ${fasta.baseName} \\ + --pkls ${msa} cat <<-END_VERSIONS > versions.yml "${task.process}": python: \$(python3 --version | sed 's/Python //g') @@ -70,10 +74,17 @@ process RUN_ALPHAFOLD2_PRED { """ touch ./"${fasta.baseName}".alphafold.pdb touch ./"${fasta.baseName}"_mqc.tsv - + mkdir "${fasta.baseName}" + touch "${fasta.baseName}/ranked_0.pdb" + touch "${fasta.baseName}/ranked_1.pdb" + touch "${fasta.baseName}/ranked_2.pdb" + touch "${fasta.baseName}/ranked_3.pdb" + touch "${fasta.baseName}/ranked_4.pdb" + touch ${fasta.baseName}_msa.tsv + cat <<-END_VERSIONS > versions.yml "${task.process}": - awk: \$(gawk --version| head -1 | sed 's/GNU Awk //; s/, API:.*//') + python: \$(python3 --version | sed 's/Python //g') END_VERSIONS """ } diff --git a/modules/local/run_esmfold.nf b/modules/local/run_esmfold.nf index f37c9eb3..bc1ee611 100644 --- a/modules/local/run_esmfold.nf +++ b/modules/local/run_esmfold.nf @@ -14,8 +14,8 @@ process RUN_ESMFOLD { val numRec output: - path ("${fasta.baseName}*.pdb"), emit: pdb - path ("${fasta.baseName}_plddt_mqc.tsv"), emit: multiqc + tuple val(meta), path ("${fasta.baseName}*.pdb"), emit: pdb + tuple val(meta), path ("${fasta.baseName}_plddt_mqc.tsv"), emit: multiqc path "versions.yml", emit: versions when: @@ -36,7 +36,7 @@ process RUN_ESMFOLD { awk '{print \$2"\\t"\$3"\\t"\$4"\\t"\$6"\\t"\$11}' "${fasta.baseName}"*.pdb | grep -v 'N/A' | uniq > plddt.tsv echo -e Atom_serial_number"\\t"Atom_name"\\t"Residue_name"\\t"Residue_sequence_number"\\t"pLDDT > header.tsv cat header.tsv plddt.tsv > "${fasta.baseName}"_plddt_mqc.tsv - + #mv "${fasta.baseName}"*.pdb ${fasta.baseName}.pdb cat <<-END_VERSIONS > versions.yml "${task.process}": esm-fold: $VERSION diff --git a/nextflow.config b/nextflow.config index 2bc5ca0a..ac81ccf0 100644 --- a/nextflow.config +++ b/nextflow.config @@ -81,7 +81,8 @@ params { // Process skipping options skip_multiqc = false - + skip_visualisation = false + // MultiQC options multiqc_config = null multiqc_title = null diff --git a/nextflow_schema.json b/nextflow_schema.json index 2d3ce68e..dc83e19a 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -203,6 +203,11 @@ "type": "boolean", "description": "Skip MultiQC.", "fa_icon": "fas fa-fast-forward" + }, + "skip_visualisation": { + "type": "boolean", + "description": "Skip Visualisation reports.", + "fa_icon": "fas fa-fast-forward" } } }, diff --git a/workflows/alphafold2.nf b/workflows/alphafold2.nf index 9a1aebae..6a470649 100644 --- a/workflows/alphafold2.nf +++ b/workflows/alphafold2.nf @@ -57,6 +57,8 @@ workflow ALPHAFOLD2 { main: ch_multiqc_files = Channel.empty() + ch_pdb = Channel.empty() + ch_msa = Channel.empty() // // Create input channel from input file provided through params.input @@ -94,7 +96,9 @@ workflow ALPHAFOLD2 { ch_pdb_seqres, ch_uniprot ) - ch_multiqc_rep = RUN_ALPHAFOLD2.out.multiqc.collect() + ch_pdb = ch_pdb.mix(RUN_ALPHAFOLD2.out.pdb) + ch_msa = ch_pdb.mix(RUN_ALPHAFOLD2.out.msa) + ch_multiqc_rep = RUN_ALPHAFOLD2.out.multiqc.map{it[1]}.collect() ch_versions = ch_versions.mix(RUN_ALPHAFOLD2.out.versions) } else if (alphafold2_mode == 'split_msa_prediction') { @@ -134,7 +138,9 @@ workflow ALPHAFOLD2 { ch_uniprot, RUN_ALPHAFOLD2_MSA.out.features ) - ch_multiqc_rep = RUN_ALPHAFOLD2_PRED.out.multiqc.collect() + ch_pdb = ch_pdb.mix(RUN_ALPHAFOLD2_PRED.out.pdb) + ch_msa = ch_pdb.mix(RUN_ALPHAFOLD2_PRED.out.msa) + ch_multiqc_rep = RUN_ALPHAFOLD2_PRED.out.multiqc.map{it[1]}.collect() ch_versions = ch_versions.mix(RUN_ALPHAFOLD2_PRED.out.versions) } @@ -144,7 +150,6 @@ workflow ALPHAFOLD2 { softwareVersionsToYAML(ch_versions) .collectFile(storeDir: "${params.outdir}/pipeline_info", name: 'nf_core_proteinfold_software_mqc_versions.yml', sort: true, newLine: true) .set { ch_collated_versions } - // // MODULE: MultiQC // @@ -166,15 +171,17 @@ workflow ALPHAFOLD2 { ch_multiqc_files = ch_multiqc_files.mix(ch_multiqc_rep) MULTIQC ( - ch_multiqc_files.collect(), - ch_multiqc_config.toList(), - ch_multiqc_custom_config.toList(), - ch_multiqc_logo.toList() + ch_multiqc_files.collect(sort: true), + ch_multiqc_config.toSortedList(), + ch_multiqc_custom_config.toSortedList(), + ch_multiqc_logo.toSortedList() ) ch_multiqc_report = MULTIQC.out.report.toList() } emit: + pdb = ch_pdb // channel: /path/to/*.pdb + msa = ch_msa // channel: /path/to/*msa.tsv multiqc_report = ch_multiqc_report // channel: /path/to/multiqc_report.html versions = ch_versions // channel: [ path(versions.yml) ] } diff --git a/workflows/esmfold.nf b/workflows/esmfold.nf index 962c01a1..18b2a7f9 100644 --- a/workflows/esmfold.nf +++ b/workflows/esmfold.nf @@ -101,7 +101,7 @@ workflow ESMFOLD { ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) ch_multiqc_files = ch_multiqc_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml')) ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) - ch_multiqc_files = ch_multiqc_files.mix(RUN_ESMFOLD.out.multiqc.collect()) + ch_multiqc_files = ch_multiqc_files.mix(RUN_ESMFOLD.out.multiqc.map{it[1]}.collect()) MULTIQC ( ch_multiqc_files.collect(), @@ -113,8 +113,9 @@ workflow ESMFOLD { } emit: - multiqc_report = ch_multiqc_report // channel: /path/to/multiqc_report.html - versions = ch_versions // channel: [ path(versions.yml) ] + pdb = RUN_ESMFOLD.out.pdb // channel: /path/to/*pdb + multiqc_report = ch_multiqc_report // channel: /path/to/multiqc_report.html + versions = ch_versions // channel: [ path(versions.yml) ] } /* From 586798c202a0d3d7c4a45e834fa51d113ec73db1 Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Tue, 1 Oct 2024 13:22:28 +1000 Subject: [PATCH 019/123] configure the reports --- bin/generat_report.py | 20 +++++++++++--------- main.nf | 5 ++++- modules/local/colabfold_batch.nf | 10 +++++++--- modules/local/generat_report.nf | 2 ++ nextflow_schema.json | 2 +- tower.yml | 6 ++++++ workflows/alphafold2.nf | 8 ++++---- workflows/colabfold.nf | 4 +++- 8 files changed, 38 insertions(+), 19 deletions(-) diff --git a/bin/generat_report.py b/bin/generat_report.py index dcb48911..ab8e9df0 100755 --- a/bin/generat_report.py +++ b/bin/generat_report.py @@ -1,8 +1,8 @@ #!/usr/bin/env python import os -from matplotlib import pyplot as plt import argparse +from matplotlib import pyplot as plt from collections import OrderedDict import base64 import os @@ -293,8 +293,11 @@ def pdb_to_lddt(pdb_files, generate_tsv): return output_lddt, averages + + print("Starting...") +version = '1.0.0' parser = argparse.ArgumentParser() parser.add_argument('--type', dest='in_type') parser.add_argument('--generate_tsv', choices=['y', 'n'], default = 'n', dest='generate_tsv') @@ -303,6 +306,7 @@ def pdb_to_lddt(pdb_files, generate_tsv): parser.add_argument('--name', dest='name') parser.add_argument('--output_dir',dest='output_dir') parser.add_argument('--html_template',dest='html_template') +parser.add_argument('--version', action='version', version=f'{version}') parser.set_defaults(output_dir='') parser.set_defaults(in_type='ESM-FOLD') parser.set_defaults(name='') @@ -372,15 +376,13 @@ def pdb_to_lddt(pdb_files, generate_tsv): lddt_html = in_file.read() alphafold_template = alphafold_template.replace("
", lddt_html) -""" -with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}seq_coverage.html", "r") as in_file: - alphafold_template = alphafold_template.replace(f"seq_coverage.png", f"{in_file.read()}") - -for i in range(0, 5): - with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}coverage_LDDT_{i}.html", "r") as in_file: - alphafold_template = alphafold_template.replace(f"coverage_LDDT_{i}.png", f"{in_file.read()}") +if False: + with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}seq_coverage.html", "r") as in_file: + alphafold_template = alphafold_template.replace(f"seq_coverage.png", f"{in_file.read()}") -""" + for i in range(0, 5): + with open(f"{args.output_dir}/{args.name + ('_' if args.name else '')}coverage_LDDT_{i}.html", "r") as in_file: + alphafold_template = alphafold_template.replace(f"coverage_LDDT_{i}.png", f"{in_file.read()}") with open(f"{args.output_dir}/{args.name}_{args.in_type}_report.html", "w") as out_file: out_file.write(alphafold_template) diff --git a/main.nf b/main.nf index e41745fa..53247245 100644 --- a/main.nf +++ b/main.nf @@ -155,6 +155,9 @@ workflow NFCORE_PROTEINFOLD { ) ch_multiqc = COLABFOLD.out.multiqc_report ch_versions = ch_versions.mix(COLABFOLD.out.versions) + ch_report_input = ch_report_input.mix( + COLABFOLD.out.pdb.join(COLABFOLD.out.msa).map{it[0]["model"] = "COLABFOLD"; it} + ) } // @@ -198,9 +201,9 @@ workflow NFCORE_PROTEINFOLD { ch_report_input.map{it[0].model}, Channel.fromPath("$projectDir/assets/proteinfold_template.html").first() ) + ch_versions = ch_versions.mix(GENERATE_REPORT.out.versions) } - emit: multiqc_report = ch_multiqc // channel: /path/to/multiqc_report.html versions = ch_versions // channel: [version1, version2, ...] diff --git a/modules/local/colabfold_batch.nf b/modules/local/colabfold_batch.nf index 691e4bd8..7e304eaa 100644 --- a/modules/local/colabfold_batch.nf +++ b/modules/local/colabfold_batch.nf @@ -20,9 +20,8 @@ process COLABFOLD_BATCH { output: tuple val(meta), path ("*_relaxed_rank_*.pdb") , emit: pdb tuple val(meta), path ("*_coverage.png") , emit: msa - tuple val(meta), path ("*_scores_rank.json") , emit: scores - path ("*_mqc.png") , emit: multiqc - path "versions.yml" , emit: versions + tuple val(meta), path ("*_mqc.png") , emit: multiqc + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when @@ -54,6 +53,11 @@ process COLABFOLD_BATCH { """ touch ./"${fasta.baseName}"_colabfold.pdb touch ./"${fasta.baseName}"_mqc.png + touch ./${fasta.baseName}_relaxed_rank_01.pdb + touch ./${fasta.baseName}_relaxed_rank_02.pdb + touch ./${fasta.baseName}_relaxed_rank_03.pdb + touch ./${fasta.baseName}_coverage.png + touch ./${fasta.baseName}_scores_rank.json cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/generat_report.nf b/modules/local/generat_report.nf index 77ded432..5733baa1 100644 --- a/modules/local/generat_report.nf +++ b/modules/local/generat_report.nf @@ -36,6 +36,7 @@ process GENERATE_REPORT { cat <<-END_VERSIONS > versions.yml "${task.process}": python: \$(python3 --version | sed 's/Python //g') + generate_report.py: \$(python3 --version) END_VERSIONS """ @@ -46,6 +47,7 @@ process GENERATE_REPORT { cat <<-END_VERSIONS > versions.yml "${task.process}": python: \$(python3 --version | sed 's/Python //g') + generate_report.py: \$(python3 --version) END_VERSIONS """ } diff --git a/nextflow_schema.json b/nextflow_schema.json index dc83e19a..9baab54b 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -206,7 +206,7 @@ }, "skip_visualisation": { "type": "boolean", - "description": "Skip Visualisation reports.", + "description": "Skip visualisation reports.", "fa_icon": "fas fa-fast-forward" } } diff --git a/tower.yml b/tower.yml index 7fc70c10..47e1860d 100644 --- a/tower.yml +++ b/tower.yml @@ -7,3 +7,9 @@ reports: display: "COLABFOLD MultiQC HTML report" samplesheet.csv: display: "Auto-created samplesheet with collated metadata and FASTQ paths" + "*_ALPHAFOLD2_report.html": + display: "ALPHAFOLD2 - Predected structures" + "*_ESMFOLD_report.html": + display: "ESMFOLD - Predected structures" + "*_COLABFOLD_report.html": + display: "COLABFOLD - Predected structures" \ No newline at end of file diff --git a/workflows/alphafold2.nf b/workflows/alphafold2.nf index 6a470649..97882481 100644 --- a/workflows/alphafold2.nf +++ b/workflows/alphafold2.nf @@ -171,10 +171,10 @@ workflow ALPHAFOLD2 { ch_multiqc_files = ch_multiqc_files.mix(ch_multiqc_rep) MULTIQC ( - ch_multiqc_files.collect(sort: true), - ch_multiqc_config.toSortedList(), - ch_multiqc_custom_config.toSortedList(), - ch_multiqc_logo.toSortedList() + ch_multiqc_files.collect(), + ch_multiqc_config.toList(), + ch_multiqc_custom_config.toList(), + ch_multiqc_logo.toList() ) ch_multiqc_report = MULTIQC.out.report.toList() } diff --git a/workflows/colabfold.nf b/workflows/colabfold.nf index eafc222c..c130f923 100644 --- a/workflows/colabfold.nf +++ b/workflows/colabfold.nf @@ -152,7 +152,7 @@ workflow COLABFOLD { ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) ch_multiqc_files = ch_multiqc_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml')) ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) - ch_multiqc_files = ch_multiqc_files.mix(COLABFOLD_BATCH.out.multiqc.collect()) + ch_multiqc_files = ch_multiqc_files.mix(COLABFOLD_BATCH.out.multiqc.map{it[1]}.collect()) MULTIQC ( ch_multiqc_files.collect(), @@ -164,6 +164,8 @@ workflow COLABFOLD { } emit: + pdb = COLABFOLD_BATCH.out.pdb // channel: /path/to/*.pdb + msa = COLABFOLD_BATCH.out.msa // channel: /path/to/*_coverage.png multiqc_report = ch_multiqc_report // channel: /path/to/multiqc_report.html versions = ch_versions // channel: [ path(versions.yml) ] } From 6985dcd7e35a86d1f9af328bd16a9e8bd816fd35 Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Tue, 1 Oct 2024 14:14:53 +1000 Subject: [PATCH 020/123] rename esmfold output pdb --- bin/generat_report.py | 2 +- modules/local/run_esmfold.nf | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/generat_report.py b/bin/generat_report.py index ab8e9df0..9a353846 100755 --- a/bin/generat_report.py +++ b/bin/generat_report.py @@ -308,7 +308,7 @@ def pdb_to_lddt(pdb_files, generate_tsv): parser.add_argument('--html_template',dest='html_template') parser.add_argument('--version', action='version', version=f'{version}') parser.set_defaults(output_dir='') -parser.set_defaults(in_type='ESM-FOLD') +parser.set_defaults(in_type='ESMFOLD') parser.set_defaults(name='') args = parser.parse_args() diff --git a/modules/local/run_esmfold.nf b/modules/local/run_esmfold.nf index bc1ee611..633d3fab 100644 --- a/modules/local/run_esmfold.nf +++ b/modules/local/run_esmfold.nf @@ -36,7 +36,9 @@ process RUN_ESMFOLD { awk '{print \$2"\\t"\$3"\\t"\$4"\\t"\$6"\\t"\$11}' "${fasta.baseName}"*.pdb | grep -v 'N/A' | uniq > plddt.tsv echo -e Atom_serial_number"\\t"Atom_name"\\t"Residue_name"\\t"Residue_sequence_number"\\t"pLDDT > header.tsv cat header.tsv plddt.tsv > "${fasta.baseName}"_plddt_mqc.tsv - #mv "${fasta.baseName}"*.pdb ${fasta.baseName}.pdb + mv "${fasta.baseName}"*.pdb tmp.pdb + mv tmp.pdb ${fasta.baseName}.pdb + cat <<-END_VERSIONS > versions.yml "${task.process}": esm-fold: $VERSION From 81e877ec6c16f34ded0c772f8667274c7408563b Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Wed, 2 Oct 2024 11:13:25 +1000 Subject: [PATCH 021/123] average, tooltip & msa png --- assets/proteinfold_template.html | 63 ++++++++++++++++++++++++-------- bin/generat_report.py | 9 +++-- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/assets/proteinfold_template.html b/assets/proteinfold_template.html index 57cfe3d7..64820aaa 100644 --- a/assets/proteinfold_template.html +++ b/assets/proteinfold_template.html @@ -14,6 +14,8 @@ crossorigin="anonymous" > + + - - - - - - - - - - - - - - -
- -
- -
- - - - - -
- -
- - -
-
-
-
-
-
<50
-
70
-
90+
-
-
-
- -
-

- Alphafold produces a - - per-residue confidence score (pLDDT) - - between 0 and 100. Some regions below 50 pLDDT may be unstructured in isolation. -

-
-
- - - - - - -
- +
- - -
-
-
Information
- -
-
-
Program: *prog_name*
-
ID: *sample_name*
+ class="h-auto w-40 min-w-32" + src="data:image/png;base64,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" + /> +
+
Protein structure prediction
+
+ -
Average:
-
- -
-
Navigation
- - -
-
- Scroll up/down - to zoom in and out -
-
- Click + drag - to rotate the structure -
-
- CTRL + click + drag - to move the structure -
-
- Click - an atom to bring it into focus -
+
+ + + + + + + +
+ +
+ +
+ + -
-
- - -
-
-
Toggle representations
-
- - - - + +
+
-
- -
-
-
-
Actions
-
- + + +
+
-
-
+ +
+ +
+
+
Information
+ +
+
+
Program: *prog_name*
+
ID: *sample_name*
+
+
Average:
+
+
+
Navigation
+ +
+
+ Scroll up/down + to zoom in and out +
+
+ Click + drag + to rotate the structure +
+
+ CTRL + click + drag + to move the structure +
+
+ Click + an atom to bring it into focus +
+
+
+
+ +
+
+
Toggle representations
+
+ + + + +
+
+
+
+
+
Actions
+
+ + +
+
+
+
Download
+
+ + +
+
+
+
+
+
-
- -
-
-
-
-
-
-
- - -
-
-
Sequence Coverage
-
-
- -
- -
+
+
+
+
+
+
- -
-
pLDDT
-
- -
-
-
-
+ +
+
+
Sequence Coverage
+
+
+ +
+ +
+
+
+
pLDDT
+
+ +
+
+
+
+
+
- - -
- - -
-
-
- - - -
-
-

- The Australian BioCommons - is supported by - Bioplatforms Australia -

-

- Bioplatforms Australia - is enabled by - NCRIS -

-
+ +
+
+
+ + + +
+
+

+ The Australian BioCommons + is supported by + Bioplatforms Australia +

+

+ Bioplatforms Australia + is enabled by + NCRIS +

+
+
-
- - - + } + }); + }; + + const updateButtons = () => { + MODELS.forEach((name, i) => { + const id = `#btn-${name.replace(".pdb", "")}`; + const btn = document.querySelector(id); + if (!btn) return; + i == state.model ? btn.classList.add("selected") : btn.classList.remove("selected"); + }); + + REPRESENTATIONS.forEach((name) => { + const id = `#btn-${name}`.replace("+", "-"); + const btn = document.querySelector(id); + if (!btn) return; + if (name in state.representations) { + btn.classList.add("selected"); + } else { + btn.classList.remove("selected"); + } + }); + + // Show "Nothing to display" if no representations are selected + document.querySelector("#ngl-nothing").style.display = Object.keys(state.representations).length + ? "none" + : "flex"; + }; + From b32a64772b363ffd1e9a3cd9280c1be4846c824d Mon Sep 17 00:00:00 2001 From: Ziad Al Bkhetan Date: Wed, 2 Oct 2024 16:41:56 +1000 Subject: [PATCH 028/123] lint report --- assets/proteinfold_template.html | 1676 +++++++++++++++--------------- 1 file changed, 838 insertions(+), 838 deletions(-) diff --git a/assets/proteinfold_template.html b/assets/proteinfold_template.html index 17ee85a6..7016d326 100644 --- a/assets/proteinfold_template.html +++ b/assets/proteinfold_template.html @@ -1,270 +1,270 @@ - - - - - Protein structure prediction - - - - - - - - - - -