diff --git a/.dockstore.yml b/.dockstore.yml index f8fad517f..d55d6539a 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -230,6 +230,11 @@ workflows: primaryDescriptorPath: /pipes/WDL/workflows/sarscov2_genbank.wdl testParameterFiles: - empty.json + - name: sarscov2_illumina_full + subclass: WDL + primaryDescriptorPath: /pipes/WDL/workflows/sarscov2_illumina_full.wdl + testParameterFiles: + - empty.json - name: sarscov2_lineages subclass: WDL primaryDescriptorPath: /pipes/WDL/workflows/sarscov2_lineages.wdl diff --git a/pipes/WDL/tasks/tasks_assembly.wdl b/pipes/WDL/tasks/tasks_assembly.wdl index 690ba8acb..8d0b6d8cb 100644 --- a/pipes/WDL/tasks/tasks_assembly.wdl +++ b/pipes/WDL/tasks/tasks_assembly.wdl @@ -15,7 +15,7 @@ task assemble { String sample_name = basename(basename(reads_unmapped_bam, ".bam"), ".taxfilt") Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-assemble:2.1.16.0" + String docker="quay.io/broadinstitute/viral-assemble:2.1.16.1" } command { @@ -80,7 +80,7 @@ task scaffold { Float? scaffold_min_pct_contig_aligned Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-assemble:2.1.16.0" + String docker="quay.io/broadinstitute/viral-assemble:2.1.16.1" # do this in multiple steps in case the input doesn't actually have "assembly1-x" in the name String sample_name = basename(basename(contigs_fasta, ".fasta"), ".assembly1-spades") @@ -226,7 +226,7 @@ task align_reads { Boolean? skip_mark_dupes=false Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" String sample_name = basename(basename(basename(reads_unmapped_bam, ".bam"), ".taxfilt"), ".clean") } @@ -342,7 +342,7 @@ task refine_assembly_with_aligned_reads { Int? min_coverage=3 Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-assemble:2.1.16.0" + String docker="quay.io/broadinstitute/viral-assemble:2.1.16.1" } parameter_meta { @@ -389,9 +389,16 @@ task refine_assembly_with_aligned_reads { refined.fasta "${sample_name}.fasta" "${sample_name}" # collect variant counts - bcftools filter -e "FMT/DP<${min_coverage}" -S . "${sample_name}.sites.vcf.gz" -Ou | bcftools filter -i "AC>1" -Ou > "${sample_name}.diffs.vcf" - bcftools filter -i 'TYPE="snp"' "${sample_name}.diffs.vcf" | bcftools query -f '%POS\n' | wc -l | tee num_snps - bcftools filter -i 'TYPE!="snp"' "${sample_name}.diffs.vcf" | bcftools query -f '%POS\n' | wc -l | tee num_indels + if (( $(cat refined.fasta | wc -l) > 1 )); then + bcftools filter -e "FMT/DP<${min_coverage}" -S . "${sample_name}.sites.vcf.gz" -Ou | bcftools filter -i "AC>1" -Ou > "${sample_name}.diffs.vcf" + bcftools filter -i 'TYPE="snp"' "${sample_name}.diffs.vcf" | bcftools query -f '%POS\n' | wc -l | tee num_snps + bcftools filter -i 'TYPE!="snp"' "${sample_name}.diffs.vcf" | bcftools query -f '%POS\n' | wc -l | tee num_indels + else + # empty output + echo "0" > num_snps + echo "0" > num_indels + cp "${sample_name}.sites.vcf.gz" "${sample_name}.diffs.vcf" + fi # collect figures of merit set +o pipefail # grep will exit 1 if it fails to find the pattern @@ -434,7 +441,7 @@ task refine { Int? min_coverage=1 Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-assemble:2.1.16.0" + String docker="quay.io/broadinstitute/viral-assemble:2.1.16.1" String assembly_basename=basename(basename(assembly_fasta, ".fasta"), ".scaffold") } @@ -504,7 +511,7 @@ task refine_2x_and_plot { String? plot_coverage_novoalign_options="-r Random -l 40 -g 40 -x 20 -t 100 -k" Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-assemble:2.1.16.0" + String docker="quay.io/broadinstitute/viral-assemble:2.1.16.1" # do this in two steps in case the input doesn't actually have "cleaned" in the name String sample_name = basename(basename(reads_unmapped_bam, ".bam"), ".cleaned") @@ -636,7 +643,7 @@ task run_discordance { String out_basename = "run" Int min_coverage=4 - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { diff --git a/pipes/WDL/tasks/tasks_demux.wdl b/pipes/WDL/tasks/tasks_demux.wdl index 5ccb32fec..4323c8764 100644 --- a/pipes/WDL/tasks/tasks_demux.wdl +++ b/pipes/WDL/tasks/tasks_demux.wdl @@ -6,7 +6,7 @@ task merge_tarballs { String out_filename Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { @@ -19,17 +19,17 @@ task merge_tarballs { file_utils.py --version | tee VERSION file_utils.py merge_tarballs \ - ${out_filename} ${sep=' ' tar_chunks} \ + ~{out_filename} ~{sep=' ' tar_chunks} \ --loglevel=DEBUG } output { - File combined_tar = "${out_filename}" + File combined_tar = "~{out_filename}" String viralngs_version = read_string("VERSION") } runtime { - docker: "${docker}" + docker: docker memory: select_first([machine_mem_gb, 7]) + " GB" cpu: 16 disks: "local-disk 2625 LOCAL" @@ -38,6 +38,47 @@ task merge_tarballs { } } +task samplesheet_rename_ids { + input { + File old_sheet + File? rename_map + String old_id_col = 'internal_id' + String new_id_col = 'external_id' + } + String new_base = basename(old_sheet, '.txt') + command <<< + python3 << CODE + import csv + + # read in the rename_map file + old_to_new = {} + with open('~{default="/dev/null" rename_map}', 'rt') as inf: + for row in csv.DictReader(inf, delimiter='\t'): + old_to_new[row['~{old_id_col}']] = row['~{new_id_col}'] + + # change all ids in the sample column to new ids + with open('~{old_sheet}', 'rt') as inf: + reader = csv.DictReader(inf, delimiter='\t') + with open('~{new_base}.renamed.txt', 'w', newline='') as outf: + writer = csv.DictWriter(outf, reader.fieldnames, delimiter='\t', dialect=csv.unix_dialect, quoting=csv.QUOTE_MINIMAL) + writer.writeheader() + for row in reader: + row['sample'] = old_to_new.get(row['sample'], row['sample']) + writer.writerow(row) + CODE + >>> + output { + File new_sheet = '~{new_base}.renamed.txt' + } + runtime { + docker: "python:slim" + memory: "1 GB" + cpu: 1 + disks: "local-disk 50 HDD" + dx_instance_type: "mem1_ssd1_v2_x2" + } +} + task illumina_demux { input { File flowcell_tgz @@ -60,7 +101,13 @@ task illumina_demux { Boolean? forceGC=true Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" + } + parameter_meta { + flowcell_tgz: { + description: "Illumina BCL directory compressed as tarball. Must contain RunInfo.xml (unless overridden by runinfo), SampleSheet.csv (unless overridden by samplesheet), RTAComplete.txt, and Data/Intensities/BaseCalls/*", + patterns: ["*.tar.gz", ".tar.zst", ".tar.bz2", ".tar.lz4", ".tgz"] + } } command { @@ -77,12 +124,12 @@ task illumina_demux { read_utils.py --version | tee VERSION read_utils.py extract_tarball \ - ${flowcell_tgz} $FLOWCELL_DIR \ + ~{flowcell_tgz} $FLOWCELL_DIR \ --loglevel=DEBUG # if we are overriding the RunInfo file, use the path of the file provided. Otherwise find the file - if [ -n "${runinfo}" ]; then - RUNINFO_FILE="${runinfo}" + if [ -n "~{runinfo}" ]; then + RUNINFO_FILE="~{runinfo}" else # full RunInfo.xml path RUNINFO_FILE="$(find $FLOWCELL_DIR -type f -name RunInfo.xml | head -n 1)" @@ -157,45 +204,48 @@ task illumina_demux { # use the passed-in (or default) WDL value first, then fall back to the auto-scaled value # if the result of this is null (nothing is passed in, no autoscaled value, no param is passed to the command) - if [ -n "${minimumBaseQuality}" ]; then demux_min_base_quality="${minimumBaseQuality}"; else demux_min_base_quality="$demux_min_base_quality"; fi + if [ -n "~{minimumBaseQuality}" ]; then demux_min_base_quality="~{minimumBaseQuality}"; else demux_min_base_quality="$demux_min_base_quality"; fi if [ -n "$demux_min_base_quality" ]; then demux_min_base_quality="--minimum_base_quality=$demux_min_base_quality";fi - if [ -n "${threads}" ]; then demux_threads="${threads}"; else demux_threads="$demux_threads"; fi + if [ -n "~{threads}" ]; then demux_threads="~{threads}"; else demux_threads="$demux_threads"; fi if [ -n "$demux_threads" ]; then demux_threads="--threads=$demux_threads"; fi - if [ -n "${maxReadsInRamPerTile}" ]; then max_reads_in_ram_per_tile="${maxReadsInRamPerTile}"; else max_reads_in_ram_per_tile="$max_reads_in_ram_per_tile"; fi + if [ -n "~{maxReadsInRamPerTile}" ]; then max_reads_in_ram_per_tile="~{maxReadsInRamPerTile}"; else max_reads_in_ram_per_tile="$max_reads_in_ram_per_tile"; fi if [ -n "$max_reads_in_ram_per_tile" ]; then max_reads_in_ram_per_tile="--max_reads_in_ram_per_tile=$max_reads_in_ram_per_tile"; fi - if [ -n "${maxRecordsInRam}" ]; then max_records_in_ram="${maxRecordsInRam}"; else max_records_in_ram="$max_records_in_ram"; fi + if [ -n "~{maxRecordsInRam}" ]; then max_records_in_ram="~{maxRecordsInRam}"; else max_records_in_ram="$max_records_in_ram"; fi if [ -n "$max_records_in_ram" ]; then max_records_in_ram="--max_records_in_ram=$max_records_in_ram"; fi # note that we are intentionally setting --threads to about 2x the core # count. seems to still provide speed benefit (over 1x) when doing so. illumina.py illumina_demux \ $FLOWCELL_DIR \ - ${lane} \ + ~{lane} \ . \ - ${'--sampleSheet=' + samplesheet} \ - ${'--runInfo=' + runinfo} \ - ${'--sequencing_center=' + sequencingCenter} \ + ~{'--sampleSheet=' + samplesheet} \ + ~{'--runInfo=' + runinfo} \ + ~{'--sequencing_center=' + sequencingCenter} \ --outMetrics=metrics.txt \ --commonBarcodes=barcodes.txt \ - ${'--flowcell=' + flowcell} \ + ~{'--flowcell=' + flowcell} \ $demux_min_base_quality \ - ${'--max_mismatches=' + maxMismatches} \ - ${'--min_mismatch_delta=' + minMismatchDelta} \ - ${'--max_no_calls=' + maxNoCalls} \ - ${'--read_structure=' + readStructure} \ - ${'--minimum_quality=' + minimumQuality} \ - ${'--run_start_date=' + runStartDate} \ + ~{'--max_mismatches=' + maxMismatches} \ + ~{'--min_mismatch_delta=' + minMismatchDelta} \ + ~{'--max_no_calls=' + maxNoCalls} \ + ~{'--read_structure=' + readStructure} \ + ~{'--minimum_quality=' + minimumQuality} \ + ~{'--run_start_date=' + runStartDate} \ $max_reads_in_ram_per_tile \ $max_records_in_ram \ --JVMmemory="$mem_in_mb"m \ $demux_threads \ - ${true='--force_gc=true' false="--force_gc=false" forceGC} \ + ~{true='--force_gc=true' false="--force_gc=false" forceGC} \ --append_run_id \ --compression_level=5 \ + --out_meta_by_sample meta_by_sample.json \ + --out_meta_by_filename meta_by_fname.json \ + --out_runinfo runinfo.json \ --loglevel=DEBUG illumina.py guess_barcodes --expected_assigned_fraction=0 barcodes.txt metrics.txt barcodes_outliers.txt @@ -208,6 +258,7 @@ task illumina_demux { echo "$(basename $bam .bam)" >> $OUT_BASENAMES done + # fastqc FASTQC_HARDCODED_MEM_PER_THREAD=250 # the value fastqc sets for -Xmx per thread, not adjustable num_cpus=$(nproc) num_bam_files=$(cat $OUT_BASENAMES | wc -l) @@ -215,7 +266,7 @@ task illumina_demux { num_fastqc_threads=1 total_ram_needed_mb=250 - # determine the number of fastq jobs + # determine the number of fastqc jobs while [[ $total_ram_needed_mb -lt $mem_in_mb ]] && [[ $num_fastqc_jobs -lt $num_cpus ]] && [[ $num_fastqc_jobs -lt $num_bam_files ]]; do num_fastqc_jobs=$(($num_fastqc_jobs+1)) total_ram_needed_mb=$(($total_ram_needed_mb+$FASTQC_HARDCODED_MEM_PER_THREAD)) @@ -257,10 +308,17 @@ task illumina_demux { Int runtime_sec = ceil(read_float("UPTIME_SEC")) Int cpu_load_15min = ceil(read_float("LOAD_15M")) String viralngs_version = read_string("VERSION") + + Map[String,Map[String,String]] meta_by_sample = read_json('meta_by_sample.json') + Map[String,Map[String,String]] meta_by_filename = read_json('meta_by_fname.json') + Map[String,String] run_info = read_json('runinfo.json') + File meta_by_sample_json = 'meta_by_sample.json' + File meta_by_filename_json = 'meta_by_fname.json' + File run_info_json = 'runinfo.json' } runtime { - docker: "${docker}" + docker: docker memory: select_first([machine_mem_gb, 200]) + " GB" cpu: 32 disks: "local-disk 2625 LOCAL" @@ -269,3 +327,61 @@ task illumina_demux { preemptible: 0 # this is the very first operation before scatter, so let's get it done quickly & reliably } } + +task map_map_setdefault { + input { + File map_map_json + Array[String] sub_keys + } + command <<< + python3 << CODE + import json + sub_keys = '~{sep="*" sub_keys}'.split('*') + with open('~{map_map_json}', 'rt') as inf: + out = json.load(inf) + for k in out.keys(): + for sub_key in sub_keys: + out[k].setdefault(sub_key, "") + with open('out.json', 'wt') as outf: + json.dump(out, outf, indent=2) + CODE + >>> + output { + File out_json = 'out.json' + } + runtime { + docker: "python:slim" + memory: "1 GB" + cpu: 1 + disks: "local-disk 20 HDD" + dx_instance_type: "mem1_ssd1_v2_x2" + } +} + +task merge_maps { + input { + Array[File] maps_jsons + } + command <<< + python3 << CODE + import json + infiles = '~{sep='*' maps_jsons}'.split('*') + out = {} + for fname in infiles: + with open(fname, 'rt') as inf: + out.update(json.load(inf)) + with open('out.json', 'wt') as outf: + json.dump(out, outf, indent=2) + CODE + >>> + output { + Map[String,Map[String,String]] merged = read_json('out.json') + } + runtime { + docker: "python:slim" + memory: "1 GB" + cpu: 1 + disks: "local-disk 20 HDD" + dx_instance_type: "mem1_ssd1_v2_x2" + } +} diff --git a/pipes/WDL/tasks/tasks_interhost.wdl b/pipes/WDL/tasks/tasks_interhost.wdl index 43ff479fc..ce5ebd365 100644 --- a/pipes/WDL/tasks/tasks_interhost.wdl +++ b/pipes/WDL/tasks/tasks_interhost.wdl @@ -142,7 +142,7 @@ task index_ref { File? novocraft_license Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { diff --git a/pipes/WDL/tasks/tasks_ncbi.wdl b/pipes/WDL/tasks/tasks_ncbi.wdl index d72da8b2d..6485634da 100644 --- a/pipes/WDL/tasks/tasks_ncbi.wdl +++ b/pipes/WDL/tasks/tasks_ncbi.wdl @@ -205,7 +205,7 @@ task structured_comments { File? filter_to_ids - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } String out_base = basename(assembly_stats_tsv, '.txt') command <<< @@ -248,6 +248,34 @@ task structured_comments { } } +task prefix_fasta_header { + input { + File genome_fasta + String prefix + } + String out_basename = basename(genome_fasta, ".fasta") + command <<< + set -e + python3 <'): + line = ">{}{}\n".format('~{prefix}', line.rstrip()[1:]) + outf.write(line) + CODE + >>> + output { + File renamed_fasta = "~{out_basename}.fasta" + } + runtime { + docker: "python:slim" + memory: "1 GB" + cpu: 1 + dx_instance_type: "mem1_ssd1_v2_x2" + } +} + task rename_fasta_header { input { File genome_fasta @@ -255,7 +283,7 @@ task rename_fasta_header { String out_basename = basename(genome_fasta, ".fasta") - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { set -e @@ -273,6 +301,77 @@ task rename_fasta_header { } } +task gisaid_meta_prep { + input { + File source_modifier_table + File structured_comments + String out_name + String continent = "North America" + } + command <<< + python3 << CODE + import os.path + import csv + + # lookup table files to dicts + sample_to_cmt = {} + with open('~{structured_comments}', 'rt') as inf: + for row in csv.DictReader(inf, delimiter='\t'): + sample_to_cmt[row['SeqID']] = row + + out_headers = ('submitter', 'fn', 'covv_virus_name', 'covv_type', 'covv_passage', 'covv_collection_date', 'covv_location', 'covv_add_location', 'covv_host', 'covv_add_host_info', 'covv_gender', 'covv_patient_age', 'covv_patient_status', 'covv_specimen', 'covv_outbreak', 'covv_last_vaccinated', 'covv_treatment', 'covv_seq_technology', 'covv_assembly_method', 'covv_coverage', 'covv_orig_lab', 'covv_orig_lab_addr', 'covv_provider_sample_id', 'covv_subm_lab', 'covv_subm_lab_addr', 'covv_subm_sample_id', 'covv_authors', 'covv_comment', 'comment_type') + + with open('~{out_name}', 'wt') as outf: + writer = csv.DictWriter(outf, out_headers, delimiter='\t', dialect=csv.unix_dialect, quoting=csv.QUOTE_MINIMAL) + writer.writeheader() + + with open('~{source_modifier_table}', 'rt') as inf: + for row in csv.DictReader(inf, delimiter='\t'): + writer.writerow({ + 'covv_virus_name': 'hCoV-19/' +row['Sequence_ID'], + 'covv_collection_date': row['collection_date'], + 'covv_location': '~{continent} / ' + row['country'].replace(':',' /'), + + 'covv_type': 'betacoronavirus', + 'covv_passage': 'Original', + 'covv_host': 'Human', + 'covv_gender': 'unknown', + 'covv_patient_age': 'unknown', + 'covv_patient_status': 'unknown', + + 'covv_assembly_method': sample_to_cmt[row['Sequence_ID']]['Assembly Method'], + 'covv_coverage': sample_to_cmt[row['Sequence_ID']]['Coverage'], + 'covv_seq_technology': sample_to_cmt[row['Sequence_ID']]['Sequencing Technology'], + + 'covv_orig_lab': row['collected_by'], + 'covv_subm_lab': 'REQUIRED', + 'covv_authors': 'REQUIRED', + 'covv_orig_lab_addr': 'REQUIRED', + 'covv_subm_lab_addr': 'REQUIRED', + 'submitter': 'REQUIRED', + 'fn': 'REQUIRED', + }) + + #covv_specimen + + assert row['isolation_source'] == 'Clinical' + assert row['host'] == 'Homo sapiens' + assert row['organism'] == 'Severe acute respiratory syndrome coronavirus 2' + assert row['db_xref'] == 'taxon:2697049' + + CODE + >>> + output { + File meta_tsv = "~{out_name}" + } + runtime { + docker: "python:slim" + memory: "1 GB" + cpu: 1 + dx_instance_type: "mem1_ssd1_v2_x2" + } +} + task lookup_table_by_filename { input { String id @@ -301,7 +400,7 @@ task sra_meta_prep { description: "Prepare tables for submission to NCBI's SRA database. This only works on bam files produced by illumina.py illumina_demux --append_run_id in viral-core." } input { - Array[String] cleaned_bam_filepaths + Array[File] cleaned_bam_filepaths File biosample_map Array[File] library_metadata String platform @@ -310,11 +409,13 @@ task sra_meta_prep { Boolean paired String out_name = "sra_metadata.tsv" - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } parameter_meta { cleaned_bam_filepaths: { - description: "Complete path and filename of unaligned bam files containing cleaned (submittable) reads.", + description: "Unaligned bam files containing cleaned (submittable) reads.", + localization_optional: true, + stream: true, patterns: ["*.bam"] } biosample_map: { @@ -415,7 +516,7 @@ task sra_meta_prep { docker: docker memory: "1 GB" cpu: 1 - disks: "local-disk 50 HDD" + disks: "local-disk 100 HDD" dx_instance_type: "mem1_ssd1_v2_x2" } } @@ -453,7 +554,7 @@ task biosample_to_genbank { File biosample_map = "${base}.biosample.map.txt" } runtime { - docker: "${docker}" + docker: docker memory: "1 GB" cpu: 1 dx_instance_type: "mem1_ssd1_v2_x2" @@ -611,7 +712,7 @@ task package_genbank_ftp_submission { String spuid_namespace String account_name - String docker="quay.io/broadinstitute/viral-baseimage:0.1.19" + String docker="quay.io/broadinstitute/viral-baseimage:0.1.20" } command <<< set -e diff --git a/pipes/WDL/tasks/tasks_nextstrain.wdl b/pipes/WDL/tasks/tasks_nextstrain.wdl index 94a52eead..12d76ee6f 100644 --- a/pipes/WDL/tasks/tasks_nextstrain.wdl +++ b/pipes/WDL/tasks/tasks_nextstrain.wdl @@ -30,7 +30,7 @@ task gzcat { input { Array[File] infiles String output_name - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command <<< python3 <>> runtime { - docker : "python" - memory : select_first([machine_mem_gb, 3]) + " GB" - cpu : 1 + docker: "python:slim" + memory: select_first([machine_mem_gb, 3]) + " GB" + cpu: 1 disks: "local-disk 375 LOCAL" dx_instance_type: "mem1_ssd1_v2_x2" } @@ -264,7 +264,7 @@ task filter_sequences_by_length { File sequences_fasta Int min_non_N = 1 - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } parameter_meta { sequences_fasta: { diff --git a/pipes/WDL/tasks/tasks_read_utils.wdl b/pipes/WDL/tasks/tasks_read_utils.wdl index 69dfb1990..31c0df7af 100644 --- a/pipes/WDL/tasks/tasks_read_utils.wdl +++ b/pipes/WDL/tasks/tasks_read_utils.wdl @@ -1,8 +1,39 @@ version 1.0 +task max { + input { + Array[Int] list + Int default_empty=0 + } + command <<< + python3 << CODE + inlist = '~{sep="*" list}'.split('*') + print(str(max(map(int, [x for x in inlist if x]), default = ~{default_empty}))) + CODE + >>> + output { + Int max = read_int(stdout()) + } + runtime { + docker: "python:slim" + memory: "1 GB" + cpu: 1 + disks: "local-disk 10 HDD" + dx_instance_type: "mem1_ssd1_v2_x2" + } +} + task group_bams_by_sample { input { - Array[String] bam_filepaths + Array[File] bam_filepaths + } + parameter_meta { + bam_filepaths: { + description: "all bam files", + localization_optional: true, + stream: true, + patterns: ["*.bam"] + } } command <<< python3 << CODE @@ -32,11 +63,61 @@ task group_bams_by_sample { CODE >>> output { - Array[Array[String]+] grouped_bam_filepaths = read_tsv('grouped_bams') - Array[String] sample_names = read_lines('sample_names') + Array[Array[File]+] grouped_bam_filepaths = read_tsv('grouped_bams') + Array[String] sample_names = read_lines('sample_names') } runtime { - docker: "python" + docker: "python:slim" + memory: "1 GB" + cpu: 1 + disks: "local-disk 100 HDD" + dx_instance_type: "mem1_ssd1_v2_x2" + } +} + +task get_sample_meta { + input { + Array[File] samplesheets_extended + + String docker="quay.io/broadinstitute/viral-core:2.1.18" + } + command <<< + python3 << CODE + import os.path + import csv + import json + import util.file + + # WDL arrays to python arrays + library_metadata = '~{sep="*" samplesheets_extended}'.split('*') + + # lookup table files to dicts + meta = {} + meta_cols = ('sample','amplicon_set','control') + for col in meta_cols: + meta[col] = {} + for libfile in library_metadata: + with open(libfile, 'rt') as inf: + for row in csv.DictReader(inf, delimiter='\t'): + sanitized = util.file.string_to_file_name(row['sample']) + for col in meta_cols: + meta[col].setdefault(sanitized, '') + if row.get(col): + meta[col][sanitized] = row[col] + + # write outputs + for col in meta_cols: + with open(col, 'wt') as outf: + json.dump(meta[col], outf, indent=2) + CODE + >>> + output { + Map[String,String] original_names = read_json('sample') + Map[String,String] amplicon_set = read_json('amplicon_set') + Map[String,String] control = read_json('control') + } + runtime { + docker: docker memory: "1 GB" cpu: 1 disks: "local-disk 50 HDD" @@ -44,6 +125,7 @@ task group_bams_by_sample { } } + task merge_and_reheader_bams { meta { description: "Merge and/or reheader bam files using a mapping table. This task can modify read group tags in a BAM header field for single BAM files or as part of a BAM merge operation. The output is a single BAM file (given one or more input BAMs) and a three-column tab delimited text table that defines: the field, the old value, and the new value (e.g. LB, old_lib_name, new_lib_name or SM, old_sample_name, new_sample_name)" @@ -55,7 +137,7 @@ task merge_and_reheader_bams { File? reheader_table String out_basename - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { @@ -115,7 +197,7 @@ task rmdup_ubam { String method="mvicuna" Int? machine_mem_gb - String? docker="quay.io/broadinstitute/viral-core:2.1.16" + String? docker="quay.io/broadinstitute/viral-core:2.1.18" } parameter_meta { @@ -169,7 +251,7 @@ task downsample_bams { Boolean? deduplicateAfter=false Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { @@ -228,7 +310,7 @@ task FastqToUBAM { String? platform_name String? sequencing_center - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } parameter_meta { fastq_1: { description: "Unaligned read1 file in fastq format", patterns: ["*.fastq", "*.fastq.gz", "*.fq", "*.fq.gz"] } diff --git a/pipes/WDL/tasks/tasks_reports.wdl b/pipes/WDL/tasks/tasks_reports.wdl index 777c7c1ab..b718bf15b 100644 --- a/pipes/WDL/tasks/tasks_reports.wdl +++ b/pipes/WDL/tasks/tasks_reports.wdl @@ -10,7 +10,7 @@ task plot_coverage { Boolean bin_large_plots=false String? binning_summary_statistic="max" # max or min - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { @@ -85,7 +85,7 @@ task coverage_report { Array[File] mapped_bam_idx # optional.. speeds it up if you provide it, otherwise we auto-index String out_report_name="coverage_report.txt" - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { @@ -144,7 +144,7 @@ task fastqc { input { File reads_bam - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } String reads_basename=basename(reads_bam, ".bam") @@ -177,7 +177,7 @@ task align_and_count { Int topNHits = 3 Int? machine_mem_gb - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } String reads_basename=basename(reads_bam, ".bam") @@ -188,20 +188,22 @@ task align_and_count { read_utils.py --version | tee VERSION - ln -s ${reads_bam} ${reads_basename}.bam + ln -s "${reads_bam}" "${reads_basename}.bam" read_utils.py minimap2_idxstats \ - ${reads_basename}.bam \ - ${ref_db} \ - --outStats ${reads_basename}.count.${ref_basename}.txt.unsorted \ + "${reads_basename}.bam" \ + "${ref_db}" \ + --outStats "${reads_basename}.count.${ref_basename}.txt.unsorted" \ --loglevel=DEBUG - sort -b -r -n -k3 ${reads_basename}.count.${ref_basename}.txt.unsorted > ${reads_basename}.count.${ref_basename}.txt - head -n ${topNHits} ${reads_basename}.count.${ref_basename}.txt > ${reads_basename}.count.${ref_basename}.top_${topNHits}_hits.txt + sort -b -r -n -k3 "${reads_basename}.count.${ref_basename}.txt.unsorted" > "${reads_basename}.count.${ref_basename}.txt" + head -n ${topNHits} "${reads_basename}.count.${ref_basename}.txt" > "${reads_basename}.count.${ref_basename}.top_${topNHits}_hits.txt" + head -1 "${reads_basename}.count.${ref_basename}.txt" | cut -f 1 > "${reads_basename}.count.${ref_basename}.top.txt" } output { File report = "${reads_basename}.count.${ref_basename}.txt" File report_top_hits = "${reads_basename}.count.${ref_basename}.top_${topNHits}_hits.txt" + String top_hit_id = read_string("${reads_basename}.count.${ref_basename}.top.txt") String viralngs_version = read_string("VERSION") } @@ -220,7 +222,7 @@ task align_and_count_summary { String output_prefix="count_summary" - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { @@ -393,7 +395,7 @@ task tsv_join { String id_col String out_basename - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { @@ -420,7 +422,7 @@ task tsv_stack { input { Array[File]+ input_tsvs String out_basename - String docker="quay.io/broadinstitute/viral-core:2.1.16" + String docker="quay.io/broadinstitute/viral-core:2.1.18" } command { @@ -450,7 +452,7 @@ task compare_two_genomes { File genome_two String out_basename - String docker="quay.io/broadinstitute/viral-assemble:2.1.16.0" + String docker="quay.io/broadinstitute/viral-assemble:2.1.16.1" } command { diff --git a/pipes/WDL/tasks/tasks_sarscov2.wdl b/pipes/WDL/tasks/tasks_sarscov2.wdl index 03bb6ff9d..17dfda84a 100644 --- a/pipes/WDL/tasks/tasks_sarscov2.wdl +++ b/pipes/WDL/tasks/tasks_sarscov2.wdl @@ -29,8 +29,9 @@ task nextclade_one_sample { cp "~{basename}".nextclade.tsv input.tsv python3 <= min_reads_per_bam) { + File cleaned_bam_passing = deplete.cleaned_bam + } + if (deplete.depletion_read_count_post < min_reads_per_bam) { + File empty_bam = raw_reads + } } #### SRA submission prep - call ncbi.sra_meta_prep { - input: - cleaned_bam_filepaths = deplete.cleaned_bam, - out_name = "sra_metadata-~{basename(flowcell_tgz, '.tar.gz')}.tsv" + if(defined(biosample_map)) { + call ncbi.sra_meta_prep { + input: + cleaned_bam_filepaths = select_all(cleaned_bam_passing), + biosample_map = select_first([biosample_map]), + library_metadata = samplesheet_rename_ids.new_sheet, + platform = "ILLUMINA", + out_name = "sra_metadata-~{basename(flowcell_tgz, '.tar.gz')}.tsv" + } } #### summary stats @@ -72,14 +117,20 @@ workflow demux_deplete { counts_txt = spikein.report } + # TO DO: flag all libraries where highest spike-in is not what was expected in extended samplesheet + output { Array[File] raw_reads_unaligned_bams = flatten(illumina_demux.raw_reads_unaligned_bams) Array[Int] read_counts_raw = deplete.depletion_read_count_pre - Array[File] cleaned_reads_unaligned_bams = deplete.cleaned_bam + Map[String,Map[String,String]] meta_by_filename = meta_filename.merged + Map[String,Map[String,String]] meta_by_sample = meta_sample.merged + + Array[File] cleaned_reads_unaligned_bams = select_all(cleaned_bam_passing) + Array[File] cleaned_bams_tiny = select_all(empty_bam) Array[Int] read_counts_depleted = deplete.depletion_read_count_post - File sra_metadata = sra_meta_prep.sra_metadata + File? sra_metadata = sra_meta_prep.sra_metadata Array[File] demux_metrics = illumina_demux.metrics Array[File] demux_commonBarcodes = illumina_demux.commonBarcodes diff --git a/pipes/WDL/workflows/sarscov2_illumina_full.wdl b/pipes/WDL/workflows/sarscov2_illumina_full.wdl index 85b5934e0..614bb6f3c 100644 --- a/pipes/WDL/workflows/sarscov2_illumina_full.wdl +++ b/pipes/WDL/workflows/sarscov2_illumina_full.wdl @@ -20,50 +20,85 @@ workflow sarscov2_illumina_full { } parameter_meta { + flowcell_tgz: { + description: "Illumina BCL directory compressed as tarball. Must contain RunInfo.xml, SampleSheet.csv, RTAComplete.txt, and Data/Intensities/BaseCalls/*", + patterns: ["*.tar.gz", ".tar.zst", ".tar.bz2", ".tar.lz4", ".tgz"] + } + samplesheets: { + description: "Custom formatted 'extended' format tsv samplesheets that will override any SampleSheet.csv in the illumina BCL directory. Must supply one file per lane of the flowcell, and must provide them in lane order. Required tsv column headings are: sample, library_id_per_sample, barcode_1, barcode_2 (if paired reads, omit if single-end), library_strategy, library_source, library_selection, design_description. 'sample' must correspond to a biological sample. 'sample' x 'library_id_per_sample' must be unique within a samplesheet and correspond to independent libraries from the same original sample. barcode_1 and barcode_2 must correspond to the actual index sequence. Remaining columns must follow strict ontology: see 3rd tab of https://www.ncbi.nlm.nih.gov/core/assets/sra/files/SRA_metadata_acc_example.xlsx for controlled vocabulary and term definitions.", + patterns: ["*.txt", "*.tsv"] + } + sample_rename_map: { + description: "If 'samples' need to be renamed, provide a two-column tsv that contains at least the following columns: internal_id, external_id. All samples will be renamed prior to analysis. Any samples described in the samplesheets that are not present in sample_rename_map will be unaltered. If this is omitted, no samples will be renamed.", + patterns: ["*.txt", "*.tsv"] + } + reference_fasta: { description: "Reference genome to align reads to.", patterns: ["*.fasta"] } - ampseq_trim_coords_bed: { + amplicon_bed_prefix: { description: "amplicon primers to trim in reference coordinate space (0-based BED format)", patterns: ["*.bed"] } biosample_attributes: { - description: "A post-submission attributes file from NCBI BioSample, which is available at https://submit.ncbi.nlm.nih.gov/subs/ and clicking on 'Download attributes file with BioSample accessions'.", + description: "A post-submission attributes file from NCBI BioSample, which is available at https://submit.ncbi.nlm.nih.gov/subs/ and clicking on 'Download attributes file with BioSample accessions'. The 'sample_name' column must match the external_ids used in sample_rename_map (or internal ids if sample_rename_map is omitted).", patterns: ["*.txt", "*.tsv"] } } input { - File flowcell_tgz - Array[File]+ samplesheets ## must be in lane order! + File flowcell_tgz + Array[File]+ samplesheets ## must be in lane order! File reference_fasta - File ampseq_trim_coords_bed + String amplicon_bed_prefix File biosample_attributes - File? rename_map + File? sample_rename_map Int taxid = 2697049 Int min_genome_bases = 20000 + Int min_reads_per_bam = 100 + String gisaid_prefix = 'hCoV-19/' File spikein_db - File trim_clip_db Array[File]? bmtaggerDbs # .tar.gz, .tgz, .tar.bz2, .tar.lz4, .fasta, or .fasta.gz Array[File]? blastDbs # .tar.gz, .tgz, .tar.bz2, .tar.lz4, .fasta, or .fasta.gz Array[File]? bwaDbs } - #### demux each lane + #### demux each lane (rename samples if requested) scatter(lane_sheet in zip(range(length(samplesheets)), samplesheets)) { - call demux.illumina_demux as illumina_demux { + call demux.samplesheet_rename_ids { + input: + old_sheet = lane_sheet.right, + rename_map = sample_rename_map + } + call demux.illumina_demux { input: flowcell_tgz = flowcell_tgz, lane = lane_sheet.left + 1, - samplesheet = lane_sheet.right + samplesheet = samplesheet_rename_ids.new_sheet } + call demux.map_map_setdefault as meta_default_sample { + input: + map_map_json = illumina_demux.meta_by_sample_json, + sub_keys = ["amplicon_set", "control"] + } + call demux.map_map_setdefault as meta_default_filename { + input: + map_map_json = illumina_demux.meta_by_filename_json, + sub_keys = ["spike_in"] + } + } + call demux.merge_maps as meta_sample { + input: maps_jsons = meta_default_sample.out_json + } + call demux.merge_maps as meta_filename { + input: maps_jsons = meta_default_filename.out_json } #### human depletion & spike-in counting for all files @@ -80,13 +115,25 @@ workflow sarscov2_illumina_full { blastDbs = blastDbs, bwaDbs = bwaDbs } + + if (deplete.depletion_read_count_post >= min_reads_per_bam) { + File cleaned_bam_passing = deplete.cleaned_bam + } + if (deplete.depletion_read_count_post < min_reads_per_bam) { + File empty_bam = raw_reads + } + + # TO DO: flag all libraries where highest spike-in is not what was expected in extended samplesheet } #### SRA submission prep call ncbi.sra_meta_prep { input: - cleaned_bam_filepaths = deplete.cleaned_bam, - out_name = "sra_metadata-~{basename(flowcell_tgz, '.tar.gz')}.tsv" + cleaned_bam_filepaths = select_all(cleaned_bam_passing), + biosample_map = biosample_attributes, + library_metadata = samplesheet_rename_ids.new_sheet, + out_name = "sra_metadata-~{basename(flowcell_tgz, '.tar.gz')}.tsv", + platform = "ILLUMINA" } #### summary stats @@ -105,27 +152,48 @@ workflow sarscov2_illumina_full { counts_txt = spikein.report } - ### assembly and analyses per biosample + ### gather data by biosample call read_utils.group_bams_by_sample { input: - bam_filepaths = deplete.cleaned_bam + bam_filepaths = select_all(cleaned_bam_passing) } + ### assembly and analyses per biosample scatter(name_reads in zip(group_bams_by_sample.sample_names, group_bams_by_sample.grouped_bam_filepaths)) { + Boolean ampseq = (meta_sample.merged[name_reads.left]["amplicon_set"] != "") + String orig_name = meta_sample.merged[name_reads.left]["sample_original"] + # assemble genome + if (ampseq) { + String trim_coords_bed = amplicon_bed_prefix + meta_sample.merged[name_reads.left]["amplicon_set"] + ".bed" + } call assemble_refbased.assemble_refbased { input: reads_unmapped_bams = name_reads.right, reference_fasta = reference_fasta, - sample_name = name_reads.left - # TO DO: lookup skip_mark_dupes and trim_coords_bed from metadata + sample_name = name_reads.left, + aligner = "minimap2", + skip_mark_dupes = ampseq, + trim_coords_bed = trim_coords_bed, + min_coverage = if ampseq then 20 else 3 + } + + # log controls + if (meta_sample.merged[name_reads.left]["control"] == 'NTC') { + Int ntc_bases = assemble_refbased.assembly_length_unambiguous } # for genomes that somewhat assemble if (assemble_refbased.assembly_length_unambiguous >= min_genome_bases) { - File passing_assemblies = assemble_refbased.assembly_fasta - String passing_assembly_ids = name_reads.left - Array[String] assembly_meta = [name_reads.left, assemble_refbased.assembly_mean_coverage] + call ncbi.rename_fasta_header { + input: + genome_fasta = assemble_refbased.assembly_fasta, + new_name = orig_name + } + + File passing_assemblies = rename_fasta_header.renamed_fasta + String passing_assembly_ids = orig_name + Array[String] assembly_cmt = [orig_name, "Broad viral-ngs v. " + illumina_demux.viralngs_version[0], assemble_refbased.assembly_mean_coverage] # lineage assignment call sarscov2.nextclade_one_sample { @@ -144,18 +212,54 @@ workflow sarscov2_illumina_full { } if (vadr.num_alerts==0) { File submittable_genomes = passing_assemblies - String submittable_id = name_reads.left + String submittable_id = orig_name } if (vadr.num_alerts>0) { - String failed_annotation_id = name_reads.left + String failed_annotation_id = orig_name } } if (assemble_refbased.assembly_length_unambiguous < min_genome_bases) { - String failed_assembly_id = name_reads.left + String failed_assembly_id = orig_name + } + + Map[String,String?] assembly_stats = { + 'sample_orig': orig_name, + 'sample': name_reads.left, + 'amplicon_set': meta_sample.merged[name_reads.left]["amplicon_set"], + 'assembly_mean_coverage': assemble_refbased.assembly_mean_coverage, + 'nextclade_clade': nextclade_one_sample.nextclade_clade , + 'nextclade_aa_subs': nextclade_one_sample.aa_subs_csv, + 'nextclade_aa_dels': nextclade_one_sample.aa_dels_csv, + 'pangolin_clade': pangolin_one_sample.pangolin_clade + } + Map[String,File?] assembly_files = { + 'assembly_fasta': assemble_refbased.assembly_fasta, + 'coverage_plot': assemble_refbased.align_to_ref_merged_coverage_plot, + 'aligned_bam': assemble_refbased.align_to_ref_merged_aligned_trimmed_only_bam, + 'replicate_discordant_vcf': assemble_refbased.replicate_discordant_vcf, + 'nextclade_tsv': nextclade_one_sample.nextclade_tsv, + 'pangolin_csv': pangolin_one_sample.pangolin_csv, + 'vadr_tgz': vadr.outputs_tgz + } + Map[String,Int?] assembly_metrics = { + 'assembly_length_unambiguous': assemble_refbased.assembly_length_unambiguous, + 'dist_to_ref_snps': assemble_refbased.dist_to_ref_snps, + 'dist_to_ref_indels': assemble_refbased.dist_to_ref_indels, + 'replicate_concordant_sites': assemble_refbased.replicate_concordant_sites, + 'replicate_discordant_snps': assemble_refbased.replicate_discordant_snps, + 'replicate_discordant_indels': assemble_refbased.replicate_discordant_indels, + 'num_read_groups': assemble_refbased.num_read_groups, + 'num_libraries': assemble_refbased.num_libraries, + 'vadr_num_alerts': vadr.num_alerts } + } - # TO DO: add some error checks / filtration if NTCs assemble above some length or above other genomes + # TO DO: filter out genomes from submission that are less than ntc_bases.max + call read_utils.max as ntc { + input: + list = select_all(ntc_bases) + } ### prep genbank submission call nextstrain.concatenate as submit_genomes { @@ -172,7 +276,7 @@ workflow sarscov2_illumina_full { } call ncbi.structured_comments { input: - assembly_stats_tsv = write_tsv(flatten([[['SeqID','Coverage']],select_all(assembly_meta)])), + assembly_stats_tsv = write_tsv(flatten([[['SeqID','Assembly Method','Coverage']],select_all(assembly_cmt)])), filter_to_ids = write_lines(select_all(submittable_id)) } call ncbi.package_genbank_ftp_submission { @@ -182,9 +286,25 @@ workflow sarscov2_illumina_full { structured_comment_table = structured_comments.structured_comment_table } + ### prep gisaid submission + call ncbi.prefix_fasta_header as prefix_gisaid { + input: + genome_fasta = submit_genomes.combined, + prefix = gisaid_prefix + } + call ncbi.gisaid_meta_prep { + input: + source_modifier_table = biosample_to_genbank.genbank_source_modifier_table, + structured_comments = structured_comments.structured_comment_table, + out_name = "gisaid_meta.tsv" + } + output { Array[File] raw_reads_unaligned_bams = flatten(illumina_demux.raw_reads_unaligned_bams) - Array[File] cleaned_reads_unaligned_bams = deplete.cleaned_bam + Array[File] cleaned_reads_unaligned_bams = select_all(cleaned_bam_passing) + Array[File] cleaned_bams_tiny = select_all(empty_bam) + + Map[String,Map[String,String]] meta_by_filename = meta_filename.merged Array[Int] read_counts_raw = deplete.depletion_read_count_pre Array[Int] read_counts_depleted = deplete.depletion_read_count_post @@ -195,6 +315,8 @@ workflow sarscov2_illumina_full { Array[File] passing_assemblies_fasta = select_all(passing_assemblies) Array[File] submittable_assemblies_fasta = select_all(submittable_genomes) + Int max_ntc_bases = ntc.max + Array[File] demux_metrics = illumina_demux.metrics Array[File] demux_commonBarcodes = illumina_demux.commonBarcodes Array[File] demux_outlierBarcodes = illumina_demux.outlierBarcodes @@ -203,13 +325,9 @@ workflow sarscov2_illumina_full { File multiqc_report_cleaned = multiqc_cleaned.multiqc_report File spikein_counts = spike_summary.count_summary - # TO DO: bundle outputs into structs or some meaningful thing - #String nextclade_clade = nextclade_one_sample.nextclade_clade - #File nextclade_tsv = nextclade_one_sample.nextclade_tsv - #String nextclade_aa_subs = nextclade_one_sample.aa_subs_csv - #String nextclade_aa_dels = nextclade_one_sample.aa_dels_csv - #String pangolin_clade = pangolin_one_sample.pangolin_clade - #File pangolin_csv = pangolin_one_sample.pangolin_csv + Array[Map[String,String?]] per_assembly_stats = assembly_stats + Array[Map[String,File?]] per_assembly_files = assembly_files + Array[Map[String,Int?]] per_assembly_metrics = assembly_metrics File submission_zip = package_genbank_ftp_submission.submission_zip File submission_xml = package_genbank_ftp_submission.submission_xml @@ -217,10 +335,14 @@ workflow sarscov2_illumina_full { Array[File] vadr_outputs = select_all(vadr.outputs_tgz) File genbank_source_table = biosample_to_genbank.genbank_source_modifier_table + File gisaid_fasta = prefix_gisaid.renamed_fasta + File gisaid_meta_tsv = gisaid_meta_prep.meta_tsv + Array[String] assembled_ids = select_all(passing_assembly_ids) Array[String] submittable_ids = select_all(submittable_id) Array[String] failed_assembly_ids = select_all(failed_assembly_id) Array[String] failed_annotation_ids = select_all(failed_annotation_id) + Int num_read_files = length(select_all(cleaned_bam_passing)) Int num_assembled = length(select_all(passing_assemblies)) Int num_failed_assembly = length(select_all(failed_assembly_id)) Int num_submittable = length(select_all(submittable_id)) diff --git a/requirements-modules.txt b/requirements-modules.txt index fafd06aec..cfe37f698 100644 --- a/requirements-modules.txt +++ b/requirements-modules.txt @@ -1,5 +1,5 @@ -broadinstitute/viral-core=2.1.16 -broadinstitute/viral-assemble=2.1.16.0 +broadinstitute/viral-core=2.1.18 +broadinstitute/viral-assemble=2.1.16.1 broadinstitute/viral-classify=2.1.16.0 broadinstitute/viral-phylo=2.1.16.0 broadinstitute/beast-beagle-cuda=1.10.5pre