From dddfc9cba31d87589d6a14eec4254bcb077370f1 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 10 Nov 2021 12:46:41 +0100 Subject: [PATCH 01/34] added preprocessing of raw reads --- assets/adapters.fa | 317 +++++++++++++++++++++++++++++++++ config/run.config | 41 +++++ main.nf | 185 ++++++++++++++++--- modules/nevermore/functions.nf | 16 ++ modules/nevermore/qc/bbduk.nf | 33 ++++ modules/nevermore/qc/fastqc.nf | 22 +++ scripts/assess_readlengths.py | 48 +++++ scripts/check_readlengths.py | 1 + scripts/hltrim.py | 82 +++++++++ 9 files changed, 725 insertions(+), 20 deletions(-) create mode 100755 assets/adapters.fa create mode 100644 modules/nevermore/functions.nf create mode 100644 modules/nevermore/qc/bbduk.nf create mode 100644 modules/nevermore/qc/fastqc.nf create mode 100644 scripts/assess_readlengths.py create mode 100644 scripts/hltrim.py diff --git a/assets/adapters.fa b/assets/adapters.fa new file mode 100755 index 0000000..a87d425 --- /dev/null +++ b/assets/adapters.fa @@ -0,0 +1,317 @@ +>Reverse_adapter +AGATCGGAAGAGCACACGTCTGAACTCCAGTCACATCACGATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Universal_Adapter +AATGATACGGCGACCACCGAGATCTACACTCTTTCCCTACACGACGCTCTTCCGATCT +>pcr_dimer +AATGATACGGCGACCACCGAGATCTACACTCTTTCCCTACACGACGCTCTTCCGATCTAGATCGGAAGAGCGGTTCAGCAGGAATGCCGAGACCGATCTCGTATGCCGTCTTCTGCTTG +>PCR_Primers +AATGATACGGCGACCACCGAGATCTACACTCTTTCCCTACACGACGCTCTTCCGATCTCAAGCAGAAGACGGCATACGAGCTCTTCCGATCT +>TruSeq_Adapter_Index_1_6 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACATCACGATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_2 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACCGATGTATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_3 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACTTAGGCATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_4 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACTGACCAATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_5 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACACAGTGATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_6 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACGCCAATATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_7 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACCAGATCATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_8 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACACTTGAATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_9 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACGATCAGATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_10 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACTAGCTTATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_11 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACGGCTACATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_12 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACCTTGTAATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_13 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACAGTCAACAATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_14 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACAGTTCCGTATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_15 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACATGTCAGAATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_16 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACCCGTCCCGATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_18_7 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACGTCCGCACATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_19 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACGTGAAACGATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_20 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACGTGGCCTTATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_21 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACGTTTCGGAATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_22 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACCGTACGTAATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_23 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACGAGTGGATATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_25 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACACTGATATATCTCGTATGCCGTCTTCTGCTTG +>TruSeq_Adapter_Index_27 +GATCGGAAGAGCACACGTCTGAACTCCAGTCACATTCCTTTATCTCGTATGCCGTCTTCTGCTTG +>I5_Nextera_Transposase_1 +CTGTCTCTTATACACATCTGACGCTGCCGACGA +>I7_Nextera_Transposase_1 +CTGTCTCTTATACACATCTCCGAGCCCACGAGAC +>I5_Nextera_Transposase_2 +CTGTCTCTTATACACATCTCTGATGGCGCGAGGGAGGC +>I7_Nextera_Transposase_2 +CTGTCTCTTATACACATCTCTGAGCGGGCTGGCAAGGC +>I5_Primer_Nextera_XT_and_Nextera_Enrichment_[N/S/E]501 +GACGCTGCCGACGAGCGATCTAGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_and_Nextera_Enrichment_[N/S/E]502 +GACGCTGCCGACGAATAGAGAGGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_and_Nextera_Enrichment_[N/S/E]503 +GACGCTGCCGACGAAGAGGATAGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_and_Nextera_Enrichment_[N/S/E]504 +GACGCTGCCGACGATCTACTCTGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_and_Nextera_Enrichment_[N/S/E]505 +GACGCTGCCGACGACTCCTTACGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_and_Nextera_Enrichment_[N/S/E]506 +GACGCTGCCGACGATATGCAGTGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_and_Nextera_Enrichment_[N/S/E]507 +GACGCTGCCGACGATACTCCTTGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_and_Nextera_Enrichment_[N/S/E]508 +GACGCTGCCGACGAAGGCTTAGGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_and_Nextera_Enrichment_[N/S/E]517 +GACGCTGCCGACGATCTTACGCGTGTAGATCTCGGTGGTCGCCGTATCATT +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N701 +CCGAGCCCACGAGACTAAGGCGAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N702 +CCGAGCCCACGAGACCGTACTAGATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N703 +CCGAGCCCACGAGACAGGCAGAAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N704 +CCGAGCCCACGAGACTCCTGAGCATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N705 +CCGAGCCCACGAGACGGACTCCTATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N706 +CCGAGCCCACGAGACTAGGCATGATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N707 +CCGAGCCCACGAGACCTCTCTACATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N708 +CCGAGCCCACGAGACCAGAGAGGATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N709 +CCGAGCCCACGAGACGCTACGCTATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N710 +CCGAGCCCACGAGACCGAGGCTGATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N711 +CCGAGCCCACGAGACAAGAGGCAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_and_Nextera_Enrichment_N712 +CCGAGCCCACGAGACGTAGAGGAATCTCGTATGCCGTCTTCTGCTTG +>I5_Primer_Nextera_XT_Index_Kit_v2_S502 +GACGCTGCCGACGAATAGAGAGGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S503 +GACGCTGCCGACGAAGAGGATAGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S505 +GACGCTGCCGACGACTCCTTACGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S506 +GACGCTGCCGACGATATGCAGTGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S507 +GACGCTGCCGACGATACTCCTTGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S508 +GACGCTGCCGACGAAGGCTTAGGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S510 +GACGCTGCCGACGAATTAGACGGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S511 +GACGCTGCCGACGACGGAGAGAGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S513 +GACGCTGCCGACGACTAGTCGAGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S515 +GACGCTGCCGACGAAGCTAGAAGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S516 +GACGCTGCCGACGAACTCTAGGGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S517 +GACGCTGCCGACGATCTTACGCGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S518 +GACGCTGCCGACGACTTAATAGGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S520 +GACGCTGCCGACGAATAGCCTTGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S521 +GACGCTGCCGACGATAAGGCTCGTGTAGATCTCGGTGGTCGCCGTATCATT +>I5_Primer_Nextera_XT_Index_Kit_v2_S522 +GACGCTGCCGACGATCGCATAAGTGTAGATCTCGGTGGTCGCCGTATCATT +>I7_Primer_Nextera_XT_Index_Kit_v2_N701 +CCGAGCCCACGAGACTAAGGCGAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N702 +CCGAGCCCACGAGACCGTACTAGATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N703 +CCGAGCCCACGAGACAGGCAGAAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N704 +CCGAGCCCACGAGACTCCTGAGCATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N705 +CCGAGCCCACGAGACGGACTCCTATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N706 +CCGAGCCCACGAGACTAGGCATGATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N707 +CCGAGCCCACGAGACCTCTCTACATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N710 +CCGAGCCCACGAGACCGAGGCTGATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N711 +CCGAGCCCACGAGACAAGAGGCAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N712 +CCGAGCCCACGAGACGTAGAGGAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N714 +CCGAGCCCACGAGACGCTCATGAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N715 +CCGAGCCCACGAGACATCTCAGGATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N716 +CCGAGCCCACGAGACACTCGCTAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N718 +CCGAGCCCACGAGACGGAGCTACATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N719 +CCGAGCCCACGAGACGCGTAGTAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N720 +CCGAGCCCACGAGACCGGAGCCTATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N721 +CCGAGCCCACGAGACTACGCTGCATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N722 +CCGAGCCCACGAGACATGCGCAGATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N723 +CCGAGCCCACGAGACTAGCGCTCATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N724 +CCGAGCCCACGAGACACTGAGCGATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N726 +CCGAGCCCACGAGACCCTAAGACATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N727 +CCGAGCCCACGAGACCGATCAGTATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N728 +CCGAGCCCACGAGACTGCAGCTAATCTCGTATGCCGTCTTCTGCTTG +>I7_Primer_Nextera_XT_Index_Kit_v2_N729 +CCGAGCCCACGAGACTCGACGTCATCTCGTATGCCGTCTTCTGCTTG +>I5_Adapter_Nextera +CTGATGGCGCGAGGGAGGCGTGTAGATCTCGGTGGTCGCCGTATCATT +>I7_Adapter_Nextera_No_Barcode +CTGAGCGGGCTGGCAAGGCAGACCGATCTCGTATGCCGTCTTCTGCTTG +>Nextera_LMP_Read1_External_Adapter +GATCGGAAGAGCACACGTCTGAACTCCAGTCAC +>Nextera_LMP_Read2_External_Adapter +GATCGGAAGAGCGTCGTGTAGGGAAAGAGTGT +>RNA_Adapter_(RA5)_part_#_15013205 +GATCGTCGGACTGTAGAACTCTGAAC +>RNA_Adapter_(RA3)_part_#_15013207 +CCTTGGCACCCGAGAATTCCA +>Stop_Oligo_(STP)_8 +CCACGGGAACGTGGTGGAATTC +>RNA_RT_Primer_(RTP)_part_#_15013981 +TGGAATTCTCGGGTGCCAAGGC +>RNA_PCR_Primer_(RP1)_part_#_15013198 +TCGGACTGTAGAACTCTGAACGTGTAGATCTCGGTGGTCGCCGTATCATT +>RNA_PCR_Primer_Index_1_(RPI1)_2,9 +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACATCACGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_2_(RPI2) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCGATGTATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_3_(RPI3) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACTTAGGCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_4_(RPI4) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACTGACCAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_5_(RPI5) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACACAGTGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_6_(RPI6) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGCCAATATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_7_(RPI7) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCAGATCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_8_(RPI8) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACACTTGAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_9_(RPI9) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGATCAGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_10_(RPI10) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACTAGCTTATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_11_(RPI11) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGGCTACATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_12_(RPI12) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCTTGTAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_13_(RPI13) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACAGTCAAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_14_(RPI14) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACAGTTCCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_15_(RPI15) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACATGTCAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_16_(RPI16) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCCGTCCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_17_(RPI17) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGTAGAGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_18_(RPI18) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGTCCGCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_19_(RPI19) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGTGAAAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_20_(RPI20) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGTGGCCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_21_(RPI21) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGTTTCGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_22_(RPI22) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCGTACGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_23_(RPI23) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGAGTGGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_24_(RPI24) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGGTAGCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_25_(RPI25) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACACTGATATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_26_(RPI26) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACATGAGCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_27_(RPI27) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACATTCCTATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_28_(RPI28) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCAAAAGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_29_(RPI29) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCAACTAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_30_(RPI30) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCACCGGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_31_(RPI31) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCACGATATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_32_(RPI32) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCACTCAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_33_(RPI33) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCAGGCGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_34_(RPI34) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCATGGCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_35_(RPI35) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCATTTTATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_36_(RPI36) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCCAACAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_37_(RPI37) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCGGAATATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_38_(RPI38) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCTAGCTATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_39_(RPI39) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCTATACATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_40_(RPI40) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACCTCAGAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_41_(RPI41) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACGACGACATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_42_(RPI42) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACTAATCGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_43_(RPI43) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACTACAGCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_44_(RPI44) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACTATAATATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_45_(RPI45) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACTCATTCATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_46_(RPI46) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACTCCCGAATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_47_(RPI47) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACTCGAAGATCTCGTATGCCGTCTTCTGCTTG +>RNA_PCR_Primer_Index_48_(RPI48) +TGGAATTCTCGGGTGCCAAGGAACTCCAGTCACTCGGCAATCTCGTATGCCGTCTTCTGCTTG +>PhiX_read1_adapter +AGATCGGAAGAGCGGTTCAGCAGGAATGCCGAGACCGATCTCGTATGCCGTCTTCTGCTTGAAA +>PhiX_read2_adapter +AGATCGGAAGAGCGTCGTGTAGGGAAAGAGTGTAGATCTCGGTGGTCGCCGTATCATTAAAAAA +>Bisulfite_R1 +AGATCGGAAGAGCACACGTCTGAAC +>Bisulfite_R2 +AGATCGGAAGAGCGTCGTGTAGGGA +>Illumina Small RNA v1.5 3p Adapter +ATCTCGTATGCCGTCTTCTGCTTG +>Illumina RNA 3p Adapter (RA3) +TGGAATTCTCGGGTGCCAAGG +>Illumina RNA 5p Adapter (RA5) +GTTCAGAGTTCTACAGTCCGACGATC +>Illumina 3p RNA Adapter +TCGTATGCCGTCTTCTGCTTGT + diff --git a/config/run.config b/config/run.config index f48623b..b27512b 100644 --- a/config/run.config +++ b/config/run.config @@ -4,6 +4,29 @@ params { min_overlap = 20 left_primer = 0 right_primer = 0 + + /* + bbduk qc parameters + s. https://jgi.doe.gov/data-and-tools/bbtools/bb-tools-user-guide/bbduk-guide/ + qtrim=rl trimq=3 : gentle quality trimming (only discard bases < phred 3; phred 2 = junk marker) on either side (rl) of the read + maq=25 : discard reads below average quality of pred 25 + minlen=45 : discard reads < 45bp + ref=?? ktrim=r k=23 mink=11 hdist=1 tpe tbo : right-side k-mer based adapter clipping with 1 mismatch allowed, try overlap-detection (tbo), and trim pairs to same length (tpe) upon adapter detection + ftm=5 : get rid off (n*5)+1st base (last sequencing cycle illumina garbage) + entropy=0.5 entropywindow=50 entropyk=5 : discard low complexity sequences + */ + + qc_params_primers = "qtrim=rl trimq=3 ktrim=l k=14 mink=1 hdist=1 cu=t" + qc_params_adapters = "qtrim=rl trimq=3 ktrim=l k=23 mink=1 hdist=1 tpe tbo cu=t" + qc_minlen = 100 + + + + + + + + } /* section below needs to be adjusted to local cluster */ @@ -53,6 +76,24 @@ process { maxRetries = 3 cpus = 8 } + withName: qc_bbduk { + container = "oras://ghcr.io/zellerlab/gaga2:latest" + executor = "slurm" + errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} + cpus = 4 + memory = {8.GB * task.attempt} + time = '2h' + maxRetries = 3 + } + withName: fastqc { + container = "oras://ghcr.io/zellerlab/gaga2:latest" + executor = "slurm" + errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} + cpus = 2 + memory = {4.GB * task.attempt} + time = '7d' + maxRetries = 3 + } } singularity { diff --git a/main.nf b/main.nf index c02871d..5080ca9 100644 --- a/main.nf +++ b/main.nf @@ -2,6 +2,10 @@ nextflow.enable.dsl = 2 +include { qc_bbduk } from "./modules/nevermore/qc/bbduk" +include { fastqc } from "./modules/nevermore/qc/fastqc" +include { classify_sample } from "./modules/nevermore/functions" + //def helpMessage() { // log.info """ @@ -38,19 +42,25 @@ nextflow.enable.dsl = 2 // exit 0 //} // -if ( !params.min_overlap ) { - params.min_overlap = 20 -} -if ( !params.left_primer ) { - params.left_primer = 0 -} -if ( !params.right_primer ) { - params.right_primer = 0 -} +process check_readlengths { + //publishDir "${params.output_dir}", mode: params.publish_mode + + input: + path input_reads + + output: + path("RUN_FIGARO"), emit: run_figaro, optional: true + + script: + """ + python ${projectDir}/scripts/check_readlengths.py . . + """ +} + process ltrim { publishDir "${params.output_dir}", mode: params.publish_mode @@ -68,12 +78,38 @@ process ltrim { } +process figaro_dummy { + publishDir "${params.output_dir}", mode: params.publish_mode + + input: + path input_reads + val is_paired_end + path run_figaro_sentinel + + output: + path("figaro/trimParameters.txt"), emit: trim_params + + + script: + + """ + mkdir figaro + echo "13 13\n" > figaro/trimParameters.txt + """ +} + + + + + + process figaro { publishDir "${params.output_dir}", mode: params.publish_mode input: path input_reads val is_paired_end + //path run_figaro_sentinel output: path("figaro/trimParameters.json"), emit: trim_params @@ -81,6 +117,7 @@ process figaro { script: def paired_params = (is_paired_end == true) ? "-r ${params.right_primer} -m ${params.min_overlap}" : "" + """ figaro -i . -o figaro/ -a ${params.amplicon_length} -f ${params.left_primer} ${paired_params} """ @@ -157,6 +194,74 @@ process dada2_analysis { } +process assess_read_length_distribution { + input: + //tuple val(sample), path(fastqc_reports) + path(fastq_reports) + + output: + path("read_length_thresholds.txt"), emit: read_length + + script: + """ + python ${projectDir}/scripts/assess_readlengths.py . > read_length_thresholds.txt + """ +} + + + +process homogenise_readlengths { + input: + tuple val(sample), path(reads) + path(read_lengths) + + output: + //path("${sample.id}_R*.{fastq,fq,fastq.gz,fq.gz}"), emit: homogenised_reads + path("${sample.id}/*.{fastq,fq,fastq.gz,fq.gz}"), emit: reads + stdout emit: blah + + script: + """ + read_len=\$(head -n 1 ${read_lengths} | cut -f 1) + echo \$read_len + python ${projectDir}/scripts/hltrim.py ${reads} -c \$read_len -o ${sample.id} + """ +} + +workflow raw_reads_figaro { + + take: + reads + run_figaro + + main: + qc_bbduk(reads, "${projectDir}/assets/adapters.fa") + + fastqc(qc_bbduk.out.reads) + fastqc_ch = fastqc.out.reports + .map { sample, report -> return report } + .collect() + //.groupTuple(sort: true) + + assess_read_length_distribution(fastqc_ch) + homogenise_readlengths(qc_bbduk.out.reads, assess_read_length_distribution.out.read_length) + + hom_reads = homogenise_readlengths.out.reads.collect() + figaro(hom_reads, !params.single_end) + extract_trim_parameters(figaro.out.trim_params) + + //figaro_dummy(files_only_ch, is_paired_end, check_readlengths.out.run_figaro) + //trim_params_ch = trim_params_ch.concat(figaro_dummy.out.trim_params) + //extract_trim_parameters(figaro_dummy.out.trim_params) + + emit: + reads = hom_reads + trim_params = extract_trim_parameters.out.trim_params + +} + + + workflow { fastq_ch = Channel .fromPath(params.input_dir + "/**_*[12].{fastq,fq,fastq.gz,fq.gz}") @@ -166,22 +271,31 @@ workflow { return tuple(sample, file) } .groupTuple(sort: true) + .map { classify_sample(it[0], it[1]) } - if (!params.single_end) { - library_layout = "PAIRED"; - dada2_preprocess_script = "$projectDir/R_scripts/dada2_preprocess_paired.R" - dada2_analysis_script = "$projectDir/R_scripts/dada2_analysis_paired.R" - is_paired_end = true - } else { + if (params.single_end) { library_layout = "SINGLE"; dada2_preprocess_script = "$projectDir/R_scripts/dada2_preprocess_single.R" dada2_analysis_script = "$projectDir/R_scripts/dada2_analysis_single.R" is_paired_end = false + } else { + library_layout = "PAIRED"; + dada2_preprocess_script = "$projectDir/R_scripts/dada2_preprocess_paired.R" + dada2_analysis_script = "$projectDir/R_scripts/dada2_analysis_paired.R" + is_paired_end = true } print library_layout - fastq_ch.collect().view() + /*qc_bbduk(fastq_ch, "${projectDir}/assets/adapters.fa") + + fastqc(qc_bbduk.out.reads) + fastqc_ch = fastqc.out.reports + .groupTuple(sort: true) + + assess_read_length_distribution(fastqc.out.reports.collect()) + homogenise_readlengths(qc_bbduk.out.reads, assess_read_length_distribution.out.read_length) + */ files_only_ch = fastq_ch .map { sample, files -> return files } @@ -189,11 +303,42 @@ workflow { files_only_ch.view() - ltrim(files_only_ch) + check_readlengths(files_only_ch) + + raw_reads_figaro(fastq_ch, check_readlengths.out.run_figaro) + + trim_params = file("${workDir}/trim_params.txt") + trim_params.text = "-1 -1\n" - figaro(ltrim.out.ltrimmed_reads, is_paired_end) - extract_trim_parameters(figaro.out.trim_params) + trim_params_ch = Channel.empty() + .concat(raw_reads_figaro.out.trim_params) + .concat(Channel.fromPath("${workDir}/trim_params.txt")) - dada2_preprocess(ltrim.out.ltrimmed_reads, extract_trim_parameters.out.trim_params, dada2_preprocess_script, is_paired_end) + dada2_preprocess(raw_reads_figaro.out.reads, trim_params_ch.first(), dada2_preprocess_script, is_paired_end) + dada2_analysis(dada2_preprocess.out.filtered_reads, dada2_preprocess.out.trim_table, dada2_analysis_script, is_paired_end) + + + + //check_readlengths.out.skip_figaro.view() + +/* trim_params_ch = Channel.empty() + //figaro_dummy(files_only_ch, is_paired_end, check_readlengths.out.run_figaro) + //trim_params_ch = trim_params_ch.concat(figaro_dummy.out.trim_params) + extract_trim_parameters(figaro_dummy.out.trim_params) + trim_params_ch = trim_params_ch.concat(extract_trim_parameters.out.trim_params) + + trim_params = file("${workDir}/trim_params.txt") + trim_params.text = "-1 -1\n" + + trim_params_ch = trim_params_ch.concat(Channel.fromPath("${workDir}/trim_params.txt")) + + //trim_params_ch.first().view() +*/ + +/* + ltrim(files_only_ch) + // dada2_preprocess(ltrim.out.ltrimmed_reads, extract_trim_parameters.out.trim_params, dada2_preprocess_script, is_paired_end) + dada2_preprocess(ltrim.out.ltrimmed_reads, trim_params_ch.first(), dada2_preprocess_script, is_paired_end) dada2_analysis(dada2_preprocess.out.filtered_reads, dada2_preprocess.out.trim_table, dada2_analysis_script, is_paired_end) +*/ } diff --git a/modules/nevermore/functions.nf b/modules/nevermore/functions.nf new file mode 100644 index 0000000..ebd4ddf --- /dev/null +++ b/modules/nevermore/functions.nf @@ -0,0 +1,16 @@ +def classify_sample(sample, files) { + + def meta = [:] + meta.is_paired = (files instanceof Collection && files.size() == 2) + meta.id = sample + meta.pair_id = (files[0].name ==~ /.*R[12]\.(fastq|fq)(\.gz)?/) ? "R" : "" + + return [meta, files] + + if (meta.is_paired) { + return [meta, files] + } + + return [meta, [files]] + +} diff --git a/modules/nevermore/qc/bbduk.nf b/modules/nevermore/qc/bbduk.nf new file mode 100644 index 0000000..11bc587 --- /dev/null +++ b/modules/nevermore/qc/bbduk.nf @@ -0,0 +1,33 @@ + +process qc_bbduk { + publishDir path: params.output_dir, mode: params.publish_mode, pattern: "${sample.id}/${sample.id}.bbduk_stats.txt" + + input: + tuple val(sample), path(reads) + path(adapters) + + output: + tuple val(sample), path("${sample.id}/${sample.id}_R*.fastq.gz"), emit: reads + tuple val(sample), path("${sample.id}/${sample.id}_O.fastq.gz"), emit: orphans, optional: true + path("${sample.id}/${sample.id}.bbduk_stats.txt") + + script: + def maxmem = task.memory.toString().replace(/ GB/, "g") + //def read1 = "in1=${sample.id}_R1.fastq.gz out1=${sample.id}/${sample.id}_R1.fastq.gz" + def read1 = "in1=${sample.id}_${sample.pair_id}1.fastq.gz out1=${sample.id}/${sample.id}_R1.fastq.gz" + //read2 = sample.is_paired ? "in2=${sample.id}_R2.fastq.gz out2=${sample.id}/${sample.id}_R2.fastq.gz outs=${sample.id}/${sample.id}_O.fastq.gz" : "" + read2 = sample.is_paired ? "in2=${sample.id}_${sample.pair_id}2.fastq.gz out2=${sample.id}/${sample.id}_R2.fastq.gz outs=${sample.id}/${sample.id}_O.fastq.gz" : "" + + if (params.primers) { + qc_params = params.qc_params_primers + trim_params = "literal=${params.primers} minlen=${params.qc_minlen}" + } else { + qc_params = params.qc_params_adapters + trim_params = "ref=${adapters} minlen=${params.qc_minlen}" + } + + """ + mkdir -p ${sample.id} + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t ${qc_params} ${trim_params} stats=${sample.id}/${sample.id}.bbduk_stats.txt ${read1} ${read2} + """ +} diff --git a/modules/nevermore/qc/fastqc.nf b/modules/nevermore/qc/fastqc.nf new file mode 100644 index 0000000..2afd3fa --- /dev/null +++ b/modules/nevermore/qc/fastqc.nf @@ -0,0 +1,22 @@ +process fastqc { + publishDir params.output_dir, mode: params.publish_mode, pattern: "raw_counts/*.txt" + + input: + tuple val(sample), path(reads) + + output: + tuple val(sample), path("fastqc/*/*fastqc_data.txt"), emit: reports + tuple val(sample), path("raw_counts/${sample.id}.txt"), emit: counts + + script: + def process_r2 = (sample.is_paired) ? "fastqc -t $task.cpus --extract --outdir=fastqc ${sample.id}_R2.fastq.gz && mv fastqc/${sample.id}_R2_fastqc/fastqc_data.txt fastqc/${sample.id}_R2_fastqc/${sample.id}_R2_fastqc_data.txt" : ""; + + """ + mkdir -p fastqc + mkdir -p raw_counts + fastqc -t $task.cpus --extract --outdir=fastqc ${sample.id}_R1.fastq.gz && mv fastqc/${sample.id}_R1_fastqc/fastqc_data.txt fastqc/${sample.id}_R1_fastqc/${sample.id}_R1_fastqc_data.txt + ${process_r2} + grep "Total Sequences" fastqc/*/*data.txt > seqcount.txt + echo \$(wc -l seqcount.txt)\$'\t'\$(head -n1 seqcount.txt | cut -f 2) > raw_counts/${sample.id}.txt + """ +} diff --git a/scripts/assess_readlengths.py b/scripts/assess_readlengths.py new file mode 100644 index 0000000..0341897 --- /dev/null +++ b/scripts/assess_readlengths.py @@ -0,0 +1,48 @@ +import argparse +import glob +import os + +from collections import Counter + +def parse_fastqc_report(f): + readlengths = list() + active = False + for line in open(f): + active = active or (not active and line.startswith(">>Sequence Length Distribution")) + if active: + if line.startswith(">>END_MODULE"): + break + if line[0] not in (">", "#"): + length, count = line.split("\t") + readlengths.append((int(length.split("-")[0]), float(count))) + return dict(item for item in readlengths if item[1] > 0) + + +def main(): + + ap = argparse.ArgumentParser() + ap.add_argument("input_dir", type=str, default=".") + args = ap.parse_args() + + read_lengths = Counter() + for fastq_report in glob.glob(os.path.join(args.input_dir, "*fastqc_data.txt")): + read_lengths.update(parse_fastqc_report(fastq_report)) + + yields = list() + for length, count in read_lengths.items(): + yields.append((length, sum(v for k, v in read_lengths.items() if k >= length), sum(v * length for k, v in read_lengths.items() if k >= length))) + + for length, reads, bases in sorted(yields, key=lambda x:(x[2], x[1]), reverse=True): + print(length, reads, bases, sep="\t") + + + + # print(*read_lengths.items(), sep="\n") + + + # print(read_lengths) + + + +if __name__ == "__main__": + main() diff --git a/scripts/check_readlengths.py b/scripts/check_readlengths.py index ad172cb..6a69878 100644 --- a/scripts/check_readlengths.py +++ b/scripts/check_readlengths.py @@ -36,6 +36,7 @@ def main(): print("WARNING: read lengths are not homogenous in {fq}. Figaro will not be executed.".format(fq=fn)) open(os.path.join(args.output_dir, "SKIP_FIGARO"), "wt").close() return None + open(os.path.join(args.output_dir, "RUN_FIGARO"), "wt").close() diff --git a/scripts/hltrim.py b/scripts/hltrim.py new file mode 100644 index 0000000..a5fb3bc --- /dev/null +++ b/scripts/hltrim.py @@ -0,0 +1,82 @@ +import os +import sys +import gzip +import argparse +import pathlib +import contextlib + +from collections import Counter + + +def read_fastq_stream(fq_in): + for line in fq_in: + _id, seq, _, qual = line, next(fq_in), next(fq_in), next(fq_in) + yield _id.strip(), seq.strip(), qual.strip() + + +def process_fastq_record(fq_rec, cutlen): + if fq_rec is None: + return None + + _id, seq, qual = fq_rec + _len = len(seq) + + if _len < cutlen: + return None + + return _id, seq[:cutlen], qual[:cutlen] + + +def main(): + ap = argparse.ArgumentParser() + # ap.add_argument("--r1", type=str) + # ap.add_argument("--r2", type=str) + ap.add_argument("reads", nargs="*", type=str) + ap.add_argument("--outdir", "-o", type=str, default="output") + ap.add_argument("--cutlen", "-c", type=int) + args = ap.parse_args() + + pathlib.Path(args.outdir).mkdir(exist_ok=True, parents=True) + + reads = sorted(args.reads) + r1 = reads[0] + r2 = reads[1] if len(reads) > 1 else None + + _open = gzip.open if r1.endswith(".gz") else open + + + r1_in = _open(r1, "rt") + r2_in = _open(r2, "rt") if r2 else contextlib.nullcontext() + + r1_out = _open(os.path.join(args.outdir, r1), "wt") + r2_out = _open(os.path.join(args.outdir, r2), "wt") if r2 else contextlib.nullcontext() + + with r1_in, r2_in, r1_out, r2_out: + r1_stream = read_fastq_stream(r1_in) + r2_stream = read_fastq_stream(r2_in) if r2 else None + + while True: + try: + r1_rec = process_fastq_record(next(r1_stream), args.cutlen) + except StopIteration: + break + if r2_stream: + try: + r2_rec = process_fastq_record(next(r2_stream), args.cutlen) + except StopIteration: + raise ValueError("r2 cannot be exhausted before r1") + else: + r2_rec = None + + if r1_rec is not None and (r2_rec is not None or r2_stream is None): + print(*r1_rec[0:2], "+", r1_rec[-1], sep="\n", file=r1_out) + if r2_rec is not None: + print(*r2_rec[0:2], "+", r2_rec[-1], sep="\n", file=r2_out) + + + + + + +if __name__ == "__main__": + main() From e187ad6ea416db6d4a332dd67d4d529c3d4b3d18 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 10 Nov 2021 13:24:51 +0100 Subject: [PATCH 02/34] version bump -> 0.4 --- nextflow.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextflow.config b/nextflow.config index 8d27654..904d854 100644 --- a/nextflow.config +++ b/nextflow.config @@ -4,5 +4,5 @@ manifest { description = "DADA2/figaro-based 16S amplicon analysis pipeline" name = "gaga2" nextflowVersion = ">=21.0" - version = "0.3" + version = "0.4" } From b229a70b4f08bcb3330d29492eb364bf53189f71 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 10 Nov 2021 13:26:09 +0100 Subject: [PATCH 03/34] added --preprocessed flag + changed flow-logic; minor cleanup --- main.nf | 119 ++++++++------------------------------------------------ 1 file changed, 17 insertions(+), 102 deletions(-) diff --git a/main.nf b/main.nf index 5080ca9..85dce9e 100644 --- a/main.nf +++ b/main.nf @@ -41,14 +41,9 @@ include { classify_sample } from "./modules/nevermore/functions" // helpMessage() // exit 0 //} -// - - process check_readlengths { - //publishDir "${params.output_dir}", mode: params.publish_mode - input: path input_reads @@ -61,47 +56,6 @@ process check_readlengths { """ } -process ltrim { - publishDir "${params.output_dir}", mode: params.publish_mode - - input: - path input_reads - - output: - path("ltrim/*.{fastq,fq,fastq.gz,fq.gz}"), emit: ltrimmed_reads - - script: - """ - mkdir -p ltrim/ - python ${projectDir}/scripts/ltrim.py . ltrim/ - """ - -} - -process figaro_dummy { - publishDir "${params.output_dir}", mode: params.publish_mode - - input: - path input_reads - val is_paired_end - path run_figaro_sentinel - - output: - path("figaro/trimParameters.txt"), emit: trim_params - - - script: - - """ - mkdir figaro - echo "13 13\n" > figaro/trimParameters.txt - """ -} - - - - - process figaro { publishDir "${params.output_dir}", mode: params.publish_mode @@ -109,7 +63,6 @@ process figaro { input: path input_reads val is_paired_end - //path run_figaro_sentinel output: path("figaro/trimParameters.json"), emit: trim_params @@ -121,15 +74,14 @@ process figaro { """ figaro -i . -o figaro/ -a ${params.amplicon_length} -f ${params.left_primer} ${paired_params} """ - // -r ${params.right_primer} -m ${params.min_overlap} } + process extract_trim_parameters { input: path(trim_params) output: - //tuple val(left), val(right), emit: trim_params path("trim_params.txt"), emit: trim_params script: @@ -139,6 +91,7 @@ process extract_trim_parameters { } + process dada2_preprocess { publishDir "${params.output_dir}", mode: params.publish_mode @@ -196,7 +149,6 @@ process dada2_analysis { process assess_read_length_distribution { input: - //tuple val(sample), path(fastqc_reports) path(fastq_reports) output: @@ -209,27 +161,24 @@ process assess_read_length_distribution { } - process homogenise_readlengths { input: tuple val(sample), path(reads) path(read_lengths) output: - //path("${sample.id}_R*.{fastq,fq,fastq.gz,fq.gz}"), emit: homogenised_reads path("${sample.id}/*.{fastq,fq,fastq.gz,fq.gz}"), emit: reads - stdout emit: blah script: """ read_len=\$(head -n 1 ${read_lengths} | cut -f 1) - echo \$read_len python ${projectDir}/scripts/hltrim.py ${reads} -c \$read_len -o ${sample.id} """ } + workflow raw_reads_figaro { - + take: reads run_figaro @@ -241,7 +190,6 @@ workflow raw_reads_figaro { fastqc_ch = fastqc.out.reports .map { sample, report -> return report } .collect() - //.groupTuple(sort: true) assess_read_length_distribution(fastqc_ch) homogenise_readlengths(qc_bbduk.out.reads, assess_read_length_distribution.out.read_length) @@ -250,16 +198,11 @@ workflow raw_reads_figaro { figaro(hom_reads, !params.single_end) extract_trim_parameters(figaro.out.trim_params) - //figaro_dummy(files_only_ch, is_paired_end, check_readlengths.out.run_figaro) - //trim_params_ch = trim_params_ch.concat(figaro_dummy.out.trim_params) - //extract_trim_parameters(figaro_dummy.out.trim_params) - emit: reads = hom_reads trim_params = extract_trim_parameters.out.trim_params - -} +} workflow { @@ -287,58 +230,30 @@ workflow { print library_layout - /*qc_bbduk(fastq_ch, "${projectDir}/assets/adapters.fa") + trim_params_ch = Channel.empty() - fastqc(qc_bbduk.out.reads) - fastqc_ch = fastqc.out.reports - .groupTuple(sort: true) + if (!params.preprocessed) { - assess_read_length_distribution(fastqc.out.reports.collect()) - homogenise_readlengths(qc_bbduk.out.reads, assess_read_length_distribution.out.read_length) - */ + /* check if dataset was preprocessed */ - files_only_ch = fastq_ch - .map { sample, files -> return files } - .collect() + files_only_ch = fastq_ch + .map { sample, files -> return files } + .collect() - files_only_ch.view() + check_readlengths(files_only_ch) - check_readlengths(files_only_ch) + raw_reads_figaro(fastq_ch, check_readlengths.out.run_figaro) + trim_params_ch = trim_params_ch + .concat(raw_reads_figaro.out.trim_params) - raw_reads_figaro(fastq_ch, check_readlengths.out.run_figaro) + } trim_params = file("${workDir}/trim_params.txt") trim_params.text = "-1 -1\n" - trim_params_ch = Channel.empty() - .concat(raw_reads_figaro.out.trim_params) + trim_params_ch = trim_params_ch .concat(Channel.fromPath("${workDir}/trim_params.txt")) dada2_preprocess(raw_reads_figaro.out.reads, trim_params_ch.first(), dada2_preprocess_script, is_paired_end) dada2_analysis(dada2_preprocess.out.filtered_reads, dada2_preprocess.out.trim_table, dada2_analysis_script, is_paired_end) - - - - //check_readlengths.out.skip_figaro.view() - -/* trim_params_ch = Channel.empty() - //figaro_dummy(files_only_ch, is_paired_end, check_readlengths.out.run_figaro) - //trim_params_ch = trim_params_ch.concat(figaro_dummy.out.trim_params) - extract_trim_parameters(figaro_dummy.out.trim_params) - trim_params_ch = trim_params_ch.concat(extract_trim_parameters.out.trim_params) - - trim_params = file("${workDir}/trim_params.txt") - trim_params.text = "-1 -1\n" - - trim_params_ch = trim_params_ch.concat(Channel.fromPath("${workDir}/trim_params.txt")) - - //trim_params_ch.first().view() -*/ - -/* - ltrim(files_only_ch) - // dada2_preprocess(ltrim.out.ltrimmed_reads, extract_trim_parameters.out.trim_params, dada2_preprocess_script, is_paired_end) - dada2_preprocess(ltrim.out.ltrimmed_reads, trim_params_ch.first(), dada2_preprocess_script, is_paired_end) - dada2_analysis(dada2_preprocess.out.filtered_reads, dada2_preprocess.out.trim_table, dada2_analysis_script, is_paired_end) -*/ } From c0727c70d9d3aa2850661a517fdf8180dbbee8d7 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 10 Nov 2021 14:01:31 +0100 Subject: [PATCH 04/34] Update README.md --- README.md | 54 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 498a4f7..3c8d92f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,18 @@ # gaga2 - automated 16S amplicon analysis with Figaro/DADA2 ## Installation instructions -`TBD` +`gaga2` requires a working `nextflow` installation (v20.4+). + +Other dependencies: +* bbmap +* fastqc +* figaro +* R v4+ with dada2, devtools, tidyverse, and cowplot installed + +For convenience, `gaga2` comes with a Singularity container with all dependencies installed. + +```singularity pull oras://ghcr.io/zellerlab/gaga2:latest``` + ## Usage instructions @@ -9,8 +20,8 @@ `gaga2` takes as input Illumina paired-end 16S amplicon sequences (e.g. sequenced on a MiSeq). #### Prerequisites -* Read files need to be named according the typical pattern `_*[12].{fastq,fq,fastq.gz,fq.gz}` -and need to be arranged in a sample-based directory structure: +* Read files need to be named according the typical pattern `_R?[12].{fastq,fq,fastq.gz,fq.gz}`. +They should, but don't have to, be arranged in a sample-based directory structure: ``` (aka "input_dir") @@ -26,29 +37,42 @@ and need to be arranged in a sample-based directory structure: |____ ``` -* If input reads have been "invasively" preprocessed (as evidenced by length differences between reads), -`gaga2` will generate an empty sentinel file `/SKIP_FIGARO`. -This will prevent `Figaro` from executing and should automatically advance to `dada2` processing. +A flat directory structure (with all read files in the same directory) or a deeply-branched (with read files scattered over multiple levels) should also work. + +If `gaga2` preprocesses the reads, it will automatically use `_R1/2` endings internally. + +* If input reads have already been preprocessed, you can set the `--preprocessed` flag. In this case, `gaga2` will do no preprocessing at all and instruct `dada2` to perform no trimming. Otherwie, `gaga2` will assess the read lengths for uniformity. If read lengths differ within and between samples, preprocessing with `figaro` is not possible and `dada2` will be run without trimming. * Samples with less than `110` reads after `dada2` preprocessing, will be discarded. ### Running gaga2 -The typical command for running `gaga2` is -`gaga2.nf --input_dir --output_dir --amplicon_length --left_primer --right_primer ` +`gaga2` can be directly run from github. + +`nextflow run zellerlab/gaga2 ` + +To obtain a newer version, do a + +`nextflow pull zellerlab/gaga2` + +before. + +In addition, you should obtain a copy of the `run.config` from the `gaga2` github repo and modify it according to your environment. #### Mandatory arguments -* `input_dir` is the project directory mentioned above. **Recommended: absolute path** -* `output_dir` will be created automatically. **Recommended: absolute path** -* `amplicon_length`, `left_primer`, and `right_primer` are derived from the experiment parameters +* `--input_dir` is the project directory mentioned above. +* `--output_dir` will be created automatically. +* `--amplicon_length` this is derived from your experiment parameters (this is not read-length, but the length of the, well, amplicon!) +* `--single_end` this is only required for single-end libraries (auto-detection of library-type is in progress) #### Optional arguments * `--min_overlap` of read pairs is `20bp` by default -* `-w, -work-dir` should be set to `/work`, otherwise it will be automatically placed in the current directory. +* `--primers ` or `--left_primer`, and `--right_primer` If primer sequences are provided via `--primers`, `gaga2` will remove primers and upstream sequences (using `bbduk`), such as adapters based on the primer sequences. If non-zero primer lengths are provided instead (via `--left_primer` and `--right_primer`), `figaro` will take those into account when determining the best trim positions. +* `--preprocessed` will prevent any further preprocessing by `gaga2` - this flag should only be used if the read data is reliably clean. ### internal beta-testing instructions -* `source /g/scb2/zeller/schudoma/software/wrappers/gaga2_wrapper` **before** submitting job to cluster -* please report issues/requests/feedback in the github issue tracker -* If you want to run `gaga2` on the cluster, `nextflow` alone requires `>4GB memory` just for 'managing'. +* The old gaga2 version can be run with `source /g/scb2/zeller/schudoma/software/wrappers/gaga2_wrapper` **before** submitting job to cluster +* Please report issues/requests/feedback in the github issue tracker +* If you want to run `gaga2` on the cluster, `nextflow` alone requires `>=5GB` memory just for 'managing'. From 31cd58bb6e673c4666f2e32248d4fa67a245828c Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 10 Nov 2021 20:58:48 +0100 Subject: [PATCH 05/34] fixed some that would generate the wrong table format when having a single sample --- R_scripts/dada2_analysis_single.R | 3 +++ R_scripts/dada2_preprocess_single.R | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/R_scripts/dada2_analysis_single.R b/R_scripts/dada2_analysis_single.R index 6787896..93bb288 100644 --- a/R_scripts/dada2_analysis_single.R +++ b/R_scripts/dada2_analysis_single.R @@ -78,6 +78,9 @@ table(nchar(getSequences(seqtab.nochim))) # track reads getN = function(x) sum(getUniques(x)) +print(r1_dada) +print(filter_table) +print(seqtab.nochim) track = cbind(filter_table, sapply(r1_dada, getN), rowSums(seqtab.nochim)) print(c("TRACK", length(track), length(sample_ids))) colnames(track) = c("input", "filtered", "denoisedF", "nonchim") diff --git a/R_scripts/dada2_preprocess_single.R b/R_scripts/dada2_preprocess_single.R index 83e9945..8ecd75e 100644 --- a/R_scripts/dada2_preprocess_single.R +++ b/R_scripts/dada2_preprocess_single.R @@ -55,7 +55,7 @@ keep = file.exists(r1_filtered) & out[,"reads.out"] >= MIN_READ_THRESHOLD r1_remove = r1_filtered[!keep] #file.exists(r1_filtered) & out["reads.out"] < MIN_READ_THRESHOLD] sapply(r1_remove, file.remove) -out = out[keep,] +out = out[keep,,drop=FALSE] write.table(out, file="filter_trim_table.final.tsv", sep="\t") r1_filtered = r1_filtered[keep] From 2c089d2021cb58645602d017566bb6bbf7d8162c Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 10 Nov 2021 21:29:32 +0100 Subject: [PATCH 06/34] Added support for different R1/R2 lengths * R1/R2 lengths are now assessed and homogenised separately (i.e. they can have different lengths as supported by Figaro) * initial read length distribution assessment is now performed by fastqc * fastq filenames are normalised to R1/R2 naming scheme * removed pair_id from sample meta information --- main.nf | 83 ++++++++++++++++++++++++---------- modules/nevermore/functions.nf | 1 - modules/nevermore/qc/bbduk.nf | 5 +- scripts/assess_readlengths.py | 44 ++++++++++++------ scripts/hltrim.py | 12 +++-- 5 files changed, 101 insertions(+), 44 deletions(-) diff --git a/main.nf b/main.nf index 85dce9e..a3832d6 100644 --- a/main.nf +++ b/main.nf @@ -43,20 +43,6 @@ include { classify_sample } from "./modules/nevermore/functions" //} -process check_readlengths { - input: - path input_reads - - output: - path("RUN_FIGARO"), emit: run_figaro, optional: true - - script: - """ - python ${projectDir}/scripts/check_readlengths.py . . - """ -} - - process figaro { publishDir "${params.output_dir}", mode: params.publish_mode @@ -88,7 +74,6 @@ process extract_trim_parameters { """ python $projectDir/scripts/trim_params.py $trim_params > trim_params.txt """ - } @@ -153,6 +138,7 @@ process assess_read_length_distribution { output: path("read_length_thresholds.txt"), emit: read_length + path("READSET_HOMOGENEOUS"), emit: hom_reads_marker, optional: true script: """ @@ -171,7 +157,7 @@ process homogenise_readlengths { script: """ - read_len=\$(head -n 1 ${read_lengths} | cut -f 1) + read_len=\$(head -n 1 ${read_lengths} | cut -f 1,4 | tr "\t" ",") python ${projectDir}/scripts/hltrim.py ${reads} -c \$read_len -o ${sample.id} """ } @@ -184,7 +170,7 @@ workflow raw_reads_figaro { run_figaro main: - qc_bbduk(reads, "${projectDir}/assets/adapters.fa") + qc_bbduk(reads, "${projectDir}/assets/adapters.fa", run_figaro) fastqc(qc_bbduk.out.reads) fastqc_ch = fastqc.out.reports @@ -205,6 +191,48 @@ workflow raw_reads_figaro { } +workflow check_for_preprocessing { + take: + reads + + main: + reads.view() + fastqc(reads) + assess_read_length_distribution( + fastqc.out.reports + .map { sample, report -> return report } + .collect() + ) + + emit: + readlen_dist = assess_read_length_distribution.out.read_length + hom_reads_marker = assess_read_length_distribution.out.hom_reads_marker +} + + +process prepare_fastqs { + input: + tuple val(sample), path(fq) + + output: + tuple val(sample), path("fastq/${sample.id}/${sample.id}_R*.fastq.gz"), emit: reads + + script: + if (sample.is_paired) { + """ + mkdir -p fastq/${sample.id} + ln -sf ../../${fq[0]} fastq/${sample.id}/${sample.id}_R1.fastq.gz + ln -sf ../../${fq[1]} fastq/${sample.id}/${sample.id}_R2.fastq.gz + """ + } else { + """ + mkdir -p fastq/${sample.id} + ln -sf ../../${fq[0]} fastq/${sample.id}/${sample.id}_R1.fastq.gz + """ + } +} + + workflow { fastq_ch = Channel .fromPath(params.input_dir + "/**_*[12].{fastq,fq,fastq.gz,fq.gz}") @@ -216,6 +244,8 @@ workflow { .groupTuple(sort: true) .map { classify_sample(it[0], it[1]) } + prepare_fastqs(fastq_ch) + if (params.single_end) { library_layout = "SINGLE"; dada2_preprocess_script = "$projectDir/R_scripts/dada2_preprocess_single.R" @@ -231,21 +261,20 @@ workflow { print library_layout trim_params_ch = Channel.empty() + dada_reads_ch = Channel.empty() if (!params.preprocessed) { /* check if dataset was preprocessed */ - files_only_ch = fastq_ch - .map { sample, files -> return files } - .collect() - - check_readlengths(files_only_ch) + check_for_preprocessing(prepare_fastqs.out.reads) - raw_reads_figaro(fastq_ch, check_readlengths.out.run_figaro) + raw_reads_figaro(prepare_fastqs.out.reads, check_for_preprocessing.out.hom_reads_marker) trim_params_ch = trim_params_ch .concat(raw_reads_figaro.out.trim_params) + dada_reads_ch = dada_reads_ch.concat(raw_reads_figaro.out.reads) + } trim_params = file("${workDir}/trim_params.txt") @@ -254,6 +283,12 @@ workflow { trim_params_ch = trim_params_ch .concat(Channel.fromPath("${workDir}/trim_params.txt")) - dada2_preprocess(raw_reads_figaro.out.reads, trim_params_ch.first(), dada2_preprocess_script, is_paired_end) + dada_reads_ch = dada_reads_ch.concat( + prepare_fastqs.out.reads + .map { sample, reads -> reads } + .collect() + ) + + dada2_preprocess(dada_reads_ch.first(), trim_params_ch.first(), dada2_preprocess_script, is_paired_end) dada2_analysis(dada2_preprocess.out.filtered_reads, dada2_preprocess.out.trim_table, dada2_analysis_script, is_paired_end) } diff --git a/modules/nevermore/functions.nf b/modules/nevermore/functions.nf index ebd4ddf..799253e 100644 --- a/modules/nevermore/functions.nf +++ b/modules/nevermore/functions.nf @@ -3,7 +3,6 @@ def classify_sample(sample, files) { def meta = [:] meta.is_paired = (files instanceof Collection && files.size() == 2) meta.id = sample - meta.pair_id = (files[0].name ==~ /.*R[12]\.(fastq|fq)(\.gz)?/) ? "R" : "" return [meta, files] diff --git a/modules/nevermore/qc/bbduk.nf b/modules/nevermore/qc/bbduk.nf index 11bc587..ec3f948 100644 --- a/modules/nevermore/qc/bbduk.nf +++ b/modules/nevermore/qc/bbduk.nf @@ -5,6 +5,7 @@ process qc_bbduk { input: tuple val(sample), path(reads) path(adapters) + path(run_sentinel) output: tuple val(sample), path("${sample.id}/${sample.id}_R*.fastq.gz"), emit: reads @@ -14,9 +15,9 @@ process qc_bbduk { script: def maxmem = task.memory.toString().replace(/ GB/, "g") //def read1 = "in1=${sample.id}_R1.fastq.gz out1=${sample.id}/${sample.id}_R1.fastq.gz" - def read1 = "in1=${sample.id}_${sample.pair_id}1.fastq.gz out1=${sample.id}/${sample.id}_R1.fastq.gz" + def read1 = "in1=${sample.id}_R1.fastq.gz out1=${sample.id}/${sample.id}_R1.fastq.gz" //read2 = sample.is_paired ? "in2=${sample.id}_R2.fastq.gz out2=${sample.id}/${sample.id}_R2.fastq.gz outs=${sample.id}/${sample.id}_O.fastq.gz" : "" - read2 = sample.is_paired ? "in2=${sample.id}_${sample.pair_id}2.fastq.gz out2=${sample.id}/${sample.id}_R2.fastq.gz outs=${sample.id}/${sample.id}_O.fastq.gz" : "" + read2 = sample.is_paired ? "in2=${sample.id}_R2.fastq.gz out2=${sample.id}/${sample.id}_R2.fastq.gz outs=${sample.id}/${sample.id}_O.fastq.gz" : "" if (params.primers) { qc_params = params.qc_params_primers diff --git a/scripts/assess_readlengths.py b/scripts/assess_readlengths.py index 0341897..3135796 100644 --- a/scripts/assess_readlengths.py +++ b/scripts/assess_readlengths.py @@ -24,24 +24,40 @@ def main(): ap.add_argument("input_dir", type=str, default=".") args = ap.parse_args() - read_lengths = Counter() - for fastq_report in glob.glob(os.path.join(args.input_dir, "*fastqc_data.txt")): - read_lengths.update(parse_fastqc_report(fastq_report)) + read_yields = {} + for r in (1, 2): + read_lengths = Counter() + for fastq_report in glob.glob(os.path.join(args.input_dir, f"*{r}_fastqc_data.txt")): + read_lengths.update(parse_fastqc_report(fastq_report)) - yields = list() - for length, count in read_lengths.items(): - yields.append((length, sum(v for k, v in read_lengths.items() if k >= length), sum(v * length for k, v in read_lengths.items() if k >= length))) + yields = list() + for length, count in read_lengths.items(): + yields.append( + (length, sum(v for k, v in read_lengths.items() if k >= length), sum(v * length for k, v in read_lengths.items() if k >= length)) + ) + for length, reads, bases in sorted(yields, key=lambda x:(x[2], x[1]), reverse=True): + read_yields.setdefault(r, list()).append((length, reads, bases)) - for length, reads, bases in sorted(yields, key=lambda x:(x[2], x[1]), reverse=True): - print(length, reads, bases, sep="\t") - - + r1_lengths = read_yields.get(1, list()) + r2_lengths = read_yields.get(2, list()) + + if not r2_lengths: + all_lengths = r1_lengths + is_hom = len(r1_lengths) == 1 + else: + if len(r1_lengths) < len(r2_lengths): + r1_lengths.extend(("NA", "NA", "NA") for i in range(len(r2_lengths) - len(r1_lengths))) + elif len(r2_lengths) < len(r1_lengths): + r2_lengths.extend(("NA", "NA", "NA") for i in range(len(r1_lengths) - len(r2_lengths))) + all_lengths = [x + y for x, y in zip(r1_lengths, r2_lengths)] + is_hom = len(r1_lengths) == len(r2_lengths) == 1 + + for item in all_lengths: + print(*item, sep="\t") - # print(*read_lengths.items(), sep="\n") + if is_hom: + open("READSET_HOMOGENEOUS", "wt").close() - - # print(read_lengths) - if __name__ == "__main__": diff --git a/scripts/hltrim.py b/scripts/hltrim.py index a5fb3bc..f9f572e 100644 --- a/scripts/hltrim.py +++ b/scripts/hltrim.py @@ -33,11 +33,17 @@ def main(): # ap.add_argument("--r2", type=str) ap.add_argument("reads", nargs="*", type=str) ap.add_argument("--outdir", "-o", type=str, default="output") - ap.add_argument("--cutlen", "-c", type=int) + ap.add_argument("--cutlen", "-c", type=str) args = ap.parse_args() pathlib.Path(args.outdir).mkdir(exist_ok=True, parents=True) + cutlen = args.cutlen.split(",") + if len(cutlen) == 1: + r1cut, r2cut = int(cutlen[0]), None + else: + r1cut, r2cut, *_ = map(int, cutlen) + reads = sorted(args.reads) r1 = reads[0] r2 = reads[1] if len(reads) > 1 else None @@ -57,12 +63,12 @@ def main(): while True: try: - r1_rec = process_fastq_record(next(r1_stream), args.cutlen) + r1_rec = process_fastq_record(next(r1_stream), r1cut) except StopIteration: break if r2_stream: try: - r2_rec = process_fastq_record(next(r2_stream), args.cutlen) + r2_rec = process_fastq_record(next(r2_stream), r2cut) except StopIteration: raise ValueError("r2 cannot be exhausted before r1") else: From b80eaaa6ad2475eb50dc9b42cdd9ba4628dfbe3a Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Thu, 11 Nov 2021 13:06:36 +0100 Subject: [PATCH 07/34] cleanup --- config/run.config | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/config/run.config b/config/run.config index b27512b..68e1ede 100644 --- a/config/run.config +++ b/config/run.config @@ -42,13 +42,6 @@ executor { process { cache = 'lenient' - withName: ltrim { - executor = "slurm" - errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} - memory = '8.GB' - time = '4d' - maxRetries = 3 - } withName: figaro { container = "oras://ghcr.io/zellerlab/gaga2:latest" // conda = "/g/scb/zeller/schudoma/software/conda/figaro" @@ -57,17 +50,9 @@ process { memory = '16.GB' time = '4d' maxRetries = 3 - } - withName: dada2_preprocess { - container = "oras://ghcr.io/zellerlab/gaga2:latest" - executor = "slurm" - errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} - memory = '16.GB' - time = '4d' - maxRetries = 3 cpus = 8 } - withName: dada2_analysis { + withLabel: dada2 { container = "oras://ghcr.io/zellerlab/gaga2:latest" executor = "slurm" errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} @@ -76,7 +61,7 @@ process { maxRetries = 3 cpus = 8 } - withName: qc_bbduk { + withLabel: bbduk { container = "oras://ghcr.io/zellerlab/gaga2:latest" executor = "slurm" errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} From 56a6a6d10547fe9fa42aacb6c486fae76a01ca91 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Thu, 11 Nov 2021 13:07:09 +0100 Subject: [PATCH 08/34] added bbduk label --- modules/nevermore/qc/bbduk.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/nevermore/qc/bbduk.nf b/modules/nevermore/qc/bbduk.nf index ec3f948..22a91b6 100644 --- a/modules/nevermore/qc/bbduk.nf +++ b/modules/nevermore/qc/bbduk.nf @@ -1,5 +1,6 @@ process qc_bbduk { + label 'bbduk' publishDir path: params.output_dir, mode: params.publish_mode, pattern: "${sample.id}/${sample.id}.bbduk_stats.txt" input: From 192e030829f52679ea2332d19e140b82507f02e1 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Thu, 11 Nov 2021 13:10:37 +0100 Subject: [PATCH 09/34] readlen homogenisation is now done by bbduk; added bbduk/dada2 labels --- main.nf | 72 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/main.nf b/main.nf index a3832d6..5c1c5a0 100644 --- a/main.nf +++ b/main.nf @@ -78,6 +78,7 @@ process extract_trim_parameters { process dada2_preprocess { + label 'dada2' publishDir "${params.output_dir}", mode: params.publish_mode input: @@ -105,6 +106,7 @@ process dada2_preprocess { process dada2_analysis { + label 'dada2' publishDir "${params.output_dir}", mode: params.publish_mode input: @@ -143,23 +145,37 @@ process assess_read_length_distribution { script: """ python ${projectDir}/scripts/assess_readlengths.py . > read_length_thresholds.txt - """ + """ } process homogenise_readlengths { - input: + label 'bbduk' + + input: tuple val(sample), path(reads) path(read_lengths) - output: + output: path("${sample.id}/*.{fastq,fq,fastq.gz,fq.gz}"), emit: reads - script: - """ - read_len=\$(head -n 1 ${read_lengths} | cut -f 1,4 | tr "\t" ",") - python ${projectDir}/scripts/hltrim.py ${reads} -c \$read_len -o ${sample.id} - """ + script: + def maxmem = task.memory.toString().replace(/ GB/, "g") + + if (sample.is_paired) { + """ + mkdir -p ${sample.id} + r1len=\$(head -n 1 ${read_lengths} | cut -f 1) + r2len=\$(head -n 1 ${read_lengths} | cut -f 4) + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$((r1len-1)) ftr=\$((r1len-1)) stats=${sample.id}/${sample.id}.homr_stats_1.txt in=${sample.id}_R1.fastq.gz out=${sample.id}/${sample.id}_R1.fastq.gz + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$((r2len-1)) ftr=\$((r2len-1)) stats=${sample.id}/${sample.id}.homr_stats_2.txt in=${sample.id}_R2.fastq.gz out=${sample.id}/${sample.id}_R2.fastq.gz + """ + } else { + """ + mkdir -p ${sample.id} + r1len=\$(head -n 1 ${read_lengths} | cut -f 1) + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$((r1len-1)) ftr=\$((r1len-1)) stats=${sample.id}/${sample.id}.homr_stats_1.txt in=${sample.id}_R1.fastq.gz out=${sample.id}/${sample.id}_R1.fastq.gz + """ } @@ -211,25 +227,25 @@ workflow check_for_preprocessing { process prepare_fastqs { - input: - tuple val(sample), path(fq) - - output: - tuple val(sample), path("fastq/${sample.id}/${sample.id}_R*.fastq.gz"), emit: reads - - script: - if (sample.is_paired) { - """ - mkdir -p fastq/${sample.id} - ln -sf ../../${fq[0]} fastq/${sample.id}/${sample.id}_R1.fastq.gz - ln -sf ../../${fq[1]} fastq/${sample.id}/${sample.id}_R2.fastq.gz - """ - } else { - """ - mkdir -p fastq/${sample.id} - ln -sf ../../${fq[0]} fastq/${sample.id}/${sample.id}_R1.fastq.gz - """ - } + input: + tuple val(sample), path(fq) + + output: + tuple val(sample), path("fastq/${sample.id}/${sample.id}_R*.fastq.gz"), emit: reads + + script: + if (sample.is_paired) { + """ + mkdir -p fastq/${sample.id} + ln -sf ../../${fq[0]} fastq/${sample.id}/${sample.id}_R1.fastq.gz + ln -sf ../../${fq[1]} fastq/${sample.id}/${sample.id}_R2.fastq.gz + """ + } else { + """ + mkdir -p fastq/${sample.id} + ln -sf ../../${fq[0]} fastq/${sample.id}/${sample.id}_R1.fastq.gz + """ + } } @@ -278,7 +294,7 @@ workflow { } trim_params = file("${workDir}/trim_params.txt") - trim_params.text = "-1 -1\n" + trim_params.text = "-1 -1\n" trim_params_ch = trim_params_ch .concat(Channel.fromPath("${workDir}/trim_params.txt")) From ab6772961ff8d708c380c22f629554013adac21c Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Thu, 11 Nov 2021 13:15:02 +0100 Subject: [PATCH 10/34] tidy directories --- R_scripts/dada2.R | 95 -------------- R_scripts/dada2_analysis.R | 121 ------------------ R_scripts/dada2_preprocess.R | 82 ------------ gaga2_conda.yml => misc/gaga2_conda.yml | 0 gaga2_install.sh => misc/gaga2_install.sh | 0 .../legacy_scripts}/check_readlengths.py | 0 .../legacy_scripts}/gather_fastq_files.py | 0 {scripts => misc/legacy_scripts}/hltrim.py | 0 {scripts => misc/legacy_scripts}/ltrim.py | 0 9 files changed, 298 deletions(-) delete mode 100644 R_scripts/dada2.R delete mode 100644 R_scripts/dada2_analysis.R delete mode 100644 R_scripts/dada2_preprocess.R rename gaga2_conda.yml => misc/gaga2_conda.yml (100%) rename gaga2_install.sh => misc/gaga2_install.sh (100%) rename {scripts => misc/legacy_scripts}/check_readlengths.py (100%) rename {scripts => misc/legacy_scripts}/gather_fastq_files.py (100%) rename {scripts => misc/legacy_scripts}/hltrim.py (100%) rename {scripts => misc/legacy_scripts}/ltrim.py (100%) diff --git a/R_scripts/dada2.R b/R_scripts/dada2.R deleted file mode 100644 index ab15f74..0000000 --- a/R_scripts/dada2.R +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env Rscript -.libPaths(c('/g/scb2/zeller/SHARED/software/R/3.5', .libPaths())) -library("dada2");packageVersion("dada2") - -args = commandArgs(trailingOnly=TRUE) -input_dir = args[1] -output_dir = args[2] - -length_cut = as.numeric(c(args[3], args[4])) -print(length_cut) -if (length_cut[1] == -1 || length_cut[2] == -1) { - length_cut = 0 -} -exp_err = as.numeric(c(2,2)) # get from cargs? apparently not. - -list.files(input_dir) -sample_ids = basename(list.files(input_dir)) -print(sample_ids) - -r1_raw = sort(list.files(file.path(input_dir, sample_ids), pattern="_R?1.(fastq|fq)(.gz)?", full.names=TRUE)) -r2_raw = sort(list.files(file.path(input_dir, sample_ids), pattern="_R?2.(fastq|fq)(.gz)?", full.names=TRUE)) - -print(r1_raw) -print(r2_raw) - -#TODO: this only generates one plot -pdf("read_quality.pdf", useDingbats=FALSE, width=12, height=6) -for (i in 1:length(r1_raw)) { - print(plotQualityProfile(c(r1_raw[i], r2_raw[i]))) -} -dev.off() - -r1_filtered = file.path(output_dir, "filtered", basename(r1_raw)) -r2_filtered = file.path(output_dir, "filtered", basename(r2_raw)) - -print(r1_filtered) -print(r2_filtered) - - -out = filterAndTrim(r1_raw, r1_filtered, r2_raw, r2_filtered, truncLen=length_cut, - maxN=0, maxEE=exp_err, truncQ=2, rm.phix=TRUE, - compress=TRUE, multithread=TRUE) -write.table(out, file="filter_trim_table.tsv", sep="\t") - -# update read files -keep = file.exists(r1_filtered) & file.exists(r2_filtered) & out[,"reads.out"] > 100 -r1_filtered = r1_filtered[keep] -r2_filtered = r2_filtered[keep] -sample_ids = sample_ids[keep] - -r1_error = learnErrors(r1_filtered, multithread=TRUE) -r2_error = learnErrors(r2_filtered, multithread=TRUE) - -pdf("error_model.pdf", width=12, height=12, paper='special') -print(plotErrors(r1_error, nominalQ=TRUE)) -print(plotErrors(r2_error, nominalQ=TRUE)) -dev.off() - - -r1_uniq = derepFastq(r1_filtered, verbose=TRUE) -r2_uniq = derepFastq(r2_filtered, verbose=TRUE) -names(r1_uniq) = sample_ids -names(r2_uniq) = sample_ids - -r1_dada = dada(r1_uniq, err=r1_error, multithread=TRUE) -r2_dada = dada(r2_uniq, err=r2_error, multithread=TRUE) - -#Inspecting the returned dada-class object: -r1_dada[[1]] - - -merged = mergePairs(r1_dada, r1_uniq, r2_dada, r2_uniq, verbose=TRUE) - -# Inspect the merger data.frame from the first sample -head(merged[[1]]) - -seqtab = makeSequenceTable(merged) -n_OTU = dim(seqtab)[2] -# Inspect distribution of sequence lengths -table(nchar(getSequences(seqtab))) - -seqtab.nochim = removeBimeraDenovo(seqtab, method="consensus", multithread=TRUE, verbose=TRUE) -n_OTU_after_removing_chimeras = dim(seqtab.nochim)[2] -# relative abundance of the chimeric sequences -rel_ab_chimeras = 1 - (sum(seqtab.nochim)/sum(seqtab)) -table(nchar(getSequences(seqtab.nochim))) - -getN = function(x) sum(getUniques(x)) -track = cbind(out[keep,], sapply(r1_dada, getN), sapply(r2_dada, getN), sapply(merged, getN), rowSums(seqtab.nochim)) -colnames(track) = c("input", "filtered", "denoisedF", "denoisedR", "merged", "nonchim") -rownames(track) = sample_ids -head(track) -write.table(track, file = "summary_table.tsv", sep="\t") - -save(seqtab, seqtab.nochim, r1_error, r2_error, track, file = "result.RData") diff --git a/R_scripts/dada2_analysis.R b/R_scripts/dada2_analysis.R deleted file mode 100644 index a351b7f..0000000 --- a/R_scripts/dada2_analysis.R +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env Rscript - -library("dada2");packageVersion("dada2") -library("tidyverse") -library("cowplot") - -print("YYY") - -# handle command line args -args = commandArgs(trailingOnly=TRUE) -input_dir = args[1] -output_dir = args[2] - -filter_table = read.table(args[3]) - -if (length(args) >= 4) { - nthreads = as.numeric(c(args[4])) -} else { - nthreads = TRUE -} - -# get the read files and sample names -list.files(input_dir) -sample_ids = basename(list.files(input_dir)) -print(sample_ids) - -r1_filtered = sort(list.files(file.path(input_dir, sample_ids), pattern = "_R?1.(fastq|fq)(.gz)?", full.names = TRUE)) -r2_filtered = sort(list.files(file.path(input_dir, sample_ids), pattern = "_R?2.(fastq|fq)(.gz)?", full.names = TRUE)) -r1_filtered = sort(list.files(file.path(input_dir), pattern = "_R?1.(fastq|fq)(.gz)?", full.names = TRUE)) -r2_filtered = sort(list.files(file.path(input_dir), pattern = "_R?2.(fastq|fq)(.gz)?", full.names = TRUE)) - -print(r1_filtered) -print(r2_filtered) - -# learn error rate -r1_error = learnErrors(r1_filtered, multithread=nthreads) -r2_error = learnErrors(r2_filtered, multithread=nthreads) - -pdf("error_model.pdf", width=12, height=12, paper='special') -print(plotErrors(r1_error, nominalQ=TRUE)) -print(plotErrors(r2_error, nominalQ=TRUE)) -dev.off() - -# remove replicates -r1_uniq = derepFastq(r1_filtered, verbose=TRUE) -r2_uniq = derepFastq(r2_filtered, verbose=TRUE) -print(r1_uniq[[1]]) -print(c("LENGTHCHECK:", length(r1_uniq), length(r2_uniq), length(sample_ids))) -#names(r1_uniq) = sample_ids -#names(r2_uniq) = sample_ids -r1_dada = dada(r1_uniq, err=r1_error, multithread=nthreads) -r2_dada = dada(r2_uniq, err=r2_error, multithread=nthreads) - -#Inspecting the returned dada-class object: -r1_dada[[1]] - -# merge read pairs -print("merging") -merged = mergePairs(r1_dada, r1_uniq, r2_dada, r2_uniq, verbose=TRUE) -# Inspect the merger data.frame from the first sample -head(merged[[1]]) - -# construct sequence table -print("making sequence table") -seqtab = makeSequenceTable(merged) -n_OTU = dim(seqtab)[2] -# Inspect distribution of sequence lengths -print(dim(seqtab)) -print("ASV length") -print(table(nchar(getSequences(seqtab)))) - -# remove chimeras -seqtab.nochim = removeBimeraDenovo(seqtab, method="consensus", multithread=nthreads, verbose=TRUE) -n_OTU_after_removing_chimeras = dim(seqtab.nochim)[2] -print(dim(seqtab.nochim)) -asv.table = t(seqtab.nochim) - -# export asv table -asvs = tibble(id=paste0('ASV_', seq_len(nrow(asv.table))), - ASV=rownames(asv.table)) -write_tsv(asvs, 'ASVs.tsv') - -saved_rownames = rownames(asv.table) -rownames(asv.table) = asvs$id -write.table(asv.table, file = 'asv_table.tsv', - sep='\t', quote = FALSE, row.names = TRUE, col.names = TRUE) -rownames(asv.table) = saved_rownames - -# relative abundance of the chimeric sequences -rel_ab_chimeras = 1 - (sum(seqtab.nochim)/sum(seqtab)) -table(nchar(getSequences(seqtab.nochim))) - -# track reads -getN = function(x) sum(getUniques(x)) -track = cbind(filter_table, sapply(r1_dada, getN), sapply(r2_dada, getN), sapply(merged, getN), rowSums(seqtab.nochim)) -print(c("TRACK", length(track), length(sample_ids))) -colnames(track) = c("input", "filtered", "denoisedF", "denoisedR", "merged", "nonchim") -# rownames(track) = sample_ids -print(head(track)) -print("Filtering") -print(summary(track[,6] / track[,1])) -write.table(track, file = "summary_table.tsv", sep="\t") - -save(seqtab, seqtab.nochim, r1_error, r2_error, track, file = "result.RData") - -# prevalance sanity check -pdf('dada2_figures.pdf') -temp = prop.table(asv.table, 2) -print(temp) -hist(log10(temp), 100, main='abundance') -prev = rowMeans(asv.table!=0) -hist(prev, 50, main='prevalence') -mean.ab = rowMeans(log10(temp + 1e-05)) -hist(mean.ab, 50, main='log.abundance') -print("Prevalence") -print(summary(prev)) -print("Mean abundance") -print(summary(mean.ab)) -plot(prev, mean.ab, main='prevalence vs abundance') -plot(nchar(rownames(asv.table)), prev, main='ASV length vs prevalence') -dev.off() diff --git a/R_scripts/dada2_preprocess.R b/R_scripts/dada2_preprocess.R deleted file mode 100644 index 5c9b975..0000000 --- a/R_scripts/dada2_preprocess.R +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env Rscript - -library("dada2");packageVersion("dada2") -library("tidyverse") -library("cowplot") - -MIN_READ_THRESHOLD = 110 - -# handle command line args -args = commandArgs(trailingOnly=TRUE) -input_dir = args[1] -output_dir = args[2] - -length_cut = as.numeric(c(args[3], args[4])) -print(length_cut) -if (length_cut[1] == -1 || length_cut[2] == -1) { - length_cut = 0 -} -exp_err = as.numeric(c(2,2)) # get from cargs? apparently not. - -if (length(args) >= 5) { - nthreads = as.numeric(c(args[5])) -} else { - nthreads = TRUE -} - -# get the read files and sample names -list.files(input_dir) -#sample_ids = basename(list.files(input_dir)) -#print(sample_ids) - -r1_raw = sort(list.files(input_dir, pattern = "_R?1.(fastq|fq)(.gz)?", full.names = TRUE)) -r2_raw = sort(list.files(input_dir, pattern = "_R?2.(fastq|fq)(.gz)?", full.names = TRUE)) -print(r1_raw) -print(r2_raw) - -# get the quality profiles -x.f = plotQualityProfile(r1_raw, aggregate = TRUE) -x.r = plotQualityProfile(r2_raw, aggregate = TRUE) -g = plot_grid(x.f, x.r, labels = c('forward', 'reverse')) -ggsave(g, filename = "read_quality.pdf", - width = 12, height = 7, useDingbats=FALSE) - -# perform filtering and trimming -r1_filtered = file.path(output_dir, basename(r1_raw)) -r2_filtered = file.path(output_dir, basename(r2_raw)) - -print(r1_filtered) -print(r2_filtered) - -out = filterAndTrim(r1_raw, r1_filtered, r2_raw, r2_filtered, truncLen=length_cut, - maxN=0, maxEE=exp_err, truncQ=2, rm.phix=TRUE, - compress=TRUE, multithread=nthreads) -# colnames(out) = c("sample", "reads.in", "reads.out") -rownames(out) = str_replace(rownames(out), "_R?[12].(fastq|fq)(.gz)?", "") -write.table(out, file="filter_trim_table.tsv", sep="\t") - -# remove -keep = file.exists(r1_filtered) & file.exists(r2_filtered) & out[,"reads.out"] >= MIN_READ_THRESHOLD -r1_remove = r1_filtered[!keep] #file.exists(r1_filtered) & out["reads.out"] < MIN_READ_THRESHOLD] -r2_remove = r2_filtered[!keep] #file.exists(r2_filtered) & out["reads.out"] < MIN_READ_THRESHOLD] -sapply(r1_remove, file.remove) -sapply(r2_remove, file.remove) - -out = out[keep,] -write.table(out, file="filter_trim_table.final.tsv", sep="\t") - -r1_filtered = r1_filtered[keep] -r2_filtered = r2_filtered[keep] - -# get the quality profiles post-qc -x.f = plotQualityProfile(r1_filtered, aggregate = TRUE) -x.r = plotQualityProfile(r2_filtered, aggregate = TRUE) -g = plot_grid(x.f, x.r, labels = c('forward', 'reverse')) -ggsave(g, filename = "read_quality_postqc.pdf", - width = 12, height = 7, useDingbats=FALSE) - -# update read files -#keep = file.exists(r1_filtered) & file.exists(r2_filtered) & out[,"reads.out"] > 100 -#r1_filtered = r1_filtered[keep] -#r2_filtered = r2_filtered[keep] -#sample_ids = sample_ids[keep] diff --git a/gaga2_conda.yml b/misc/gaga2_conda.yml similarity index 100% rename from gaga2_conda.yml rename to misc/gaga2_conda.yml diff --git a/gaga2_install.sh b/misc/gaga2_install.sh similarity index 100% rename from gaga2_install.sh rename to misc/gaga2_install.sh diff --git a/scripts/check_readlengths.py b/misc/legacy_scripts/check_readlengths.py similarity index 100% rename from scripts/check_readlengths.py rename to misc/legacy_scripts/check_readlengths.py diff --git a/scripts/gather_fastq_files.py b/misc/legacy_scripts/gather_fastq_files.py similarity index 100% rename from scripts/gather_fastq_files.py rename to misc/legacy_scripts/gather_fastq_files.py diff --git a/scripts/hltrim.py b/misc/legacy_scripts/hltrim.py similarity index 100% rename from scripts/hltrim.py rename to misc/legacy_scripts/hltrim.py diff --git a/scripts/ltrim.py b/misc/legacy_scripts/ltrim.py similarity index 100% rename from scripts/ltrim.py rename to misc/legacy_scripts/ltrim.py From 8b049da035313bc1e5876be6d1727608b06cb814 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Thu, 11 Nov 2021 17:50:27 +0100 Subject: [PATCH 11/34] added missing curly bracket --- main.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/main.nf b/main.nf index 5c1c5a0..f303e42 100644 --- a/main.nf +++ b/main.nf @@ -176,6 +176,7 @@ process homogenise_readlengths { r1len=\$(head -n 1 ${read_lengths} | cut -f 1) bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$((r1len-1)) ftr=\$((r1len-1)) stats=${sample.id}/${sample.id}.homr_stats_1.txt in=${sample.id}_R1.fastq.gz out=${sample.id}/${sample.id}_R1.fastq.gz """ + } } From a21e31f1923208ca99a9f88e60a25f66eb5b9a36 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Thu, 11 Nov 2021 18:35:08 +0100 Subject: [PATCH 12/34] added mapseq-profiling of asv sequences --- config/run.config | 45 +++++++++++++++++++++------- main.nf | 25 +++++++++++++++- modules/profilers/mapseq.nf | 58 +++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 modules/profilers/mapseq.nf diff --git a/config/run.config b/config/run.config index 68e1ede..7bc76dd 100644 --- a/config/run.config +++ b/config/run.config @@ -5,8 +5,8 @@ params { left_primer = 0 right_primer = 0 - /* - bbduk qc parameters + /* + bbduk qc parameters s. https://jgi.doe.gov/data-and-tools/bbtools/bb-tools-user-guide/bbduk-guide/ qtrim=rl trimq=3 : gentle quality trimming (only discard bases < phred 3; phred 2 = junk marker) on either side (rl) of the read maq=25 : discard reads below average quality of pred 25 @@ -19,14 +19,10 @@ params { qc_params_primers = "qtrim=rl trimq=3 ktrim=l k=14 mink=1 hdist=1 cu=t" qc_params_adapters = "qtrim=rl trimq=3 ktrim=l k=23 mink=1 hdist=1 tpe tbo cu=t" qc_minlen = 100 - - - - - - - + mapseq_bin = "mapseq" + mapseq_db_path = projectDir + mapseq_db_name = "" } /* section below needs to be adjusted to local cluster */ @@ -47,7 +43,7 @@ process { // conda = "/g/scb/zeller/schudoma/software/conda/figaro" executor = "slurm" errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} - memory = '16.GB' + memory = '16.GB' time = '4d' maxRetries = 3 cpus = 8 @@ -56,7 +52,7 @@ process { container = "oras://ghcr.io/zellerlab/gaga2:latest" executor = "slurm" errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} - memory = '16.GB' + memory = '16.GB' time = '4d' maxRetries = 3 cpus = 8 @@ -79,6 +75,33 @@ process { time = '7d' maxRetries = 3 } + withName: mapseq_otutable { + container = "oras://ghcr.io/zellerlab/gaga2:latest" + executor = "slurm" + errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} + cpus = 1 + memory = {2.GB * task.attempt} + time = '24h' + maxRetries = 3 + } + withName: collate_mapseq_tables { + container = "oras://ghcr.io/zellerlab/gaga2:latest" + executor = "slurm" + errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} + cpus = 1 + memory = {2.GB * task.attempt} + time = '24h' + maxRetries = 3 + } + withName: mapseq { + container = "oras://ghcr.io/zellerlab/gaga2:latest" + executor = "slurm" + errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} + cpus = 8 + memory = {8.GB * task.attempt} + time = '7d' + maxRetries = 3 + } } singularity { diff --git a/main.nf b/main.nf index f303e42..4798c7e 100644 --- a/main.nf +++ b/main.nf @@ -5,6 +5,7 @@ nextflow.enable.dsl = 2 include { qc_bbduk } from "./modules/nevermore/qc/bbduk" include { fastqc } from "./modules/nevermore/qc/fastqc" include { classify_sample } from "./modules/nevermore/functions" +include { mapseq; mapseq_otutable } from "./modules/profilers/mapseq" //def helpMessage() { @@ -121,7 +122,7 @@ process dada2_analysis { path("summary_table.tsv") path("result.RData") path("dada2_figures.pdf") - path("ASVs.tsv") + path("ASVs.tsv"), emit: asv_sequences path("asv_table.tsv") script: @@ -133,6 +134,24 @@ process dada2_analysis { """ } +process asv2fasta { + publishDir "${params.output_dir}", mode: params.publish_mode + + input: + path(asv_seqs) + + output: + tuple val(meta), path("ASVs.fasta"), emit: asv_fasta + + script: + meta = [:] + meta.id = "all" + meta.is_paired = false + """ + tail -n +2 ${asv_seqs} | sed 's/^/>/' | tr '\t' '\n' > ASVs.fasta + """ + +} process assess_read_length_distribution { input: @@ -308,4 +327,8 @@ workflow { dada2_preprocess(dada_reads_ch.first(), trim_params_ch.first(), dada2_preprocess_script, is_paired_end) dada2_analysis(dada2_preprocess.out.filtered_reads, dada2_preprocess.out.trim_table, dada2_analysis_script, is_paired_end) + asv2fasta(dada2_analysis.out.asv_sequences) + + mapseq(asv2fasta.out.asv_fasta, params.mapseq_db_path, params.mapseq_db_name) + mapseq_otutable(mapseq.out.bac_ssu) } diff --git a/modules/profilers/mapseq.nf b/modules/profilers/mapseq.nf new file mode 100644 index 0000000..78338ca --- /dev/null +++ b/modules/profilers/mapseq.nf @@ -0,0 +1,58 @@ +process mapseq { + input: + tuple val(sample), path(seqs) + path(db_path) + val(db_name) + + output: + path("${sample.id}/${sample.id}_bac_ssu.mseq"), emit: bac_ssu + + script: + def db = (db_name == "default" || db_name == "") ? "" : "${db_path}/${db_name}.fasta ${db_path}/*.tax*" + + """ + mkdir -p ${sample.id} + ${params.mapseq_bin} ${seqs} > ${sample.id}/${sample.id}_bac_ssu.mseq + """ +} + + +process mapseq_otutable { + publishDir params.output_dir, mode: params.publish_mode + + input: + path(mapped_seqs) + + output: + path("mapseq.otucounts.txt") + + script: + """ + ${params.mapseq_bin} -otucounts ${mapped_seqs} > mapseq.otucounts.txt + """ +} + + +process collate_mapseq_tables { + publishDir params.output_dir, mode: params.publish_mode + + input: + path(mapped_seqs) + + output: + path("mapseq_tables/mapseq_counts_genus_*_bac_ssu.tsv"), emit: ssu_tables + + script: + if (mapped_seqs.size() == 2) { + """ + mkdir -p mapseq_tables + ${params.mapseq_bin} -otutable -tl 5 \$(ls *_R1_bac_ssu.mseq) | sed 's/_R1_bac_ssu.mseq//g' > mapseq_tables/mapseq_counts_genus_fwd_bac_ssu.tsv + ${params.mapseq_bin} -otutable -tl 5 \$(ls *_R2_bac_ssu.mseq) | sed 's/_R2_bac_ssu.mseq//g' > mapseq_tables/mapseq_counts_genus_rev_bac_ssu.tsv + """ + } else { + """ + mkdir -p mapseq_tables + ${params.mapseq_bin} -otutable -tl 5 \$(ls *_R1_bac_ssu.mseq) | sed 's/_R1_bac_ssu.mseq//g' > mapseq_tables/mapseq_counts_genus_fwd_bac_ssu.tsv + """ + } +} From 5edcff8de65819d28afb3ba3cb0beee2870f192c Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Thu, 11 Nov 2021 18:40:34 +0100 Subject: [PATCH 13/34] added docs folder --- docs/img/x | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/img/x diff --git a/docs/img/x b/docs/img/x new file mode 100644 index 0000000..e69de29 From 46b3ec9b7fda1ef014c2620df992b65d7a1a14a4 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Thu, 11 Nov 2021 18:41:29 +0100 Subject: [PATCH 14/34] Add files via upload added gaga2 workflow diagram --- docs/img/gaga2_flow.png | Bin 0 -> 55648 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/img/gaga2_flow.png diff --git a/docs/img/gaga2_flow.png b/docs/img/gaga2_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..556f85623d00cb070e88ca2dc948601c63931bc3 GIT binary patch literal 55648 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 W@d(?! Date: Thu, 11 Nov 2021 18:44:46 +0100 Subject: [PATCH 15/34] Update README.md added workflow diagram --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3c8d92f..938d5e9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # gaga2 - automated 16S amplicon analysis with Figaro/DADA2 +![](docs/img/gaga2_flow.png) + ## Installation instructions `gaga2` requires a working `nextflow` installation (v20.4+). From 0c78457c64872fdb0b4c5890e2ccc5e4dab90c84 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 17 Nov 2021 13:19:41 +0100 Subject: [PATCH 16/34] added parameters for dada2 chimera removal --- R_scripts/dada2_analysis_paired.R | 6 +++++- R_scripts/dada2_analysis_single.R | 6 +++++- config/run.config | 3 +++ main.nf | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/R_scripts/dada2_analysis_paired.R b/R_scripts/dada2_analysis_paired.R index a351b7f..6476bcb 100644 --- a/R_scripts/dada2_analysis_paired.R +++ b/R_scripts/dada2_analysis_paired.R @@ -19,6 +19,9 @@ if (length(args) >= 4) { nthreads = TRUE } +dada2_chimera_method = args[5] # +dada2_chimera_min_fold_parent_over_abundance = as.numeric(c(args[6])) + # get the read files and sample names list.files(input_dir) sample_ids = basename(list.files(input_dir)) @@ -70,7 +73,8 @@ print("ASV length") print(table(nchar(getSequences(seqtab)))) # remove chimeras -seqtab.nochim = removeBimeraDenovo(seqtab, method="consensus", multithread=nthreads, verbose=TRUE) +#seqtab.nochim = removeBimeraDenovo(seqtab, method="consensus", multithread=nthreads, verbose=TRUE) +seqtab.nochim = removeBimeraDenovo(seqtab, method=dada2_chimera_method, minFoldParentOverAbundance=dada2_chimera_min_fold_parent_over_abundance, multithread=nthreads, verbose=TRUE) n_OTU_after_removing_chimeras = dim(seqtab.nochim)[2] print(dim(seqtab.nochim)) asv.table = t(seqtab.nochim) diff --git a/R_scripts/dada2_analysis_single.R b/R_scripts/dada2_analysis_single.R index 93bb288..b57f55c 100644 --- a/R_scripts/dada2_analysis_single.R +++ b/R_scripts/dada2_analysis_single.R @@ -19,6 +19,9 @@ if (length(args) >= 4) { nthreads = TRUE } +dada2_chimera_method = args[5] # +dada2_chimera_min_fold_parent_over_abundance = as.numeric(c(args[6])) + # get the read files and sample names list.files(input_dir) sample_ids = basename(list.files(input_dir)) @@ -56,7 +59,8 @@ print("ASV length") print(table(nchar(getSequences(seqtab)))) # remove chimeras -seqtab.nochim = removeBimeraDenovo(seqtab, method="consensus", multithread=nthreads, verbose=TRUE) +#seqtab.nochim = removeBimeraDenovo(seqtab, method="consensus", multithread=nthreads, verbose=TRUE) +seqtab.nochim = removeBimeraDenovo(seqtab, method=dada2_chimera_method, minFoldParentOverAbundance=dada2_chimera_min_fold_parent_over_abundance, multithread=nthreads, verbose=TRUE) n_OTU_after_removing_chimeras = dim(seqtab.nochim)[2] print(dim(seqtab.nochim)) asv.table = t(seqtab.nochim) diff --git a/config/run.config b/config/run.config index 7bc76dd..05baa3a 100644 --- a/config/run.config +++ b/config/run.config @@ -23,6 +23,9 @@ params { mapseq_bin = "mapseq" mapseq_db_path = projectDir mapseq_db_name = "" + + dada2_chimera_method = "consensus" // can be "consensus" (default dada2 since 1.4) or "pool" + dada2_chimera_min_fold_parent_over_abundance = 2 } /* section below needs to be adjusted to local cluster */ diff --git a/main.nf b/main.nf index 4798c7e..84c1858 100644 --- a/main.nf +++ b/main.nf @@ -130,7 +130,7 @@ process dada2_analysis { mkdir -p dada2_in/ for f in \$(find . -maxdepth 1 -type l); do ln -s ../\$f dada2_in/; done rm dada2_in/*.R dada2_in/filter_trim_table.final.tsv - Rscript --vanilla ${dada2_script} dada2_in/ dada2_analysis/ filter_trim_table.final.tsv $task.cpus > dada2_analysis.log + Rscript --vanilla ${dada2_script} dada2_in/ dada2_analysis/ filter_trim_table.final.tsv $task.cpus ${params.dada2_chimera_method} ${params.dada2_chimera_min_fold_parent_over_abundance} > dada2_analysis.log """ } From 75429dd5d3b6ed0045323cb598d29f268887a241 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Thu, 18 Nov 2021 17:58:30 +0100 Subject: [PATCH 17/34] removed vortex-code (prevalence check) as it fails for certain samples --- R_scripts/dada2_analysis_paired.R | 32 +++++++++++++++---------------- R_scripts/dada2_analysis_single.R | 32 +++++++++++++++---------------- main.nf | 2 +- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/R_scripts/dada2_analysis_paired.R b/R_scripts/dada2_analysis_paired.R index 6476bcb..f01ed3d 100644 --- a/R_scripts/dada2_analysis_paired.R +++ b/R_scripts/dada2_analysis_paired.R @@ -107,19 +107,19 @@ write.table(track, file = "summary_table.tsv", sep="\t") save(seqtab, seqtab.nochim, r1_error, r2_error, track, file = "result.RData") -# prevalance sanity check -pdf('dada2_figures.pdf') -temp = prop.table(asv.table, 2) -print(temp) -hist(log10(temp), 100, main='abundance') -prev = rowMeans(asv.table!=0) -hist(prev, 50, main='prevalence') -mean.ab = rowMeans(log10(temp + 1e-05)) -hist(mean.ab, 50, main='log.abundance') -print("Prevalence") -print(summary(prev)) -print("Mean abundance") -print(summary(mean.ab)) -plot(prev, mean.ab, main='prevalence vs abundance') -plot(nchar(rownames(asv.table)), prev, main='ASV length vs prevalence') -dev.off() +## prevalance sanity check +#pdf('dada2_figures.pdf') +#temp = prop.table(asv.table, 2) +#print(temp) +#hist(log10(temp), 100, main='abundance') +#prev = rowMeans(asv.table!=0) +#hist(prev, 50, main='prevalence') +#mean.ab = rowMeans(log10(temp + 1e-05)) +#hist(mean.ab, 50, main='log.abundance') +#print("Prevalence") +#print(summary(prev)) +#print("Mean abundance") +#print(summary(mean.ab)) +#plot(prev, mean.ab, main='prevalence vs abundance') +#plot(nchar(rownames(asv.table)), prev, main='ASV length vs prevalence') +#dev.off() diff --git a/R_scripts/dada2_analysis_single.R b/R_scripts/dada2_analysis_single.R index b57f55c..c9845a7 100644 --- a/R_scripts/dada2_analysis_single.R +++ b/R_scripts/dada2_analysis_single.R @@ -96,19 +96,19 @@ write.table(track, file = "summary_table.tsv", sep="\t") save(seqtab, seqtab.nochim, r1_error, track, file = "result.RData") -# prevalance sanity check -pdf('dada2_figures.pdf') -temp = prop.table(asv.table, 2) -print(temp) -hist(log10(temp), 100, main='abundance') -prev = rowMeans(asv.table!=0) -hist(prev, 50, main='prevalence') -mean.ab = rowMeans(log10(temp + 1e-05)) -hist(mean.ab, 50, main='log.abundance') -print("Prevalence") -print(summary(prev)) -print("Mean abundance") -print(summary(mean.ab)) -plot(prev, mean.ab, main='prevalence vs abundance') -plot(nchar(rownames(asv.table)), prev, main='ASV length vs prevalence') -dev.off() +## prevalance sanity check +#pdf('dada2_figures.pdf') +#temp = prop.table(asv.table, 2) +#print(temp) +#hist(log10(temp), 100, main='abundance') +#prev = rowMeans(asv.table!=0) +#hist(prev, 50, main='prevalence') +#mean.ab = rowMeans(log10(temp + 1e-05)) +#hist(mean.ab, 50, main='log.abundance') +#print("Prevalence") +#print(summary(prev)) +#print("Mean abundance") +#print(summary(mean.ab)) +#plot(prev, mean.ab, main='prevalence vs abundance') +#plot(nchar(rownames(asv.table)), prev, main='ASV length vs prevalence') +#dev.off() diff --git a/main.nf b/main.nf index 84c1858..08024a4 100644 --- a/main.nf +++ b/main.nf @@ -121,7 +121,7 @@ process dada2_analysis { path("error_model.pdf") path("summary_table.tsv") path("result.RData") - path("dada2_figures.pdf") + path("dada2_figures.pdf"), optional: true path("ASVs.tsv"), emit: asv_sequences path("asv_table.tsv") From 4c996cbf47a41e0f8648e50c98a1d8614628faab Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Fri, 19 Nov 2021 20:03:55 +0100 Subject: [PATCH 18/34] Update flow diagram --- docs/img/gaga2_flow.png | Bin 55648 -> 49291 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/img/gaga2_flow.png b/docs/img/gaga2_flow.png index 556f85623d00cb070e88ca2dc948601c63931bc3..51cc16864bbb1c9fd1e210e90c3824dac690da4d 100644 GIT binary patch literal 49291 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 literal 55648 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 W@d(?! Date: Fri, 19 Nov 2021 20:05:15 +0100 Subject: [PATCH 19/34] removed accidentally created file --- docs/img/x | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/img/x diff --git a/docs/img/x b/docs/img/x deleted file mode 100644 index e69de29..0000000 From ebf395d8273f99c5d0947046f8c55ebf98c15db2 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Fri, 19 Nov 2021 20:43:54 +0100 Subject: [PATCH 20/34] added illumina 16S technical sequences --- assets/adapters.fa | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/assets/adapters.fa b/assets/adapters.fa index a87d425..9de5e45 100755 --- a/assets/adapters.fa +++ b/assets/adapters.fa @@ -1,3 +1,11 @@ +>AmpliconPCR16S_primer_fwd +TCGTCGGCAGCGTCAGATGTGTATAAGAGACAGCCTACGGGNGGCWGCAG +>AmpliconPCR16S_primer_rev +GTCTCGTGGGCTCGGAGATGTGTATAAGAGACAGGACTACHVGGGTATCTAATCC +>Overhang_fwd +TCGTCGGCAGCGTCAGATGTGTATAAGAGACAG +>Overhand_rev +GTCTCGTGGGCTCGGAGATGTGTATAAGAGACAG >Reverse_adapter AGATCGGAAGAGCACACGTCTGAACTCCAGTCACATCACGATCTCGTATGCCGTCTTCTGCTTG >TruSeq_Universal_Adapter From d413236e2f31b76877027a5c61c740b1dc5ab453 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Fri, 19 Nov 2021 23:15:52 +0100 Subject: [PATCH 21/34] changed read length assessment * paired-end reads are now filtered by whether they're spanning the amplicon length (figaro-requirement) * single-end reads are not filtered --- scripts/assess_readlengths.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/scripts/assess_readlengths.py b/scripts/assess_readlengths.py index 3135796..a8eb5c0 100644 --- a/scripts/assess_readlengths.py +++ b/scripts/assess_readlengths.py @@ -22,8 +22,12 @@ def main(): ap = argparse.ArgumentParser() ap.add_argument("input_dir", type=str, default=".") + ap.add_argument("--amplicon_length", type=int, required=True) + ap.add_argument("--min_overlap", type=int, required=True) args = ap.parse_args() + minlength = args.amplicon_length + args.min_overlap + read_yields = {} for r in (1, 2): read_lengths = Counter() @@ -45,15 +49,22 @@ def main(): all_lengths = r1_lengths is_hom = len(r1_lengths) == 1 else: - if len(r1_lengths) < len(r2_lengths): - r1_lengths.extend(("NA", "NA", "NA") for i in range(len(r2_lengths) - len(r1_lengths))) - elif len(r2_lengths) < len(r1_lengths): - r2_lengths.extend(("NA", "NA", "NA") for i in range(len(r1_lengths) - len(r2_lengths))) - all_lengths = [x + y for x, y in zip(r1_lengths, r2_lengths)] is_hom = len(r1_lengths) == len(r2_lengths) == 1 + smaller = min(len(r1_lengths), len(r2_lengths)) + all_lengths = [x + y for x, y in zip(r1_lengths[:smaller], r2_lengths[:smaller])] + + + #if len(r1_lengths) < len(r2_lengths): + # # r1_lengths.extend(("NA", "NA", "NA") for i in range(len(r2_lengths) - len(r1_lengths))) + # r2_lengths = r2_lengths[:len(r1_lengths)] + #elif len(r2_lengths) < len(r1_lengths): + # # r2_lengths.extend(("NA", "NA", "NA") for i in range(len(r1_lengths) - len(r2_lengths))) + # r1_lengths = r1_lengths[:len(r2_lengths)] + #all_lengths = [x + y for x, y in zip(r1_lengths, r2_lengths)] for item in all_lengths: - print(*item, sep="\t") + if len(item) == 3 or item[0] + item[3] > minlength: + print(*item, sep="\t") if is_hom: open("READSET_HOMOGENEOUS", "wt").close() From 667604417a0dd5d9602ddb3e1992c77b530c4af2 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Fri, 19 Nov 2021 23:18:23 +0100 Subject: [PATCH 22/34] changed adapter/primer removal to 2-step fwd/rev clipping --- config/run.config | 44 +++++++++++++++++++++++------------ modules/nevermore/qc/bbduk.nf | 33 +++++++++++++++----------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/config/run.config b/config/run.config index 05baa3a..6c755bc 100644 --- a/config/run.config +++ b/config/run.config @@ -1,29 +1,43 @@ params { publish_mode = "symlink" + // default figaro parameters + // overlap between read1 and read2 min_overlap = 20 + // length of left primer left_primer = 0 + // length of right primer right_primer = 0 - /* - bbduk qc parameters - s. https://jgi.doe.gov/data-and-tools/bbtools/bb-tools-user-guide/bbduk-guide/ - qtrim=rl trimq=3 : gentle quality trimming (only discard bases < phred 3; phred 2 = junk marker) on either side (rl) of the read - maq=25 : discard reads below average quality of pred 25 - minlen=45 : discard reads < 45bp - ref=?? ktrim=r k=23 mink=11 hdist=1 tpe tbo : right-side k-mer based adapter clipping with 1 mismatch allowed, try overlap-detection (tbo), and trim pairs to same length (tpe) upon adapter detection - ftm=5 : get rid off (n*5)+1st base (last sequencing cycle illumina garbage) - entropy=0.5 entropywindow=50 entropyk=5 : discard low complexity sequences - */ - - qc_params_primers = "qtrim=rl trimq=3 ktrim=l k=14 mink=1 hdist=1 cu=t" - qc_params_adapters = "qtrim=rl trimq=3 ktrim=l k=23 mink=1 hdist=1 tpe tbo cu=t" + // only keep reads of at least length = qc_minlen qc_minlen = 100 + // If only primer lengths are supplied, figaro/dada2 will take care of primer removal. + // Otherwise, if primer sequences are supplied, + // primer + adapter removal is a two-step process: + // 1. gentle quality trimming (< phred 3) + remove left primer on 5'-R1 and potentially on 3'-R2 (rc) + // 2. remove right primer on 5'-R2 and potentially on 3'-R1 (rc) + + // Primer removal is highly dataset-specific, you might have to play with the settings below: + // also refer to: https://jgi.doe.gov/data-and-tools/bbtools/bb-tools-user-guide/bbduk-guide/ + // cu=t : allow degenerate primer sequences + // qtrim=rl trimq=3 : gentle quality trimming (< phred 3) on both sides + // ktrim=(r|l) : clip adapters from right xor left end -- DO NOT MODIFY. + // restrictleft|restrictright : only take into account the first / last N bases for adapter clipping + // k=9 hdist=1: adapter/primer k-mers of length 9 have to match with at most one mismatch + // mink=1: at the ends of reads, perfect (mismatch=0) adapter/primer k-mer matches of length 1 are allowed (similar to cutadapt) + // -- to allow mismatches, set hdist2 to a positive, non-zero integer + + p5_primer_params = "cu=t qtrim=rl ktrim=l trimq=3 k=9 mink=1 hdist=1 restrictleft=50" + p3_primer_params = "cu=t ktrim=r k=9 mink=1 hdist=1 restrictright=50" + + // default settings for mapseq (when served from gaga2-singularity container) + // only edit these three, if you're using a local mapseq installation mapseq_bin = "mapseq" - mapseq_db_path = projectDir - mapseq_db_name = "" + mapseq_db_path = projectDir // dirname of /path/to/mapseq_folder (i.e. /path/to) + mapseq_db_name = "" // basename of /path/to/mapseq_folder (i.e. mapseq_folder) + // parameters for dada2 chimera removal -- potentially useful for troubleshooting dada2_chimera_method = "consensus" // can be "consensus" (default dada2 since 1.4) or "pool" dada2_chimera_min_fold_parent_over_abundance = 2 } diff --git a/modules/nevermore/qc/bbduk.nf b/modules/nevermore/qc/bbduk.nf index 22a91b6..505c276 100644 --- a/modules/nevermore/qc/bbduk.nf +++ b/modules/nevermore/qc/bbduk.nf @@ -1,35 +1,40 @@ - -process qc_bbduk { +process qc_bbduk_amplicon { label 'bbduk' - publishDir path: params.output_dir, mode: params.publish_mode, pattern: "${sample.id}/${sample.id}.bbduk_stats.txt" + publishDir path: params.output_dir, mode: params.publish_mode, pattern: "${sample.id}/${sample.id}.*bbduk_stats.txt" input: tuple val(sample), path(reads) path(adapters) - path(run_sentinel) output: tuple val(sample), path("${sample.id}/${sample.id}_R*.fastq.gz"), emit: reads tuple val(sample), path("${sample.id}/${sample.id}_O.fastq.gz"), emit: orphans, optional: true - path("${sample.id}/${sample.id}.bbduk_stats.txt") + path("${sample.id}/${sample.id}.*bbduk_stats.txt") script: def maxmem = task.memory.toString().replace(/ GB/, "g") - //def read1 = "in1=${sample.id}_R1.fastq.gz out1=${sample.id}/${sample.id}_R1.fastq.gz" - def read1 = "in1=${sample.id}_R1.fastq.gz out1=${sample.id}/${sample.id}_R1.fastq.gz" - //read2 = sample.is_paired ? "in2=${sample.id}_R2.fastq.gz out2=${sample.id}/${sample.id}_R2.fastq.gz outs=${sample.id}/${sample.id}_O.fastq.gz" : "" - read2 = sample.is_paired ? "in2=${sample.id}_R2.fastq.gz out2=${sample.id}/${sample.id}_R2.fastq.gz outs=${sample.id}/${sample.id}_O.fastq.gz" : "" + + def read1_p5 = "in1=${sample.id}_R1.fastq.gz out1=${sample.id}_R1.p5.fastq.gz" + def read1_p3 = "in1=${sample.id}_R1.p5.fastq.gz out1=${sample.id}/${sample.id}_R1.fastq.gz" + + def read2_p5 = "" + def read2_p3 = "" + + if (sample.is_paired) { + read2_p5 = "in2=${sample.id}_R2.fastq.gz out2=${sample.id}_R2.p5.fastq.gz" + read2_p3 = "in2=${sample.id}_R2.p5.fastq.gz out2=${sample.id}/${sample.id}_R2.fastq.gz" + } if (params.primers) { - qc_params = params.qc_params_primers - trim_params = "literal=${params.primers} minlen=${params.qc_minlen}" + trim_params = "literal=${params.primers} minlength=${params.qc_minlen}" } else { - qc_params = params.qc_params_adapters - trim_params = "ref=${adapters} minlen=${params.qc_minlen}" + trim_params = "ref=${adapters} minlength=${params.qc_minlen}" } """ mkdir -p ${sample.id} - bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t ${qc_params} ${trim_params} stats=${sample.id}/${sample.id}.bbduk_stats.txt ${read1} ${read2} + + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t ${params.p5_primer_params} ${trim_params} stats=${sample.id}/${sample.id}.fwd_bbduk_stats.txt ${read1_p5} ${read2_p5} + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t ${params.p3_primer_params} ${trim_params} stats=${sample.id}/${sample.id}.rev_bbduk_stats.txt ${read1_p3} ${read2_p3} """ } From e1a824eba734bbb44ba8457dcbf8288f325811b6 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Fri, 19 Nov 2021 23:19:50 +0100 Subject: [PATCH 23/34] main workflow changes * fixed issue with homogeneous length trimming * removed check for preprocessed reads that would prevent preprocessing for 'garbage' data --- main.nf | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/main.nf b/main.nf index 08024a4..9122e99 100644 --- a/main.nf +++ b/main.nf @@ -2,7 +2,7 @@ nextflow.enable.dsl = 2 -include { qc_bbduk } from "./modules/nevermore/qc/bbduk" +include { qc_bbduk_amplicon } from "./modules/nevermore/qc/bbduk" include { fastqc } from "./modules/nevermore/qc/fastqc" include { classify_sample } from "./modules/nevermore/functions" include { mapseq; mapseq_otutable } from "./modules/profilers/mapseq" @@ -163,7 +163,7 @@ process assess_read_length_distribution { script: """ - python ${projectDir}/scripts/assess_readlengths.py . > read_length_thresholds.txt + python ${projectDir}/scripts/assess_readlengths.py --amplicon_length ${params.amplicon_length} --min_overlap ${params.min_overlap} . > read_length_thresholds.txt """ } @@ -186,14 +186,20 @@ process homogenise_readlengths { mkdir -p ${sample.id} r1len=\$(head -n 1 ${read_lengths} | cut -f 1) r2len=\$(head -n 1 ${read_lengths} | cut -f 4) - bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$((r1len-1)) ftr=\$((r1len-1)) stats=${sample.id}/${sample.id}.homr_stats_1.txt in=${sample.id}_R1.fastq.gz out=${sample.id}/${sample.id}_R1.fastq.gz - bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$((r2len-1)) ftr=\$((r2len-1)) stats=${sample.id}/${sample.id}.homr_stats_2.txt in=${sample.id}_R2.fastq.gz out=${sample.id}/${sample.id}_R2.fastq.gz + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t trd=t minlength=\$r1len ftr=\$((r1len-1)) stats=${sample.id}/${sample.id}.homr_stats_1.txt in=${sample.id}_R1.fastq.gz out=left.fastq.gz + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t trd=t minlength=\$r2len ftr=\$((r2len-1)) stats=${sample.id}/${sample.id}.homr_stats_2.txt in=${sample.id}_R2.fastq.gz out=right.fastq.gz + gzip -dc left.fastq.gz | awk 'NR%4==1' | sed 's/^@//' | sed 's/\\/1//' | sort > left.txt + gzip -dc right.fastq.gz | awk 'NR%4==1' | sed 's/^@//' | sed 's/\\/2//' | sort > right.txt + join -1 1 -2 1 left.txt right.txt > both.txt + seqtk subseq left.fastq.gz both.txt | gzip -c - > ${sample.id}/${sample.id}_R1.fastq.gz + seqtk subseq right.fastq.gz both.txt | gzip -c - > ${sample.id}/${sample.id}_R2.fastq.gz + rm left.* right.* both.txt """ } else { """ mkdir -p ${sample.id} r1len=\$(head -n 1 ${read_lengths} | cut -f 1) - bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$((r1len-1)) ftr=\$((r1len-1)) stats=${sample.id}/${sample.id}.homr_stats_1.txt in=${sample.id}_R1.fastq.gz out=${sample.id}/${sample.id}_R1.fastq.gz + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$r1len ftr=\$((r1len-1)) stats=${sample.id}/${sample.id}.homr_stats_1.txt in=${sample.id}_R1.fastq.gz out=${sample.id}/${sample.id}_R1.fastq.gz """ } } @@ -203,18 +209,17 @@ workflow raw_reads_figaro { take: reads - run_figaro main: - qc_bbduk(reads, "${projectDir}/assets/adapters.fa", run_figaro) + qc_bbduk_amplicon(reads, "${projectDir}/assets/adapters.fa") - fastqc(qc_bbduk.out.reads) + fastqc(qc_bbduk_amplicon.out.reads) fastqc_ch = fastqc.out.reports .map { sample, report -> return report } .collect() assess_read_length_distribution(fastqc_ch) - homogenise_readlengths(qc_bbduk.out.reads, assess_read_length_distribution.out.read_length) + homogenise_readlengths(qc_bbduk_amplicon.out.reads, assess_read_length_distribution.out.read_length) hom_reads = homogenise_readlengths.out.reads.collect() figaro(hom_reads, !params.single_end) @@ -301,11 +306,7 @@ workflow { if (!params.preprocessed) { - /* check if dataset was preprocessed */ - - check_for_preprocessing(prepare_fastqs.out.reads) - - raw_reads_figaro(prepare_fastqs.out.reads, check_for_preprocessing.out.hom_reads_marker) + raw_reads_figaro(prepare_fastqs.out.reads) trim_params_ch = trim_params_ch .concat(raw_reads_figaro.out.trim_params) From 416d79b5f78e8b104c79de13d76df7e2e11853b2 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Fri, 19 Nov 2021 23:21:59 +0100 Subject: [PATCH 24/34] version bump -> 0.4.1 --- nextflow.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextflow.config b/nextflow.config index 904d854..2000bfa 100644 --- a/nextflow.config +++ b/nextflow.config @@ -4,5 +4,5 @@ manifest { description = "DADA2/figaro-based 16S amplicon analysis pipeline" name = "gaga2" nextflowVersion = ">=21.0" - version = "0.4" + version = "0.4.1" } From 277b56d9f07ef6678adaa16541bfb3f518e688d5 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Sat, 20 Nov 2021 00:00:00 +0100 Subject: [PATCH 25/34] Update README.md --- README.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 938d5e9..c725f78 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,18 @@ Other dependencies: * fastqc * figaro * R v4+ with dada2, devtools, tidyverse, and cowplot installed +* MapSeq For convenience, `gaga2` comes with a Singularity container with all dependencies installed. ```singularity pull oras://ghcr.io/zellerlab/gaga2:latest``` +#### MapSeq +While most dependencies can be installed via conda, `MapSeq` can not. If you don't want / can't use the `gaga2` singularity container, you need to make `gaga2` aware of your own MapSeq installation. In this case (and only in this case!), please modify the following entries in the `run.config`: +* `mapseq_bin` should be the path to your `mapseq` binary; if `mapseq` is in your `$PATH`, you can leave this entry unchanged. +* `mapseq_db_path` is the `dirname` of the path to your `mapseq` database, i.e. for `/path/to/mapseq_db_folder/` it is `/path/to/`. +* `mapseq_db_name` is the `basename` of the path to your `mapseq` database, i.e. for `/path/to/mapseq_db_folder/` it is `mapseq_db_folder`. + ## Usage instructions @@ -28,22 +35,22 @@ They should, but don't have to, be arranged in a sample-based directory structur ``` (aka "input_dir") |___ - | |____ - | |____ + | |____ _R?1.{fastq,fq,fastq.gz,fq.gz} + | |____ _R?2.{fastq,fq,fastq.gz,fq.gz} | |___ | |____ | |___ - |____ - |____ + |____ _R?1.{fastq,fq,fastq.gz,fq.gz} + |____ _R?2.{fastq,fq,fastq.gz,fq.gz} ``` A flat directory structure (with all read files in the same directory) or a deeply-branched (with read files scattered over multiple levels) should also work. If `gaga2` preprocesses the reads, it will automatically use `_R1/2` endings internally. -* If input reads have already been preprocessed, you can set the `--preprocessed` flag. In this case, `gaga2` will do no preprocessing at all and instruct `dada2` to perform no trimming. Otherwie, `gaga2` will assess the read lengths for uniformity. If read lengths differ within and between samples, preprocessing with `figaro` is not possible and `dada2` will be run without trimming. +* If input reads have already been preprocessed, you can set the `--preprocessed` flag. In this case, `gaga2` will do no preprocessing at all and instruct `dada2` to perform no trimming. Otherwise, `gaga2` will preprocess the reads with `bbduk`. If primer sequences are given via `--primers [,]`, `bbduk` will attempt to remove them + any up/downstream adapter remains. Otherwise, `bbduk` will attempt to remove adapter remains only. Reads are then cut to a uniform length (all forward reads across all samples and all reverse reads across all samples need to have the length, but forward and reverse reads may differ in length.) The latter treatment is a requirement for `figaro`, which will then take the primer lengths (supplied via `--left_primer` and `--right_primer`) into account when determining the optimal cut sites. * Samples with less than `110` reads after `dada2` preprocessing, will be discarded. @@ -61,6 +68,8 @@ before. In addition, you should obtain a copy of the `run.config` from the `gaga2` github repo and modify it according to your environment. +Additional arguments can be set via the command line (e.g. `--input_dir /path/to/input_dir`) or in the params section of the `run.config` file (recommended for documentation and reproducibility purposes.) + #### Mandatory arguments * `--input_dir` is the project directory mentioned above. * `--output_dir` will be created automatically. @@ -71,6 +80,9 @@ In addition, you should obtain a copy of the `run.config` from the `gaga2` githu * `--min_overlap` of read pairs is `20bp` by default * `--primers ` or `--left_primer`, and `--right_primer` If primer sequences are provided via `--primers`, `gaga2` will remove primers and upstream sequences (using `bbduk`), such as adapters based on the primer sequences. If non-zero primer lengths are provided instead (via `--left_primer` and `--right_primer`), `figaro` will take those into account when determining the best trim positions. * `--preprocessed` will prevent any further preprocessing by `gaga2` - this flag should only be used if the read data is reliably clean. +* `--qc_minlen` sets the minimum length for reads to be retained after preprocessing. +* `--dada2_chimera_method` sets the method with which `dada2` will remove chimeras (default: `"consensus"`, alternative: `"pool"`) +* `--dada2_chimera_min_fold_parent_over_abundance` sets the `minFoldParentOverAbundance` parameter for `removeBimeraDeNovo` (default: `2`) ### internal beta-testing instructions From 6ee07ba5b2b60e672142035606baf369706484b0 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Sat, 20 Nov 2021 00:00:29 +0100 Subject: [PATCH 26/34] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c725f78..3d7fc42 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Other dependencies: * bbmap * fastqc +* seqtk * figaro * R v4+ with dada2, devtools, tidyverse, and cowplot installed * MapSeq From 66bb7ce9f557d9001527f6a1d51ccdafda97deaf Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Sat, 20 Nov 2021 00:30:23 +0100 Subject: [PATCH 27/34] fixed post-merge main.nf --- main.nf | 154 -------------------------------------------------------- 1 file changed, 154 deletions(-) diff --git a/main.nf b/main.nf index f065f23..cbde474 100644 --- a/main.nf +++ b/main.nf @@ -134,117 +134,6 @@ process dada2_analysis { """ } -process asv2fasta { - publishDir "${params.output_dir}", mode: params.publish_mode - - input: - path(asv_seqs) - - output: - tuple val(meta), path("ASVs.fasta"), emit: asv_fasta - - script: - meta = [:] - meta.id = "all" - meta.is_paired = false - """ - tail -n +2 ${asv_seqs} | sed 's/^/>/' | tr '\t' '\n' > ASVs.fasta - """ -} - -process assess_read_length_distribution { - input: - path(fastq_reports) - - output: - path("read_length_thresholds.txt"), emit: read_length - path("READSET_HOMOGENEOUS"), emit: hom_reads_marker, optional: true - - script: - """ - python ${projectDir}/scripts/assess_readlengths.py . > read_length_thresholds.txt - """ -} - - -process homogenise_readlengths { - label 'bbduk' - - input: - tuple val(sample), path(reads) - path(read_lengths) - - output: - path("${sample.id}/*.{fastq,fq,fastq.gz,fq.gz}"), emit: reads - - script: - def maxmem = task.memory.toString().replace(/ GB/, "g") - - if (sample.is_paired) { - """ - mkdir -p ${sample.id} - r1len=\$(head -n 1 ${read_lengths} | cut -f 1) - r2len=\$(head -n 1 ${read_lengths} | cut -f 4) - bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$((r1len-1)) ftr=\$((r1len-1)) stats=${sample.id}/${sample.id}.homr_stats_1.txt in=${sample.id}_R1.fastq.gz out=${sample.id}/${sample.id}_R1.fastq.gz - bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$((r2len-1)) ftr=\$((r2len-1)) stats=${sample.id}/${sample.id}.homr_stats_2.txt in=${sample.id}_R2.fastq.gz out=${sample.id}/${sample.id}_R2.fastq.gz - """ - } else { - """ - mkdir -p ${sample.id} - r1len=\$(head -n 1 ${read_lengths} | cut -f 1) - bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t minlength=\$((r1len-1)) ftr=\$((r1len-1)) stats=${sample.id}/${sample.id}.homr_stats_1.txt in=${sample.id}_R1.fastq.gz out=${sample.id}/${sample.id}_R1.fastq.gz - """ - } -} - - -workflow raw_reads_figaro { - - take: - reads - run_figaro - - main: - qc_bbduk(reads, "${projectDir}/assets/adapters.fa", run_figaro) - - fastqc(qc_bbduk.out.reads) - fastqc_ch = fastqc.out.reports - .map { sample, report -> return report } - .collect() - - assess_read_length_distribution(fastqc_ch) - homogenise_readlengths(qc_bbduk.out.reads, assess_read_length_distribution.out.read_length) - - hom_reads = homogenise_readlengths.out.reads.collect() - figaro(hom_reads, !params.single_end) - extract_trim_parameters(figaro.out.trim_params) - - emit: - reads = hom_reads - trim_params = extract_trim_parameters.out.trim_params - -} - - -workflow check_for_preprocessing { - take: - reads - - main: - reads.view() - fastqc(reads) - assess_read_length_distribution( - fastqc.out.reports - .map { sample, report -> return report } - .collect() - ) - - emit: - readlen_dist = assess_read_length_distribution.out.read_length - hom_reads_marker = assess_read_length_distribution.out.hom_reads_marker -} - - process prepare_fastqs { input: tuple val(sample), path(fq) @@ -361,49 +250,6 @@ workflow raw_reads_figaro { emit: reads = hom_reads trim_params = extract_trim_parameters.out.trim_params - -} - - -workflow check_for_preprocessing { - take: - reads - - main: - reads.view() - fastqc(reads) - assess_read_length_distribution( - fastqc.out.reports - .map { sample, report -> return report } - .collect() - ) - - emit: - readlen_dist = assess_read_length_distribution.out.read_length - hom_reads_marker = assess_read_length_distribution.out.hom_reads_marker -} - - -process prepare_fastqs { - input: - tuple val(sample), path(fq) - - output: - tuple val(sample), path("fastq/${sample.id}/${sample.id}_R*.fastq.gz"), emit: reads - - script: - if (sample.is_paired) { - """ - mkdir -p fastq/${sample.id} - ln -sf ../../${fq[0]} fastq/${sample.id}/${sample.id}_R1.fastq.gz - ln -sf ../../${fq[1]} fastq/${sample.id}/${sample.id}_R2.fastq.gz - """ - } else { - """ - mkdir -p fastq/${sample.id} - ln -sf ../../${fq[0]} fastq/${sample.id}/${sample.id}_R1.fastq.gz - """ - } } From 38858f0cdf5e18b567b267e0ca6d476bcd8012c2 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Mon, 22 Nov 2021 13:49:20 +0100 Subject: [PATCH 28/34] increased run time for dada2 processes, added custom mapseq db --- config/run.config | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/config/run.config b/config/run.config index 2399e93..c67e5d0 100644 --- a/config/run.config +++ b/config/run.config @@ -34,8 +34,11 @@ params { // default settings for mapseq (when served from gaga2-singularity container) // only edit these three, if you're using a local mapseq installation mapseq_bin = "mapseq" - mapseq_db_path = projectDir // dirname of /path/to/mapseq_folder (i.e. /path/to) - mapseq_db_name = "" // basename of /path/to/mapseq_folder (i.e. mapseq_folder) + //mapseq_db_path = projectDir // dirname of /path/to/mapseq_folder (i.e. /path/to) + //mapseq_db_name = "" // basename of /path/to/mapseq_folder (i.e. mapseq_folder) + mapseq_db_path = "/g/scb/zeller/schudoma/mapseq_db" + mapseq_db_name = "zeller_db" + // parameters for dada2 chimera removal -- potentially useful for troubleshooting dada2_chimera_method = "consensus" // can be "consensus" (default dada2 since 1.4) or "pool" @@ -61,7 +64,7 @@ process { executor = "slurm" errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} memory = '16.GB' - time = '4d' + time = '14d' maxRetries = 3 cpus = 8 } @@ -70,7 +73,7 @@ process { executor = "slurm" errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} memory = '16.GB' - time = '4d' + time = '14d' maxRetries = 3 cpus = 8 } @@ -80,7 +83,7 @@ process { errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} cpus = 4 memory = {8.GB * task.attempt} - time = '2h' + time = '24h' maxRetries = 3 } withName: fastqc { From 4aecd92ba6e24c03cbb00a86e4a8338ea4fa1481 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Mon, 22 Nov 2021 13:51:34 +0100 Subject: [PATCH 29/34] changed mapseq output to simple, allowed custom mapseq databases --- README.md | 6 +++++- config/run.config | 2 +- main.nf | 2 +- modules/profilers/mapseq.nf | 8 +++++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3d7fc42..aed5de4 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,15 @@ For convenience, `gaga2` comes with a Singularity container with all dependencie ```singularity pull oras://ghcr.io/zellerlab/gaga2:latest``` #### MapSeq -While most dependencies can be installed via conda, `MapSeq` can not. If you don't want / can't use the `gaga2` singularity container, you need to make `gaga2` aware of your own MapSeq installation. In this case (and only in this case!), please modify the following entries in the `run.config`: +While most dependencies can be installed via conda, `MapSeq` can not. If you don't want / can't use the `gaga2` singularity container, you need to make `gaga2` aware of your own MapSeq installation. In this case, please modify the following entries in the `run.config`: * `mapseq_bin` should be the path to your `mapseq` binary; if `mapseq` is in your `$PATH`, you can leave this entry unchanged. * `mapseq_db_path` is the `dirname` of the path to your `mapseq` database, i.e. for `/path/to/mapseq_db_folder/` it is `/path/to/`. * `mapseq_db_name` is the `basename` of the path to your `mapseq` database, i.e. for `/path/to/mapseq_db_folder/` it is `mapseq_db_folder`. +#### Custom MapSeq databases. + +If you use the MapSeq as provided by the gaga2 container, you can modify the `mapseq_db_path` and `mapseq_db_name` parameters to point at a custom database. This custom database will be used in addition to MapSeq's own database and is currently limited to a single taxonomy. Please note, that the database files need to be named `.fasta` and `.tax`. + ## Usage instructions diff --git a/config/run.config b/config/run.config index c67e5d0..661f293 100644 --- a/config/run.config +++ b/config/run.config @@ -38,7 +38,7 @@ params { //mapseq_db_name = "" // basename of /path/to/mapseq_folder (i.e. mapseq_folder) mapseq_db_path = "/g/scb/zeller/schudoma/mapseq_db" mapseq_db_name = "zeller_db" - + // parameters for dada2 chimera removal -- potentially useful for troubleshooting dada2_chimera_method = "consensus" // can be "consensus" (default dada2 since 1.4) or "pool" diff --git a/main.nf b/main.nf index cbde474..9d52567 100644 --- a/main.nf +++ b/main.nf @@ -310,5 +310,5 @@ workflow { asv2fasta(dada2_analysis.out.asv_sequences) mapseq(asv2fasta.out.asv_fasta, params.mapseq_db_path, params.mapseq_db_name) - mapseq_otutable(mapseq.out.bac_ssu) + // mapseq_otutable(mapseq.out.bac_ssu) } diff --git a/modules/profilers/mapseq.nf b/modules/profilers/mapseq.nf index 78338ca..e5cfbee 100644 --- a/modules/profilers/mapseq.nf +++ b/modules/profilers/mapseq.nf @@ -1,4 +1,6 @@ process mapseq { + publishDir params.output_dir, mode: params.publish_mode + input: tuple val(sample), path(seqs) path(db_path) @@ -10,9 +12,13 @@ process mapseq { script: def db = (db_name == "default" || db_name == "") ? "" : "${db_path}/${db_name}.fasta ${db_path}/*.tax*" + db_run = (db != "") ? "${params.mapseq_bin} -outfmt simple ${seqs} ${db} > ${sample.id}/${sample.id}.${db_name}_bac_ssu.mseq" : "" + """ mkdir -p ${sample.id} - ${params.mapseq_bin} ${seqs} > ${sample.id}/${sample.id}_bac_ssu.mseq + ${params.mapseq_bin} -outfmt simple ${seqs} > ${sample.id}/${sample.id}_bac_ssu.mseq + + ${db_run} """ } From 9a354109cbaf6de228be5c4d463d558378bada25 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 24 Nov 2021 13:34:33 +0100 Subject: [PATCH 30/34] changed publish_mode to copy, upped memory for dada2, added custom mapseq db --- config/run.config | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/run.config b/config/run.config index 661f293..b08752d 100644 --- a/config/run.config +++ b/config/run.config @@ -1,5 +1,5 @@ params { - publish_mode = "symlink" + publish_mode = "copy" // default figaro parameters // overlap between read1 and read2 @@ -36,7 +36,7 @@ params { mapseq_bin = "mapseq" //mapseq_db_path = projectDir // dirname of /path/to/mapseq_folder (i.e. /path/to) //mapseq_db_name = "" // basename of /path/to/mapseq_folder (i.e. mapseq_folder) - mapseq_db_path = "/g/scb/zeller/schudoma/mapseq_db" + mapseq_db_path = "/g/scb/zeller/schudoma/mapseq_db/zeller_db" mapseq_db_name = "zeller_db" @@ -72,7 +72,7 @@ process { container = "oras://ghcr.io/zellerlab/gaga2:latest" executor = "slurm" errorStrategy = {task.attempt <= 3 ? "retry" : "ignore"} - memory = '16.GB' + memory = {16.GB * task.attempt} time = '14d' maxRetries = 3 cpus = 8 From 8eddf2c01009b3ef83f518cd713968c81f0885e2 Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 24 Nov 2021 13:36:10 +0100 Subject: [PATCH 31/34] improved read length resolution during assessment * read length distributions are now obtained from bbduk histograms (non-binned) * experimental: allow shorter reads instead of forcing to completely cover amplicon --- scripts/assess_readlengths.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/scripts/assess_readlengths.py b/scripts/assess_readlengths.py index b3ca74c..0032b00 100644 --- a/scripts/assess_readlengths.py +++ b/scripts/assess_readlengths.py @@ -18,6 +18,14 @@ def parse_fastqc_report(f): return dict(item for item in readlengths if item[1] > 0) +def parse_bbduk_hist(f): + return { + int(line.strip().split("\t")[0]): int(line.strip().split("\t")[1]) + for line in open(f) + if line[0] != "#" + } + + def main(): ap = argparse.ArgumentParser() @@ -26,13 +34,16 @@ def main(): ap.add_argument("--min_overlap", type=int, required=True) args = ap.parse_args() - minlength = args.amplicon_length + args.min_overlap + minlength = 0.5 * args.amplicon_length + args.min_overlap + # minlength = 0 read_yields = {} - for r in (1, 2): + for mate in (1, 2): read_lengths = Counter() - for fastq_report in glob.glob(os.path.join(args.input_dir, f"*{r}_fastqc_data.txt")): - read_lengths.update(parse_fastqc_report(fastq_report)) + #for fastq_report in glob.glob(os.path.join(args.input_dir, f"*{mate}_fastqc_data.txt")): + # read_lengths.update(parse_fastqc_report(fastq_report)) + for hist in glob.glob(os.path.join(args.input_dir, f"*R{mate}.post_lhist.txt")): + read_lengths.update(parse_bbduk_hist(hist)) yields = list() for length, count in read_lengths.items(): @@ -40,7 +51,7 @@ def main(): (length, sum(v for k, v in read_lengths.items() if k >= length), sum(v * length for k, v in read_lengths.items() if k >= length)) ) for length, reads, bases in sorted(yields, key=lambda x:(x[2], x[1]), reverse=True): - read_yields.setdefault(r, list()).append((length, reads, bases)) + read_yields.setdefault(mate, list()).append((length, reads, bases)) r1_lengths = read_yields.get(1, list()) r2_lengths = read_yields.get(2, list()) From a71619144ab977aaa03f0b4505b4942a6f64863b Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 24 Nov 2021 13:39:43 +0100 Subject: [PATCH 32/34] added stepwise read preprocessing, individually cleaning r1/r2 from adapters/primers --- modules/nevermore/qc/bbduk.nf | 84 +++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/modules/nevermore/qc/bbduk.nf b/modules/nevermore/qc/bbduk.nf index c914f92..96d9540 100644 --- a/modules/nevermore/qc/bbduk.nf +++ b/modules/nevermore/qc/bbduk.nf @@ -1,15 +1,16 @@ process qc_bbduk_amplicon { label 'bbduk' - publishDir path: params.output_dir, mode: params.publish_mode, pattern: "${sample.id}/${sample.id}.*bbduk_stats.txt" + publishDir path: params.output_dir, mode: params.publish_mode, pattern: "${sample.id}/${sample.id}.*{txt,lhist}" //bbduk_stats.txt" input: tuple val(sample), path(reads) - path(adapters) + path(adapters) output: tuple val(sample), path("${sample.id}/${sample.id}_R*.fastq.gz"), emit: reads tuple val(sample), path("${sample.id}/${sample.id}_O.fastq.gz"), emit: orphans, optional: true path("${sample.id}/${sample.id}.*bbduk_stats.txt") + path("${sample.id}/${sample.id}*lhist") script: def maxmem = task.memory.toString().replace(/ GB/, "g") @@ -19,10 +20,12 @@ process qc_bbduk_amplicon { def read2_p5 = "" def read2_p3 = "" + def read2_hist = "" if (sample.is_paired) { read2_p5 = "in2=${sample.id}_R2.fastq.gz out2=${sample.id}_R2.p5.fastq.gz" read2_p3 = "in2=${sample.id}_R2.p5.fastq.gz out2=${sample.id}/${sample.id}_R2.fastq.gz" + read2_hist = "bbduk.sh -Xmx${maxmem} in1=${sample.id}/${sample.id}_R2.fastq.gz lhist=${sample.id}/${sample.id}_R2.post_lhist" } if (params.primers) { @@ -33,7 +36,80 @@ process qc_bbduk_amplicon { """ mkdir -p ${sample.id} - bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t ${params.p5_primer_params} ${trim_params} stats=${sample.id}/${sample.id}.fwd_bbduk_stats.txt ${read1_p5} ${read2_p5} - bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t ${params.p3_primer_params} ${trim_params} stats=${sample.id}/${sample.id}.rev_bbduk_stats.txt ${read1_p3} ${read2_p3} + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t ${params.p5_primer_params} ${trim_params} stats=${sample.id}/${sample.id}.fwd_bbduk_stats.txt ${read1_p5} ${read2_p5} lhist=${sample.id}/${sample.id}.p5_lhist + bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t ${params.p3_primer_params} ${trim_params} stats=${sample.id}/${sample.id}.rev_bbduk_stats.txt ${read1_p3} ${read2_p3} lhist=${sample.id}/${sample.id}.p3_lhist + bbduk.sh -Xmx${maxmem} t=${task.cpus} in1=${sample.id}/${sample.id}_R1.fastq.gz \$(echo ${read2_p3} | cut -f 1,3 -d =) lhist=${sample.id}/${sample.id}.post_lhist + + bbduk.sh -Xmx${maxmem} in1=${sample.id}/${sample.id}_R1.fastq.gz lhist=${sample.id}/${sample.id}_R1.post_lhist + ${read2_hist} + """ + // bbduk.sh -Xmx${maxmem} t=${task.cpus} in1=${sample.id}_R1.fastq.gz \$(echo ${read2_p5} | cut -f 1 -d " ") lhist=${sample.id}/${sample.id}.pre_lhist +} + +process qc_bbduk_stepwise_amplicon { + label 'bbduk' + publishDir path: params.output_dir, mode: params.publish_mode, pattern: "${sample.id}/${sample.id}*txt" + + input: + tuple val(sample), path(reads) + path(adapters) + + output: + tuple val(sample), path("${sample.id}/${sample.id}_R*.fastq.gz"), emit: reads + tuple val(sample), path("${sample.id}/${sample.id}_O.fastq.gz"), emit: orphans, optional: true + path("${sample.id}/${sample.id}.*bbduk_stats.txt"), optional: true + path("${sample.id}/${sample.id}*lhist.txt"), emit: read_lengths, optional: true + + script: + def maxmem = task.memory.toString().replace(/ GB/, "g") + + if (params.primers) { + trim_params = "literal=${params.primers} minlength=${params.qc_minlen}" + } else { + trim_params = "ref=${adapters} minlength=${params.qc_minlen}" + } + + def bbduk_call = "bbduk.sh -Xmx${maxmem} t=${task.cpus} ordered=t trd=t" + + ref_p5_r1 = (params.primers) ? "literal=" + params.primers.split(",")[0] : "ref=${adapters}" + ref_p5_r2 = (params.primers && !params.single_end) ? "literal=" + params.primers.split(",")[1] : "ref=${adapters}" + ref_p3_r1 = ref_p5_r2 + ref_p3_r2 = ref_p5_r1 + + if (params.single_end) { + """ + mkdir -p ${sample.id} + ${bbduk_call} ${trim_params} in1=${sample.id}_R1.fastq.gz out1=${sample.id}/${sample.id}_R1.fastq.gz stats=${sample.id}/${sample.id}.fwd_bbduk_stats.txt lhist=${sample.id}/${sample.id}.p5_lhist.txt + ${bbduk_call} in1=${sample.id}/${sample.id}_R1.fastq.gz lhist=${sample.id}/${sample.id}_R1.post_lhist.txt + """ + } else if (params.long_reads) { + """ + mkdir -p ${sample.id} + ${bbduk_call} ${ref_p5_r1} minlength=${params.qc_minlen} ${params.p5_primer_params} in1=${sample.id}_R1.fastq.gz out1=fwd_p5.fastq.gz + ${bbduk_call} ${ref_p5_r2} minlength=${params.qc_minlen} ${params.p5_primer_params} in1=${sample.id}_R2.fastq.gz out1=rev_p5.fastq.gz + ${bbduk_call} ${ref_p3_r1} minlength=${params.qc_minlen} ${params.p3_primer_params} in1=fwd_p5.fastq.gz out1=fwd.fastq.gz + ${bbduk_call} ${ref_p3_r2} minlength=${params.qc_minlen} ${params.p3_primer_params} in1=rev_p5.fastq.gz out1=rev.fastq.gz + gzip -dc fwd.fastq.gz | awk 'NR%4==1' | sed 's/^@//' | sed 's/\\/1//' | sort > fwd.txt + gzip -dc rev.fastq.gz | awk 'NR%4==1' | sed 's/^@//' | sed 's/\\/2//' | sort > rev.txt + join -1 1 -2 1 fwd.txt rev.txt > both.txt + seqtk subseq fwd.fastq.gz both.txt | gzip -c - > ${sample.id}/${sample.id}_R1.fastq.gz + seqtk subseq rev.fastq.gz both.txt | gzip -c - > ${sample.id}/${sample.id}_R2.fastq.gz + ${bbduk_call} in1=${sample.id}/${sample.id}_R1.fastq.gz lhist=${sample.id}/${sample.id}_R1.post_lhist.txt + ${bbduk_call} in1=${sample.id}/${sample.id}_R2.fastq.gz lhist=${sample.id}/${sample.id}_R2.post_lhist.txt + """ + } else { + """ + mkdir -p ${sample.id} + ${bbduk_call} ${ref_p5_r1} minlength=${params.qc_minlen} ${params.p5_primer_params} in1=${sample.id}_R1.fastq.gz out1=fwd.fastq.gz + ${bbduk_call} ${ref_p5_r2} minlength=${params.qc_minlen} ${params.p5_primer_params} in1=${sample.id}_R2.fastq.gz out1=rev.fastq.gz + gzip -dc fwd.fastq.gz | awk 'NR%4==1' | sed 's/^@//' | sed 's/\\/1//' | sort > fwd.txt + gzip -dc rev.fastq.gz | awk 'NR%4==1' | sed 's/^@//' | sed 's/\\/2//' | sort > rev.txt + join -1 1 -2 1 fwd.txt rev.txt > both.txt + seqtk subseq fwd.fastq.gz both.txt | gzip -c - > ${sample.id}/${sample.id}_R1.fastq.gz + seqtk subseq rev.fastq.gz both.txt | gzip -c - > ${sample.id}/${sample.id}_R2.fastq.gz + ${bbduk_call} in1=${sample.id}/${sample.id}_R1.fastq.gz lhist=${sample.id}/${sample.id}_R1.post_lhist.txt + ${bbduk_call} in1=${sample.id}/${sample.id}_R2.fastq.gz lhist=${sample.id}/${sample.id}_R2.post_lhist.txt + """ + } } From 7a1c483825d3d0024443cd685304c63bac44d03b Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 24 Nov 2021 13:51:08 +0100 Subject: [PATCH 33/34] various changes to preprocessing/data flow * version 0.5 * if reads are not covering the full amplicon size, a shortened amplicon size is provided to figaro (experimental) * bbduk length histograms (instead of fastqc) are now provided to read length assessment * sample classification was (temporarily?) moved into the fastq-collection Channel (due to issues with nf 21.10+) * resolved a data flow issue that would allow dada2 processes to start before the preprocessing is finished --- main.nf | 81 +++++++++++++++++++++++++++++++------------------ nextflow.config | 2 +- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/main.nf b/main.nf index 9d52567..95b5794 100644 --- a/main.nf +++ b/main.nf @@ -2,7 +2,7 @@ nextflow.enable.dsl = 2 -include { qc_bbduk_amplicon } from "./modules/nevermore/qc/bbduk" +include { qc_bbduk_stepwise_amplicon } from "./modules/nevermore/qc/bbduk" include { fastqc } from "./modules/nevermore/qc/fastqc" include { classify_sample } from "./modules/nevermore/functions" include { mapseq; mapseq_otutable } from "./modules/profilers/mapseq" @@ -48,8 +48,9 @@ process figaro { publishDir "${params.output_dir}", mode: params.publish_mode input: - path input_reads - val is_paired_end + path(input_reads) + val(is_paired_end) + val(read_lengths) output: path("figaro/trimParameters.json"), emit: trim_params @@ -58,9 +59,20 @@ process figaro { script: def paired_params = (is_paired_end == true) ? "-r ${params.right_primer} -m ${params.min_overlap}" : "" - """ - figaro -i . -o figaro/ -a ${params.amplicon_length} -f ${params.left_primer} ${paired_params} - """ + if (params.single_end) { + """ + figaro -i . -o figaro/ -a ${params.amplicon_length} -f ${params.left_primer} + """ + } else { + amplen = read_lengths[0].toInteger() + read_lengths[1].toInteger() - params.min_overlap.toInteger() - 1 + if (amplen >= params.amplicon_length.toInteger()) { + amplen = params.amplicon_length.toInteger() + } + println 'Amplicon length', amplen, params.amplicon_length + """ + figaro -i . -o figaro/ -a ${amplen} -f ${params.left_primer} -r ${params.right_primer} -m ${params.min_overlap} + """ + } } @@ -172,7 +184,6 @@ process asv2fasta { """ tail -n +2 ${asv_seqs} | sed 's/^/>/' | tr '\t' '\n' > ASVs.fasta """ - } process assess_read_length_distribution { @@ -189,7 +200,6 @@ process assess_read_length_distribution { """ } - process homogenise_readlengths { label 'bbduk' @@ -233,18 +243,22 @@ workflow raw_reads_figaro { reads main: - qc_bbduk_amplicon(reads, "${projectDir}/assets/adapters.fa") + qc_bbduk_stepwise_amplicon(reads, "${projectDir}/assets/adapters.fa") - fastqc(qc_bbduk_amplicon.out.reads) - fastqc_ch = fastqc.out.reports - .map { sample, report -> return report } - .collect() + fastqc(qc_bbduk_stepwise_amplicon.out.reads) - assess_read_length_distribution(fastqc_ch) - homogenise_readlengths(qc_bbduk_amplicon.out.reads, assess_read_length_distribution.out.read_length) + read_lengths_ch = qc_bbduk_stepwise_amplicon.out.read_lengths.collect() + assess_read_length_distribution(read_lengths_ch) + homogenise_readlengths(qc_bbduk_stepwise_amplicon.out.reads, assess_read_length_distribution.out.read_length) + + read_lengths_figaro_input_ch = assess_read_length_distribution.out.read_length + .splitCsv(header: false, sep: '\t') + .first() + .map { values -> (params.single_end) ? tuple(values[0], null) : tuple(values[0], values[3]) } + .view() hom_reads = homogenise_readlengths.out.reads.collect() - figaro(hom_reads, !params.single_end) + figaro(hom_reads, !params.single_end, read_lengths_figaro_input_ch) extract_trim_parameters(figaro.out.trim_params) emit: @@ -262,7 +276,13 @@ workflow { return tuple(sample, file) } .groupTuple(sort: true) - .map { classify_sample(it[0], it[1]) } + .map { sample, files -> + def meta = [:] + meta.is_paired = (files instanceof Collection && files.size() == 2) + meta.id = sample + + return tuple(meta, files) + } prepare_fastqs(fastq_ch) @@ -289,26 +309,27 @@ workflow { trim_params_ch = trim_params_ch .concat(raw_reads_figaro.out.trim_params) - dada_reads_ch = dada_reads_ch.concat(raw_reads_figaro.out.reads) + dada_reads_ch = dada_reads_ch.concat(raw_reads_figaro.out.reads).collect() - } + } else { - trim_params = file("${workDir}/trim_params.txt") - trim_params.text = "-1 -1\n" + trim_params = file("${workDir}/trim_params.txt") + trim_params.text = "-1 -1\n" - trim_params_ch = trim_params_ch - .concat(Channel.fromPath("${workDir}/trim_params.txt")) + trim_params_ch = trim_params_ch + .concat(Channel.fromPath("${workDir}/trim_params.txt")) - dada_reads_ch = dada_reads_ch.concat( - prepare_fastqs.out.reads - .map { sample, reads -> reads } - .collect() - ) + dada_reads_ch = dada_reads_ch.concat( + prepare_fastqs.out.reads + .map { sample, reads -> reads } + .collect() + ) + + } - dada2_preprocess(dada_reads_ch.first(), trim_params_ch.first(), dada2_preprocess_script, is_paired_end) + dada2_preprocess(dada_reads_ch, trim_params_ch, dada2_preprocess_script, is_paired_end) dada2_analysis(dada2_preprocess.out.filtered_reads, dada2_preprocess.out.trim_table, dada2_analysis_script, is_paired_end) asv2fasta(dada2_analysis.out.asv_sequences) mapseq(asv2fasta.out.asv_fasta, params.mapseq_db_path, params.mapseq_db_name) - // mapseq_otutable(mapseq.out.bac_ssu) } diff --git a/nextflow.config b/nextflow.config index 2000bfa..d4032a3 100644 --- a/nextflow.config +++ b/nextflow.config @@ -4,5 +4,5 @@ manifest { description = "DADA2/figaro-based 16S amplicon analysis pipeline" name = "gaga2" nextflowVersion = ">=21.0" - version = "0.4.1" + version = "0.5" } From 35cf5581dadc682935422f980aafecf29116b7fe Mon Sep 17 00:00:00 2001 From: Christian Schudoma Date: Wed, 24 Nov 2021 14:00:03 +0100 Subject: [PATCH 34/34] added --long_reads documentation --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index aed5de4..8aab699 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ Additional arguments can be set via the command line (e.g. `--input_dir /path/to * `--min_overlap` of read pairs is `20bp` by default * `--primers ` or `--left_primer`, and `--right_primer` If primer sequences are provided via `--primers`, `gaga2` will remove primers and upstream sequences (using `bbduk`), such as adapters based on the primer sequences. If non-zero primer lengths are provided instead (via `--left_primer` and `--right_primer`), `figaro` will take those into account when determining the best trim positions. * `--preprocessed` will prevent any further preprocessing by `gaga2` - this flag should only be used if the read data is reliably clean. +* `--long_reads` should be only set if individual reads (i.e., forward and/or reverse) are known to be of equal length or longer than the amplicon. This will trigger primer/adapter removal (if enabled) on the 3'-ends in order to get rid of readthroughs. * `--qc_minlen` sets the minimum length for reads to be retained after preprocessing. * `--dada2_chimera_method` sets the method with which `dada2` will remove chimeras (default: `"consensus"`, alternative: `"pool"`) * `--dada2_chimera_min_fold_parent_over_abundance` sets the `minFoldParentOverAbundance` parameter for `removeBimeraDeNovo` (default: `2`)