From 911686b92d848b749aac87d7c54372225711a2d9 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Wed, 14 Dec 2022 17:36:58 -0500 Subject: [PATCH 001/297] Added workflow to convert a BAM file to fastq files --- .dockstore.yml | 4 ++++ wdl/SRBamToFq.wdl | 27 ++++++++++++++++++++++ wdl/tasks/SRUtils.wdl | 52 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 wdl/SRBamToFq.wdl create mode 100644 wdl/tasks/SRUtils.wdl diff --git a/.dockstore.yml b/.dockstore.yml index d9902316f..79335eacb 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -108,3 +108,7 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/PBMASIsoSeqDemultiplex.wdl testParameterFiles: +- name: SRBamToFq + subclass: wdl + primaryDescriptorPath: /wdl/SRBamToFq.wdl + testParameterFiles: diff --git a/wdl/SRBamToFq.wdl b/wdl/SRBamToFq.wdl new file mode 100644 index 000000000..6727f0b1d --- /dev/null +++ b/wdl/SRBamToFq.wdl @@ -0,0 +1,27 @@ +version 1.0 + +import "tasks/SRUtils.wdl" as SRUtils +import "tasks/Finalize.wdl" as FF + +workflow SRBamToFq { + input { + File bam + String participant_name + + String gcs_out_root_dir + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRBamToFq/~{participant_name}" + + call SRUtils.BamToFq { input: bam = bam, prefix = participant_name } + + call FF.FinalizeToFile as FinalizeFqEnd1 { input: outdir = outdir, file = BamToFq.fq_end1 } + call FF.FinalizeToFile as FinalizeFqEnd2 { input: outdir = outdir, file = BamToFq.fq_end2 } + call FF.FinalizeToFile as FinalizeFqUnpaired { input: outdir = outdir, file = BamToFq.fq_unpaired } + + output { + File fq_end1 = FinalizeFqEnd1.gcs_path + File fq_end2 = FinalizeFqEnd2.gcs_path + File fq_unpaired = FinalizeFqUnpaired.gcs_path + } +} \ No newline at end of file diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl new file mode 100644 index 000000000..9ccd20958 --- /dev/null +++ b/wdl/tasks/SRUtils.wdl @@ -0,0 +1,52 @@ +version 1.0 + +import "Structs.wdl" + +task BamToFq { + input { + File bam + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 3*ceil(size(bam, "GB")) + + command <<< + set -euxo pipefail + + samtools sort -n ~{bam} | samtools bam2fq \ + -n \ + -s /dev/null \ + -1 ~{prefix}.end1.fq.gz \ + -2 ~{prefix}.end2.fq.gz \ + -0 ~{prefix}.unpaired.fq.gz + >>> + + output { + File fq_end1 = "~{prefix}.end1.fq.gz" + File fq_end2 = "~{prefix}.end1.fq.gz" + File fq_unpaired = "~{prefix}.unpaired.fq.gz" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-utils:0.1.8" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From f0caf2381b845b4d828a6909f9b476a4f279f571 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Wed, 14 Dec 2022 21:49:56 -0500 Subject: [PATCH 002/297] Workflow to index a BAM file --- .dockstore.yml | 4 ++++ wdl/SRIndexBam.wdl | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 wdl/SRIndexBam.wdl diff --git a/.dockstore.yml b/.dockstore.yml index 79335eacb..c3953cfa6 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -112,3 +112,7 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/SRBamToFq.wdl testParameterFiles: +- name: SRIndexBam + subclass: wdl + primaryDescriptorPath: /wdl/SRIndexBam.wdl + testParameterFiles: diff --git a/wdl/SRIndexBam.wdl b/wdl/SRIndexBam.wdl new file mode 100644 index 000000000..4f376876c --- /dev/null +++ b/wdl/SRIndexBam.wdl @@ -0,0 +1,19 @@ +version 1.0 + +import "tasks/Utils.wdl" as Utils +import "tasks/Finalize.wdl" as FF + +workflow SRIndexBam { + input { + File bam + String outdir + } + + call Utils.Index { input: bam = bam } + + call FF.FinalizeToFile as FinalizeBamIndex { input: outdir = outdir, file = Index.bai } + + output { + File bai = FinalizeBamIndex.gcs_path + } +} \ No newline at end of file From fc15f6952f69b3134e6b087b62f15e50025dcf3c Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Wed, 14 Dec 2022 22:56:39 -0500 Subject: [PATCH 003/297] More disk space for BamToFq task --- wdl/tasks/SRUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 9ccd20958..ebde23302 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -10,7 +10,7 @@ task BamToFq { RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 3*ceil(size(bam, "GB")) + Int disk_size = 1 + 4*ceil(size(bam, "GB")) command <<< set -euxo pipefail From 62fbd6c0eabe25794bcf0e7445a110555383b107 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Wed, 14 Dec 2022 23:22:52 -0500 Subject: [PATCH 004/297] Whole genome processing pipeline for short read data --- wdl/SRWholeGenome.wdl | 133 ++++++++++++++++++++++++++++ wdl/tasks/CallVariantsIllumina.wdl | 71 +++++++++++++++ wdl/tasks/DeepVariant.wdl | 136 +++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+) create mode 100644 wdl/SRWholeGenome.wdl create mode 100644 wdl/tasks/CallVariantsIllumina.wdl create mode 100644 wdl/tasks/DeepVariant.wdl diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl new file mode 100644 index 000000000..d952d414a --- /dev/null +++ b/wdl/SRWholeGenome.wdl @@ -0,0 +1,133 @@ +version 1.0 + +###################################################################################### +## A workflow that performs single sample variant calling on Illumina reads from +## one or more flow cells. The workflow merges multiple samples into a single BAM +## prior to variant calling. +###################################################################################### + +import "tasks/SRUtils.wdl" as PB +import "tasks/Utils.wdl" as Utils +import "tasks/CallVariantsIllumina.wdl" as VAR +import "tasks/Finalize.wdl" as FF + +import "tasks/SampleLevelAlignedMetrics.wdl" as COV + +workflow SRWholeGenome { + input { + Array[File] aligned_bams + Array[File] aligned_bais + + File? bed_to_compute_coverage + + File ref_map_file + + String participant_name + + String gcs_out_root_dir + + Boolean call_small_variants = true + + Boolean? run_dv_pepper_analysis = true + Int? dvp_threads = 32 + Int? dvp_memory = 128 + } + + Map[String, String] ref_map = read_map(ref_map_file) + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRWholeGenome/~{participant_name}" + + # gather across (potential multiple) input CCS BAMs + if (length(aligned_bams) > 1) { + scatter (pair in zip(aligned_bams, aligned_bais)) { + call Utils.InferSampleName {input: bam = pair.left, bai = pair.right} + } + call Utils.CheckOnSamplenames {input: sample_names = InferSampleName.sample_name} + + call Utils.MergeBams as MergeAllReads { input: bams = aligned_bams, prefix = participant_name } + } + + File bam = select_first([MergeAllReads.merged_bam, aligned_bams[0]]) + File bai = select_first([MergeAllReads.merged_bai, aligned_bais[0]]) + + call COV.SampleLevelAlignedMetrics as coverage { + input: + aligned_bam = bam, + aligned_bai = bai, + ref_fasta = ref_map['fasta'], + bed_to_compute_coverage = bed_to_compute_coverage + } + + String dir = outdir + "/alignments" + + call FF.FinalizeToFile as FinalizeBam { input: outdir = dir, file = bam, name = "~{participant_name}.bam" } + call FF.FinalizeToFile as FinalizeBai { input: outdir = dir, file = bai, name = "~{participant_name}.bam.bai" } + + if (defined(bed_to_compute_coverage)) { call FF.FinalizeToFile as FinalizeRegionalCoverage { input: outdir = dir, file = select_first([coverage.bed_cov_summary]) } } + + #################################################################################################### + if (call_small_variants) { + + # verify arguments are provided + if (call_small_variants) { + if (! defined(run_dv_pepper_analysis)) {call Utils.StopWorkflow as run_dv_pepper_analysis_not_provided {input: reason = "Unprovided arg run_dv_pepper_analysis"}} + if (! defined(dvp_threads)) {call Utils.StopWorkflow as dvp_threads_not_provided {input: reason = "Unprovided arg dvp_threads"}} + } + + call VAR.CallVariants { + input: + bam = bam, + bai = bai, + sample_id = participant_name, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + + prefix = participant_name, + + call_small_variants = call_small_variants, + + run_dv_pepper_analysis = select_first([run_dv_pepper_analysis]), + dvp_threads = select_first([dvp_threads]), + dvp_memory = select_first([dvp_memory]), + } + + String smalldir = outdir + "/variants/small" + + if (call_small_variants) { + if (select_first([run_dv_pepper_analysis])) { + call FF.FinalizeToFile as FinalizeDVPepperVcf { input: outdir = smalldir, file = select_first([CallVariants.dvp_vcf])} + call FF.FinalizeToFile as FinalizeDVPepperTbi { input: outdir = smalldir, file = select_first([CallVariants.dvp_tbi])} + call FF.FinalizeToFile as FinalizeDVPepperGVcf { input: outdir = smalldir, file = select_first([CallVariants.dvp_g_vcf])} + call FF.FinalizeToFile as FinalizeDVPepperGTbi { input: outdir = smalldir, file = select_first([CallVariants.dvp_g_tbi])} + } + } + } + + output { + File aligned_bam = FinalizeBam.gcs_path + File aligned_bai = FinalizeBai.gcs_path + + Float aligned_num_reads = coverage.aligned_num_reads + Float aligned_num_bases = coverage.aligned_num_bases + Float aligned_frac_bases = coverage.aligned_frac_bases + Float aligned_est_fold_cov = coverage.aligned_est_fold_cov + + Float aligned_read_length_mean = coverage.aligned_read_length_mean + Float aligned_read_length_median = coverage.aligned_read_length_median + Float aligned_read_length_stdev = coverage.aligned_read_length_stdev + Float aligned_read_length_N50 = coverage.aligned_read_length_N50 + + Float average_identity = coverage.average_identity + Float median_identity = coverage.median_identity + + File? bed_cov_summary = FinalizeRegionalCoverage.gcs_path + + ######################################## + + File? dvp_vcf = FinalizeDVPepperVcf.gcs_path + File? dvp_tbi = FinalizeDVPepperTbi.gcs_path + File? dvp_g_vcf = FinalizeDVPepperGVcf.gcs_path + File? dvp_g_tbi = FinalizeDVPepperGTbi.gcs_path + } +} diff --git a/wdl/tasks/CallVariantsIllumina.wdl b/wdl/tasks/CallVariantsIllumina.wdl new file mode 100644 index 000000000..a5cfca4cf --- /dev/null +++ b/wdl/tasks/CallVariantsIllumina.wdl @@ -0,0 +1,71 @@ +version 1.0 + +import "Utils.wdl" +import "VariantUtils.wdl" +import "DeepVariant.wdl" as DV + +workflow CallVariants { + meta { + description: "A workflow for calling small variants from an Illumina BAM file." + } + input { + File bam + File bai + + String prefix + String sample_id + + File ref_fasta + File ref_fasta_fai + File ref_dict + + Boolean call_small_variants + Boolean call_small_vars_on_mitochondria = true + + Boolean run_dv_pepper_analysis + Int? dvp_threads + Int? dvp_memory + } + + ###################################################################### + # Block for small variants handling + ###################################################################### + + call Utils.RandomZoneSpewer as arbitrary {input: num_of_zones = 3} + + # todo: merge the two scattering scheme into a better one + if (call_small_variants) { + # Scatter by chromosome + Array[String] default_filter = ['random', 'chrUn', 'decoy', 'alt', 'HLA', 'EBV'] + Array[String] use_filter = if (call_small_vars_on_mitochondria) then default_filter else flatten([['chrM'],default_filter]) + call Utils.MakeChrIntervalList as SmallVariantsScatterPrepp { + input: + ref_dict = ref_dict, + filter = use_filter + } + + # size-balanced scatter + if (run_dv_pepper_analysis) { + call DV.DeepVariant { + input: + bam = bam, + bai = bai, + ref_fasta = ref_fasta, + ref_fasta_fai = ref_fasta_fai, + + pepper_threads = select_first([dvp_threads]), + pepper_memory = select_first([dvp_memory]), + dv_threads = select_first([dvp_threads]), + dv_memory = select_first([dvp_memory]), + zones = arbitrary.zones + } + } + } + + output { + File? dvp_g_vcf = DeepVariant.gVCF + File? dvp_g_tbi = DeepVariant.gVCF_tbi + File? dvp_vcf = DeepVariant.VCF + File? dvp_tbi = DeepVariant.VCF_tbi + } +} diff --git a/wdl/tasks/DeepVariant.wdl b/wdl/tasks/DeepVariant.wdl new file mode 100644 index 000000000..c614f051d --- /dev/null +++ b/wdl/tasks/DeepVariant.wdl @@ -0,0 +1,136 @@ +version 1.0 + +####################################################### +# This pipeline calls small variants using DeepVariant. +####################################################### + +import "Structs.wdl" + + +workflow DeepVariant { + + meta { + description: "Workflow for getting VCF and gVCF from DeepVariant. Note VCF is un-phased." + } + + input { + File bam + File bai + + File ref_fasta + File ref_fasta_fai + + Int pepper_threads + Int pepper_memory + + Int dv_threads + Int dv_memory + + String zones = "us-central1-b us-central1-c" + } + + parameter_meta { + # when running large scale workflows, we sometimes see errors like the following + # A resource limit has delayed the operation: generic::resource_exhausted: allocating: selecting resources: selecting region and zone: + # no available zones: 2763 LOCAL_SSD_TOTAL_GB (738/30000 available) usage too high + zones: "select which zone (GCP) to run this task" + } + + call DV as deep_variant { + input: + bam = bam, + bai = bai, + ref_fasta = ref_fasta, + ref_fasta_fai = ref_fasta_fai, + threads = dv_threads, + memory = dv_memory, + zones = zones + } + + output { + File VCF = deep_variant.VCF + File VCF_tbi = deep_variant.VCF_tbi + + File gVCF = deep_variant.gVCF + File gVCF_tbi = deep_variant.gVCF_tbi + } +} + +task DV { + + input { + File bam + File bai + + File ref_fasta + File ref_fasta_fai + + Int threads + Int memory + String zones + + RuntimeAttr? runtime_attr_override + } + + String prefix = basename(bam, ".bam") + ".deepvariant" + String output_root = "/cromwell_root/dv_output" + + Int bam_sz = ceil(size(bam, "GB")) + Boolean is_big_bam = bam_sz > 100 + Int inflation_factor = if (is_big_bam) then 10 else 5 + Int minimal_disk = 1000 + Int disk_size = if inflation_factor * bam_sz > minimal_disk then inflation_factor * bam_sz else minimal_disk + + command <<< + set -euxo pipefail + + num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) + + mkdir -p "~{output_root}" + + /opt/deepvariant/bin/run_deepvariant \ + --model_type=WGS \ + --ref=~{ref_fasta} \ + --reads=~{bam} \ + --output_vcf="~{output_root}/~{prefix}.vcf.gz" \ + --output_gvcf="~{output_root}/~{prefix}.g.vcf.gz" \ + --num_shards="${num_core}" + + find "~{output_root}/" -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g' \ + > "~{output_root}/dir_structure.txt" + >>> + + output { + File output_dir_structure = "~{output_root}/dir_structure.txt" + + File VCF = "~{output_root}/~{prefix}.vcf.gz" + File VCF_tbi = "~{output_root}/~{prefix}.vcf.gz.tbi" + + File gVCF = "~{output_root}/~{prefix}.g.vcf.gz" + File gVCF_tbi = "~{output_root}/~{prefix}.g.vcf.gz.tbi" + + File visual_report_html = "~{output_root}/~{prefix}.visual_report.html" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: threads, + mem_gb: memory, + disk_gb: disk_size, + boot_disk_gb: 100, + preemptible_tries: 3, + max_retries: 0, + docker: "google/deepvariant:1.4.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + zones: zones + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From 7e4ba54cfd0b915d2ea5dd0b2bd70722dc8e3149 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Wed, 14 Dec 2022 23:24:42 -0500 Subject: [PATCH 005/297] Added short read whole genome pipeline to Dockstore --- .dockstore.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.dockstore.yml b/.dockstore.yml index c3953cfa6..a621b802c 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -116,3 +116,7 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/SRIndexBam.wdl testParameterFiles: +- name: SRWholeGenome + subclass: wdl + primaryDescriptorPath: /wdl/SRWholeGenome.wdl + testParameterFiles: From 0ad9235ea1622af9391ffc4e1c3c13f0781ccb9d Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 16 Dec 2022 14:11:43 -0500 Subject: [PATCH 006/297] Intermediate Commit. Added several tasks for Short Read processing. --- wdl/SRFlowcell.wdl | 97 +++++++++++++++++++ wdl/tasks/SRUtils.wdl | 211 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 wdl/SRFlowcell.wdl diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl new file mode 100644 index 000000000..218c00a00 --- /dev/null +++ b/wdl/SRFlowcell.wdl @@ -0,0 +1,97 @@ +version 1.0 + +########################################################################################## +## A workflow that preprocesses short read flowcell data in preparation for variant calling. +## This workflow contains the following steps: +## 1) Sam -> Fastq (if necessary) +## 2) Alignment to reference with bwa-mem2 (https://github.com/bwa-mem2/bwa-mem2) +## 3) Mark Duplicate reads +## 4) Recalibrate base quality scores. +########################################################################################## + +import "tasks/SRUtils.wdl" as SRUTIL +import "tasks/Utils.wdl" as Utils +import "tasks/AlignedMetrics.wdl" as AM +import "tasks/Finalize.wdl" as FF + +workflow SRFlowcell { + input { + File bam + File bai + + String SM + String LB + + File ref_map_file + + Int? num_shards + String dir_prefix + + String gcs_out_root_dir + + Boolean DEBUG_MODE = false + } + + parameter_meta { + bam: "GCS path to unmapped bam" + bai: "GCS path to bai index for unmapped bam" + ref_map_file: "table indicating reference sequence and auxillary file locations" + + SM: "the value to place in the BAM read group's SM field" + LB: "the value to place in the BAM read group's LB (library) field" + + num_shards: "number of shards into which fastq files should be batched" + dir_prefix: "directory prefix for output files" + + DEBUG_MODE: "[default valued] enables debugging tasks / subworkflows (default: false)" + + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" + } + + # Get ref info: + Map[String, String] ref_map = read_map(ref_map_file) + + # Call our timestamp so we can store outputs without clobbering previous runs: + call Utils.GetCurrentTimestampString as WdlExecutionStartTimestamp { input: } + + # Create an outdir: + String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}/" + WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}" + + # Convert input SAM/BAM to FASTQ: + call SRUTIL.Bam2FqPicard as Bam2Fastq { + input: + bam = bam, + prefix = SM + } + + # Align reads to reference with BWA-MEM2: + call SRUTIL.BwaMem2 as AlignReads { + input: + name_sorted_fastq = Bam2Fastq.fastq, + ref_fasta = ref_map["fasta"], + ref_fasta_index = ref_map["fai"], + ref_dict = ref_map["dict"], + ref_0123 = ref_map["0123"], + ref_amb = ref_map["amb"], + ref_ann = ref_map["ann"], + ref_bwt = ref_map["bwt"], + ref_pac = ref_map["pac"], + prefix = SM + ".sorted" + } + + # Merge aligned reads and unaligned reads: + call SRUTIL.MergeBamAlignment as MergeBamAlignment { + input: + aligned_bam = AlignReads.bam, + unaligned_bam = bam, + ref_fasta = ref_map["fasta"], + ref_fasta_index = ref_map["fai"], + ref_dict = ref_map["dict"], + } + +# Mark Duplicates +# +# Fingerprinting? +# +# BQSR +} \ No newline at end of file diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index ebde23302..20a18a533 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -50,3 +50,214 @@ task BamToFq { docker: select_first([runtime_attr.docker, default_attr.docker]) } } + +task Bam2FqPicard { + input { + File bam + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size(bam, "GB")) + + command <<< + set -euxo pipefail + + java -Xms8192m -Xmx8192m -jar /usr/gitc/picard.jar \ + SamToFastq \ + INPUT=~{bam} \ + FASTQ=~{prefix}.fastq \ + INTERLEAVE=true \ + NON_PF=true + >>> + + output { + File fastq = "~{prefix}.fastq" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 16, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "broadinstitute/picard:2.27.5" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + +} + +task BwaMem2 { + input { + File name_sorted_fastq + + File ref_fasta + File ref_fasta_index + File ref_dict + File ref_0123 + File ref_amb + File ref_ann + File ref_bwt + File ref_pac + + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size(name_sorted_fastq, "GB")) + + 4*ceil(size(ref_fasta, "GB")) + + 4*ceil(size(ref_fasta_index, "GB")) + + 4*ceil(size(ref_dict, "GB")) + + 4*ceil(size(ref_amb, "GB")) + + 4*ceil(size(ref_ann, "GB")) + + 4*ceil(size(ref_bwt, "GB")) + + 4*ceil(size(ref_pac, "GB")) + + 4*ceil(size(ref_0123, "GB")) + + command <<< + set -euxo pipefail + + # Make sure we use all our proocesors: + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + let np=${np}-1 + + # Breakdown of the arguments: + # -K INT process INT input bases in each batch regardless of nThreads (for reproducibility) [] + # -p Smart pairing. If two adjacent reads have the same + # name, they are considered to form a read pair. This + # way, paired-end and single-end reads can be mixed in a + # single FASTA/Q stream. + # -v INT verbose level: 1=error, 2=warning, 3=message, 4+=debugging [3] + # -t INT number of threads [1] + # -Y use soft clipping for supplementary alignments + + bwa-mem2 mem -K 100000000 -p -v 3 -t ${np} -Y ~{ref_fasta} ~{name_sorted_fastq} | \ + samtools view -1 - > ~{prefix}.bam + + samtools index -@${np} ~{prefix}.bam + >>> + + output { + File bam = "~{prefix}.bam" + File bai = "~{prefix}.bam.bai" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 16, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-utils:0.2.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task MergeBamAlignment { + input { + File aligned_bam + File unaligned_bam + + File ref_fasta + File ref_fasta_index + File ref_dict + + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size(aligned_bam, "GB")) + + 4*ceil(size(unaligned_bam, "GB")) + + 4*ceil(size(ref_fasta, "GB")) + + 4*ceil(size(ref_fasta_index, "GB")) + + 4*ceil(size(ref_dict, "GB")) + + command <<< + set -euxo pipefail + + # Make sure we use all our proocesors: + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + let np=${np}-1 + + java -Dsamjdk.compression_level=2 -Xms8192m -Xmx8192m -jar /usr/gitc/picard.jar \ + MergeBamAlignment \ + VALIDATION_STRINGENCY=SILENT \ + EXPECTED_ORIENTATIONS=FR \ + ATTRIBUTES_TO_RETAIN=X0 \ + ATTRIBUTES_TO_REMOVE=NM \ + ATTRIBUTES_TO_REMOVE=MD \ + ALIGNED_BAM=~{aligned_bam} \ + UNMAPPED_BAM=~{unaligned_bam} \ + OUTPUT=~{prefix}.bam \ + REFERENCE_SEQUENCE=~{ref_fasta} \ + SORT_ORDER="unsorted" \ + IS_BISULFITE_SEQUENCE=false \ + ALIGNED_READS_ONLY=false \ + CLIP_ADAPTERS=false \ + MAX_RECORDS_IN_RAM=2000000 \ + ADD_MATE_CIGAR=true \ + MAX_INSERTIONS_OR_DELETIONS=-1 \ + PRIMARY_ALIGNMENT_STRATEGY=MostDistant \ + PROGRAM_RECORD_ID="bwamem" \ + PROGRAM_GROUP_VERSION="${BWA_VERSION}" \ + PROGRAM_GROUP_COMMAND_LINE="bwa-mem2 mem -K 100000000 -p -v 3 -t 15 -Y" \ + PROGRAM_GROUP_NAME="bwa-mem2" \ + UNMAPPED_READ_STRATEGY=COPY_TO_TAG \ + ALIGNER_PROPER_PAIR_FLAGS=true \ + UNMAP_CONTAMINANT_READS=true \ + ADD_PG_TAG_TO_READS=false + + samtools index -@${np} ~{prefix}.bam + >>> + + output { + File bam = "~{prefix}.bam" + File bai = "~{prefix}.bam.bai" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 16, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "broadinstitute/picard:2.27.5" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From ab958ac2c1f067d54f12aa40d0cef177d83e6b80 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 19 Dec 2022 16:06:57 -0500 Subject: [PATCH 007/297] Finished initial implementation of SRFlowcell. (UNTESTED) --- wdl/SRFlowcell.wdl | 55 ++++++++++++++-- wdl/tasks/SRUtils.wdl | 148 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 192 insertions(+), 11 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 218c00a00..08a71beab 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -48,6 +48,15 @@ workflow SRFlowcell { gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" } + #################################### + # _____ _ + # |_ _|_ _ ___| | _____ + # | |/ _` / __| |/ / __| + # | | (_| \__ \ <\__ \ + # |_|\__,_|___/_|\_\___/ + # + #################################### + # Get ref info: Map[String, String] ref_map = read_map(ref_map_file) @@ -76,7 +85,7 @@ workflow SRFlowcell { ref_ann = ref_map["ann"], ref_bwt = ref_map["bwt"], ref_pac = ref_map["pac"], - prefix = SM + ".sorted" + prefix = SM + ".aligned" } # Merge aligned reads and unaligned reads: @@ -89,9 +98,47 @@ workflow SRFlowcell { ref_dict = ref_map["dict"], } -# Mark Duplicates -# + # Mark Duplicates + call SRUTIL.MarkDuplicates as MarkDuplicates { + input: + input_bam = MergeBamAlignment.bam, + prefix = SM + ".aligned.markDuplicates." + } + + # Sort Duplicate Marked Bam: + call Utils.SortBam as SortAlignedDuplicateMarkedBam { + input: + input_bam = MarkDuplicates.bam, + prefix = SM + ".aligned.markDuplicates.sorted" + } + # Fingerprinting? # -# BQSR + # BQSR + call SRUTIL.BaseRecalibrator as BaseRecalibrator { + input: + input_bam = SortAlignedDuplicateMarkedBam.sorted_bam, + input_bam_index = SortAlignedDuplicateMarkedBam.sorted_bai, + + ref_fasta = ref_map["fasta"], + ref_fasta_index = ref_map["fai"], + ref_dict = ref_map["dict"], + + known_sites_vcf = ref_map["known_sites_vcf"], + known_sites_index = ref_map["known_sites_vcf_idx"], + + prefix = SM + ".baseRecalibratorReport" + } + + ############################################ + # ___ _ _ + # / _ \ _ _| |_ _ __ _ _| |_ + # | | | | | | | __| '_ \| | | | __| + # | |_| | |_| | |_| |_) | |_| | |_ + # \___/ \__,_|\__| .__/ \__,_|\__| + # |_| + ############################################ + output { + + } } \ No newline at end of file diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 20a18a533..e3a8b1dc9 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -64,7 +64,7 @@ task Bam2FqPicard { command <<< set -euxo pipefail - java -Xms8192m -Xmx8192m -jar /usr/gitc/picard.jar \ + java -Xms8192m -Xmx30768m -jar /usr/picard/picard.jar \ SamToFastq \ INPUT=~{bam} \ FASTQ=~{prefix}.fastq \ @@ -84,7 +84,7 @@ task Bam2FqPicard { boot_disk_gb: 10, preemptible_tries: 1, max_retries: 1, - docker: "broadinstitute/picard:2.27.5" + docker: "us.gcr.io/broad-gotc-prod/picard-cloud:2.26.10" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -204,7 +204,7 @@ task MergeBamAlignment { np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') let np=${np}-1 - java -Dsamjdk.compression_level=2 -Xms8192m -Xmx8192m -jar /usr/gitc/picard.jar \ + java -Dsamjdk.compression_level=2 -Xms8192m -Xmx30768m -jar /usr/picard/picard.jar \ MergeBamAlignment \ VALIDATION_STRINGENCY=SILENT \ EXPECTED_ORIENTATIONS=FR \ @@ -231,13 +231,10 @@ task MergeBamAlignment { ALIGNER_PROPER_PAIR_FLAGS=true \ UNMAP_CONTAMINANT_READS=true \ ADD_PG_TAG_TO_READS=false - - samtools index -@${np} ~{prefix}.bam >>> output { File bam = "~{prefix}.bam" - File bai = "~{prefix}.bam.bai" } ######################### @@ -248,7 +245,7 @@ task MergeBamAlignment { boot_disk_gb: 10, preemptible_tries: 1, max_retries: 1, - docker: "broadinstitute/picard:2.27.5" + docker: "us.gcr.io/broad-gotc-prod/picard-cloud:2.26.10" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -261,3 +258,140 @@ task MergeBamAlignment { docker: select_first([runtime_attr.docker, default_attr.docker]) } } + +task MarkDuplicates { + input { + File input_bam + + String prefix + + # The program default for READ_NAME_REGEX is appropriate in nearly every case. + # Sometimes we wish to supply "null" in order to turn off optical duplicate detection + # This can be desirable if you don't mind the estimated library size being wrong and optical duplicate detection is taking >7 days and failing + String? read_name_regex + + Float? sorting_collection_size_ratio + + RuntimeAttr? runtime_attr_override + } + + Int compression_level = 2 + Int java_memory_size_mb = 30768 + + Int disk_size = 1 + 4*ceil(size(input_bam, "GB")) + + # Task is assuming query-sorted input so that the Secondary and Supplementary reads get marked correctly + # This works because the output of BWA is query-grouped and therefore, so is the output of MergeBamAlignment. + # While query-grouped isn't actually query-sorted, it's good enough for MarkDuplicates with ASSUME_SORT_ORDER="queryname" + + command { + java -Dsamjdk.compression_level=~{compression_level} -Xms~{java_memory_size_mb}m -jar /usr/picard/picard.jar \ + MarkDuplicates \ + INPUT=~{input_bam} \ + OUTPUT=~{prefix}.bam \ + METRICS_FILE=~{prefix}.metrics.txt \ + VALIDATION_STRINGENCY=SILENT \ + ~{"READ_NAME_REGEX=" + read_name_regex} \ + ~{"SORTING_COLLECTION_SIZE_RATIO=" + sorting_collection_size_ratio} \ + OPTICAL_DUPLICATE_PIXEL_DISTANCE=2500 \ + ASSUME_SORT_ORDER="queryname" \ + CLEAR_DT="false" \ + ADD_PG_TAG_TO_READS=false + } + + output { + File bam = "~{prefix}.bam" + File metrics = "~{prefix}.metrics.txt" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 16, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gotc-prod/picard-cloud:2.26.10" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + + +# Generate Base Quality Score Recalibration (BQSR) model +task BaseRecalibrator { + input { + File input_bam + File input_bam_index + + File ref_dict + File ref_fasta + File ref_fasta_index + + File known_sites_vcf + File known_sites_index + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size(input_bam, "GB")) + + 4*ceil(size(input_bam_index, "GB")) + + 2*ceil(size(ref_dict, "GB")) + + 2*ceil(size(ref_fasta, "GB")) + + 2*ceil(size(ref_fasta_index, "GB")) + + 2*ceil(size(known_sites_vcf, "GB")) + + 2*ceil(size(known_sites_index, "GB")) + + parameter_meta { + input_bam: { + localization_optional: true + } + } + + command { + gatk --java-options "-XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10 -XX:+PrintFlagsFinal \ + -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCDetails \ + -Xloggc:gc_log.log -Xms5000m -Xmx5500m" \ + BaseRecalibrator \ + -R ~{ref_fasta} \ + -I ~{input_bam} \ + --use-original-qualities \ + -O ~{prefix}.txt \ + --known-sites ~{known_sites_vcf} + } + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 16, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gotc-prod/picard-cloud:2.26.10" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + output { + File recalibration_report = "~{prefix}.txt" + } +} + From 22be39b7a2f0c1c501b6de411f4cecc388cb1459 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 19 Dec 2022 17:00:23 -0500 Subject: [PATCH 008/297] Now theoretically SRFlowcell.wdl works. (UNTESTED) --- wdl/SRFlowcell.wdl | 81 +++++++++++++++++++++++++++++++++++++++---- wdl/tasks/SRUtils.wdl | 81 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 7 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 08a71beab..7f2541937 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -24,7 +24,6 @@ workflow SRFlowcell { File ref_map_file - Int? num_shards String dir_prefix String gcs_out_root_dir @@ -96,25 +95,26 @@ workflow SRFlowcell { ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], ref_dict = ref_map["dict"], + prefix = SM + ".aligned.merged" } # Mark Duplicates call SRUTIL.MarkDuplicates as MarkDuplicates { input: input_bam = MergeBamAlignment.bam, - prefix = SM + ".aligned.markDuplicates." + prefix = SM + ".aligned.merged.markDuplicates." } # Sort Duplicate Marked Bam: call Utils.SortBam as SortAlignedDuplicateMarkedBam { input: input_bam = MarkDuplicates.bam, - prefix = SM + ".aligned.markDuplicates.sorted" + prefix = SM + ".aligned.merged.markDuplicates.sorted" } # Fingerprinting? -# - # BQSR + + # Recalibrate Base Scores: call SRUTIL.BaseRecalibrator as BaseRecalibrator { input: input_bam = SortAlignedDuplicateMarkedBam.sorted_bam, @@ -125,11 +125,80 @@ workflow SRFlowcell { ref_dict = ref_map["dict"], known_sites_vcf = ref_map["known_sites_vcf"], - known_sites_index = ref_map["known_sites_vcf_idx"], + known_sites_index = ref_map["known_sites_index"], prefix = SM + ".baseRecalibratorReport" } + call SRUTIL.ApplyBQSR as ApplyBQSR { + input: + input_bam = SortAlignedDuplicateMarkedBam.sorted_bam, + input_bam_index = SortAlignedDuplicateMarkedBam.sorted_bai, + + ref_fasta = ref_map["fasta"], + ref_fasta_index = ref_map["fai"], + ref_dict = ref_map["dict"], + + recalibration_report = BaseRecalibrator.recalibration_report, + + prefix = SM + ".aligned.merged.markDuplicates.sorted.BQSR" + } + + ############################################ + # _____ _ _ _ + # | ___(_)_ __ __ _| (_)_______ + # | |_ | | '_ \ / _` | | |_ / _ \ + # | _| | | | | | (_| | | |/ / __/ + # |_| |_|_| |_|\__,_|_|_/___\___| + # + ############################################ + File keyfile = BaseRecalibrator.recalibration_report + String reads_dir = outdir + "/reads" + String metrics_dir = outdir + "/metrics" + + # Finalize our reads first: + call FF.FinalizeToDir as FinalizeUnalignedReads { + input: + outdir = reads_dir + "/unaligned", + files = + [ + bam, + bai, + Bam2Fastq.fastq + ], + keyfile = keyfile + } + + call FF.FinalizeToDir as FinalizeAlignedReads { + input: + outdir = reads_dir + "/aligned", + files = + [ + AlignReads.bam, + AlignReads.bai, + MergeBamAlignment.bam, + MarkDuplicates.bam, + SortAlignedDuplicateMarkedBam.sorted_bam, + SortAlignedDuplicateMarkedBam.sorted_bai, + ApplyBQSR.recalibrated_bam, + ApplyBQSR.recalibrated_bai, + ], + keyfile = keyfile + } + + # Finalize our metrics: + call FF.FinalizeToDir as FinalizeMetrics { + input: + outdir = metrics_dir, + files = + [ + MarkDuplicates.metrics, + BaseRecalibrator.recalibration_report, + ApplyBQSR.recalibrated_bam_checksum, + ], + keyfile = keyfile + } + ############################################ # ___ _ _ # / _ \ _ _| |_ _ __ _ _| |_ diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index e3a8b1dc9..b1bf81e8f 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -378,7 +378,7 @@ task BaseRecalibrator { boot_disk_gb: 10, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gotc-prod/picard-cloud:2.26.10" + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -395,3 +395,82 @@ task BaseRecalibrator { } } +task ApplyBQSR { + input { + File input_bam + File input_bam_index + + File ref_dict + File ref_fasta + File ref_fasta_index + + File recalibration_report + + Boolean bin_base_qualities = true + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int compression_level = 2 + Int java_memory_size_mb = 30768 + + parameter_meta { + input_bam: { + localization_optional: true + } + } + + Int disk_size = 1 + 4*ceil(size(input_bam, "GB")) + + 4*ceil(size(input_bam_index, "GB")) + + 2*ceil(size(ref_dict, "GB")) + + 2*ceil(size(ref_fasta, "GB")) + + 2*ceil(size(ref_fasta_index, "GB")) + + 2*ceil(size(recalibration_report, "GB")) + + command { + gatk --java-options "-XX:+PrintFlagsFinal -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps \ + -XX:+PrintGCDetails -Xloggc:gc_log.log \ + -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10 -Dsamjdk.compression_level=~{compression_level} -Xms8192m -Xmx~{java_memory_size_mb}m" \ + ApplyBQSR \ + --create-output-bam-md5 \ + --add-output-sam-program-record \ + -R ~{ref_fasta} \ + -I ~{input_bam} \ + --use-original-qualities \ + -O ~{prefix}.bam \ + -bqsr ~{recalibration_report} \ + ~{true='--static-quantized-quals 10' false='' bin_base_qualities} \ + ~{true='--static-quantized-quals 20' false='' bin_base_qualities} \ + ~{true='--static-quantized-quals 30' false='' bin_base_qualities} \ + + + } + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 16, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + output { + File recalibrated_bam = "~{prefix}.bam" + File recalibrated_bai = "~{prefix}.bam.bai" + File recalibrated_bam_checksum = "~{prefix}.bam.md5" + } +} + From f9053ad11f9f422e23bff6efd403731099095250 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 21 Dec 2022 13:06:14 -0500 Subject: [PATCH 009/297] SRFlowcell.wdl now works. Started on HaplotypeCaller integration. --- wdl/SRFlowcell.wdl | 107 +++++++++++++++++---------- wdl/tasks/AlignedMetrics.wdl | 45 ++++++++++++ wdl/tasks/HaplotypeCaller.wdl | 88 ++++++++++++++++++++++ wdl/tasks/SRUtils.wdl | 133 +++++++++++++++++++++++++++++++--- wdl/tasks/Utils.wdl | 4 +- 5 files changed, 326 insertions(+), 51 deletions(-) create mode 100644 wdl/tasks/HaplotypeCaller.wdl diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 7f2541937..9fd00255e 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -60,22 +60,29 @@ workflow SRFlowcell { Map[String, String] ref_map = read_map(ref_map_file) # Call our timestamp so we can store outputs without clobbering previous runs: - call Utils.GetCurrentTimestampString as WdlExecutionStartTimestamp { input: } + call Utils.GetCurrentTimestampString as t_001_WdlExecutionStartTimestamp { input: } # Create an outdir: - String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}/" + WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}" + String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}/" + t_001_WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}" + + # Convert the given bam to a uBAM (needed for previous aligned data): + call SRUTIL.RevertSam as t_002_RevertSam { + input: + input_bam = bam, + prefix = SM + ".revertSam" + } # Convert input SAM/BAM to FASTQ: - call SRUTIL.Bam2FqPicard as Bam2Fastq { + call SRUTIL.Bam2FqPicard as t_003_Bam2Fastq { input: - bam = bam, + bam = t_002_RevertSam.bam, prefix = SM } # Align reads to reference with BWA-MEM2: - call SRUTIL.BwaMem2 as AlignReads { + call SRUTIL.BwaMem2 as t_004_AlignReads { input: - name_sorted_fastq = Bam2Fastq.fastq, + name_sorted_fastq = t_003_Bam2Fastq.fastq, ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], ref_dict = ref_map["dict"], @@ -84,41 +91,50 @@ workflow SRFlowcell { ref_ann = ref_map["ann"], ref_bwt = ref_map["bwt"], ref_pac = ref_map["pac"], + mark_short_splits_as_secondary = true, prefix = SM + ".aligned" } # Merge aligned reads and unaligned reads: - call SRUTIL.MergeBamAlignment as MergeBamAlignment { + call SRUTIL.MergeBamAlignment as t_005_MergeBamAlignment { input: - aligned_bam = AlignReads.bam, - unaligned_bam = bam, + aligned_bam = t_004_AlignReads.bam, + unaligned_bam = t_002_RevertSam.bam, ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], ref_dict = ref_map["dict"], prefix = SM + ".aligned.merged" } + # This was for GATK 3. We probably don't need it now. +# # Fix mates: +# call SRUTIL.FixMate as t_006_FixMate { +# input: +# input_bam = t_005_MergeBamAlignment.bam, +# prefix = SM + ".aligned.merged.fixmates" +# } + # Mark Duplicates - call SRUTIL.MarkDuplicates as MarkDuplicates { + call SRUTIL.MarkDuplicates as t_006_MarkDuplicates { input: - input_bam = MergeBamAlignment.bam, - prefix = SM + ".aligned.merged.markDuplicates." + input_bam = t_005_MergeBamAlignment.bam, + prefix = SM + ".aligned.merged.markDuplicates" } # Sort Duplicate Marked Bam: - call Utils.SortBam as SortAlignedDuplicateMarkedBam { + call Utils.SortBam as t_007_SortAlignedDuplicateMarkedBam { input: - input_bam = MarkDuplicates.bam, + input_bam = t_006_MarkDuplicates.bam, prefix = SM + ".aligned.merged.markDuplicates.sorted" } # Fingerprinting? # Recalibrate Base Scores: - call SRUTIL.BaseRecalibrator as BaseRecalibrator { + call SRUTIL.BaseRecalibrator as t_008_BaseRecalibrator { input: - input_bam = SortAlignedDuplicateMarkedBam.sorted_bam, - input_bam_index = SortAlignedDuplicateMarkedBam.sorted_bai, + input_bam = t_007_SortAlignedDuplicateMarkedBam.sorted_bam, + input_bam_index = t_007_SortAlignedDuplicateMarkedBam.sorted_bai, ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], @@ -130,20 +146,34 @@ workflow SRFlowcell { prefix = SM + ".baseRecalibratorReport" } - call SRUTIL.ApplyBQSR as ApplyBQSR { + call SRUTIL.ApplyBQSR as t_009_ApplyBQSR { input: - input_bam = SortAlignedDuplicateMarkedBam.sorted_bam, - input_bam_index = SortAlignedDuplicateMarkedBam.sorted_bai, + input_bam = t_007_SortAlignedDuplicateMarkedBam.sorted_bam, + input_bam_index = t_007_SortAlignedDuplicateMarkedBam.sorted_bai, ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], ref_dict = ref_map["dict"], - recalibration_report = BaseRecalibrator.recalibration_report, + recalibration_report = t_008_BaseRecalibrator.recalibration_report, prefix = SM + ".aligned.merged.markDuplicates.sorted.BQSR" } + ############################################# + # __ __ _ _ + # | \/ | ___| |_ _ __(_) ___ ___ + # | |\/| |/ _ \ __| '__| |/ __/ __| + # | | | | __/ |_| | | | (__\__ \ + # |_| |_|\___|\__|_| |_|\___|___/ + # + ############################################# + + call AM.SamStats as t_010_SamStats { + input: + bam = t_009_ApplyBQSR.recalibrated_bam + } + ############################################ # _____ _ _ _ # | ___(_)_ __ __ _| (_)_______ @@ -152,53 +182,52 @@ workflow SRFlowcell { # |_| |_|_| |_|\__,_|_|_/___\___| # ############################################ - File keyfile = BaseRecalibrator.recalibration_report + File keyfile = t_010_SamStats.sam_stats String reads_dir = outdir + "/reads" String metrics_dir = outdir + "/metrics" # Finalize our reads first: - call FF.FinalizeToDir as FinalizeUnalignedReads { + call FF.FinalizeToDir as t_011_FinalizeUnalignedReads { input: outdir = reads_dir + "/unaligned", files = [ bam, bai, - Bam2Fastq.fastq + t_003_Bam2Fastq.fastq ], keyfile = keyfile } - call FF.FinalizeToDir as FinalizeAlignedReads { + call FF.FinalizeToDir as t_012_FinalizeAlignedReads { input: outdir = reads_dir + "/aligned", files = [ - AlignReads.bam, - AlignReads.bai, - MergeBamAlignment.bam, - MarkDuplicates.bam, - SortAlignedDuplicateMarkedBam.sorted_bam, - SortAlignedDuplicateMarkedBam.sorted_bai, - ApplyBQSR.recalibrated_bam, - ApplyBQSR.recalibrated_bai, + t_004_AlignReads.bam, + t_005_MergeBamAlignment.bam, + t_006_MarkDuplicates.bam, + t_007_SortAlignedDuplicateMarkedBam.sorted_bam, + t_007_SortAlignedDuplicateMarkedBam.sorted_bai, + t_009_ApplyBQSR.recalibrated_bam, + t_009_ApplyBQSR.recalibrated_bai, ], keyfile = keyfile } # Finalize our metrics: - call FF.FinalizeToDir as FinalizeMetrics { + call FF.FinalizeToDir as t_013_FinalizeMetrics { input: outdir = metrics_dir, files = [ - MarkDuplicates.metrics, - BaseRecalibrator.recalibration_report, - ApplyBQSR.recalibrated_bam_checksum, + t_006_MarkDuplicates.metrics, + t_008_BaseRecalibrator.recalibration_report, + t_010_SamStats.sam_stats ], keyfile = keyfile } - + ############################################ # ___ _ _ # / _ \ _ _| |_ _ __ _ _| |_ @@ -210,4 +239,4 @@ workflow SRFlowcell { output { } -} \ No newline at end of file +} diff --git a/wdl/tasks/AlignedMetrics.wdl b/wdl/tasks/AlignedMetrics.wdl index 6c25a031a..bc2464c11 100644 --- a/wdl/tasks/AlignedMetrics.wdl +++ b/wdl/tasks/AlignedMetrics.wdl @@ -338,6 +338,51 @@ task CoverageTrack { } } +task SamStats { + input { + File bam + + RuntimeAttr? runtime_attr_override + } + + String basename = basename(bam, ".bam") + Int disk_size = 2*ceil(size(bam, "GB")) + + command <<< + set -euxo pipefail + + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + + samtools stats -@${np} ~{bam} > ~{basename}.sam_stats.txt + >>> + + output { + File sam_stats = "~{basename}.sam_stats.txt" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-metrics:0.1.11" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + + task FlagStats { input { File bam diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl new file mode 100644 index 000000000..6ad616220 --- /dev/null +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -0,0 +1,88 @@ +version 1.0 + +import "Utils.wdl" + +task HaplotypeCaller_GATK4_VCF { + input { + File input_bam + File input_bam_index + File interval_list + String vcf_basename + File ref_dict + File ref_fasta + File ref_fasta_index + Float? contamination + Boolean make_gvcf + Boolean make_bamout + Int preemptible_tries + Int hc_scatter + Boolean run_dragen_mode_variant_calling = false + Boolean use_dragen_hard_filtering = false + Boolean use_spanning_event_genotyping = true + File? dragstr_model + String gatk_docker = "us.gcr.io/broad-gatk/gatk:4.2.6.1" + Int memory_multiplier = 1 + } + + Int memory_size_mb = ceil(8000 * memory_multiplier) + + String output_suffix = if make_gvcf then ".g.vcf.gz" else ".vcf.gz" + String output_file_name = vcf_basename + output_suffix + + Float ref_size = size(ref_fasta, "GiB") + size(ref_fasta_index, "GiB") + size(ref_dict, "GiB") + Int disk_size = ceil(((size(input_bam, "GiB") + 30) / hc_scatter) + ref_size) + 20 + + String bamout_arg = if make_bamout then "-bamout ~{vcf_basename}.bamout.bam" else "" + + parameter_meta { + input_bam: { + localization_optional: true + } + } + + command <<< + set -e + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') + let java_memory_size_mb=available_memory_mb-1024 + echo Total available memory: ${available_memory_mb} MB >&2 + echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 + + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ + HaplotypeCaller \ + -R ~{ref_fasta} \ + -I ~{input_bam} \ + -L ~{interval_list} \ + -O ~{output_file_name} \ + -contamination ~{default=0 contamination} \ + -G StandardAnnotation -G StandardHCAnnotation ~{true="-G AS_StandardAnnotation" false="" make_gvcf} \ + ~{true="--dragen-mode" false="" run_dragen_mode_variant_calling} \ + ~{false="--disable-spanning-event-genotyping" true="" use_spanning_event_genotyping} \ + ~{if defined(dragstr_model) then "--dragstr-params-path " + dragstr_model else ""} \ + -GQB 10 -GQB 20 -GQB 30 -GQB 40 -GQB 50 -GQB 60 -GQB 70 -GQB 80 -GQB 90 \ + ~{true="-ERC GVCF" false="" make_gvcf} \ + ~{bamout_arg} + + # Cromwell doesn't like optional task outputs, so we have to touch this file. + touch ~{vcf_basename}.bamout.bam + >>> + + runtime { + docker: gatk_docker + preemptible: preemptible_tries + memory: "~{memory_size_mb} MiB" + cpu: "2" + bootDiskSizeGb: 15 + disks: "local-disk " + disk_size + " HDD" + } + + output { + File output_vcf = "~{output_file_name}" + File output_vcf_index = "~{output_file_name}.tbi" + File bamout = "~{vcf_basename}.bamout.bam" + } +} \ No newline at end of file diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index b1bf81e8f..2e11480be 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -51,6 +51,48 @@ task BamToFq { } } +task FixMate { + input { + File input_bam + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size(input_bam, "GB")) + + command <<< + set -euxo pipefail + + samtools fixmate ~{input_bam} ~{prefix}.bam + >>> + + output { + File bam = "~{prefix}.bam" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 16, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-utils:0.1.8" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + task Bam2FqPicard { input { File bam @@ -114,6 +156,8 @@ task BwaMem2 { String prefix = "out" + Boolean mark_short_splits_as_secondary = false + RuntimeAttr? runtime_attr_override } @@ -132,7 +176,9 @@ task BwaMem2 { # Make sure we use all our proocesors: np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') - let np=${np}-1 + if [[ ${np} -gt 2 ]] ; then + let np=${np}-1 + fi # Breakdown of the arguments: # -K INT process INT input bases in each batch regardless of nThreads (for reproducibility) [] @@ -144,15 +190,20 @@ task BwaMem2 { # -t INT number of threads [1] # -Y use soft clipping for supplementary alignments - bwa-mem2 mem -K 100000000 -p -v 3 -t ${np} -Y ~{ref_fasta} ~{name_sorted_fastq} | \ - samtools view -1 - > ~{prefix}.bam - - samtools index -@${np} ~{prefix}.bam + bwa-mem2 mem \ + -K 100000000 \ + -p \ + -v 3 \ + -t ${np} \ + -Y \ + ~{true='-M' false="" mark_short_splits_as_secondary} \ + ~{ref_fasta} \ + ~{name_sorted_fastq} | \ + samtools view -1 - > ~{prefix}.bam >>> output { File bam = "~{prefix}.bam" - File bai = "~{prefix}.bam.bai" } ######################### @@ -223,8 +274,8 @@ task MergeBamAlignment { ADD_MATE_CIGAR=true \ MAX_INSERTIONS_OR_DELETIONS=-1 \ PRIMARY_ALIGNMENT_STRATEGY=MostDistant \ - PROGRAM_RECORD_ID="bwamem" \ - PROGRAM_GROUP_VERSION="${BWA_VERSION}" \ + PROGRAM_RECORD_ID="bwa-mem2" \ + PROGRAM_GROUP_VERSION="2.2.1" \ PROGRAM_GROUP_COMMAND_LINE="bwa-mem2 mem -K 100000000 -p -v 3 -t 15 -Y" \ PROGRAM_GROUP_NAME="bwa-mem2" \ UNMAPPED_READ_STRATEGY=COPY_TO_TAG \ @@ -429,7 +480,7 @@ task ApplyBQSR { + 2*ceil(size(ref_fasta_index, "GB")) + 2*ceil(size(recalibration_report, "GB")) - command { + command <<< gatk --java-options "-XX:+PrintFlagsFinal -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps \ -XX:+PrintGCDetails -Xloggc:gc_log.log \ -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10 -Dsamjdk.compression_level=~{compression_level} -Xms8192m -Xmx~{java_memory_size_mb}m" \ @@ -445,8 +496,11 @@ task ApplyBQSR { ~{true='--static-quantized-quals 20' false='' bin_base_qualities} \ ~{true='--static-quantized-quals 30' false='' bin_base_qualities} \ + # Make sure we use all our proocesors: + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') - } + samtools index -@${np} ~{prefix}.bam + >>> ######################### RuntimeAttr default_attr = object { cpu_cores: 16, @@ -470,7 +524,64 @@ task ApplyBQSR { output { File recalibrated_bam = "~{prefix}.bam" File recalibrated_bai = "~{prefix}.bam.bai" - File recalibrated_bam_checksum = "~{prefix}.bam.md5" } } +task RevertSam { + input { + File input_bam + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int compression_level = 2 + Int java_memory_size_mb = 30768 + + Int disk_size = 1 + 4*ceil(size(input_bam, "GB")) + + # As documented on the GATK website: + # https://gatk.broadinstitute.org/hc/en-us/articles/4403687183515--How-to-Generate-an-unmapped-BAM-from-FASTQ-or-aligned-BAM + command { + java -Dsamjdk.compression_level=~{compression_level} -Xms~{java_memory_size_mb}m -jar /usr/picard/picard.jar \ + RevertSam \ + INPUT=~{input_bam} \ + OUTPUT=~{prefix}.bam \ + SANITIZE=true \ + MAX_DISCARD_FRACTION=0.005 \ + ATTRIBUTE_TO_CLEAR=XT \ + ATTRIBUTE_TO_CLEAR=XN \ + ATTRIBUTE_TO_CLEAR=AS \ + ATTRIBUTE_TO_CLEAR=OC \ + ATTRIBUTE_TO_CLEAR=OP \ + SORT_ORDER=queryname \ + RESTORE_ORIGINAL_QUALITIES=true \ + REMOVE_DUPLICATE_INFORMATION=true \ + REMOVE_ALIGNMENT_INFORMATION=true + } + + output { + File bam = "~{prefix}.bam" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 16, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gotc-prod/picard-cloud:2.26.10" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} \ No newline at end of file diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 0b67ff67b..dc7aee9e9 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -170,6 +170,8 @@ task SortBam { prefix: "[default-valued] prefix for output BAM" } + Int disk_size = 10 + 10*ceil(size(input_bam, "GB")) + command <<< set -euxo pipefail @@ -188,7 +190,7 @@ task SortBam { RuntimeAttr default_attr = object { cpu_cores: 2, mem_gb: 4, - disk_gb: 10, + disk_gb: disk_size, boot_disk_gb: 10, preemptible_tries: 3, max_retries: 1, From 07351a9f04d63daad208c9f2767293987f06d9ef Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sun, 25 Dec 2022 02:23:56 -0500 Subject: [PATCH 010/297] Updates to SRFlowcell to handle paired-end fastq files (since that's apparently how Pf7 data in SRA/ENA are stored) --- .dockstore.yml | 4 ++ wdl/SRFlowcell.wdl | 143 +++++++++++++++++++++++++++++------------- wdl/tasks/SRUtils.wdl | 9 ++- 3 files changed, 110 insertions(+), 46 deletions(-) diff --git a/.dockstore.yml b/.dockstore.yml index a621b802c..d4bdf110f 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -120,3 +120,7 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/SRWholeGenome.wdl testParameterFiles: +- name: SRFlowcell + subclass: wdl + primaryDescriptorPath: /wdl/SRFlowcell.wdl + testParameterFiles: diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 9fd00255e..e88801a66 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -12,12 +12,16 @@ version 1.0 import "tasks/SRUtils.wdl" as SRUTIL import "tasks/Utils.wdl" as Utils import "tasks/AlignedMetrics.wdl" as AM +import "tasks/NanoPlot.wdl" as NP import "tasks/Finalize.wdl" as FF workflow SRFlowcell { input { - File bam - File bai + File? bam + File? bai + + File? fq_end1 + File? fq_end2 String SM String LB @@ -34,6 +38,10 @@ workflow SRFlowcell { parameter_meta { bam: "GCS path to unmapped bam" bai: "GCS path to bai index for unmapped bam" + + fq_end1: "GCS path to end1 of paired-end fastq" + fq_end2: "GCS path to end2 of paired-end fastq" + ref_map_file: "table indicating reference sequence and auxillary file locations" SM: "the value to place in the BAM read group's SM field" @@ -65,24 +73,30 @@ workflow SRFlowcell { # Create an outdir: String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}/" + t_001_WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}" - # Convert the given bam to a uBAM (needed for previous aligned data): - call SRUTIL.RevertSam as t_002_RevertSam { - input: - input_bam = bam, - prefix = SM + ".revertSam" + if (defined(bam)) { + # Convert the given bam to a uBAM (needed for previous aligned data): + call SRUTIL.RevertSam as t_002_RevertSam { + input: + input_bam = select_first([bam]), + prefix = SM + ".revertSam" + } + + # Convert input SAM/BAM to FASTQ: + call SRUTIL.BamToFq as t_003_Bam2Fastq { + input: + bam = t_002_RevertSam.bam, + prefix = SM + } } - # Convert input SAM/BAM to FASTQ: - call SRUTIL.Bam2FqPicard as t_003_Bam2Fastq { - input: - bam = t_002_RevertSam.bam, - prefix = SM - } + File fq1 = select_first([fq_end1, t_003_Bam2Fastq.fq_end1]) + File fq2 = select_first([fq_end2, t_003_Bam2Fastq.fq_end2]) # Align reads to reference with BWA-MEM2: call SRUTIL.BwaMem2 as t_004_AlignReads { input: - name_sorted_fastq = t_003_Bam2Fastq.fastq, + fq_end1 = fq1, + fq_end2 = fq2, ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], ref_dict = ref_map["dict"], @@ -95,29 +109,33 @@ workflow SRFlowcell { prefix = SM + ".aligned" } - # Merge aligned reads and unaligned reads: - call SRUTIL.MergeBamAlignment as t_005_MergeBamAlignment { - input: - aligned_bam = t_004_AlignReads.bam, - unaligned_bam = t_002_RevertSam.bam, - ref_fasta = ref_map["fasta"], - ref_fasta_index = ref_map["fai"], - ref_dict = ref_map["dict"], - prefix = SM + ".aligned.merged" + if (defined(bam)) { + # Merge aligned reads and unaligned reads: + call SRUTIL.MergeBamAlignment as t_005_MergeBamAlignment { + input: + aligned_bam = t_004_AlignReads.bam, + unaligned_bam = select_first([t_002_RevertSam.bam]), + ref_fasta = ref_map["fasta"], + ref_fasta_index = ref_map["fai"], + ref_dict = ref_map["dict"], + prefix = SM + ".aligned.merged" + } } - # This was for GATK 3. We probably don't need it now. + File merged_bam = select_first([t_004_AlignReads.bam, t_005_MergeBamAlignment.bam]) + +# This was for GATK 3. We probably don't need it now. # # Fix mates: # call SRUTIL.FixMate as t_006_FixMate { # input: -# input_bam = t_005_MergeBamAlignment.bam, +# input_bam = merged_bam, # prefix = SM + ".aligned.merged.fixmates" # } # Mark Duplicates call SRUTIL.MarkDuplicates as t_006_MarkDuplicates { input: - input_bam = t_005_MergeBamAlignment.bam, + input_bam = merged_bam, prefix = SM + ".aligned.merged.markDuplicates" } @@ -174,6 +192,17 @@ workflow SRFlowcell { bam = t_009_ApplyBQSR.recalibrated_bam } + call NP.NanoPlotFromBam as t_011_NanoPlotFromBam { + input: + bam = t_009_ApplyBQSR.recalibrated_bam, + bai = t_009_ApplyBQSR.recalibrated_bai + } + + call Utils.ComputeGenomeLength as t_012_ComputeGenomeLength { + input: + fasta = ref_map['fasta'] + } + ############################################ # _____ _ _ _ # | ___(_)_ __ __ _| (_)_______ @@ -187,36 +216,48 @@ workflow SRFlowcell { String metrics_dir = outdir + "/metrics" # Finalize our reads first: - call FF.FinalizeToDir as t_011_FinalizeUnalignedReads { - input: - outdir = reads_dir + "/unaligned", - files = - [ - bam, - bai, - t_003_Bam2Fastq.fastq - ], - keyfile = keyfile - } +# call FF.FinalizeToDir as t_013_FinalizeUnalignedReads { +# input: +# outdir = reads_dir + "/unaligned", +# files = +# [ +# bam, +# bai, +# t_003_Bam2Fastq.fastq +# ], +# keyfile = keyfile +# } - call FF.FinalizeToDir as t_012_FinalizeAlignedReads { + call FF.FinalizeToDir as t_014_FinalizeAlignedReads { input: outdir = reads_dir + "/aligned", files = [ t_004_AlignReads.bam, - t_005_MergeBamAlignment.bam, + merged_bam, t_006_MarkDuplicates.bam, t_007_SortAlignedDuplicateMarkedBam.sorted_bam, t_007_SortAlignedDuplicateMarkedBam.sorted_bai, - t_009_ApplyBQSR.recalibrated_bam, - t_009_ApplyBQSR.recalibrated_bai, ], keyfile = keyfile } + call FF.FinalizeToFile as t_015_FinalizeAlignedBam { + input: + outdir = reads_dir + "/aligned", + file = t_009_ApplyBQSR.recalibrated_bam, + keyfile = keyfile + } + + call FF.FinalizeToFile as t_016_FinalizeAlignedBai { + input: + outdir = reads_dir + "/aligned", + file = t_009_ApplyBQSR.recalibrated_bai, + keyfile = keyfile + } + # Finalize our metrics: - call FF.FinalizeToDir as t_013_FinalizeMetrics { + call FF.FinalizeToDir as t_017_FinalizeMetrics { input: outdir = metrics_dir, files = @@ -237,6 +278,22 @@ workflow SRFlowcell { # |_| ############################################ output { - + # Aligned BAM file + File aligned_bam = t_015_FinalizeAlignedBam.gcs_path + File aligned_bai = t_016_FinalizeAlignedBai.gcs_path + + # Aligned read stats + Float aligned_num_reads = t_011_NanoPlotFromBam.stats_map['number_of_reads'] + Float aligned_num_bases = t_011_NanoPlotFromBam.stats_map['number_of_bases_aligned'] + Float aligned_frac_bases = t_011_NanoPlotFromBam.stats_map['fraction_bases_aligned'] + Float aligned_est_fold_cov = t_011_NanoPlotFromBam.stats_map['number_of_bases_aligned']/t_012_ComputeGenomeLength.length + + Float aligned_read_length_mean = t_011_NanoPlotFromBam.stats_map['mean_read_length'] + Float aligned_read_length_median = t_011_NanoPlotFromBam.stats_map['median_read_length'] + Float aligned_read_length_stdev = t_011_NanoPlotFromBam.stats_map['read_length_stdev'] + Float aligned_read_length_N50 = t_011_NanoPlotFromBam.stats_map['n50'] + + Float average_identity = t_011_NanoPlotFromBam.stats_map['average_identity'] + Float median_identity = t_011_NanoPlotFromBam.stats_map['median_identity'] } } diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 2e11480be..de0b8c1f4 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -143,7 +143,8 @@ task Bam2FqPicard { task BwaMem2 { input { - File name_sorted_fastq + File fq_end1 + File fq_end2 File ref_fasta File ref_fasta_index @@ -161,7 +162,8 @@ task BwaMem2 { RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 4*ceil(size(name_sorted_fastq, "GB")) + Int disk_size = 1 + 4*ceil(size(fq_end1, "GB")) + + 4*ceil(size(fq_end2, "GB")) + 4*ceil(size(ref_fasta, "GB")) + 4*ceil(size(ref_fasta_index, "GB")) + 4*ceil(size(ref_dict, "GB")) @@ -198,7 +200,8 @@ task BwaMem2 { -Y \ ~{true='-M' false="" mark_short_splits_as_secondary} \ ~{ref_fasta} \ - ~{name_sorted_fastq} | \ + ~{fq_end1} \ + ~{fq_end2} | \ samtools view -1 - > ~{prefix}.bam >>> From a2119df456c64bfe572556d9a0ba29a5ea3609e8 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 3 Jan 2023 15:16:27 +0000 Subject: [PATCH 011/297] Updating `sr-utils` docker image. --- docker/sr-utils/Dockerfile | 8 ++++++++ docker/sr-utils/Makefile | 11 ++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/docker/sr-utils/Dockerfile b/docker/sr-utils/Dockerfile index f2c68da1d..6868d01ca 100644 --- a/docker/sr-utils/Dockerfile +++ b/docker/sr-utils/Dockerfile @@ -9,5 +9,13 @@ COPY ./environment.yml / RUN conda env create -f /environment.yml && conda clean -a ENV PATH=/opt/conda/envs/sr-utils/bin/:/root/google-cloud-sdk/bin/:${PATH} +# Install BWA-MEM2: +RUN wget https://github.com/bwa-mem2/bwa-mem2/releases/download/v2.2.1/bwa-mem2-2.2.1_x64-linux.tar.bz2 && \ + tar -xf bwa-mem2-2.2.1_x64-linux.tar.bz2 && \ + mv bwa-mem2-2.2.1_x64-linux /opt/ && \ + for f in $(find /opt/bwa-mem2-2.2.1_x64-linux/ -type f -name \*bwa-mem\* ) ; do ln -s $f /usr/local/bin/$(basename $f) ; done && \ + rm bwa-mem2-2.2.1_x64-linux.tar.bz2 + # set LD library path ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/conda/envs/sr-utils/lib/ + diff --git a/docker/sr-utils/Makefile b/docker/sr-utils/Makefile index e2911596b..c44223e7e 100644 --- a/docker/sr-utils/Makefile +++ b/docker/sr-utils/Makefile @@ -1,5 +1,5 @@ IMAGE_NAME = sr-utils -VERSION = 0.1.0 +VERSION = 0.2.0 TAG1 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):$(VERSION) TAG2 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):latest @@ -7,11 +7,12 @@ TAG2 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):latest all: | build push build: - docker build -t $(TAG1) -t $(TAG2) . + docker build -t $(TAG1) -t $(TAG2) . build_no_cache: - docker build --no-cache -t $(TAG1) -t $(TAG2) . + docker build --no-cache -t $(TAG1) -t $(TAG2) . push: - docker push $(TAG1) - docker push $(TAG2) + docker push $(TAG1) + docker push $(TAG2) + From 3e176a0bffb12ac9c2aeff693b8318535fa0d716 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 3 Jan 2023 10:53:21 -0500 Subject: [PATCH 012/297] Updates to SRFlowcell.wdl and associated docker. - Added some outputs to SRFlowcell to be similar to PBFlowcell.wdl. - Added stats script for short reads. --- docker/sr-utils/python/compute_sr_stats.py | 72 ++++++++++++++++++++++ wdl/SRFlowcell.wdl | 38 +++++++++--- 2 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 docker/sr-utils/python/compute_sr_stats.py diff --git a/docker/sr-utils/python/compute_sr_stats.py b/docker/sr-utils/python/compute_sr_stats.py new file mode 100644 index 000000000..03c43ddd3 --- /dev/null +++ b/docker/sr-utils/python/compute_sr_stats.py @@ -0,0 +1,72 @@ +import numpy as np +import pysam + + +def n50(lengths): + all_len = sorted(lengths, reverse=True) + csum = np.cumsum(all_len) + n2 = int(sum(lengths) / 2) + csumn2 = min(csum[csum >= n2]) + ind = np.where(csum == csumn2) + + return all_len[int(ind[0])] + + +def get_bam_stats(bam_file_path, qual_thresh=None): + # Open the file and get ready to iterate: + with pysam.AlignmentFile(bam_file_path, "rb", check_sq=False, require_index=False) as bam_file: + + # Get total number of reads if we have an index: + total_reads = None + if bam_file.has_index(): + idx_stats = bam_file.get_index_statistics() + unaligned_reads = bam_file.nocoordinate + aligned_reads = reduce(lambda a, b: a + b, [x.total for x in idx_stats]) if len(idx_stats) > 0 else 0 + total_reads = unaligned_reads + aligned_reads + + n_reads = 0 if not total_reads else total_reads + read_lengths = [] + quals = [] + total_bases = 0 + + # Iterate through our reads + for read in tqdm(bam_file, desc=f"Collecting Bam Stats" + (f" (rq >= {qual_thresh})" if qual_thresh else ""), + total=total_reads, unit=" read"): + l = len(read.query_sequence) + q = np.mean(read.query_qualities) + + if qual_thresh and q < qual_thresh: + continue + + quals.append(q) + total_bases += l + read_lengths.append(l) + + if not total_reads: + n_reads += 1 + + return n_reads, total_bases, np.mean(quals), np.median(quals), np.array(read_lengths) + + +def main(): + parser = argparse.ArgumentParser(description='Compute short read bam file stats', prog='compute_sr_stats') + parser.add_argument('-q', '--qual-threshold', type=int, default=0, help="Phred-scale quality threshold") + parser.add_argument('bam_file_path', type=str, help="Path to bam file") + args = parser.parse_args() + + n_reads, n_bases, mean_qual, median_qual, read_lengths = get_bam_stats(args.bam_file_path, args.qual_threshold) + + print(f"reads\t{n_reads}") + print(f"bases\t{n_bases}") + print(f"mean_qual\t{mean_qual}") + print(f"median_qual\t{median_qual}") + + print(f"read_mean\t{int(np.mean(read_lengths)) if len(read_lengths) > 0 else 0}") + print(f"read_median\t{int(np.median(read_lengths)) if len(read_lengths) > 0 else 0}") + print(f"read_stdev\t{int(np.std(read_lengths)) if len(read_lengths) > 0 else 0}") + print(f"read_n50\t{n50(read_lengths) if len(read_lengths) > 0 else 0}") + + +if __name__ == "__main__": + main() + diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index e88801a66..e66da308d 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -192,16 +192,8 @@ workflow SRFlowcell { bam = t_009_ApplyBQSR.recalibrated_bam } - call NP.NanoPlotFromBam as t_011_NanoPlotFromBam { - input: - bam = t_009_ApplyBQSR.recalibrated_bam, - bai = t_009_ApplyBQSR.recalibrated_bai - } - - call Utils.ComputeGenomeLength as t_012_ComputeGenomeLength { - input: - fasta = ref_map['fasta'] - } + call NP.NanoPlotFromBam as t_011_NanoPlotFromBam { input: bam = t_009_ApplyBQSR.recalibrated_bam, bai = t_009_ApplyBQSR.recalibrated_bai } + call Utils.ComputeGenomeLength as t_012_ComputeGenomeLength { input: fasta = ref_map['fasta'] } ############################################ # _____ _ _ _ @@ -278,10 +270,36 @@ workflow SRFlowcell { # |_| ############################################ output { + # Unaligned reads + File? fq = reads_dir + "/unaligned/" + basename(t_003_Bam2Fastq.fastq) + + # Unaligned BAM file + File? unaligned_bam = reads_dir + "/unaligned/" + basename(bam) + File? unaligned_bai = reads_dir + "/unaligned/" + basename(bai) + # Aligned BAM file File aligned_bam = t_015_FinalizeAlignedBam.gcs_path File aligned_bai = t_016_FinalizeAlignedBai.gcs_path +# # Unaligned read stats +# Float num_reads = SummarizeSubreadsPBI.results['reads'] +# Float num_bases = SummarizeSubreadsPBI.results['bases'] +# Float raw_est_fold_cov = SummarizeSubreadsPBI.results['bases']/t_012_ComputeGenomeLength.length +# +# Float read_length_mean = SummarizeSubreadsPBI.results['subread_mean'] +# Float read_length_median = SummarizeSubreadsPBI.results['subread_median'] +# Float read_length_stdev = SummarizeSubreadsPBI.results['subread_stdev'] +# Float read_length_N50 = SummarizeSubreadsPBI.results['subread_n50'] +# +# Float read_qual_mean = SummarizeSubreadsPBI.results['mean_qual'] +# Float read_qual_median = SummarizeSubreadsPBI.results['median_qual'] +# +# Float num_reads_Q5 = SummarizeAlignedQ5PBI.results['reads'] +# Float num_reads_Q7 = SummarizeAlignedQ7PBI.results['reads'] +# Float num_reads_Q10 = SummarizeAlignedQ10PBI.results['reads'] +# Float num_reads_Q12 = SummarizeAlignedQ12PBI.results['reads'] +# Float num_reads_Q15 = SummarizeAlignedQ15PBI.results['reads'] + # Aligned read stats Float aligned_num_reads = t_011_NanoPlotFromBam.stats_map['number_of_reads'] Float aligned_num_bases = t_011_NanoPlotFromBam.stats_map['number_of_bases_aligned'] From 6c990b418a83527b8d94ece11d6ba436a5654202 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 3 Jan 2023 17:30:06 +0000 Subject: [PATCH 013/297] Adding pysam. --- docker/sr-utils/environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/sr-utils/environment.yml b/docker/sr-utils/environment.yml index 537e00470..1734c29de 100644 --- a/docker/sr-utils/environment.yml +++ b/docker/sr-utils/environment.yml @@ -6,3 +6,4 @@ channels: dependencies: - samtools - bwa + - pysam From dec8e66de599fabfbac9f88b1e88ed535df8daa0 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 3 Jan 2023 17:31:35 +0000 Subject: [PATCH 014/297] Adding `compute_sr_stats.py` to `sr-utils` docker. --- docker/sr-utils/Dockerfile | 2 ++ docker/sr-utils/python/compute_sr_stats.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docker/sr-utils/Dockerfile b/docker/sr-utils/Dockerfile index 6868d01ca..cc0adc19c 100644 --- a/docker/sr-utils/Dockerfile +++ b/docker/sr-utils/Dockerfile @@ -16,6 +16,8 @@ RUN wget https://github.com/bwa-mem2/bwa-mem2/releases/download/v2.2.1/bwa-mem2- for f in $(find /opt/bwa-mem2-2.2.1_x64-linux/ -type f -name \*bwa-mem\* ) ; do ln -s $f /usr/local/bin/$(basename $f) ; done && \ rm bwa-mem2-2.2.1_x64-linux.tar.bz2 +COPY ./python /python + # set LD library path ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/conda/envs/sr-utils/lib/ diff --git a/docker/sr-utils/python/compute_sr_stats.py b/docker/sr-utils/python/compute_sr_stats.py index 03c43ddd3..59b5f3289 100644 --- a/docker/sr-utils/python/compute_sr_stats.py +++ b/docker/sr-utils/python/compute_sr_stats.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import numpy as np import pysam From 6e61afa16d66dbe86e063692b95e13766af724fa Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 3 Jan 2023 13:22:46 -0500 Subject: [PATCH 015/297] `SRFlowcell.wdl` now includes all outputs and validates. --- wdl/SRFlowcell.wdl | 114 +++++++++++++++++++++++++++--------------- wdl/tasks/SRUtils.wdl | 54 +++++++++++++++++++- 2 files changed, 128 insertions(+), 40 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index e66da308d..558851d61 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -89,14 +89,14 @@ workflow SRFlowcell { } } - File fq1 = select_first([fq_end1, t_003_Bam2Fastq.fq_end1]) - File fq2 = select_first([fq_end2, t_003_Bam2Fastq.fq_end2]) + File fq_e1 = select_first([fq_end1, t_003_Bam2Fastq.fq_end1]) + File fq_e2 = select_first([fq_end2, t_003_Bam2Fastq.fq_end2]) # Align reads to reference with BWA-MEM2: call SRUTIL.BwaMem2 as t_004_AlignReads { input: - fq_end1 = fq1, - fq_end2 = fq2, + fq_end1 = fq_e1, + fq_end2 = fq_e2, ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], ref_dict = ref_map["dict"], @@ -195,6 +195,15 @@ workflow SRFlowcell { call NP.NanoPlotFromBam as t_011_NanoPlotFromBam { input: bam = t_009_ApplyBQSR.recalibrated_bam, bai = t_009_ApplyBQSR.recalibrated_bai } call Utils.ComputeGenomeLength as t_012_ComputeGenomeLength { input: fasta = ref_map['fasta'] } + call SRUTIL.ComputeBamStats as t_013_ComputeBamStats { input: bam_file = t_009_ApplyBQSR.recalibrated_bam } + + # Collect stats on aligned reads: + call SRUTIL.ComputeBamStats as t_013_ComputeBamStatsQ5 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 5 } + call SRUTIL.ComputeBamStats as t_013_ComputeBamStatsQ7 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 7 } + call SRUTIL.ComputeBamStats as t_013_ComputeBamStatsQ10 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 10 } + call SRUTIL.ComputeBamStats as t_013_ComputeBamStatsQ12 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 12 } + call SRUTIL.ComputeBamStats as t_013_ComputeBamStatsQ15 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 15 } + ############################################ # _____ _ _ _ # | ___(_)_ __ __ _| (_)_______ @@ -203,22 +212,34 @@ workflow SRFlowcell { # |_| |_|_| |_|\__,_|_|_/___\___| # ############################################ - File keyfile = t_010_SamStats.sam_stats + File keyfile = t_013_ComputeBamStats.results_file String reads_dir = outdir + "/reads" String metrics_dir = outdir + "/metrics" - # Finalize our reads first: -# call FF.FinalizeToDir as t_013_FinalizeUnalignedReads { -# input: -# outdir = reads_dir + "/unaligned", -# files = -# [ -# bam, -# bai, -# t_003_Bam2Fastq.fastq -# ], -# keyfile = keyfile -# } + # Finalize our unaligned reads first: + call FF.FinalizeToDir as t_013_FinalizeUnalignedFastqReads { + input: + outdir = reads_dir + "/unaligned", + files = + [ + fq_e1, + fq_e2, + ], + keyfile = keyfile + } + if (defined(bam)) { + call FF.FinalizeToDir as t_013_FinalizeUnalignedReadsFromBam { + input: + outdir = reads_dir + "/unaligned", + files = select_all( + [ + bam, + bai, + t_003_Bam2Fastq.fq_unpaired, + ]), + keyfile = keyfile + } + } call FF.FinalizeToDir as t_014_FinalizeAlignedReads { input: @@ -256,11 +277,24 @@ workflow SRFlowcell { [ t_006_MarkDuplicates.metrics, t_008_BaseRecalibrator.recalibration_report, - t_010_SamStats.sam_stats + t_010_SamStats.sam_stats, + t_013_ComputeBamStats.results, + t_013_ComputeBamStatsQ5.results, + t_013_ComputeBamStatsQ7.results, + t_013_ComputeBamStatsQ10.results, + t_013_ComputeBamStatsQ12.results, + t_013_ComputeBamStatsQ15.results, ], keyfile = keyfile } + # Prep a few files for output: + if (defined(bam)) { + File unaligned_bam_o = reads_dir + "/unaligned/" + basename(select_first([bam])) + File unaligned_bai_o = reads_dir + "/unaligned/" + basename(select_first([bai])) + File fqboup = reads_dir + "/unaligned/" + basename(select_first([t_003_Bam2Fastq.fq_unpaired])) + } + ############################################ # ___ _ _ # / _ \ _ _| |_ _ __ _ _| |_ @@ -271,34 +305,36 @@ workflow SRFlowcell { ############################################ output { # Unaligned reads - File? fq = reads_dir + "/unaligned/" + basename(t_003_Bam2Fastq.fastq) + File fq1 = fq_e1 + File fq2 = fq_e2 + File? fq_unpaired = t_003_Bam2Fastq.fq_unpaired # Unaligned BAM file - File? unaligned_bam = reads_dir + "/unaligned/" + basename(bam) - File? unaligned_bai = reads_dir + "/unaligned/" + basename(bai) + File? unaligned_bam = unaligned_bam_o + File? unaligned_bai = unaligned_bai_o # Aligned BAM file File aligned_bam = t_015_FinalizeAlignedBam.gcs_path File aligned_bai = t_016_FinalizeAlignedBai.gcs_path -# # Unaligned read stats -# Float num_reads = SummarizeSubreadsPBI.results['reads'] -# Float num_bases = SummarizeSubreadsPBI.results['bases'] -# Float raw_est_fold_cov = SummarizeSubreadsPBI.results['bases']/t_012_ComputeGenomeLength.length -# -# Float read_length_mean = SummarizeSubreadsPBI.results['subread_mean'] -# Float read_length_median = SummarizeSubreadsPBI.results['subread_median'] -# Float read_length_stdev = SummarizeSubreadsPBI.results['subread_stdev'] -# Float read_length_N50 = SummarizeSubreadsPBI.results['subread_n50'] -# -# Float read_qual_mean = SummarizeSubreadsPBI.results['mean_qual'] -# Float read_qual_median = SummarizeSubreadsPBI.results['median_qual'] -# -# Float num_reads_Q5 = SummarizeAlignedQ5PBI.results['reads'] -# Float num_reads_Q7 = SummarizeAlignedQ7PBI.results['reads'] -# Float num_reads_Q10 = SummarizeAlignedQ10PBI.results['reads'] -# Float num_reads_Q12 = SummarizeAlignedQ12PBI.results['reads'] -# Float num_reads_Q15 = SummarizeAlignedQ15PBI.results['reads'] + # Unaligned read stats + Float num_reads = t_013_ComputeBamStats.results['reads'] + Float num_bases = t_013_ComputeBamStats.results['bases'] + Float raw_est_fold_cov = t_013_ComputeBamStats.results['bases']/t_012_ComputeGenomeLength.length + + Float read_length_mean = t_013_ComputeBamStats.results['subread_mean'] + Float read_length_median = t_013_ComputeBamStats.results['subread_median'] + Float read_length_stdev = t_013_ComputeBamStats.results['subread_stdev'] + Float read_length_N50 = t_013_ComputeBamStats.results['subread_n50'] + + Float read_qual_mean = t_013_ComputeBamStats.results['mean_qual'] + Float read_qual_median = t_013_ComputeBamStats.results['median_qual'] + + Float num_reads_Q5 = t_013_ComputeBamStatsQ5.results['reads'] + Float num_reads_Q7 = t_013_ComputeBamStatsQ7.results['reads'] + Float num_reads_Q10 = t_013_ComputeBamStatsQ10.results['reads'] + Float num_reads_Q12 = t_013_ComputeBamStatsQ12.results['reads'] + Float num_reads_Q15 = t_013_ComputeBamStatsQ15.results['reads'] # Aligned read stats Float aligned_num_reads = t_011_NanoPlotFromBam.stats_map['number_of_reads'] diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index de0b8c1f4..9ba72898f 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -587,4 +587,56 @@ task RevertSam { maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } -} \ No newline at end of file +} + +task ComputeBamStats { + input { + File bam_file + Int? qual_threshold + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 2*ceil(size(bam_file, "GB")) + String qual_thresh_arg = if defined(qual_threshold) then " -q " else "" + + String qual_stats_file_decoration = if defined(qual_threshold) then ".q" + qual_threshold else "" + + String stats_file_name = basename(bam_file, ".bam") + ".stats_map" + qual_stats_file_decoration + ".txt" + + command <<< + set -euxo pipefail + + python3 /python/compute_sr_stats.py \ + ~{qual_thresh_arg}~{default="" sep=" -q " qual_threshold} \ + ~{bam_file} \ + | tee ~{stats_file_name} + >>> + + output { + Map[String, Float] results = read_map(stats_file_name) + File results_file = stats_file_name + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 16, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 3, + max_retries: 2, + docker: "us.gcr.io/broad-dsp-lrma/sr-utils:0.2.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + From 46d26f1d6fde900c2b692be82eee557851ffb155 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 3 Jan 2023 13:24:03 -0500 Subject: [PATCH 016/297] Renumbered WDL tasks. --- wdl/SRFlowcell.wdl | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 558851d61..74f3cc926 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -198,11 +198,11 @@ workflow SRFlowcell { call SRUTIL.ComputeBamStats as t_013_ComputeBamStats { input: bam_file = t_009_ApplyBQSR.recalibrated_bam } # Collect stats on aligned reads: - call SRUTIL.ComputeBamStats as t_013_ComputeBamStatsQ5 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 5 } - call SRUTIL.ComputeBamStats as t_013_ComputeBamStatsQ7 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 7 } - call SRUTIL.ComputeBamStats as t_013_ComputeBamStatsQ10 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 10 } - call SRUTIL.ComputeBamStats as t_013_ComputeBamStatsQ12 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 12 } - call SRUTIL.ComputeBamStats as t_013_ComputeBamStatsQ15 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 15 } + call SRUTIL.ComputeBamStats as t_014_ComputeBamStatsQ5 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 5 } + call SRUTIL.ComputeBamStats as t_015_ComputeBamStatsQ7 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 7 } + call SRUTIL.ComputeBamStats as t_016_ComputeBamStatsQ10 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 10 } + call SRUTIL.ComputeBamStats as t_017_ComputeBamStatsQ12 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 12 } + call SRUTIL.ComputeBamStats as t_018_ComputeBamStatsQ15 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 15 } ############################################ # _____ _ _ _ @@ -217,7 +217,7 @@ workflow SRFlowcell { String metrics_dir = outdir + "/metrics" # Finalize our unaligned reads first: - call FF.FinalizeToDir as t_013_FinalizeUnalignedFastqReads { + call FF.FinalizeToDir as t_019_FinalizeUnalignedFastqReads { input: outdir = reads_dir + "/unaligned", files = @@ -228,7 +228,7 @@ workflow SRFlowcell { keyfile = keyfile } if (defined(bam)) { - call FF.FinalizeToDir as t_013_FinalizeUnalignedReadsFromBam { + call FF.FinalizeToDir as t_020_FinalizeUnalignedReadsFromBam { input: outdir = reads_dir + "/unaligned", files = select_all( @@ -241,7 +241,7 @@ workflow SRFlowcell { } } - call FF.FinalizeToDir as t_014_FinalizeAlignedReads { + call FF.FinalizeToDir as t_021_FinalizeAlignedReads { input: outdir = reads_dir + "/aligned", files = @@ -255,14 +255,14 @@ workflow SRFlowcell { keyfile = keyfile } - call FF.FinalizeToFile as t_015_FinalizeAlignedBam { + call FF.FinalizeToFile as t_022_FinalizeAlignedBam { input: outdir = reads_dir + "/aligned", file = t_009_ApplyBQSR.recalibrated_bam, keyfile = keyfile } - call FF.FinalizeToFile as t_016_FinalizeAlignedBai { + call FF.FinalizeToFile as t_023_FinalizeAlignedBai { input: outdir = reads_dir + "/aligned", file = t_009_ApplyBQSR.recalibrated_bai, @@ -270,7 +270,7 @@ workflow SRFlowcell { } # Finalize our metrics: - call FF.FinalizeToDir as t_017_FinalizeMetrics { + call FF.FinalizeToDir as t_024_FinalizeMetrics { input: outdir = metrics_dir, files = @@ -279,11 +279,11 @@ workflow SRFlowcell { t_008_BaseRecalibrator.recalibration_report, t_010_SamStats.sam_stats, t_013_ComputeBamStats.results, - t_013_ComputeBamStatsQ5.results, - t_013_ComputeBamStatsQ7.results, - t_013_ComputeBamStatsQ10.results, - t_013_ComputeBamStatsQ12.results, - t_013_ComputeBamStatsQ15.results, + t_014_ComputeBamStatsQ5.results, + t_015_ComputeBamStatsQ7.results, + t_016_ComputeBamStatsQ10.results, + t_017_ComputeBamStatsQ12.results, + t_018_ComputeBamStatsQ15.results, ], keyfile = keyfile } @@ -314,8 +314,8 @@ workflow SRFlowcell { File? unaligned_bai = unaligned_bai_o # Aligned BAM file - File aligned_bam = t_015_FinalizeAlignedBam.gcs_path - File aligned_bai = t_016_FinalizeAlignedBai.gcs_path + File aligned_bam = t_022_FinalizeAlignedBam.gcs_path + File aligned_bai = t_023_FinalizeAlignedBai.gcs_path # Unaligned read stats Float num_reads = t_013_ComputeBamStats.results['reads'] @@ -330,11 +330,11 @@ workflow SRFlowcell { Float read_qual_mean = t_013_ComputeBamStats.results['mean_qual'] Float read_qual_median = t_013_ComputeBamStats.results['median_qual'] - Float num_reads_Q5 = t_013_ComputeBamStatsQ5.results['reads'] - Float num_reads_Q7 = t_013_ComputeBamStatsQ7.results['reads'] - Float num_reads_Q10 = t_013_ComputeBamStatsQ10.results['reads'] - Float num_reads_Q12 = t_013_ComputeBamStatsQ12.results['reads'] - Float num_reads_Q15 = t_013_ComputeBamStatsQ15.results['reads'] + Float num_reads_Q5 = t_014_ComputeBamStatsQ5.results['reads'] + Float num_reads_Q7 = t_015_ComputeBamStatsQ7.results['reads'] + Float num_reads_Q10 = t_016_ComputeBamStatsQ10.results['reads'] + Float num_reads_Q12 = t_017_ComputeBamStatsQ12.results['reads'] + Float num_reads_Q15 = t_018_ComputeBamStatsQ15.results['reads'] # Aligned read stats Float aligned_num_reads = t_011_NanoPlotFromBam.stats_map['number_of_reads'] From b937b2398bf056223ecfed70e22c25fb047d6ad8 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 3 Jan 2023 16:29:36 -0500 Subject: [PATCH 017/297] Fixing `bwa-mem2` options to be correct for 2 FASTQ files. --- wdl/tasks/SRUtils.wdl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 9ba72898f..ada39935b 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -184,17 +184,12 @@ task BwaMem2 { # Breakdown of the arguments: # -K INT process INT input bases in each batch regardless of nThreads (for reproducibility) [] - # -p Smart pairing. If two adjacent reads have the same - # name, they are considered to form a read pair. This - # way, paired-end and single-end reads can be mixed in a - # single FASTA/Q stream. # -v INT verbose level: 1=error, 2=warning, 3=message, 4+=debugging [3] # -t INT number of threads [1] # -Y use soft clipping for supplementary alignments bwa-mem2 mem \ -K 100000000 \ - -p \ -v 3 \ -t ${np} \ -Y \ From 6de00f1d0ec246f86099d1fe81470d35d96e3205 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 4 Jan 2023 10:39:09 -0500 Subject: [PATCH 018/297] Fixing read group error. --- wdl/SRFlowcell.wdl | 165 +++++++++++++++++++++--------------------- wdl/tasks/SRUtils.wdl | 6 ++ 2 files changed, 88 insertions(+), 83 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 74f3cc926..335b5d256 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -33,6 +33,8 @@ workflow SRFlowcell { String gcs_out_root_dir Boolean DEBUG_MODE = false + + String platform = "illumina" } parameter_meta { @@ -87,13 +89,17 @@ workflow SRFlowcell { bam = t_002_RevertSam.bam, prefix = SM } + + call Utils.GetRawReadGroup as t_004_GetRawReadGroup { input: gcs_bam_path = select_first([bam]) } } File fq_e1 = select_first([fq_end1, t_003_Bam2Fastq.fq_end1]) File fq_e2 = select_first([fq_end2, t_003_Bam2Fastq.fq_end2]) + String RG = select_first([t_004_GetRawReadGroup.rg, "@RG\tID:" + SM + "_" + LB + "\tPL:" + platform + "\tLB:" + LB + "\tSM:" + SM]) + # Align reads to reference with BWA-MEM2: - call SRUTIL.BwaMem2 as t_004_AlignReads { + call SRUTIL.BwaMem2 as t_005_AlignReads { input: fq_end1 = fq_e1, fq_end2 = fq_e2, @@ -106,14 +112,15 @@ workflow SRFlowcell { ref_bwt = ref_map["bwt"], ref_pac = ref_map["pac"], mark_short_splits_as_secondary = true, + read_group = RG, prefix = SM + ".aligned" } if (defined(bam)) { # Merge aligned reads and unaligned reads: - call SRUTIL.MergeBamAlignment as t_005_MergeBamAlignment { + call SRUTIL.MergeBamAlignment as t_006_MergeBamAlignment { input: - aligned_bam = t_004_AlignReads.bam, + aligned_bam = t_005_AlignReads.bam, unaligned_bam = select_first([t_002_RevertSam.bam]), ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], @@ -122,37 +129,29 @@ workflow SRFlowcell { } } - File merged_bam = select_first([t_004_AlignReads.bam, t_005_MergeBamAlignment.bam]) - -# This was for GATK 3. We probably don't need it now. -# # Fix mates: -# call SRUTIL.FixMate as t_006_FixMate { -# input: -# input_bam = merged_bam, -# prefix = SM + ".aligned.merged.fixmates" -# } + File merged_bam = select_first([t_005_AlignReads.bam, t_006_MergeBamAlignment.bam]) # Mark Duplicates - call SRUTIL.MarkDuplicates as t_006_MarkDuplicates { + call SRUTIL.MarkDuplicates as t_007_MarkDuplicates { input: input_bam = merged_bam, prefix = SM + ".aligned.merged.markDuplicates" } # Sort Duplicate Marked Bam: - call Utils.SortBam as t_007_SortAlignedDuplicateMarkedBam { + call Utils.SortBam as t_008_SortAlignedDuplicateMarkedBam { input: - input_bam = t_006_MarkDuplicates.bam, + input_bam = t_007_MarkDuplicates.bam, prefix = SM + ".aligned.merged.markDuplicates.sorted" } -# Fingerprinting? +# TODO: Add Fingerprinting? # Recalibrate Base Scores: - call SRUTIL.BaseRecalibrator as t_008_BaseRecalibrator { + call SRUTIL.BaseRecalibrator as t_009_BaseRecalibrator { input: - input_bam = t_007_SortAlignedDuplicateMarkedBam.sorted_bam, - input_bam_index = t_007_SortAlignedDuplicateMarkedBam.sorted_bai, + input_bam = t_008_SortAlignedDuplicateMarkedBam.sorted_bam, + input_bam_index = t_008_SortAlignedDuplicateMarkedBam.sorted_bai, ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], @@ -164,16 +163,16 @@ workflow SRFlowcell { prefix = SM + ".baseRecalibratorReport" } - call SRUTIL.ApplyBQSR as t_009_ApplyBQSR { + call SRUTIL.ApplyBQSR as t_010_ApplyBQSR { input: - input_bam = t_007_SortAlignedDuplicateMarkedBam.sorted_bam, - input_bam_index = t_007_SortAlignedDuplicateMarkedBam.sorted_bai, + input_bam = t_008_SortAlignedDuplicateMarkedBam.sorted_bam, + input_bam_index = t_008_SortAlignedDuplicateMarkedBam.sorted_bai, ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], ref_dict = ref_map["dict"], - recalibration_report = t_008_BaseRecalibrator.recalibration_report, + recalibration_report = t_009_BaseRecalibrator.recalibration_report, prefix = SM + ".aligned.merged.markDuplicates.sorted.BQSR" } @@ -187,22 +186,22 @@ workflow SRFlowcell { # ############################################# - call AM.SamStats as t_010_SamStats { + call AM.SamStats as t_011_SamStats { input: - bam = t_009_ApplyBQSR.recalibrated_bam + bam = t_010_ApplyBQSR.recalibrated_bam } - call NP.NanoPlotFromBam as t_011_NanoPlotFromBam { input: bam = t_009_ApplyBQSR.recalibrated_bam, bai = t_009_ApplyBQSR.recalibrated_bai } - call Utils.ComputeGenomeLength as t_012_ComputeGenomeLength { input: fasta = ref_map['fasta'] } + call NP.NanoPlotFromBam as t_012_NanoPlotFromBam { input: bam = t_010_ApplyBQSR.recalibrated_bam, bai = t_010_ApplyBQSR.recalibrated_bai } + call Utils.ComputeGenomeLength as t_013_ComputeGenomeLength { input: fasta = ref_map['fasta'] } - call SRUTIL.ComputeBamStats as t_013_ComputeBamStats { input: bam_file = t_009_ApplyBQSR.recalibrated_bam } + call SRUTIL.ComputeBamStats as t_014_ComputeBamStats { input: bam_file = t_010_ApplyBQSR.recalibrated_bam } # Collect stats on aligned reads: - call SRUTIL.ComputeBamStats as t_014_ComputeBamStatsQ5 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 5 } - call SRUTIL.ComputeBamStats as t_015_ComputeBamStatsQ7 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 7 } - call SRUTIL.ComputeBamStats as t_016_ComputeBamStatsQ10 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 10 } - call SRUTIL.ComputeBamStats as t_017_ComputeBamStatsQ12 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 12 } - call SRUTIL.ComputeBamStats as t_018_ComputeBamStatsQ15 { input: bam_file = t_009_ApplyBQSR.recalibrated_bam, qual_threshold = 15 } + call SRUTIL.ComputeBamStats as t_015_ComputeBamStatsQ5 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 5 } + call SRUTIL.ComputeBamStats as t_016_ComputeBamStatsQ7 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 7 } + call SRUTIL.ComputeBamStats as t_017_ComputeBamStatsQ10 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 10 } + call SRUTIL.ComputeBamStats as t_018_ComputeBamStatsQ12 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 12 } + call SRUTIL.ComputeBamStats as t_019_ComputeBamStatsQ15 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 15 } ############################################ # _____ _ _ _ @@ -212,12 +211,12 @@ workflow SRFlowcell { # |_| |_|_| |_|\__,_|_|_/___\___| # ############################################ - File keyfile = t_013_ComputeBamStats.results_file + File keyfile = t_014_ComputeBamStats.results_file String reads_dir = outdir + "/reads" String metrics_dir = outdir + "/metrics" # Finalize our unaligned reads first: - call FF.FinalizeToDir as t_019_FinalizeUnalignedFastqReads { + call FF.FinalizeToDir as t_020_FinalizeUnalignedFastqReads { input: outdir = reads_dir + "/unaligned", files = @@ -228,7 +227,7 @@ workflow SRFlowcell { keyfile = keyfile } if (defined(bam)) { - call FF.FinalizeToDir as t_020_FinalizeUnalignedReadsFromBam { + call FF.FinalizeToDir as t_021_FinalizeUnalignedReadsFromBam { input: outdir = reads_dir + "/unaligned", files = select_all( @@ -241,49 +240,49 @@ workflow SRFlowcell { } } - call FF.FinalizeToDir as t_021_FinalizeAlignedReads { + call FF.FinalizeToDir as t_022_FinalizeAlignedReads { input: outdir = reads_dir + "/aligned", files = [ - t_004_AlignReads.bam, + t_005_AlignReads.bam, merged_bam, - t_006_MarkDuplicates.bam, - t_007_SortAlignedDuplicateMarkedBam.sorted_bam, - t_007_SortAlignedDuplicateMarkedBam.sorted_bai, + t_007_MarkDuplicates.bam, + t_008_SortAlignedDuplicateMarkedBam.sorted_bam, + t_008_SortAlignedDuplicateMarkedBam.sorted_bai, ], keyfile = keyfile } - call FF.FinalizeToFile as t_022_FinalizeAlignedBam { + call FF.FinalizeToFile as t_023_FinalizeAlignedBam { input: outdir = reads_dir + "/aligned", - file = t_009_ApplyBQSR.recalibrated_bam, + file = t_010_ApplyBQSR.recalibrated_bam, keyfile = keyfile } - call FF.FinalizeToFile as t_023_FinalizeAlignedBai { + call FF.FinalizeToFile as t_024_FinalizeAlignedBai { input: outdir = reads_dir + "/aligned", - file = t_009_ApplyBQSR.recalibrated_bai, + file = t_010_ApplyBQSR.recalibrated_bai, keyfile = keyfile } # Finalize our metrics: - call FF.FinalizeToDir as t_024_FinalizeMetrics { + call FF.FinalizeToDir as t_025_FinalizeMetrics { input: outdir = metrics_dir, files = [ - t_006_MarkDuplicates.metrics, - t_008_BaseRecalibrator.recalibration_report, - t_010_SamStats.sam_stats, - t_013_ComputeBamStats.results, - t_014_ComputeBamStatsQ5.results, - t_015_ComputeBamStatsQ7.results, - t_016_ComputeBamStatsQ10.results, - t_017_ComputeBamStatsQ12.results, - t_018_ComputeBamStatsQ15.results, + t_007_MarkDuplicates.metrics, + t_009_BaseRecalibrator.recalibration_report, + t_011_SamStats.sam_stats, + t_014_ComputeBamStats.results, + t_015_ComputeBamStatsQ5.results, + t_016_ComputeBamStatsQ7.results, + t_017_ComputeBamStatsQ10.results, + t_018_ComputeBamStatsQ12.results, + t_019_ComputeBamStatsQ15.results, ], keyfile = keyfile } @@ -314,40 +313,40 @@ workflow SRFlowcell { File? unaligned_bai = unaligned_bai_o # Aligned BAM file - File aligned_bam = t_022_FinalizeAlignedBam.gcs_path - File aligned_bai = t_023_FinalizeAlignedBai.gcs_path + File aligned_bam = t_023_FinalizeAlignedBam.gcs_path + File aligned_bai = t_024_FinalizeAlignedBai.gcs_path # Unaligned read stats - Float num_reads = t_013_ComputeBamStats.results['reads'] - Float num_bases = t_013_ComputeBamStats.results['bases'] - Float raw_est_fold_cov = t_013_ComputeBamStats.results['bases']/t_012_ComputeGenomeLength.length + Float num_reads = t_014_ComputeBamStats.results['reads'] + Float num_bases = t_014_ComputeBamStats.results['bases'] + Float raw_est_fold_cov = t_014_ComputeBamStats.results['bases']/t_013_ComputeGenomeLength.length - Float read_length_mean = t_013_ComputeBamStats.results['subread_mean'] - Float read_length_median = t_013_ComputeBamStats.results['subread_median'] - Float read_length_stdev = t_013_ComputeBamStats.results['subread_stdev'] - Float read_length_N50 = t_013_ComputeBamStats.results['subread_n50'] + Float read_length_mean = t_014_ComputeBamStats.results['subread_mean'] + Float read_length_median = t_014_ComputeBamStats.results['subread_median'] + Float read_length_stdev = t_014_ComputeBamStats.results['subread_stdev'] + Float read_length_N50 = t_014_ComputeBamStats.results['subread_n50'] - Float read_qual_mean = t_013_ComputeBamStats.results['mean_qual'] - Float read_qual_median = t_013_ComputeBamStats.results['median_qual'] + Float read_qual_mean = t_014_ComputeBamStats.results['mean_qual'] + Float read_qual_median = t_014_ComputeBamStats.results['median_qual'] - Float num_reads_Q5 = t_014_ComputeBamStatsQ5.results['reads'] - Float num_reads_Q7 = t_015_ComputeBamStatsQ7.results['reads'] - Float num_reads_Q10 = t_016_ComputeBamStatsQ10.results['reads'] - Float num_reads_Q12 = t_017_ComputeBamStatsQ12.results['reads'] - Float num_reads_Q15 = t_018_ComputeBamStatsQ15.results['reads'] + Float num_reads_Q5 = t_015_ComputeBamStatsQ5.results['reads'] + Float num_reads_Q7 = t_016_ComputeBamStatsQ7.results['reads'] + Float num_reads_Q10 = t_017_ComputeBamStatsQ10.results['reads'] + Float num_reads_Q12 = t_018_ComputeBamStatsQ12.results['reads'] + Float num_reads_Q15 = t_019_ComputeBamStatsQ15.results['reads'] # Aligned read stats - Float aligned_num_reads = t_011_NanoPlotFromBam.stats_map['number_of_reads'] - Float aligned_num_bases = t_011_NanoPlotFromBam.stats_map['number_of_bases_aligned'] - Float aligned_frac_bases = t_011_NanoPlotFromBam.stats_map['fraction_bases_aligned'] - Float aligned_est_fold_cov = t_011_NanoPlotFromBam.stats_map['number_of_bases_aligned']/t_012_ComputeGenomeLength.length - - Float aligned_read_length_mean = t_011_NanoPlotFromBam.stats_map['mean_read_length'] - Float aligned_read_length_median = t_011_NanoPlotFromBam.stats_map['median_read_length'] - Float aligned_read_length_stdev = t_011_NanoPlotFromBam.stats_map['read_length_stdev'] - Float aligned_read_length_N50 = t_011_NanoPlotFromBam.stats_map['n50'] - - Float average_identity = t_011_NanoPlotFromBam.stats_map['average_identity'] - Float median_identity = t_011_NanoPlotFromBam.stats_map['median_identity'] + Float aligned_num_reads = t_012_NanoPlotFromBam.stats_map['number_of_reads'] + Float aligned_num_bases = t_012_NanoPlotFromBam.stats_map['number_of_bases_aligned'] + Float aligned_frac_bases = t_012_NanoPlotFromBam.stats_map['fraction_bases_aligned'] + Float aligned_est_fold_cov = t_012_NanoPlotFromBam.stats_map['number_of_bases_aligned']/t_013_ComputeGenomeLength.length + + Float aligned_read_length_mean = t_012_NanoPlotFromBam.stats_map['mean_read_length'] + Float aligned_read_length_median = t_012_NanoPlotFromBam.stats_map['median_read_length'] + Float aligned_read_length_stdev = t_012_NanoPlotFromBam.stats_map['read_length_stdev'] + Float aligned_read_length_N50 = t_012_NanoPlotFromBam.stats_map['n50'] + + Float average_identity = t_012_NanoPlotFromBam.stats_map['average_identity'] + Float median_identity = t_012_NanoPlotFromBam.stats_map['median_identity'] } } diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index ada39935b..2f6ca255e 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -155,6 +155,8 @@ task BwaMem2 { File ref_bwt File ref_pac + String? read_group + String prefix = "out" Boolean mark_short_splits_as_secondary = false @@ -173,6 +175,8 @@ task BwaMem2 { + 4*ceil(size(ref_pac, "GB")) + 4*ceil(size(ref_0123, "GB")) + String rg_arg = if defined(read_group) then " -R " else "" + command <<< set -euxo pipefail @@ -187,12 +191,14 @@ task BwaMem2 { # -v INT verbose level: 1=error, 2=warning, 3=message, 4+=debugging [3] # -t INT number of threads [1] # -Y use soft clipping for supplementary alignments + # -R STR read group header line such as '@RG\tID:foo\tSM:bar' [null] bwa-mem2 mem \ -K 100000000 \ -v 3 \ -t ${np} \ -Y \ + ~{rg_arg}'~{default="" sep=" -R " read_group}' \ ~{true='-M' false="" mark_short_splits_as_secondary} \ ~{ref_fasta} \ ~{fq_end1} \ From 1bf82ff14ed69c12cad09080475f2d8b14a5b5a3 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 4 Jan 2023 21:47:29 +0000 Subject: [PATCH 019/297] Fixed missing import. --- docker/sr-utils/environment.yml | 1 + docker/sr-utils/python/compute_sr_stats.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/sr-utils/environment.yml b/docker/sr-utils/environment.yml index 1734c29de..297805cca 100644 --- a/docker/sr-utils/environment.yml +++ b/docker/sr-utils/environment.yml @@ -7,3 +7,4 @@ dependencies: - samtools - bwa - pysam + - numpy diff --git a/docker/sr-utils/python/compute_sr_stats.py b/docker/sr-utils/python/compute_sr_stats.py index 59b5f3289..0a33e4f24 100644 --- a/docker/sr-utils/python/compute_sr_stats.py +++ b/docker/sr-utils/python/compute_sr_stats.py @@ -2,7 +2,7 @@ import numpy as np import pysam - +import argparse def n50(lengths): all_len = sorted(lengths, reverse=True) From c38b8ed34b854551c222ca0160b8ba9e8e4f2030 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 4 Jan 2023 17:38:49 -0500 Subject: [PATCH 020/297] Added in HC single sample calling and some joint calling stuff. --- wdl/SRWholeGenome.wdl | 89 +++++-- wdl/tasks/CallVariantsIllumina.wdl | 6 +- wdl/tasks/HaplotypeCaller.wdl | 403 +++++++++++++++++++++++------ wdl/tasks/SRJointGenotyping.wdl | 171 ++++++++++++ wdl/tasks/SRUtils.wdl | 51 ++++ wdl/tasks/Utils.wdl | 3 + 6 files changed, 614 insertions(+), 109 deletions(-) create mode 100644 wdl/tasks/SRJointGenotyping.wdl diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index d952d414a..e9d2e9b78 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -6,9 +6,9 @@ version 1.0 ## prior to variant calling. ###################################################################################### -import "tasks/SRUtils.wdl" as PB import "tasks/Utils.wdl" as Utils import "tasks/CallVariantsIllumina.wdl" as VAR +import "tasks/HaplotypeCaller.wdl" as HC import "tasks/Finalize.wdl" as FF import "tasks/SampleLevelAlignedMetrics.wdl" as COV @@ -26,11 +26,17 @@ workflow SRWholeGenome { String gcs_out_root_dir + File dbsnp_vcf + Boolean call_small_variants = true - Boolean? run_dv_pepper_analysis = true - Int? dvp_threads = 32 - Int? dvp_memory = 128 + Boolean run_HC_analysis = true + Boolean run_dv_pepper_analysis = true + Int dvp_threads = 32 + Int dvp_memory = 128 + + String mito_contig = "chrM" + Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! } Map[String, String] ref_map = read_map(ref_map_file) @@ -66,15 +72,19 @@ workflow SRWholeGenome { if (defined(bed_to_compute_coverage)) { call FF.FinalizeToFile as FinalizeRegionalCoverage { input: outdir = dir, file = select_first([coverage.bed_cov_summary]) } } #################################################################################################### - if (call_small_variants) { - # verify arguments are provided - if (call_small_variants) { - if (! defined(run_dv_pepper_analysis)) {call Utils.StopWorkflow as run_dv_pepper_analysis_not_provided {input: reason = "Unprovided arg run_dv_pepper_analysis"}} - if (! defined(dvp_threads)) {call Utils.StopWorkflow as dvp_threads_not_provided {input: reason = "Unprovided arg dvp_threads"}} + # Some input handling: + if ((!run_dv_pepper_analysis) && (!run_HC_analysis)) { + call Utils.StopWorkflow as short_variant_caller_analysis_not_provided { + input: reason = "One of the following must be set to true: run_dv_pepper_analysis(~{run_dv_pepper_analysis}), run_HC_analysis(~{run_HC_analysis})" } + } - call VAR.CallVariants { + String smalldir = outdir + "/variants/small" + + # Handle DeepVariant First: + if (run_dv_pepper_analysis) { + call VAR.CallVariants as CallVariantsWithDeepVariant { input: bam = bam, bai = bai, @@ -83,25 +93,47 @@ workflow SRWholeGenome { ref_fasta_fai = ref_map['fai'], ref_dict = ref_map['dict'], - prefix = participant_name, + prefix = participant_name + ".deep_variant", call_small_variants = call_small_variants, - run_dv_pepper_analysis = select_first([run_dv_pepper_analysis]), - dvp_threads = select_first([dvp_threads]), - dvp_memory = select_first([dvp_memory]), + run_dv_pepper_analysis = run_dv_pepper_analysis, + dvp_threads = dvp_threads, + dvp_memory = dvp_memory, + + mito_contig = mito_contig, + contigs_names_to_ignore = contigs_names_to_ignore, } - String smalldir = outdir + "/variants/small" + call FF.FinalizeToFile as FinalizeDVPepperVcf { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_vcf])} + call FF.FinalizeToFile as FinalizeDVPepperTbi { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_tbi])} + call FF.FinalizeToFile as FinalizeDVPepperGVcf { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_g_vcf])} + call FF.FinalizeToFile as FinalizeDVPepperGTbi { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_g_tbi])} + } - if (call_small_variants) { - if (select_first([run_dv_pepper_analysis])) { - call FF.FinalizeToFile as FinalizeDVPepperVcf { input: outdir = smalldir, file = select_first([CallVariants.dvp_vcf])} - call FF.FinalizeToFile as FinalizeDVPepperTbi { input: outdir = smalldir, file = select_first([CallVariants.dvp_tbi])} - call FF.FinalizeToFile as FinalizeDVPepperGVcf { input: outdir = smalldir, file = select_first([CallVariants.dvp_g_vcf])} - call FF.FinalizeToFile as FinalizeDVPepperGTbi { input: outdir = smalldir, file = select_first([CallVariants.dvp_g_tbi])} - } + # Now we handle HaplotypeCaller data: + if (run_HC_analysis) { + call HC.CallVariantsWithHaplotypeCaller { + input: + bam = bam, + bai = bai, + sample_id = participant_name, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + + prefix = participant_name + ".haplotype_caller", + + mito_contig = mito_contig, + contigs_names_to_ignore = contigs_names_to_ignore, } + + call FF.FinalizeToFile as FinalizeHCVcf { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_vcf])} + call FF.FinalizeToFile as FinalizeHCTbi { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_vcf_index])} + call FF.FinalizeToFile as FinalizeHCGVcf { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_gvcf])} + call FF.FinalizeToFile as FinalizeHCGTbi { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_gvcf_index])} + call FF.FinalizeToFile as FinalizeHCBamOut { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.bamout])} + call FF.FinalizeToFile as FinalizeHCBaiOut { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.bamout_index])} } output { @@ -125,9 +157,18 @@ workflow SRWholeGenome { ######################################## - File? dvp_vcf = FinalizeDVPepperVcf.gcs_path - File? dvp_tbi = FinalizeDVPepperTbi.gcs_path + File? dvp_vcf = FinalizeDVPepperVcf.gcs_path + File? dvp_tbi = FinalizeDVPepperTbi.gcs_path File? dvp_g_vcf = FinalizeDVPepperGVcf.gcs_path File? dvp_g_tbi = FinalizeDVPepperGTbi.gcs_path + + ######################################## + + File? hc_vcf = FinalizeHCVcf.gcs_path + File? hc_tbi = FinalizeHCTbi.gcs_path + File? hc_g_vcf = FinalizeHCGVcf.gcs_path + File? hc_g_tbi = FinalizeHCGTbi.gcs_path + File? hc_bamout = FinalizeHCBamOut.gcs_path + File? hc_baiout = FinalizeHCBaiOut.gcs_path } } diff --git a/wdl/tasks/CallVariantsIllumina.wdl b/wdl/tasks/CallVariantsIllumina.wdl index a5cfca4cf..347d1840c 100644 --- a/wdl/tasks/CallVariantsIllumina.wdl +++ b/wdl/tasks/CallVariantsIllumina.wdl @@ -25,6 +25,9 @@ workflow CallVariants { Boolean run_dv_pepper_analysis Int? dvp_threads Int? dvp_memory + + String mito_contig = "chrM" + Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! } ###################################################################### @@ -36,8 +39,7 @@ workflow CallVariants { # todo: merge the two scattering scheme into a better one if (call_small_variants) { # Scatter by chromosome - Array[String] default_filter = ['random', 'chrUn', 'decoy', 'alt', 'HLA', 'EBV'] - Array[String] use_filter = if (call_small_vars_on_mitochondria) then default_filter else flatten([['chrM'],default_filter]) + Array[String] use_filter = if (call_small_vars_on_mitochondria) then contigs_names_to_ignore else flatten([[mito_contig], contigs_names_to_ignore]) call Utils.MakeChrIntervalList as SmallVariantsScatterPrepp { input: ref_dict = ref_dict, diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index 6ad616220..7bd26251c 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -1,88 +1,325 @@ version 1.0 +import "Structs.wdl" import "Utils.wdl" +import "SRUtils.wdl" as SRUTIL +import "SRJointGenotyping.wdl" as SRJOINT + +workflow CallVariantsWithHaplotypeCaller { + meta { + author: "Jonn Smith" + description: "A workflow for calling small variants with GATK HaplotypeCaller from an Illumina BAM file." + } + + input { + File bam + File bai + + String prefix + String sample_id + + File ref_fasta + File ref_fasta_fai + File ref_dict + + File dbsnp_vcf + + Boolean call_vars_on_mitochondria = true + + String mito_contig = "chrM" + Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! + } + + # Scatter by chromosome: + Array[String] use_filter = if (call_vars_on_mitochondria) then contigs_names_to_ignore else flatten([[mito_contig], contigs_names_to_ignore]) + call Utils.MakeChrIntervalList as SmallVariantsScatterPrep { + input: + ref_dict = ref_dict, + filter = use_filter + } + + # Call over the scattered intervals: + scatter (c in SmallVariantsScatterPrep.chrs) { + String contig_for_small_var = c[0] + + call HaplotypeCaller_GATK4_VCF as CallVariantsWithHC { + input: + input_bam = bam, + input_bam_index = bai, + prefix = prefix + "." + contig_for_small_var, + ref_fasta = ref_fasta, + ref_fasta_index = ref_fasta_fai, + ref_dict = ref_dict, + make_gvcf = true, + make_bamout = true, + single_interval = contig_for_small_var, + contamination = 0, + use_spanning_event_genotyping = true + } + } + + # Merge the output GVCFs: + call SRUTIL.MergeVCFs as MergeGVCFs { + input: + input_vcfs = CallVariantsWithHC.output_vcf, + input_vcfs_indexes = CallVariantsWithHC.output_vcf_index, + prefix = prefix + } + + # Merge the output BAMs: + call MergeBamouts as MergeVariantCalledBamOuts { + input: + bams = CallVariantsWithHC.bamout, + prefix = "~{prefix}.bamout" + } + + # Now reblock the GVCF to combine hom ref blocks and save $ / storage: + call ReblockGVCF { + input: + gvcf = MergeGVCFs.output_vcf, + gvcf_index = MergeGVCFs.output_vcf_index, + ref_fasta = ref_fasta, + ref_fasta_fai = ref_fasta_fai, + ref_dict = ref_dict, + prefix = prefix + } + + # Collapse the GVCF into a regular VCF: + call SRJOINT.GenotypeGVCFs as CollapseGVCFtoVCF { + input: + input_gvcf_data = ReblockGVCF.output_gvcf, + interval_list = SmallVariantsScatterPrep.interval_list, + ref_fasta = ref_fasta, + ref_fasta_fai = ref_fasta_fai, + ref_dict = ref_dict, + dbsnp_vcf = dbsnp_vcf, + prefix = prefix, + } + + output { + File output_gvcf = ReblockGVCF.output_gvcf + File output_gvcf_index = ReblockGVCF.output_gvcf_index + File output_vcf = CollapseGVCFtoVCF.output_vcf + File output_vcf_index = CollapseGVCFtoVCF.output_vcf_index + File bamout = MergeVariantCalledBamOuts.output_bam + File bamout_index = MergeVariantCalledBamOuts.output_bam_index + } +} task HaplotypeCaller_GATK4_VCF { - input { - File input_bam - File input_bam_index - File interval_list - String vcf_basename - File ref_dict - File ref_fasta - File ref_fasta_index - Float? contamination - Boolean make_gvcf - Boolean make_bamout - Int preemptible_tries - Int hc_scatter - Boolean run_dragen_mode_variant_calling = false - Boolean use_dragen_hard_filtering = false - Boolean use_spanning_event_genotyping = true - File? dragstr_model - String gatk_docker = "us.gcr.io/broad-gatk/gatk:4.2.6.1" - Int memory_multiplier = 1 - } - - Int memory_size_mb = ceil(8000 * memory_multiplier) - - String output_suffix = if make_gvcf then ".g.vcf.gz" else ".vcf.gz" - String output_file_name = vcf_basename + output_suffix - - Float ref_size = size(ref_fasta, "GiB") + size(ref_fasta_index, "GiB") + size(ref_dict, "GiB") - Int disk_size = ceil(((size(input_bam, "GiB") + 30) / hc_scatter) + ref_size) + 20 - - String bamout_arg = if make_bamout then "-bamout ~{vcf_basename}.bamout.bam" else "" - - parameter_meta { - input_bam: { - localization_optional: true - } - } - - command <<< - set -e - # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit - # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from - # memory_size_gb because of Cromwell's retry with more memory feature. - # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, - # which do not rely on the output format of the `free` command. - available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') - let java_memory_size_mb=available_memory_mb-1024 - echo Total available memory: ${available_memory_mb} MB >&2 - echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 - - gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ - HaplotypeCaller \ - -R ~{ref_fasta} \ - -I ~{input_bam} \ - -L ~{interval_list} \ - -O ~{output_file_name} \ - -contamination ~{default=0 contamination} \ - -G StandardAnnotation -G StandardHCAnnotation ~{true="-G AS_StandardAnnotation" false="" make_gvcf} \ - ~{true="--dragen-mode" false="" run_dragen_mode_variant_calling} \ - ~{false="--disable-spanning-event-genotyping" true="" use_spanning_event_genotyping} \ - ~{if defined(dragstr_model) then "--dragstr-params-path " + dragstr_model else ""} \ - -GQB 10 -GQB 20 -GQB 30 -GQB 40 -GQB 50 -GQB 60 -GQB 70 -GQB 80 -GQB 90 \ - ~{true="-ERC GVCF" false="" make_gvcf} \ - ~{bamout_arg} - - # Cromwell doesn't like optional task outputs, so we have to touch this file. - touch ~{vcf_basename}.bamout.bam - >>> - - runtime { - docker: gatk_docker - preemptible: preemptible_tries - memory: "~{memory_size_mb} MiB" - cpu: "2" - bootDiskSizeGb: 15 - disks: "local-disk " + disk_size + " HDD" - } - - output { - File output_vcf = "~{output_file_name}" - File output_vcf_index = "~{output_file_name}.tbi" - File bamout = "~{vcf_basename}.bamout.bam" - } -} \ No newline at end of file + meta { + author: "Jonn Smith" + notes: "Adapted from the WARP pipeline found here: https://github.com/broadinstitute/warp.git" + } + + input { + File input_bam + File input_bam_index + + String prefix + + File ref_dict + File ref_fasta + File ref_fasta_index + + Boolean make_gvcf + Boolean make_bamout + + String? single_interval + File? interval_list + Float? contamination + + Boolean use_spanning_event_genotyping = true + + RuntimeAttr? runtime_attr_override + } + + String output_suffix = if make_gvcf then ".g.vcf.gz" else ".vcf.gz" + String output_file_name = prefix + output_suffix + + Float ref_size = size(ref_fasta, "GiB") + size(ref_fasta_index, "GiB") + size(ref_dict, "GiB") + Int disk_size = 2*ceil(((size(input_bam, "GiB") + 30)) + ref_size) + 20 + + String bamout_arg = if make_bamout then "-bamout ~{prefix}.bamout.bam" else "" + + String interval_arg = if (defined(interval_list) || defined(single_interval)) then " -L " else "" + String interval_arg_value = if defined(interval_list) then interval_list else if defined(single_interval) then single_interval else "" + + parameter_meta { + input_bam: { localization_optional: true } + } + + command <<< + set -euxo pipefail + + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + + available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') + let java_memory_size_mb=available_memory_mb-1024 + echo Total available memory: ${available_memory_mb} MB >&2 + echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 + + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ + HaplotypeCaller \ + -R ~{ref_fasta} \ + -I ~{input_bam} \ + ~{interval_arg}~{default="" sep=" -L " interval_list} \ + -O ~{output_file_name} \ + -contamination ~{default=0 contamination} \ + -GQB 10 -GQB 20 -GQB 30 -GQB 40 -GQB 50 -GQB 60 -GQB 70 -GQB 80 -GQB 90 \ + ~{false="--disable-spanning-event-genotyping" true="" use_spanning_event_genotyping} \ + -G StandardAnnotation -G StandardHCAnnotation ~{true="-G AS_StandardAnnotation" false="" make_gvcf} \ + ~{true="-ERC GVCF" false="" make_gvcf} \ + ~{bamout_arg} + + # Cromwell doesn't like optional task outputs, so we have to touch this file. + touch ~{prefix}.bamout.bam + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 16, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File output_vcf = "~{output_file_name}" + File output_vcf_index = "~{output_file_name}.tbi" + File bamout = "~{prefix}.bamout.bam" + } +} + + +# This task is here because merging bamout files using Picard produces an error. +task MergeBamouts { + + input { + Array[File] bams + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = ceil(size(bams, "GiB") * 2) + 10 + + command <<< + + set -euxo pipefail + + # Make sure we use all our processors: + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + + ithreads=${np} + let mthreads=${np}-1 + + samtools merge -@${mthreads} ~{prefix}.bam ~{sep=" " bams} + samtools index -@${ithreads} ~{prefix}.bam + mv ~{prefix}.bam.bai ~{prefix}.bai + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "biocontainers/samtools:1.3.1" + } + + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File output_bam = "~{prefix}.bam" + File output_bam_index = "~{prefix}.bai" + } +} + +task ReblockGVCF { + + input { + File gvcf + File gvcf_index + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String prefix + Float? tree_score_cutoff + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = ceil((size(gvcf, "GiB") * 4) + size(ref_fasta, "GiB") + size(ref_fasta_fai, "GiB") + size(ref_dict, "GiB") + 10) + + command { + set -euxo pipefail + + gatk --java-options "-Xms3000m -Xmx3000m" \ + ReblockGVCF \ + -R ~{ref_fasta} \ + -V ~{gvcf} \ + -do-qual-approx \ + --floor-blocks -GQB 20 -GQB 30 -GQB 40 \ + ~{"--tree-score-threshold-to-no-call " + tree_score_cutoff} \ + -O ~{prefix}.rb.g.vcf.gz + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File output_gvcf = "~{prefix}.rb.g.vcf.gz" + File output_gvcf_index = "~{prefix}.rb.g.vcf.gz.tbi" + } +} diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl new file mode 100644 index 000000000..1e9584219 --- /dev/null +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -0,0 +1,171 @@ +version 1.0 + +import "Structs.wdl" + +task ImportGVCFs { + + input { + File sample_name_map + + File interval_list + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String prefix + + Int batch_size + + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size(ref_fasta, "GB") + size(ref_fasta_fai, "GB") + size(ref_dict, "GB")) + + Int disk_size = 1 + 4*ref_size + + command <<< + set -euxo pipefail + + # Make sure that the output directory does not exist: + [ -e ~{prefix} ] && rm -rf ~{prefix} + + # + # Notes from WARP Team: + # + # We've seen some GenomicsDB performance regressions related to intervals, so we're going to pretend we only have a single interval + # using the --merge-input-intervals arg + # There's no data in between since we didn't run HaplotypeCaller over those loci so we're not wasting any compute + + # The memory setting here is very important and must be several GiB lower + # than the total memory allocated to the VM because this tool uses + # a significant amount of non-heap memory for native libraries. + # Also, testing has shown that the multithreaded reader initialization + # does not scale well beyond 5 threads, so don't increase beyond that. + gatk --java-options "-Xms8000m -Xmx25000m" \ + GenomicsDBImport \ + --genomicsdb-workspace-path ~{prefix} \ + --batch-size ~{batch_size} \ + -L ~{interval_list} \ + --sample-name-map ~{sample_name_map} \ + --reader-threads 5 \ + --merge-input-intervals \ + --consolidate + + tar -cf ~{prefix}.tar ~{prefix} + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File output_genomicsdb = "~{prefix}.tar" + } +} + +task GenotypeGVCFs { + + input { + File input_gvcf_data + File interval_list + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String dbsnp_vcf + + String prefix + + Boolean keep_combined_raw_annotations = false + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size(ref_fasta, "GB") + size(ref_fasta_fai, "GB") + size(ref_dict, "GB")) + Int db_snp_size = ceil(size(dbsnp_vcf, "GB")) + + Int disk_size = 1 + 4*ceil(size(input_gvcf_data, "GB")) + ref_size + db_snp_size + + parameter_meta { + input_gvcf_data: { help: "Either a single GVCF file or a GenomicsDB Tar file." } + interval_list: { + localization_optional: true + } + } + + command <<< + set -euxo pipefail + + # We must determine if our input variants are in a genomicsdb file or in a VCF. + # The easiest way is to see if the input is a .tar file: + + is_genomics_db=true + filename=$(basename -- "~{input_gvcf_data}") + extension="${filename##*.}" + if [[ "${extension}" != "tar" ]] ; then + is_genomics_db=false + fi + + if $is_genomics_db ; then + tar -xf ~{input_gvcf_data} + INPUT_FILE="gendb://$(basename ~{input_gvcf_data} .tar)" + else + INPUT_FILE=~{input_gvcf_data} + fi + + gatk --java-options "-Xms8000m -Xmx25000m" \ + GenotypeGVCFs \ + -R ~{ref_fasta} \ + -O ~{prefix}.vcf \ + -D ~{dbsnp_vcf} \ + -G StandardAnnotation -G AS_StandardAnnotation \ + --only-output-calls-starting-in-intervals \ + -V ${INPUT_FILE} \ + -L ~{interval_list} \ + ~{true='--keep-combined-raw-annotations' false='' keep_combined_raw_annotations} \ + --merge-input-intervals + >>> + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 26, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File output_vcf = "~{prefix}.vcf" + File output_vcf_index = "~{prefix}.vcf.tbi" + } +} \ No newline at end of file diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 2f6ca255e..03674823c 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -641,3 +641,54 @@ task ComputeBamStats { } } +task MergeVCFs { + meta { + description: "Combine multiple VCFs or GVCFs from scattered HaplotypeCaller runs" + } + + input { + Array[File] input_vcfs + Array[File] input_vcfs_indexes + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int compression_level = 2 + Int java_memory_size_mb = 30768 + + Int disk_size = ceil(size(input_vcfs, "GiB") * 2.5) + 10 + + command { + java -Xms2000m -Xmx2500m -jar /usr/picard/picard.jar \ + MergeVcfs \ + INPUT=~{sep=' INPUT=' input_vcfs} \ + OUTPUT=~{prefix}.vcf + } + + output { + File output_vcf = "~{prefix}.vcf" + File output_vcf_index = "~{prefix}.vcf.tbi" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 3, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gotc-prod/picard-cloud:2.26.10" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index dc7aee9e9..1593c3059 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -284,10 +284,13 @@ task MakeChrIntervalList { sed 's/[SL]N://g' | \ grep -v -e '^@HD' ~{true='-e' false='' length(filter) > 0} ~{sep=" -e " filter} | \ tee chrs.txt + + cat chrs.txt | awk '{printf("%s:%d-%d\n", $1,$2,$3)}' > intervalList.intervals >>> output { Array[Array[String]] chrs = read_tsv("chrs.txt") + File interval_list = "intervalList.intervals" } ######################### From c1c1c7745dbf0f5394e8031230d86ad5cc5ac5e6 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 5 Jan 2023 10:37:17 -0500 Subject: [PATCH 021/297] Updated version of SRUtils docker. --- wdl/tasks/SRUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 03674823c..99d5a8aad 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -627,7 +627,7 @@ task ComputeBamStats { boot_disk_gb: 10, preemptible_tries: 3, max_retries: 2, - docker: "us.gcr.io/broad-dsp-lrma/sr-utils:0.2.0" + docker: "us.gcr.io/broad-dsp-lrma/sr-utils:0.2.1" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { From 878b2c76c438dfad1ebd8f2bb744016e82cfbddf Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 5 Jan 2023 13:17:14 -0500 Subject: [PATCH 022/297] Fixing minor issues and more joint calling implementation. --- wdl/SRFlowcell.wdl | 20 +++---- wdl/SRJointCallGVCFs.wdl | 81 +++++++++++++++++++++++++++ wdl/SRWholeGenome.wdl | 3 +- wdl/tasks/HaplotypeCaller.wdl | 6 ++ wdl/tasks/SRJointGenotyping.wdl | 99 +++++++++++++++++++++++++++++++-- wdl/tasks/SRUtils.wdl | 2 +- 6 files changed, 194 insertions(+), 17 deletions(-) create mode 100644 wdl/SRJointCallGVCFs.wdl diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 335b5d256..7cde96c64 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -277,12 +277,12 @@ workflow SRFlowcell { t_007_MarkDuplicates.metrics, t_009_BaseRecalibrator.recalibration_report, t_011_SamStats.sam_stats, - t_014_ComputeBamStats.results, - t_015_ComputeBamStatsQ5.results, - t_016_ComputeBamStatsQ7.results, - t_017_ComputeBamStatsQ10.results, - t_018_ComputeBamStatsQ12.results, - t_019_ComputeBamStatsQ15.results, + t_014_ComputeBamStats.results_file, + t_015_ComputeBamStatsQ5.results_file, + t_016_ComputeBamStatsQ7.results_file, + t_017_ComputeBamStatsQ10.results_file, + t_018_ComputeBamStatsQ12.results_file, + t_019_ComputeBamStatsQ15.results_file, ], keyfile = keyfile } @@ -321,10 +321,10 @@ workflow SRFlowcell { Float num_bases = t_014_ComputeBamStats.results['bases'] Float raw_est_fold_cov = t_014_ComputeBamStats.results['bases']/t_013_ComputeGenomeLength.length - Float read_length_mean = t_014_ComputeBamStats.results['subread_mean'] - Float read_length_median = t_014_ComputeBamStats.results['subread_median'] - Float read_length_stdev = t_014_ComputeBamStats.results['subread_stdev'] - Float read_length_N50 = t_014_ComputeBamStats.results['subread_n50'] + Float read_length_mean = t_014_ComputeBamStats.results['read_mean'] + Float read_length_median = t_014_ComputeBamStats.results['read_median'] + Float read_length_stdev = t_014_ComputeBamStats.results['read_stdev'] + Float read_length_N50 = t_014_ComputeBamStats.results['read_n50'] Float read_qual_mean = t_014_ComputeBamStats.results['mean_qual'] Float read_qual_median = t_014_ComputeBamStats.results['median_qual'] diff --git a/wdl/SRJointCallGVCFs.wdl b/wdl/SRJointCallGVCFs.wdl new file mode 100644 index 000000000..0ee1d5755 --- /dev/null +++ b/wdl/SRJointCallGVCFs.wdl @@ -0,0 +1,81 @@ +version 1.0 + +############################################################################################################# +## A workflow that performs joint calling on single-sample gVCFs from GATK4 HaplotypeCaller using GenomicsDB. +############################################################################################################# + +import "SRJointGenotyping.wdl" as SRJOINT +import "tasks/Finalize.wdl" as FF + +workflow SRJointCallGVCFs { + input { + Array[File] gvcfs + Array[File] tbis + + File ref_map_file + + File interval_list + + String prefix + + String gcs_out_root_dir + } + + parameter_meta { + gvcfs: "GCS paths to gVCF files" + tbis: "GCS paths to gVCF tbi files" + ref_map_file: "table indicating reference sequence and auxillary file locations" + prefix: "prefix for output joint-called gVCF and tabix index" + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRJointCallGVCFs/~{prefix}" + + Map[String, String] ref_map = read_map(ref_map_file) + + # Create sample-name map: + call SRJOINT.CreateSampleNameMap as CreateSampleNameMap { + input: + gvcfs = gvcfs, + prefix = prefix + } + + # Import our data into GenomicsDB: + call SRJOINT.ImportGVCFs as ImportGVCFsIntoGenomicsDB { + input: + sample_name_map = CreateSampleNameMap.sample_name_map, + interval_list = interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix, + batch_size = 50, + } + + # Joint call + call SRJOINT.GenotypeGVCFs as JointCallGVCFs { + input: + input_gvcf_data = ImportGVCFsIntoGenomicsDB.output_genomicsdb, + interval_list = interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + dbsnp_vcf = ref_map["known_sites_vcf"], + prefix = prefix, + } + + # Finalize + call FF.FinalizeToFile as FinalizeGVCF { input: outdir = outdir, file = JointCallGVCFs.output_vcf } + call FF.FinalizeToFile as FinalizeTBI { input: outdir = outdir, file = JointCallGVCFs.output_vcf_index } + + ########## + # store the results into designated bucket + ########## + + output { + File joint_gvcf = FinalizeGVCF.gcs_path + File joint_gvcf_tbi = FinalizeTBI.gcs_path + } +} + + diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index e9d2e9b78..c5591a8ba 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -26,8 +26,6 @@ workflow SRWholeGenome { String gcs_out_root_dir - File dbsnp_vcf - Boolean call_small_variants = true Boolean run_HC_analysis = true @@ -121,6 +119,7 @@ workflow SRWholeGenome { ref_fasta = ref_map['fasta'], ref_fasta_fai = ref_map['fai'], ref_dict = ref_map['dict'], + dbsnp_vcf = ref_map["known_sites_vcf"], prefix = participant_name + ".haplotype_caller", diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index 7bd26251c..e86fc1837 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -26,6 +26,8 @@ workflow CallVariantsWithHaplotypeCaller { Boolean call_vars_on_mitochondria = true + Int ploidy = 2 + String mito_contig = "chrM" Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! } @@ -54,6 +56,7 @@ workflow CallVariantsWithHaplotypeCaller { make_bamout = true, single_interval = contig_for_small_var, contamination = 0, + ploidy = ploidy, use_spanning_event_genotyping = true } } @@ -122,6 +125,8 @@ task HaplotypeCaller_GATK4_VCF { File ref_fasta File ref_fasta_index + Int ploidy = 2 + Boolean make_gvcf Boolean make_bamout @@ -170,6 +175,7 @@ task HaplotypeCaller_GATK4_VCF { ~{interval_arg}~{default="" sep=" -L " interval_list} \ -O ~{output_file_name} \ -contamination ~{default=0 contamination} \ + --sample-ploidy ~{ploidy} \ -GQB 10 -GQB 20 -GQB 30 -GQB 40 -GQB 50 -GQB 60 -GQB 70 -GQB 80 -GQB 90 \ ~{false="--disable-spanning-event-genotyping" true="" use_spanning_event_genotyping} \ -G StandardAnnotation -G StandardHCAnnotation ~{true="-G AS_StandardAnnotation" false="" make_gvcf} \ diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 1e9584219..f92136ed7 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -1,6 +1,97 @@ version 1.0 import "Structs.wdl" +import "Utils.wdl" as Utils + +task CreateSampleNameMap { + + meta { + description: "Creates the sample / name-map file of the GVCFs for ingest into ImportGVCFs. + NOTE: Some of this functionality is duplicated from Utils.InferSampleName. + This is intentional - we don't want to localize all these files or shard over potentially thousands of input GVCFs." + } + + input { + Array[File] gvcfs + String prefix + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + gvcfs: { + help: "Array of single-sample GVCF files.", + localization_optional: true + } + } + + Int disk_size_gb = 20 + + String outfile_name = "~{prefix}.sample_name_map.tsv" + + command <<< + set -euxo pipefail + + # Put our gvcfs into a file we can iterate over: + gvcf_file_list=~{write_lines(gvcfs)} + + # Initialize a file for the sample names: + [ -e ~{outfile_name} ] && rm -rf ~{outfile_name} + + # Set our access token: + export GCS_OAUTH_TOKEN=$(gcloud auth application-default print-access-token) + + let i=1 + while read file_path ; do + + # Get our read group from the header: + samtools view -H ${file_path} > header.txt + [ ! $(grep -q '^@RG' header.txt) ] && echo "No read group line found in GVCF: ${file_path}" && exit 1 + + # Get the sample name from the read group: + grep '^@RG' header.txt | sed 's/\t/\n/g' | grep '^SM:' | sed 's/SM://g' | sort | uniq > sample.names.txt + [[ $(wc -l sample.names.txt) -gt 1 ]] && echo "Multiple sample names found in GVCF: ${file_path}" && exit 1 + + # Make sure the samplename has an actual name: + [ $(grep -iq "unnamedsample" sample.names.txt) ] && echo "Sample name found to be unnamedsample in GVCF: ${file_path}" && exit 1 + + # Add the sample name and GVCF path to the sample name file: + echo -e "$(cat ${sample.names.txt})\t${file_path}" >> ~{outfile_name} + + let i=$i+1 + if [[ $i -gt 50 ]] ; then + # Periodically we should update the token so we don't have problems with long file lists: + export GCS_OAUTH_TOKEN=$(gcloud auth application-default print-access-token) + i=0 + fi + done < ${gvcf_file_list} + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 2, + disk_gb: disk_size_gb, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-basic:0.1.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File sample_name_map = outfile_name + } +} task ImportGVCFs { @@ -15,7 +106,7 @@ task ImportGVCFs { String prefix - Int batch_size + Int batch_size = 50 RuntimeAttr? runtime_attr_override } @@ -44,7 +135,7 @@ task ImportGVCFs { # does not scale well beyond 5 threads, so don't increase beyond that. gatk --java-options "-Xms8000m -Xmx25000m" \ GenomicsDBImport \ - --genomicsdb-workspace-path ~{prefix} \ + --genomicsdb-workspace-path ~{prefix}.genomicsDB \ --batch-size ~{batch_size} \ -L ~{interval_list} \ --sample-name-map ~{sample_name_map} \ @@ -52,7 +143,7 @@ task ImportGVCFs { --merge-input-intervals \ --consolidate - tar -cf ~{prefix}.tar ~{prefix} + tar -cf ~{prefix}.genomicsDB.tar ~{prefix}.genomicsDB >>> ######################### @@ -77,7 +168,7 @@ task ImportGVCFs { } output { - File output_genomicsdb = "~{prefix}.tar" + File output_genomicsdb = "~{prefix}.genomicsDB.tar" } } diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 99d5a8aad..d9c6d97ce 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -625,7 +625,7 @@ task ComputeBamStats { mem_gb: 16, disk_gb: disk_size, boot_disk_gb: 10, - preemptible_tries: 3, + preemptible_tries: 1, max_retries: 2, docker: "us.gcr.io/broad-dsp-lrma/sr-utils:0.2.1" } From 85b5f6102c6eee4fbb4b0232ba8e5878dddad30c Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 5 Jan 2023 18:18:29 +0000 Subject: [PATCH 023/297] Fixing issue in `sr-utils` python script. --- docker/sr-utils/Makefile | 2 +- docker/sr-utils/environment.yml | 2 ++ docker/sr-utils/python/compute_sr_stats.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docker/sr-utils/Makefile b/docker/sr-utils/Makefile index c44223e7e..cbfd359fd 100644 --- a/docker/sr-utils/Makefile +++ b/docker/sr-utils/Makefile @@ -1,5 +1,5 @@ IMAGE_NAME = sr-utils -VERSION = 0.2.0 +VERSION = 0.2.1 TAG1 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):$(VERSION) TAG2 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):latest diff --git a/docker/sr-utils/environment.yml b/docker/sr-utils/environment.yml index 297805cca..ebbc6f0bd 100644 --- a/docker/sr-utils/environment.yml +++ b/docker/sr-utils/environment.yml @@ -8,3 +8,5 @@ dependencies: - bwa - pysam - numpy + - tqdm + diff --git a/docker/sr-utils/python/compute_sr_stats.py b/docker/sr-utils/python/compute_sr_stats.py index 0a33e4f24..aeb2e9fe9 100644 --- a/docker/sr-utils/python/compute_sr_stats.py +++ b/docker/sr-utils/python/compute_sr_stats.py @@ -3,6 +3,7 @@ import numpy as np import pysam import argparse +from tqdm import tqdm def n50(lengths): all_len = sorted(lengths, reverse=True) From b31aba44921339acb6fe67c52e894f85bd7962ef Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 5 Jan 2023 13:44:35 -0500 Subject: [PATCH 024/297] Fixed some bugs in SRWholeGenome and SRJointGenotyping. --- wdl/SRWholeGenome.wdl | 10 ++++------ wdl/tasks/SRJointGenotyping.wdl | 4 +--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index c5591a8ba..895cd3012 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -10,7 +10,6 @@ import "tasks/Utils.wdl" as Utils import "tasks/CallVariantsIllumina.wdl" as VAR import "tasks/HaplotypeCaller.wdl" as HC import "tasks/Finalize.wdl" as FF - import "tasks/SampleLevelAlignedMetrics.wdl" as COV workflow SRWholeGenome { @@ -18,8 +17,6 @@ workflow SRWholeGenome { Array[File] aligned_bams Array[File] aligned_bais - File? bed_to_compute_coverage - File ref_map_file String participant_name @@ -33,7 +30,8 @@ workflow SRWholeGenome { Int dvp_threads = 32 Int dvp_memory = 128 - String mito_contig = "chrM" + File? bed_to_compute_coverage + Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! } @@ -99,7 +97,7 @@ workflow SRWholeGenome { dvp_threads = dvp_threads, dvp_memory = dvp_memory, - mito_contig = mito_contig, + mito_contig = ref_map['mt_chr_name'], contigs_names_to_ignore = contigs_names_to_ignore, } @@ -123,7 +121,7 @@ workflow SRWholeGenome { prefix = participant_name + ".haplotype_caller", - mito_contig = mito_contig, + mito_contig = ref_map['mt_chr_name'], contigs_names_to_ignore = contigs_names_to_ignore, } diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index f92136ed7..a2e0c83fc 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -6,9 +6,7 @@ import "Utils.wdl" as Utils task CreateSampleNameMap { meta { - description: "Creates the sample / name-map file of the GVCFs for ingest into ImportGVCFs. - NOTE: Some of this functionality is duplicated from Utils.InferSampleName. - This is intentional - we don't want to localize all these files or shard over potentially thousands of input GVCFs." + description: "Creates the sample / name-map file of the GVCFs for ingest into ImportGVCFs. NOTE: Some of this functionality is duplicated from Utils.InferSampleName. This is intentional - we don't want to localize all these files or shard over potentially thousands of input GVCFs." } input { From e533b2f31f8b4e3e4fc35861d6cdb740ee21dc5c Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 5 Jan 2023 14:09:20 -0500 Subject: [PATCH 025/297] Updated `ApplyBQSR` to emit original qual scores by default. --- wdl/tasks/SRUtils.wdl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index d9c6d97ce..289b4136f 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -462,6 +462,7 @@ task ApplyBQSR { File recalibration_report Boolean bin_base_qualities = true + Boolean emit_original_quals = true String prefix @@ -496,6 +497,7 @@ task ApplyBQSR { --use-original-qualities \ -O ~{prefix}.bam \ -bqsr ~{recalibration_report} \ + --emit-original-quals ~{emit_original_quals} \ ~{true='--static-quantized-quals 10' false='' bin_base_qualities} \ ~{true='--static-quantized-quals 20' false='' bin_base_qualities} \ ~{true='--static-quantized-quals 30' false='' bin_base_qualities} \ From 0a03022fdf66e4dbd3d3598a3ffadee1be50e325 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 6 Jan 2023 13:16:00 -0500 Subject: [PATCH 026/297] Updates to `SRWholeGenome` to make it work (in testing). --- wdl/SRJointCallGVCFs.wdl | 2 ++ wdl/SRWholeGenome.wdl | 5 +++ wdl/tasks/HaplotypeCaller.wdl | 28 +++++++++++++--- wdl/tasks/SRJointGenotyping.wdl | 6 +++- wdl/tasks/SRUtils.wdl | 58 +++++++++++++++++++++++++++++---- 5 files changed, 88 insertions(+), 11 deletions(-) diff --git a/wdl/SRJointCallGVCFs.wdl b/wdl/SRJointCallGVCFs.wdl index 0ee1d5755..b055bc6f2 100644 --- a/wdl/SRJointCallGVCFs.wdl +++ b/wdl/SRJointCallGVCFs.wdl @@ -64,6 +64,8 @@ workflow SRJointCallGVCFs { prefix = prefix, } + ## TODO: Add VQSR here. + # Finalize call FF.FinalizeToFile as FinalizeGVCF { input: outdir = outdir, file = JointCallGVCFs.output_vcf } call FF.FinalizeToFile as FinalizeTBI { input: outdir = outdir, file = JointCallGVCFs.output_vcf_index } diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 895cd3012..6cee0b9e4 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -30,6 +30,8 @@ workflow SRWholeGenome { Int dvp_threads = 32 Int dvp_memory = 128 + Int ploidy = 2 + File? bed_to_compute_coverage Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! @@ -80,6 +82,7 @@ workflow SRWholeGenome { # Handle DeepVariant First: if (run_dv_pepper_analysis) { + # TODO: Revert BQSR base qualities here if necessary. DeepVariant works better on non-BQSR'd data. call VAR.CallVariants as CallVariantsWithDeepVariant { input: bam = bam, @@ -119,6 +122,8 @@ workflow SRWholeGenome { ref_dict = ref_map['dict'], dbsnp_vcf = ref_map["known_sites_vcf"], + ploidy = ploidy, + prefix = participant_name + ".haplotype_caller", mito_contig = ref_map['mt_chr_name'], diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index e86fc1837..8acd8e92b 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -69,6 +69,12 @@ workflow CallVariantsWithHaplotypeCaller { prefix = prefix } + # Index the GVCF: + call SRUTIL.IndexFeatureFile as IndexGVCF { + input: + feature_file = MergeGVCFs.output_vcf + } + # Merge the output BAMs: call MergeBamouts as MergeVariantCalledBamOuts { input: @@ -76,11 +82,17 @@ workflow CallVariantsWithHaplotypeCaller { prefix = "~{prefix}.bamout" } + # Index the Bamout: + call Utils.Index as IndexBamout { + input: + bam = MergeVariantCalledBamOuts.output_bam + } + # Now reblock the GVCF to combine hom ref blocks and save $ / storage: call ReblockGVCF { input: gvcf = MergeGVCFs.output_vcf, - gvcf_index = MergeGVCFs.output_vcf_index, + gvcf_index = IndexGVCF.index, ref_fasta = ref_fasta, ref_fasta_fai = ref_fasta_fai, ref_dict = ref_dict, @@ -91,6 +103,7 @@ workflow CallVariantsWithHaplotypeCaller { call SRJOINT.GenotypeGVCFs as CollapseGVCFtoVCF { input: input_gvcf_data = ReblockGVCF.output_gvcf, + input_gvcf_index = ReblockGVCF.output_gvcf_index, interval_list = SmallVariantsScatterPrep.interval_list, ref_fasta = ref_fasta, ref_fasta_fai = ref_fasta_fai, @@ -99,13 +112,15 @@ workflow CallVariantsWithHaplotypeCaller { prefix = prefix, } + ## TODO: Add VQSR here. + output { File output_gvcf = ReblockGVCF.output_gvcf File output_gvcf_index = ReblockGVCF.output_gvcf_index File output_vcf = CollapseGVCFtoVCF.output_vcf File output_vcf_index = CollapseGVCFtoVCF.output_vcf_index File bamout = MergeVariantCalledBamOuts.output_bam - File bamout_index = MergeVariantCalledBamOuts.output_bam_index + File bamout_index = IndexBamout.bai } } @@ -172,7 +187,7 @@ task HaplotypeCaller_GATK4_VCF { HaplotypeCaller \ -R ~{ref_fasta} \ -I ~{input_bam} \ - ~{interval_arg}~{default="" sep=" -L " interval_list} \ + ~{interval_arg}~{default="" sep=" -L " interval_arg_value} \ -O ~{output_file_name} \ -contamination ~{default=0 contamination} \ --sample-ploidy ~{ploidy} \ @@ -236,7 +251,12 @@ task MergeBamouts { np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') ithreads=${np} + + # If the number of processors = 1, then `let` will return 1 here: + # So we need to turn off `set -e` for this command: + set +e let mthreads=${np}-1 + set -e samtools merge -@${mthreads} ~{prefix}.bam ~{sep=" " bams} samtools index -@${ithreads} ~{prefix}.bam @@ -251,7 +271,7 @@ task MergeBamouts { boot_disk_gb: 10, preemptible_tries: 1, max_retries: 1, - docker: "biocontainers/samtools:1.3.1" + docker: "us.gcr.io/broad-dsp-lrma/lr-basic:0.1.1" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index a2e0c83fc..cb1aaf576 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -174,6 +174,8 @@ task GenotypeGVCFs { input { File input_gvcf_data + File? input_gvcf_index # Required if passing a VCF file. + File interval_list File ref_fasta @@ -231,6 +233,8 @@ task GenotypeGVCFs { -L ~{interval_list} \ ~{true='--keep-combined-raw-annotations' false='' keep_combined_raw_annotations} \ --merge-input-intervals + + ls >>> ######################### RuntimeAttr default_attr = object { @@ -255,6 +259,6 @@ task GenotypeGVCFs { output { File output_vcf = "~{prefix}.vcf" - File output_vcf_index = "~{prefix}.vcf.tbi" + File output_vcf_index = "~{prefix}.vcf.idx" } } \ No newline at end of file diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 289b4136f..b627880dd 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -656,21 +656,17 @@ task MergeVCFs { RuntimeAttr? runtime_attr_override } - Int compression_level = 2 - Int java_memory_size_mb = 30768 - Int disk_size = ceil(size(input_vcfs, "GiB") * 2.5) + 10 - command { + command <<< java -Xms2000m -Xmx2500m -jar /usr/picard/picard.jar \ MergeVcfs \ INPUT=~{sep=' INPUT=' input_vcfs} \ OUTPUT=~{prefix}.vcf - } + >>> output { File output_vcf = "~{prefix}.vcf" - File output_vcf_index = "~{prefix}.vcf.tbi" } ######################### @@ -694,3 +690,53 @@ task MergeVCFs { docker: select_first([runtime_attr.docker, default_attr.docker]) } } + + +task IndexFeatureFile { + meta { + description: "Create a Tribble index for a feature file using GATK. Feature files are defined inside GATK and include VCF, BED, GTF, and other files." + } + + input { + File feature_file + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = ceil(size(feature_file, "GiB") * 2) + 10 + + String fname = basename(feature_file) + + command <<< + mv ~{feature_file} ~{fname} + gatk --java-options "-Xmx1500m" \ + IndexFeatureFile \ + -I ~{fname} + >>> + + output { + File index = "~{fname}.idx" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 2, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + +} \ No newline at end of file From 99cb370e5fcce08a8a9bb68f8a7d0d2ef281d449 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 6 Jan 2023 15:31:38 -0500 Subject: [PATCH 027/297] Added BQSR reversion to DV workflow. - Also added code from WARP for VQSR - still needs to be incorporated. --- wdl/SRWholeGenome.wdl | 16 ++- wdl/tasks/SRJointGenotyping.wdl | 4 +- wdl/tasks/SRUtils.wdl | 71 ++++++++++ wdl/tasks/VariantUtils.wdl | 238 ++++++++++++++++++++++++++++++++ 4 files changed, 325 insertions(+), 4 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 6cee0b9e4..4937d2c9e 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -7,6 +7,7 @@ version 1.0 ###################################################################################### import "tasks/Utils.wdl" as Utils +import "tasks/SRUtils.wdl" as SRUTIL import "tasks/CallVariantsIllumina.wdl" as VAR import "tasks/HaplotypeCaller.wdl" as HC import "tasks/Finalize.wdl" as FF @@ -82,11 +83,20 @@ workflow SRWholeGenome { # Handle DeepVariant First: if (run_dv_pepper_analysis) { - # TODO: Revert BQSR base qualities here if necessary. DeepVariant works better on non-BQSR'd data. + + # Deep Variant runs better with raw base quals because it has already learned the error modes. + # We need to revert our recalibration before calling variants: + call SRUTIL.RevertBaseQualities as RevertBQSRQuals { + input: + bam = bam, + bai = bai, + prefix = basename(bam, ".bam") + ".reverted_base_quals" + } + call VAR.CallVariants as CallVariantsWithDeepVariant { input: - bam = bam, - bai = bai, + bam = RevertBQSRQuals.bam_out, + bai = RevertBQSRQuals.bai_out, sample_id = participant_name, ref_fasta = ref_map['fasta'], ref_fasta_fai = ref_map['fai'], diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index cb1aaf576..92bb4e414 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -44,7 +44,9 @@ task CreateSampleNameMap { # Get our read group from the header: samtools view -H ${file_path} > header.txt - [ ! $(grep -q '^@RG' header.txt) ] && echo "No read group line found in GVCF: ${file_path}" && exit 1 + + # Fail if we don't have a header: + grep -q '^@RG' header.txt || echo "No read group line found in GVCF: ${file_path}" && exit 1 # Get the sample name from the read group: grep '^@RG' header.txt | sed 's/\t/\n/g' | grep '^SM:' | sed 's/SM://g' | sort | uniq > sample.names.txt diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index b627880dd..664e627ba 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -738,5 +738,76 @@ task IndexFeatureFile { maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } +} + + +task RevertBaseQualities { + meta { + description: "Replace base qualities in the bam file with those located in the `OQ` tag. If `ApplyBQSR` has not been run on the given bam file, no changes are made and the original file is returned." + } + + input { + File bam + File? bai + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = ceil(size(bam, "GiB") * 4) + 10 + + command <<< + set -euxo pipefail + # Check if the input bam has been run through `ApplyBQSR`. + # If not, we can just return the input bam. + samtools view -H ~{bam} | grep '^@PG' > header.pg.txt + + grep -q 'ID:GATK ApplyBQSR' header.pg.txt > applybqsr.pg.txt + rv=$? + + if [[ $rv -eq 0 ]] && grep -q '\-\-emit-original-quals' applybqsr.pg.txt ; then + # OK - our data has had it's base quality scores recalibrated. + # We must revert them: + gatk \ + RevertBaseQualityScores \ + -I ~{bam} \ + -O ~{prefix}.bam + else + # BQSR was not applied. Just copy input -> output + cp ~{bam} ~{prefix}.bam + if [[ ! -e '~{bai}' ]] ; then + samtools index ~{prefix}.bam + else + cp ~{bai} ~{prefix}.bam.bai + fi + fi + >>> + + output { + File bam_out = "~{prefix}.bam" + File bai_out = "~{prefix}.bam.bai" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 2, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } } \ No newline at end of file diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 25d3f8a8b..4fdc7e22b 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -626,3 +626,241 @@ task FixSnifflesVCF { docker: select_first([runtime_attr.docker, default_attr.docker]) } } + +######################################################################################################################## +######################################################################################################################## +######################################################################################################################## + +# TODO: Fix these tasks that were copied from WARP: + +#task HardFilterAndMakeSitesOnlyVcf { +# +# input { +# File vcf +# File vcf_index +# Float excess_het_threshold +# +# String variant_filtered_vcf_filename +# String sites_only_vcf_filename +# +# Int disk_size +# String gatk_docker = "us.gcr.io/broad-gatk/gatk:4.2.6.1" +# } +# +# command <<< +# set -euo pipefail +# +# gatk --java-options "-Xms3000m -Xmx3250m" \ +# VariantFiltration \ +# --filter-expression "ExcessHet > ~{excess_het_threshold}" \ +# --filter-name ExcessHet \ +# -O ~{variant_filtered_vcf_filename} \ +# -V ~{vcf} +# +# gatk --java-options "-Xms3000m -Xmx3250m" \ +# MakeSitesOnlyVcf \ +# -I ~{variant_filtered_vcf_filename} \ +# -O ~{sites_only_vcf_filename} +# >>> +# +# runtime { +# memory: "3750 MiB" +# cpu: "1" +# bootDiskSizeGb: 15 +# disks: "local-disk " + disk_size + " HDD" +# preemptible: 1 +# docker: gatk_docker +# } +# +# output { +# File variant_filtered_vcf = "~{variant_filtered_vcf_filename}" +# File variant_filtered_vcf_index = "~{variant_filtered_vcf_filename}.tbi" +# File sites_only_vcf = "~{sites_only_vcf_filename}" +# File sites_only_vcf_index = "~{sites_only_vcf_filename}.tbi" +# } +#} +# +#task IndelsVariantRecalibrator { +# +# input { +# String recalibration_filename +# String tranches_filename +# +# Array[String] recalibration_tranche_values +# Array[String] recalibration_annotation_values +# +# File sites_only_variant_filtered_vcf +# File sites_only_variant_filtered_vcf_index +# +# File mills_resource_vcf +# File axiomPoly_resource_vcf +# File dbsnp_resource_vcf +# File mills_resource_vcf_index +# File axiomPoly_resource_vcf_index +# File dbsnp_resource_vcf_index +# Boolean use_allele_specific_annotations +# Int max_gaussians = 4 +# +# Int disk_size +# String gatk_docker = "us.gcr.io/broad-gatk/gatk:4.2.6.1" +# } +# +# command <<< +# set -euo pipefail +# +# gatk --java-options "-Xms24000m -Xmx25000m" \ +# VariantRecalibrator \ +# -V ~{sites_only_variant_filtered_vcf} \ +# -O ~{recalibration_filename} \ +# --tranches-file ~{tranches_filename} \ +# --trust-all-polymorphic \ +# -tranche ~{sep=' -tranche ' recalibration_tranche_values} \ +# -an ~{sep=' -an ' recalibration_annotation_values} \ +# ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ +# -mode INDEL \ +# --max-gaussians ~{max_gaussians} \ +# -resource:mills,known=false,training=true,truth=true,prior=12 ~{mills_resource_vcf} \ +# -resource:axiomPoly,known=false,training=true,truth=false,prior=10 ~{axiomPoly_resource_vcf} \ +# -resource:dbsnp,known=true,training=false,truth=false,prior=2 ~{dbsnp_resource_vcf} +# >>> +# +# runtime { +# memory: "26000 MiB" +# cpu: "2" +# bootDiskSizeGb: 15 +# disks: "local-disk " + disk_size + " HDD" +# preemptible: 1 +# docker: gatk_docker +# } +# +# output { +# File recalibration = "~{recalibration_filename}" +# File recalibration_index = "~{recalibration_filename}.idx" +# File tranches = "~{tranches_filename}" +# } +#} +# +#task SNPsVariantRecalibratorCreateModel { +# +# input { +# String recalibration_filename +# String tranches_filename +# Int downsampleFactor +# String model_report_filename +# +# Array[String] recalibration_tranche_values +# Array[String] recalibration_annotation_values +# +# File sites_only_variant_filtered_vcf +# File sites_only_variant_filtered_vcf_index +# +# File hapmap_resource_vcf +# File omni_resource_vcf +# File one_thousand_genomes_resource_vcf +# File dbsnp_resource_vcf +# File hapmap_resource_vcf_index +# File omni_resource_vcf_index +# File one_thousand_genomes_resource_vcf_index +# File dbsnp_resource_vcf_index +# Boolean use_allele_specific_annotations +# Int max_gaussians = 6 +# +# Int disk_size +# String gatk_docker = "us.gcr.io/broad-gatk/gatk:4.2.6.1" +# } +# +# command <<< +# set -euo pipefail +# +# gatk --java-options "-Xms100g -Xmx100g" \ +# VariantRecalibrator \ +# -V ~{sites_only_variant_filtered_vcf} \ +# -O ~{recalibration_filename} \ +# --tranches-file ~{tranches_filename} \ +# --trust-all-polymorphic \ +# -tranche ~{sep=' -tranche ' recalibration_tranche_values} \ +# -an ~{sep=' -an ' recalibration_annotation_values} \ +# ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ +# -mode SNP \ +# --sample-every-Nth-variant ~{downsampleFactor} \ +# --output-model ~{model_report_filename} \ +# --max-gaussians ~{max_gaussians} \ +# -resource:hapmap,known=false,training=true,truth=true,prior=15 ~{hapmap_resource_vcf} \ +# -resource:omni,known=false,training=true,truth=true,prior=12 ~{omni_resource_vcf} \ +# -resource:1000G,known=false,training=true,truth=false,prior=10 ~{one_thousand_genomes_resource_vcf} \ +# -resource:dbsnp,known=true,training=false,truth=false,prior=7 ~{dbsnp_resource_vcf} +# >>> +# +# runtime { +# memory: "104 GiB" +# cpu: "2" +# bootDiskSizeGb: 15 +# disks: "local-disk " + disk_size + " HDD" +# preemptible: 1 +# docker: gatk_docker +# } +# +# output { +# File model_report = "~{model_report_filename}" +# } +#} +# +#task ApplyRecalibration { +# +# input { +# String recalibrated_vcf_filename +# File input_vcf +# File input_vcf_index +# File indels_recalibration +# File indels_recalibration_index +# File indels_tranches +# File snps_recalibration +# File snps_recalibration_index +# File snps_tranches +# Float indel_filter_level +# Float snp_filter_level +# Boolean use_allele_specific_annotations +# Int disk_size +# String gatk_docker = "us.gcr.io/broad-gatk/gatk:4.2.6.1" +# } +# +# command <<< +# set -euo pipefail +# +# gatk --java-options "-Xms5000m -Xmx6500m" \ +# ApplyVQSR \ +# -O tmp.indel.recalibrated.vcf \ +# -V ~{input_vcf} \ +# --recal-file ~{indels_recalibration} \ +# ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ +# --tranches-file ~{indels_tranches} \ +# --truth-sensitivity-filter-level ~{indel_filter_level} \ +# --create-output-variant-index true \ +# -mode INDEL +# +# gatk --java-options "-Xms5000m -Xmx6500m" \ +# ApplyVQSR \ +# -O ~{recalibrated_vcf_filename} \ +# -V tmp.indel.recalibrated.vcf \ +# --recal-file ~{snps_recalibration} \ +# ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ +# --tranches-file ~{snps_tranches} \ +# --truth-sensitivity-filter-level ~{snp_filter_level} \ +# --create-output-variant-index true \ +# -mode SNP +# >>> +# +# runtime { +# memory: "7000 MiB" +# cpu: "1" +# bootDiskSizeGb: 15 +# disks: "local-disk " + disk_size + " HDD" +# preemptible: 1 +# docker: gatk_docker +# } +# +# output { +# File recalibrated_vcf = "~{recalibrated_vcf_filename}" +# File recalibrated_vcf_index = "~{recalibrated_vcf_filename}.tbi" +# } +#} From e14663b611dda4d1c9441e13241844ea686efec4 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 6 Jan 2023 17:11:18 -0500 Subject: [PATCH 028/297] Renamed `SRJointCallGVCFs.wdl -> SRJointCallGVCFsWithGenomicsDB.wdl`. --- ...allGVCFs.wdl => SRJointCallGVCFsWithGenomicsDB.wdl} | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) rename wdl/{SRJointCallGVCFs.wdl => SRJointCallGVCFsWithGenomicsDB.wdl} (85%) diff --git a/wdl/SRJointCallGVCFs.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl similarity index 85% rename from wdl/SRJointCallGVCFs.wdl rename to wdl/SRJointCallGVCFsWithGenomicsDB.wdl index b055bc6f2..e56058ef3 100644 --- a/wdl/SRJointCallGVCFs.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -7,7 +7,7 @@ version 1.0 import "SRJointGenotyping.wdl" as SRJOINT import "tasks/Finalize.wdl" as FF -workflow SRJointCallGVCFs { +workflow SRJointCallGVCFsWithGenomicsDB { input { Array[File] gvcfs Array[File] tbis @@ -29,10 +29,16 @@ workflow SRJointCallGVCFs { gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" } - String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRJointCallGVCFs/~{prefix}" + String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRJointCallGVCFsWithGenomicsDB/~{prefix}" Map[String, String] ref_map = read_map(ref_map_file) + # From WARP: + # For small callsets (fewer than 1000 samples) we can gather the VCF shards and collect metrics directly. + # For anything larger, we need to keep the VCF sharded and gather metrics collected from them. + # We allow overriding this default behavior for testing / special requests. + Boolean is_small_callset = gvcfs <= 1000 + # Create sample-name map: call SRJOINT.CreateSampleNameMap as CreateSampleNameMap { input: From e9dddb211d63c50dac1b2496d5bde5c5e14de8c2 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 6 Jan 2023 18:23:48 -0500 Subject: [PATCH 029/297] Added note. --- wdl/SRWholeGenome.wdl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 4937d2c9e..c7b568c45 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -140,6 +140,8 @@ workflow SRWholeGenome { contigs_names_to_ignore = contigs_names_to_ignore, } + # TODO: Apply VQSR here and have a second VQSR VCF output. We'll need the raw VCF for joint calling. + call FF.FinalizeToFile as FinalizeHCVcf { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_vcf])} call FF.FinalizeToFile as FinalizeHCTbi { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_vcf_index])} call FF.FinalizeToFile as FinalizeHCGVcf { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_gvcf])} From 464715f383df636f7d58d776261a28c6eb038477 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 02:41:02 -0500 Subject: [PATCH 030/297] Updated to utilize a custom SnpEff database and scan through a list of known drug resistance mutations (which can be updated later if necessary) --- wdl/ONTPfTypeDrugResistanceMarkers.wdl | 138 ++++++++++++++----------- 1 file changed, 80 insertions(+), 58 deletions(-) diff --git a/wdl/ONTPfTypeDrugResistanceMarkers.wdl b/wdl/ONTPfTypeDrugResistanceMarkers.wdl index 1cb96d224..2ba8a34a4 100644 --- a/wdl/ONTPfTypeDrugResistanceMarkers.wdl +++ b/wdl/ONTPfTypeDrugResistanceMarkers.wdl @@ -6,6 +6,8 @@ import "tasks/Finalize.wdl" as FF workflow ONTPfTypeDrugResistanceMarkers { input { File vcf + File snpeff_db + File drug_resistance_list String dir_prefix String gcs_out_root_dir @@ -13,87 +15,51 @@ workflow ONTPfTypeDrugResistanceMarkers { String outdir = sub(gcs_out_root_dir, "/$", "") + "/ONTPfTypeDrugResistanceMarkers/~{dir_prefix}" - call AnnotateEffectsOfSelectedVariants { input: vcf = vcf } + call FunctionallyAnnotateVariants { input: vcf = vcf, snpeff_db = snpeff_db } + call CallDrugResistanceMutations { + input: + vcf = vcf, + drug_resistance_list = drug_resistance_list + } # Finalize data String dir = outdir + "/reports" - call FF.FinalizeToFile as FinalizeDRReport { input: outdir = dir, file = AnnotateEffectsOfSelectedVariants.report } + call FF.FinalizeToFile as FinalizeAnnotatedVCF { input: outdir = dir, file = FunctionallyAnnotateVariants.annotated_vcf } + call FF.FinalizeToFile as FinalizeDRReport { input: outdir = dir, file = CallDrugResistanceMutations.report } output { + File annotated_vcf = FinalizeAnnotatedVCF.gcs_path File drug_res_report = FinalizeDRReport.gcs_path } } -task AnnotateEffectsOfSelectedVariants { +task FunctionallyAnnotateVariants { input { File vcf + File snpeff_db RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 2*ceil(size(vcf, "GB")) - String base = basename(vcf, ".vcf.gz") + Int disk_size = 1 + 4*ceil(size([vcf, snpeff_db], "GB")) + String prefix = basename(vcf, ".vcf.gz") command <<< set -x - zcat ~{vcf} | \ - sed 's/^Pf3D7_0//' | \ - sed 's/^Pf3D7_1/1/' | \ - sed 's/_v3\t/\t/' | \ - awk '{ if ($0 ~ "^#" || (length($4) == 1 && length($5) == 1 && $7 == "PASS")) print $0 }' \ - > reformatted.vcf - - /usr/local/bin/snpEff ann -v Plasmodium_falciparum reformatted.vcf \ - > ann.vcf - - grep PF3D7_0417200 ann.vcf | grep p.Cys50Arg | wc -l | awk '{ if ($1 > 0) print "pfdhfr\tPF3D7_0417200\tp.Cys50Arg\t+"; else print "pfdhfr\tPF3D7_0417200\tp.Cys50Arg\t-" }' > drug_resistance_report.txt - grep PF3D7_0417200 ann.vcf | grep p.Asn51Ile | wc -l | awk '{ if ($1 > 0) print "pfdhfr\tPF3D7_0417200\tp.Asn51Ile\t+"; else print "pfdhfr\tPF3D7_0417200\tp.Asn51Ile\t-" }' >> drug_resistance_report.txt - grep PF3D7_0417200 ann.vcf | grep p.Cys59Arg | wc -l | awk '{ if ($1 > 0) print "pfdhfr\tPF3D7_0417200\tp.Cys59Arg\t+"; else print "pfdhfr\tPF3D7_0417200\tp.Cys59Arg\t-" }' >> drug_resistance_report.txt - grep PF3D7_0417200 ann.vcf | grep p.Ser108Asn | wc -l | awk '{ if ($1 > 0) print "pfdhfr\tPF3D7_0417200\tp.Ser108Asn\t+"; else print "pfdhfr\tPF3D7_0417200\tp.Ser108Asn\t-" }' >> drug_resistance_report.txt - grep PF3D7_0417200 ann.vcf | grep p.Ile164Lys | wc -l | awk '{ if ($1 > 0) print "pfdhfr\tPF3D7_0417200\tp.Ile164Lys\t+"; else print "pfdhfr\tPF3D7_0417200\tp.Ile164Lys\t-" }' >> drug_resistance_report.txt - - grep PF3D7_0523000 ann.vcf | grep p.Asn86Tyr | wc -l | awk '{ if ($1 > 0) print "pfmdr1\tPF3D7_0523000\tp.Asn86Tyr\t+"; else print "pfmdr1\tPF3D7_0523000\tp.Asn86Tyr\t-" }' >> drug_resistance_report.txt - grep PF3D7_0523000 ann.vcf | grep p.Tyr184Phe | wc -l | awk '{ if ($1 > 0) print "pfmdr1\tPF3D7_0523000\tp.Tyr184Phe\t+"; else print "pfmdr1\tPF3D7_0523000\tp.Tyr184Phe\t-" }' >> drug_resistance_report.txt - grep PF3D7_0523000 ann.vcf | grep p.Ser1034Cys | wc -l | awk '{ if ($1 > 0) print "pfmdr1\tPF3D7_0523000\tp.Ser1034Cys\t+"; else print "pfmdr1\tPF3D7_0523000\tp.Ser1034Cys\t-" }' >> drug_resistance_report.txt - grep PF3D7_0523000 ann.vcf | grep p.Asn1024Asp | wc -l | awk '{ if ($1 > 0) print "pfmdr1\tPF3D7_0523000\tp.Asn1024Asp\t+"; else print "pfmdr1\tPF3D7_0523000\tp.Asn1024Asp\t-" }' >> drug_resistance_report.txt - grep PF3D7_0523000 ann.vcf | grep p.Asp1246Tyr | wc -l | awk '{ if ($1 > 0) print "pfmdr1\tPF3D7_0523000\tp.Asp1246Tyr\t+"; else print "pfmdr1\tPF3D7_0523000\tp.Asp1246Tyr\t-" }' >> drug_resistance_report.txt - - grep PF3D7_0709000 ann.vcf | grep p.Lys76Thr | wc -l | awk '{ if ($1 > 0) print "pfcrt\tPF3D7_0709000\tp.Lys76Thr\t+"; else print "pfcrt\tPF3D7_0709000\tp.Lys76Thr\t-" }' >> drug_resistance_report.txt - grep PF3D7_0709000 ann.vcf | grep p.Met74Ile | wc -l | awk '{ if ($1 > 0) print "pfcrt\tPF3D7_0709000\tp.Met74Ile\t+"; else print "pfcrt\tPF3D7_0709000\tp.Met74Ile\t-" }' >> drug_resistance_report.txt - grep PF3D7_0709000 ann.vcf | grep p.Asn75Glu | wc -l | awk '{ if ($1 > 0) print "pfcrt\tPF3D7_0709000\tp.Asn75Glu\t+"; else print "pfcrt\tPF3D7_0709000\tp.Asn75Glu\t-" }' >> drug_resistance_report.txt - grep PF3D7_0709000 ann.vcf | grep p.Cys72Ser | wc -l | awk '{ if ($1 > 0) print "pfcrt\tPF3D7_0709000\tp.Lys76Thr\t+"; else print "pfcrt\tPF3D7_0709000\tp.Lys76Thr\t-" }' >> drug_resistance_report.txt - grep PF3D7_0709000 ann.vcf | grep p.His97Tyr | wc -l | awk '{ if ($1 > 0) print "pfcrt\tPF3D7_0709000\tp.His97Tyr\t+"; else print "pfcrt\tPF3D7_0709000\tp.His97Tyr\t-" }' >> drug_resistance_report.txt - grep PF3D7_0709000 ann.vcf | grep p.Cys101Phe | wc -l | awk '{ if ($1 > 0) print "pfcrt\tPF3D7_0709000\tp.Cys101Phe\t+"; else print "pfcrt\tPF3D7_0709000\tp.Cys101Phe\t-" }' >> drug_resistance_report.txt - grep PF3D7_0709000 ann.vcf | grep p.Phe145Ile | wc -l | awk '{ if ($1 > 0) print "pfcrt\tPF3D7_0709000\tp.Phe145Ile\t+"; else print "pfcrt\tPF3D7_0709000\tp.Phe145Ile\t-" }' >> drug_resistance_report.txt - grep PF3D7_0709000 ann.vcf | grep p.Met343Leu | wc -l | awk '{ if ($1 > 0) print "pfcrt\tPF3D7_0709000\tp.Met343Leu\t+"; else print "pfcrt\tPF3D7_0709000\tp.Met343Leu\t-" }' >> drug_resistance_report.txt - grep PF3D7_0709000 ann.vcf | grep p.Ser350Arg | wc -l | awk '{ if ($1 > 0) print "pfcrt\tPF3D7_0709000\tp.Ser350Arg\t+"; else print "pfcrt\tPF3D7_0709000\tp.Ser350Arg\t-" }' >> drug_resistance_report.txt - grep PF3D7_0709000 ann.vcf | grep p.Gly353Val | wc -l | awk '{ if ($1 > 0) print "pfcrt\tPF3D7_0709000\tp.Glu353Val\t+"; else print "pfcrt\tPF3D7_0709000\tp.Glu353Val\t-" }' >> drug_resistance_report.txt - - grep PF3D7_0810800 ann.vcf | grep p.Ser436Ala | wc -l | awk '{ if ($1 > 0) print "pfdhps\tPF3D7_0810800\tp.Ser436Ala\t+"; else print "pfdhps\tPF3D7_0810800\tp.Ser436Ala\t-" }' >> drug_resistance_report.txt - grep PF3D7_0810800 ann.vcf | grep p.Lys437Gly | wc -l | awk '{ if ($1 > 0) print "pfdhps\tPF3D7_0810800\tp.Lys437Gly\t+"; else print "pfdhps\tPF3D7_0810800\tp.Lys437Gly\t-" }' >> drug_resistance_report.txt - grep PF3D7_0810800 ann.vcf | grep p.Lys540Glu | wc -l | awk '{ if ($1 > 0) print "pfdhps\tPF3D7_0810800\tp.Lys540Glu\t+"; else print "pfdhps\tPF3D7_0810800\tp.Lys540Glu\t-" }' >> drug_resistance_report.txt - grep PF3D7_0810800 ann.vcf | grep p.Ala581Gly | wc -l | awk '{ if ($1 > 0) print "pfdhps\tPF3D7_0810800\tp.Ala581Gly\t+"; else print "pfdhps\tPF3D7_0810800\tp.Ala581Gly\t-" }' >> drug_resistance_report.txt - grep PF3D7_0810800 ann.vcf | grep p.Ala613Thr | wc -l | awk '{ if ($1 > 0) print "pfdhps\tPF3D7_0810800\tp.Ala613Thr\t+"; else print "pfdhps\tPF3D7_0810800\tp.Ala613Thr\t-" }' >> drug_resistance_report.txt - grep PF3D7_0810800 ann.vcf | grep p.Ala613Ser | wc -l | awk '{ if ($1 > 0) print "pfdhps\tPF3D7_0810800\tp.Ala613Ser\t+"; else print "pfdhps\tPF3D7_0810800\tp.Ala613Ser\t-" }' >> drug_resistance_report.txt - - grep PF3D7_1343700 ann.vcf | grep p.Tyr493His | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Tyr493His\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Tyr493His\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Arg539Thr | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Arg539Thr\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Arg539Thr\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Ile543Thr | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Ile543Thr\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Ile543Thr\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Arg561His | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Arg561His\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Arg561His\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Cys580Tyr | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Cys580Tyr\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Cys580Tyr\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Ala675Val | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Ala675Val\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Ala675Val\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Phe446Ile | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Phe446Ile\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Phe446Ile\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Met476Ile | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Met476Ile\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Met476Ile\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Asn458Tyr | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Asn458Tyr\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Asn458Tyr\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Phe553Leu | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Phe553Leu\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Phe553Leu\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Phe574Leu | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Phe574Leu\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Phe574Leu\t-" }' >> drug_resistance_report.txt - grep PF3D7_1343700 ann.vcf | grep p.Arg633Ile | wc -l | awk '{ if ($1 > 0) print "pfkelch13\tPF3D7_1343700\tp.Arg633Ile\t+"; else print "pfkelch13\tPF3D7_1343700\tp.Arg633Ile\t-" }' >> drug_resistance_report.txt + gunzip -c ~{snpeff_db} | tar xvf - + + /usr/local/bin/snpEff ann -v \ + -c $PWD/snpeff_db/snpEff.config \ + -dataDir $PWD/snpeff_db/data \ + PlasmoDB-61_Pfalciparum3D7_Genome \ + ~{vcf} \ + | gzip > ~{prefix}.vcf.gz >>> output { - File report = "drug_resistance_report.txt" + File annotated_vcf = "~{prefix}.vcf.gz" } ######################### @@ -117,3 +83,59 @@ task AnnotateEffectsOfSelectedVariants { docker: select_first([runtime_attr.docker, default_attr.docker]) } } + +task CallDrugResistanceMutations { + input { + File vcf + File drug_resistance_list + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 2*ceil(size([vcf, drug_resistance_list], "GB")) + String prefix = basename(vcf, ".vcf.gz") + + command <<< + set -x + + while read LINE; do + GENE_NAME=$(echo $LINE | awk '{print $1}') + GENE_ID=$(echo $LINE | awk '{print $2}') + MUTATION=$(echo $LINE | awk '{print $3}') + + grep $GENE_ID ~{vcf} | \ + grep $MUTATION | \ + wc -l | \ + awk -v gene_name=$GENE_NAME \ + -v gene_id=$GENE_ID \ + -v mutation=$MUTATION \ + '{ gene_name "\t" gene_id "\t" mutation "\t" ($1 > 0) }' \ + >> ~{prefix}.drug_resistance_report.txt + done <~{drug_resistance_list} + >>> + + output { + File report = "~{prefix}.drug_resistance_report.txt" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 1, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "quay.io/biocontainers/snpeff:5.1d--hdfd78af_0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From cf6eb317d5946b9aa2e93a4db012a2d095e47005 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 02:48:02 -0500 Subject: [PATCH 031/297] Fixed task dependency chain --- wdl/ONTPfTypeDrugResistanceMarkers.wdl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wdl/ONTPfTypeDrugResistanceMarkers.wdl b/wdl/ONTPfTypeDrugResistanceMarkers.wdl index 2ba8a34a4..4e2dad82c 100644 --- a/wdl/ONTPfTypeDrugResistanceMarkers.wdl +++ b/wdl/ONTPfTypeDrugResistanceMarkers.wdl @@ -16,9 +16,10 @@ workflow ONTPfTypeDrugResistanceMarkers { String outdir = sub(gcs_out_root_dir, "/$", "") + "/ONTPfTypeDrugResistanceMarkers/~{dir_prefix}" call FunctionallyAnnotateVariants { input: vcf = vcf, snpeff_db = snpeff_db } + call CallDrugResistanceMutations { input: - vcf = vcf, + vcf = FunctionallyAnnotateVariants.annotated_vcf, drug_resistance_list = drug_resistance_list } From 2b8f3020e55190e86693bb9dabd87b1231cd0760 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 03:03:47 -0500 Subject: [PATCH 032/297] Fixed some file naming errors --- wdl/ONTPfTypeDrugResistanceMarkers.wdl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/wdl/ONTPfTypeDrugResistanceMarkers.wdl b/wdl/ONTPfTypeDrugResistanceMarkers.wdl index 4e2dad82c..f93c5a874 100644 --- a/wdl/ONTPfTypeDrugResistanceMarkers.wdl +++ b/wdl/ONTPfTypeDrugResistanceMarkers.wdl @@ -44,7 +44,7 @@ task FunctionallyAnnotateVariants { } Int disk_size = 1 + 4*ceil(size([vcf, snpeff_db], "GB")) - String prefix = basename(vcf, ".vcf.gz") + String prefix = basename(basename(vcf, ".gz"), ".vcf") command <<< set -x @@ -56,11 +56,16 @@ task FunctionallyAnnotateVariants { -dataDir $PWD/snpeff_db/data \ PlasmoDB-61_Pfalciparum3D7_Genome \ ~{vcf} \ - | gzip > ~{prefix}.vcf.gz + | gzip > ~{prefix}.annotated.vcf.gz + + mv snpEff_summary.html ~{prefix}.snpEff_summary.html + mv snpEff_genes.html ~{prefix}.snpEff_genes.html >>> output { - File annotated_vcf = "~{prefix}.vcf.gz" + File annotated_vcf = "~{prefix}.annotated.vcf.gz" + File snpEff_summary = "~{prefix}.snpEff_summary.html" + File snpEff_genes = "~{prefix}.snpEff_genes.txt" } ######################### @@ -94,7 +99,7 @@ task CallDrugResistanceMutations { } Int disk_size = 1 + 2*ceil(size([vcf, drug_resistance_list], "GB")) - String prefix = basename(vcf, ".vcf.gz") + String prefix = basename(basename(vcf, ".gz"), ".vcf") command <<< set -x From c26adc5838857301ed27160b84291178040c4675 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 03:17:16 -0500 Subject: [PATCH 033/297] Fixed typo --- wdl/ONTPfTypeDrugResistanceMarkers.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/ONTPfTypeDrugResistanceMarkers.wdl b/wdl/ONTPfTypeDrugResistanceMarkers.wdl index f93c5a874..b98fc56c4 100644 --- a/wdl/ONTPfTypeDrugResistanceMarkers.wdl +++ b/wdl/ONTPfTypeDrugResistanceMarkers.wdl @@ -59,7 +59,7 @@ task FunctionallyAnnotateVariants { | gzip > ~{prefix}.annotated.vcf.gz mv snpEff_summary.html ~{prefix}.snpEff_summary.html - mv snpEff_genes.html ~{prefix}.snpEff_genes.html + mv snpEff_genes.txt ~{prefix}.snpEff_genes.txt >>> output { From 55869e120e5dc8de41bf54dc1e40c31f01b364c6 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 03:43:35 -0500 Subject: [PATCH 034/297] Fixed another typo --- wdl/ONTPfTypeDrugResistanceMarkers.wdl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wdl/ONTPfTypeDrugResistanceMarkers.wdl b/wdl/ONTPfTypeDrugResistanceMarkers.wdl index b98fc56c4..1e86a613f 100644 --- a/wdl/ONTPfTypeDrugResistanceMarkers.wdl +++ b/wdl/ONTPfTypeDrugResistanceMarkers.wdl @@ -109,7 +109,8 @@ task CallDrugResistanceMutations { GENE_ID=$(echo $LINE | awk '{print $2}') MUTATION=$(echo $LINE | awk '{print $3}') - grep $GENE_ID ~{vcf} | \ + zcat ~{vcf} | \ + grep $GENE_ID | \ grep $MUTATION | \ wc -l | \ awk -v gene_name=$GENE_NAME \ From 063a1dc1af030b827b5d8426e178ffe4ead80812 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 03:49:53 -0500 Subject: [PATCH 035/297] Finalize SnpEff summary and genes files --- wdl/ONTPfTypeDrugResistanceMarkers.wdl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wdl/ONTPfTypeDrugResistanceMarkers.wdl b/wdl/ONTPfTypeDrugResistanceMarkers.wdl index 1e86a613f..03e1d4f90 100644 --- a/wdl/ONTPfTypeDrugResistanceMarkers.wdl +++ b/wdl/ONTPfTypeDrugResistanceMarkers.wdl @@ -27,11 +27,15 @@ workflow ONTPfTypeDrugResistanceMarkers { String dir = outdir + "/reports" call FF.FinalizeToFile as FinalizeAnnotatedVCF { input: outdir = dir, file = FunctionallyAnnotateVariants.annotated_vcf } + call FF.FinalizeToFile as FinalizeSnpEffSummary { input: outdir = dir, file = FunctionallyAnnotateVariants.snpEff_summary } + call FF.FinalizeToFile as FinalizeSnpEffGenes { input: outdir = dir, file = FunctionallyAnnotateVariants.snpEff_genes } call FF.FinalizeToFile as FinalizeDRReport { input: outdir = dir, file = CallDrugResistanceMutations.report } output { File annotated_vcf = FinalizeAnnotatedVCF.gcs_path File drug_res_report = FinalizeDRReport.gcs_path + File snpEff_summary = FinalizeSnpEffSummary.gcs_path + File snpEff_genes = FinalizeSnpEffGenes.gcs_path } } From 062c45684931e04c15849cf9edc8d190e28b508c Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 04:28:21 -0500 Subject: [PATCH 036/297] Fixed bugs in awk script --- wdl/ONTPfTypeDrugResistanceMarkers.wdl | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/wdl/ONTPfTypeDrugResistanceMarkers.wdl b/wdl/ONTPfTypeDrugResistanceMarkers.wdl index 03e1d4f90..8aa43c048 100644 --- a/wdl/ONTPfTypeDrugResistanceMarkers.wdl +++ b/wdl/ONTPfTypeDrugResistanceMarkers.wdl @@ -113,15 +113,10 @@ task CallDrugResistanceMutations { GENE_ID=$(echo $LINE | awk '{print $2}') MUTATION=$(echo $LINE | awk '{print $3}') - zcat ~{vcf} | \ - grep $GENE_ID | \ - grep $MUTATION | \ - wc -l | \ - awk -v gene_name=$GENE_NAME \ - -v gene_id=$GENE_ID \ - -v mutation=$MUTATION \ - '{ gene_name "\t" gene_id "\t" mutation "\t" ($1 > 0) }' \ - >> ~{prefix}.drug_resistance_report.txt + zcat ~{vcf} | grep $GENE_ID | grep $MUTATION | wc -l | \ + awk -v gene_name=$GENE_NAME -v gene_id=$GENE_ID -v mutation=$MUTATION \ + '{ print gene_name, gene_id, mutation, ($1 > 0) ? "present" : "absent" }' | \ + tee -a ~{prefix}.drug_resistance_report.txt done <~{drug_resistance_list} >>> From 2f70d95ef4767912c33ee633ba5a1326eb2338e4 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 20:46:38 -0500 Subject: [PATCH 037/297] For short reads, replace NanoPlot with samtools stats and FastQC --- wdl/SRFlowcell.wdl | 37 ++++++++--------- wdl/tasks/AlignedMetrics.wdl | 52 ++++++++++++++++++++++++ wdl/tasks/FastQC.wdl | 79 ++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 wdl/tasks/FastQC.wdl diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 7cde96c64..a5a116f3b 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -12,7 +12,7 @@ version 1.0 import "tasks/SRUtils.wdl" as SRUTIL import "tasks/Utils.wdl" as Utils import "tasks/AlignedMetrics.wdl" as AM -import "tasks/NanoPlot.wdl" as NP +import "tasks/FastQC.wdl" as FastQC import "tasks/Finalize.wdl" as FF workflow SRFlowcell { @@ -186,12 +186,12 @@ workflow SRFlowcell { # ############################################# - call AM.SamStats as t_011_SamStats { + call AM.SamStatsMap as t_011_SamStats { input: bam = t_010_ApplyBQSR.recalibrated_bam } - call NP.NanoPlotFromBam as t_012_NanoPlotFromBam { input: bam = t_010_ApplyBQSR.recalibrated_bam, bai = t_010_ApplyBQSR.recalibrated_bai } + call FastQC.FastQC as t_012_FastQC { input: bam = t_010_ApplyBQSR.recalibrated_bam, bai = t_010_ApplyBQSR.recalibrated_bai } call Utils.ComputeGenomeLength as t_013_ComputeGenomeLength { input: fasta = ref_map['fasta'] } call SRUTIL.ComputeBamStats as t_014_ComputeBamStats { input: bam_file = t_010_ApplyBQSR.recalibrated_bam } @@ -321,10 +321,7 @@ workflow SRFlowcell { Float num_bases = t_014_ComputeBamStats.results['bases'] Float raw_est_fold_cov = t_014_ComputeBamStats.results['bases']/t_013_ComputeGenomeLength.length - Float read_length_mean = t_014_ComputeBamStats.results['read_mean'] - Float read_length_median = t_014_ComputeBamStats.results['read_median'] - Float read_length_stdev = t_014_ComputeBamStats.results['read_stdev'] - Float read_length_N50 = t_014_ComputeBamStats.results['read_n50'] + Float read_length = t_014_ComputeBamStats.results['read_mean'] Float read_qual_mean = t_014_ComputeBamStats.results['mean_qual'] Float read_qual_median = t_014_ComputeBamStats.results['median_qual'] @@ -336,17 +333,19 @@ workflow SRFlowcell { Float num_reads_Q15 = t_019_ComputeBamStatsQ15.results['reads'] # Aligned read stats - Float aligned_num_reads = t_012_NanoPlotFromBam.stats_map['number_of_reads'] - Float aligned_num_bases = t_012_NanoPlotFromBam.stats_map['number_of_bases_aligned'] - Float aligned_frac_bases = t_012_NanoPlotFromBam.stats_map['fraction_bases_aligned'] - Float aligned_est_fold_cov = t_012_NanoPlotFromBam.stats_map['number_of_bases_aligned']/t_013_ComputeGenomeLength.length - - Float aligned_read_length_mean = t_012_NanoPlotFromBam.stats_map['mean_read_length'] - Float aligned_read_length_median = t_012_NanoPlotFromBam.stats_map['median_read_length'] - Float aligned_read_length_stdev = t_012_NanoPlotFromBam.stats_map['read_length_stdev'] - Float aligned_read_length_N50 = t_012_NanoPlotFromBam.stats_map['n50'] - - Float average_identity = t_012_NanoPlotFromBam.stats_map['average_identity'] - Float median_identity = t_012_NanoPlotFromBam.stats_map['median_identity'] + Float aligned_num_reads = t_012_FastQC.stats_map['number_of_reads'] + Float aligned_num_bases = t_011_SamStats.stats_map['bases_mapped'] + Float aligned_frac_bases = t_011_SamStats.stats_map['bases_mapped']/t_011_SamStats.stats_map['total_length'] + Float aligned_est_fold_cov = t_011_SamStats.stats_map['bases_mapped']/t_013_ComputeGenomeLength.length + + Float aligned_read_length = t_012_FastQC.stats_map['read_length'] + + Float insert_size_average = t_011_SamStats.stats_map['insert_size_average'] + Float insert_size_standard_deviation = t_011_SamStats.stats_map['insert_size_standard_deviation'] + Float pct_properly_paired_reads = t_011_SamStats.stats_map['percentage_of_properly_paired_reads_%'] + + Float average_identity = 100.0 - (100.0*t_011_SamStats.stats_map['mismatches']/t_011_SamStats.stats_map['bases_mapped']) + + File fastqc_report = t_012_FastQC.report } } diff --git a/wdl/tasks/AlignedMetrics.wdl b/wdl/tasks/AlignedMetrics.wdl index bc2464c11..2b8e46c82 100644 --- a/wdl/tasks/AlignedMetrics.wdl +++ b/wdl/tasks/AlignedMetrics.wdl @@ -382,6 +382,58 @@ task SamStats { } } +task SamStatsMap { + input { + File bam + + RuntimeAttr? runtime_attr_override + } + + String basename = basename(bam, ".bam") + Int disk_size = 2*ceil(size(bam, "GB")) + + command <<< + set -euxo pipefail + + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + + samtools stats -@${np} ~{bam} > ~{basename}.sam_stats.txt + + grep '^SN' ~{basename}.sam_stats.txt | \ + cut -f 2- | \ + sed 's/://g' | \ + sed 's/ /_/g' | \ + sed 's/[\(\)]//g' | \ + sed 's/[[:space:]]*#.*//' \ + > map.txt + >>> + + output { + File sam_stats = "~{basename}.sam_stats.txt" + Map[String, Float] stats_map = read_map("map.txt") + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-metrics:0.1.11" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} task FlagStats { input { diff --git a/wdl/tasks/FastQC.wdl b/wdl/tasks/FastQC.wdl new file mode 100644 index 000000000..baee524dd --- /dev/null +++ b/wdl/tasks/FastQC.wdl @@ -0,0 +1,79 @@ +version 1.0 + +import "Structs.wdl" + +task FastQC { + input { + File bam + File bai + + Int num_cpus = 4 + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 2*ceil(size([bam, bai], "GB")) + + command <<< + set -euxo pipefail + + num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) + + fastqc -t $num_core --extract ~{bam} + + FASTQC_DATA="~{basename(bam, ".bam")}_fastqc/fastqc_data.txt" + + number_of_reads=$(grep 'Total Sequences' $FASTQC_DATA | awk '{ print $3 }') + read_length=$(grep 'Sequence length' $FASTQC_DATA | awk '{ print $3 }') + + echo $number_of_reads | awk '{ print "number_of_reads\t" $3 }' >> map.txt + echo $read_length | awk '{ print "read_length\t" $3 }' >> map.txt + echo $number_of_reads $read_length | awk '{ print "number_of_bases\t" $1*$2 }' >> map.txt + + mean_qual=$(sed -n '/Per base sequence quality/,/END_MODULE/p' $FASTQC_DATA | \ + grep -v '^#' | \ + grep -v '>>' | \ + awk '{ print $2 }' | \ + awk '{x+=$1; next} END{print x/NR}') + + echo $mean_qual | awk '{ print "mean_qual\t" $1 }' >> map.txt + + median_qual=$(sed -n '/Per base sequence quality/,/END_MODULE/p' $FASTQC_DATA | \ + grep -v '^#' | \ + grep -v '>>' | \ + awk '{ print $2 }' | \ + awk '{x+=$1; next} END{print x/NR}' | \ + sort -n | \ + awk '{ a[i++]=$1; } END { x=int((i+1)/2); if (x < (i+1)/2) print (a[x-1]+a[x])/2; else print a[x-1]; }') + + echo $median_qual | awk '{ print "median_qual\t" $1 }' >> map.txt + >>> + + output { + Map[String, Float] stats_map = read_map("map.txt") + + File stats = "~{basename(bam, '.bam')}_fastqc/fastqc_data.txt" + File report = "~{basename(bam, '.bam')}_fastqc/fastqc_report.html" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "staphb/fastqc:latest" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} \ No newline at end of file From 0a3b14a406f3d95f0143baf2998f945bdccb56ec Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 21:47:18 -0500 Subject: [PATCH 038/297] Print out the path of the stats file --- wdl/tasks/FastQC.wdl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wdl/tasks/FastQC.wdl b/wdl/tasks/FastQC.wdl index baee524dd..dcec2f777 100644 --- a/wdl/tasks/FastQC.wdl +++ b/wdl/tasks/FastQC.wdl @@ -21,7 +21,9 @@ task FastQC { fastqc -t $num_core --extract ~{bam} - FASTQC_DATA="~{basename(bam, ".bam")}_fastqc/fastqc_data.txt" + find . -name 'fastqc_data.txt' + + FASTQC_DATA="$(find . -name 'fastqc_data.txt')" number_of_reads=$(grep 'Total Sequences' $FASTQC_DATA | awk '{ print $3 }') read_length=$(grep 'Sequence length' $FASTQC_DATA | awk '{ print $3 }') From 7c6bffbc14d0c08a313661c551440256c04e9d9a Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 22:24:27 -0500 Subject: [PATCH 039/297] Fixed some path issues --- wdl/tasks/FastQC.wdl | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/wdl/tasks/FastQC.wdl b/wdl/tasks/FastQC.wdl index dcec2f777..0ee15d55e 100644 --- a/wdl/tasks/FastQC.wdl +++ b/wdl/tasks/FastQC.wdl @@ -21,18 +21,17 @@ task FastQC { fastqc -t $num_core --extract ~{bam} - find . -name 'fastqc_data.txt' + find . -name 'fastqc_data.txt' -exec mv {} fastq_data.txt + find . -name 'fastqc_report.html' -exec mv {} fastq_report.html - FASTQC_DATA="$(find . -name 'fastqc_data.txt')" - - number_of_reads=$(grep 'Total Sequences' $FASTQC_DATA | awk '{ print $3 }') - read_length=$(grep 'Sequence length' $FASTQC_DATA | awk '{ print $3 }') + number_of_reads=$(grep 'Total Sequences' fastqc_data.txt | awk '{ print $3 }') + read_length=$(grep 'Sequence length' fastqc_data.txt | awk '{ print $3 }') echo $number_of_reads | awk '{ print "number_of_reads\t" $3 }' >> map.txt echo $read_length | awk '{ print "read_length\t" $3 }' >> map.txt echo $number_of_reads $read_length | awk '{ print "number_of_bases\t" $1*$2 }' >> map.txt - mean_qual=$(sed -n '/Per base sequence quality/,/END_MODULE/p' $FASTQC_DATA | \ + mean_qual=$(sed -n '/Per base sequence quality/,/END_MODULE/p' fastqc_data.txt | \ grep -v '^#' | \ grep -v '>>' | \ awk '{ print $2 }' | \ @@ -40,7 +39,7 @@ task FastQC { echo $mean_qual | awk '{ print "mean_qual\t" $1 }' >> map.txt - median_qual=$(sed -n '/Per base sequence quality/,/END_MODULE/p' $FASTQC_DATA | \ + median_qual=$(sed -n '/Per base sequence quality/,/END_MODULE/p' fastqc_data.txt | \ grep -v '^#' | \ grep -v '>>' | \ awk '{ print $2 }' | \ @@ -54,8 +53,8 @@ task FastQC { output { Map[String, Float] stats_map = read_map("map.txt") - File stats = "~{basename(bam, '.bam')}_fastqc/fastqc_data.txt" - File report = "~{basename(bam, '.bam')}_fastqc/fastqc_report.html" + File stats = "fastqc_data.txt" + File report = "fastqc_report.html" } ######################### From 3437145f913ab8476c357ab06c5de58a1c4b6589 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 22:52:47 -0500 Subject: [PATCH 040/297] Fixed stupid typo --- wdl/tasks/FastQC.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/FastQC.wdl b/wdl/tasks/FastQC.wdl index 0ee15d55e..5c54e330e 100644 --- a/wdl/tasks/FastQC.wdl +++ b/wdl/tasks/FastQC.wdl @@ -21,8 +21,8 @@ task FastQC { fastqc -t $num_core --extract ~{bam} - find . -name 'fastqc_data.txt' -exec mv {} fastq_data.txt - find . -name 'fastqc_report.html' -exec mv {} fastq_report.html + find . -name 'fastqc_data.txt' -exec mv {} fastq_data.txt \; + find . -name 'fastqc_report.html' -exec mv {} fastq_report.html \; number_of_reads=$(grep 'Total Sequences' fastqc_data.txt | awk '{ print $3 }') read_length=$(grep 'Sequence length' fastqc_data.txt | awk '{ print $3 }') From 626511d8bd22e83376b40c7c7c392d94bbec0616 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 7 Jan 2023 23:18:38 -0500 Subject: [PATCH 041/297] Yet another typo --- wdl/tasks/FastQC.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/FastQC.wdl b/wdl/tasks/FastQC.wdl index 5c54e330e..1d6199721 100644 --- a/wdl/tasks/FastQC.wdl +++ b/wdl/tasks/FastQC.wdl @@ -21,8 +21,8 @@ task FastQC { fastqc -t $num_core --extract ~{bam} - find . -name 'fastqc_data.txt' -exec mv {} fastq_data.txt \; - find . -name 'fastqc_report.html' -exec mv {} fastq_report.html \; + find . -name 'fastqc_data.txt' -exec mv {} fastqc_data.txt \; + find . -name 'fastqc_report.html' -exec mv {} fastqc_report.html \; number_of_reads=$(grep 'Total Sequences' fastqc_data.txt | awk '{ print $3 }') read_length=$(grep 'Sequence length' fastqc_data.txt | awk '{ print $3 }') From 9c0ee524063fb3ec3b67c71a5837cd213d7404be Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sun, 8 Jan 2023 00:38:39 -0500 Subject: [PATCH 042/297] Fixed more typos --- wdl/tasks/FastQC.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/FastQC.wdl b/wdl/tasks/FastQC.wdl index 1d6199721..5a88d999d 100644 --- a/wdl/tasks/FastQC.wdl +++ b/wdl/tasks/FastQC.wdl @@ -27,8 +27,8 @@ task FastQC { number_of_reads=$(grep 'Total Sequences' fastqc_data.txt | awk '{ print $3 }') read_length=$(grep 'Sequence length' fastqc_data.txt | awk '{ print $3 }') - echo $number_of_reads | awk '{ print "number_of_reads\t" $3 }' >> map.txt - echo $read_length | awk '{ print "read_length\t" $3 }' >> map.txt + echo $number_of_reads | awk '{ print "number_of_reads\t" $1 }' >> map.txt + echo $read_length | awk '{ print "read_length\t" $1 }' >> map.txt echo $number_of_reads $read_length | awk '{ print "number_of_bases\t" $1*$2 }' >> map.txt mean_qual=$(sed -n '/Per base sequence quality/,/END_MODULE/p' fastqc_data.txt | \ From 49befaf17926fcef227e0a7fc9eb239995435399 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sun, 8 Jan 2023 01:26:33 -0500 Subject: [PATCH 043/297] Finalize FastQC report --- wdl/SRFlowcell.wdl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index a5a116f3b..9a7bff453 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -287,6 +287,12 @@ workflow SRFlowcell { keyfile = keyfile } + call FF.FinalizeToFile as t_026_FinalizeFastQCReport { + input: + outdir = metrics_dir, + file = t_012_FastQC.report + } + # Prep a few files for output: if (defined(bam)) { File unaligned_bam_o = reads_dir + "/unaligned/" + basename(select_first([bam])) @@ -346,6 +352,6 @@ workflow SRFlowcell { Float average_identity = 100.0 - (100.0*t_011_SamStats.stats_map['mismatches']/t_011_SamStats.stats_map['bases_mapped']) - File fastqc_report = t_012_FastQC.report + File fastqc_report = t_026_FinalizeFastQCReport.gcs_path } } From 8ac0c6cc198d2c280d25158bfaced8900091dba0 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sun, 8 Jan 2023 16:46:04 -0500 Subject: [PATCH 044/297] Fixed a bug wherein FastQC can return a range for read length, which breaks the WDL map reading function as the value is no longer purely numeric. --- wdl/tasks/FastQC.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/FastQC.wdl b/wdl/tasks/FastQC.wdl index 5a88d999d..52c5a0bf4 100644 --- a/wdl/tasks/FastQC.wdl +++ b/wdl/tasks/FastQC.wdl @@ -25,7 +25,7 @@ task FastQC { find . -name 'fastqc_report.html' -exec mv {} fastqc_report.html \; number_of_reads=$(grep 'Total Sequences' fastqc_data.txt | awk '{ print $3 }') - read_length=$(grep 'Sequence length' fastqc_data.txt | awk '{ print $3 }') + read_length=$(grep 'Sequence length' fastqc_data.txt | awk '{ print $3 }' | cut -f2 -d'-') echo $number_of_reads | awk '{ print "number_of_reads\t" $1 }' >> map.txt echo $read_length | awk '{ print "read_length\t" $1 }' >> map.txt From adf4f6bfa015967436bb0a821b79314c63ab1a60 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 9 Jan 2023 10:31:22 -0500 Subject: [PATCH 045/297] Added comment to `bwa-mem2` --- wdl/tasks/SRUtils.wdl | 1 + 1 file changed, 1 insertion(+) diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 664e627ba..42bb0852a 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -192,6 +192,7 @@ task BwaMem2 { # -t INT number of threads [1] # -Y use soft clipping for supplementary alignments # -R STR read group header line such as '@RG\tID:foo\tSM:bar' [null] + # -M mark shorter split hits as secondary bwa-mem2 mem \ -K 100000000 \ From 27fec3766efc3839f950b6002c6e8eae9e1eee71 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 9 Jan 2023 17:27:17 -0500 Subject: [PATCH 046/297] Added VQSR to SRWholeGenome. STILL UNTESTED! --- wdl/SRWholeGenome.wdl | 83 ++++- wdl/tasks/VariantUtils.wdl | 676 ++++++++++++++++++++++++------------- 2 files changed, 519 insertions(+), 240 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index c7b568c45..692c3cbb0 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -8,6 +8,7 @@ version 1.0 import "tasks/Utils.wdl" as Utils import "tasks/SRUtils.wdl" as SRUTIL +import "tasks/VariantUtils.wdl" as VARUTIL import "tasks/CallVariantsIllumina.wdl" as VAR import "tasks/HaplotypeCaller.wdl" as HC import "tasks/Finalize.wdl" as FF @@ -33,6 +34,14 @@ workflow SRWholeGenome { Int ploidy = 2 + Float snp_filter_level = 99.7 + Array[String] snp_recalibration_annotation_values = ["QD", "MQRankSum", "ReadPosRankSum", "FS", "MQ", "SOR", "DP"] + Array[Float] snp_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.8, 99.6, 99.5, 99.4, 99.3, 99.0, 98.0, 97.0, 90.0 ] + + Float indel_filter_level = 99.0 + Array[String] indel_recalibration_annotation_values = ["FS", "ReadPosRankSum", "MQRankSum", "QD", "SOR", "DP"] + Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] + File? bed_to_compute_coverage Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! @@ -140,14 +149,71 @@ workflow SRWholeGenome { contigs_names_to_ignore = contigs_names_to_ignore, } - # TODO: Apply VQSR here and have a second VQSR VCF output. We'll need the raw VCF for joint calling. + call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { + input: + vcf = CallVariantsWithHaplotypeCaller.output_vcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + prefix = participant_name + ".indels", + recalibration_tranche_values = snp_recalibration_tranche_values, + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = [ref_map["known_sites_vcf"]], + known_reference_variants_identifier = [ref_map["known_sites_index"]], + is_known = [true], + is_training = [true], + is_truth = [false], + prior = [15], + use_allele_specific_annotations = true, + max_gaussians = 8, + } + + call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { + input: + vcf = CallVariantsWithHaplotypeCaller.output_vcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + prefix = participant_name + ".snps", + recalibration_tranche_values = snp_recalibration_tranche_values, + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = [ref_map["known_sites_vcf"]], + known_reference_variants_identifier = [ref_map["known_sites_index"]], + is_known = [true], + is_training = [true], + is_truth = [false], + prior = [15], + use_allele_specific_annotations = true, + max_gaussians = 8, + } + + call VARUTIL.ApplyVqsr as ApplyVqsr { + input: + vcf = CallVariantsWithHaplotypeCaller.output_vcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + + prefix = participant_name + ".vqsr_filtered", + + snps_recalibration = TrainVQSROnHCSnpVariants.recalibration, + snps_recalibration_index = TrainVQSROnHCSnpVariants.recalibration_index, + snps_tranches = TrainVQSROnHCSnpVariants.tranches, + snp_filter_level = snp_filter_level, + + indels_recalibration = TrainVQSROnHCIndelVariants.recalibration, + indels_recalibration_index = TrainVQSROnHCIndelVariants.recalibration_index, + indels_tranches = TrainVQSROnHCIndelVariants.tranches, + indel_filter_level = indel_filter_level, + use_allele_specific_annotations = true, + } + + # Finalize the raw Joint Calls: call FF.FinalizeToFile as FinalizeHCVcf { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_vcf])} call FF.FinalizeToFile as FinalizeHCTbi { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_vcf_index])} call FF.FinalizeToFile as FinalizeHCGVcf { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_gvcf])} call FF.FinalizeToFile as FinalizeHCGTbi { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_gvcf_index])} call FF.FinalizeToFile as FinalizeHCBamOut { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.bamout])} call FF.FinalizeToFile as FinalizeHCBaiOut { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.bamout_index])} + + # Finalize the reclibrated / filtered variants: + call FF.FinalizeToFile as FinalizeHCVqsrVcf { input: outdir = smalldir, file = select_first([ApplyVqsr.recalibrated_vcf])} + call FF.FinalizeToFile as FinalizeHCVqsrTbi { input: outdir = smalldir, file = select_first([ApplyVqsr.recalibrated_vcf_index])} } output { @@ -178,11 +244,14 @@ workflow SRWholeGenome { ######################################## - File? hc_vcf = FinalizeHCVcf.gcs_path - File? hc_tbi = FinalizeHCTbi.gcs_path - File? hc_g_vcf = FinalizeHCGVcf.gcs_path - File? hc_g_tbi = FinalizeHCGTbi.gcs_path - File? hc_bamout = FinalizeHCBamOut.gcs_path - File? hc_baiout = FinalizeHCBaiOut.gcs_path + File? hc_vcf = FinalizeHCVcf.gcs_path + File? hc_tbi = FinalizeHCTbi.gcs_path + File? hc_vqsr_vcf = FinalizeHCVqsrVcf.gcs_path + File? hc_vqsr_tbi = FinalizeHCVqsrTbi.gcs_path + File? hc_g_vcf = FinalizeHCGVcf.gcs_path + File? hc_g_tbi = FinalizeHCGTbi.gcs_path + File? hc_bamout = FinalizeHCBamOut.gcs_path + File? hc_baiout = FinalizeHCBaiOut.gcs_path + } } diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 4fdc7e22b..bd1373be8 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -631,236 +631,446 @@ task FixSnifflesVCF { ######################################################################################################################## ######################################################################################################################## -# TODO: Fix these tasks that were copied from WARP: - -#task HardFilterAndMakeSitesOnlyVcf { -# -# input { -# File vcf -# File vcf_index -# Float excess_het_threshold -# -# String variant_filtered_vcf_filename -# String sites_only_vcf_filename -# -# Int disk_size -# String gatk_docker = "us.gcr.io/broad-gatk/gatk:4.2.6.1" -# } -# -# command <<< -# set -euo pipefail -# -# gatk --java-options "-Xms3000m -Xmx3250m" \ -# VariantFiltration \ -# --filter-expression "ExcessHet > ~{excess_het_threshold}" \ -# --filter-name ExcessHet \ -# -O ~{variant_filtered_vcf_filename} \ -# -V ~{vcf} -# -# gatk --java-options "-Xms3000m -Xmx3250m" \ -# MakeSitesOnlyVcf \ -# -I ~{variant_filtered_vcf_filename} \ -# -O ~{sites_only_vcf_filename} -# >>> -# -# runtime { -# memory: "3750 MiB" -# cpu: "1" -# bootDiskSizeGb: 15 -# disks: "local-disk " + disk_size + " HDD" -# preemptible: 1 -# docker: gatk_docker -# } -# -# output { -# File variant_filtered_vcf = "~{variant_filtered_vcf_filename}" -# File variant_filtered_vcf_index = "~{variant_filtered_vcf_filename}.tbi" -# File sites_only_vcf = "~{sites_only_vcf_filename}" -# File sites_only_vcf_index = "~{sites_only_vcf_filename}.tbi" -# } -#} -# -#task IndelsVariantRecalibrator { -# -# input { -# String recalibration_filename -# String tranches_filename -# -# Array[String] recalibration_tranche_values -# Array[String] recalibration_annotation_values -# -# File sites_only_variant_filtered_vcf -# File sites_only_variant_filtered_vcf_index -# -# File mills_resource_vcf -# File axiomPoly_resource_vcf -# File dbsnp_resource_vcf -# File mills_resource_vcf_index -# File axiomPoly_resource_vcf_index -# File dbsnp_resource_vcf_index -# Boolean use_allele_specific_annotations -# Int max_gaussians = 4 -# -# Int disk_size -# String gatk_docker = "us.gcr.io/broad-gatk/gatk:4.2.6.1" -# } -# -# command <<< -# set -euo pipefail -# -# gatk --java-options "-Xms24000m -Xmx25000m" \ -# VariantRecalibrator \ -# -V ~{sites_only_variant_filtered_vcf} \ -# -O ~{recalibration_filename} \ -# --tranches-file ~{tranches_filename} \ -# --trust-all-polymorphic \ -# -tranche ~{sep=' -tranche ' recalibration_tranche_values} \ -# -an ~{sep=' -an ' recalibration_annotation_values} \ -# ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ -# -mode INDEL \ -# --max-gaussians ~{max_gaussians} \ -# -resource:mills,known=false,training=true,truth=true,prior=12 ~{mills_resource_vcf} \ -# -resource:axiomPoly,known=false,training=true,truth=false,prior=10 ~{axiomPoly_resource_vcf} \ -# -resource:dbsnp,known=true,training=false,truth=false,prior=2 ~{dbsnp_resource_vcf} -# >>> -# -# runtime { -# memory: "26000 MiB" -# cpu: "2" -# bootDiskSizeGb: 15 -# disks: "local-disk " + disk_size + " HDD" -# preemptible: 1 -# docker: gatk_docker -# } -# -# output { -# File recalibration = "~{recalibration_filename}" -# File recalibration_index = "~{recalibration_filename}.idx" -# File tranches = "~{tranches_filename}" -# } -#} -# -#task SNPsVariantRecalibratorCreateModel { -# -# input { -# String recalibration_filename -# String tranches_filename -# Int downsampleFactor -# String model_report_filename -# -# Array[String] recalibration_tranche_values -# Array[String] recalibration_annotation_values -# -# File sites_only_variant_filtered_vcf -# File sites_only_variant_filtered_vcf_index -# -# File hapmap_resource_vcf -# File omni_resource_vcf -# File one_thousand_genomes_resource_vcf -# File dbsnp_resource_vcf -# File hapmap_resource_vcf_index -# File omni_resource_vcf_index -# File one_thousand_genomes_resource_vcf_index -# File dbsnp_resource_vcf_index -# Boolean use_allele_specific_annotations -# Int max_gaussians = 6 -# -# Int disk_size -# String gatk_docker = "us.gcr.io/broad-gatk/gatk:4.2.6.1" -# } -# -# command <<< -# set -euo pipefail -# -# gatk --java-options "-Xms100g -Xmx100g" \ -# VariantRecalibrator \ -# -V ~{sites_only_variant_filtered_vcf} \ -# -O ~{recalibration_filename} \ -# --tranches-file ~{tranches_filename} \ -# --trust-all-polymorphic \ -# -tranche ~{sep=' -tranche ' recalibration_tranche_values} \ -# -an ~{sep=' -an ' recalibration_annotation_values} \ -# ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ -# -mode SNP \ -# --sample-every-Nth-variant ~{downsampleFactor} \ -# --output-model ~{model_report_filename} \ -# --max-gaussians ~{max_gaussians} \ -# -resource:hapmap,known=false,training=true,truth=true,prior=15 ~{hapmap_resource_vcf} \ -# -resource:omni,known=false,training=true,truth=true,prior=12 ~{omni_resource_vcf} \ -# -resource:1000G,known=false,training=true,truth=false,prior=10 ~{one_thousand_genomes_resource_vcf} \ -# -resource:dbsnp,known=true,training=false,truth=false,prior=7 ~{dbsnp_resource_vcf} -# >>> -# -# runtime { -# memory: "104 GiB" -# cpu: "2" -# bootDiskSizeGb: 15 -# disks: "local-disk " + disk_size + " HDD" -# preemptible: 1 -# docker: gatk_docker -# } -# -# output { -# File model_report = "~{model_report_filename}" -# } -#} -# -#task ApplyRecalibration { -# -# input { -# String recalibrated_vcf_filename -# File input_vcf -# File input_vcf_index -# File indels_recalibration -# File indels_recalibration_index -# File indels_tranches -# File snps_recalibration -# File snps_recalibration_index -# File snps_tranches -# Float indel_filter_level -# Float snp_filter_level -# Boolean use_allele_specific_annotations -# Int disk_size -# String gatk_docker = "us.gcr.io/broad-gatk/gatk:4.2.6.1" -# } -# -# command <<< -# set -euo pipefail -# -# gatk --java-options "-Xms5000m -Xmx6500m" \ -# ApplyVQSR \ -# -O tmp.indel.recalibrated.vcf \ -# -V ~{input_vcf} \ -# --recal-file ~{indels_recalibration} \ -# ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ -# --tranches-file ~{indels_tranches} \ -# --truth-sensitivity-filter-level ~{indel_filter_level} \ -# --create-output-variant-index true \ -# -mode INDEL -# -# gatk --java-options "-Xms5000m -Xmx6500m" \ -# ApplyVQSR \ -# -O ~{recalibrated_vcf_filename} \ -# -V tmp.indel.recalibrated.vcf \ -# --recal-file ~{snps_recalibration} \ -# ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ -# --tranches-file ~{snps_tranches} \ -# --truth-sensitivity-filter-level ~{snp_filter_level} \ -# --create-output-variant-index true \ -# -mode SNP -# >>> -# -# runtime { -# memory: "7000 MiB" -# cpu: "1" -# bootDiskSizeGb: 15 -# disks: "local-disk " + disk_size + " HDD" -# preemptible: 1 -# docker: gatk_docker -# } -# -# output { -# File recalibrated_vcf = "~{recalibrated_vcf_filename}" -# File recalibrated_vcf_index = "~{recalibrated_vcf_filename}.tbi" -# } -#} +task HardFilterVcf { + + input { + File vcf + File vcf_index + + String prefix + + # From WARP: + # ExcessHet is a phred-scaled p-value. We want a cutoff of anything more extreme + # than a z-score of -4.5 which is a p-value of 3.4e-06, which phred-scaled is 54.69 + Float excess_het_threshold = 54.69 + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size([vcf, vcf_index], "GB")) + + command <<< + set -euo pipefail + + # Get amount of memory to use: + mem_available=$(free -m | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-1000 + let mem_max=${mem_available}-750 + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + VariantFiltration \ + --filter-expression "ExcessHet > ~{excess_het_threshold}" \ + --filter-name ExcessHet \ + -V ~{vcf} \ + -O ~{prefix}.hard_filtered.vcf + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File variant_filtered_vcf = "~{prefix}.hard_filtered.vcf" + File variant_filtered_vcf_index = "~{prefix}.hard_filtered.vcf.tbi" + } +} + +task HardFilterAndMakeSitesOnlyVcf { + + input { + File vcf + File vcf_index + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size([vcf, vcf_index], "GB")) + + command <<< + set -euo pipefail + + # Get amount of memory to use: + mem_available=$(free -m | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-1000 + let mem_max=${mem_available}-750 + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + MakeSitesOnlyVcf \ + -I ~{vcf} \ + -O ~{prefix}.sites_only.vcf + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File sites_only_vcf = "~{prefix}.hard_filtered.sites_only.vcf" + File sites_only_vcf_index = "~{prefix}.hard_filtered.sites_only.vcf.tbi" + } +} + +task IndelsVariantRecalibrator { + + input { + File vcf + File vcf_index + + String prefix + + Array[String] recalibration_tranche_values + Array[String] recalibration_annotation_values + + Array[File] known_reference_variants + Array[File] known_reference_variants_index + Array[File] known_reference_variants_identifier + Array[Boolean] is_known + Array[Boolean] is_training + Array[Boolean] is_truth + Array[Int] prior + + Boolean use_allele_specific_annotations + Int max_gaussians = 4 + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + vcf: "Sites only VCF. Can be pre-filtered using hard-filters." + vcf_index: "Tribble Index for sites only VCF." + known_reference_variants: "Array of known reference VCF files. For humans, dbSNP is one example." + known_reference_variants_index: "Array of index files for known reference VCF files." + known_reference_variants_identifier: "Array of boolean values the identifier / name for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." + is_known: "Array of boolean values indicating if the known_reference_variant file at the same array position contains known variants. Must be the same length as `known_reference_variants`." + is_training: "Array of boolean values indicating if the known_reference_variant file at the same array position contains training data. Must be the same length as `known_reference_variants`." + is_truth: "Array of boolean values indicating if the known_reference_variant file at the same array position contains truth data. Must be the same length as `known_reference_variants`." + prior: "Array of integer values indicating the priors for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." + } + + + Int disk_size = 10 + ceil(size(known_reference_variants, "GB")) + + 4*ceil(size(vcf, "GB")) + + 2*ceil(size(vcf_index, "GB")) + + command <<< + set -euxo pipefail + + # We need to generate resource strings from the input arrays. + # First we check that the arrays are the same length: + if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_index)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_known)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_training)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_truth)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(prior)} ]] ; then + echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants)}" 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants_index)}" 1>&2 + echo " known_reference_variants_identifier = ~{length(known_reference_variants_identifier)}" 1>&2 + echo " is_known = ~{length(is_known)}" 1>&2 + echo " is_training = ~{length(is_training)}" 1>&2 + echo " is_truth = ~{length(is_truth)}" 1>&2 + echo " prior = ~{length(prior)}" 1>&2 + false + fi + + # Now we can write out the arrays into a TSV file and add them line by line to the execution: + # Create the TSV: + options_tsv=~{write_tsv(transpose([known_reference_variants_identifier, is_known, is_training, is_truth, prior, known_reference_variants]))} + + # Now read them into a string: + resource_flags=$(awk '{printf("--resource:%s,known=$s,training=%s,truth=%s,prior=%d %s ", $1, $2, $3, $4, $5, $6)}') + + # Get amount of memory to use: + mem_available=$(free -g | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-2 + let mem_max=${mem_available}-1 + + gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ + VariantRecalibrator \ + -V ~{vcf} \ + -O ~{prefix}.recal \ + --tranches-file ~{prefix}.tranches \ + --trust-all-polymorphic \ + -tranche ~{sep=' -tranche ' recalibration_tranche_values} \ + -an ~{sep=' -an ' recalibration_annotation_values} \ + ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ + -mode INDEL \ + --output-model ~{prefix}.model.report \ + --max-gaussians ~{max_gaussians} \ + ${resource_flags} + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 26, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File recalibration = "~{prefix}.recal" + File recalibration_index = "~~{prefix}.recal.idx" + File tranches = "~{prefix}.tranches" + File model_report = "~{prefix}.model.report" + } +} + +task SNPsVariantRecalibratorCreateModel { + + input { + File vcf + File vcf_index + + String prefix + + Array[String] recalibration_tranche_values + Array[String] recalibration_annotation_values + + Array[File] known_reference_variants + Array[File] known_reference_variants_index + Array[File] known_reference_variants_identifier + Array[Boolean] is_known + Array[Boolean] is_training + Array[Boolean] is_truth + Array[Int] prior + + Int? downsampleFactor + + Boolean use_allele_specific_annotations + Int max_gaussians = 6 + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + vcf: "Sites only VCF. Can be pre-filtered using hard-filters." + vcf_index: "Tribble Index for sites only VCF." + known_reference_variants: "Array of known reference VCF files. For humans, dbSNP is one example." + known_reference_variants_index: "Array of index files for known reference VCF files." + known_reference_variants_identifier: "Array of boolean values the identifier / name for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." + is_known: "Array of boolean values indicating if the known_reference_variant file at the same array position contains known variants. Must be the same length as `known_reference_variants`." + is_training: "Array of boolean values indicating if the known_reference_variant file at the same array position contains training data. Must be the same length as `known_reference_variants`." + is_truth: "Array of boolean values indicating if the known_reference_variant file at the same array position contains truth data. Must be the same length as `known_reference_variants`." + prior: "Array of integer values indicating the priors for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." + } + + Int disk_size = 10 + ceil(size(known_reference_variants, "GB")) + + 4*ceil(size(vcf, "GB")) + + 2*ceil(size(vcf_index, "GB")) + + String downsample_factor_arg = if defined(downsampleFactor) then " --sample-every-Nth-variant " else "" + + command <<< + set -euxo pipefail + + # We need to generate resource strings from the input arrays. + # First we check that the arrays are the same length: + if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_index)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_known)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_training)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_truth)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(prior)} ]] ; then + echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants)}" 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants_index)}" 1>&2 + echo " known_reference_variants_identifier = ~{length(known_reference_variants_identifier)}" 1>&2 + echo " is_known = ~{length(is_known)}" 1>&2 + echo " is_training = ~{length(is_training)}" 1>&2 + echo " is_truth = ~{length(is_truth)}" 1>&2 + echo " prior = ~{length(prior)}" 1>&2 + false + fi + + # Now we can write out the arrays into a TSV file and add them line by line to the execution: + # Create the TSV: + options_tsv=~{write_tsv(transpose([known_reference_variants_identifier, is_known, is_training, is_truth, prior, known_reference_variants]))} + + # Now read them into a string: + resource_flags=$(awk '{printf("--resource:%s,known=$s,training=%s,truth=%s,prior=%d %s ", $1, $2, $3, $4, $5, $6)}') + + # Get amount of memory to use: + mem_available=$(free -g | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-2 + let mem_max=${mem_available}-1 + + gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ + VariantRecalibrator \ + -V ~{vcf} \ + -O ~{prefix}.recal \ + --tranches-file ~{prefix}.tranches \ + --trust-all-polymorphic \ + -tranche ~{sep=' -tranche ' recalibration_tranche_values} \ + -an ~{sep=' -an ' recalibration_annotation_values} \ + ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ + -mode SNP \ + ~{downsample_factor_arg}~{default="" sep=" --sample-every-Nth-variant " downsampleFactor} \ + --output-model ~{prefix}.model.report \ + --max-gaussians ~{max_gaussians} \ + ${resource_flags} + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 64, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File recalibration = "~{prefix}.recal" + File recalibration_index = "~~{prefix}.recal.idx" + File tranches = "~{prefix}.tranches" + File model_report = "~{prefix}.model.report" + } +} + +task ApplyVqsr { + + input { + File vcf + File vcf_index + + String prefix + + File snps_recalibration + File snps_recalibration_index + File snps_tranches + Float snp_filter_level + + File indels_recalibration + File indels_recalibration_index + File indels_tranches + Float indel_filter_level + + Boolean use_allele_specific_annotations + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 10 + ceil(size([vcf, vcf_index], "GB")) + + 2*ceil(size([snps_recalibration, snps_recalibration_index, snps_tranches], "GB")) + + 2*ceil(size([indels_recalibration, indels_recalibration_index, indels_tranches], "GB")) + + command <<< + set -euxo pipefail + + # Get amount of memory to use: + mem_available=$(free -m | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-2000 + let mem_max=${mem_available}-500 + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + ApplyVQSR \ + -V ~{vcf} \ + -O tmp.indel.recalibrated.vcf \ + --recal-file ~{indels_recalibration} \ + ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ + --tranches-file ~{indels_tranches} \ + --truth-sensitivity-filter-level ~{indel_filter_level} \ + --create-output-variant-index true \ + -mode INDEL + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + ApplyVQSR \ + -V tmp.indel.recalibrated.vcf \ + -O ~{prefix}.recalibrated.vcf \ + --recal-file ~{snps_recalibration} \ + ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ + --tranches-file ~{snps_tranches} \ + --truth-sensitivity-filter-level ~{snp_filter_level} \ + --create-output-variant-index true \ + -mode SNP + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 7, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File recalibrated_vcf = "~{prefix}.recalibrated.vcf" + File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.tbi" + } +} From 35ad7615709b1209752a53836a305c5f53d917ed Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 9 Jan 2023 17:29:20 -0500 Subject: [PATCH 047/297] Fixed typos in `VQSR` tasks. --- wdl/SRWholeGenome.wdl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 692c3cbb0..da2f6f1b3 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -157,7 +157,8 @@ workflow SRWholeGenome { recalibration_tranche_values = snp_recalibration_tranche_values, recalibration_annotation_values = snp_recalibration_annotation_values, known_reference_variants = [ref_map["known_sites_vcf"]], - known_reference_variants_identifier = [ref_map["known_sites_index"]], + known_reference_variants_index = [ref_map["known_sites_index"]], + known_reference_variants_identifier = ["pfcrosses"], is_known = [true], is_training = [true], is_truth = [false], @@ -174,7 +175,8 @@ workflow SRWholeGenome { recalibration_tranche_values = snp_recalibration_tranche_values, recalibration_annotation_values = snp_recalibration_annotation_values, known_reference_variants = [ref_map["known_sites_vcf"]], - known_reference_variants_identifier = [ref_map["known_sites_index"]], + known_reference_variants_index = [ref_map["known_sites_index"]], + known_reference_variants_identifier = ["pfcrosses"], is_known = [true], is_training = [true], is_truth = [false], From d7b5616c3c86d1deb1f79882448c76f1aa96a2df Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 9 Jan 2023 17:50:42 -0500 Subject: [PATCH 048/297] HC now uses `--linked-de-bruijn-graph` --- wdl/tasks/HaplotypeCaller.wdl | 1 + 1 file changed, 1 insertion(+) diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index 8acd8e92b..0e9d3d806 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -191,6 +191,7 @@ task HaplotypeCaller_GATK4_VCF { -O ~{output_file_name} \ -contamination ~{default=0 contamination} \ --sample-ploidy ~{ploidy} \ + --linked-de-bruijn-graph \ -GQB 10 -GQB 20 -GQB 30 -GQB 40 -GQB 50 -GQB 60 -GQB 70 -GQB 80 -GQB 90 \ ~{false="--disable-spanning-event-genotyping" true="" use_spanning_event_genotyping} \ -G StandardAnnotation -G StandardHCAnnotation ~{true="-G AS_StandardAnnotation" false="" make_gvcf} \ From 17b9f6f1b38962842604b0085845717a607ff413 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 10 Jan 2023 12:33:51 -0500 Subject: [PATCH 049/297] Added VQSR to `SrWholeGenome` and `SRJointCallGVCFsWithGenomicsDB` --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 120 +++++++++++++++++++++++-- wdl/SRWholeGenome.wdl | 63 ++++++++----- wdl/tasks/VariantUtils.wdl | 26 +++--- 3 files changed, 168 insertions(+), 41 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index e56058ef3..267508e27 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -4,7 +4,8 @@ version 1.0 ## A workflow that performs joint calling on single-sample gVCFs from GATK4 HaplotypeCaller using GenomicsDB. ############################################################################################################# -import "SRJointGenotyping.wdl" as SRJOINT +import "tasks/SRJointGenotyping.wdl" as SRJOINT +import "tasks/VariantUtils.wdl" as VARUTIL import "tasks/Finalize.wdl" as FF workflow SRJointCallGVCFsWithGenomicsDB { @@ -16,6 +17,14 @@ workflow SRJointCallGVCFsWithGenomicsDB { File interval_list + Float snp_filter_level = 99.7 + Array[String] snp_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] + Array[Float] snp_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.8, 99.6, 99.5, 99.4, 99.3, 99.0, 98.0, 97.0, 90.0 ] + + Float indel_filter_level = 99.0 + Array[String] indel_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] + Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] + String prefix String gcs_out_root_dir @@ -37,7 +46,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { # For small callsets (fewer than 1000 samples) we can gather the VCF shards and collect metrics directly. # For anything larger, we need to keep the VCF sharded and gather metrics collected from them. # We allow overriding this default behavior for testing / special requests. - Boolean is_small_callset = gvcfs <= 1000 + Boolean is_small_callset = length(gvcfs) <= 1000 # Create sample-name map: call SRJOINT.CreateSampleNameMap as CreateSampleNameMap { @@ -70,19 +79,114 @@ workflow SRJointCallGVCFsWithGenomicsDB { prefix = prefix, } - ## TODO: Add VQSR here. + # First make a sites-only VCF for recal (smaller file, easier to work with): + call VARUTIL.MakeSitesOnlyVcf as MakeSitesOnlyGVCF { + input: + vcf = JointCallGVCFs.output_vcf, + vcf_index = JointCallGVCFs.output_vcf_index, + prefix = prefix + } + + # Now we run VariantRecalibrator for indels and snps: + call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { + input: + vcf = MakeSitesOnlyGVCF.sites_only_vcf, + vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, + prefix = prefix + ".indels", + recalibration_tranche_values = snp_recalibration_tranche_values, + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = [ref_map["known_sites_vcf"]], + known_reference_variants_index = [ref_map["known_sites_index"]], + known_reference_variants_identifier = ["pfcrosses"], + is_known = [true], + is_training = [true], + is_truth = [true], + prior = [15], + use_allele_specific_annotations = true, + max_gaussians = 8, + } + + call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { + input: + vcf = MakeSitesOnlyGVCF.sites_only_vcf, + vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, + prefix = prefix + ".snps", + recalibration_tranche_values = snp_recalibration_tranche_values, + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = [ref_map["known_sites_vcf"]], + known_reference_variants_index = [ref_map["known_sites_index"]], + known_reference_variants_identifier = ["pfcrosses"], + is_known = [true], + is_training = [true], + is_truth = [true], + prior = [15], + use_allele_specific_annotations = true, + max_gaussians = 8, + } + + call VARUTIL.ApplyVqsr as ApplyVqsr { + input: + vcf = JointCallGVCFs.output_vcf, + vcf_index = JointCallGVCFs.output_vcf_index, + + prefix = prefix + ".vqsr_filtered", - # Finalize - call FF.FinalizeToFile as FinalizeGVCF { input: outdir = outdir, file = JointCallGVCFs.output_vcf } - call FF.FinalizeToFile as FinalizeTBI { input: outdir = outdir, file = JointCallGVCFs.output_vcf_index } + snps_recalibration = TrainVQSROnHCSnpVariants.recalibration, + snps_recalibration_index = TrainVQSROnHCSnpVariants.recalibration_index, + snps_tranches = TrainVQSROnHCSnpVariants.tranches, + snp_filter_level = snp_filter_level, + + indels_recalibration = TrainVQSROnHCIndelVariants.recalibration, + indels_recalibration_index = TrainVQSROnHCIndelVariants.recalibration_index, + indels_tranches = TrainVQSROnHCIndelVariants.tranches, + indel_filter_level = indel_filter_level, + + use_allele_specific_annotations = true, + } + + # Finalize: + File keyfile = ApplyVqsr.recalibrated_vcf_index + + call FF.FinalizeToFile as FinalizeGenomicsDB { input: outdir = outdir, keyfile = keyfile, file = ImportGVCFsIntoGenomicsDB.output_genomicsdb } + + call FF.FinalizeToFile as FinalizeRawVCF { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf } + call FF.FinalizeToFile as FinalizeRawTBI { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf_index } + + call FF.FinalizeToFile as FinalizeIndelRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } + call FF.FinalizeToFile as FinalizeIndelRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } + call FF.FinalizeToFile as FinalizeIndelRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } + call FF.FinalizeToFile as FinalizeIndelRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } + + call FF.FinalizeToFile as FinalizeSnpRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } + call FF.FinalizeToFile as FinalizeSnpRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } + call FF.FinalizeToFile as FinalizeSnpRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } + call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } + + call FF.FinalizeToFile as FinalizeVQSRVCF { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } + call FF.FinalizeToFile as FinalizeVQSRTBI { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } ########## # store the results into designated bucket ########## output { - File joint_gvcf = FinalizeGVCF.gcs_path - File joint_gvcf_tbi = FinalizeTBI.gcs_path + File genomicsDB = FinalizeGenomicsDB.gcs_path + + File raw_joint_vcf = FinalizeRawVCF.gcs_path + File raw_joint_vcf_tbi = FinalizeRawTBI.gcs_path + + File? vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path + File? vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path + File? vqsr_indel_recal_tranches = FinalizeIndelRecalTranches.gcs_path + File? vqsr_indel_recal_model_report = FinalizeIndelRecalModelReport.gcs_path + + File? vqsr_snp_recal_file = FinalizeSnpRecalFile.gcs_path + File? vqsr_snp_recal_file_index = FinalizeSnpRecalIndex.gcs_path + File? vqsr_snp_recal_tranches = FinalizeSnpRecalTranches.gcs_path + File? vqsr_snp_recal_model_report = FinalizeSnpRecalModelReport.gcs_path + + File joint_recalibrated_vcf = FinalizeVQSRVCF.gcs_path + File joint_recalibrated_vcf_tbi = FinalizeVQSRTBI.gcs_path } } diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index da2f6f1b3..d3fa8d666 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -35,11 +35,11 @@ workflow SRWholeGenome { Int ploidy = 2 Float snp_filter_level = 99.7 - Array[String] snp_recalibration_annotation_values = ["QD", "MQRankSum", "ReadPosRankSum", "FS", "MQ", "SOR", "DP"] + Array[String] snp_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] Array[Float] snp_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.8, 99.6, 99.5, 99.4, 99.3, 99.0, 98.0, 97.0, 90.0 ] Float indel_filter_level = 99.0 - Array[String] indel_recalibration_annotation_values = ["FS", "ReadPosRankSum", "MQRankSum", "QD", "SOR", "DP"] + Array[String] indel_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] File? bed_to_compute_coverage @@ -123,10 +123,10 @@ workflow SRWholeGenome { contigs_names_to_ignore = contigs_names_to_ignore, } - call FF.FinalizeToFile as FinalizeDVPepperVcf { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_vcf])} - call FF.FinalizeToFile as FinalizeDVPepperTbi { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_tbi])} - call FF.FinalizeToFile as FinalizeDVPepperGVcf { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_g_vcf])} - call FF.FinalizeToFile as FinalizeDVPepperGTbi { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_g_tbi])} + call FF.FinalizeToFile as FinalizeDVPepperVcf { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_vcf]) } + call FF.FinalizeToFile as FinalizeDVPepperTbi { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_tbi]) } + call FF.FinalizeToFile as FinalizeDVPepperGVcf { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_g_vcf]) } + call FF.FinalizeToFile as FinalizeDVPepperGTbi { input: outdir = smalldir, file = select_first([CallVariantsWithDeepVariant.dvp_g_tbi]) } } # Now we handle HaplotypeCaller data: @@ -161,7 +161,7 @@ workflow SRWholeGenome { known_reference_variants_identifier = ["pfcrosses"], is_known = [true], is_training = [true], - is_truth = [false], + is_truth = [true], prior = [15], use_allele_specific_annotations = true, max_gaussians = 8, @@ -179,7 +179,7 @@ workflow SRWholeGenome { known_reference_variants_identifier = ["pfcrosses"], is_known = [true], is_training = [true], - is_truth = [false], + is_truth = [true], prior = [15], use_allele_specific_annotations = true, max_gaussians = 8, @@ -205,17 +205,31 @@ workflow SRWholeGenome { use_allele_specific_annotations = true, } + # Create a Keyfile for finalization: + File keyfile = ApplyVqsr.recalibrated_vcf_index + # Finalize the raw Joint Calls: - call FF.FinalizeToFile as FinalizeHCVcf { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_vcf])} - call FF.FinalizeToFile as FinalizeHCTbi { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_vcf_index])} - call FF.FinalizeToFile as FinalizeHCGVcf { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_gvcf])} - call FF.FinalizeToFile as FinalizeHCGTbi { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.output_gvcf_index])} - call FF.FinalizeToFile as FinalizeHCBamOut { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.bamout])} - call FF.FinalizeToFile as FinalizeHCBaiOut { input: outdir = smalldir, file = select_first([CallVariantsWithHaplotypeCaller.bamout_index])} + call FF.FinalizeToFile as FinalizeHCVcf { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.output_vcf } + call FF.FinalizeToFile as FinalizeHCTbi { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.output_vcf_index } + call FF.FinalizeToFile as FinalizeHCGVcf { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.output_gvcf } + call FF.FinalizeToFile as FinalizeHCGTbi { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.output_gvcf_index } + call FF.FinalizeToFile as FinalizeHCBamOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout } + call FF.FinalizeToFile as FinalizeHCBaiOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout_index } + + # Finalize the VQSR files: + call FF.FinalizeToFile as FinalizeIndelRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } + call FF.FinalizeToFile as FinalizeIndelRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } + call FF.FinalizeToFile as FinalizeIndelRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } + call FF.FinalizeToFile as FinalizeIndelRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } + + call FF.FinalizeToFile as FinalizeSnpRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } + call FF.FinalizeToFile as FinalizeSnpRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } + call FF.FinalizeToFile as FinalizeSnpRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } + call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } # Finalize the reclibrated / filtered variants: - call FF.FinalizeToFile as FinalizeHCVqsrVcf { input: outdir = smalldir, file = select_first([ApplyVqsr.recalibrated_vcf])} - call FF.FinalizeToFile as FinalizeHCVqsrTbi { input: outdir = smalldir, file = select_first([ApplyVqsr.recalibrated_vcf_index])} + call FF.FinalizeToFile as FinalizeHCVqsrVcf { input: outdir = smalldir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } + call FF.FinalizeToFile as FinalizeHCVqsrTbi { input: outdir = smalldir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } } output { @@ -246,14 +260,23 @@ workflow SRWholeGenome { ######################################## - File? hc_vcf = FinalizeHCVcf.gcs_path - File? hc_tbi = FinalizeHCTbi.gcs_path - File? hc_vqsr_vcf = FinalizeHCVqsrVcf.gcs_path - File? hc_vqsr_tbi = FinalizeHCVqsrTbi.gcs_path File? hc_g_vcf = FinalizeHCGVcf.gcs_path File? hc_g_tbi = FinalizeHCGTbi.gcs_path File? hc_bamout = FinalizeHCBamOut.gcs_path File? hc_baiout = FinalizeHCBaiOut.gcs_path + File? hc_raw_vcf = FinalizeHCVcf.gcs_path + File? hc_raw_tbi = FinalizeHCTbi.gcs_path + File? hc_vqsr_vcf = FinalizeHCVqsrVcf.gcs_path + File? hc_vqsr_tbi = FinalizeHCVqsrTbi.gcs_path + + File? vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path + File? vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path + File? vqsr_indel_recal_tranches = FinalizeIndelRecalTranches.gcs_path + File? vqsr_indel_recal_model_report = FinalizeIndelRecalModelReport.gcs_path + File? vqsr_snp_recal_file = FinalizeSnpRecalFile.gcs_path + File? vqsr_snp_recal_file_index = FinalizeSnpRecalIndex.gcs_path + File? vqsr_snp_recal_tranches = FinalizeSnpRecalTranches.gcs_path + File? vqsr_snp_recal_model_report = FinalizeSnpRecalModelReport.gcs_path } } diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index bd1373be8..1500cb743 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -662,7 +662,7 @@ task HardFilterVcf { --filter-expression "ExcessHet > ~{excess_het_threshold}" \ --filter-name ExcessHet \ -V ~{vcf} \ - -O ~{prefix}.hard_filtered.vcf + -O ~{prefix}.hard_filtered.vcf.gz >>> ######################### @@ -687,12 +687,12 @@ task HardFilterVcf { } output { - File variant_filtered_vcf = "~{prefix}.hard_filtered.vcf" + File variant_filtered_vcf = "~{prefix}.hard_filtered.vcf.gz" File variant_filtered_vcf_index = "~{prefix}.hard_filtered.vcf.tbi" } } -task HardFilterAndMakeSitesOnlyVcf { +task MakeSitesOnlyVcf { input { File vcf @@ -716,7 +716,7 @@ task HardFilterAndMakeSitesOnlyVcf { gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ MakeSitesOnlyVcf \ -I ~{vcf} \ - -O ~{prefix}.sites_only.vcf + -O ~{prefix}.sites_only.vcf.gz >>> ######################### @@ -741,8 +741,8 @@ task HardFilterAndMakeSitesOnlyVcf { } output { - File sites_only_vcf = "~{prefix}.hard_filtered.sites_only.vcf" - File sites_only_vcf_index = "~{prefix}.hard_filtered.sites_only.vcf.tbi" + File sites_only_vcf = "~{prefix}.sites_only.vcf.gz" + File sites_only_vcf_index = "~{prefix}.sites_only.vcf.tbi" } } @@ -759,7 +759,7 @@ task IndelsVariantRecalibrator { Array[File] known_reference_variants Array[File] known_reference_variants_index - Array[File] known_reference_variants_identifier + Array[String] known_reference_variants_identifier Array[Boolean] is_known Array[Boolean] is_training Array[Boolean] is_truth @@ -815,7 +815,7 @@ task IndelsVariantRecalibrator { options_tsv=~{write_tsv(transpose([known_reference_variants_identifier, is_known, is_training, is_truth, prior, known_reference_variants]))} # Now read them into a string: - resource_flags=$(awk '{printf("--resource:%s,known=$s,training=%s,truth=%s,prior=%d %s ", $1, $2, $3, $4, $5, $6)}') + resource_flags=$(awk '{printf("--resource:%s,known=%s,training=%s,truth=%s,prior=%d %s ", $1, $2, $3, $4, $5, $6)}' ${options_tsv}) # Get amount of memory to use: mem_available=$(free -g | grep '^Mem' | awk '{print $2}') @@ -860,7 +860,7 @@ task IndelsVariantRecalibrator { output { File recalibration = "~{prefix}.recal" - File recalibration_index = "~~{prefix}.recal.idx" + File recalibration_index = "~{prefix}.recal.idx" File tranches = "~{prefix}.tranches" File model_report = "~{prefix}.model.report" } @@ -879,7 +879,7 @@ task SNPsVariantRecalibratorCreateModel { Array[File] known_reference_variants Array[File] known_reference_variants_index - Array[File] known_reference_variants_identifier + Array[String] known_reference_variants_identifier Array[Boolean] is_known Array[Boolean] is_training Array[Boolean] is_truth @@ -938,7 +938,7 @@ task SNPsVariantRecalibratorCreateModel { options_tsv=~{write_tsv(transpose([known_reference_variants_identifier, is_known, is_training, is_truth, prior, known_reference_variants]))} # Now read them into a string: - resource_flags=$(awk '{printf("--resource:%s,known=$s,training=%s,truth=%s,prior=%d %s ", $1, $2, $3, $4, $5, $6)}') + resource_flags=$(awk '{printf("--resource:%s,known=%s,training=%s,truth=%s,prior=%d %s ", $1, $2, $3, $4, $5, $6)}' ${options_tsv}) # Get amount of memory to use: mem_available=$(free -g | grep '^Mem' | awk '{print $2}') @@ -984,7 +984,7 @@ task SNPsVariantRecalibratorCreateModel { output { File recalibration = "~{prefix}.recal" - File recalibration_index = "~~{prefix}.recal.idx" + File recalibration_index = "~{prefix}.recal.idx" File tranches = "~{prefix}.tranches" File model_report = "~{prefix}.model.report" } @@ -1071,6 +1071,6 @@ task ApplyVqsr { output { File recalibrated_vcf = "~{prefix}.recalibrated.vcf" - File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.tbi" + File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.idx" } } From bb908b202e04a2ff0f5e1fe3a68e72a0f96eb80d Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 11 Jan 2023 09:16:26 -0500 Subject: [PATCH 050/297] Updated to GATK 4.3.0.0 --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 28 +++++++++++++------------- wdl/tasks/HaplotypeCaller.wdl | 4 ++-- wdl/tasks/SRJointGenotyping.wdl | 4 ++-- wdl/tasks/SRUtils.wdl | 8 ++++---- wdl/tasks/Utils.wdl | 2 +- wdl/tasks/VariantUtils.wdl | 10 ++++----- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 267508e27..3e844209e 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -11,7 +11,7 @@ import "tasks/Finalize.wdl" as FF workflow SRJointCallGVCFsWithGenomicsDB { input { Array[File] gvcfs - Array[File] tbis + Array[File] gvcf_indices File ref_map_file @@ -32,7 +32,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { parameter_meta { gvcfs: "GCS paths to gVCF files" - tbis: "GCS paths to gVCF tbi files" + gvcf_indices: "GCS paths to gVCF tbi files" ref_map_file: "table indicating reference sequence and auxillary file locations" prefix: "prefix for output joint-called gVCF and tabix index" gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" @@ -59,24 +59,24 @@ workflow SRJointCallGVCFsWithGenomicsDB { call SRJOINT.ImportGVCFs as ImportGVCFsIntoGenomicsDB { input: sample_name_map = CreateSampleNameMap.sample_name_map, - interval_list = interval_list, - ref_fasta = ref_map['fasta'], - ref_fasta_fai = ref_map['fai'], - ref_dict = ref_map['dict'], - prefix = prefix, - batch_size = 50, + interval_list = interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix, + batch_size = 50, } # Joint call call SRJOINT.GenotypeGVCFs as JointCallGVCFs { input: input_gvcf_data = ImportGVCFsIntoGenomicsDB.output_genomicsdb, - interval_list = interval_list, - ref_fasta = ref_map['fasta'], - ref_fasta_fai = ref_map['fai'], - ref_dict = ref_map['dict'], - dbsnp_vcf = ref_map["known_sites_vcf"], - prefix = prefix, + interval_list = interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + dbsnp_vcf = ref_map["known_sites_vcf"], + prefix = prefix, } # First make a sites-only VCF for recal (smaller file, easier to work with): diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index 0e9d3d806..a6ca4bbd9 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -210,7 +210,7 @@ task HaplotypeCaller_GATK4_VCF { boot_disk_gb: 15, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) @@ -331,7 +331,7 @@ task ReblockGVCF { boot_disk_gb: 15, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 92bb4e414..d6436ea62 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -154,7 +154,7 @@ task ImportGVCFs { boot_disk_gb: 15, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -246,7 +246,7 @@ task GenotypeGVCFs { boot_disk_gb: 15, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 42bb0852a..d68d2b0a4 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -434,7 +434,7 @@ task BaseRecalibrator { boot_disk_gb: 10, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -516,7 +516,7 @@ task ApplyBQSR { boot_disk_gb: 10, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -727,7 +727,7 @@ task IndexFeatureFile { boot_disk_gb: 10, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -799,7 +799,7 @@ task RevertBaseQualities { boot_disk_gb: 10, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 1593c3059..086bed950 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -656,7 +656,7 @@ task DownsampleSam { boot_disk_gb: 10, preemptible_tries: 2, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.0.0" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 1500cb743..9fa74124e 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -673,7 +673,7 @@ task HardFilterVcf { boot_disk_gb: 15, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -727,7 +727,7 @@ task MakeSitesOnlyVcf { boot_disk_gb: 15, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -845,7 +845,7 @@ task IndelsVariantRecalibrator { boot_disk_gb: 15, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -969,7 +969,7 @@ task SNPsVariantRecalibratorCreateModel { boot_disk_gb: 15, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { @@ -1056,7 +1056,7 @@ task ApplyVqsr { boot_disk_gb: 15, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.2.6.1" + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { From 38732276af4de286f468bd7c3757c118931eaef6 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 11 Jan 2023 14:43:20 -0500 Subject: [PATCH 051/297] Added region annotation. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 24 +++++- wdl/tasks/SRJointGenotyping.wdl | 23 +++--- wdl/tasks/VariantUtils.wdl | 109 +++++++++++++++++++++++-- 3 files changed, 136 insertions(+), 20 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 3e844209e..51e0e426c 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -25,6 +25,10 @@ workflow SRJointCallGVCFsWithGenomicsDB { Array[String] indel_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] + Array[File]? annotation_bed_files + Array[File]? annotation_bed_file_indexes + Array[String]? annotation_bed_file_annotation_names + String prefix String gcs_out_root_dir @@ -144,8 +148,21 @@ workflow SRJointCallGVCFsWithGenomicsDB { use_allele_specific_annotations = true, } + # Now we need to annotate our variants by region: + if (defined(annotation_bed_files)) { + call VARUTIL.AnnotateVcfWithBedRegions as AnnotateVcfRegions { + input: + vcf = ApplyVqsr.recalibrated_vcf, + vcf_index = ApplyVqsr.recalibrated_vcf_index, + bed_files = select_first([annotation_bed_files]), + bed_file_indexes = select_first([annotation_bed_file_indexes]), + bed_file_annotation_names = select_first([annotation_bed_file_annotation_names]), + prefix = prefix + ".region_annotated" + } + } + # Finalize: - File keyfile = ApplyVqsr.recalibrated_vcf_index + File keyfile = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]) call FF.FinalizeToFile as FinalizeGenomicsDB { input: outdir = outdir, keyfile = keyfile, file = ImportGVCFsIntoGenomicsDB.output_genomicsdb } @@ -165,6 +182,11 @@ workflow SRJointCallGVCFsWithGenomicsDB { call FF.FinalizeToFile as FinalizeVQSRVCF { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } call FF.FinalizeToFile as FinalizeVQSRTBI { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } + if (defined(annotation_bed_files)) { + call FF.FinalizeToFile as FinalizeRegionAnnotatedVcf { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf]) } + call FF.FinalizeToFile as FinalizeRegionAnnotatedVcfIndex { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf_index]) } + } + ########## # store the results into designated bucket ########## diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index d6436ea62..d018c5973 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -27,6 +27,9 @@ task CreateSampleNameMap { String outfile_name = "~{prefix}.sample_name_map.tsv" + # Every so often we should reauthorize so `bcftools` can continue to access our data: + Int re_auth_interval = 50 + command <<< set -euxo pipefail @@ -42,24 +45,20 @@ task CreateSampleNameMap { let i=1 while read file_path ; do - # Get our read group from the header: - samtools view -H ${file_path} > header.txt - - # Fail if we don't have a header: - grep -q '^@RG' header.txt || echo "No read group line found in GVCF: ${file_path}" && exit 1 + # Get our sample list from our file: + bcftools query -l ${file_path} > sample_names.txt - # Get the sample name from the read group: - grep '^@RG' header.txt | sed 's/\t/\n/g' | grep '^SM:' | sed 's/SM://g' | sort | uniq > sample.names.txt - [[ $(wc -l sample.names.txt) -gt 1 ]] && echo "Multiple sample names found in GVCF: ${file_path}" && exit 1 + # Make sure we only have one sample name: + [[ $(wc -l sample_names.txt | awk '{print $1}') -ne 1 ]] && echo "Incorrect number of sample names found in GVCF (there can be only one!): ${file_path}" && exit 1 # Make sure the samplename has an actual name: - [ $(grep -iq "unnamedsample" sample.names.txt) ] && echo "Sample name found to be unnamedsample in GVCF: ${file_path}" && exit 1 + [ $(grep -iq "unnamedsample" sample_names.txt) ] && echo "Sample name found to be unnamedsample in GVCF: ${file_path}" && exit 1 # Add the sample name and GVCF path to the sample name file: - echo -e "$(cat ${sample.names.txt})\t${file_path}" >> ~{outfile_name} + echo -e "$(cat sample_names.txt)\t${file_path}" >> ~{outfile_name} let i=$i+1 - if [[ $i -gt 50 ]] ; then + if [[ $i -gt ~{re_auth_interval} ]] ; then # Periodically we should update the token so we don't have problems with long file lists: export GCS_OAUTH_TOKEN=$(gcloud auth application-default print-access-token) i=0 @@ -227,7 +226,7 @@ task GenotypeGVCFs { gatk --java-options "-Xms8000m -Xmx25000m" \ GenotypeGVCFs \ -R ~{ref_fasta} \ - -O ~{prefix}.vcf \ + -O ~{prefix}.vcf.gz \ -D ~{dbsnp_vcf} \ -G StandardAnnotation -G AS_StandardAnnotation \ --only-output-calls-starting-in-intervals \ diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 9fa74124e..a123e141b 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -688,7 +688,7 @@ task HardFilterVcf { output { File variant_filtered_vcf = "~{prefix}.hard_filtered.vcf.gz" - File variant_filtered_vcf_index = "~{prefix}.hard_filtered.vcf.tbi" + File variant_filtered_vcf_index = "~{prefix}.hard_filtered.vcf.gz.idx" } } @@ -742,7 +742,102 @@ task MakeSitesOnlyVcf { output { File sites_only_vcf = "~{prefix}.sites_only.vcf.gz" - File sites_only_vcf_index = "~{prefix}.sites_only.vcf.tbi" + File sites_only_vcf_index = "~{prefix}.sites_only.vcf.gz.idx" + } +} + +task AnnotateVcfWithBedRegions { + input { + File vcf + File vcf_index + + Array[File] bed_files + Array[File] bed_file_indexes + Array[String] bed_file_annotation_names + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size([vcf, vcf_index, bed_files, bed_file_indexes], "GB")) + + + + command <<< + set -euxo pipefail + + # Get amount of memory to use: + mem_available=$(free -m | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-1000 + let mem_max=${mem_available}-750 + + # We need to generate argument strings from the input arrays. + # First we check that the arrays are the same length: + if [[ ~{length(bed_files)} -ne ~{length(bed_file_indexes)} ]] || \ + [[ ~{length(bed_files)} -ne ~{length(bed_file_annotation_names)} ]] || \ + echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 + echo " bed_files = ~{length(bed_files)}" 1>&2 + echo " bed_file_indices = ~{length(bed_file_indexes)}" 1>&2 + echo " bed_file_annotation_names = ~{length(bed_file_annotation_names)}" 1>&2 + false + fi + + # Now we can write out the arrays into a TSV file and add them line by line to the execution: + # Create the TSV: + options_tsv=~{write_tsv(transpose([bed_files, bed_file_annotation_names]))} + + # Now we have to run `VariantFiltration` multiple times on its own output so that it can + # annotate each region in the file: + # NOTE: This is dumb, but must be done because the `--mask` and `--mask-name` inputs are not arrays. + + input_vcf=~{vcf} + output_vcf=~{prefix}.intermediate.vcf.gz + while read mask_options ; do + + bed_file=$(echo "${mask_options}" | awk -F'\t' '{print $1}') + mask_name=$(echo "${mask_options}" | awk -F'\t' '{print $2}') + + echo -e "RUNNING GATK ON NEW MASK: ${mask_name}\t${bed_file}" + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + VariantFiltration \ + -V ${input_vcf} \ + -O ${output_vcf} \ + --mask ${bed_file} \ + --mask-name ${mask_name} + + mv ${output_vcf} ~{prefix}.new_input.vcf.gz + input_vcf=~{prefix}.new_input.vcf.gz + done < options_tsv + + mv ${output_vcf} ~{prefix}.vcf.gz + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File annotated_vcf = "~{prefix}.vcf.gz" + File annotated_vcf_index = "~{prefix}.vcf.gz.idx" } } @@ -1028,7 +1123,7 @@ task ApplyVqsr { gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ ApplyVQSR \ -V ~{vcf} \ - -O tmp.indel.recalibrated.vcf \ + -O tmp.indel.recalibrated.vcf.gz \ --recal-file ~{indels_recalibration} \ ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ --tranches-file ~{indels_tranches} \ @@ -1038,8 +1133,8 @@ task ApplyVqsr { gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ ApplyVQSR \ - -V tmp.indel.recalibrated.vcf \ - -O ~{prefix}.recalibrated.vcf \ + -V tmp.indel.recalibrated.vcf.gz \ + -O ~{prefix}.recalibrated.vcf.gz \ --recal-file ~{snps_recalibration} \ ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ --tranches-file ~{snps_tranches} \ @@ -1070,7 +1165,7 @@ task ApplyVqsr { } output { - File recalibrated_vcf = "~{prefix}.recalibrated.vcf" - File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.idx" + File recalibrated_vcf = "~{prefix}.recalibrated.vcf.gz" + File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.gz.idx" } } From 8656cedd680fcf405b2a409619bdf8bb9dc80f1a Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 11 Jan 2023 15:59:23 -0500 Subject: [PATCH 052/297] Fixing vcf.gz typo --- wdl/tasks/SRJointGenotyping.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index d018c5973..5e60ee769 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -259,7 +259,7 @@ task GenotypeGVCFs { } output { - File output_vcf = "~{prefix}.vcf" - File output_vcf_index = "~{prefix}.vcf.idx" + File output_vcf = "~{prefix}.vcf.gz" + File output_vcf_index = "~{prefix}.vcf.gz.idx" } } \ No newline at end of file From 9827f261da3e1339e446787d91440a78bfd00d83 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 11 Jan 2023 16:26:03 -0500 Subject: [PATCH 053/297] Fixed index extension error. --- wdl/tasks/SRJointGenotyping.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 5e60ee769..612132801 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -260,6 +260,6 @@ task GenotypeGVCFs { output { File output_vcf = "~{prefix}.vcf.gz" - File output_vcf_index = "~{prefix}.vcf.gz.idx" + File output_vcf_index = "~{prefix}.vcf.gz.tbi" } } \ No newline at end of file From 59995aeffd23ece8fed34e4cde79e9b2ff0bf50c Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 11 Jan 2023 16:44:37 -0500 Subject: [PATCH 054/297] Updated more vcf.gz index names. --- wdl/tasks/SRJointGenotyping.wdl | 2 -- wdl/tasks/VariantUtils.wdl | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 612132801..35cacde2a 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -234,8 +234,6 @@ task GenotypeGVCFs { -L ~{interval_list} \ ~{true='--keep-combined-raw-annotations' false='' keep_combined_raw_annotations} \ --merge-input-intervals - - ls >>> ######################### RuntimeAttr default_attr = object { diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index a123e141b..70fe11496 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -688,7 +688,7 @@ task HardFilterVcf { output { File variant_filtered_vcf = "~{prefix}.hard_filtered.vcf.gz" - File variant_filtered_vcf_index = "~{prefix}.hard_filtered.vcf.gz.idx" + File variant_filtered_vcf_index = "~{prefix}.hard_filtered.vcf.gz.tbi" } } @@ -742,7 +742,7 @@ task MakeSitesOnlyVcf { output { File sites_only_vcf = "~{prefix}.sites_only.vcf.gz" - File sites_only_vcf_index = "~{prefix}.sites_only.vcf.gz.idx" + File sites_only_vcf_index = "~{prefix}.sites_only.vcf.gz.tbi" } } @@ -837,7 +837,7 @@ task AnnotateVcfWithBedRegions { output { File annotated_vcf = "~{prefix}.vcf.gz" - File annotated_vcf_index = "~{prefix}.vcf.gz.idx" + File annotated_vcf_index = "~{prefix}.vcf.gz.tbi" } } @@ -1166,6 +1166,6 @@ task ApplyVqsr { output { File recalibrated_vcf = "~{prefix}.recalibrated.vcf.gz" - File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.gz.idx" + File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.gz.tbi" } } From cd57bc7bfd65457368dd5159e8b1c7740e31fa57 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 12 Jan 2023 11:37:46 -0500 Subject: [PATCH 055/297] Fixed variant annotation task. --- wdl/tasks/VariantUtils.wdl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 70fe11496..43817af3b 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -762,8 +762,6 @@ task AnnotateVcfWithBedRegions { Int disk_size = 1 + 4*ceil(size([vcf, vcf_index, bed_files, bed_file_indexes], "GB")) - - command <<< set -euxo pipefail @@ -775,7 +773,7 @@ task AnnotateVcfWithBedRegions { # We need to generate argument strings from the input arrays. # First we check that the arrays are the same length: if [[ ~{length(bed_files)} -ne ~{length(bed_file_indexes)} ]] || \ - [[ ~{length(bed_files)} -ne ~{length(bed_file_annotation_names)} ]] || \ + [[ ~{length(bed_files)} -ne ~{length(bed_file_annotation_names)} ]] ; then echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 echo " bed_files = ~{length(bed_files)}" 1>&2 echo " bed_file_indices = ~{length(bed_file_indexes)}" 1>&2 @@ -808,10 +806,13 @@ task AnnotateVcfWithBedRegions { --mask-name ${mask_name} mv ${output_vcf} ~{prefix}.new_input.vcf.gz + mv ${output_vcf}.tbi ~{prefix}.new_input.vcf.gz.tbi input_vcf=~{prefix}.new_input.vcf.gz - done < options_tsv + done < ${options_tsv} - mv ${output_vcf} ~{prefix}.vcf.gz + # Because of the `mv` at the end of the loop we need to move the "new_input" files here: + mv ~{prefix}.new_input.vcf.gz ~{prefix}.vcf.gz + mv ~{prefix}.new_input.vcf.gz.tbi ~{prefix}.vcf.gz.tbi >>> ######################### From cf9d2f84e598947e1a33e33ed6155c64b23990c3 Mon Sep 17 00:00:00 2001 From: Steve Huang Date: Tue, 13 Dec 2022 16:14:58 -0500 Subject: [PATCH 056/297] Terra changed where submissions go (now at gs:///submissions/) Some small optimizations too. --- wdl/CleanupIntermediate.wdl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/wdl/CleanupIntermediate.wdl b/wdl/CleanupIntermediate.wdl index 2aad460b5..e52de533e 100644 --- a/wdl/CleanupIntermediate.wdl +++ b/wdl/CleanupIntermediate.wdl @@ -3,7 +3,7 @@ version 1.0 workflow CleanupIntermediate { # Ironicaly, this generates intermeidate files too, but they are tiny. meta { - description: "A workflow to clean up intermediate files from running workflows. Use at your own risk." + description: "A workflow to clean up intermediate files from running workflows on Terra. Use at your own risk." } input { @@ -31,17 +31,15 @@ task CleanupAFolder { } command <<< - echo "started" - gsutil -q rm -rf gs://~{bucket_name}/~{submission_id} + timeout 23h gsutil -q rm -rf gs://~{bucket_name}/submissions/~{submission_id} || echo "Timed out. Please try again." >>> runtime { cpu: 1 memory: "4 GiB" - disks: "local-disk 50 HDD" - bootDiskSizeGb: 10 + disks: "local-disk 10 HDD" preemptible_tries: 1 max_retries: 1 - docker:"google/cloud-sdk:latest" + docker:"us.gcr.io/google.com/cloudsdktool/google-cloud-cli:alpine" } -} \ No newline at end of file +} From 383015ce49ef480af631742d809d61ae787e17b3 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Fri, 16 Dec 2022 17:11:19 +0000 Subject: [PATCH 057/297] Autobump version 3.0.57 --> 3.0.58 --- README.md | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 512934441..f441efb2d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Generic badge](https://img.shields.io/badge/version-3.0.57-blue.svg)](https://shields.io/) +[![Generic badge](https://img.shields.io/badge/version-3.0.58-blue.svg)](https://shields.io/) ![CI/CD](https://github.com/broadinstitute/long-read-pipelines/workflows/CI/CD/badge.svg) ![Nightly](https://github.com/broadinstitute/long-read-pipelines/workflows/Nightly/badge.svg) diff --git a/VERSION b/VERSION index d0ae6b362..fcd9a1136 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -version=3.0.57 +version=3.0.58 From c2b08c9405616c7dc75212ae08434e02b1f4cedb Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 3 Dec 2022 20:36:51 -0500 Subject: [PATCH 058/297] Workflow to perform joint calling on gVCFs with GLNexus --- wdl/JointCallGVCFs.wdl | 43 +++++++++++++++++++++ wdl/tasks/GLNexus.wdl | 86 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 wdl/JointCallGVCFs.wdl create mode 100644 wdl/tasks/GLNexus.wdl diff --git a/wdl/JointCallGVCFs.wdl b/wdl/JointCallGVCFs.wdl new file mode 100644 index 000000000..4b11e3865 --- /dev/null +++ b/wdl/JointCallGVCFs.wdl @@ -0,0 +1,43 @@ +version 1.0 + +############################################################################################ +## A workflow that performs joint calling on gVCFs (usually from DeepVariant) using GLNexus. +############################################################################################ + +import "tasks/GLNexus.wdl" as GLNexus +import "tasks/Finalize.wdl" as FF + +workflow JointCallGVCFs { + input { + Array[File] gvcfs + File? bed + String prefix + + String gcs_out_root_dir + } + + parameter_meta { + gvcfs: "GCS path to aligned BAM files" + bed: "three-column BED file with ranges to analyze" + prefix: "prefix for output joint-called gVCF and tabix index" + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/JointCallGVCFs/~{prefix}" + + # Gather across multiple input gVCFs + call GLNexus.JointCall { input: gvcfs = gvcfs, bed = bed, prefix = prefix } + + # Finalize + call FF.FinalizeToFile as FinalizeGVCF { input: outdir = outdir, file = JointCall.joint_gvcf } + call FF.FinalizeToFile as FinalizeTBI { input: outdir = outdir, file = JointCall.joint_gvcf_tbi } + + ########## + # store the results into designated bucket + ########## + + output { + File joint_gvcf = FinalizeGVCF.gcs_path + File joint_gvcf_tbi = FinalizeTBI.gcs_path + } +} diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl new file mode 100644 index 000000000..d945f6caf --- /dev/null +++ b/wdl/tasks/GLNexus.wdl @@ -0,0 +1,86 @@ +version 1.0 + +########################################################################################## +# This pipeline joint-calls GVCFs with GLNexus (https://github.com/dnanexus-rnd/GLnexus). +# It also permits intervals to be specified so that joint calling only takes place on a +# subset of intervals (this can be useful for finding duplicate samples). +########################################################################################## + +import "Utils.wdl" +import "VariantUtils.wdl" + +task JointCall { + input { + Array[File] gvcfs + File? bed + + String config = "DeepVariantWGS" + Boolean more_PL = false + Boolean squeeze = false + Boolean trim_uncalled_alleles = false + + Int num_cpus = 32 + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + gvcfs: "gVCF files to perform joint calling upon" + bed: "three-column BED file with ranges to analyze (if not specified, use full length of all contigs)" + + config: "configuration preset name or .yml filename" + more_PL: "include PL from reference bands and other cases omitted by default" + squeeze: "reduce pVCF size by suppressing detail in cells derived from reference bands" + trim_uncalled_alleles: "remove alleles with no output GT calls in postprocessing" + + num_cpus: "number of CPUs to use" + prefix: "output prefix for joined-called BCF and GVCF files" + } + + Int disk_size = 1 + 3*ceil(size(gvcfs, "GB")) + + command <<< + set -euxo pipefail + + # See https://github.com/dnanexus-rnd/GLnexus/wiki/Performance for guidance on performance settings + ulimit -Sn 65536 + + glnexus_cli \ + --dir /tmp/glnexus \ + --config ~{config} \ + ~{if more_PL then "--more-PL" else ""} \ + ~{if squeeze then "--squeeze" else ""} \ + ~{if trim_uncalled_alleles then "--trim-uncalled-alleles" else ""} \ + ~{if defined(bed) then "--bed ~{select_first([bed])}" else ""} \ + --list ~{write_lines(gvcfs)} | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz + + tabix -p vcf ~{prefix}.g.vcf.bgz + >>> + + output { + File joint_gvcf = "~{prefix}.g.vcf.bgz" + File joint_gvcf_tbi = "~{prefix}.g.vcf.bgz.tbi" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: num_cpus, + mem_gb: 2*num_cpus, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "ghcr.io/dnanexus-rnd/glnexus:v1.4.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From f0bd376ead9becaa1caeb02d8826c5efc51a938c Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 3 Dec 2022 23:06:16 -0500 Subject: [PATCH 059/297] Workflow for joint-calling gVCFs with GLNexus --- .dockstore.yml | 4 ++++ wdl/{JointCallGVCFs.wdl => LRJointCallGVCFs.wdl} | 2 +- wdl/tasks/GLNexus.wdl | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) rename wdl/{JointCallGVCFs.wdl => LRJointCallGVCFs.wdl} (98%) diff --git a/.dockstore.yml b/.dockstore.yml index d4bdf110f..76f24a7ca 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -108,6 +108,10 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/PBMASIsoSeqDemultiplex.wdl testParameterFiles: +- name: LRJointCallGVCFs + subclass: wdl + primaryDescriptorPath: /wdl/LRJointCallGVCFs.wdl + testParameterFiles: - name: SRBamToFq subclass: wdl primaryDescriptorPath: /wdl/SRBamToFq.wdl diff --git a/wdl/JointCallGVCFs.wdl b/wdl/LRJointCallGVCFs.wdl similarity index 98% rename from wdl/JointCallGVCFs.wdl rename to wdl/LRJointCallGVCFs.wdl index 4b11e3865..457d7f885 100644 --- a/wdl/JointCallGVCFs.wdl +++ b/wdl/LRJointCallGVCFs.wdl @@ -7,7 +7,7 @@ version 1.0 import "tasks/GLNexus.wdl" as GLNexus import "tasks/Finalize.wdl" as FF -workflow JointCallGVCFs { +workflow LRJointCallGVCFs { input { Array[File] gvcfs File? bed diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index d945f6caf..268547d18 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -53,7 +53,8 @@ task JointCall { ~{if squeeze then "--squeeze" else ""} \ ~{if trim_uncalled_alleles then "--trim-uncalled-alleles" else ""} \ ~{if defined(bed) then "--bed ~{select_first([bed])}" else ""} \ - --list ~{write_lines(gvcfs)} | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz + --list ~{write_lines(gvcfs)} \ + | bcftools view | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz tabix -p vcf ~{prefix}.g.vcf.bgz >>> From 72d29d076d7d6454e40effcdbde47532391ef2e1 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 3 Dec 2022 23:24:23 -0500 Subject: [PATCH 060/297] Increased size of disk to store intermediate files --- wdl/tasks/GLNexus.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 268547d18..d4e33d24e 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -38,7 +38,7 @@ task JointCall { prefix: "output prefix for joined-called BCF and GVCF files" } - Int disk_size = 1 + 3*ceil(size(gvcfs, "GB")) + Int disk_size = 1 + 20*ceil(size(gvcfs, "GB")) command <<< set -euxo pipefail From 19cd1cbc8ba4380f82614d7f6bdaa2032694aa76 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sun, 4 Dec 2022 01:53:11 -0500 Subject: [PATCH 061/297] Change location of temp directory --- wdl/tasks/GLNexus.wdl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index d4e33d24e..206d91645 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -38,7 +38,7 @@ task JointCall { prefix: "output prefix for joined-called BCF and GVCF files" } - Int disk_size = 1 + 20*ceil(size(gvcfs, "GB")) + Int disk_size = 1 + 5*ceil(size(gvcfs, "GB")) command <<< set -euxo pipefail @@ -47,7 +47,6 @@ task JointCall { ulimit -Sn 65536 glnexus_cli \ - --dir /tmp/glnexus \ --config ~{config} \ ~{if more_PL then "--more-PL" else ""} \ ~{if squeeze then "--squeeze" else ""} \ From 18d7a8f24fa3f1c1e74fb35a7837f56c892429bb Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sun, 4 Dec 2022 21:12:12 -0500 Subject: [PATCH 062/297] Separate joint calling from bgzipping and indexing for better restartability. --- wdl/tasks/GLNexus.wdl | 91 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 5 deletions(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 206d91645..8fcc80c0e 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -9,7 +9,7 @@ version 1.0 import "Utils.wdl" import "VariantUtils.wdl" -task JointCall { +workflow JointCall { input { Array[File] gvcfs File? bed @@ -38,12 +38,50 @@ task JointCall { prefix: "output prefix for joined-called BCF and GVCF files" } - Int disk_size = 1 + 5*ceil(size(gvcfs, "GB")) + call Call { + input: + gvcfs = gvcfs, + bed = bed, + + config = config, + more_PL = more_PL, + squeeze = squeeze, + trim_uncalled_alleles = trim_uncalled_alleles, + + num_cpus = num_cpus, + prefix = prefix + } + + call ZipAndIndex { input: joint_bcf = Call.joint_bcf, num_cpus = num_cpus, prefix = prefix } + + output { + File joint_gvcf = ZipAndIndex.joint_gvcf + File joint_gvcf_tbi = ZipAndIndex.joint_gvcf_tbi + } +} + +task Call { + input { + Array[File] gvcfs + File? bed + + String config = "DeepVariantWGS" + Boolean more_PL = false + Boolean squeeze = false + Boolean trim_uncalled_alleles = false + + Int num_cpus = 32 + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 10*ceil(size(gvcfs, "GB")) command <<< - set -euxo pipefail + set -x - # See https://github.com/dnanexus-rnd/GLnexus/wiki/Performance for guidance on performance settings + # For guidance on performance settings, see https://github.com/dnanexus-rnd/GLnexus/wiki/Performance ulimit -Sn 65536 glnexus_cli \ @@ -53,8 +91,51 @@ task JointCall { ~{if trim_uncalled_alleles then "--trim-uncalled-alleles" else ""} \ ~{if defined(bed) then "--bed ~{select_first([bed])}" else ""} \ --list ~{write_lines(gvcfs)} \ - | bcftools view | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz + > ~{prefix}.bcf + >>> + + output { + File joint_bcf = "~{prefix}.bcf" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: num_cpus, + mem_gb: 2*num_cpus, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "ghcr.io/dnanexus-rnd/glnexus:v1.4.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task ZipAndIndex { + input { + File joint_bcf + + Int num_cpus = 8 + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size(joint_bcf, "GB")) + + command <<< + set -euxo pipefail + bcftools view ~{joint_bcf} | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz tabix -p vcf ~{prefix}.g.vcf.bgz >>> From 37e7ad0105a2e325f629504f54a130f2540f41d8 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Thu, 8 Dec 2022 17:59:24 -0500 Subject: [PATCH 063/297] Performance improvements --- wdl/tasks/GLNexus.wdl | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 8fcc80c0e..888c4f8cf 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -19,7 +19,7 @@ workflow JointCall { Boolean squeeze = false Boolean trim_uncalled_alleles = false - Int num_cpus = 32 + Int num_cpus = 96 String prefix = "out" RuntimeAttr? runtime_attr_override @@ -38,6 +38,8 @@ workflow JointCall { prefix: "output prefix for joined-called BCF and GVCF files" } + call Utils.ComputeAllowedLocalSSD as Guess { input: intended_gb = 1 + 10*ceil(size(gvcfs, "GB")) } + call Call { input: gvcfs = gvcfs, @@ -49,7 +51,9 @@ workflow JointCall { trim_uncalled_alleles = trim_uncalled_alleles, num_cpus = num_cpus, - prefix = prefix + prefix = prefix, + + num_ssds = Guess.numb_of_local_ssd } call ZipAndIndex { input: joint_bcf = Call.joint_bcf, num_cpus = num_cpus, prefix = prefix } @@ -73,10 +77,12 @@ task Call { Int num_cpus = 32 String prefix = "out" + Int? num_ssds + RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 10*ceil(size(gvcfs, "GB")) + Int disk_size = if defined(num_ssds) then 1 + 375*select_first([num_ssds]) else 1 + 10*ceil(size(gvcfs, "GB")) command <<< set -x @@ -101,18 +107,18 @@ task Call { ######################### RuntimeAttr default_attr = object { cpu_cores: num_cpus, - mem_gb: 2*num_cpus, + mem_gb: 4*num_cpus, disk_gb: disk_size, boot_disk_gb: 10, - preemptible_tries: 1, - max_retries: 1, + preemptible_tries: 0, + max_retries: 0, docker: "ghcr.io/dnanexus-rnd/glnexus:v1.4.1" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" - disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) From 5c57c47f5125b897d50acf39d7a72d55ab6b38a9 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Thu, 8 Dec 2022 21:02:52 -0500 Subject: [PATCH 064/297] Revert those changes because the amount of disk space we need is actually too large to benefit from Steve's ComputeAllowedLocalSSD optimization. --- wdl/tasks/GLNexus.wdl | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 888c4f8cf..03a72bf42 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -38,8 +38,6 @@ workflow JointCall { prefix: "output prefix for joined-called BCF and GVCF files" } - call Utils.ComputeAllowedLocalSSD as Guess { input: intended_gb = 1 + 10*ceil(size(gvcfs, "GB")) } - call Call { input: gvcfs = gvcfs, @@ -52,8 +50,6 @@ workflow JointCall { num_cpus = num_cpus, prefix = prefix, - - num_ssds = Guess.numb_of_local_ssd } call ZipAndIndex { input: joint_bcf = Call.joint_bcf, num_cpus = num_cpus, prefix = prefix } @@ -77,12 +73,10 @@ task Call { Int num_cpus = 32 String prefix = "out" - Int? num_ssds - RuntimeAttr? runtime_attr_override } - Int disk_size = if defined(num_ssds) then 1 + 375*select_first([num_ssds]) else 1 + 10*ceil(size(gvcfs, "GB")) + Int disk_size = 1 + 10*ceil(size(gvcfs, "GB")) command <<< set -x @@ -118,7 +112,7 @@ task Call { runtime { cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" - disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) From d83058ffd06ff88d8c6cbf0555f31e821c66396f Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Fri, 9 Dec 2022 01:13:15 -0500 Subject: [PATCH 065/297] Now optimized to use multiple high-performance disks --- wdl/tasks/GLNexus.wdl | 74 ++++++++++++------------------------------- 1 file changed, 20 insertions(+), 54 deletions(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 03a72bf42..a78260dfd 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -38,6 +38,8 @@ workflow JointCall { prefix: "output prefix for joined-called BCF and GVCF files" } + call Utils.ComputeAllowedLocalSSD as Guess { input: intended_gb = 2*ceil(size(gvcfs, "GB")) } + call Call { input: gvcfs = gvcfs, @@ -50,13 +52,13 @@ workflow JointCall { num_cpus = num_cpus, prefix = prefix, - } - call ZipAndIndex { input: joint_bcf = Call.joint_bcf, num_cpus = num_cpus, prefix = prefix } + num_ssds = Guess.numb_of_local_ssd + } output { - File joint_gvcf = ZipAndIndex.joint_gvcf - File joint_gvcf_tbi = ZipAndIndex.joint_gvcf_tbi + File joint_gvcf = Call.joint_gvcf + File joint_gvcf_tbi = Call.joint_gvcf_tbi } } @@ -70,73 +72,36 @@ task Call { Boolean squeeze = false Boolean trim_uncalled_alleles = false + Int num_ssds = 1 Int num_cpus = 32 String prefix = "out" RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 10*ceil(size(gvcfs, "GB")) + Int disk_size = 1 + 5*ceil(size(gvcfs, "GB")) command <<< set -x + df -h + # For guidance on performance settings, see https://github.com/dnanexus-rnd/GLnexus/wiki/Performance ulimit -Sn 65536 glnexus_cli \ + --dir /mnt/tmp/GLnexus.DB \ --config ~{config} \ ~{if more_PL then "--more-PL" else ""} \ ~{if squeeze then "--squeeze" else ""} \ ~{if trim_uncalled_alleles then "--trim-uncalled-alleles" else ""} \ ~{if defined(bed) then "--bed ~{select_first([bed])}" else ""} \ --list ~{write_lines(gvcfs)} \ - > ~{prefix}.bcf - >>> - - output { - File joint_bcf = "~{prefix}.bcf" - } - - ######################### - RuntimeAttr default_attr = object { - cpu_cores: num_cpus, - mem_gb: 4*num_cpus, - disk_gb: disk_size, - boot_disk_gb: 10, - preemptible_tries: 0, - max_retries: 0, - docker: "ghcr.io/dnanexus-rnd/glnexus:v1.4.1" - } - RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) - runtime { - cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) - memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" - disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" - bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) - preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) - maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) - docker: select_first([runtime_attr.docker, default_attr.docker]) - } -} - -task ZipAndIndex { - input { - File joint_bcf - - Int num_cpus = 8 - String prefix = "out" + | bcftools view | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz - RuntimeAttr? runtime_attr_override - } - - Int disk_size = 1 + 4*ceil(size(joint_bcf, "GB")) - - command <<< - set -euxo pipefail - - bcftools view ~{joint_bcf} | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz tabix -p vcf ~{prefix}.g.vcf.bgz + + df -h >>> output { @@ -147,18 +112,19 @@ task ZipAndIndex { ######################### RuntimeAttr default_attr = object { cpu_cores: num_cpus, - mem_gb: 2*num_cpus, - disk_gb: disk_size, + mem_gb: 4*num_cpus, + disk_gb: 0, # ignored boot_disk_gb: 10, - preemptible_tries: 1, - max_retries: 1, + preemptible_tries: 0, + max_retries: 0, docker: "ghcr.io/dnanexus-rnd/glnexus:v1.4.1" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" - disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" +# disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + disks: "local-disk ~{375*num_ssds} LOCAL, /mnt/tmp ~{disk_size} SSD" bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) From 384c7d6e2e4414c991b871b24c5e02b05146eeb9 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Mon, 12 Dec 2022 02:04:21 -0500 Subject: [PATCH 066/297] Create a Hail MatrixTable of the joint-called VCF --- wdl/LRJointCallGVCFs.wdl | 2 ++ wdl/tasks/GLNexus.wdl | 61 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/wdl/LRJointCallGVCFs.wdl b/wdl/LRJointCallGVCFs.wdl index 457d7f885..34ca00f5a 100644 --- a/wdl/LRJointCallGVCFs.wdl +++ b/wdl/LRJointCallGVCFs.wdl @@ -31,6 +31,7 @@ workflow LRJointCallGVCFs { # Finalize call FF.FinalizeToFile as FinalizeGVCF { input: outdir = outdir, file = JointCall.joint_gvcf } call FF.FinalizeToFile as FinalizeTBI { input: outdir = outdir, file = JointCall.joint_gvcf_tbi } + call FF.FinalizeToFile as FinalizeMT { input: outdir = outdir, file = JointCall.joint_mt_tar_gz } ########## # store the results into designated bucket @@ -39,5 +40,6 @@ workflow LRJointCallGVCFs { output { File joint_gvcf = FinalizeGVCF.gcs_path File joint_gvcf_tbi = FinalizeTBI.gcs_path + File joint_mt_tar_gz = FinalizeMT.gcs_path } } diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index a78260dfd..5acbb87eb 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -56,9 +56,17 @@ workflow JointCall { num_ssds = Guess.numb_of_local_ssd } + call ConvertToHailMT { + input: + gvcf = Call.joint_gvcf, + tbi = Call.joint_gvcf_tbi, + prefix = prefix + } + output { File joint_gvcf = Call.joint_gvcf File joint_gvcf_tbi = Call.joint_gvcf_tbi + File joint_mt_tar_gz = ConvertToHailMT.joint_mt_tar_gz } } @@ -131,3 +139,56 @@ task Call { docker: select_first([runtime_attr.docker, default_attr.docker]) } } + +task ConvertToHailMT { + input { + File gvcf + File tbi + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 2*ceil(size(gvcf, "GB")) + + command <<< + set -x + + python3 <>> + + output { + File joint_mt_tar_gz = "~{prefix}.mt.tar.gz" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 64, + disk_gb: 10, + boot_disk_gb: 10, + preemptible_tries: 0, + max_retries: 0, + docker: "hailgenetics/hail:0.2.105" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From a85213dca4731e2800bcf42317dc1818aba7b484 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Wed, 14 Dec 2022 16:45:06 -0500 Subject: [PATCH 067/297] Some reorganization that will hopefully improve call-caching. --- wdl/LRJointCallGVCFs.wdl | 17 +++- wdl/tasks/GLNexus.wdl | 175 ++++++++++++--------------------------- wdl/tasks/Hail.wdl | 65 +++++++++++++++ 3 files changed, 132 insertions(+), 125 deletions(-) create mode 100644 wdl/tasks/Hail.wdl diff --git a/wdl/LRJointCallGVCFs.wdl b/wdl/LRJointCallGVCFs.wdl index 34ca00f5a..f8b1780e8 100644 --- a/wdl/LRJointCallGVCFs.wdl +++ b/wdl/LRJointCallGVCFs.wdl @@ -5,6 +5,7 @@ version 1.0 ############################################################################################ import "tasks/GLNexus.wdl" as GLNexus +import "tasks/Hail.wdl" as Hail import "tasks/Finalize.wdl" as FF workflow LRJointCallGVCFs { @@ -28,10 +29,21 @@ workflow LRJointCallGVCFs { # Gather across multiple input gVCFs call GLNexus.JointCall { input: gvcfs = gvcfs, bed = bed, prefix = prefix } + call Hail.ConvertToHailMT { + input: + gvcf = JointCall.joint_gvcf, + tbi = JointCall.joint_gvcf_tbi, + prefix = prefix, + finalize_to_dir = outdir + } + # Finalize call FF.FinalizeToFile as FinalizeGVCF { input: outdir = outdir, file = JointCall.joint_gvcf } call FF.FinalizeToFile as FinalizeTBI { input: outdir = outdir, file = JointCall.joint_gvcf_tbi } - call FF.FinalizeToFile as FinalizeMT { input: outdir = outdir, file = JointCall.joint_mt_tar_gz } + + if (defined(ConvertToHailMT.joint_mt_tar_gz)) { + call FF.FinalizeToFile as FinalizeMT { input: outdir = outdir, file = select_first([ConvertToHailMT.joint_mt_tar_gz]) } + } ########## # store the results into designated bucket @@ -40,6 +52,7 @@ workflow LRJointCallGVCFs { output { File joint_gvcf = FinalizeGVCF.gcs_path File joint_gvcf_tbi = FinalizeTBI.gcs_path - File joint_mt_tar_gz = FinalizeMT.gcs_path + File? joint_mt_tar_gz = FinalizeMT.gcs_path + String joint_mt = if (defined(FinalizeMT.gcs_path)) then select_first([FinalizeMT.gcs_path]) else "~{outdir}/~{prefix}.mt" } } diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 5acbb87eb..45831679b 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -9,7 +9,56 @@ version 1.0 import "Utils.wdl" import "VariantUtils.wdl" -workflow JointCall { +#workflow JointCall { +# input { +# Array[File] gvcfs +# File? bed +# +# String config = "DeepVariantWGS" +# Boolean more_PL = false +# Boolean squeeze = false +# Boolean trim_uncalled_alleles = false +# +# Int num_cpus = 96 +# String prefix = "out" +# +# RuntimeAttr? runtime_attr_override +# } +# +# parameter_meta { +# gvcfs: "gVCF files to perform joint calling upon" +# bed: "three-column BED file with ranges to analyze (if not specified, use full length of all contigs)" +# +# config: "configuration preset name or .yml filename" +# more_PL: "include PL from reference bands and other cases omitted by default" +# squeeze: "reduce pVCF size by suppressing detail in cells derived from reference bands" +# trim_uncalled_alleles: "remove alleles with no output GT calls in postprocessing" +# +# num_cpus: "number of CPUs to use" +# prefix: "output prefix for joined-called BCF and GVCF files" +# } +# +# call Call { +# input: +# gvcfs = gvcfs, +# bed = bed, +# +# config = config, +# more_PL = more_PL, +# squeeze = squeeze, +# trim_uncalled_alleles = trim_uncalled_alleles, +# +# num_cpus = num_cpus, +# prefix = prefix +# } +# +# output { +# File joint_gvcf = Call.joint_gvcf +# File joint_gvcf_tbi = Call.joint_gvcf_tbi +# } +#} + +task JointCall { input { Array[File] gvcfs File? bed @@ -19,81 +68,17 @@ workflow JointCall { Boolean squeeze = false Boolean trim_uncalled_alleles = false - Int num_cpus = 96 - String prefix = "out" - - RuntimeAttr? runtime_attr_override - } - - parameter_meta { - gvcfs: "gVCF files to perform joint calling upon" - bed: "three-column BED file with ranges to analyze (if not specified, use full length of all contigs)" - - config: "configuration preset name or .yml filename" - more_PL: "include PL from reference bands and other cases omitted by default" - squeeze: "reduce pVCF size by suppressing detail in cells derived from reference bands" - trim_uncalled_alleles: "remove alleles with no output GT calls in postprocessing" - - num_cpus: "number of CPUs to use" - prefix: "output prefix for joined-called BCF and GVCF files" - } - - call Utils.ComputeAllowedLocalSSD as Guess { input: intended_gb = 2*ceil(size(gvcfs, "GB")) } - - call Call { - input: - gvcfs = gvcfs, - bed = bed, - - config = config, - more_PL = more_PL, - squeeze = squeeze, - trim_uncalled_alleles = trim_uncalled_alleles, - - num_cpus = num_cpus, - prefix = prefix, - - num_ssds = Guess.numb_of_local_ssd - } - - call ConvertToHailMT { - input: - gvcf = Call.joint_gvcf, - tbi = Call.joint_gvcf_tbi, - prefix = prefix - } - - output { - File joint_gvcf = Call.joint_gvcf - File joint_gvcf_tbi = Call.joint_gvcf_tbi - File joint_mt_tar_gz = ConvertToHailMT.joint_mt_tar_gz - } -} - -task Call { - input { - Array[File] gvcfs - File? bed - - String config = "DeepVariantWGS" - Boolean more_PL = false - Boolean squeeze = false - Boolean trim_uncalled_alleles = false - - Int num_ssds = 1 Int num_cpus = 32 String prefix = "out" RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 5*ceil(size(gvcfs, "GB")) + Int disk_size = 1 + 3*ceil(size(gvcfs, "GB")) command <<< set -x - df -h - # For guidance on performance settings, see https://github.com/dnanexus-rnd/GLnexus/wiki/Performance ulimit -Sn 65536 @@ -108,8 +93,6 @@ task Call { | bcftools view | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz tabix -p vcf ~{prefix}.g.vcf.bgz - - df -h >>> output { @@ -131,61 +114,7 @@ task Call { runtime { cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" -# disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" - disks: "local-disk ~{375*num_ssds} LOCAL, /mnt/tmp ~{disk_size} SSD" - bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) - preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) - maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) - docker: select_first([runtime_attr.docker, default_attr.docker]) - } -} - -task ConvertToHailMT { - input { - File gvcf - File tbi - String prefix = "out" - - RuntimeAttr? runtime_attr_override - } - - Int disk_size = 1 + 2*ceil(size(gvcf, "GB")) - - command <<< - set -x - - python3 <>> - - output { - File joint_mt_tar_gz = "~{prefix}.mt.tar.gz" - } - - ######################### - RuntimeAttr default_attr = object { - cpu_cores: 4, - mem_gb: 64, - disk_gb: 10, - boot_disk_gb: 10, - preemptible_tries: 0, - max_retries: 0, - docker: "hailgenetics/hail:0.2.105" - } - RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) - runtime { - cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) - memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" - disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + disks: "local-disk ~{disk_size} SSD, /mnt/tmp ~{disk_size} SSD" bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl new file mode 100644 index 000000000..772f57642 --- /dev/null +++ b/wdl/tasks/Hail.wdl @@ -0,0 +1,65 @@ +version 1.0 + +import "Structs.wdl" + +task ConvertToHailMT { + input { + File gvcf + File tbi + + String reference = "GRCh38" + String prefix = "out" + + String? finalize_to_dir + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 2*ceil(size(gvcf, "GB")) + + command <<< + set -x + + python3 <>> + + output { + File? joint_mt_tar_gz = "~{prefix}.mt.tar.gz" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 64, + disk_gb: 10, + boot_disk_gb: 10, + preemptible_tries: 0, + max_retries: 0, + docker: "hailgenetics/hail:0.2.105" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From 912ed67a11e2c760a511d2e951e78a7dbff4f278 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Thu, 15 Dec 2022 13:00:19 -0500 Subject: [PATCH 068/297] Some simplifications to Hail MT delocalization --- wdl/LRJointCallGVCFs.wdl | 9 ++++----- wdl/tasks/Hail.wdl | 11 +++-------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/wdl/LRJointCallGVCFs.wdl b/wdl/LRJointCallGVCFs.wdl index f8b1780e8..44c2fd180 100644 --- a/wdl/LRJointCallGVCFs.wdl +++ b/wdl/LRJointCallGVCFs.wdl @@ -41,9 +41,9 @@ workflow LRJointCallGVCFs { call FF.FinalizeToFile as FinalizeGVCF { input: outdir = outdir, file = JointCall.joint_gvcf } call FF.FinalizeToFile as FinalizeTBI { input: outdir = outdir, file = JointCall.joint_gvcf_tbi } - if (defined(ConvertToHailMT.joint_mt_tar_gz)) { - call FF.FinalizeToFile as FinalizeMT { input: outdir = outdir, file = select_first([ConvertToHailMT.joint_mt_tar_gz]) } - } +# if (defined(ConvertToHailMT.joint_mt_tar_gz)) { +# call FF.FinalizeToFile as FinalizeMT { input: outdir = outdir, file = select_first([ConvertToHailMT.joint_mt_tar_gz]) } +# } ########## # store the results into designated bucket @@ -52,7 +52,6 @@ workflow LRJointCallGVCFs { output { File joint_gvcf = FinalizeGVCF.gcs_path File joint_gvcf_tbi = FinalizeTBI.gcs_path - File? joint_mt_tar_gz = FinalizeMT.gcs_path - String joint_mt = if (defined(FinalizeMT.gcs_path)) then select_first([FinalizeMT.gcs_path]) else "~{outdir}/~{prefix}.mt" + String joint_mt = ConvertToHailMT.gcs_path } } diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index 772f57642..29b0729ee 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -10,7 +10,7 @@ task ConvertToHailMT { String reference = "GRCh38" String prefix = "out" - String? finalize_to_dir + String finalize_to_dir RuntimeAttr? runtime_attr_override } @@ -30,16 +30,11 @@ task ConvertToHailMT { EOF - if [ "~{defined(finalize_to_dir)}" == "true" ] - then - gsutil -m rsync -Cr ~{prefix}.mt ~{finalize_to_dir}/~{prefix}.mt - else - tar zcvf ~{prefix}.mt.tar.gz ~{prefix}.mt - fi + gsutil -m rsync -Cr ~{prefix}.mt ~{finalize_to_dir}/~{prefix}.mt >>> output { - File? joint_mt_tar_gz = "~{prefix}.mt.tar.gz" + String gcs_path = "~{finalize_to_dir}/~{prefix}.mt" } ######################### From 374768ef0c62125bc8e94906fa6b6ee8ec44912f Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Thu, 15 Dec 2022 13:02:14 -0500 Subject: [PATCH 069/297] Changed some variable names for consistency --- wdl/LRJointCallGVCFs.wdl | 6 +----- wdl/tasks/Hail.wdl | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/wdl/LRJointCallGVCFs.wdl b/wdl/LRJointCallGVCFs.wdl index 44c2fd180..72b663f57 100644 --- a/wdl/LRJointCallGVCFs.wdl +++ b/wdl/LRJointCallGVCFs.wdl @@ -34,17 +34,13 @@ workflow LRJointCallGVCFs { gvcf = JointCall.joint_gvcf, tbi = JointCall.joint_gvcf_tbi, prefix = prefix, - finalize_to_dir = outdir + outdir = outdir } # Finalize call FF.FinalizeToFile as FinalizeGVCF { input: outdir = outdir, file = JointCall.joint_gvcf } call FF.FinalizeToFile as FinalizeTBI { input: outdir = outdir, file = JointCall.joint_gvcf_tbi } -# if (defined(ConvertToHailMT.joint_mt_tar_gz)) { -# call FF.FinalizeToFile as FinalizeMT { input: outdir = outdir, file = select_first([ConvertToHailMT.joint_mt_tar_gz]) } -# } - ########## # store the results into designated bucket ########## diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index 29b0729ee..8ff0f75fa 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -10,7 +10,7 @@ task ConvertToHailMT { String reference = "GRCh38" String prefix = "out" - String finalize_to_dir + String outdir RuntimeAttr? runtime_attr_override } @@ -30,11 +30,11 @@ task ConvertToHailMT { EOF - gsutil -m rsync -Cr ~{prefix}.mt ~{finalize_to_dir}/~{prefix}.mt + gsutil -m rsync -Cr ~{prefix}.mt ~{outdir}/~{prefix}.mt >>> output { - String gcs_path = "~{finalize_to_dir}/~{prefix}.mt" + String gcs_path = "~{outdir}/~{prefix}.mt" } ######################### From 7cae1ec0aea72e5d59eff91a562d32d36b0ea548 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 17 Dec 2022 12:48:01 -0500 Subject: [PATCH 070/297] Temporary workflow to convert an existing GVCF to a Hail MatrixTable --- .dockstore.yml | 4 ++++ wdl/ConvertToHailMT.wdl | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 wdl/ConvertToHailMT.wdl diff --git a/.dockstore.yml b/.dockstore.yml index 76f24a7ca..e2ab2ec35 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -112,6 +112,10 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/LRJointCallGVCFs.wdl testParameterFiles: +- name: ConvertToHailMT + subclass: wdl + primaryDescriptorPath: /wdl/ConvertToHailMT.wdl + testParameterFiles: - name: SRBamToFq subclass: wdl primaryDescriptorPath: /wdl/SRBamToFq.wdl diff --git a/wdl/ConvertToHailMT.wdl b/wdl/ConvertToHailMT.wdl new file mode 100644 index 000000000..f1e086319 --- /dev/null +++ b/wdl/ConvertToHailMT.wdl @@ -0,0 +1,38 @@ +version 1.0 + +############################################################################################ +## A workflow that performs joint calling on gVCFs (usually from DeepVariant) using GLNexus. +############################################################################################ + +import "tasks/GLNexus.wdl" as GLNexus +import "tasks/Hail.wdl" as Hail +import "tasks/Finalize.wdl" as FF + +workflow ConvertToHailMT { + input { + File joint_gvcf + File joint_gvcf_tbi + String prefix + + String gcs_out_root_dir + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/JointCallGVCFs/~{prefix}" + + # Gather across multiple input gVCFs + call Hail.ConvertToHailMT { + input: + gvcf = joint_gvcf, + tbi = joint_gvcf_tbi, + prefix = prefix, + outdir = outdir + } + + ########## + # store the results into designated bucket + ########## + + output { + String joint_mt = ConvertToHailMT.gcs_path + } +} From 1b4e0680e45f746dd3642dede690f796dc407b87 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 17 Dec 2022 18:14:15 -0500 Subject: [PATCH 071/297] Allocate more disk space --- wdl/tasks/Hail.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index 8ff0f75fa..b7bcee788 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -15,7 +15,7 @@ task ConvertToHailMT { RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 2*ceil(size(gvcf, "GB")) + Int disk_size = 1 + 5*ceil(size(gvcf, "GB")) command <<< set -x From 55f30ba4e8ed47cd6fa7012a2b4aab954dc72d60 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 17 Dec 2022 20:05:56 -0500 Subject: [PATCH 072/297] Allow optional specification of a reference sequence for Hail --- wdl/tasks/Hail.wdl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index b7bcee788..8dca89c69 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -8,6 +8,8 @@ task ConvertToHailMT { File tbi String reference = "GRCh38" + String? ref_fasta + String? ref_fai String prefix = "out" String outdir @@ -23,7 +25,11 @@ task ConvertToHailMT { python3 < Date: Sat, 17 Dec 2022 22:18:46 -0500 Subject: [PATCH 073/297] Debugging --- wdl/tasks/Hail.wdl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index 8dca89c69..9cb814be3 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -26,10 +26,13 @@ task ConvertToHailMT { import hail as hl + print('~{defined(ref_fasta)}') + if '~{defined(ref_fasta)}' == 'true': ref = hl.ReferenceGenome.from_fasta_file('~{reference}', '~{ref_fasta}', '~{ref_fai}') + print("Hello!") else: - hl.init(default_reference='~{reference}', idempotent=True) + hl.init(default_reference='~{reference}') callset = hl.import_vcf('~{gvcf}', array_elements_required=False) callset.write('~{prefix}.mt', overwrite=True) From 20e5d35ab94a8bc7bb1989716fcc4070455ef34d Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 17 Dec 2022 22:47:11 -0500 Subject: [PATCH 074/297] More debugging --- wdl/tasks/Hail.wdl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index 9cb814be3..ee0647c28 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -30,9 +30,8 @@ task ConvertToHailMT { if '~{defined(ref_fasta)}' == 'true': ref = hl.ReferenceGenome.from_fasta_file('~{reference}', '~{ref_fasta}', '~{ref_fai}') - print("Hello!") - else: - hl.init(default_reference='~{reference}') + + hl.init(default_reference='~{reference}') callset = hl.import_vcf('~{gvcf}', array_elements_required=False) callset.write('~{prefix}.mt', overwrite=True) From e7fde6ea2b9984e60354a78c1e84fa1130ca8c93 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 17 Dec 2022 22:53:40 -0500 Subject: [PATCH 075/297] Forgot to specify a disk size properly --- wdl/tasks/Hail.wdl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index ee0647c28..c98659a28 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -17,7 +17,7 @@ task ConvertToHailMT { RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 5*ceil(size(gvcf, "GB")) + Int disk_size = 1 + 3*ceil(size(gvcf, "GB")) command <<< set -x @@ -26,8 +26,6 @@ task ConvertToHailMT { import hail as hl - print('~{defined(ref_fasta)}') - if '~{defined(ref_fasta)}' == 'true': ref = hl.ReferenceGenome.from_fasta_file('~{reference}', '~{ref_fasta}', '~{ref_fai}') @@ -49,7 +47,7 @@ task ConvertToHailMT { RuntimeAttr default_attr = object { cpu_cores: 4, mem_gb: 64, - disk_gb: 10, + disk_gb: disk_size, boot_disk_gb: 10, preemptible_tries: 0, max_retries: 0, From c1b34ce7983aa7515c73596c2634988564e0c86b Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 17 Dec 2022 23:31:03 -0500 Subject: [PATCH 076/297] Custom reference sequence usage needs to be specified in the hl.import_vcf call --- wdl/tasks/Hail.wdl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index c98659a28..2690f7809 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -25,14 +25,19 @@ task ConvertToHailMT { python3 < Date: Fri, 23 Dec 2022 02:02:38 -0500 Subject: [PATCH 077/297] Try to resolve file corruption issue by separating the joint calling and conversion to .g.vcf.bgz steps --- wdl/tasks/GLNexus.wdl | 156 +++++++++++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 54 deletions(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 45831679b..6ff1b22f1 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -9,56 +9,61 @@ version 1.0 import "Utils.wdl" import "VariantUtils.wdl" -#workflow JointCall { -# input { -# Array[File] gvcfs -# File? bed -# -# String config = "DeepVariantWGS" -# Boolean more_PL = false -# Boolean squeeze = false -# Boolean trim_uncalled_alleles = false -# -# Int num_cpus = 96 -# String prefix = "out" -# -# RuntimeAttr? runtime_attr_override -# } -# -# parameter_meta { -# gvcfs: "gVCF files to perform joint calling upon" -# bed: "three-column BED file with ranges to analyze (if not specified, use full length of all contigs)" -# -# config: "configuration preset name or .yml filename" -# more_PL: "include PL from reference bands and other cases omitted by default" -# squeeze: "reduce pVCF size by suppressing detail in cells derived from reference bands" -# trim_uncalled_alleles: "remove alleles with no output GT calls in postprocessing" -# -# num_cpus: "number of CPUs to use" -# prefix: "output prefix for joined-called BCF and GVCF files" -# } -# -# call Call { -# input: -# gvcfs = gvcfs, -# bed = bed, -# -# config = config, -# more_PL = more_PL, -# squeeze = squeeze, -# trim_uncalled_alleles = trim_uncalled_alleles, -# -# num_cpus = num_cpus, -# prefix = prefix -# } -# -# output { -# File joint_gvcf = Call.joint_gvcf -# File joint_gvcf_tbi = Call.joint_gvcf_tbi -# } -#} - -task JointCall { +workflow JointCall { + input { + Array[File] gvcfs + File? bed + + String config = "DeepVariantWGS" + Boolean more_PL = false + Boolean squeeze = false + Boolean trim_uncalled_alleles = false + + Int num_cpus = 96 + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + gvcfs: "gVCF files to perform joint calling upon" + bed: "three-column BED file with ranges to analyze (if not specified, use full length of all contigs)" + + config: "configuration preset name or .yml filename" + more_PL: "include PL from reference bands and other cases omitted by default" + squeeze: "reduce pVCF size by suppressing detail in cells derived from reference bands" + trim_uncalled_alleles: "remove alleles with no output GT calls in postprocessing" + + num_cpus: "number of CPUs to use" + prefix: "output prefix for joined-called BCF and GVCF files" + } + + call Call { + input: + gvcfs = gvcfs, + bed = bed, + + config = config, + more_PL = more_PL, + squeeze = squeeze, + trim_uncalled_alleles = trim_uncalled_alleles, + + num_cpus = num_cpus, + prefix = prefix + } + + call CompressAndIndex { + input: + joint_bcf = Call.joint_bcf + } + + output { + File joint_gvcf = CompressAndIndex.joint_gvcf + File joint_gvcf_tbi = CompressAndIndex.joint_gvcf_tbi + } +} + +task Call { input { Array[File] gvcfs File? bed @@ -74,7 +79,7 @@ task JointCall { RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 3*ceil(size(gvcfs, "GB")) + Int disk_size = 1 + 5*ceil(size(gvcfs, "GB")) command <<< set -x @@ -90,8 +95,51 @@ task JointCall { ~{if trim_uncalled_alleles then "--trim-uncalled-alleles" else ""} \ ~{if defined(bed) then "--bed ~{select_first([bed])}" else ""} \ --list ~{write_lines(gvcfs)} \ - | bcftools view | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz + > ~{prefix}.bcf + >>> + + output { + File joint_bcf = "~{prefix}.bcf" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: num_cpus, + mem_gb: 4*num_cpus, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 0, + max_retries: 0, + docker: "ghcr.io/dnanexus-rnd/glnexus:v1.4.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task CompressAndIndex { + input { + File joint_bcf + + Int num_cpus = 8 + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 3*ceil(size(joint_bcf, "GB")) + + command <<< + set -x + bcftools view ~{joint_bcf} | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz tabix -p vcf ~{prefix}.g.vcf.bgz >>> @@ -104,7 +152,7 @@ task JointCall { RuntimeAttr default_attr = object { cpu_cores: num_cpus, mem_gb: 4*num_cpus, - disk_gb: 0, # ignored + disk_gb: disk_size, boot_disk_gb: 10, preemptible_tries: 0, max_retries: 0, @@ -114,7 +162,7 @@ task JointCall { runtime { cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" - disks: "local-disk ~{disk_size} SSD, /mnt/tmp ~{disk_size} SSD" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) From dba9b5c1295250948d451e79b5d98b1405db417e Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Fri, 23 Dec 2022 02:37:35 -0500 Subject: [PATCH 078/297] Fixed bad temp dir specification --- wdl/tasks/GLNexus.wdl | 1 - 1 file changed, 1 deletion(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 6ff1b22f1..6b69b1484 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -88,7 +88,6 @@ task Call { ulimit -Sn 65536 glnexus_cli \ - --dir /mnt/tmp/GLnexus.DB \ --config ~{config} \ ~{if more_PL then "--more-PL" else ""} \ ~{if squeeze then "--squeeze" else ""} \ From c6fd5040e7f5fe5f3837148e59da0a18c68d5b57 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 24 Dec 2022 22:34:20 -0500 Subject: [PATCH 079/297] A contig-parallelized joint-calling workflow --- wdl/LRJointCallGVCFs.wdl | 19 +++- wdl/tasks/GLNexus.wdl | 213 +++++++++++++++++++++++++++++++++++---- 2 files changed, 207 insertions(+), 25 deletions(-) diff --git a/wdl/LRJointCallGVCFs.wdl b/wdl/LRJointCallGVCFs.wdl index 72b663f57..143f4c2ce 100644 --- a/wdl/LRJointCallGVCFs.wdl +++ b/wdl/LRJointCallGVCFs.wdl @@ -11,23 +11,34 @@ import "tasks/Finalize.wdl" as FF workflow LRJointCallGVCFs { input { Array[File] gvcfs - File? bed + Array[File] tbis + File ref_map_file + String prefix String gcs_out_root_dir } parameter_meta { - gvcfs: "GCS path to aligned BAM files" - bed: "three-column BED file with ranges to analyze" + gvcfs: "GCS paths to gVCF files" + tbis: "GCS paths to gVCF tbi files" + ref_map_file: "table indicating reference sequence and auxillary file locations" prefix: "prefix for output joint-called gVCF and tabix index" gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" } String outdir = sub(gcs_out_root_dir, "/$", "") + "/JointCallGVCFs/~{prefix}" + Map[String, String] ref_map = read_map(ref_map_file) + # Gather across multiple input gVCFs - call GLNexus.JointCall { input: gvcfs = gvcfs, bed = bed, prefix = prefix } + call GLNexus.JointCall { + input: + gvcfs = gvcfs, + tbis = tbis, + dict = ref_map['dict'], + prefix = prefix + } call Hail.ConvertToHailMT { input: diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 6b69b1484..b773b5c3a 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -12,6 +12,9 @@ import "VariantUtils.wdl" workflow JointCall { input { Array[File] gvcfs + Array[File] tbis + + File dict File? bed String config = "DeepVariantWGS" @@ -19,7 +22,7 @@ workflow JointCall { Boolean squeeze = false Boolean trim_uncalled_alleles = false - Int num_cpus = 96 + Int? num_cpus String prefix = "out" RuntimeAttr? runtime_attr_override @@ -27,7 +30,9 @@ workflow JointCall { parameter_meta { gvcfs: "gVCF files to perform joint calling upon" - bed: "three-column BED file with ranges to analyze (if not specified, use full length of all contigs)" + tbis: "gVCF index files" + dict: "reference sequence dictionary" + bed: "intervals to which joint calling should be restricted" config: "configuration preset name or .yml filename" more_PL: "include PL from reference bands and other cases omitted by default" @@ -38,48 +43,166 @@ workflow JointCall { prefix: "output prefix for joined-called BCF and GVCF files" } - call Call { - input: - gvcfs = gvcfs, - bed = bed, + Int cpus_exp = if defined(num_cpus) then num_cpus else 2*length(gvcfs) + Int cpus_act = if cpus_exp < 96 then cpus_exp else 96 - config = config, - more_PL = more_PL, - squeeze = squeeze, - trim_uncalled_alleles = trim_uncalled_alleles, + # List all of the contigs in the reference + call GetRanges { input: dict = dict, bed = bed } - num_cpus = num_cpus, - prefix = prefix + # Shard all gVCFs into per-contig shards + scatter (p in zip(gvcfs, tbis)) { + call ShardVCFByRanges { input: gvcf = p.left, tbi = p.right, ranges = GetRanges.ranges } } - call CompressAndIndex { - input: - joint_bcf = Call.joint_bcf + # Joint-call in parallel over chromosomes + scatter (i in range(length(ShardVCFByRanges.sharded_gvcfs[0]))) { + Array[File] per_contig_gvcfs = transpose(ShardVCFByRanges.sharded_gvcfs)[i] + + call Call { + input: + gvcfs = per_contig_gvcfs, + + config = config, + more_PL = more_PL, + squeeze = squeeze, + trim_uncalled_alleles = trim_uncalled_alleles, + + num_cpus = cpus_act, + prefix = prefix + } } + # Concatenate the contig-sharded joint calls into a single joint callset + call ConcatBCFs { input: bcfs = Call.joint_bcf, prefix = prefix } + output { - File joint_gvcf = CompressAndIndex.joint_gvcf - File joint_gvcf_tbi = CompressAndIndex.joint_gvcf_tbi + File joint_gvcf = ConcatBCFs.joint_gvcf + File joint_gvcf_tbi = ConcatBCFs.joint_gvcf_tbi + } +} + +task GetRanges { + input { + File dict + File? bed + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + ceil(size(dict, "GB")) + + command <<< + set -euxo pipefail + + if [[ "~{defined(bed)}" == "true" ]]; then + cat ~{bed} | awk '{ print $1 ":" $2 "-" $3 }' > ranges.txt + else + grep '^@SQ' ~{dict} | \ + awk '{ print $2, $3 }' | \ + sed 's/[SL]N://g' | \ + awk '{ print $1 ":0-" $2 }' \ + > ranges.txt + fi + >>> + + output { + Array[String] ranges = read_lines("ranges.txt") + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 1, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 0, + docker: "ghcr.io/dnanexus-rnd/glnexus:v1.4.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task ShardVCFByRanges { + input { + File gvcf + File tbi + Array[String] ranges + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + ceil(size(gvcf, "GB")) + + command <<< + set -euxo pipefail + + mkdir per_contig + + INDEX=0 + for RANGE in ~{sep=' ' ranges} + do + PINDEX=$(printf "%06d" $INDEX) + FRANGE=$(echo $RANGE | sed 's/[:-]/___/g') + OUTFILE="per_contig/$PINDEX.~{basename(gvcf, ".g.vcf.gz")}.locus_$FRANGE.g.vcf.gz" + + bcftools view ~{gvcf} $RANGE | bgzip > $OUTFILE + + INDEX=$(($INDEX+1)) + done + >>> + + output { + Array[File] sharded_gvcfs = glob("per_contig/*") + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 1, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 0, + docker: "ghcr.io/dnanexus-rnd/glnexus:v1.4.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) } } task Call { input { Array[File] gvcfs - File? bed String config = "DeepVariantWGS" Boolean more_PL = false Boolean squeeze = false Boolean trim_uncalled_alleles = false - Int num_cpus = 32 + Int num_cpus = 96 String prefix = "out" RuntimeAttr? runtime_attr_override } Int disk_size = 1 + 5*ceil(size(gvcfs, "GB")) + Int mem = 4*num_cpus command <<< set -x @@ -87,12 +210,14 @@ task Call { # For guidance on performance settings, see https://github.com/dnanexus-rnd/GLnexus/wiki/Performance ulimit -Sn 65536 + echo ~{gvcfs[0]} | sed 's/.*locus_//' | sed 's/.g.vcf.bgz//' | sed 's/___/\t/g' > range.bed + glnexus_cli \ --config ~{config} \ + --bed range.bed \ ~{if more_PL then "--more-PL" else ""} \ ~{if squeeze then "--squeeze" else ""} \ ~{if trim_uncalled_alleles then "--trim-uncalled-alleles" else ""} \ - ~{if defined(bed) then "--bed ~{select_first([bed])}" else ""} \ --list ~{write_lines(gvcfs)} \ > ~{prefix}.bcf >>> @@ -104,7 +229,7 @@ task Call { ######################### RuntimeAttr default_attr = object { cpu_cores: num_cpus, - mem_gb: 4*num_cpus, + mem_gb: mem, disk_gb: disk_size, boot_disk_gb: 10, preemptible_tries: 0, @@ -168,3 +293,49 @@ task CompressAndIndex { docker: select_first([runtime_attr.docker, default_attr.docker]) } } + +task ConcatBCFs { + input { + Array[File] bcfs + + Int num_cpus = 4 + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 2*ceil(size(bcfs, "GB")) + + command <<< + set -euxo pipefail + + bcftools concat -n ~{sep=' ' bcfs} | bcftools view | bgzip -@ ~{num_cpus} -c > ~{prefix}.g.vcf.bgz + tabix -p vcf ~{prefix}.g.vcf.bgz + >>> + + output { + File joint_gvcf = "~{prefix}.g.vcf.bgz" + File joint_gvcf_tbi = "~{prefix}.g.vcf.bgz.tbi" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: num_cpus, + mem_gb: 8, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 0, + docker: "ghcr.io/dnanexus-rnd/glnexus:v1.4.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From 311886ad71a8f9ff0ce18ff6fce33ced312ebc3a Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 24 Dec 2022 22:36:39 -0500 Subject: [PATCH 080/297] Forgot to surround optional num_cpus in select_first() --- wdl/tasks/GLNexus.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index b773b5c3a..f7f584214 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -43,7 +43,7 @@ workflow JointCall { prefix: "output prefix for joined-called BCF and GVCF files" } - Int cpus_exp = if defined(num_cpus) then num_cpus else 2*length(gvcfs) + Int cpus_exp = if defined(num_cpus) then select_first([num_cpus]) else 2*length(gvcfs) Int cpus_act = if cpus_exp < 96 then cpus_exp else 96 # List all of the contigs in the reference From 8b652d1d2546c03ec203e4ddb803096f8b7f8ec3 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 24 Dec 2022 23:35:18 -0500 Subject: [PATCH 081/297] Allow max cpus to be configurable --- wdl/tasks/GLNexus.wdl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index f7f584214..1641193ef 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -23,6 +23,7 @@ workflow JointCall { Boolean trim_uncalled_alleles = false Int? num_cpus + Int max_cpus = 64 String prefix = "out" RuntimeAttr? runtime_attr_override @@ -40,11 +41,12 @@ workflow JointCall { trim_uncalled_alleles: "remove alleles with no output GT calls in postprocessing" num_cpus: "number of CPUs to use" + max_cpus: "maximum number of CPUs to allow" prefix: "output prefix for joined-called BCF and GVCF files" } Int cpus_exp = if defined(num_cpus) then select_first([num_cpus]) else 2*length(gvcfs) - Int cpus_act = if cpus_exp < 96 then cpus_exp else 96 + Int cpus_act = if cpus_exp < max_cpus then cpus_exp else max_cpus # List all of the contigs in the reference call GetRanges { input: dict = dict, bed = bed } From 6456f1d79877bab9611ec3ec1963e337c42d189e Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sun, 25 Dec 2022 03:18:20 -0500 Subject: [PATCH 082/297] Increase disk size request --- wdl/tasks/GLNexus.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 1641193ef..92cd08cbd 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -142,7 +142,7 @@ task ShardVCFByRanges { RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + ceil(size(gvcf, "GB")) + Int disk_size = 1 + 2*ceil(size(gvcf, "GB")) command <<< set -euxo pipefail From 277ab6e9f915a025c84bf2aca6b27f5a34a3c411 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Fri, 30 Dec 2022 19:28:10 -0500 Subject: [PATCH 083/297] Temporary workflow to handle some issues arising from the LRJointCallGVCFs workflow --- .dockstore.yml | 4 ++++ wdl/LRConvertBCFs.wdl | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 wdl/LRConvertBCFs.wdl diff --git a/.dockstore.yml b/.dockstore.yml index e2ab2ec35..14aecec28 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -116,6 +116,10 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/ConvertToHailMT.wdl testParameterFiles: +- name: LRConvertBCF + subclass: wdl + primaryDescriptorPath: /wdl/LRConvertBCF.wdl + testParameterFiles: - name: SRBamToFq subclass: wdl primaryDescriptorPath: /wdl/SRBamToFq.wdl diff --git a/wdl/LRConvertBCFs.wdl b/wdl/LRConvertBCFs.wdl new file mode 100644 index 000000000..6eed49e9e --- /dev/null +++ b/wdl/LRConvertBCFs.wdl @@ -0,0 +1,46 @@ +version 1.0 + +############################################################################################ +## A workflow that converts a BCF file into a .vcf.gz file. Meant to temporarily handle some +## transient issues stemming from the LRJointCallGVCFs workflow. Should be removed eventually. +############################################################################################ + +import "tasks/GLNexus.wdl" as GLNexus +import "tasks/Hail.wdl" as Hail +import "tasks/Finalize.wdl" as FF + +workflow LRConvertBCF { + input { + File joint_bcf + String prefix + + String gcs_out_root_dir + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/JointCallGVCFs/~{prefix}" + + # Convert the joint .bcf callset into a single joint .vcf.bgz callset + call GLNexus.ConcatBCFs { input: bcfs = [ joint_bcf ], prefix = prefix } + + call Hail.ConvertToHailMT { + input: + gvcf = ConcatBCFs.joint_gvcf, + tbi = ConcatBCFs.joint_gvcf_tbi, + prefix = prefix, + outdir = outdir + } + + # Finalize + call FF.FinalizeToFile as FinalizeGVCF { input: outdir = outdir, file = ConcatBCFs.joint_gvcf } + call FF.FinalizeToFile as FinalizeTBI { input: outdir = outdir, file = ConcatBCFs.joint_gvcf_tbi } + + ########## + # store the results into designated bucket + ########## + + output { + File joint_gvcf = FinalizeGVCF.gcs_path + File joint_gvcf_tbi = FinalizeTBI.gcs_path + String joint_mt = ConvertToHailMT.gcs_path + } +} From 57b5c94ce634c1c0d01fa4735eddd4e0c32e8b36 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Fri, 30 Dec 2022 20:11:36 -0500 Subject: [PATCH 084/297] Fixed a typo --- wdl/{LRConvertBCFs.wdl => LRConvertBCF.wdl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename wdl/{LRConvertBCFs.wdl => LRConvertBCF.wdl} (100%) diff --git a/wdl/LRConvertBCFs.wdl b/wdl/LRConvertBCF.wdl similarity index 100% rename from wdl/LRConvertBCFs.wdl rename to wdl/LRConvertBCF.wdl From d70f12ab254819ec3e6bcc3bde5c06c2c5977d03 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 14 Jan 2023 13:24:12 -0500 Subject: [PATCH 085/297] Added documentation --- wdl/ConvertToHailMT.wdl | 7 +++++++ wdl/tasks/GLNexus.wdl | 20 ++++++++++++++++++++ wdl/tasks/Hail.wdl | 4 ++++ 3 files changed, 31 insertions(+) diff --git a/wdl/ConvertToHailMT.wdl b/wdl/ConvertToHailMT.wdl index f1e086319..1304d35e5 100644 --- a/wdl/ConvertToHailMT.wdl +++ b/wdl/ConvertToHailMT.wdl @@ -17,6 +17,13 @@ workflow ConvertToHailMT { String gcs_out_root_dir } + parameter_meta { + joint_gvcf: "joint-called gVCF file" + joint_gvcf_tbi: ".tbi index for joint-called gVCF file" + prefix: "prefix for output Hail MatrixTable" + gcs_out_root_dir: "GCS bucket in which to store the Hail MatrixTable" + } + String outdir = sub(gcs_out_root_dir, "/$", "") + "/JointCallGVCFs/~{prefix}" # Gather across multiple input gVCFs diff --git a/wdl/tasks/GLNexus.wdl b/wdl/tasks/GLNexus.wdl index 92cd08cbd..31d61a90e 100644 --- a/wdl/tasks/GLNexus.wdl +++ b/wdl/tasks/GLNexus.wdl @@ -84,6 +84,10 @@ workflow JointCall { } task GetRanges { + meta { + description: "Select loci over which to parallelize downstream operations." + } + input { File dict File? bed @@ -134,6 +138,10 @@ task GetRanges { } task ShardVCFByRanges { + meta { + description: "Split VCF into smaller ranges for parallelization." + } + input { File gvcf File tbi @@ -189,6 +197,10 @@ task ShardVCFByRanges { } task Call { + meta { + description: "Joint-call gVCFs with GLNexus." + } + input { Array[File] gvcfs @@ -251,6 +263,10 @@ task Call { } task CompressAndIndex { + meta { + description: "Convert a BCF file to a vcf.bgz file and index it." + } + input { File joint_bcf @@ -297,6 +313,10 @@ task CompressAndIndex { } task ConcatBCFs { + meta { + description: "Concatenate BCFs into a single .vcf.bgz file and index it." + } + input { Array[File] bcfs diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index 2690f7809..b1ca0d74c 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -3,6 +3,10 @@ version 1.0 import "Structs.wdl" task ConvertToHailMT { + meta { + description: "Convert a .vcf.bgz file to a Hail MatrixTable and copy it to a final gs:// URL." + } + input { File gvcf File tbi From 79836bea3f6bcbaec5bda3a947c1f310a00c8e85 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 14 Jan 2023 20:24:22 +0000 Subject: [PATCH 086/297] Autobump version 3.0.58 --> 3.0.59 --- README.md | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f441efb2d..8f517bd6f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Generic badge](https://img.shields.io/badge/version-3.0.58-blue.svg)](https://shields.io/) +[![Generic badge](https://img.shields.io/badge/version-3.0.59-blue.svg)](https://shields.io/) ![CI/CD](https://github.com/broadinstitute/long-read-pipelines/workflows/CI/CD/badge.svg) ![Nightly](https://github.com/broadinstitute/long-read-pipelines/workflows/Nightly/badge.svg) diff --git a/VERSION b/VERSION index fcd9a1136..43d663807 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -version=3.0.58 +version=3.0.59 From 22b249d530b69816d3edec77302aa60bd6c133c0 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Wed, 14 Dec 2022 17:36:58 -0500 Subject: [PATCH 087/297] Added workflow to convert a BAM file to fastq files --- .dockstore.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.dockstore.yml b/.dockstore.yml index 14aecec28..f4a20a077 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -108,6 +108,10 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/PBMASIsoSeqDemultiplex.wdl testParameterFiles: +- name: SRBamToFq + subclass: wdl + primaryDescriptorPath: /wdl/SRBamToFq.wdl + testParameterFiles: - name: LRJointCallGVCFs subclass: wdl primaryDescriptorPath: /wdl/LRJointCallGVCFs.wdl From f884211262938bae432d5793c3be4c8aba99fa44 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 14 Dec 2022 21:49:56 -0500 Subject: [PATCH 088/297] Many updates for short read Malaria processing. - Added SRFlowcell.wdl, SRWholeGenome.wdl - Added SRJointGenotyping.wdl, LRJointCallGVCFsWithGenomicsDB.wdl - Updated `sr-utils` docker image - Workflow to index a BAM file - Whole genome processing pipeline for short read data - Added short read whole genome pipeline to Dockstore - Added start to workflows to do GCNV calling. - Added LRJointCallGVCFsWithGenomicsDB to dockstore. - Removed downstream calls to `nanoplot` in SRWholeGenome - Added hail matrix table output to LRJointCallGVCFsWithGenomicsDB --- .dockstore.yml | 16 ++ wdl/LRJointCallGVCFsWithGenomicsDB.wdl | 230 +++++++++++++++++++++++++ wdl/SRCallGermlineCNVs.wdl | 40 +++++ wdl/SRWholeGenome.wdl | 64 ++++--- wdl/tasks/SRGermlineCNVs.wdl | 6 + 5 files changed, 336 insertions(+), 20 deletions(-) create mode 100644 wdl/LRJointCallGVCFsWithGenomicsDB.wdl create mode 100644 wdl/SRCallGermlineCNVs.wdl create mode 100644 wdl/tasks/SRGermlineCNVs.wdl diff --git a/.dockstore.yml b/.dockstore.yml index f4a20a077..82cee3ddd 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -112,6 +112,22 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/SRBamToFq.wdl testParameterFiles: +- name: SRIndexBam + subclass: wdl + primaryDescriptorPath: /wdl/SRIndexBam.wdl + testParameterFiles: +- name: SRWholeGenome + subclass: wdl + primaryDescriptorPath: /wdl/SRWholeGenome.wdl + testParameterFiles: +- name: SRFlowcell + subclass: wdl + primaryDescriptorPath: /wdl/SRFlowcell.wdl + testParameterFiles: +- name: LRJointCallGVCFsWithGenomicsDB + subclass: wdl + primaryDescriptorPath: /wdl/LRJointCallGVCFsWithGenomicsDB.wdl + testParameterFiles: - name: LRJointCallGVCFs subclass: wdl primaryDescriptorPath: /wdl/LRJointCallGVCFs.wdl diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl new file mode 100644 index 000000000..cc0ceb139 --- /dev/null +++ b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl @@ -0,0 +1,230 @@ +version 1.0 + +############################################################################################################# +## A workflow that performs joint calling on single-sample gVCFs from GATK4 HaplotypeCaller using GenomicsDB. +############################################################################################################# + +import "tasks/SRJointGenotyping.wdl" as SRJOINT +import "tasks/VariantUtils.wdl" as VARUTIL +import "tasks/Hail.wdl" as Hail +import "tasks/Finalize.wdl" as FF + +workflow LRJointCallGVCFsWithGenomicsDB { + input { + Array[File] gvcfs + Array[File] gvcf_indices + + File ref_map_file + + File interval_list + + Float snp_filter_level = 99.7 + Array[String] snp_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] + Array[Float] snp_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.8, 99.6, 99.5, 99.4, 99.3, 99.0, 98.0, 97.0, 90.0 ] + + Float indel_filter_level = 99.0 + Array[String] indel_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] + Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] + + Array[File]? annotation_bed_files + Array[File]? annotation_bed_file_indexes + Array[String]? annotation_bed_file_annotation_names + + String prefix + + String gcs_out_root_dir + } + + parameter_meta { + gvcfs: "GCS paths to gVCF files" + gvcf_indices: "GCS paths to gVCF tbi files" + ref_map_file: "table indicating reference sequence and auxillary file locations" + prefix: "prefix for output joint-called gVCF and tabix index" + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/LRJointCallGVCFsWithGenomicsDB/~{prefix}" + + Map[String, String] ref_map = read_map(ref_map_file) + + # From WARP: + # For small callsets (fewer than 1000 samples) we can gather the VCF shards and collect metrics directly. + # For anything larger, we need to keep the VCF sharded and gather metrics collected from them. + # We allow overriding this default behavior for testing / special requests. + Boolean is_small_callset = length(gvcfs) <= 1000 + + # Create sample-name map: + call SRJOINT.CreateSampleNameMap as CreateSampleNameMap { + input: + gvcfs = gvcfs, + prefix = prefix + } + + # Import our data into GenomicsDB: + call SRJOINT.ImportGVCFs as ImportGVCFsIntoGenomicsDB { + input: + sample_name_map = CreateSampleNameMap.sample_name_map, + interval_list = interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix, + batch_size = 50, + } + + # Joint call + call SRJOINT.GenotypeGVCFs as JointCallGVCFs { + input: + input_gvcf_data = ImportGVCFsIntoGenomicsDB.output_genomicsdb, + interval_list = interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + dbsnp_vcf = ref_map["known_sites_vcf"], + prefix = prefix, + } + + # First make a sites-only VCF for recal (smaller file, easier to work with): + call VARUTIL.MakeSitesOnlyVcf as MakeSitesOnlyGVCF { + input: + vcf = JointCallGVCFs.output_vcf, + vcf_index = JointCallGVCFs.output_vcf_index, + prefix = prefix + } + + # Now we run VariantRecalibrator for indels and snps: + call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { + input: + vcf = MakeSitesOnlyGVCF.sites_only_vcf, + vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, + prefix = prefix + ".indels", + recalibration_tranche_values = snp_recalibration_tranche_values, + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = [ref_map["known_sites_vcf"]], + known_reference_variants_index = [ref_map["known_sites_index"]], + known_reference_variants_identifier = ["pfcrosses"], + is_known = [true], + is_training = [true], + is_truth = [true], + prior = [15], + use_allele_specific_annotations = true, + max_gaussians = 8, + } + + call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { + input: + vcf = MakeSitesOnlyGVCF.sites_only_vcf, + vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, + prefix = prefix + ".snps", + recalibration_tranche_values = snp_recalibration_tranche_values, + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = [ref_map["known_sites_vcf"]], + known_reference_variants_index = [ref_map["known_sites_index"]], + known_reference_variants_identifier = ["pfcrosses"], + is_known = [true], + is_training = [true], + is_truth = [true], + prior = [15], + use_allele_specific_annotations = true, + max_gaussians = 8, + } + + call VARUTIL.ApplyVqsr as ApplyVqsr { + input: + vcf = JointCallGVCFs.output_vcf, + vcf_index = JointCallGVCFs.output_vcf_index, + + prefix = prefix + ".vqsr_filtered", + + snps_recalibration = TrainVQSROnHCSnpVariants.recalibration, + snps_recalibration_index = TrainVQSROnHCSnpVariants.recalibration_index, + snps_tranches = TrainVQSROnHCSnpVariants.tranches, + snp_filter_level = snp_filter_level, + + indels_recalibration = TrainVQSROnHCIndelVariants.recalibration, + indels_recalibration_index = TrainVQSROnHCIndelVariants.recalibration_index, + indels_tranches = TrainVQSROnHCIndelVariants.tranches, + indel_filter_level = indel_filter_level, + + use_allele_specific_annotations = true, + } + + # Now we need to annotate our variants by region: + if (defined(annotation_bed_files)) { + call VARUTIL.AnnotateVcfWithBedRegions as AnnotateVcfRegions { + input: + vcf = ApplyVqsr.recalibrated_vcf, + vcf_index = ApplyVqsr.recalibrated_vcf_index, + bed_files = select_first([annotation_bed_files]), + bed_file_indexes = select_first([annotation_bed_file_indexes]), + bed_file_annotation_names = select_first([annotation_bed_file_annotation_names]), + prefix = prefix + ".region_annotated" + } + } + + # Finally convert the output to a HAIL Matrix Table: + call Hail.ConvertToHailMT as CreateHailMatrixTable { + input: + gvcf = select_first([AnnotateVcfRegions.annotated_vcf, ApplyVqsr.recalibrated_vcf]), + tbi = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]), + prefix = prefix, + outdir = outdir + } + + # Finalize: + File keyfile = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]) + + call FF.FinalizeToFile as FinalizeGenomicsDB { input: outdir = outdir, keyfile = keyfile, file = ImportGVCFsIntoGenomicsDB.output_genomicsdb } + + call FF.FinalizeToFile as FinalizeRawVCF { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf } + call FF.FinalizeToFile as FinalizeRawTBI { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf_index } + + call FF.FinalizeToFile as FinalizeIndelRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } + call FF.FinalizeToFile as FinalizeIndelRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } + call FF.FinalizeToFile as FinalizeIndelRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } + call FF.FinalizeToFile as FinalizeIndelRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } + + call FF.FinalizeToFile as FinalizeSnpRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } + call FF.FinalizeToFile as FinalizeSnpRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } + call FF.FinalizeToFile as FinalizeSnpRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } + call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } + + call FF.FinalizeToFile as FinalizeVQSRVCF { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } + call FF.FinalizeToFile as FinalizeVQSRTBI { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } + + if (defined(annotation_bed_files)) { + call FF.FinalizeToFile as FinalizeRegionAnnotatedVcf { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf]) } + call FF.FinalizeToFile as FinalizeRegionAnnotatedVcfIndex { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf_index]) } + } + + ########## + # store the results into designated bucket + ########## + + output { + File genomicsDB = FinalizeGenomicsDB.gcs_path + + File raw_joint_vcf = FinalizeRawVCF.gcs_path + File raw_joint_vcf_tbi = FinalizeRawTBI.gcs_path + + File? vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path + File? vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path + File? vqsr_indel_recal_tranches = FinalizeIndelRecalTranches.gcs_path + File? vqsr_indel_recal_model_report = FinalizeIndelRecalModelReport.gcs_path + + File? vqsr_snp_recal_file = FinalizeSnpRecalFile.gcs_path + File? vqsr_snp_recal_file_index = FinalizeSnpRecalIndex.gcs_path + File? vqsr_snp_recal_tranches = FinalizeSnpRecalTranches.gcs_path + File? vqsr_snp_recal_model_report = FinalizeSnpRecalModelReport.gcs_path + + File joint_recalibrated_vcf = FinalizeVQSRVCF.gcs_path + File joint_recalibrated_vcf_tbi = FinalizeVQSRTBI.gcs_path + + File? annotated_joint_vcf = AnnotateVcfRegions.annotated_vcf + File? annotated_joint_vcf_tbi = AnnotateVcfRegions.annotated_vcf_index + + File joint_mt = CreateHailMatrixTable.gcs_path + } +} + + diff --git a/wdl/SRCallGermlineCNVs.wdl b/wdl/SRCallGermlineCNVs.wdl new file mode 100644 index 000000000..7897e10be --- /dev/null +++ b/wdl/SRCallGermlineCNVs.wdl @@ -0,0 +1,40 @@ +version 1.0 + +########################################################################## +## A workflow that performs germline CNV calling using GATK4's GCNV tools. +########################################################################## + +import "tasks/SRGermlineCNVs.wdl" as SRJOINT +import "tasks/Finalize.wdl" as FF + +workflow SRCallGermlineCNVs { + input { + Array[File] gvcfs + Array[File] gvcf_indices + + File ref_map_file + + File interval_list + + String prefix + + String gcs_out_root_dir + } + + parameter_meta { + gvcfs: "GCS paths to gVCF files" + gvcf_indices: "GCS paths to gVCF tbi files" + ref_map_file: "table indicating reference sequence and auxillary file locations" + prefix: "prefix for output joint-called gVCF and tabix index" + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRJointCallGVCFsWithGenomicsDB/~{prefix}" + + Map[String, String] ref_map = read_map(ref_map_file) + + + output { + + } +} diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index d3fa8d666..929f1fd29 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -11,6 +11,8 @@ import "tasks/SRUtils.wdl" as SRUTIL import "tasks/VariantUtils.wdl" as VARUTIL import "tasks/CallVariantsIllumina.wdl" as VAR import "tasks/HaplotypeCaller.wdl" as HC +import "tasks/AlignedMetrics.wdl" as AM +import "tasks/FastQC.wdl" as FastQC import "tasks/Finalize.wdl" as FF import "tasks/SampleLevelAlignedMetrics.wdl" as COV @@ -64,20 +66,40 @@ workflow SRWholeGenome { File bam = select_first([MergeAllReads.merged_bam, aligned_bams[0]]) File bai = select_first([MergeAllReads.merged_bai, aligned_bais[0]]) - call COV.SampleLevelAlignedMetrics as coverage { - input: - aligned_bam = bam, - aligned_bai = bai, - ref_fasta = ref_map['fasta'], - bed_to_compute_coverage = bed_to_compute_coverage + # Collect sample-level metrics: + call AM.SamStatsMap as SamStats { input: bam = bam } + call FastQC.FastQC as FastQC { input: bam = bam, bai = bai } + call Utils.ComputeGenomeLength as ComputeGenomeLength { input: fasta = ref_map['fasta'] } + call SRUTIL.ComputeBamStats as ComputeBamStats { input: bam_file = bam } + + if (defined(bed_to_compute_coverage)) { + call AM.MosDepthOverBed as MosDepth { + input: + bam = bam, + bai = bai, + bed = select_first([bed_to_compute_coverage]) + } + + call COV.SummarizeDepthOverWholeBed as RegionalCoverage { + input: + mosdepth_output = MosDepth.regions + } } - String dir = outdir + "/alignments" + String bam_dir = outdir + "/alignments" + + call FF.FinalizeToFile as FinalizeBam { input: outdir = bam_dir, file = bam, name = "~{participant_name}.bam" } + call FF.FinalizeToFile as FinalizeBai { input: outdir = bam_dir, file = bai, name = "~{participant_name}.bam.bai" } - call FF.FinalizeToFile as FinalizeBam { input: outdir = dir, file = bam, name = "~{participant_name}.bam" } - call FF.FinalizeToFile as FinalizeBai { input: outdir = dir, file = bai, name = "~{participant_name}.bam.bai" } + if (defined(bed_to_compute_coverage)) { call FF.FinalizeToFile as FinalizeRegionalCoverage { input: outdir = bam_dir, file = select_first([RegionalCoverage.cov_summary]) } } + + String metrics_dir = outdir + "/metrics" + call FF.FinalizeToFile as FinalizeFastQCReport { + input: + outdir = metrics_dir, + file = FastQC.report + } - if (defined(bed_to_compute_coverage)) { call FF.FinalizeToFile as FinalizeRegionalCoverage { input: outdir = dir, file = select_first([coverage.bed_cov_summary]) } } #################################################################################################### @@ -236,18 +258,20 @@ workflow SRWholeGenome { File aligned_bam = FinalizeBam.gcs_path File aligned_bai = FinalizeBai.gcs_path - Float aligned_num_reads = coverage.aligned_num_reads - Float aligned_num_bases = coverage.aligned_num_bases - Float aligned_frac_bases = coverage.aligned_frac_bases - Float aligned_est_fold_cov = coverage.aligned_est_fold_cov + Float aligned_num_reads = FastQC.stats_map['number_of_reads'] + Float aligned_num_bases = SamStats.stats_map['bases_mapped'] + Float aligned_frac_bases = SamStats.stats_map['bases_mapped']/SamStats.stats_map['total_length'] + Float aligned_est_fold_cov = SamStats.stats_map['bases_mapped']/ComputeGenomeLength.length + + Float aligned_read_length = FastQC.stats_map['read_length'] + + Float insert_size_average = SamStats.stats_map['insert_size_average'] + Float insert_size_standard_deviation = SamStats.stats_map['insert_size_standard_deviation'] + Float pct_properly_paired_reads = SamStats.stats_map['percentage_of_properly_paired_reads_%'] - Float aligned_read_length_mean = coverage.aligned_read_length_mean - Float aligned_read_length_median = coverage.aligned_read_length_median - Float aligned_read_length_stdev = coverage.aligned_read_length_stdev - Float aligned_read_length_N50 = coverage.aligned_read_length_N50 + Float average_identity = 100.0 - (100.0*SamStats.stats_map['mismatches']/SamStats.stats_map['bases_mapped']) - Float average_identity = coverage.average_identity - Float median_identity = coverage.median_identity + File fastqc_report = FinalizeFastQCReport.gcs_path File? bed_cov_summary = FinalizeRegionalCoverage.gcs_path diff --git a/wdl/tasks/SRGermlineCNVs.wdl b/wdl/tasks/SRGermlineCNVs.wdl new file mode 100644 index 000000000..933879352 --- /dev/null +++ b/wdl/tasks/SRGermlineCNVs.wdl @@ -0,0 +1,6 @@ +version 1.0 + +################################################################################## +## A collection of tasks related to germline CNV calling using GATK4's GCNV tools. +################################################################################## + From 7d8715e1d6f3f97c05ce50a39b027c352f8fe545 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sat, 14 Jan 2023 18:43:14 -0500 Subject: [PATCH 089/297] Fixed genome name in Hail MT --- wdl/LRJointCallGVCFsWithGenomicsDB.wdl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl index cc0ceb139..899162dd4 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl @@ -167,6 +167,9 @@ workflow LRJointCallGVCFsWithGenomicsDB { input: gvcf = select_first([AnnotateVcfRegions.annotated_vcf, ApplyVqsr.recalibrated_vcf]), tbi = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]), + reference = sub(sub(ref_map["fasta"], "^.*/", ""), "\.[fasta]*$", ""), + ref_fasta = ref_map["fasta"], + ref_fai = ref_map["fai"], prefix = prefix, outdir = outdir } From cd978193b633c6a14ca180b4b045e4d55a3891ed Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 14 Jan 2023 21:06:25 -0500 Subject: [PATCH 090/297] Added workflow to create a Zarr store --- docker/lr-sgkit/Dockerfile | 21 ++++++++ docker/lr-sgkit/Makefile | 17 +++++++ docker/lr-sgkit/environment.yml | 86 +++++++++++++++++++++++++++++++++ wdl/ConvertToZarr.wdl | 45 +++++++++++++++++ wdl/tasks/SGKit.wdl | 72 +++++++++++++++++++++++++++ 5 files changed, 241 insertions(+) create mode 100644 docker/lr-sgkit/Dockerfile create mode 100644 docker/lr-sgkit/Makefile create mode 100644 docker/lr-sgkit/environment.yml create mode 100644 wdl/ConvertToZarr.wdl create mode 100644 wdl/tasks/SGKit.wdl diff --git a/docker/lr-sgkit/Dockerfile b/docker/lr-sgkit/Dockerfile new file mode 100644 index 000000000..0913be0b9 --- /dev/null +++ b/docker/lr-sgkit/Dockerfile @@ -0,0 +1,21 @@ +FROM continuumio/miniconda3 + +MAINTAINER Kiran V Garimella + +# copy other resources +COPY ./environment.yml / + +# install conda packages +RUN conda env create -f /environment.yml && conda clean -a +RUN echo "source activate lr-sgkit" > ~/.bashrc +ENV PATH=/opt/conda/envs/lr-sgkit/bin/:/root/google-cloud-sdk/bin/:${PATH} + +# install gsutil +RUN apt-get --allow-releaseinfo-change update +RUN apt install -y curl git-lfs parallel +RUN curl https://sdk.cloud.google.com | bash + +# Setup crcmodc for gsutil: +RUN apt-get install -y gcc python3-dev python3-setuptools && \ + pip3 uninstall -y crcmod && \ + pip3 install --no-cache-dir -U crcmod diff --git a/docker/lr-sgkit/Makefile b/docker/lr-sgkit/Makefile new file mode 100644 index 000000000..3e467f819 --- /dev/null +++ b/docker/lr-sgkit/Makefile @@ -0,0 +1,17 @@ +IMAGE_NAME = lr-sgkit +VERSION = 0.5.0 # This should match the version number at https://github.com/pystatgen/sgkit/releases + +TAG1 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):$(VERSION) +TAG2 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):latest + +all: | build push + +build: + docker build -t $(TAG1) -t $(TAG2) . + +build_no_cache: + docker build --no-cache -t $(TAG1) -t $(TAG2) . + +push: + docker push $(TAG1) + docker push $(TAG2) diff --git a/docker/lr-sgkit/environment.yml b/docker/lr-sgkit/environment.yml new file mode 100644 index 000000000..d5bc55fec --- /dev/null +++ b/docker/lr-sgkit/environment.yml @@ -0,0 +1,86 @@ +name: lr-sgkit +channels: + - conda-forge + - defaults +dependencies: + - _libgcc_mutex=0.1=conda_forge + - _openmp_mutex=4.5=2_gnu + - bzip2=1.0.8=h7f98852_4 + - ca-certificates=2022.12.7=ha878542_0 + - ld_impl_linux-64=2.39=hcc3a1bd_1 + - libffi=3.4.2=h7f98852_5 + - libgcc-ng=12.2.0=h65d4601_19 + - libgomp=12.2.0=h65d4601_19 + - libnsl=2.0.0=h7f98852_0 + - libsqlite=3.40.0=h753d276_0 + - libuuid=2.32.1=h7f98852_1000 + - libzlib=1.2.13=h166bdaf_4 + - ncurses=6.3=h27087fc_1 + - openssl=3.0.7=h0b41bf4_1 + - pip=22.3.1=pyhd8ed1ab_0 + - python=3.9.15=hba424b6_0_cpython + - readline=8.1.2=h0f457ee_0 + - setuptools=65.6.3=pyhd8ed1ab_0 + - tk=8.6.12=h27826a3_0 + - tzdata=2022g=h191b570_0 + - wheel=0.38.4=pyhd8ed1ab_0 + - xz=5.2.6=h166bdaf_0 + - pip: + - aiohttp==3.8.3 + - aiosignal==1.3.1 + - asciitree==0.3.3 + - async-timeout==4.0.2 + - attrs==22.2.0 + - certifi==2022.12.7 + - charset-normalizer==2.1.1 + - click==8.1.3 + - cloudpickle==2.2.0 + - coloredlogs==15.0.1 + - crcmod==1.7 + - cyvcf2==0.30.14 + - dask==2022.1.0 + - dask-glm==0.2.0 + - dask-ml==2022.5.27 + - distributed==2022.1.0 + - entrypoints==0.4 + - fasteners==0.18 + - frozenlist==1.3.3 + - fsspec==2022.11.0 + - heapdict==1.0.1 + - humanfriendly==10.0 + - idna==3.4 + - jinja2==3.1.2 + - joblib==1.2.0 + - llvmlite==0.39.1 + - locket==1.0.0 + - markupsafe==2.1.1 + - msgpack==1.0.4 + - multidict==6.0.4 + - multipledispatch==0.6.0 + - numba==0.56.4 + - numcodecs==0.11.0 + - numpy==1.21.6 + - packaging==23.0 + - pandas==1.3.5 + - partd==1.3.0 + - psutil==5.9.4 + - python-dateutil==2.8.2 + - pytz==2022.7.1 + - pyyaml==6.0 + - requests==2.28.2 + - scikit-learn==1.2.0 + - scipy==1.7.3 + - sgkit==0.5.0 + - six==1.16.0 + - sortedcontainers==2.4.0 + - tblib==1.7.0 + - threadpoolctl==3.1.0 + - toolz==0.12.0 + - tornado==6.2 + - typing-extensions==4.4.0 + - urllib3==1.26.14 + - xarray==2022.12.0 + - yarl==1.8.2 + - zarr==2.10.3 + - zict==2.2.0 +prefix: /opt/conda/envs/lr-sgkit diff --git a/wdl/ConvertToZarr.wdl b/wdl/ConvertToZarr.wdl new file mode 100644 index 000000000..23633fb79 --- /dev/null +++ b/wdl/ConvertToZarr.wdl @@ -0,0 +1,45 @@ +version 1.0 + +################################################################# +## A workflow that converts a joint-called gVCF to a Zarr store. +################################################################# + +import "tasks/GLNexus.wdl" as GLNexus +import "tasks/SGKit.wdl" as SGKit +import "tasks/Finalize.wdl" as FF + +workflow ConvertToZarrStore { + input { + File joint_gvcf + File joint_gvcf_tbi + String prefix + + String gcs_out_root_dir + } + + parameter_meta { + joint_gvcf: "joint-called gVCF file" + joint_gvcf_tbi: ".tbi index for joint-called gVCF file" + prefix: "prefix for output Zarr store" + gcs_out_root_dir: "GCS bucket in which to store the Zarr store" + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/JointCallGVCFs/~{prefix}" + + # Gather across multiple input gVCFs + call SGKit.ConvertToZarrStore { + input: + gvcf = joint_gvcf, + tbi = joint_gvcf_tbi, + prefix = prefix, + outdir = outdir + } + + ########## + # store the results into designated bucket + ########## + + output { + String joint_zarr = ConvertToZarrStore.gcs_path + } +} \ No newline at end of file diff --git a/wdl/tasks/SGKit.wdl b/wdl/tasks/SGKit.wdl new file mode 100644 index 000000000..56a762e70 --- /dev/null +++ b/wdl/tasks/SGKit.wdl @@ -0,0 +1,72 @@ +version 1.0 + +import "Structs.wdl" + +task ConvertToZarrStore { + meta { + description: "Convert a .vcf.bgz file to a Zarr store and copy it to a final gs:// URL." + } + + input { + File gvcf + File tbi + + String reference = "GRCh38" + String? ref_fasta + String? ref_fai + String prefix = "out" + + Int num_cpus = 4 + + String outdir + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 3*ceil(size(gvcf, "GB")) + + command <<< + set -x + + python3 <>> + + output { + String gcs_path = "~{outdir}/~{prefix}.zarr" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: num_cpus, + mem_gb: 16, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 0, + max_retries: 0, + docker: "us.gcr.io/broad-dsp-lrma/lr-sgkit:0.5.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} \ No newline at end of file From 0a78e3c27761252867e55dc974dee578e841c471 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 14 Jan 2023 21:11:26 -0500 Subject: [PATCH 091/297] Register Zarr store workflow in Dockstore --- .dockstore.yml | 4 ++++ wdl/{ConvertToZarr.wdl => ConvertToZarrStore.wdl} | 0 2 files changed, 4 insertions(+) rename wdl/{ConvertToZarr.wdl => ConvertToZarrStore.wdl} (100%) diff --git a/.dockstore.yml b/.dockstore.yml index 82cee3ddd..79fe7ab28 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -156,3 +156,7 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/SRFlowcell.wdl testParameterFiles: +- name: ConvertToZarrStore + subclass: wdl + primaryDescriptorPath: /wdl/ConvertToZarrStore.wdl + testParameterFiles: diff --git a/wdl/ConvertToZarr.wdl b/wdl/ConvertToZarrStore.wdl similarity index 100% rename from wdl/ConvertToZarr.wdl rename to wdl/ConvertToZarrStore.wdl From ec52f5b93e93f994d6445cc2c32183f03cdebc61 Mon Sep 17 00:00:00 2001 From: Kiran V Garimella Date: Sat, 14 Jan 2023 21:30:37 -0500 Subject: [PATCH 092/297] Reordering dockstore entries --- .dockstore.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.dockstore.yml b/.dockstore.yml index 79fe7ab28..9f85c4dd4 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -136,6 +136,10 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/ConvertToHailMT.wdl testParameterFiles: +- name: ConvertToZarrStore + subclass: wdl + primaryDescriptorPath: /wdl/ConvertToZarrStore.wdl + testParameterFiles: - name: LRConvertBCF subclass: wdl primaryDescriptorPath: /wdl/LRConvertBCF.wdl @@ -156,7 +160,3 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/SRFlowcell.wdl testParameterFiles: -- name: ConvertToZarrStore - subclass: wdl - primaryDescriptorPath: /wdl/ConvertToZarrStore.wdl - testParameterFiles: From 60835868c3287c1dc6a36724fbfec8353dd5c2d3 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 16 Jan 2023 21:13:15 -0500 Subject: [PATCH 093/297] Adding in temporary WDL to work around dockstore. --- ...ointCallGVCFsWithGenomicsDB_monolithic.wdl | 1119 +++++++++++++++++ 1 file changed, 1119 insertions(+) create mode 100644 wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl new file mode 100644 index 000000000..8e5675ca0 --- /dev/null +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -0,0 +1,1119 @@ +version 1.0 + +############################################################################################################# +## A workflow that performs joint calling on single-sample gVCFs from GATK4 HaplotypeCaller using GenomicsDB. +############################################################################################################# + +import "tasks/Hail.wdl" as Hail +import "tasks/Finalize.wdl" as FF + +struct RuntimeAttr { + Float? mem_gb + Int? cpu_cores + Int? disk_gb + Int? boot_disk_gb + Int? preemptible_tries + Int? max_retries + String? docker +} + +struct DataTypeParameters { + Int num_shards + String map_preset +} + +workflow LRJointCallGVCFsWithGenomicsDB { + input { + Array[File] gvcfs + Array[File] gvcf_indices + + File ref_map_file + + File interval_list + + Float snp_filter_level = 99.7 + Array[String] snp_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] + Array[Float] snp_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.8, 99.6, 99.5, 99.4, 99.3, 99.0, 98.0, 97.0, 90.0 ] + + Float indel_filter_level = 99.0 + Array[String] indel_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] + Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] + + Array[File]? annotation_bed_files + Array[File]? annotation_bed_file_indexes + Array[String]? annotation_bed_file_annotation_names + + String prefix + + String gcs_out_root_dir + } + + parameter_meta { + gvcfs: "GCS paths to gVCF files" + gvcf_indices: "GCS paths to gVCF tbi files" + ref_map_file: "table indicating reference sequence and auxillary file locations" + prefix: "prefix for output joint-called gVCF and tabix index" + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/LRJointCallGVCFsWithGenomicsDB/~{prefix}" + + Map[String, String] ref_map = read_map(ref_map_file) + + # From WARP: + # For small callsets (fewer than 1000 samples) we can gather the VCF shards and collect metrics directly. + # For anything larger, we need to keep the VCF sharded and gather metrics collected from them. + # We allow overriding this default behavior for testing / special requests. + Boolean is_small_callset = length(gvcfs) <= 1000 + + # Create sample-name map: + call CreateSampleNameMap as CreateSampleNameMap { + input: + gvcfs = gvcfs, + prefix = prefix + } + + # Import our data into GenomicsDB: + call ImportGVCFs as ImportGVCFsIntoGenomicsDB { + input: + sample_name_map = CreateSampleNameMap.sample_name_map, + interval_list = interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix, + batch_size = 50, + } + + # Joint call + call GenotypeGVCFs as JointCallGVCFs { + input: + input_gvcf_data = ImportGVCFsIntoGenomicsDB.output_genomicsdb, + interval_list = interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + dbsnp_vcf = ref_map["known_sites_vcf"], + prefix = prefix, + } + + # First make a sites-only VCF for recal (smaller file, easier to work with): + call MakeSitesOnlyVcf as MakeSitesOnlyGVCF { + input: + vcf = JointCallGVCFs.output_vcf, + vcf_index = JointCallGVCFs.output_vcf_index, + prefix = prefix + } + + # Now we run VariantRecalibrator for indels and snps: + call IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { + input: + vcf = MakeSitesOnlyGVCF.sites_only_vcf, + vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, + prefix = prefix + ".indels", + recalibration_tranche_values = snp_recalibration_tranche_values, + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = [ref_map["known_sites_vcf"]], + known_reference_variants_index = [ref_map["known_sites_index"]], + known_reference_variants_identifier = ["pfcrosses"], + is_known = [true], + is_training = [true], + is_truth = [true], + prior = [15], + use_allele_specific_annotations = true, + max_gaussians = 8, + } + + call SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { + input: + vcf = MakeSitesOnlyGVCF.sites_only_vcf, + vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, + prefix = prefix + ".snps", + recalibration_tranche_values = snp_recalibration_tranche_values, + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = [ref_map["known_sites_vcf"]], + known_reference_variants_index = [ref_map["known_sites_index"]], + known_reference_variants_identifier = ["pfcrosses"], + is_known = [true], + is_training = [true], + is_truth = [true], + prior = [15], + use_allele_specific_annotations = true, + max_gaussians = 8, + } + + call ApplyVqsr as ApplyVqsr { + input: + vcf = JointCallGVCFs.output_vcf, + vcf_index = JointCallGVCFs.output_vcf_index, + + prefix = prefix + ".vqsr_filtered", + + snps_recalibration = TrainVQSROnHCSnpVariants.recalibration, + snps_recalibration_index = TrainVQSROnHCSnpVariants.recalibration_index, + snps_tranches = TrainVQSROnHCSnpVariants.tranches, + snp_filter_level = snp_filter_level, + + indels_recalibration = TrainVQSROnHCIndelVariants.recalibration, + indels_recalibration_index = TrainVQSROnHCIndelVariants.recalibration_index, + indels_tranches = TrainVQSROnHCIndelVariants.tranches, + indel_filter_level = indel_filter_level, + + use_allele_specific_annotations = true, + } + + # Now we need to annotate our variants by region: + if (defined(annotation_bed_files)) { + call AnnotateVcfWithBedRegions as AnnotateVcfRegions { + input: + vcf = ApplyVqsr.recalibrated_vcf, + vcf_index = ApplyVqsr.recalibrated_vcf_index, + bed_files = select_first([annotation_bed_files]), + bed_file_indexes = select_first([annotation_bed_file_indexes]), + bed_file_annotation_names = select_first([annotation_bed_file_annotation_names]), + prefix = prefix + ".region_annotated" + } + } + + # Finally convert the output to a HAIL Matrix Table: + call ConvertToHailMT as CreateHailMatrixTable { + input: + gvcf = select_first([AnnotateVcfRegions.annotated_vcf, ApplyVqsr.recalibrated_vcf]), + tbi = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]), + reference = sub(sub(ref_map["fasta"], "^.*/", ""), "\.[fasta]*$", ""), + ref_fasta = ref_map["fasta"], + ref_fai = ref_map["fai"], + prefix = prefix, + outdir = outdir + } + + # Finalize: + File keyfile = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]) + + call FF.FinalizeToFile as FinalizeGenomicsDB { input: outdir = outdir, keyfile = keyfile, file = ImportGVCFsIntoGenomicsDB.output_genomicsdb } + + call FF.FinalizeToFile as FinalizeRawVCF { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf } + call FF.FinalizeToFile as FinalizeRawTBI { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf_index } + + call FF.FinalizeToFile as FinalizeIndelRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } + call FF.FinalizeToFile as FinalizeIndelRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } + call FF.FinalizeToFile as FinalizeIndelRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } + call FF.FinalizeToFile as FinalizeIndelRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } + + call FF.FinalizeToFile as FinalizeSnpRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } + call FF.FinalizeToFile as FinalizeSnpRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } + call FF.FinalizeToFile as FinalizeSnpRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } + call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } + + call FF.FinalizeToFile as FinalizeVQSRVCF { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } + call FF.FinalizeToFile as FinalizeVQSRTBI { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } + + if (defined(annotation_bed_files)) { + call FF.FinalizeToFile as FinalizeRegionAnnotatedVcf { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf]) } + call FF.FinalizeToFile as FinalizeRegionAnnotatedVcfIndex { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf_index]) } + } + + ########## + # store the results into designated bucket + ########## + + output { + File genomicsDB = FinalizeGenomicsDB.gcs_path + + File raw_joint_vcf = FinalizeRawVCF.gcs_path + File raw_joint_vcf_tbi = FinalizeRawTBI.gcs_path + + File? vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path + File? vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path + File? vqsr_indel_recal_tranches = FinalizeIndelRecalTranches.gcs_path + File? vqsr_indel_recal_model_report = FinalizeIndelRecalModelReport.gcs_path + + File? vqsr_snp_recal_file = FinalizeSnpRecalFile.gcs_path + File? vqsr_snp_recal_file_index = FinalizeSnpRecalIndex.gcs_path + File? vqsr_snp_recal_tranches = FinalizeSnpRecalTranches.gcs_path + File? vqsr_snp_recal_model_report = FinalizeSnpRecalModelReport.gcs_path + + File joint_recalibrated_vcf = FinalizeVQSRVCF.gcs_path + File joint_recalibrated_vcf_tbi = FinalizeVQSRTBI.gcs_path + + File? annotated_joint_vcf = AnnotateVcfRegions.annotated_vcf + File? annotated_joint_vcf_tbi = AnnotateVcfRegions.annotated_vcf_index + + File joint_mt = CreateHailMatrixTable.gcs_path + } +} + + +task CreateSampleNameMap { + + meta { + description: "Creates the sample / name-map file of the GVCFs for ingest into ImportGVCFs. NOTE: Some of this functionality is duplicated from Utils.InferSampleName. This is intentional - we don't want to localize all these files or shard over potentially thousands of input GVCFs." + } + + input { + Array[File] gvcfs + String prefix + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + gvcfs: { + help: "Array of single-sample GVCF files.", + localization_optional: true + } + } + + Int disk_size_gb = 20 + + String outfile_name = "~{prefix}.sample_name_map.tsv" + + # Every so often we should reauthorize so `bcftools` can continue to access our data: + Int re_auth_interval = 50 + + command <<< + set -euxo pipefail + + # Put our gvcfs into a file we can iterate over: + gvcf_file_list=~{write_lines(gvcfs)} + + # Initialize a file for the sample names: + [ -e ~{outfile_name} ] && rm -rf ~{outfile_name} + + # Set our access token: + export GCS_OAUTH_TOKEN=$(gcloud auth application-default print-access-token) + + let i=1 + while read file_path ; do + + # Get our sample list from our file: + bcftools query -l ${file_path} > sample_names.txt + + # Make sure we only have one sample name: + [[ $(wc -l sample_names.txt | awk '{print $1}') -ne 1 ]] && echo "Incorrect number of sample names found in GVCF (there can be only one!): ${file_path}" && exit 1 + + # Make sure the samplename has an actual name: + [ $(grep -iq "unnamedsample" sample_names.txt) ] && echo "Sample name found to be unnamedsample in GVCF: ${file_path}" && exit 1 + + # Add the sample name and GVCF path to the sample name file: + echo -e "$(cat sample_names.txt)\t${file_path}" >> ~{outfile_name} + + let i=$i+1 + if [[ $i -gt ~{re_auth_interval} ]] ; then + # Periodically we should update the token so we don't have problems with long file lists: + export GCS_OAUTH_TOKEN=$(gcloud auth application-default print-access-token) + i=0 + fi + done < ${gvcf_file_list} + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 2, + disk_gb: disk_size_gb, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-basic:0.1.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File sample_name_map = outfile_name + } +} + +task ImportGVCFs { + + input { + File sample_name_map + + File interval_list + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String prefix + + Int batch_size = 50 + + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size(ref_fasta, "GB") + size(ref_fasta_fai, "GB") + size(ref_dict, "GB")) + + Int disk_size = 1 + 4*ref_size + + command <<< + set -euxo pipefail + + # Make sure that the output directory does not exist: + [ -e ~{prefix} ] && rm -rf ~{prefix} + + # + # Notes from WARP Team: + # + # We've seen some GenomicsDB performance regressions related to intervals, so we're going to pretend we only have a single interval + # using the --merge-input-intervals arg + # There's no data in between since we didn't run HaplotypeCaller over those loci so we're not wasting any compute + + # The memory setting here is very important and must be several GiB lower + # than the total memory allocated to the VM because this tool uses + # a significant amount of non-heap memory for native libraries. + # Also, testing has shown that the multithreaded reader initialization + # does not scale well beyond 5 threads, so don't increase beyond that. + gatk --java-options "-Xms8000m -Xmx25000m" \ + GenomicsDBImport \ + --genomicsdb-workspace-path ~{prefix}.genomicsDB \ + --batch-size ~{batch_size} \ + -L ~{interval_list} \ + --sample-name-map ~{sample_name_map} \ + --reader-threads 5 \ + --merge-input-intervals \ + --consolidate + + tar -cf ~{prefix}.genomicsDB.tar ~{prefix}.genomicsDB + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File output_genomicsdb = "~{prefix}.genomicsDB.tar" + } +} + +task GenotypeGVCFs { + + input { + File input_gvcf_data + File? input_gvcf_index # Required if passing a VCF file. + + File interval_list + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String dbsnp_vcf + + String prefix + + Boolean keep_combined_raw_annotations = false + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size(ref_fasta, "GB") + size(ref_fasta_fai, "GB") + size(ref_dict, "GB")) + Int db_snp_size = ceil(size(dbsnp_vcf, "GB")) + + Int disk_size = 1 + 4*ceil(size(input_gvcf_data, "GB")) + ref_size + db_snp_size + + parameter_meta { + input_gvcf_data: { help: "Either a single GVCF file or a GenomicsDB Tar file." } + interval_list: { + localization_optional: true + } + } + + command <<< + set -euxo pipefail + + # We must determine if our input variants are in a genomicsdb file or in a VCF. + # The easiest way is to see if the input is a .tar file: + + is_genomics_db=true + filename=$(basename -- "~{input_gvcf_data}") + extension="${filename##*.}" + if [[ "${extension}" != "tar" ]] ; then + is_genomics_db=false + fi + + if $is_genomics_db ; then + tar -xf ~{input_gvcf_data} + INPUT_FILE="gendb://$(basename ~{input_gvcf_data} .tar)" + else + INPUT_FILE=~{input_gvcf_data} + fi + + gatk --java-options "-Xms8000m -Xmx25000m" \ + GenotypeGVCFs \ + -R ~{ref_fasta} \ + -O ~{prefix}.vcf.gz \ + -D ~{dbsnp_vcf} \ + -G StandardAnnotation -G AS_StandardAnnotation \ + --only-output-calls-starting-in-intervals \ + -V ${INPUT_FILE} \ + -L ~{interval_list} \ + ~{true='--keep-combined-raw-annotations' false='' keep_combined_raw_annotations} \ + --merge-input-intervals + >>> + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 26, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File output_vcf = "~{prefix}.vcf.gz" + File output_vcf_index = "~{prefix}.vcf.gz.tbi" + } +} + + +task HardFilterVcf { + + input { + File vcf + File vcf_index + + String prefix + + # From WARP: + # ExcessHet is a phred-scaled p-value. We want a cutoff of anything more extreme + # than a z-score of -4.5 which is a p-value of 3.4e-06, which phred-scaled is 54.69 + Float excess_het_threshold = 54.69 + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size([vcf, vcf_index], "GB")) + + command <<< + set -euo pipefail + + # Get amount of memory to use: + mem_available=$(free -m | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-1000 + let mem_max=${mem_available}-750 + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + VariantFiltration \ + --filter-expression "ExcessHet > ~{excess_het_threshold}" \ + --filter-name ExcessHet \ + -V ~{vcf} \ + -O ~{prefix}.hard_filtered.vcf.gz + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File variant_filtered_vcf = "~{prefix}.hard_filtered.vcf.gz" + File variant_filtered_vcf_index = "~{prefix}.hard_filtered.vcf.gz.tbi" + } +} + +task MakeSitesOnlyVcf { + + input { + File vcf + File vcf_index + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size([vcf, vcf_index], "GB")) + + command <<< + set -euo pipefail + + # Get amount of memory to use: + mem_available=$(free -m | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-1000 + let mem_max=${mem_available}-750 + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + MakeSitesOnlyVcf \ + -I ~{vcf} \ + -O ~{prefix}.sites_only.vcf.gz + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File sites_only_vcf = "~{prefix}.sites_only.vcf.gz" + File sites_only_vcf_index = "~{prefix}.sites_only.vcf.gz.tbi" + } +} + +task AnnotateVcfWithBedRegions { + input { + File vcf + File vcf_index + + Array[File] bed_files + Array[File] bed_file_indexes + Array[String] bed_file_annotation_names + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size([vcf, vcf_index, bed_files, bed_file_indexes], "GB")) + + command <<< + set -euxo pipefail + + # Get amount of memory to use: + mem_available=$(free -m | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-1000 + let mem_max=${mem_available}-750 + + # We need to generate argument strings from the input arrays. + # First we check that the arrays are the same length: + if [[ ~{length(bed_files)} -ne ~{length(bed_file_indexes)} ]] || \ + [[ ~{length(bed_files)} -ne ~{length(bed_file_annotation_names)} ]] ; then + echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 + echo " bed_files = ~{length(bed_files)}" 1>&2 + echo " bed_file_indices = ~{length(bed_file_indexes)}" 1>&2 + echo " bed_file_annotation_names = ~{length(bed_file_annotation_names)}" 1>&2 + false + fi + + # Now we can write out the arrays into a TSV file and add them line by line to the execution: + # Create the TSV: + options_tsv=~{write_tsv(transpose([bed_files, bed_file_annotation_names]))} + + # Now we have to run `VariantFiltration` multiple times on its own output so that it can + # annotate each region in the file: + # NOTE: This is dumb, but must be done because the `--mask` and `--mask-name` inputs are not arrays. + + input_vcf=~{vcf} + output_vcf=~{prefix}.intermediate.vcf.gz + while read mask_options ; do + + bed_file=$(echo "${mask_options}" | awk -F'\t' '{print $1}') + mask_name=$(echo "${mask_options}" | awk -F'\t' '{print $2}') + + echo -e "RUNNING GATK ON NEW MASK: ${mask_name}\t${bed_file}" + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + VariantFiltration \ + -V ${input_vcf} \ + -O ${output_vcf} \ + --mask ${bed_file} \ + --mask-name ${mask_name} + + mv ${output_vcf} ~{prefix}.new_input.vcf.gz + mv ${output_vcf}.tbi ~{prefix}.new_input.vcf.gz.tbi + input_vcf=~{prefix}.new_input.vcf.gz + done < ${options_tsv} + + # Because of the `mv` at the end of the loop we need to move the "new_input" files here: + mv ~{prefix}.new_input.vcf.gz ~{prefix}.vcf.gz + mv ~{prefix}.new_input.vcf.gz.tbi ~{prefix}.vcf.gz.tbi + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File annotated_vcf = "~{prefix}.vcf.gz" + File annotated_vcf_index = "~{prefix}.vcf.gz.tbi" + } +} + +task IndelsVariantRecalibrator { + + input { + File vcf + File vcf_index + + String prefix + + Array[String] recalibration_tranche_values + Array[String] recalibration_annotation_values + + Array[File] known_reference_variants + Array[File] known_reference_variants_index + Array[String] known_reference_variants_identifier + Array[Boolean] is_known + Array[Boolean] is_training + Array[Boolean] is_truth + Array[Int] prior + + Boolean use_allele_specific_annotations + Int max_gaussians = 4 + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + vcf: "Sites only VCF. Can be pre-filtered using hard-filters." + vcf_index: "Tribble Index for sites only VCF." + known_reference_variants: "Array of known reference VCF files. For humans, dbSNP is one example." + known_reference_variants_index: "Array of index files for known reference VCF files." + known_reference_variants_identifier: "Array of boolean values the identifier / name for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." + is_known: "Array of boolean values indicating if the known_reference_variant file at the same array position contains known variants. Must be the same length as `known_reference_variants`." + is_training: "Array of boolean values indicating if the known_reference_variant file at the same array position contains training data. Must be the same length as `known_reference_variants`." + is_truth: "Array of boolean values indicating if the known_reference_variant file at the same array position contains truth data. Must be the same length as `known_reference_variants`." + prior: "Array of integer values indicating the priors for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." + } + + + Int disk_size = 10 + ceil(size(known_reference_variants, "GB")) + + 4*ceil(size(vcf, "GB")) + + 2*ceil(size(vcf_index, "GB")) + + command <<< + set -euxo pipefail + + # We need to generate resource strings from the input arrays. + # First we check that the arrays are the same length: + if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_index)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_known)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_training)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_truth)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(prior)} ]] ; then + echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants)}" 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants_index)}" 1>&2 + echo " known_reference_variants_identifier = ~{length(known_reference_variants_identifier)}" 1>&2 + echo " is_known = ~{length(is_known)}" 1>&2 + echo " is_training = ~{length(is_training)}" 1>&2 + echo " is_truth = ~{length(is_truth)}" 1>&2 + echo " prior = ~{length(prior)}" 1>&2 + false + fi + + # Now we can write out the arrays into a TSV file and add them line by line to the execution: + # Create the TSV: + options_tsv=~{write_tsv(transpose([known_reference_variants_identifier, is_known, is_training, is_truth, prior, known_reference_variants]))} + + # Now read them into a string: + resource_flags=$(awk '{printf("--resource:%s,known=%s,training=%s,truth=%s,prior=%d %s ", $1, $2, $3, $4, $5, $6)}' ${options_tsv}) + + # Get amount of memory to use: + mem_available=$(free -g | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-2 + let mem_max=${mem_available}-1 + + gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ + VariantRecalibrator \ + -V ~{vcf} \ + -O ~{prefix}.recal \ + --tranches-file ~{prefix}.tranches \ + --trust-all-polymorphic \ + -tranche ~{sep=' -tranche ' recalibration_tranche_values} \ + -an ~{sep=' -an ' recalibration_annotation_values} \ + ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ + -mode INDEL \ + --output-model ~{prefix}.model.report \ + --max-gaussians ~{max_gaussians} \ + ${resource_flags} + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 26, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File recalibration = "~{prefix}.recal" + File recalibration_index = "~{prefix}.recal.idx" + File tranches = "~{prefix}.tranches" + File model_report = "~{prefix}.model.report" + } +} + +task SNPsVariantRecalibratorCreateModel { + + input { + File vcf + File vcf_index + + String prefix + + Array[String] recalibration_tranche_values + Array[String] recalibration_annotation_values + + Array[File] known_reference_variants + Array[File] known_reference_variants_index + Array[String] known_reference_variants_identifier + Array[Boolean] is_known + Array[Boolean] is_training + Array[Boolean] is_truth + Array[Int] prior + + Int? downsampleFactor + + Boolean use_allele_specific_annotations + Int max_gaussians = 6 + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + vcf: "Sites only VCF. Can be pre-filtered using hard-filters." + vcf_index: "Tribble Index for sites only VCF." + known_reference_variants: "Array of known reference VCF files. For humans, dbSNP is one example." + known_reference_variants_index: "Array of index files for known reference VCF files." + known_reference_variants_identifier: "Array of boolean values the identifier / name for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." + is_known: "Array of boolean values indicating if the known_reference_variant file at the same array position contains known variants. Must be the same length as `known_reference_variants`." + is_training: "Array of boolean values indicating if the known_reference_variant file at the same array position contains training data. Must be the same length as `known_reference_variants`." + is_truth: "Array of boolean values indicating if the known_reference_variant file at the same array position contains truth data. Must be the same length as `known_reference_variants`." + prior: "Array of integer values indicating the priors for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." + } + + Int disk_size = 10 + ceil(size(known_reference_variants, "GB")) + + 4*ceil(size(vcf, "GB")) + + 2*ceil(size(vcf_index, "GB")) + + String downsample_factor_arg = if defined(downsampleFactor) then " --sample-every-Nth-variant " else "" + + command <<< + set -euxo pipefail + + # We need to generate resource strings from the input arrays. + # First we check that the arrays are the same length: + if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_index)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_known)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_training)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_truth)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(prior)} ]] ; then + echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants)}" 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants_index)}" 1>&2 + echo " known_reference_variants_identifier = ~{length(known_reference_variants_identifier)}" 1>&2 + echo " is_known = ~{length(is_known)}" 1>&2 + echo " is_training = ~{length(is_training)}" 1>&2 + echo " is_truth = ~{length(is_truth)}" 1>&2 + echo " prior = ~{length(prior)}" 1>&2 + false + fi + + # Now we can write out the arrays into a TSV file and add them line by line to the execution: + # Create the TSV: + options_tsv=~{write_tsv(transpose([known_reference_variants_identifier, is_known, is_training, is_truth, prior, known_reference_variants]))} + + # Now read them into a string: + resource_flags=$(awk '{printf("--resource:%s,known=%s,training=%s,truth=%s,prior=%d %s ", $1, $2, $3, $4, $5, $6)}' ${options_tsv}) + + # Get amount of memory to use: + mem_available=$(free -g | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-2 + let mem_max=${mem_available}-1 + + gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ + VariantRecalibrator \ + -V ~{vcf} \ + -O ~{prefix}.recal \ + --tranches-file ~{prefix}.tranches \ + --trust-all-polymorphic \ + -tranche ~{sep=' -tranche ' recalibration_tranche_values} \ + -an ~{sep=' -an ' recalibration_annotation_values} \ + ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ + -mode SNP \ + ~{downsample_factor_arg}~{default="" sep=" --sample-every-Nth-variant " downsampleFactor} \ + --output-model ~{prefix}.model.report \ + --max-gaussians ~{max_gaussians} \ + ${resource_flags} + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 64, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File recalibration = "~{prefix}.recal" + File recalibration_index = "~{prefix}.recal.idx" + File tranches = "~{prefix}.tranches" + File model_report = "~{prefix}.model.report" + } +} + +task ApplyVqsr { + + input { + File vcf + File vcf_index + + String prefix + + File snps_recalibration + File snps_recalibration_index + File snps_tranches + Float snp_filter_level + + File indels_recalibration + File indels_recalibration_index + File indels_tranches + Float indel_filter_level + + Boolean use_allele_specific_annotations + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 10 + ceil(size([vcf, vcf_index], "GB")) + + 2*ceil(size([snps_recalibration, snps_recalibration_index, snps_tranches], "GB")) + + 2*ceil(size([indels_recalibration, indels_recalibration_index, indels_tranches], "GB")) + + command <<< + set -euxo pipefail + + # Get amount of memory to use: + mem_available=$(free -m | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-2000 + let mem_max=${mem_available}-500 + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + ApplyVQSR \ + -V ~{vcf} \ + -O tmp.indel.recalibrated.vcf.gz \ + --recal-file ~{indels_recalibration} \ + ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ + --tranches-file ~{indels_tranches} \ + --truth-sensitivity-filter-level ~{indel_filter_level} \ + --create-output-variant-index true \ + -mode INDEL + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + ApplyVQSR \ + -V tmp.indel.recalibrated.vcf.gz \ + -O ~{prefix}.recalibrated.vcf.gz \ + --recal-file ~{snps_recalibration} \ + ~{true='--use-allele-specific-annotations' false='' use_allele_specific_annotations} \ + --tranches-file ~{snps_tranches} \ + --truth-sensitivity-filter-level ~{snp_filter_level} \ + --create-output-variant-index true \ + -mode SNP + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 7, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File recalibrated_vcf = "~{prefix}.recalibrated.vcf.gz" + File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.gz.tbi" + } +} + + +task ConvertToHailMT { + meta { + description: "Convert a .vcf.bgz file to a Hail MatrixTable and copy it to a final gs:// URL." + } + + input { + File gvcf + File tbi + + String reference = "GRCh38" + String? ref_fasta + String? ref_fai + String prefix = "out" + + String outdir + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 3*ceil(size(gvcf, "GB")) + + command <<< + set -x + + python3 <>> + + output { + String gcs_path = "~{outdir}/~{prefix}.mt" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 64, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 0, + max_retries: 0, + docker: "hailgenetics/hail:0.2.105" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + From 991105dbb52b3991d7354e9c8b2e6ed4bdfdde00 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 16 Jan 2023 21:19:52 -0500 Subject: [PATCH 094/297] Fixing typos. --- ...ointCallGVCFsWithGenomicsDB_monolithic.wdl | 91 +++++++++++++++---- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl index 8e5675ca0..6f416084a 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -4,9 +4,6 @@ version 1.0 ## A workflow that performs joint calling on single-sample gVCFs from GATK4 HaplotypeCaller using GenomicsDB. ############################################################################################################# -import "tasks/Hail.wdl" as Hail -import "tasks/Finalize.wdl" as FF - struct RuntimeAttr { Float? mem_gb Int? cpu_cores @@ -190,27 +187,27 @@ workflow LRJointCallGVCFsWithGenomicsDB { # Finalize: File keyfile = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]) - call FF.FinalizeToFile as FinalizeGenomicsDB { input: outdir = outdir, keyfile = keyfile, file = ImportGVCFsIntoGenomicsDB.output_genomicsdb } + call FinalizeToFile as FinalizeGenomicsDB { input: outdir = outdir, keyfile = keyfile, file = ImportGVCFsIntoGenomicsDB.output_genomicsdb } - call FF.FinalizeToFile as FinalizeRawVCF { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf } - call FF.FinalizeToFile as FinalizeRawTBI { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf_index } + call FinalizeToFile as FinalizeRawVCF { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf } + call FinalizeToFile as FinalizeRawTBI { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf_index } - call FF.FinalizeToFile as FinalizeIndelRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } - call FF.FinalizeToFile as FinalizeIndelRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } - call FF.FinalizeToFile as FinalizeIndelRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } - call FF.FinalizeToFile as FinalizeIndelRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } + call FinalizeToFile as FinalizeIndelRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } + call FinalizeToFile as FinalizeIndelRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } + call FinalizeToFile as FinalizeIndelRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } + call FinalizeToFile as FinalizeIndelRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } - call FF.FinalizeToFile as FinalizeSnpRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } - call FF.FinalizeToFile as FinalizeSnpRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } - call FF.FinalizeToFile as FinalizeSnpRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } - call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } + call FinalizeToFile as FinalizeSnpRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } + call FinalizeToFile as FinalizeSnpRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } + call FinalizeToFile as FinalizeSnpRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } + call FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } - call FF.FinalizeToFile as FinalizeVQSRVCF { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } - call FF.FinalizeToFile as FinalizeVQSRTBI { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } + call FinalizeToFile as FinalizeVQSRVCF { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } + call FinalizeToFile as FinalizeVQSRTBI { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } if (defined(annotation_bed_files)) { - call FF.FinalizeToFile as FinalizeRegionAnnotatedVcf { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf]) } - call FF.FinalizeToFile as FinalizeRegionAnnotatedVcfIndex { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf_index]) } + call FinalizeToFile as FinalizeRegionAnnotatedVcf { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf]) } + call FinalizeToFile as FinalizeRegionAnnotatedVcfIndex { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf_index]) } } ########## @@ -513,7 +510,7 @@ task HardFilterVcf { String prefix # From WARP: - # ExcessHet is a phred-scaled p-value. We want a cutoff of anything more extreme + # ExcessHet is a phred-scaled p-value. We want a cutoof anything more extreme # than a z-score of -4.5 which is a p-value of 3.4e-06, which phred-scaled is 54.69 Float excess_het_threshold = 54.69 @@ -1117,3 +1114,59 @@ task ConvertToHailMT { } } +task FinalizeToFile { + input { + File file + String outdir + String? name + + File? keyfile + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + file: { + description: "file to finalize", + localization_optional: true + } + keyfile : "[optional] File used to key this finaliation. Finalization will not take place until the KeyFile exists. This can be used to force the finaliation to wait until a certain point in a workflow. NOTE: The latest WDL development spec includes the `after` keyword which will obviate this." + outdir: "directory to which files should be uploaded" + name: "name to set for uploaded file" + } + + String gcs_output_dir = sub(outdir, "/+$", "") + String gcs_output_file = gcs_output_dir + "/" + select_first([name, basename(file)]) + + command <<< + set -euxo pipefail + + gsutil -m cp "~{file}" "~{gcs_output_file}" + >>> + + output { + String gcs_path = gcs_output_file + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 1, + disk_gb: 10, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 2, + docker: "us.gcr.io/broad-dsp-lrma/lr-finalize:0.1.2" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + From a4195d60d9243d13c1e112da8969b669114a65a7 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 17 Jan 2023 07:20:46 -0500 Subject: [PATCH 095/297] Updating disk space required for `ImportGVCFs` --- wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl index 6f416084a..a3c6ed006 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -350,7 +350,7 @@ task ImportGVCFs { Int ref_size = ceil(size(ref_fasta, "GB") + size(ref_fasta_fai, "GB") + size(ref_dict, "GB")) - Int disk_size = 1 + 4*ref_size + Int disk_size = 8192 + 4*ref_size command <<< set -euxo pipefail From b7bdab27e48da8a1471f9ac462dab39aa2bff915 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 17 Jan 2023 08:49:58 -0500 Subject: [PATCH 096/297] Fixing disk space issue with genomicsDB. --- wdl/LRJointCallGVCFsWithGenomicsDB.wdl | 2 ++ wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl | 13 +++++++++++++ wdl/tasks/SRJointGenotyping.wdl | 11 +++++++++++ 3 files changed, 26 insertions(+) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl index 899162dd4..8a597b4bf 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl @@ -70,6 +70,8 @@ workflow LRJointCallGVCFsWithGenomicsDB { ref_dict = ref_map['dict'], prefix = prefix, batch_size = 50, + # We need to override this because we're not actually sending the GVCF over (just a list): + runtime_attr_override = object {disk_gb: 4 * CreateSampleNameMap.total_gvcf_size + 2 * ceil(size(ref_map['fasta'], "GB"))} } # Joint call diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl index a3c6ed006..c66278efa 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -80,6 +80,8 @@ workflow LRJointCallGVCFsWithGenomicsDB { ref_dict = ref_map['dict'], prefix = prefix, batch_size = 50, + # We need to override this because we're not actually sending the GVCF over (just a list): + runtime_attr_override = object {disk_gb: 4 * CreateSampleNameMap.total_gvcf_size + 2 * ceil(size(ref_map['fasta'], "GB"))} } # Joint call @@ -264,6 +266,7 @@ task CreateSampleNameMap { Int disk_size_gb = 20 String outfile_name = "~{prefix}.sample_name_map.tsv" + String size_file = "~{prefix}.total_gvcf_file_size.txt" # Every so often we should reauthorize so `bcftools` can continue to access our data: Int re_auth_interval = 50 @@ -280,6 +283,9 @@ task CreateSampleNameMap { # Set our access token: export GCS_OAUTH_TOKEN=$(gcloud auth application-default print-access-token) + # Create a temporary file to store file sizes in: + size_file=$(mktemp) + let i=1 while read file_path ; do @@ -295,6 +301,9 @@ task CreateSampleNameMap { # Add the sample name and GVCF path to the sample name file: echo -e "$(cat sample_names.txt)\t${file_path}" >> ~{outfile_name} + # Add the file size to the size file: + gsutil du -sac ${file_path} | tail -n1 | awk '{print $1}' >> ${size_file} + let i=$i+1 if [[ $i -gt ~{re_auth_interval} ]] ; then # Periodically we should update the token so we don't have problems with long file lists: @@ -302,6 +311,9 @@ task CreateSampleNameMap { i=0 fi done < ${gvcf_file_list} + + # Now calculate the final file size: + awk '{s += $1}END{print s}' ${size_file} > ~{size_file} >>> ######################### @@ -327,6 +339,7 @@ task CreateSampleNameMap { output { File sample_name_map = outfile_name + Int total_gvcf_size = read_int(size_file) } } diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 35cacde2a..e9274c23b 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -26,6 +26,7 @@ task CreateSampleNameMap { Int disk_size_gb = 20 String outfile_name = "~{prefix}.sample_name_map.tsv" + String size_file = "~{prefix}.total_gvcf_file_size.txt" # Every so often we should reauthorize so `bcftools` can continue to access our data: Int re_auth_interval = 50 @@ -42,6 +43,9 @@ task CreateSampleNameMap { # Set our access token: export GCS_OAUTH_TOKEN=$(gcloud auth application-default print-access-token) + # Create a temporary file to store file sizes in: + size_file=$(mktemp) + let i=1 while read file_path ; do @@ -57,6 +61,9 @@ task CreateSampleNameMap { # Add the sample name and GVCF path to the sample name file: echo -e "$(cat sample_names.txt)\t${file_path}" >> ~{outfile_name} + # Add the file size to the size file: + gsutil du -sac ${file_path} | tail -n1 | awk '{print $1}' >> ${size_file} + let i=$i+1 if [[ $i -gt ~{re_auth_interval} ]] ; then # Periodically we should update the token so we don't have problems with long file lists: @@ -64,6 +71,9 @@ task CreateSampleNameMap { i=0 fi done < ${gvcf_file_list} + + # Now calculate the final file size: + awk '{s += $1}END{print s}' ${size_file} > ~{size_file} >>> ######################### @@ -89,6 +99,7 @@ task CreateSampleNameMap { output { File sample_name_map = outfile_name + Int total_gvcf_size = read_int(size_file) } } From 37169203709183083cb6550d01c8cc93467c37a4 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 17 Jan 2023 09:23:36 -0500 Subject: [PATCH 097/297] Fixing disk space reqs. --- wdl/LRJointCallGVCFsWithGenomicsDB.wdl | 2 +- wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl index 8a597b4bf..214edce01 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl @@ -71,7 +71,7 @@ workflow LRJointCallGVCFsWithGenomicsDB { prefix = prefix, batch_size = 50, # We need to override this because we're not actually sending the GVCF over (just a list): - runtime_attr_override = object {disk_gb: 4 * CreateSampleNameMap.total_gvcf_size + 2 * ceil(size(ref_map['fasta'], "GB"))} + runtime_attr_override = object {disk_gb: 10 + CreateSampleNameMap.total_gvcf_size + 2 * ceil(size(ref_map['fasta'], "GB"))} } # Joint call diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl index c66278efa..ec25a01ce 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -81,7 +81,7 @@ workflow LRJointCallGVCFsWithGenomicsDB { prefix = prefix, batch_size = 50, # We need to override this because we're not actually sending the GVCF over (just a list): - runtime_attr_override = object {disk_gb: 4 * CreateSampleNameMap.total_gvcf_size + 2 * ceil(size(ref_map['fasta'], "GB"))} + runtime_attr_override = object {disk_gb: 10 + CreateSampleNameMap.total_gvcf_size + 2 * ceil(size(ref_map['fasta'], "GB"))} } # Joint call From 89ecbd33d2c691989fd80032df1d5a87be57cb92 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 17 Jan 2023 10:13:17 -0500 Subject: [PATCH 098/297] Fixed woeful byte -> GB conversion error. --- wdl/LRJointCallGVCFsWithGenomicsDB.wdl | 2 +- wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl | 6 +++--- wdl/tasks/SRJointGenotyping.wdl | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl index 214edce01..23e218219 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl @@ -71,7 +71,7 @@ workflow LRJointCallGVCFsWithGenomicsDB { prefix = prefix, batch_size = 50, # We need to override this because we're not actually sending the GVCF over (just a list): - runtime_attr_override = object {disk_gb: 10 + CreateSampleNameMap.total_gvcf_size + 2 * ceil(size(ref_map['fasta'], "GB"))} + runtime_attr_override = object {disk_gb: 10 + CreateSampleNameMap.total_gvcf_size_gb + 2 * ceil(size(ref_map['fasta'], "GB"))} } # Joint call diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl index ec25a01ce..67b508c82 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -81,7 +81,7 @@ workflow LRJointCallGVCFsWithGenomicsDB { prefix = prefix, batch_size = 50, # We need to override this because we're not actually sending the GVCF over (just a list): - runtime_attr_override = object {disk_gb: 10 + CreateSampleNameMap.total_gvcf_size + 2 * ceil(size(ref_map['fasta'], "GB"))} + runtime_attr_override = object {disk_gb: 10 + CreateSampleNameMap.total_gvcf_size_gb + 2 * ceil(size(ref_map['fasta'], "GB"))} } # Joint call @@ -266,7 +266,7 @@ task CreateSampleNameMap { Int disk_size_gb = 20 String outfile_name = "~{prefix}.sample_name_map.tsv" - String size_file = "~{prefix}.total_gvcf_file_size.txt" + String size_file = "~{prefix}.total_gvcf_file_size_gb.txt" # Every so often we should reauthorize so `bcftools` can continue to access our data: Int re_auth_interval = 50 @@ -339,7 +339,7 @@ task CreateSampleNameMap { output { File sample_name_map = outfile_name - Int total_gvcf_size = read_int(size_file) + Int total_gvcf_size_gb = ceil(read_int(size_file) / (1024 * 1024 * 1024)) # Convert bytes to GB } } diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index e9274c23b..3de93c495 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -99,7 +99,7 @@ task CreateSampleNameMap { output { File sample_name_map = outfile_name - Int total_gvcf_size = read_int(size_file) + Int total_gvcf_size_gb = ceil(read_int(size_file) / (1024 * 1024 * 1024)) # Convert bytes to GB } } From 17be0abde81ca4c3f68ec0199fd0ab91628ad5fd Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 17 Jan 2023 10:24:44 -0500 Subject: [PATCH 099/297] Fixing dupe entries in `.dockstore.yml` --- .dockstore.yml | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/.dockstore.yml b/.dockstore.yml index 9f85c4dd4..0f7f7f8f5 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -143,20 +143,4 @@ workflows: - name: LRConvertBCF subclass: wdl primaryDescriptorPath: /wdl/LRConvertBCF.wdl - testParameterFiles: -- name: SRBamToFq - subclass: wdl - primaryDescriptorPath: /wdl/SRBamToFq.wdl - testParameterFiles: -- name: SRIndexBam - subclass: wdl - primaryDescriptorPath: /wdl/SRIndexBam.wdl - testParameterFiles: -- name: SRWholeGenome - subclass: wdl - primaryDescriptorPath: /wdl/SRWholeGenome.wdl - testParameterFiles: -- name: SRFlowcell - subclass: wdl - primaryDescriptorPath: /wdl/SRFlowcell.wdl - testParameterFiles: + testParameterFiles: \ No newline at end of file From dd39fdfb2364199c9765899fb1e34c36e2337103 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 17 Jan 2023 16:30:34 -0500 Subject: [PATCH 100/297] Adjusted disk size due to `tar` in the command block of `ImportGVCFs` --- wdl/LRJointCallGVCFsWithGenomicsDB.wdl | 5 +++-- wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl index 23e218219..311b024a9 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl @@ -70,8 +70,9 @@ workflow LRJointCallGVCFsWithGenomicsDB { ref_dict = ref_map['dict'], prefix = prefix, batch_size = 50, - # We need to override this because we're not actually sending the GVCF over (just a list): - runtime_attr_override = object {disk_gb: 10 + CreateSampleNameMap.total_gvcf_size_gb + 2 * ceil(size(ref_map['fasta'], "GB"))} + # We need to override this because we're not actually sending the GVCF over (just a list) + # ALSO, we're currently tarring the genomicsDB, so we need double the space here, plus some slop: + runtime_attr_override = object {disk_gb: 10 + (2 * CreateSampleNameMap.total_gvcf_size_gb) + (2 * ceil(size(ref_map['fasta'], "GB")))} } # Joint call diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl index 67b508c82..39a248389 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -80,8 +80,9 @@ workflow LRJointCallGVCFsWithGenomicsDB { ref_dict = ref_map['dict'], prefix = prefix, batch_size = 50, - # We need to override this because we're not actually sending the GVCF over (just a list): - runtime_attr_override = object {disk_gb: 10 + CreateSampleNameMap.total_gvcf_size_gb + 2 * ceil(size(ref_map['fasta'], "GB"))} + # We need to override this because we're not actually sending the GVCF over (just a list) + # ALSO, we're currently tarring the genomicsDB, so we need double the space here, plus some slop: + runtime_attr_override = object {disk_gb: 10 + (2 * CreateSampleNameMap.total_gvcf_size_gb) + (2 * ceil(size(ref_map['fasta'], "GB")))} } # Joint call From e09bf4a907c335f0cf67311c20a296d0f7f2f841 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 17 Jan 2023 16:33:36 -0500 Subject: [PATCH 101/297] Removed preemption from `ImportGVCFs` --- wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl | 2 +- wdl/tasks/SRJointGenotyping.wdl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl index 39a248389..3239ded1a 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -403,7 +403,7 @@ task ImportGVCFs { mem_gb: 32, disk_gb: disk_size, boot_disk_gb: 15, - preemptible_tries: 1, + preemptible_tries: 0, max_retries: 1, docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 3de93c495..83d82c8d4 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -162,7 +162,7 @@ task ImportGVCFs { mem_gb: 32, disk_gb: disk_size, boot_disk_gb: 15, - preemptible_tries: 1, + preemptible_tries: 0, max_retries: 1, docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } From 1e15392fe10e8099b5de057a7486f083bc89211b Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 17 Jan 2023 17:08:28 -0500 Subject: [PATCH 102/297] Updated calls to have `0` preemptable attempts (for now). --- wdl/LRJointCallGVCFsWithGenomicsDB.wdl | 4 ++-- ...ointCallGVCFsWithGenomicsDB_monolithic.wdl | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl index 311b024a9..714f6c6b4 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl @@ -71,8 +71,8 @@ workflow LRJointCallGVCFsWithGenomicsDB { prefix = prefix, batch_size = 50, # We need to override this because we're not actually sending the GVCF over (just a list) - # ALSO, we're currently tarring the genomicsDB, so we need double the space here, plus some slop: - runtime_attr_override = object {disk_gb: 10 + (2 * CreateSampleNameMap.total_gvcf_size_gb) + (2 * ceil(size(ref_map['fasta'], "GB")))} + # ALSO, we're currently tarring the genomicsDB, so we need at least double the space here, plus some slop: + runtime_attr_override = object {disk_gb: 10 + (3 * CreateSampleNameMap.total_gvcf_size_gb) + (2 * ceil(size(ref_map['fasta'], "GB")))} } # Joint call diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl index 3239ded1a..dd57899de 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -81,8 +81,8 @@ workflow LRJointCallGVCFsWithGenomicsDB { prefix = prefix, batch_size = 50, # We need to override this because we're not actually sending the GVCF over (just a list) - # ALSO, we're currently tarring the genomicsDB, so we need double the space here, plus some slop: - runtime_attr_override = object {disk_gb: 10 + (2 * CreateSampleNameMap.total_gvcf_size_gb) + (2 * ceil(size(ref_map['fasta'], "GB")))} + # ALSO, we're currently tarring the genomicsDB, so we need at least double the space here, plus some slop: + runtime_attr_override = object {disk_gb: 10 + (3 * CreateSampleNameMap.total_gvcf_size_gb) + (2 * ceil(size(ref_map['fasta'], "GB"))), preemptible_tries: 0} } # Joint call @@ -95,6 +95,7 @@ workflow LRJointCallGVCFsWithGenomicsDB { ref_dict = ref_map['dict'], dbsnp_vcf = ref_map["known_sites_vcf"], prefix = prefix, + runtime_attr_override = object {preemptible_tries: 0}, # Disable preemption for prototype. } # First make a sites-only VCF for recal (smaller file, easier to work with): @@ -102,7 +103,8 @@ workflow LRJointCallGVCFsWithGenomicsDB { input: vcf = JointCallGVCFs.output_vcf, vcf_index = JointCallGVCFs.output_vcf_index, - prefix = prefix + prefix = prefix, + runtime_attr_override = object {preemptible_tries: 0}, # Disable preemption for prototype. } # Now we run VariantRecalibrator for indels and snps: @@ -122,6 +124,7 @@ workflow LRJointCallGVCFsWithGenomicsDB { prior = [15], use_allele_specific_annotations = true, max_gaussians = 8, + runtime_attr_override = object {preemptible_tries: 0}, # Disable preemption for prototype. } call SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { @@ -140,6 +143,7 @@ workflow LRJointCallGVCFsWithGenomicsDB { prior = [15], use_allele_specific_annotations = true, max_gaussians = 8, + runtime_attr_override = object {preemptible_tries: 0}, # Disable preemption for prototype. } call ApplyVqsr as ApplyVqsr { @@ -160,6 +164,8 @@ workflow LRJointCallGVCFsWithGenomicsDB { indel_filter_level = indel_filter_level, use_allele_specific_annotations = true, + + runtime_attr_override = object {preemptible_tries: 0}, # Disable preemption for prototype. } # Now we need to annotate our variants by region: @@ -171,7 +177,9 @@ workflow LRJointCallGVCFsWithGenomicsDB { bed_files = select_first([annotation_bed_files]), bed_file_indexes = select_first([annotation_bed_file_indexes]), bed_file_annotation_names = select_first([annotation_bed_file_annotation_names]), - prefix = prefix + ".region_annotated" + prefix = prefix + ".region_annotated", + + runtime_attr_override = object {preemptible_tries: 0}, # Disable preemption for prototype. } } @@ -184,7 +192,9 @@ workflow LRJointCallGVCFsWithGenomicsDB { ref_fasta = ref_map["fasta"], ref_fai = ref_map["fai"], prefix = prefix, - outdir = outdir + outdir = outdir, + + runtime_attr_override = object {preemptible_tries: 0}, # Disable preemption for prototype. } # Finalize: @@ -403,7 +413,7 @@ task ImportGVCFs { mem_gb: 32, disk_gb: disk_size, boot_disk_gb: 15, - preemptible_tries: 0, + preemptible_tries: 1, max_retries: 1, docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } From 34a594ca1125cf7a610970b712af91223e2e951a Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 17 Jan 2023 18:10:18 -0500 Subject: [PATCH 103/297] Attempt to fix `read_int` issue. --- wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl | 9 +++++---- wdl/tasks/SRJointGenotyping.wdl | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl index dd57899de..e22a115bb 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -277,7 +277,7 @@ task CreateSampleNameMap { Int disk_size_gb = 20 String outfile_name = "~{prefix}.sample_name_map.tsv" - String size_file = "~{prefix}.total_gvcf_file_size_gb.txt" + String size_file_gb = "~{prefix}.total_gvcf_file_size_gb.txt" # Every so often we should reauthorize so `bcftools` can continue to access our data: Int re_auth_interval = 50 @@ -323,8 +323,9 @@ task CreateSampleNameMap { fi done < ${gvcf_file_list} - # Now calculate the final file size: - awk '{s += $1}END{print s}' ${size_file} > ~{size_file} + # Now calculate the final file size in GB: + # We include an additional GB in case we have a very small dataset: + awk '{s += $1}END{print int(1+s/(1024*1024*1024))}' ${size_file} > ~{size_file_gb} >>> ######################### @@ -350,7 +351,7 @@ task CreateSampleNameMap { output { File sample_name_map = outfile_name - Int total_gvcf_size_gb = ceil(read_int(size_file) / (1024 * 1024 * 1024)) # Convert bytes to GB + Int total_gvcf_size_gb = read_int(size_file_gb) } } diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 83d82c8d4..69e0e9a49 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -26,7 +26,7 @@ task CreateSampleNameMap { Int disk_size_gb = 20 String outfile_name = "~{prefix}.sample_name_map.tsv" - String size_file = "~{prefix}.total_gvcf_file_size.txt" + String size_file_gb = "~{prefix}.total_gvcf_file_size.txt" # Every so often we should reauthorize so `bcftools` can continue to access our data: Int re_auth_interval = 50 @@ -72,8 +72,9 @@ task CreateSampleNameMap { fi done < ${gvcf_file_list} - # Now calculate the final file size: - awk '{s += $1}END{print s}' ${size_file} > ~{size_file} + # Now calculate the final file size in GB: + # We include an additional GB in case we have a very small dataset: + awk '{s += $1}END{print int(1+s/(1024*1024*1024))}' ${size_file} > ~{size_file_gb} >>> ######################### @@ -99,7 +100,7 @@ task CreateSampleNameMap { output { File sample_name_map = outfile_name - Int total_gvcf_size_gb = ceil(read_int(size_file) / (1024 * 1024 * 1024)) # Convert bytes to GB + Int total_gvcf_size_gb = read_int(size_file_gb) } } From a5818a39e9d92394476085380c2cfe74e28695d5 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 23 Jan 2023 14:24:27 -0500 Subject: [PATCH 104/297] Fixed issue with disk size in Joint Calling. --- wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl | 2 +- wdl/tasks/HaplotypeCaller.wdl | 2 -- wdl/tasks/SRJointGenotyping.wdl | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl index e22a115bb..0aa8e6bb9 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB_monolithic.wdl @@ -351,7 +351,7 @@ task CreateSampleNameMap { output { File sample_name_map = outfile_name - Int total_gvcf_size_gb = read_int(size_file_gb) + Int total_gvcf_size_gb = read_int("~{size_file_gb}") } } diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index a6ca4bbd9..e60a2aae8 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -112,8 +112,6 @@ workflow CallVariantsWithHaplotypeCaller { prefix = prefix, } - ## TODO: Add VQSR here. - output { File output_gvcf = ReblockGVCF.output_gvcf File output_gvcf_index = ReblockGVCF.output_gvcf_index diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 69e0e9a49..0475fb601 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -100,7 +100,7 @@ task CreateSampleNameMap { output { File sample_name_map = outfile_name - Int total_gvcf_size_gb = read_int(size_file_gb) + Int total_gvcf_size_gb = read_int("~{size_file_gb}") } } From 8f3d56c0d90b80a466ced16b55cc1828ffa8d5d2 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 8 Feb 2023 11:56:23 -0500 Subject: [PATCH 105/297] Minor updates to variant / joint calling. - Joint calling now is no longer preemptible. - added `--annotate-with-num-discovered-alleles` flag to HaplotypeCaller. --- wdl/LRJointCallGVCFsWithGenomicsDB.wdl | 3 ++- wdl/tasks/HaplotypeCaller.wdl | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl index 714f6c6b4..545238a83 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl @@ -72,7 +72,7 @@ workflow LRJointCallGVCFsWithGenomicsDB { batch_size = 50, # We need to override this because we're not actually sending the GVCF over (just a list) # ALSO, we're currently tarring the genomicsDB, so we need at least double the space here, plus some slop: - runtime_attr_override = object {disk_gb: 10 + (3 * CreateSampleNameMap.total_gvcf_size_gb) + (2 * ceil(size(ref_map['fasta'], "GB")))} + runtime_attr_override = object {disk_gb: 10 + (3 * CreateSampleNameMap.total_gvcf_size_gb) + (2 * ceil(size(ref_map['fasta'], "GB"))), preemptible_tries: 0} } # Joint call @@ -85,6 +85,7 @@ workflow LRJointCallGVCFsWithGenomicsDB { ref_dict = ref_map['dict'], dbsnp_vcf = ref_map["known_sites_vcf"], prefix = prefix, + runtime_attr_override = object {preemptible_tries: 0}, # Disable preemption for prototype. } # First make a sites-only VCF for recal (smaller file, easier to work with): diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index e60a2aae8..db0c50ff1 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -190,6 +190,7 @@ task HaplotypeCaller_GATK4_VCF { -contamination ~{default=0 contamination} \ --sample-ploidy ~{ploidy} \ --linked-de-bruijn-graph \ + --annotate-with-num-discovered-alleles \ -GQB 10 -GQB 20 -GQB 30 -GQB 40 -GQB 50 -GQB 60 -GQB 70 -GQB 80 -GQB 90 \ ~{false="--disable-spanning-event-genotyping" true="" use_spanning_event_genotyping} \ -G StandardAnnotation -G StandardHCAnnotation ~{true="-G AS_StandardAnnotation" false="" make_gvcf} \ From f0b917745612162583ee7e47edb060b1de09aa00 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 8 Feb 2023 15:28:59 -0500 Subject: [PATCH 106/297] Adding in diagram of lrma-sp-malaria pipeline. --- .../lrma_sr_malaria_pipeline_diagram.png | Bin 0 -> 136389 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 extra_documentation/sp_malaria/lrma_sr_malaria_pipeline_diagram.png diff --git a/extra_documentation/sp_malaria/lrma_sr_malaria_pipeline_diagram.png b/extra_documentation/sp_malaria/lrma_sr_malaria_pipeline_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..d41830eda076f11240a996f041d08146f62bf5ea GIT binary patch literal 136389 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

+Z+Pe$Gx98067uccv@>P;Qx?9 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 z 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 literal 0 HcmV?d00001 From 556abc6302ba429698494c9701f7867fdec6a803 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 9 Feb 2023 13:17:11 -0500 Subject: [PATCH 107/297] Minor change to legacy monitoring script. --- scripts/monitor/legacy/vm_local_monitoring_script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/monitor/legacy/vm_local_monitoring_script.sh b/scripts/monitor/legacy/vm_local_monitoring_script.sh index b504fca70..ba154a2b8 100644 --- a/scripts/monitor/legacy/vm_local_monitoring_script.sh +++ b/scripts/monitor/legacy/vm_local_monitoring_script.sh @@ -22,7 +22,7 @@ # cloud storage folder. set -Eeuo pipefail -MONITOR_MOUNT_POINT=${MONITOR_MOUNT_POINT:-"/"} +MONITOR_MOUNT_POINT=${MONITOR_MOUNT_POINT:-"/cromwell_root"} SLEEP_TIME=${SLEEP_TIME:-"10"} function getCpuUsage() { From 740cda3907e90f37d19e7678692ff32dd1e98c4c Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 13 Feb 2023 23:44:34 -0500 Subject: [PATCH 108/297] Added `BenchmarkVCFs` and instrumentation to other tasks. --- wdl/BenchmarkVCFs.wdl | 1537 ++++++++++++++++++++++++++++++++++ wdl/tasks/AlignedMetrics.wdl | 8 + wdl/tasks/FastQC.wdl | 8 + wdl/tasks/SRUtils.wdl | 69 +- wdl/tasks/Utils.wdl | 61 ++ 5 files changed, 1682 insertions(+), 1 deletion(-) create mode 100644 wdl/BenchmarkVCFs.wdl diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl new file mode 100644 index 000000000..47800f0c3 --- /dev/null +++ b/wdl/BenchmarkVCFs.wdl @@ -0,0 +1,1537 @@ +version 1.0 + +# Borrowed and adapted from the Broad Institute's Hydrogen/Palantir repo, courtesy of Michael Gatzen: +# +# https://github.com/broadinstitute/palantir-workflows/tree/mg_benchmark_compare/BenchmarkVCFs +# +# Permalink: +# https://github.com/broadinstitute/palantir-workflows/blob/0bf48efc6de818364993e46d89591a035cfd80c7/BenchmarkVCFs/BenchmarkVCFs.wdl + +workflow Benchmark { + input{ + + File evalVcf + String evalLabel + File evalVcfIndex + + File truthVcf + String truthLabel + File truthVcfIndex + + File confidenceInterval + + File ref_map_file + String referenceVersion + + String? analysisRegion + File? hapMap + + Array[File] stratIntervals = [] + Array[String] stratLabels = [] + Array[String]? jexlVariantSelectors + Array[String]? variantSelectorLabels + + Int? threadsVcfEval=2 + Boolean doIndelLengthStratification=true + Int? preemptible + String gatkTag="4.0.11.0" + Boolean requireMatchingGenotypes=true + File? gatkJarForAnnotation + Array[String]? annotationNames + Boolean enableRefOverlap = false + Boolean passingOnly=true + String? vcfScoreField + String? dummyInputForTerraCallCaching + } + + meta { + description: "A workflow to calculate sensitivity and precision of a germline variant calling pipeline by comparing a 'call' vcf produced by the pipeline to a gold standard 'truth' vcf. Allows for stratification based on interval lists, bed files, or variant types defined according to GATK SelectVariants." + } + + parameter_meta { + evalVcf: {description: "vcfs to be evaluated"} + evalLabel: {description: "label to identify vcf to be evaluated"} + evalVcfIndex: {description: "vcf index for evalVcf"} + truthVcf: {description: "truth vcf against which to evaluate"} + truthLabel: {description: "label by which to indentify truth set"} + confidenceInterval: {description: "confidence interval for truth set (can be bed or picard interval_list)"} + ref_map_file: {description: "table indicating reference sequence and auxillary file locations" } + hapMap: {description: "reference haplotype map for CrosscheckFingerprints"} + referenceVersion: {description: "reference version used, for igv xml session (must be either 'hg19' or 'hg38')"} + stratIntervals: {description: "intervals for stratifiction (can be picard interval_list or bed format)"} + stratLabels: {description: "labels by which to identify stratification intervals (must be same length as stratIntervals)"} + jexlVariantSelectors: {description: "variant types to select over (defined by jexl fed to GATK SelectVariants)"} + variantSelectorLabels: {description: "labels by which to identify variant selectors (must be same length as jexlVariantSelectors)"} + doIndelLengthStratification: {description: "whether or not to perform stratification by indel length"} + requireMatchingGenotypes: {description: "whether to require genotypes to match in order to be a true positive"} + gatkTag: {description: "version of gatk docker to use. Defaults to 4.0.11.0"} + analysisRegion: {description: "if provided (gatk format, single interval e.g., 'chr20', or 'chr20:1-10') all the analysis will be performed only within the region."} + passingOnly: {description:"Have vcfEval only consider the passing variants"} + vcfScoreField: {description:"Have vcfEval use this field for making the roc-plot. If this is an info field (like VSQLOD) it should be provided as INFO.VQSLOD, otherewise it is assumed to be a format field."} + gatkJarForAnnotation: {description:"GATK jar that can calculate necessary annotations for jexl Selections when using VCFEval."} + annotationNames: {description:"Annotation arguments to GATK (-A argument, multiple OK)"} + dummyInputForTerraCallCaching: {description:"When running on Terra, use workspace.name as this input to ensure that all tasks will only cache hit to runs in your own workspace. This will prevent call caching from failing with 'Cache Miss (10 failed copy attempts)'. Outside of Terra this can be left empty. This dummy input is only needed for tasks that have no inputs specific to the sample being run (such as CreateIntervalList which does not take in any sample data)."} + } + + # Get ref info: + Map[String, String] ref_map = read_map(ref_map_file) + + if (defined(analysisRegion)) { + call CreateIntervalList { + input: + reference = ref_map["fasta"], + reference_index = ref_map["fai"], + reference_dict = ref_map["dict"], + interval_string = select_first([analysisRegion]), + gatkTag = gatkTag, + dummyInputForTerraCallCaching = dummyInputForTerraCallCaching + } + } + + Array[File] actualStratIntervals=flatten([[""], stratIntervals]) + Array[String] actualStratLabels=flatten([[""], stratLabels]) + Array[String] actualSelectorLabels=select_first([variantSelectorLabels,[""]]) + Array[String] actualSelectorJEXL=select_first([jexlVariantSelectors,[""]]) + + #check that lengths of different arrays are compatible + if (length(actualStratLabels)!= length(actualStratIntervals)) { + call ErrorWithMessage as Error6 { + input: + message="Stratification vcf list is length "+length(actualStratIntervals)+" while stratification labels list is length "+length(actualStratLabels) + } + } + + if (length(actualSelectorLabels) != length(actualSelectorJEXL)) { + call ErrorWithMessage as Error7 { + input: + message="Variant selector list is length "+length(actualSelectorJEXL)+" while labels list is "+length(actualSelectorLabels) + } + } + + if (defined(hapMap)) { + call MatchEvalTruth as Match { + input: + evalVcf=evalVcf, + truthVcf=truthVcf, + evalVcfIndex=evalVcfIndex, + truthVcfIndex=truthVcfIndex, + hapMap=select_first([hapMap]), + gatkTag=gatkTag, + preemptible=preemptible + } + } + Array[String] indelLabels=["deletion","insertion","indel_fine_m20","indel_fine_m19","indel_fine_m18","indel_fine_m17","indel_fine_m16","indel_fine_m15", + "indel_fine_m14","indel_fine_m13","indel_fine_m12","indel_fine_m11","indel_fine_m10","indel_fine_m9","indel_fine_m8","indel_fine_m7", + "indel_fine_m6","indel_fine_m5","indel_fine_m4","indel_fine_m3","indel_fine_m2","indel_fine_m1","indel_fine_1","indel_fine_2","indel_fine_3", + "indel_fine_4","indel_fine_5","indel_fine_6","indel_fine_7","indel_fine_8","indel_fine_9","indel_fine_10","indel_fine_11","indel_fine_12", + "indel_fine_13","indel_fine_14","indel_fine_15","indel_fine_16","indel_fine_17","indel_fine_18","indel_fine_19","indel_fine_20","indel_coarse_m30.0", + "indel_coarse_m25.0","indel_coarse_m20.0","indel_coarse_m15.0","indel_coarse_m10.0","indel_coarse_m5.0","indel_coarse_0.0","indel_coarse_5.0", + "indel_coarse_10.0","indel_coarse_15.0","indel_coarse_20.0","indel_coarse_25.0","indel_coarse_30.0"] + + Array[String] indelJexl=["vc.isSimpleIndel() && vc.getIndelLengths().0<0","vc.isSimpleIndel() && vc.getIndelLengths().0>0","vc.isSimpleIndel() && vc.getIndelLengths().0==-20", + "vc.isSimpleIndel() && vc.getIndelLengths().0==-19","vc.isSimpleIndel() && vc.getIndelLengths().0==-18","vc.isSimpleIndel() && vc.getIndelLengths().0==-17", + "vc.isSimpleIndel() && vc.getIndelLengths().0==-16","vc.isSimpleIndel() && vc.getIndelLengths().0==-15","vc.isSimpleIndel() && vc.getIndelLengths().0==-14", + "vc.isSimpleIndel() && vc.getIndelLengths().0==-13","vc.isSimpleIndel() && vc.getIndelLengths().0==-12","vc.isSimpleIndel() && vc.getIndelLengths().0==-11", + "vc.isSimpleIndel() && vc.getIndelLengths().0==-10","vc.isSimpleIndel() && vc.getIndelLengths().0==-9","vc.isSimpleIndel() && vc.getIndelLengths().0==-8", + "vc.isSimpleIndel() && vc.getIndelLengths().0==-7","vc.isSimpleIndel() && vc.getIndelLengths().0==-6","vc.isSimpleIndel() && vc.getIndelLengths().0==-5", + "vc.isSimpleIndel() && vc.getIndelLengths().0==-4","vc.isSimpleIndel() && vc.getIndelLengths().0==-3","vc.isSimpleIndel() && vc.getIndelLengths().0==-2", + "vc.isSimpleIndel() && vc.getIndelLengths().0==-1","vc.isSimpleIndel() && vc.getIndelLengths().0==1","vc.isSimpleIndel() && vc.getIndelLengths().0==2", + "vc.isSimpleIndel() && vc.getIndelLengths().0==3","vc.isSimpleIndel() && vc.getIndelLengths().0==4","vc.isSimpleIndel() && vc.getIndelLengths().0==5", + "vc.isSimpleIndel() && vc.getIndelLengths().0==6","vc.isSimpleIndel() && vc.getIndelLengths().0==7","vc.isSimpleIndel() && vc.getIndelLengths().0==8", + "vc.isSimpleIndel() && vc.getIndelLengths().0==9","vc.isSimpleIndel() && vc.getIndelLengths().0==10","vc.isSimpleIndel() && vc.getIndelLengths().0==11", + "vc.isSimpleIndel() && vc.getIndelLengths().0==12","vc.isSimpleIndel() && vc.getIndelLengths().0==13","vc.isSimpleIndel() && vc.getIndelLengths().0==14", + "vc.isSimpleIndel() && vc.getIndelLengths().0==15","vc.isSimpleIndel() && vc.getIndelLengths().0==16","vc.isSimpleIndel() && vc.getIndelLengths().0==17", + "vc.isSimpleIndel() && vc.getIndelLengths().0==18","vc.isSimpleIndel() && vc.getIndelLengths().0==19","vc.isSimpleIndel() && vc.getIndelLengths().0==20", + "vc.isSimpleIndel() && vc.getIndelLengths().0<-27.5 && vc.getIndelLengths().0>-32.5","vc.isSimpleIndel() && vc.getIndelLengths().0<-22.5 && vc.getIndelLengths().0>-27.5", + "vc.isSimpleIndel() && vc.getIndelLengths().0<-17.5 && vc.getIndelLengths().0>-22.5","vc.isSimpleIndel() && vc.getIndelLengths().0<-12.5 && vc.getIndelLengths().0>-17.5", + "vc.isSimpleIndel() && vc.getIndelLengths().0<-7.5 && vc.getIndelLengths().0>-12.5","vc.isSimpleIndel() && vc.getIndelLengths().0<-2.5 && vc.getIndelLengths().0>-7.5", + "vc.isSimpleIndel() && vc.getIndelLengths().0<2.5 && vc.getIndelLengths().0>-2.5","vc.isSimpleIndel() && vc.getIndelLengths().0<7.5 && vc.getIndelLengths().0>2.5", + "vc.isSimpleIndel() && vc.getIndelLengths().0<12.5 && vc.getIndelLengths().0>7.5","vc.isSimpleIndel() && vc.getIndelLengths().0<17.5 && vc.getIndelLengths().0>12.5", + "vc.isSimpleIndel() && vc.getIndelLengths().0<22.5 && vc.getIndelLengths().0>17.5","vc.isSimpleIndel() && vc.getIndelLengths().0<27.5 && vc.getIndelLengths().0>22.5", + "vc.isSimpleIndel() && vc.getIndelLengths().0<32.5 && vc.getIndelLengths().0>27.5"] + + scatter (indel in zip(indelLabels,indelJexl)) { + VariantSelector indelSelectors = object{ jexl : indel.right, + label : indel.left + } + } + + if (defined(jexlVariantSelectors)) { + scatter (select in zip(actualSelectorLabels,actualSelectorJEXL)) { + VariantSelector variantSelectors = object{ jexl: select.right, + label : select.left + } + } + } + + Array[VariantSelector] defaultVS = [object{ jexl: "vc.isIndel() && vc.getHetCount() == 1", label: "HetIndel" }, + object{jexl: "vc.isIndel() && vc.getHomVarCount() == 1", label: "HomVarIndel"}, + object{jexl: "vc.isSNP() && vc.getHetCount() == 1", label: "HetSNP"}, + object{jexl: "vc.isSNP() && vc.getHomVarCount() == 1", label: "HomVarSNP"}] + Array[VariantSelector] actualVariantSelectors = flatten(select_all([defaultVS,variantSelectors])) + + if (defined(stratIntervals)) { + scatter (stratIL in actualStratIntervals) { + if(stratIL!="") { + call ConvertIntervals as StratConvertIntervals { + input: + inputIntervals=stratIL, + refDict=ref_map["dict"], + gatkTag=gatkTag, + subset_interval=CreateIntervalList.interval_list, + preemptible=preemptible, + dummyInputForTerraCallCaching=dummyInputForTerraCallCaching + + } + } + } + } + Array[File] stratBeds=select_all(flatten(select_all([[""],StratConvertIntervals.bed]))) + Array[File] stratILs=select_all(flatten(select_all([[""],StratConvertIntervals.intervalList]))) + + scatter (strat in zip(zip(stratILs,stratBeds),actualStratLabels)) { + Stratifier stratifiers = object {intervalList : strat.left.left, + bed : strat.left.right, + label : strat.right + } + } + + call ConvertIntervals as ConfidenceConvertIntervals { + input: + inputIntervals=confidenceInterval, + refDict=ref_map["dict"], + gatkTag=gatkTag, + preemptible=preemptible, + subset_interval=CreateIntervalList.interval_list, + dummyInputForTerraCallCaching=dummyInputForTerraCallCaching + } + + scatter (stratifier in stratifiers) { + + if (stratifier.label != "" && stratifier.intervalList != "") { + String stratLabel=select_first([stratifier.label,""]) + File stratIL=select_first([stratifier.intervalList,""]) + File stratBed=select_first([stratifier.bed,""]) + String outputPreStrat=evalLabel+"_"+truthLabel+"_"+stratLabel + } + String outputPrefix=select_first([outputPreStrat,evalLabel+"_"+truthLabel]) + + + + call CheckForVariants as CheckForVariantsEval { + input: + vcf=evalVcf, + vcfIndex=evalVcfIndex, + confidenceIL=ConfidenceConvertIntervals.intervalList, + stratIL=stratIL, + gatkTag=gatkTag, + preemptible=preemptible + } + + call CheckForVariants as CheckForVariantsTruth { + input: + vcf=truthVcf, + vcfIndex=truthVcfIndex, + confidenceIL=ConfidenceConvertIntervals.intervalList, + stratIL=stratIL, + gatkTag=gatkTag, + preemptible=preemptible + } + + if (CheckForVariantsTruth.variantsFound && CheckForVariantsEval.variantsFound) { + call VcfEval as StandardVcfEval { + input: + truthVCF=truthVcf, + truthVCFIndex=truthVcfIndex, + evalVCF=evalVcf, + evalVCFIndex=evalVcfIndex, + confidenceBed=ConfidenceConvertIntervals.bed, + stratBed=stratBed, + ref=ref_map["fasta"], + refDict=ref_map["dict"], + refIndex=ref_map["fai"], + outputPre=outputPrefix+"_vcfeval", + threads=threadsVcfEval, + preemptible=preemptible, + requireMatchingGenotypes=requireMatchingGenotypes, + passingOnly=passingOnly, + vcfScoreField=vcfScoreField, + enableRefOverlap=enableRefOverlap + } + + call WriteXMLfile as VcfEvalWriteXMLfile { + input: + input_files=select_all([StandardVcfEval.outVcf,ConfidenceConvertIntervals.bed,stratifier.bed]), + input_names=select_all([outputPrefix+"_vcfeval","confidence_intervals",stratifier.label]), + reference_version=referenceVersion, + file_name=outputPrefix+"_vcfeval" + } + + call CountUNKVcfEval { + input: + vcf=StandardVcfEval.outVcf, + vcfIndex=StandardVcfEval.outVcfIndex, + gatkTag=gatkTag, + preemptible=preemptible + } + } + + String areVariants=if(CheckForVariantsTruth.variantsFound && CheckForVariantsEval.variantsFound) then "yes" else "no" + call SummariseVcfEval { + input: + evalLabel=evalLabel, + truthLabel=truthLabel, + stratLabel=stratLabel, + summaryFile=StandardVcfEval.outSummary, + igvSession=VcfEvalWriteXMLfile.igv_session, + areVariants=areVariants, + unkSNP=CountUNKVcfEval.UNK_SNP, + unkINDEL=CountUNKVcfEval.UNK_INDEL, + preemptible=preemptible + } + } + + scatter ( i in range(length(stratifiers)) ) { + AnnotatedVcfs annotatedVcfsList = object{vcfVcfEval : StandardVcfEval.outVcf[i], + vcfVcfEvalIndex : StandardVcfEval.outVcfIndex[i], + stratLabel : stratifiers[i].label, + evalLabel : evalLabel, + truthLabel : truthLabel, + stratBed : stratBed[i], + confidenceBed : ConfidenceConvertIntervals.bed, + namePrefix : outputPrefix[i] + } + } + + + scatter (indelCombo in cross(annotatedVcfsList,indelSelectors)) { + EvalStratSelectorCombo evalStratIndelCombos = object{annotatedVcfs : indelCombo.left, + variantSelector : indelCombo.right + } + } + + scatter (evalStratIndelCombo in evalStratIndelCombos) { + String jexl=evalStratIndelCombo.variantSelector.jexl + File? vcfVcfEval=evalStratIndelCombo.annotatedVcfs.vcfVcfEval + File? vcfVcfEvalIndex=evalStratIndelCombo.annotatedVcfs.vcfVcfEvalIndex + String evalIndelLabel=evalStratIndelCombo.annotatedVcfs.evalLabel + String truthIndelLabel=evalStratIndelCombo.annotatedVcfs.truthLabel + String? stratIndelLabel=evalStratIndelCombo.annotatedVcfs.stratLabel + String indelLabel=evalStratIndelCombo.variantSelector.label + File? stratIndelBed=evalStratIndelCombo.annotatedVcfs.stratBed + File? confidenceBed=evalStratIndelCombo.annotatedVcfs.confidenceBed + String namePrefix=evalStratIndelCombo.annotatedVcfs.namePrefix+"_"+indelLabel + + if (defined(vcfVcfEval) && defined(vcfVcfEvalIndex) && doIndelLengthStratification) { + call EvalForVariantSelection as EvalIndelLengthVcfEval { + input: + vcf=vcfVcfEval, + vcfIndex=vcfVcfEvalIndex, + jexl=jexl, + engine="VcfEval", + selectTPCall="CALL == 'TP'", + selectTPBase="BASE == 'TP'", + selectFN="(BASE == 'FN' || BASE == 'FN_CA')", + selectFP="(CALL == 'FP' || CALL == 'FP_CA')", + sampleCall="CALLS", + sampleBase="BASELINE", + gatkTag=gatkTag, + preemptible=preemptible, + gatkJarForAnnotation=gatkJarForAnnotation, + annotationNames=annotationNames, + reference=ref_map["fasta"], + refDict=ref_map["dict"], + refIndex=ref_map["fai"] + } + + call WriteXMLfile as VcfEvalIndelWriteXMLfile { + input: + input_files=select_all([EvalIndelLengthVcfEval.selectedTPCall,EvalIndelLengthVcfEval.selectedTPBase,EvalIndelLengthVcfEval.selectedFP,EvalIndelLengthVcfEval.selectedFN,vcfVcfEval,confidenceBed,stratIndelBed]), + input_names=select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",stratIndelLabel]), + reference_version=referenceVersion, + file_name=namePrefix+"_vcfeval" + } + + call SummariseForIndelSelection as VcfEvalSummariseForIndelSelection { + input: + evalLabel=evalIndelLabel, + truthLabel=truthIndelLabel, + stratLabel=stratIndelLabel, + indelLabel=indelLabel, + engine="VcfEval", + igvSession=VcfEvalIndelWriteXMLfile.igv_session, + TP_CALL=EvalIndelLengthVcfEval.TP_CALL, + TP_BASE=EvalIndelLengthVcfEval.TP_BASE, + FP=EvalIndelLengthVcfEval.FP, + FN=EvalIndelLengthVcfEval.FN, + preemptible=preemptible + } + } + } + + + + + scatter (selectorCombo in cross(annotatedVcfsList,actualVariantSelectors)) { + EvalStratSelectorCombo evalStratSelectorCombos = object{annotatedVcfs : selectorCombo.left, + variantSelector : selectorCombo.right + } + } + scatter (evalStratSelectorCombo in evalStratSelectorCombos) { + if (defined(evalStratSelectorCombo.annotatedVcfs.vcfVcfEval) && defined(evalStratSelectorCombo.annotatedVcfs.vcfVcfEvalIndex)) { + call EvalForVariantSelection as EvalSelectorVcfEval { + input: + vcf=evalStratSelectorCombo.annotatedVcfs.vcfVcfEval, + vcfIndex=evalStratSelectorCombo.annotatedVcfs.vcfVcfEvalIndex, + jexl=evalStratSelectorCombo.variantSelector.jexl, + engine="VcfEval", + selectTPCall="CALL == 'TP'", + selectTPBase="BASE == 'TP'", + selectFN="(BASE == 'FN' || BASE == 'FN_CA')", + selectFP="(CALL == 'FP' || CALL == 'FP_CA')", + sampleCall="CALLS", + sampleBase="BASELINE", + gatkTag=gatkTag, + preemptible=preemptible, + gatkJarForAnnotation=gatkJarForAnnotation, + annotationNames=annotationNames, + reference=ref_map["fasta"], + refDict=ref_map["dict"], + refIndex=ref_map["fai"] + } + call WriteXMLfile as VcfEvalSelectorWriteXMLfile { + input: + input_files=select_all([EvalSelectorVcfEval.selectedTPCall,EvalSelectorVcfEval.selectedTPBase,EvalSelectorVcfEval.selectedFP,EvalSelectorVcfEval.selectedFN, + evalStratSelectorCombo.annotatedVcfs.vcfVcfEval,evalStratSelectorCombo.annotatedVcfs.confidenceBed,evalStratSelectorCombo.annotatedVcfs.stratBed]), + input_names=select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",evalStratSelectorCombo.annotatedVcfs.stratLabel]), + reference_version=referenceVersion, + file_name=evalStratSelectorCombo.annotatedVcfs.namePrefix+"_"+evalStratSelectorCombo.variantSelector.label+"_vcfeval" + } + + call SummariseForVariantSelection as VcfEvalSummariseForVariantSelection { + input: + evalLabel=evalStratSelectorCombo.annotatedVcfs.evalLabel, + truthLabel=evalStratSelectorCombo.annotatedVcfs.truthLabel, + stratLabel=evalStratSelectorCombo.annotatedVcfs.stratLabel, + variantLabel=evalStratSelectorCombo.variantSelector.label, + engine="VcfEval", + igvSession=VcfEvalSelectorWriteXMLfile.igv_session, + TP_CALL=EvalSelectorVcfEval.TP_CALL, + TP_BASE=EvalSelectorVcfEval.TP_BASE, + FP=EvalSelectorVcfEval.FP, + FN=EvalSelectorVcfEval.FN, + preemptible=preemptible + } + } + } + + Array[File] summaries = flatten([SummariseVcfEval.summaryOut,select_all(VcfEvalSummariseForVariantSelection.summaryOut), + select_all(VcfEvalSummariseForIndelSelection.summaryOut)]) + + call CombineSummaries { + input: + summaries=summaries, + preemptible=preemptible + } + + ################################################################################ + + output { + File summary=CombineSummaries.summaryOut + Float snpPrecision=SummariseVcfEval.snpPrecision[0] + Float indelPrecision=SummariseVcfEval.indelPrecision[0] + Float snpRecall=SummariseVcfEval.snpRecall[0] + Float indelRecall=SummariseVcfEval.indelRecall[0] + Float snpF1Score=SummariseVcfEval.snpF1Score[0] + Float indelF1Score=SummariseVcfEval.indelF1Score[0] + Array[File?] snpRocs=StandardVcfEval.outSnpRoc + Array[File?] nonSnpRocs=StandardVcfEval.outNonSnpRoc + } +} + +################################################################################ +################################################################################ +################################################################################ + +struct EvalTruthMatch { + File truthVcf + File truthVcfIndex + File confidenceIntervals + String truthLabel + File evalVcf + File evalVcfIndex + String evalLabel +} + +struct VariantSelector { + String jexl + String label +} + +struct Stratifier { + File? intervalList + File? bed + String? label +} + +struct EvalStratCombo { + EvalTruthMatch evalTruthMatch + Stratifier stratifier +} + +struct AnnotatedVcfs { + File? vcfVcfEval + File? vcfVcfEvalIndex + String? stratLabel + String evalLabel + String truthLabel + File? stratBed + File? confidenceBed + String namePrefix +} +struct EvalStratSelectorCombo { + AnnotatedVcfs annotatedVcfs + VariantSelector variantSelector +} + +#Check to see if there are variants in the given vcf which overlap the confidence and stratification intervals +task CheckForVariants { + input{ + File vcf + File vcfIndex + File confidenceIL + File? stratIL + Int? preemptible + Int? memoryMaybe + String gatkTag + } + Int memoryDefault=16 + Int memoryJava=select_first([memoryMaybe,memoryDefault]) + Int memoryRam=memoryJava+2 + + Int disk_size = 10 + ceil(size(vcf, "GB") + size(vcfIndex, "GB") + size(confidenceIL, "GB") + size(stratIL, "GB")) + + command <<< + set -xeuo pipefail + + nVariants="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V ~{vcf} -L ~{confidenceIL} ~{"-L " + stratIL} -isr INTERSECTION | tail -1)" + if [ "$nVariants" -gt "0" ]; then echo "true" > outBool.txt; else echo "false" > outBool.txt; fi + >>> + + runtime { + docker: "us.gcr.io/broad-gatk/gatk:"+gatkTag + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + bootDiskSizeGb: "16" + memory: memoryRam + " GB" + } + + output { + Boolean variantsFound=read_boolean("outBool.txt") + } +} + +#Evaluate evalVCF against truthVCF using vcfeval +task VcfEval { + input{ + File truthVCF + File truthVCFIndex + File evalVCF + File evalVCFIndex + File confidenceBed + File? stratBed + File ref + File refDict + File refIndex + String outputPre + Boolean passingOnly + String? vcfScoreField + Int? preemptible + String? memUser + Int? threads + Boolean requireMatchingGenotypes + Boolean enableRefOverlap = false + } + String memDefault="16 GB" + String mem=select_first([memUser,memDefault]) + + Int cpu=select_first([threads,1]) + Int disk_size = 50 + ceil(size(truthVCF, "GB") + size(truthVCFIndex, "GB") + 2.2 * size(evalVCF, "GB") + size(evalVCFIndex, "GB") + size(confidenceBed, "GB") + size(stratBed, "GB") + size(ref, "GB") + size(refDict, "GB") + size(refIndex, "GB")) + + command <<< + set -xeuo pipefail + + /bin/rtg-tools/rtg format -o rtg_ref ~{ref} + /bin/rtg-tools/rtg vcfeval \ + ~{false="--all-records" true="" passingOnly} \ + ~{"--vcf-score-field=" + vcfScoreField} \ + ~{false="--squash-ploidy" true="" requireMatchingGenotypes} \ + ~{true="--ref-overlap" false="" enableRefOverlap} \ + -b ~{truthVCF} -c ~{evalVCF} \ + -e ~{confidenceBed} ~{"--bed-regions " + stratBed} \ + --output-mode combine --decompose -t rtg_ref \ + ~{"--threads "+threads} -o output_dir + + for f in output_dir/*; do + mv $f ~{outputPre}_"$(basename "$f")"; + done + + /bin/rtg-tools/rtg rocplot --precision-sensitivity --title="~{outputPre} SNP" --svg=~{outputPre}.snp.svg ~{outputPre}_snp_roc.tsv.gz + /bin/rtg-tools/rtg rocplot --precision-sensitivity --title="~{outputPre} INDEL" --svg=~{outputPre}.indel.svg ~{outputPre}_non_snp_roc.tsv.gz + + python3 -<<"EOF" ~{outputPre}_snp_roc.tsv.gz ~{outputPre}_non_snp_roc.tsv.gz ~{outputPre}_summary.csv + import gzip + import sys + + indel_sensitivity=0 + indel_precision=0 + indel_fscore=0 + indel_TP_Base=0 + indel_TP_Eval=0 + indel_FP=0 + indel_FN=0 + + snp_sensitivity=0 + snp_precision=0 + snp_fscore=0 + snp_TP_Base=0 + snp_TP_Eval=0 + snp_FP=0 + snp_FN=0 + + with gzip.open(sys.argv[1],"rt") as f_snp: + for line in f_snp: + try: + snp_sensitivity=float(line.split()[6]) + snp_precision=float(line.split()[5]) + snp_fscore=float(line.split()[7]) + snp_TP_Eval=float(line.split()[3]) + snp_TP_Base=float(line.split()[1]) + snp_FP=float(line.split()[2]) + snp_FN=float(line.split()[4]) + except ValueError: + continue + except IndexError: + continue + f_snp.close() + with gzip.open(sys.argv[2],"rt") as f_indel: + for line in f_indel: + try: + indel_sensitivity=float(line.split()[6]) + indel_precision=float(line.split()[5]) + indel_fscore=float(line.split()[7]) + indel_TP_Eval=float(line.split()[3]) + indel_TP_Base=float(line.split()[1]) + indel_FP=float(line.split()[2]) + indel_FN=float(line.split()[4]) + except ValueError: + continue + except IndexError: + continue + f_indel.close() + + str_indel_sensitivity=str(indel_sensitivity) + str_indel_precision=str(indel_precision) + str_indel_fscore=str(indel_fscore) + str_snp_sensitivity=str(snp_sensitivity) + str_snp_precision=str(snp_precision) + str_snp_fscore=str(snp_fscore) + + + if indel_TP_Eval+indel_FP==0: + str_indel_precision="NA" + if indel_TP_Base+indel_FN==0: + str_indel_sensitivity="NA" + if str_indel_sensitivity=="NA" or str_indel_precision=="NA": + str_indel_fscore="NA" + + if snp_TP_Eval+snp_FP==0: + str_snp_precision="NA" + if snp_TP_Base+snp_FN==0: + str_snp_sensitivity="NA" + if str_snp_sensitivity=="NA" or str_snp_precision=="NA": + str_snp_fscore="NA" + + + + with open(sys.argv[3],"wt") as f_out: + f_out.write(",".join(["Type","Precision","Recall","F1_Score","TP_Eval","TP_Base","FP","FN"])+"\n") + f_out.write(",".join(["SNP",str_snp_precision,str_snp_sensitivity,str_snp_fscore,str(snp_TP_Eval),str(snp_TP_Base),str(snp_FP),str(snp_FN)])+"\n") + f_out.write(",".join(["INDEL",str_indel_precision,str_indel_sensitivity,str_indel_fscore,str(indel_TP_Eval),str(indel_TP_Base),str(indel_FP),str(indel_FN)])+"\n") + f_out.close() + EOF + >>> + + runtime { + docker: "ckachulis/rtg-tools:0.1" + preemptible: select_first([preemptible,0]) + memory: mem + cpu: cpu + disks: "local-disk " + disk_size + " HDD" + } + + output { + Array[File] outs=glob("${outputPre}_*") + File outSummary="${outputPre}_summary.csv" + File outVcf="${outputPre}_output.vcf.gz" + File outVcfIndex="${outputPre}_output.vcf.gz.tbi" + File outSnpRocPlot="~{outputPre}.snp.svg" + File outNonRocPlot="~{outputPre}.indel.svg" + File outSnpRoc="${outputPre}_snp_roc.tsv.gz" + File outNonSnpRoc="${outputPre}_non_snp_roc.tsv.gz" + } +} + +#Evaluate evalVCF against truthVCF using hap.py +task EvalHappy { + input{ + File truthVCF + File truthVCFIndex + File evalVCF + File confidenceBed + File? stratBed + File ref + File refDict + File refIndex + String outputPre + String? memUser + Int? preemptible + Int? threads + String happyTag + } + String memDefault="16 GB" + String mem=select_first([memUser,memDefault]) + + Int cpu=select_first([threads,1]) + Int disk_size = 10 + ceil(size(truthVCF, "GB") + size(truthVCFIndex, "GB") + 2.2 * size(evalVCF, "GB") + size(confidenceBed, "GB") + size(stratBed, "GB") + size(ref, "GB") + size(refDict, "GB") + size(refIndex, "GB")) + + + command <<< + /opt/hap.py/bin/hap.py ~{truthVCF} ~{evalVCF} -f ~{confidenceBed} -r ~{ref} -V ~{"-T " + stratBed} -L --preprocess-truth ~{"--threads "+threads} -o ~{outputPre} + >>> + + runtime { + docker: "pkrusche/hap.py:"+happyTag + memory: mem + preemptible: select_first([preemptible,0]) + cpu: cpu + disks: "local-disk " + disk_size + " HDD" + } + + output { + Array[File] outs=glob("${outputPre}*") + File outSummary="${outputPre}.summary.csv" + File outVcf="${outputPre}.vcf.gz" + File outVcfIndex="${outputPre}.vcf.gz.tbi" + } +} + +#Evaluate evalVCF against truthVCF using picard GenotypeConcordance +task EvalGATKGC { + input{ + File truthVCF + File truthVCFIndex + File evalVCF + File evalVCFIndex + File intervalList + File? stratIL + File ref + File refDict + File refIndex + String outputPre + Int? preemptible + Int? memoryMaybe + String gatkTag + } + Int memoryDefault=16 + Int memoryJava=select_first([memoryMaybe,memoryDefault]) + Int memoryRam=memoryJava+2 + Int disk_size = 10 + ceil(size(truthVCF, "GB") + size(truthVCFIndex, "GB") + 2.2 * size(evalVCF, "GB") + size(intervalList, "GB") + size(stratIL, "GB") + size(evalVCFIndex, "GB") + size(ref, "GB") + size(refDict, "GB") + size(refIndex, "GB")) + + + command <<< + gatk --java-options "-Xmx~{memoryJava}G" GenotypeConcordance -TV ~{truthVCF} -CV ~{evalVCF} -R ~{ref} -INTERVALS ~{intervalList} ~{"-INTERVALS " + stratIL} -USE_VCF_INDEX -OUTPUT_VCF -O ~{outputPre} + >>> + + runtime { + docker: "us.gcr.io/broad-gatk/gatk:"+gatkTag + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + bootDiskSizeGb: "16" + memory: memoryRam + " GB" + } + + output { + Array[File] out=glob("${outputPre}*") + File outSummary="${outputPre}.genotype_concordance_summary_metrics" + File outCounts="${outputPre}.genotype_concordance_contingency_metrics" + File outVcf="${outputPre}.genotype_concordance.vcf.gz" + File outVcfIndex="${outputPre}.genotype_concordance.vcf.gz.tbi" + } +} + +#takes in either a .bed or .intervallist and returns both a .bed and .intervallist version of the input +task ConvertIntervals { + input { + File inputIntervals + File? subset_interval + Int? preemptible + Int? memoryMaybe + File refDict + String gatkTag + String? dummyInputForTerraCallCaching + } + + Int memoryDefault=16 + Int memoryJava=select_first([memoryMaybe,memoryDefault]) + Int memoryRam=memoryJava+2 + Int disk_size = 10 + ceil(3 * size(inputIntervals, "GB") + size(refDict, "GB")) + + command <<< + set -xeuo pipefail + + # convert bed to interval_list, or copy interval_list + if [[ ~{inputIntervals} == *.bed || ~{inputIntervals} == *.bed.gz ]]; then + gatk --java-options "-Xmx~{memoryJava}G" \ + BedToIntervalList \ + -I ~{inputIntervals} \ + -O initial_intervals.interval_list \ + -SD ~{refDict} + else + cp ~{inputIntervals} initial_intervals.interval_list + fi + + # optionally intersect interval_list with subset_interval + + if [ ! -z ~{subset_interval} ]; then + gatk --java-options "-Xmx~{memoryJava}G" \ + IntervalListTools \ + -I initial_intervals.interval_list \ + -I ~{subset_interval} \ + -ACTION INTERSECT \ + -O intervals.interval_list + else + mv initial_intervals.interval_list intervals.interval_list + fi + + # convert result to BED + gatk --java-options "-Xmx~{memoryJava}G" \ + IntervalListToBed \ + -I intervals.interval_list \ + -O intervals.bed + >>> + + runtime { + docker: "us.gcr.io/broad-gatk/gatk:"+gatkTag + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + bootDiskSizeGb: "16" + memory: memoryRam + " GB" + } + + output { + File bed="intervals.bed" + File intervalList="intervals.interval_list" + } +} + +#For now, due to a bug, the ouput annotated VCF from hap.py has a hardcoded HG19 header, so the VCF header must be fixed to represent the correct reference. +#Additionally, due to a separate bug, UpdateVCFSequenceDictionary crashes in this case when trying to index a bgzipped vcf. So for now have to perform indexing in a separate command. +task FixVcfHeader { + input { + File vcf + File vcfIndex + File ref + File refDict + File refIndex + Int? preemptible + Int? memoryMaybe + String gatkTag + } + Int memoryDefault=16 + Int memoryJava=select_first([memoryMaybe,memoryDefault]) + Int memoryRam=memoryJava+2 + Int disk_size = 10 + ceil(2.2 * size(vcf, "GB") + 2.2 * size(vcfIndex, "GB") + size(ref, "GB") + size(refDict, "GB") + size(refIndex, "GB")) + + command <<< + set -xeuo pipefail + + gatk --java-options "-Xmx~{memoryJava}G" UpdateVCFSequenceDictionary -V ~{vcf} -O fixed.vcf.gz --source-dictionary ~{refDict} --replace --create-output-variant-index false + gatk --java-options "-Xmx~{memoryJava}G" IndexFeatureFile -F fixed.vcf.gz + >>> + + runtime { + docker: "us.gcr.io/broad-gatk/gatk:"+gatkTag + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + bootDiskSizeGb: "16" + memory: memoryRam + " GB" + } + + output { + File outVcf="fixed.vcf.gz" + File outVcfIndex="fixed.vcf.gz.tbi" + } + +} + +#Count number of variants which were outside confidence region based on vcfeval annotated vcf +task CountUNKVcfEval { + input { + File? vcf="" + File? vcfIndex="" + Int? preemptible + Int? memoryMaybe + String gatkTag + } + Int memoryDefault=16 + Int memoryJava=select_first([memoryMaybe,memoryDefault]) + Int memoryRam=memoryJava+2 + Int disk_size = 10 + ceil(size(vcf, "GB") + size(vcfIndex, "GB")) + + command <<< + set -xeuo pipefail + + gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V ~{vcf} -O selected.unk.snp.vcf.gz -select "(CALL == 'OUT')" --select-type-to-include SNP + gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V ~{vcf} -O selected.unk.indel.vcf.gz -select "(CALL == 'OUT')" --select-type-to-include INDEL + + UNK_SNP="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.unk.snp.vcf.gz | tail -1)" + UNK_INDEL="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.unk.indel.vcf.gz | tail -1)" + + echo "$UNK_SNP" > unk_snp.txt + echo "$UNK_INDEL" > unk_indel.txt + >>> + runtime { + docker: "us.gcr.io/broad-gatk/gatk:"+gatkTag + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + bootDiskSizeGb: "16" + memory: memoryRam + " GB" + } + output { + Int UNK_SNP=read_int("unk_snp.txt") + Int UNK_INDEL=read_int("unk_indel.txt") + } +} + +#Count number of variants which were outside confidence region based on GenotypeConcordance annotated vcf +task CountUNKGC { + input { + File? vcfAnnotated="" + File? vcfIndexAnnotated="" + File vcfOrig + File vcfIndexOrig + Int? preemptible + Int? memoryMaybe + File? stratIL + String gatkTag + } + Int memoryDefault=16 + Int memoryJava=select_first([memoryMaybe,memoryDefault]) + Int memoryRam=memoryJava+2 + Int disk_size = 10 + ceil(size(vcfAnnotated, "GB") + size(vcfIndexAnnotated, "GB") + 2 * size(vcfOrig, "GB") + size(vcfIndexOrig, "GB") + size(stratIL, "GB")) + + command <<< + set -xeuo pipefail + + gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V ~{vcfOrig} -O selected.unk.snp.vcf.gz ~{"-L "+stratIL} --select-type-to-include SNP --discordance ~{vcfAnnotated} + gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V ~{vcfOrig} -O selected.unk.indel.vcf.gz ~{"-L "+stratIL} --select-type-to-include INDEL --discordance ~{vcfAnnotated} + + UNK_SNP="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.unk.snp.vcf.gz | tail -1)" + UNK_INDEL="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.unk.indel.vcf.gz | tail -1)" + + echo "$UNK_SNP" > unk_snp.txt + echo "$UNK_INDEL" > unk_indel.txt + >>> + + + runtime { + docker: "us.gcr.io/broad-gatk/gatk:"+gatkTag + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + bootDiskSizeGb: "16" + memory: memoryRam + " GB" + } + + output { + Int UNK_SNP=read_int("unk_snp.txt") + Int UNK_INDEL=read_int("unk_indel.txt") + } +} + +#Count TP,FP,FN for a particular selection of variants given by jexl +task EvalForVariantSelection { + input { + File? vcf="" + File? vcfIndex="" + String jexl + Int? preemptible + Int? memoryMaybe + String engine + String selectTPCall + String selectTPBase + String selectFN + String selectFP + String sampleCall + String sampleBase + String gatkTag + + Array[String] annotationNames=[] + File? gatkJarForAnnotation + File reference + File refDict + File refIndex + } + + Int memoryDefault=16 + Int memoryJava=select_first([memoryMaybe,memoryDefault]) + Int memoryRam=memoryJava+2 + + String selectionTPCall=jexl + " && " + selectTPCall + String selectionTPBase=jexl + " && " + selectTPBase + String selectionFN=jexl + " && " + selectFN + String selectionFP=jexl + " && " + selectFP + + Int disk_size = 10 + ceil(4.2 * size(vcf, "GB") + 2.2 * size(vcfIndex, "GB") + size(reference, "GB")) + + command <<< + set -xeuo pipefail + + VCF=~{vcf} + if [[ ! -z "~{gatkJarForAnnotation}" ]]; then + java -jar ~{gatkJarForAnnotation} VariantAnnotator -V ~{vcf} -O annotated.vcf.gz ~{true="-A" false="" length(annotationNames)>0} ~{sep=" -A " annotationNames} -R ~{reference} + VCF=annotated.vcf.gz + else + touch annotated.vcf.gz + fi + + gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V $VCF -O selected.TP_CALL.vcf.gz -select "~{selectionTPCall}" -sn ~{sampleCall} + gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V $VCF -O selected.TP_BASE.vcf.gz -select "~{selectionTPBase}" -sn ~{sampleBase} + gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V $VCF -O selected.FN.vcf.gz -select "~{selectionFN}" -sn ~{sampleBase} + gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V $VCF -O selected.FP.vcf.gz -select "~{selectionFP}" -sn ~{sampleCall} + + TP_CALL="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.TP_CALL.vcf.gz | tail -1)" + TP_BASE="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.TP_BASE.vcf.gz | tail -1)" + FN="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.FN.vcf.gz | tail -1)" + FP="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.FP.vcf.gz | tail -1)" + + echo "$TP_CALL" > tp_call.txt + echo "$TP_BASE" > tp_base.txt + echo "$FN" > fn.txt + echo "$FP" > fp.txt + >>> + + runtime { + docker: "us.gcr.io/broad-gatk/gatk:"+gatkTag + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + bootDiskSizeGb: "16" + memory: memoryRam + " GB" + } + + output { + Int TP_CALL=read_int("tp_call.txt") + Int TP_BASE=read_int("tp_base.txt") + Int FP=read_int("fp.txt") + Int FN=read_int("fn.txt") + File selectedTPCall="selected.TP_CALL.vcf.gz" + File selectedTPBase="selected.TP_BASE.vcf.gz" + File selectedFP="selected.FP.vcf.gz" + File selectedFN="selected.FN.vcf.gz" + + File annotated="annotated.vcf.gz" + File selectedTPCallIndex="selected.TP_CALL.vcf.gz.tbi" + File selectedTPBaseIndex="selected.TP_BASE.vcf.gz.tbi" + File selectedFPIndex="selected.FP.vcf.gz.tbi" + File selectedFNIndex="selected.FN.vcf.gz.tbi" + } + +} + +#create csv file of statistics based on TP,FP,FN +task SummariseForIndelSelection { + input { + String evalLabel + String truthLabel + String? stratLabel + String indelLabel + String engine + String igvSession + Int TP_CALL + Int TP_BASE + Int FP + Int FN + Int? preemptible + + } + + command <<< + set -xeuo pipefail + + Rscript -<<"EOF" ~{TP_CALL} ~{TP_BASE} ~{FN} ~{FP} ~{evalLabel} ~{truthLabel} ~{indelLabel} ~{engine} ~{default="" stratLabel} ~{igvSession} + GetSelectionValue<-function(name, target) { + + if(target=="insertion" || target=="deletion") { + return(NA) + } + pos_start<-regexpr(target,name) + sub=substring(name,pos_start+attr(pos_start,"match.length")+1,nchar(name)) + split_sub=strsplit(sub,"_") + val=if(grepl("^m",split_sub[[1]][[1]])) -as.double(gsub("m","",split_sub[[1]][[1]])) else as.double(split_sub[[1]][[1]]) + } + + args <-commandArgs(trailingOnly=TRUE) + indel_options <-c("deletion","insertion","indel_fine","indel_coarse") + indel_type <- mapply(grepl,indel_options,args[7]) + indel_type <- indel_options[indel_type[indel_options]] + indel_length <- GetSelectionValue(args[7],indel_type) + if (length(args)<10) { + stratifier <- NA + } else { + stratifier <- args[9] + } + table <- data.frame("Name"=args[5], "Truth_Set"=args[6],"Comparison_Engine"=args[8],"Stratifier"=stratifier, + "IndelLength"= indel_length, + "Recall"=as.numeric(args[2])/(as.numeric(args[2])+as.numeric(args[3])),"Precision"=as.numeric(args[1])/(as.numeric(args[1])+as.numeric(args[4])),"TP_Base"=as.numeric(args[2]),"TP_Eval"=as.numeric(args[1]), + "FP"=as.numeric(args[4]),"FN"=as.numeric(args[3]),"IGV_Session"=args[length(args)],"Summary_Type"=indel_type) + table$F1_Score <- 2*table$Precision*table$Recall/(table$Precision+table$Recall) + write.csv(table,paste(args[8],".",indel_type,".summary.csv",sep=""),row.names=FALSE) + EOF + >>> + + runtime { + docker: "rocker/tidyverse" + preemptible: select_first([preemptible,0]) + disks: "local-disk 10 HDD" + } + + output { + File summaryOut=glob("*.summary.csv")[0] + } + +} + +#create csv file of statistics based on TP,FP,FN +task SummariseForVariantSelection { + input { + String evalLabel + String truthLabel + String? stratLabel + String variantLabel + String engine + String igvSession + Int TP_CALL + Int TP_BASE + Int FP + Int FN + Int? preemptible + + } + + command <<< + set -xeuo pipefail + + Rscript -<<"EOF" ~{TP_CALL} ~{TP_BASE} ~{FN} ~{FP} ~{evalLabel} ~{truthLabel} ~{variantLabel} ~{engine} ~{default="" stratLabel} ~{igvSession} + args <-commandArgs(trailingOnly=TRUE) + if (length(args)<10) { + stratifier <- NA + } else { + stratifier <- args[9] + } + table <- data.frame("Name"=args[5], "Truth_Set"=args[6],"Comparison_Engine"=args[8],"Stratifier"=stratifier, + "IndelLength"= NA, + "Recall"=as.numeric(args[2])/(as.numeric(args[2])+as.numeric(args[3])),"Precision"=as.numeric(args[1])/(as.numeric(args[1])+as.numeric(args[4])),"TP_Base"=as.numeric(args[2]),"TP_Eval"=as.numeric(args[1]), + "FP"=as.numeric(args[4]),"FN"=as.numeric(args[3]),"IGV_Session"=args[length(args)],"Summary_Type"="summary","Type"=args[7]) + table$F1_Score <- 2*table$Precision*table$Recall/(table$Precision+table$Recall) + write.csv(table,paste(args[8],".",args[7],".summary.csv",sep=""),row.names=FALSE) + EOF + >>> + + runtime { + docker: "rocker/tidyverse" + preemptible: select_first([preemptible,0]) + disks: "local-disk 10 HDD" + } + + output { + File summaryOut=glob("*.summary.csv")[0] + } + +} + +#Convert vcfeval output statistics to final output format +task SummariseVcfEval { + input { + String evalLabel + String truthLabel + String areVariants + String? igvSession + String? stratLabel + File? summaryFile + Int? unkSNP + Int? unkINDEL + Int? preemptible + } + Int disk_size = 10 + ceil(2.2 * size(summaryFile, "GB")) + + command <<< + set -xeuo pipefail + + Rscript -<<"EOF" ~{evalLabel} ~{truthLabel} ~{default="" summaryFile} ~{default="" stratLabel} ~{default="" igvSession} ~{default="" unkSNP} ~{default="" unkINDEL} ~{areVariants} + args <- commandArgs(trailingOnly = TRUE) + if (args[length(args)]=="yes") { + table_vcfeval <- read.csv(args[3]) + if (length(args)==7) { + table_vcfeval$Stratifier <- NA + table_vcfeval$IGV_Session <- args[4] + table_vcfeval$UNK[table_vcfeval$Type=="SNP"]=args[5] + table_vcfeval$UNK[table_vcfeval$Type=="INDEL"]=args[6] + } else { + table_vcfeval$Stratifier <- args[4] + table_vcfeval$IGV_Session <- args[5] + table_vcfeval$UNK[table_vcfeval$Type=="SNP"]=args[6] + table_vcfeval$UNK[table_vcfeval$Type=="INDEL"]=args[7] + } + write(ifelse(is.na(table_vcfeval[table_vcfeval$Type=="SNP", ]$Precision), 0, table_vcfeval[table_vcfeval$Type=="SNP", ]$Precision), "snpPrecision.txt") + write(ifelse(is.na(table_vcfeval[table_vcfeval$Type=="INDEL",]$Precision), 0, table_vcfeval[table_vcfeval$Type=="INDEL",]$Precision), "indelPrecision.txt") + write(ifelse(is.na(table_vcfeval[table_vcfeval$Type=="SNP", ]$Recall), 0, table_vcfeval[table_vcfeval$Type=="SNP", ]$Recall), "snpRecall.txt") + write(ifelse(is.na(table_vcfeval[table_vcfeval$Type=="INDEL",]$Recall), 0, table_vcfeval[table_vcfeval$Type=="INDEL",]$Recall), "indelRecall.txt") + write(ifelse(is.na(table_vcfeval[table_vcfeval$Type=="SNP", ]$F1_Score), 0, table_vcfeval[table_vcfeval$Type=="SNP", ]$F1_Score), "snpF1Score.txt") + write(ifelse(is.na(table_vcfeval[table_vcfeval$Type=="INDEL",]$F1_Score), 0, table_vcfeval[table_vcfeval$Type=="INDEL",]$F1_Score), "indelF1Score.txt") + } else { + types <- c("INDEL","SNP") + recall <- c(NA,NA) + precision <- c(NA,NA) + f1_score <- c(NA,NA) + tp <- c(0,0) + fp <- c(0,0) + fn <- c(0,0) + unk <- c(0,0) + igv_session <- c(NA,NA) + table_vcfeval <- data.frame("Type"=types,"Recall"=recall,"Precision"=precision,"F1_Score"=f1_score,"TP_Base"=tp,"TP_Eval"=tp,"FP"=fp,"FN"=fn,"UNK"=unk,"IGV_Session"=igv_session) + if (length(args)==3) { + table_vcfeval$Stratifier <- NA + } else { + table_vcfeval$Stratifier <- args[3] + } + write(0,"snpPrecision.txt") + write(0,"indelPrecision.txt") + write(0,"snpRecall.txt") + write(0,"indelRecall.txt") + write(0,"snpF1Score.txt") + write(0,"indelF1Score.txt") + } + table_vcfeval$Name <- args[1] + table_vcfeval$Truth_Set <- args[2] + table_vcfeval$Summary_Type <- "summary" + table_vcfeval$Comparison_Engine <-"VcfEval" + write.csv(table_vcfeval,"vcfeval.summary.csv",row.names=FALSE) + EOF + >>> + + runtime { + docker: "rocker/tidyverse" + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + } + + output{ + File summaryOut="vcfeval.summary.csv" + Float snpPrecision=read_float("snpPrecision.txt") + Float indelPrecision=read_float("indelPrecision.txt") + Float snpRecall=read_float("snpRecall.txt") + Float indelRecall=read_float("indelRecall.txt") + Float snpF1Score=read_float("snpF1Score.txt") + Float indelF1Score=read_float("indelF1Score.txt") + } +} + +#Convert hap.py output statistics to final output format +task SummariseHappy { + input { + String evalLabel + String truthLabel + String areVariants + String? stratLabel + File? summaryFile + Int? preemptible + String? igvSession + } + + Int disk_size = 10 + ceil(2.2 * size(summaryFile, "GB")) + + command <<< + Rscript -<<"EOF" ~{evalLabel} ~{truthLabel} ~{default="" summaryFile} ~{default="" stratLabel} ~{default="" igvSession} ~{areVariants} + args <-commandArgs(trailingOnly = TRUE) + if (args[length(args)]=="yes") { + table_happy <- read.csv(args[3]) + colnames(table_happy)[10]<-"Recall" + colnames(table_happy)[11]<-"Precision" + colnames(table_happy)[13]<-"F1_Score" + colnames(table_happy)[4]<-"TP_Base" + table_happy$TP_Eval=table_happy$TP_Base + colnames(table_happy)[5]<-"FN" + colnames(table_happy)[7]<-"FP" + colnames(table_happy)[8]<-"UNK" + if (length(args)==5) { + table_happy$Stratifier <- NA + table_happy$IGV_Session <- args[4] + } else { + table_happy$Stratifier <-args[4] + table_happy$IGV_Session <- args[5] + } + + + } else { + types <- c("INDEL","SNP") + recall <- c(NA,NA) + precision <- c(NA,NA) + f1_score <- c(NA,NA) + igv_session <- c(NA,NA) + tp <- c(0,0) + fp <- c(0,0) + fn <- c(0,0) + unk <- c(0,0) + filters <- c("PASS","PASS") + table_happy <- data.frame("Type"=types,"Recall"=recall,"Precision"=precision,"F1_Score"=f1_score,"TP_Base"=tp,"TP_Eval"=tp,"FP"=fp,"FN"=fn,"UNK"=unk,"Filter"=filters,"IGV_Session"=igv_session) + if (length(args)==3) { + table_happy$Stratifier <- NA + } else { + table_happy$Stratifier <-args[3] + } + + } + table_happy$Name <- args[1] + table_happy$Truth_Set <- args[2] + table_happy$Comparison_Engine <-"Happy" + table_happy$Summary_Type <- "summary" + table_happy<-table_happy[table_happy$Filter=="PASS",c("Type","Recall","Precision","Name","Truth_Set","Comparison_Engine","F1_Score","TP_Eval","TP_Base","FP","FN","UNK","Stratifier","IGV_Session","Summary_Type")] + if (nrow(subset(table_happy,Type=="INDEL"))==0) { + table_add <- data.frame("Type"="INDEL","Recall"=NA,"Precision"=NA,"F1_Score"=NA,"TP_Eval"=0,"TP_Base"=0,"FP"=0,"FN"=0,"UNK"=0,"Name"=args[1],"Truth_Set"=args[2],"Comparison_Engine"="Happy","Stratifier"=table_happy$Stratifier[1],"IGV_Session"=NA,"Summary_Type"="summary") + table_happy <- rbind(table_happy,table_add) + } + write.csv(table_happy,"happy.summary.csv",row.names=FALSE) + EOF + >>> + + runtime { + docker: "rocker/tidyverse" + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + } + + output{ + File summaryOut="happy.summary.csv" + } +} + +#Convert GenotypeConcordance output statistics to final output format +task SummariseGATKGC { + input { + String evalLabel + String truthLabel + String areVariants + String? stratLabel + File? summaryFile + File? summaryCounts + Int? unkSNP + Int? unkINDEL + Int? preemptible + String? igvSession + } + + Int disk_size = 10 + ceil(2.2 * size(summaryFile, "GB") + 2.2 * size(summaryCounts, "GB")) + + command <<< + set -xeuo pipefail + + Rscript -<<"EOF" ~{evalLabel} ~{truthLabel} ~{default="" summaryFile} ~{default="" summaryCounts} ~{default="" stratLabel} ~{default="" igvSession} ~{default="" unkSNP} ~{default="" unkINDEL} ~{areVariants} + args <- commandArgs(trailingOnly = TRUE) + if (args[length(args)]=="yes") { + table_GC <- read.table(args[3],skip=6, header=TRUE,sep="\t",na.strings="?") + table_counts_GC <- read.table(args[4],skip=6, header=TRUE,sep="\t",na.strings="?") + table_GC$F1_Score <- 2*(table_GC$VAR_PPV*table_GC$VAR_SENSITIVITY)/(table_GC$VAR_PPV+table_GC$VAR_SENSITIVITY) + table_GC$TP_Eval <- table_counts_GC$TP_COUNT + table_GC$TP_Base <- table_counts_GC$TP_COUNT + table_GC$FP <- table_counts_GC$FP_COUNT + table_GC$FN <- table_counts_GC$FN_COUNT + colnames(table_GC)[10]<-"Recall" + colnames(table_GC)[11]<-"Precision" + colnames(table_GC)[1]<-"Type" + if (length(args)==8) { + table_GC$Stratifier <- NA + table_GC$IGV_Session <- args[5] + table_GC$UNK[table_GC$Type=="SNP"]=args[6] + table_GC$UNK[table_GC$Type=="INDEL"]=args[7] + + } else { + table_GC$Stratifier <- args[5] + table_GC$IGV_Session <- args[6] + table_GC$UNK[table_GC$Type=="SNP"]=args[7] + table_GC$UNK[table_GC$Type=="INDEL"]=args[8] + } + } else { + types <- c("INDEL","SNP") + recall <- c(NA,NA) + precision <- c(NA,NA) + f1_score <- c(NA,NA) + tp <- c(0,0) + fp <- c(0,0) + fn <- c(0,0) + unk <- c(0,0) + igv_session <- c(NA,NA) + table_GC <- data.frame("Type"=types,"Recall"=recall,"Precision"=precision,"F1_Score"=f1_score,"TP_Eval"=tp,"TP_Base"=tp,"FP"=fp,"FN"=fn,"UNK"=unk,"IGV_Session"=igv_session) + if (length(args)==3) { + table_GC$Stratifier <- NA + } else { + table_GC$Stratifier <- args[3] + } + } + table_GC$Name <- args[1] + table_GC$Truth_Set <- args[2] + table_GC$Comparison_Engine <-"GATK_GC" + table_GC$Summary_Type <- "summary" + table_GC <- table_GC[,c("Type","Recall","Precision","Name","Truth_Set","Comparison_Engine","F1_Score","TP_Eval","TP_Base","FP","FN","UNK","Stratifier","IGV_Session","Summary_Type")] + write.csv(table_GC,"gatkgc.summary.csv",row.names=FALSE) + EOF + >>> + + runtime { + docker: "rocker/tidyverse" + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + } + + output{ + File summaryOut="gatkgc.summary.csv" + } +} + +#Combine summaries from multiple csv into a single csv +task CombineSummaries { + input { + Array[File] summaries + Int? preemptible + } + String dollar="$" + + Int disk_size = 10 + ceil(2 * size(summaries, "GB")) + command <<< + set -xeuo pipefail + + Rscript -<<"EOF" + library(readr) + library(dplyr) + library(purrr) + summary_files <- read_csv("~{write_lines(summaries)}", col_names=FALSE) + merged<- as.list(summary_files$X1) %>% map(read_csv) %>% reduce(bind_rows) + write.csv(merged,"summary.csv",row.names=FALSE) + EOF + >>> + + runtime { + docker: "rocker/tidyverse" + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + } + + output{ + File summaryOut="summary.csv" + } +} + +#Use CrosscheckFingerprints to match evaluation vcfs to appropriate truth vcfs +task MatchEvalTruth { + input{ + File evalVcf + File truthVcf + File evalVcfIndex + File truthVcfIndex + File hapMap + Int? preemptible + Int? memoryMaybe + String gatkTag + } + Int memoryDefault=16 + Int memoryJava=select_first([memoryMaybe,memoryDefault]) + Int memoryRam=memoryJava+2 + Int disk_size = 10 + ceil(size(hapMap, "GB") + size(evalVcf, "GB") + size(evalVcfIndex, "GB") + size(truthVcf, "GB") + size(truthVcfIndex, "GB")) + + command <<< + gatk --java-options "-Xmx~{memoryJava}G" CrosscheckFingerprints -I ~{evalVcf} -SI ~{truthVcf} -H ~{hapMap} --CROSSCHECK_MODE CHECK_ALL_OTHERS --CROSSCHECK_BY FILE --EXPECT_ALL_GROUPS_TO_MATCH + >>> + + runtime { + docker: "us.gcr.io/broad-gatk/gatk:"+gatkTag + preemptible: select_first([preemptible,0]) + disks: "local-disk " + disk_size + " HDD" + bootDiskSizeGb: "16" + memory: memoryRam + " GB" + } +} + +# creates an IGV session +# given a list of IGV compatible file paths +task WriteXMLfile { + input { + Array[String] input_files + String reference_version + String file_name + + Array[String]? input_names="" + Array[String] input_names_prefix = if defined(input_names) then prefix('-n ', select_first([input_names])) else [] + } + command { + bash /usr/writeIGV.sh ~{reference_version} ~{sep=" " input_files} ~{sep=" " input_names_prefix} > "~{file_name}.xml" + } + runtime { + docker: "quay.io/mduran/generate-igv-session_2:v1.0" + } + output { + File igv_session = "${file_name}.xml" + } +} + +task CreateIntervalList{ + input { + File reference + File reference_index + File reference_dict + String interval_string + String gatkTag + String? dummyInputForTerraCallCaching + } + command { + gatk PreprocessIntervals \ + -R ~{reference} \ + -L ~{interval_string} \ + -O output.interval_list \ + --bin-length 0 \ + -imr OVERLAPPING_ONLY \ + -padding 0 + } + output { + File interval_list = "output.interval_list" + } + runtime { + preemptible: 3 + docker: "us.gcr.io/broad-gatk/gatk:"+gatkTag + disks: "local-disk 100 HDD" + memory: "4 GB" + } +} + +#Print given message to stderr and return an error +task ErrorWithMessage{ + input { + String message + } + command <<< + >&2 echo "Error: ~{message}" + exit 1 + >>> + + runtime { + docker: "ubuntu" + } +} \ No newline at end of file diff --git a/wdl/tasks/AlignedMetrics.wdl b/wdl/tasks/AlignedMetrics.wdl index 2b8e46c82..d916d8476 100644 --- a/wdl/tasks/AlignedMetrics.wdl +++ b/wdl/tasks/AlignedMetrics.wdl @@ -395,6 +395,12 @@ task SamStatsMap { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') samtools stats -@${np} ~{bam} > ~{basename}.sam_stats.txt @@ -406,6 +412,8 @@ task SamStatsMap { sed 's/[\(\)]//g' | \ sed 's/[[:space:]]*#.*//' \ > map.txt + + kill $monitoring_pid >>> output { diff --git a/wdl/tasks/FastQC.wdl b/wdl/tasks/FastQC.wdl index 52c5a0bf4..b1d58e37b 100644 --- a/wdl/tasks/FastQC.wdl +++ b/wdl/tasks/FastQC.wdl @@ -17,6 +17,12 @@ task FastQC { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) fastqc -t $num_core --extract ~{bam} @@ -48,6 +54,8 @@ task FastQC { awk '{ a[i++]=$1; } END { x=int((i+1)/2); if (x < (i+1)/2) print (a[x-1]+a[x])/2; else print a[x-1]; }') echo $median_qual | awk '{ print "median_qual\t" $1 }' >> map.txt + + kill $monitoring_pid >>> output { diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index d68d2b0a4..12936df4b 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -15,12 +15,20 @@ task BamToFq { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + samtools sort -n ~{bam} | samtools bam2fq \ -n \ -s /dev/null \ -1 ~{prefix}.end1.fq.gz \ -2 ~{prefix}.end2.fq.gz \ -0 ~{prefix}.unpaired.fq.gz + + kill $monitoring_pid >>> output { @@ -180,6 +188,12 @@ task BwaMem2 { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + # Make sure we use all our proocesors: np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') if [[ ${np} -gt 2 ]] ; then @@ -205,6 +219,8 @@ task BwaMem2 { ~{fq_end1} \ ~{fq_end2} | \ samtools view -1 - > ~{prefix}.bam + + kill $monitoring_pid >>> output { @@ -256,6 +272,12 @@ task MergeBamAlignment { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + # Make sure we use all our proocesors: np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') let np=${np}-1 @@ -287,6 +309,8 @@ task MergeBamAlignment { ALIGNER_PROPER_PAIR_FLAGS=true \ UNMAP_CONTAMINANT_READS=true \ ADD_PG_TAG_TO_READS=false + + kill $monitoring_pid >>> output { @@ -341,6 +365,13 @@ task MarkDuplicates { # While query-grouped isn't actually query-sorted, it's good enough for MarkDuplicates with ASSUME_SORT_ORDER="queryname" command { + + export MONITOR_MOUNT_POINT="/cromwell_root" + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + java -Dsamjdk.compression_level=~{compression_level} -Xms~{java_memory_size_mb}m -jar /usr/picard/picard.jar \ MarkDuplicates \ INPUT=~{input_bam} \ @@ -353,6 +384,8 @@ task MarkDuplicates { ASSUME_SORT_ORDER="queryname" \ CLEAR_DT="false" \ ADD_PG_TAG_TO_READS=false + + kill $monitoring_pid } output { @@ -416,6 +449,12 @@ task BaseRecalibrator { } command { + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + gatk --java-options "-XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10 -XX:+PrintFlagsFinal \ -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCDetails \ -Xloggc:gc_log.log -Xms5000m -Xmx5500m" \ @@ -425,6 +464,8 @@ task BaseRecalibrator { --use-original-qualities \ -O ~{prefix}.txt \ --known-sites ~{known_sites_vcf} + + kill $monitoring_pid } ######################### RuntimeAttr default_attr = object { @@ -487,6 +528,13 @@ task ApplyBQSR { + 2*ceil(size(recalibration_report, "GB")) command <<< + + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + gatk --java-options "-XX:+PrintFlagsFinal -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps \ -XX:+PrintGCDetails -Xloggc:gc_log.log \ -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10 -Dsamjdk.compression_level=~{compression_level} -Xms8192m -Xmx~{java_memory_size_mb}m" \ @@ -507,6 +555,8 @@ task ApplyBQSR { np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') samtools index -@${np} ~{prefix}.bam + + kill $monitoring_pid >>> ######################### RuntimeAttr default_attr = object { @@ -545,11 +595,18 @@ task RevertSam { Int compression_level = 2 Int java_memory_size_mb = 30768 - Int disk_size = 1 + 4*ceil(size(input_bam, "GB")) + Int disk_size = 1 + 20*ceil(size(input_bam, "GB")) # As documented on the GATK website: # https://gatk.broadinstitute.org/hc/en-us/articles/4403687183515--How-to-Generate-an-unmapped-BAM-from-FASTQ-or-aligned-BAM command { + + export MONITOR_MOUNT_POINT="/cromwell_root" + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + java -Dsamjdk.compression_level=~{compression_level} -Xms~{java_memory_size_mb}m -jar /usr/picard/picard.jar \ RevertSam \ INPUT=~{input_bam} \ @@ -565,6 +622,8 @@ task RevertSam { RESTORE_ORIGINAL_QUALITIES=true \ REMOVE_DUPLICATE_INFORMATION=true \ REMOVE_ALIGNMENT_INFORMATION=true + + kill $monitoring_pid } output { @@ -611,10 +670,18 @@ task ComputeBamStats { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + python3 /python/compute_sr_stats.py \ ~{qual_thresh_arg}~{default="" sep=" -q " qual_threshold} \ ~{bam_file} \ | tee ~{stats_file_name} + + kill $monitoring_pid >>> output { diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 086bed950..b499b533b 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -175,10 +175,18 @@ task SortBam { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) samtools sort -@$num_core -o ~{prefix}.bam ~{input_bam} samtools index ~{prefix}.bam + + kill $monitoring_pid >>> output { @@ -1880,11 +1888,19 @@ task ComputeGenomeLength { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + samtools dict ~{fasta} | \ grep '^@SQ' | \ awk '{ print $3 }' | \ sed 's/LN://' | \ awk '{ sum += $1 } END { print sum }' > length.txt + + kill $monitoring_pid >>> output { @@ -2430,3 +2446,48 @@ task MapToTsv { docker: "us.gcr.io/broad-dsp-lrma/lr-basic:0.1.1" } } + +task CreateIGVSession{ + meta { + description: "Create an IGV session given a list of IGV compatible file paths. Adapted / borrowed from https://github.com/broadinstitute/palantir-workflows/blob/mg_benchmark_compare/BenchmarkVCFs ." + } + input { + Array[String] input_bams + Array[String] input_vcfs + String reference_short_name + String output_name + + RuntimeAttr? runtime_attr_override + } + + Array[String] input_files = flatten([input_bams, input_vcfs]) + + command { + bash /usr/writeIGV.sh ~{reference_version} ~{sep=" " input_files} > "~{file_name}.xml" + } + + output { + File igv_session = "${file_name}.xml" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 1, + disk_gb: 50, + boot_disk_gb: 10, + preemptible_tries: 3, + max_retries: 1, + docker: "quay.io/mduran/generate-igv-session_2:v1.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} \ No newline at end of file From aa5e26faad4156344bf394ad8f2751fb0bc35a05 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 13 Feb 2023 23:52:26 -0500 Subject: [PATCH 109/297] Fixing typos. --- wdl/tasks/Utils.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index b499b533b..2839e33e7 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -2463,11 +2463,11 @@ task CreateIGVSession{ Array[String] input_files = flatten([input_bams, input_vcfs]) command { - bash /usr/writeIGV.sh ~{reference_version} ~{sep=" " input_files} > "~{file_name}.xml" + bash /usr/writeIGV.sh ~{reference_short_name} ~{sep=" " input_files} > "~{output_name}.xml" } output { - File igv_session = "${file_name}.xml" + File igv_session = "${output_name}.xml" } ######################### From b02e46fd1980566675fb1d9c3e73ccec81b556d7 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 15 Feb 2023 10:18:47 -0500 Subject: [PATCH 110/297] Adding BenchmarkVCFs to dockstore. --- .dockstore.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.dockstore.yml b/.dockstore.yml index 0f7f7f8f5..2e0ce3254 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -143,4 +143,8 @@ workflows: - name: LRConvertBCF subclass: wdl primaryDescriptorPath: /wdl/LRConvertBCF.wdl + testParameterFiles: +- name: BenchmarkVCFs + subclass: wdl + primaryDescriptorPath: /wdl/BenchmarkVCFs.wdl testParameterFiles: \ No newline at end of file From 530af793499069948027fdde5e34e0c4fa0684b0 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 15 Feb 2023 12:52:54 -0500 Subject: [PATCH 111/297] Fixing output name in `SRWholeGenome.wdl` --- wdl/SRWholeGenome.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 929f1fd29..23c80102b 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -263,7 +263,7 @@ workflow SRWholeGenome { Float aligned_frac_bases = SamStats.stats_map['bases_mapped']/SamStats.stats_map['total_length'] Float aligned_est_fold_cov = SamStats.stats_map['bases_mapped']/ComputeGenomeLength.length - Float aligned_read_length = FastQC.stats_map['read_length'] + Float aligned_read_length_mean = FastQC.stats_map['read_length'] Float insert_size_average = SamStats.stats_map['insert_size_average'] Float insert_size_standard_deviation = SamStats.stats_map['insert_size_standard_deviation'] From 18de47b99cc68512e873e2ed0a0558f79a02493c Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 15 Feb 2023 17:41:12 -0500 Subject: [PATCH 112/297] Minor changes to `BenchmarkVCFs` --- wdl/BenchmarkVCFs.wdl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index 47800f0c3..d355e219b 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -21,7 +21,6 @@ workflow Benchmark { File confidenceInterval File ref_map_file - String referenceVersion String? analysisRegion File? hapMap @@ -57,7 +56,6 @@ workflow Benchmark { confidenceInterval: {description: "confidence interval for truth set (can be bed or picard interval_list)"} ref_map_file: {description: "table indicating reference sequence and auxillary file locations" } hapMap: {description: "reference haplotype map for CrosscheckFingerprints"} - referenceVersion: {description: "reference version used, for igv xml session (must be either 'hg19' or 'hg38')"} stratIntervals: {description: "intervals for stratifiction (can be picard interval_list or bed format)"} stratLabels: {description: "labels by which to identify stratification intervals (must be same length as stratIntervals)"} jexlVariantSelectors: {description: "variant types to select over (defined by jexl fed to GATK SelectVariants)"} @@ -263,7 +261,7 @@ workflow Benchmark { input: input_files=select_all([StandardVcfEval.outVcf,ConfidenceConvertIntervals.bed,stratifier.bed]), input_names=select_all([outputPrefix+"_vcfeval","confidence_intervals",stratifier.label]), - reference_version=referenceVersion, + reference_version=ref_map["fasta"], file_name=outputPrefix+"_vcfeval" } @@ -348,7 +346,7 @@ workflow Benchmark { input: input_files=select_all([EvalIndelLengthVcfEval.selectedTPCall,EvalIndelLengthVcfEval.selectedTPBase,EvalIndelLengthVcfEval.selectedFP,EvalIndelLengthVcfEval.selectedFN,vcfVcfEval,confidenceBed,stratIndelBed]), input_names=select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",stratIndelLabel]), - reference_version=referenceVersion, + reference_version=ref_map["fasta"], file_name=namePrefix+"_vcfeval" } @@ -404,7 +402,7 @@ workflow Benchmark { input_files=select_all([EvalSelectorVcfEval.selectedTPCall,EvalSelectorVcfEval.selectedTPBase,EvalSelectorVcfEval.selectedFP,EvalSelectorVcfEval.selectedFN, evalStratSelectorCombo.annotatedVcfs.vcfVcfEval,evalStratSelectorCombo.annotatedVcfs.confidenceBed,evalStratSelectorCombo.annotatedVcfs.stratBed]), input_names=select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",evalStratSelectorCombo.annotatedVcfs.stratLabel]), - reference_version=referenceVersion, + reference_version=ref_map["fasta"], file_name=evalStratSelectorCombo.annotatedVcfs.namePrefix+"_"+evalStratSelectorCombo.variantSelector.label+"_vcfeval" } @@ -536,19 +534,26 @@ task VcfEval { input{ File truthVCF File truthVCFIndex + File evalVCF File evalVCFIndex + File confidenceBed + File? stratBed + File ref File refDict File refIndex + String outputPre Boolean passingOnly + String? vcfScoreField Int? preemptible String? memUser Int? threads + Boolean requireMatchingGenotypes Boolean enableRefOverlap = false } From 481c007c76e48b654d4e2fa0e201f922af66a713 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 15 Feb 2023 18:27:03 -0500 Subject: [PATCH 113/297] Adding support for viewing reads in the auto-generated IGV sessions. --- wdl/BenchmarkVCFs.wdl | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index d355e219b..941a79054 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -13,10 +13,14 @@ workflow Benchmark { File evalVcf String evalLabel File evalVcfIndex + File? evalBam + String? evalBamLabel File truthVcf String truthLabel File truthVcfIndex + File? truthBam + String? truthBamLabel File confidenceInterval @@ -51,8 +55,12 @@ workflow Benchmark { evalVcf: {description: "vcfs to be evaluated"} evalLabel: {description: "label to identify vcf to be evaluated"} evalVcfIndex: {description: "vcf index for evalVcf"} + evalBam: {description: "bam file contaning the reads that generated the evalVcf"} + evalBamLabel: {description: "label to use for the evalBam in IGV"} truthVcf: {description: "truth vcf against which to evaluate"} truthLabel: {description: "label by which to indentify truth set"} + truthBam: {description: "bam file contaning the reads that generated the truthVcf"} + truthBamLabel: {description: "label to use for the truthBam in IGV"} confidenceInterval: {description: "confidence interval for truth set (can be bed or picard interval_list)"} ref_map_file: {description: "table indicating reference sequence and auxillary file locations" } hapMap: {description: "reference haplotype map for CrosscheckFingerprints"} @@ -259,8 +267,8 @@ workflow Benchmark { call WriteXMLfile as VcfEvalWriteXMLfile { input: - input_files=select_all([StandardVcfEval.outVcf,ConfidenceConvertIntervals.bed,stratifier.bed]), - input_names=select_all([outputPrefix+"_vcfeval","confidence_intervals",stratifier.label]), + input_files=flatten(select_all([StandardVcfEval.outVcf,ConfidenceConvertIntervals.bed,stratifier.bed]), select_all([evalBam, truthBam])), + input_names=flatten(select_all([outputPrefix+"_vcfeval","confidence_intervals",stratifier.label]), select_all([evalBamLabel, truthBamLabel])), reference_version=ref_map["fasta"], file_name=outputPrefix+"_vcfeval" } @@ -344,8 +352,8 @@ workflow Benchmark { call WriteXMLfile as VcfEvalIndelWriteXMLfile { input: - input_files=select_all([EvalIndelLengthVcfEval.selectedTPCall,EvalIndelLengthVcfEval.selectedTPBase,EvalIndelLengthVcfEval.selectedFP,EvalIndelLengthVcfEval.selectedFN,vcfVcfEval,confidenceBed,stratIndelBed]), - input_names=select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",stratIndelLabel]), + input_files=flatten(select_all([EvalIndelLengthVcfEval.selectedTPCall,EvalIndelLengthVcfEval.selectedTPBase,EvalIndelLengthVcfEval.selectedFP,EvalIndelLengthVcfEval.selectedFN,vcfVcfEval,confidenceBed,stratIndelBed]), select_all([evalBam, truthBam])), + input_names=flatten(select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",stratIndelLabel]), select_all([evalBamLabel, truthBamLabel])), reference_version=ref_map["fasta"], file_name=namePrefix+"_vcfeval" } @@ -399,9 +407,9 @@ workflow Benchmark { } call WriteXMLfile as VcfEvalSelectorWriteXMLfile { input: - input_files=select_all([EvalSelectorVcfEval.selectedTPCall,EvalSelectorVcfEval.selectedTPBase,EvalSelectorVcfEval.selectedFP,EvalSelectorVcfEval.selectedFN, - evalStratSelectorCombo.annotatedVcfs.vcfVcfEval,evalStratSelectorCombo.annotatedVcfs.confidenceBed,evalStratSelectorCombo.annotatedVcfs.stratBed]), - input_names=select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",evalStratSelectorCombo.annotatedVcfs.stratLabel]), + input_files=flatten(select_all([EvalSelectorVcfEval.selectedTPCall,EvalSelectorVcfEval.selectedTPBase,EvalSelectorVcfEval.selectedFP,EvalSelectorVcfEval.selectedFN, + evalStratSelectorCombo.annotatedVcfs.vcfVcfEval,evalStratSelectorCombo.annotatedVcfs.confidenceBed,evalStratSelectorCombo.annotatedVcfs.stratBed]), select_all([evalBam, truthBam])), + input_names=flatten(select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",evalStratSelectorCombo.annotatedVcfs.stratLabel]), select_all([evalBamLabel, truthBamLabel])), reference_version=ref_map["fasta"], file_name=evalStratSelectorCombo.annotatedVcfs.namePrefix+"_"+evalStratSelectorCombo.variantSelector.label+"_vcfeval" } From ae5be971e6262a40d310578437bb43dd8d3a6454 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 15 Feb 2023 18:33:34 -0500 Subject: [PATCH 114/297] fixing typo. --- wdl/BenchmarkVCFs.wdl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index 941a79054..9228739db 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -267,8 +267,8 @@ workflow Benchmark { call WriteXMLfile as VcfEvalWriteXMLfile { input: - input_files=flatten(select_all([StandardVcfEval.outVcf,ConfidenceConvertIntervals.bed,stratifier.bed]), select_all([evalBam, truthBam])), - input_names=flatten(select_all([outputPrefix+"_vcfeval","confidence_intervals",stratifier.label]), select_all([evalBamLabel, truthBamLabel])), + input_files=flatten([select_all([StandardVcfEval.outVcf,ConfidenceConvertIntervals.bed,stratifier.bed]), select_all([evalBam, truthBam])]), + input_names=flatten([select_all([outputPrefix+"_vcfeval","confidence_intervals",stratifier.label]), select_all([evalBamLabel, truthBamLabel])]), reference_version=ref_map["fasta"], file_name=outputPrefix+"_vcfeval" } @@ -352,8 +352,8 @@ workflow Benchmark { call WriteXMLfile as VcfEvalIndelWriteXMLfile { input: - input_files=flatten(select_all([EvalIndelLengthVcfEval.selectedTPCall,EvalIndelLengthVcfEval.selectedTPBase,EvalIndelLengthVcfEval.selectedFP,EvalIndelLengthVcfEval.selectedFN,vcfVcfEval,confidenceBed,stratIndelBed]), select_all([evalBam, truthBam])), - input_names=flatten(select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",stratIndelLabel]), select_all([evalBamLabel, truthBamLabel])), + input_files=flatten([select_all([EvalIndelLengthVcfEval.selectedTPCall,EvalIndelLengthVcfEval.selectedTPBase,EvalIndelLengthVcfEval.selectedFP,EvalIndelLengthVcfEval.selectedFN,vcfVcfEval,confidenceBed,stratIndelBed]), select_all([evalBam, truthBam])]), + input_names=flatten([select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",stratIndelLabel]), select_all([evalBamLabel, truthBamLabel])]), reference_version=ref_map["fasta"], file_name=namePrefix+"_vcfeval" } @@ -407,9 +407,9 @@ workflow Benchmark { } call WriteXMLfile as VcfEvalSelectorWriteXMLfile { input: - input_files=flatten(select_all([EvalSelectorVcfEval.selectedTPCall,EvalSelectorVcfEval.selectedTPBase,EvalSelectorVcfEval.selectedFP,EvalSelectorVcfEval.selectedFN, - evalStratSelectorCombo.annotatedVcfs.vcfVcfEval,evalStratSelectorCombo.annotatedVcfs.confidenceBed,evalStratSelectorCombo.annotatedVcfs.stratBed]), select_all([evalBam, truthBam])), - input_names=flatten(select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",evalStratSelectorCombo.annotatedVcfs.stratLabel]), select_all([evalBamLabel, truthBamLabel])), + input_files=flatten([select_all([EvalSelectorVcfEval.selectedTPCall,EvalSelectorVcfEval.selectedTPBase,EvalSelectorVcfEval.selectedFP,EvalSelectorVcfEval.selectedFN, + evalStratSelectorCombo.annotatedVcfs.vcfVcfEval,evalStratSelectorCombo.annotatedVcfs.confidenceBed,evalStratSelectorCombo.annotatedVcfs.stratBed]), select_all([evalBam, truthBam])]), + input_names=flatten([select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",evalStratSelectorCombo.annotatedVcfs.stratLabel]), select_all([evalBamLabel, truthBamLabel])]), reference_version=ref_map["fasta"], file_name=evalStratSelectorCombo.annotatedVcfs.namePrefix+"_"+evalStratSelectorCombo.variantSelector.label+"_vcfeval" } From 08a83f1d16b6f81a4bd046782e41078c122323db Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 15 Feb 2023 23:10:13 -0500 Subject: [PATCH 115/297] Fixed formatting and problem with IGV xml generation. --- wdl/BenchmarkVCFs.wdl | 522 +++++++++++++++++++++--------------------- 1 file changed, 264 insertions(+), 258 deletions(-) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index 9228739db..d654b66b4 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -34,15 +34,15 @@ workflow Benchmark { Array[String]? jexlVariantSelectors Array[String]? variantSelectorLabels - Int? threadsVcfEval=2 - Boolean doIndelLengthStratification=true + Int? threadsVcfEval = 2 + Boolean doIndelLengthStratification = true Int? preemptible String gatkTag="4.0.11.0" - Boolean requireMatchingGenotypes=true + Boolean requireMatchingGenotypes = true File? gatkJarForAnnotation Array[String]? annotationNames Boolean enableRefOverlap = false - Boolean passingOnly=true + Boolean passingOnly = true String? vcfScoreField String? dummyInputForTerraCallCaching } @@ -94,10 +94,10 @@ workflow Benchmark { } } - Array[File] actualStratIntervals=flatten([[""], stratIntervals]) - Array[String] actualStratLabels=flatten([[""], stratLabels]) - Array[String] actualSelectorLabels=select_first([variantSelectorLabels,[""]]) - Array[String] actualSelectorJEXL=select_first([jexlVariantSelectors,[""]]) + Array[File] actualStratIntervals = flatten([[""], stratIntervals]) + Array[String] actualStratLabels = flatten([[""], stratLabels]) + Array[String] actualSelectorLabels = select_first([variantSelectorLabels,[""]]) + Array[String] actualSelectorJEXL = select_first([jexlVariantSelectors,[""]]) #check that lengths of different arrays are compatible if (length(actualStratLabels)!= length(actualStratIntervals)) { @@ -117,13 +117,13 @@ workflow Benchmark { if (defined(hapMap)) { call MatchEvalTruth as Match { input: - evalVcf=evalVcf, - truthVcf=truthVcf, - evalVcfIndex=evalVcfIndex, - truthVcfIndex=truthVcfIndex, - hapMap=select_first([hapMap]), - gatkTag=gatkTag, - preemptible=preemptible + evalVcf = evalVcf, + truthVcf = truthVcf, + evalVcfIndex = evalVcfIndex, + truthVcfIndex = truthVcfIndex, + hapMap = select_first([hapMap]), + gatkTag = gatkTag, + preemptible = preemptible } } Array[String] indelLabels=["deletion","insertion","indel_fine_m20","indel_fine_m19","indel_fine_m18","indel_fine_m17","indel_fine_m16","indel_fine_m15", @@ -181,19 +181,19 @@ workflow Benchmark { if(stratIL!="") { call ConvertIntervals as StratConvertIntervals { input: - inputIntervals=stratIL, - refDict=ref_map["dict"], - gatkTag=gatkTag, - subset_interval=CreateIntervalList.interval_list, - preemptible=preemptible, - dummyInputForTerraCallCaching=dummyInputForTerraCallCaching + inputIntervals = stratIL, + refDict = ref_map["dict"], + gatkTag = gatkTag, + subset_interval = CreateIntervalList.interval_list, + preemptible = preemptible, + dummyInputForTerraCallCaching = dummyInputForTerraCallCaching } } } } - Array[File] stratBeds=select_all(flatten(select_all([[""],StratConvertIntervals.bed]))) - Array[File] stratILs=select_all(flatten(select_all([[""],StratConvertIntervals.intervalList]))) + Array[File] stratBeds = select_all(flatten(select_all([[""],StratConvertIntervals.bed]))) + Array[File] stratILs = select_all(flatten(select_all([[""],StratConvertIntervals.intervalList]))) scatter (strat in zip(zip(stratILs,stratBeds),actualStratLabels)) { Stratifier stratifiers = object {intervalList : strat.left.left, @@ -204,96 +204,94 @@ workflow Benchmark { call ConvertIntervals as ConfidenceConvertIntervals { input: - inputIntervals=confidenceInterval, - refDict=ref_map["dict"], - gatkTag=gatkTag, - preemptible=preemptible, - subset_interval=CreateIntervalList.interval_list, - dummyInputForTerraCallCaching=dummyInputForTerraCallCaching + inputIntervals = confidenceInterval, + refDict = ref_map["dict"], + gatkTag = gatkTag, + preemptible = preemptible, + subset_interval = CreateIntervalList.interval_list, + dummyInputForTerraCallCaching = dummyInputForTerraCallCaching } scatter (stratifier in stratifiers) { if (stratifier.label != "" && stratifier.intervalList != "") { - String stratLabel=select_first([stratifier.label,""]) - File stratIL=select_first([stratifier.intervalList,""]) - File stratBed=select_first([stratifier.bed,""]) - String outputPreStrat=evalLabel+"_"+truthLabel+"_"+stratLabel + String stratLabel = select_first([stratifier.label,""]) + File stratIL = select_first([stratifier.intervalList,""]) + File stratBed = select_first([stratifier.bed,""]) + String outputPreStrat = evalLabel+"_"+truthLabel+"_"+stratLabel } - String outputPrefix=select_first([outputPreStrat,evalLabel+"_"+truthLabel]) - - + String outputPrefix = select_first([outputPreStrat,evalLabel+"_"+truthLabel]) call CheckForVariants as CheckForVariantsEval { input: - vcf=evalVcf, - vcfIndex=evalVcfIndex, - confidenceIL=ConfidenceConvertIntervals.intervalList, - stratIL=stratIL, - gatkTag=gatkTag, - preemptible=preemptible + vcf = evalVcf, + vcfIndex = evalVcfIndex, + confidenceIL = ConfidenceConvertIntervals.intervalList, + stratIL = stratIL, + gatkTag = gatkTag, + preemptible = preemptible } call CheckForVariants as CheckForVariantsTruth { input: - vcf=truthVcf, - vcfIndex=truthVcfIndex, - confidenceIL=ConfidenceConvertIntervals.intervalList, - stratIL=stratIL, - gatkTag=gatkTag, - preemptible=preemptible + vcf = truthVcf, + vcfIndex = truthVcfIndex, + confidenceIL = ConfidenceConvertIntervals.intervalList, + stratIL = stratIL, + gatkTag = gatkTag, + preemptible = preemptible } if (CheckForVariantsTruth.variantsFound && CheckForVariantsEval.variantsFound) { call VcfEval as StandardVcfEval { input: - truthVCF=truthVcf, - truthVCFIndex=truthVcfIndex, - evalVCF=evalVcf, - evalVCFIndex=evalVcfIndex, - confidenceBed=ConfidenceConvertIntervals.bed, - stratBed=stratBed, - ref=ref_map["fasta"], - refDict=ref_map["dict"], - refIndex=ref_map["fai"], - outputPre=outputPrefix+"_vcfeval", - threads=threadsVcfEval, - preemptible=preemptible, - requireMatchingGenotypes=requireMatchingGenotypes, - passingOnly=passingOnly, - vcfScoreField=vcfScoreField, - enableRefOverlap=enableRefOverlap + truthVCF = truthVcf, + truthVCFIndex = truthVcfIndex, + evalVCF = evalVcf, + evalVCFIndex = evalVcfIndex, + confidenceBed = ConfidenceConvertIntervals.bed, + stratBed = stratBed, + ref = ref_map["fasta"], + refDict = ref_map["dict"], + refIndex = ref_map["fai"], + outputPre = outputPrefix+"_vcfeval", + threads = threadsVcfEval, + preemptible = preemptible, + requireMatchingGenotypes = requireMatchingGenotypes, + passingOnly = passingOnly, + vcfScoreField = vcfScoreField, + enableRefOverlap = enableRefOverlap } call WriteXMLfile as VcfEvalWriteXMLfile { input: - input_files=flatten([select_all([StandardVcfEval.outVcf,ConfidenceConvertIntervals.bed,stratifier.bed]), select_all([evalBam, truthBam])]), - input_names=flatten([select_all([outputPrefix+"_vcfeval","confidence_intervals",stratifier.label]), select_all([evalBamLabel, truthBamLabel])]), - reference_version=ref_map["fasta"], - file_name=outputPrefix+"_vcfeval" + input_files = flatten([select_all([StandardVcfEval.outVcf,ConfidenceConvertIntervals.bed,stratifier.bed]), select_all([evalBam, truthBam])]), + input_names = flatten([select_all([outputPrefix+"_vcfeval","confidence_intervals",stratifier.label]), select_all([evalBamLabel, truthBamLabel])]), + reference_version = ref_map["fasta"], + file_name = outputPrefix+"_vcfeval" } call CountUNKVcfEval { input: - vcf=StandardVcfEval.outVcf, - vcfIndex=StandardVcfEval.outVcfIndex, - gatkTag=gatkTag, - preemptible=preemptible + vcf = StandardVcfEval.outVcf, + vcfIndex = StandardVcfEval.outVcfIndex, + gatkTag = gatkTag, + preemptible = preemptible } } - String areVariants=if(CheckForVariantsTruth.variantsFound && CheckForVariantsEval.variantsFound) then "yes" else "no" + String areVariants = if(CheckForVariantsTruth.variantsFound && CheckForVariantsEval.variantsFound) then "yes" else "no" call SummariseVcfEval { input: - evalLabel=evalLabel, - truthLabel=truthLabel, - stratLabel=stratLabel, - summaryFile=StandardVcfEval.outSummary, - igvSession=VcfEvalWriteXMLfile.igv_session, - areVariants=areVariants, - unkSNP=CountUNKVcfEval.UNK_SNP, - unkINDEL=CountUNKVcfEval.UNK_INDEL, - preemptible=preemptible + evalLabel = evalLabel, + truthLabel = truthLabel, + stratLabel = stratLabel, + summaryFile = StandardVcfEval.outSummary, + igvSession = VcfEvalWriteXMLfile.igv_session, + areVariants = areVariants, + unkSNP = CountUNKVcfEval.UNK_SNP, + unkINDEL = CountUNKVcfEval.UNK_INDEL, + preemptible = preemptible } } @@ -317,23 +315,23 @@ workflow Benchmark { } scatter (evalStratIndelCombo in evalStratIndelCombos) { - String jexl=evalStratIndelCombo.variantSelector.jexl - File? vcfVcfEval=evalStratIndelCombo.annotatedVcfs.vcfVcfEval - File? vcfVcfEvalIndex=evalStratIndelCombo.annotatedVcfs.vcfVcfEvalIndex - String evalIndelLabel=evalStratIndelCombo.annotatedVcfs.evalLabel - String truthIndelLabel=evalStratIndelCombo.annotatedVcfs.truthLabel - String? stratIndelLabel=evalStratIndelCombo.annotatedVcfs.stratLabel - String indelLabel=evalStratIndelCombo.variantSelector.label - File? stratIndelBed=evalStratIndelCombo.annotatedVcfs.stratBed - File? confidenceBed=evalStratIndelCombo.annotatedVcfs.confidenceBed - String namePrefix=evalStratIndelCombo.annotatedVcfs.namePrefix+"_"+indelLabel + String jexl = evalStratIndelCombo.variantSelector.jexl + File? vcfVcfEval = evalStratIndelCombo.annotatedVcfs.vcfVcfEval + File? vcfVcfEvalIndex = evalStratIndelCombo.annotatedVcfs.vcfVcfEvalIndex + String evalIndelLabel = evalStratIndelCombo.annotatedVcfs.evalLabel + String truthIndelLabel = evalStratIndelCombo.annotatedVcfs.truthLabel + String? stratIndelLabel = evalStratIndelCombo.annotatedVcfs.stratLabel + String indelLabel = evalStratIndelCombo.variantSelector.label + File? stratIndelBed = evalStratIndelCombo.annotatedVcfs.stratBed + File? confidenceBed = evalStratIndelCombo.annotatedVcfs.confidenceBed + String namePrefix = evalStratIndelCombo.annotatedVcfs.namePrefix+"_"+indelLabel if (defined(vcfVcfEval) && defined(vcfVcfEvalIndex) && doIndelLengthStratification) { call EvalForVariantSelection as EvalIndelLengthVcfEval { input: - vcf=vcfVcfEval, - vcfIndex=vcfVcfEvalIndex, - jexl=jexl, + vcf = vcfVcfEval, + vcfIndex = vcfVcfEvalIndex, + jexl = jexl, engine="VcfEval", selectTPCall="CALL == 'TP'", selectTPBase="BASE == 'TP'", @@ -341,36 +339,36 @@ workflow Benchmark { selectFP="(CALL == 'FP' || CALL == 'FP_CA')", sampleCall="CALLS", sampleBase="BASELINE", - gatkTag=gatkTag, - preemptible=preemptible, - gatkJarForAnnotation=gatkJarForAnnotation, - annotationNames=annotationNames, - reference=ref_map["fasta"], - refDict=ref_map["dict"], - refIndex=ref_map["fai"] + gatkTag = gatkTag, + preemptible = preemptible, + gatkJarForAnnotation = gatkJarForAnnotation, + annotationNames = annotationNames, + reference = ref_map["fasta"], + refDict = ref_map["dict"], + refIndex = ref_map["fai"] } call WriteXMLfile as VcfEvalIndelWriteXMLfile { input: - input_files=flatten([select_all([EvalIndelLengthVcfEval.selectedTPCall,EvalIndelLengthVcfEval.selectedTPBase,EvalIndelLengthVcfEval.selectedFP,EvalIndelLengthVcfEval.selectedFN,vcfVcfEval,confidenceBed,stratIndelBed]), select_all([evalBam, truthBam])]), - input_names=flatten([select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",stratIndelLabel]), select_all([evalBamLabel, truthBamLabel])]), - reference_version=ref_map["fasta"], - file_name=namePrefix+"_vcfeval" + input_files = flatten([select_all([EvalIndelLengthVcfEval.selectedTPCall,EvalIndelLengthVcfEval.selectedTPBase,EvalIndelLengthVcfEval.selectedFP,EvalIndelLengthVcfEval.selectedFN,vcfVcfEval,confidenceBed,stratIndelBed]), select_all([evalBam, truthBam])]), + input_names = flatten([select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",stratIndelLabel]), select_all([evalBamLabel, truthBamLabel])]), + reference_version = ref_map["fasta"], + file_name = namePrefix+"_vcfeval" } call SummariseForIndelSelection as VcfEvalSummariseForIndelSelection { input: - evalLabel=evalIndelLabel, - truthLabel=truthIndelLabel, - stratLabel=stratIndelLabel, - indelLabel=indelLabel, + evalLabel = evalIndelLabel, + truthLabel = truthIndelLabel, + stratLabel = stratIndelLabel, + indelLabel = indelLabel, engine="VcfEval", - igvSession=VcfEvalIndelWriteXMLfile.igv_session, - TP_CALL=EvalIndelLengthVcfEval.TP_CALL, - TP_BASE=EvalIndelLengthVcfEval.TP_BASE, - FP=EvalIndelLengthVcfEval.FP, - FN=EvalIndelLengthVcfEval.FN, - preemptible=preemptible + igvSession = VcfEvalIndelWriteXMLfile.igv_session, + TP_CALL = EvalIndelLengthVcfEval.TP_CALL, + TP_BASE = EvalIndelLengthVcfEval.TP_BASE, + FP = EvalIndelLengthVcfEval.FP, + FN = EvalIndelLengthVcfEval.FN, + preemptible = preemptible } } } @@ -387,9 +385,9 @@ workflow Benchmark { if (defined(evalStratSelectorCombo.annotatedVcfs.vcfVcfEval) && defined(evalStratSelectorCombo.annotatedVcfs.vcfVcfEvalIndex)) { call EvalForVariantSelection as EvalSelectorVcfEval { input: - vcf=evalStratSelectorCombo.annotatedVcfs.vcfVcfEval, - vcfIndex=evalStratSelectorCombo.annotatedVcfs.vcfVcfEvalIndex, - jexl=evalStratSelectorCombo.variantSelector.jexl, + vcf = evalStratSelectorCombo.annotatedVcfs.vcfVcfEval, + vcfIndex = evalStratSelectorCombo.annotatedVcfs.vcfVcfEvalIndex, + jexl = evalStratSelectorCombo.variantSelector.jexl, engine="VcfEval", selectTPCall="CALL == 'TP'", selectTPBase="BASE == 'TP'", @@ -397,36 +395,36 @@ workflow Benchmark { selectFP="(CALL == 'FP' || CALL == 'FP_CA')", sampleCall="CALLS", sampleBase="BASELINE", - gatkTag=gatkTag, - preemptible=preemptible, - gatkJarForAnnotation=gatkJarForAnnotation, - annotationNames=annotationNames, - reference=ref_map["fasta"], - refDict=ref_map["dict"], - refIndex=ref_map["fai"] + gatkTag = gatkTag, + preemptible = preemptible, + gatkJarForAnnotation = gatkJarForAnnotation, + annotationNames = annotationNames, + reference = ref_map["fasta"], + refDict = ref_map["dict"], + refIndex = ref_map["fai"] } call WriteXMLfile as VcfEvalSelectorWriteXMLfile { input: - input_files=flatten([select_all([EvalSelectorVcfEval.selectedTPCall,EvalSelectorVcfEval.selectedTPBase,EvalSelectorVcfEval.selectedFP,EvalSelectorVcfEval.selectedFN, + input_files = flatten([select_all([EvalSelectorVcfEval.selectedTPCall,EvalSelectorVcfEval.selectedTPBase,EvalSelectorVcfEval.selectedFP,EvalSelectorVcfEval.selectedFN, evalStratSelectorCombo.annotatedVcfs.vcfVcfEval,evalStratSelectorCombo.annotatedVcfs.confidenceBed,evalStratSelectorCombo.annotatedVcfs.stratBed]), select_all([evalBam, truthBam])]), - input_names=flatten([select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",evalStratSelectorCombo.annotatedVcfs.stratLabel]), select_all([evalBamLabel, truthBamLabel])]), - reference_version=ref_map["fasta"], - file_name=evalStratSelectorCombo.annotatedVcfs.namePrefix+"_"+evalStratSelectorCombo.variantSelector.label+"_vcfeval" + input_names = flatten([select_all(["TP_Eval","TP_Base","FP","FN","All_Variants","confidence_intervals",evalStratSelectorCombo.annotatedVcfs.stratLabel]), select_all([evalBamLabel, truthBamLabel])]), + reference_version = ref_map["fasta"], + file_name = evalStratSelectorCombo.annotatedVcfs.namePrefix+"_"+evalStratSelectorCombo.variantSelector.label+"_vcfeval" } call SummariseForVariantSelection as VcfEvalSummariseForVariantSelection { input: - evalLabel=evalStratSelectorCombo.annotatedVcfs.evalLabel, - truthLabel=evalStratSelectorCombo.annotatedVcfs.truthLabel, - stratLabel=evalStratSelectorCombo.annotatedVcfs.stratLabel, - variantLabel=evalStratSelectorCombo.variantSelector.label, + evalLabel = evalStratSelectorCombo.annotatedVcfs.evalLabel, + truthLabel = evalStratSelectorCombo.annotatedVcfs.truthLabel, + stratLabel = evalStratSelectorCombo.annotatedVcfs.stratLabel, + variantLabel = evalStratSelectorCombo.variantSelector.label, engine="VcfEval", - igvSession=VcfEvalSelectorWriteXMLfile.igv_session, - TP_CALL=EvalSelectorVcfEval.TP_CALL, - TP_BASE=EvalSelectorVcfEval.TP_BASE, - FP=EvalSelectorVcfEval.FP, - FN=EvalSelectorVcfEval.FN, - preemptible=preemptible + igvSession = VcfEvalSelectorWriteXMLfile.igv_session, + TP_CALL = EvalSelectorVcfEval.TP_CALL, + TP_BASE = EvalSelectorVcfEval.TP_BASE, + FP = EvalSelectorVcfEval.FP, + FN = EvalSelectorVcfEval.FN, + preemptible = preemptible } } } @@ -436,22 +434,22 @@ workflow Benchmark { call CombineSummaries { input: - summaries=summaries, - preemptible=preemptible + summaries = summaries, + preemptible = preemptible } ################################################################################ output { - File summary=CombineSummaries.summaryOut - Float snpPrecision=SummariseVcfEval.snpPrecision[0] - Float indelPrecision=SummariseVcfEval.indelPrecision[0] - Float snpRecall=SummariseVcfEval.snpRecall[0] - Float indelRecall=SummariseVcfEval.indelRecall[0] - Float snpF1Score=SummariseVcfEval.snpF1Score[0] - Float indelF1Score=SummariseVcfEval.indelF1Score[0] - Array[File?] snpRocs=StandardVcfEval.outSnpRoc - Array[File?] nonSnpRocs=StandardVcfEval.outNonSnpRoc + File summary = CombineSummaries.summaryOut + Float snpPrecision = SummariseVcfEval.snpPrecision[0] + Float indelPrecision = SummariseVcfEval.indelPrecision[0] + Float snpRecall = SummariseVcfEval.snpRecall[0] + Float indelRecall = SummariseVcfEval.indelRecall[0] + Float snpF1Score = SummariseVcfEval.snpF1Score[0] + Float indelF1Score = SummariseVcfEval.indelF1Score[0] + Array[File?] snpRocs = StandardVcfEval.outSnpRoc + Array[File?] nonSnpRocs = StandardVcfEval.outNonSnpRoc } } @@ -511,9 +509,9 @@ task CheckForVariants { Int? memoryMaybe String gatkTag } - Int memoryDefault=16 - Int memoryJava=select_first([memoryMaybe,memoryDefault]) - Int memoryRam=memoryJava+2 + Int memoryDefault = 16 + Int memoryJava = select_first([memoryMaybe,memoryDefault]) + Int memoryRam = memoryJava+2 Int disk_size = 10 + ceil(size(vcf, "GB") + size(vcfIndex, "GB") + size(confidenceIL, "GB") + size(stratIL, "GB")) @@ -533,7 +531,7 @@ task CheckForVariants { } output { - Boolean variantsFound=read_boolean("outBool.txt") + Boolean variantsFound = read_boolean("outBool.txt") } } @@ -566,9 +564,9 @@ task VcfEval { Boolean enableRefOverlap = false } String memDefault="16 GB" - String mem=select_first([memUser,memDefault]) + String mem = select_first([memUser,memDefault]) - Int cpu=select_first([threads,1]) + Int cpu = select_first([threads,1]) Int disk_size = 50 + ceil(size(truthVCF, "GB") + size(truthVCFIndex, "GB") + 2.2 * size(evalVCF, "GB") + size(evalVCFIndex, "GB") + size(confidenceBed, "GB") + size(stratBed, "GB") + size(ref, "GB") + size(refDict, "GB") + size(refIndex, "GB")) command <<< @@ -596,32 +594,32 @@ task VcfEval { import gzip import sys - indel_sensitivity=0 - indel_precision=0 - indel_fscore=0 - indel_TP_Base=0 - indel_TP_Eval=0 - indel_FP=0 - indel_FN=0 - - snp_sensitivity=0 - snp_precision=0 - snp_fscore=0 - snp_TP_Base=0 - snp_TP_Eval=0 - snp_FP=0 - snp_FN=0 + indel_sensitivity = 0 + indel_precision = 0 + indel_fscore = 0 + indel_TP_Base = 0 + indel_TP_Eval = 0 + indel_FP = 0 + indel_FN = 0 + + snp_sensitivity = 0 + snp_precision = 0 + snp_fscore = 0 + snp_TP_Base = 0 + snp_TP_Eval = 0 + snp_FP = 0 + snp_FN = 0 with gzip.open(sys.argv[1],"rt") as f_snp: for line in f_snp: try: - snp_sensitivity=float(line.split()[6]) - snp_precision=float(line.split()[5]) - snp_fscore=float(line.split()[7]) - snp_TP_Eval=float(line.split()[3]) - snp_TP_Base=float(line.split()[1]) - snp_FP=float(line.split()[2]) - snp_FN=float(line.split()[4]) + snp_sensitivity = float(line.split()[6]) + snp_precision = float(line.split()[5]) + snp_fscore = float(line.split()[7]) + snp_TP_Eval = float(line.split()[3]) + snp_TP_Base = float(line.split()[1]) + snp_FP = float(line.split()[2]) + snp_FN = float(line.split()[4]) except ValueError: continue except IndexError: @@ -630,25 +628,25 @@ task VcfEval { with gzip.open(sys.argv[2],"rt") as f_indel: for line in f_indel: try: - indel_sensitivity=float(line.split()[6]) - indel_precision=float(line.split()[5]) - indel_fscore=float(line.split()[7]) - indel_TP_Eval=float(line.split()[3]) - indel_TP_Base=float(line.split()[1]) - indel_FP=float(line.split()[2]) - indel_FN=float(line.split()[4]) + indel_sensitivity = float(line.split()[6]) + indel_precision = float(line.split()[5]) + indel_fscore = float(line.split()[7]) + indel_TP_Eval = float(line.split()[3]) + indel_TP_Base = float(line.split()[1]) + indel_FP = float(line.split()[2]) + indel_FN = float(line.split()[4]) except ValueError: continue except IndexError: continue f_indel.close() - str_indel_sensitivity=str(indel_sensitivity) - str_indel_precision=str(indel_precision) - str_indel_fscore=str(indel_fscore) - str_snp_sensitivity=str(snp_sensitivity) - str_snp_precision=str(snp_precision) - str_snp_fscore=str(snp_fscore) + str_indel_sensitivity = str(indel_sensitivity) + str_indel_precision = str(indel_precision) + str_indel_fscore = str(indel_fscore) + str_snp_sensitivity = str(snp_sensitivity) + str_snp_precision = str(snp_precision) + str_snp_fscore = str(snp_fscore) if indel_TP_Eval+indel_FP==0: @@ -684,7 +682,7 @@ task VcfEval { } output { - Array[File] outs=glob("${outputPre}_*") + Array[File] outs = glob("${outputPre}_*") File outSummary="${outputPre}_summary.csv" File outVcf="${outputPre}_output.vcf.gz" File outVcfIndex="${outputPre}_output.vcf.gz.tbi" @@ -713,9 +711,9 @@ task EvalHappy { String happyTag } String memDefault="16 GB" - String mem=select_first([memUser,memDefault]) + String mem = select_first([memUser,memDefault]) - Int cpu=select_first([threads,1]) + Int cpu = select_first([threads,1]) Int disk_size = 10 + ceil(size(truthVCF, "GB") + size(truthVCFIndex, "GB") + 2.2 * size(evalVCF, "GB") + size(confidenceBed, "GB") + size(stratBed, "GB") + size(ref, "GB") + size(refDict, "GB") + size(refIndex, "GB")) @@ -732,7 +730,7 @@ task EvalHappy { } output { - Array[File] outs=glob("${outputPre}*") + Array[File] outs = glob("${outputPre}*") File outSummary="${outputPre}.summary.csv" File outVcf="${outputPre}.vcf.gz" File outVcfIndex="${outputPre}.vcf.gz.tbi" @@ -756,9 +754,9 @@ task EvalGATKGC { Int? memoryMaybe String gatkTag } - Int memoryDefault=16 - Int memoryJava=select_first([memoryMaybe,memoryDefault]) - Int memoryRam=memoryJava+2 + Int memoryDefault = 16 + Int memoryJava = select_first([memoryMaybe,memoryDefault]) + Int memoryRam = memoryJava+2 Int disk_size = 10 + ceil(size(truthVCF, "GB") + size(truthVCFIndex, "GB") + 2.2 * size(evalVCF, "GB") + size(intervalList, "GB") + size(stratIL, "GB") + size(evalVCFIndex, "GB") + size(ref, "GB") + size(refDict, "GB") + size(refIndex, "GB")) @@ -775,7 +773,7 @@ task EvalGATKGC { } output { - Array[File] out=glob("${outputPre}*") + Array[File] out = glob("${outputPre}*") File outSummary="${outputPre}.genotype_concordance_summary_metrics" File outCounts="${outputPre}.genotype_concordance_contingency_metrics" File outVcf="${outputPre}.genotype_concordance.vcf.gz" @@ -795,9 +793,9 @@ task ConvertIntervals { String? dummyInputForTerraCallCaching } - Int memoryDefault=16 - Int memoryJava=select_first([memoryMaybe,memoryDefault]) - Int memoryRam=memoryJava+2 + Int memoryDefault = 16 + Int memoryJava = select_first([memoryMaybe,memoryDefault]) + Int memoryRam = memoryJava+2 Int disk_size = 10 + ceil(3 * size(inputIntervals, "GB") + size(refDict, "GB")) command <<< @@ -861,9 +859,9 @@ task FixVcfHeader { Int? memoryMaybe String gatkTag } - Int memoryDefault=16 - Int memoryJava=select_first([memoryMaybe,memoryDefault]) - Int memoryRam=memoryJava+2 + Int memoryDefault = 16 + Int memoryJava = select_first([memoryMaybe,memoryDefault]) + Int memoryRam = memoryJava+2 Int disk_size = 10 + ceil(2.2 * size(vcf, "GB") + 2.2 * size(vcfIndex, "GB") + size(ref, "GB") + size(refDict, "GB") + size(refIndex, "GB")) command <<< @@ -897,9 +895,9 @@ task CountUNKVcfEval { Int? memoryMaybe String gatkTag } - Int memoryDefault=16 - Int memoryJava=select_first([memoryMaybe,memoryDefault]) - Int memoryRam=memoryJava+2 + Int memoryDefault = 16 + Int memoryJava = select_first([memoryMaybe,memoryDefault]) + Int memoryRam = memoryJava+2 Int disk_size = 10 + ceil(size(vcf, "GB") + size(vcfIndex, "GB")) command <<< @@ -922,8 +920,8 @@ task CountUNKVcfEval { memory: memoryRam + " GB" } output { - Int UNK_SNP=read_int("unk_snp.txt") - Int UNK_INDEL=read_int("unk_indel.txt") + Int UNK_SNP = read_int("unk_snp.txt") + Int UNK_INDEL = read_int("unk_indel.txt") } } @@ -939,9 +937,9 @@ task CountUNKGC { File? stratIL String gatkTag } - Int memoryDefault=16 - Int memoryJava=select_first([memoryMaybe,memoryDefault]) - Int memoryRam=memoryJava+2 + Int memoryDefault = 16 + Int memoryJava = select_first([memoryMaybe,memoryDefault]) + Int memoryRam = memoryJava+2 Int disk_size = 10 + ceil(size(vcfAnnotated, "GB") + size(vcfIndexAnnotated, "GB") + 2 * size(vcfOrig, "GB") + size(vcfIndexOrig, "GB") + size(stratIL, "GB")) command <<< @@ -967,8 +965,8 @@ task CountUNKGC { } output { - Int UNK_SNP=read_int("unk_snp.txt") - Int UNK_INDEL=read_int("unk_indel.txt") + Int UNK_SNP = read_int("unk_snp.txt") + Int UNK_INDEL = read_int("unk_indel.txt") } } @@ -996,14 +994,14 @@ task EvalForVariantSelection { File refIndex } - Int memoryDefault=16 - Int memoryJava=select_first([memoryMaybe,memoryDefault]) - Int memoryRam=memoryJava+2 + Int memoryDefault = 16 + Int memoryJava = select_first([memoryMaybe,memoryDefault]) + Int memoryRam = memoryJava+2 - String selectionTPCall=jexl + " && " + selectTPCall - String selectionTPBase=jexl + " && " + selectTPBase - String selectionFN=jexl + " && " + selectFN - String selectionFP=jexl + " && " + selectFP + String selectionTPCall = jexl + " && " + selectTPCall + String selectionTPBase = jexl + " && " + selectTPBase + String selectionFN = jexl + " && " + selectFN + String selectionFP = jexl + " && " + selectFP Int disk_size = 10 + ceil(4.2 * size(vcf, "GB") + 2.2 * size(vcfIndex, "GB") + size(reference, "GB")) @@ -1013,7 +1011,7 @@ task EvalForVariantSelection { VCF=~{vcf} if [[ ! -z "~{gatkJarForAnnotation}" ]]; then java -jar ~{gatkJarForAnnotation} VariantAnnotator -V ~{vcf} -O annotated.vcf.gz ~{true="-A" false="" length(annotationNames)>0} ~{sep=" -A " annotationNames} -R ~{reference} - VCF=annotated.vcf.gz + VCF = annotated.vcf.gz else touch annotated.vcf.gz fi @@ -1043,10 +1041,10 @@ task EvalForVariantSelection { } output { - Int TP_CALL=read_int("tp_call.txt") - Int TP_BASE=read_int("tp_base.txt") - Int FP=read_int("fp.txt") - Int FN=read_int("fn.txt") + Int TP_CALL = read_int("tp_call.txt") + Int TP_BASE = read_int("tp_base.txt") + Int FP = read_int("fp.txt") + Int FN = read_int("fn.txt") File selectedTPCall="selected.TP_CALL.vcf.gz" File selectedTPBase="selected.TP_BASE.vcf.gz" File selectedFP="selected.FP.vcf.gz" @@ -1088,12 +1086,12 @@ task SummariseForIndelSelection { return(NA) } pos_start<-regexpr(target,name) - sub=substring(name,pos_start+attr(pos_start,"match.length")+1,nchar(name)) - split_sub=strsplit(sub,"_") - val=if(grepl("^m",split_sub[[1]][[1]])) -as.double(gsub("m","",split_sub[[1]][[1]])) else as.double(split_sub[[1]][[1]]) + sub = substring(name,pos_start+attr(pos_start,"match.length")+1,nchar(name)) + split_sub = strsplit(sub,"_") + val = if(grepl("^m",split_sub[[1]][[1]])) -as.double(gsub("m","",split_sub[[1]][[1]])) else as.double(split_sub[[1]][[1]]) } - args <-commandArgs(trailingOnly=TRUE) + args <-commandArgs(trailingOnly = TRUE) indel_options <-c("deletion","insertion","indel_fine","indel_coarse") indel_type <- mapply(grepl,indel_options,args[7]) indel_type <- indel_options[indel_type[indel_options]] @@ -1108,7 +1106,7 @@ task SummariseForIndelSelection { "Recall"=as.numeric(args[2])/(as.numeric(args[2])+as.numeric(args[3])),"Precision"=as.numeric(args[1])/(as.numeric(args[1])+as.numeric(args[4])),"TP_Base"=as.numeric(args[2]),"TP_Eval"=as.numeric(args[1]), "FP"=as.numeric(args[4]),"FN"=as.numeric(args[3]),"IGV_Session"=args[length(args)],"Summary_Type"=indel_type) table$F1_Score <- 2*table$Precision*table$Recall/(table$Precision+table$Recall) - write.csv(table,paste(args[8],".",indel_type,".summary.csv",sep=""),row.names=FALSE) + write.csv(table,paste(args[8],".",indel_type,".summary.csv",sep=""),row.names = FALSE) EOF >>> @@ -1119,7 +1117,7 @@ task SummariseForIndelSelection { } output { - File summaryOut=glob("*.summary.csv")[0] + File summaryOut = glob("*.summary.csv")[0] } } @@ -1145,7 +1143,7 @@ task SummariseForVariantSelection { set -xeuo pipefail Rscript -<<"EOF" ~{TP_CALL} ~{TP_BASE} ~{FN} ~{FP} ~{evalLabel} ~{truthLabel} ~{variantLabel} ~{engine} ~{default="" stratLabel} ~{igvSession} - args <-commandArgs(trailingOnly=TRUE) + args <-commandArgs(trailingOnly = TRUE) if (length(args)<10) { stratifier <- NA } else { @@ -1156,7 +1154,7 @@ task SummariseForVariantSelection { "Recall"=as.numeric(args[2])/(as.numeric(args[2])+as.numeric(args[3])),"Precision"=as.numeric(args[1])/(as.numeric(args[1])+as.numeric(args[4])),"TP_Base"=as.numeric(args[2]),"TP_Eval"=as.numeric(args[1]), "FP"=as.numeric(args[4]),"FN"=as.numeric(args[3]),"IGV_Session"=args[length(args)],"Summary_Type"="summary","Type"=args[7]) table$F1_Score <- 2*table$Precision*table$Recall/(table$Precision+table$Recall) - write.csv(table,paste(args[8],".",args[7],".summary.csv",sep=""),row.names=FALSE) + write.csv(table,paste(args[8],".",args[7],".summary.csv",sep=""),row.names = FALSE) EOF >>> @@ -1167,7 +1165,7 @@ task SummariseForVariantSelection { } output { - File summaryOut=glob("*.summary.csv")[0] + File summaryOut = glob("*.summary.csv")[0] } } @@ -1238,7 +1236,7 @@ task SummariseVcfEval { table_vcfeval$Truth_Set <- args[2] table_vcfeval$Summary_Type <- "summary" table_vcfeval$Comparison_Engine <-"VcfEval" - write.csv(table_vcfeval,"vcfeval.summary.csv",row.names=FALSE) + write.csv(table_vcfeval,"vcfeval.summary.csv",row.names = FALSE) EOF >>> @@ -1250,12 +1248,12 @@ task SummariseVcfEval { output{ File summaryOut="vcfeval.summary.csv" - Float snpPrecision=read_float("snpPrecision.txt") - Float indelPrecision=read_float("indelPrecision.txt") - Float snpRecall=read_float("snpRecall.txt") - Float indelRecall=read_float("indelRecall.txt") - Float snpF1Score=read_float("snpF1Score.txt") - Float indelF1Score=read_float("indelF1Score.txt") + Float snpPrecision = read_float("snpPrecision.txt") + Float indelPrecision = read_float("indelPrecision.txt") + Float snpRecall = read_float("snpRecall.txt") + Float indelRecall = read_float("indelRecall.txt") + Float snpF1Score = read_float("snpF1Score.txt") + Float indelF1Score = read_float("indelF1Score.txt") } } @@ -1282,7 +1280,7 @@ task SummariseHappy { colnames(table_happy)[11]<-"Precision" colnames(table_happy)[13]<-"F1_Score" colnames(table_happy)[4]<-"TP_Base" - table_happy$TP_Eval=table_happy$TP_Base + table_happy$TP_Eval = table_happy$TP_Base colnames(table_happy)[5]<-"FN" colnames(table_happy)[7]<-"FP" colnames(table_happy)[8]<-"UNK" @@ -1323,7 +1321,7 @@ task SummariseHappy { table_add <- data.frame("Type"="INDEL","Recall"=NA,"Precision"=NA,"F1_Score"=NA,"TP_Eval"=0,"TP_Base"=0,"FP"=0,"FN"=0,"UNK"=0,"Name"=args[1],"Truth_Set"=args[2],"Comparison_Engine"="Happy","Stratifier"=table_happy$Stratifier[1],"IGV_Session"=NA,"Summary_Type"="summary") table_happy <- rbind(table_happy,table_add) } - write.csv(table_happy,"happy.summary.csv",row.names=FALSE) + write.csv(table_happy,"happy.summary.csv",row.names = FALSE) EOF >>> @@ -1361,8 +1359,8 @@ task SummariseGATKGC { Rscript -<<"EOF" ~{evalLabel} ~{truthLabel} ~{default="" summaryFile} ~{default="" summaryCounts} ~{default="" stratLabel} ~{default="" igvSession} ~{default="" unkSNP} ~{default="" unkINDEL} ~{areVariants} args <- commandArgs(trailingOnly = TRUE) if (args[length(args)]=="yes") { - table_GC <- read.table(args[3],skip=6, header=TRUE,sep="\t",na.strings="?") - table_counts_GC <- read.table(args[4],skip=6, header=TRUE,sep="\t",na.strings="?") + table_GC <- read.table(args[3],skip = 6, header = TRUE,sep="\t",na.strings="?") + table_counts_GC <- read.table(args[4],skip = 6, header = TRUE,sep="\t",na.strings="?") table_GC$F1_Score <- 2*(table_GC$VAR_PPV*table_GC$VAR_SENSITIVITY)/(table_GC$VAR_PPV+table_GC$VAR_SENSITIVITY) table_GC$TP_Eval <- table_counts_GC$TP_COUNT table_GC$TP_Base <- table_counts_GC$TP_COUNT @@ -1405,7 +1403,7 @@ task SummariseGATKGC { table_GC$Comparison_Engine <-"GATK_GC" table_GC$Summary_Type <- "summary" table_GC <- table_GC[,c("Type","Recall","Precision","Name","Truth_Set","Comparison_Engine","F1_Score","TP_Eval","TP_Base","FP","FN","UNK","Stratifier","IGV_Session","Summary_Type")] - write.csv(table_GC,"gatkgc.summary.csv",row.names=FALSE) + write.csv(table_GC,"gatkgc.summary.csv",row.names = FALSE) EOF >>> @@ -1436,9 +1434,9 @@ task CombineSummaries { library(readr) library(dplyr) library(purrr) - summary_files <- read_csv("~{write_lines(summaries)}", col_names=FALSE) + summary_files <- read_csv("~{write_lines(summaries)}", col_names = FALSE) merged<- as.list(summary_files$X1) %>% map(read_csv) %>% reduce(bind_rows) - write.csv(merged,"summary.csv",row.names=FALSE) + write.csv(merged,"summary.csv",row.names = FALSE) EOF >>> @@ -1465,9 +1463,9 @@ task MatchEvalTruth { Int? memoryMaybe String gatkTag } - Int memoryDefault=16 - Int memoryJava=select_first([memoryMaybe,memoryDefault]) - Int memoryRam=memoryJava+2 + Int memoryDefault = 16 + Int memoryJava = select_first([memoryMaybe,memoryDefault]) + Int memoryRam = memoryJava+2 Int disk_size = 10 + ceil(size(hapMap, "GB") + size(evalVcf, "GB") + size(evalVcfIndex, "GB") + size(truthVcf, "GB") + size(truthVcfIndex, "GB")) command <<< @@ -1492,11 +1490,19 @@ task WriteXMLfile { String file_name Array[String]? input_names="" - Array[String] input_names_prefix = if defined(input_names) then prefix('-n ', select_first([input_names])) else [] - } - command { - bash /usr/writeIGV.sh ~{reference_version} ~{sep=" " input_files} ~{sep=" " input_names_prefix} > "~{file_name}.xml" } + + Array[String] input_names_prefix = if defined(input_names) then prefix('-n ', select_first([input_names])) else [] + + command <<< + set -euxo pipefail + + # because of some nonsense above, we need to play some bash tricks here to make sure that + # the inputs are labeled correctly: + echo '~{sep=" " input_names_prefix}' | sed -e 's#-n[ \t]*-n#-n#g' -e 's#-n[ \t]*$##' > labels.txt + + bash /usr/writeIGV.sh ~{reference_version} ~{sep=" " input_files} $(cat labels.txt) > "~{file_name}.xml" + >>> runtime { docker: "quay.io/mduran/generate-igv-session_2:v1.0" } From ba5ce9f3018d73980dddbb920a8c04b63c8db646 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 16 Feb 2023 13:22:28 -0500 Subject: [PATCH 116/297] Added `CompareVcfBenchmarks.wdl`. --- .dockstore.yml | 4 + wdl/CompareBenchmarks.wdl | 450 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 454 insertions(+) create mode 100644 wdl/CompareBenchmarks.wdl diff --git a/.dockstore.yml b/.dockstore.yml index 2e0ce3254..192550393 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -147,4 +147,8 @@ workflows: - name: BenchmarkVCFs subclass: wdl primaryDescriptorPath: /wdl/BenchmarkVCFs.wdl + testParameterFiles: +- name: CompareVcfBenchmarks + subclass: wdl + primaryDescriptorPath: /wdl/CompareVcfBenchmarks.wdl testParameterFiles: \ No newline at end of file diff --git a/wdl/CompareBenchmarks.wdl b/wdl/CompareBenchmarks.wdl new file mode 100644 index 000000000..6c9bca949 --- /dev/null +++ b/wdl/CompareBenchmarks.wdl @@ -0,0 +1,450 @@ +version 1.0 + +# Adapted from the palantir / Hydrogen team repository (https://github.com/broadinstitute/palantir-workflows/tree/mg_benchmark_compare/BenchmarkVCFs) +# Original Author: Michael Gatzen +# Documentation: https://github.com/broadinstitute/palantir-workflows/blob/mg_benchmark_compare/BenchmarkVCFs/README_CompareBenchmarks.md + +workflow CompareVcfBenchmarks { + input { + Array[String] sample_ids + Array[String] configurations + Array[File] benchmark_summaries + Array[String]? stratifiers + + Boolean include_counts = true + Boolean generate_gc_plots = true + + Array[String]? order_of_samples + Array[String]? order_of_configurations + Array[Int]? deltas + + Int? mem_gb + Int? preemptible + } + + call CompareBenchmarksTask { + input: + sample_ids = sample_ids, + configurations = configurations, + benchmark_summaries = benchmark_summaries, + stratifiers = stratifiers, + include_counts = include_counts, + order_of_samples = order_of_samples, + order_of_configurations = order_of_configurations, + deltas = deltas, + mem_gb = mem_gb, + preemptible = preemptible + } + + if (generate_gc_plots) { + call CreateGCPlotsTask { + input: + sample_ids = sample_ids, + configurations = configurations, + benchmark_summaries = benchmark_summaries, + order_of_samples = order_of_samples, + order_of_configurations = order_of_configurations, + mem_gb = mem_gb, + preemptible = preemptible + } + } + + output { + File comparison_csv = CompareBenchmarksTask.comparison_csv + File raw_data = CompareBenchmarksTask.raw_data + Array[File]? gc_plots = CreateGCPlotsTask.gc_plots + } +} + +task CreateGCPlotsTask { + input { + Array[String] sample_ids + Array[String] configurations + Array[File] benchmark_summaries + + Array[String]? order_of_samples + Array[String]? order_of_configurations + + Int mem_gb = 4 + Int preemptible = 0 + } + + String order_of_samples_arg = if !defined(order_of_samples) then "" else "--order-of-samples" + Array[String] order_of_samples_or_empty = select_first([order_of_samples, []]) + String order_of_configurations_arg = if !defined(order_of_configurations) then "" else "--order-of-configurations" + Array[String] order_of_configurations_or_empty = select_first([order_of_configurations, []]) + + command <<< + set -xeuo pipefail + + source activate compare_benchmarks + + cat <<'EOF' > script.py +import argparse +import numpy as np +import pandas as pd +import matplotlib +import matplotlib.pyplot as plt + +matplotlib.rcParams['text.usetex'] = False +matplotlib.rcParams['mathtext.default'] = 'regular' +matplotlib.rcParams['font.family'] = 'serif' + +def get_value_from_table(data, sample_id, configuration, stratifier, var_type, column): + return data.query('sample_id == @sample_id and configuration == @configuration and Stratifier == @stratifier and Type == @var_type').iloc[0][column] + +def calculate_metrics(data, unique_sample_ids, unique_configurations, stratifiers): + recalculated_data = pd.DataFrame(columns=['sample_id', 'configuration', 'Stratifier', 'Type', 'TP', 'FP', 'FN', 'Precision', 'Sensitivity', 'F-Measure']) + for sample_id in unique_sample_ids: + for configuration in unique_configurations: + for stratifier in stratifiers: + tp, fp, fn = dict(), dict(), dict() + tp['SNP'] = get_value_from_table(data, sample_id, configuration, stratifier, 'SNP', 'TP_Base') + fp['SNP'] = get_value_from_table(data, sample_id, configuration, stratifier, 'SNP', 'FP') + fn['SNP'] = get_value_from_table(data, sample_id, configuration, stratifier, 'SNP', 'FN') + tp['INDEL'] = get_value_from_table(data, sample_id, configuration, stratifier, 'INDEL', 'TP_Base') + fp['INDEL'] = get_value_from_table(data, sample_id, configuration, stratifier, 'INDEL', 'FP') + fn['INDEL'] = get_value_from_table(data, sample_id, configuration, stratifier, 'INDEL', 'FN') + tp['all'] = tp['SNP'] + tp['INDEL'] + fp['all'] = fp['SNP'] + fp['INDEL'] + fn['all'] = fn['SNP'] + fn['INDEL'] + + for var_type in ['SNP', 'INDEL', 'all']: + recalculated_data = recalculated_data.append({ + 'sample_id': sample_id, + 'configuration': configuration, + 'Stratifier': stratifier, + 'Type': var_type, + 'TP': tp[var_type], + 'FP': fp[var_type], + 'FN': fn[var_type], + 'Precision': tp[var_type]/(tp[var_type] + fp[var_type]) if tp[var_type] + fp[var_type] > 0 else np.nan, + 'Sensitivity': tp[var_type]/(tp[var_type] + fn[var_type]) if tp[var_type] + fn[var_type] > 0 else np.nan, + 'F-Measure': tp[var_type]/(tp[var_type] + 0.5*(fp[var_type] + fn[var_type])) if tp[var_type] + fp[var_type] + fn[var_type] > 0 else np.nan + }, ignore_index=True) + return recalculated_data + +def plot_sample(data, i_sample, sample_id, unique_configurations, stratifiers): + fig, axes = plt.subplots(2, 2, figsize=(10, 8)) + for column, var_type in enumerate(['SNP', 'INDEL']): + for row, metric in enumerate(['Sensitivity', 'Precision']): + ax = axes[row, column] + X = np.arange(len(stratifiers)) + for configuration in unique_configurations: + ax.plot(X, [get_value_from_table(data, sample_id, configuration, stratifier, var_type, metric) for stratifier in stratifiers], label=configuration) + + ax.set_xticks(X) + ax.set_xticklabels([stratifier.replace('gc', '') for stratifier in stratifiers], rotation=45) + ax.set_xlabel('GC bin') + ax.set_ylabel(metric) + ax.set_title(var_type, zorder=0) + axes[0, 0].legend() + fig.suptitle(sample_id) + plt.tight_layout() + + fig.savefig(f'gc_plot_{i_sample}_{sample_id}.png', dpi=100) + + +def main(sample_ids, configurations, summaries, order_of_samples, order_of_configurations): + if len(sample_ids) != len(configurations) or len(sample_ids) != len(summaries): + raise RuntimeError('The number of sample_ids, configurations, and summaries tables must be equal.') + + samples_data = [] + for i in range(len(sample_ids)): + sample_data = pd.read_csv(summaries[i]) + + # Filter out everything other than SNP or INDEL rows, and stratifiers starting with "gc" + sample_data = sample_data.loc[((sample_data['Type'] == 'SNP') | (sample_data['Type'] == 'INDEL')) & (sample_data['Stratifier'].str.startswith("gc"))] + + # Add sample_id and configuration names + sample_data['sample_id'] = sample_ids[i] + sample_data['configuration'] = configurations[i] + samples_data.append(sample_data) + data = pd.concat(samples_data) + + if order_of_samples is None: + unique_sample_ids = data['sample_id'].unique() + else: + unique_sample_ids = order_of_samples + + if order_of_configurations is None: + unique_configurations = data['configuration'].unique() + else: + unique_configurations = order_of_configurations + + gc_stratifiers = data['Stratifier'].unique().astype(str) + gc_stratifiers.sort() + + data = calculate_metrics(data, unique_sample_ids, unique_configurations, gc_stratifiers) + + for i_sample_id, sample_id in enumerate(unique_sample_ids): + plot_sample(data, i_sample_id, sample_id, unique_configurations, gc_stratifiers) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Create a table to compare output of BenchmarkVCFs.') + parser.add_argument('--order-of-samples', type=str, nargs='+', help='Order of samples. If not specified, the order will be the same as the supplied inputs.') + parser.add_argument('--order-of-configurations', type=str, nargs='+', help='Order of configurations. If not specified, the order will be the same as the supplied inputs.') + required_named = parser.add_argument_group('Required named arguments') + required_named.add_argument('--sample-ids', required=True, type=str, nargs='+') + required_named.add_argument('--configurations', required=True, type=str, nargs='+') + required_named.add_argument('--summaries', required=True, type=str, nargs='+') + args = parser.parse_args() + main(args.sample_ids, args.configurations, args.summaries, args.order_of_samples, args.order_of_configurations) +EOF + python script.py --sample-ids ~{sep=' ' sample_ids} --configurations ~{sep=' ' configurations} --summaries ~{sep=' ' benchmark_summaries} ~{order_of_samples_arg} ~{sep=' ' order_of_samples_or_empty} ~{order_of_configurations_arg} ~{sep=' ' order_of_configurations_or_empty} + >>> + + runtime { + docker: "us.gcr.io/broad-dsde-methods/benchmark_vcfs/compare_benchmarks:1.1.0" + preemptible: preemptible + memory: mem_gb + " GB" + disks: "local-disk 20 HDD" + } + + output { + Array[File] gc_plots = glob("gc_plot_*.png") + } +} + + + +task CompareBenchmarksTask { + input { + Array[String] sample_ids + Array[String] configurations + Array[File] benchmark_summaries + Array[String]? stratifiers + + Boolean include_counts + + Array[String]? order_of_samples + Array[String]? order_of_configurations + Array[Int]? deltas + + Int mem_gb = 4 + Int preemptible = 0 + } + + String stratifiers_arg = if !defined(stratifiers) then "" else "--stratifiers" + Array[String] stratifiers_or_empty = select_first([stratifiers, []]) + + String order_of_samples_arg = if !defined(order_of_samples) then "" else "--order-of-samples" + Array[String] order_of_samples_or_empty = select_first([order_of_samples, []]) + String order_of_configurations_arg = if !defined(order_of_configurations) then "" else "--order-of-configurations" + Array[String] order_of_configurations_or_empty = select_first([order_of_configurations, []]) + + String deltas_arg = if !defined(deltas) then "" else "--deltas" + Array[Int] deltas_or_empty = select_first([deltas, []]) + + command <<< + set -xeuo pipefail + + source activate compare_benchmarks + + cat <<'EOF' > script.py +import argparse +import numpy as np +import pandas as pd + +# The purpose of this class is to significantly simplify writing a TSV (or otherwise separated) table. +# For example, when passing an array of strings to cells(arr), the elements will be written separated +# by the separator sep, which is equivalent to file.write(sep.join(arr)). However, the methods of this class allow +# for chaining of these write commands to easily concatenate multiple calls, which can be helpful for writing +# custom-formatted tables, e.g. output.cells(arr1).sep().cells(arr2).new_line() instead of having to issue multiple +# file.write() calls. +class ChainableOutput: + def __init__(self, file, sep:str): + self.file = file + self.separator = sep + + def cells(self, cells:list): + self.file.write(self.separator.join([str(cell) for cell in cells])) + return self + def sep(self): + self.file.write(self.separator) + return self + def new_line(self): + self.file.write('\n') + return self + +def write_header(output:ChainableOutput, unique_sample_ids, unique_configurations, deltas, include_counts): + # This function will write the header for the comparison table. In general, the layout looks like this: + # | | | | Precision | Sensitivity | ... + # | | | | sample1 | sample2 | sample1 | sample2 | ... + # | | | | config1 | config2 | config1 | config2 | config1 | config2 | config1 | config2 | ... + + # If include_counts is True then TP, FP and FN will be written before Precision, Sensitivity and F-Measure + + # Line 1 + output.cells([''] * 4).sep() + if include_counts: + output.cells(['TP'] + [''] * (len(unique_sample_ids) * (len(unique_configurations) + len(deltas)) - 1)).sep() + output.cells(['FP'] + [''] * (len(unique_sample_ids) * (len(unique_configurations) + len(deltas)) - 1)).sep() + output.cells(['FN'] + [''] * (len(unique_sample_ids) * (len(unique_configurations) + len(deltas)) - 1)).sep() + output.cells(['Precision'] + [''] * (len(unique_sample_ids) * (len(unique_configurations) + len(deltas)) - 1)).sep() + output.cells(['Sensitivity'] + [''] * (len(unique_sample_ids) * (len(unique_configurations) + len(deltas)) - 1)).sep() + output.cells(['F-Measure'] + [''] * (len(unique_sample_ids) * (len(unique_configurations) + len(deltas)) - 1)).sep() + output.cells([''] * 2).new_line() + + # Line 2 + output.cells([''] * 4) + for _ in (['TP', 'FP', 'FN'] if include_counts else []) + ['Precision', 'Sensitivity', 'F-Measure']: + for sample_id in unique_sample_ids: + output.sep().cells([sample_id] + [''] * (len(unique_configurations) + len(deltas) - 1)) + output.sep().cells([''] * 2).new_line() + + # Line 3 + output.cells([''] * 4) + for metric in (['TP', 'FP', 'FN'] if include_counts else []) + ['Precision', 'Sensitivity', 'F-Measure']: + for sample_id in unique_sample_ids: + # Write configurations themselves + for configuration in unique_configurations: + output.sep().cells([configuration]) + # Write deltas + for delta_pair in deltas: + if metric in ['TP', 'FP', 'FN']: + output.sep().cells([f'delta({unique_configurations[delta_pair[1]]}-{unique_configurations[delta_pair[0]]})']) + else: + output.sep().cells([f'delta%({unique_configurations[delta_pair[1]]}-{unique_configurations[delta_pair[0]]})']) + output.sep().cells([''] * 2).new_line() + +def get_value_from_table(data, sample_id, configuration, stratifier, var_type, column): + try: + return data.query('sample_id == @sample_id and configuration == @configuration and Stratifier == @stratifier and Type == @var_type').iloc[0][column] + except IndexError as e: + raise RuntimeError(f'Failed querying table for sample_id: {sample_id}, configuration: {configuration}, stratifier {stratifier}, var_type: {var_type}, column: {column}. Make sure that the set of stratifiers and samples in the order_of_ arguments exactly matches the set of stratifiers and samples used for BenchmarkVCFs.') + +def write_stratifier(output:ChainableOutput, stratifier:str, data:pd.DataFrame, unique_sample_ids:list, unique_configurations:list, deltas:list, include_counts): + for var_type in ['SNP', 'INDEL', 'all']: + # Placeholder to print percentage of genome for each stratification + output.cells(['', '']).sep() + # Only print stratifier name in the first row + output.cells([stratifier if var_type == 'SNP' else '', var_type]) + for metric in (['TP', 'FP', 'FN'] if include_counts else []) + ['Precision', 'Sensitivity', 'F-Measure']: + for sample_id in unique_sample_ids: + for configuration in unique_configurations: + output.sep().cells(['{:.5f}'.format(get_value_from_table(data, sample_id, configuration, stratifier, var_type, metric))]) + for delta_pair in deltas: + base_value = get_value_from_table(data, sample_id, unique_configurations[delta_pair[0]], stratifier, var_type, metric) + current_value = get_value_from_table(data, sample_id, unique_configurations[delta_pair[1]], stratifier, var_type, metric) + if metric in ['TP', 'FP', 'FN']: + delta = int(current_value) - int(base_value) + output.sep().cells(['{}'.format(delta)]) + else: + delta_pct = (current_value - base_value) / base_value + output.sep().cells(['{:.2%}'.format(delta_pct)]) + output.sep().cells([var_type, stratifier if var_type == 'SNP' else '']).new_line() + +def calculate_metrics(data, unique_sample_ids, unique_configurations, stratifiers): + recalculated_data = pd.DataFrame(columns=['sample_id', 'configuration', 'Stratifier', 'Type', 'TP', 'FP', 'FN', 'Precision', 'Sensitivity', 'F-Measure']) + for sample_id in unique_sample_ids: + for configuration in unique_configurations: + for stratifier in stratifiers: + tp, fp, fn = dict(), dict(), dict() + tp['SNP'] = get_value_from_table(data, sample_id, configuration, stratifier, 'SNP', 'TP_Base') + fp['SNP'] = get_value_from_table(data, sample_id, configuration, stratifier, 'SNP', 'FP') + fn['SNP'] = get_value_from_table(data, sample_id, configuration, stratifier, 'SNP', 'FN') + tp['INDEL'] = get_value_from_table(data, sample_id, configuration, stratifier, 'INDEL', 'TP_Base') + fp['INDEL'] = get_value_from_table(data, sample_id, configuration, stratifier, 'INDEL', 'FP') + fn['INDEL'] = get_value_from_table(data, sample_id, configuration, stratifier, 'INDEL', 'FN') + tp['all'] = tp['SNP'] + tp['INDEL'] + fp['all'] = fp['SNP'] + fp['INDEL'] + fn['all'] = fn['SNP'] + fn['INDEL'] + + for var_type in ['SNP', 'INDEL', 'all']: + recalculated_data = recalculated_data.append({ + 'sample_id': sample_id, + 'configuration': configuration, + 'Stratifier': stratifier, + 'Type': var_type, + 'TP': tp[var_type], + 'FP': fp[var_type], + 'FN': fn[var_type], + 'Precision': tp[var_type]/(tp[var_type] + fp[var_type]) if tp[var_type] + fp[var_type] > 0 else np.nan, + 'Sensitivity': tp[var_type]/(tp[var_type] + fn[var_type]) if tp[var_type] + fn[var_type] > 0 else np.nan, + 'F-Measure': tp[var_type]/(tp[var_type] + 0.5*(fp[var_type] + fn[var_type])) if tp[var_type] + fp[var_type] + fn[var_type] > 0 else np.nan + }, ignore_index=True) + return recalculated_data + + + +def main(sample_ids, configurations, summaries, stratifiers, order_of_samples, order_of_configurations, deltas_array, include_counts): + if len(sample_ids) != len(configurations) or len(sample_ids) != len(summaries): + raise RuntimeError('The number of sample_id, configurations, and summary tables must be equal.') + if deltas_array is None: + deltas_array = [] + if len(deltas_array) % 2 != 0: + raise RuntimeError('The number of indices in the delta argument must be even. Please use --help or check the documentation on how to use this argument.') + + deltas = [(int(deltas_array[i]), int(deltas_array[i+1])) for i in range(0, len(deltas_array), 2)] + + samples_data = [] + for i in range(len(sample_ids)): + sample_data = pd.read_csv(summaries[i]) + + # Filter out everything other than SNP or INDEL rows + sample_data = sample_data.loc[(sample_data['Type'] == 'SNP') | (sample_data['Type'] == 'INDEL')] + + # Add sample_id and configuration names + sample_data['sample_id'] = sample_ids[i] + sample_data['configuration'] = configurations[i] + samples_data.append(sample_data) + data = pd.concat(samples_data) + data = data.fillna({'Stratifier': 'all'}) + + if order_of_samples is None: + unique_sample_ids = data['sample_id'].unique() + else: + unique_sample_ids = order_of_samples + + if order_of_configurations is None: + unique_configurations = data['configuration'].unique() + else: + unique_configurations = order_of_configurations + + if stratifiers is None: + stratifiers = data['Stratifier'].unique() + else: + stratifiers = ['all'] + stratifiers + + data = calculate_metrics(data, unique_sample_ids, unique_configurations, stratifiers) + + data.to_csv('raw_data.csv') + + with open('comparison.csv', 'w') as output_file: + chainable_output = ChainableOutput(output_file, ',') + write_header(chainable_output, unique_sample_ids, unique_configurations, deltas, include_counts) + for stratifier in stratifiers: + write_stratifier(chainable_output, stratifier, data, unique_sample_ids, unique_configurations, deltas, include_counts) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Create a table to compare output of BenchmarkVCFs.') + parser.add_argument('--stratifiers', type=str, nargs='*', help='Explicitly specify the stratifiers that have to be present in all samples. "all" will automatically be added. If not specified, the stratifiers will be inferred. If specified, this argument also defines the order of the stratifiers.') + parser.add_argument('--order-of-samples', type=str, nargs='+', help='Order of samples. If not specified, the order will be the same as the supplied inputs.') + parser.add_argument('--order-of-configurations', type=str, nargs='+', help='Order of configurations. If not specified, the order will be the same as the supplied inputs.') + parser.add_argument('--deltas', type=str, nargs='+', help='A list of configuration (zero-based) indices to compare. E.g. for comparing configurations 0 to 1 and 0 to 2, pass the values 0 1 0 2.') + parser.add_argument('--include-counts', action='store_true', help='If set, include the TP/FP/FN counts in the output table.') + required_named = parser.add_argument_group('Required named arguments') + required_named.add_argument('--sample-ids', required=True, type=str, nargs='+') + required_named.add_argument('--configurations', required=True, type=str, nargs='+') + required_named.add_argument('--summaries', required=True, type=str, nargs='+') + args = parser.parse_args() + main(args.sample_ids, args.configurations, args.summaries, args.stratifiers, args.order_of_samples, args.order_of_configurations, args.deltas, args.include_counts) +EOF + python script.py --sample-ids ~{sep=' ' sample_ids} --configurations ~{sep=' ' configurations} --summaries ~{sep=' ' benchmark_summaries} ~{stratifiers_arg} ~{sep=' ' stratifiers_or_empty} ~{order_of_samples_arg} ~{sep=' ' order_of_samples_or_empty} ~{order_of_configurations_arg} ~{sep=' ' order_of_configurations_or_empty} ~{deltas_arg} ~{sep=' ' deltas_or_empty} ~{true="--include-counts" false="" include_counts} + >>> + + runtime { + docker: "us.gcr.io/broad-dsde-methods/benchmark_vcfs/compare_benchmarks:1.1.0" + preemptible: preemptible + memory: mem_gb + " GB" + disks: "local-disk 20 HDD" + } + + output { + File comparison_csv = "comparison.csv" + File raw_data = "raw_data.csv" + } +} \ No newline at end of file From 0313616ee0528d4498263438515da18c260c7bc4 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 16 Feb 2023 13:35:29 -0500 Subject: [PATCH 117/297] Fixed file name. --- wdl/{CompareBenchmarks.wdl => CompareVcfBenchmarks.wdl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename wdl/{CompareBenchmarks.wdl => CompareVcfBenchmarks.wdl} (100%) diff --git a/wdl/CompareBenchmarks.wdl b/wdl/CompareVcfBenchmarks.wdl similarity index 100% rename from wdl/CompareBenchmarks.wdl rename to wdl/CompareVcfBenchmarks.wdl From 08420fe30faa83974a3cd4b9d71d5e905a02033c Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 16 Feb 2023 15:10:49 -0500 Subject: [PATCH 118/297] Changing default for gc plots. --- wdl/CompareVcfBenchmarks.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/CompareVcfBenchmarks.wdl b/wdl/CompareVcfBenchmarks.wdl index 6c9bca949..e87de45d2 100644 --- a/wdl/CompareVcfBenchmarks.wdl +++ b/wdl/CompareVcfBenchmarks.wdl @@ -12,7 +12,7 @@ workflow CompareVcfBenchmarks { Array[String]? stratifiers Boolean include_counts = true - Boolean generate_gc_plots = true + Boolean generate_gc_plots = false Array[String]? order_of_samples Array[String]? order_of_configurations From 4f7b975a661ea9ae090db9a344ec450a5b63ddaf Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 27 Feb 2023 18:38:53 -0500 Subject: [PATCH 119/297] Adding `resources.log` to outputs for monitored tasks. --- wdl/tasks/AlignedMetrics.wdl | 1 + wdl/tasks/FastQC.wdl | 1 + wdl/tasks/SRUtils.wdl | 8 ++++++++ wdl/tasks/Utils.wdl | 2 ++ 4 files changed, 12 insertions(+) diff --git a/wdl/tasks/AlignedMetrics.wdl b/wdl/tasks/AlignedMetrics.wdl index d916d8476..86e0826ce 100644 --- a/wdl/tasks/AlignedMetrics.wdl +++ b/wdl/tasks/AlignedMetrics.wdl @@ -419,6 +419,7 @@ task SamStatsMap { output { File sam_stats = "~{basename}.sam_stats.txt" Map[String, Float] stats_map = read_map("map.txt") + File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/FastQC.wdl b/wdl/tasks/FastQC.wdl index b1d58e37b..40c765663 100644 --- a/wdl/tasks/FastQC.wdl +++ b/wdl/tasks/FastQC.wdl @@ -63,6 +63,7 @@ task FastQC { File stats = "fastqc_data.txt" File report = "fastqc_report.html" + File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 12936df4b..3e63bd70b 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -35,6 +35,7 @@ task BamToFq { File fq_end1 = "~{prefix}.end1.fq.gz" File fq_end2 = "~{prefix}.end1.fq.gz" File fq_unpaired = "~{prefix}.unpaired.fq.gz" + File monitoring_log = "resources.log" } ######################### @@ -225,6 +226,7 @@ task BwaMem2 { output { File bam = "~{prefix}.bam" + File monitoring_log = "resources.log" } ######################### @@ -315,6 +317,7 @@ task MergeBamAlignment { output { File bam = "~{prefix}.bam" + File monitoring_log = "resources.log" } ######################### @@ -391,6 +394,7 @@ task MarkDuplicates { output { File bam = "~{prefix}.bam" File metrics = "~{prefix}.metrics.txt" + File monitoring_log = "resources.log" } ######################### @@ -489,6 +493,7 @@ task BaseRecalibrator { } output { File recalibration_report = "~{prefix}.txt" + File monitoring_log = "resources.log" } } @@ -581,6 +586,7 @@ task ApplyBQSR { output { File recalibrated_bam = "~{prefix}.bam" File recalibrated_bai = "~{prefix}.bam.bai" + File monitoring_log = "resources.log" } } @@ -628,6 +634,7 @@ task RevertSam { output { File bam = "~{prefix}.bam" + File monitoring_log = "resources.log" } ######################### @@ -687,6 +694,7 @@ task ComputeBamStats { output { Map[String, Float] results = read_map(stats_file_name) File results_file = stats_file_name + File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 2839e33e7..5efdf6634 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -192,6 +192,7 @@ task SortBam { output { File sorted_bam = "~{prefix}.bam" File sorted_bai = "~{prefix}.bam.bai" + File monitoring_log = "resources.log" } ######################### @@ -1905,6 +1906,7 @@ task ComputeGenomeLength { output { Float length = read_float("length.txt") + File monitoring_log = "resources.log" } ######################### From f7afa73ee946ad8323b0ee45b668851519df0f8e Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 28 Feb 2023 12:21:04 -0500 Subject: [PATCH 120/297] Added instrumentation for the rest of the WDLs to check resources. --- wdl/tasks/DeepVariant.wdl | 10 +++++ wdl/tasks/Hail.wdl | 10 +++++ wdl/tasks/HaplotypeCaller.wdl | 22 ++++++++--- wdl/tasks/SRJointGenotyping.wdl | 39 ++++++++++++++----- wdl/tasks/SRUtils.wdl | 10 +++++ wdl/tasks/VariantUtils.wdl | 68 ++++++++++++++++++++++++--------- 6 files changed, 125 insertions(+), 34 deletions(-) diff --git a/wdl/tasks/DeepVariant.wdl b/wdl/tasks/DeepVariant.wdl index c614f051d..59243c14b 100644 --- a/wdl/tasks/DeepVariant.wdl +++ b/wdl/tasks/DeepVariant.wdl @@ -84,6 +84,12 @@ task DV { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) mkdir -p "~{output_root}" @@ -98,6 +104,8 @@ task DV { find "~{output_root}/" -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g' \ > "~{output_root}/dir_structure.txt" + + kill $monitoring_pid >>> output { @@ -110,6 +118,8 @@ task DV { File gVCF_tbi = "~{output_root}/~{prefix}.g.vcf.gz.tbi" File visual_report_html = "~{output_root}/~{prefix}.visual_report.html" + + File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index b1ca0d74c..af35e442e 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -26,6 +26,12 @@ task ConvertToHailMT { command <<< set -x + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + python3 <>> output { String gcs_path = "~{outdir}/~{prefix}.mt" + + File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index db0c50ff1..3f9e980ad 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -170,6 +170,12 @@ task HaplotypeCaller_GATK4_VCF { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from # memory_size_gb because of Cromwell's retry with more memory feature. @@ -199,8 +205,18 @@ task HaplotypeCaller_GATK4_VCF { # Cromwell doesn't like optional task outputs, so we have to touch this file. touch ~{prefix}.bamout.bam + + kill $monitoring_pid >>> + output { + File output_vcf = "~{output_file_name}" + File output_vcf_index = "~{output_file_name}.tbi" + File bamout = "~{prefix}.bamout.bam" + + File monitoring_log = "resources.log" + } + ######################### RuntimeAttr default_attr = object { cpu_cores: 2, @@ -222,12 +238,6 @@ task HaplotypeCaller_GATK4_VCF { maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } - - output { - File output_vcf = "~{output_file_name}" - File output_vcf_index = "~{output_file_name}.tbi" - File bamout = "~{prefix}.bamout.bam" - } } diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 0475fb601..33d8fef52 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -129,6 +129,12 @@ task ImportGVCFs { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + # Make sure that the output directory does not exist: [ -e ~{prefix} ] && rm -rf ~{prefix} @@ -155,8 +161,16 @@ task ImportGVCFs { --consolidate tar -cf ~{prefix}.genomicsDB.tar ~{prefix}.genomicsDB + + kill $monitoring_pid >>> + output { + File output_genomicsdb = "~{prefix}.genomicsDB.tar" + + File monitoring_log = "resources.log" + } + ######################### RuntimeAttr default_attr = object { cpu_cores: 4, @@ -177,10 +191,6 @@ task ImportGVCFs { maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } - - output { - File output_genomicsdb = "~{prefix}.genomicsDB.tar" - } } task GenotypeGVCFs { @@ -218,6 +228,12 @@ task GenotypeGVCFs { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + # We must determine if our input variants are in a genomicsdb file or in a VCF. # The easiest way is to see if the input is a .tar file: @@ -246,7 +262,17 @@ task GenotypeGVCFs { -L ~{interval_list} \ ~{true='--keep-combined-raw-annotations' false='' keep_combined_raw_annotations} \ --merge-input-intervals + + kill $monitoring_pid >>> + + output { + File output_vcf = "~{prefix}.vcf.gz" + File output_vcf_index = "~{prefix}.vcf.gz.tbi" + + File monitoring_log = "resources.log" + } + ######################### RuntimeAttr default_attr = object { cpu_cores: 2, @@ -267,9 +293,4 @@ task GenotypeGVCFs { maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } - - output { - File output_vcf = "~{prefix}.vcf.gz" - File output_vcf_index = "~{prefix}.vcf.gz.tbi" - } } \ No newline at end of file diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 3e63bd70b..e8800ebc0 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -836,6 +836,12 @@ task RevertBaseQualities { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + # Check if the input bam has been run through `ApplyBQSR`. # If not, we can just return the input bam. samtools view -H ~{bam} | grep '^@PG' > header.pg.txt @@ -859,11 +865,15 @@ task RevertBaseQualities { cp ~{bai} ~{prefix}.bam.bai fi fi + + kill $monitoring_pid >>> output { File bam_out = "~{prefix}.bam" File bai_out = "~{prefix}.bam.bai" + + File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 43817af3b..3cccc85bc 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -887,6 +887,12 @@ task IndelsVariantRecalibrator { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + # We need to generate resource strings from the input arrays. # First we check that the arrays are the same length: if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ @@ -931,8 +937,19 @@ task IndelsVariantRecalibrator { --output-model ~{prefix}.model.report \ --max-gaussians ~{max_gaussians} \ ${resource_flags} + + kill $monitoring_pid >>> + output { + File recalibration = "~{prefix}.recal" + File recalibration_index = "~{prefix}.recal.idx" + File tranches = "~{prefix}.tranches" + File model_report = "~{prefix}.model.report" + + File monitoring_log = "resources.log" + } + ######################### RuntimeAttr default_attr = object { cpu_cores: 2, @@ -953,13 +970,6 @@ task IndelsVariantRecalibrator { maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } - - output { - File recalibration = "~{prefix}.recal" - File recalibration_index = "~{prefix}.recal.idx" - File tranches = "~{prefix}.tranches" - File model_report = "~{prefix}.model.report" - } } task SNPsVariantRecalibratorCreateModel { @@ -1010,6 +1020,12 @@ task SNPsVariantRecalibratorCreateModel { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + # We need to generate resource strings from the input arrays. # First we check that the arrays are the same length: if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ @@ -1055,8 +1071,19 @@ task SNPsVariantRecalibratorCreateModel { --output-model ~{prefix}.model.report \ --max-gaussians ~{max_gaussians} \ ${resource_flags} + + kill $monitoring_pid >>> + output { + File recalibration = "~{prefix}.recal" + File recalibration_index = "~{prefix}.recal.idx" + File tranches = "~{prefix}.tranches" + File model_report = "~{prefix}.model.report" + + File monitoring_log = "resources.log" + } + ######################### RuntimeAttr default_attr = object { cpu_cores: 2, @@ -1077,13 +1104,6 @@ task SNPsVariantRecalibratorCreateModel { maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } - - output { - File recalibration = "~{prefix}.recal" - File recalibration_index = "~{prefix}.recal.idx" - File tranches = "~{prefix}.tranches" - File model_report = "~{prefix}.model.report" - } } task ApplyVqsr { @@ -1116,6 +1136,12 @@ task ApplyVqsr { command <<< set -euxo pipefail + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + # Get amount of memory to use: mem_available=$(free -m | grep '^Mem' | awk '{print $2}') let mem_start=${mem_available}-2000 @@ -1142,8 +1168,17 @@ task ApplyVqsr { --truth-sensitivity-filter-level ~{snp_filter_level} \ --create-output-variant-index true \ -mode SNP + + kill $monitoring_pid >>> + output { + File recalibrated_vcf = "~{prefix}.recalibrated.vcf.gz" + File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.gz.tbi" + + File monitoring_log = "resources.log" + } + ######################### RuntimeAttr default_attr = object { cpu_cores: 1, @@ -1164,9 +1199,4 @@ task ApplyVqsr { maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } - - output { - File recalibrated_vcf = "~{prefix}.recalibrated.vcf.gz" - File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.gz.tbi" - } } From 2c8eff452506912402d469e49f16c18b79c66b6e Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 28 Feb 2023 15:44:02 -0500 Subject: [PATCH 121/297] Adding in docker image for malaria work. --- docker/lr-malaria/Dockerfile | 26 + docker/lr-malaria/Makefile | 17 + docker/lr-malaria/environment.yml | 22 + docker/lr-malaria/python/barcodes_terra_v2.py | 1589 +++++++++++++++++ 4 files changed, 1654 insertions(+) create mode 100644 docker/lr-malaria/Dockerfile create mode 100644 docker/lr-malaria/Makefile create mode 100644 docker/lr-malaria/environment.yml create mode 100644 docker/lr-malaria/python/barcodes_terra_v2.py diff --git a/docker/lr-malaria/Dockerfile b/docker/lr-malaria/Dockerfile new file mode 100644 index 000000000..750a2b5e7 --- /dev/null +++ b/docker/lr-malaria/Dockerfile @@ -0,0 +1,26 @@ +FROM continuumio/miniconda3 + +MAINTAINER Jonn Smith + +# copy other resources +COPY ./environment.yml / + +# install conda packages +RUN conda env create -f /environment.yml && conda clean -a +ENV PATH=/root/google-cloud-sdk/bin/:${PATH} + +# install gsutil +RUN apt-get --allow-releaseinfo-change update +RUN apt install -y curl git-lfs time datamash +RUN curl https://sdk.cloud.google.com | bash + +# Setup crcmodc for gsutil: +RUN apt-get install -y gcc python3-dev python3-setuptools && \ + pip3 uninstall -y crcmod && \ + pip3 install --no-cache-dir -U crcmod + +# copy python scripts +COPY python/* /python_scripts/ + +# activate conda environment +RUN echo "source activate lr-malaria" > ~/.bashrc diff --git a/docker/lr-malaria/Makefile b/docker/lr-malaria/Makefile new file mode 100644 index 000000000..83aaa9d1c --- /dev/null +++ b/docker/lr-malaria/Makefile @@ -0,0 +1,17 @@ +IMAGE_NAME = lr-malaria +VERSION = 0.0.1 + +TAG1 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):$(VERSION) +TAG2 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):latest + +all: | build push + +build: + docker build -t $(TAG1) -t $(TAG2) . + +build_no_cache: + docker build --no-cache -t $(TAG1) -t $(TAG2) . + +push: + docker push $(TAG1) + docker push $(TAG2) diff --git a/docker/lr-malaria/environment.yml b/docker/lr-malaria/environment.yml new file mode 100644 index 000000000..879359ec6 --- /dev/null +++ b/docker/lr-malaria/environment.yml @@ -0,0 +1,22 @@ +name: lr-malaria +channels: + - default + - bioconda + - anaconda +dependencies: + - python=3.6 + - cython + - pip + - pip: + - numpy + - scipy + - pandas + - seaborn + - statsmodels + - networkx + - matplotlib + - pyplot + - patsy + - sklearn + - numpyencoder + - tqdm diff --git a/docker/lr-malaria/python/barcodes_terra_v2.py b/docker/lr-malaria/python/barcodes_terra_v2.py new file mode 100644 index 000000000..590036d8f --- /dev/null +++ b/docker/lr-malaria/python/barcodes_terra_v2.py @@ -0,0 +1,1589 @@ +#!/usr/bin/env python +# coding: utf-8 + +import random +import copy +import itertools +import sys +import json + +import scipy +import pandas as pd +import numpy as np +import seaborn as sns +import numpy.polynomial.polynomial as poly +import statsmodels.api as sm +import networkx as nx +import matplotlib.pyplot as plt + +from pandas import DataFrame, read_csv, read_excel +from collections import defaultdict, Counter +from matplotlib.patches import Patch +from patsy import dmatrices +from sklearn import linear_model +from numpyencoder import NumpyEncoder +from sklearn.neighbors import KernelDensity + + +# F_loci = (h_mono - h_poly) / h_mono + + +def remove_duplicate_df(df, field="Haplotype"): + duplicate_rows = pd.DataFrame.duplicated(df, field, keep="first") + return df[~duplicate_rows] + + +def inverse_var_weight(p_array, var_array): + var_weighted_num = np.nansum(np.divide(p_array, var_array, where=var_array != 0)) + var_weighted_denom = np.nansum( + np.divide([1 for _ in var_array], var_array, where=var_array != 0) + ) + weighted_mean = var_weighted_num / var_weighted_denom + + weighted_var = 1 / np.sum(1 / var_array) + weighted_std = np.sqrt(weighted_var) + weighted_ci = ( + weighted_mean - 1.96 * weighted_std, + weighted_mean + 1.96 * weighted_std, + ) + + return weighted_mean, weighted_var, weighted_ci + + +def is_unique(s): + a = s.to_numpy() # s.values (pandas<0.24) + return (a[0] == a).all() # True = homozygous, #False = Heterozygous + + +def return_stats(s, convert_numpy=True): + if convert_numpy: + a = s.to_numpy() # s.values (pandas<0.24) + else: + s = a + missing_screen = a != "X" + poly_screen = a != "N" + + if missing_screen.all() == False: + return np.NaN # missing data present in this comparison, skip + elif poly_screen.all() == True: + return 1.0 - (a[0] == a).all() # 0 = homozygous, #1 = Heterozygous + # status = 1. -(a[0] == a).all() #0 = homozygous, #1 = Heterozygous + # if status == 1.0: + # random_p = np.random.random() + # if random_p < Ftrue: + # return 1. - status + # else: + # return status + # else: + # return status + + else: # N present in one of the comparisons, skip for now + return np.NaN + + +def quantify_het(barcode_comparison, Ftrue=0, axis=0): + null_het = np.nansum(barcode_comparison, axis=axis) * (1 - Ftrue) + return null_het + + +def quantify_total(barcode_comparison, axis=0): + return np.sum(~np.isnan(barcode_comparison), axis=axis) + + +def run_fn_tests(): + test_het_calculator() + test_unique() + + +def test_het_calculator(): + expected_het, expected_total = (12, 16) + test_barcode1 = ( + 6 * ["A"] + 6 * ["T"] + 6 * ["C"] + 6 * ["G"] + 6 * ["X"] + 6 * ["N"] + ) + test_barcode2 = 6 * ["A", "T", "C", "G", "X", "N"] + test_df = DataFrame([test_barcode1, test_barcode2]) + b = test_df.apply(return_stats, axis=0).to_list() + heterozygotes = np.sum(quantify_het(b), axis=0) + total = np.sum(quantify_total(b), axis=0) + assert ( + heterozygotes == expected_het + ), "Heterozygote sites should be {expected_het}, identified {x}".format( + expected_het=expected_het, x=heterozygotes + ) + assert ( + total == 16 + ), "Total usable sites should be {expected_total}, identified {x}".format( + x=total, expected_total=expected_total + ) + + +def test_unique(): + test_df1 = DataFrame([["A"], ["B"]]) + test_df2 = DataFrame(["A"], ["A"]) + assert ( + is_unique(DataFrame(test_df1)) == False + ), "Failed unique test, misidentified [A,B] as being composed of a single unique entity" + assert ( + is_unique(DataFrame(test_df2)) == True + ), "Failed unique test, misidentified [A,A] as being composed of a multiple unique entities" + + +def check_combinations(combo_barcodes): + arr_2d = combo_barcodes.to_numpy() + assert ( + np.array_equal(arr_2d[0], arr_2d[1]) == False + ), "Duplicate barcodes were sampled" + + +def calculate_RH_sample(H_mono, H_poly): + if H_mono > H_poly: + RH = (H_mono - H_poly) / H_mono + else: + RH = (H_mono - H_poly) / H_mono + return RH + + +def bootstrap(array, iterations): + resample = np.random.choice(array, (iterations, len(array)), replace=True) + return np.mean(resample, axis=1) + + +def RH_classifier(RH): + RH = float(RH) + if RH > 0.4: + return "cotx" + elif RH > 0.3: + return "cotx_probable" + elif RH > -0.1: + return "coi=2" + elif RH > -0.2: + return "coi=2_probable" + elif RH > -0.4: + return "coi=3" + elif RH > -0.6: + return "coi=3_probable" + elif RH > -0.8: + return "coi=4_probable" + else: + return "coi=4" + + +def calculate_Shannons_index(p_array): + H = -np.sum([p_array * np.log(p_array)]) + return H + + +def calculate_Evenness(H_array, k): + E = H_array / np.log(k) + return E + + +def calculate_H12(p_array): + p_array = sorted(p_array) + p1 = p_array[0] + p2 = p_array[1] + other_p = np.asarray(p_array[2:]) + sum_rest = np.sum(other_p**2) + H12 = (p1 + p2) ** 2 + sum_rest + return H12 + + +def interpret_cotx_simbinbarcode(sim, cotx_event=1): + """wokrks only for coi =2""" + sim = sim[str(cotx_event)] + converted_barcodes = [] + for binbarcode in sim: + if binbarcode == 0: + barcode = 24 * [0] + else: + barcode = [int(i) for i in list("{0:0b}".format(binbarcode))] + while len(barcode) < 24: + barcode = [ + 0 + ] + barcode # the conversion does not preserve leading zeros + converted_barcodes.append(barcode) + return np.asarray(converted_barcodes) + + +def load_cotx_simulations(): + cotx_simulation_data = defaultdict( + lambda: defaultdict(list) + ) # dict[initial_coi][cotx_event] + + cotx_file = "cotx_barcodes_2.txt" + cotx_data = json.load(open(cotx_file)) + for simulation in cotx_data: + for n_cotx_event in [1, 2, 3]: + if len(simulation[str(n_cotx_event)]) > 1: + cotx_simulation_data[2]["cotx_event{n}".format(n=n_cotx_event)].append( + interpret_cotx_simbinbarcode(simulation, cotx_event=n_cotx_event) + ) + + for initial_coi in [3, 4, 5]: + cotx_file = "cotx_barcodes_{x}.txt".format(x=initial_coi) + cotx_data = json.load(open(cotx_file)) + for simulation in cotx_data: + for n_cotx in [1, 2, 3]: + if len(simulation[str(n_cotx)]) > 1: + cotx_simulation_data[initial_coi][ + "cotx_event{x}".format(x=n_cotx) + ].append(np.asarray(simulation[str(n_cotx)])) + return cotx_simulation_data + + +def wilson(p, n, z=1.96): + denominator = 1 + z**2 / n + centre_adjusted_probability = p + z * z / (2 * n) + adjusted_standard_deviation = np.sqrt((p * (1 - p) + z * z / (4 * n)) / n) + + lower_bound = ( + centre_adjusted_probability - z * adjusted_standard_deviation + ) / denominator + upper_bound = ( + centre_adjusted_probability + z * adjusted_standard_deviation + ) / denominator + return lower_bound, upper_bound + + +class Barcode_Stats: + cpalette_converter = { + "green": "viridis", + "blue": "mako", + "cornflowerblue": "mako", + "crimson": "rocket", + "orange": "Oranges", + "purple": "Purples_r", + } + + def __init__(self, file, ISO3, sheet_name=None, adjustedN=False): + self.ISO3 = ISO3 + + if not sheet_name: + sheet_name = ISO3 + self.master_df = DataFrame(read_excel(file, sheet_name=ISO3)) + else: + self.master_df = DataFrame(read_excel(file, sheet_name=sheet_name)) + self.loci_position_names = list(self.master_df.columns[7:31]) + + self.extract_region(ISO3) + self.extract_mono_poly_stats() + self.calc_stats() + self.calculate_polyhet_timeseries() + self.quantify_observed_poly_het(adjustedN) + self.calculate_RH() + + def label_haplotypes(self): + barcode_haplotypes_dict = {} + unique_barcode_haplotype = 0 + barcode_haplotypes = [] + for row in self.barcodes_df[self.barcodes_df.columns[7:31]].to_numpy(): + barcode = ("").join(row) + if barcode not in barcode_haplotypes_dict: + barcode_haplotypes_dict[barcode] = unique_barcode_haplotype + unique_barcode_haplotype += 1 + barcode_haplotypes.append(barcode_haplotypes_dict[barcode]) + self.barcodes_df["Haplotype"] = barcode_haplotypes + + def extract_region(self, ISO3): + tmp_df = self.master_df[self.master_df["ISO3"] == ISO3] # Thies samples only + for position in self.loci_position_names: + tmp_df[position] = [ + x.strip().upper() for x in tmp_df[position] + ] # noticed a tab formatting in these columns + self.barcode_pos_dict = {} + fin = open("barcode_pos.txt") + for i, line in enumerate(fin.readlines()[1:]): + line = line.strip().split("\t") + self.barcode_pos_dict[self.loci_position_names[i]] = line[0] + ":" + line[1] + + tmp_df["M/P"] = [x.strip() for x in tmp_df["M/P"]] + + control_samples = [ + "3D7", + "3D7-1", + "3D7-2", + "3d7", + "Dd2-MOD", + "Dd2-Mod", + "Dd2/Mod", + "Dd2_MOD", + "DD2", + "Dd2", + ] + + self.barcodes_df = tmp_df[ + (tmp_df["X"] <= 2) & (~tmp_df["Sample_Name"].isin(control_samples)) + ] + self.mono_barcode_df = self.barcodes_df[ + (self.barcodes_df["X"] <= 2) + & (self.barcodes_df["M/P"] == "M") + & (~self.barcodes_df["Sample_Name"].isin(control_samples)) + ] + self.chrono_years = sorted(Counter(self.barcodes_df["Year"]).keys()) + self.label_haplotypes() + + def extract_mono_poly_stats(self, fail_threshold=2): + self.barcode_year_dfs = {} + self.mono_barcode_year_dfs = {} + self.poly_barcode_year_dfs = {} + for year in self.chrono_years: + self.barcode_year_dfs[year] = self.barcodes_df[ + (self.barcodes_df["Year"] == year) + & (self.barcodes_df["X"] <= fail_threshold) + ] + + self.mono_barcode_year_dfs[year] = self.barcodes_df[ + (self.barcodes_df["Year"] == year) + & (self.barcodes_df["X"] <= fail_threshold) + & (self.barcodes_df["M/P"] == "M") + ] + + self.poly_barcode_year_dfs[year] = self.barcodes_df[ + (self.barcodes_df["Year"] == year) + & (self.barcodes_df["X"] <= fail_threshold) + & (self.barcodes_df["M/P"] == "P") + ] + self.mono_barcode_year_dfs[year].reset_index(drop=True, inplace=True) + self.poly_barcode_year_dfs[year].reset_index(drop=True, inplace=True) + self.n_singles = np.asarray( + [len(self.mono_barcode_year_dfs[year]) for year in self.chrono_years] + ) + self.n_poly = np.asarray( + [len(self.poly_barcode_year_dfs[year]) for year in self.chrono_years] + ) + + self.n_total = np.asarray( + [len(self.barcode_year_dfs[year]) for year in self.chrono_years] + ) + + self.loci_allele_dict = {} + for column in self.mono_barcode_df.columns[7:31]: + counts = Counter(self.mono_barcode_df[column].to_numpy()) + counts.pop("X", 0) + counts.pop("N", 0) + major_allele = max(counts, key=counts.get) + counts.pop(major_allele, 0) + try: + minor_allele = max(counts, key=counts.get) + except: + minor_allele = None + self.loci_allele_dict[column] = (major_allele, minor_allele) + + self.haplotype_counts = {} + for year in self.chrono_years: + counts = Counter( + self.barcode_year_dfs[year][self.barcode_year_dfs[year]["M/P"] == "M"][ + "Haplotype" + ] + ) + self.haplotype_counts[year] = counts + + def calc_stats(self): + self.popgen_stats = defaultdict(list) + n = self.n_singles + self.n_poly + p = self.n_poly / n + q = 1.0 - p + variances = p * q / n + wilson_interval = [wilson(prop, n_samples) for prop, n_samples in zip(p, n)] + + self.popgen_stats["p_poly_fract"] = list(p) + self.popgen_stats["var_poly_fract"] = list(variances) + weighted_mean, weighted_var, weighted_ci = inverse_var_weight(p, p * q / n) + self.popgen_stats["poly_fract_inv_var"] = ( + weighted_mean, + weighted_var, + weighted_ci, + ) + self.popgen_stats["poly_wilson"] = wilson_interval + x = np.asarray(self.chrono_years) + X = sm.add_constant(x) + y = np.array(p) + model = sm.OLS(y, X).fit() + self.popgen_stats["poly_fract_model"] = model + + # print('Unique Mono Fract') + self.unique_mono_count = {} + self.repeat_haps = defaultdict(lambda: defaultdict(lambda: 0)) + for year in self.chrono_years: + sorted_hid = sorted( + self.haplotype_counts[year], + key=lambda x: self.haplotype_counts[year][x], + reverse=True, + ) + for hid in sorted_hid: + if self.haplotype_counts[year][hid] != 1: + self.repeat_haps[year][hid] = self.haplotype_counts[year][hid] + else: + self.repeat_haps[year]["unique"] += 1 + self.unique_mono_count[year] = self.repeat_haps[year]["unique"] + + self.total_mono = np.asarray( + [ + np.sum(list(self.haplotype_counts[year].values())) + for year in self.chrono_years + ] + ) + p_mono_unique = ( + np.asarray([self.unique_mono_count[year] for year in self.chrono_years]) + / self.total_mono + ) + p_mono_clonal = 1.0 - p_mono_unique + var_mono_unique = (p_mono_unique * (1.0 - p_mono_unique)) / self.total_mono + self.popgen_stats["p_mono_unique"] = list(p_mono_unique) + self.popgen_stats["var_mono_unique"] = list(var_mono_unique) + self.popgen_stats["wilson_mono_unique"] = [ + wilson(p, n) for p, n in zip(p_mono_unique, self.total_mono) + ] + x = np.asarray(self.chrono_years) + X = sm.add_constant(x) + y = np.array(p_mono_unique) + model = sm.OLS(y, X).fit() + self.popgen_stats["mono_unique_model"] = model + weighted_mean, weighted_var, weighted_ci = inverse_var_weight( + p_mono_unique, var_mono_unique + ) + self.popgen_stats["mono_unique_inv_var"] = ( + weighted_mean, + weighted_var, + weighted_ci, + ) + + self.popgen_stats["p_mono_clonal"] = list(p_mono_clonal) + self.popgen_stats["var_mono_clonal"] = list( + var_mono_unique + ) # it's the same because it is the inverse + self.popgen_stats["wilson_mono_clonal"] = [ + wilson(p, n) for p, n in zip(p_mono_clonal, self.total_mono) + ] + x = np.asarray(self.chrono_years) + X = sm.add_constant(x) + y = np.array(p_mono_clonal) + model = sm.OLS(y, X).fit() + self.popgen_stats["mono_clonal_model"] = model + weighted_mean, weighted_var, weighted_ci = inverse_var_weight( + p_mono_clonal, var_mono_unique + ) + self.popgen_stats["mono_clonal_inv_var"] = ( + weighted_mean, + weighted_var, + weighted_ci, + ) + + # calculate mono diversity - all + for year in self.chrono_years: + hap_ids = np.asarray(list(self.haplotype_counts[year].keys())) + hap_counts = np.asarray(list(self.haplotype_counts[year].values())) + hap_freqs = hap_counts / np.sum(hap_counts) + sampling_idxes = np.random.choice(hap_ids, p=hap_freqs, size=(200, 200)) + shannon_idxes, evenness_scores, H12_scores = [], [], [] + for sampling_idx in sampling_idxes: + sampled_counts = Counter(sampling_idx) + sampled_freqs = np.asarray(list(sampled_counts.values())) / 200 + + H12 = calculate_H12(sampled_freqs) + shannon_idx = calculate_Shannons_index(sampled_freqs) + shannon_idxes.append(shannon_idx) + evenness = calculate_Evenness( + shannon_idx, len(list(sampled_counts.keys())) + ) + evenness_scores.append(evenness) + H12_scores.append(H12) + + self.popgen_stats["shannon_idx_mean"].append(np.mean(shannon_idxes)) + self.popgen_stats["evenness_mean"].append(np.mean(evenness_scores)) + self.popgen_stats["H12_mean"].append(np.mean(H12_scores)) + + self.popgen_stats["shannon_idx_var"].append(np.var(shannon_idxes)) + self.popgen_stats["evenness_var"].append(np.var(evenness_scores)) + self.popgen_stats["H12_var"].append(np.var(H12_scores)) + + self.popgen_stats["shannon_idx_mean"] = np.array( + self.popgen_stats["shannon_idx_mean"] + ) + model = sm.OLS(self.popgen_stats["shannon_idx_mean"], X).fit() + self.popgen_stats["shannon_idx_model"] = model + + self.popgen_stats["evenness_mean"] = np.array( + self.popgen_stats["evenness_mean"] + ) + model = sm.OLS(self.popgen_stats["evenness_mean"], X).fit() + self.popgen_stats["evenness_model"] = model + + self.popgen_stats["H12_mean"] = np.array(self.popgen_stats["H12_mean"]) + model = sm.OLS(self.popgen_stats["H12_mean"], X).fit() + self.popgen_stats["H12_model"] = model + + if "mccoil_median" in self.barcodes_df.columns: + for year, df in self.barcodes_df.groupby("Year"): + self.popgen_stats["mccoil_coi"].append(np.mean(df["mccoil_median"])) + self.popgen_stats["mccoil_coi_std"].append(np.std(df["mccoil_median"])) + for year, df in self.barcodes_df.groupby("Year"): + self.popgen_stats["mccoil_coi_poly"].append( + np.mean([x for x in df["mccoil_median"] if x >= 2]) + ) + self.popgen_stats["mccoil_coi_poly_std"].append( + np.std([x for x in df["mccoil_median"] if x >= 2]) + ) + + def calculate_polyhet_timeseries(self): + self.poly_het_dict = defaultdict(dict) + for year in self.chrono_years: + for position in self.loci_position_names: + counts = Counter(self.poly_barcode_year_dfs[year][position].to_list()) + n_missing = counts.pop("X", 0) + total = np.sum(list(counts.values())) + n_het = counts.pop("N", 0) + p_het = n_het / total + # print(year, position, p_het, n_missing, n_missing/total, total) + self.poly_het_dict[year][position] = (p_het, total) + + self.poly_het_timeseries = defaultdict(list) + for loci in self.loci_position_names: + for year in self.chrono_years: + self.poly_het_timeseries[loci].append(self.poly_het_dict[year][loci][0]) + + def quantify_observed_poly_het(self, adjustedN=False): + self.poly_barcode_het_dist = defaultdict(list) + self.poly_barcode_het_avg = {} + self.poly_samples = {} + if not adjustedN: + # counting N from barcode + for year in self.chrono_years: + for i, row in enumerate( + self.poly_barcode_year_dfs[year][ + self.poly_barcode_year_dfs[year].columns[7:31] + ].to_numpy() + ): + barcode = ("").join(row) + barcode_counts = Counter(barcode) + barcode_counts.pop("X", 0) + total = np.sum(list(barcode_counts.values())) + het = barcode_counts.pop("N") + H_poly_barcode = het / total + self.poly_barcode_het_dist[year].append(H_poly_barcode) + self.poly_samples[year] = list( + self.poly_barcode_year_dfs[year]["Sample_Name"] + ) + self.poly_barcode_het_avg[year] = np.mean( + self.poly_barcode_het_dist[year] + ) + else: + for year in self.chrono_years: + total = 24.0 - np.asarray(self.poly_barcode_year_dfs[year]["X"]) + het = ( + np.asarray(self.poly_barcode_year_dfs[year]["Adjusted_Het"]) / total + ) + self.poly_barcode_het_dist[year] = het + self.poly_samples[year] = list( + self.poly_barcode_year_dfs[year]["Sample_Name"] + ) + self.poly_barcode_het_avg[year] = np.mean(het) + + def sample_poly_barcodes(self, year, coi=2, samples=100, Ftrue=0): + combinations = np.random.randint( + self.mono_barcode_year_dfs[year].index[0], + self.mono_barcode_year_dfs[year].index[-1], + size=(samples, coi), + ) + sampled_poly_barcodes = [] + for i, combo in enumerate(combinations): + sampled_haplotypes = self.mono_barcode_year_dfs[year].loc[ + combo, ["Haplotype"] + ] + flag = len(np.unique(list(sampled_haplotypes["Haplotype"]))) != coi + while flag: + combo = np.random.randint( + self.mono_barcode_year_dfs[year].index[0], + self.mono_barcode_year_dfs[year].index[-1], + coi, + ) + combinations[i] = combo + sampled_haplotypes = self.mono_barcode_year_dfs[year].loc[ + combo, ["Haplotype"] + ] + flag = len(np.unique(list(sampled_haplotypes["Haplotype"]))) != coi + combo_barcodes = self.mono_barcode_year_dfs[year].loc[ + combo, self.loci_position_names + ] + check_combinations(combo_barcodes) + # sampled_poly_barcodes.append(combo_barcodes.apply(lambda x: return_stats(x, Ftrue), axis = 0).to_list()) + sampled_poly_barcodes.append( + combo_barcodes.apply(lambda x: return_stats(x), axis=0).to_list() + ) + + sampled_poly_barcodes = np.asarray(sampled_poly_barcodes) + + return sampled_poly_barcodes + + def simulator(self, n_poly, n_iterations=1000, Ftrue=0, axis=0, coi=2): + run_fn_tests() + poly_simulations = defaultdict(list) + for year, n in zip(self.chrono_years, n_poly): + for n_rep in range(n_iterations): + attempt_count = 1 + if n_rep % 100 == 0: + print(year, n, n_rep) + b = self.sample_poly_barcodes(year, coi=coi, samples=n) + heterozygotes = quantify_het(b, Ftrue, axis=axis) + total = quantify_total(b, axis=axis) + p_het = heterozygotes / total + while ( + np.isfinite(p_het).all() == False + ): # if zero is found in the total + assert attempt_count <= 3, "maximum number of attempts reached" + print("attempting resample {x}".format(x=attempt_count)) + b = self.sample_poly_barcodes(year, samples=n) + heterozygotes = quantify_het(b, Ftrue, axis=axis) + total = quantify_total(b, axis=axis) + p_het = heterozygotes / total + attempt_count += 1 + poly_simulations[year].append(p_het) + poly_simulations[year] = np.asarray(poly_simulations[year]) + return poly_simulations + + def sample_cotx_barcodes_from_mono( + self, year, coi=2, samples=100, initial_coi=2, cotx_event=1 + ): + # cotx_sim stats + cotx_sim_filtered_data = [ + x + for x in self.cotx_simulation_data[initial_coi][ + "cotx_event{x}".format(x=cotx_event) + ] + if len(x) >= coi + ] + random_sim_idxes = np.asarray( + random.sample(range(len(cotx_sim_filtered_data)), samples) + ) + sampled_cotx_barcodes = np.asarray(cotx_sim_filtered_data, dtype="object")[ + random_sim_idxes + ] + + # superinfection layer + combinations = np.random.randint( + self.mono_barcode_year_dfs[year].index[0], + self.mono_barcode_year_dfs[year].index[-1], + size=(samples, initial_coi), + ) + sampled_poly_barcodes = [] + for i, combo in enumerate(combinations): + sampled_haplotypes = self.mono_barcode_year_dfs[year].loc[ + combo, ["Haplotype"] + ] + flag = len(np.unique(list(sampled_haplotypes["Haplotype"]))) != initial_coi + while flag: + combo = np.random.randint( + self.mono_barcode_year_dfs[year].index[0], + self.mono_barcode_year_dfs[year].index[-1], + initial_coi, + ) + combinations[i] = combo + sampled_haplotypes = self.mono_barcode_year_dfs[year].loc[ + combo, ["Haplotype"] + ] + flag = ( + len(np.unique(list(sampled_haplotypes["Haplotype"]))) != initial_coi + ) + combo_barcodes = self.mono_barcode_year_dfs[year].loc[ + combo, self.loci_position_names + ] + # print(combo, combo_barcodes) + check_combinations(combo_barcodes) + + # print(sampled_cotx_barcodes[i]) + relatedness_maps = [] + for n in range(coi): + relatedness_maps.append(sampled_cotx_barcodes[i][n]) + # relatedness_map1 = sampled_cotx_barcodes[i][0] + # relatedness_map2 = sampled_cotx_barcodes[i][1] + + combo_barcodes = combo_barcodes.to_numpy() + # print(relatedness_maps) + # print(combo_barcodes) + cotx_strains = [] + for relatedness_map in relatedness_maps: + tmp = [ + combo_barcodes[strain_choice][position] + for position, strain_choice in enumerate(relatedness_map) + ] + cotx_strains.append(tmp) + # cotx_strain1 = [combo_barcodes[strain_choice][position] for position,strain_choice in enumerate(relatedness_map1)] + # cotx_strain2 = [combo_barcodes[strain_choice][position] for position,strain_choice in enumerate(relatedness_map2)] + # print('Cotx Strains') + # print(cotx_strains) + df = DataFrame(cotx_strains) # [cotx_strain1, cotx_strain2]) + sampled_poly_barcodes.append( + df.apply(lambda x: return_stats(x), axis=0).to_list() + ) + + sampled_poly_barcodes = np.asarray(sampled_poly_barcodes) + + return sampled_poly_barcodes + + def simulator_cotx( + self, n_poly, n_iterations=1000, axis=0, coi=2, initial_coi=2, cotx_event=1 + ): + run_fn_tests() + poly_simulations = defaultdict(list) + for year, n in zip(self.chrono_years, n_poly): + for n_rep in range(n_iterations): + attempt_count = 1 + if n_rep % 100 == 0: + print(year, n, n_rep) + b = self.sample_cotx_barcodes_from_mono( + year, + coi=coi, + samples=n, + initial_coi=initial_coi, + cotx_event=cotx_event, + ) + heterozygotes = quantify_het(b, Ftrue=0, axis=axis) + total = quantify_total(b, axis=axis) + p_het = heterozygotes / total + while ( + np.isfinite(p_het).all() == False + ): # if zero is found in the total + assert attempt_count <= 3, "maximum number of attempts reached" + print("attempting resample {x}".format(x=attempt_count)) + b = self.sample_poly_barcodes(year, samples=n) + heterozygotes = quantify_het(b, Ftrue, axis=axis) + total = quantify_total(b, axis=axis) + p_het = heterozygotes / total + attempt_count += 1 + poly_simulations[year].append(p_het) + poly_simulations[year] = np.asarray(poly_simulations[year]) + return poly_simulations + + def calculate_RH(self, n_poly_per_year=20, n_iter=20): + self.RH_barcode_dict = {} + self.observed_RH = defaultdict(list) + + print("Simulating mono barcode sampling for RH") + H_mono_barcodes = self.simulator( + [n_poly_per_year for x in self.n_poly], n_iter, 0, axis=1 + ) + self.calculate_RH_barcode_distribution(H_mono_barcodes) + self.calculate_RHyear_distribution(H_mono_barcodes) + self.H_mono_barcodes = H_mono_barcodes + + # actual samples + minimum_sample = np.min(self.n_poly) + for year in self.chrono_years: + for i, H_poly_barcode in enumerate(self.poly_barcode_het_dist[year]): + RH_sample_dist = [ + calculate_RH_sample(np.mean(sim_trace), H_poly_barcode) + for sim_trace in self.H_mono_barcodes[year] + ] + self.observed_RH[year].append(RH_sample_dist) + + X = sm.add_constant(self.chrono_years) + y = np.array( + [np.mean(self.RH_barcode_dict[year]) for year in self.chrono_years] + ) + model1 = sm.OLS(y, X).fit() + self.RH_barcode_dict["model"] = model1 + + data, Rh_sample_averages, cotx_averages = ( + [], + defaultdict(list), + defaultdict(list), + ) + for year in self.chrono_years: + for sample_name, RH_sample_dist in zip( + self.poly_samples[year], self.observed_RH[year] + ): + data.append([sample_name, year, np.mean(RH_sample_dist)]) + self.RH_df = DataFrame(data) + self.RH_df.columns = ["Sample", "Year", "RH"] + self.RH_df["classification"] = self.RH_df["RH"].apply(RH_classifier) + self.poly_df = pd.merge( + self.master_df, self.RH_df, left_on="Sample_Name", right_on="Sample" + ) + + for year, df in self.RH_df.groupby("Year"): + total_sample = np.asarray(df["RH"]) + cotx_total_samples = np.asarray(df["classification"]) + + sampling_idxes = np.random.randint(0, len(total_sample), size=(100, 200)) + for sampling_idx in sampling_idxes: + RH_sample = total_sample[sampling_idx] + Rh_sample_averages[year].append(np.mean(RH_sample)) + + cotx_samples = cotx_total_samples[sampling_idx] + cotx_counts = Counter(cotx_samples) + p_cotx = (cotx_counts["cotx"] + cotx_counts["cotx_probable"]) / len( + sampling_idx + ) + cotx_averages[year].append(p_cotx) + averages = np.asarray( + [np.mean(Rh_sample_averages[year]) for year in self.chrono_years] + ) + variances = np.asarray( + [np.var(Rh_sample_averages[year]) for year in self.chrono_years] + ) + weighted_mean, weighted_var, weighted_ci = inverse_var_weight( + averages, variances + ) + + self.RH_yearly_averages = averages + self.RH_yearly_variances = variances + self.RH_average = weighted_mean + self.RH_weighted_var = weighted_var + self.RH_ci = weighted_ci + + def calculate_RH_barcode_distribution(self, poly_simulations): + def distance_RH_trace(RH, H_mono_trace, H_poly_trace): + H_mono_trace = np.asarray(H_mono_trace) + H_poly_trace = np.asarray(H_poly_trace) + distance = np.sum((H_mono_trace - (H_mono_trace * RH) - H_poly_trace) ** 2) + return distance + + H_poly_trace = list(self.poly_barcode_het_avg.values()) + n_reps = len(poly_simulations[list(poly_simulations.keys())[0]]) + barcode_het_timetraces = [] + for irep in range(n_reps): + timetrace = [] + for year in self.chrono_years: + timetrace.append(np.mean(poly_simulations[year][irep])) + barcode_het_timetraces.append(timetrace) + + RH_barcode_distribution = [] + for itrace in range(n_reps): + output = scipy.optimize.minimize( + lambda RH: distance_RH_trace( + RH, barcode_het_timetraces[itrace], H_poly_trace + ), + x0=[0.5], + bounds=[(-1, 1)], + ) + RH_barcode_distribution.append(output) + self.RH_barcode_dict["total"] = [ + output.x[0] for output in RH_barcode_distribution + ] + + def calculate_RHyear_distribution(self, poly_simulations): + def distance_RHyear_trace( + RH, H_mono_traces_dict, itrace, year, H_poly_dict=self.poly_barcode_het_dist + ): + distance = 0 + H_mono_trace = np.mean(H_mono_traces_dict[year][itrace]) + H_poly_trace = np.mean(H_poly_dict[year]) + distance_array = (H_mono_trace - (H_mono_trace * RH) - H_poly_trace) ** 2 + return np.sum(distance_array) + + for year in self.chrono_years: + n_reps = len(poly_simulations[list(poly_simulations.keys())[0]]) + RH_year_distribution = [] + for itrace in range(n_reps): + output = scipy.optimize.minimize( + lambda RH: distance_RHyear_trace( + RH, poly_simulations, itrace, year + ), + x0=[0.5], + bounds=[(0, 1)], + ) + RH_year_distribution.append(output) + + self.RH_barcode_dict[year] = [ + output.x[0] for output in RH_year_distribution + ] + + def simulate_coi_cotx_sweep(self, n_poly=200, n_iter=200, oocyst_alpha=2.5): + self.model_expectations = defaultdict(lambda: defaultdict(list)) + self.cotx_simulation_data = load_cotx_simulations(oocyst_alpha) + + H_mono_barcode_coi = defaultdict(dict) + for coi in [2, 3, 4, 5]: + print("Simulating COI={coi} expectation".format(coi=coi)) + H_mono_barcode_coi[coi] = self.simulator( + [n_poly for x in self.n_poly], n_iter, 0, axis=1, coi=coi + ) + + for year in self.chrono_years: + for coi in [2, 3, 4, 5]: + for sim_iteration in H_mono_barcode_coi[coi][year]: + mean_het = np.mean(self.H_mono_barcodes[year]) + for sim in sim_iteration: + self.model_expectations[ + "coi={x}, oocyst_alpha={alpha}".format( + x=coi, alpha=oocyst_alpha + ) + ][year].append(calculate_RH_sample(mean_het, sim)) + + for initial_coi in [2, 3, 4, 5]: + for cotx_round in [1, 2, 3]: + print( + "Initial COI = {initial_coi}, Simulating cotx round {x}".format( + initial_coi=initial_coi, x=cotx_round + ) + ) + H_mono_barcode_cotx = self.simulator_cotx( + [n_poly for x in self.n_poly], + n_iter, + axis=1, + coi=2, + initial_coi=initial_coi, + cotx_event=cotx_round, + ) + for year in self.chrono_years: + for sim_iteration in H_mono_barcode_cotx[year]: + mean_het = np.mean(self.H_mono_barcodes[year]) + for sim in sim_iteration: + self.model_expectations[ + "cotx_{initial_coi}_{alpha}_{cotx_round}".format( + initial_coi=initial_coi, + alpha=oocyst_alpha, + cotx_round=cotx_round, + ) + ][year].append(calculate_RH_sample(mean_het, sim)) + + def plot_sample_distribution( + self, color, ax=None, x_annotate=-0.2, y_annotate=0.1, legend=True, title=None + ): + if not ax: + fig, ax = plt.subplots() + ax.bar( + [year for year in self.chrono_years], + self.n_singles, + color="grey", + alpha=0.3, + ) + # yerr = np.sqrt(n_total * n_singles/n_total * n_poly/n_total)) + + bar = ax.bar( + [year for year in self.chrono_years], + self.n_poly, + color=color, + bottom=self.n_singles, + ) # , yerr = np.sqrt(n_total * n_singles/n_total * n_poly/n_total)) + + for x, y, z in zip(self.chrono_years, self.n_singles, self.n_singles): + x = round(x, 2) + y = round(y, 2) + ax.annotate( + str(z), + (x + x_annotate, y + y_annotate), + fontsize=15, + color="black", + fontweight="bold", + ) + + for x, y, z in zip( + self.chrono_years, self.n_singles + self.n_poly, self.n_poly + ): + x = round(x, 2) + y = round(y, 2) + ax.annotate( + str(z), + (x + x_annotate, y + y_annotate), + fontsize=12, + color=color, + fontweight="bold", + ) + + legend_elements = [ + Patch(facecolor="grey", edgecolor="black", label="Monogenomic"), + Patch(facecolor=color, edgecolor="black", label="Polygenomic"), + ] + if legend: + ax.legend(handles=legend_elements) + if not title: + ax.set_title("Sample Distribution", fontsize=20) + else: + ax.set_title(title, fontsize=20, loc="left") + ax.set_xticks(self.chrono_years) + ax.set_xticklabels(self.chrono_years) + ax.tick_params(labelsize=15) + return ax + + def plot_mono_poly_fract( + self, + color, + ax=None, + x_annotate=-0.02, + y_annotate=0.02, + annotate_color="black", + title=None, + ): + if not ax: + fig, ax = plt.subplots() + p_mono = self.n_singles / self.n_total + bar = ax.bar( + [year for year in self.chrono_years], p_mono, color="grey", alpha=0.3 + ) + for b, z in zip(bar, [round(_, 2) for _ in p_mono]): + x, y = b._x0, b._height + # print(x,y) + if str(x) == "nan": + x = 0 + if str(y) == "nan": + y = 0 + x = round(x, 2) + y = round(y, 2) + ax.annotate( + str(z), + (x + x_annotate, y + y_annotate), + fontsize=12, + color=annotate_color, + fontweight="bold", + ) + + ax.bar( + [year for year in self.chrono_years], + self.n_poly / self.n_total, + color=color, + bottom=self.n_singles / self.n_total, + ) + if not title: + ax.set_title("Mono vs Poly Fraction", fontsize=20) + else: + ax.set_title(title, fontsize=20, loc="left") + ax.tick_params(labelsize=15) + ax.set_xlim(self.chrono_years[0] - 1, self.chrono_years[-1] + 1) + ax.set_xticks(self.chrono_years) + ax.set_xticklabels(self.chrono_years) + + def plot_mono_hap_sharing( + self, + color, + ax=None, + annotate_color="black", + x_annotate=0.2, + y_annotate=0.05, + title=None, + ): + if not ax: + fig, ax = plt.subplots() + + for year in self.chrono_years: + bottom, unique = 0, 0 + sorted_hid = sorted( + self.haplotype_counts[year], + key=lambda x: self.haplotype_counts[year][x], + reverse=True, + ) + cpalette = sns.color_palette( + Barcode_Stats.cpalette_converter[color], len(self.repeat_haps[year]) + ) + total = np.sum(list(self.repeat_haps[year].values())) + for i, hid in enumerate(self.repeat_haps[year]): + if hid != "unique": + height = self.repeat_haps[year][hid] / total + ax.bar( + [year], + height, + bottom=bottom, + color=cpalette[i], + edgecolor="black", + ) + bottom += height + # shared_fracts.append(round(bottom,2)) + bar = ax.bar( + [year], + self.repeat_haps[year]["unique"] / total, + bottom=bottom, + color="grey", + alpha=0.3, + ) + + shared_fracts = [ + round(_, 2) for _ in 1.0 - np.asarray(self.popgen_stats["p_mono_unique"]) + ] + for x, y, z in zip(self.chrono_years, shared_fracts, shared_fracts): + x = round(x, 2) + y = round(y, 2) + ax.annotate( + str(z), + (x - x_annotate, y + y_annotate), + fontsize=12, + color=annotate_color, + fontweight="bold", + ) + + if not title: + ax.set_title("Mono Clonality", fontsize=20) + else: + ax.set_title(title, fontsize=20, loc="left") + ax.tick_params(labelsize=15) + ax.set_xlim(self.chrono_years[0] - 1, self.chrono_years[-1] + 1) + ax.set_xticks(self.chrono_years) + ax.set_xticklabels(self.chrono_years) + + def plot_persistent_clones( + self, color, ax=None, x_annotate=[-0.3, 0.2], y_annotate=0.1, title=None + ): + if not ax: + fig, ax = plt.subplots() + cpalette = sns.color_palette(Barcode_Stats.cpalette_converter[color], 10) + hap_stats = defaultdict(dict) + total_clusters = 1 + for year in self.chrono_years: + sorted_hid = sorted( + self.haplotype_counts[year], + key=lambda x: self.haplotype_counts[year][x], + reverse=True, + ) + for hid in sorted_hid: + if self.haplotype_counts[year][hid] != 1: + hap_stats[hid][year] = self.haplotype_counts[year][hid] + total_clusters += 1 + cpalette = sns.color_palette( + Barcode_Stats.cpalette_converter[color], total_clusters + ) + + count = 1 + flipper = 0 + for haplotype in hap_stats: + x_array, y_array, s_array = [], [], [] + for year in hap_stats[haplotype]: + x_array.append(year) + y_array.append(count) + s_array.append(hap_stats[haplotype][year] * 100) + ax.scatter( + x_array, y_array, s_array, color=cpalette[count - 1], edgecolor="black" + ) + ax.plot(x_array, y_array, color=cpalette[count - 1]) + for i, txt in enumerate(s_array): + ax.annotate( + int(txt / 100.0), + (x_array[i] + x_annotate[flipper], y_array[i] - y_annotate), + fontsize=12, + color="black", + fontweight="bold", + ) + if flipper == 0: + flipper = 1 + else: + flipper = 0 + count += 1 + if not title: + ax.set_title("Mono Clonality", fontsize=20) + else: + ax.set_title(title, fontsize=20, loc="left") + ax.tick_params(labelsize=15) + ax.tick_params(left=False, labelleft=False) + + ax.set_xlim(self.chrono_years[0] - 1, self.chrono_years[-1] + 1) + ax.set_xticks(self.chrono_years) + ax.set_xticklabels(self.chrono_years) + + def plot_longitudinal( + self, field, color="orange", ax=None, inverse_var=False, title=None + ): + if not ax: + fig, ax = plt.subplots() + fields = { + "mono_unique": ( + "p_mono_unique", + "var_mono_unique", + "mono_unique_model", + "mono_unique_inv_var", + ), + "poly_fract": ( + "p_poly_fract", + "var_poly_fract", + "poly_fract_model", + "poly_fract_inv_var", + ), + "cotx": ("cotx_average", "cotx_var", "cotx_inv_var"), + } + #'evenness': ('evenness_mean', 'evenness_var', 'evenness_model'), + #'H12': ('H12_mean', 'H12_var', 'H12_model')} + + p = self.popgen_stats[fields[field][0]] + variances = self.popgen_stats[fields[field][1]] + + if not inverse_var: + model = self.popgen_stats[fields[field][2]] + x = np.asarray(self.chrono_years) + X = sm.add_constant(x) + ax.scatter(self.chrono_years, p, color=color) + ax.plot(x, model.predict(X), color=color) + ax.fill_between( + self.chrono_years, + p + 2.5 * np.sqrt(variances), + p - 2.5 * np.sqrt(variances), + alpha=0.3, + color=color, + ) + ax.set_ylim(0, 1) + + ax.set_title(field, fontsize=20) + # ax.set_xlabel('Year', fontsize = 15) + ax.set_ylabel("Proportion", fontsize=15) + ax.tick_params(labelsize=15) + ax.set_xlim(self.chrono_years[0] - 1, self.chrono_years[-1] + 1) + ax.set_xticks(self.chrono_years) + ax.set_xticklabels(self.chrono_years) + + else: + shifted_x = [x + 1 for x in range(len(self.chrono_years))] + ax.errorbar( + shifted_x, + p, + color=color, + yerr=1.96 * np.sqrt(variances), + markersize=20, + markeredgecolor=color, + markerfacecolor=color, + fmt=".", + ecolor=color, + capsize=10, + ) + + weighted_mean, weighted_var, weighted_coi = self.popgen_stats[ + fields[field][3] + ] + x = [shifted_x[0], shifted_x[-1]] + ax.plot(x, [weighted_mean for _ in x], color=color, linewidth=3) + ax.fill_between( + x, + [weighted_coi[0] for _ in x], + [weighted_coi[1] for _ in x], + color=color, + linewidth=3, + alpha=0.2, + ) + ax.set_ylim(0, 1) + if not title: + ax.set_title(field, fontsize=20) + else: + ax.set_title(title, fontsize=20, loc="left") + # ax.set_xlabel('Year', fontsize = 15) + ax.set_ylabel("Proportion", fontsize=15) + ax.tick_params(labelsize=15) + ax.set_xlim(shifted_x[0] - 0.5, shifted_x[-1] + 0.5) + ax.set_xticks(shifted_x) + ax.set_xticklabels(shifted_x) + + def plot_RH_average_confidence(self, color="orange", ax=None): + if not ax: + fig, ax = plt.subplots() + RH = np.mean(self.RH_barcode_dict["total"]) + RH_ci = ( + np.percentile(self.RH_barcode_dict["total"], 2.5), + np.percentile(self.RH_barcode_dict["total"], 97.5), + ) + ax.hist(self.RH_barcode_dict["total"], color="orange") + + ax.set_xlabel(r"$R_{H}$", fontsize=15) + ax.set_ylabel("Freq", fontsize=15) + + def plot_RHsample_longitudinal_average(self, color="orange", ax=None): + if not ax: + fig, ax = plt.subplots() + y = np.array( + [np.mean(self.RH_barcode_dict[year]) for year in self.chrono_years] + ) + X = sm.add_constant(self.chrono_years) + + ax.scatter(self.chrono_years, y, color=color) + ax.plot( + self.chrono_years, self.RH_barcode_dict["model"].predict(X), color=color + ) + ax.boxplot( + [self.RH_barcode_dict[year] for year in self.chrono_years], + positions=self.chrono_years, + showfliers=False, + notch=True, + patch_artist=True, + boxprops=dict(facecolor=color, color=color), + capprops=dict(color=color), + whiskerprops=dict(color=color), + flierprops=dict(color=color, markeredgecolor=color), + medianprops=dict(color=color), + ) + + ax.set_ylim(0, 0.5) + ax.tick_params(axis="both", labelsize=15) + ax.set_ylabel(r"$R_{H}$", fontsize=20) + ax.set_xlabel("Year", fontsize=20) + + def plot_RHsample_longitudinal(self, color="orange", ax=None): + if not ax: + fig, ax = plt.subplots(figsize=(12, 5)) + b = sns.swarmplot(x="Year", y="RH", data=self.RH_df, color=color, ax=ax) + ax.plot( + [0 - 0.5, len(self.chrono_years) + 0.5], + [self.RH_average, self.RH_average], + color="black", + linewidth=3, + ) + ax.tick_params(axis="both", labelsize=15) + + sns.boxplot( + showmeans=True, + # meanline=True, + meanprops={ + "marker": "s", + "markerfacecolor": "white", + "markeredgecolor": color, + "markersize": "12", + }, + medianprops={"visible": False}, + whiskerprops={"visible": False}, + zorder=10, + x="Year", + y="RH", + data=self.RH_df, + showfliers=False, + showbox=False, + showcaps=False, + ax=ax, + ) + ax.set_xlabel("Year", fontsize=20) + ax.set_ylabel(r"$R_{H}$", fontsize=20) + legend_elements = [ + Patch( + facecolor="black", + edgecolor="black", + label=r"$R_{H}=$" + + str(round(self.RH_average, 2)) + + " " + + "({ci1},{ci2})".format( + ci1=str(round(self.RH_ci[0], 2)), ci2=str(round(self.RH_ci[1], 2)) + ), + ) + ] + + ax.legend(handles=legend_elements, fontsize=15) + ax.set_ylim(-1.1, 1.0) + + def plot_cotx_sweep(self, ax=None): + if not ax: + fig, ax = plt.subplots(figsize=(12, 5)) + + simulation_boxplot_results = [] + for year in self.chrono_years: + for key in self.model_expectations: + for RH in self.model_expectations[key][year]: + simulation_boxplot_results.append([year, RH, key]) + df = DataFrame(simulation_boxplot_results) + df.columns = ["Year", "RH", "Condition"] + df_melt = df.melt(id_vars=["Year", "Condition"], value_vars="RH") + cotx_colors = sns.color_palette("rocket", 4) + superinfection_colors = sns.color_palette("mako_r", 3) + order = [ + "cotx_5_2_3", + "cotx_4_2_3", + "cotx_3_2_3", + "cotx_2_2_3", + "cotx_5_2_2", + "cotx_4_2_2", + "cotx_3_2_2", + "cotx_2_2_2", + "cotx_5_2_1", + "cotx_4_2_1", + "cotx_3_2_1", + "cotx_2_2_1", + "coi=2", + "coi=3", + "coi=4", + ] + colors = 3 * list(cotx_colors.as_hex()) + list(superinfection_colors.as_hex()) + custom_pal = {} + for x, y in zip(order, colors): + custom_pal[x] = y + + ax.fill_between([-0.5, 3.5], [1.0, 1.0], [-1.5, -1.5], color="grey", alpha=0.1) + ax.fill_between( + [8 - 0.5, 11.5], [1.0, 1.0], [-1.5, -1.5], color="grey", alpha=0.1 + ) + + b = sns.boxplot( + data=df_melt, + x="Condition", + y="value", + showfliers=False, + ax=ax, + palette=custom_pal, + showmeans=True, + meanprops={ + "marker": "o", + "markerfacecolor": "white", + "markeredgecolor": "black", + "markersize": "10", + }, + order=order, + ) + + ax.tick_params(axis="both", labelsize=15) + ax.set_ylabel(r"$R_{H}$", fontsize=20) + ax.set_xlabel("Condition", fontsize=20) + + line3 = 4 * ["3"] + 4 * ["2"] + 4 * ["1"] + ["COI=2", "COI=3", "COI=4"] + ax.set_xticklabels(line3, rotation=45) + + legend_elements = [ + Patch( + facecolor=cotx_colors[0], edgecolor="black", label=r"$COI_{i,cotx}=5$" + ), + Patch( + facecolor=cotx_colors[1], edgecolor="black", label=r"$COI_{i,cotx}=4$" + ), + Patch( + facecolor=cotx_colors[2], edgecolor="black", label=r"$COI_{i,cotx}=3$" + ), + Patch( + facecolor=cotx_colors[3], edgecolor="black", label=r"$COI_{i,cotx}=2$" + ), + Patch( + facecolor=superinfection_colors[0], + edgecolor="black", + label=r"$COI_{i,super}=2$", + ), + Patch( + facecolor=superinfection_colors[1], + edgecolor="black", + label=r"$COI_{i,super}=3$", + ), + Patch( + facecolor=superinfection_colors[2], + edgecolor="black", + label=r"$COI_{i,super}=4$", + ), + ] + + ax.legend(handles=legend_elements, fontsize=12) + + ax.plot([0, 14], [0, 0], color="black", linestyle="--") + ax.plot([0, 14], [0.3, 0.3], linestyle="--", color="crimson") + ax.annotate("Cotx Detection\n Threshold", [12.5, 0.35], color="crimson") + + def plot_RH_classification(self, color="orange", ax=None): + if not ax: + fig, ax = plt.subplots(figsize=(12, 5)) + classification_counts = Counter(self.RH_df["classification"]) + total = np.sum(list(classification_counts.values())) + x_array = np.asarray([0, 1, 2, 3]) + # colors = sns.color_palette(Barcode_Stats.cpalette_converter[color],3) + + cat1 = [ + classification_counts[key] / total + for key in ["cotx", "coi=2", "coi=3", "coi=4"] + ] + cat2 = [ + classification_counts[key] / total + for key in [ + "cotx_probable", + "coi=2_probable", + "coi=3_probable", + "coi=4_probable", + ] + ] + ax.bar(x_array, cat1, color=color) + ax.bar(x_array, cat2, bottom=cat1, color=color) + + proportions = np.asarray(cat1) + np.asarray(cat2) + + stdev_array = [] + for p in proportions: + wilson_low, wilson_high = wilson(p, total) + rel_low_boundary = p - wilson_low + rel_high_boundary = wilson_high - p + stdev_array.append((rel_low_boundary, rel_high_boundary)) + stdev_array = np.asarray(stdev_array).T + + ax.errorbar( + x_array, proportions, yerr=stdev_array, fmt=".", capsize=5, color="black" + ) + + ax.set_xticks(x_array) + ax.set_xticklabels( + ["Cotransmission", "COI=2", "COI=3", "COI=4"], fontsize=15, rotation=45 + ) + ax.set_ylabel("Proportion", fontsize=15) + ax.tick_params(labelsize=15) + ax.set_ylim(0, 1) + # ax.set_title('2020', fontsize = 20) + + def generate_summary_report(self, output_file=None): + data_report = {} + data_report["n_singles"] = self.n_singles + data_report["n_poly"] = self.n_poly + data_report["n_total"] = self.n_singles + self.n_poly + + data_report["poly_fract_data"] = [ + (round(p, 2), [round(x, 2) for x in ci]) + for p, ci in zip( + self.popgen_stats["p_poly_fract"], self.popgen_stats["poly_wilson"] + ) + ] + data_report["p_mono_unique"] = [ + (round(p, 2), [round(x, 2) for x in ci]) + for p, ci in zip( + self.popgen_stats["p_mono_unique"], + self.popgen_stats["wilson_mono_unique"], + ) + ] + + data_report["realmccoilcoi"] = [ + (round(p, 2), [max(round(p - 1.96 * std, 2), 0), round(p + 1.96 * std, 2)]) + for p, std in zip( + self.popgen_stats["mccoil_coi"], self.popgen_stats["mccoil_coi_std"] + ) + ] + data_report["realmccoilcoi_poly"] = [ + (round(p, 2), [max(round(p - 1.96 * std, 2), 0), round(p + 1.96 * std, 2)]) + for p, std in zip( + self.popgen_stats["mccoil_coi_poly"], + self.popgen_stats["mccoil_coi_poly_std"], + ) + ] + + data_report["RH_array"] = [ + (round(p, 2), [round(p - 1.96 * std, 2), round(p + 1.96 * std, 2)]) + for p, std in zip( + self.RH_yearly_averages, np.sqrt(self.RH_yearly_variances) + ) + ] + data_report_df = pd.DataFrame.from_dict(data_report).T + data_report_df.columns = self.chrono_years + if output_file: + data_report_df.to_csv(output_file) + return data_report_df + + +def show_stats(ISO3, color): + fig = plt.figure(figsize=(20, 15)) + axes = [fig.add_subplot(3, 3, i + 1) for i in range(0, 9)] + BS[ISO3].plot_sample_distribution(color, axes[0]) + BS[ISO3].plot_mono_poly_fract(color, axes[1], x_annotate=0.0) + BS[ISO3].plot_longitudinal("poly_fract", color, axes[2]) + axes[2].set_title("Poly Fraction", fontsize=15) + + BS[ISO3].plot_mono_hap_sharing(color, axes[3], x_annotate=0.35, y_annotate=0.03) + BS[ISO3].plot_persistent_clones(color, ax=axes[4], x_annotate=[-0.5, 0.5]) + BS[ISO3].plot_longitudinal("mono_unique", color, axes[5]) + axes[5].set_title("Unique Mono Fraction", fontsize=15) + + BS[ISO3].plot_RHsample_longitudinal(ax=axes[6], color=color) + BS[ISO3].plot_RH_classification(ax=axes[7], color=color) + + counts = Counter(BS[ISO3].RH_df["classification"]) + total = np.sum(list(counts.values())) + p = (counts["cotx"] + counts["cotx_probable"]) / total + stdev = np.sqrt(p * (1 - p) / total) + fig.savefig(ISO3 + "_summary_figure.svg") + + +file = "barcodes_2.22.2023_coi_results.xlsx" +BS = {} + +# for ISO3 in ['SLP', 'KDG', 'RTP', 'NDO:PP:PS', 'SES:PP:PS', 'BAN:PP:PS', 'CSS', +# 'DEG', 'GAB', 'KLD', 'KML', 'MAK', 'MDR', 'SED', 'SMS', 'TAM', 'TBK', 'VLG', +# 'NDO:PP:DM1', 'SES:PP:DM1', 'BAN:PP:DM1', 'NDO:PP:DM2', 'SES:PP:DM2', 'BAN:PP:DM2', +# 'NDO:PP:DM3', 'SES:PP:DM3', 'BAN:PP:DM3']: +# print(ISO3) + +ISO3 = sys.argv[1] +BS[ISO3] = Barcode_Stats( + file, ISO3, sheet_name=ISO3.replace(":", "_"), adjustedN=False +) # adjustedN=True) + +show_stats(ISO3, color="crimson") + +BS[ISO3].generate_summary_report("{ISO3}_summary.csv".format(ISO3=ISO3)) + +BS[ISO3].mono_barcode_df.to_csv("{ISO3}_mono_barcodes.csv".format(ISO3=ISO3)) +BS[ISO3].poly_df.to_csv("{ISO3}_poly_barcodes.csv".format(ISO3=ISO3)) From 23ea87edb44b80150d4d1ae32dd66fad5ac012d7 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 28 Feb 2023 22:41:03 +0000 Subject: [PATCH 122/297] Fixing requirements for barcode script. --- docker/lr-malaria/Dockerfile | 3 ++- docker/lr-malaria/Makefile | 8 ++++---- docker/lr-malaria/environment.yml | 3 +-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docker/lr-malaria/Dockerfile b/docker/lr-malaria/Dockerfile index 750a2b5e7..83a75ff4e 100644 --- a/docker/lr-malaria/Dockerfile +++ b/docker/lr-malaria/Dockerfile @@ -1,4 +1,4 @@ -FROM continuumio/miniconda3 +FROM continuumio/miniconda3:22.11.1 MAINTAINER Jonn Smith @@ -21,6 +21,7 @@ RUN apt-get install -y gcc python3-dev python3-setuptools && \ # copy python scripts COPY python/* /python_scripts/ +RUN chmod +x /python_scripts/* # activate conda environment RUN echo "source activate lr-malaria" > ~/.bashrc diff --git a/docker/lr-malaria/Makefile b/docker/lr-malaria/Makefile index 83aaa9d1c..673b8d967 100644 --- a/docker/lr-malaria/Makefile +++ b/docker/lr-malaria/Makefile @@ -7,11 +7,11 @@ TAG2 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):latest all: | build push build: - docker build -t $(TAG1) -t $(TAG2) . + docker build -t $(TAG1) -t $(TAG2) . build_no_cache: - docker build --no-cache -t $(TAG1) -t $(TAG2) . + docker build --no-cache -t $(TAG1) -t $(TAG2) . push: - docker push $(TAG1) - docker push $(TAG2) + docker push $(TAG1) + docker push $(TAG2) diff --git a/docker/lr-malaria/environment.yml b/docker/lr-malaria/environment.yml index 879359ec6..a23f46161 100644 --- a/docker/lr-malaria/environment.yml +++ b/docker/lr-malaria/environment.yml @@ -15,8 +15,7 @@ dependencies: - statsmodels - networkx - matplotlib - - pyplot - patsy - - sklearn + - scikit-learn - numpyencoder - tqdm From 0c64ff2cc50a81374b1d31effabe3a92a6b07ead Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 28 Feb 2023 18:29:45 -0500 Subject: [PATCH 123/297] Cleaned up the barcode script a little. --- docker/lr-malaria/environment.yml | 1 + ...es_terra_v2.py => process_barcode_data.py} | 241 ++++++++++-------- 2 files changed, 138 insertions(+), 104 deletions(-) rename docker/lr-malaria/python/{barcodes_terra_v2.py => process_barcode_data.py} (90%) mode change 100644 => 100755 diff --git a/docker/lr-malaria/environment.yml b/docker/lr-malaria/environment.yml index a23f46161..339c1706a 100644 --- a/docker/lr-malaria/environment.yml +++ b/docker/lr-malaria/environment.yml @@ -16,6 +16,7 @@ dependencies: - networkx - matplotlib - patsy + - openpyxl - scikit-learn - numpyencoder - tqdm diff --git a/docker/lr-malaria/python/barcodes_terra_v2.py b/docker/lr-malaria/python/process_barcode_data.py old mode 100644 new mode 100755 similarity index 90% rename from docker/lr-malaria/python/barcodes_terra_v2.py rename to docker/lr-malaria/python/process_barcode_data.py index 590036d8f..049ed5502 --- a/docker/lr-malaria/python/barcodes_terra_v2.py +++ b/docker/lr-malaria/python/process_barcode_data.py @@ -1,11 +1,17 @@ #!/usr/bin/env python # coding: utf-8 +# Processes barcode data for P. falciparum samples +# Based on work from the following publication: https://doi.org/10.1093/pnasnexus/pgac187 +# Orignal Author: Wesley Wong +# Modified by: Jonn Smith + import random import copy import itertools import sys import json +import argparse import scipy import pandas as pd @@ -24,10 +30,6 @@ from numpyencoder import NumpyEncoder from sklearn.neighbors import KernelDensity - -# F_loci = (h_mono - h_poly) / h_mono - - def remove_duplicate_df(df, field="Haplotype"): duplicate_rows = pd.DataFrame.duplicated(df, field, keep="first") return df[~duplicate_rows] @@ -67,22 +69,13 @@ def return_stats(s, convert_numpy=True): return np.NaN # missing data present in this comparison, skip elif poly_screen.all() == True: return 1.0 - (a[0] == a).all() # 0 = homozygous, #1 = Heterozygous - # status = 1. -(a[0] == a).all() #0 = homozygous, #1 = Heterozygous - # if status == 1.0: - # random_p = np.random.random() - # if random_p < Ftrue: - # return 1. - status - # else: - # return status - # else: - # return status else: # N present in one of the comparisons, skip for now return np.NaN -def quantify_het(barcode_comparison, Ftrue=0, axis=0): - null_het = np.nansum(barcode_comparison, axis=axis) * (1 - Ftrue) +def quantify_het(barcode_comparison, f_true=0, axis=0): + null_het = np.nansum(barcode_comparison, axis=axis) * (1 - f_true) return null_het @@ -135,12 +128,12 @@ def check_combinations(combo_barcodes): ), "Duplicate barcodes were sampled" -def calculate_RH_sample(H_mono, H_poly): - if H_mono > H_poly: - RH = (H_mono - H_poly) / H_mono +def calculate_RH_sample(h_mono, h_poly): + if h_mono > h_poly: + rh = (h_mono - h_poly) / h_mono else: - RH = (H_mono - H_poly) / H_mono - return RH + rh = (h_mono - h_poly) / h_mono + return rh def bootstrap(array, iterations): @@ -148,44 +141,44 @@ def bootstrap(array, iterations): return np.mean(resample, axis=1) -def RH_classifier(RH): - RH = float(RH) - if RH > 0.4: +def rh_classifier(rh): + rh = float(rh) + if rh > 0.4: return "cotx" - elif RH > 0.3: + elif rh > 0.3: return "cotx_probable" - elif RH > -0.1: + elif rh > -0.1: return "coi=2" - elif RH > -0.2: + elif rh > -0.2: return "coi=2_probable" - elif RH > -0.4: + elif rh > -0.4: return "coi=3" - elif RH > -0.6: + elif rh > -0.6: return "coi=3_probable" - elif RH > -0.8: + elif rh > -0.8: return "coi=4_probable" else: return "coi=4" -def calculate_Shannons_index(p_array): - H = -np.sum([p_array * np.log(p_array)]) - return H +def calculate_shannons_index(p_array): + h = -np.sum([p_array * np.log(p_array)]) + return h -def calculate_Evenness(H_array, k): - E = H_array / np.log(k) - return E +def calculate_evenness(h_array, k): + e = h_array / np.log(k) + return e -def calculate_H12(p_array): +def calculate_h12(p_array): p_array = sorted(p_array) p1 = p_array[0] p2 = p_array[1] other_p = np.asarray(p_array[2:]) sum_rest = np.sum(other_p**2) - H12 = (p1 + p2) ** 2 + sum_rest - return H12 + h12 = (p1 + p2) ** 2 + sum_rest + return h12 def interpret_cotx_simbinbarcode(sim, cotx_event=1): @@ -245,7 +238,7 @@ def wilson(p, n, z=1.96): return lower_bound, upper_bound -class Barcode_Stats: +class BarcodeStats: cpalette_converter = { "green": "viridis", "blue": "mako", @@ -255,21 +248,63 @@ class Barcode_Stats: "purple": "Purples_r", } - def __init__(self, file, ISO3, sheet_name=None, adjustedN=False): + # Set up field names from sheet: + multi_poly_field = "M/P" + sample_name_field = "Sample_Name" + + def __init__(self, excel_file, ISO3, barcode_file_path, sheet_name=None, adjusted_n=False): + self.ISO3 = ISO3 + self.barcode_file_path = barcode_file_path if not sheet_name: sheet_name = ISO3 - self.master_df = DataFrame(read_excel(file, sheet_name=ISO3)) + self.master_df = DataFrame(read_excel(excel_file, sheet_name=ISO3)) else: - self.master_df = DataFrame(read_excel(file, sheet_name=sheet_name)) + self.master_df = DataFrame(read_excel(excel_file, sheet_name=sheet_name)) self.loci_position_names = list(self.master_df.columns[7:31]) + # Define all the fields we're going to create: + self.poly_het_dict = None + self.total_mono = None + self.repeat_haps = None + self.unique_mono_count = None + self.popgen_stats = None + self.haplotype_counts = None + self.loci_allele_dict = None + self.n_total = None + self.n_poly = None + self.n_singles = None + self.poly_barcode_year_dfs = None + self.mono_barcode_year_dfs = None + self.barcode_year_dfs = None + self.chrono_years = None + self.mono_barcode_df = None + self.barcodes_df = None + self.barcode_pos_dict = None + self.poly_het_timeseries = None + self.poly_barcode_het_dist = None + self.poly_barcode_het_avg = None + self.poly_samples = None + self.RH_barcode_dict = None + self.observed_RH = None + self.H_mono_barcodes = None + self.RH_df = None + self.poly_df = None + self.RH_yearly_averages = None + self.RH_yearly_variances = None + self.RH_average = None + self.RH_weighted_var = None + self.RH_ci = None + self.model_expectations = None + self.cotx_simulation_data = None + + # Now process our data: self.extract_region(ISO3) self.extract_mono_poly_stats() self.calc_stats() self.calculate_polyhet_timeseries() - self.quantify_observed_poly_het(adjustedN) + self.quantify_observed_poly_het(adjusted_n) self.calculate_RH() def label_haplotypes(self): @@ -277,7 +312,7 @@ def label_haplotypes(self): unique_barcode_haplotype = 0 barcode_haplotypes = [] for row in self.barcodes_df[self.barcodes_df.columns[7:31]].to_numpy(): - barcode = ("").join(row) + barcode = "".join(row) if barcode not in barcode_haplotypes_dict: barcode_haplotypes_dict[barcode] = unique_barcode_haplotype unique_barcode_haplotype += 1 @@ -285,18 +320,20 @@ def label_haplotypes(self): self.barcodes_df["Haplotype"] = barcode_haplotypes def extract_region(self, ISO3): - tmp_df = self.master_df[self.master_df["ISO3"] == ISO3] # Thies samples only + tmp_df = self.master_df[self.master_df["ISO3"] == ISO3] for position in self.loci_position_names: tmp_df[position] = [ x.strip().upper() for x in tmp_df[position] ] # noticed a tab formatting in these columns + self.barcode_pos_dict = {} - fin = open("barcode_pos.txt") - for i, line in enumerate(fin.readlines()[1:]): - line = line.strip().split("\t") - self.barcode_pos_dict[self.loci_position_names[i]] = line[0] + ":" + line[1] + with open(self.barcode_file_path) as f: + next(f) + for i, line in enumerate(f): + line = line.strip().split("\t") + self.barcode_pos_dict[self.loci_position_names[i]] = line[0] + ":" + line[1] - tmp_df["M/P"] = [x.strip() for x in tmp_df["M/P"]] + tmp_df[BarcodeStats.multi_poly_field] = [x.strip() for x in tmp_df[BarcodeStats.multi_poly_field]] control_samples = [ "3D7", @@ -312,12 +349,12 @@ def extract_region(self, ISO3): ] self.barcodes_df = tmp_df[ - (tmp_df["X"] <= 2) & (~tmp_df["Sample_Name"].isin(control_samples)) + (tmp_df["X"] <= 2) & (~tmp_df[BarcodeStats.sample_name_field].isin(control_samples)) ] self.mono_barcode_df = self.barcodes_df[ (self.barcodes_df["X"] <= 2) - & (self.barcodes_df["M/P"] == "M") - & (~self.barcodes_df["Sample_Name"].isin(control_samples)) + & (self.barcodes_df[BarcodeStats.multi_poly_field] == "M") + & (~self.barcodes_df[BarcodeStats.sample_name_field].isin(control_samples)) ] self.chrono_years = sorted(Counter(self.barcodes_df["Year"]).keys()) self.label_haplotypes() @@ -335,13 +372,13 @@ def extract_mono_poly_stats(self, fail_threshold=2): self.mono_barcode_year_dfs[year] = self.barcodes_df[ (self.barcodes_df["Year"] == year) & (self.barcodes_df["X"] <= fail_threshold) - & (self.barcodes_df["M/P"] == "M") + & (self.barcodes_df[BarcodeStats.multi_poly_field] == "M") ] self.poly_barcode_year_dfs[year] = self.barcodes_df[ (self.barcodes_df["Year"] == year) & (self.barcodes_df["X"] <= fail_threshold) - & (self.barcodes_df["M/P"] == "P") + & (self.barcodes_df[BarcodeStats.multi_poly_field] == "P") ] self.mono_barcode_year_dfs[year].reset_index(drop=True, inplace=True) self.poly_barcode_year_dfs[year].reset_index(drop=True, inplace=True) @@ -372,7 +409,7 @@ def extract_mono_poly_stats(self, fail_threshold=2): self.haplotype_counts = {} for year in self.chrono_years: counts = Counter( - self.barcode_year_dfs[year][self.barcode_year_dfs[year]["M/P"] == "M"][ + self.barcode_year_dfs[year][self.barcode_year_dfs[year][BarcodeStats.multi_poly_field] == "M"][ "Haplotype" ] ) @@ -480,10 +517,10 @@ def calc_stats(self): sampled_counts = Counter(sampling_idx) sampled_freqs = np.asarray(list(sampled_counts.values())) / 200 - H12 = calculate_H12(sampled_freqs) - shannon_idx = calculate_Shannons_index(sampled_freqs) + H12 = calculate_h12(sampled_freqs) + shannon_idx = calculate_shannons_index(sampled_freqs) shannon_idxes.append(shannon_idx) - evenness = calculate_Evenness( + evenness = calculate_evenness( shannon_idx, len(list(sampled_counts.keys())) ) evenness_scores.append(evenness) @@ -554,7 +591,7 @@ def quantify_observed_poly_het(self, adjustedN=False): self.poly_barcode_year_dfs[year].columns[7:31] ].to_numpy() ): - barcode = ("").join(row) + barcode = "".join(row) barcode_counts = Counter(barcode) barcode_counts.pop("X", 0) total = np.sum(list(barcode_counts.values())) @@ -562,7 +599,7 @@ def quantify_observed_poly_het(self, adjustedN=False): H_poly_barcode = het / total self.poly_barcode_het_dist[year].append(H_poly_barcode) self.poly_samples[year] = list( - self.poly_barcode_year_dfs[year]["Sample_Name"] + self.poly_barcode_year_dfs[year][BarcodeStats.sample_name_field] ) self.poly_barcode_het_avg[year] = np.mean( self.poly_barcode_het_dist[year] @@ -575,11 +612,11 @@ def quantify_observed_poly_het(self, adjustedN=False): ) self.poly_barcode_het_dist[year] = het self.poly_samples[year] = list( - self.poly_barcode_year_dfs[year]["Sample_Name"] + self.poly_barcode_year_dfs[year][BarcodeStats.sample_name_field] ) self.poly_barcode_het_avg[year] = np.mean(het) - def sample_poly_barcodes(self, year, coi=2, samples=100, Ftrue=0): + def sample_poly_barcodes(self, year, coi=2, samples=100): combinations = np.random.randint( self.mono_barcode_year_dfs[year].index[0], self.mono_barcode_year_dfs[year].index[-1], @@ -606,7 +643,6 @@ def sample_poly_barcodes(self, year, coi=2, samples=100, Ftrue=0): combo, self.loci_position_names ] check_combinations(combo_barcodes) - # sampled_poly_barcodes.append(combo_barcodes.apply(lambda x: return_stats(x, Ftrue), axis = 0).to_list()) sampled_poly_barcodes.append( combo_barcodes.apply(lambda x: return_stats(x), axis=0).to_list() ) @@ -615,7 +651,7 @@ def sample_poly_barcodes(self, year, coi=2, samples=100, Ftrue=0): return sampled_poly_barcodes - def simulator(self, n_poly, n_iterations=1000, Ftrue=0, axis=0, coi=2): + def simulator(self, n_poly, n_iterations=1000, f_true=0, axis=0, coi=2): run_fn_tests() poly_simulations = defaultdict(list) for year, n in zip(self.chrono_years, n_poly): @@ -624,7 +660,7 @@ def simulator(self, n_poly, n_iterations=1000, Ftrue=0, axis=0, coi=2): if n_rep % 100 == 0: print(year, n, n_rep) b = self.sample_poly_barcodes(year, coi=coi, samples=n) - heterozygotes = quantify_het(b, Ftrue, axis=axis) + heterozygotes = quantify_het(b, f_true, axis=axis) total = quantify_total(b, axis=axis) p_het = heterozygotes / total while ( @@ -633,7 +669,7 @@ def simulator(self, n_poly, n_iterations=1000, Ftrue=0, axis=0, coi=2): assert attempt_count <= 3, "maximum number of attempts reached" print("attempting resample {x}".format(x=attempt_count)) b = self.sample_poly_barcodes(year, samples=n) - heterozygotes = quantify_het(b, Ftrue, axis=axis) + heterozygotes = quantify_het(b, f_true, axis=axis) total = quantify_total(b, axis=axis) p_het = heterozygotes / total attempt_count += 1 @@ -687,19 +723,13 @@ def sample_cotx_barcodes_from_mono( combo_barcodes = self.mono_barcode_year_dfs[year].loc[ combo, self.loci_position_names ] - # print(combo, combo_barcodes) check_combinations(combo_barcodes) - # print(sampled_cotx_barcodes[i]) relatedness_maps = [] for n in range(coi): relatedness_maps.append(sampled_cotx_barcodes[i][n]) - # relatedness_map1 = sampled_cotx_barcodes[i][0] - # relatedness_map2 = sampled_cotx_barcodes[i][1] combo_barcodes = combo_barcodes.to_numpy() - # print(relatedness_maps) - # print(combo_barcodes) cotx_strains = [] for relatedness_map in relatedness_maps: tmp = [ @@ -707,10 +737,7 @@ def sample_cotx_barcodes_from_mono( for position, strain_choice in enumerate(relatedness_map) ] cotx_strains.append(tmp) - # cotx_strain1 = [combo_barcodes[strain_choice][position] for position,strain_choice in enumerate(relatedness_map1)] - # cotx_strain2 = [combo_barcodes[strain_choice][position] for position,strain_choice in enumerate(relatedness_map2)] - # print('Cotx Strains') - # print(cotx_strains) + df = DataFrame(cotx_strains) # [cotx_strain1, cotx_strain2]) sampled_poly_barcodes.append( df.apply(lambda x: return_stats(x), axis=0).to_list() @@ -795,9 +822,9 @@ def calculate_RH(self, n_poly_per_year=20, n_iter=20): data.append([sample_name, year, np.mean(RH_sample_dist)]) self.RH_df = DataFrame(data) self.RH_df.columns = ["Sample", "Year", "RH"] - self.RH_df["classification"] = self.RH_df["RH"].apply(RH_classifier) + self.RH_df["classification"] = self.RH_df["RH"].apply(rh_classifier) self.poly_df = pd.merge( - self.master_df, self.RH_df, left_on="Sample_Name", right_on="Sample" + self.master_df, self.RH_df, left_on=BarcodeStats.sample_name_field, right_on="Sample" ) for year, df in self.RH_df.groupby("Year"): @@ -948,15 +975,13 @@ def plot_sample_distribution( color="grey", alpha=0.3, ) - # yerr = np.sqrt(n_total * n_singles/n_total * n_poly/n_total)) bar = ax.bar( [year for year in self.chrono_years], self.n_poly, color=color, bottom=self.n_singles, - ) # , yerr = np.sqrt(n_total * n_singles/n_total * n_poly/n_total)) - + ) for x, y, z in zip(self.chrono_years, self.n_singles, self.n_singles): x = round(x, 2) y = round(y, 2) @@ -1063,7 +1088,7 @@ def plot_mono_hap_sharing( reverse=True, ) cpalette = sns.color_palette( - Barcode_Stats.cpalette_converter[color], len(self.repeat_haps[year]) + BarcodeStats.cpalette_converter[color], len(self.repeat_haps[year]) ) total = np.sum(list(self.repeat_haps[year].values())) for i, hid in enumerate(self.repeat_haps[year]): @@ -1114,7 +1139,7 @@ def plot_persistent_clones( ): if not ax: fig, ax = plt.subplots() - cpalette = sns.color_palette(Barcode_Stats.cpalette_converter[color], 10) + cpalette = sns.color_palette(BarcodeStats.cpalette_converter[color], 10) hap_stats = defaultdict(dict) total_clusters = 1 for year in self.chrono_years: @@ -1128,7 +1153,7 @@ def plot_persistent_clones( hap_stats[hid][year] = self.haplotype_counts[year][hid] total_clusters += 1 cpalette = sns.color_palette( - Barcode_Stats.cpalette_converter[color], total_clusters + BarcodeStats.cpalette_converter[color], total_clusters ) count = 1 @@ -1187,8 +1212,6 @@ def plot_longitudinal( ), "cotx": ("cotx_average", "cotx_var", "cotx_inv_var"), } - #'evenness': ('evenness_mean', 'evenness_var', 'evenness_model'), - #'H12': ('H12_mean', 'H12_var', 'H12_model')} p = self.popgen_stats[fields[field][0]] variances = self.popgen_stats[fields[field][1]] @@ -1456,7 +1479,6 @@ def plot_RH_classification(self, color="orange", ax=None): classification_counts = Counter(self.RH_df["classification"]) total = np.sum(list(classification_counts.values())) x_array = np.asarray([0, 1, 2, 3]) - # colors = sns.color_palette(Barcode_Stats.cpalette_converter[color],3) cat1 = [ classification_counts[key] / total @@ -1567,23 +1589,34 @@ def show_stats(ISO3, color): fig.savefig(ISO3 + "_summary_figure.svg") -file = "barcodes_2.22.2023_coi_results.xlsx" -BS = {} +if __name__ == "__main__": -# for ISO3 in ['SLP', 'KDG', 'RTP', 'NDO:PP:PS', 'SES:PP:PS', 'BAN:PP:PS', 'CSS', -# 'DEG', 'GAB', 'KLD', 'KML', 'MAK', 'MDR', 'SED', 'SMS', 'TAM', 'TBK', 'VLG', -# 'NDO:PP:DM1', 'SES:PP:DM1', 'BAN:PP:DM1', 'NDO:PP:DM2', 'SES:PP:DM2', 'BAN:PP:DM2', -# 'NDO:PP:DM3', 'SES:PP:DM3', 'BAN:PP:DM3']: -# print(ISO3) - -ISO3 = sys.argv[1] -BS[ISO3] = Barcode_Stats( - file, ISO3, sheet_name=ISO3.replace(":", "_"), adjustedN=False -) # adjustedN=True) + # Set up our CLI args: + parser = argparse.ArgumentParser( + description=f"Processes P. falciparum data from an excel file into actionable information (e.g. CoI estimates)." + ) -show_stats(ISO3, color="crimson") + requiredNamed = parser.add_argument_group('required named arguments') + requiredNamed.add_argument('-f', '--excel-file', + help='Excel file containing data to process', + required=True) + requiredNamed.add_argument('-s', '--sheet', + help='Sheet name to process.', + required=True) + requiredNamed.add_argument('-b', '--barcodes', + help='Barcode file to use.', + required=True) + args = parser.parse_args() + + # Do the work: + BS = {} + ISO3 = args.sheet + BS[ISO3] = BarcodeStats( + args.excel_file, ISO3, args.barcodes, sheet_name=ISO3.replace(":", "_"), adjusted_n=False + ) -BS[ISO3].generate_summary_report("{ISO3}_summary.csv".format(ISO3=ISO3)) + show_stats(ISO3, color="crimson") -BS[ISO3].mono_barcode_df.to_csv("{ISO3}_mono_barcodes.csv".format(ISO3=ISO3)) -BS[ISO3].poly_df.to_csv("{ISO3}_poly_barcodes.csv".format(ISO3=ISO3)) + BS[ISO3].generate_summary_report("{ISO3}_summary.csv".format(ISO3=ISO3)) + BS[ISO3].mono_barcode_df.to_csv("{ISO3}_mono_barcodes.csv".format(ISO3=ISO3)) + BS[ISO3].poly_df.to_csv("{ISO3}_poly_barcodes.csv".format(ISO3=ISO3)) From 4baac1646d3d35241e5dc7b4295edd45c7c17180 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 1 Mar 2023 17:54:52 -0500 Subject: [PATCH 124/297] Updated barcode script to handle csv/tsv input. --- .../lr-malaria/python/process_barcode_data.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/docker/lr-malaria/python/process_barcode_data.py b/docker/lr-malaria/python/process_barcode_data.py index 049ed5502..935fa776c 100755 --- a/docker/lr-malaria/python/process_barcode_data.py +++ b/docker/lr-malaria/python/process_barcode_data.py @@ -252,17 +252,20 @@ class BarcodeStats: multi_poly_field = "M/P" sample_name_field = "Sample_Name" - def __init__(self, excel_file, ISO3, barcode_file_path, sheet_name=None, adjusted_n=False): + def __init__(self, input_file, ISO3, barcode_file_path, sheet_name=None, adjusted_n=False): self.ISO3 = ISO3 self.barcode_file_path = barcode_file_path - if not sheet_name: - sheet_name = ISO3 - self.master_df = DataFrame(read_excel(excel_file, sheet_name=ISO3)) + if input_file.endswith(".xls") or input_file.endswith(".xlsx"): + if not sheet_name: + self.master_df = DataFrame(read_excel(input_file, sheet_name=ISO3)) + else: + self.master_df = DataFrame(read_excel(input_file, sheet_name=sheet_name)) + self.loci_position_names = list(self.master_df.columns[7:31]) else: - self.master_df = DataFrame(read_excel(excel_file, sheet_name=sheet_name)) - self.loci_position_names = list(self.master_df.columns[7:31]) + sep = "\t" if input_file.endswith(".tsv") else "," + self.master_df = DataFrame(read_csv(input_file, sep=sep)) # Define all the fields we're going to create: self.poly_het_dict = None @@ -1597,8 +1600,8 @@ def show_stats(ISO3, color): ) requiredNamed = parser.add_argument_group('required named arguments') - requiredNamed.add_argument('-f', '--excel-file', - help='Excel file containing data to process', + requiredNamed.add_argument('-f', '--input-file', + help='TSV/CSV/Excel file containing data to process', required=True) requiredNamed.add_argument('-s', '--sheet', help='Sheet name to process.', @@ -1608,11 +1611,18 @@ def show_stats(ISO3, color): required=True) args = parser.parse_args() + # Do some validation here: + if (args.input_file.endswith(".xls") or args.input_file.endswith(".xlsx")) and not args.sheet: + print("ERROR: You must provide a sheet name with an excel file input.", file=sys.stderr) + sys.exit(1) + # Do the work: BS = {} ISO3 = args.sheet + sheet_name = ISO3.replace(":", "_") + BS[ISO3] = BarcodeStats( - args.excel_file, ISO3, args.barcodes, sheet_name=ISO3.replace(":", "_"), adjusted_n=False + args.input_file, ISO3, args.barcodes, sheet_name=sheet_name, adjusted_n=False ) show_stats(ISO3, color="crimson") From 8583c7f88e00b500bc81a36fc6c435071ec5a4af Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 1 Mar 2023 18:01:35 -0500 Subject: [PATCH 125/297] Now it actually works for CSVs/TSVs. --- docker/lr-malaria/python/process_barcode_data.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/lr-malaria/python/process_barcode_data.py b/docker/lr-malaria/python/process_barcode_data.py index 935fa776c..59619b8d8 100755 --- a/docker/lr-malaria/python/process_barcode_data.py +++ b/docker/lr-malaria/python/process_barcode_data.py @@ -262,10 +262,11 @@ def __init__(self, input_file, ISO3, barcode_file_path, sheet_name=None, adjuste self.master_df = DataFrame(read_excel(input_file, sheet_name=ISO3)) else: self.master_df = DataFrame(read_excel(input_file, sheet_name=sheet_name)) - self.loci_position_names = list(self.master_df.columns[7:31]) else: sep = "\t" if input_file.endswith(".tsv") else "," - self.master_df = DataFrame(read_csv(input_file, sep=sep)) + self.master_df = DataFrame(read_csv(input_file, sep=sep, header=0)) + + self.loci_position_names = list(self.master_df.columns[7:31]) # Define all the fields we're going to create: self.poly_het_dict = None @@ -1590,6 +1591,7 @@ def show_stats(ISO3, color): p = (counts["cotx"] + counts["cotx_probable"]) / total stdev = np.sqrt(p * (1 - p) / total) fig.savefig(ISO3 + "_summary_figure.svg") + fig.savefig(ISO3 + "_summary_figure.png") if __name__ == "__main__": From e3d2a400e21bc3cb9147ca15973ce79206af375f Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 1 Mar 2023 18:03:33 -0500 Subject: [PATCH 126/297] Minor updates. --- docker/lr-malaria/python/process_barcode_data.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docker/lr-malaria/python/process_barcode_data.py b/docker/lr-malaria/python/process_barcode_data.py index 59619b8d8..231e62d8b 100755 --- a/docker/lr-malaria/python/process_barcode_data.py +++ b/docker/lr-malaria/python/process_barcode_data.py @@ -1573,6 +1573,7 @@ def generate_summary_report(self, output_file=None): def show_stats(ISO3, color): fig = plt.figure(figsize=(20, 15)) axes = [fig.add_subplot(3, 3, i + 1) for i in range(0, 9)] + BS[ISO3].plot_sample_distribution(color, axes[0]) BS[ISO3].plot_mono_poly_fract(color, axes[1], x_annotate=0.0) BS[ISO3].plot_longitudinal("poly_fract", color, axes[2]) @@ -1586,10 +1587,13 @@ def show_stats(ISO3, color): BS[ISO3].plot_RHsample_longitudinal(ax=axes[6], color=color) BS[ISO3].plot_RH_classification(ax=axes[7], color=color) + # Calculate some stats: counts = Counter(BS[ISO3].RH_df["classification"]) total = np.sum(list(counts.values())) p = (counts["cotx"] + counts["cotx_probable"]) / total stdev = np.sqrt(p * (1 - p) / total) + + # We should save both a CSV and a TSV: fig.savefig(ISO3 + "_summary_figure.svg") fig.savefig(ISO3 + "_summary_figure.png") @@ -1598,7 +1602,7 @@ def show_stats(ISO3, color): # Set up our CLI args: parser = argparse.ArgumentParser( - description=f"Processes P. falciparum data from an excel file into actionable information (e.g. CoI estimates)." + description=f"Processes P. falciparum data from a spreadsheet into actionable information (e.g. CoI estimates)." ) requiredNamed = parser.add_argument_group('required named arguments') From 07fe5f23561be7d3ea43fc5428e244b01d2dd614 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 1 Mar 2023 18:15:05 -0500 Subject: [PATCH 127/297] Fixed ingest of new barcode def TSV. --- .../lr-malaria/python/process_barcode_data.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docker/lr-malaria/python/process_barcode_data.py b/docker/lr-malaria/python/process_barcode_data.py index 231e62d8b..ca5e4c17d 100755 --- a/docker/lr-malaria/python/process_barcode_data.py +++ b/docker/lr-malaria/python/process_barcode_data.py @@ -257,6 +257,7 @@ def __init__(self, input_file, ISO3, barcode_file_path, sheet_name=None, adjuste self.ISO3 = ISO3 self.barcode_file_path = barcode_file_path + # Ingest the input file: if input_file.endswith(".xls") or input_file.endswith(".xlsx"): if not sheet_name: self.master_df = DataFrame(read_excel(input_file, sheet_name=ISO3)) @@ -266,7 +267,13 @@ def __init__(self, input_file, ISO3, barcode_file_path, sheet_name=None, adjuste sep = "\t" if input_file.endswith(".tsv") else "," self.master_df = DataFrame(read_csv(input_file, sep=sep, header=0)) - self.loci_position_names = list(self.master_df.columns[7:31]) + # Ingest the barcode file: + tmp_barcode_def_df = DataFrame(read_csv(barcode_file_path, sep="\t")) + self.loci_position_names = list(tmp_barcode_def_df["name"].values) + self.barcode_pos_dict = {name: f"{contig}:{pos}" for name, contig, pos in + zip(tmp_barcode_def_df['name'], tmp_barcode_def_df['chr'], tmp_barcode_def_df['pos'])} + # We don't have to, but we should clean up our mess: + del tmp_barcode_def_df # Define all the fields we're going to create: self.poly_het_dict = None @@ -285,7 +292,6 @@ def __init__(self, input_file, ISO3, barcode_file_path, sheet_name=None, adjuste self.chrono_years = None self.mono_barcode_df = None self.barcodes_df = None - self.barcode_pos_dict = None self.poly_het_timeseries = None self.poly_barcode_het_dist = None self.poly_barcode_het_avg = None @@ -329,13 +335,6 @@ def extract_region(self, ISO3): tmp_df[position] = [ x.strip().upper() for x in tmp_df[position] ] # noticed a tab formatting in these columns - - self.barcode_pos_dict = {} - with open(self.barcode_file_path) as f: - next(f) - for i, line in enumerate(f): - line = line.strip().split("\t") - self.barcode_pos_dict[self.loci_position_names[i]] = line[0] + ":" + line[1] tmp_df[BarcodeStats.multi_poly_field] = [x.strip() for x in tmp_df[BarcodeStats.multi_poly_field]] From a4ddaa02117b88dd455b83621a978b6338fa0487 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 2 Mar 2023 10:20:27 -0500 Subject: [PATCH 128/297] Adding in WDL for P. falciparum barcode panel processing. --- wdl/PanelProcessMalariaBarcodesForRh.wdl | 232 +++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 wdl/PanelProcessMalariaBarcodesForRh.wdl diff --git a/wdl/PanelProcessMalariaBarcodesForRh.wdl b/wdl/PanelProcessMalariaBarcodesForRh.wdl new file mode 100644 index 000000000..53ce19907 --- /dev/null +++ b/wdl/PanelProcessMalariaBarcodesForRh.wdl @@ -0,0 +1,232 @@ +version 1.0 + +########################################################################################## +## A workflow that processes P. falciparum SNP panels (read: barcodes) and calculates several +## metrics that are relevant to studying the epidemiology of the disease. +## +## This WDL calls a script written by Wes Wong and based on the following paper: +## https://doi.org/10.1093/pnasnexus/pgac187 +########################################################################################## + +import "tasks/Structs.wdl" +import "tasks/Utils.wdl" as Utils +import "tasks/Finalize.wdl" as FF + +workflow PanelProcessMalariaBarcodesForRh { + input { + + # Unfortunately the easiest way to make this work would be to pass a spreadsheet into the script. + # Because of how Terra is structured, this isn't really possible. + # Instead, we pass each column and construct a spreadsheet in the task. + # This way we can have one big table of the values in Terra and we don't have to make the data hard + # to visualize. + + # High-level required info: + String location_code + File barcode_def_tsv + + # Spreadsheet data: + Array[String] cc + Array[String] ISO3 + Array[String] Year + Array[String] Number_Text + Array[String] Sample_Name + Array[String] Raw_Name + Array[String] Barcode_String + Array[String] A1 + Array[String] B1 + Array[String] A2 + Array[String] B2 + Array[String] A3 + Array[String] B3 + Array[String] A4 + Array[String] B4 + Array[String] A5 + Array[String] B5 + Array[String] A6 + Array[String] B6 + Array[String] A7 + Array[String] B7 + Array[String] A8 + Array[String] B8 + Array[String] A9 + Array[String] B9 + Array[String] A10 + Array[String] B10 + Array[String] A11 + Array[String] B11 + Array[String] A12 + Array[String] B12 + Array[String] X + Array[String] N + Array[String] M_P + Array[String] Delta_CT_Threshold + Array[String] Adjusted_Het + Array[String] mccoil_median + + String dir_prefix + String gcs_out_root_dir + + Boolean DEBUG_MODE = false + } + + parameter_meta { + # TODO: FILL THIS IN + } + + #################################### + # _____ _ + # |_ _|_ _ ___| | _____ + # | |/ _` / __| |/ / __| + # | | (_| \__ \ <\__ \ + # |_|\__,_|___/_|\_\___/ + # + #################################### + + # Call our timestamp so we can store outputs without clobbering previous runs: + call Utils.GetCurrentTimestampString as t_001_WdlExecutionStartTimestamp { input: } + + # Create an outdir: + String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/PanelProcessMalariaBarcodesForRh/~{dir_prefix}/" + t_001_WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/PanelProcessMalariaBarcodesForRh/~{dir_prefix}" + + call ProcessBarcodeSpreadsheet { + input: + location_code = location_code, + barcode_def_tsv = barcode_def_tsv, + cc = cc, + ISO3 = ISO3, + Year = Year, + Number_Text = Number_Text, + Sample_Name = Sample_Name, + Raw_Name = Raw_Name, + Barcode_String = Barcode_String, + A1 = A1, + B1 = B1, + A2 = A2, + B2 = B2, + A3 = A3, + B3 = B3, + A4 = A4, + B4 = B4, + A5 = A5, + B5 = B5, + A6 = A6, + B6 = B6, + A7 = A7, + B7 = B7, + A8 = A8, + B8 = B8, + A9 = A9, + B9 = B9, + A10 = A10, + B10 = B10, + A11 = A11, + B11 = B11, + A12 = A12, + B12 = B12, + X = X, + N = N, + M_P = M_P, + Delta_CT_Threshold = Delta_CT_Threshold, + Adjusted_Het = Adjusted_Het, + mccoil_median = mccoil_median + } +} + +task ProcessBarcodeSpreadsheet { + input { + # High-level required info: + String location_code + File barcode_def_tsv + + # Spreadsheet data: + Array[String] cc + Array[String] ISO3 + Array[String] Year + Array[String] Number_Text + Array[String] Sample_Name + Array[String] Raw_Name + Array[String] Barcode_String + Array[String] A1 + Array[String] B1 + Array[String] A2 + Array[String] B2 + Array[String] A3 + Array[String] B3 + Array[String] A4 + Array[String] B4 + Array[String] A5 + Array[String] B5 + Array[String] A6 + Array[String] B6 + Array[String] A7 + Array[String] B7 + Array[String] A8 + Array[String] B8 + Array[String] A9 + Array[String] B9 + Array[String] A10 + Array[String] B10 + Array[String] A11 + Array[String] B11 + Array[String] A12 + Array[String] B12 + Array[String] X + Array[String] N + Array[String] M_P + Array[String] Delta_CT_Threshold + Array[String] Adjusted_Het + Array[String] mccoil_median + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 10 + + # Create a header for the inputs so we can generate a TSV: + Array[String] header = ["cc", "ISO3", "Year", "Number_Text", "Sample_Name", "Raw_Name", "Barcode_String", "A1", "B1", "A2", "B2", "A3", "B3", "A4", "B4", "A5", "B5", "A6", "B6", "A7", "B7", "A8", "B8", "A9", "B9", "A10", "B10", "A11", "B11", "A12", "B12", "X", "N", "M_P", "Delta_CT_Threshold", "Adjusted_Het", "mccoil_median"] + + String input_tsv_path = "~{location_code}.tmp_input.tsv" + + command <<< + set -euxo pipefail + + ## Generate the input TSV: + tmp_input_tsv=~{write_tsv([header, cc, ISO3, Year, Number_Text, Sample_Name, Raw_Name, Barcode_String, A1, B1, A2, B2, A3, B3, A4, B4, A5, B5, A6, B6, A7, B7, A8, B8, A9, B9, A10, B10, A11, B11, A12, B12, X, N, M_P, Delta_CT_Threshold, Adjusted_Het, mccoil_median])} + mv ${tmp_input_tsv} ~{input_tsv_path} + + ## Run the script: + /python_scripts/process_barcode_data.py -b ~{barcode_def_tsv} -s ~{location_code} -f ~{input_tsv_path} + >>> + + output { + File summary_figure_svg = "~{location_code}_summary_figure.svg" + File summary_figure_png = "~{location_code}_summary_figure.png" + File summary_stats = "~{location_code}_summary.csv" + File mono_barcode_stats = "~{location_code}_mono_barcodes.csv" + File poly_barcode_stats = "~{location_code}_poly_barcodes.csv" + + File input_tsv = input_tsv_path + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 2, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-malaria:0.0.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From 4dc4404df4ad400e1a3ba54d05e038987ebfefa5 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 2 Mar 2023 10:21:14 -0500 Subject: [PATCH 129/297] Adding PanelProcessMalariaBarcodesForRh.wdl to dockstore. --- .dockstore.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.dockstore.yml b/.dockstore.yml index 192550393..9a7770ff8 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -151,4 +151,8 @@ workflows: - name: CompareVcfBenchmarks subclass: wdl primaryDescriptorPath: /wdl/CompareVcfBenchmarks.wdl + testParameterFiles: +- name: PanelProcessMalariaBarcodesForRh + subclass: wdl + primaryDescriptorPath: /wdl/PanelProcessMalariaBarcodesForRh.wdl testParameterFiles: \ No newline at end of file From d35ec0027e815d3972054aa474e245e120950aef Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 2 Mar 2023 10:44:57 -0500 Subject: [PATCH 130/297] Adding some tests for python versioning and conda env. --- wdl/PanelProcessMalariaBarcodesForRh.wdl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wdl/PanelProcessMalariaBarcodesForRh.wdl b/wdl/PanelProcessMalariaBarcodesForRh.wdl index 53ce19907..a82212d9e 100644 --- a/wdl/PanelProcessMalariaBarcodesForRh.wdl +++ b/wdl/PanelProcessMalariaBarcodesForRh.wdl @@ -191,6 +191,10 @@ task ProcessBarcodeSpreadsheet { command <<< set -euxo pipefail + which python + source activate lr-malaria + which python + ## Generate the input TSV: tmp_input_tsv=~{write_tsv([header, cc, ISO3, Year, Number_Text, Sample_Name, Raw_Name, Barcode_String, A1, B1, A2, B2, A3, B3, A4, B4, A5, B5, A6, B6, A7, B7, A8, B8, A9, B9, A10, B10, A11, B11, A12, B12, X, N, M_P, Delta_CT_Threshold, Adjusted_Het, mccoil_median])} mv ${tmp_input_tsv} ~{input_tsv_path} From ca3368062c2798258a1a15dd394347b7f23b8b85 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 2 Mar 2023 10:53:22 -0500 Subject: [PATCH 131/297] Fixed conda env sourcing. --- wdl/PanelProcessMalariaBarcodesForRh.wdl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wdl/PanelProcessMalariaBarcodesForRh.wdl b/wdl/PanelProcessMalariaBarcodesForRh.wdl index a82212d9e..ca0683cf2 100644 --- a/wdl/PanelProcessMalariaBarcodesForRh.wdl +++ b/wdl/PanelProcessMalariaBarcodesForRh.wdl @@ -189,11 +189,9 @@ task ProcessBarcodeSpreadsheet { String input_tsv_path = "~{location_code}.tmp_input.tsv" command <<< - set -euxo pipefail - - which python source activate lr-malaria - which python + + set -euxo pipefail ## Generate the input TSV: tmp_input_tsv=~{write_tsv([header, cc, ISO3, Year, Number_Text, Sample_Name, Raw_Name, Barcode_String, A1, B1, A2, B2, A3, B3, A4, B4, A5, B5, A6, B6, A7, B7, A8, B8, A9, B9, A10, B10, A11, B11, A12, B12, X, N, M_P, Delta_CT_Threshold, Adjusted_Het, mccoil_median])} From d4a4b211086f1f61dbe58abe9aad83b226443d80 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 2 Mar 2023 10:59:32 -0500 Subject: [PATCH 132/297] Fixed orientation of input tsv. --- wdl/PanelProcessMalariaBarcodesForRh.wdl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wdl/PanelProcessMalariaBarcodesForRh.wdl b/wdl/PanelProcessMalariaBarcodesForRh.wdl index ca0683cf2..5750fab30 100644 --- a/wdl/PanelProcessMalariaBarcodesForRh.wdl +++ b/wdl/PanelProcessMalariaBarcodesForRh.wdl @@ -194,8 +194,10 @@ task ProcessBarcodeSpreadsheet { set -euxo pipefail ## Generate the input TSV: - tmp_input_tsv=~{write_tsv([header, cc, ISO3, Year, Number_Text, Sample_Name, Raw_Name, Barcode_String, A1, B1, A2, B2, A3, B3, A4, B4, A5, B5, A6, B6, A7, B7, A8, B8, A9, B9, A10, B10, A11, B11, A12, B12, X, N, M_P, Delta_CT_Threshold, Adjusted_Het, mccoil_median])} - mv ${tmp_input_tsv} ~{input_tsv_path} + tmp_header_tsv=~{write_tsv([header])} + tmp_data_tsv=~{write_tsv(transpose([cc, ISO3, Year, Number_Text, Sample_Name, Raw_Name, Barcode_String, A1, B1, A2, B2, A3, B3, A4, B4, A5, B5, A6, B6, A7, B7, A8, B8, A9, B9, A10, B10, A11, B11, A12, B12, X, N, M_P, Delta_CT_Threshold, Adjusted_Het, mccoil_median]))} + + cat ${tmp_header_tsv} ${tmp_data_tsv} > ~{input_tsv_path} ## Run the script: /python_scripts/process_barcode_data.py -b ~{barcode_def_tsv} -s ~{location_code} -f ~{input_tsv_path} From 1dc9be249435fcce406ab9a13b5fb6c1cd773dc1 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 2 Mar 2023 11:09:24 -0500 Subject: [PATCH 133/297] Changing Multi/Poly column name to Terra convention. --- docker/lr-malaria/python/process_barcode_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/lr-malaria/python/process_barcode_data.py b/docker/lr-malaria/python/process_barcode_data.py index ca5e4c17d..98e115222 100755 --- a/docker/lr-malaria/python/process_barcode_data.py +++ b/docker/lr-malaria/python/process_barcode_data.py @@ -249,7 +249,7 @@ class BarcodeStats: } # Set up field names from sheet: - multi_poly_field = "M/P" + multi_poly_field = "M_P" sample_name_field = "Sample_Name" def __init__(self, input_file, ISO3, barcode_file_path, sheet_name=None, adjusted_n=False): From e2ede54f2c275512ee2fd17ebcf997a5e90ef8cb Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 2 Mar 2023 11:31:37 -0500 Subject: [PATCH 134/297] Fixing parsing issue with `ISO3` field. --- docker/lr-malaria/python/process_barcode_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/lr-malaria/python/process_barcode_data.py b/docker/lr-malaria/python/process_barcode_data.py index 98e115222..6a9b6b2f1 100755 --- a/docker/lr-malaria/python/process_barcode_data.py +++ b/docker/lr-malaria/python/process_barcode_data.py @@ -1623,7 +1623,7 @@ def show_stats(ISO3, color): # Do the work: BS = {} - ISO3 = args.sheet + ISO3 = args.sheet.replace(".", ":") # We have to replace '.' with ':' here because of Terra conventions. sheet_name = ISO3.replace(":", "_") BS[ISO3] = BarcodeStats( From bf6a27d8b7deafa6085684cd9fba79f2b99ae653 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 2 Mar 2023 11:41:23 -0500 Subject: [PATCH 135/297] Fixing `:` -> `.` conversion. --- docker/lr-malaria/python/process_barcode_data.py | 12 +++++++----- wdl/PanelProcessMalariaBarcodesForRh.wdl | 13 +++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/docker/lr-malaria/python/process_barcode_data.py b/docker/lr-malaria/python/process_barcode_data.py index 6a9b6b2f1..81c8ea2df 100755 --- a/docker/lr-malaria/python/process_barcode_data.py +++ b/docker/lr-malaria/python/process_barcode_data.py @@ -1593,8 +1593,9 @@ def show_stats(ISO3, color): stdev = np.sqrt(p * (1 - p) / total) # We should save both a CSV and a TSV: - fig.savefig(ISO3 + "_summary_figure.svg") - fig.savefig(ISO3 + "_summary_figure.png") + base_name = ISO3.replace(":", ".") # We have to replace ':' here for ease of use with filenames. + fig.savefig(f"{base_name}_summary_figure.svg") + fig.savefig(f"{base_name}_summary_figure.png") if __name__ == "__main__": @@ -1632,6 +1633,7 @@ def show_stats(ISO3, color): show_stats(ISO3, color="crimson") - BS[ISO3].generate_summary_report("{ISO3}_summary.csv".format(ISO3=ISO3)) - BS[ISO3].mono_barcode_df.to_csv("{ISO3}_mono_barcodes.csv".format(ISO3=ISO3)) - BS[ISO3].poly_df.to_csv("{ISO3}_poly_barcodes.csv".format(ISO3=ISO3)) + base_name = ISO3.replace(":", ".") # We have to replace ':' here for ease of use with filenames. + BS[ISO3].generate_summary_report(f"{base_name}_summary.csv") + BS[ISO3].mono_barcode_df.to_csv(f"{base_name}_mono_barcodes.csv") + BS[ISO3].poly_df.to_csv(f"{base_name}_poly_barcodes.csv") diff --git a/wdl/PanelProcessMalariaBarcodesForRh.wdl b/wdl/PanelProcessMalariaBarcodesForRh.wdl index 5750fab30..c383b8d90 100644 --- a/wdl/PanelProcessMalariaBarcodesForRh.wdl +++ b/wdl/PanelProcessMalariaBarcodesForRh.wdl @@ -186,7 +186,8 @@ task ProcessBarcodeSpreadsheet { # Create a header for the inputs so we can generate a TSV: Array[String] header = ["cc", "ISO3", "Year", "Number_Text", "Sample_Name", "Raw_Name", "Barcode_String", "A1", "B1", "A2", "B2", "A3", "B3", "A4", "B4", "A5", "B5", "A6", "B6", "A7", "B7", "A8", "B8", "A9", "B9", "A10", "B10", "A11", "B11", "A12", "B12", "X", "N", "M_P", "Delta_CT_Threshold", "Adjusted_Het", "mccoil_median"] - String input_tsv_path = "~{location_code}.tmp_input.tsv" + String out_base_name = sub(location_code, ":", ".") + String input_tsv_path = "~{out_base_name}.tmp_input.tsv" command <<< source activate lr-malaria @@ -204,11 +205,11 @@ task ProcessBarcodeSpreadsheet { >>> output { - File summary_figure_svg = "~{location_code}_summary_figure.svg" - File summary_figure_png = "~{location_code}_summary_figure.png" - File summary_stats = "~{location_code}_summary.csv" - File mono_barcode_stats = "~{location_code}_mono_barcodes.csv" - File poly_barcode_stats = "~{location_code}_poly_barcodes.csv" + File summary_figure_svg = "~{out_base_name}_summary_figure.svg" + File summary_figure_png = "~{out_base_name}_summary_figure.png" + File summary_stats = "~{out_base_name}_summary.csv" + File mono_barcode_stats = "~{out_base_name}_mono_barcodes.csv" + File poly_barcode_stats = "~{out_base_name}_poly_barcodes.csv" File input_tsv = input_tsv_path } From 075618b77bda37ccd0a1526de6923139959761ae Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 2 Mar 2023 12:05:07 -0500 Subject: [PATCH 136/297] Adding outputs to the WDL. --- wdl/PanelProcessMalariaBarcodesForRh.wdl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/wdl/PanelProcessMalariaBarcodesForRh.wdl b/wdl/PanelProcessMalariaBarcodesForRh.wdl index c383b8d90..8784a32c1 100644 --- a/wdl/PanelProcessMalariaBarcodesForRh.wdl +++ b/wdl/PanelProcessMalariaBarcodesForRh.wdl @@ -89,7 +89,7 @@ workflow PanelProcessMalariaBarcodesForRh { # Create an outdir: String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/PanelProcessMalariaBarcodesForRh/~{dir_prefix}/" + t_001_WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/PanelProcessMalariaBarcodesForRh/~{dir_prefix}" - call ProcessBarcodeSpreadsheet { + call ProcessBarcodeSpreadsheet as t_002_ProcessBarcodeSpreadsheet { input: location_code = location_code, barcode_def_tsv = barcode_def_tsv, @@ -131,6 +131,16 @@ workflow PanelProcessMalariaBarcodesForRh { Adjusted_Het = Adjusted_Het, mccoil_median = mccoil_median } + + output { + File summary_figure_svg = t_002_ProcessBarcodeSpreadsheet.summary_figure_svg + File summary_figure_png = t_002_ProcessBarcodeSpreadsheet.summary_figure_png + File summary_stats = t_002_ProcessBarcodeSpreadsheet.summary_stats + File mono_barcode_stats = t_002_ProcessBarcodeSpreadsheet.mono_barcode_stats + File poly_barcode_stats = t_002_ProcessBarcodeSpreadsheet.poly_barcode_stats + + File input_tsv =t_002_ProcessBarcodeSpreadsheet.input_tsv + } } task ProcessBarcodeSpreadsheet { From 9d37761b99b590e2282aa88ececc5fbc00e0bd80 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 2 Mar 2023 12:40:47 -0500 Subject: [PATCH 137/297] Fixing outputs in the workflow. --- wdl/PanelProcessMalariaBarcodesForRh.wdl | 43 ++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/wdl/PanelProcessMalariaBarcodesForRh.wdl b/wdl/PanelProcessMalariaBarcodesForRh.wdl index 8784a32c1..574f3c021 100644 --- a/wdl/PanelProcessMalariaBarcodesForRh.wdl +++ b/wdl/PanelProcessMalariaBarcodesForRh.wdl @@ -132,6 +132,41 @@ workflow PanelProcessMalariaBarcodesForRh { mccoil_median = mccoil_median } + ############################################ + # _____ _ _ _ + # | ___(_)_ __ __ _| (_)_______ + # | |_ | | '_ \ / _` | | |_ / _ \ + # | _| | | | | | (_| | | |/ / __/ + # |_| |_|_| |_|\__,_|_|_/___\___| + # + ############################################ + File keyfile = t_002_ProcessBarcodeSpreadsheet.summary_stats + + # Finalize our outputs. We don't have many so let's do them all together: + call FF.FinalizeToDir as t_003_FinalizeOutputs { + input: + outdir = outdir, + files = + [ + t_002_ProcessBarcodeSpreadsheet.summary_figure_svg, + t_002_ProcessBarcodeSpreadsheet.summary_figure_png, + t_002_ProcessBarcodeSpreadsheet.summary_stats, + t_002_ProcessBarcodeSpreadsheet.mono_barcode_stats, + t_002_ProcessBarcodeSpreadsheet.poly_barcode_stats, + t_002_ProcessBarcodeSpreadsheet.input_tsv, + ], + keyfile = keyfile + } + + ############################################ + # ___ _ _ + # / _ \ _ _| |_ _ __ _ _| |_ + # | | | | | | | __| '_ \| | | | __| + # | |_| | |_| | |_| |_) | |_| | |_ + # \___/ \__,_|\__| .__/ \__,_|\__| + # |_| + ############################################ + output { File summary_figure_svg = t_002_ProcessBarcodeSpreadsheet.summary_figure_svg File summary_figure_png = t_002_ProcessBarcodeSpreadsheet.summary_figure_png @@ -139,10 +174,14 @@ workflow PanelProcessMalariaBarcodesForRh { File mono_barcode_stats = t_002_ProcessBarcodeSpreadsheet.mono_barcode_stats File poly_barcode_stats = t_002_ProcessBarcodeSpreadsheet.poly_barcode_stats - File input_tsv =t_002_ProcessBarcodeSpreadsheet.input_tsv + File input_tsv = t_002_ProcessBarcodeSpreadsheet.input_tsv } } +################################################################################ +################################################################################ +################################################################################ + task ProcessBarcodeSpreadsheet { input { # High-level required info: @@ -197,7 +236,7 @@ task ProcessBarcodeSpreadsheet { Array[String] header = ["cc", "ISO3", "Year", "Number_Text", "Sample_Name", "Raw_Name", "Barcode_String", "A1", "B1", "A2", "B2", "A3", "B3", "A4", "B4", "A5", "B5", "A6", "B6", "A7", "B7", "A8", "B8", "A9", "B9", "A10", "B10", "A11", "B11", "A12", "B12", "X", "N", "M_P", "Delta_CT_Threshold", "Adjusted_Het", "mccoil_median"] String out_base_name = sub(location_code, ":", ".") - String input_tsv_path = "~{out_base_name}.tmp_input.tsv" + String input_tsv_path = "~{out_base_name}.reconstructed_input.tsv" command <<< source activate lr-malaria From 4edd017e287fbb52d5d15e91c2d0f80b13a0fd99 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 7 Mar 2023 12:02:59 -0500 Subject: [PATCH 138/297] Fixed bug in hail.wdl, updated docs for Panel wdl. --- wdl/PanelProcessMalariaBarcodesForRh.wdl | 44 +++++++++++++++++++++++- wdl/tasks/Hail.wdl | 2 +- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/wdl/PanelProcessMalariaBarcodesForRh.wdl b/wdl/PanelProcessMalariaBarcodesForRh.wdl index 574f3c021..473f114fd 100644 --- a/wdl/PanelProcessMalariaBarcodesForRh.wdl +++ b/wdl/PanelProcessMalariaBarcodesForRh.wdl @@ -71,7 +71,49 @@ workflow PanelProcessMalariaBarcodesForRh { } parameter_meta { - # TODO: FILL THIS IN + location_code: "Location code of the sample. Should correspond to the ISO3 value." + barcode_def_tsv: "TSV file containing the definition of the SNP barcode sites with the columns: Name, Contig, Position" + + cc: "" + ISO3: "" + Year: "Year this dataset was collected." + Number_Text: "" + Sample_Name: "" + Raw_Name: "" + Barcode_String: "Nucleotide sequence of all barcode SNPs in genomic order." + A1: "Nucleotide at the A1 barcode position." + B1: "Nucleotide at the B1 barcode position." + A2: "Nucleotide at the A2 barcode position." + B2: "Nucleotide at the B2 barcode position." + A3: "Nucleotide at the A3 barcode position." + B3: "Nucleotide at the B3 barcode position." + A4: "Nucleotide at the A4 barcode position." + B4: "Nucleotide at the B4 barcode position." + A5: "Nucleotide at the A5 barcode position." + B5: "Nucleotide at the B5 barcode position." + A6: "Nucleotide at the A6 barcode position." + B6: "Nucleotide at the B6 barcode position." + A7: "Nucleotide at the A7 barcode position." + B7: "Nucleotide at the B7 barcode position." + A8: "Nucleotide at the A8 barcode position." + B8: "Nucleotide at the B8 barcode position." + A9: "Nucleotide at the A9 barcode position." + B9: "Nucleotide at the B9 barcode position." + A10:"Nucleotide at the A10 barcode position." + B10:"Nucleotide at the B10 barcode position." + A11:"Nucleotide at the A11 barcode position." + B11:"Nucleotide at the B11 barcode position." + A12:"Nucleotide at the A12 barcode position." + B12:"Nucleotide at the B12 barcode position." + X: "" + N: "" + M_P: "Mono- / Poly-clonal indicator." + Delta_CT_Threshold: "" + Adjusted_Het: "" + mccoil_median: "" + + dir_prefix: "directory prefix for output files" + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" } #################################### diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index af35e442e..a05e7d393 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -27,7 +27,7 @@ task ConvertToHailMT { set -x export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh chmod +x monitoring_script.sh ./monitoring_script.sh &> resources.log & monitoring_pid=$! From e943ae497f25d5bb8e755dca6235368a288c27d9 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 8 Mar 2023 12:29:05 -0500 Subject: [PATCH 139/297] Adding in images for better documentation in the barcode workspace --- .../barcode_summary.png | Bin 0 -> 70810 bytes .../pipeline_GenEpi_summary.png | Bin 0 -> 97300 bytes .../pipeline_QC_summary.png | Bin 0 -> 163452 bytes .../pipeline_summary.png | Bin 0 -> 485010 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 extra_documentation/lrma_sp_malaria_barcodes/barcode_summary.png create mode 100644 extra_documentation/lrma_sp_malaria_barcodes/pipeline_GenEpi_summary.png create mode 100644 extra_documentation/lrma_sp_malaria_barcodes/pipeline_QC_summary.png create mode 100644 extra_documentation/lrma_sp_malaria_barcodes/pipeline_summary.png diff --git a/extra_documentation/lrma_sp_malaria_barcodes/barcode_summary.png b/extra_documentation/lrma_sp_malaria_barcodes/barcode_summary.png new file mode 100644 index 0000000000000000000000000000000000000000..97a56ccfa07e15e87555583125e91fe6387db324 GIT binary patch literal 70810 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%0DW{J>ckxL4`_-lBmNLV= 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 z z)_9cA?p8qdk=Ff8f9?KK$e_5#R&(}FL}KYwg-~9U{s^mij$9zcuVaRp3%En~>67K( zF;+P9rX!_3uuriW92HE0!p_+MdiCce5;asER?Ll^-v*1RLd@WHzf2{GKYyb*e{;XR zG(~nYuLPfYoI4$g-WaHSEjg!mvVO95pM~F&YlrboS$A+ssV1(E4a%Z~ipXAZwy5yT zOXoJthTP!p>?17~2Xr^}WKS`FRm zl)Nt-_biimbKj3epKO0|HugcwW$iYJba5a4${~d9sW~<%K(8LV@!mt|hH$&sU>w!J zwP7ooE*GZVa68BfbI+DZynReV;VP+{@fJi0&LC`VylcUZvw83$L`e7m*t6JI0jFK1 z$vCJI(&6IF%4C;cXO=1nRQzYl-65LezUJ?*k->Xr6A0qTmh5%sa|~>!2?wg&4#KmNqafIosH96 zgsa!uBi7P3>at~8df%}2kI#1*#I#Scx8Nt1t+C~uhS0s{yUxW8b#+a_!AS`Xb%@m! zXw_o%Syp${RUW>oA%y;0RsQ6K2MAuW)%^3hiV)8`R9uBRfn=*j&-!qTjKJTcmpUzE z;CHv9NaLJeIkUlc8R1Tc|4{QQt}m7DcPEuc#7wq-z;hvK>ZB8DcZWtOM<}~1UnN$0 z=#}*5MU5;jYZ8H;QlG$3a`mYDe&naqEe;v_t?TYg{RW-pS-R2m>aBB%P+5ZW(o@vG zI2q;~@ahGNCU5xrVL1E=N!^QYg*}CCsXg?}I>Yp!YbPF`uf%07F636xO`;X-UeWB+ zFJuPmg~M7-_^PB0Rr674`o0 zly~;Z?XR^3)Dkhx-)l)o4~qoM29L$lNi7Qmyo8Ly;`;h@b~VxGmb{joQ>hE03rhQx z9PVpSBs~#j8d9j-lRm0&TCpMsO60F~$a|>3hMz>6rz^L&o|1rswU-w-DqMc|Bw}+U z2pSoS)rn2O@S@H&e%i>I7%)Rh>PO*b8n)B(^I0EVY_iEpd0BH+<9o!U+oj{KzrUpF z?IyV3Q|TpVfTd=&H?1o~m58n@A#~G7I|iW2M3q}-I6CvZjHA>wj#!&^e|ArVRr^&* zS`Ssrc5mmc<{1<>q178W1vvy|Ek@^X(lI27bO>%*ae;$o6E6{Ih8Hbf(i79P8E;xd zuF_zVR%ZNx@`o~I8|^wx>Mc8@5-XCG zKV^`hC561{hR9YiX$5#*nC#9&tLmInUO(kMr-AOPH8_OBKRwhNgNp(#9UiQIWG{fG zrv0jMkA?UA{Y4Nj#i~5LF&Z!pydGKSTa)^_2-W9#dF=ZDM{r^>4%M)j=p99M5VC{wa^aL3=OBRCp&( zeZX$GfB}6xa3gi_r`&1{+P^?ZT4()#0{&-L0QWd2(#cS?sI&x>v*EKewL4#7>`nO# DH&>2h literal 0 HcmV?d00001 diff --git a/extra_documentation/lrma_sp_malaria_barcodes/pipeline_GenEpi_summary.png b/extra_documentation/lrma_sp_malaria_barcodes/pipeline_GenEpi_summary.png new file mode 100644 index 0000000000000000000000000000000000000000..6153c64afdc1d90f74df0825ef764dec9ad4ea9d GIT binary patch literal 97300 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

GED?opzmW`?)iUx9kkgt5a0SAUwCbseu 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 bIfI69>$iNZv&IHm$D+wdD2l^Gjo$wcS}lgF literal 0 HcmV?d00001 diff --git a/extra_documentation/lrma_sp_malaria_barcodes/pipeline_QC_summary.png b/extra_documentation/lrma_sp_malaria_barcodes/pipeline_QC_summary.png new file mode 100644 index 0000000000000000000000000000000000000000..4b2a2a002a97ac24af3a4c5301501610e4f4d843 GIT binary patch literal 163452 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

F+4&7#H!C64A=$SQrW*_kJ@!vZIq0jh766!H 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

woqtd20~d6aWy+myrJW99MNiR?T0 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%0AAxKw5joY1kLKWo_qoc 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 Date: Sun, 12 Mar 2023 01:27:54 +0000 Subject: [PATCH 140/297] Added in renaming the sample to the new sample name in SRWholeGenome. --- wdl/SRWholeGenome.wdl | 12 ++++++-- wdl/tasks/VariantUtils.wdl | 57 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 23c80102b..7fbb52381 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -227,6 +227,14 @@ workflow SRWholeGenome { use_allele_specific_annotations = true, } + call VARUTIL.RenameSingleSampleVcf as RenameSingleSampleVcf { + input: + vcf = ApplyVqsr.recalibrated_vcf, + vcf_index = ApplyVqsr.recalibrated_vcf_index, + prefix = participant_name + ".vqsr_filtered", + new_sample_name = participant_name + } + # Create a Keyfile for finalization: File keyfile = ApplyVqsr.recalibrated_vcf_index @@ -250,8 +258,8 @@ workflow SRWholeGenome { call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } # Finalize the reclibrated / filtered variants: - call FF.FinalizeToFile as FinalizeHCVqsrVcf { input: outdir = smalldir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } - call FF.FinalizeToFile as FinalizeHCVqsrTbi { input: outdir = smalldir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } + call FF.FinalizeToFile as FinalizeHCVqsrVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeHCVqsrTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf_index } } output { diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 3cccc85bc..8436e78de 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1200,3 +1200,60 @@ task ApplyVqsr { docker: select_first([runtime_attr.docker, default_attr.docker]) } } + +task RenameSingleSampleVcf { + + input { + File vcf + File vcf_index + + String prefix + + String new_sample_name + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size([vcf, vcf_index], "GB")) + + command <<< + set -euo pipefail + + # Get amount of memory to use: + mem_available=$(free -m | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-1000 + let mem_max=${mem_available}-750 + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + RenameSampleInVcf \ + --NEW_SAMPLE_NAME ~{new_sample_name} \ + -V ~{vcf} \ + -O ~{prefix}.vcf.gz + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File new_sample_name_vcf = "{prefix}.vcf.gz" + File new_sample_name_vcf_index = "{prefix}.vcf.gz.tbi" + } +} From fad15eb7fdfa9e895b57ccc3368b06abcfc7f943 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 12 Mar 2023 09:01:25 +0000 Subject: [PATCH 141/297] Fixing typo --- wdl/tasks/VariantUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 8436e78de..f66ea140c 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1227,7 +1227,7 @@ task RenameSingleSampleVcf { gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ RenameSampleInVcf \ --NEW_SAMPLE_NAME ~{new_sample_name} \ - -V ~{vcf} \ + -I ~{vcf} \ -O ~{prefix}.vcf.gz >>> From 1ba674e2db3220c5b9a0d60f2e8979d94ebf89fc Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 12 Mar 2023 13:24:05 +0000 Subject: [PATCH 142/297] Adding indexing to Rename task. --- wdl/tasks/VariantUtils.wdl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index f66ea140c..ab6b52fed 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1229,6 +1229,11 @@ task RenameSingleSampleVcf { --NEW_SAMPLE_NAME ~{new_sample_name} \ -I ~{vcf} \ -O ~{prefix}.vcf.gz + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + IndexFeatureFile \ + -I ~{prefix}.vcf.gz \ + -O ~{prefix}.vcf.gz.tbi >>> ######################### From 5cf9bc7b5adbbb20f34caad8fa55c14d18384f33 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 12 Mar 2023 13:27:49 +0000 Subject: [PATCH 143/297] Updated finalization info. --- wdl/SRWholeGenome.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 7fbb52381..5355df26f 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -236,7 +236,7 @@ workflow SRWholeGenome { } # Create a Keyfile for finalization: - File keyfile = ApplyVqsr.recalibrated_vcf_index + File keyfile = RenameSingleSampleVcf.new_sample_name_vcf_index # Finalize the raw Joint Calls: call FF.FinalizeToFile as FinalizeHCVcf { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.output_vcf } From 79936e0cbca470ed33e4484aecd286c82de0f94a Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 12 Mar 2023 15:04:47 +0000 Subject: [PATCH 144/297] Fixing issue. --- wdl/tasks/VariantUtils.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index ab6b52fed..8a7663583 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1258,7 +1258,7 @@ task RenameSingleSampleVcf { } output { - File new_sample_name_vcf = "{prefix}.vcf.gz" - File new_sample_name_vcf_index = "{prefix}.vcf.gz.tbi" + File new_sample_name_vcf = "~{prefix}.vcf.gz" + File new_sample_name_vcf_index = "~{prefix}.vcf.gz.tbi" } } From 01072f3d47c836e2f0de44da3e772b609eef37d5 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 12 Mar 2023 20:33:50 +0000 Subject: [PATCH 145/297] Fixing sample name in other haplotypecaller files. --- wdl/SRWholeGenome.wdl | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 5355df26f..1df246390 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -227,6 +227,24 @@ workflow SRWholeGenome { use_allele_specific_annotations = true, } + ## Rename our samples so we can use them later: + ## TODO: Move this to the top! + call VARUTIL.RenameSingleSampleVcf as RenameRawHcVcf { + input: + vcf = CallVariantsWithHaplotypeCaller.output_vcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + prefix = participant_name + ".haplotype_caller", + new_sample_name = participant_name + } + + call VARUTIL.RenameSingleSampleVcf as RenameRawHcGvcf { + input: + vcf = CallVariantsWithHaplotypeCaller.output_gvcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_gvcf_index, + prefix = participant_name + ".haplotype_caller", + new_sample_name = participant_name + } + call VARUTIL.RenameSingleSampleVcf as RenameSingleSampleVcf { input: vcf = ApplyVqsr.recalibrated_vcf, @@ -239,10 +257,10 @@ workflow SRWholeGenome { File keyfile = RenameSingleSampleVcf.new_sample_name_vcf_index # Finalize the raw Joint Calls: - call FF.FinalizeToFile as FinalizeHCVcf { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.output_vcf } - call FF.FinalizeToFile as FinalizeHCTbi { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.output_vcf_index } - call FF.FinalizeToFile as FinalizeHCGVcf { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.output_gvcf } - call FF.FinalizeToFile as FinalizeHCGTbi { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.output_gvcf_index } + call FF.FinalizeToFile as FinalizeHCVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcVcf.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeHCTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcVcf.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCGVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcGvcf.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeHCGTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcGvcf.new_sample_name_vcf_index } call FF.FinalizeToFile as FinalizeHCBamOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout } call FF.FinalizeToFile as FinalizeHCBaiOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout_index } From 5547bae73643daff6343cbf573311a1c78c9c777 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 23 Mar 2023 12:37:47 -0400 Subject: [PATCH 146/297] Fixing Barcode demo WDL. --- wdl/ProcessMalariaBarcodesDemo.wdl | 301 +++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 wdl/ProcessMalariaBarcodesDemo.wdl diff --git a/wdl/ProcessMalariaBarcodesDemo.wdl b/wdl/ProcessMalariaBarcodesDemo.wdl new file mode 100644 index 000000000..466da0775 --- /dev/null +++ b/wdl/ProcessMalariaBarcodesDemo.wdl @@ -0,0 +1,301 @@ +version 1.0 + +########################################################################################## +## A workflow that processes P. falciparum SNP panels (read: barcodes) and calculates several +## metrics that are relevant to studying the epidemiology of the disease. +## +## This WDL calls a script written by Wes Wong and based on the following paper: +## https://doi.org/10.1093/pnasnexus/pgac187 +########################################################################################## + +import "tasks/Structs.wdl" +import "tasks/Utils.wdl" as Utils + +workflow ProcessMalariaBarcodesDemo { + input { + + # Unfortunately the easiest way to make this work would be to pass a spreadsheet into the script. + # Because of how Terra is structured, this isn't really possible. + # Instead, we pass each column and construct a spreadsheet in the task. + # This way we can have one big table of the values in Terra and we don't have to make the data hard + # to visualize. + + # High-level required info: + String location_code + File barcode_def_tsv + + # Spreadsheet data: + Array[String] cc + Array[String] ISO3 + Array[String] Year + Array[String] Number_Text + Array[String] Sample_Name + Array[String] Raw_Name + Array[String] Barcode_String + Array[String] A1 + Array[String] B1 + Array[String] A2 + Array[String] B2 + Array[String] A3 + Array[String] B3 + Array[String] A4 + Array[String] B4 + Array[String] A5 + Array[String] B5 + Array[String] A6 + Array[String] B6 + Array[String] A7 + Array[String] B7 + Array[String] A8 + Array[String] B8 + Array[String] A9 + Array[String] B9 + Array[String] A10 + Array[String] B10 + Array[String] A11 + Array[String] B11 + Array[String] A12 + Array[String] B12 + Array[String] X + Array[String] N + Array[String] M_P + Array[String] Delta_CT_Threshold + Array[String] Adjusted_Het + Array[String] mccoil_median + + String dir_prefix + String gcs_out_root_dir + + Boolean DEBUG_MODE = false + } + + parameter_meta { + location_code: "Location code of the sample. Should correspond to the ISO3 value." + barcode_def_tsv: "TSV file containing the definition of the SNP barcode sites with the columns: Name, Contig, Position" + + cc: "" + ISO3: "" + Year: "Year this dataset was collected." + Number_Text: "" + Sample_Name: "" + Raw_Name: "" + Barcode_String: "Nucleotide sequence of all barcode SNPs in genomic order." + A1: "Nucleotide at the A1 barcode position." + B1: "Nucleotide at the B1 barcode position." + A2: "Nucleotide at the A2 barcode position." + B2: "Nucleotide at the B2 barcode position." + A3: "Nucleotide at the A3 barcode position." + B3: "Nucleotide at the B3 barcode position." + A4: "Nucleotide at the A4 barcode position." + B4: "Nucleotide at the B4 barcode position." + A5: "Nucleotide at the A5 barcode position." + B5: "Nucleotide at the B5 barcode position." + A6: "Nucleotide at the A6 barcode position." + B6: "Nucleotide at the B6 barcode position." + A7: "Nucleotide at the A7 barcode position." + B7: "Nucleotide at the B7 barcode position." + A8: "Nucleotide at the A8 barcode position." + B8: "Nucleotide at the B8 barcode position." + A9: "Nucleotide at the A9 barcode position." + B9: "Nucleotide at the B9 barcode position." + A10:"Nucleotide at the A10 barcode position." + B10:"Nucleotide at the B10 barcode position." + A11:"Nucleotide at the A11 barcode position." + B11:"Nucleotide at the B11 barcode position." + A12:"Nucleotide at the A12 barcode position." + B12:"Nucleotide at the B12 barcode position." + X: "" + N: "" + M_P: "Mono- / Poly-clonal indicator." + Delta_CT_Threshold: "" + Adjusted_Het: "" + mccoil_median: "" + + dir_prefix: "directory prefix for output files" + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" + } + + #################################### + # _____ _ + # |_ _|_ _ ___| | _____ + # | |/ _` / __| |/ / __| + # | | (_| \__ \ <\__ \ + # |_|\__,_|___/_|\_\___/ + # + #################################### + + # Call our timestamp so we can store outputs without clobbering previous runs: + call Utils.GetCurrentTimestampString as t_001_WdlExecutionStartTimestamp { input: } + + # Create an outdir: + String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/PanelProcessMalariaBarcodesForRh/~{dir_prefix}/" + t_001_WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/PanelProcessMalariaBarcodesForRh/~{dir_prefix}" + + call ProcessBarcodeSpreadsheet as t_002_ProcessBarcodeSpreadsheet { + input: + location_code = location_code, + barcode_def_tsv = barcode_def_tsv, + cc = cc, + ISO3 = ISO3, + Year = Year, + Number_Text = Number_Text, + Sample_Name = Sample_Name, + Raw_Name = Raw_Name, + Barcode_String = Barcode_String, + A1 = A1, + B1 = B1, + A2 = A2, + B2 = B2, + A3 = A3, + B3 = B3, + A4 = A4, + B4 = B4, + A5 = A5, + B5 = B5, + A6 = A6, + B6 = B6, + A7 = A7, + B7 = B7, + A8 = A8, + B8 = B8, + A9 = A9, + B9 = B9, + A10 = A10, + B10 = B10, + A11 = A11, + B11 = B11, + A12 = A12, + B12 = B12, + X = X, + N = N, + M_P = M_P, + Delta_CT_Threshold = Delta_CT_Threshold, + Adjusted_Het = Adjusted_Het, + mccoil_median = mccoil_median + } + + ############################################ + # ___ _ _ + # / _ \ _ _| |_ _ __ _ _| |_ + # | | | | | | | __| '_ \| | | | __| + # | |_| | |_| | |_| |_) | |_| | |_ + # \___/ \__,_|\__| .__/ \__,_|\__| + # |_| + ############################################ + + output { + File summary_figure_svg = t_002_ProcessBarcodeSpreadsheet.summary_figure_svg + File summary_figure_png = t_002_ProcessBarcodeSpreadsheet.summary_figure_png + File summary_stats = t_002_ProcessBarcodeSpreadsheet.summary_stats + File mono_barcode_stats = t_002_ProcessBarcodeSpreadsheet.mono_barcode_stats + File poly_barcode_stats = t_002_ProcessBarcodeSpreadsheet.poly_barcode_stats + + File input_tsv = t_002_ProcessBarcodeSpreadsheet.input_tsv + } +} + +################################################################################ +################################################################################ +################################################################################ + +task ProcessBarcodeSpreadsheet { + input { + # High-level required info: + String location_code + File barcode_def_tsv + + # Spreadsheet data: + Array[String] cc + Array[String] ISO3 + Array[String] Year + Array[String] Number_Text + Array[String] Sample_Name + Array[String] Raw_Name + Array[String] Barcode_String + Array[String] A1 + Array[String] B1 + Array[String] A2 + Array[String] B2 + Array[String] A3 + Array[String] B3 + Array[String] A4 + Array[String] B4 + Array[String] A5 + Array[String] B5 + Array[String] A6 + Array[String] B6 + Array[String] A7 + Array[String] B7 + Array[String] A8 + Array[String] B8 + Array[String] A9 + Array[String] B9 + Array[String] A10 + Array[String] B10 + Array[String] A11 + Array[String] B11 + Array[String] A12 + Array[String] B12 + Array[String] X + Array[String] N + Array[String] M_P + Array[String] Delta_CT_Threshold + Array[String] Adjusted_Het + Array[String] mccoil_median + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 10 + + # Create a header for the inputs so we can generate a TSV: + Array[String] header = ["cc", "ISO3", "Year", "Number_Text", "Sample_Name", "Raw_Name", "Barcode_String", "A1", "B1", "A2", "B2", "A3", "B3", "A4", "B4", "A5", "B5", "A6", "B6", "A7", "B7", "A8", "B8", "A9", "B9", "A10", "B10", "A11", "B11", "A12", "B12", "X", "N", "M_P", "Delta_CT_Threshold", "Adjusted_Het", "mccoil_median"] + + String out_base_name = sub(location_code, ":", ".") + String input_tsv_path = "~{out_base_name}.reconstructed_input.tsv" + + command <<< + source activate lr-malaria + + set -euxo pipefail + + ## Generate the input TSV: + tmp_header_tsv=~{write_tsv([header])} + tmp_data_tsv=~{write_tsv(transpose([cc, ISO3, Year, Number_Text, Sample_Name, Raw_Name, Barcode_String, A1, B1, A2, B2, A3, B3, A4, B4, A5, B5, A6, B6, A7, B7, A8, B8, A9, B9, A10, B10, A11, B11, A12, B12, X, N, M_P, Delta_CT_Threshold, Adjusted_Het, mccoil_median]))} + + cat ${tmp_header_tsv} ${tmp_data_tsv} > ~{input_tsv_path} + + ## Run the script: + /python_scripts/process_barcode_data.py -b ~{barcode_def_tsv} -s ~{location_code} -f ~{input_tsv_path} + >>> + + output { + File summary_figure_svg = "~{out_base_name}_summary_figure.svg" + File summary_figure_png = "~{out_base_name}_summary_figure.png" + File summary_stats = "~{out_base_name}_summary.csv" + File mono_barcode_stats = "~{out_base_name}_mono_barcodes.csv" + File poly_barcode_stats = "~{out_base_name}_poly_barcodes.csv" + + File input_tsv = input_tsv_path + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 2, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-malaria:0.0.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From 58e9d83103d30d361245d5f79f738236b2e15fda Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 23 Mar 2023 12:53:48 -0400 Subject: [PATCH 147/297] Adding ProcessMalariaBarcodesDemo to dockstore. --- .dockstore.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.dockstore.yml b/.dockstore.yml index 9a7770ff8..71c94b465 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -155,4 +155,8 @@ workflows: - name: PanelProcessMalariaBarcodesForRh subclass: wdl primaryDescriptorPath: /wdl/PanelProcessMalariaBarcodesForRh.wdl + testParameterFiles: +- name: ProcessMalariaBarcodesDemo + subclass: wdl + primaryDescriptorPath: /wdl/ProcessMalariaBarcodesDemo.wdl testParameterFiles: \ No newline at end of file From bd34fe6750dceae24405ebbdeddbc7e4b3c83ad0 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 23 Mar 2023 13:05:57 -0400 Subject: [PATCH 148/297] Removed finalization inputs. --- wdl/ProcessMalariaBarcodesDemo.wdl | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/wdl/ProcessMalariaBarcodesDemo.wdl b/wdl/ProcessMalariaBarcodesDemo.wdl index 466da0775..63b5c1253 100644 --- a/wdl/ProcessMalariaBarcodesDemo.wdl +++ b/wdl/ProcessMalariaBarcodesDemo.wdl @@ -63,9 +63,6 @@ workflow ProcessMalariaBarcodesDemo { Array[String] Adjusted_Het Array[String] mccoil_median - String dir_prefix - String gcs_out_root_dir - Boolean DEBUG_MODE = false } @@ -73,8 +70,8 @@ workflow ProcessMalariaBarcodesDemo { location_code: "Location code of the sample. Should correspond to the ISO3 value." barcode_def_tsv: "TSV file containing the definition of the SNP barcode sites with the columns: Name, Contig, Position" - cc: "" - ISO3: "" + cc: "Country Code" + ISO3: "ISO3 formatted location code." Year: "Year this dataset was collected." Number_Text: "" Sample_Name: "" @@ -110,9 +107,6 @@ workflow ProcessMalariaBarcodesDemo { Delta_CT_Threshold: "" Adjusted_Het: "" mccoil_median: "" - - dir_prefix: "directory prefix for output files" - gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" } #################################### @@ -127,9 +121,6 @@ workflow ProcessMalariaBarcodesDemo { # Call our timestamp so we can store outputs without clobbering previous runs: call Utils.GetCurrentTimestampString as t_001_WdlExecutionStartTimestamp { input: } - # Create an outdir: - String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/PanelProcessMalariaBarcodesForRh/~{dir_prefix}/" + t_001_WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/PanelProcessMalariaBarcodesForRh/~{dir_prefix}" - call ProcessBarcodeSpreadsheet as t_002_ProcessBarcodeSpreadsheet { input: location_code = location_code, From 310a11dc877e9826f620a0616621d6d46954b03a Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 11 May 2023 10:05:01 -0400 Subject: [PATCH 149/297] Added ability to use sites-only VCFs as truth.= --- wdl/BenchmarkVCFs.wdl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index d654b66b4..2057abcd8 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -39,6 +39,7 @@ workflow Benchmark { Int? preemptible String gatkTag="4.0.11.0" Boolean requireMatchingGenotypes = true + Boolean truthIsSitesOnlyVcf = false File? gatkJarForAnnotation Array[String]? annotationNames Boolean enableRefOverlap = false @@ -70,6 +71,7 @@ workflow Benchmark { variantSelectorLabels: {description: "labels by which to identify variant selectors (must be same length as jexlVariantSelectors)"} doIndelLengthStratification: {description: "whether or not to perform stratification by indel length"} requireMatchingGenotypes: {description: "whether to require genotypes to match in order to be a true positive"} + truthIsSitesOnlyVcf: {description: "whether the truth VCF is a sites-only VCF file without any sample information"} gatkTag: {description: "version of gatk docker to use. Defaults to 4.0.11.0"} analysisRegion: {description: "if provided (gatk format, single interval e.g., 'chr20', or 'chr20:1-10') all the analysis will be performed only within the region."} passingOnly: {description:"Have vcfEval only consider the passing variants"} @@ -258,6 +260,7 @@ workflow Benchmark { threads = threadsVcfEval, preemptible = preemptible, requireMatchingGenotypes = requireMatchingGenotypes, + truthIsSitesOnlyVcf = truthIsSitesOnlyVcf, passingOnly = passingOnly, vcfScoreField = vcfScoreField, enableRefOverlap = enableRefOverlap @@ -561,6 +564,7 @@ task VcfEval { Int? threads Boolean requireMatchingGenotypes + Boolean truthIsSitesOnlyVcf Boolean enableRefOverlap = false } String memDefault="16 GB" @@ -577,6 +581,7 @@ task VcfEval { ~{false="--all-records" true="" passingOnly} \ ~{"--vcf-score-field=" + vcfScoreField} \ ~{false="--squash-ploidy" true="" requireMatchingGenotypes} \ + ~{false="--sample ALT" true="" truthIsSitesOnlyVcf} \ ~{true="--ref-overlap" false="" enableRefOverlap} \ -b ~{truthVCF} -c ~{evalVCF} \ -e ~{confidenceBed} ~{"--bed-regions " + stratBed} \ From dbed29dfa5ddfc872c72f009880d62d5628e0180 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 11 May 2023 10:47:43 -0400 Subject: [PATCH 150/297] Fixing typo. --- wdl/BenchmarkVCFs.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index 2057abcd8..155c30078 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -581,7 +581,7 @@ task VcfEval { ~{false="--all-records" true="" passingOnly} \ ~{"--vcf-score-field=" + vcfScoreField} \ ~{false="--squash-ploidy" true="" requireMatchingGenotypes} \ - ~{false="--sample ALT" true="" truthIsSitesOnlyVcf} \ + ~{false="" true="--sample ALT" truthIsSitesOnlyVcf} \ ~{true="--ref-overlap" false="" enableRefOverlap} \ -b ~{truthVCF} -c ~{evalVCF} \ -e ~{confidenceBed} ~{"--bed-regions " + stratBed} \ From bc5aa50948e3041d3d873a635214b40f5524c95b Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 11 May 2023 10:58:08 -0400 Subject: [PATCH 151/297] Fixing args with `ALT` sample mode. --- wdl/BenchmarkVCFs.wdl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index 155c30078..d2dd5f4cb 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -585,7 +585,8 @@ task VcfEval { ~{true="--ref-overlap" false="" enableRefOverlap} \ -b ~{truthVCF} -c ~{evalVCF} \ -e ~{confidenceBed} ~{"--bed-regions " + stratBed} \ - --output-mode combine --decompose -t rtg_ref \ + ~{false="--output-mode combine" true="" truthIsSitesOnlyVcf} \ + --decompose -t rtg_ref \ ~{"--threads "+threads} -o output_dir for f in output_dir/*; do From ece4d0d1bf7392445d3946cfba584e456eeda989 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 11 May 2023 16:58:30 -0400 Subject: [PATCH 152/297] Fixing a bug in final stats counting --- wdl/BenchmarkVCFs.wdl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index d2dd5f4cb..786601738 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -909,11 +909,11 @@ task CountUNKVcfEval { command <<< set -xeuo pipefail - gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V ~{vcf} -O selected.unk.snp.vcf.gz -select "(CALL == 'OUT')" --select-type-to-include SNP - gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V ~{vcf} -O selected.unk.indel.vcf.gz -select "(CALL == 'OUT')" --select-type-to-include INDEL + gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V ~{vcf} -O selected.unk.snp.vcf -select "(CALL == 'OUT')" --select-type-to-include SNP + gatk --java-options "-Xmx~{memoryJava}G" SelectVariants -V ~{vcf} -O selected.unk.indel.vcf -select "(CALL == 'OUT')" --select-type-to-include INDEL - UNK_SNP="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.unk.snp.vcf.gz | tail -1)" - UNK_INDEL="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.unk.indel.vcf.gz | tail -1)" + UNK_SNP="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.unk.snp.vcf | tail -1)" + UNK_INDEL="$(gatk --java-options "-Xmx~{memoryJava}G" CountVariants -V selected.unk.indel.vcf | tail -1)" echo "$UNK_SNP" > unk_snp.txt echo "$UNK_INDEL" > unk_indel.txt From 4c46d0753a21219c4afb54305a94f3416241540e Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 6 Jun 2023 22:44:08 -0400 Subject: [PATCH 153/297] Added step to actually remove filtered variants. --- wdl/SRWholeGenome.wdl | 11 +++++-- wdl/tasks/VariantUtils.wdl | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 1df246390..70af69558 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -227,6 +227,13 @@ workflow SRWholeGenome { use_allele_specific_annotations = true, } + call VARUTIL.SelectVariants as RemoveFilteredVariants { + input: + vcf = ApplyVqsr.recalibrated_vcf, + vcf_index = ApplyVqsr.recalibrated_vcf_index, + prefix = participant_name + ".vqsr_filtered" + } + ## Rename our samples so we can use them later: ## TODO: Move this to the top! call VARUTIL.RenameSingleSampleVcf as RenameRawHcVcf { @@ -247,8 +254,8 @@ workflow SRWholeGenome { call VARUTIL.RenameSingleSampleVcf as RenameSingleSampleVcf { input: - vcf = ApplyVqsr.recalibrated_vcf, - vcf_index = ApplyVqsr.recalibrated_vcf_index, + vcf = RemoveFilteredVariants.vcf_out, + vcf_index = RemoveFilteredVariants.vcf_out_index, prefix = participant_name + ".vqsr_filtered", new_sample_name = participant_name } diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 8a7663583..bfb34c412 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1201,6 +1201,71 @@ task ApplyVqsr { } } +task SelectVariants { + + input { + File vcf + File vcf_index + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 10 + ceil(size([vcf, vcf_index], "GB")) + + command <<< + set -euxo pipefail + + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + + # Get amount of memory to use: + mem_available=$(free -m | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-2000 + let mem_max=${mem_available}-500 + + gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ + SelectVariants \ + --exclude-filtered \ + -V ~{vcf} \ + -O ~{prefix}.vcf.gz + + kill $monitoring_pid + >>> + + output { + File vcf_out = "~{prefix}.vcf.gz" + File vcf_out_index = "~{prefix}.vcf.gz.tbi" + + File monitoring_log = "resources.log" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 7, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + task RenameSingleSampleVcf { input { From faf6f87a4f1f79c23e5f465d67bbe34dbea86bc2 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 6 Jun 2023 22:56:42 -0400 Subject: [PATCH 154/297] Fixed VQSR filtered output --- wdl/SRWholeGenome.wdl | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 70af69558..437d8070e 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -253,6 +253,14 @@ workflow SRWholeGenome { } call VARUTIL.RenameSingleSampleVcf as RenameSingleSampleVcf { + input: + vcf = ApplyVqsr.recalibrated_vcf, + vcf_index = ApplyVqsr.recalibrated_vcf_index, + prefix = participant_name + ".vqsr", + new_sample_name = participant_name + } + + call VARUTIL.RenameSingleSampleVcf as RenameSingleSampleVcfFiltered { input: vcf = RemoveFilteredVariants.vcf_out, vcf_index = RemoveFilteredVariants.vcf_out_index, @@ -283,8 +291,10 @@ workflow SRWholeGenome { call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } # Finalize the reclibrated / filtered variants: - call FF.FinalizeToFile as FinalizeHCVqsrVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf } - call FF.FinalizeToFile as FinalizeHCVqsrTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCVqsrVcf { input: outdir = smalldir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } + call FF.FinalizeToFile as FinalizeHCVqsrTbi { input: outdir = smalldir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } + call FF.FinalizeToFile as FinalizeHCVqsrFilteredVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeHCVqsrFilteredTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf_index } } output { @@ -323,8 +333,10 @@ workflow SRWholeGenome { File? hc_baiout = FinalizeHCBaiOut.gcs_path File? hc_raw_vcf = FinalizeHCVcf.gcs_path File? hc_raw_tbi = FinalizeHCTbi.gcs_path - File? hc_vqsr_vcf = FinalizeHCVqsrVcf.gcs_path - File? hc_vqsr_tbi = FinalizeHCVqsrTbi.gcs_path + File? hc_vqsr_vcf = FinalizeHCVqsrFilteredVcf.gcs_path + File? hc_vqsr_tbi = FinalizeHCVqsrFilteredTbi.gcs_path + File? hc_vqsr_raw_vcf = FinalizeHCVqsrVcf.gcs_path + File? hc_vqsr_raw_tbi = FinalizeHCVqsrTbi.gcs_path File? vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path File? vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path From 51a38ce7cb1c2a33e3b0d80e7f4447a30235816a Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 7 Jun 2023 00:18:17 -0400 Subject: [PATCH 155/297] Fixed typo in finalization. --- wdl/SRWholeGenome.wdl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 437d8070e..9d8d1c41a 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -291,10 +291,10 @@ workflow SRWholeGenome { call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } # Finalize the reclibrated / filtered variants: - call FF.FinalizeToFile as FinalizeHCVqsrVcf { input: outdir = smalldir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } - call FF.FinalizeToFile as FinalizeHCVqsrTbi { input: outdir = smalldir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } - call FF.FinalizeToFile as FinalizeHCVqsrFilteredVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf } - call FF.FinalizeToFile as FinalizeHCVqsrFilteredTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCVqsrVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeHCVqsrTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCVqsrFilteredVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcfFiltered.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeHCVqsrFilteredTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcfFiltered.new_sample_name_vcf_index } } output { From 2eebea90bf40535d0d267c8549166056068404f8 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 7 Jun 2023 13:33:51 -0400 Subject: [PATCH 156/297] Fixed a name conflict. --- wdl/SRWholeGenome.wdl | 5 +++-- wdl/tasks/VariantUtils.wdl | 14 +++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 9d8d1c41a..4b5562bdc 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -240,7 +240,7 @@ workflow SRWholeGenome { input: vcf = CallVariantsWithHaplotypeCaller.output_vcf, vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, - prefix = participant_name + ".haplotype_caller", + prefix = participant_name + ".haplotype_caller.renamed", new_sample_name = participant_name } @@ -248,7 +248,8 @@ workflow SRWholeGenome { input: vcf = CallVariantsWithHaplotypeCaller.output_gvcf, vcf_index = CallVariantsWithHaplotypeCaller.output_gvcf_index, - prefix = participant_name + ".haplotype_caller", + prefix = participant_name + ".haplotype_caller.renamed", + is_gvcf = true, new_sample_name = participant_name } diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index bfb34c412..0c1978481 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1276,11 +1276,15 @@ task RenameSingleSampleVcf { String new_sample_name + Boolean is_gvcf = false + RuntimeAttr? runtime_attr_override } Int disk_size = 1 + 4*ceil(size([vcf, vcf_index], "GB")) + String suffix = if is_gvcf then "g.vcf.gz" else "vcf.gz" + command <<< set -euo pipefail @@ -1293,12 +1297,12 @@ task RenameSingleSampleVcf { RenameSampleInVcf \ --NEW_SAMPLE_NAME ~{new_sample_name} \ -I ~{vcf} \ - -O ~{prefix}.vcf.gz + -O ~{prefix}.~{suffix} gatk --java-options "-Xms${mem_start}m -Xmx${mem_max}m" \ IndexFeatureFile \ - -I ~{prefix}.vcf.gz \ - -O ~{prefix}.vcf.gz.tbi + -I ~{prefix}.~{suffix} \ + -O ~{prefix}.~{suffix}.tbi >>> ######################### @@ -1323,7 +1327,7 @@ task RenameSingleSampleVcf { } output { - File new_sample_name_vcf = "~{prefix}.vcf.gz" - File new_sample_name_vcf_index = "~{prefix}.vcf.gz.tbi" + File new_sample_name_vcf = "~{prefix}.~{suffix}" + File new_sample_name_vcf_index = "~{prefix}.~{suffix}.tbi" } } From 5ed14d89c9c49fd95013fed5270171de3d53e1a3 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sat, 1 Jul 2023 12:41:14 -0400 Subject: [PATCH 157/297] Removing allele-specific annotations. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 6 +++--- wdl/SRWholeGenome.wdl | 6 +++--- wdl/tasks/HaplotypeCaller.wdl | 5 ++++- wdl/tasks/SRJointGenotyping.wdl | 5 ++++- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 51e0e426c..ba5438a4e 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -106,7 +106,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { is_training = [true], is_truth = [true], prior = [15], - use_allele_specific_annotations = true, + use_allele_specific_annotations = false, max_gaussians = 8, } @@ -124,7 +124,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { is_training = [true], is_truth = [true], prior = [15], - use_allele_specific_annotations = true, + use_allele_specific_annotations = false, max_gaussians = 8, } @@ -145,7 +145,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { indels_tranches = TrainVQSROnHCIndelVariants.tranches, indel_filter_level = indel_filter_level, - use_allele_specific_annotations = true, + use_allele_specific_annotations = false, } # Now we need to annotate our variants by region: diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 4b5562bdc..8311bfc4c 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -185,7 +185,7 @@ workflow SRWholeGenome { is_training = [true], is_truth = [true], prior = [15], - use_allele_specific_annotations = true, + use_allele_specific_annotations = false, max_gaussians = 8, } @@ -203,7 +203,7 @@ workflow SRWholeGenome { is_training = [true], is_truth = [true], prior = [15], - use_allele_specific_annotations = true, + use_allele_specific_annotations = false, max_gaussians = 8, } @@ -224,7 +224,7 @@ workflow SRWholeGenome { indels_tranches = TrainVQSROnHCIndelVariants.tranches, indel_filter_level = indel_filter_level, - use_allele_specific_annotations = true, + use_allele_specific_annotations = false, } call VARUTIL.SelectVariants as RemoveFilteredVariants { diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index 3f9e980ad..878a6553e 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -199,10 +199,13 @@ task HaplotypeCaller_GATK4_VCF { --annotate-with-num-discovered-alleles \ -GQB 10 -GQB 20 -GQB 30 -GQB 40 -GQB 50 -GQB 60 -GQB 70 -GQB 80 -GQB 90 \ ~{false="--disable-spanning-event-genotyping" true="" use_spanning_event_genotyping} \ - -G StandardAnnotation -G StandardHCAnnotation ~{true="-G AS_StandardAnnotation" false="" make_gvcf} \ + -G StandardAnnotation -G StandardHCAnnotation \ ~{true="-ERC GVCF" false="" make_gvcf} \ ~{bamout_arg} + # Removed for now: + # ~{true="-G AS_StandardAnnotation" false="" make_gvcf} + # Cromwell doesn't like optional task outputs, so we have to touch this file. touch ~{prefix}.bamout.bam diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 33d8fef52..ab296cf4e 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -256,13 +256,16 @@ task GenotypeGVCFs { -R ~{ref_fasta} \ -O ~{prefix}.vcf.gz \ -D ~{dbsnp_vcf} \ - -G StandardAnnotation -G AS_StandardAnnotation \ + -G StandardAnnotation \ --only-output-calls-starting-in-intervals \ -V ${INPUT_FILE} \ -L ~{interval_list} \ ~{true='--keep-combined-raw-annotations' false='' keep_combined_raw_annotations} \ --merge-input-intervals + # Removed for now: + # -G AS_StandardAnnotation + kill $monitoring_pid >>> From 5f5f117349ef981d64191459c247dff528bbd99c Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 2 Jul 2023 15:02:12 -0400 Subject: [PATCH 158/297] Exposed more recalibration inputs. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 68 +++++++++++++++++++------- wdl/SRWholeGenome.wdl | 68 +++++++++++++++++++------- 2 files changed, 100 insertions(+), 36 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index ba5438a4e..3f9cb5730 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -21,10 +21,28 @@ workflow SRJointCallGVCFsWithGenomicsDB { Array[String] snp_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] Array[Float] snp_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.8, 99.6, 99.5, 99.4, 99.3, 99.0, 98.0, 97.0, 90.0 ] + Array[File] snp_known_reference_variants + Array[File] snp_known_reference_variants_index + Array[File] snp_known_reference_variants_identifier + Array[Boolean] snp_is_known + Array[Boolean] snp_is_training + Array[Boolean] snp_is_truth + Array[Int] snp_prior + Int snp_max_gaussians = 8 + Float indel_filter_level = 99.0 Array[String] indel_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] + Array[File] indel_known_reference_variants + Array[File] indel_known_reference_variants_index + Array[File] indel_known_reference_variants_identifier + Array[Boolean] indel_is_known + Array[Boolean] indel_is_training + Array[Boolean] indel_is_truth + Array[Int] indel_prior + Int indel_max_gaussians = 8 + Array[File]? annotation_bed_files Array[File]? annotation_bed_file_indexes Array[String]? annotation_bed_file_annotation_names @@ -97,17 +115,24 @@ workflow SRJointCallGVCFsWithGenomicsDB { vcf = MakeSitesOnlyGVCF.sites_only_vcf, vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, prefix = prefix + ".indels", - recalibration_tranche_values = snp_recalibration_tranche_values, - recalibration_annotation_values = snp_recalibration_annotation_values, - known_reference_variants = [ref_map["known_sites_vcf"]], - known_reference_variants_index = [ref_map["known_sites_index"]], - known_reference_variants_identifier = ["pfcrosses"], - is_known = [true], - is_training = [true], - is_truth = [true], - prior = [15], + recalibration_tranche_values = indel_recalibration_tranche_values, + recalibration_annotation_values = indel_recalibration_annotation_values, +# known_reference_variants = [ref_map["known_sites_vcf"]], +# known_reference_variants_index = [ref_map["known_sites_index"]], +# known_reference_variants_identifier = ["pfcrosses"], +# is_known = [true], +# is_training = [true], +# is_truth = [true], +# prior = [15], + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_known = indel_is_known, + is_training = indel_is_training, + is_truth = indel_is_truth, + prior = indel_prior, use_allele_specific_annotations = false, - max_gaussians = 8, + max_gaussians = indel_max_gaussians, } call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { @@ -117,15 +142,22 @@ workflow SRJointCallGVCFsWithGenomicsDB { prefix = prefix + ".snps", recalibration_tranche_values = snp_recalibration_tranche_values, recalibration_annotation_values = snp_recalibration_annotation_values, - known_reference_variants = [ref_map["known_sites_vcf"]], - known_reference_variants_index = [ref_map["known_sites_index"]], - known_reference_variants_identifier = ["pfcrosses"], - is_known = [true], - is_training = [true], - is_truth = [true], - prior = [15], +# known_reference_variants = [ref_map["known_sites_vcf"]], +# known_reference_variants_index = [ref_map["known_sites_index"]], +# known_reference_variants_identifier = ["pfcrosses"], +# is_known = [true], +# is_training = [true], +# is_truth = [true], +# prior = [15], + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_known = snp_is_known, + is_training = snp_is_training, + is_truth = snp_is_truth, + prior = snp_prior, use_allele_specific_annotations = false, - max_gaussians = 8, + max_gaussians = snp_max_gaussians, } call VARUTIL.ApplyVqsr as ApplyVqsr { diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 8311bfc4c..b766b1f22 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -40,10 +40,28 @@ workflow SRWholeGenome { Array[String] snp_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] Array[Float] snp_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.8, 99.6, 99.5, 99.4, 99.3, 99.0, 98.0, 97.0, 90.0 ] + Array[File] snp_known_reference_variants + Array[File] snp_known_reference_variants_index + Array[File] snp_known_reference_variants_identifier + Array[Boolean] snp_is_known + Array[Boolean] snp_is_training + Array[Boolean] snp_is_truth + Array[Int] snp_prior + Int snp_max_gaussians = 8 + Float indel_filter_level = 99.0 Array[String] indel_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] + Array[File] indel_known_reference_variants + Array[File] indel_known_reference_variants_index + Array[File] indel_known_reference_variants_identifier + Array[Boolean] indel_is_known + Array[Boolean] indel_is_training + Array[Boolean] indel_is_truth + Array[Int] indel_prior + Int indel_max_gaussians = 8 + File? bed_to_compute_coverage Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! @@ -176,17 +194,24 @@ workflow SRWholeGenome { vcf = CallVariantsWithHaplotypeCaller.output_vcf, vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, prefix = participant_name + ".indels", - recalibration_tranche_values = snp_recalibration_tranche_values, - recalibration_annotation_values = snp_recalibration_annotation_values, - known_reference_variants = [ref_map["known_sites_vcf"]], - known_reference_variants_index = [ref_map["known_sites_index"]], - known_reference_variants_identifier = ["pfcrosses"], - is_known = [true], - is_training = [true], - is_truth = [true], - prior = [15], + recalibration_tranche_values = indel_recalibration_tranche_values, + recalibration_annotation_values = indel_recalibration_annotation_values, +# known_reference_variants = [ref_map["known_sites_vcf"]], +# known_reference_variants_index = [ref_map["known_sites_index"]], +# known_reference_variants_identifier = ["pfcrosses"], +# is_known = [true], +# is_training = [true], +# is_truth = [true], +# prior = [15], + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_known = indel_is_known, + is_training = indel_is_training, + is_truth = indel_is_truth, + prior = indel_prior, use_allele_specific_annotations = false, - max_gaussians = 8, + max_gaussians = indel_max_gaussians, } call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { @@ -196,15 +221,22 @@ workflow SRWholeGenome { prefix = participant_name + ".snps", recalibration_tranche_values = snp_recalibration_tranche_values, recalibration_annotation_values = snp_recalibration_annotation_values, - known_reference_variants = [ref_map["known_sites_vcf"]], - known_reference_variants_index = [ref_map["known_sites_index"]], - known_reference_variants_identifier = ["pfcrosses"], - is_known = [true], - is_training = [true], - is_truth = [true], - prior = [15], +# known_reference_variants = [ref_map["known_sites_vcf"]], +# known_reference_variants_index = [ref_map["known_sites_index"]], +# known_reference_variants_identifier = ["pfcrosses"], +# is_known = [true], +# is_training = [true], +# is_truth = [true], +# prior = [15], + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_known = snp_is_known, + is_training = snp_is_training, + is_truth = snp_is_truth, + prior = snp_prior, use_allele_specific_annotations = false, - max_gaussians = 8, + max_gaussians = snp_max_gaussians, } call VARUTIL.ApplyVqsr as ApplyVqsr { From 2687c962adffae3ac2347e0b7bdd85ceb520402d Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 2 Jul 2023 15:17:45 -0400 Subject: [PATCH 159/297] Fixing type issue with VQSR priors. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 4 ++-- wdl/SRWholeGenome.wdl | 4 ++-- wdl/tasks/VariantUtils.wdl | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 3f9cb5730..e2c1d6a78 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -27,7 +27,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { Array[Boolean] snp_is_known Array[Boolean] snp_is_training Array[Boolean] snp_is_truth - Array[Int] snp_prior + Array[Float] snp_prior Int snp_max_gaussians = 8 Float indel_filter_level = 99.0 @@ -40,7 +40,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { Array[Boolean] indel_is_known Array[Boolean] indel_is_training Array[Boolean] indel_is_truth - Array[Int] indel_prior + Array[Float] indel_prior Int indel_max_gaussians = 8 Array[File]? annotation_bed_files diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index b766b1f22..27eb9dc1a 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -46,7 +46,7 @@ workflow SRWholeGenome { Array[Boolean] snp_is_known Array[Boolean] snp_is_training Array[Boolean] snp_is_truth - Array[Int] snp_prior + Array[Float] snp_prior Int snp_max_gaussians = 8 Float indel_filter_level = 99.0 @@ -59,7 +59,7 @@ workflow SRWholeGenome { Array[Boolean] indel_is_known Array[Boolean] indel_is_training Array[Boolean] indel_is_truth - Array[Int] indel_prior + Array[Float] indel_prior Int indel_max_gaussians = 8 File? bed_to_compute_coverage diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 0c1978481..fbbd91422 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -859,7 +859,7 @@ task IndelsVariantRecalibrator { Array[Boolean] is_known Array[Boolean] is_training Array[Boolean] is_truth - Array[Int] prior + Array[Float] prior Boolean use_allele_specific_annotations Int max_gaussians = 4 @@ -989,7 +989,7 @@ task SNPsVariantRecalibratorCreateModel { Array[Boolean] is_known Array[Boolean] is_training Array[Boolean] is_truth - Array[Int] prior + Array[Float] prior Int? downsampleFactor From 3722d4bdaa1a16e8ae1b47bf30b92fa608b91e7c Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 3 Jul 2023 09:29:58 -0400 Subject: [PATCH 160/297] Fixing long read joint calling wdl. --- wdl/LRJointCallGVCFsWithGenomicsDB.wdl | 74 ++++++++++++++++++-------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl index 545238a83..bfc118541 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl @@ -22,10 +22,28 @@ workflow LRJointCallGVCFsWithGenomicsDB { Array[String] snp_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] Array[Float] snp_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.8, 99.6, 99.5, 99.4, 99.3, 99.0, 98.0, 97.0, 90.0 ] + Array[File] snp_known_reference_variants + Array[File] snp_known_reference_variants_index + Array[File] snp_known_reference_variants_identifier + Array[Boolean] snp_is_known + Array[Boolean] snp_is_training + Array[Boolean] snp_is_truth + Array[Float] snp_prior + Int snp_max_gaussians = 8 + Float indel_filter_level = 99.0 Array[String] indel_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] + Array[File] indel_known_reference_variants + Array[File] indel_known_reference_variants_index + Array[File] indel_known_reference_variants_identifier + Array[Boolean] indel_is_known + Array[Boolean] indel_is_training + Array[Boolean] indel_is_truth + Array[Float] indel_prior + Int indel_max_gaussians = 8 + Array[File]? annotation_bed_files Array[File]? annotation_bed_file_indexes Array[String]? annotation_bed_file_annotation_names @@ -102,17 +120,24 @@ workflow LRJointCallGVCFsWithGenomicsDB { vcf = MakeSitesOnlyGVCF.sites_only_vcf, vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, prefix = prefix + ".indels", - recalibration_tranche_values = snp_recalibration_tranche_values, - recalibration_annotation_values = snp_recalibration_annotation_values, - known_reference_variants = [ref_map["known_sites_vcf"]], - known_reference_variants_index = [ref_map["known_sites_index"]], - known_reference_variants_identifier = ["pfcrosses"], - is_known = [true], - is_training = [true], - is_truth = [true], - prior = [15], - use_allele_specific_annotations = true, - max_gaussians = 8, + recalibration_tranche_values = indel_recalibration_tranche_values, + recalibration_annotation_values = indel_recalibration_annotation_values, +# known_reference_variants = [ref_map["known_sites_vcf"]], +# known_reference_variants_index = [ref_map["known_sites_index"]], +# known_reference_variants_identifier = ["pfcrosses"], +# is_known = [true], +# is_training = [true], +# is_truth = [true], +# prior = [15], + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_known = indel_is_known, + is_training = indel_is_training, + is_truth = indel_is_truth, + prior = indel_prior, + use_allele_specific_annotations = false, + max_gaussians = indel_max_gaussians, } call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { @@ -122,15 +147,22 @@ workflow LRJointCallGVCFsWithGenomicsDB { prefix = prefix + ".snps", recalibration_tranche_values = snp_recalibration_tranche_values, recalibration_annotation_values = snp_recalibration_annotation_values, - known_reference_variants = [ref_map["known_sites_vcf"]], - known_reference_variants_index = [ref_map["known_sites_index"]], - known_reference_variants_identifier = ["pfcrosses"], - is_known = [true], - is_training = [true], - is_truth = [true], - prior = [15], - use_allele_specific_annotations = true, - max_gaussians = 8, +# known_reference_variants = [ref_map["known_sites_vcf"]], +# known_reference_variants_index = [ref_map["known_sites_index"]], +# known_reference_variants_identifier = ["pfcrosses"], +# is_known = [true], +# is_training = [true], +# is_truth = [true], +# prior = [15], + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_known = snp_is_known, + is_training = snp_is_training, + is_truth = snp_is_truth, + prior = snp_prior, + use_allele_specific_annotations = false, + max_gaussians = snp_max_gaussians, } call VARUTIL.ApplyVqsr as ApplyVqsr { @@ -150,7 +182,7 @@ workflow LRJointCallGVCFsWithGenomicsDB { indels_tranches = TrainVQSROnHCIndelVariants.tranches, indel_filter_level = indel_filter_level, - use_allele_specific_annotations = true, + use_allele_specific_annotations = false, } # Now we need to annotate our variants by region: From e9b393b840459f75bf87c1bafd4a74e74d558206 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 4 Jul 2023 11:12:33 -0400 Subject: [PATCH 161/297] Fixed modified joint calling to be faster. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 301 ++++++++++++++----------- wdl/tasks/Utils.wdl | 8 + wdl/tasks/VariantUtils.wdl | 61 +++++ 3 files changed, 240 insertions(+), 130 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index e2c1d6a78..9886dc8c8 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -6,6 +6,8 @@ version 1.0 import "tasks/SRJointGenotyping.wdl" as SRJOINT import "tasks/VariantUtils.wdl" as VARUTIL +import "tasks/Utils.wdl" as UTILS +import "tasks/Hail.wdl" as Hail import "tasks/Finalize.wdl" as FF workflow SRJointCallGVCFsWithGenomicsDB { @@ -60,7 +62,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" } - String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRJointCallGVCFsWithGenomicsDB/~{prefix}" + String outdir = sub(gcs_out_root_dir, "/$", "") + "/LRJointCallGVCFsWithGenomicsDB/~{prefix}" Map[String, String] ref_map = read_map(ref_map_file) @@ -77,170 +79,209 @@ workflow SRJointCallGVCFsWithGenomicsDB { prefix = prefix } - # Import our data into GenomicsDB: - call SRJOINT.ImportGVCFs as ImportGVCFsIntoGenomicsDB { + # Create interval list over which to shard the processing: + call UTILS.MakeChrIntervalList as MakeChrIntervalList { input: - sample_name_map = CreateSampleNameMap.sample_name_map, - interval_list = interval_list, - ref_fasta = ref_map['fasta'], - ref_fasta_fai = ref_map['fai'], - ref_dict = ref_map['dict'], - prefix = prefix, - batch_size = 50, + ref_dict = ref_map['dict'], } - # Joint call - call SRJOINT.GenotypeGVCFs as JointCallGVCFs { - input: - input_gvcf_data = ImportGVCFsIntoGenomicsDB.output_genomicsdb, - interval_list = interval_list, - ref_fasta = ref_map['fasta'], - ref_fasta_fai = ref_map['fai'], - ref_dict = ref_map['dict'], - dbsnp_vcf = ref_map["known_sites_vcf"], - prefix = prefix, - } + # Shard by contig for speed: + scatter (idx in range(length(MakeChrIntervalList.contig_interval_list_files))) { - # First make a sites-only VCF for recal (smaller file, easier to work with): - call VARUTIL.MakeSitesOnlyVcf as MakeSitesOnlyGVCF { - input: - vcf = JointCallGVCFs.output_vcf, - vcf_index = JointCallGVCFs.output_vcf_index, - prefix = prefix - } + String contig = MakeChrIntervalList.chrs[idx][0] + File contig_interval_list = MakeChrIntervalList.contig_interval_list_files[idx] - # Now we run VariantRecalibrator for indels and snps: - call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { - input: - vcf = MakeSitesOnlyGVCF.sites_only_vcf, - vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, - prefix = prefix + ".indels", - recalibration_tranche_values = indel_recalibration_tranche_values, - recalibration_annotation_values = indel_recalibration_annotation_values, -# known_reference_variants = [ref_map["known_sites_vcf"]], -# known_reference_variants_index = [ref_map["known_sites_index"]], -# known_reference_variants_identifier = ["pfcrosses"], -# is_known = [true], -# is_training = [true], -# is_truth = [true], -# prior = [15], - known_reference_variants = indel_known_reference_variants, - known_reference_variants_index = indel_known_reference_variants_index, - known_reference_variants_identifier = indel_known_reference_variants_identifier, - is_known = indel_is_known, - is_training = indel_is_training, - is_truth = indel_is_truth, - prior = indel_prior, - use_allele_specific_annotations = false, - max_gaussians = indel_max_gaussians, - } + # Import our data into GenomicsDB: + call SRJOINT.ImportGVCFs as ImportGVCFsIntoGenomicsDB { + input: + sample_name_map = CreateSampleNameMap.sample_name_map, + interval_list = contig_interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix + "." + contig, + batch_size = 50, + # We need to override this because we're not actually sending the GVCF over (just a list) + # ALSO, we're currently tarring the genomicsDB, so we need at least double the space here, plus some slop: + runtime_attr_override = object {disk_gb: 10 + (3 * CreateSampleNameMap.total_gvcf_size_gb) + (2 * ceil(size(ref_map['fasta'], "GB"))), preemptible_tries: 0} + } - call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { - input: - vcf = MakeSitesOnlyGVCF.sites_only_vcf, - vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, - prefix = prefix + ".snps", - recalibration_tranche_values = snp_recalibration_tranche_values, - recalibration_annotation_values = snp_recalibration_annotation_values, -# known_reference_variants = [ref_map["known_sites_vcf"]], -# known_reference_variants_index = [ref_map["known_sites_index"]], -# known_reference_variants_identifier = ["pfcrosses"], -# is_known = [true], -# is_training = [true], -# is_truth = [true], -# prior = [15], - known_reference_variants = snp_known_reference_variants, - known_reference_variants_index = snp_known_reference_variants_index, - known_reference_variants_identifier = snp_known_reference_variants_identifier, - is_known = snp_is_known, - is_training = snp_is_training, - is_truth = snp_is_truth, - prior = snp_prior, - use_allele_specific_annotations = false, - max_gaussians = snp_max_gaussians, - } + # Joint call + call SRJOINT.GenotypeGVCFs as JointCallGVCFs { + input: + input_gvcf_data = ImportGVCFsIntoGenomicsDB.output_genomicsdb, + interval_list = contig_interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + dbsnp_vcf = ref_map["known_sites_vcf"], + prefix = prefix + "." + contig + ".raw", + runtime_attr_override = object {preemptible_tries: 0}, # Disable preemption for prototype. + } - call VARUTIL.ApplyVqsr as ApplyVqsr { - input: - vcf = JointCallGVCFs.output_vcf, - vcf_index = JointCallGVCFs.output_vcf_index, + # First make a sites-only VCF for recal (smaller file, easier to work with): + call VARUTIL.MakeSitesOnlyVcf as MakeSitesOnlyVCF { + input: + vcf = JointCallGVCFs.output_vcf, + vcf_index = JointCallGVCFs.output_vcf_index, + prefix = prefix + "." + contig + ".sites_only" + } - prefix = prefix + ".vqsr_filtered", + # Now we run VariantRecalibrator for indels and snps: + call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { + input: + vcf = MakeSitesOnlyVCF.sites_only_vcf, + vcf_index = MakeSitesOnlyVCF.sites_only_vcf_index, + prefix = prefix + "." + contig + ".indels", + recalibration_tranche_values = indel_recalibration_tranche_values, + recalibration_annotation_values = indel_recalibration_annotation_values, + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_known = indel_is_known, + is_training = indel_is_training, + is_truth = indel_is_truth, + prior = indel_prior, + use_allele_specific_annotations = false, + max_gaussians = indel_max_gaussians, + } - snps_recalibration = TrainVQSROnHCSnpVariants.recalibration, - snps_recalibration_index = TrainVQSROnHCSnpVariants.recalibration_index, - snps_tranches = TrainVQSROnHCSnpVariants.tranches, - snp_filter_level = snp_filter_level, + call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { + input: + vcf = MakeSitesOnlyVCF.sites_only_vcf, + vcf_index = MakeSitesOnlyVCF.sites_only_vcf_index, + prefix = prefix + "." + contig + ".snps", + recalibration_tranche_values = snp_recalibration_tranche_values, + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_known = snp_is_known, + is_training = snp_is_training, + is_truth = snp_is_truth, + prior = snp_prior, + use_allele_specific_annotations = false, + max_gaussians = snp_max_gaussians, + } - indels_recalibration = TrainVQSROnHCIndelVariants.recalibration, - indels_recalibration_index = TrainVQSROnHCIndelVariants.recalibration_index, - indels_tranches = TrainVQSROnHCIndelVariants.tranches, - indel_filter_level = indel_filter_level, + call VARUTIL.ApplyVqsr as ApplyVqsr { + input: + vcf = JointCallGVCFs.output_vcf, + vcf_index = JointCallGVCFs.output_vcf_index, - use_allele_specific_annotations = false, - } + prefix = prefix + "." + contig + ".vqsr_filtered", - # Now we need to annotate our variants by region: - if (defined(annotation_bed_files)) { - call VARUTIL.AnnotateVcfWithBedRegions as AnnotateVcfRegions { - input: - vcf = ApplyVqsr.recalibrated_vcf, - vcf_index = ApplyVqsr.recalibrated_vcf_index, - bed_files = select_first([annotation_bed_files]), - bed_file_indexes = select_first([annotation_bed_file_indexes]), - bed_file_annotation_names = select_first([annotation_bed_file_annotation_names]), - prefix = prefix + ".region_annotated" + snps_recalibration = TrainVQSROnHCSnpVariants.recalibration, + snps_recalibration_index = TrainVQSROnHCSnpVariants.recalibration_index, + snps_tranches = TrainVQSROnHCSnpVariants.tranches, + snp_filter_level = snp_filter_level, + + indels_recalibration = TrainVQSROnHCIndelVariants.recalibration, + indels_recalibration_index = TrainVQSROnHCIndelVariants.recalibration_index, + indels_tranches = TrainVQSROnHCIndelVariants.tranches, + indel_filter_level = indel_filter_level, + + use_allele_specific_annotations = false, + } + + # Now we need to annotate our variants by region: + if (defined(annotation_bed_files)) { + call VARUTIL.AnnotateVcfWithBedRegions as AnnotateVcfRegions { + input: + vcf = ApplyVqsr.recalibrated_vcf, + vcf_index = ApplyVqsr.recalibrated_vcf_index, + bed_files = select_first([annotation_bed_files]), + bed_file_indexes = select_first([annotation_bed_file_indexes]), + bed_file_annotation_names = select_first([annotation_bed_file_annotation_names]), + prefix = prefix + "." + contig + ".region_annotated" + } } + + File recalibrated_vcf = select_first([AnnotateVcfRegions.annotated_vcf, ApplyVqsr.recalibrated_vcf]) + File recalibrated_vcf_index = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]) + } + + # Consolidate files: + call VARUTIL.GatherVcfs as GatherRawVcfs { + input: + input_vcfs = JointCallGVCFs.output_vcf, + input_vcf_indices = JointCallGVCFs.output_vcf_index, + prefix = prefix + ".raw.combined" + } + + # Consolidate files: + call VARUTIL.GatherVcfs as GatherRecalibratedVcfs { + input: + input_vcfs = recalibrated_vcf, + input_vcf_indices = recalibrated_vcf_index, + prefix = prefix + ".recalibrated.combined" + } + + # Finally convert the output to a HAIL Matrix Table: + call Hail.ConvertToHailMT as CreateHailMatrixTable { + input: + gvcf = GatherRecalibratedVcfs.output_vcf, + tbi = GatherRecalibratedVcfs.output_vcf_index, + reference = sub(sub(ref_map["fasta"], "^.*/", ""), "\.[fasta]*$", ""), + ref_fasta = ref_map["fasta"], + ref_fai = ref_map["fai"], + prefix = prefix, + outdir = outdir } # Finalize: - File keyfile = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]) + File keyfile = CreateHailMatrixTable.monitoring_log - call FF.FinalizeToFile as FinalizeGenomicsDB { input: outdir = outdir, keyfile = keyfile, file = ImportGVCFsIntoGenomicsDB.output_genomicsdb } +# call FF.FinalizeToDir as FinalizeGenomicsDB { input: outdir = outdir + "/GenomicsDB", keyfile = keyfile, file = ImportGVCFsIntoGenomicsDB.output_genomicsdb } - call FF.FinalizeToFile as FinalizeRawVCF { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf } - call FF.FinalizeToFile as FinalizeRawTBI { input: outdir = outdir, keyfile = keyfile, file = JointCallGVCFs.output_vcf_index } + call FF.FinalizeToFile as FinalizeRawVCF { input: outdir = outdir, keyfile = keyfile, file = GatherRawVcfs.output_vcf } + call FF.FinalizeToFile as FinalizeRawTBI { input: outdir = outdir, keyfile = keyfile, file = GatherRawVcfs.output_vcf_index } - call FF.FinalizeToFile as FinalizeIndelRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } - call FF.FinalizeToFile as FinalizeIndelRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } - call FF.FinalizeToFile as FinalizeIndelRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } - call FF.FinalizeToFile as FinalizeIndelRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } +# call FF.FinalizeToDir as FinalizeIndelRecalFile { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } +# call FF.FinalizeToDir as FinalizeIndelRecalIndex { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } +# call FF.FinalizeToDir as FinalizeIndelRecalTranches { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } +# call FF.FinalizeToDir as FinalizeIndelRecalModelReport { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } - call FF.FinalizeToFile as FinalizeSnpRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } - call FF.FinalizeToFile as FinalizeSnpRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } - call FF.FinalizeToFile as FinalizeSnpRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } - call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } +# call FF.FinalizeToDir as FinalizeSnpRecalFile { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } +# call FF.FinalizeToDir as FinalizeSnpRecalIndex { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } +# call FF.FinalizeToDir as FinalizeSnpRecalTranches { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } +# call FF.FinalizeToDir as FinalizeSnpRecalModelReport { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } - call FF.FinalizeToFile as FinalizeVQSRVCF { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf } - call FF.FinalizeToFile as FinalizeVQSRTBI { input: outdir = outdir, keyfile = keyfile, file = ApplyVqsr.recalibrated_vcf_index } + call FF.FinalizeToFile as FinalizeVQSRVCF { input: outdir = outdir, keyfile = keyfile, file = GatherRecalibratedVcfs.output_vcf } + call FF.FinalizeToFile as FinalizeVQSRTBI { input: outdir = outdir, keyfile = keyfile, file = GatherRecalibratedVcfs.output_vcf_index } - if (defined(annotation_bed_files)) { - call FF.FinalizeToFile as FinalizeRegionAnnotatedVcf { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf]) } - call FF.FinalizeToFile as FinalizeRegionAnnotatedVcfIndex { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf_index]) } - } +# if (defined(annotation_bed_files)) { +# call FF.FinalizeToFile as FinalizeRegionAnnotatedVcf { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf]) } +# call FF.FinalizeToFile as FinalizeRegionAnnotatedVcfIndex { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf_index]) } +# } ########## # store the results into designated bucket ########## output { - File genomicsDB = FinalizeGenomicsDB.gcs_path +# File genomicsDB = FinalizeGenomicsDB.gcs_path File raw_joint_vcf = FinalizeRawVCF.gcs_path File raw_joint_vcf_tbi = FinalizeRawTBI.gcs_path - File? vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path - File? vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path - File? vqsr_indel_recal_tranches = FinalizeIndelRecalTranches.gcs_path - File? vqsr_indel_recal_model_report = FinalizeIndelRecalModelReport.gcs_path - - File? vqsr_snp_recal_file = FinalizeSnpRecalFile.gcs_path - File? vqsr_snp_recal_file_index = FinalizeSnpRecalIndex.gcs_path - File? vqsr_snp_recal_tranches = FinalizeSnpRecalTranches.gcs_path - File? vqsr_snp_recal_model_report = FinalizeSnpRecalModelReport.gcs_path +# Array[File?] vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path +# Array[File?] vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path +# Array[File?] vqsr_indel_recal_tranches = FinalizeIndelRecalTranches.gcs_path +# Array[File?] vqsr_indel_recal_model_report = FinalizeIndelRecalModelReport.gcs_path +# +# Array[File?] vqsr_snp_recal_file = FinalizeSnpRecalFile.gcs_path +# Array[File?] vqsr_snp_recal_file_index = FinalizeSnpRecalIndex.gcs_path +# Array[File?] vqsr_snp_recal_tranches = FinalizeSnpRecalTranches.gcs_path +# Array[File?] vqsr_snp_recal_model_report = FinalizeSnpRecalModelReport.gcs_path File joint_recalibrated_vcf = FinalizeVQSRVCF.gcs_path File joint_recalibrated_vcf_tbi = FinalizeVQSRTBI.gcs_path + +# File? annotated_joint_vcf = AnnotateVcfRegions.annotated_vcf +# File? annotated_joint_vcf_tbi = AnnotateVcfRegions.annotated_vcf_index + + File joint_mt = CreateHailMatrixTable.gcs_path } } diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 5efdf6634..d0f428c0f 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -295,11 +295,19 @@ task MakeChrIntervalList { tee chrs.txt cat chrs.txt | awk '{printf("%s:%d-%d\n", $1,$2,$3)}' > intervalList.intervals + + # Now make another output - a set of individual contig interval list files: + while read line ; do + contig=$(awk '{print $1}' ${line}) + awk '{printf("%s:%d-%d\n", $1,$2,$3)}' > contig.${contig}.intervals + done < chrs.txt >>> output { Array[Array[String]] chrs = read_tsv("chrs.txt") File interval_list = "intervalList.intervals" + Array[String] contig_interval_strings = read_lines("intervalList.intervals") + Array[File] contig_interval_list_files = glob("contig.*.intervals") } ######################### diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index fbbd91422..db473df2c 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1331,3 +1331,64 @@ task RenameSingleSampleVcf { File new_sample_name_vcf_index = "~{prefix}.~{suffix}.tbi" } } + +task GatherVcfs { + + input { + Array[File] input_vcfs + Array[File] input_vcf_indices + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 10 + 3*ceil(size(input_vcfs, "GB")) + ceil(size(input_vcf_indices, "GB")) + + parameter_meta { + input_vcfs: { + localization_optional: true + } + } + + command <<< + set -euo pipefail + + # --ignore-safety-checks makes a big performance difference so we include it in our invocation. + # This argument disables expensive checks that the file headers contain the same set of + # genotyped samples and that files are in order by position of first record. + gatk --java-options "-Xms6000m -Xmx6500m" \ + GatherVcfsCloud \ + --ignore-safety-checks \ + --gather-type BLOCK \ + --input ~{sep=" --input " input_vcfs} \ + --output ~{prefix}.vcf + + tabix ~{prefix}.vcf + >>> + + output { + File output_vcf = "~{prefix}.vcf" + File output_vcf_index = "~{prefix}.vcf.tbi" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 8, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} \ No newline at end of file From 8194cdaa4ee96867bffc9fdbbe2a7faeaf62b8e5 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 4 Jul 2023 11:17:00 -0400 Subject: [PATCH 162/297] Adding `SRJointCallGVCFsWithGenomicsDB` to dockstore. --- .dockstore.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.dockstore.yml b/.dockstore.yml index 71c94b465..ca36b30cc 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -124,6 +124,10 @@ workflows: subclass: wdl primaryDescriptorPath: /wdl/SRFlowcell.wdl testParameterFiles: +- name: SRJointCallGVCFsWithGenomicsDB + subclass: wdl + primaryDescriptorPath: /wdl/SRJointCallGVCFsWithGenomicsDB.wdl + testParameterFiles: - name: LRJointCallGVCFsWithGenomicsDB subclass: wdl primaryDescriptorPath: /wdl/LRJointCallGVCFsWithGenomicsDB.wdl From cf3313d69ad1013f4420276fb11f3d1e7cc3f778 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 4 Jul 2023 11:47:43 -0400 Subject: [PATCH 163/297] Fixing bug. --- wdl/tasks/Utils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index d0f428c0f..35af11dcc 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -298,7 +298,7 @@ task MakeChrIntervalList { # Now make another output - a set of individual contig interval list files: while read line ; do - contig=$(awk '{print $1}' ${line}) + contig=$(echo "${line}" | awk '{print $1}') awk '{printf("%s:%d-%d\n", $1,$2,$3)}' > contig.${contig}.intervals done < chrs.txt >>> From 68464a943b5266f338e1b1a5af5c050434a7fbf2 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 5 Jul 2023 17:09:59 -0400 Subject: [PATCH 164/297] Parallelized the joint call. Added functional annotation. --- wdl/ONTPfTypeDrugResistanceMarkers.wdl | 58 +------------------- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 55 +++++++++++++++---- wdl/tasks/FunctionalAnnotation.wdl | 58 ++++++++++++++++++++ wdl/tasks/SRJointGenotyping.wdl | 76 ++++++++++++++++++++++++++ wdl/tasks/Utils.wdl | 2 +- 5 files changed, 181 insertions(+), 68 deletions(-) create mode 100644 wdl/tasks/FunctionalAnnotation.wdl diff --git a/wdl/ONTPfTypeDrugResistanceMarkers.wdl b/wdl/ONTPfTypeDrugResistanceMarkers.wdl index 8aa43c048..7cbcc407d 100644 --- a/wdl/ONTPfTypeDrugResistanceMarkers.wdl +++ b/wdl/ONTPfTypeDrugResistanceMarkers.wdl @@ -1,6 +1,7 @@ version 1.0 import "tasks/Structs.wdl" +import "tasks/FunctionalAnnotation.wdl" as FUNK import "tasks/Finalize.wdl" as FF workflow ONTPfTypeDrugResistanceMarkers { @@ -15,7 +16,7 @@ workflow ONTPfTypeDrugResistanceMarkers { String outdir = sub(gcs_out_root_dir, "/$", "") + "/ONTPfTypeDrugResistanceMarkers/~{dir_prefix}" - call FunctionallyAnnotateVariants { input: vcf = vcf, snpeff_db = snpeff_db } + call FUNK.FunctionallyAnnotateVariants { input: vcf = vcf, snpeff_db = snpeff_db } call CallDrugResistanceMutations { input: @@ -39,61 +40,6 @@ workflow ONTPfTypeDrugResistanceMarkers { } } -task FunctionallyAnnotateVariants { - input { - File vcf - File snpeff_db - - RuntimeAttr? runtime_attr_override - } - - Int disk_size = 1 + 4*ceil(size([vcf, snpeff_db], "GB")) - String prefix = basename(basename(vcf, ".gz"), ".vcf") - - command <<< - set -x - - gunzip -c ~{snpeff_db} | tar xvf - - - /usr/local/bin/snpEff ann -v \ - -c $PWD/snpeff_db/snpEff.config \ - -dataDir $PWD/snpeff_db/data \ - PlasmoDB-61_Pfalciparum3D7_Genome \ - ~{vcf} \ - | gzip > ~{prefix}.annotated.vcf.gz - - mv snpEff_summary.html ~{prefix}.snpEff_summary.html - mv snpEff_genes.txt ~{prefix}.snpEff_genes.txt - >>> - - output { - File annotated_vcf = "~{prefix}.annotated.vcf.gz" - File snpEff_summary = "~{prefix}.snpEff_summary.html" - File snpEff_genes = "~{prefix}.snpEff_genes.txt" - } - - ######################### - RuntimeAttr default_attr = object { - cpu_cores: 2, - mem_gb: 4, - disk_gb: disk_size, - boot_disk_gb: 10, - preemptible_tries: 2, - max_retries: 1, - docker: "quay.io/biocontainers/snpeff:5.1d--hdfd78af_0" - } - RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) - runtime { - cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) - memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" - disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" - bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) - preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) - maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) - docker: select_first([runtime_attr.docker, default_attr.docker]) - } -} - task CallDrugResistanceMutations { input { File vcf diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 9886dc8c8..1805d4680 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -8,6 +8,7 @@ import "tasks/SRJointGenotyping.wdl" as SRJOINT import "tasks/VariantUtils.wdl" as VARUTIL import "tasks/Utils.wdl" as UTILS import "tasks/Hail.wdl" as Hail +import "tasks/FunctionalAnnotation.wdl" as FUNK import "tasks/Finalize.wdl" as FF workflow SRJointCallGVCFsWithGenomicsDB { @@ -49,6 +50,8 @@ workflow SRJointCallGVCFsWithGenomicsDB { Array[File]? annotation_bed_file_indexes Array[String]? annotation_bed_file_annotation_names + File? snpeff_db + String prefix String gcs_out_root_dir @@ -72,6 +75,26 @@ workflow SRJointCallGVCFsWithGenomicsDB { # We allow overriding this default behavior for testing / special requests. Boolean is_small_callset = length(gvcfs) <= 1000 + # Create interval list over which to shard the processing: + call UTILS.MakeChrIntervalList as MakeChrIntervalList { + input: + ref_dict = ref_map['dict'], + } + + # Reblock our GVCFs: + scatter (idx_1 in range(length(gvcfs))) { + call SRJOINT.ReblockGVCF as ReblockGVCFs { + input: + input_gvcf = gvcfs[idx_1], + input_gvcf_index = gvcf_indices[idx_1], + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + # Get rid of any and all suffixes: + prefix = basename(basename(basename(gvcfs[idx_1], ".g.vcf.gz"), ".vcf.gz"), ".vcf") + } + } + # Create sample-name map: call SRJOINT.CreateSampleNameMap as CreateSampleNameMap { input: @@ -79,17 +102,11 @@ workflow SRJointCallGVCFsWithGenomicsDB { prefix = prefix } - # Create interval list over which to shard the processing: - call UTILS.MakeChrIntervalList as MakeChrIntervalList { - input: - ref_dict = ref_map['dict'], - } - # Shard by contig for speed: - scatter (idx in range(length(MakeChrIntervalList.contig_interval_list_files))) { + scatter (idx_2 in range(length(MakeChrIntervalList.contig_interval_list_files))) { - String contig = MakeChrIntervalList.chrs[idx][0] - File contig_interval_list = MakeChrIntervalList.contig_interval_list_files[idx] + String contig = MakeChrIntervalList.chrs[idx_2][0] + File contig_interval_list = MakeChrIntervalList.contig_interval_list_files[idx_2] # Import our data into GenomicsDB: call SRJOINT.ImportGVCFs as ImportGVCFsIntoGenomicsDB { @@ -199,6 +216,22 @@ workflow SRJointCallGVCFsWithGenomicsDB { File recalibrated_vcf = select_first([AnnotateVcfRegions.annotated_vcf, ApplyVqsr.recalibrated_vcf]) File recalibrated_vcf_index = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]) + + # Now functionally annotate each VCF: + if (defined(snpeff_db)) { + call FUNK.FunctionallyAnnotateVariants as FunctionallyAnnotate { + input: + vcf = recalibrated_vcf, + snpeff_db = select_first([snpeff_db]) + } + call VARUTIL.IndexVCF as IndexFunkyVcf { + input: + vcf = FunctionallyAnnotate.annotated_vcf + } + } + + File vcf_for_merging = select_first([FunctionallyAnnotate.annotated_vcf, recalibrated_vcf]) + File vcf_index_for_merging = select_first([IndexFunkyVcf.tbi, recalibrated_vcf_index]) } # Consolidate files: @@ -212,8 +245,8 @@ workflow SRJointCallGVCFsWithGenomicsDB { # Consolidate files: call VARUTIL.GatherVcfs as GatherRecalibratedVcfs { input: - input_vcfs = recalibrated_vcf, - input_vcf_indices = recalibrated_vcf_index, + input_vcfs = vcf_for_merging, + input_vcf_indices = vcf_index_for_merging, prefix = prefix + ".recalibrated.combined" } diff --git a/wdl/tasks/FunctionalAnnotation.wdl b/wdl/tasks/FunctionalAnnotation.wdl new file mode 100644 index 000000000..9a7ae9a89 --- /dev/null +++ b/wdl/tasks/FunctionalAnnotation.wdl @@ -0,0 +1,58 @@ +version 1.0 + +import "Structs.wdl" + +task FunctionallyAnnotateVariants { + input { + File vcf + File snpeff_db + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size([vcf, snpeff_db], "GB")) + String prefix = basename(basename(vcf, ".gz"), ".vcf") + + command <<< + set -x + + gunzip -c ~{snpeff_db} | tar xvf - + + /usr/local/bin/snpEff ann -v \ + -c $PWD/snpeff_db/snpEff.config \ + -dataDir $PWD/snpeff_db/data \ + PlasmoDB-61_Pfalciparum3D7_Genome \ + ~{vcf} \ + | gzip > ~{prefix}.annotated.vcf.gz + + mv snpEff_summary.html ~{prefix}.snpEff_summary.html + mv snpEff_genes.txt ~{prefix}.snpEff_genes.txt + >>> + + output { + File annotated_vcf = "~{prefix}.annotated.vcf.gz" + File snpEff_summary = "~{prefix}.snpEff_summary.html" + File snpEff_genes = "~{prefix}.snpEff_genes.txt" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "quay.io/biocontainers/snpeff:5.1d--hdfd78af_0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} \ No newline at end of file diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index ab296cf4e..71a7e9f20 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -193,6 +193,82 @@ task ImportGVCFs { } } +task ReblockGVCF { + + input { + File input_gvcf + File input_gvcf_index + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String prefix + + Array[Int] gq_blocks = [20, 30, 40] + + String? annotations_to_keep_command + Float? tree_score_cutoff + + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size(ref_fasta, "GB") + size(ref_fasta_fai, "GB") + size(ref_dict, "GB")) + + Int disk_size = 1 + 4*ceil(size(input_gvcf, "GB")) + 4*ceil(size(input_gvcf_index, "GB")) + ref_size + + command <<< + set -euxo pipefail + + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + + gatk --java-options "-Xms3000m -Xmx3000m" \ + ReblockGVCF \ + -R ~{ref_fasta} \ + -V ~{input_gvcf} \ + -do-qual-approx \ + --floor-blocks \ + -GQB ~{sep="-GQB" gq_blocks} \ + ~{annotations_to_keep_command} \ + ~{"--tree-score-threshold-to-no-call " + tree_score_cutoff} \ + -O ~{prefix}.reblocked.g.vcf.gz + + kill $monitoring_pid + >>> + + output { + File output_gvcf = "~{prefix}.reblocked.g.vcf.gz" + File output_gvcf_index = "~{prefix}.reblocked.g.vcf.gz.tbi" + + File monitoring_log = "resources.log" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 2, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + task GenotypeGVCFs { input { diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 35af11dcc..4df12e149 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -299,7 +299,7 @@ task MakeChrIntervalList { # Now make another output - a set of individual contig interval list files: while read line ; do contig=$(echo "${line}" | awk '{print $1}') - awk '{printf("%s:%d-%d\n", $1,$2,$3)}' > contig.${contig}.intervals + echo "${line}" | awk '{printf("%s:%d-%d\n", $1,$2,$3)}' > contig.${contig}.intervals done < chrs.txt >>> From 0ae8a79e88407f41952838760f360caa4732e05e Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 5 Jul 2023 19:52:51 -0400 Subject: [PATCH 165/297] Fixing issue with reblock GVCFs --- wdl/tasks/SRJointGenotyping.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 71a7e9f20..6552c2aec 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -232,7 +232,7 @@ task ReblockGVCF { -V ~{input_gvcf} \ -do-qual-approx \ --floor-blocks \ - -GQB ~{sep="-GQB" gq_blocks} \ + -GQB ~{sep=" -GQB " gq_blocks} \ ~{annotations_to_keep_command} \ ~{"--tree-score-threshold-to-no-call " + tree_score_cutoff} \ -O ~{prefix}.reblocked.g.vcf.gz From d6918090e8d8220bd1fdf59fdc8cdb124fdb4888 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 6 Jul 2023 10:24:42 -0400 Subject: [PATCH 166/297] Adding Zarr conversion into SR Joint Calling WDL. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 15 ++++++++++++++- wdl/tasks/SGKit.wdl | 3 --- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 1805d4680..529b015fe 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -9,6 +9,7 @@ import "tasks/VariantUtils.wdl" as VARUTIL import "tasks/Utils.wdl" as UTILS import "tasks/Hail.wdl" as Hail import "tasks/FunctionalAnnotation.wdl" as FUNK +import "tasks/SGKit.wdl" as SGKit import "tasks/Finalize.wdl" as FF workflow SRJointCallGVCFsWithGenomicsDB { @@ -54,6 +55,8 @@ workflow SRJointCallGVCFsWithGenomicsDB { String prefix + Boolean convert_to_zarr = false + String gcs_out_root_dir } @@ -250,7 +253,16 @@ workflow SRJointCallGVCFsWithGenomicsDB { prefix = prefix + ".recalibrated.combined" } - # Finally convert the output to a HAIL Matrix Table: + # Convert to Zarr + call SGKit.ConvertToZarrStore as ConvertToZarr { + input: + gvcf = GatherRecalibratedVcfs.output_vcf, + tbi = GatherRecalibratedVcfs.output_vcf_index, + prefix = prefix, + outdir = outdir + } + + # Convert the output to a HAIL Matrix Table: call Hail.ConvertToHailMT as CreateHailMatrixTable { input: gvcf = GatherRecalibratedVcfs.output_vcf, @@ -315,6 +327,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { # File? annotated_joint_vcf_tbi = AnnotateVcfRegions.annotated_vcf_index File joint_mt = CreateHailMatrixTable.gcs_path + File joint_zarr = ConvertToZarr.gcs_path } } diff --git a/wdl/tasks/SGKit.wdl b/wdl/tasks/SGKit.wdl index 56a762e70..d62b635b6 100644 --- a/wdl/tasks/SGKit.wdl +++ b/wdl/tasks/SGKit.wdl @@ -31,9 +31,6 @@ task ConvertToZarrStore { python3 < Date: Thu, 6 Jul 2023 10:30:11 -0400 Subject: [PATCH 167/297] Removing automatic Zarr conversion --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 529b015fe..5cebce609 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -253,14 +253,14 @@ workflow SRJointCallGVCFsWithGenomicsDB { prefix = prefix + ".recalibrated.combined" } - # Convert to Zarr - call SGKit.ConvertToZarrStore as ConvertToZarr { - input: - gvcf = GatherRecalibratedVcfs.output_vcf, - tbi = GatherRecalibratedVcfs.output_vcf_index, - prefix = prefix, - outdir = outdir - } +# # Convert to Zarr +# call SGKit.ConvertToZarrStore as ConvertToZarr { +# input: +# gvcf = GatherRecalibratedVcfs.output_vcf, +# tbi = GatherRecalibratedVcfs.output_vcf_index, +# prefix = prefix, +# outdir = outdir +# } # Convert the output to a HAIL Matrix Table: call Hail.ConvertToHailMT as CreateHailMatrixTable { @@ -327,7 +327,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { # File? annotated_joint_vcf_tbi = AnnotateVcfRegions.annotated_vcf_index File joint_mt = CreateHailMatrixTable.gcs_path - File joint_zarr = ConvertToZarr.gcs_path +# File joint_zarr = ConvertToZarr.gcs_path } } From a0705be3878f2ea99678435617491df4a0c671b6 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 10 Jul 2023 09:39:38 -0400 Subject: [PATCH 168/297] Fixed issue with VQSR in `SRJointCallGVCFsWithGenomicsDB.wdl` --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 103 +++++++++++++------------ wdl/tasks/VariantUtils.wdl | 28 +++---- 2 files changed, 69 insertions(+), 62 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 5cebce609..30c27d3eb 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -146,50 +146,57 @@ workflow SRJointCallGVCFsWithGenomicsDB { vcf_index = JointCallGVCFs.output_vcf_index, prefix = prefix + "." + contig + ".sites_only" } + } - # Now we run VariantRecalibrator for indels and snps: - call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { - input: - vcf = MakeSitesOnlyVCF.sites_only_vcf, - vcf_index = MakeSitesOnlyVCF.sites_only_vcf_index, - prefix = prefix + "." + contig + ".indels", - recalibration_tranche_values = indel_recalibration_tranche_values, - recalibration_annotation_values = indel_recalibration_annotation_values, - known_reference_variants = indel_known_reference_variants, - known_reference_variants_index = indel_known_reference_variants_index, - known_reference_variants_identifier = indel_known_reference_variants_identifier, - is_known = indel_is_known, - is_training = indel_is_training, - is_truth = indel_is_truth, - prior = indel_prior, - use_allele_specific_annotations = false, - max_gaussians = indel_max_gaussians, - } + # Now we run VariantRecalibrator for indels and snps: + call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { + input: + vcfs = MakeSitesOnlyVCF.sites_only_vcf, + vcf_indices = MakeSitesOnlyVCF.sites_only_vcf_index, + prefix = prefix + ".indels", + recalibration_tranche_values = indel_recalibration_tranche_values, + recalibration_annotation_values = indel_recalibration_annotation_values, + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_known = indel_is_known, + is_training = indel_is_training, + is_truth = indel_is_truth, + prior = indel_prior, + use_allele_specific_annotations = false, + max_gaussians = indel_max_gaussians, + } - call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { - input: - vcf = MakeSitesOnlyVCF.sites_only_vcf, - vcf_index = MakeSitesOnlyVCF.sites_only_vcf_index, - prefix = prefix + "." + contig + ".snps", - recalibration_tranche_values = snp_recalibration_tranche_values, - recalibration_annotation_values = snp_recalibration_annotation_values, - known_reference_variants = snp_known_reference_variants, - known_reference_variants_index = snp_known_reference_variants_index, - known_reference_variants_identifier = snp_known_reference_variants_identifier, - is_known = snp_is_known, - is_training = snp_is_training, - is_truth = snp_is_truth, - prior = snp_prior, - use_allele_specific_annotations = false, - max_gaussians = snp_max_gaussians, - } + call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { + input: + vcfs = MakeSitesOnlyVCF.sites_only_vcf, + vcf_indices = MakeSitesOnlyVCF.sites_only_vcf_index, + prefix = prefix + ".snps", + recalibration_tranche_values = snp_recalibration_tranche_values, + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_known = snp_is_known, + is_training = snp_is_training, + is_truth = snp_is_truth, + prior = snp_prior, + use_allele_specific_annotations = false, + max_gaussians = snp_max_gaussians, + } + + # Shard by contig for speed: + scatter (idx_3 in range(length(JointCallGVCFs.output_vcf))) { + + File joint_called_vcf = JointCallGVCFs.output_vcf[idx_3] + File joint_called_vcf_index = JointCallGVCFs.output_vcf[idx_3] call VARUTIL.ApplyVqsr as ApplyVqsr { input: - vcf = JointCallGVCFs.output_vcf, - vcf_index = JointCallGVCFs.output_vcf_index, + vcf = joint_called_vcf, + vcf_index = joint_called_vcf_index, - prefix = prefix + "." + contig + ".vqsr_filtered", + prefix = basename(joint_called_vcf, ".vcf") + ".vqsr", snps_recalibration = TrainVQSROnHCSnpVariants.recalibration, snps_recalibration_index = TrainVQSROnHCSnpVariants.recalibration_index, @@ -213,7 +220,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { bed_files = select_first([annotation_bed_files]), bed_file_indexes = select_first([annotation_bed_file_indexes]), bed_file_annotation_names = select_first([annotation_bed_file_annotation_names]), - prefix = prefix + "." + contig + ".region_annotated" + prefix = basename(ApplyVqsr.recalibrated_vcf, ".vcf") + ".region_annotated" } } @@ -253,14 +260,14 @@ workflow SRJointCallGVCFsWithGenomicsDB { prefix = prefix + ".recalibrated.combined" } -# # Convert to Zarr -# call SGKit.ConvertToZarrStore as ConvertToZarr { -# input: -# gvcf = GatherRecalibratedVcfs.output_vcf, -# tbi = GatherRecalibratedVcfs.output_vcf_index, -# prefix = prefix, -# outdir = outdir -# } + # Convert to Zarr + call SGKit.ConvertToZarrStore as ConvertToZarr { + input: + gvcf = GatherRecalibratedVcfs.output_vcf, + tbi = GatherRecalibratedVcfs.output_vcf_index, + prefix = prefix, + outdir = outdir + } # Convert the output to a HAIL Matrix Table: call Hail.ConvertToHailMT as CreateHailMatrixTable { @@ -327,7 +334,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { # File? annotated_joint_vcf_tbi = AnnotateVcfRegions.annotated_vcf_index File joint_mt = CreateHailMatrixTable.gcs_path -# File joint_zarr = ConvertToZarr.gcs_path + File joint_zarr = ConvertToZarr.gcs_path } } diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index db473df2c..34d917d40 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -845,8 +845,8 @@ task AnnotateVcfWithBedRegions { task IndelsVariantRecalibrator { input { - File vcf - File vcf_index + Array[File] vcfs + Array[File] vcf_indices String prefix @@ -868,8 +868,8 @@ task IndelsVariantRecalibrator { } parameter_meta { - vcf: "Sites only VCF. Can be pre-filtered using hard-filters." - vcf_index: "Tribble Index for sites only VCF." + vcfs: "Sites only VCFs. Can be pre-filtered using hard-filters." + vcf_indices: "Tribble Indexes for sites only VCF." known_reference_variants: "Array of known reference VCF files. For humans, dbSNP is one example." known_reference_variants_index: "Array of index files for known reference VCF files." known_reference_variants_identifier: "Array of boolean values the identifier / name for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." @@ -881,8 +881,8 @@ task IndelsVariantRecalibrator { Int disk_size = 10 + ceil(size(known_reference_variants, "GB")) - + 4*ceil(size(vcf, "GB")) - + 2*ceil(size(vcf_index, "GB")) + + 4*ceil(size(vcfs, "GB")) + + 2*ceil(size(vcf_indices, "GB")) command <<< set -euxo pipefail @@ -926,7 +926,7 @@ task IndelsVariantRecalibrator { gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ VariantRecalibrator \ - -V ~{vcf} \ + -V ~{sep=' -V ' vcfs} \ -O ~{prefix}.recal \ --tranches-file ~{prefix}.tranches \ --trust-all-polymorphic \ @@ -975,8 +975,8 @@ task IndelsVariantRecalibrator { task SNPsVariantRecalibratorCreateModel { input { - File vcf - File vcf_index + Array[File] vcfs + Array[File] vcf_indices String prefix @@ -1000,8 +1000,8 @@ task SNPsVariantRecalibratorCreateModel { } parameter_meta { - vcf: "Sites only VCF. Can be pre-filtered using hard-filters." - vcf_index: "Tribble Index for sites only VCF." + vcfs: "Sites only VCFs. Can be pre-filtered using hard-filters." + vcf_indices: "Tribble Indexes for sites only VCF." known_reference_variants: "Array of known reference VCF files. For humans, dbSNP is one example." known_reference_variants_index: "Array of index files for known reference VCF files." known_reference_variants_identifier: "Array of boolean values the identifier / name for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." @@ -1012,8 +1012,8 @@ task SNPsVariantRecalibratorCreateModel { } Int disk_size = 10 + ceil(size(known_reference_variants, "GB")) - + 4*ceil(size(vcf, "GB")) - + 2*ceil(size(vcf_index, "GB")) + + 4*ceil(size(vcfs, "GB")) + + 2*ceil(size(vcf_indices, "GB")) String downsample_factor_arg = if defined(downsampleFactor) then " --sample-every-Nth-variant " else "" @@ -1059,7 +1059,7 @@ task SNPsVariantRecalibratorCreateModel { gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ VariantRecalibrator \ - -V ~{vcf} \ + -V ~{sep=' -V ' vcfs} \ -O ~{prefix}.recal \ --tranches-file ~{prefix}.tranches \ --trust-all-polymorphic \ From 3f155ddf730134e581fc5da5b4f0152d00350dee Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 10 Jul 2023 09:48:53 -0400 Subject: [PATCH 169/297] Removing unused parameter. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 2 -- 1 file changed, 2 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 30c27d3eb..26055ee1b 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -55,8 +55,6 @@ workflow SRJointCallGVCFsWithGenomicsDB { String prefix - Boolean convert_to_zarr = false - String gcs_out_root_dir } From 8d67991422b329b3051d84560099ebeaf71a5a90 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 10 Jul 2023 10:33:28 -0400 Subject: [PATCH 170/297] Fixed a bug in `GatherVCFsCloud` --- wdl/tasks/VariantUtils.wdl | 1 - 1 file changed, 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 34d917d40..b2327f4a3 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1359,7 +1359,6 @@ task GatherVcfs { gatk --java-options "-Xms6000m -Xmx6500m" \ GatherVcfsCloud \ --ignore-safety-checks \ - --gather-type BLOCK \ --input ~{sep=" --input " input_vcfs} \ --output ~{prefix}.vcf From 7ef0b11dbc79745b8854f6d951a1c02f61f16264 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 10 Jul 2023 14:04:06 -0400 Subject: [PATCH 171/297] Fixed typo in `ApplyVQSR` task. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 26055ee1b..2a9545c10 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -187,7 +187,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { scatter (idx_3 in range(length(JointCallGVCFs.output_vcf))) { File joint_called_vcf = JointCallGVCFs.output_vcf[idx_3] - File joint_called_vcf_index = JointCallGVCFs.output_vcf[idx_3] + File joint_called_vcf_index = JointCallGVCFs.output_vcf_index[idx_3] call VARUTIL.ApplyVqsr as ApplyVqsr { input: From b0321522f89d9146f4112fa1203fca9faa2c1f44 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 10 Jul 2023 16:48:26 -0400 Subject: [PATCH 172/297] Fixing vcf extension for some outputs. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 2a9545c10..9f36aebc8 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -194,7 +194,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { vcf = joint_called_vcf, vcf_index = joint_called_vcf_index, - prefix = basename(joint_called_vcf, ".vcf") + ".vqsr", + prefix = basename(basename(joint_called_vcf, ".vcf.gz"), ".vcf") + ".vqsr", snps_recalibration = TrainVQSROnHCSnpVariants.recalibration, snps_recalibration_index = TrainVQSROnHCSnpVariants.recalibration_index, @@ -218,7 +218,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { bed_files = select_first([annotation_bed_files]), bed_file_indexes = select_first([annotation_bed_file_indexes]), bed_file_annotation_names = select_first([annotation_bed_file_annotation_names]), - prefix = basename(ApplyVqsr.recalibrated_vcf, ".vcf") + ".region_annotated" + prefix = basename(basename(ApplyVqsr.recalibrated_vcf, ".vcf.gz"), ".vcf") + ".region_annotated", } } From 2453418a66c13e9948552255afc0af874a3a36bc Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 10 Jul 2023 17:01:01 -0400 Subject: [PATCH 173/297] Fixing gzip issue. --- wdl/tasks/FunctionalAnnotation.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/FunctionalAnnotation.wdl b/wdl/tasks/FunctionalAnnotation.wdl index 9a7ae9a89..962261538 100644 --- a/wdl/tasks/FunctionalAnnotation.wdl +++ b/wdl/tasks/FunctionalAnnotation.wdl @@ -23,7 +23,7 @@ task FunctionallyAnnotateVariants { -dataDir $PWD/snpeff_db/data \ PlasmoDB-61_Pfalciparum3D7_Genome \ ~{vcf} \ - | gzip > ~{prefix}.annotated.vcf.gz + | bgzip > ~{prefix}.annotated.vcf.gz mv snpEff_summary.html ~{prefix}.snpEff_summary.html mv snpEff_genes.txt ~{prefix}.snpEff_genes.txt From f127073cbf17bc7e9dba18c0f247de74b906039c Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 11 Jul 2023 09:51:20 -0400 Subject: [PATCH 174/297] Fixed bgzip of SNPEff output. --- wdl/tasks/FunctionalAnnotation.wdl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/FunctionalAnnotation.wdl b/wdl/tasks/FunctionalAnnotation.wdl index 962261538..88cd42374 100644 --- a/wdl/tasks/FunctionalAnnotation.wdl +++ b/wdl/tasks/FunctionalAnnotation.wdl @@ -22,8 +22,9 @@ task FunctionallyAnnotateVariants { -c $PWD/snpeff_db/snpEff.config \ -dataDir $PWD/snpeff_db/data \ PlasmoDB-61_Pfalciparum3D7_Genome \ - ~{vcf} \ - | bgzip > ~{prefix}.annotated.vcf.gz + ~{vcf} > ~{prefix}.annotated.vcf + + bgzip ~{prefix}.annotated.vcf mv snpEff_summary.html ~{prefix}.snpEff_summary.html mv snpEff_genes.txt ~{prefix}.snpEff_genes.txt From a6f857b7afe3aed3d3c520884076a1133947d96e Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 11 Jul 2023 19:21:13 +0000 Subject: [PATCH 175/297] Adding functional annotation docker. --- docker/lr-functional-annotation/Dockerfile | 70 ++++++++++++++++++++++ docker/lr-functional-annotation/Makefile | 17 ++++++ 2 files changed, 87 insertions(+) create mode 100644 docker/lr-functional-annotation/Dockerfile create mode 100644 docker/lr-functional-annotation/Makefile diff --git a/docker/lr-functional-annotation/Dockerfile b/docker/lr-functional-annotation/Dockerfile new file mode 100644 index 000000000..fbbc2a850 --- /dev/null +++ b/docker/lr-functional-annotation/Dockerfile @@ -0,0 +1,70 @@ +# Start with a good base python3 image: +FROM ubuntu:20.04 +MAINTAINER Jonn Smith + +# Make sure we don't need to interact with any package installations: +ARG DEBIAN_FRONTEND=noninteractive + +# Set the working directory to / +WORKDIR / + +################################################################################ +# Install system packages: + +# Make sure we can actually talk to package repos: +RUN apt-get update +RUN apt-get install -y apt-utils +RUN apt-get -y upgrade + +RUN apt-get update && apt-get -y upgrade + +# Development / prereqs for building software: +# Utilities / tools: +# Get libcurses: +# install gsutil requirements: +RUN apt-get -y install make gcc g++ autoconf sudo && \ + apt-get -y install git bash vim time bc sed perl wget curl bzip2 man unzip && \ + apt-get -y install liblzma-dev libbz2-dev libncurses5-dev libncursesw5-dev && \ + apt-get --allow-releaseinfo-change update && \ + apt install -y curl git-lfs time datamash + +# Install python: +RUN apt-get install -y python python3 python3-pip + +# Setup crcmodc for gsutil: +RUN apt-get install -y gcc python3-dev python3-setuptools && \ + pip3 uninstall -y crcmod && \ + pip3 install --no-cache-dir -U crcmod + +# install gsutil: +RUN curl https://sdk.cloud.google.com | bash + +# Get Java: +# Install OpenJDK-8 +RUN apt-get update && \ + apt-get install -y openjdk-11-jdk && \ + apt-get install -y ant && \ + apt-get clean + +# Fix certificate issues +RUN apt-get update && \ + apt-get install ca-certificates-java && \ + apt-get clean && \ + update-ca-certificates -f + +# Setup JAVA_HOME -- useful for docker commandline +ENV JAVA_HOME /usr/lib/jvm/java-11-openjdk-amd64/ +RUN export JAVA_HOME + +# Get snPEff +RUN wget https://snpeff.blob.core.windows.net/versions/snpEff_latest_core.zip && \ + unzip snpEff_latest_core.zip + +# Get tabix (for bgzip): +RUN apt-get install -y tabix + +################################################################################ +# Final runtime configuration: +# Let's start at the root: +WORKDIR / + diff --git a/docker/lr-functional-annotation/Makefile b/docker/lr-functional-annotation/Makefile new file mode 100644 index 000000000..0d0aa4764 --- /dev/null +++ b/docker/lr-functional-annotation/Makefile @@ -0,0 +1,17 @@ +IMAGE_NAME = lr-functional-annotation +VERSION = 0.0.1 + +TAG1 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):$(VERSION) +TAG2 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):latest + +all: | build push + +build: + docker build -t $(TAG1) -t $(TAG2) . + +build_no_cache: + docker build --no-cache -t $(TAG1) -t $(TAG2) . + +push: + docker push $(TAG1) + docker push $(TAG2) From d19b8958a3337b4d48c523424bb1aae8865a37be Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 11 Jul 2023 15:17:31 -0400 Subject: [PATCH 176/297] Moving snpEff to a new docker image. --- wdl/tasks/FunctionalAnnotation.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/FunctionalAnnotation.wdl b/wdl/tasks/FunctionalAnnotation.wdl index 88cd42374..6afafc4ba 100644 --- a/wdl/tasks/FunctionalAnnotation.wdl +++ b/wdl/tasks/FunctionalAnnotation.wdl @@ -18,7 +18,7 @@ task FunctionallyAnnotateVariants { gunzip -c ~{snpeff_db} | tar xvf - - /usr/local/bin/snpEff ann -v \ + /snpEff/scripts/snpEff ann -v \ -c $PWD/snpeff_db/snpEff.config \ -dataDir $PWD/snpeff_db/data \ PlasmoDB-61_Pfalciparum3D7_Genome \ @@ -44,7 +44,7 @@ task FunctionallyAnnotateVariants { boot_disk_gb: 10, preemptible_tries: 2, max_retries: 1, - docker: "quay.io/biocontainers/snpeff:5.1d--hdfd78af_0" + docker: "us.gcr.io/broad-dsp-lrma/lr-functional-annotation:0.0.1" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { From a5d05139e6e55835a6572456eaeddab0665de413 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 11 Jul 2023 20:55:55 -0400 Subject: [PATCH 177/297] Another fix for the bgzip issue in functional annotation. --- wdl/tasks/FunctionalAnnotation.wdl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wdl/tasks/FunctionalAnnotation.wdl b/wdl/tasks/FunctionalAnnotation.wdl index 6afafc4ba..09d6061ac 100644 --- a/wdl/tasks/FunctionalAnnotation.wdl +++ b/wdl/tasks/FunctionalAnnotation.wdl @@ -10,7 +10,7 @@ task FunctionallyAnnotateVariants { RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 4*ceil(size([vcf, snpeff_db], "GB")) + Int disk_size = 1 + 5*ceil(size([vcf, snpeff_db], "GB")) String prefix = basename(basename(vcf, ".gz"), ".vcf") command <<< @@ -22,9 +22,7 @@ task FunctionallyAnnotateVariants { -c $PWD/snpeff_db/snpEff.config \ -dataDir $PWD/snpeff_db/data \ PlasmoDB-61_Pfalciparum3D7_Genome \ - ~{vcf} > ~{prefix}.annotated.vcf - - bgzip ~{prefix}.annotated.vcf + ~{vcf} | bgzip > ~{prefix}.annotated.vcf.gz mv snpEff_summary.html ~{prefix}.snpEff_summary.html mv snpEff_genes.txt ~{prefix}.snpEff_genes.txt From 3245b2eee7ac259f1d70f66de828b994d2c175d9 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 12 Jul 2023 06:17:47 -0400 Subject: [PATCH 178/297] Updated GatherVCFs task to emit vcf.gz files. --- wdl/tasks/VariantUtils.wdl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index b2327f4a3..fcf0839d4 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1360,13 +1360,13 @@ task GatherVcfs { GatherVcfsCloud \ --ignore-safety-checks \ --input ~{sep=" --input " input_vcfs} \ - --output ~{prefix}.vcf + --output ~{prefix}.vcf.gz - tabix ~{prefix}.vcf + tabix ~{prefix}.vcf.gz >>> output { - File output_vcf = "~{prefix}.vcf" + File output_vcf = "~{prefix}.vcf.gz" File output_vcf_index = "~{prefix}.vcf.tbi" } From da809dc3c5de0411d14f0e6973f035d0e3cbede1 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 12 Jul 2023 07:58:04 -0400 Subject: [PATCH 179/297] Removed explicit tabix index from GatherVCFsCloud. --- wdl/tasks/VariantUtils.wdl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index fcf0839d4..75d176958 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1351,7 +1351,7 @@ task GatherVcfs { } command <<< - set -euo pipefail + set -euxo pipefail # --ignore-safety-checks makes a big performance difference so we include it in our invocation. # This argument disables expensive checks that the file headers contain the same set of @@ -1361,8 +1361,6 @@ task GatherVcfs { --ignore-safety-checks \ --input ~{sep=" --input " input_vcfs} \ --output ~{prefix}.vcf.gz - - tabix ~{prefix}.vcf.gz >>> output { From b5636c069e3775bde7f72d8f037e437767afbb48 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 12 Jul 2023 08:39:59 -0400 Subject: [PATCH 180/297] Adding debugging code. --- wdl/tasks/VariantUtils.wdl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 75d176958..9f2eef703 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1361,6 +1361,8 @@ task GatherVcfs { --ignore-safety-checks \ --input ~{sep=" --input " input_vcfs} \ --output ~{prefix}.vcf.gz + + ls -la >>> output { @@ -1370,7 +1372,7 @@ task GatherVcfs { ######################### RuntimeAttr default_attr = object { - cpu_cores: 1, + cpu_cores: 2, mem_gb: 8, disk_gb: disk_size, boot_disk_gb: 10, From f9efd97f16518dc0f864bc52d9832a0d7bae2ef1 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 12 Jul 2023 16:00:29 -0400 Subject: [PATCH 181/297] Added back in the `tabix` indexing. --- wdl/tasks/VariantUtils.wdl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 9f2eef703..680944cdd 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1362,6 +1362,8 @@ task GatherVcfs { --input ~{sep=" --input " input_vcfs} \ --output ~{prefix}.vcf.gz + tabix -p vcf ~{prefix}.vcf.gz + ls -la >>> From b9ba8926a5e567d4e3b3921aafd87c0e70a2f511 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 12 Jul 2023 17:28:02 -0400 Subject: [PATCH 182/297] Fixing elusive typo in outputs for `GatherVCFs` --- wdl/tasks/VariantUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 680944cdd..3e76ad8f9 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1369,7 +1369,7 @@ task GatherVcfs { output { File output_vcf = "~{prefix}.vcf.gz" - File output_vcf_index = "~{prefix}.vcf.tbi" + File output_vcf_index = "~{prefix}.vcf.gz.tbi" } ######################### From 5450c7156f5258d38b30fd98621956a97f3b4ccb Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 13 Jul 2023 16:28:27 -0400 Subject: [PATCH 183/297] Added output for annotated VCF. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 9f36aebc8..82da3c29b 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -305,9 +305,11 @@ workflow SRJointCallGVCFsWithGenomicsDB { # call FF.FinalizeToFile as FinalizeRegionAnnotatedVcfIndex { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf_index]) } # } - ########## - # store the results into designated bucket - ########## + # Make an alias for the functionally annotated data: + if (defined(snpeff_db)) { + File annotated_vcf = FinalizeVQSRVCF.gcs_path + File annotated_vcf_tbi = FinalizeVQSRTBI.gcs_path + } output { # File genomicsDB = FinalizeGenomicsDB.gcs_path @@ -328,8 +330,8 @@ workflow SRJointCallGVCFsWithGenomicsDB { File joint_recalibrated_vcf = FinalizeVQSRVCF.gcs_path File joint_recalibrated_vcf_tbi = FinalizeVQSRTBI.gcs_path -# File? annotated_joint_vcf = AnnotateVcfRegions.annotated_vcf -# File? annotated_joint_vcf_tbi = AnnotateVcfRegions.annotated_vcf_index + File? annotated_joint_vcf = annotated_vcf + File? annotated_joint_vcf_tbi = annotated_vcf_tbi File joint_mt = CreateHailMatrixTable.gcs_path File joint_zarr = ConvertToZarr.gcs_path From c15514fbc73ca507acb47c4c1f497642061b5f47 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 13 Jul 2023 17:11:58 -0400 Subject: [PATCH 184/297] Fixed outputs for SRJointCallGVCFsWithGenomicsDB.wdl --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 43 ++++++++++++-------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 82da3c29b..247dd4fe2 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -282,29 +282,24 @@ workflow SRJointCallGVCFsWithGenomicsDB { # Finalize: File keyfile = CreateHailMatrixTable.monitoring_log -# call FF.FinalizeToDir as FinalizeGenomicsDB { input: outdir = outdir + "/GenomicsDB", keyfile = keyfile, file = ImportGVCFsIntoGenomicsDB.output_genomicsdb } + call FF.FinalizeToDir as FinalizeGenomicsDB { input: outdir = outdir + "/GenomicsDB", keyfile = keyfile, files = ImportGVCFsIntoGenomicsDB.output_genomicsdb } call FF.FinalizeToFile as FinalizeRawVCF { input: outdir = outdir, keyfile = keyfile, file = GatherRawVcfs.output_vcf } call FF.FinalizeToFile as FinalizeRawTBI { input: outdir = outdir, keyfile = keyfile, file = GatherRawVcfs.output_vcf_index } -# call FF.FinalizeToDir as FinalizeIndelRecalFile { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } -# call FF.FinalizeToDir as FinalizeIndelRecalIndex { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } -# call FF.FinalizeToDir as FinalizeIndelRecalTranches { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } -# call FF.FinalizeToDir as FinalizeIndelRecalModelReport { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } + call FF.FinalizeToFile as FinalizeIndelRecalFile { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } + call FF.FinalizeToFile as FinalizeIndelRecalIndex { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } + call FF.FinalizeToFile as FinalizeIndelRecalTranches { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } + call FF.FinalizeToFile as FinalizeIndelRecalModelReport { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } -# call FF.FinalizeToDir as FinalizeSnpRecalFile { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } -# call FF.FinalizeToDir as FinalizeSnpRecalIndex { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } -# call FF.FinalizeToDir as FinalizeSnpRecalTranches { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } -# call FF.FinalizeToDir as FinalizeSnpRecalModelReport { input: outdir = outdir + "/recalibration_files, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } + call FF.FinalizeToFile as FinalizeSnpRecalFile { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } + call FF.FinalizeToFile as FinalizeSnpRecalIndex { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } + call FF.FinalizeToFile as FinalizeSnpRecalTranches { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } + call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } call FF.FinalizeToFile as FinalizeVQSRVCF { input: outdir = outdir, keyfile = keyfile, file = GatherRecalibratedVcfs.output_vcf } call FF.FinalizeToFile as FinalizeVQSRTBI { input: outdir = outdir, keyfile = keyfile, file = GatherRecalibratedVcfs.output_vcf_index } -# if (defined(annotation_bed_files)) { -# call FF.FinalizeToFile as FinalizeRegionAnnotatedVcf { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf]) } -# call FF.FinalizeToFile as FinalizeRegionAnnotatedVcfIndex { input: outdir = outdir, keyfile = keyfile, file = select_first([AnnotateVcfRegions.annotated_vcf_index]) } -# } - # Make an alias for the functionally annotated data: if (defined(snpeff_db)) { File annotated_vcf = FinalizeVQSRVCF.gcs_path @@ -312,20 +307,20 @@ workflow SRJointCallGVCFsWithGenomicsDB { } output { -# File genomicsDB = FinalizeGenomicsDB.gcs_path + String genomicsDB = FinalizeGenomicsDB.gcs_dir File raw_joint_vcf = FinalizeRawVCF.gcs_path File raw_joint_vcf_tbi = FinalizeRawTBI.gcs_path -# Array[File?] vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path -# Array[File?] vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path -# Array[File?] vqsr_indel_recal_tranches = FinalizeIndelRecalTranches.gcs_path -# Array[File?] vqsr_indel_recal_model_report = FinalizeIndelRecalModelReport.gcs_path -# -# Array[File?] vqsr_snp_recal_file = FinalizeSnpRecalFile.gcs_path -# Array[File?] vqsr_snp_recal_file_index = FinalizeSnpRecalIndex.gcs_path -# Array[File?] vqsr_snp_recal_tranches = FinalizeSnpRecalTranches.gcs_path -# Array[File?] vqsr_snp_recal_model_report = FinalizeSnpRecalModelReport.gcs_path + File vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path + File vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path + File vqsr_indel_recal_tranches = FinalizeIndelRecalTranches.gcs_path + File vqsr_indel_recal_model_report = FinalizeIndelRecalModelReport.gcs_path + + File vqsr_snp_recal_file = FinalizeSnpRecalFile.gcs_path + File vqsr_snp_recal_file_index = FinalizeSnpRecalIndex.gcs_path + File vqsr_snp_recal_tranches = FinalizeSnpRecalTranches.gcs_path + File vqsr_snp_recal_model_report = FinalizeSnpRecalModelReport.gcs_path File joint_recalibrated_vcf = FinalizeVQSRVCF.gcs_path File joint_recalibrated_vcf_tbi = FinalizeVQSRTBI.gcs_path From a75c71498da5997bf3bb8e097fb18818dfbdaa9d Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 18 Jul 2023 14:14:03 -0400 Subject: [PATCH 185/297] Fixing issue in SRWholeGenome caused by previous commit. --- wdl/SRWholeGenome.wdl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 27eb9dc1a..f0b6a3a02 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -191,8 +191,8 @@ workflow SRWholeGenome { call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { input: - vcf = CallVariantsWithHaplotypeCaller.output_vcf, - vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + vcfs = [CallVariantsWithHaplotypeCaller.output_vcf], + vcf_indices = [CallVariantsWithHaplotypeCaller.output_vcf_index], prefix = participant_name + ".indels", recalibration_tranche_values = indel_recalibration_tranche_values, recalibration_annotation_values = indel_recalibration_annotation_values, @@ -216,8 +216,8 @@ workflow SRWholeGenome { call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { input: - vcf = CallVariantsWithHaplotypeCaller.output_vcf, - vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + vcfs = [CallVariantsWithHaplotypeCaller.output_vcf], + vcf_indices = [CallVariantsWithHaplotypeCaller.output_vcf_index], prefix = participant_name + ".snps", recalibration_tranche_values = snp_recalibration_tranche_values, recalibration_annotation_values = snp_recalibration_annotation_values, From 2f9994dbc4309e4b81703eda05a85ab0113d2385 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 20 Jul 2023 09:56:03 -0400 Subject: [PATCH 186/297] Fixing critical bug in `SRUtils::Bam2Fq` --- wdl/tasks/SRUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index e8800ebc0..230e06573 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -33,7 +33,7 @@ task BamToFq { output { File fq_end1 = "~{prefix}.end1.fq.gz" - File fq_end2 = "~{prefix}.end1.fq.gz" + File fq_end2 = "~{prefix}.end2.fq.gz" File fq_unpaired = "~{prefix}.unpaired.fq.gz" File monitoring_log = "resources.log" } From b79a1fbf1c942bc7390f3add9fa61a9e6be3b425 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 20 Jul 2023 12:44:49 -0400 Subject: [PATCH 187/297] Fixed corner case in `Finalize::FinalizeToDir` --- wdl/tasks/Finalize.wdl | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/wdl/tasks/Finalize.wdl b/wdl/tasks/Finalize.wdl index fdebb1998..434415bff 100644 --- a/wdl/tasks/Finalize.wdl +++ b/wdl/tasks/Finalize.wdl @@ -82,11 +82,27 @@ task FinalizeToDir { command <<< set -euxo pipefail - cat ~{write_lines(files)} | gsutil -m cp -I "~{gcs_output_dir}" + # Only copy files that are not the same as their destinations. + # This is a very far corner case but can come up if you're finalizing input files + # and you're re-running data (e.g. re-running `SRFlowcell`). + + clean_out_dir=$(echo "~{gcs_output_dir}" | sed 's@/[ \t]*$@@') + + while read src_file_path ; do + bn=$(basename ${src_file_path}) + if [[ "${src_file_path}" == "${clean_out_dir}/${bn}" ]] ; then + echo "Source and destination file paths are the same. Skipping file: ${src_file_path}" + else + echo "${src_file_path}" + fi + done < ~{write_lines(files)} > sanitized_file_list.txt + + cat sanitized_file_list.txt | gsutil -m cp -I "~{gcs_output_dir}" >>> output { String gcs_dir = gcs_output_dir + File copied_files_list = "sanitized_file_list.txt" } ######################### From 1f5f75c6b6caea6a07ebc825781ae581a9b58eaa Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 20 Jul 2023 12:53:14 -0400 Subject: [PATCH 188/297] Whitespace change to make dockstore register changes. --- wdl/SRFlowcell.wdl | 1 + 1 file changed, 1 insertion(+) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 9a7bff453..0c7f6eef1 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -293,6 +293,7 @@ workflow SRFlowcell { file = t_012_FastQC.report } + # Prep a few files for output: if (defined(bam)) { File unaligned_bam_o = reads_dir + "/unaligned/" + basename(select_first([bam])) From d62f6041111c88e243955c7c4f062f3c103fd21b Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 20 Jul 2023 16:03:01 -0400 Subject: [PATCH 189/297] Fixing issue with bash string comparisons in `Finalize::FinalizeToDir` --- wdl/SRFlowcell.wdl | 2 +- wdl/tasks/Finalize.wdl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 0c7f6eef1..d58fefb8d 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -293,7 +293,7 @@ workflow SRFlowcell { file = t_012_FastQC.report } - + # Prep a few files for output: if (defined(bam)) { File unaligned_bam_o = reads_dir + "/unaligned/" + basename(select_first([bam])) diff --git a/wdl/tasks/Finalize.wdl b/wdl/tasks/Finalize.wdl index 434415bff..3a602e3f2 100644 --- a/wdl/tasks/Finalize.wdl +++ b/wdl/tasks/Finalize.wdl @@ -90,7 +90,7 @@ task FinalizeToDir { while read src_file_path ; do bn=$(basename ${src_file_path}) - if [[ "${src_file_path}" == "${clean_out_dir}/${bn}" ]] ; then + if [[ ${src_file_path} == ${clean_out_dir}/${bn} ]] ; then echo "Source and destination file paths are the same. Skipping file: ${src_file_path}" else echo "${src_file_path}" From fd1ed84b92d49410318ea2209e0a56eb3e32fb99 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 20 Jul 2023 17:39:55 -0400 Subject: [PATCH 190/297] Minor bugfix. --- wdl/tasks/Finalize.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/Finalize.wdl b/wdl/tasks/Finalize.wdl index 3a602e3f2..56d0798cb 100644 --- a/wdl/tasks/Finalize.wdl +++ b/wdl/tasks/Finalize.wdl @@ -91,7 +91,7 @@ task FinalizeToDir { while read src_file_path ; do bn=$(basename ${src_file_path}) if [[ ${src_file_path} == ${clean_out_dir}/${bn} ]] ; then - echo "Source and destination file paths are the same. Skipping file: ${src_file_path}" + echo "Source and destination file paths are the same. Skipping file: ${src_file_path}" 1>&2 else echo "${src_file_path}" fi From 03232d11f7fd775d57b61e7ac05f0d82847673ec Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 1 Aug 2023 12:11:46 -0400 Subject: [PATCH 191/297] Added wdl to train CNNScoreVariants. --- wdl/tasks/utils/TrainCnnFilters.wdl | 331 ++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 wdl/tasks/utils/TrainCnnFilters.wdl diff --git a/wdl/tasks/utils/TrainCnnFilters.wdl b/wdl/tasks/utils/TrainCnnFilters.wdl new file mode 100644 index 000000000..f7c46bcf7 --- /dev/null +++ b/wdl/tasks/utils/TrainCnnFilters.wdl @@ -0,0 +1,331 @@ +version 1.0 + +workflow TrainCnnFilters { + meta { + author: "Jonn Smith" + description: "A workflow for training the 1D and 2D CNN filtration methods in GATK." + } + + input { + + } + + output { + + } +} + + +task Create1DReferenceTensors { + + meta { + author: "Jonn Smith" + description: "Task to create 1D reference tensors for the 1D CNN." + } + + input { + File vcf_input + + File ref_fasta + File ref_fasta_fai + File ref_dict + + File truth_vcf + File truth_bed + + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size(vcf_input, "GB") + + size(ref_fasta, "GB") + size(ref_fasta_fai, "GB") + size(ref_dict, "GB") + + size(truth_vcf, "GB") + + size(truth_bed, "GB") + ) + + command <<< + set -euxo pipefail + + export MONITOR_MOUNT_POINT="/cromwell_root" + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + + gatk CNNVariantWriteTensors \ + -R ~{ref_fasta} \ + -V ~{vcf_input} \ + -truth-vcf ~{truth_vcf} \ + -truth-bed ~{truth_bed} \ + -tensor-type reference \ + --downsample-snps 1 \ + --downsample-indels 1 \ + --max-tensors 10000000 \ + -output-tensor-dir ~{prefix}_1D_tensor_dir + + # No need to zip - the files are .hd5 formatted: + tar -cf ~{prefix}_1D_tensor_dir.tar ~{prefix}_1D_tensor_dir + + kill $monitoring_pid + >>> + + output { + File monitoring_log = "resources.log" + File tensor_dir_tar = "~{prefix}_1D_tensor_dir.tar" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task Create2DReadTensors { + + meta { + author: "Jonn Smith" + description: "Task to create 2D read tensors for the 2D CNN." + } + + input { + File bam_input + File vcf_input + + File ref_fasta + File ref_fasta_fai + File ref_dict + + File truth_vcf + File truth_bed + + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 4*ceil(size(bam_input, "GB") + + size(vcf_input, "GB") + + size(ref_fasta, "GB") + size(ref_fasta_fai, "GB") + size(ref_dict, "GB") + + size(truth_vcf, "GB") + + size(truth_bed, "GB") + ) + + command <<< + set -euxo pipefail + + export MONITOR_MOUNT_POINT="/cromwell_root" + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + + gatk CNNVariantWriteTensors \ + -R ~{ref_fasta} \ + -V ~{vcf_input} \ + -bam-file ~{bam_input} \ + -truth-vcf ~{truth_vcf} \ + -truth-bed ~{truth_bed} \ + -tensor-type read_tensor \ + --downsample-snps 1 \ + --downsample-indels 1 \ + --max-tensors 10000000 \ + -output-tensor-dir ~{prefix}_2D_tensor_dir + + # No need to zip - the files are .hd5 formatted: + tar -cf ~{prefix}_2D_tensor_dir.tar ~{prefix}_2D_tensor_dir + + kill $monitoring_pid + >>> + + output { + File monitoring_log = "resources.log" + File tensor_dir_tar = "~{prefix}_2D_tensor_dir.tar" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task TrainCnn1D { + + meta { + author: "Jonn Smith" + description: "Task to train the 1D CNN with 1D tensors" + } + + input { + Array[File] tensor_tars + + Int epochs = 100 + Int training_steps = 100 + Int validation_steps = 6 + + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 30*ceil(size(tensor_tars, "GB")) + + command <<< + set -euxo pipefail + + export MONITOR_MOUNT_POINT="/cromwell_root" + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + + # Must pre-process the given tensor_tars into a single folder: + + ${GATK} CNNVariantTrain \ + -tensor-type reference \ + --epochs ~{epochs} \ + --training-steps ~{training_steps} \ + --validation-steps ~{validation_steps} \ + -input-tensor-dir p_falciparum_ref_model_tensors_all_variants/ \ + -model-name ~{prefix}_CNN_1D_model \ + + kill $monitoring_pid + >>> + + output { + File monitoring_log = "resources.log" + File model_hd5 = "~{prefix}_CNN_1D_model.hd5" + File model_json = "~{prefix}_CNN_1D_model.json" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task TrainCnn { + + meta { + author: "Jonn Smith" + description: "Task to train the CNN." + } + + input { + Array[File] tensor_tars + String tensor_type # Can be either "reference" or "read_tensor" + + Int epochs = 100 + Int training_steps = 100 + Int validation_steps = 6 + + String prefix = "out" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 30*ceil(size(tensor_tars, "GB")) + + command <<< + set -euxo pipefail + + export MONITOR_MOUNT_POINT="/cromwell_root" + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + + # Must pre-process the given tensor_tars into a single folder: + mkdir tensors + cd tensors + while read f ; do + tar --strip-components 1 -xf $f + done < ${write_lines(tensor_tars)} + cd ../ + + ${GATK} CNNVariantTrain \ + -tensor-type reference \ + --epochs ~{epochs} \ + --training-steps ~{training_steps} \ + --validation-steps ~{validation_steps} \ + -input-tensor-dir tensors/ \ + -model-name ~{prefix}_CNN_~{tensor_type}_model \ + + kill $monitoring_pid + >>> + + output { + File monitoring_log = "resources.log" + File model_hd5 = "~{prefix}_CNN_~{tensor_type}_model.hd5" + File model_json = "~{prefix}_CNN_~{tensor_type}_model.json" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} \ No newline at end of file From b6c61e3ef9ce58586803d4fb51beca9d58c45641 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 1 Aug 2023 15:33:44 -0400 Subject: [PATCH 192/297] Added wdl to train CNN filters. --- wdl/{tasks/utils => }/TrainCnnFilters.wdl | 181 +++++++++++++--------- 1 file changed, 108 insertions(+), 73 deletions(-) rename wdl/{tasks/utils => }/TrainCnnFilters.wdl (66%) diff --git a/wdl/tasks/utils/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl similarity index 66% rename from wdl/tasks/utils/TrainCnnFilters.wdl rename to wdl/TrainCnnFilters.wdl index f7c46bcf7..9f246df88 100644 --- a/wdl/tasks/utils/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -1,5 +1,9 @@ version 1.0 +import "tasks/Structs.wdl" as Structs +import "tasks/Utils.wdl" as Utils +import "tasks/Finalize.wdl" as FF + workflow TrainCnnFilters { meta { author: "Jonn Smith" @@ -7,15 +11,110 @@ workflow TrainCnnFilters { } input { + Array[File] vcfs + Array[File] vcf_indices + + Array[File] bams + Array[File] bais + + Array[File] truth_vcfs + Array[File] truth_vcf_indices + Array[File] truth_beds + + File ref_map_file + + String prefix = "out" + } + + parameter_meta { + vcfs: "GCS path to VCF files containing called variants on which to train / test / validate the CNN models." + vcf_indices: "GCS path to index files for called variants on which to train / test / validate the CNN models." + + bams: "GCS path to bam files containing the either the mapped reads from which variants were called, or a bam-out from the variant caller that produced the input VCF files." + bais: "GCS path to index files for the bam files containing the either the mapped reads from which variants were called, or a bam-out from the variant caller that produced the input VCF files." + + truth_vcfs: "GCS path to VCF files containing validated variant calls (\"truth\") for the corresponding called variants in `vcfs`." + truth_vcf_indices: "GCS path to index files for VCF files containing validated variant calls (\"truth\") for the corresponding called variants in `vcfs`." + truth_beds: "GCS path to bed files with confident regions for the given `truth_vcfs`" + + ref_map_file: "table indicating reference sequence and auxillary file locations" + } + + # Get ref info: + Map[String, String] ref_map = read_map(ref_map_file) + + # TODO: Validate that lengths of all inputs are the same: + if ((length(vcfs) != length(vcf_indices)) || (length(vcfs) != length(vcf_indices)) || (length(vcfs) != length(vcf_indices)) || (length(vcfs) != length(vcf_indices)) || (length(vcfs) != length(vcf_indices))) { + call Utils.FailWithWarning {input: warning="Not all input arrays have the same length!"} + } + + # First create tensors for the input data: + scatter (idx_1 in range(length(vcfs))) { + # 1D CNN: + call Create1DReferenceTensors { + input: + vcf_input = vcfs[idx_1], + vcf_idx = vcf_indices[idx_1], + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map["fai"], + ref_dict = ref_map['dict'], + truth_vcf = truth_vcfs[idx_1], + truth_vcf_idx = truth_vcf_indices[idx_1], + truth_bed = truth_beds[idx_1], + prefix = prefix + "_shard_" + idx_1 + "reference" + } + # 2D CNN: + call Create2DReadTensors { + input: + bam_input = bams[idx_1], + bai_input = bais[idx_1], + vcf_input = vcfs[idx_1], + vcf_idx = vcf_indices[idx_1], + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map["fai"], + ref_dict = ref_map['dict'], + truth_vcf = truth_vcfs[idx_1], + truth_vcf_idx = truth_vcf_indices[idx_1], + truth_bed = truth_beds[idx_1], + prefix = prefix + "_shard_" + idx_1 + "reference" + } + } + # Train the models with the created Tensors: + # CNN 1D: + call TrainCnn as TrainCnn1D { + input: + tensor_tars = Create1DReferenceTensors.tensor_dir_tar, + tensor_type = "reference", + epochs = 100, + training_steps = 100, + validation_steps = 6, + prefix = prefix + "_CNN_1D_Model" + } + + # CNN 2D: + call TrainCnn as TrainCnn2D { + input: + tensor_tars = Create2DReadTensors.tensor_dir_tar, + tensor_type = "read_tensor", + epochs = 100, + training_steps = 100, + validation_steps = 6, + prefix = prefix + "_CNN_2D_Model" } output { + Array[File] cnn_1d_tensors = Create1DReferenceTensors.tensor_dir_tar + Array[File] cnn_2d_tensors = Create2DReadTensors.tensor_dir_tar + + File cnn_1d_model_json = TrainCnn1D.model_json + File cnn_1d_model_hd5 = TrainCnn1D.model_hd5 + File cnn_2d_model_json = TrainCnn2D.model_json + File cnn_2d_model_hd5 = TrainCnn2D.model_hd5 } } - task Create1DReferenceTensors { meta { @@ -25,12 +124,14 @@ task Create1DReferenceTensors { input { File vcf_input + File vcf_idx File ref_fasta File ref_fasta_fai File ref_dict File truth_vcf + File truth_vcf_idx File truth_bed String prefix = "out" @@ -106,13 +207,17 @@ task Create2DReadTensors { input { File bam_input + File bai_input + File vcf_input + File vcf_idx File ref_fasta File ref_fasta_fai File ref_dict File truth_vcf + File truth_vcf_idx File truth_bed String prefix = "out" @@ -181,77 +286,6 @@ task Create2DReadTensors { } } -task TrainCnn1D { - - meta { - author: "Jonn Smith" - description: "Task to train the 1D CNN with 1D tensors" - } - - input { - Array[File] tensor_tars - - Int epochs = 100 - Int training_steps = 100 - Int validation_steps = 6 - - String prefix = "out" - - RuntimeAttr? runtime_attr_override - } - - Int disk_size = 1 + 30*ceil(size(tensor_tars, "GB")) - - command <<< - set -euxo pipefail - - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - - # Must pre-process the given tensor_tars into a single folder: - - ${GATK} CNNVariantTrain \ - -tensor-type reference \ - --epochs ~{epochs} \ - --training-steps ~{training_steps} \ - --validation-steps ~{validation_steps} \ - -input-tensor-dir p_falciparum_ref_model_tensors_all_variants/ \ - -model-name ~{prefix}_CNN_1D_model \ - - kill $monitoring_pid - >>> - - output { - File monitoring_log = "resources.log" - File model_hd5 = "~{prefix}_CNN_1D_model.hd5" - File model_json = "~{prefix}_CNN_1D_model.json" - } - - ######################### - RuntimeAttr default_attr = object { - cpu_cores: 4, - mem_gb: 32, - disk_gb: disk_size, - boot_disk_gb: 10, - preemptible_tries: 1, - max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" - } - RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) - runtime { - cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) - memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" - disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" - bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) - preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) - maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) - docker: select_first([runtime_attr.docker, default_attr.docker]) - } -} - task TrainCnn { meta { @@ -272,7 +306,8 @@ task TrainCnn { RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 30*ceil(size(tensor_tars, "GB")) + # We need a lot of disk space here for the unpacked tensors and final model: + Int disk_size = 1 + 4*ceil(size(tensor_tars, "GB")) command <<< set -euxo pipefail From 6a7cafbd8365b1f321139392cf5e670e067290fb Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 2 Aug 2023 11:27:50 -0400 Subject: [PATCH 193/297] Added in more memory for MarkDuplicates. --- wdl/tasks/SRUtils.wdl | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 230e06573..960f444c5 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -359,7 +359,6 @@ task MarkDuplicates { } Int compression_level = 2 - Int java_memory_size_mb = 30768 Int disk_size = 1 + 4*ceil(size(input_bam, "GB")) @@ -375,32 +374,35 @@ task MarkDuplicates { ./monitoring_script.sh &> resources.log & monitoring_pid=$! - java -Dsamjdk.compression_level=~{compression_level} -Xms~{java_memory_size_mb}m -jar /usr/picard/picard.jar \ - MarkDuplicates \ - INPUT=~{input_bam} \ - OUTPUT=~{prefix}.bam \ - METRICS_FILE=~{prefix}.metrics.txt \ - VALIDATION_STRINGENCY=SILENT \ - ~{"READ_NAME_REGEX=" + read_name_regex} \ - ~{"SORTING_COLLECTION_SIZE_RATIO=" + sorting_collection_size_ratio} \ - OPTICAL_DUPLICATE_PIXEL_DISTANCE=2500 \ - ASSUME_SORT_ORDER="queryname" \ - CLEAR_DT="false" \ - ADD_PG_TAG_TO_READS=false + tot_mem_mb=$(free -m | grep '^Mem' | awk '{print $2}') + let java_memory_size_mb=${tot_mem_mb}-2048 + + java -Dsamjdk.compression_level=~{compression_level} -Xms${java_memory_size_mb}m -jar /usr/picard/picard.jar \ + MarkDuplicates \ + INPUT=~{input_bam} \ + OUTPUT=~{prefix}.bam \ + METRICS_FILE=~{prefix}.metrics.txt \ + VALIDATION_STRINGENCY=SILENT \ + ~{"READ_NAME_REGEX=" + read_name_regex} \ + ~{"SORTING_COLLECTION_SIZE_RATIO=" + sorting_collection_size_ratio} \ + OPTICAL_DUPLICATE_PIXEL_DISTANCE=2500 \ + ASSUME_SORT_ORDER="queryname" \ + CLEAR_DT="false" \ + ADD_PG_TAG_TO_READS=false kill $monitoring_pid } output { + File monitoring_log = "resources.log" File bam = "~{prefix}.bam" File metrics = "~{prefix}.metrics.txt" - File monitoring_log = "resources.log" } ######################### RuntimeAttr default_attr = object { cpu_cores: 16, - mem_gb: 32, + mem_gb: 48, disk_gb: disk_size, boot_disk_gb: 10, preemptible_tries: 1, From ddfe2a030049041ad14dcc2061afe1e5ddc4c2e1 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 2 Aug 2023 15:05:03 -0400 Subject: [PATCH 194/297] Fixing memory for `MarkDuplicates`. --- wdl/tasks/SRUtils.wdl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 960f444c5..35179a7ca 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -366,7 +366,7 @@ task MarkDuplicates { # This works because the output of BWA is query-grouped and therefore, so is the output of MergeBamAlignment. # While query-grouped isn't actually query-sorted, it's good enough for MarkDuplicates with ASSUME_SORT_ORDER="queryname" - command { + command <<< export MONITOR_MOUNT_POINT="/cromwell_root" curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh @@ -391,7 +391,7 @@ task MarkDuplicates { ADD_PG_TAG_TO_READS=false kill $monitoring_pid - } + >>> output { File monitoring_log = "resources.log" @@ -402,7 +402,7 @@ task MarkDuplicates { ######################### RuntimeAttr default_attr = object { cpu_cores: 16, - mem_gb: 48, + mem_gb: 32, disk_gb: disk_size, boot_disk_gb: 10, preemptible_tries: 1, From 0fa9d7fc33439b74d3a5475e79cf83b33797cc4d Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 2 Aug 2023 15:55:29 -0400 Subject: [PATCH 195/297] Giving more headroom for OS processes. --- wdl/tasks/SRUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 35179a7ca..1492c4ef3 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -375,7 +375,7 @@ task MarkDuplicates { monitoring_pid=$! tot_mem_mb=$(free -m | grep '^Mem' | awk '{print $2}') - let java_memory_size_mb=${tot_mem_mb}-2048 + let java_memory_size_mb=${tot_mem_mb}-5120 java -Dsamjdk.compression_level=~{compression_level} -Xms${java_memory_size_mb}m -jar /usr/picard/picard.jar \ MarkDuplicates \ From 2719e8dd71e23ffcffd1b0c38a569a2a39de1b7e Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 9 Aug 2023 18:06:22 -0400 Subject: [PATCH 196/297] Fixed disk space in Create2DReadTensors --- wdl/TrainCnnFilters.wdl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wdl/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl index 9f246df88..0df45384e 100644 --- a/wdl/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -225,11 +225,11 @@ task Create2DReadTensors { RuntimeAttr? runtime_attr_override } - Int disk_size = 1 + 4*ceil(size(bam_input, "GB") + - size(vcf_input, "GB") + - size(ref_fasta, "GB") + size(ref_fasta_fai, "GB") + size(ref_dict, "GB") + - size(truth_vcf, "GB") + - size(truth_bed, "GB") + Int disk_size = 10 + 8*ceil(size(bam_input, "GB") + + size(vcf_input, "GB") + + size(ref_fasta, "GB") + size(ref_fasta_fai, "GB") + size(ref_dict, "GB") + + size(truth_vcf, "GB") + + size(truth_bed, "GB") ) command <<< From 743fa045bf933238d83ad0e3f2875d06c0ab444d Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 10 Aug 2023 09:34:02 -0400 Subject: [PATCH 197/297] Fixing CNN training task. --- wdl/TrainCnnFilters.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl index 0df45384e..a7d22ab67 100644 --- a/wdl/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -323,7 +323,7 @@ task TrainCnn { cd tensors while read f ; do tar --strip-components 1 -xf $f - done < ${write_lines(tensor_tars)} + done < ~{write_lines(tensor_tars)} cd ../ ${GATK} CNNVariantTrain \ From 87de80b674cf0a2df7a2c647725c0efe913cb293 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 10 Aug 2023 12:26:00 -0400 Subject: [PATCH 198/297] Fixing another typo. --- wdl/TrainCnnFilters.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl index a7d22ab67..2f662ed8e 100644 --- a/wdl/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -326,7 +326,7 @@ task TrainCnn { done < ~{write_lines(tensor_tars)} cd ../ - ${GATK} CNNVariantTrain \ + gatk CNNVariantTrain \ -tensor-type reference \ --epochs ~{epochs} \ --training-steps ~{training_steps} \ From b6aeec262e9677da84594f8cf23e9ff3a3745a17 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 10 Aug 2023 12:26:28 -0400 Subject: [PATCH 199/297] Removing preemptible from CNN Training WDL. --- wdl/TrainCnnFilters.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl index 2f662ed8e..4b04e791f 100644 --- a/wdl/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -349,7 +349,7 @@ task TrainCnn { mem_gb: 32, disk_gb: disk_size, boot_disk_gb: 10, - preemptible_tries: 1, + preemptible_tries: 0, max_retries: 1, docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } From 009c0fc5f19cc6f8dfe1e4445e56373de959142f Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 10 Aug 2023 16:29:31 -0400 Subject: [PATCH 200/297] Provisioned GPUs for `TrainCNN`. --- wdl/TrainCnnFilters.wdl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wdl/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl index 4b04e791f..80ed4dbc9 100644 --- a/wdl/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -354,9 +354,13 @@ task TrainCnn { docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + + # NOTE: We NEED GPUs to train the CNNs, so we don't allow for them to be modified by runtime attributes. runtime { cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + gpuType: "nvidia-tesla-t4" + gpuCount: 4 disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) From 27e188b19c7ed1727a1d433f288d4d695b973843 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 11 Aug 2023 14:18:17 -0400 Subject: [PATCH 201/297] Added log messages. --- wdl/TrainCnnFilters.wdl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wdl/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl index 80ed4dbc9..204736609 100644 --- a/wdl/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -327,6 +327,7 @@ task TrainCnn { cd ../ gatk CNNVariantTrain \ + --verbosity DEBUG \ -tensor-type reference \ --epochs ~{epochs} \ --training-steps ~{training_steps} \ @@ -334,6 +335,8 @@ task TrainCnn { -input-tensor-dir tensors/ \ -model-name ~{prefix}_CNN_~{tensor_type}_model \ + ls -la + kill $monitoring_pid >>> From ad089bcfb85dd66acf461d78370164644d1067a2 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 18 Aug 2023 12:08:58 -0400 Subject: [PATCH 202/297] Updated CNN model training and haplotypecaller parameters. - Exposed CNN model training parameters in the WDLs. - Set `HaplotypeCaller` to use the AVX accelerated Smith-Waterman library if it is possible on the executing machine. --- wdl/TrainCnnFilters.wdl | 46 ++++++++++++++++++++++++++++++++++- wdl/tasks/HaplotypeCaller.wdl | 1 + 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/wdl/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl index 204736609..5e5f68875 100644 --- a/wdl/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -100,6 +100,7 @@ workflow TrainCnnFilters { epochs = 100, training_steps = 100, validation_steps = 6, + optimizer_learning_rate = 0.000001, prefix = prefix + "_CNN_2D_Model" } @@ -253,6 +254,38 @@ task Create2DReadTensors { --max-tensors 10000000 \ -output-tensor-dir ~{prefix}_2D_tensor_dir +# Now check if the tensors contain NaN values: +python << CODE + +import os +import h5py + +import numpy as np + +print() + +def find_files(folder_path, ext): + all_files = [] + for root, directories, files in os.walk(folder_path): + for file in files: + if file.endswith(ext): + all_files.append(os.path.join(root, file)) + return all_files + +hd5_files = find_files("~{prefix}_2D_tensor_dir", "hd5") + +print(f"Inspecting {len(hd5_files)} hd5 files... ") +for f in hd5_files: + with h5py.File(f, 'r') as hd5: + for k in hd5.keys(): + n = np.isnan(np.array(hd5[k])) + if n.sum() > 0: + print(f"File: {f}: Found {n.sum()} NaN(s) in key: {k}") +print("Done.") +print() + +CODE + # No need to zip - the files are .hd5 formatted: tar -cf ~{prefix}_2D_tensor_dir.tar ~{prefix}_2D_tensor_dir @@ -301,6 +334,12 @@ task TrainCnn { Int training_steps = 100 Int validation_steps = 6 + Float optimizer_beta1 = 0.9 + Float optimizer_beta2 = 0.999 + Float optimizer_clipnorm = 1.0 + Float optimizer_epsilon = 0.00000001 # 1.0e-8 + Float optimizer_learning_rate = 0.0001 # 1.0e-4 + String prefix = "out" RuntimeAttr? runtime_attr_override @@ -332,6 +371,11 @@ task TrainCnn { --epochs ~{epochs} \ --training-steps ~{training_steps} \ --validation-steps ~{validation_steps} \ + --optimizer-beta-1 ~{optimizer_beta1} \ + --optimizer-beta-2 ~{optimizer_beta2} \ + --optimizer-clipnorm ~{optimizer_clipnorm} \ + --optimizer-epsilon ~{optimizer_epsilon} \ + --optimizer-learning-rate ~{optimizer_learning_rate} \ -input-tensor-dir tensors/ \ -model-name ~{prefix}_CNN_~{tensor_type}_model \ @@ -354,7 +398,7 @@ task TrainCnn { boot_disk_gb: 10, preemptible_tries: 0, max_retries: 1, - docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + docker: "broadinstitute/gatk-nightly:2023-08-18-4.4.0.0-57-g98f63667a-NIGHTLY-SNAPSHOT" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index 878a6553e..781b7323f 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -201,6 +201,7 @@ task HaplotypeCaller_GATK4_VCF { ~{false="--disable-spanning-event-genotyping" true="" use_spanning_event_genotyping} \ -G StandardAnnotation -G StandardHCAnnotation \ ~{true="-ERC GVCF" false="" make_gvcf} \ + --smith-waterman FASTEST_AVAILABLE \ ~{bamout_arg} # Removed for now: From 4574d2e268d505cf74cee7568c5f214de0588e70 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 5 Sep 2023 16:06:28 -0400 Subject: [PATCH 203/297] Multiple updates. - Added some NaN checking in TrainCnnFilters.wdl - Updated SRWholeGenome and HaplotypeCaller to include an option to use pileup mode. --- wdl/SRWholeGenome.wdl | 5 +++++ wdl/TrainCnnFilters.wdl | 35 +++++++++++++++++++++++++++++- wdl/tasks/HaplotypeCaller.wdl | 40 ++++++++++++++++++++++------------- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index f0b6a3a02..93523646e 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -31,6 +31,9 @@ workflow SRWholeGenome { Boolean run_HC_analysis = true Boolean run_dv_pepper_analysis = true + + Boolean enable_hc_pileup_mode = true + Int dvp_threads = 32 Int dvp_memory = 128 @@ -185,6 +188,8 @@ workflow SRWholeGenome { prefix = participant_name + ".haplotype_caller", + enable_pileup_mode = enable_hc_pileup_mode, + mito_contig = ref_map['mt_chr_name'], contigs_names_to_ignore = contigs_names_to_ignore, } diff --git a/wdl/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl index 5e5f68875..6e5480c34 100644 --- a/wdl/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -360,9 +360,42 @@ task TrainCnn { # Must pre-process the given tensor_tars into a single folder: mkdir tensors cd tensors + + # Let's try to do this multi-threaded: + # NOTE: Yes, I know this is multi-processing, but I'm in a hurry here. + + # Get the max number of threads to use: + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + let max_threads=${np}-1 + if [[ $max_threads -le 0 ]] ; then + max_threads=1 + fi + + # Dispatch some jobs: + num_active_threads=0 while read f ; do - tar --strip-components 1 -xf $f + # If we have reached the maximum number of threads, we should wait for a while: + if [[ $num_active_threads -ge $max_threads ]] ; then + # Wait for the next background process to finish: + wait -n + + # Give ourselves some wiggle room: + sleep 5 + + # Refresh the number of active threads: + num_active_threads=$(jobs | wc -l) + fi + + # Extract our tensors to the `tensors` folder: + tar --strip-components 1 -xf $f & + + # Update the number of active threads: + let num_active_threads=${num_active_threads}+1 done < ~{write_lines(tensor_tars)} + + # Wait for the rest of our background processes to finish: + wait + cd ../ gatk CNNVariantTrain \ diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index 781b7323f..14ec46fc4 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -28,6 +28,8 @@ workflow CallVariantsWithHaplotypeCaller { Int ploidy = 2 + Boolean enable_pileup_mode = false + String mito_contig = "chrM" Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! } @@ -54,6 +56,7 @@ workflow CallVariantsWithHaplotypeCaller { ref_dict = ref_dict, make_gvcf = true, make_bamout = true, + enable_pileup_mode = enable_pileup_mode, single_interval = contig_for_small_var, contamination = 0, ploidy = ploidy, @@ -88,22 +91,25 @@ workflow CallVariantsWithHaplotypeCaller { bam = MergeVariantCalledBamOuts.output_bam } - # Now reblock the GVCF to combine hom ref blocks and save $ / storage: - call ReblockGVCF { - input: - gvcf = MergeGVCFs.output_vcf, - gvcf_index = IndexGVCF.index, - ref_fasta = ref_fasta, - ref_fasta_fai = ref_fasta_fai, - ref_dict = ref_dict, - prefix = prefix - } +# We're disabling ReblockGVCF for now. +# It's removing some annotations we may need later. + +# # Now reblock the GVCF to combine hom ref blocks and save $ / storage: +# call ReblockGVCF { +# input: +# gvcf = MergeGVCFs.output_vcf, +# gvcf_index = IndexGVCF.index, +# ref_fasta = ref_fasta, +# ref_fasta_fai = ref_fasta_fai, +# ref_dict = ref_dict, +# prefix = prefix +# } # Collapse the GVCF into a regular VCF: call SRJOINT.GenotypeGVCFs as CollapseGVCFtoVCF { input: - input_gvcf_data = ReblockGVCF.output_gvcf, - input_gvcf_index = ReblockGVCF.output_gvcf_index, + input_gvcf_data = MergeGVCFs.output_vcf, + input_gvcf_index = IndexGVCF.index, interval_list = SmallVariantsScatterPrep.interval_list, ref_fasta = ref_fasta, ref_fasta_fai = ref_fasta_fai, @@ -113,8 +119,8 @@ workflow CallVariantsWithHaplotypeCaller { } output { - File output_gvcf = ReblockGVCF.output_gvcf - File output_gvcf_index = ReblockGVCF.output_gvcf_index + File output_gvcf = MergeGVCFs.output_vcf + File output_gvcf_index = IndexGVCF.index File output_vcf = CollapseGVCFtoVCF.output_vcf File output_vcf_index = CollapseGVCFtoVCF.output_vcf_index File bamout = MergeVariantCalledBamOuts.output_bam @@ -149,6 +155,8 @@ task HaplotypeCaller_GATK4_VCF { Boolean use_spanning_event_genotyping = true + Boolean enable_pileup_mode = false + RuntimeAttr? runtime_attr_override } @@ -196,15 +204,17 @@ task HaplotypeCaller_GATK4_VCF { -contamination ~{default=0 contamination} \ --sample-ploidy ~{ploidy} \ --linked-de-bruijn-graph \ + ~{true="--pileup-detection --pileup-detection-enable-indel-pileup-calling" false="" enable_pileup_mode} \ --annotate-with-num-discovered-alleles \ -GQB 10 -GQB 20 -GQB 30 -GQB 40 -GQB 50 -GQB 60 -GQB 70 -GQB 80 -GQB 90 \ ~{false="--disable-spanning-event-genotyping" true="" use_spanning_event_genotyping} \ -G StandardAnnotation -G StandardHCAnnotation \ + -A AssemblyComplexity \ ~{true="-ERC GVCF" false="" make_gvcf} \ --smith-waterman FASTEST_AVAILABLE \ ~{bamout_arg} - # Removed for now: + # Removed for now because we need to qualify the pipeline with standard annotations first. # ~{true="-G AS_StandardAnnotation" false="" make_gvcf} # Cromwell doesn't like optional task outputs, so we have to touch this file. From a76a9ec49fcba82b82cc75b5ea36a190f4bbbd41 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 6 Sep 2023 03:36:43 -0400 Subject: [PATCH 204/297] Added task to compute fingerprint of a bam file. --- wdl/tasks/VariantUtils.wdl | 62 +++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 3e76ad8f9..0a9c0cd75 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1392,4 +1392,64 @@ task GatherVcfs { maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } -} \ No newline at end of file +} + +task ExtractFingerprint { + + input { + File bam + File bai + + File haplotype_database_file + + File ref_fasta + File ref_index + File ref_dict + + String prefix = "fingerprint" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 10 + 2*ceil((size(bam, "GB")) + ceil(size(bai, "GB")) + ceil(size(ref_fasta, "GB")) + ceil(size(ref_index, "GB")) + ceil(size(ref_dict, "GB"))) + + command <<< + set -euxo pipefail + + # Extract the fingerprint with the haplotype file: + gatk ExtractFingerprint \ + -H ~{haplotype_database_file} \ + -I ~{bam} \ + -R ~{ref_fasta} \ + -O ~{prefix}.vcf + + # Convert the fingerprint to a string: + bcftools query -f '%REF %ALT [%PL]\n' tmp.vcf | awk '{split($3,p,","); if (p[1]==0) {printf("%s",$1)} else {printf("%s",$2)}}' > ~{prefix}.string.txt + >>> + + output { + File output_vcf = "~{prefix}.vcf" + File fingerprint_string = read_string("~{prefix}.string.txt") + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 8, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From 97356f6e839a0718a5d9989b3578fda40eb14318 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 6 Sep 2023 12:59:29 -0400 Subject: [PATCH 205/297] Added task for `ExtractVariantAnnotations` --- wdl/tasks/VariantUtils.wdl | 126 +++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 0a9c0cd75..6f3ecdac2 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1453,3 +1453,129 @@ task ExtractFingerprint { docker: select_first([runtime_attr.docker, default_attr.docker]) } } + + +task ExtractVariantAnnotations { + + input { + File vcf + File vcf_index + + String prefix + + String mode + + Array[String] recalibration_annotation_values + + Array[File] known_reference_variants + Array[File] known_reference_variants_index + Array[String] known_reference_variants_identifier + Array[Boolean] is_training + Array[Boolean] is_calibration + + Int max_unlabeled_variants = 0 + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + vcf: "VCF File from which to extract annotations." + vcf_index: "Index for the given VCF file." + prefix: "Prefix of the output files." + mode: "SNP or INDEL" + known_reference_variants: "Array of known reference VCF files. For humans, dbSNP is one example." + known_reference_variants_index: "Array of index files for known reference VCF files." + known_reference_variants_identifier: "Array of boolean values the identifier / name for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." + is_training: "Array of boolean values indicating if the known_reference_variant file at the same array position should be used for 'training' data. Must be the same length as `known_reference_variants`." + is_calibration: "Array of boolean values indicating if the known_reference_variant file at the same array position should be used for 'calibration' data. Must be the same length as `known_reference_variants`." + max_unlabeled_variants: "How many sites should be used for unlableled training data. Setting this to values > 0 will enable a positive-negative training model." + } + + Int disk_size = 10 + ceil(size(known_reference_variants, "GB")) + + 4*ceil(size(vcf, "GB")) + + 2*ceil(size(vcf_index, "GB")) + + 2*ceil(size(known_reference_variants, "GB")) + + command <<< + set -euxo pipefail + + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + + # We need to generate resource strings from the input arrays. + # First we check that the arrays are the same length: + if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_index)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_training)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_calibration)} ]] || \ + echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants)}" 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants_index)}" 1>&2 + echo " known_reference_variants_identifier = ~{length(known_reference_variants_identifier)}" 1>&2 + echo " is_training = ~{length(is_training)}" 1>&2 + echo " is_calibration = ~{length(is_calibration)}" 1>&2 + false + fi + + # Now we can write out the arrays into a TSV file and add them line by line to the execution: + # Create the TSV: + options_tsv=~{write_tsv(transpose([known_reference_variants_identifier, is_training, is_calibration, known_reference_variants]))} + + # Now read them into a string: + resource_flags=$(awk '{printf("--resource:%s,training=%s,calibration=%s %s ", $1, $2, $3, $4)}' ${options_tsv}) + + # Get amount of memory to use: + mem_available=$(free -g | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-2 + + gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ + ExtractVariantAnnotations \ + -V ~{vcf} \ + -A ~{sep=' -A ' recalibration_annotation_values} \ + --mode ~{mode} \ + --maximum-number-of-unlableled-variants ~{max_unlabeled_variants} \ + ${resource_flags} \ + -O ~{prefix}_extracted_annotations_~{mode} \ + &> ~{prefix}_ExtractVariantAnnotations_~{mode}.log + + # If we set max_unlabeled_variants to 0, we need to make sure that file exists: + touch ~{prefix}_extracted_annotations_~{mode}.unlabeled.annot.hdf5 + + kill $monitoring_pid + >>> + + output { + File annotation_hdf5 = "~{prefix}_extracted_annotations_~{mode}.annot.hdf5" + File unlabeled_annotation_hdf5 = "~{prefix}_extracted_annotations_~{mode}.unlabeled.annot.hdf5" + File sites_only_vcf = "~{prefix}_extracted_annotations_~{mode}.vcf.gz" + File sites_only_vcf_index = "~{prefix}_extracted_annotations_~{mode}.vcf.tbi" + + File log = "~{prefix}_ExtractVariantAnnotations_~{mode}.log" + + File monitoring_log = "resources.log" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 26, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} \ No newline at end of file From fc35d5a96415c4f458e5dfe77a0208de4ca26d14 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 6 Sep 2023 13:37:35 -0400 Subject: [PATCH 206/297] Added task for TrainVariantAnnotationsModel --- wdl/tasks/VariantUtils.wdl | 91 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 6f3ecdac2..af2533bc4 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1558,6 +1558,97 @@ task ExtractVariantAnnotations { File monitoring_log = "resources.log" } + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 26, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task TrainVariantAnnotationsModel { + + input { + File annotation_hdf5 + String mode + String prefix + + File? unlabeled_annotation_hdf5 + Float calibration_sensitivity_threshold = 0.95 + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + annotation_hdf5: "Labeled-annotations HDF5 file." + mode: "SNP or INDEL" + prefix: "Prefix of the output files." + unlabeled_annotation_hdf5: "Unlabeled-annotations HDF5 file (optional)" + calibration_sensitivity_threshold: "Calibration-set sensitivity threshold. (optional)" + } + + Int disk_size = 10 + 4*ceil(size(annotation_hdf5, "GB")) + + 4*ceil(size(unlabeled_annotation_hdf5, "GB")) + + String cal_sense_arg = if defined(unlabeled_annotation_hdf5) then " --calibration-sensitivity-threshold ~{calibration_sensitivity_threshold}" else "" + + command <<< + set -euxo pipefail + + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + + # Get amount of memory to use: + mem_available=$(free -g | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-2 + + gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ + TrainVariantAnnotationsModel \ + --annotations-hdf5 ~{annotation_hdf5} \ + --mode ~{mode} \ + ~{"--unlabeled-annotations-hdf5 " + unlabeled_annotation_hdf5} \ + ~{cal_sense_arg} \ + -O ~{prefix}_train_~{mode} \ + &> ~{prefix}_TrainVariantAnnotationsModel_~{mode}.log + + # We must ensure the optional outputs all exist: + touch ~{prefix}_train_~{mode}.unlabeledScores.hdf5 + touch ~{prefix}_train_~{mode}.calibrationScores.hdf5 + touch ~{prefix}_train_~{mode}.negative.scorer.pkl + + kill $monitoring_pid + >>> + + output { + File training_scores = "~{prefix}_train_~{mode}.trainingScores.hdf5" + File positive_model_scorer_pickle = "~{prefix}_train_~{mode}.scorer.pkl" + + File unlabeled_positive_model_scores = "~{prefix}_train_~{mode}.unlabeledScores.hdf5" + File calibration_set_scores = "~{prefix}_train_~{mode}.calibrationScores.hdf5" + File negative_model_scorer_pickle = "~{prefix}_train_~{mode}.negative.scorer.pkl" + + File log = "~{prefix}_TrainVariantAnnotationsModel_~{mode}.log" + + File monitoring_log = "resources.log" + } + ######################### RuntimeAttr default_attr = object { cpu_cores: 2, From 8aef65f3c5fe0a4192bf7cbff1673d4256ac9707 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 6 Sep 2023 16:11:16 -0400 Subject: [PATCH 207/297] Intermediate commit. --- wdl/SRWholeGenome.wdl | 247 +++++++++++++++++++++++-------------- wdl/tasks/VariantUtils.wdl | 159 ++++++++++++++++++++++-- 2 files changed, 298 insertions(+), 108 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 93523646e..000ccf7f9 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -39,31 +39,25 @@ workflow SRWholeGenome { Int ploidy = 2 - Float snp_filter_level = 99.7 - Array[String] snp_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] - Array[Float] snp_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.8, 99.6, 99.5, 99.4, 99.3, 99.0, 98.0, 97.0, 90.0 ] + Float snp_calibration_sensitivity = 0.99 + Int snp_max_unlabeled_variants = 0 + Array[String] snp_recalibration_annotation_values = [ "BaseQRankSum", "ExcessHet", "FS", "HAPCOMP", "HAPDOM", "HEC", "MQ", "MQRankSum", "QD", "ReadPosRankSum", "SOR", "DP" ] Array[File] snp_known_reference_variants Array[File] snp_known_reference_variants_index Array[File] snp_known_reference_variants_identifier - Array[Boolean] snp_is_known Array[Boolean] snp_is_training - Array[Boolean] snp_is_truth - Array[Float] snp_prior - Int snp_max_gaussians = 8 + Array[Boolean] snp_is_calibration - Float indel_filter_level = 99.0 - Array[String] indel_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] - Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] + Float indel_calibration_sensitivity = 0.99 + Int indel_max_unlabeled_variants = 0 + Array[String] indel_recalibration_annotation_values = [ "BaseQRankSum", "ExcessHet", "FS", "HAPCOMP", "HAPDOM", "HEC", "MQ", "MQRankSum", "QD", "ReadPosRankSum", "SOR", "DP" ] Array[File] indel_known_reference_variants Array[File] indel_known_reference_variants_index Array[File] indel_known_reference_variants_identifier - Array[Boolean] indel_is_known Array[Boolean] indel_is_training - Array[Boolean] indel_is_truth - Array[Float] indel_prior - Int indel_max_gaussians = 8 + Array[Boolean] indel_is_calibration File? bed_to_compute_coverage @@ -194,81 +188,123 @@ workflow SRWholeGenome { contigs_names_to_ignore = contigs_names_to_ignore, } - call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { + ######################################################################## + # Call VETS / VQSR-lite: + call VARUTIL.ExtractVariantAnnotations as ExtractIndelVariantAnnotations { input: - vcfs = [CallVariantsWithHaplotypeCaller.output_vcf], - vcf_indices = [CallVariantsWithHaplotypeCaller.output_vcf_index], - prefix = participant_name + ".indels", - recalibration_tranche_values = indel_recalibration_tranche_values, + vcf = CallVariantsWithHaplotypeCaller.output_vcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + + prefix = participant_name, + mode = "INDEL", + recalibration_annotation_values = indel_recalibration_annotation_values, -# known_reference_variants = [ref_map["known_sites_vcf"]], -# known_reference_variants_index = [ref_map["known_sites_index"]], -# known_reference_variants_identifier = ["pfcrosses"], -# is_known = [true], -# is_training = [true], -# is_truth = [true], -# prior = [15], + known_reference_variants = indel_known_reference_variants, known_reference_variants_index = indel_known_reference_variants_index, known_reference_variants_identifier = indel_known_reference_variants_identifier, - is_known = indel_is_known, is_training = indel_is_training, - is_truth = indel_is_truth, - prior = indel_prior, - use_allele_specific_annotations = false, - max_gaussians = indel_max_gaussians, + is_calibration = indel_is_calibration, + + max_unlabeled_variants = indel_max_unlabeled_variants, } - call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { + call VARUTIL.ExtractVariantAnnotations as ExtractSnpVariantAnnotations { input: - vcfs = [CallVariantsWithHaplotypeCaller.output_vcf], - vcf_indices = [CallVariantsWithHaplotypeCaller.output_vcf_index], - prefix = participant_name + ".snps", - recalibration_tranche_values = snp_recalibration_tranche_values, + vcf = CallVariantsWithHaplotypeCaller.output_vcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + + prefix = participant_name, + mode = "SNP", + recalibration_annotation_values = snp_recalibration_annotation_values, -# known_reference_variants = [ref_map["known_sites_vcf"]], -# known_reference_variants_index = [ref_map["known_sites_index"]], -# known_reference_variants_identifier = ["pfcrosses"], -# is_known = [true], -# is_training = [true], -# is_truth = [true], -# prior = [15], + known_reference_variants = snp_known_reference_variants, known_reference_variants_index = snp_known_reference_variants_index, known_reference_variants_identifier = snp_known_reference_variants_identifier, - is_known = snp_is_known, is_training = snp_is_training, - is_truth = snp_is_truth, - prior = snp_prior, - use_allele_specific_annotations = false, - max_gaussians = snp_max_gaussians, + is_calibration = snp_is_calibration, + + max_unlabeled_variants = snp_max_unlabeled_variants, + } + + call VARUTIL.TrainVariantAnnotationsModel as TrainIndelVariantAnnotationsModel { + input: + annotation_hdf5 = ExtractIndelVariantAnnotations.annotation_hdf5, + mode = "INDEL", + prefix = participant_name, } - call VARUTIL.ApplyVqsr as ApplyVqsr { + call VARUTIL.TrainVariantAnnotationsModel as TrainSnpVariantAnnotationsModel { + input: + annotation_hdf5 = ExtractIndelVariantAnnotations.annotation_hdf5, + mode = "INDEL", + prefix = participant_name, + } + + call VARUTIL.ScoreVariantAnnotations as ScoreSnpVariantAnnotations { input: vcf = CallVariantsWithHaplotypeCaller.output_vcf, vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, - prefix = participant_name + ".vqsr_filtered", - snps_recalibration = TrainVQSROnHCSnpVariants.recalibration, - snps_recalibration_index = TrainVQSROnHCSnpVariants.recalibration_index, - snps_tranches = TrainVQSROnHCSnpVariants.tranches, - snp_filter_level = snp_filter_level, + sites_only_extracted_vcf = ExtractSnpVariantAnnotations.sites_only_vcf, + sites_only_extracted_vcf_index = ExtractSnpVariantAnnotations.sites_only_vcf_index, + + model_prefix = participant_name + "_train_SNP", + model_files = flatten([[TrainSnpVariantAnnotationsModel.training_scores, TrainSnpVariantAnnotationsModel.positive_model_scorer_pickle], select_all([ + TrainSnpVariantAnnotationsModel.unlabeled_positive_model_scores, + TrainSnpVariantAnnotationsModel.calibration_set_scores, + TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle + ])]), + prefix = participant_name, + mode = "SNP", - indels_recalibration = TrainVQSROnHCIndelVariants.recalibration, - indels_recalibration_index = TrainVQSROnHCIndelVariants.recalibration_index, - indels_tranches = TrainVQSROnHCIndelVariants.tranches, - indel_filter_level = indel_filter_level, + calibration_sensitivity_threshold = snp_calibration_sensitivity, - use_allele_specific_annotations = false, + recalibration_annotation_values = snp_recalibration_annotation_values, + + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_training = snp_is_training, + is_calibration = snp_is_calibration, } + call VARUTIL.ScoreVariantAnnotations as ScoreIndelVariantAnnotations { + input: + vcf = ScoreSnpVariantAnnotations.scored_vcf, + vcf_index = ScoreSnpVariantAnnotations.scored_vcf_index, + + sites_only_extracted_vcf = ExtractIndelVariantAnnotations.sites_only_vcf, + sites_only_extracted_vcf_index = ExtractIndelVariantAnnotations.sites_only_vcf_index, + + model_prefix = participant_name + "_train_INDEL", + model_files = flatten([[TrainIndelVariantAnnotationsModel.training_scores, TrainIndelVariantAnnotationsModel.positive_model_scorer_pickle], select_all([ + TrainIndelVariantAnnotationsModel.unlabeled_positive_model_scores, + TrainIndelVariantAnnotationsModel.calibration_set_scores, + TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle + ])]), + prefix = participant_name, + mode = "INDEL", + + calibration_sensitivity_threshold = indel_calibration_sensitivity, + + recalibration_annotation_values = indel_recalibration_annotation_values, + + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_training = indel_is_training, + is_calibration = indel_is_calibration, + } + ######################################################################## + call VARUTIL.SelectVariants as RemoveFilteredVariants { input: - vcf = ApplyVqsr.recalibrated_vcf, - vcf_index = ApplyVqsr.recalibrated_vcf_index, - prefix = participant_name + ".vqsr_filtered" + vcf = ScoreIndelVariantAnnotations.scored_vcf, + vcf_index = ScoreIndelVariantAnnotations.scored_vcf_index, + prefix = participant_name + ".vets_filtered" } ## Rename our samples so we can use them later: @@ -292,9 +328,9 @@ workflow SRWholeGenome { call VARUTIL.RenameSingleSampleVcf as RenameSingleSampleVcf { input: - vcf = ApplyVqsr.recalibrated_vcf, - vcf_index = ApplyVqsr.recalibrated_vcf_index, - prefix = participant_name + ".vqsr", + vcf = ScoreIndelVariantAnnotations.scored_vcf, + vcf_index = ScoreIndelVariantAnnotations.scored_vcf_index, + prefix = participant_name + ".scored", new_sample_name = participant_name } @@ -302,7 +338,7 @@ workflow SRWholeGenome { input: vcf = RemoveFilteredVariants.vcf_out, vcf_index = RemoveFilteredVariants.vcf_out_index, - prefix = participant_name + ".vqsr_filtered", + prefix = participant_name + ".vets_filtered", new_sample_name = participant_name } @@ -317,22 +353,53 @@ workflow SRWholeGenome { call FF.FinalizeToFile as FinalizeHCBamOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout } call FF.FinalizeToFile as FinalizeHCBaiOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout_index } - # Finalize the VQSR files: - call FF.FinalizeToFile as FinalizeIndelRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } - call FF.FinalizeToFile as FinalizeIndelRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } - call FF.FinalizeToFile as FinalizeIndelRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } - call FF.FinalizeToFile as FinalizeIndelRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } - - call FF.FinalizeToFile as FinalizeSnpRecalFile { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } - call FF.FinalizeToFile as FinalizeSnpRecalIndex { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } - call FF.FinalizeToFile as FinalizeSnpRecalTranches { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } - call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir, keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } - # Finalize the reclibrated / filtered variants: - call FF.FinalizeToFile as FinalizeHCVqsrVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf } - call FF.FinalizeToFile as FinalizeHCVqsrTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf_index } - call FF.FinalizeToFile as FinalizeHCVqsrFilteredVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcfFiltered.new_sample_name_vcf } - call FF.FinalizeToFile as FinalizeHCVqsrFilteredTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcfFiltered.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCRescoredVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeHCRescoredTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCRescoredFilteredVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcfFiltered.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeHCRescoredFilteredTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcfFiltered.new_sample_name_vcf_index } + + ################################ + # Finalize the VETS files: + ############ + + # ExtractVariantAnnotations: + call FF.FinalizeToFile as FinalizeSnpExtractedAnnotations { input: outdir = outdir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.annotation_hdf5 } + call FF.FinalizeToFile as FinalizeSnpExtractedSitesOnlyVcf { input: outdir = outdir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.sites_only_vcf } + call FF.FinalizeToFile as FinalizeSnpExtractedSitesOnlyVcfIndex { input: outdir = outdir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.sites_only_vcf_index } + if (defined(ExtractSnpVariantAnnotations.unlabeled_annotation_hdf5)) { + call FF.FinalizeToFile as FinalizeSnpExtractedUnlabeledAnnotations { input: outdir = outdir, keyfile = keyfile, file = select_first([ExtractSnpVariantAnnotations.unlabeled_annotation_hdf5]) } + } + call FF.FinalizeToFile as FinalizeIndelExtractedAnnotations { input: outdir = outdir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.annotation_hdf5 } + call FF.FinalizeToFile as FinalizeIndelExtractedSitesOnlyVcf { input: outdir = outdir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.sites_only_vcf } + call FF.FinalizeToFile as FinalizeIndelExtractedSitesOnlyVcfIndex { input: outdir = outdir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.sites_only_vcf_index } + if (defined(ExtractIndelVariantAnnotations.unlabeled_annotation_hdf5)) { + call FF.FinalizeToFile as FinalizeIndelExtractedUnlabeledAnnotations { input: outdir = outdir, keyfile = keyfile, file = select_first([ExtractIndelVariantAnnotations.unlabeled_annotation_hdf5]) } + } + + # TrainVariantAnnotationsModel + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsTrainingScores { input: outdir = outdir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.training_scores } + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsPositiveModelScorer { input: outdir = outdir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.positive_model_scorer_pickle } + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsUnlabeledPositiveModelScores { input: outdir = outdir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.unlabeled_positive_model_scores } + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsCalibrationSetScores { input: outdir = outdir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.calibration_set_scores } + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsNegativeModelScorer { input: outdir = outdir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle } + + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsTrainingScores { input: outdir = outdir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.training_scores } + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsPositiveModelScorer { input: outdir = outdir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.positive_model_scorer_pickle } + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsUnlabeledPositiveModelScores { input: outdir = outdir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.unlabeled_positive_model_scores } + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsCalibrationSetScores { input: outdir = outdir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.calibration_set_scores } + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsNegativeModelScorer { input: outdir = outdir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle } + + # ScoreVariantAnnotations + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoredVcf { input: outdir = outdir, keyfile = keyfile, file = ScoreSnpVariantAnnotations.scored_vcf } + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoredVcfIndex { input: outdir = outdir, keyfile = keyfile, file = ScoreSnpVariantAnnotations.scored_vcf_index } + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsAnnotationsHdf5 { input: outdir = outdir, keyfile = keyfile, file = ScoreSnpVariantAnnotations.annotations_hdf5 } + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoresHdf5 { input: outdir = outdir, keyfile = keyfile, file = ScoreSnpVariantAnnotations.scores_hdf5 } + + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoredVcf { input: outdir = outdir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf } + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoredVcfIndex { input: outdir = outdir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf_index } + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsAnnotationsHdf5 { input: outdir = outdir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.annotations_hdf5 } + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoresHdf5 { input: outdir = outdir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scores_hdf5 } } output { @@ -371,19 +438,9 @@ workflow SRWholeGenome { File? hc_baiout = FinalizeHCBaiOut.gcs_path File? hc_raw_vcf = FinalizeHCVcf.gcs_path File? hc_raw_tbi = FinalizeHCTbi.gcs_path - File? hc_vqsr_vcf = FinalizeHCVqsrFilteredVcf.gcs_path - File? hc_vqsr_tbi = FinalizeHCVqsrFilteredTbi.gcs_path - File? hc_vqsr_raw_vcf = FinalizeHCVqsrVcf.gcs_path - File? hc_vqsr_raw_tbi = FinalizeHCVqsrTbi.gcs_path - - File? vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path - File? vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path - File? vqsr_indel_recal_tranches = FinalizeIndelRecalTranches.gcs_path - File? vqsr_indel_recal_model_report = FinalizeIndelRecalModelReport.gcs_path - - File? vqsr_snp_recal_file = FinalizeSnpRecalFile.gcs_path - File? vqsr_snp_recal_file_index = FinalizeSnpRecalIndex.gcs_path - File? vqsr_snp_recal_tranches = FinalizeSnpRecalTranches.gcs_path - File? vqsr_snp_recal_model_report = FinalizeSnpRecalModelReport.gcs_path + File? hc_rescored_vcf = FinalizeHCRescoredFilteredVcf.gcs_path + File? hc_rescored_tbi = FinalizeHCRescoredFilteredTbi.gcs_path + File? hc_rescored_raw_vcf = FinalizeHCRescoredVcf.gcs_path + File? hc_rescored_raw_tbi = FinalizeHCRescoredTbi.gcs_path } } diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index af2533bc4..59d6c869c 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1494,7 +1494,7 @@ task ExtractVariantAnnotations { Int disk_size = 10 + ceil(size(known_reference_variants, "GB")) + 4*ceil(size(vcf, "GB")) + 2*ceil(size(vcf_index, "GB")) - + 2*ceil(size(known_reference_variants, "GB")) + + 2*ceil(size(known_reference_variants_index, "GB")) command <<< set -euxo pipefail @@ -1541,18 +1541,16 @@ task ExtractVariantAnnotations { -O ~{prefix}_extracted_annotations_~{mode} \ &> ~{prefix}_ExtractVariantAnnotations_~{mode}.log - # If we set max_unlabeled_variants to 0, we need to make sure that file exists: - touch ~{prefix}_extracted_annotations_~{mode}.unlabeled.annot.hdf5 - kill $monitoring_pid >>> output { File annotation_hdf5 = "~{prefix}_extracted_annotations_~{mode}.annot.hdf5" - File unlabeled_annotation_hdf5 = "~{prefix}_extracted_annotations_~{mode}.unlabeled.annot.hdf5" File sites_only_vcf = "~{prefix}_extracted_annotations_~{mode}.vcf.gz" File sites_only_vcf_index = "~{prefix}_extracted_annotations_~{mode}.vcf.tbi" + File? unlabeled_annotation_hdf5 = "~{prefix}_extracted_annotations_~{mode}.unlabeled.annot.hdf5" + File log = "~{prefix}_ExtractVariantAnnotations_~{mode}.log" File monitoring_log = "resources.log" @@ -1628,11 +1626,6 @@ task TrainVariantAnnotationsModel { -O ~{prefix}_train_~{mode} \ &> ~{prefix}_TrainVariantAnnotationsModel_~{mode}.log - # We must ensure the optional outputs all exist: - touch ~{prefix}_train_~{mode}.unlabeledScores.hdf5 - touch ~{prefix}_train_~{mode}.calibrationScores.hdf5 - touch ~{prefix}_train_~{mode}.negative.scorer.pkl - kill $monitoring_pid >>> @@ -1640,15 +1633,155 @@ task TrainVariantAnnotationsModel { File training_scores = "~{prefix}_train_~{mode}.trainingScores.hdf5" File positive_model_scorer_pickle = "~{prefix}_train_~{mode}.scorer.pkl" - File unlabeled_positive_model_scores = "~{prefix}_train_~{mode}.unlabeledScores.hdf5" - File calibration_set_scores = "~{prefix}_train_~{mode}.calibrationScores.hdf5" - File negative_model_scorer_pickle = "~{prefix}_train_~{mode}.negative.scorer.pkl" + File? unlabeled_positive_model_scores = "~{prefix}_train_~{mode}.unlabeledScores.hdf5" + File? calibration_set_scores = "~{prefix}_train_~{mode}.calibrationScores.hdf5" + File? negative_model_scorer_pickle = "~{prefix}_train_~{mode}.negative.scorer.pkl" File log = "~{prefix}_TrainVariantAnnotationsModel_~{mode}.log" File monitoring_log = "resources.log" } + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 26, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-gatk/gatk:4.3.0.0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + + +task ScoreVariantAnnotations { + + input { + File vcf + File vcf_index + + File sites_only_extracted_vcf + File sites_only_extracted_vcf_index + + String model_prefix + Array[File] model_files + + String prefix + + String mode + + Float calibration_sensitivity_threshold = 0.99 + + Array[String] recalibration_annotation_values + + Array[File] known_reference_variants + Array[File] known_reference_variants_index + Array[String] known_reference_variants_identifier + Array[Boolean] is_training + Array[Boolean] is_calibration + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + vcf: "VCF File from which to extract annotations." + vcf_index: "Index for the given VCF file." + prefix: "Prefix of the output files." + mode: "SNP or INDEL" + known_reference_variants: "Array of known reference VCF files. For humans, dbSNP is one example." + known_reference_variants_index: "Array of index files for known reference VCF files." + known_reference_variants_identifier: "Array of boolean values the identifier / name for the known_reference_variant file at the same array position. Must be the same length as `known_reference_variants`." + is_training: "Array of boolean values indicating if the known_reference_variant file at the same array position should be used for 'training' data. Must be the same length as `known_reference_variants`." + is_calibration: "Array of boolean values indicating if the known_reference_variant file at the same array position should be used for 'calibration' data. Must be the same length as `known_reference_variants`." + } + + Int disk_size = 10 + 4*ceil(size(vcf, "GB")) + + 2*ceil(size(vcf_index, "GB")) + + 2*ceil(size(sites_only_extracted_vcf, "GB")) + + 2*ceil(size(sites_only_extracted_vcf_index, "GB")) + + 2*ceil(size(known_reference_variants, "GB")) + + 2*ceil(size(known_reference_variants_index, "GB")) + + 2*ceil(size(model_files, "GB")) + + command <<< + set -euxo pipefail + + export MONITOR_MOUNT_POINT="/cromwell_root" + wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + + # We need to generate resource strings from the input arrays. + # First we check that the arrays are the same length: + if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_index)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_training)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_calibration)} ]] || \ + echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants)}" 1>&2 + echo " known_reference_variants = ~{length(known_reference_variants_index)}" 1>&2 + echo " known_reference_variants_identifier = ~{length(known_reference_variants_identifier)}" 1>&2 + echo " is_training = ~{length(is_training)}" 1>&2 + echo " is_calibration = ~{length(is_calibration)}" 1>&2 + false + fi + + # Now we can write out the arrays into a TSV file and add them line by line to the execution: + # Create the TSV: + options_tsv=~{write_tsv(transpose([known_reference_variants_identifier, is_training, is_calibration, known_reference_variants]))} + + # Now read them into a string: + resource_flags=$(awk '{printf("--resource:%s,training=%s,calibration=%s %s ", $1, $2, $3, $4)}' ${options_tsv}) + + # Get amount of memory to use: + mem_available=$(free -g | grep '^Mem' | awk '{print $2}') + let mem_start=${mem_available}-2 + + mode_lower=$(echo ~{mode} | tr 'A-Z' 'a-z') + + # Set up model files: + mkdir model_files + ln -s ~{sep=" model_files && ln -s " model_files} model_files + + gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ + ScoreVariantAnnotations \ + -V ~{vcf} \ + -A ~{sep=' -A ' recalibration_annotation_values} \ + --mode ~{mode} \ + --model-prefix model_files/~{model_prefix}.train \ + ${resource_flags} \ + --resource:extracted,extracted=true ~{sites_only_extracted_vcf} \ + --${mode_lower}-calibration-sensitivity-threshold ~{calibration_sensitivity_threshold} \ + -O ~{prefix}_~{mode}_scored \ + &> ~{prefix}_ScoreVariantAnnotations_~{mode}.log + + kill $monitoring_pid + >>> + + output { + File scored_vcf = "~{prefix}_~{mode}_scored.vcf.gz" + File scored_vcf_index = "~{prefix}_~{mode}_scored.vcf.gz.tbi" + + File? annotations_hdf5 = "~{prefix}_~{mode}_scored.annot.hdf5" + File? scores_hdf5 = "~{prefix}_~{mode}_scored.scores.hdf5" + + File log = "~{prefix}_ExtractVariantAnnotations_~{mode}.log" + + File monitoring_log = "resources.log" + } + ######################### RuntimeAttr default_attr = object { cpu_cores: 2, From d6d6219c1a93de3d150311a2d5bab9f79f0457fb Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 8 Sep 2023 11:49:01 -0400 Subject: [PATCH 208/297] Updates for VETS integration and file outputs. - Updated workflows to use finalized files as outputs instead of files in the working directories. - Fixed validation error in `LRJointCallGVCFsWithGenomicsDB.wdl`. - Updated `SRJointCallGVCFsWithGenomicsDB.wdl` to use VETS instead of VQSR. --- wdl/LRJointCallGVCFsWithGenomicsDB.wdl | 8 +- wdl/SRFlowcell.wdl | 26 ++- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 307 ++++++++++++++++--------- wdl/SRWholeGenome.wdl | 93 +++++--- wdl/tasks/VariantUtils.wdl | 10 +- 5 files changed, 279 insertions(+), 165 deletions(-) diff --git a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl index bfc118541..3323fce79 100644 --- a/wdl/LRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/LRJointCallGVCFsWithGenomicsDB.wdl @@ -117,8 +117,8 @@ workflow LRJointCallGVCFsWithGenomicsDB { # Now we run VariantRecalibrator for indels and snps: call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { input: - vcf = MakeSitesOnlyGVCF.sites_only_vcf, - vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, + vcfs = [MakeSitesOnlyGVCF.sites_only_vcf], + vcf_indices = [MakeSitesOnlyGVCF.sites_only_vcf_index], prefix = prefix + ".indels", recalibration_tranche_values = indel_recalibration_tranche_values, recalibration_annotation_values = indel_recalibration_annotation_values, @@ -142,8 +142,8 @@ workflow LRJointCallGVCFsWithGenomicsDB { call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { input: - vcf = MakeSitesOnlyGVCF.sites_only_vcf, - vcf_index = MakeSitesOnlyGVCF.sites_only_vcf_index, + vcfs = [MakeSitesOnlyGVCF.sites_only_vcf], + vcf_indices = [MakeSitesOnlyGVCF.sites_only_vcf_index], prefix = prefix + ".snps", recalibration_tranche_values = snp_recalibration_tranche_values, recalibration_annotation_values = snp_recalibration_annotation_values, diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index d58fefb8d..0072877de 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -213,12 +213,14 @@ workflow SRFlowcell { ############################################ File keyfile = t_014_ComputeBamStats.results_file String reads_dir = outdir + "/reads" + String unaligned_reads_dir = outdir + "/reads/unaligned" + String aligned_reads_dir = outdir + "/reads/aligned" String metrics_dir = outdir + "/metrics" # Finalize our unaligned reads first: call FF.FinalizeToDir as t_020_FinalizeUnalignedFastqReads { input: - outdir = reads_dir + "/unaligned", + outdir = unaligned_reads_dir, files = [ fq_e1, @@ -229,7 +231,7 @@ workflow SRFlowcell { if (defined(bam)) { call FF.FinalizeToDir as t_021_FinalizeUnalignedReadsFromBam { input: - outdir = reads_dir + "/unaligned", + outdir = unaligned_reads_dir, files = select_all( [ bam, @@ -242,7 +244,7 @@ workflow SRFlowcell { call FF.FinalizeToDir as t_022_FinalizeAlignedReads { input: - outdir = reads_dir + "/aligned", + outdir = aligned_reads_dir, files = [ t_005_AlignReads.bam, @@ -256,14 +258,14 @@ workflow SRFlowcell { call FF.FinalizeToFile as t_023_FinalizeAlignedBam { input: - outdir = reads_dir + "/aligned", + outdir = aligned_reads_dir, file = t_010_ApplyBQSR.recalibrated_bam, keyfile = keyfile } call FF.FinalizeToFile as t_024_FinalizeAlignedBai { input: - outdir = reads_dir + "/aligned", + outdir = aligned_reads_dir, file = t_010_ApplyBQSR.recalibrated_bai, keyfile = keyfile } @@ -295,10 +297,12 @@ workflow SRFlowcell { # Prep a few files for output: + File fq1_o = unaligned_reads_dir + "/" + basename(fq_e1) + File fq2_o = unaligned_reads_dir + "/" + basename(fq_e2) if (defined(bam)) { - File unaligned_bam_o = reads_dir + "/unaligned/" + basename(select_first([bam])) - File unaligned_bai_o = reads_dir + "/unaligned/" + basename(select_first([bai])) - File fqboup = reads_dir + "/unaligned/" + basename(select_first([t_003_Bam2Fastq.fq_unpaired])) + File unaligned_bam_o = unaligned_reads_dir + "/" + basename(select_first([bam])) + File unaligned_bai_o = unaligned_reads_dir + "/" + basename(select_first([bai])) + File fqboup = unaligned_reads_dir + "/" + basename(select_first([t_003_Bam2Fastq.fq_unpaired])) } ############################################ @@ -311,9 +315,9 @@ workflow SRFlowcell { ############################################ output { # Unaligned reads - File fq1 = fq_e1 - File fq2 = fq_e2 - File? fq_unpaired = t_003_Bam2Fastq.fq_unpaired + File fq1 = fq1_o + File fq2 = fq2_o + File? fq_unpaired = fqboup # Unaligned BAM file File? unaligned_bam = unaligned_bam_o diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 247dd4fe2..78dbba875 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -21,31 +21,25 @@ workflow SRJointCallGVCFsWithGenomicsDB { File interval_list - Float snp_filter_level = 99.7 - Array[String] snp_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] - Array[Float] snp_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.8, 99.6, 99.5, 99.4, 99.3, 99.0, 98.0, 97.0, 90.0 ] + Float snp_calibration_sensitivity = 0.99 + Int snp_max_unlabeled_variants = 0 + Array[String] snp_recalibration_annotation_values = [ "BaseQRankSum", "ExcessHet", "FS", "HAPCOMP", "HAPDOM", "HEC", "MQ", "MQRankSum", "QD", "ReadPosRankSum", "SOR", "DP" ] Array[File] snp_known_reference_variants Array[File] snp_known_reference_variants_index Array[File] snp_known_reference_variants_identifier - Array[Boolean] snp_is_known Array[Boolean] snp_is_training - Array[Boolean] snp_is_truth - Array[Float] snp_prior - Int snp_max_gaussians = 8 + Array[Boolean] snp_is_calibration - Float indel_filter_level = 99.0 - Array[String] indel_recalibration_annotation_values = ["QD", "FS", "SOR", "MQRankSum", "ReadPosRankSum"] - Array[Float] indel_recalibration_tranche_values = [100.0, 99.95, 99.9, 99.5, 99.0, 97.0, 96.0, 95.0, 94.0, 93.5, 93.0, 92.0, 91.0, 90.0] + Float indel_calibration_sensitivity = 0.99 + Int indel_max_unlabeled_variants = 0 + Array[String] indel_recalibration_annotation_values = [ "BaseQRankSum", "ExcessHet", "FS", "HAPCOMP", "HAPDOM", "HEC", "MQ", "MQRankSum", "QD", "ReadPosRankSum", "SOR", "DP" ] Array[File] indel_known_reference_variants Array[File] indel_known_reference_variants_index Array[File] indel_known_reference_variants_identifier - Array[Boolean] indel_is_known Array[Boolean] indel_is_training - Array[Boolean] indel_is_truth - Array[Float] indel_prior - Int indel_max_gaussians = 8 + Array[Boolean] indel_is_calibration Array[File]? annotation_bed_files Array[File]? annotation_bed_file_indexes @@ -66,36 +60,16 @@ workflow SRJointCallGVCFsWithGenomicsDB { gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" } - String outdir = sub(gcs_out_root_dir, "/$", "") + "/LRJointCallGVCFsWithGenomicsDB/~{prefix}" + String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRJointCallGVCFsWithGenomicsDB/~{prefix}" Map[String, String] ref_map = read_map(ref_map_file) - # From WARP: - # For small callsets (fewer than 1000 samples) we can gather the VCF shards and collect metrics directly. - # For anything larger, we need to keep the VCF sharded and gather metrics collected from them. - # We allow overriding this default behavior for testing / special requests. - Boolean is_small_callset = length(gvcfs) <= 1000 - # Create interval list over which to shard the processing: call UTILS.MakeChrIntervalList as MakeChrIntervalList { input: ref_dict = ref_map['dict'], } - # Reblock our GVCFs: - scatter (idx_1 in range(length(gvcfs))) { - call SRJOINT.ReblockGVCF as ReblockGVCFs { - input: - input_gvcf = gvcfs[idx_1], - input_gvcf_index = gvcf_indices[idx_1], - ref_fasta = ref_map['fasta'], - ref_fasta_fai = ref_map['fai'], - ref_dict = ref_map['dict'], - # Get rid of any and all suffixes: - prefix = basename(basename(basename(gvcfs[idx_1], ".g.vcf.gz"), ".vcf.gz"), ".vcf") - } - } - # Create sample-name map: call SRJOINT.CreateSampleNameMap as CreateSampleNameMap { input: @@ -104,10 +78,10 @@ workflow SRJointCallGVCFsWithGenomicsDB { } # Shard by contig for speed: - scatter (idx_2 in range(length(MakeChrIntervalList.contig_interval_list_files))) { + scatter (idx_1 in range(length(MakeChrIntervalList.contig_interval_list_files))) { - String contig = MakeChrIntervalList.chrs[idx_2][0] - File contig_interval_list = MakeChrIntervalList.contig_interval_list_files[idx_2] + String contig = MakeChrIntervalList.chrs[idx_1][0] + File contig_interval_list = MakeChrIntervalList.contig_interval_list_files[idx_1] # Import our data into GenomicsDB: call SRJOINT.ImportGVCFs as ImportGVCFsIntoGenomicsDB { @@ -146,84 +120,146 @@ workflow SRJointCallGVCFsWithGenomicsDB { } } - # Now we run VariantRecalibrator for indels and snps: - call VARUTIL.IndelsVariantRecalibrator as TrainVQSROnHCIndelVariants { + # Merge all sites-only VCFs + call VARUTIL.GatherVcfs as MergeSitesOnlyVCFs { + input: + input_vcfs = MakeSitesOnlyVCF.sites_only_vcf, + input_vcf_indices = MakeSitesOnlyVCF.sites_only_vcf_index, + prefix = prefix + ".sites_only" + } + + ######################################################################## + # Call VETS / VQSR-lite: + call VARUTIL.ExtractVariantAnnotations as ExtractIndelVariantAnnotations { input: - vcfs = MakeSitesOnlyVCF.sites_only_vcf, - vcf_indices = MakeSitesOnlyVCF.sites_only_vcf_index, - prefix = prefix + ".indels", - recalibration_tranche_values = indel_recalibration_tranche_values, + vcf = MergeSitesOnlyVCFs.output_vcf, + vcf_index = MergeSitesOnlyVCFs.output_vcf_index, + + prefix = prefix, + mode = "INDEL", + recalibration_annotation_values = indel_recalibration_annotation_values, + known_reference_variants = indel_known_reference_variants, known_reference_variants_index = indel_known_reference_variants_index, known_reference_variants_identifier = indel_known_reference_variants_identifier, - is_known = indel_is_known, is_training = indel_is_training, - is_truth = indel_is_truth, - prior = indel_prior, - use_allele_specific_annotations = false, - max_gaussians = indel_max_gaussians, + is_calibration = indel_is_calibration, + + max_unlabeled_variants = indel_max_unlabeled_variants, } - call VARUTIL.SNPsVariantRecalibratorCreateModel as TrainVQSROnHCSnpVariants { + call VARUTIL.ExtractVariantAnnotations as ExtractSnpVariantAnnotations { input: - vcfs = MakeSitesOnlyVCF.sites_only_vcf, - vcf_indices = MakeSitesOnlyVCF.sites_only_vcf_index, - prefix = prefix + ".snps", - recalibration_tranche_values = snp_recalibration_tranche_values, + vcf = MergeSitesOnlyVCFs.output_vcf, + vcf_index = MergeSitesOnlyVCFs.output_vcf_index, + + prefix = prefix, + mode = "SNP", + recalibration_annotation_values = snp_recalibration_annotation_values, + known_reference_variants = snp_known_reference_variants, known_reference_variants_index = snp_known_reference_variants_index, known_reference_variants_identifier = snp_known_reference_variants_identifier, - is_known = snp_is_known, is_training = snp_is_training, - is_truth = snp_is_truth, - prior = snp_prior, - use_allele_specific_annotations = false, - max_gaussians = snp_max_gaussians, + is_calibration = snp_is_calibration, + + max_unlabeled_variants = snp_max_unlabeled_variants, + } + + call VARUTIL.TrainVariantAnnotationsModel as TrainIndelVariantAnnotationsModel { + input: + annotation_hdf5 = ExtractIndelVariantAnnotations.annotation_hdf5, + mode = "INDEL", + prefix = prefix, + } + + call VARUTIL.TrainVariantAnnotationsModel as TrainSnpVariantAnnotationsModel { + input: + annotation_hdf5 = ExtractIndelVariantAnnotations.annotation_hdf5, + mode = "INDEL", + prefix = prefix, } # Shard by contig for speed: - scatter (idx_3 in range(length(JointCallGVCFs.output_vcf))) { + scatter (idx_2 in range(length(JointCallGVCFs.output_vcf))) { - File joint_called_vcf = JointCallGVCFs.output_vcf[idx_3] - File joint_called_vcf_index = JointCallGVCFs.output_vcf_index[idx_3] + String contig_2 = MakeChrIntervalList.chrs[idx_2][0] + File joint_called_vcf = JointCallGVCFs.output_vcf[idx_2] + File joint_called_vcf_index = JointCallGVCFs.output_vcf_index[idx_2] - call VARUTIL.ApplyVqsr as ApplyVqsr { + call VARUTIL.ScoreVariantAnnotations as ScoreSnpVariantAnnotations { input: vcf = joint_called_vcf, vcf_index = joint_called_vcf_index, - prefix = basename(basename(joint_called_vcf, ".vcf.gz"), ".vcf") + ".vqsr", + sites_only_extracted_vcf = ExtractSnpVariantAnnotations.sites_only_vcf, + sites_only_extracted_vcf_index = ExtractSnpVariantAnnotations.sites_only_vcf_index, - snps_recalibration = TrainVQSROnHCSnpVariants.recalibration, - snps_recalibration_index = TrainVQSROnHCSnpVariants.recalibration_index, - snps_tranches = TrainVQSROnHCSnpVariants.tranches, - snp_filter_level = snp_filter_level, + model_prefix = prefix + "_train_SNP", + model_files = flatten([[TrainSnpVariantAnnotationsModel.training_scores, TrainSnpVariantAnnotationsModel.positive_model_scorer_pickle], select_all([ + TrainSnpVariantAnnotationsModel.unlabeled_positive_model_scores, + TrainSnpVariantAnnotationsModel.calibration_set_scores, + TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle + ])]), + prefix = prefix + "_SNP_" + contig_2, + mode = "SNP", - indels_recalibration = TrainVQSROnHCIndelVariants.recalibration, - indels_recalibration_index = TrainVQSROnHCIndelVariants.recalibration_index, - indels_tranches = TrainVQSROnHCIndelVariants.tranches, - indel_filter_level = indel_filter_level, + calibration_sensitivity_threshold = snp_calibration_sensitivity, - use_allele_specific_annotations = false, + recalibration_annotation_values = snp_recalibration_annotation_values, + + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_training = snp_is_training, + is_calibration = snp_is_calibration, + } + + call VARUTIL.ScoreVariantAnnotations as ScoreIndelVariantAnnotations { + input: + vcf = ScoreSnpVariantAnnotations.scored_vcf, + vcf_index = ScoreSnpVariantAnnotations.scored_vcf_index, + + sites_only_extracted_vcf = ExtractIndelVariantAnnotations.sites_only_vcf, + sites_only_extracted_vcf_index = ExtractIndelVariantAnnotations.sites_only_vcf_index, + + model_prefix = prefix + "_train_INDEL", + model_files = flatten([[TrainIndelVariantAnnotationsModel.training_scores, TrainIndelVariantAnnotationsModel.positive_model_scorer_pickle], select_all([ + TrainIndelVariantAnnotationsModel.unlabeled_positive_model_scores, + TrainIndelVariantAnnotationsModel.calibration_set_scores, + TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle + ])]), + prefix = prefix + "_ALL_" + contig_2, + mode = "INDEL", + + calibration_sensitivity_threshold = indel_calibration_sensitivity, + + recalibration_annotation_values = indel_recalibration_annotation_values, + + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_training = indel_is_training, + is_calibration = indel_is_calibration, } # Now we need to annotate our variants by region: if (defined(annotation_bed_files)) { call VARUTIL.AnnotateVcfWithBedRegions as AnnotateVcfRegions { input: - vcf = ApplyVqsr.recalibrated_vcf, - vcf_index = ApplyVqsr.recalibrated_vcf_index, + vcf = ScoreIndelVariantAnnotations.scored_vcf, + vcf_index = ScoreIndelVariantAnnotations.scored_vcf_index, bed_files = select_first([annotation_bed_files]), bed_file_indexes = select_first([annotation_bed_file_indexes]), bed_file_annotation_names = select_first([annotation_bed_file_annotation_names]), - prefix = basename(basename(ApplyVqsr.recalibrated_vcf, ".vcf.gz"), ".vcf") + ".region_annotated", + prefix = basename(basename(ScoreIndelVariantAnnotations.scored_vcf, ".vcf.gz"), ".vcf") + ".region_annotated", } } - File recalibrated_vcf = select_first([AnnotateVcfRegions.annotated_vcf, ApplyVqsr.recalibrated_vcf]) - File recalibrated_vcf_index = select_first([AnnotateVcfRegions.annotated_vcf_index, ApplyVqsr.recalibrated_vcf_index]) + File recalibrated_vcf = select_first([AnnotateVcfRegions.annotated_vcf, ScoreIndelVariantAnnotations.scored_vcf]) + File recalibrated_vcf_index = select_first([AnnotateVcfRegions.annotated_vcf_index, ScoreIndelVariantAnnotations.scored_vcf_index]) # Now functionally annotate each VCF: if (defined(snpeff_db)) { @@ -251,18 +287,18 @@ workflow SRJointCallGVCFsWithGenomicsDB { } # Consolidate files: - call VARUTIL.GatherVcfs as GatherRecalibratedVcfs { + call VARUTIL.GatherVcfs as GatherRescoredVcfs { input: input_vcfs = vcf_for_merging, input_vcf_indices = vcf_index_for_merging, - prefix = prefix + ".recalibrated.combined" + prefix = prefix + ".rescored.combined" } # Convert to Zarr call SGKit.ConvertToZarrStore as ConvertToZarr { input: - gvcf = GatherRecalibratedVcfs.output_vcf, - tbi = GatherRecalibratedVcfs.output_vcf_index, + gvcf = GatherRescoredVcfs.output_vcf, + tbi = GatherRescoredVcfs.output_vcf_index, prefix = prefix, outdir = outdir } @@ -270,8 +306,8 @@ workflow SRJointCallGVCFsWithGenomicsDB { # Convert the output to a HAIL Matrix Table: call Hail.ConvertToHailMT as CreateHailMatrixTable { input: - gvcf = GatherRecalibratedVcfs.output_vcf, - tbi = GatherRecalibratedVcfs.output_vcf_index, + gvcf = GatherRescoredVcfs.output_vcf, + tbi = GatherRescoredVcfs.output_vcf_index, reference = sub(sub(ref_map["fasta"], "^.*/", ""), "\.[fasta]*$", ""), ref_fasta = ref_map["fasta"], ref_fai = ref_map["fai"], @@ -279,31 +315,94 @@ workflow SRJointCallGVCFsWithGenomicsDB { outdir = outdir } - # Finalize: + ################################ + # Finalize the regular output files: + ############ File keyfile = CreateHailMatrixTable.monitoring_log + String recalibration_dir = outdir + "/recalibration_files" + String recalibration_model_dir = outdir + "/recalibration_files/model" + String recalibration_results_dir = outdir + "/recalibration_files/results" call FF.FinalizeToDir as FinalizeGenomicsDB { input: outdir = outdir + "/GenomicsDB", keyfile = keyfile, files = ImportGVCFsIntoGenomicsDB.output_genomicsdb } call FF.FinalizeToFile as FinalizeRawVCF { input: outdir = outdir, keyfile = keyfile, file = GatherRawVcfs.output_vcf } call FF.FinalizeToFile as FinalizeRawTBI { input: outdir = outdir, keyfile = keyfile, file = GatherRawVcfs.output_vcf_index } - call FF.FinalizeToFile as FinalizeIndelRecalFile { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration } - call FF.FinalizeToFile as FinalizeIndelRecalIndex { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCIndelVariants.recalibration_index } - call FF.FinalizeToFile as FinalizeIndelRecalTranches { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCIndelVariants.tranches } - call FF.FinalizeToFile as FinalizeIndelRecalModelReport { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCIndelVariants.model_report } + call FF.FinalizeToFile as FinalizeVETSVCF { input: outdir = outdir, keyfile = keyfile, file = GatherRescoredVcfs.output_vcf } + call FF.FinalizeToFile as FinalizeVETSTBI { input: outdir = outdir, keyfile = keyfile, file = GatherRescoredVcfs.output_vcf_index } + + ################################ + # Finalize the VETS files: + ############ - call FF.FinalizeToFile as FinalizeSnpRecalFile { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration } - call FF.FinalizeToFile as FinalizeSnpRecalIndex { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCSnpVariants.recalibration_index } - call FF.FinalizeToFile as FinalizeSnpRecalTranches { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCSnpVariants.tranches } - call FF.FinalizeToFile as FinalizeSnpRecalModelReport { input: outdir = outdir + "/recalibration_files", keyfile = keyfile, file = TrainVQSROnHCSnpVariants.model_report } + # ExtractVariantAnnotations: + call FF.FinalizeToFile as FinalizeSnpExtractedAnnotations { input: outdir = recalibration_model_dir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.annotation_hdf5 } + call FF.FinalizeToFile as FinalizeSnpExtractedSitesOnlyVcf { input: outdir = recalibration_model_dir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.sites_only_vcf } + call FF.FinalizeToFile as FinalizeSnpExtractedSitesOnlyVcfIndex { input: outdir = recalibration_model_dir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.sites_only_vcf_index } + if (defined(ExtractSnpVariantAnnotations.unlabeled_annotation_hdf5)) { + call FF.FinalizeToFile as FinalizeSnpExtractedUnlabeledAnnotations { input: outdir = recalibration_model_dir, keyfile = keyfile, file = select_first([ExtractSnpVariantAnnotations.unlabeled_annotation_hdf5]) } + } + call FF.FinalizeToFile as FinalizeIndelExtractedAnnotations { input: outdir = recalibration_model_dir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.annotation_hdf5 } + call FF.FinalizeToFile as FinalizeIndelExtractedSitesOnlyVcf { input: outdir = recalibration_model_dir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.sites_only_vcf } + call FF.FinalizeToFile as FinalizeIndelExtractedSitesOnlyVcfIndex { input: outdir = recalibration_model_dir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.sites_only_vcf_index } + if (defined(ExtractIndelVariantAnnotations.unlabeled_annotation_hdf5)) { + call FF.FinalizeToFile as FinalizeIndelExtractedUnlabeledAnnotations { input: outdir = recalibration_model_dir, keyfile = keyfile, file = select_first([ExtractIndelVariantAnnotations.unlabeled_annotation_hdf5]) } + } - call FF.FinalizeToFile as FinalizeVQSRVCF { input: outdir = outdir, keyfile = keyfile, file = GatherRecalibratedVcfs.output_vcf } - call FF.FinalizeToFile as FinalizeVQSRTBI { input: outdir = outdir, keyfile = keyfile, file = GatherRecalibratedVcfs.output_vcf_index } + # TrainVariantAnnotationsModel + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsTrainingScores { input: outdir = recalibration_model_dir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.training_scores } + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsPositiveModelScorer { input: outdir = recalibration_model_dir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.positive_model_scorer_pickle } + if (defined(TrainSnpVariantAnnotationsModel.unlabeled_positive_model_scores)) { + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsUnlabeledPositiveModelScores { input: outdir = recalibration_model_dir, keyfile = keyfile, file = select_first([TrainSnpVariantAnnotationsModel.unlabeled_positive_model_scores]) } + } + if (defined(TrainSnpVariantAnnotationsModel.calibration_set_scores)) { + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsCalibrationSetScores { input: outdir = recalibration_model_dir, keyfile = keyfile, file = select_first([TrainSnpVariantAnnotationsModel.calibration_set_scores]) } + } + if (defined(TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle)) { + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsNegativeModelScorer { input: outdir = recalibration_model_dir, keyfile = keyfile, file = select_first([TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle]) } + } + + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsTrainingScores { input: outdir = recalibration_model_dir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.training_scores } + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsPositiveModelScorer { input: outdir = recalibration_model_dir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.positive_model_scorer_pickle } + if (defined(TrainIndelVariantAnnotationsModel.unlabeled_positive_model_scores)) { + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsUnlabeledPositiveModelScores { input: outdir = recalibration_model_dir, keyfile = keyfile, file = select_first([TrainIndelVariantAnnotationsModel.unlabeled_positive_model_scores]) } + } + if (defined(TrainIndelVariantAnnotationsModel.calibration_set_scores)) { + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsCalibrationSetScores { input: outdir = recalibration_model_dir, keyfile = keyfile, file = select_first([TrainIndelVariantAnnotationsModel.calibration_set_scores]) } + } + if (defined(TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle)) { + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsNegativeModelScorer { input: outdir = recalibration_model_dir, keyfile = keyfile, file = select_first([TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle]) } + } + + # ScoreVariantAnnotations + # This was done per-contig, so we need to finalize per-contig: + scatter (idx_3 in range(length(MakeChrIntervalList.contig_interval_list_files))) { + + String contig_3 = MakeChrIntervalList.chrs[idx_3][0] + + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoredVcf { input: outdir = recalibration_results_dir + "/" + contig_3, keyfile = keyfile, file = ScoreSnpVariantAnnotations.scored_vcf[idx_3] } + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoredVcfIndex { input: outdir = recalibration_results_dir + "/" + contig_3, keyfile = keyfile, file = ScoreSnpVariantAnnotations.scored_vcf_index[idx_3] } + if (defined(ScoreSnpVariantAnnotations.annotations_hdf5)) { + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsAnnotationsHdf5 { input: outdir = recalibration_results_dir + "/" + contig_3, keyfile = keyfile, file = select_first([ScoreSnpVariantAnnotations.annotations_hdf5[idx_3]]) } + } + if (defined(ScoreSnpVariantAnnotations.scores_hdf5)) { + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoresHdf5 { input: outdir = recalibration_results_dir + "/" + contig_3, keyfile = keyfile, file = select_first([ScoreSnpVariantAnnotations.scores_hdf5[idx_3]]) } + } + + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoredVcf { input: outdir = recalibration_results_dir + "/" + contig_3, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf[idx_3] } + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoredVcfIndex { input: outdir = recalibration_results_dir + "/" + contig_3, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf_index[idx_3] } + if (defined(ScoreIndelVariantAnnotations.annotations_hdf5)) { + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsAnnotationsHdf5 { input: outdir = recalibration_results_dir + "/" + contig_3, keyfile = keyfile, file = select_first([ScoreIndelVariantAnnotations.annotations_hdf5[idx_3]]) } + } + if (defined(ScoreIndelVariantAnnotations.scores_hdf5)) { + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoresHdf5 { input: outdir = recalibration_results_dir + "/" + contig_3, keyfile = keyfile, file = select_first([ScoreIndelVariantAnnotations.scores_hdf5[idx_3]]) } + } + } # Make an alias for the functionally annotated data: if (defined(snpeff_db)) { - File annotated_vcf = FinalizeVQSRVCF.gcs_path - File annotated_vcf_tbi = FinalizeVQSRTBI.gcs_path + File annotated_vcf = FinalizeVETSVCF.gcs_path + File annotated_vcf_tbi = FinalizeVETSTBI.gcs_path } output { @@ -312,18 +411,8 @@ workflow SRJointCallGVCFsWithGenomicsDB { File raw_joint_vcf = FinalizeRawVCF.gcs_path File raw_joint_vcf_tbi = FinalizeRawTBI.gcs_path - File vqsr_indel_recal_file = FinalizeIndelRecalFile.gcs_path - File vqsr_indel_recal_file_index = FinalizeIndelRecalIndex.gcs_path - File vqsr_indel_recal_tranches = FinalizeIndelRecalTranches.gcs_path - File vqsr_indel_recal_model_report = FinalizeIndelRecalModelReport.gcs_path - - File vqsr_snp_recal_file = FinalizeSnpRecalFile.gcs_path - File vqsr_snp_recal_file_index = FinalizeSnpRecalIndex.gcs_path - File vqsr_snp_recal_tranches = FinalizeSnpRecalTranches.gcs_path - File vqsr_snp_recal_model_report = FinalizeSnpRecalModelReport.gcs_path - - File joint_recalibrated_vcf = FinalizeVQSRVCF.gcs_path - File joint_recalibrated_vcf_tbi = FinalizeVQSRTBI.gcs_path + File joint_recalibrated_vcf = FinalizeVETSVCF.gcs_path + File joint_recalibrated_vcf_tbi = FinalizeVETSTBI.gcs_path File? annotated_joint_vcf = annotated_vcf File? annotated_joint_vcf_tbi = annotated_vcf_tbi diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 000ccf7f9..58f83ca00 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -68,6 +68,11 @@ workflow SRWholeGenome { String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRWholeGenome/~{participant_name}" + String bam_dir = outdir + "/alignments" + String metrics_dir = outdir + "/metrics" + String smalldir = outdir + "/variants/small" + String recalibration_dir = outdir + "/variants/recalibration_files" + # gather across (potential multiple) input CCS BAMs if (length(aligned_bams) > 1) { scatter (pair in zip(aligned_bams, aligned_bais)) { @@ -101,14 +106,12 @@ workflow SRWholeGenome { } } - String bam_dir = outdir + "/alignments" - call FF.FinalizeToFile as FinalizeBam { input: outdir = bam_dir, file = bam, name = "~{participant_name}.bam" } call FF.FinalizeToFile as FinalizeBai { input: outdir = bam_dir, file = bai, name = "~{participant_name}.bam.bai" } if (defined(bed_to_compute_coverage)) { call FF.FinalizeToFile as FinalizeRegionalCoverage { input: outdir = bam_dir, file = select_first([RegionalCoverage.cov_summary]) } } - String metrics_dir = outdir + "/metrics" + call FF.FinalizeToFile as FinalizeFastQCReport { input: outdir = metrics_dir, @@ -125,8 +128,6 @@ workflow SRWholeGenome { } } - String smalldir = outdir + "/variants/small" - # Handle DeepVariant First: if (run_dv_pepper_analysis) { @@ -257,7 +258,7 @@ workflow SRWholeGenome { TrainSnpVariantAnnotationsModel.calibration_set_scores, TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle ])]), - prefix = participant_name, + prefix = participant_name + "_SNP", mode = "SNP", calibration_sensitivity_threshold = snp_calibration_sensitivity, @@ -285,7 +286,7 @@ workflow SRWholeGenome { TrainIndelVariantAnnotationsModel.calibration_set_scores, TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle ])]), - prefix = participant_name, + prefix = participant_name + "_ALL", mode = "INDEL", calibration_sensitivity_threshold = indel_calibration_sensitivity, @@ -308,7 +309,7 @@ workflow SRWholeGenome { } ## Rename our samples so we can use them later: - ## TODO: Move this to the top! + ## TODO: Move this to the top so we don't have to rename a million files! call VARUTIL.RenameSingleSampleVcf as RenameRawHcVcf { input: vcf = CallVariantsWithHaplotypeCaller.output_vcf, @@ -364,42 +365,62 @@ workflow SRWholeGenome { ############ # ExtractVariantAnnotations: - call FF.FinalizeToFile as FinalizeSnpExtractedAnnotations { input: outdir = outdir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.annotation_hdf5 } - call FF.FinalizeToFile as FinalizeSnpExtractedSitesOnlyVcf { input: outdir = outdir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.sites_only_vcf } - call FF.FinalizeToFile as FinalizeSnpExtractedSitesOnlyVcfIndex { input: outdir = outdir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.sites_only_vcf_index } + call FF.FinalizeToFile as FinalizeSnpExtractedAnnotations { input: outdir = recalibration_dir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.annotation_hdf5 } + call FF.FinalizeToFile as FinalizeSnpExtractedSitesOnlyVcf { input: outdir = recalibration_dir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.sites_only_vcf } + call FF.FinalizeToFile as FinalizeSnpExtractedSitesOnlyVcfIndex { input: outdir = recalibration_dir, keyfile = keyfile, file = ExtractSnpVariantAnnotations.sites_only_vcf_index } if (defined(ExtractSnpVariantAnnotations.unlabeled_annotation_hdf5)) { - call FF.FinalizeToFile as FinalizeSnpExtractedUnlabeledAnnotations { input: outdir = outdir, keyfile = keyfile, file = select_first([ExtractSnpVariantAnnotations.unlabeled_annotation_hdf5]) } + call FF.FinalizeToFile as FinalizeSnpExtractedUnlabeledAnnotations { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([ExtractSnpVariantAnnotations.unlabeled_annotation_hdf5]) } } - call FF.FinalizeToFile as FinalizeIndelExtractedAnnotations { input: outdir = outdir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.annotation_hdf5 } - call FF.FinalizeToFile as FinalizeIndelExtractedSitesOnlyVcf { input: outdir = outdir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.sites_only_vcf } - call FF.FinalizeToFile as FinalizeIndelExtractedSitesOnlyVcfIndex { input: outdir = outdir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.sites_only_vcf_index } + call FF.FinalizeToFile as FinalizeIndelExtractedAnnotations { input: outdir = recalibration_dir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.annotation_hdf5 } + call FF.FinalizeToFile as FinalizeIndelExtractedSitesOnlyVcf { input: outdir = recalibration_dir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.sites_only_vcf } + call FF.FinalizeToFile as FinalizeIndelExtractedSitesOnlyVcfIndex { input: outdir = recalibration_dir, keyfile = keyfile, file = ExtractIndelVariantAnnotations.sites_only_vcf_index } if (defined(ExtractIndelVariantAnnotations.unlabeled_annotation_hdf5)) { - call FF.FinalizeToFile as FinalizeIndelExtractedUnlabeledAnnotations { input: outdir = outdir, keyfile = keyfile, file = select_first([ExtractIndelVariantAnnotations.unlabeled_annotation_hdf5]) } + call FF.FinalizeToFile as FinalizeIndelExtractedUnlabeledAnnotations { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([ExtractIndelVariantAnnotations.unlabeled_annotation_hdf5]) } } # TrainVariantAnnotationsModel - call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsTrainingScores { input: outdir = outdir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.training_scores } - call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsPositiveModelScorer { input: outdir = outdir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.positive_model_scorer_pickle } - call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsUnlabeledPositiveModelScores { input: outdir = outdir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.unlabeled_positive_model_scores } - call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsCalibrationSetScores { input: outdir = outdir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.calibration_set_scores } - call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsNegativeModelScorer { input: outdir = outdir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle } - - call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsTrainingScores { input: outdir = outdir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.training_scores } - call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsPositiveModelScorer { input: outdir = outdir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.positive_model_scorer_pickle } - call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsUnlabeledPositiveModelScores { input: outdir = outdir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.unlabeled_positive_model_scores } - call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsCalibrationSetScores { input: outdir = outdir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.calibration_set_scores } - call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsNegativeModelScorer { input: outdir = outdir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle } + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsTrainingScores { input: outdir = recalibration_dir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.training_scores } + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsPositiveModelScorer { input: outdir = recalibration_dir, keyfile = keyfile, file = TrainSnpVariantAnnotationsModel.positive_model_scorer_pickle } + if (defined(TrainSnpVariantAnnotationsModel.unlabeled_positive_model_scores)) { + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsUnlabeledPositiveModelScores { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([TrainSnpVariantAnnotationsModel.unlabeled_positive_model_scores]) } + } + if (defined(TrainSnpVariantAnnotationsModel.calibration_set_scores)) { + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsCalibrationSetScores { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([TrainSnpVariantAnnotationsModel.calibration_set_scores]) } + } + if (defined(TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle)) { + call FF.FinalizeToFile as FinalizeSnpTrainVariantAnnotationsNegativeModelScorer { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle]) } + } + + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsTrainingScores { input: outdir = recalibration_dir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.training_scores } + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsPositiveModelScorer { input: outdir = recalibration_dir, keyfile = keyfile, file = TrainIndelVariantAnnotationsModel.positive_model_scorer_pickle } + if (defined(TrainIndelVariantAnnotationsModel.unlabeled_positive_model_scores)) { + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsUnlabeledPositiveModelScores { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([TrainIndelVariantAnnotationsModel.unlabeled_positive_model_scores]) } + } + if (defined(TrainIndelVariantAnnotationsModel.calibration_set_scores)) { + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsCalibrationSetScores { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([TrainIndelVariantAnnotationsModel.calibration_set_scores]) } + } + if (defined(TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle)) { + call FF.FinalizeToFile as FinalizeIndelTrainVariantAnnotationsNegativeModelScorer { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle]) } + } # ScoreVariantAnnotations - call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoredVcf { input: outdir = outdir, keyfile = keyfile, file = ScoreSnpVariantAnnotations.scored_vcf } - call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoredVcfIndex { input: outdir = outdir, keyfile = keyfile, file = ScoreSnpVariantAnnotations.scored_vcf_index } - call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsAnnotationsHdf5 { input: outdir = outdir, keyfile = keyfile, file = ScoreSnpVariantAnnotations.annotations_hdf5 } - call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoresHdf5 { input: outdir = outdir, keyfile = keyfile, file = ScoreSnpVariantAnnotations.scores_hdf5 } - - call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoredVcf { input: outdir = outdir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf } - call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoredVcfIndex { input: outdir = outdir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf_index } - call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsAnnotationsHdf5 { input: outdir = outdir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.annotations_hdf5 } - call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoresHdf5 { input: outdir = outdir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scores_hdf5 } + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoredVcf { input: outdir = recalibration_dir, keyfile = keyfile, file = ScoreSnpVariantAnnotations.scored_vcf } + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoredVcfIndex { input: outdir = recalibration_dir, keyfile = keyfile, file = ScoreSnpVariantAnnotations.scored_vcf_index } + if (defined(ScoreSnpVariantAnnotations.annotations_hdf5)) { + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsAnnotationsHdf5 { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([ScoreSnpVariantAnnotations.annotations_hdf5]) } + } + if (defined(ScoreSnpVariantAnnotations.scores_hdf5)) { + call FF.FinalizeToFile as FinalizeScoreSnpVariantAnnotationsScoresHdf5 { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([ScoreSnpVariantAnnotations.scores_hdf5]) } + } + + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoredVcf { input: outdir = recalibration_dir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf } + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoredVcfIndex { input: outdir = recalibration_dir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf_index } + if (defined(ScoreIndelVariantAnnotations.annotations_hdf5)) { + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsAnnotationsHdf5 { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([ScoreIndelVariantAnnotations.annotations_hdf5]) } + } + if (defined(ScoreIndelVariantAnnotations.scores_hdf5)) { + call FF.FinalizeToFile as FinalizeScoreIndelVariantAnnotationsScoresHdf5 { input: outdir = recalibration_dir, keyfile = keyfile, file = select_first([ScoreIndelVariantAnnotations.scores_hdf5]) } + } } output { diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 59d6c869c..286805660 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1764,18 +1764,18 @@ task ScoreVariantAnnotations { ${resource_flags} \ --resource:extracted,extracted=true ~{sites_only_extracted_vcf} \ --${mode_lower}-calibration-sensitivity-threshold ~{calibration_sensitivity_threshold} \ - -O ~{prefix}_~{mode}_scored \ + -O ~{prefix}_scored \ &> ~{prefix}_ScoreVariantAnnotations_~{mode}.log kill $monitoring_pid >>> output { - File scored_vcf = "~{prefix}_~{mode}_scored.vcf.gz" - File scored_vcf_index = "~{prefix}_~{mode}_scored.vcf.gz.tbi" + File scored_vcf = "~{prefix}_scored.vcf.gz" + File scored_vcf_index = "~{prefix}_scored.vcf.gz.tbi" - File? annotations_hdf5 = "~{prefix}_~{mode}_scored.annot.hdf5" - File? scores_hdf5 = "~{prefix}_~{mode}_scored.scores.hdf5" + File? annotations_hdf5 = "~{prefix}_scored.annot.hdf5" + File? scores_hdf5 = "~{prefix}_scored.scores.hdf5" File log = "~{prefix}_ExtractVariantAnnotations_~{mode}.log" From 1c51f6e17db4b52676bd0e162cd67fce16fa8f03 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 8 Sep 2023 15:14:58 -0400 Subject: [PATCH 209/297] Updates for barcoding. - Added fingerprinting / barcode task, `ExtractFingerprintAndBarcode`, to `VariantUtils.wdl`. - Added call to `ExtractFingerprintAndBarcode` in `SRWholeGenome.wdl`. - Updated version of sr-utils to latest in `SRUtils::BwaMem2`. --- wdl/SRWholeGenome.wdl | 23 ++++++ wdl/tasks/SRUtils.wdl | 2 +- wdl/tasks/VariantUtils.wdl | 164 +++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 58f83ca00..1f782810a 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -61,6 +61,8 @@ workflow SRWholeGenome { File? bed_to_compute_coverage + File? fingerprint_haploytpe_db_file + Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! } @@ -301,6 +303,19 @@ workflow SRWholeGenome { } ######################################################################## + if (defined(fingerprint_haploytpe_db_file)) { + call VARUTIL.ExtractFingerprintAndBarcode as FingerprintAndBarcodeVcf { + input: + vcf = ScoreIndelVariantAnnotations.scored_vcf, + vcf_index = ScoreIndelVariantAnnotations.scored_vcf_index, + haplotype_database_file = select_first([fingerprint_haploytpe_db_file]), + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = participant_name + } + } + call VARUTIL.SelectVariants as RemoveFilteredVariants { input: vcf = ScoreIndelVariantAnnotations.scored_vcf, @@ -360,6 +375,11 @@ workflow SRWholeGenome { call FF.FinalizeToFile as FinalizeHCRescoredFilteredVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcfFiltered.new_sample_name_vcf } call FF.FinalizeToFile as FinalizeHCRescoredFilteredTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcfFiltered.new_sample_name_vcf_index } + # Finalize other outputs: + if (defined(fingerprint_haploytpe_db_file)) { + call FF.FinalizeToFile as FinalizeFingerprintVcf { input: outdir = smalldir, keyfile = keyfile, file = select_first([FingerprintAndBarcodeVcf.output_vcf]) } + } + ################################ # Finalize the VETS files: ############ @@ -444,6 +464,9 @@ workflow SRWholeGenome { File? bed_cov_summary = FinalizeRegionalCoverage.gcs_path + File? fingerprint_vcf = FinalizeFingerprintVcf.gcs_path + String? barcode = FingerprintAndBarcodeVcf.barcode + ######################################## File? dvp_vcf = FinalizeDVPepperVcf.gcs_path diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 1492c4ef3..e8de0c120 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -237,7 +237,7 @@ task BwaMem2 { boot_disk_gb: 10, preemptible_tries: 1, max_retries: 1, - docker: "us.gcr.io/broad-dsp-lrma/sr-utils:0.2.0" + docker: "us.gcr.io/broad-dsp-lrma/sr-utils:0.2.1" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 286805660..73b24da94 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1454,6 +1454,170 @@ task ExtractFingerprint { } } +task ExtractFingerprintAndBarcode { + + input { + File vcf + File vcf_index + + File haplotype_database_file + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String prefix = "fingerprint" + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 10 + ceil(size(vcf, "GB")) + + ceil(size(vcf_index, "GB")) + + ceil(size(haplotype_database_file, "GB")) + + ceil(size(ref_fasta, "GB")) + + ceil(size(ref_fasta_fai, "GB")) + + ceil(size(ref_dict, "GB")) + + command <<< + set -euxo pipefail + +python << CODE + +import pysam + +from collections import defaultdict +from tqdm import tqdm + +def read_reference(reference_fasta): + + ref = dict() + + print(f"Ingesting FASTA reference: {reference_fasta}") + with pysam.FastaFile(reference_fasta) as f: + + # Get all sequence names + contigs = f.references + + for contig in contigs: + sequence = f.fetch(contig) + ref[contig] = sequence + + return ref + + +def extract_barcode(vcf_file, haplotype_database_file, ref_seq_dict, vcf_out_path): + '''Extract a barcode from the given VCF file. + Produces a barcode string as well as a VCF file with any variants from the + barcode sites in the original file. + + Based on haplotype_database_files used in Picard fingerprinting. + Example: gs://gcp-public-data--broad-references/hg38/v0/Homo_sapiens_assembly38.haplotype_database.txt + ''' + + # Read in the fingerprint file: + barcode_site_contig_pos_dict = defaultdict(list) + with open(haplotype_database_file, 'r') as f: + for line in f: + if line.startswith("@") or line.startswith("#"): + continue + fields = line.strip().split('\t') + barcode_site_contig_pos_dict[fields[0]].append(int(fields[1])) + + barcode_alleles = [] + num_sites = sum([len(s) for s in barcode_site_contig_pos_dict.values()]) + num_found = 0 + + with pysam.VariantFile(vcf_file, 'r') as vcf: + with pysam.VariantFile(vcf_out_path, 'w', header=vcf.header) as vcf_out: + with tqdm(desc="Extracting barcode variants", total=num_sites) as pbar: + for contig, sites in barcode_site_contig_pos_dict.items(): + for site in sites: + variants = vcf.fetch(contig=contig, start=site-1, stop=site) + found = False + for variant in variants: + # Should only get here if we are at the site. + found = True + gts = [s['GT'] for s in variant.samples.values()] + if len(gts) > 1: + # Too many genotypes, set N: + bca = "N" + elif gts[0] == (0, 0): + # Ref: + bca = ref_seq_dict[contig][site-1] + elif gts[0] == (0, 1) or gts[0] == (1, 0): + # Het -> multi-infection -> N + bca = "N" + # Must be Hom VAR + elif len(variant.alts[0]) != 1: + # INDEL / MNP -> N + bca = "N" + else: + bca = variant.alts[0] + + barcode_alleles.append(bca) + + # in any event we have found our variant and we can stop: + break + + num_found += found + if found: + # Write the variant to the output file: + vcf_out.write(variant) + else: + # We need to pull from the reference for this site: + # Add 1 for genomic coordinates: + bca = ref_seq_dict[contig][site-1] + + # Add our barcode allele: + barcode_alleles.append(bca) + + pbar.update(1) + + return "".join(barcode_alleles) + + + +# Read the reference file: +ref = read_reference("~{ref_fasta}") + +# Calculate the barcode info: +barcode = extract_barcode(vcf_file, "~{haplotype_database_file}", ref, "~{prefix}.fingerprint.vcf") + +print(f"Extracted barcode: {barcode}") + +# Write the barcode string to a file: +with open("~{prefix}.barcode.txt", 'w') as f: + f.write(f"{barcode}\n") + +CODE + >>> + + output { + File output_vcf = "~{prefix}.fingerprint.vcf" + File barcode = read_string("~{prefix}.barcode.txt") + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 8, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-utils:0.2.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} task ExtractVariantAnnotations { From 6892a3a084de7e099dc5ffc15e4b4d129f899fc7 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 8 Sep 2023 16:31:38 -0400 Subject: [PATCH 210/297] Minor efficiency cleanup. --- wdl/SRWholeGenome.wdl | 84 +++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 1f782810a..f23b051da 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -191,12 +191,29 @@ workflow SRWholeGenome { contigs_names_to_ignore = contigs_names_to_ignore, } + # Make sure our sample name is correct: + call VARUTIL.RenameSingleSampleVcf as RenameRawHcVcf { + input: + vcf = CallVariantsWithHaplotypeCaller.output_vcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + prefix = participant_name + ".haplotype_caller.renamed", + new_sample_name = participant_name + } + call VARUTIL.RenameSingleSampleVcf as RenameRawHcGvcf { + input: + vcf = CallVariantsWithHaplotypeCaller.output_gvcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_gvcf_index, + prefix = participant_name + ".haplotype_caller.renamed", + is_gvcf = true, + new_sample_name = participant_name + } + ######################################################################## # Call VETS / VQSR-lite: call VARUTIL.ExtractVariantAnnotations as ExtractIndelVariantAnnotations { input: - vcf = CallVariantsWithHaplotypeCaller.output_vcf, - vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + vcf = RenameRawHcVcf.new_sample_name_vcf, + vcf_index = RenameRawHcVcf.new_sample_name_vcf_index, prefix = participant_name, mode = "INDEL", @@ -214,8 +231,8 @@ workflow SRWholeGenome { call VARUTIL.ExtractVariantAnnotations as ExtractSnpVariantAnnotations { input: - vcf = CallVariantsWithHaplotypeCaller.output_vcf, - vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + vcf = RenameRawHcVcf.new_sample_name_vcf, + vcf_index = RenameRawHcVcf.new_sample_name_vcf_index, prefix = participant_name, mode = "SNP", @@ -247,8 +264,8 @@ workflow SRWholeGenome { call VARUTIL.ScoreVariantAnnotations as ScoreSnpVariantAnnotations { input: - vcf = CallVariantsWithHaplotypeCaller.output_vcf, - vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + vcf = RenameRawHcVcf.new_sample_name_vcf, + vcf_index = RenameRawHcVcf.new_sample_name_vcf_index, sites_only_extracted_vcf = ExtractSnpVariantAnnotations.sites_only_vcf, @@ -320,46 +337,11 @@ workflow SRWholeGenome { input: vcf = ScoreIndelVariantAnnotations.scored_vcf, vcf_index = ScoreIndelVariantAnnotations.scored_vcf_index, - prefix = participant_name + ".vets_filtered" - } - - ## Rename our samples so we can use them later: - ## TODO: Move this to the top so we don't have to rename a million files! - call VARUTIL.RenameSingleSampleVcf as RenameRawHcVcf { - input: - vcf = CallVariantsWithHaplotypeCaller.output_vcf, - vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, - prefix = participant_name + ".haplotype_caller.renamed", - new_sample_name = participant_name - } - - call VARUTIL.RenameSingleSampleVcf as RenameRawHcGvcf { - input: - vcf = CallVariantsWithHaplotypeCaller.output_gvcf, - vcf_index = CallVariantsWithHaplotypeCaller.output_gvcf_index, - prefix = participant_name + ".haplotype_caller.renamed", - is_gvcf = true, - new_sample_name = participant_name - } - - call VARUTIL.RenameSingleSampleVcf as RenameSingleSampleVcf { - input: - vcf = ScoreIndelVariantAnnotations.scored_vcf, - vcf_index = ScoreIndelVariantAnnotations.scored_vcf_index, - prefix = participant_name + ".scored", - new_sample_name = participant_name - } - - call VARUTIL.RenameSingleSampleVcf as RenameSingleSampleVcfFiltered { - input: - vcf = RemoveFilteredVariants.vcf_out, - vcf_index = RemoveFilteredVariants.vcf_out_index, - prefix = participant_name + ".vets_filtered", - new_sample_name = participant_name + prefix = participant_name + ".filtered" } # Create a Keyfile for finalization: - File keyfile = RenameSingleSampleVcf.new_sample_name_vcf_index + File keyfile = RemoveFilteredVariants.vcf_out_index # Finalize the raw Joint Calls: call FF.FinalizeToFile as FinalizeHCVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcVcf.new_sample_name_vcf } @@ -370,10 +352,10 @@ workflow SRWholeGenome { call FF.FinalizeToFile as FinalizeHCBaiOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout_index } # Finalize the reclibrated / filtered variants: - call FF.FinalizeToFile as FinalizeHCRescoredVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf } - call FF.FinalizeToFile as FinalizeHCRescoredTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcf.new_sample_name_vcf_index } - call FF.FinalizeToFile as FinalizeHCRescoredFilteredVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcfFiltered.new_sample_name_vcf } - call FF.FinalizeToFile as FinalizeHCRescoredFilteredTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameSingleSampleVcfFiltered.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCRescoredVcf { input: outdir = smalldir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf } + call FF.FinalizeToFile as FinalizeHCRescoredTbi { input: outdir = smalldir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf_index } + call FF.FinalizeToFile as FinalizeHCRescoredFilteredVcf { input: outdir = smalldir, keyfile = keyfile, file = RemoveFilteredVariants.vcf_out } + call FF.FinalizeToFile as FinalizeHCRescoredFilteredTbi { input: outdir = smalldir, keyfile = keyfile, file = RemoveFilteredVariants.vcf_out_index } # Finalize other outputs: if (defined(fingerprint_haploytpe_db_file)) { @@ -482,9 +464,9 @@ workflow SRWholeGenome { File? hc_baiout = FinalizeHCBaiOut.gcs_path File? hc_raw_vcf = FinalizeHCVcf.gcs_path File? hc_raw_tbi = FinalizeHCTbi.gcs_path - File? hc_rescored_vcf = FinalizeHCRescoredFilteredVcf.gcs_path - File? hc_rescored_tbi = FinalizeHCRescoredFilteredTbi.gcs_path - File? hc_rescored_raw_vcf = FinalizeHCRescoredVcf.gcs_path - File? hc_rescored_raw_tbi = FinalizeHCRescoredTbi.gcs_path + File? hc_rescored_vcf = FinalizeHCRescoredVcf.gcs_path + File? hc_rescored_tbi = FinalizeHCRescoredTbi.gcs_path + File? hc_rescored_filtered_vcf = FinalizeHCRescoredFilteredVcf.gcs_path + File? hc_rescored_filtered_tbi = FinalizeHCRescoredFilteredTbi.gcs_path } } From 61f31b3e424c683fdd15e59799a2e4b64ad14741 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 8 Sep 2023 17:05:13 -0400 Subject: [PATCH 211/297] Updates to functional annotation. - Now FunctionalAnnotation::FunctionallyAnnotateVariants natively produces a VCF Index. - `SRJointCallGVCFsWithGenomicsDB` and `ONTPfTypeDrugResistanceMarkers` now use the new natively-produced VCF index and do not re-index the file. - `ONTPfTypeDrugResistanceMarkers` can now optionally skip to the `CallDrugResistanceMutations` step if the input file is already functionally annotated. --- wdl/ONTPfTypeDrugResistanceMarkers.wdl | 26 ++++++++++++++++++-------- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 12 ++++-------- wdl/tasks/FunctionalAnnotation.wdl | 4 ++++ 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/wdl/ONTPfTypeDrugResistanceMarkers.wdl b/wdl/ONTPfTypeDrugResistanceMarkers.wdl index 7cbcc407d..ed4dadd16 100644 --- a/wdl/ONTPfTypeDrugResistanceMarkers.wdl +++ b/wdl/ONTPfTypeDrugResistanceMarkers.wdl @@ -12,31 +12,41 @@ workflow ONTPfTypeDrugResistanceMarkers { String dir_prefix String gcs_out_root_dir + + Boolean do_functional_annotation = true } String outdir = sub(gcs_out_root_dir, "/$", "") + "/ONTPfTypeDrugResistanceMarkers/~{dir_prefix}" - call FUNK.FunctionallyAnnotateVariants { input: vcf = vcf, snpeff_db = snpeff_db } + if (do_functional_annotation) { + call FUNK.FunctionallyAnnotateVariants { input: vcf = vcf, snpeff_db = snpeff_db } + } call CallDrugResistanceMutations { input: - vcf = FunctionallyAnnotateVariants.annotated_vcf, + vcf = select_first([FunctionallyAnnotateVariants.annotated_vcf, vcf]), drug_resistance_list = drug_resistance_list } # Finalize data String dir = outdir + "/reports" - call FF.FinalizeToFile as FinalizeAnnotatedVCF { input: outdir = dir, file = FunctionallyAnnotateVariants.annotated_vcf } - call FF.FinalizeToFile as FinalizeSnpEffSummary { input: outdir = dir, file = FunctionallyAnnotateVariants.snpEff_summary } - call FF.FinalizeToFile as FinalizeSnpEffGenes { input: outdir = dir, file = FunctionallyAnnotateVariants.snpEff_genes } call FF.FinalizeToFile as FinalizeDRReport { input: outdir = dir, file = CallDrugResistanceMutations.report } + if (do_functional_annotation) { + call FF.FinalizeToFile as FinalizeAnnotatedVCF { input: outdir = dir, file = select_first([FunctionallyAnnotateVariants.annotated_vcf]) } + call FF.FinalizeToFile as FinalizeAnnotatedVCFIndex { input: outdir = dir, file = select_first([FunctionallyAnnotateVariants.annotated_vcf_index]) } + call FF.FinalizeToFile as FinalizeSnpEffSummary { input: outdir = dir, file = select_first([FunctionallyAnnotateVariants.snpEff_summary]) } + call FF.FinalizeToFile as FinalizeSnpEffGenes { input: outdir = dir, file = select_first([FunctionallyAnnotateVariants.snpEff_genes]) } + } + output { - File annotated_vcf = FinalizeAnnotatedVCF.gcs_path File drug_res_report = FinalizeDRReport.gcs_path - File snpEff_summary = FinalizeSnpEffSummary.gcs_path - File snpEff_genes = FinalizeSnpEffGenes.gcs_path + + File? annotated_vcf = FinalizeAnnotatedVCF.gcs_path + File? annotated_vcf_index = FinalizeAnnotatedVCFIndex.gcs_path + File? snpEff_summary = FinalizeSnpEffSummary.gcs_path + File? snpEff_genes = FinalizeSnpEffGenes.gcs_path } } diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 78dbba875..6fda0b8cb 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -268,14 +268,10 @@ workflow SRJointCallGVCFsWithGenomicsDB { vcf = recalibrated_vcf, snpeff_db = select_first([snpeff_db]) } - call VARUTIL.IndexVCF as IndexFunkyVcf { - input: - vcf = FunctionallyAnnotate.annotated_vcf - } } File vcf_for_merging = select_first([FunctionallyAnnotate.annotated_vcf, recalibrated_vcf]) - File vcf_index_for_merging = select_first([IndexFunkyVcf.tbi, recalibrated_vcf_index]) + File vcf_index_for_merging = select_first([FunctionallyAnnotate.annotated_vcf_index, recalibrated_vcf_index]) } # Consolidate files: @@ -414,11 +410,11 @@ workflow SRJointCallGVCFsWithGenomicsDB { File joint_recalibrated_vcf = FinalizeVETSVCF.gcs_path File joint_recalibrated_vcf_tbi = FinalizeVETSTBI.gcs_path - File? annotated_joint_vcf = annotated_vcf - File? annotated_joint_vcf_tbi = annotated_vcf_tbi - File joint_mt = CreateHailMatrixTable.gcs_path File joint_zarr = ConvertToZarr.gcs_path + + File? annotated_joint_vcf = annotated_vcf + File? annotated_joint_vcf_tbi = annotated_vcf_tbi } } diff --git a/wdl/tasks/FunctionalAnnotation.wdl b/wdl/tasks/FunctionalAnnotation.wdl index 09d6061ac..107dea2d6 100644 --- a/wdl/tasks/FunctionalAnnotation.wdl +++ b/wdl/tasks/FunctionalAnnotation.wdl @@ -26,10 +26,14 @@ task FunctionallyAnnotateVariants { mv snpEff_summary.html ~{prefix}.snpEff_summary.html mv snpEff_genes.txt ~{prefix}.snpEff_genes.txt + + # Index the output VCF file: + tabix -p vcf ~{prefix}.annotated.vcf.gz >>> output { File annotated_vcf = "~{prefix}.annotated.vcf.gz" + File annotated_vcf_index = "~{prefix}.annotated.vcf.gz.tbi" File snpEff_summary = "~{prefix}.snpEff_summary.html" File snpEff_genes = "~{prefix}.snpEff_genes.txt" } From d41e073dae25ffe44de66013f975367210ac1837 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 11 Sep 2023 11:27:17 -0400 Subject: [PATCH 212/297] Added more snpEff outputs to `SRJointCallGVCFsWithGenomicsDB` --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 6fda0b8cb..6d2d5d857 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -318,6 +318,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { String recalibration_dir = outdir + "/recalibration_files" String recalibration_model_dir = outdir + "/recalibration_files/model" String recalibration_results_dir = outdir + "/recalibration_files/results" + String snpeff_results_dir = outdir + "/snpEff" call FF.FinalizeToDir as FinalizeGenomicsDB { input: outdir = outdir + "/GenomicsDB", keyfile = keyfile, files = ImportGVCFsIntoGenomicsDB.output_genomicsdb } @@ -327,6 +328,11 @@ workflow SRJointCallGVCFsWithGenomicsDB { call FF.FinalizeToFile as FinalizeVETSVCF { input: outdir = outdir, keyfile = keyfile, file = GatherRescoredVcfs.output_vcf } call FF.FinalizeToFile as FinalizeVETSTBI { input: outdir = outdir, keyfile = keyfile, file = GatherRescoredVcfs.output_vcf_index } + if (defined(snpeff_db)) { + call FF.FinalizeToDir as FinalizeSnpEffSummary { input: outdir = snpeff_results_dir, keyfile = keyfile, files = select_all(FunctionallyAnnotate.snpEff_summary) } + call FF.FinalizeToDir as FinalizeSnpEffGenes { input: outdir = snpeff_results_dir, keyfile = keyfile, files = select_all(FunctionallyAnnotate.snpEff_genes) } + } + ################################ # Finalize the VETS files: ############ @@ -415,6 +421,9 @@ workflow SRJointCallGVCFsWithGenomicsDB { File? annotated_joint_vcf = annotated_vcf File? annotated_joint_vcf_tbi = annotated_vcf_tbi + + String? snpEff_summary = FinalizeSnpEffSummary.gcs_dir + String? snpEff_genes = FinalizeSnpEffGenes.gcs_dir } } From 06d4b95dda0bb5d21b2c90cceceead8564941042 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 12 Sep 2023 14:16:46 -0400 Subject: [PATCH 213/297] Fixing the outputs for merge vcfs. --- wdl/tasks/HaplotypeCaller.wdl | 10 ++-------- wdl/tasks/SRUtils.wdl | 5 +++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index 14ec46fc4..599168b67 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -72,12 +72,6 @@ workflow CallVariantsWithHaplotypeCaller { prefix = prefix } - # Index the GVCF: - call SRUTIL.IndexFeatureFile as IndexGVCF { - input: - feature_file = MergeGVCFs.output_vcf - } - # Merge the output BAMs: call MergeBamouts as MergeVariantCalledBamOuts { input: @@ -109,7 +103,7 @@ workflow CallVariantsWithHaplotypeCaller { call SRJOINT.GenotypeGVCFs as CollapseGVCFtoVCF { input: input_gvcf_data = MergeGVCFs.output_vcf, - input_gvcf_index = IndexGVCF.index, + input_gvcf_index = MergeGVCFs.output_vcf_index, interval_list = SmallVariantsScatterPrep.interval_list, ref_fasta = ref_fasta, ref_fasta_fai = ref_fasta_fai, @@ -120,7 +114,7 @@ workflow CallVariantsWithHaplotypeCaller { output { File output_gvcf = MergeGVCFs.output_vcf - File output_gvcf_index = IndexGVCF.index + File output_gvcf_index = MergeGVCFs.output_vcf_index File output_vcf = CollapseGVCFtoVCF.output_vcf File output_vcf_index = CollapseGVCFtoVCF.output_vcf_index File bamout = MergeVariantCalledBamOuts.output_bam diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index e8de0c120..12f33ffb7 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -740,11 +740,12 @@ task MergeVCFs { java -Xms2000m -Xmx2500m -jar /usr/picard/picard.jar \ MergeVcfs \ INPUT=~{sep=' INPUT=' input_vcfs} \ - OUTPUT=~{prefix}.vcf + OUTPUT=~{prefix}.vcf.gz >>> output { - File output_vcf = "~{prefix}.vcf" + File output_vcf = "~{prefix}.vcf.gz" + File output_vcf_index = "~{prefix}.vcf.gz.tbi" } ######################### From 36b45cc5af6fc7acb2873ed1b229737512f2391f Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 12 Sep 2023 16:01:47 -0400 Subject: [PATCH 214/297] Fixing bug in WDL for `ExtractVariantAnnotations` and `ScoreVariantAnnotations`. --- wdl/tasks/VariantUtils.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 73b24da94..71e3d76bb 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1674,7 +1674,7 @@ task ExtractVariantAnnotations { if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_index)} ]] || \ [[ ~{length(known_reference_variants)} -ne ~{length(is_training)} ]] || \ - [[ ~{length(known_reference_variants)} -ne ~{length(is_calibration)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_calibration)} ]] \ echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 echo " known_reference_variants = ~{length(known_reference_variants)}" 1>&2 echo " known_reference_variants = ~{length(known_reference_variants_index)}" 1>&2 @@ -1892,7 +1892,7 @@ task ScoreVariantAnnotations { if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_index)} ]] || \ [[ ~{length(known_reference_variants)} -ne ~{length(is_training)} ]] || \ - [[ ~{length(known_reference_variants)} -ne ~{length(is_calibration)} ]] || \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_calibration)} ]] \ echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 echo " known_reference_variants = ~{length(known_reference_variants)}" 1>&2 echo " known_reference_variants = ~{length(known_reference_variants_index)}" 1>&2 From ce7d9212ec30f4e7b1d737cd77e02746ec6bd37b Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 13 Sep 2023 08:50:42 -0400 Subject: [PATCH 215/297] Fxiing another typo/. --- wdl/tasks/VariantUtils.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 71e3d76bb..5f9183f1e 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1674,7 +1674,7 @@ task ExtractVariantAnnotations { if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_index)} ]] || \ [[ ~{length(known_reference_variants)} -ne ~{length(is_training)} ]] || \ - [[ ~{length(known_reference_variants)} -ne ~{length(is_calibration)} ]] \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_calibration)} ]] ; then echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 echo " known_reference_variants = ~{length(known_reference_variants)}" 1>&2 echo " known_reference_variants = ~{length(known_reference_variants_index)}" 1>&2 @@ -1892,7 +1892,7 @@ task ScoreVariantAnnotations { if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_index)} ]] || \ [[ ~{length(known_reference_variants)} -ne ~{length(is_training)} ]] || \ - [[ ~{length(known_reference_variants)} -ne ~{length(is_calibration)} ]] \ + [[ ~{length(known_reference_variants)} -ne ~{length(is_calibration)} ]] ; then echo "ERROR: Not all input arrays for known variants contain the same number of elements: " 1>&2 echo " known_reference_variants = ~{length(known_reference_variants)}" 1>&2 echo " known_reference_variants = ~{length(known_reference_variants_index)}" 1>&2 From acd1bb0e2eee5fa120414b34a9539800263b1960 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 13 Sep 2023 14:35:22 -0400 Subject: [PATCH 216/297] Fixed a copy/paste error for memory configuration in a few tasks. --- wdl/tasks/VariantUtils.wdl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 5f9183f1e..6b1c20527 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1694,6 +1694,7 @@ task ExtractVariantAnnotations { # Get amount of memory to use: mem_available=$(free -g | grep '^Mem' | awk '{print $2}') let mem_start=${mem_available}-2 + let mem_max=${mem_available}-2 gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ ExtractVariantAnnotations \ @@ -1780,6 +1781,7 @@ task TrainVariantAnnotationsModel { # Get amount of memory to use: mem_available=$(free -g | grep '^Mem' | awk '{print $2}') let mem_start=${mem_available}-2 + let mem_max=${mem_available}-2 gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ TrainVariantAnnotationsModel \ @@ -1912,6 +1914,7 @@ task ScoreVariantAnnotations { # Get amount of memory to use: mem_available=$(free -g | grep '^Mem' | awk '{print $2}') let mem_start=${mem_available}-2 + let mem_max=${mem_available}-2 mode_lower=$(echo ~{mode} | tr 'A-Z' 'a-z') From 198478f8c8561059e142c294bd3809b1ae8b7301 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 13 Sep 2023 15:41:53 -0400 Subject: [PATCH 217/297] Fixing typo in argument for `ScoreVariantAnnotations` --- wdl/tasks/VariantUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 6b1c20527..d495bed30 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1701,7 +1701,7 @@ task ExtractVariantAnnotations { -V ~{vcf} \ -A ~{sep=' -A ' recalibration_annotation_values} \ --mode ~{mode} \ - --maximum-number-of-unlableled-variants ~{max_unlabeled_variants} \ + --maximum-number-of-unlabeled-variants ~{max_unlabeled_variants} \ ${resource_flags} \ -O ~{prefix}_extracted_annotations_~{mode} \ &> ~{prefix}_ExtractVariantAnnotations_~{mode}.log From d921c3716d6d3bd6cfccdae9f4d96daf3ec882f7 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 13 Sep 2023 19:06:40 -0400 Subject: [PATCH 218/297] Fixed typo in ScoreVariantAnnotations and ExtractVariantAnnotations. --- wdl/tasks/VariantUtils.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index d495bed30..d6081c35f 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1712,7 +1712,7 @@ task ExtractVariantAnnotations { output { File annotation_hdf5 = "~{prefix}_extracted_annotations_~{mode}.annot.hdf5" File sites_only_vcf = "~{prefix}_extracted_annotations_~{mode}.vcf.gz" - File sites_only_vcf_index = "~{prefix}_extracted_annotations_~{mode}.vcf.tbi" + File sites_only_vcf_index = "~{prefix}_extracted_annotations_~{mode}.vcf.gz.tbi" File? unlabeled_annotation_hdf5 = "~{prefix}_extracted_annotations_~{mode}.unlabeled.annot.hdf5" @@ -1944,7 +1944,7 @@ task ScoreVariantAnnotations { File? annotations_hdf5 = "~{prefix}_scored.annot.hdf5" File? scores_hdf5 = "~{prefix}_scored.scores.hdf5" - File log = "~{prefix}_ExtractVariantAnnotations_~{mode}.log" + File log = "~{prefix}_ScoreVariantAnnotations_~{mode}.log" File monitoring_log = "resources.log" } From 0d837ef2fd635c8053b1897a5e55e3d89b156d84 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 13 Sep 2023 20:43:00 -0400 Subject: [PATCH 219/297] Adding some debugging for outputs. --- wdl/tasks/VariantUtils.wdl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index d6081c35f..cbab4a9ad 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1769,6 +1769,9 @@ task TrainVariantAnnotationsModel { String cal_sense_arg = if defined(unlabeled_annotation_hdf5) then " --calibration-sensitivity-threshold ~{calibration_sensitivity_threshold}" else "" + # Needed for output. I think there's a bug in the output naming for this tool. + String mode_lower = if mode == "SNP" then "snp" else "INDEL" + command <<< set -euxo pipefail @@ -1792,12 +1795,15 @@ task TrainVariantAnnotationsModel { -O ~{prefix}_train_~{mode} \ &> ~{prefix}_TrainVariantAnnotationsModel_~{mode}.log + # Debugging: + find . + kill $monitoring_pid >>> output { File training_scores = "~{prefix}_train_~{mode}.trainingScores.hdf5" - File positive_model_scorer_pickle = "~{prefix}_train_~{mode}.scorer.pkl" + File positive_model_scorer_pickle = "~{prefix}_train_~{mode}.~{mode_lower}.scorer.pkl" File? unlabeled_positive_model_scores = "~{prefix}_train_~{mode}.unlabeledScores.hdf5" File? calibration_set_scores = "~{prefix}_train_~{mode}.calibrationScores.hdf5" From 84b904ecc2b04a7bf090544d8b36dabc7257d519 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 13 Sep 2023 23:44:19 -0400 Subject: [PATCH 220/297] Fixing outputs for `TrainVariantAnnotationsModel` --- wdl/tasks/VariantUtils.wdl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index cbab4a9ad..709eb271d 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1802,12 +1802,12 @@ task TrainVariantAnnotationsModel { >>> output { - File training_scores = "~{prefix}_train_~{mode}.trainingScores.hdf5" + File training_scores = "~{prefix}_train_~{mode}.~{mode_lower}.trainingScores.hdf5" File positive_model_scorer_pickle = "~{prefix}_train_~{mode}.~{mode_lower}.scorer.pkl" - File? unlabeled_positive_model_scores = "~{prefix}_train_~{mode}.unlabeledScores.hdf5" - File? calibration_set_scores = "~{prefix}_train_~{mode}.calibrationScores.hdf5" - File? negative_model_scorer_pickle = "~{prefix}_train_~{mode}.negative.scorer.pkl" + File? unlabeled_positive_model_scores = "~{prefix}_train_~{mode}.~{mode_lower}.unlabeledScores.hdf5" + File? calibration_set_scores = "~{prefix}_train_~{mode}.~{mode_lower}.calibrationScores.hdf5" + File? negative_model_scorer_pickle = "~{prefix}_train_~{mode}.~{mode_lower}.negative.scorer.pkl" File log = "~{prefix}_TrainVariantAnnotationsModel_~{mode}.log" From 81d0f2e45015d2bd3bc79e65954c127a8f711925 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 14 Sep 2023 00:22:46 -0400 Subject: [PATCH 221/297] Fixing the same output file issue. --- wdl/tasks/VariantUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 709eb271d..2a7026fd4 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1770,7 +1770,7 @@ task TrainVariantAnnotationsModel { String cal_sense_arg = if defined(unlabeled_annotation_hdf5) then " --calibration-sensitivity-threshold ~{calibration_sensitivity_threshold}" else "" # Needed for output. I think there's a bug in the output naming for this tool. - String mode_lower = if mode == "SNP" then "snp" else "INDEL" + String mode_lower = if mode == "SNP" then "snp" else "indel" command <<< set -euxo pipefail From 34e74d86f18ed9c76d009ebec65e57c6e6a6cffd Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 14 Sep 2023 09:37:31 -0400 Subject: [PATCH 222/297] Updated `ScoreVariantAnnotations` with some debugging. --- wdl/tasks/VariantUtils.wdl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 2a7026fd4..1b1092ede 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1795,9 +1795,6 @@ task TrainVariantAnnotationsModel { -O ~{prefix}_train_~{mode} \ &> ~{prefix}_TrainVariantAnnotationsModel_~{mode}.log - # Debugging: - find . - kill $monitoring_pid >>> @@ -1930,6 +1927,7 @@ task ScoreVariantAnnotations { gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ ScoreVariantAnnotations \ + --verbosity DEBUG \ -V ~{vcf} \ -A ~{sep=' -A ' recalibration_annotation_values} \ --mode ~{mode} \ @@ -1940,6 +1938,9 @@ task ScoreVariantAnnotations { -O ~{prefix}_scored \ &> ~{prefix}_ScoreVariantAnnotations_~{mode}.log + # Debugging: + find . + kill $monitoring_pid >>> From 3483e10929043e3141cef518f59c871372647db9 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 14 Sep 2023 11:55:40 -0400 Subject: [PATCH 223/297] Additional debugging. --- wdl/tasks/VariantUtils.wdl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 1b1092ede..4fde00492 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1706,6 +1706,9 @@ task ExtractVariantAnnotations { -O ~{prefix}_extracted_annotations_~{mode} \ &> ~{prefix}_ExtractVariantAnnotations_~{mode}.log + # Print log to stdout: + cat ~{prefix}_ExtractVariantAnnotations_~{mode}.log + kill $monitoring_pid >>> @@ -1795,6 +1798,9 @@ task TrainVariantAnnotationsModel { -O ~{prefix}_train_~{mode} \ &> ~{prefix}_TrainVariantAnnotationsModel_~{mode}.log + # Print log to stdout: + cat ~{prefix}_TrainVariantAnnotationsModel_~{mode}.log + kill $monitoring_pid >>> @@ -1925,6 +1931,9 @@ task ScoreVariantAnnotations { mkdir model_files ln -s ~{sep=" model_files && ln -s " model_files} model_files + # Debugging: + find . + gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ ScoreVariantAnnotations \ --verbosity DEBUG \ @@ -1938,8 +1947,8 @@ task ScoreVariantAnnotations { -O ~{prefix}_scored \ &> ~{prefix}_ScoreVariantAnnotations_~{mode}.log - # Debugging: - find . + # Print log to stdout: + cat ~{prefix}_ScoreVariantAnnotations_~{mode}.log kill $monitoring_pid >>> From 09594a1a3a794411b056443d0f9f69e935c147c1 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 14 Sep 2023 12:51:53 -0400 Subject: [PATCH 224/297] Fixing typo in `SRWholeGenome` for VETS. --- wdl/SRWholeGenome.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index f23b051da..753731472 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -258,7 +258,7 @@ workflow SRWholeGenome { call VARUTIL.TrainVariantAnnotationsModel as TrainSnpVariantAnnotationsModel { input: annotation_hdf5 = ExtractIndelVariantAnnotations.annotation_hdf5, - mode = "INDEL", + mode = "SNP", prefix = participant_name, } From 0ad718bd72c62b34facf899c094b2feb3333ba7f Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 14 Sep 2023 13:11:44 -0400 Subject: [PATCH 225/297] Fixing yet another typo in `SRWholeGenome` --- wdl/SRWholeGenome.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 753731472..8feb6b954 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -257,7 +257,7 @@ workflow SRWholeGenome { call VARUTIL.TrainVariantAnnotationsModel as TrainSnpVariantAnnotationsModel { input: - annotation_hdf5 = ExtractIndelVariantAnnotations.annotation_hdf5, + annotation_hdf5 = ExtractSnpVariantAnnotations.annotation_hdf5, mode = "SNP", prefix = participant_name, } From 3b897d6950741e983b0495443d536259d7f95ce3 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 14 Sep 2023 14:10:20 -0400 Subject: [PATCH 226/297] Fixed another issue. --- wdl/tasks/VariantUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 4fde00492..b6b84004e 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1940,7 +1940,7 @@ task ScoreVariantAnnotations { -V ~{vcf} \ -A ~{sep=' -A ' recalibration_annotation_values} \ --mode ~{mode} \ - --model-prefix model_files/~{model_prefix}.train \ + --model-prefix model_files/~{model_prefix} \ ${resource_flags} \ --resource:extracted,extracted=true ~{sites_only_extracted_vcf} \ --${mode_lower}-calibration-sensitivity-threshold ~{calibration_sensitivity_threshold} \ From ef1b22538ee6e0205d29b2704c937cac516c43ab Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 15 Sep 2023 09:38:23 -0400 Subject: [PATCH 227/297] Fixing barcode extraction wdl. --- wdl/tasks/VariantUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index b6b84004e..662329632 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1581,7 +1581,7 @@ def extract_barcode(vcf_file, haplotype_database_file, ref_seq_dict, vcf_out_pat ref = read_reference("~{ref_fasta}") # Calculate the barcode info: -barcode = extract_barcode(vcf_file, "~{haplotype_database_file}", ref, "~{prefix}.fingerprint.vcf") +barcode = extract_barcode("~{vcf}", "~{haplotype_database_file}", ref, "~{prefix}.fingerprint.vcf") print(f"Extracted barcode: {barcode}") From 5d350ea23ed0ef8814ca9303b23ff14831bca1fe Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 15 Sep 2023 10:20:06 -0400 Subject: [PATCH 228/297] Updated output type of barcode to be `String` instead of `File`. --- wdl/tasks/VariantUtils.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 662329632..ae230c946 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1594,7 +1594,7 @@ CODE output { File output_vcf = "~{prefix}.fingerprint.vcf" - File barcode = read_string("~{prefix}.barcode.txt") + String barcode = read_string("~{prefix}.barcode.txt") } ######################### From c17f87db7567cf6cc9b59eddc068ffe860ec23bc Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 15 Sep 2023 11:16:56 -0400 Subject: [PATCH 229/297] Removing separate log file for gatk in VETS tasks. --- wdl/tasks/VariantUtils.wdl | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index ae230c946..adacb8d75 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1703,12 +1703,8 @@ task ExtractVariantAnnotations { --mode ~{mode} \ --maximum-number-of-unlabeled-variants ~{max_unlabeled_variants} \ ${resource_flags} \ - -O ~{prefix}_extracted_annotations_~{mode} \ - &> ~{prefix}_ExtractVariantAnnotations_~{mode}.log - - # Print log to stdout: - cat ~{prefix}_ExtractVariantAnnotations_~{mode}.log - + -O ~{prefix}_extracted_annotations_~{mode} + kill $monitoring_pid >>> @@ -1795,11 +1791,7 @@ task TrainVariantAnnotationsModel { --mode ~{mode} \ ~{"--unlabeled-annotations-hdf5 " + unlabeled_annotation_hdf5} \ ~{cal_sense_arg} \ - -O ~{prefix}_train_~{mode} \ - &> ~{prefix}_TrainVariantAnnotationsModel_~{mode}.log - - # Print log to stdout: - cat ~{prefix}_TrainVariantAnnotationsModel_~{mode}.log + -O ~{prefix}_train_~{mode} kill $monitoring_pid >>> @@ -1944,11 +1936,7 @@ task ScoreVariantAnnotations { ${resource_flags} \ --resource:extracted,extracted=true ~{sites_only_extracted_vcf} \ --${mode_lower}-calibration-sensitivity-threshold ~{calibration_sensitivity_threshold} \ - -O ~{prefix}_scored \ - &> ~{prefix}_ScoreVariantAnnotations_~{mode}.log - - # Print log to stdout: - cat ~{prefix}_ScoreVariantAnnotations_~{mode}.log + -O ~{prefix}_scored kill $monitoring_pid >>> @@ -1960,8 +1948,6 @@ task ScoreVariantAnnotations { File? annotations_hdf5 = "~{prefix}_scored.annot.hdf5" File? scores_hdf5 = "~{prefix}_scored.scores.hdf5" - File log = "~{prefix}_ScoreVariantAnnotations_~{mode}.log" - File monitoring_log = "resources.log" } From 9b7b27918b166cadbccbfbf2e36546d752917951 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 18 Sep 2023 09:15:27 -0400 Subject: [PATCH 230/297] Fixed output file issue. --- wdl/tasks/VariantUtils.wdl | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index adacb8d75..9200296dd 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1698,13 +1698,14 @@ task ExtractVariantAnnotations { gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ ExtractVariantAnnotations \ - -V ~{vcf} \ - -A ~{sep=' -A ' recalibration_annotation_values} \ - --mode ~{mode} \ - --maximum-number-of-unlabeled-variants ~{max_unlabeled_variants} \ - ${resource_flags} \ - -O ~{prefix}_extracted_annotations_~{mode} - + --verbosity DEBUG \ + -V ~{vcf} \ + -A ~{sep=' -A ' recalibration_annotation_values} \ + --mode ~{mode} \ + --maximum-number-of-unlabeled-variants ~{max_unlabeled_variants} \ + ${resource_flags} \ + -O ~{prefix}_extracted_annotations_~{mode} + kill $monitoring_pid >>> @@ -1715,8 +1716,6 @@ task ExtractVariantAnnotations { File? unlabeled_annotation_hdf5 = "~{prefix}_extracted_annotations_~{mode}.unlabeled.annot.hdf5" - File log = "~{prefix}_ExtractVariantAnnotations_~{mode}.log" - File monitoring_log = "resources.log" } @@ -1787,6 +1786,7 @@ task TrainVariantAnnotationsModel { gatk --java-options "-Xms${mem_start}g -Xmx${mem_max}g" \ TrainVariantAnnotationsModel \ + --verbosity DEBUG \ --annotations-hdf5 ~{annotation_hdf5} \ --mode ~{mode} \ ~{"--unlabeled-annotations-hdf5 " + unlabeled_annotation_hdf5} \ @@ -1804,8 +1804,6 @@ task TrainVariantAnnotationsModel { File? calibration_set_scores = "~{prefix}_train_~{mode}.~{mode_lower}.calibrationScores.hdf5" File? negative_model_scorer_pickle = "~{prefix}_train_~{mode}.~{mode_lower}.negative.scorer.pkl" - File log = "~{prefix}_TrainVariantAnnotationsModel_~{mode}.log" - File monitoring_log = "resources.log" } From eab15e351735e2d44b158d06637f0ea1fbf7116f Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 18 Sep 2023 16:26:38 -0400 Subject: [PATCH 231/297] Fixing issue in joint calling workflow. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 4 ++-- wdl/SRWholeGenome.wdl | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 6d2d5d857..e2859ecf5 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -177,8 +177,8 @@ workflow SRJointCallGVCFsWithGenomicsDB { call VARUTIL.TrainVariantAnnotationsModel as TrainSnpVariantAnnotationsModel { input: - annotation_hdf5 = ExtractIndelVariantAnnotations.annotation_hdf5, - mode = "INDEL", + annotation_hdf5 = ExtractSnpVariantAnnotations.annotation_hdf5, + mode = "SNP", prefix = prefix, } diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 8feb6b954..6af821ef1 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -267,7 +267,6 @@ workflow SRWholeGenome { vcf = RenameRawHcVcf.new_sample_name_vcf, vcf_index = RenameRawHcVcf.new_sample_name_vcf_index, - sites_only_extracted_vcf = ExtractSnpVariantAnnotations.sites_only_vcf, sites_only_extracted_vcf_index = ExtractSnpVariantAnnotations.sites_only_vcf_index, From bdaa117261ae81c27e01ad2ff8ad5950f72d73de Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 19 Sep 2023 11:22:12 -0400 Subject: [PATCH 232/297] Added flag to `SRWholeGenome` to indicate data has been processed. --- wdl/SRWholeGenome.wdl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 6af821ef1..518e7a3e4 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -443,6 +443,8 @@ workflow SRWholeGenome { File fastqc_report = FinalizeFastQCReport.gcs_path + Boolean successfully_processed = true + File? bed_cov_summary = FinalizeRegionalCoverage.gcs_path File? fingerprint_vcf = FinalizeFingerprintVcf.gcs_path From fe50a5fe810fdda716c11519534d59729a769787 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 19 Sep 2023 11:23:34 -0400 Subject: [PATCH 233/297] Changed snpEff results folder name --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index e2859ecf5..81d069681 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -318,7 +318,7 @@ workflow SRJointCallGVCFsWithGenomicsDB { String recalibration_dir = outdir + "/recalibration_files" String recalibration_model_dir = outdir + "/recalibration_files/model" String recalibration_results_dir = outdir + "/recalibration_files/results" - String snpeff_results_dir = outdir + "/snpEff" + String snpeff_results_dir = outdir + "/snpEff_results" call FF.FinalizeToDir as FinalizeGenomicsDB { input: outdir = outdir + "/GenomicsDB", keyfile = keyfile, files = ImportGVCFsIntoGenomicsDB.output_genomicsdb } From 41e53896202811f5c2f522c5e06369aaa54b33bd Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 19 Sep 2023 13:34:43 -0400 Subject: [PATCH 234/297] Added Workflow to extract single-organism contaminants. --- .../RemoveSingleOrganismContamination.wdl | 160 ++++++++++++++++++ wdl/tasks/Utils.wdl | 4 +- 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 wdl/tasks/RemoveSingleOrganismContamination.wdl diff --git a/wdl/tasks/RemoveSingleOrganismContamination.wdl b/wdl/tasks/RemoveSingleOrganismContamination.wdl new file mode 100644 index 000000000..25e607898 --- /dev/null +++ b/wdl/tasks/RemoveSingleOrganismContamination.wdl @@ -0,0 +1,160 @@ +version 1.0 + +import "Structs.wdl" as Structs +import "SRUtils.wdl" as SRUTIL +import "Utils.wdl" as Utils + +workflow RemoveSingleOrganismContamination { + meta { + author: "Jonn Smith" + description: "A workflow to remove contamination originating from a single organism from a dataset." + } + + input { + File? input_bam + File? input_bai + + File? fq_end1 + File? fq_end2 + + String SM + String LB + String platform = "illumina" + + File contaminant_ref_name + File contaminant_ref_map_file + + String dir_prefix + String gcs_out_root_dir + + Boolean DEBUG_MODE = false + } + + parameter_meta { + input_bam: "GCS path to unmapped bam" + input_bai: "GCS path to bai index for unmapped bam" + + fq_end1: "GCS path to end1 of paired-end fastq" + fq_end2: "GCS path to end2 of paired-end fastq" + + SM: "the value to place in the BAM read group's SM field" + LB: "the value to place in the BAM read group's LB (library) field" + platform: "[default valued] the value to place in the BAM read group's PL (platform) field (default: illumina)" + + contaminant_ref_name: "Name of the contaminant genome to be used in output files." + contaminant_ref_map_file: "Table indicating reference sequence and auxillary file locations." + + dir_prefix: "directory prefix for output files" + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" + + DEBUG_MODE: "[default valued] enables debugging tasks / subworkflows (default: false)" + } + + # Call our timestamp so we can store outputs without clobbering previous runs: + call Utils.GetCurrentTimestampString as t_001_WdlExecutionStartTimestamp { input: } + + # Some basic error handling: + if (!defined(input_bam) && (!defined(fq_end1) || !defined(fq_end2))) { + call Utils.FailWithWarning as t_002_NoInputFileProvidedFailure { + input: warning = "No input file has been provided! You must provide either an input bam or input fastq1/fastq2 files." + } + } + if (defined(input_bam) && (defined(fq_end1) || defined(fq_end2))) { + call Utils.FailWithWarning as t_002_TooManyInputsProvidedFailure { + input: warning = "Too many inputs provided! You must provide EITHER an input bam OR input fastq1/fastq2 files." + } + } + + # Create an outdir: + String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}/" + t_001_WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}" + + # Get ref info: + Map[String, String] ref_map = read_map(contaminant_ref_map_file) + + if (defined(input_bam)) { + # Convert the given bam to a uBAM (needed for previous aligned data): + call SRUTIL.RevertSam as t_003_RevertSam { + input: + input_bam = select_first([input_bam]), + prefix = SM + ".revertSam" + } + + # Convert input SAM/BAM to FASTQ: + call SRUTIL.BamToFq as t_004_Bam2Fastq { + input: + bam = t_003_RevertSam.bam, + prefix = SM + } + call Utils.GetRawReadGroup as t_005_GetRawReadGroup { input: gcs_bam_path = select_first([input_bam]) } + } + + File fq_e1 = select_first([fq_end1, t_004_Bam2Fastq.fq_end1]) + File fq_e2 = select_first([fq_end2, t_004_Bam2Fastq.fq_end2]) + + String RG = select_first([t_005_GetRawReadGroup.rg, "@RG\tID:" + SM + "_" + LB + "\tPL:" + platform + "\tLB:" + LB + "\tSM:" + SM]) + + # Align data to contaminant reference: + call SRUTIL.BwaMem2 as t_006_AlignReads { + input: + fq_end1 = fq_e1, + fq_end2 = fq_e2, + + ref_fasta = ref_map["fasta"], + ref_fasta_index = ref_map["fai"], + ref_dict = ref_map["dict"], + ref_0123 = ref_map["0123"], + ref_amb = ref_map["amb"], + ref_ann = ref_map["ann"], + ref_bwt = ref_map["bwt"], + ref_pac = ref_map["pac"], + + mark_short_splits_as_secondary = true, + + read_group = RG, + prefix = SM + ".contaminant_aligned." + contaminant_ref_name + } + + call Utils.FilterReadsBySamFlags as t_007_ExtractDecontaminatedReads { + input: + bam = t_006_AlignReads.bam, + sam_flags = "256", + extra_args = " -f 12 ", + prefix = SM + ".decontaminated" + } + + call Utils.FilterReadsBySamFlags as t_008_ExtractContaminatedReads { + input: + bam = t_006_AlignReads.bam, + sam_flags = "12", + prefix = SM + ".contaminated_" + contaminant_ref_name + "_reads" + } + + call Utils.SortBam as t_009_SortDecontaminatedReads { + input: + input_bam = t_007_ExtractDecontaminatedReads.output_bam, + extra_args = " -n ", + prefix = SM + ".decontaminated.sorted" + } + + call Utils.SortBam as t_010_SortContaminatedReads { + input: + input_bam = t_008_ExtractContaminatedReads.output_bam, + extra_args = " -n ", + prefix = SM + ".contaminated_" + contaminant_ref_name + "_reads.sorted" + } + + # Convert input SAM/BAM to FASTQ: + call SRUTIL.BamToFq as t_011_CreateFastqFromDecontaminatedReads { + input: + bam = t_009_SortDecontaminatedReads.sorted_bam, + prefix = SM + ".decontaminated" + } + + output { + File contaminated_bam = t_010_SortContaminatedReads.sorted_bam + File contaminated_bam_index = t_010_SortContaminatedReads.sorted_bai + + File decontaminated_fq1 = t_011_CreateFastqFromDecontaminatedReads.fq_end1 + File decontaminated_fq2 = t_011_CreateFastqFromDecontaminatedReads.fq_end2 + } +} \ No newline at end of file diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 4df12e149..8db862a4b 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -162,6 +162,8 @@ task SortBam { File input_bam String prefix = "sorted" + String? extra_args = "" + RuntimeAttr? runtime_attr_override } @@ -183,7 +185,7 @@ task SortBam { num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) - samtools sort -@$num_core -o ~{prefix}.bam ~{input_bam} + samtools sort ~{extra_args} -@$num_core -o ~{prefix}.bam ~{input_bam} samtools index ~{prefix}.bam kill $monitoring_pid From 2c8a8fd377c8dda82e3cdf4273b10d07d463d805 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 19 Sep 2023 13:46:04 -0400 Subject: [PATCH 235/297] Fixed outputs for `RemoveSingleOrganismContamination`to be finalized. --- wdl/SRFlowcell.wdl | 2 + .../RemoveSingleOrganismContamination.wdl | 37 ++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 0072877de..6b03e8dbc 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -27,6 +27,7 @@ workflow SRFlowcell { String LB File ref_map_file + File? contaminant_ref_map_file String dir_prefix @@ -45,6 +46,7 @@ workflow SRFlowcell { fq_end2: "GCS path to end2 of paired-end fastq" ref_map_file: "table indicating reference sequence and auxillary file locations" + ref_map_file: "table indicating reference sequence and auxillary file locations for a single-organism contaminant" SM: "the value to place in the BAM read group's SM field" LB: "the value to place in the BAM read group's LB (library) field" diff --git a/wdl/tasks/RemoveSingleOrganismContamination.wdl b/wdl/tasks/RemoveSingleOrganismContamination.wdl index 25e607898..1c0eb294b 100644 --- a/wdl/tasks/RemoveSingleOrganismContamination.wdl +++ b/wdl/tasks/RemoveSingleOrganismContamination.wdl @@ -3,6 +3,7 @@ version 1.0 import "Structs.wdl" as Structs import "SRUtils.wdl" as SRUTIL import "Utils.wdl" as Utils +import "Finalize.wdl" as FF workflow RemoveSingleOrganismContamination { meta { @@ -66,7 +67,7 @@ workflow RemoveSingleOrganismContamination { } # Create an outdir: - String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}/" + t_001_WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}" + String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/RemoveSingleOrganismContamination/~{dir_prefix}/" + t_001_WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/RemoveSingleOrganismContamination/~{dir_prefix}" # Get ref info: Map[String, String] ref_map = read_map(contaminant_ref_map_file) @@ -150,11 +151,37 @@ workflow RemoveSingleOrganismContamination { prefix = SM + ".decontaminated" } + ############################################ + # _____ _ _ _ + # | ___(_)_ __ __ _| (_)_______ + # | |_ | | '_ \ / _` | | |_ / _ \ + # | _| | | | | | (_| | | |/ / __/ + # |_| |_|_| |_|\__,_|_|_/___\___| + # + ############################################ + + # Chosen because it's a relatively small file. + File keyfile = t_011_CreateFastqFromDecontaminatedReads.monitoring_log + + call FF.FinalizeToFile as t_012_FinalizeContaminatedBam { input: outdir = outdir, file = t_010_SortContaminatedReads.sorted_bam, keyfile = keyfile } + call FF.FinalizeToFile as t_013_FinalizeContaminatedBai { input: outdir = outdir, file = t_010_SortContaminatedReads.sorted_bai, keyfile = keyfile } + call FF.FinalizeToFile as t_014_FinalizeDecontaminatedFq1 { input: outdir = outdir, file = t_011_CreateFastqFromDecontaminatedReads.fq_end1, keyfile = keyfile } + call FF.FinalizeToFile as t_015_FinalizeDecontaminatedFq2 { input: outdir = outdir, file = t_011_CreateFastqFromDecontaminatedReads.fq_end2, keyfile = keyfile } + + ############################################ + # ___ _ _ + # / _ \ _ _| |_ _ __ _ _| |_ + # | | | | | | | __| '_ \| | | | __| + # | |_| | |_| | |_| |_) | |_| | |_ + # \___/ \__,_|\__| .__/ \__,_|\__| + # |_| + ############################################ + output { - File contaminated_bam = t_010_SortContaminatedReads.sorted_bam - File contaminated_bam_index = t_010_SortContaminatedReads.sorted_bai + File contaminated_bam = t_012_FinalizeContaminatedBam.gcs_path + File contaminated_bam_index = t_013_FinalizeContaminatedBai.gcs_path - File decontaminated_fq1 = t_011_CreateFastqFromDecontaminatedReads.fq_end1 - File decontaminated_fq2 = t_011_CreateFastqFromDecontaminatedReads.fq_end2 + File decontaminated_fq1 = t_014_FinalizeDecontaminatedFq1.gcs_path + File decontaminated_fq2 = t_015_FinalizeDecontaminatedFq2.gcs_path } } \ No newline at end of file From b103772d8652572bd03e4383032a0cc8f05ed264 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 19 Sep 2023 14:10:07 -0400 Subject: [PATCH 236/297] Connected `RemoveSingleOrganismContamination` in `SRFlowcell` --- wdl/SRFlowcell.wdl | 41 ++++++++++++++++--- .../RemoveSingleOrganismContamination.wdl | 4 +- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 6b03e8dbc..fbf209ca5 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -13,6 +13,7 @@ import "tasks/SRUtils.wdl" as SRUTIL import "tasks/Utils.wdl" as Utils import "tasks/AlignedMetrics.wdl" as AM import "tasks/FastQC.wdl" as FastQC +import "tasks/RemoveSingleOrganismContamination.wdl" as DECONTAMINATE import "tasks/Finalize.wdl" as FF workflow SRFlowcell { @@ -27,6 +28,7 @@ workflow SRFlowcell { String LB File ref_map_file + String? contaminant_ref_name File? contaminant_ref_map_file String dir_prefix @@ -46,7 +48,8 @@ workflow SRFlowcell { fq_end2: "GCS path to end2 of paired-end fastq" ref_map_file: "table indicating reference sequence and auxillary file locations" - ref_map_file: "table indicating reference sequence and auxillary file locations for a single-organism contaminant" + contaminant_ref_name: "Name of the contaminant genome to be used in output files." + contaminant_ref_map_file: "table indicating reference sequence and auxillary file locations for a single-organism contaminant" SM: "the value to place in the BAM read group's SM field" LB: "the value to place in the BAM read group's LB (library) field" @@ -95,8 +98,33 @@ workflow SRFlowcell { call Utils.GetRawReadGroup as t_004_GetRawReadGroup { input: gcs_bam_path = select_first([bam]) } } - File fq_e1 = select_first([fq_end1, t_003_Bam2Fastq.fq_end1]) - File fq_e2 = select_first([fq_end2, t_003_Bam2Fastq.fq_end2]) + # OK, this is inefficient, but let's NOW extract our contaminated reads if we have the info. + # TODO: Move this into the sections above to make it more efficient. Specifically where we convert bam -> fastq. + if (defined(contaminant_ref_map_file)) { + + # Call our sub-workflow for decontamination: + # NOTE: We don't need to be too concerned with the finalization info. + # This will be partially filled in by the WDL itself, so we can pass the same inputs for + # these things here (e.g. `dir_prefix`): + call DECONTAMINATE.RemoveSingleOrganismContamination as DecontaminateSample { + input: + fq_end1 = select_first([fq_end1, t_003_Bam2Fastq.fq_end1]), + fq_end2 = select_first([fq_end2, t_003_Bam2Fastq.fq_end2]), + + SM = SM, + LB = LB, + platform = platform, + + contaminant_ref_name = select_first([contaminant_ref_name]), + contaminant_ref_map_file = select_first([contaminant_ref_map_file]), + + dir_prefix = dir_prefix, + gcs_out_root_dir = gcs_out_root_dir + } + } + + File fq_e1 = select_first([DecontaminateSample.decontaminated_fq1, fq_end1, t_003_Bam2Fastq.fq_end1]) + File fq_e2 = select_first([DecontaminateSample.decontaminated_fq2, fq_end2, t_003_Bam2Fastq.fq_end2]) String RG = select_first([t_004_GetRawReadGroup.rg, "@RG\tID:" + SM + "_" + LB + "\tPL:" + platform + "\tLB:" + LB + "\tSM:" + SM]) @@ -297,14 +325,13 @@ workflow SRFlowcell { file = t_012_FastQC.report } - # Prep a few files for output: File fq1_o = unaligned_reads_dir + "/" + basename(fq_e1) File fq2_o = unaligned_reads_dir + "/" + basename(fq_e2) if (defined(bam)) { File unaligned_bam_o = unaligned_reads_dir + "/" + basename(select_first([bam])) File unaligned_bai_o = unaligned_reads_dir + "/" + basename(select_first([bai])) - File fqboup = unaligned_reads_dir + "/" + basename(select_first([t_003_Bam2Fastq.fq_unpaired])) + File fqboup = unaligned_reads_dir + "/" + basename(select_first([DecontaminateSample.decontaminated_unpaired, t_003_Bam2Fastq.fq_unpaired])) } ############################################ @@ -325,6 +352,10 @@ workflow SRFlowcell { File? unaligned_bam = unaligned_bam_o File? unaligned_bai = unaligned_bai_o + # Contaminated BAM file: + File? contaminated_bam = DecontaminateSample.contaminated_bam + File? contaminated_bai = DecontaminateSample.contaminated_bam_index + # Aligned BAM file File aligned_bam = t_023_FinalizeAlignedBam.gcs_path File aligned_bai = t_024_FinalizeAlignedBai.gcs_path diff --git a/wdl/tasks/RemoveSingleOrganismContamination.wdl b/wdl/tasks/RemoveSingleOrganismContamination.wdl index 1c0eb294b..4bada316b 100644 --- a/wdl/tasks/RemoveSingleOrganismContamination.wdl +++ b/wdl/tasks/RemoveSingleOrganismContamination.wdl @@ -22,7 +22,7 @@ workflow RemoveSingleOrganismContamination { String LB String platform = "illumina" - File contaminant_ref_name + String contaminant_ref_name File contaminant_ref_map_file String dir_prefix @@ -167,6 +167,7 @@ workflow RemoveSingleOrganismContamination { call FF.FinalizeToFile as t_013_FinalizeContaminatedBai { input: outdir = outdir, file = t_010_SortContaminatedReads.sorted_bai, keyfile = keyfile } call FF.FinalizeToFile as t_014_FinalizeDecontaminatedFq1 { input: outdir = outdir, file = t_011_CreateFastqFromDecontaminatedReads.fq_end1, keyfile = keyfile } call FF.FinalizeToFile as t_015_FinalizeDecontaminatedFq2 { input: outdir = outdir, file = t_011_CreateFastqFromDecontaminatedReads.fq_end2, keyfile = keyfile } + call FF.FinalizeToFile as t_015_FinalizeDecontaminatedUnpaired { input: outdir = outdir, file = t_011_CreateFastqFromDecontaminatedReads.fq_unpaired, keyfile = keyfile } ############################################ # ___ _ _ @@ -183,5 +184,6 @@ workflow RemoveSingleOrganismContamination { File decontaminated_fq1 = t_014_FinalizeDecontaminatedFq1.gcs_path File decontaminated_fq2 = t_015_FinalizeDecontaminatedFq2.gcs_path + File decontaminated_unpaired = t_015_FinalizeDecontaminatedUnpaired.gcs_path } } \ No newline at end of file From fc8697f431e2b165bea8a374587cd78e130f0a16 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 20 Sep 2023 23:57:44 -0400 Subject: [PATCH 237/297] Increasing the memory for contamination alignment --- wdl/tasks/RemoveSingleOrganismContamination.wdl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wdl/tasks/RemoveSingleOrganismContamination.wdl b/wdl/tasks/RemoveSingleOrganismContamination.wdl index 4bada316b..668b81f5a 100644 --- a/wdl/tasks/RemoveSingleOrganismContamination.wdl +++ b/wdl/tasks/RemoveSingleOrganismContamination.wdl @@ -112,7 +112,9 @@ workflow RemoveSingleOrganismContamination { mark_short_splits_as_secondary = true, read_group = RG, - prefix = SM + ".contaminant_aligned." + contaminant_ref_name + prefix = SM + ".contaminant_aligned." + contaminant_ref_name, + + runtime_attr_override = object {mem_gb: 64} # Need a lot of ram to use BWA-MEM2 } call Utils.FilterReadsBySamFlags as t_007_ExtractDecontaminatedReads { From e25e272733b8506cfbe9a9a0fabc9550b867e567 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 21 Sep 2023 10:24:02 -0400 Subject: [PATCH 238/297] Adding missing sort step to read decontamination workflow. --- .../RemoveSingleOrganismContamination.wdl | 67 ++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/wdl/tasks/RemoveSingleOrganismContamination.wdl b/wdl/tasks/RemoveSingleOrganismContamination.wdl index 668b81f5a..c5a0576b1 100644 --- a/wdl/tasks/RemoveSingleOrganismContamination.wdl +++ b/wdl/tasks/RemoveSingleOrganismContamination.wdl @@ -61,7 +61,7 @@ workflow RemoveSingleOrganismContamination { } } if (defined(input_bam) && (defined(fq_end1) || defined(fq_end2))) { - call Utils.FailWithWarning as t_002_TooManyInputsProvidedFailure { + call Utils.FailWithWarning as t_003_TooManyInputsProvidedFailure { input: warning = "Too many inputs provided! You must provide EITHER an input bam OR input fastq1/fastq2 files." } } @@ -74,28 +74,28 @@ workflow RemoveSingleOrganismContamination { if (defined(input_bam)) { # Convert the given bam to a uBAM (needed for previous aligned data): - call SRUTIL.RevertSam as t_003_RevertSam { + call SRUTIL.RevertSam as t_004_RevertSam { input: input_bam = select_first([input_bam]), prefix = SM + ".revertSam" } # Convert input SAM/BAM to FASTQ: - call SRUTIL.BamToFq as t_004_Bam2Fastq { + call SRUTIL.BamToFq as t_005_Bam2Fastq { input: - bam = t_003_RevertSam.bam, + bam = t_004_RevertSam.bam, prefix = SM } - call Utils.GetRawReadGroup as t_005_GetRawReadGroup { input: gcs_bam_path = select_first([input_bam]) } + call Utils.GetRawReadGroup as t_006_GetRawReadGroup { input: gcs_bam_path = select_first([input_bam]) } } - File fq_e1 = select_first([fq_end1, t_004_Bam2Fastq.fq_end1]) - File fq_e2 = select_first([fq_end2, t_004_Bam2Fastq.fq_end2]) + File fq_e1 = select_first([fq_end1, t_005_Bam2Fastq.fq_end1]) + File fq_e2 = select_first([fq_end2, t_005_Bam2Fastq.fq_end2]) - String RG = select_first([t_005_GetRawReadGroup.rg, "@RG\tID:" + SM + "_" + LB + "\tPL:" + platform + "\tLB:" + LB + "\tSM:" + SM]) + String RG = select_first([t_006_GetRawReadGroup.rg, "@RG\tID:" + SM + "_" + LB + "\tPL:" + platform + "\tLB:" + LB + "\tSM:" + SM]) # Align data to contaminant reference: - call SRUTIL.BwaMem2 as t_006_AlignReads { + call SRUTIL.BwaMem2 as t_007_AlignReads { input: fq_end1 = fq_e1, fq_end2 = fq_e2, @@ -117,39 +117,46 @@ workflow RemoveSingleOrganismContamination { runtime_attr_override = object {mem_gb: 64} # Need a lot of ram to use BWA-MEM2 } - call Utils.FilterReadsBySamFlags as t_007_ExtractDecontaminatedReads { + # Sort aligned bam: + call Utils.SortBam as t_008_SortAlignedBam { input: - bam = t_006_AlignReads.bam, + input_bam = t_007_AlignReads.bam, + prefix =SM + ".contaminant_aligned." + contaminant_ref_name + ".sorted" + } + + call Utils.FilterReadsBySamFlags as t_009_ExtractDecontaminatedReads { + input: + bam = t_008_SortAlignedBam.sorted_bam, sam_flags = "256", extra_args = " -f 12 ", prefix = SM + ".decontaminated" } - call Utils.FilterReadsBySamFlags as t_008_ExtractContaminatedReads { + call Utils.FilterReadsBySamFlags as t_010_ExtractContaminatedReads { input: - bam = t_006_AlignReads.bam, + bam = t_008_SortAlignedBam.sorted_bam, sam_flags = "12", prefix = SM + ".contaminated_" + contaminant_ref_name + "_reads" } - call Utils.SortBam as t_009_SortDecontaminatedReads { + call Utils.SortBam as t_011_SortDecontaminatedReads { input: - input_bam = t_007_ExtractDecontaminatedReads.output_bam, + input_bam = t_009_ExtractDecontaminatedReads.output_bam, extra_args = " -n ", prefix = SM + ".decontaminated.sorted" } - call Utils.SortBam as t_010_SortContaminatedReads { + call Utils.SortBam as t_012_SortContaminatedReads { input: - input_bam = t_008_ExtractContaminatedReads.output_bam, + input_bam = t_010_ExtractContaminatedReads.output_bam, extra_args = " -n ", prefix = SM + ".contaminated_" + contaminant_ref_name + "_reads.sorted" } # Convert input SAM/BAM to FASTQ: - call SRUTIL.BamToFq as t_011_CreateFastqFromDecontaminatedReads { + call SRUTIL.BamToFq as t_013_CreateFastqFromDecontaminatedReads { input: - bam = t_009_SortDecontaminatedReads.sorted_bam, + bam = t_011_SortDecontaminatedReads.sorted_bam, prefix = SM + ".decontaminated" } @@ -163,13 +170,13 @@ workflow RemoveSingleOrganismContamination { ############################################ # Chosen because it's a relatively small file. - File keyfile = t_011_CreateFastqFromDecontaminatedReads.monitoring_log + File keyfile = t_013_CreateFastqFromDecontaminatedReads.monitoring_log - call FF.FinalizeToFile as t_012_FinalizeContaminatedBam { input: outdir = outdir, file = t_010_SortContaminatedReads.sorted_bam, keyfile = keyfile } - call FF.FinalizeToFile as t_013_FinalizeContaminatedBai { input: outdir = outdir, file = t_010_SortContaminatedReads.sorted_bai, keyfile = keyfile } - call FF.FinalizeToFile as t_014_FinalizeDecontaminatedFq1 { input: outdir = outdir, file = t_011_CreateFastqFromDecontaminatedReads.fq_end1, keyfile = keyfile } - call FF.FinalizeToFile as t_015_FinalizeDecontaminatedFq2 { input: outdir = outdir, file = t_011_CreateFastqFromDecontaminatedReads.fq_end2, keyfile = keyfile } - call FF.FinalizeToFile as t_015_FinalizeDecontaminatedUnpaired { input: outdir = outdir, file = t_011_CreateFastqFromDecontaminatedReads.fq_unpaired, keyfile = keyfile } + call FF.FinalizeToFile as t_014_FinalizeContaminatedBam { input: outdir = outdir, file = t_012_SortContaminatedReads.sorted_bam, keyfile = keyfile } + call FF.FinalizeToFile as t_015_FinalizeContaminatedBai { input: outdir = outdir, file = t_012_SortContaminatedReads.sorted_bai, keyfile = keyfile } + call FF.FinalizeToFile as t_016_FinalizeDecontaminatedFq1 { input: outdir = outdir, file = t_013_CreateFastqFromDecontaminatedReads.fq_end1, keyfile = keyfile } + call FF.FinalizeToFile as t_017_FinalizeDecontaminatedFq2 { input: outdir = outdir, file = t_013_CreateFastqFromDecontaminatedReads.fq_end2, keyfile = keyfile } + call FF.FinalizeToFile as t_018_FinalizeDecontaminatedUnpaired { input: outdir = outdir, file = t_013_CreateFastqFromDecontaminatedReads.fq_unpaired, keyfile = keyfile } ############################################ # ___ _ _ @@ -181,11 +188,11 @@ workflow RemoveSingleOrganismContamination { ############################################ output { - File contaminated_bam = t_012_FinalizeContaminatedBam.gcs_path - File contaminated_bam_index = t_013_FinalizeContaminatedBai.gcs_path + File contaminated_bam = t_014_FinalizeContaminatedBam.gcs_path + File contaminated_bam_index = t_015_FinalizeContaminatedBai.gcs_path - File decontaminated_fq1 = t_014_FinalizeDecontaminatedFq1.gcs_path - File decontaminated_fq2 = t_015_FinalizeDecontaminatedFq2.gcs_path - File decontaminated_unpaired = t_015_FinalizeDecontaminatedUnpaired.gcs_path + File decontaminated_fq1 = t_016_FinalizeDecontaminatedFq1.gcs_path + File decontaminated_fq2 = t_017_FinalizeDecontaminatedFq2.gcs_path + File decontaminated_unpaired = t_018_FinalizeDecontaminatedUnpaired.gcs_path } } \ No newline at end of file From 7accfa4d900dc94bb782b0108f010e5c0001ff51 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 21 Sep 2023 15:56:32 -0400 Subject: [PATCH 239/297] Fixed RemoveSingleOrganismContamination. Minor change to ReblockGVCFs --- wdl/tasks/HaplotypeCaller.wdl | 5 + .../RemoveSingleOrganismContamination.wdl | 167 +++++++++++++++--- 2 files changed, 144 insertions(+), 28 deletions(-) diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index 599168b67..c4aad7b60 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -322,11 +322,15 @@ task ReblockGVCF { String prefix Float? tree_score_cutoff + Array[String]? annotations_to_keep + RuntimeAttr? runtime_attr_override } Int disk_size = ceil((size(gvcf, "GiB") * 4) + size(ref_fasta, "GiB") + size(ref_fasta_fai, "GiB") + size(ref_dict, "GiB") + 10) + String annotations_to_keep_arg = if defined(annotations_to_keep) then "--annotations-to-keep" else "" + command { set -euxo pipefail @@ -337,6 +341,7 @@ task ReblockGVCF { -do-qual-approx \ --floor-blocks -GQB 20 -GQB 30 -GQB 40 \ ~{"--tree-score-threshold-to-no-call " + tree_score_cutoff} \ + ~{annotations_to_keep_arg} ~{sep=" --annotations-to-keep " annotations_to_keep} \ -O ~{prefix}.rb.g.vcf.gz } diff --git a/wdl/tasks/RemoveSingleOrganismContamination.wdl b/wdl/tasks/RemoveSingleOrganismContamination.wdl index c5a0576b1..88b637eee 100644 --- a/wdl/tasks/RemoveSingleOrganismContamination.wdl +++ b/wdl/tasks/RemoveSingleOrganismContamination.wdl @@ -117,46 +117,39 @@ workflow RemoveSingleOrganismContamination { runtime_attr_override = object {mem_gb: 64} # Need a lot of ram to use BWA-MEM2 } - # Sort aligned bam: - call Utils.SortBam as t_008_SortAlignedBam { + call ExtractReadsWithSamtools as t_008_ExtractDecontaminatedReads { input: - input_bam = t_007_AlignReads.bam, - prefix =SM + ".contaminant_aligned." + contaminant_ref_name + ".sorted" - } - - call Utils.FilterReadsBySamFlags as t_009_ExtractDecontaminatedReads { - input: - bam = t_008_SortAlignedBam.sorted_bam, + bam = t_007_AlignReads.bam, sam_flags = "256", extra_args = " -f 12 ", prefix = SM + ".decontaminated" } - call Utils.FilterReadsBySamFlags as t_010_ExtractContaminatedReads { + call ExtractReadsWithSamtools as t_009_ExtractContaminatedReads { input: - bam = t_008_SortAlignedBam.sorted_bam, + bam = t_007_AlignReads.bam, sam_flags = "12", prefix = SM + ".contaminated_" + contaminant_ref_name + "_reads" } - call Utils.SortBam as t_011_SortDecontaminatedReads { + call SortBamWithoutIndexing as t_010_SortDecontaminatedReads { input: - input_bam = t_009_ExtractDecontaminatedReads.output_bam, + input_bam = t_008_ExtractDecontaminatedReads.output_bam, extra_args = " -n ", prefix = SM + ".decontaminated.sorted" } - call Utils.SortBam as t_012_SortContaminatedReads { + call SortBamWithoutIndexing as t_011_SortContaminatedReads { input: - input_bam = t_010_ExtractContaminatedReads.output_bam, + input_bam = t_009_ExtractContaminatedReads.output_bam, extra_args = " -n ", prefix = SM + ".contaminated_" + contaminant_ref_name + "_reads.sorted" } # Convert input SAM/BAM to FASTQ: - call SRUTIL.BamToFq as t_013_CreateFastqFromDecontaminatedReads { + call SRUTIL.BamToFq as t_012_CreateFastqFromDecontaminatedReads { input: - bam = t_011_SortDecontaminatedReads.sorted_bam, + bam = t_010_SortDecontaminatedReads.sorted_bam, prefix = SM + ".decontaminated" } @@ -170,13 +163,12 @@ workflow RemoveSingleOrganismContamination { ############################################ # Chosen because it's a relatively small file. - File keyfile = t_013_CreateFastqFromDecontaminatedReads.monitoring_log + File keyfile = t_012_CreateFastqFromDecontaminatedReads.monitoring_log - call FF.FinalizeToFile as t_014_FinalizeContaminatedBam { input: outdir = outdir, file = t_012_SortContaminatedReads.sorted_bam, keyfile = keyfile } - call FF.FinalizeToFile as t_015_FinalizeContaminatedBai { input: outdir = outdir, file = t_012_SortContaminatedReads.sorted_bai, keyfile = keyfile } - call FF.FinalizeToFile as t_016_FinalizeDecontaminatedFq1 { input: outdir = outdir, file = t_013_CreateFastqFromDecontaminatedReads.fq_end1, keyfile = keyfile } - call FF.FinalizeToFile as t_017_FinalizeDecontaminatedFq2 { input: outdir = outdir, file = t_013_CreateFastqFromDecontaminatedReads.fq_end2, keyfile = keyfile } - call FF.FinalizeToFile as t_018_FinalizeDecontaminatedUnpaired { input: outdir = outdir, file = t_013_CreateFastqFromDecontaminatedReads.fq_unpaired, keyfile = keyfile } + call FF.FinalizeToFile as t_013_FinalizeContaminatedBam { input: outdir = outdir, file = t_011_SortContaminatedReads.sorted_bam, keyfile = keyfile } + call FF.FinalizeToFile as t_014_FinalizeDecontaminatedFq1 { input: outdir = outdir, file = t_012_CreateFastqFromDecontaminatedReads.fq_end1, keyfile = keyfile } + call FF.FinalizeToFile as t_015_FinalizeDecontaminatedFq2 { input: outdir = outdir, file = t_012_CreateFastqFromDecontaminatedReads.fq_end2, keyfile = keyfile } + call FF.FinalizeToFile as t_016_FinalizeDecontaminatedUnpaired { input: outdir = outdir, file = t_012_CreateFastqFromDecontaminatedReads.fq_unpaired, keyfile = keyfile } ############################################ # ___ _ _ @@ -188,11 +180,130 @@ workflow RemoveSingleOrganismContamination { ############################################ output { - File contaminated_bam = t_014_FinalizeContaminatedBam.gcs_path - File contaminated_bam_index = t_015_FinalizeContaminatedBai.gcs_path + File contaminated_bam = t_013_FinalizeContaminatedBam.gcs_path + + File decontaminated_fq1 = t_014_FinalizeDecontaminatedFq1.gcs_path + File decontaminated_fq2 = t_015_FinalizeDecontaminatedFq2.gcs_path + File decontaminated_unpaired = t_016_FinalizeDecontaminatedUnpaired.gcs_path + } +} + +task ExtractReadsWithSamtools { + meta { + description : "Filter reads based on sam flags. Reads with ANY of the given flags will be removed from the given dataset. Does not sort or index the results." + author : "Jonn Smith" + email : "jonn@broadinstitute.org" + } + + input { + File bam + String sam_flags + + String extra_args = "" + + String prefix = "filtered_reads" - File decontaminated_fq1 = t_016_FinalizeDecontaminatedFq1.gcs_path - File decontaminated_fq2 = t_017_FinalizeDecontaminatedFq2.gcs_path - File decontaminated_unpaired = t_018_FinalizeDecontaminatedUnpaired.gcs_path + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + bam: "BAM file to be filtered." + sam_flags: "Flags for which to remove reads. Reads with ANY of the given flags will be removed from the given dataset." + prefix : "[Optional] Prefix string to name the output file (Default: filtered_reads)." + } + + Int disk_size = 20 + ceil(11 * size(bam, "GiB")) + + command <<< + + # Make sure we use all our proocesors: + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + + samtools view -h -b -F ~{sam_flags} -@$np ~{extra_args} ~{bam} > ~{prefix}.bam + >>> + + output { + File output_bam = "~{prefix}.bam" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 1, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-align:0.1.26" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task SortBamWithoutIndexing { + input { + File input_bam + String prefix = "sorted" + + String? extra_args = "" + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + input_bam: "input BAM" + prefix: "[default-valued] prefix for output BAM" + } + + Int disk_size = 10 + 10*ceil(size(input_bam, "GB")) + + command <<< + set -euxo pipefail + + export MONITOR_MOUNT_POINT="/cromwell_root" + curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh + chmod +x monitoring_script.sh + ./monitoring_script.sh &> resources.log & + monitoring_pid=$! + + num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) + + samtools sort ~{extra_args} -@$num_core -o ~{prefix}.bam ~{input_bam} + + kill $monitoring_pid + >>> + + output { + File sorted_bam = "~{prefix}.bam" + File monitoring_log = "resources.log" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 3, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-utils:0.1.8" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) } } \ No newline at end of file From 3560ca4f16610707b91f61eb6c1cd3da50f66928 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 21 Sep 2023 17:28:19 -0400 Subject: [PATCH 240/297] Fixing issue in `SRFlowcell` caused by other chantges. --- wdl/SRFlowcell.wdl | 1 - 1 file changed, 1 deletion(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index fbf209ca5..a4e26286d 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -354,7 +354,6 @@ workflow SRFlowcell { # Contaminated BAM file: File? contaminated_bam = DecontaminateSample.contaminated_bam - File? contaminated_bai = DecontaminateSample.contaminated_bam_index # Aligned BAM file File aligned_bam = t_023_FinalizeAlignedBam.gcs_path From 633d74434dd176232868f3d785cbe4c9438dc4d0 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 25 Sep 2023 09:43:44 -0400 Subject: [PATCH 241/297] Removed decontamination task. Added AlignedMetrics. --- wdl/SRFlowcell.wdl | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index a4e26286d..8bfc8a928 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -79,6 +79,10 @@ workflow SRFlowcell { # Create an outdir: String outdir = if DEBUG_MODE then sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}/" + t_001_WdlExecutionStartTimestamp.timestamp_string else sub(gcs_out_root_dir, "/$", "") + "/SRFlowcell/~{dir_prefix}" + String reads_dir = outdir + "/reads" + String unaligned_reads_dir = outdir + "/reads/unaligned" + String aligned_reads_dir = outdir + "/reads/aligned" + String metrics_dir = outdir + "/metrics" if (defined(bam)) { # Convert the given bam to a uBAM (needed for previous aligned data): @@ -100,7 +104,8 @@ workflow SRFlowcell { # OK, this is inefficient, but let's NOW extract our contaminated reads if we have the info. # TODO: Move this into the sections above to make it more efficient. Specifically where we convert bam -> fastq. - if (defined(contaminant_ref_map_file)) { + # TODO: Re-enable this section after decontamination is fixed. The alignment based method with BWA-MEM doesn't work. Not clear why, but this does seem somewhat inadequate (simplistic alignment-based strategies). + if (false && defined(contaminant_ref_map_file)) { # Call our sub-workflow for decontamination: # NOTE: We don't need to be too concerned with the finalization info. @@ -223,7 +228,6 @@ workflow SRFlowcell { call FastQC.FastQC as t_012_FastQC { input: bam = t_010_ApplyBQSR.recalibrated_bam, bai = t_010_ApplyBQSR.recalibrated_bai } call Utils.ComputeGenomeLength as t_013_ComputeGenomeLength { input: fasta = ref_map['fasta'] } - call SRUTIL.ComputeBamStats as t_014_ComputeBamStats { input: bam_file = t_010_ApplyBQSR.recalibrated_bam } # Collect stats on aligned reads: @@ -233,6 +237,15 @@ workflow SRFlowcell { call SRUTIL.ComputeBamStats as t_018_ComputeBamStatsQ12 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 12 } call SRUTIL.ComputeBamStats as t_019_ComputeBamStatsQ15 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 15 } + call AM.AlignedMetrics as PerFlowcellMetrics { + input: + aligned_bam = t_010_ApplyBQSR.recalibrated_bam, + aligned_bai = t_010_ApplyBQSR.recalibrated_bai, + ref_fasta = ref_map['fasta'], + ref_dict = ref_map['dict'], + gcs_output_dir = metrics_dir + } + ############################################ # _____ _ _ _ # | ___(_)_ __ __ _| (_)_______ @@ -242,10 +255,6 @@ workflow SRFlowcell { # ############################################ File keyfile = t_014_ComputeBamStats.results_file - String reads_dir = outdir + "/reads" - String unaligned_reads_dir = outdir + "/reads/unaligned" - String aligned_reads_dir = outdir + "/reads/aligned" - String metrics_dir = outdir + "/metrics" # Finalize our unaligned reads first: call FF.FinalizeToDir as t_020_FinalizeUnalignedFastqReads { From 10d26d78494d6cef725ce11ed5cd47cca4acf4e0 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 8 Oct 2023 15:24:17 -0400 Subject: [PATCH 242/297] Added WDL to extract reads from amplicon regions. Added `ExtractRegionsFromBam.wdl` Added task: `Utils::GetReadsInBedfileRegions` Minor comments added to `VariantUtils::ExtractFingerprintAndBarcode` --- wdl/ExtractRegionsFromBam.wdl | 61 +++++++++++++++++++++++++++++++++++ wdl/tasks/Utils.wdl | 61 +++++++++++++++++++++++++++++++++++ wdl/tasks/VariantUtils.wdl | 4 ++- 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 wdl/ExtractRegionsFromBam.wdl diff --git a/wdl/ExtractRegionsFromBam.wdl b/wdl/ExtractRegionsFromBam.wdl new file mode 100644 index 000000000..b07b06150 --- /dev/null +++ b/wdl/ExtractRegionsFromBam.wdl @@ -0,0 +1,61 @@ +version 1.0 + +import "tasks/Utils.wdl" as Utils +import "tasks/SRUtils.wdl" as SRUtils +import "tasks/Finalize.wdl" as FF + +workflow ExtractRegionsFromBam { + meta { + desciption: "Extract reads from the given bam file which overlap the regions in the given bed file." + } + + input { + String gcs_bam_path + File regions_bed + + String participant_name + String extraction_comment + + String gcs_out_root_dir + } + + parameter_meta { + gcs_bam_path: "GCS URL to bam file from which to extract reads." + regions_bed: "Bed file containing regions for which to extract reads." + participant_name: "Participant (or sample) name for the given bam file." + extraction_comment: "Comment to add to the end of the output filename." + } + + # First clean the extraction comment: + String clean_comment = sub(extraction_comment, "[!$\t\n\r/<>:\"\\|?*&%#@'`~\[\]\{\}]", "_") + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/ExtractRegionsFromBam/~{participant_name}_~{extraction_comment}" + + call Utils.GetReadsInBedFileRegions as GetReadsInBedFileRegions { + input: + gcs_bam_path = gcs_bam_path, + regions_bed = regions_bed, + prefix = "~{participant_name}_~{extraction_comment}", + } + + call SRUtils.BamToFq as Bam2Fastq { + input: + bam = GetReadsInBedFileRegions.bam, + prefix = "~{participant_name}_~{extraction_comment}" + } + + call FF.FinalizeToFile as FinalizeBam { input: outdir = outdir, file = GetReadsInBedFileRegions.bam } + call FF.FinalizeToFile as FinalizeBai { input: outdir = outdir, file = GetReadsInBedFileRegions.bai } + call FF.FinalizeToFile as FinalizeFqEnd1 { input: outdir = outdir, file = Bam2Fastq.fq_end1 } + call FF.FinalizeToFile as FinalizeFqEnd2 { input: outdir = outdir, file = Bam2Fastq.fq_end2 } + call FF.FinalizeToFile as FinalizeFqUnpaired { input: outdir = outdir, file = Bam2Fastq.fq_unpaired } + + output { + File bam = FinalizeBam.gcs_path + File bai = FinalizeBai.gcs_path + File fq_end1 = FinalizeFqEnd1.gcs_path + File fq_end2 = FinalizeFqEnd2.gcs_path + File fq_unpaired = FinalizeFqUnpaired.gcs_path + + } +} \ No newline at end of file diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 8db862a4b..1dcba6b7f 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -2360,6 +2360,67 @@ task GetRawReadGroup { } } +task GetReadsInBedFileRegions { + meta { + desciption: "Get the reads from the given bam path which overlap the regions in the given bed file." + } + + input { + String gcs_bam_path + File regions_bed + + String prefix = "reads" + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + gcs_bam_path: "GCS URL to bam file from which to extract reads." + regions_bed: "Bed file containing regions for which to extract reads." + prefix: "[default-valued] prefix for output BAM" + runtime_attr_override: "Runtime attributes override struct." + } + + Int disk_size = 2 * ceil(size([gcs_bam_path, regions_bed], "GB")) + + command <<< + set -x + export GCS_OAUTH_TOKEN=`gcloud auth application-default print-access-token` + + # Make sure we use all our proocesors: + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + + samtools view -@${np} -b -h -L ~{regions_bed} ~{gcs_bam_path} | samtools sort - > ~{prefix}.bam + samtools index -@${np} ~{prefix}.bam + >>> + + output { + File bam = "~{prefix}.bam" + File bai = "~{prefix}.bai" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 16, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 3, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-pb:0.1.30" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + task FailWithWarning { input { String warning diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index 9200296dd..aeee71f8b 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -1564,10 +1564,12 @@ def extract_barcode(vcf_file, haplotype_database_file, ref_seq_dict, vcf_out_pat # Write the variant to the output file: vcf_out.write(variant) else: - # We need to pull from the reference for this site: + # We need to pull from the reference for this site. # Add 1 for genomic coordinates: bca = ref_seq_dict[contig][site-1] + # TODO: it is possible that we should instead add an X here (https://doi.org/10.1093%2Fpnasnexus%2Fpgac187). Double-check with Wes. + # Add our barcode allele: barcode_alleles.append(bca) From 6ebfbe5ccbeb28f9c027180e4b1446e9d5853f01 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 8 Oct 2023 23:43:12 -0400 Subject: [PATCH 243/297] Added new diagram of high-level view of pipeline. --- ..._sr_malaria_pipeline_diagram_high_level.png | Bin 0 -> 110174 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 extra_documentation/sp_malaria/lrma_sr_malaria_pipeline_diagram_high_level.png diff --git a/extra_documentation/sp_malaria/lrma_sr_malaria_pipeline_diagram_high_level.png b/extra_documentation/sp_malaria/lrma_sr_malaria_pipeline_diagram_high_level.png new file mode 100644 index 0000000000000000000000000000000000000000..708bba56806e9fe2272d839e32dc5902dd81b0b7 GIT binary patch literal 110174 zcmZ^L1zc2H_ckC%OM`R`4MQU>DcvSt)fmIHX5#a0omokAY9#B|!uw9qR_ z7llvhXja#8?SM_iq6h)8}G3=9QPsqRKaOpp{4K zio%5ynpu{pgcfuhPJU)5n33n70h9G0vn>mAZ$O;$`WpQ+N};cVWx>8y-F#mtM)7M1A= z0T_d+ob|~h9~eSBSYu*c5S3Z9Vf35S{S!amjtrF|#85Pw%6=(| zCxx;J{l>jix#Bp2u=sFjv^v8@j^9enM#>LAJeU0Th2!^fN@MtDdLK;?@rr$o1Usw%OB)<)iVGS6>@Ce|r(L`5YlFMP)JE)XHgz?;03xQz%5v zLP-gZ8F-HZhX9WY2TTPVJn$bb%nA1B>7NLPCsZvSWJ3*=Vz#L!> z8c{SVDk@z5WXaAcARxfb!NtzS#RiAb?&YAYlMgG2y6x7+w$;#o4mAxI+!*xwe?Ooo8(9k^G=wJW-^b_iC_3t~` zIsf%ozyq>B9AW1KbFlyan!T~I_=jc>NB%VX{ak-;C;V_RK~*bvsEw|al`Sx;K-WZh z`FMnX-{$`u`FBVEwA6BjI!W5w0uA4Y{(D;fYW$xE|F_}qTSESQOHM9+?*F{!e;oRc zrVq~`sN!S=+|J}-5JfqK+5gXHf1NMP{xHJ-G2(xw^Y^E~e2St8v;S*th@$1@XnVlH ziNVQ9iEFyU@8+O(XlYyz=gF3$5GR806Xi)JMzOh0GKUqRH(6WMof$t*X@bpB5~w9h zKUD+-mzkqUt80=>&`kOH5Lq`*w_Y!KO?7Tfb#I*robNWwELd0Fe%s1zm}d$7hocC;Adi`PGQ@ii*YN9)m;8EB+GO8Ec(l^o!S^$&+o!o}FA zsi4MetsmPI|KTy=apJjD5B_No;Bm_65Q{M?>7KnzWBR8-Ku~|YE%J{;gwPo5pB&Km z{lyN~KTpP3k9rq!sY+h%=|sb{=;Dl?y~xgDv;7R8%k~#e3M20$Y16FhgYK1C`|jJb zh*SLirbW9hrEcF_+(qx}z2>f5M?$uj$#a{jdUi*)XUkp(1DeLv{XI^+-1*jwn-yHULDTO^DPb~K#J`WLXQIV^6 zSnGS0?Qyb6$IH1)`K$MuF3YB2lCNv>5teZLT<|%Q&*2EY&(*J^(=c*ZNE3b2yhYX} z-?Z?FqG=W?9{t7D)#(nyjE4KN$JQmEh}YS~f<=zY%#rO-EP2-4=2sn~(^I8P9=Xc5 zwO;4@U6X@EM%j%@#$3>`fsrwJik(gM9Qw9D(D8&`Y<5kD&(E!)Fw;we{z$cs{7@!{pH2a@yONkT-}^ZV_LV*ROvDB z*14_t%=+FO$t-%GOxo@>&VoN}rWyHiwouIv^)5h%qKQLOl*d!qqJD|@>e+WfyYH?l z8m&GL%|OgzXl#8i){uc2JGW`wNNunoOv>{)%cxuJ=fVTa9bfI#EGE(^*Z|A6UQ5TI zQ8oz)op27=H1;gjErs2)(j+Z`4slt$))vBvpxT2k?~C)px`)=LZ;t9Sd=V{jy zUcu+`iHD+Vw}Gc~nby_1#i$3|%l6~o_R77M^;qd`rgT2rOq0Qx7Duy1^CTtQ#c0xJ zQ@;1NZui~&_o}6be*O2;$K&kwi+1g{7L9(B_muZxH&`d#*&wr_=sMu}#|2$?UqRxb zXfv6Vz83{1Khq}>k}2s|e9jG&^N8l>8ZHlizRe@iuFLfP?8qh2d!Vl=J^S`$ z#=z~{03Tgv+T4#G=1i|)5k*{4BEP#!OY5)YDOHU#FQqe8WwM~DxJ`xn`U9qx60DksZR_>~2%o1UlJmy~W9ryZfhyE8_9x0R_qkhHtYt-QNCI^9aA zgO;$HxRdT&PzSO*yG_elXjAWFQV?%lk0G$+kA4W5Fhw~O9|Vh;{%q*NORYzdxl(4l z%ISIe`r>fSH^-w7l>t%HY8tZr{CvsTb&_{>t|ZMcQ=7=p*kt6qOzN-gP%Z>)2kw2U z=f=;ke$XDsyM2;lfPUF7`ESP{rbNr(^j+4h|KfurmeNv7u8>>x%9qV%1X5`^QZ?R~ zh-#VN*UR6;Ez@&mXS*D${UA2)HbNZ+;`{NjD9nU7G|T_*TmH4X@;CA3TxJboWcYeX z;d+xKB71C-HNn5#fvN$@7v^y>ePl=WLAJDSB1_zK|LYG`apCa;gA3+>-G!TFY9QMZ zR{ViFf}u?HujgWp5UWyu+?VcdF)&2?`DIhOQ_355p347uEM7V>MFSXKA^s9H&7l0J z)~kg-_h}3ei+O{$sgNH0R8FEV)W$__PmBJ4J=Gu%6*c9uOba&4lB#2|Cc~eu6QF!y z92cwi%XM9_dJ(E$FD3s!)Z#st-_g_2rymUo^nFWR-NJ-!B7)iw#Jft22d0gbP}md~A)RuK}1#+$hO{J zBl@rQ$8?ANSkE8k&eC~vqAuVO@}^hYLew81AtsKhDfDa&*r^jEU!=XoBL2JQ{wSI* z2o=?BKs~vbo=WFtt!>JzL#1 z`RnIryv1k=k%d{nL>~dJ=#S4WiWALjR7&%!w%lK8jj#>HB;CB`->GcC-C+_2#?L2f zE7uELH`(EF;w@r!3mCHqTMDmT&Xv%W`}yw!cq+eaU&N54%mqKqjjr3NZl?!)dZZpU({6lF(HKhlzw~*7=Ix$}FY(Y5;KKv%SUH=AW}QMl*ch+FVX-OOo*tcaS^t;?l9DqO;{AWGT??Sr*pd|!*OTiT9lp$-$@BX$6^Wfel zaqpg4?>bY}aIYS<`*@5c_ZqND=I7tsjFeiC+aCWnBnx<{xC&U-)3HFqPEXz2t5a*v zh;o;ojP`)RHL!78v$pe2zusKVloYp{Yif+8CL5wc zJ@$rzA?J;{DXD2~=#HGtDq6pY-BOEV6MZO6+7g~*C`O&n**t@Fd#0w^-cqo~=gtdQ zl z$~kT2xV`KT#q11ZlXDC~h*+rwwJ*6Y+9tIfj)^7PC}r_A#_%mL)=j2!n(fwf-yPQ- zB_%U34v5|yO4dL|37=C!snF<#j4MH?+OG4gRp|_4<6pJ)*eXoXp;W0Rj8*ka#_}Y{ z(AD==pF4C`3H59ec(iITZUBeojWGo{s0vw9d}~JQoiFdqg!bNRBzU5!;+q)El&R4! z0=N=)F&{%JV7|##PtWJ{Yt(yjVtz-MRE17Wu5I%-SHSm*5%YVU*;s!njsOsja2+Pl z>7u>G=4Qa*m&lUtlb<`3q-fVG=LizKdkY7p(#`>KFBk`5;w~bdG3M#$I}Xy=sy>EA zL`a`#>)C1n4s!W(N3-7hR|#4l$%jVI3?+y!fQ5E_pZ5T6VuNU~H(mkY#TJ09OrIV@ zAC~jpctcE3$d71ZZkul|vyH&|e=kdk-vhtFaA2_STzY5-Q_Jpqc9LCw*S5g)s6tS6 zQsrTq5a7F{E(1-G9WtP)sDm4K6GSvon86_uP12B7m12@7J%RD$7QrD-S$wwTYg%8j znHs1pX7fUH9Ek<3rwDc)Ect7efC9-d&+)WjO23CWYhH*f=&SKMTcJ@`=1DX zK^bX7!NCs5l=deVaJ?^8%z(KWIkNQN=jC)!?QXBD2QE2{vx&OOm$4jJy4K`*ZZkcf zNKz5Cnx8ly=fZM5p0k3U?*eA(TqtfA+yt0R)^FNZtpkn%CllPa!c+X~811)bi|B$3 zgjk1Yfr;q`^2?IDmEiW>`U3gsSU67e2PZNO1Kdf)*#Y}E%?BgG7z-!&qIMub69c}| zKU_Ml5zBEpGU{BLllv1R;3l80qa@5&2y5elM+Q7o^enRM)A6) z^DMLZ@vE=pT82c$Ll2Hs)ete@BU6YwC8p;Kj;ZpvOUWFV{s0J8aauZYz-9= zUZr;g)mV7G`eY7i3HWq9*T_1Fpnz{cEM?=h*QlfJQpFl|PwA-tEoHLtn0~tu2>+bu zsicjk%FsT=$7>8wn_&)zX8*<-^*Al3GkUl7oj_w@y`Y z4(B;iDn`M0bL@2o7;fA9+w*hra-lar;#Wp_-}BaGhPwr2IoP6Z>Py;}fE9ohfPomE z7E*Erps$yqhOZCa4e0hGaLC=Ogy~|>5fcsj-CwUPWAY?3h!k;1;qU;CsCfH57oQ>uhN6CyjqdU%WyVb;=Quq> zg3mnVXc&xC`K~CX3i{8g(_|7k>;un_r8C&p^*(vDe8DaDoXQrEE>{yvJdF~YX+?`v z2vlP@O|zza&w|-LaTl>S;sa8k)($|Kc{^WE5PH)3INiM6Z3)kma+8*0?QXyzJ!xwC zv@PS2GiXM=Dcb#)k-89!ufB}N3L*#ol5*;oJRk@Cy+&jaDmj6L$tASkvW_-yTlHh- zdy)ztu0pMH!b7kn=7H04wqU(i)x6BbS6>m+LaBlK+vNq|qO=5)F`f7BQ-YJUP^&bU znu91C-Riaq&kB~H$qE8)AHUq+uH2VoXUTt=(G@Sr_1MfP^WlH`+}C@oRkjp}@>|Pn z(^cHNuJ_x0oQY^TUazxWN)X-6Gi9kOo1{7E&Z|$c4K+J+?x@R8?v9VZD{nXx9 z-f47kH0)qTN6jgh$EXKsKv@4aTY?u!<-BO9*`+Z^T)GI11YLa|Bi zYE1wbDiOWEIVod9o|2$Jsws9>{o>Rq3ydBsoCYIT(p51A9Z#aQ(1L{*`|yE=$1Npi z*KZ3{js|b3Wu#TAUbT4yAoS8~9f`)w26~oiW zynFgg^)2Er+wh>1?)%$mfXZtBkzM@AG6M>@h3=XK-|Iae{Tb;f!VC?r%X$E!JR4ie zd?O0|*6tQpKGLz3Y%|krzfuVv2k=}qx$^|4=&zuLY95LfA?kgFDPS+gmUUmiSAQ6Y z_5l#xUIasKI1C&1;(EKHCQ~DP0pMy(0pQDr;7>bM|Is)Dq)cp~JpZ!+NpKvk{}!Lx zia<_CK2f4}oG(;^hg3H@2r+H#P*8Z?6^^@}&zVv}7_>8JUl>HUAp+o?Ta0!BDEN zp>AzDiE+Nr_;@cPpeU#G+f=S1)(>{DT!`^jO2Vd&b3nh>?ilEBGs|9k^KjHmCwPd{ z3PX|cTeLb@)>FN4?02g%90$%(b?|3voN)%xX;`YaqOe_S@+24bI4jXPdz&)r+Y9~g zjsS!7Vgs@pa(sERC9|Q-gr72OpxmIVA+VwPWtl0e(3c-2H5BqR71Zf@D&L@LX4mr5 z;w+rc_!SHFJZ@mMQesryAcrTN2 zGGqP6nwHD;3}Pv_BHC*?J7uN-cDIUQ73S@Q)euWARN4igVCMd!PppsX0v)SWUB7DS zw61`D+u^b+L~VUXX;MC$bReMfnE^29tG*(cxUuw5VWM4@5+p>O*#1n09-MsLw z(*)sxwY?n+j4H2d(fS1^2j8FUNsuOrf=1<8s*-2?gf+O^ZxA10!MY&*1uC*Qo#Esk za!&#Wrh6s6`LKkQ4{S-)MY+YMFwCTY4#aAQXwl~rWGIO33CYcr4wB72?l@rQEdq|1 zByzGfc@fx4q@7QM5+R#NYa%~j`ypx4x$8b@zjAwz`i3b}~Gfd;rS>PX+$)&OSSQxT)HJR-=9$M015w*RJ%n@NEsXHIiW#BFH+)Y0< zCnUGK`EZOr%eF~3fjN1Idg<}(I_za>W3Meg%Km#<_szLMyzfkE^3LCAUql}sz~|yv z##jNdaoaZlS}%9D0}I0tK3AcAK#mSy`}@6G-yt*ZgWHj(J)AR(yL2E8UNYu>TmrGh zld#6t9vIYQJvJ~}g#7RXP+>Veyl56@ivxw>a(GesME$4a0JwOX+VT!e9xUJz-l+N6 z5~Jc|ZN@$!P8jG7*!ES$qEQVak$F4y?Ih^Frag6Ym`5Aw{c;RB=J%J=ey~ZrIJNXD zfIHk=itrI3v;u3`n=WDpvDa}G#m_l|S*qfC!zAzr%bcfAXN&;;*Q8jAc8WX|9TIsY z{j_;^{Ku#6fd$U20fM0SOrzy-eAKDr!zg5Gh%}674NM5{*x6mD3}#egH<-cm3JIl* z+VfT*Lz>x>z2=ot-H3WTVi4n9wzVr`Bz}aN_-;PW?4*|s!1VFC?n0b1NIUD(!F8E~ z+X;|R+3G#-b=*5mV7yQ@)EXZM7)?faJ`^K>ROW1)|z3HYtIFAjkEg%W7GW zWc?GwsNog@Glw-yj8{SJ%~8Zp=}ZcuugQ|@N;>UWW#l#kODf03l$t-1-A4J{eP00k zDqF>&pZaMo9J%B^j6gDSz(Yrb+p4sE0Om=wISQNOp!>e-Alt>T)bhn;bh#l*7F@TR zEc3_}f7Y59bXsklC{(PK<&mQaY`6k_+c2HDg6P3CmLVb}F{qrSdh9JUa^pYEvhQlm zLpnN?h!#mUZ+(fh>5RI26?Wn8Eneg;8PJb_Swck!KM=+u7tTPG900NXn5JO5wzV5L ze`Qk-5rCy;gIZ!-cV;H_8gTBhq#2_S@l~mxkP{jOU^)^Q)MKxF9Z8_I1=2c)Xa&~Q zt@Q7%o!I*uvv!A;*z2*8BZfsWpc&IpXxh9HwGkdWGvlMt#~sGK2$Ylw(O($raPUJ1 z$fO_>MJ+?**`7;njt8OWQO}dzO^))WUDmnw6GJcV@wjXou8HFa!nGIy2(!}S?}rAA zTMyGamfwy5KyuvheIwogcQN!b#j@kz5TU1`nDuCM?gK48M&HYRx&!7DeU`RIGg6Xa zPn1Ygm8@qf82Q&Zdp?tVg%MTW9vYgEhaHK{eXkjm9XtJWv_B8Hg*_lZJU$q8B4;f+ zangBwyuLGUnb#O6dNra?I1s&ds{Rlbl@kR-;4zTIDfWIl0kR#_tl*KIhqMdJ>V-rs zOP$0PtNH!|i$$!Py{f*;wSS38vLshGvyPKyEJDaWE^I*oQX9p`R|I9C)B|wHBTdb) z!5m$bz`Wpc2 zEda?u#UwO5)~^mFG+h*Z=eXLj1Iqdy8!672GP-jcJYvKoA^_>&%NbSj3cEVNYpgHu z8ZQkXAErkvqEoDe;l}}#Z2_<+-W~5YJikGx9sJg3>Ta-mX|SVu8>R%u&$F!C-^<~z zN>ks*p>dGAP(PTc4SE0RyH@9|)SagU^8${R{aBlkWX;TRAsZAspIc}+a#f#1`BkpG zjuZxx9m`%J=Mn{o_DDd605>7ss9ywNU3_n2pC#Z2Y}{A3?6>WB&%Ff&#W_)aLo#G{ zbL@qU`(R-HPBs%r_w1#Y=qE-ebMJu9saJqNJXxc=fceHANAgHikaGG51V0Hi9HWcs8@#xwCXqOslF{$ z%1U*bL1Su;%5!-FL}v@)6a*WWe#yf;Uo;;!33G4{z?y7yU+d4sqUly>4oJ5S7%>s; z(`+PX6L9LMPzJf^HCRj*y2L7cNrkmf4WcWzPS$+EQxQ&ZqBc+;tm?d|c8yr6H>^1M z5q{;C)!UVWPP{(rH+9Rm<8g#GgITHTF(uO?`u4lx;3*3Dab4bY+fH?sv(8i&L)+u# zj2x$kU+T#MDyhlNZ)3NYhAiuR>NmGK(`-92oYUS{&56QN4jm|1-sv`2lw^P6GfZ(+ z%sDBRYjvNp+K^*69$c6m^qde>nHq02`1CXb+JW_yJMH|@j_6Y$sh&1y=)C!~agEo8 zS#qS0@lRk`R*0BA{?@3BpJ-YNX-1mJ0H4hqqAHt;8_x2`0Q-9hfnd1{;xE7C6}Bzp zNasQMqau~W9JuG7R=+3GLG6o49t=3Q#e}SY!a;Hanu^v;+=P6~?n_?tl`fy6)4Yb3 z>C2Oa!NSj*O+?Q zFyevFzf=EZO6Y*BaZ7lMGu;!&<3+u9vrFt!u`JknM#m0NZOKs@TjO@^dwezK&bbU9 zTTF`5EH{m^2T}sSL#{7B=Zbi}@cHBy4`eZ}obW>#3k>=?Uzt%LL9vrxPi4^{g>f*X zv9fQl5@~?#C0?bT?F!SWgKgx-JSL^KdP1FHT!54f>{JopkR2x&pErYJc|mpD^q8l0>) zrdw9pSmJXN&<9aJdeCaPZ<44zEH@|mRnseJqf{VlR5>b%9ZYdLd8elj!zqBf=9s2EC+Q1}IPg%m|wC5Z8#Q zC>Aq*iy#58BS3`@Pn>nUNEm#qt`DTmlbzNqaTkleNH7tk<(v1ZPlu$*31OAMLWm60 zwF&cNbdv*)b|l3~K#cva^H8Fi)yTLoA|#mU&k{j^!*elYl;D2$MKy9T#q!8^eT5b> zT`+-ls&B?0x&w$UuuC&x_rivh3n7+UkM|Zg7h;Fju71+W5s$cohunGIau7~4O0H{1 zgA;7N#9B+Nk>Yp0cXJ%j0N)kqw7noI2|}XzIYyQ?jBlxrK43^T15>}+ z1{ebSU$@c%q`KnzFU;&yUI;{XTi0~*@p@kzegTL|l9)Z*tV;*_wd{NhjFo`wuqbXc zF4k)YON%AdY3~Ohc~51o?3f6|!p+*OdlGpzB#$Fb@+pv*vz)-l67O}lU?DPFxb95ZO~beb;eXus{yDZ@(RA+@&skP5kd81thY74^rVM;!7!`~lVxEFc3`h3E8s zd_eNz)f7aRhLKT{GK|8rjQw+-IYMiT5kp>vo|Sk5D&Eb9j6%IUQJ;xU>LroX2R0D^ zh9NzkJ5^f^M9%?j$tI!bCM+TS+VmL_`=G>e;>34sC~*yajsl@UGs;n#W)i}y*w%Xv9B-L>gP1y6-D`&g*j~;8r zK&Jm!gee!^)?m^{f$@5ph>v}jgCa?)!%R;_kco^wW*3e>V^~`N!kT1vX4G>-&!2R( z5r+ENZ)5#_;-|g9eb(H%Wh6A1uX2U_2=bWvGDc(iB5jz$t5TqOR~0Hl?xOi-*OY%T zTfq6oI~dZF`~DfY>9GI z`vRPC_}(I(Rmo}(&nYwQ8Tp;fTZ+o%Ij5YR6nq$DKb7O+r}xOwi1wtYv&Be*#XQ{)zmhiwI(7gzeyx+xbkg5T)o0C8IeOF;vtnMbbd2?%Pq&WVa0M09ji|0o5D3&rP4? zS%^K0@}GXS@X3aKGTVB+_bhB4hry@*PVzPO=Z(t0J3@nBDMOA%C+*YoN>FWL0EiSL*_TNJKx%y&(WCnJKS@|oqklw zwYltU`a$X!C*vZ$_FEa2C-gfcR@}Cxa&8*|K6&n*(Q_??=+goc;ncUu`#Gm-Xeck8 zHVVWum{J8u%pRqpaX9rZce)@&?Y11Qqs`pFIg9WA4wZaNOcafJBSF5(O=b(Gy!~-@ zkk)kB&85zIUY%AHO%(X$^(nSGi6Q1as)>(-$tU;I;%n*)U9Y!2AXkKAWFhn}BXyp>xgIu70$b zGbe5*!epzW<({CG=%4{D`JhMW4#xDCBC-j3#$SQ?vIBu!!WUgwxOdMks@J_f4j!Uw zZR*F~x`|{(f!p7+Pd6HTkwZmxL}FBG=%iHLrG+%Wkk)Y9E)OJ)e%$fvY&(=~J%wjA zIYJ}lFcxcNLLja-9upH3+DO*8p-C|NPq~Gd8sIS!fHVs2%Q7O{Q#VvJepWQqLL^iu z;|GW4UZqhagJ!_w%(5UG+|WrL5fKqNEa{P(iufmv4F=W&s=#ky)MC_O*J_{Qa@{Gx z;CQ2*HShz~=K-a$lZXyeuLg;uM)IO+KV_uJK$hq8owImZ#BqOjd#$%KQ`NZ@G5+fZ zaqZd!RuNUeAbu3x}^u;L3}s4)UI}1>N0OcOmmiijC8SvCOyK2vOhK= z*jm1^D29eTMsBF?}b(5JK zpM9HXyV%hm9xcUYnN~IPo^4fJyz}}54Lr~Lcyu{bLrcam$>6I@uP_yLf-xHrqK{29 z%uhz>5$lI_A`-ah@+s&biOOc8z~+r3!sp}xecF{%alp_m5S?fLqL6z%{Sw|<1(A;V zWX&u2oY*=5Nc6J)Oo@s7X*utALlIBtv0F7hlr$>IQ}yxx^4h6t zE^Tyq&gb14MUT&>%UdsSCJm%tq`^_jOB+>m4WHjvO>zFsvz_hBC}~m*(U17(yDP0Aeu|4iKV2Ok zjwZ%qpQ(G;Ph8$@{5E$DJ-Sb|M$$=&Z%|q$sqQ`cEMqhv1eX(Nv|-a~iS8d#!xp0> za7{kM+ME_l>86EgXdEHZ98HUo3|u?UeETSHBt?C6z(x}PfhKyL&!Es5hAJhCp9u0c z3IPfkw3DCGzO(IM)k8>d&ze}bLFJJUx=WL2GEdBDnQT){anjCILm=ofy37^JZg=Fl zzi@-_bDC4Qa^4rZf^bpK+26DLoVSfHff~^t$tB{?f)`JT-11=OIo6mk{A*G1%&69M z5@GjRql3fSZ9aNQJP{q5fWViJ+x%4jYg3^jW;=~C=+mBI+jSwTyAhKVJyoU4-mj?R z8Nv9ad_ykLjGf1TQfH}8FF|Ru$e=hg$<+#uydPQhuEw&+kcj@ENC+6jxAviW&fIC0bl?^RTF0ny)wmK`eOO>S zft@M@g^KaL_Hs~%VutL zjdVkZ8Ko|)&VAT4_pH`2a!hrv-}I1X*7aiqlAu2-YCt+(9byD+IKf>oD&8)TpJV;N zxLx`laXa-)D(8>x?sxD`qbi6nW!f_w(A!CbgtyN%l{jhFxj`yNDa0|Aw46y^tvAlDLI%CftvuDaeK6&tK%L*S9i{Z)Vvsv6MrE!a2X$}eY_>RD7U(0|k%A=+M{TR!pnAr`_iO@;FKJPu>nkQ=o zqJyYx`Ig{f#Xe~g3;aXk=pB~8YbGqjl2;Z@Mn-C}ekhN`xj4EG00Da^O_rKXVu3$; zV123s*|TN$B6bPXaavh#?x+Leg(S{Q#X(_MpeYZ^+B3*=>?gDus#1>!O{({}t{Hc% zVH?L?>$e`qqYOkpV?Q8GnR2{!kxfX_Qnf%(_&Vg_IvC3>H1{VL|C{&14)}y4rWUFm zoxcWA!&V5xh`tGDmJ6(}D0cl~hSV1aUx^hS73T6XjzXlvDaL^d zL8P5G2i()7b_#^TR2ZuLcsv#cd;GXU#&-hh)CMT!FrgUQ!oPdS$O3O#TwfQM7^0NX za_FH@SZa(ZOMawMhavqDpLK?D>*S;~%kDUW(cM71S3PFhTC}@=6K{F~I#+A5EMLoG zc1YfOG9~oZHn1*(pgZ-`FTG6X@YeAyk(Ht&b7PQYuV7+KQ_H7V&hR9KO(UWvT3b8} zBxd;rDhRUapn|YSH9OdbXK;Pws55y!)NAFP21A}~uq9~L^{5WbabgqVxH7#y9Cwa^ z&Z(_*@MuSsReoNTExlz>=N;m`^Roy|i|k}|m(rw%H09qf^@qf*kx?rpaqf$71){jl zWKBYuEqp(wb2BQ#?{Sh7f;0BQ*^S1LEgBh##M~d4F4YIF1%P4$?kqx&J^Uah7|IZ( zU7b-exldbk0n_>V`w`Ditpt}`=aP&Y2vu)$b}A_}0M0vVEYb|eRJwI?2M|v<*iHLJ zyWjL-bAQln47RYE-baRv>7JD=#gQ6O48_)jM8qNLS)wMmRvQAb76{J)T)~v4$+W@8 zm`Ga2!+C&WUydC6ZR&;VR29#=He={OAMFzg$1^lK@5k*iZ<80( znKaX&`@^~@gqK~+)f%Rotx7$~8i)9^6SI>!LeB>8M{h1q8iwRSr8L+k&ZNbRgmI{j+VAt%9)94AJ5DTWu7k#;ij? zCVH@GD$1Sn{6pSraX2D^<_kU;MjZ?FAO7hf1t5r6i!J8xj;T0a9H*H>zbV`-SehnI zA2I3)7gc-kQ#!t3352Nb#9+};U&UQfQ`R+RB@5F^W*~8=X8%*1m5!AR*5`uw;^13icB2dAO!^j5zmh6zQqC}*-ax0;ebk;*(w5}{AITnr@ z1!cI0mZ*1g4DjGH3T($IHo*9rXt{=xhS#?WKe7d3QTCbs0;688n zv|mFLd=7FO-b@2T>a33)iKvIrEn2md9AZp9l7PIaxQz&7=V5EXX9TB;ta$)E@*!*e zA&4d5tsMH-SY3p{kvjLfFh2g{cMi?f9)O%5Io z!Q~H@X0v7**mJ!`-j@cLomtgabXK2Su$^}zS*m57JNpRi0d1!10FDeO2R9{TE)cPC zSgV@Hv956H4ZsmA`aiV*n^n4f0||>mQ6lIZ?%n2PSCSk048UztVjBx|^4BF`*2j#Sqd+pYVZmt6K;O16L zV1iANn`fTs^6z2zd2_34TJT8}Xy*AwoyrmzHkT{iu6;$#h`dpgk{cd=Mfmp z3dG@GvFoHB)b+ekVgNJJae5CMjM^)V0?T*s&)|cy6EvR$a4|h%FC>Lo{v;v+)t zJx=#>v6dEL>=f4|0*Xm{W5PH2CE@NXu#< zx-i2&=9q|n=8Udo4sO_sUO}LI!B2Eda;64FJ09_{Mm}qv&M=zf%PRy>t4R>m=a8Bg zM(@GOfUHgerM6ehi3c|@p(XUYo_F3c`KaeAAb(+LLH7vJhhUNvKM6ptfStCiel?5; z?Th=i*%B|9_05uS;z6I-QT{1*UTb|)Nzm0bcpZiieD%ybiC*c(&aNAaoX4 zUbFJqcw$weD8#Nvb|S^T%UQ?kubTs=Y$U9Q>yoFM*>*lh+JP+sy?ya*j%%jdcuzWEy{f$j}Y@R z`56>Bov9_qXHz;jvPLEb>e)2$u8eZTMKt&U(L|u#Bs0uW&ob6Xu`8RI%8WQlHfdw3 zO3S~WKVXbLzJcrT_I%}8<*B~lkFi|m;u|#hxBbBmEA9Y)b!$qbW2(;?ibc7%Vd6oW$36cW9?0G}2Q1kSbu*`M@&%1-TZKR{@;I zYg%nVGP2Q6x0N2i81CSgBkI$JNz4Vx<3w8^Ov70U-uYUwrNJ^RWVw|^zE8zCt?Enu z=7adz1o!kDpaA*dmabL^wJ_~9#XiU6x(A512GK>sAINmnXz$70mN@Y@P8G>0mh4gD ziHVY3mV^VXnt^IE9URRopk{>+_7yu@D0);PcNKQDQ^b}UfY>+`LvLPbRtX3zr&&;u zu_(xNN%JS@UY!GsE=gXf$ngm6-IXd@b=?q&8ldMkAR^!S8`S^;YTOn8y6yEm!umxT zSIGi#u9X{I2nZ&0*ND%T{B30j#Qj&m?xqPD+37`pX+dvL^1D5Ok_ewyco^gIv;r)T zMvsT~IU~y>@$utX)gu-I+rjdk@v$VcF)afG(1L759~AUuDoFPO-)LD!8$`+V=7*|1 zxlA77=?fD=)*lWj8&^6IlaLS6GnV+Sy*W==x|lRdFUZ}v#=Zg4x{n?$`PkuJ?R!m8 z_yGmDbd&+_M>BzZ{}*U`mm-u(To9)cecet5g6eetB93Bjr=Jo*7jG8(g(K9sY_{QF zF65L>%tD{_5vrtwDfbLz%kuFpB_Ecv?z*NCLhEFmp3llfB05ksoWzP18x2$Rm=gjlfLELNcOi%xn2z|d<@5U{oM4Aou z=XoDTxP?&jpFUKeaN}Ac_6j~}kWV&cjeo10vrUj=9@9^7v0z=0pgsk~qp+?c- z1T4rJ1GOTe-Z(?><~{VA9v&#&x+pQfhDhx%j>gO|PJdSp^X9;{F;hSjvQq+}#Ko*z zLk*}&XSpG)j=Nkb0gxw&aE~hZyUbNd3SUJH|3E*X@;?Kt(@v5Z9Cn3Rpr-SL9Y<<- z3)%?@kqM%8pFSv8DV<#lAqWtCM=9&qK9%}GiQc1V@}xxyxtIAEph}KX6A<$^udGgh z0;pj6kKDEmdq39Da+n~Jh3*3cWP>2>B-Zenwr%D-6OM*X!@p{x{@??DlrLlm1Evgh z)I(@DqSWt9JW#YpAny<3|KJBrihyjqaLr}>v{*$lk^0#j->aX?w(eFjm-Ssf*H<8h zf!^qIOjCNGj!|4zhSJ3G)uuIpI!dJ%>Y{a|Bo&iKP&26$~ zj|U^;L%N1CjeWVx>T~HGT}E)7v7%?Z$Ww8qa3~GuZLasRT%J@ge~`D@XTMShayfa4 zg|#QyAV$K6BCLZ*A3%z?ush#i3#c=H5u+q6{E@8xE9{WU-^8Z66MR9mQG!w_Q2ge% zxl+AF)ntKE$qVbdM;OTBCQ!9}#l6UkZUK#kXEmOLq=Fz%>Vq(8%Ulmz96+V2kxEg11Rj(2z0M&UrN2xX)o@L{`s zz&{^HVwY);iCL(4w^ubSj9D(y*--={QO1F+`4P+*P*YvAgLtVWX4a?}?>S6{C1OET ztwZP-Tf=!jkq7CmH$DXC0+*WZICV>ZnCmn4=5zV+ofb!rTw z?DhG9QSlR^Rei)kmbjJ*C-Bz0n7{YFKZ;nsN}lMlvn0>IdJ4QLCk!w(d;El$Yk;Br z2cU>^o329B7-oKA&6{X|Es0HJ1iN?n`DrlM@6IEyEC}KUn(29oZ7rPqG9xgHV6Ne_W4!1JSfRna@3(AF>`!i%HQ zfo<~&)^|(SMKdO6ieoAwdk;Z|WwQM%7I;wIVK11^RO-&u7(LH>%C>T%@Hsc#5~OaH z!|)$x0SUA3t?NTECk1*0#NuNrpy+?$xAcVT&f?`k z$Y&snveV$5;E$-Yo`|Sp%6eN%rSI}ptAS5OJOUx{I|~HWG~zEo#e)?N*p~8-F`3~3 z`6mJ9NRtf##5&+fWQGF6`>T9)>;>HPG{6-P14*0IW{Fw|)OfzMq~Slme1SQs=b`N9 zp|~8YB#|eMHct2xP_}N)-fje zmq>SaZbBNQOF$YaX^<4@?nV$$8l=0sySeYy^L^*snR^|EVfX{rTKip3{o-MxUQ$HF zS8nz8f(qL_x%IGh2>s$-oPo@F95VGf!Ey(v{*y%8FJVr_;oc#_+^1iruU8rZ)h&EI zod{R8`EWZSDbx#M{xXw{xDfNt$LOo{T%%!`ZfuK3^1suUHyH&OLNDLGK=U5Ld`ray z7`|troJLqr)b-k7{Cs%W^$I+Osf zA@vBkMP1LW6Zg=k0;q|ZiW%0PKQy|&_PXB6Rf3C;$&|x{o(vI;D2M^MMFI1F&v2dy z9H1qw7uyGOhdb%&l+-OhZM@zt+=D83L)o`M=AyCH#I+4#MHid>0Q1;WhhQ6rS#Q@E z2LzAfz_TTAsp^!Tzn}$woG&fzN(?swxl-8>g$mNsHZn?eQEsUCkdW88sJNnsF0&_s z|6GHR-8@SDey{0dK#}_>QSpOm0}`prto=FYk7j@jun4_8BOAsEw6U>|GP7zXjlwo7 zGXwR$K?PVqw8!Xcgc&@lMb}{WKQ{pb$2sq6yfa&0$q%F@ftI%DSayImU8Y`TsEA9c zF%mGr2Q>z{rwZ+qA@4c@_>U>pwT1c3J+cIzA2WN0Rm)?iMbcpR9H}LFsIk` z`xX3)MX09)jiM-iaJ8Y1R;>-O&sOA2mBmUrE|ZSNSEl?AG9Euj-jqvviZDXZ{~>&5Xyoqf+kuoUcjm3zrPO_!X~!^dD>$_fA*cfoldD9Kks&4wn%g>5g(Al zJfr8)@ni-j9;HIP@9RIw**yV)()$(?(hchR2KArO?5iN8)q3AuUwtK^g|C#9z=$ap zNN~WwMK?YKox=HkJ29{G=?U3Z$RGL3-@NyZ(Dx054Cb4CHyKOS|NQ<<#x;fp;2C=9 zv>J8t;Z}{pP$Ut!yOipJyd~gGiuCqp(+%K=2T+Tpi7rRKz(#_n zMBN-OPKj2$1S&^1*3gP^N<|pQU({FO(Zy&zlA(4$_BRbE%ho1zfQePJ2j0AMkUnr+ zlDGd}81#Ea4+a^vLjsfTZaXq_pJjwttS>`fFomeyhAih4W$D1E#{Y(d?dNT(cvNJ+ zgrFpgoBNxCJ-}`Xrj~LxfTk*RKwbJlR)>izlIF3`-P+Ow(vnwulxunb!Q)Qyoyp~o zmL0OL%RfQ_175sBgVt4R(gurdzEBAE>eGi~V##J`fhn~2q6{#83HjZtK-AQB({+tb z^aU!KP-lO2ZR_eL9%bYI9(r`&0&xQsPrxfxF^b`<%g=|Nq}VqfhG_0?E^5)`>%X373hdBX09nd2>%Y`57ZZbfNJeS1IA!%Lvqew7r@*S)GO6% z{-b%dX_ObZ`g%lWxdjB%Sy!>=2GFDpxZ_jM==u2@gI3i~yxKclziGAWEJ{iR##(@obWj>a)AfC1vUkqD53*m!s4kSSnoBM<5lfM!89JNd53ww6-uE|leKbS8vxD% zq|bznV!-HfiXJsvgcv;2H*lA&7PEEb&*jrJ(jkfnG0iE7rvVa(Sh5OF{yxzhYK88- z5iC-ToKfAKH_!95;7Z)jjd$*5yHzcfl0PA?3TO0wvz)TT-B0YsUz>S9eH-F@amm)> zf_}yX2B{gAVVFfb)HLxYv}1)Hu>ovm1E)!TE9H-o-LN&*qx$p2%0ElI^flnT zPx`q8s273qX0@|>tlcEOM1JV1LUt}RFREdG>ONG2po=%;r&`e!C6)J?_i_OV1 zrZ#z56|U5b<-3do9Ig0B$HviNv%d{gez7Ie7A<+3pqPSmI$fHhTkX?J=WFmI_hKq| z5J4@Z-W=nF0uC(K{@q(mxrzF}_d6E*C{zf`594{>KAhSPPfmlr-@5*HxTmey+TU?A zmj3$!|2tRs=LZLhC>%Ds(L6=x30;?~I`oZHM>!k*yf{P^QP`jZ6(=3WLUIAwdOxJ_ zn@!A{p86V9h^FmWe9f8rLqU)7=YUWL)Lcv1S zWM!+}`R6l5hGRx0$tG7AWK+L^E_8K;5;L7cT~ZlAE{$(05Cz}phh{V@i~7fR>cmLw zx|YS;udVy*<1z5e(pAFfZI}Pt94C~D$yqCf31yc1hLS0`#L1!rWW0eZKm#@|g@es> ztw|9?+#_b*G}(s$0d^pvmBpVdP^I4itwXhyCWp_EVSVuF{@je<2~DgRJkQT0ArQFdkKE}(r_Q5=J#8Uv;-IhXW@pO8XF55r@sfqj)X z)=LCr6z#c?f4W_0TPwst`Jwt3I0_I8ep__zS0Ky2k^Rqy8ZSZk6Ei#vxIOw$yha zkv|I6f3H>H22mzmU=CV;qzL+W=Y!$`W5MHqpZDkorX{J6ZaFj{sR+ZWD&YsmJk3#2 zP|LhT#<5g_#o5=zG7A|&o98niRevf)uJL+(`+ti&aE38`32mE(fqeHF)Nn|O-u8Q{$^c*~O|EfOE5`Pp!OP#7 z;lJN~l`tig5lWhld*)%oAC+RiObQj!lP*B;HOTzMh7n1%i43ab|M#nu(T)UFhyXoWW_|V%Vqk|)9Y`jn^Y4WC?@bH!Qy(q`kzOkZgTSNO z-=d)V<s?_i zHa};8^T(+56jwR;&&mJiaK%Y3f;_~<=T8FsvXtbry>8A0h{iI6{iWuD?Tgw@MKD88m2@ zj(*6t_STB3oSoAz~U%2of`F@Z;*z~_572PK(4cGrR(^Jp zdyVSkj^jHmW3V}t32C_nm(>k*A+wySfF#{0{U;ScXrjKM5V4ttGwL->`Y0qc?<{*5 z_=d*jsu}>mX&1C8)qvSG1N^w~WX-d0gIm+ zV1ps1O1lEu|IJOO5g41vrG_h3zRe8(UBHK^22IJdA3X9dD2B_RJ^O&bjg`+ea0=MQ zu9Wl&r~o465qD6kZ0{i5t)39+lVxLtw+`fzR{s_1fD=he1QYw=`%ac!^LSdC#&5Ev zq_a<0{v;vy!9qz$i_{E%%XWbO0_k=TKm{1r%Q)$6k&uhaJajjY?r%@zJ=T-VJZqC0 zH9rw@oj;Ywa^OQiBiWtF%tMCofsq}JwU)*qu#_>YF(1x)5g!0G?3=EKVlGhQGz$pm zkQ_!Ae$k{`K_e!x;psK)OEg5rWjd8=b{ypA+HTZu^|sikwV9hf2jy)Q7-u&2NqO)O zn@HKCV|}ZY<{Krd0rb>+i4OJU)p>fr4+`mGTUqx%bRnq&3HF{Ru*t3CX`f!Z(zr&4uM@+o5is z3me9`CbnSqHn)){KS9Kbm0@4tD^F3cvnHi}t^eM|JvF!sjbQ}|A00hErDC8y30#{6 zj*ZKt5aKX_i+K-Q-o+1(cY*5cjCVI@3lmw!c@qrp9;$xZ^R}$TYlmC0sOm0(5n3887@q0vAYAxvFq>J{*F|8Gq5t-!ooc(~u- zqL=5{`r5=(hy^hSPDz-MIWZ56uN@cgP2>w6_r#%?W%cMRSvk4|Ennz|FCF?F1!TWmFr@7=o; zI|ooI(zXE9%P!s1BneW$bzjE9gslGMk6{5cAV!hGU@0LkbZC@H_9O@`5{>iw`dmd6 z%!_4gqr2W7b>9U(HcXVF2lh}UwPkDKg@5}ukkXvzoYY%MhyZ6*?~7U}XDG2XB}2h& z80s~>*^nc=i1##?G#g0O+*aIl1V%$AlEMnhOH4!OIZ4I&Q7;pj7p;^%c>qKC|}XdaA7W6bR(#tOEIbLI1oH@@vbL%CXF0Bmj9A(99euM?esDBHKm-vCOw}E;0&pRK*`>wP(x1uu(4RvX|Tk|UV@>l z4E@Bvvr*q^Suz`JT6mSU7To3AmF_rR`cMBRATro`XmN56e7yuHbe*c7u2v|{J{NlR zyFaPUmv#zt(Dk!hb@6cZUZ9QN1%9Kn4kTW3$?U(NRs}DdIBpdTT;^Rq{Vrr>K?GZw<>mKfJ%$zMi7B3wGLH|HtYZGuPDxgi= z{YmClydChQG^kwhnZ!*3XCs+P1`uBf+$KwkGkna2# zl8z_QRf)b=sT$ZbS%=a&N}9QW6)chprx4y!P?C00ND^?_>|zHa3l4Y1pR3(v=k_x;^;QF>od7Xkmwy+%!>dU5@7`P67FMAOW36F{@2P%LoT;8a91Y{B5ab7ZoN-kNL ze+TeS-)01}+8r%-3OJU;`obF*Mg09>dxQ*TcWkG(Xwof!;md-Wh9SIm$-KjPwld<2 zl-hR|rQzPmRMP`__l0;}XH82mCt?KgF!IM) z>t1Z6Wl6OucX$!ge`fz=UxYw(89|mhDgcf-Ij^O2|DKpwTwivG0l*>lKwbsjaQWUi zPuAci?*5uitB}Ajz((?{YFJiyV>UiS-xT%77h3~gwsO*zhwt-vyaVyKS7z+>P#JSiv<(6*;dxCH-G z&wwZKjsRTB;^9(<<@DK>``<@X@XA+_Lj2|s8&}^x)4E=DW>ZSdx~xDwMhyOesA}5i zq%Z5;#dXTm!t@hmfvOcbzVUNG^H%eFFz{+}n*NsU^Cp1j-;^loYf(Iij|N|3PxKFuKoK&tk5akAc@O|9V!{B#RB zjk{2Q$5)ay=nQHAr1JLc&lLh}$6%n=@$S8P|92y=U!>BzZCJxV@ zHrVIp*T7-|y^oH?_; zd)P51M)f+z*RHdRuC3!cJ@jurwvYDk>fqq2cMZS{uV=SyDgWa2{b5RuNzE{-?RppJ zdB$DTQn++HDr>chlyf7hZGQI*G+yfTs=m_3Q8N^9FY$z(8rQk%D`MYC^Su6Vx&|-1 z1)PQ;|JCYefrBSkf(~&6#VEVKb0g;fclsRU9_{W3VhZY$xyUwPN=fhjW$yzi?QNMb z)X5q;d}gejbrA6W4HBExS37Uu4AgX^wrTt`irg94>beCi0V`t*(4NLQZ4P~3NCnwD z&~(3hM*bXFRQ8s2QQ~0TJ;x1w)C?x^Ew(OTh&c_=lPt#=RM3-4LDf^GK*%0x1q_;x zp1$caG8`ZR&8XoFD4t{F)c?%$RV-lZ;3?2hJb%_q+3dWKYP?*1kh)vbF{jb)?bq%K z+xwlLJ{?5rX)?%v*JP;ZdT97$3bY(Xg93M{x)U1+DO@gTXARe(o2fJ;9wP&SJ1=Z~ zUAE$IJuY;5=DjxfOU-8wK6n+?EAL~|s9!XU?>BCE^WNr(?#&_(o=Xbt*PrNlbibS= zA@KBxJ*CW#Tu))IjZ@S5mF8iZSc%HlHEvNQr&O7@yOX3p(^^_LJ-D~I67jy|xAbo3 zhF-;d!bN$y_w6r*N%$oG8$!bSV?^GQ*h=f^jo;RT%@<|Rj)NZa;b7E}C_&$PvH9ZW z)q~Sd-;#cIkNYIt-N}x~3#Lio@3yL$1gMV7sVXkPRTS4BCrPwGPlMXO#Tz&cA#hMHMkf*Ae}4AsgJW|S z#KBr)Zu)L(S>lh&!8FwKBE=hE)qB9wAe;o?kmqM#8zU2*h%G`(4JgH4eKf#5_5-l> zO4J3-j^e`X$Lt869{MYV*HDQ&ClHjC#`1z@F58(X)pa@K957ra|BTh;HZK5PPzD4U zZF<1`Z=nX-`kj2oum^ujf*uIUdp)&_h!yRO41-}EfXwL}_#C;N%QKX?^ZbelztgliK;V5 zQg=X3;b!TL!a>$zbP?^|Ksy$8ASmReweD=@iWYsbIMK*a``bYylcD(pX;9wfL4+_lGS}lX()h zQNPqwL<(O2=oXME>~mE_RKwTZhESpZ`HbHGU!PGI8t~Guyx^p>mj9@++-LzTRde=4 zHvSfrl;{BeW!x<&Ea}n4*D#*@nC^XTVYzO!Zpa_%DbJ^Sq25 zLMG@3V16swHr6<)u@G!2t|A!4_UH6y(mZjcdxI4@ilX%4^RJQ?j{ zd&ze^lr2zW`3+mgG@WuG@nnxkwBGfzbU`$w(-#NKTB+DnDx@9>EI&T#7T#ZS``)2v z@5iGlx|)bO87Wbhs+&t|kq(+HEsLwaaoCJZ5)_TM(Zg4sr8A-~g@Wos;e#nJm)^Yj z+^TC}oe9Osx(S-1qPg0U+g9B&Vl1Se!{`U1t@RI6sQrG%Hpb7>3c7w7uybH;B=wu) ze@b=$N9uS*v#f#vW(^=FFKBP=0Y}oV5+{pTUlYh1#C4L78lFD4ZB)hcOyJ(=xeN7;`XL(w~W!!>`o=m zuM@eNSnaN>n0FI98fLBu*~=5|x%z6%=jgU;CUTl}S)-n0O&t02+5M2GcOa_ZH&gTE zvTb3e8;Ggk(}WC&m-a>qSxo5A{y<6)X=fApB0;Xj@XjO0NhNil6W83h3A$>#e_VVJJY~}|~e#dxu6^G$9+(|=mA|rZk>HWS$ z&#A8Elji<->)jPJ!GZCwGHZmlj{So7m5Kq#uTq4YOSj^_A(})bu_f5B##sy*$)z1Z zhF|-!Kg`zCQY0mP@mdn}=}A%bWFSr;^^FCxs>&QD`4SfD<0?OY{ErNW$ z{$Hv_u#h(#aIzD2i`U0hE#*-A0N6iei^+|E0U#(@ft(IJZQW#Dq2#AM(0W-GYu#Df ze{WB?R4i>e4kdluNfII&rL%lDyaD8!%}z^z6J$ntjri?kLdWq_^VNDvOR48Qi#L<; z#_!ze_=q;MmB8u91utvuJr4x@rx?8W{D&`YAj3L4uk5r`1a0g{Z`_t@$Iq&Gvsj5$ z1YxuH8;5Wns)pnmYFFQ2jvZ{%d3RYU0FB+{rnJu6S=^vzD~*Ke89IKocTKt$5&E9@ zVhOG*+NFp(mV4F5`+gq@y=?0`)7-W-c^CC>HoUySo0@GFBye!EQ_(AT&uzs3={liM zTc|6)@=3UqJGm4MZ%>%@B5N^>a&z36%6LM0Bq>QyT5Cfq=gCB0_(M)rBNI+a%dsC! z>u3ymIrFKc;{j)|87w>{Kq?pvTI0sYz|s{k(y$nBa&s&# zLdG=om23+0jPr%kRm$^z#){(r*A60yGSt-|!u{{z+yJrC$9-UP+E!@<+LK!&S3m@> zgbur16>0Aj+Lj3_fj|3|vI5G8ev4n$F`(GfVg!YfhEj5GO;;D5Z4Of`*o+Ct!}{Ew zS2hn_yuP`cAhqr{EiSf5VdB1EIXk1-%e|^B+R4AGpnLAPUlho3dlqzHHO%$fdQO&i ztc?Em8sJ%6y4GPj?~Z;ScPu~LE>&8v`y@7ee*g8Qvu3IEteN?*@(U>f8okzjXCE_+ zd8clE9i7QfMJ3V&-#1QqK8@FWao)GOA+ED6$B{8hbxKZ|xp6+<+L-!!K|O=YySG@> zz2gDDqHWu<7vo}Oz%R$kEnqTz#P5Pyrmjb1!|Octz5b!(VC7K^*6P`C=}g&%UR8<5 zzNz-@=UC$#;@Y!~EYI7yfKu~GyJxmzmsyszVkv4&jz=>VUbxrrzqFZn4wTY;@jFQ_ z)|Z};l&9;$euk0fS6a4%Kvb6>y;CTGbI^1Ij<}ppb7HvhIn}{lqBq8sYN2g1JnoGD zXpP8VsW98qKJv*K6iY*Q~9kKqwFvxdpX4PdW33DpjhK9ov+&cUa7@{C~BrTL(KgG7+X<`wPT zPlZ&APm=nXtugC3rDnS@j}a~>gQ3eSR5*ar$OB9l8TD{fw=)*u1n_x(+-uy1ahO+S z%Hn(L>-DtxmlBxz+};;r`69VMdJb=_uRkX29_~COC=_NT%Vf-{X-|J>d~zM$u*4(j zJZAJ8=X`O|x8|foSH%8%ntiFRV{u@M;6=!8ah)V0tK0HJr3=oYqK+=!eW-^W*nEeE zGfO;e)Hw}n?Www|3NG{u_G-n<^;1t#ZtxAVRJxS3NIM^- zaXS!6<1%;8&)vLom}T+l&OWx=F8YPXYJlhL>gHem#L4hncdkI5nN84aFzH#?I&;_M zun$2CTA~%DXxHiHTq`cm{m^_A3zd8eAD*BEgZBX&`j=_%(gvwyhL_iO!{QKu68)9l z3*W2P1Frqmt9@S{ciiLomQ!@o(+}`nITUhZ(?72}7RW0~El1euu!j|%a#Z{!XxfJd zyh-$1npQqX#fu0bBEpc9!F~LIjt=XLiI~vS7K%-uObaU+VE@?Td^be3P%RUTQ=EMX zc%2!f;^^SddF>X3OnyeefYE5@;&*@5Z;OWID*e-hwhLVuCh>mzdwr)Mr$4WbB*P$M@A5C^9i`>9ug9gJgsUv09zvJ z>+56DtI24-blw_)?^Bosx(AQO*lSeu*X4%PN^7*f>~1|Pixsc(zG_~HMedm@%iDjR z#2p;@Cf6aEQz*_E_&M$;>n_z$y|nnD>+$N#G(PVvhEqKHq2wLAD)V8xlO@$KaTXA593(c9k0xmtYVq8rAogIl?#X3_=53B_`uGBz>NA3g$7l@* zY^QK>zC5IkB=`LZj@KR$3y zrL0j*bpSI_j`vleQ!@9Guxtd3K>UhJ))eL)Me=U0k0PC<#<D#2A?fj9f2|gG^XFH7nPo#$0^XrAKh*)msQVf&;k>kKW&`axv!?qh!vayj&fUkKidp#AEgs&2 zH8OjCfsZGax0gP>uJR;Gn{N((V^KjNN{$x$?!J+8+H6;w9ZMaxq&u6?3|(GXWBtrL z%&*#K)VocL^|TYY;W%ybvUYoC_|tERq~_PGI3*FcW4wu@Y$O*0^@w^nzDC*_!w6BsorBEL+NF=*1#$8b|7*+{d=tGKPZ@2NWMAC49KKd~byZ7}(> zPR~+^hrH8~E%^fW612lDKA&B#x660<&Y=$HphNxqR~#=*1c?BGKB;~VN)<#PV&Kcl zFu{{OpG6e%+U=n$Qn*gmOf?o z2)Sz#b3432ZjF|V_02q@?nwmU;iEQepJe7nLz!7Lb&rs(EhF+boYE4oK+o9{dk>$z zZd++7_kpFH6M?H_{3}S&Fg3=6U8gIUySQ2i;?$IxH!Q8_{Q*gdTgkm9Bmm`mT>SWM zW|uXdG!hh^FZZj6MvJ-oZq_Ut1fEcqSBEEfZgYa|C}*pTss$=iJ6n(ZyEAI{Z~IjSqx~3tY@c+grMPP42Lo(i{psCBL;d40ft)q@GIzWH9xGH@!Eo z=4-LXfq&;bzCEGdQz47V>la!#!uVucHLm^h3hQuJ0Do^(Z!;Auc3}WvckN4c1G@WZ zS;3&!AximIG`%|L6V)EC*x~iQz2Hz0R5b2-b!xV^TzSsjRgRh4`)Ll|2W3q@N zaYRwt*H?T2)EzYh$y-ELNs;rx2fwb5tfR=pO!w~gim;Mu; zzUjZ;#0QJ%J#|zjmU@>XK?MPTyEVSn_Un+gT(kpWF-_`zjEGPnseP#hqnXq6!^IwZ z|C|*5j%JT5xLnQ$|EepxjJFr{1s3tkPO*NLR~hb7&_c`1cNB2UV8p+s|foYKzXae0r@K?+h(>>9ak zy!rGK9}dazY|C8Q9icIy8?)bE2kIQ1nYHA3A}H~1bp#V-lR`WAPacG>Z{uQF6-x)% z;^K>`iACF0G*#_~5$392hgGsuX=@W{3+lc^Ph$DlUGAj36wl5t#d%;m8_+tT(o>US z4>KGd8|Zzq71}}nFd7rBqox+yMLfw$I-cw2bW@Zj^?cG{R^UhQidk{jmB--yvl|>V zuA*OcKJ}=Ca1Bleeg`dkA9%*Lvo(%h{EVh=TwlIH0hV+@6Y+Zzc~qN;e9F?t6fgdU zcM&o_baPOUMX5{m3O)au!tID*zj=F?urVivpd%{7G41IGa3xFI?9=HlD~^b;D6P`I z&ZNSyf3vUmI*&n(4JY4bgwN|c-6fq8C5T-;Uv+#GD>McDUns;`?0Y0}^ftUl=9`K_WvZH>**{ELA&x=MI*cuJNRDd#P#mMiL0ZdZ2pvBwX*9lM&4E9*mKo)P# zbF}1QH(#ml3Uw7b+VVb)&VE~KnG`azu5h-cWXW*BOoA|HutFtkrc+dsUsS19qP=s~ z=&!aus6 z5>~piMwu9aNHI-@T1vW_g~o*Vld3!`QJ7--^jV%rTr}IoVrQ?1k>_LE{-tYXUy4uM zybnURDO3kyaM%nOI_g4NO)zTn&|%?GAO>M)4u~>TeW=ll;%X=aoMe3if|S$)3~ebc zR)`^)I?myMI~wpSijE0cg_;_AR4xU5!TzF9xxZ?Bj!aTN0vo^fiktI_8d zqRF_?bxP2v>de0aqH zPg;!8PZgh%&RgwP&!*Sv`Chu-Tz|F^Rx!IT?U4lbBp}?lvO?=x5wao-*TY3$sjq}IazxnaW8+T2tV3!_O3agpw_qjOVl=fZ z!CB5AS!cjg?58zHsdGoaWFKh743caSEM9Fhth{lzoU)UDSJHk?_n3RI%b&~0KAnyZ z2J!QUp#V;|_;z!4bN>Kork$IFqQHw=Oz~;F3vA~61%zUqo#!}7{+|Fk^TbK#K$ny#LrJd}TIK?mF@ReDr)LUGx}VL%Fo zuw?jj1wM()idv;LMuH(nzei&6wFda6YE~|=-<@hQ2*EdJqszyeg|nG&3K`B8r?=)z zM>tM~lrW?V`m{?j{t^!@602Gz@KPn>?uua(s7SERntdm`ygOA#YkK9C2SK-CHDTi! zYz|0QZ|a;_Ij4|KbKpEjza5J){h6ziL8`}3;^}Wu($!{`a_wWcmS`&Mh7hpr{1~%j zGgOGvd6l1qbad}wf9ky;XTo5*!15Z!#I;~Aa}dzvPyqMcZE2&mp1Aej@e|PUd4+<{Put(`|=bOWi-y`SgHS+*{bT+GQ(75L<;l=eZ zqwCQk&#{WdiavJS+(l))iNBPnM}VdWzCV-heQ5A!+Bh+41CJZzi0i)D-c@p)^y(i( z19+d4J|@LYB{!D4J($S8azQff+ECW&+Q@i{S!dTTJ|R=HxqZ8zbLe(An>CC5^e5!I zAAFv6EtAq$310wZ8y?Izm#}aoeCep5qAxSt>k``N<^f$KT2&eaHg)(FvX^%?V^|Hv zoiyPF<|5`4kT5+1OD#SI1w8?~{sJaSMHh@Fuju1Q0zRMz)52|!73Ma{1Bu-BVGLNQ z_2GD}*BBfC)Qj&LE2q@Uw*K$8MqU;Q-x&5jK8C0IC*_8{0!==)_yRxUk0R3QKDaY_cbw~bziON6 z(n=~UT8QV6Bqj&NLVq%ca^}Qt`P0?9r5@Qt`SnOrv`LD+nn(yNTr(r%v|PHtCm?Iw77MJbt#!!|M0qVFv29!|1Iq(+^*l)Y_q-7*8vUnmWx# zn4@)TW0b#coBgoxQ=^W6Q~sXh_q55ISxO5PZPN=#Oq33G%iC?8oX0ya-t158zCt_k zi0@1-5mRV7vXzJzb37U6d=Z}OdbP;IQfN2bv__mJ9lG0Tce6`km2$*(tnx0vr8M0F zKayT!kJ)YAkFNJX@{>#Z=u{Mgvak++6O5S)L)rGYW%cJrPv>HC#C-|A*txEyYj6~W zD4o9@UdC|ne!?fNWg>x^QK-=9bTH2^lgJ`W+;s%c2F7CV9347+Cral&FcQ`-59QxU z#QwZ?=>P)A;rux?yHX8Q&~!RpvyPW&1%dk7`0&toJ1Ft@ld_Q|z-G2znSQp)oH*E{ z`?4aa4Z!7X{tNoI`^zpG;6vKmdeJxf%zREiDcp(;w*W>n`S=h$maSXtx4FPvLk=xN!Xlw{TjBBtzX-s^PxJGNb#E^?h zhw4fNpRHmFYoTll7Ud;Ad**ht{G4e|7pVb8JaP|b5$o8!Dqx~-zs}i&jTP${v8=@V z#Q#A1vc)538C_}Wt9EBID15jgJeDr6w=yu8ay^inCG)!@kpIUspmWkjUM zUthT1ju({g82^@t@wc7A*Y{yCuJ7;IBZ{u2iHD84gllt{i=D4jzy37O>W7S#bxpFD ze=ZOWuwH7=<$&xyk}=0xc*pDUl)j2IC{b&-&A+#6adx1y;3amXlRNYGwa1qBZV&G~ z8j!~tXyjCAhrj&e1(5nEQo!pvLn%ipu+9t|o0cu)F?hI@#WTWj)p*}R^&;WIngtDF zi~gSKZI4PliVb>L?#11!%C^ClsM}pVJ|Co}8k_E{wUgo!&8K@bZOvm~)G-=Or%Knc zpb4lhdk1}D+Hh3|qRoxm iV3p(l5~(roZ87}MG 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 ge?Cn{ Date: Sun, 8 Oct 2023 23:44:39 -0400 Subject: [PATCH 244/297] Adding in workspace markdown documentation. --- .../sp_malaria/workspace_markdown_doc.md | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 extra_documentation/sp_malaria/workspace_markdown_doc.md diff --git a/extra_documentation/sp_malaria/workspace_markdown_doc.md b/extra_documentation/sp_malaria/workspace_markdown_doc.md new file mode 100644 index 000000000..0dbb46f91 --- /dev/null +++ b/extra_documentation/sp_malaria/workspace_markdown_doc.md @@ -0,0 +1,63 @@ +## LRMA Special Project: Malaria + +This is the Long Read Methods and Applications (LRMA) Special Project Workspace for short-read malaria data processing. This workspace is a rapidly-evolving prototype and is not yet ready for heavy usage. The current focus of this workspace is _P. falciparum_, but the processing steps here are generalized and can be adapted to other _Plasmodium_ species. + +## Variant Calling Pipeline + +As part of this workspace there are workflows to call variants on both single samples, and for joint calling across cohorts of samples. The + +The main variant calling pipeline has has the following high-level structure: + +![LRMA SP Malaria Variant Calling](https://github.com/broadinstitute/long-read-pipelines/raw/jts_kvg_sp_malaria/extra_documentation/sp_malaria/lrma_sr_malaria_pipeline_diagram.png) + +## Data + +### Datasets + +This workspace has been seeded with the [PF7](https://www.malariagen.net/apps/pf7/) samples, and the [crosses](https://www.malariagen.net/parasite/p-falciparum-genetic-crosses). Over time further samples will be added. + +### Data Structure + +The data processing is broken down into three levels (similar to other LRMA projects) in the following Terra data tables: +* Sample (flowcell data) +* Sample Set (sample data / single-sample calling) +* Sample Set Set (cohort data for joint calling) + +_Sample / Flowcell_ data consists of reads from a single flowcell. The sample from which these reads have been processed may or may not be represented in other flowcells. + +_Sample Set_ data consists of all data from a specific sample. This may include data from multiple flowcells. + +_Sample Set Set / Cohort_ data consists of data from multiple samples. + + +## Task List +The following is a preliminary task list for this workspace. It may or may not be up-to-date: + +| Level | Category | To-do items | Assignment | Status | +|-------|------------------------------|-----------------------------------------------------|------------|--------| +| 0 | Download and prepare data | Download Pf7 | Kiran | Done | +| | | Experimental crosses | Jonn | Done | +| | | Additional Broad datasets | Wes/Jonn | | +| 1 | Sample-level processing | Alignment with bwa-mem2 to Pf3D7 | Jonn | Done | +| | | Mark duplicates | Jonn | Done | +| | | BQSR | Jonn | Done | +| 2 | Participant-level processing | Sample merging | Kiran | Done | +| | | Call with DeepVariant | Kiran | Done | +| | | Call with HaplotypeCaller | Jonn | Done | +| | | Apply VQSR | Jonn | Done | +| | | Annotate with SnpEff | Kiran | Done | +| 3 | Cohort-level processing | Apply VQSR | Jonn | Done | +| | | Joint call DeepVariant calls with GLNexus | Kiran | Done | +| | | Joint call HaplotypeCaller calls with GenotypeGVCFs | Jonn | Done | +| | | Joint call HaplotypeCaller calls with GVS | Jonn | | +| | | Estimate MOI with DEploid | Kiran | | +| | | **Call CNVs with gCNV** | Jonn | | +| | | Convert SNV/indel calls to Hail MatrixTables | Kiran | Done | +| | | Convert SNV/indel calls to Zarr Store | Kiran | Done | +| 4 | Evaluation | Comparison to long read references | Jonn | | +| | | Compute Mendelian violation rate | Jonn | | +| | | Compare results to Pf3k/Pf7 manuscripts | Jonn | | +| 5 | Biology | Drug resistance markers | Kiran | Done | +| | | HRP2/HRP3 deletions | Jonn | | +| | | Population genetics | Jonn | | +| | | **Mapping (lat/long)** | Jonn / Bridget | In Progress | From 4f9db3ba828087df25cbcb36a275184a2495e14b Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 8 Oct 2023 23:50:08 -0400 Subject: [PATCH 245/297] Updated workspace markdown doc. --- .../sp_malaria/workspace_markdown_doc.md | 49 +++++-------------- 1 file changed, 11 insertions(+), 38 deletions(-) diff --git a/extra_documentation/sp_malaria/workspace_markdown_doc.md b/extra_documentation/sp_malaria/workspace_markdown_doc.md index 0dbb46f91..cd447058f 100644 --- a/extra_documentation/sp_malaria/workspace_markdown_doc.md +++ b/extra_documentation/sp_malaria/workspace_markdown_doc.md @@ -1,20 +1,25 @@ -## LRMA Special Project: Malaria +# _P. falciparum_ Short Read Whole Genome Workspace +This is the workspace for short read whole genome variant discovery and analysis in _Plasmodium falciparum_. This workspace can call variants in a single-sample, joint call cohorts of samples, and perform various tertiary analyses (e.g. drug resistance screening, rapid diagnostic test evasion screening, etc.). -This is the Long Read Methods and Applications (LRMA) Special Project Workspace for short-read malaria data processing. This workspace is a rapidly-evolving prototype and is not yet ready for heavy usage. The current focus of this workspace is _P. falciparum_, but the processing steps here are generalized and can be adapted to other _Plasmodium_ species. +While the current focus of this workspace is _P. falciparum_, but the processing steps here are generalized and can be adapted to other _Plasmodium_ species. ## Variant Calling Pipeline -As part of this workspace there are workflows to call variants on both single samples, and for joint calling across cohorts of samples. The +As part of this workspace there are workflows to call variants on both single samples, and for joint calling across cohorts of samples. The main variant calling pipeline has has the following high-level structure: -![LRMA SP Malaria Variant Calling](https://github.com/broadinstitute/long-read-pipelines/raw/jts_kvg_sp_malaria/extra_documentation/sp_malaria/lrma_sr_malaria_pipeline_diagram.png) +![LRMA SP Malaria Variant Calling](https://github.com/broadinstitute/long-read-pipelines/raw/jts_kvg_sp_malaria/extra_documentation/sp_malaria/lrma_sr_malaria_pipeline_diagram_high_level.png) ## Data ### Datasets -This workspace has been seeded with the [PF7](https://www.malariagen.net/apps/pf7/) samples, and the [crosses](https://www.malariagen.net/parasite/p-falciparum-genetic-crosses). Over time further samples will be added. +The following datasets are currently in this workspace: +- [PF7](https://www.malariagen.net/apps/pf7/) +- The MalariaGEN [crosses](https://www.malariagen.net/parasite/p-falciparum-genetic-crosses) +- 2022 data collected in Senegal +- 2019 data collected in Senegal ### Data Structure @@ -25,39 +30,7 @@ The data processing is broken down into three levels (similar to other LRMA proj _Sample / Flowcell_ data consists of reads from a single flowcell. The sample from which these reads have been processed may or may not be represented in other flowcells. -_Sample Set_ data consists of all data from a specific sample. This may include data from multiple flowcells. +_Sample Set_ data consists of all data from a specific sample. This may include data from multiple flowcells that belong to the same "participant" (i.e. same strain / clone). _Sample Set Set / Cohort_ data consists of data from multiple samples. - -## Task List -The following is a preliminary task list for this workspace. It may or may not be up-to-date: - -| Level | Category | To-do items | Assignment | Status | -|-------|------------------------------|-----------------------------------------------------|------------|--------| -| 0 | Download and prepare data | Download Pf7 | Kiran | Done | -| | | Experimental crosses | Jonn | Done | -| | | Additional Broad datasets | Wes/Jonn | | -| 1 | Sample-level processing | Alignment with bwa-mem2 to Pf3D7 | Jonn | Done | -| | | Mark duplicates | Jonn | Done | -| | | BQSR | Jonn | Done | -| 2 | Participant-level processing | Sample merging | Kiran | Done | -| | | Call with DeepVariant | Kiran | Done | -| | | Call with HaplotypeCaller | Jonn | Done | -| | | Apply VQSR | Jonn | Done | -| | | Annotate with SnpEff | Kiran | Done | -| 3 | Cohort-level processing | Apply VQSR | Jonn | Done | -| | | Joint call DeepVariant calls with GLNexus | Kiran | Done | -| | | Joint call HaplotypeCaller calls with GenotypeGVCFs | Jonn | Done | -| | | Joint call HaplotypeCaller calls with GVS | Jonn | | -| | | Estimate MOI with DEploid | Kiran | | -| | | **Call CNVs with gCNV** | Jonn | | -| | | Convert SNV/indel calls to Hail MatrixTables | Kiran | Done | -| | | Convert SNV/indel calls to Zarr Store | Kiran | Done | -| 4 | Evaluation | Comparison to long read references | Jonn | | -| | | Compute Mendelian violation rate | Jonn | | -| | | Compare results to Pf3k/Pf7 manuscripts | Jonn | | -| 5 | Biology | Drug resistance markers | Kiran | Done | -| | | HRP2/HRP3 deletions | Jonn | | -| | | Population genetics | Jonn | | -| | | **Mapping (lat/long)** | Jonn / Bridget | In Progress | From 83f77f4c5612cbbab78361d54f266947d9a03113 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 9 Oct 2023 00:54:14 -0400 Subject: [PATCH 246/297] Updated dockstore; Added `PfalciparumDrugResistanceSummary.wdl` - Added `PfalciparumDrugResistanceSummary.wdl` - Added `PfalciparumDrugResistanceSummary.wdl` and `ExtractRegionsFromBam.wdl` to `.dockstore.yml` - parameter_meta change in ExtractRegionsFromBam.wdl --- .dockstore.yml | 8 + wdl/ExtractRegionsFromBam.wdl | 1 + wdl/PfalciparumDrugResistanceSummary.wdl | 267 +++++++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 wdl/PfalciparumDrugResistanceSummary.wdl diff --git a/.dockstore.yml b/.dockstore.yml index ca36b30cc..e14323241 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -163,4 +163,12 @@ workflows: - name: ProcessMalariaBarcodesDemo subclass: wdl primaryDescriptorPath: /wdl/ProcessMalariaBarcodesDemo.wdl + testParameterFiles: +- name: ExtractRegionsFromBam + subclass: wdl + primaryDescriptorPath: /wdl/ExtractRegionsFromBam.wdl + testParameterFiles: +- name: PfalciparumDrugResistanceSummary + subclass: wdl + primaryDescriptorPath: /wdl/PfalciparumDrugResistanceSummary.wdl testParameterFiles: \ No newline at end of file diff --git a/wdl/ExtractRegionsFromBam.wdl b/wdl/ExtractRegionsFromBam.wdl index b07b06150..181441260 100644 --- a/wdl/ExtractRegionsFromBam.wdl +++ b/wdl/ExtractRegionsFromBam.wdl @@ -24,6 +24,7 @@ workflow ExtractRegionsFromBam { regions_bed: "Bed file containing regions for which to extract reads." participant_name: "Participant (or sample) name for the given bam file." extraction_comment: "Comment to add to the end of the output filename." + gcs_out_root_dir: "Output folder into which to place the results of this workflow." } # First clean the extraction comment: diff --git a/wdl/PfalciparumDrugResistanceSummary.wdl b/wdl/PfalciparumDrugResistanceSummary.wdl new file mode 100644 index 000000000..e8565e487 --- /dev/null +++ b/wdl/PfalciparumDrugResistanceSummary.wdl @@ -0,0 +1,267 @@ +version 1.0 + +import "tasks/Finalize.wdl" as FF + +workflow PfalciparumDrugResistanceSummary { + meta { + desciption: "Create a drug resistance report based on the given raw drug resistance loci report." + } + + input { + File raw_drug_resistance_report + + String participant_name + + String gcs_out_root_dir + } + + parameter_meta { + raw_drug_resistance_report: "File containing a raw drug resistance report to use to determine drug resistance." + participant_name: "Participant (or sample) name for the given bam file." + gcs_out_root_dir: "Output folder into which to place the results of this workflow." + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/PfalciparumDrugResistanceSummary/~{participant_name}" + + call CreateDrugResistanceSummary as CreateDrugResistanceSummary { + input: + raw_drug_resistance_report = raw_drug_resistance_report, + prefix = participant_name + } + + call FF.FinalizeToFile as FinalizeDrugResistanceSummary { input: outdir = outdir, file = CreateDrugResistanceSummary.resistance_summary } + + output { + File drug_resistance_summary = FinalizeDrugResistanceSummary.gcs_path + } +} + +task CreateDrugResistanceSummary { + meta { + desciption: "Create a drug resistance report based on the given raw drug resistance loci report." + } + + input { + File raw_drug_resistance_report + String prefix + RuntimeAttr? runtime_attr_override + } + + String outfile_name = "~{prefix}.drug_resistance_summary.txt" + + Int disk_size = 1 + 4*ceil(size(raw_drug_resistance_report, "GB")) + + command <<< + python3 <>> + output { + File resistance_summary = "~{outfile_name}" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 1, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-utils:0.1.8" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} \ No newline at end of file From 52c0322c1f190613a3aed1365818f581769bcc14 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 9 Oct 2023 01:12:08 -0400 Subject: [PATCH 247/297] Fixing bug in PfalciparumDrugResistanceSummary.wdl --- wdl/PfalciparumDrugResistanceSummary.wdl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wdl/PfalciparumDrugResistanceSummary.wdl b/wdl/PfalciparumDrugResistanceSummary.wdl index e8565e487..f861ace38 100644 --- a/wdl/PfalciparumDrugResistanceSummary.wdl +++ b/wdl/PfalciparumDrugResistanceSummary.wdl @@ -221,12 +221,12 @@ task CreateDrugResistanceSummary { return DrugSensitivity.UNDETERMINED # Get the drug resistances: - chloroquine = get_chloroquine_sensitivity(dr_report_contents) - pyrimethamine = get_pyrimethamine_sensitivity(dr_report_contents) - sulfadoxine = get_sulfadoxine_sensitivity(dr_report_contents) - mefloquine = get_mefloquine_sensitivity(dr_report_contents) - artemisinin = get_artemisinin_sensitivity(dr_report_contents) - piperaquine = get_piperaquine_sensitivity(dr_report_contents) + chloroquine = get_chloroquine_sensitivity("~{raw_drug_resistance_report}") + pyrimethamine = get_pyrimethamine_sensitivity("~{raw_drug_resistance_report}") + sulfadoxine = get_sulfadoxine_sensitivity("~{raw_drug_resistance_report}") + mefloquine = get_mefloquine_sensitivity("~{raw_drug_resistance_report}") + artemisinin = get_artemisinin_sensitivity("~{raw_drug_resistance_report}") + piperaquine = get_piperaquine_sensitivity("~{raw_drug_resistance_report}") with open("~{outfile_name}", 'w') as f: f.write(f"#~{prefix} Drug Resistances:") From 27e55baa6f999e8e8a96be8f6db8c7e28b084409 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 9 Oct 2023 07:46:34 -0400 Subject: [PATCH 248/297] Fixing typo. --- wdl/PfalciparumDrugResistanceSummary.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/PfalciparumDrugResistanceSummary.wdl b/wdl/PfalciparumDrugResistanceSummary.wdl index f861ace38..110d63630 100644 --- a/wdl/PfalciparumDrugResistanceSummary.wdl +++ b/wdl/PfalciparumDrugResistanceSummary.wdl @@ -229,7 +229,7 @@ task CreateDrugResistanceSummary { piperaquine = get_piperaquine_sensitivity("~{raw_drug_resistance_report}") with open("~{outfile_name}", 'w') as f: - f.write(f"#~{prefix} Drug Resistances:") + f.write(f"#~{prefix} Drug Resistances:\n") f.write(f"Chloroquine: {chloroquine.name}\n") f.write(f"Pyrimethamine: {pyrimethamine.name}\n") f.write(f"Sulfadoxine: {sulfadoxine.name}\n") From 2fc6de2e7606a77f1576e8c43ea20d1c8884515a Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 13 Nov 2023 15:17:01 -0500 Subject: [PATCH 249/297] Adding docker file and make file for Karamoko Niare's pipeline. --- docker/sr-malaria-niare-pipeline/Dockerfile | 140 ++++++++++++++++++++ docker/sr-malaria-niare-pipeline/Makefile | 18 +++ 2 files changed, 158 insertions(+) create mode 100644 docker/sr-malaria-niare-pipeline/Dockerfile create mode 100644 docker/sr-malaria-niare-pipeline/Makefile diff --git a/docker/sr-malaria-niare-pipeline/Dockerfile b/docker/sr-malaria-niare-pipeline/Dockerfile new file mode 100644 index 000000000..10eb7650c --- /dev/null +++ b/docker/sr-malaria-niare-pipeline/Dockerfile @@ -0,0 +1,140 @@ +# Start with a good base python3 image: +FROM ubuntu:20.04 +MAINTAINER Jonn Smith + +# Make sure we don't need to interact with any package installations: +ARG DEBIAN_FRONTEND=noninteractive + +# Set the working directory to / +WORKDIR / + +######################################################################################################################## +# DEPENDENCIES + +# install gsutil +RUN apt-get --allow-releaseinfo-change update +RUN apt install -y curl git-lfs parallel +RUN curl https://sdk.cloud.google.com | bash + +# Setup crcmodc for gsutil: +RUN apt-get install -y gcc python3-dev python3-setuptools && \ + pip3 uninstall -y crcmod && \ + pip3 install --no-cache-dir -U crcmod + +# Dependencies for samtools: +RUN apt-get install -y bzip2 curl gnupg2 libc-dev ncurses-dev libcurl4-openssl-dev libssl-dev libbz2-dev liblzma-dev + +# Google cloud support for samtools: +RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list \ + && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - \ + && apt-get update -y \ + && apt-get install google-cloud-sdk -y + +######################################################################################################################## +# SOFTWARE: + +# Datamash: +RUN apt-get install -y datamash + +RUN mkdir -p /usr/local/bin /usr/local/lib /usr/local/etc + +# BWA: +RUN wget https://github.com/lh3/bwa/releases/download/v0.7.15/bwakit-0.7.15_x64-linux.tar.bz2 && \ + tar -xf bwakit-0.7.15_x64-linux.tar.bz2 && \ + cd bwa.kit && \ + mv bwa fermi2 fermi2.pl htsbox k8 ropebwt2 run-HLA run-bwamem run-gen-ref samblaster samtools seqtk trimadap typeHLA.sh typeHLA-selctg.js typeHLA.js bwa-postalt.js /usr/local/bin && \ + mkdir /usr/local/etc/bwa && \ + mv README.md doc resource-GRCh38 resource-human-HLA /usr/local/etc/bwa && \ + cd .. && \ + rm -rf bwakit-0.7.15_x64-linux.tar.bz2 bwa.kit + +# TABIX: +RUN wget https://github.com/samtools/htslib/releases/download/1.18/htslib-1.18.tar.bz2 && \ + tar -xf htslib-1.18.tar.bz2 && \ + cd htslib-1.18 && \ + ./configure && make && make install && \ + cd .. \ + rm -rf htslib-1.18 htslib-1.18.tar.bz2 + +# VCF Tools: +RUN wget https://github.com/vcftools/vcftools/releases/download/v0.1.16/vcftools-0.1.16.tar.gz && \ + tar -xf vcftools-0.1.16.tar.gz && \ + cd vcftools-0.1.16 && \ + ./configure && make && make install && \ + cd .. \ + rm -rf vcftools-0.1.16 vcftools-0.1.16.tar.gz + +# bcftools: +RUN wget https://github.com/samtools/bcftools/releases/download/1.9/bcftools-1.9.tar.bz2 && \ + tar -xf bcftools-1.9.tar.bz2 && \ + cd bcftools-1.9 && \ + ./configure && make && make install && \ + cd .. \ + rm -rf bcftools-1.9 bcftools-1.9.tar.bz2 + +# Samtools: +# Get samtools source: +RUN wget https://github.com/samtools/samtools/releases/download/1.11/samtools-1.11.tar.bz2 && \ + tar -xjf samtools-1.11.tar.bz2 && \ + cd samtools-1.11 && \ + ./configure && make install && \ + cd .. && \ + rm -rf samtools-1.11 samtools-1.11.tar.bz2 + +# gatk/4.2.2.0: +RUN wget https://github.com/broadinstitute/gatk/releases/download/4.2.2.0/gatk-4.2.2.0.zip && \ + unzip gatk-4.2.2.0.zip && \ + cd gatk-4.2.2.0 && \ + mv gatk gatk-completion.sh gatk-package-4.2.2.0-local.jar gatk-package-4.2.2.0-spark.jar /usr/local/bin && \ + mkdir -p /usr/local/etc/gatk && \ + mv GATKConfig.EXAMPLE.properties README.md gatkPythonPackageArchive.zip gatkcondaenv.yml gatkdoc scripts /usr/local/etc/gatk && \ + cd .. \ + rm -rf gatk-4.2.2.0.zip gatk-4.2.2.0 + +# zlib/1.2.11: +RUN wget https://www.zlib.net/fossils/zlib-1.2.11.tar.gz && \ + tar -xf zlib-1.2.11.tar.gz && \ + cd zlib-1.2.11 && \ + ./configure && make && make install && \ + cd .. \ + rm -rf zlib-1.2.11 zlib-1.2.11.tar.gz + +# plink/1.90: +RUN mkdir plink && \ + cd plink && \ + wget https://s3.amazonaws.com/plink1-assets/plink_linux_x86_64_20231018.zip && \ + unzip plink_linux_x86_64_20231018.zip && \ + rm plink_linux_x86_64_20231018.zip && \ + mv plink prettify /usr/local/bin && \ + mkdir /usr/local/etc/plink && \ + mv toy.ped toy.map /usr/local/etc/plink/ && \ + cd .. && \ + rm -rf plink + +# trimmomatic/0.36: +RUN wget http://www.usadellab.org/cms/uploads/supplementary/Trimmomatic/Trimmomatic-0.36.zip && \ + mkdir -p /opt && \ + mv Trimmomatic-0.36 /opt/ \ + echo -ne "#!/usr/local/env bash\n\njava -jar /opt/Trimmomatic-0.36/trimmomatic-0.36.jar $@\n\n" > /usr/local/bin/TrimmomaticPE && \ + chmod +x /usr/local/bin/TrimmomaticPE && \ + rm Trimmomatic-0.36.zip + +######################################################################################################################## +######################################################################################################################## +######################################################################################################################## + +# Might not need these: + +# gsl/2.7.1: + +# RAiSD/2.8: + +# R/4.1.0: + +# sratoolkit/2.8.2-1: +# BROKEN +#RUN wget https://github.com/ncbi/sra-tools/archive/refs/tags/2.8.2-1.tar.gz && \ +# tar -xf 2.8.2-1.tar.gz && \ +# cd sra-tools-2.8.2-1 && \ + + diff --git a/docker/sr-malaria-niare-pipeline/Makefile b/docker/sr-malaria-niare-pipeline/Makefile new file mode 100644 index 000000000..45c266872 --- /dev/null +++ b/docker/sr-malaria-niare-pipeline/Makefile @@ -0,0 +1,18 @@ +IMAGE_NAME = sr-malaria-niare-pipeline +VERSION = 0.0.1 + +TAG1 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):$(VERSION) +TAG2 = us.gcr.io/broad-dsp-lrma/$(IMAGE_NAME):latest + +all: | build push + +build: + docker build -t $(TAG1) -t $(TAG2) . + +build_no_cache: + docker build --no-cache -t $(TAG1) -t $(TAG2) . + +push: + docker push $(TAG1) + docker push $(TAG2) + From 17197981f516e3db11c3890354521917e1d9ced4 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 13 Nov 2023 21:01:58 +0000 Subject: [PATCH 250/297] Fixed bugs in `sr-malaria-niare-pipeline` dockerfile. --- docker/sr-malaria-niare-pipeline/Dockerfile | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docker/sr-malaria-niare-pipeline/Dockerfile b/docker/sr-malaria-niare-pipeline/Dockerfile index 10eb7650c..25fce4796 100644 --- a/docker/sr-malaria-niare-pipeline/Dockerfile +++ b/docker/sr-malaria-niare-pipeline/Dockerfile @@ -11,16 +11,18 @@ WORKDIR / ######################################################################################################################## # DEPENDENCIES -# install gsutil +# Install some dependencies for gsutil: RUN apt-get --allow-releaseinfo-change update RUN apt install -y curl git-lfs parallel -RUN curl https://sdk.cloud.google.com | bash # Setup crcmodc for gsutil: -RUN apt-get install -y gcc python3-dev python3-setuptools && \ +RUN apt-get install -y gcc python3 python3-pip python3-dev python3-setuptools && \ pip3 uninstall -y crcmod && \ pip3 install --no-cache-dir -U crcmod +# Install gsutil: +RUN curl https://sdk.cloud.google.com | bash + # Dependencies for samtools: RUN apt-get install -y bzip2 curl gnupg2 libc-dev ncurses-dev libcurl4-openssl-dev libssl-dev libbz2-dev liblzma-dev @@ -30,12 +32,12 @@ RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.c && apt-get update -y \ && apt-get install google-cloud-sdk -y +# Additional Dependencies: +RUN apt-get install -y curl wget datamash pkg-config zip unzip + ######################################################################################################################## # SOFTWARE: -# Datamash: -RUN apt-get install -y datamash - RUN mkdir -p /usr/local/bin /usr/local/lib /usr/local/etc # BWA: @@ -114,7 +116,8 @@ RUN mkdir plink && \ # trimmomatic/0.36: RUN wget http://www.usadellab.org/cms/uploads/supplementary/Trimmomatic/Trimmomatic-0.36.zip && \ mkdir -p /opt && \ - mv Trimmomatic-0.36 /opt/ \ + unzip Trimmomatic-0.36.zip && \ + mv Trimmomatic-0.36 /opt/ && \ echo -ne "#!/usr/local/env bash\n\njava -jar /opt/Trimmomatic-0.36/trimmomatic-0.36.jar $@\n\n" > /usr/local/bin/TrimmomaticPE && \ chmod +x /usr/local/bin/TrimmomaticPE && \ rm Trimmomatic-0.36.zip From bb4294814ef950241478c4aa3c3b9d70bb5e040a Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 17 Nov 2023 10:24:55 -0500 Subject: [PATCH 251/297] Exposed het rate parameters. --- wdl/SRWholeGenome.wdl | 8 ++++++++ wdl/tasks/HaplotypeCaller.wdl | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/wdl/SRWholeGenome.wdl b/wdl/SRWholeGenome.wdl index 518e7a3e4..97e7141bd 100644 --- a/wdl/SRWholeGenome.wdl +++ b/wdl/SRWholeGenome.wdl @@ -39,6 +39,11 @@ workflow SRWholeGenome { Int ploidy = 2 + Float heterozygosity = 0.001 + Float heterozygosity_stdev = 0.01 + Float indel_heterozygosity = 0.000125 + + Float snp_calibration_sensitivity = 0.99 Int snp_max_unlabeled_variants = 0 Array[String] snp_recalibration_annotation_values = [ "BaseQRankSum", "ExcessHet", "FS", "HAPCOMP", "HAPDOM", "HEC", "MQ", "MQRankSum", "QD", "ReadPosRankSum", "SOR", "DP" ] @@ -182,6 +187,9 @@ workflow SRWholeGenome { dbsnp_vcf = ref_map["known_sites_vcf"], ploidy = ploidy, + heterozygosity = heterozygosity, + heterozygosity_stdev = heterozygosity_stdev, + indel_heterozygosity = indel_heterozygosity, prefix = participant_name + ".haplotype_caller", diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index c4aad7b60..f1fd8554f 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -28,6 +28,10 @@ workflow CallVariantsWithHaplotypeCaller { Int ploidy = 2 + Float heterozygosity = 0.001 + Float heterozygosity_stdev = 0.01 + Float indel_heterozygosity = 0.000125 + Boolean enable_pileup_mode = false String mito_contig = "chrM" @@ -60,6 +64,9 @@ workflow CallVariantsWithHaplotypeCaller { single_interval = contig_for_small_var, contamination = 0, ploidy = ploidy, + heterozygosity = heterozygosity, + heterozygosity_stdev = heterozygosity_stdev, + indel_heterozygosity = indel_heterozygosity, use_spanning_event_genotyping = true } } @@ -140,6 +147,10 @@ task HaplotypeCaller_GATK4_VCF { Int ploidy = 2 + Float heterozygosity = 0.001 + Float heterozygosity_stdev = 0.01 + Float indel_heterozygosity = 0.000125 + Boolean make_gvcf Boolean make_bamout @@ -197,6 +208,9 @@ task HaplotypeCaller_GATK4_VCF { -O ~{output_file_name} \ -contamination ~{default=0 contamination} \ --sample-ploidy ~{ploidy} \ + --heterozygosity ~{heterozygosity} \ + --heterozygosity-stdev ~{heterozygosity_stdev} \ + --indel-heterozygosity ~{indel_heterozygosity} \ --linked-de-bruijn-graph \ ~{true="--pileup-detection --pileup-detection-enable-indel-pileup-calling" false="" enable_pileup_mode} \ --annotate-with-num-discovered-alleles \ From d9a44b967532b672e87cc75259bb7b30fcfaa7ca Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 27 Nov 2023 12:51:45 -0500 Subject: [PATCH 252/297] Adding debug info to BenchmarkVCFs.wdl --- wdl/BenchmarkVCFs.wdl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index 786601738..740e95df6 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -589,6 +589,11 @@ task VcfEval { --decompose -t rtg_ref \ ~{"--threads "+threads} -o output_dir + # Debugging: + echo "DEBUG Directory Listing:" + ls * + ls */* + for f in output_dir/*; do mv $f ~{outputPre}_"$(basename "$f")"; done From e32dad7cdd394aa8c146876c47a4f23d27214cd1 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 27 Nov 2023 12:55:19 -0500 Subject: [PATCH 253/297] Commenting out debugging code. --- wdl/BenchmarkVCFs.wdl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index 740e95df6..a02f1e208 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -589,10 +589,10 @@ task VcfEval { --decompose -t rtg_ref \ ~{"--threads "+threads} -o output_dir - # Debugging: - echo "DEBUG Directory Listing:" - ls * - ls */* +# # Debugging: +# echo "DEBUG Directory Listing:" +# ls * +# ls */* for f in output_dir/*; do mv $f ~{outputPre}_"$(basename "$f")"; From 7c359bf80bbdeba0896f8c08374f25340788bf39 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 27 Nov 2023 15:07:35 -0500 Subject: [PATCH 254/297] Removed debugging code. --- wdl/BenchmarkVCFs.wdl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/wdl/BenchmarkVCFs.wdl b/wdl/BenchmarkVCFs.wdl index a02f1e208..786601738 100644 --- a/wdl/BenchmarkVCFs.wdl +++ b/wdl/BenchmarkVCFs.wdl @@ -589,11 +589,6 @@ task VcfEval { --decompose -t rtg_ref \ ~{"--threads "+threads} -o output_dir -# # Debugging: -# echo "DEBUG Directory Listing:" -# ls * -# ls */* - for f in output_dir/*; do mv $f ~{outputPre}_"$(basename "$f")"; done From c39eb091be2d661fe9373bcfd2d013c8ea394c37 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 30 Nov 2023 14:24:47 -0500 Subject: [PATCH 255/297] Added in WDLs to reproduce Niare et al. GATK4 pipeline. --- ...tCallGVCFsWithGenomicsDB_Pf_Niare_VETS.wdl | 293 ++++++ ...tCallGVCFsWithGenomicsDB_Pf_Niare_VQSR.wdl | 189 ++++ wdl/SRWholeGenome_Pf_Niare_VETS.wdl | 267 +++++ wdl/SRWholeGenome_Pf_Niare_their_VQSR.wdl | 216 +++++ wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 912 ++++++++++++++++++ wdl/tasks/SRJointGenotyping.wdl | 6 +- wdl/tasks/Utils.wdl | 65 ++ 7 files changed, 1946 insertions(+), 2 deletions(-) create mode 100644 wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VETS.wdl create mode 100644 wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VQSR.wdl create mode 100644 wdl/SRWholeGenome_Pf_Niare_VETS.wdl create mode 100644 wdl/SRWholeGenome_Pf_Niare_their_VQSR.wdl create mode 100644 wdl/tasks/Pf_Niare_HaplotypeCaller.wdl diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VETS.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VETS.wdl new file mode 100644 index 000000000..baaaea841 --- /dev/null +++ b/wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VETS.wdl @@ -0,0 +1,293 @@ +version 1.0 + +############################################################################################################# +## A workflow that performs joint calling on single-sample gVCFs from GATK4 HaplotypeCaller using GenomicsDB. +############################################################################################################# + +import "tasks/SRJointGenotyping.wdl" as SRJOINT +import "tasks/VariantUtils.wdl" as VARUTIL +import "tasks/Utils.wdl" as UTILS +import "tasks/Finalize.wdl" as FF +import "tasks/Pf_Niare_HaplotypeCaller.wdl" as Niare_HC + +workflow SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VQSR { + input { + Array[File] gvcfs + Array[File] gvcf_indices + + File ref_map_file + + Float snp_calibration_sensitivity = 0.99 + Int snp_max_unlabeled_variants = 0 + Array[String] snp_recalibration_annotation_values = [ "BaseQRankSum", "ExcessHet", "FS", "HAPCOMP", "HAPDOM", "HEC", "MQ", "MQRankSum", "QD", "ReadPosRankSum", "SOR", "DP" ] + + Array[File] snp_known_reference_variants + Array[File] snp_known_reference_variants_index + Array[File] snp_known_reference_variants_identifier + Array[Boolean] snp_is_training + Array[Boolean] snp_is_calibration + + Float indel_calibration_sensitivity = 0.99 + Int indel_max_unlabeled_variants = 0 + Array[String] indel_recalibration_annotation_values = [ "BaseQRankSum", "ExcessHet", "FS", "HAPCOMP", "HAPDOM", "HEC", "MQ", "MQRankSum", "QD", "ReadPosRankSum", "SOR", "DP" ] + + Array[File] indel_known_reference_variants + Array[File] indel_known_reference_variants_index + Array[File] indel_known_reference_variants_identifier + Array[Boolean] indel_is_training + Array[Boolean] indel_is_calibration + + Array[File]? annotation_bed_files + Array[File]? annotation_bed_file_indexes + Array[String]? annotation_bed_file_annotation_names + + String prefix + + String gcs_out_root_dir + } + + parameter_meta { + gvcfs: "GCS paths to gVCF files" + gvcf_indices: "GCS paths to gVCF tbi files" + ref_map_file: "table indicating reference sequence and auxillary file locations" + prefix: "prefix for output joint-called gVCF and tabix index" + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VQSR/~{prefix}" + + Map[String, String] ref_map = read_map(ref_map_file) + + # Create interval list over which to shard the processing: + call UTILS.MakeChrIntervalList as MakeChrIntervalList { + input: + ref_dict = ref_map['dict'], + } + + # Create sample-name map: + call SRJOINT.CreateSampleNameMap as CreateSampleNameMap { + input: + gvcfs = gvcfs, + prefix = prefix + } + + # Shard by contig for speed: + scatter (idx_1 in range(length(MakeChrIntervalList.contig_interval_list_files))) { + + String contig = MakeChrIntervalList.chrs[idx_1][0] + File contig_interval_list = MakeChrIntervalList.contig_interval_list_files[idx_1] + + call Niare_HC.GenomicsDbImport as GenomicsDbImport { + input: + sample_name_map = CreateSampleNameMap.sample_name_map, + interval_list = contig_interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix + "." + contig, + } + + # Shard again by contig chunk: + call UTILS.SplitContigToIntervals as SplitContigToIntervals { + input: + ref_dict = ref_map['dict'], + contig = contig + } + + scatter (idx_2 in range(length(SplitContigToIntervals.individual_bed_files))) { + + File genotype_gvcfs_intervals = SplitContigToIntervals.individual_bed_files[idx_2] + + call Niare_HC.GenotypeGVCFs as GenotypeGVCFs { + input: + input_gvcf_data = GenomicsDbImport.output_genomicsdb, + interval_list = genotype_gvcfs_intervals, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix + "." + contig + ".raw", + } + } + + # Merge all raw VCFs: + call VARUTIL.GatherVcfs as GatherVcfs { + input: + input_vcfs = GenotypeGVCFs.output_vcf, + input_vcf_indices = GenotypeGVCFs.output_vcf_index, + prefix = prefix + "." + contig + ".raw.merged", + } + + # First make a sites-only VCF for recal (smaller file, easier to work with): + call VARUTIL.MakeSitesOnlyVcf as MakeSitesOnlyVCF { + input: + vcf = GatherVcfs.output_vcf, + vcf_index = GatherVcfs.output_vcf_index, + prefix = prefix + "." + contig + ".sites_only" + } + } + + # Merge all sites-only VCFs + call VARUTIL.GatherVcfs as GatherSitesOnlyVCFs { + input: + input_vcfs = MakeSitesOnlyVCF.sites_only_vcf, + input_vcf_indices = MakeSitesOnlyVCF.sites_only_vcf_index, + prefix = prefix + ".sites_only" + } + + ######################################################################## + # Call VETS / VQSR-lite: + call VARUTIL.ExtractVariantAnnotations as ExtractIndelVariantAnnotations { + input: + vcf = GatherSitesOnlyVCFs.output_vcf, + vcf_index = GatherSitesOnlyVCFs.output_vcf_index, + + prefix = prefix, + mode = "INDEL", + + recalibration_annotation_values = indel_recalibration_annotation_values, + + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_training = indel_is_training, + is_calibration = indel_is_calibration, + + max_unlabeled_variants = indel_max_unlabeled_variants, + } + + call VARUTIL.ExtractVariantAnnotations as ExtractSnpVariantAnnotations { + input: + vcf = GatherSitesOnlyVCFs.output_vcf, + vcf_index = GatherSitesOnlyVCFs.output_vcf_index, + + prefix = prefix, + mode = "SNP", + + recalibration_annotation_values = snp_recalibration_annotation_values, + + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_training = snp_is_training, + is_calibration = snp_is_calibration, + + max_unlabeled_variants = snp_max_unlabeled_variants, + } + + call VARUTIL.TrainVariantAnnotationsModel as TrainIndelVariantAnnotationsModel { + input: + annotation_hdf5 = ExtractIndelVariantAnnotations.annotation_hdf5, + mode = "INDEL", + prefix = prefix, + } + + call VARUTIL.TrainVariantAnnotationsModel as TrainSnpVariantAnnotationsModel { + input: + annotation_hdf5 = ExtractSnpVariantAnnotations.annotation_hdf5, + mode = "SNP", + prefix = prefix, + } + + # Shard by contig for speed: + scatter (idx_3 in range(length(GatherVcfs.output_vcf))) { + + String contig_2 = MakeChrIntervalList.chrs[idx_3][0] + File joint_called_vcf = GatherVcfs.output_vcf[idx_3] + File joint_called_vcf_index = GatherVcfs.output_vcf_index[idx_3] + + call VARUTIL.ScoreVariantAnnotations as ScoreSnpVariantAnnotations { + input: + vcf = joint_called_vcf, + vcf_index = joint_called_vcf_index, + + sites_only_extracted_vcf = ExtractSnpVariantAnnotations.sites_only_vcf, + sites_only_extracted_vcf_index = ExtractSnpVariantAnnotations.sites_only_vcf_index, + + model_prefix = prefix + "_train_SNP", + model_files = flatten([[TrainSnpVariantAnnotationsModel.training_scores, TrainSnpVariantAnnotationsModel.positive_model_scorer_pickle], select_all([ + TrainSnpVariantAnnotationsModel.unlabeled_positive_model_scores, + TrainSnpVariantAnnotationsModel.calibration_set_scores, + TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle + ])]), + prefix = prefix + "_SNP_" + contig_2, + mode = "SNP", + + calibration_sensitivity_threshold = snp_calibration_sensitivity, + + recalibration_annotation_values = snp_recalibration_annotation_values, + + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_training = snp_is_training, + is_calibration = snp_is_calibration, + } + + call VARUTIL.ScoreVariantAnnotations as ScoreIndelVariantAnnotations { + input: + vcf = ScoreSnpVariantAnnotations.scored_vcf, + vcf_index = ScoreSnpVariantAnnotations.scored_vcf_index, + + sites_only_extracted_vcf = ExtractIndelVariantAnnotations.sites_only_vcf, + sites_only_extracted_vcf_index = ExtractIndelVariantAnnotations.sites_only_vcf_index, + + model_prefix = prefix + "_train_INDEL", + model_files = flatten([[TrainIndelVariantAnnotationsModel.training_scores, TrainIndelVariantAnnotationsModel.positive_model_scorer_pickle], select_all([ + TrainIndelVariantAnnotationsModel.unlabeled_positive_model_scores, + TrainIndelVariantAnnotationsModel.calibration_set_scores, + TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle + ])]), + prefix = prefix + "_ALL_" + contig_2, + mode = "INDEL", + + calibration_sensitivity_threshold = indel_calibration_sensitivity, + + recalibration_annotation_values = indel_recalibration_annotation_values, + + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_training = indel_is_training, + is_calibration = indel_is_calibration, + } + + # Now we need to annotate our variants by region: + if (defined(annotation_bed_files)) { + call VARUTIL.AnnotateVcfWithBedRegions as AnnotateVcfRegions { + input: + vcf = ScoreIndelVariantAnnotations.scored_vcf, + vcf_index = ScoreIndelVariantAnnotations.scored_vcf_index, + bed_files = select_first([annotation_bed_files]), + bed_file_indexes = select_first([annotation_bed_file_indexes]), + bed_file_annotation_names = select_first([annotation_bed_file_annotation_names]), + prefix = basename(basename(ScoreIndelVariantAnnotations.scored_vcf, ".vcf.gz"), ".vcf") + ".region_annotated", + } + } + + File vcf_for_merging = select_first([AnnotateVcfRegions.annotated_vcf, ScoreIndelVariantAnnotations.scored_vcf]) + File vcf_index_for_merging = select_first([AnnotateVcfRegions.annotated_vcf_index, ScoreIndelVariantAnnotations.scored_vcf_index]) + } + + # Consolidate files: + call VARUTIL.GatherVcfs as GatherRescoredVcfs { + input: + input_vcfs = vcf_for_merging, + input_vcf_indices = vcf_index_for_merging, + prefix = prefix + ".rescored.combined" + } + + ################################ + # Finalize the regular output files: + ############ + File keyfile = GatherRescoredVcfs.output_vcf_index + + call FF.FinalizeToFile as FinalizeVETSVCF { input: outdir = outdir, keyfile = keyfile, file = GatherRescoredVcfs.output_vcf } + call FF.FinalizeToFile as FinalizeVETSTBI { input: outdir = outdir, keyfile = keyfile, file = GatherRescoredVcfs.output_vcf_index } + + + output { + File joint_recalibrated_vcf = FinalizeVETSVCF.gcs_path + File joint_recalibrated_vcf_tbi = FinalizeVETSTBI.gcs_path + } +} + diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VQSR.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VQSR.wdl new file mode 100644 index 000000000..3668dec49 --- /dev/null +++ b/wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VQSR.wdl @@ -0,0 +1,189 @@ +version 1.0 + +############################################################################################################# +## A workflow that performs joint calling on single-sample gVCFs from GATK4 HaplotypeCaller using GenomicsDB. +############################################################################################################# + +import "tasks/SRJointGenotyping.wdl" as SRJOINT +import "tasks/VariantUtils.wdl" as VARUTIL +import "tasks/Utils.wdl" as UTILS +import "tasks/Finalize.wdl" as FF +import "tasks/Pf_Niare_HaplotypeCaller.wdl" as Niare_HC + +workflow SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VQSR { + input { + Array[File] gvcfs + Array[File] gvcf_indices + + File ref_map_file + + File vqsr_sites_vcf + File vqsr_sites_vcf_index + + String prefix + + String gcs_out_root_dir + } + + parameter_meta { + gvcfs: "GCS paths to gVCF files" + gvcf_indices: "GCS paths to gVCF tbi files" + ref_map_file: "table indicating reference sequence and auxillary file locations" + prefix: "prefix for output joint-called gVCF and tabix index" + gcs_out_root_dir: "GCS bucket to store the reads, variants, and metrics files" + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VQSR/~{prefix}" + + Map[String, String] ref_map = read_map(ref_map_file) + + # Create interval list over which to shard the processing: + call UTILS.MakeChrIntervalList as MakeChrIntervalList { + input: + ref_dict = ref_map['dict'], + } + + # Create sample-name map: + call SRJOINT.CreateSampleNameMap as CreateSampleNameMap { + input: + gvcfs = gvcfs, + prefix = prefix + } + + # Shard by contig for speed: + scatter (idx_1 in range(length(MakeChrIntervalList.contig_interval_list_files))) { + + String contig = MakeChrIntervalList.chrs[idx_1][0] + File contig_interval_list = MakeChrIntervalList.contig_interval_list_files[idx_1] + + call Niare_HC.GenomicsDbImport as GenomicsDbImport { + input: + sample_name_map = CreateSampleNameMap.sample_name_map, + interval_list = contig_interval_list, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix + "." + contig, + } + + # Shard again by contig chunk: + call UTILS.SplitContigToIntervals as SplitContigToIntervals { + input: + ref_dict = ref_map['dict'], + contig = contig + } + + scatter (idx_2 in range(length(SplitContigToIntervals.individual_bed_files))) { + + File genotype_gvcfs_intervals = SplitContigToIntervals.individual_bed_files[idx_2] + + call Niare_HC.GenotypeGVCFs as GenotypeGVCFs { + input: + input_gvcf_data = GenomicsDbImport.output_genomicsdb, + interval_list = genotype_gvcfs_intervals, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix + "." + contig + ".raw", + } + } + + # Merge all raw VCFs: + call VARUTIL.GatherVcfs as GatherVcfs { + input: + input_vcfs = GenotypeGVCFs.output_vcf, + input_vcf_indices = GenotypeGVCFs.output_vcf_index, + prefix = prefix + "." + contig + ".raw.merged", + } + + # Normalize variants here: + call Niare_HC.NormalizeVcfSplittingMultiallelics as NormalizeVcfSplittingMultiallelics { + input: + input_vcf = GatherVcfs.output_vcf, + input_vcf_index = GatherVcfs.output_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix + "." + contig + ".raw.merged.normalized", + } + + # Run variant recalibrator and VQSR: + call Niare_HC.VariantRecalibratorIndel as VariantRecalibratorIndel { + input: + input_vcf = NormalizeVcfSplittingMultiallelics.output_vcf, + input_vcf_index = NormalizeVcfSplittingMultiallelics.output_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + sites_only_vcf = vqsr_sites_vcf, + sites_only_vcf_index = vqsr_sites_vcf_index, + prefix = prefix + "." + contig, + } + + call Niare_HC.ApplyVqsrIndel as ApplyVqsrIndel { + input: + input_vcf = NormalizeVcfSplittingMultiallelics.output_vcf, + input_vcf_index = NormalizeVcfSplittingMultiallelics.output_vcf_index, + recal_file = VariantRecalibratorIndel.recalibration, + recal_file_index = VariantRecalibratorIndel.recalibration_index, + recal_tranches = VariantRecalibratorIndel.tranches, + prefix = prefix + "." + contig + "raw", + } + + call Niare_HC.VariantRecalibratorSnp as VariantRecalibratorSnp { + input: + input_vcf = ApplyVqsrIndel.output_vcf, + input_vcf_index = ApplyVqsrIndel.output_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + sites_only_vcf = vqsr_sites_vcf, + sites_only_vcf_index = vqsr_sites_vcf_index, + prefix = prefix + "." + contig, + } + + call Niare_HC.ApplyVqsrIndel as ApplyVqsrSnp { + input: + input_vcf = ApplyVqsrIndel.output_vcf, + input_vcf_index = ApplyVqsrIndel.output_vcf_index, + recal_file = VariantRecalibratorSnp.recalibration, + recal_file_index = VariantRecalibratorSnp.recalibration_index, + recal_tranches = VariantRecalibratorSnp.tranches, + prefix = prefix + "." + contig + "raw", + } + + # Merge multi-allelic sites after recalibration: + call Niare_HC.MergeMultiAllelicSitesPostRecalibration as MergeMultiAllelicSitesPostRecalibration { + input: + input_vcf = ApplyVqsrSnp.output_vcf, + input_vcf_index = ApplyVqsrSnp.output_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = prefix + "." + contig + ".recalibrated", + } + } + + # Consolidate files: + call VARUTIL.GatherVcfs as GatherRescoredVcfs { + input: + input_vcfs = MergeMultiAllelicSitesPostRecalibration.output_vcf, + input_vcf_indices = MergeMultiAllelicSitesPostRecalibration.output_vcf_index, + prefix = prefix + ".rescored.combined" + } + + ################################ + # Finalize the regular output files: + ############ + File keyfile = GatherRescoredVcfs.output_vcf_index + + call FF.FinalizeToFile as FinalizeVETSVCF { input: outdir = outdir, keyfile = keyfile, file = GatherRescoredVcfs.output_vcf } + call FF.FinalizeToFile as FinalizeVETSTBI { input: outdir = outdir, keyfile = keyfile, file = GatherRescoredVcfs.output_vcf_index } + + + output { + File joint_recalibrated_vcf = FinalizeVETSVCF.gcs_path + File joint_recalibrated_vcf_tbi = FinalizeVETSTBI.gcs_path + } +} + diff --git a/wdl/SRWholeGenome_Pf_Niare_VETS.wdl b/wdl/SRWholeGenome_Pf_Niare_VETS.wdl new file mode 100644 index 000000000..e146c1883 --- /dev/null +++ b/wdl/SRWholeGenome_Pf_Niare_VETS.wdl @@ -0,0 +1,267 @@ +version 1.0 + +###################################################################################### +## A workflow that performs single sample variant calling on Illumina reads from +## one or more flow cells. The workflow merges multiple samples into a single BAM +## prior to variant calling. +###################################################################################### + +import "tasks/Utils.wdl" as Utils +import "tasks/SRUtils.wdl" as SRUTIL +import "tasks/Finalize.wdl" as FF +import "tasks/VariantUtils.wdl" as VARUTIL +import "tasks/Pf_Niare_HaplotypeCaller.wdl" as Niare_HC + + +workflow SRWholeGenome_Pf_Niare_VETS { + input { + Array[File] aligned_bams + Array[File] aligned_bais + + File ref_map_file + + String participant_name + + String gcs_out_root_dir + + File vcf_calling_interval_list + File genotype_gvcfs_intervals + + Float snp_calibration_sensitivity = 0.99 + Int snp_max_unlabeled_variants = 0 + Array[String] snp_recalibration_annotation_values = [ "BaseQRankSum", "ExcessHet", "FS", "HAPCOMP", "HAPDOM", "HEC", "MQ", "MQRankSum", "QD", "ReadPosRankSum", "SOR", "DP" ] + + Array[File] snp_known_reference_variants + Array[File] snp_known_reference_variants_index + Array[File] snp_known_reference_variants_identifier + Array[Boolean] snp_is_training + Array[Boolean] snp_is_calibration + + Float indel_calibration_sensitivity = 0.99 + Int indel_max_unlabeled_variants = 0 + Array[String] indel_recalibration_annotation_values = [ "BaseQRankSum", "ExcessHet", "FS", "HAPCOMP", "HAPDOM", "HEC", "MQ", "MQRankSum", "QD", "ReadPosRankSum", "SOR", "DP" ] + + Array[File] indel_known_reference_variants + Array[File] indel_known_reference_variants_index + Array[File] indel_known_reference_variants_identifier + Array[Boolean] indel_is_training + Array[Boolean] indel_is_calibration + + File? bed_to_compute_coverage + + Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! + } + + Map[String, String] ref_map = read_map(ref_map_file) + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRWholeGenome_Pf_Niare_VETS/~{participant_name}" + + String bam_dir = outdir + "/alignments" + String metrics_dir = outdir + "/metrics" + String smalldir = outdir + "/variants/small" + String recalibration_dir = outdir + "/variants/recalibration_files" + + # gather across (potential multiple) input CCS BAMs + if (length(aligned_bams) > 1) { + scatter (pair in zip(aligned_bams, aligned_bais)) { + call Utils.InferSampleName {input: bam = pair.left, bai = pair.right} + } + call Utils.CheckOnSamplenames {input: sample_names = InferSampleName.sample_name} + + call Utils.MergeBams as MergeAllReads { input: bams = aligned_bams, prefix = participant_name } + } + + File bam = select_first([MergeAllReads.merged_bam, aligned_bams[0]]) + File bai = select_first([MergeAllReads.merged_bai, aligned_bais[0]]) + + #################################################################################################### + # HC Call Variants: + + # Now we handle HaplotypeCaller data: + call Niare_HC.CallVariantsWithHaplotypeCaller { + input: + bam = bam, + bai = bai, + sample_id = participant_name, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + + vcf_calling_interval_list = vcf_calling_interval_list, + genotype_gvcfs_intervals = genotype_gvcfs_intervals, + + prefix = participant_name + ".haplotype_caller", + + mito_contig = ref_map['mt_chr_name'], + contigs_names_to_ignore = contigs_names_to_ignore, + } + + # Make sure our sample name is correct: + call VARUTIL.RenameSingleSampleVcf as RenameRawHcVcf { + input: + vcf = CallVariantsWithHaplotypeCaller.output_vcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + prefix = participant_name + ".haplotype_caller.renamed", + new_sample_name = participant_name + } + call VARUTIL.RenameSingleSampleVcf as RenameRawHcGvcf { + input: + vcf = CallVariantsWithHaplotypeCaller.output_gvcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_gvcf_index, + prefix = participant_name + ".haplotype_caller.renamed", + is_gvcf = true, + new_sample_name = participant_name + } + + ######################################################################## + # Call VETS / VQSR-lite: + call VARUTIL.ExtractVariantAnnotations as ExtractIndelVariantAnnotations { + input: + vcf = RenameRawHcVcf.new_sample_name_vcf, + vcf_index = RenameRawHcVcf.new_sample_name_vcf_index, + + prefix = participant_name, + mode = "INDEL", + + recalibration_annotation_values = indel_recalibration_annotation_values, + + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_training = indel_is_training, + is_calibration = indel_is_calibration, + + max_unlabeled_variants = indel_max_unlabeled_variants, + } + + call VARUTIL.ExtractVariantAnnotations as ExtractSnpVariantAnnotations { + input: + vcf = RenameRawHcVcf.new_sample_name_vcf, + vcf_index = RenameRawHcVcf.new_sample_name_vcf_index, + + prefix = participant_name, + mode = "SNP", + + recalibration_annotation_values = snp_recalibration_annotation_values, + + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_training = snp_is_training, + is_calibration = snp_is_calibration, + + max_unlabeled_variants = snp_max_unlabeled_variants, + } + + call VARUTIL.TrainVariantAnnotationsModel as TrainIndelVariantAnnotationsModel { + input: + annotation_hdf5 = ExtractIndelVariantAnnotations.annotation_hdf5, + mode = "INDEL", + prefix = participant_name, + } + + call VARUTIL.TrainVariantAnnotationsModel as TrainSnpVariantAnnotationsModel { + input: + annotation_hdf5 = ExtractSnpVariantAnnotations.annotation_hdf5, + mode = "SNP", + prefix = participant_name, + } + + call VARUTIL.ScoreVariantAnnotations as ScoreSnpVariantAnnotations { + input: + vcf = RenameRawHcVcf.new_sample_name_vcf, + vcf_index = RenameRawHcVcf.new_sample_name_vcf_index, + + sites_only_extracted_vcf = ExtractSnpVariantAnnotations.sites_only_vcf, + sites_only_extracted_vcf_index = ExtractSnpVariantAnnotations.sites_only_vcf_index, + + model_prefix = participant_name + "_train_SNP", + model_files = flatten([[TrainSnpVariantAnnotationsModel.training_scores, TrainSnpVariantAnnotationsModel.positive_model_scorer_pickle], select_all([ + TrainSnpVariantAnnotationsModel.unlabeled_positive_model_scores, + TrainSnpVariantAnnotationsModel.calibration_set_scores, + TrainSnpVariantAnnotationsModel.negative_model_scorer_pickle + ])]), + prefix = participant_name + "_SNP", + mode = "SNP", + + calibration_sensitivity_threshold = snp_calibration_sensitivity, + + recalibration_annotation_values = snp_recalibration_annotation_values, + + known_reference_variants = snp_known_reference_variants, + known_reference_variants_index = snp_known_reference_variants_index, + known_reference_variants_identifier = snp_known_reference_variants_identifier, + is_training = snp_is_training, + is_calibration = snp_is_calibration, + } + + call VARUTIL.ScoreVariantAnnotations as ScoreIndelVariantAnnotations { + input: + vcf = ScoreSnpVariantAnnotations.scored_vcf, + vcf_index = ScoreSnpVariantAnnotations.scored_vcf_index, + + sites_only_extracted_vcf = ExtractIndelVariantAnnotations.sites_only_vcf, + sites_only_extracted_vcf_index = ExtractIndelVariantAnnotations.sites_only_vcf_index, + + model_prefix = participant_name + "_train_INDEL", + model_files = flatten([[TrainIndelVariantAnnotationsModel.training_scores, TrainIndelVariantAnnotationsModel.positive_model_scorer_pickle], select_all([ + TrainIndelVariantAnnotationsModel.unlabeled_positive_model_scores, + TrainIndelVariantAnnotationsModel.calibration_set_scores, + TrainIndelVariantAnnotationsModel.negative_model_scorer_pickle + ])]), + prefix = participant_name + "_ALL", + mode = "INDEL", + + calibration_sensitivity_threshold = indel_calibration_sensitivity, + + recalibration_annotation_values = indel_recalibration_annotation_values, + + known_reference_variants = indel_known_reference_variants, + known_reference_variants_index = indel_known_reference_variants_index, + known_reference_variants_identifier = indel_known_reference_variants_identifier, + is_training = indel_is_training, + is_calibration = indel_is_calibration, + } + ######################################################################## + + call VARUTIL.SelectVariants as RemoveFilteredVariants { + input: + vcf = ScoreIndelVariantAnnotations.scored_vcf, + vcf_index = ScoreIndelVariantAnnotations.scored_vcf_index, + prefix = participant_name + ".filtered" + } + + # Create a Keyfile for finalization: + File keyfile = RemoveFilteredVariants.vcf_out_index + + # Finalize the raw Joint Calls: + call FF.FinalizeToFile as FinalizeRawHCVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcVcf.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeRawHCTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcVcf.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCGVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcGvcf.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeHCGTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcGvcf.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCBamOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout } + call FF.FinalizeToFile as FinalizeHCBaiOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout_index } + + # Finalize the reclibrated / filtered variants: + call FF.FinalizeToFile as FinalizeHCRescoredVcf { input: outdir = smalldir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf } + call FF.FinalizeToFile as FinalizeHCRescoredTbi { input: outdir = smalldir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf_index } + call FF.FinalizeToFile as FinalizeHCRescoredFilteredVcf { input: outdir = smalldir, keyfile = keyfile, file = RemoveFilteredVariants.vcf_out } + call FF.FinalizeToFile as FinalizeHCRescoredFilteredTbi { input: outdir = smalldir, keyfile = keyfile, file = RemoveFilteredVariants.vcf_out_index } + + output { + Boolean successfully_processed = true + + ######################################## + + File? hc_g_vcf = FinalizeHCGVcf.gcs_path + File? hc_g_tbi = FinalizeHCGTbi.gcs_path + File? hc_bamout = FinalizeHCBamOut.gcs_path + File? hc_baiout = FinalizeHCBaiOut.gcs_path + File? hc_raw_vcf = FinalizeRawHCVcf.gcs_path + File? hc_raw_tbi = FinalizeRawHCTbi.gcs_path + File? hc_rescored_vcf = FinalizeHCRescoredVcf.gcs_path + File? hc_rescored_tbi = FinalizeHCRescoredVcf.gcs_path + File? hc_rescored_hard_filtered_vcf = FinalizeHCRescoredFilteredVcf.gcs_path + File? hc_rescored_hard_filtered_tbi = FinalizeHCRescoredFilteredTbi.gcs_path + } +} diff --git a/wdl/SRWholeGenome_Pf_Niare_their_VQSR.wdl b/wdl/SRWholeGenome_Pf_Niare_their_VQSR.wdl new file mode 100644 index 000000000..7f6ec0926 --- /dev/null +++ b/wdl/SRWholeGenome_Pf_Niare_their_VQSR.wdl @@ -0,0 +1,216 @@ +version 1.0 + +###################################################################################### +## A workflow that performs single sample variant calling on Illumina reads from +## one or more flow cells. The workflow merges multiple samples into a single BAM +## prior to variant calling. +###################################################################################### + +import "tasks/Utils.wdl" as Utils +import "tasks/SRUtils.wdl" as SRUTIL +import "tasks/Finalize.wdl" as FF +import "tasks/VariantUtils.wdl" as VARUTIL +import "tasks/Pf_Niare_HaplotypeCaller.wdl" as Niare_HC + +workflow SRWholeGenome_Pf_Niare_VQSR { + input { + Array[File] aligned_bams + Array[File] aligned_bais + + File ref_map_file + + String participant_name + + File vcf_calling_interval_list + File genotype_gvcfs_intervals + + String gcs_out_root_dir + + File vqsr_sites_vcf + File vqsr_sites_vcf_index + + Boolean call_vars_on_mitochondria = false + String mito_contig = "chrM" + Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! + } + + Map[String, String] ref_map = read_map(ref_map_file) + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/SRWholeGenome_Pf_Niare_VQSR/~{participant_name}" + + String bam_dir = outdir + "/alignments" + String metrics_dir = outdir + "/metrics" + String smalldir = outdir + "/variants/small" + String recalibration_dir = outdir + "/variants/recalibration_files" + + # gather across (potential multiple) input CCS BAMs + if (length(aligned_bams) > 1) { + scatter (pair in zip(aligned_bams, aligned_bais)) { + call Utils.InferSampleName {input: bam = pair.left, bai = pair.right} + } + call Utils.CheckOnSamplenames {input: sample_names = InferSampleName.sample_name} + + call Utils.MergeBams as MergeAllReads { input: bams = aligned_bams, prefix = participant_name } + } + + File bam = select_first([MergeAllReads.merged_bam, aligned_bams[0]]) + File bai = select_first([MergeAllReads.merged_bai, aligned_bais[0]]) + + #################################################################################################### + # HC Call Variants: + + # Now we handle HaplotypeCaller data: + call Niare_HC.CallVariantsWithHaplotypeCaller { + input: + bam = bam, + bai = bai, + sample_id = participant_name, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + + vcf_calling_interval_list = vcf_calling_interval_list, + genotype_gvcfs_intervals = genotype_gvcfs_intervals, + + prefix = participant_name + ".haplotype_caller", + + mito_contig = ref_map['mt_chr_name'], + contigs_names_to_ignore = contigs_names_to_ignore, + } + + # Make sure our sample name is correct: + call VARUTIL.RenameSingleSampleVcf as RenameRawHcVcf { + input: + vcf = CallVariantsWithHaplotypeCaller.output_vcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_vcf_index, + prefix = participant_name + ".haplotype_caller.renamed", + new_sample_name = participant_name + } + call VARUTIL.RenameSingleSampleVcf as RenameRawHcGvcf { + input: + vcf = CallVariantsWithHaplotypeCaller.output_gvcf, + vcf_index = CallVariantsWithHaplotypeCaller.output_gvcf_index, + prefix = participant_name + ".haplotype_caller.renamed", + is_gvcf = true, + new_sample_name = participant_name + } + + ################################################################################################ + # VQSR: + + # Scatter by chromosome: + Array[String] use_filter = if (call_vars_on_mitochondria) then contigs_names_to_ignore else flatten([[mito_contig], contigs_names_to_ignore]) + call Utils.MakeChrIntervalList as SmallVariantsScatterPrep { + input: + ref_dict = ref_map['dict'], + filter = use_filter + } + + # Call over the scattered intervals: + scatter (c in SmallVariantsScatterPrep.chrs) { + String contig_for_small_var = c[0] + + call Niare_HC.NormalizeVcfSplittingMultiallelics as NormalizeVcfPreVqsr { + input: + input_vcf = RenameRawHcVcf.new_sample_name_vcf, + input_vcf_index = RenameRawHcVcf.new_sample_name_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = participant_name + "." + contig_for_small_var + ".norm" + } + + call Niare_HC.VariantRecalibratorIndel as VariantRecalibratorIndel { + input: + input_vcf = NormalizeVcfPreVqsr.output_vcf, + input_vcf_index = NormalizeVcfPreVqsr.output_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + sites_only_vcf = vqsr_sites_vcf, + sites_only_vcf_index = vqsr_sites_vcf_index, + prefix = participant_name + "." + contig_for_small_var + ".norm", + } + + call Niare_HC.ApplyVqsrIndel as ApplyVqsrIndel { + input: + input_vcf = NormalizeVcfPreVqsr.output_vcf, + input_vcf_index = NormalizeVcfPreVqsr.output_vcf_index, + recal_file = VariantRecalibratorIndel.recalibration, + recal_tranches = VariantRecalibratorIndel.tranches, + prefix = participant_name + "." + contig_for_small_var + ".norm", + } + + call Niare_HC.VariantRecalibratorSnp as VariantRecalibratorSnp { + input: + input_vcf = ApplyVqsrIndel.output_vcf, + input_vcf_index = ApplyVqsrIndel.output_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + sites_only_vcf = vqsr_sites_vcf, + sites_only_vcf_index = vqsr_sites_vcf_index, + prefix = participant_name + "." + contig_for_small_var + ".norm", + } + + call Niare_HC.ApplyVqsrSnp as ApplyVqsrSnp { + input: + input_vcf = ApplyVqsrIndel.output_vcf, + input_vcf_index = ApplyVqsrIndel.output_vcf_index, + recal_file = VariantRecalibratorIndel.recalibration, + recal_tranches = VariantRecalibratorIndel.tranches, + prefix = participant_name + "." + contig_for_small_var + ".norm.indel_recal", + } + + call Niare_HC.MergeMultiAllelicSitesPostRecalibration as MergeMultiAllelicSitesPostRecalibration { + input: + input_vcf = ApplyVqsrSnp.output_vcf, + input_vcf_index = ApplyVqsrSnp.output_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = participant_name + "." + contig_for_small_var, + } + } + + call SRUTIL.MergeVCFs as MergeVCFs { + input: + input_vcfs = MergeMultiAllelicSitesPostRecalibration.output_vcf, + input_vcfs_indexes = MergeMultiAllelicSitesPostRecalibration.output_vcf_index, + prefix = participant_name + ".recalibrated" + } + + ################################################################################################ + + # Create a Keyfile for finalization: + File keyfile = MergeVCFs.output_vcf_index + + # Finalize the raw Joint Calls: + call FF.FinalizeToFile as FinalizeRawHCVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcVcf.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeRawHCTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcVcf.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCGVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcGvcf.new_sample_name_vcf } + call FF.FinalizeToFile as FinalizeHCGTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcGvcf.new_sample_name_vcf_index } + call FF.FinalizeToFile as FinalizeHCBamOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout } + call FF.FinalizeToFile as FinalizeHCBaiOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout_index } + call FF.FinalizeToFile as FinalizeRecalibratedVcf { input: outdir = smalldir, keyfile = keyfile, file = MergeVCFs.output_vcf } + call FF.FinalizeToFile as FinalizeRecalibratedVcfIndex { input: outdir = smalldir, keyfile = keyfile, file = MergeVCFs.output_vcf_index } + + ################################ + # Finalize the VETS files: + ############ + + output { + Boolean successfully_processed = true + + ######################################## + + File? hc_g_vcf = FinalizeHCGVcf.gcs_path + File? hc_g_tbi = FinalizeHCGTbi.gcs_path + File? hc_bamout = FinalizeHCBamOut.gcs_path + File? hc_baiout = FinalizeHCBaiOut.gcs_path + File? hc_raw_vcf = FinalizeRawHCVcf.gcs_path + File? hc_raw_tbi = FinalizeRawHCTbi.gcs_path + File? hc_rescored_vcf = FinalizeRecalibratedVcf.gcs_path + File? hc_rescored_tbi = FinalizeRecalibratedVcfIndex.gcs_path + } +} diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl new file mode 100644 index 000000000..b36969853 --- /dev/null +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -0,0 +1,912 @@ +version 1.0 + +import "Structs.wdl" +import "Utils.wdl" +import "SRUtils.wdl" as SRUTIL +import "SRJointGenotyping.wdl" as SRJOINT + +workflow CallVariantsWithHaplotypeCaller { + meta { + author: "Jonn Smith" + description: "A workflow for calling small variants with GATK HaplotypeCaller from an Illumina BAM file using the methods laid out by Niare et al. (https://doi.org/10.1186/s12936-023-04632-0)." + } + + input { + File bam + File bai + + String prefix + String sample_id + + File ref_fasta + File ref_fasta_fai + File ref_dict + + File vcf_calling_interval_list + File genotype_gvcfs_intervals + + Boolean call_vars_on_mitochondria = false + + String mito_contig = "chrM" + Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! + } + + # Scatter by chromosome: + Array[String] use_filter = if (call_vars_on_mitochondria) then contigs_names_to_ignore else flatten([[mito_contig], contigs_names_to_ignore]) + call Utils.MakeChrIntervalList as SmallVariantsScatterPrep { + input: + ref_dict = ref_dict, + filter = use_filter + } + + # Call over the scattered intervals: + scatter (c in SmallVariantsScatterPrep.chrs) { + String contig_for_small_var = c[0] + + call HaplotypeCaller_NIARE_GATK4_VCF as CallVariantsWithHC { + input: + input_bam = bam, + input_bam_index = bai, + prefix = prefix + "." + contig_for_small_var, + ref_fasta = ref_fasta, + ref_fasta_index = ref_fasta_fai, + ref_dict = ref_dict, + interval_list = vcf_calling_interval_list + } + } + + # Merge the output GVCFs: + call SRUTIL.MergeVCFs as MergeGVCFs { + input: + input_vcfs = CallVariantsWithHC.output_vcf, + input_vcfs_indexes = CallVariantsWithHC.output_vcf_index, + prefix = prefix + } + + # Merge the output BAMs: + call MergeBamouts as MergeVariantCalledBamOuts { + input: + bams = CallVariantsWithHC.bamout, + prefix = "~{prefix}.bamout" + } + + # Index the Bamout: + call Utils.Index as IndexBamout { + input: + bam = MergeVariantCalledBamOuts.output_bam + } + + # Collapse the GVCF into a regular VCF: + call SRJOINT.GenotypeGVCFs as CollapseGVCFtoVCF { + input: + input_gvcf_data = MergeGVCFs.output_vcf, + input_gvcf_index = MergeGVCFs.output_vcf_index, + interval_list = genotype_gvcfs_intervals, + ref_fasta = ref_fasta, + ref_fasta_fai = ref_fasta_fai, + ref_dict = ref_dict, + prefix = prefix, + } + + output { + File output_gvcf = MergeGVCFs.output_vcf + File output_gvcf_index = MergeGVCFs.output_vcf_index + File output_vcf = CollapseGVCFtoVCF.output_vcf + File output_vcf_index = CollapseGVCFtoVCF.output_vcf_index + File bamout = MergeVariantCalledBamOuts.output_bam + File bamout_index = IndexBamout.bai + } +} + +task HaplotypeCaller_NIARE_GATK4_VCF { + meta { + author: "Jonn Smith" + notes: "Reproducing methods laid out by Niare et al. (https://doi.org/10.1186/s12936-023-04632-0) to perform testing." + } + + input { + File input_bam + File input_bam_index + + String prefix + + File ref_dict + File ref_fasta + File ref_fasta_index + + File interval_list + + RuntimeAttr? runtime_attr_override + } + + String output_file_name = prefix + ".g.vcf.gz" + + Float ref_size = size(ref_fasta, "GiB") + size(ref_fasta_index, "GiB") + size(ref_dict, "GiB") + Int disk_size = 2*ceil(((size(input_bam, "GiB") + 30)) + ref_size) + 20 + + parameter_meta { + input_bam: { localization_optional: true } + } + + command <<< + set -euxo pipefail + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + + available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') + let java_memory_size_mb=available_memory_mb-1024 + echo Total available memory: ${available_memory_mb} MB >&2 + echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 + +# gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ +# HaplotypeCaller \ +# -R ~{ref_fasta} \ +# -I ~{input_bam} \ +# -ERC GVCF \ +# -ploidy 2 \ # Default: 2 +# --native-pair-hmm-threads 16 \ # Default: 4 +# -O ~{output_file_name} \ +# --assembly-region-padding 100 \ # Default: 100 +# --max-num-haplotypes-in-population 128 \ # Default: 128 +# --kmer-size 10 \ # Default: 10, 25 +# --kmer-size 25 \ # Default: 10, 25 +# --min-dangling-branch-length 4 \ # default: 4 +# --heterozygosity 0.0029 \ +# --indel-heterozygosity 0.0017 \ +# --min-assembly-region-size 100 \ # default: 50 +# -L ~{interval_list} \ +# -mbq 5 \ # default 10 +# -DF MappingQualityReadFilter \ +# --base-quality-score-threshold 12 \ # 18 +# -bamout ~{prefix}.bamout.bam + + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ + HaplotypeCaller \ + -R ~{ref_fasta} \ + -I ~{input_bam} \ + -ERC GVCF \ + -ploidy 2 \ + --native-pair-hmm-threads 16 \ + -O ~{output_file_name} \ + --assembly-region-padding 100 \ + --max-num-haplotypes-in-population 128 \ + --kmer-size 10 \ + --kmer-size 25 \ + --min-dangling-branch-length 4 \ + --heterozygosity 0.0029 \ + --indel-heterozygosity 0.0017 \ + --min-assembly-region-size 100 \ + -L ~{interval_list} \ + -mbq 5 \ + -DF MappingQualityReadFilter \ + --base-quality-score-threshold 12 \ + -bamout ~{prefix}.bamout.bam + + >>> + + output { + File output_vcf = "~{output_file_name}" + File output_vcf_index = "~{output_file_name}.tbi" + File bamout = "~{prefix}.bamout.bam" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 2, + mem_gb: 16, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-malaria-niare-pipeline:0.0.1" + } + + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +# This task is here because merging bamout files using Picard produces an error. +task MergeBamouts { + + input { + Array[File] bams + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = ceil(size(bams, "GiB") * 2) + 10 + + command <<< + + set -euxo pipefail + + # Make sure we use all our processors: + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + + ithreads=${np} + + # If the number of processors = 1, then `let` will return 1 here: + # So we need to turn off `set -e` for this command: + set +e + let mthreads=${np}-1 + set -e + + samtools merge -@${mthreads} ~{prefix}.bam ~{sep=" " bams} + samtools index -@${ithreads} ~{prefix}.bam + mv ~{prefix}.bam.bai ~{prefix}.bai + >>> + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 4, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 1, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-basic:0.1.1" + } + + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } + + output { + File output_bam = "~{prefix}.bam" + File output_bam_index = "~{prefix}.bai" + } +} + +task GenomicsDbImport { + meta { + author: "Jonn Smith" + notes: "Reproducing methods laid out by Niare et al. (https://doi.org/10.1186/s12936-023-04632-0) to perform testing." + } + + input { + File sample_name_map + + File interval_list + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String prefix + + Int batch_size = 100 + + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size([sample_name_map, interval_list, ref_fasta, ref_fasta_fai, ref_dict], "GB")) + + Int disk_size = 1 + 4*ref_size + + command <<< + set -euxo pipefail + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + + available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') + let java_memory_size_mb=available_memory_mb-1024 + echo Total available memory: ${available_memory_mb} MB >&2 + echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 + + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ + GenomicsDBImport \ + --sample-name-map ~{sample_name_map} \ + --genomicsdb-workspace-path ~{prefix}.genomicsDB \ + --batch-size ~{batch_size} \ + -L ~{interval_list} \ + --genomicsdb-segment-size 8048576 \ + --genomicsdb-vcf-buffer-size 160384 + + tar -cf ~{prefix}.genomicsDB.tar ~{prefix}.genomicsDB + >>> + + output { + File output_genomicsdb = "~{prefix}.genomicsDB.tar" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 0, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-malaria-niare-pipeline:0.0.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task GenotypeGVCFs { + meta { + author: "Jonn Smith" + notes: "Reproducing methods laid out by Niare et al. (https://doi.org/10.1186/s12936-023-04632-0) to perform testing. Mild adaptations due to infrastructure." + } + + input { + File input_gvcf_data + File? input_gvcf_index # Required if passing a VCF file. + + File interval_list + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String prefix + + Int batch_size = 100 + + RuntimeAttr? runtime_attr_override + } + + parameter_meta { + input_gvcf_data: { help: "Either a single GVCF file or a GenomicsDB Tar file." } + interval_list: { + localization_optional: true + } + } + + Int ref_size = ceil(size([input_gvcf_data, input_gvcf_index, ref_fasta, ref_fasta_fai, ref_dict, interval_list], "GB")) + + Int disk_size = 1 + 4*ref_size + + command <<< + set -euxo pipefail + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + + # We must determine if our input variants are in a genomicsdb file or in a VCF. + # The easiest way is to see if the input is a .tar file: + + is_genomics_db=true + filename=$(basename -- "~{input_gvcf_data}") + extension="${filename##*.}" + if [[ "${extension}" != "tar" ]] ; then + is_genomics_db=false + fi + + if $is_genomics_db ; then + tar -xf ~{input_gvcf_data} + INPUT_FILE="gendb://$(basename ~{input_gvcf_data} .tar)" + else + INPUT_FILE=~{input_gvcf_data} + fi + + available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') + let java_memory_size_mb=available_memory_mb-1024 + echo Total available memory: ${available_memory_mb} MB >&2 + echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 + + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ + GenotypeGVCFs \ + --genomicsdb-use-bcf-codec true \ + -R ~{ref_fasta} \ + -V ${INPUT_FILE} \ + -L ~{interval_list} \ + --max-genotype-count 1024 \ + -O ~{prefix}.vcf.gz \ + -stand-call-conf 30 + + >>> + + output { + File output_vcf = "~{prefix}.vcf.gz" + File output_vcf_index = "~{prefix}.vcf.gz.tbi" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 0, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-malaria-niare-pipeline:0.0.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task NormalizeVcfSplittingMultiallelics { + meta { + author: "Jonn Smith" + notes: "Reproducing methods laid out by Niare et al. (https://doi.org/10.1186/s12936-023-04632-0) to perform testing. Mild adaptations due to infrastructure." + } + + input { + File input_vcf + File input_vcf_index + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String prefix + + Int batch_size = 100 + + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size([input_vcf, input_vcf_index, ref_fasta, ref_fasta_fai, ref_dict], "GB")) + + Int disk_size = 1 + 4*ref_size + + command <<< + set -euxo pipefail + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + + + bcftools norm -m-any ~{input_vcf} | \ + bcftools norm --check-ref -w -f ~{ref_fasta} | \ + bcftools annotate \ + -Ob \ + -x 'ID' \ + -I +'%CHROM:%POS:%POS:%REF:%ALT' | \ + bcftools view -i 'AC>0' -Oz -o ~{prefix}.vcf.gz + + tabix -p vcf ~{prefix}.vcf.gz + >>> + + output { + File output_vcf = "~{prefix}.vcf.gz" + File output_vcf_index = "~{prefix}.vcf.gz.tbi" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 0, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-malaria-niare-pipeline:0.0.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task VariantRecalibratorIndel { + meta { + author: "Jonn Smith" + notes: "Reproducing methods laid out by Niare et al. (https://doi.org/10.1186/s12936-023-04632-0) to perform testing. Mild adaptations due to infrastructure." + } + + input { + File input_vcf + File input_vcf_index + + File ref_fasta + File ref_fasta_fai + File ref_dict + + File sites_only_vcf + File sites_only_vcf_index + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size([input_vcf, input_vcf_index, ref_fasta, ref_fasta_fai, ref_dict, sites_only_vcf, sites_only_vcf_index], "GB")) + + Int disk_size = 1 + 4*ref_size + + command <<< + set -euxo pipefail + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + + + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ + VariantRecalibrator \ + -R ~{ref_fasta} \ + -V ~{input_vcf} \ + --trust-all-polymorphic \ + -an QD -an DP -an FS -an SOR -an MQ \ + -mode INDEL \ + --max-gaussians 4 \ + -resource:Brown,known=true,training=true,truth=true,prior=15.0 ~{sites_only_vcf} \ + -O ~{prefix}.indel_recal \ + --output-model ~{prefix}.indel.model.report \ + --tranches-file ~{prefix}.raw.indel.tranches \ + --rscript-file ~{prefix}.raw.indel.plots.R + + >>> + + output { + File recalibration = "~{prefix}.indel_recal" + File recalibration_index = "~{prefix}.indel_recal.idx" + File tranches = "~{prefix}.raw.indel.tranches" + File model_report = "~{prefix}.indel.model.report" + File r_script_file = "~{prefix}.raw.indel.plots.R" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 0, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-malaria-niare-pipeline:0.0.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task VariantRecalibratorSnp { + meta { + author: "Jonn Smith" + notes: "Reproducing methods laid out by Niare et al. (https://doi.org/10.1186/s12936-023-04632-0) to perform testing. Mild adaptations due to infrastructure." + } + + input { + File input_vcf + File input_vcf_index + + File ref_fasta + File ref_fasta_fai + File ref_dict + + File sites_only_vcf + File sites_only_vcf_index + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size([input_vcf, input_vcf_index, ref_fasta, ref_fasta_fai, ref_dict, sites_only_vcf, sites_only_vcf_index], "GB")) + + Int disk_size = 1 + 4*ref_size + + command <<< + set -euxo pipefail + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ + VariantRecalibrator \ + -R ~{ref_fasta} \ + -V ~{input_vcf} \ + --trust-all-polymorphic \ + -an QD -an DP -an FS -an SOR -an MQ \ + -mode SNP \ + --max-gaussians 4 \ + -resource:Brown,known=true,training=true,truth=true,prior=15.0 ~{sites_only_vcf} \ + -O ~{prefix}.snp_recal \ + --tranches-file ~{prefix}.raw.snp.tranches \ + --output-model ~{prefix}.snp.model.report \ + --rscript-file ~{prefix}.raw.snp.plots.R + + >>> + + output { + File recalibration = "~{prefix}.snp_recal" + File recalibration_index = "~{prefix}.snp_recal.idx" + File tranches = "~{prefix}.raw.snp.tranches" + File model_report = "~{prefix}.snp.model.report" + File r_script_file = "~{prefix}.raw.snp.plots.R" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 0, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-malaria-niare-pipeline:0.0.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task ApplyVqsrIndel { + meta { + author: "Jonn Smith" + notes: "Reproducing methods laid out by Niare et al. (https://doi.org/10.1186/s12936-023-04632-0) to perform testing. Mild adaptations due to infrastructure." + } + + input { + File input_vcf + File input_vcf_index + + File recal_file + File recal_file_index + File recal_tranches + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size(input_vcf, "GB") + size(recal_file, "GB") + size(recal_tranches, "GB")) + + Int disk_size = 1 + 4*ref_size + + command <<< + set -euxo pipefail + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ + ApplyVQSR \ + -V ~{input_vcf} \ + --recal-file ~{recal_file} \ + --tranches-file ~{recal_tranches} \ + --create-output-variant-index true \ + --lod-score-cutoff -2.0 \ + --exclude-filtered false \ + -mode INDEL \ + -O ~{prefix}.indel_recal.vcf.gz + + >>> + + output { + File output_vcf = "~{prefix}.indel_recal.vcf.gz" + File output_vcf_index = "~{prefix}.indel_recal.vcf.gz.tbi" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 0, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-malaria-niare-pipeline:0.0.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task ApplyVqsrSnp { + meta { + author: "Jonn Smith" + notes: "Reproducing methods laid out by Niare et al. (https://doi.org/10.1186/s12936-023-04632-0) to perform testing. Mild adaptations due to infrastructure." + } + + input { + File input_vcf + File input_vcf_index + + File recal_file + File recal_tranches + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size(input_vcf, "GB") + size(recal_file, "GB") + size(recal_tranches, "GB")) + + Int disk_size = 1 + 4*ref_size + + command <<< + set -euxo pipefail + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ + ApplyVQSR \ + -V ~{input_vcf} \ + --recal-file ~{recal_file} \ + --tranches-file ~{recal_tranches} \ + --create-output-variant-index true \ + --lod-score-cutoff 0.0 \ + --exclude-filtered false \ + -mode SNP \ + -O ~{prefix}.indel_recal.vcf.gz + + >>> + + output { + File output_vcf = "~{prefix}.indel_recal.vcf.gz" + File output_vcf_index = "~{prefix}.indel_recal.vcf.gz.tbi" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 0, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-malaria-niare-pipeline:0.0.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + +task MergeMultiAllelicSitesPostRecalibration { + meta { + author: "Jonn Smith" + notes: "Reproducing methods laid out by Niare et al. (https://doi.org/10.1186/s12936-023-04632-0) to perform testing. Mild adaptations due to infrastructure." + } + + input { + File input_vcf + File input_vcf_index + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int ref_size = ceil(size([input_vcf, input_vcf_index, ref_fasta, ref_fasta_fai, ref_dict], "GB")) + + Int disk_size = 1 + 4*ref_size + + command <<< + set -euxo pipefail + # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit + # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from + # memory_size_gb because of Cromwell's retry with more memory feature. + # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, + # which do not rely on the output format of the `free` command. + + # Make sure we use all our processors: + np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ + SelectVariants \ + -R ~{ref_fasta} \ + -V ~{input_vcf} \ + -O ~{prefix}.raw.recal.pass.vcf.gz \ + --exclude-filtered true + + bcftools norm \ + -m+any ~{prefix}.raw.recal.pass.vcf.gz \ + --check-ref -f ~{ref_fasta} \ + -Oz \ + -o ~{prefix}.pass.merged.vcf.gz \ + --threads ${np} + + tabix -p vcf ~{prefix}.pass.merged.vcf.gz +done + + >>> + + output { + File output_vcf = "~{prefix}.pass.merged.vcf.gz" + File output_vcf_index = "~{prefix}.pass.merged.vcf.gz.tbi" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 4, + mem_gb: 32, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 0, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/sr-malaria-niare-pipeline:0.0.1" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} + diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 6552c2aec..4b83b5ccf 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -281,7 +281,7 @@ task GenotypeGVCFs { File ref_fasta_fai File ref_dict - String dbsnp_vcf + String? dbsnp_vcf String prefix @@ -294,6 +294,8 @@ task GenotypeGVCFs { Int disk_size = 1 + 4*ceil(size(input_gvcf_data, "GB")) + ref_size + db_snp_size + String dbsnp_vcf_arg = if defined(dbsnp_vcf) then "-D ~{dbsnp_vcf} " else "" + parameter_meta { input_gvcf_data: { help: "Either a single GVCF file or a GenomicsDB Tar file." } interval_list: { @@ -331,7 +333,7 @@ task GenotypeGVCFs { GenotypeGVCFs \ -R ~{ref_fasta} \ -O ~{prefix}.vcf.gz \ - -D ~{dbsnp_vcf} \ + ~{dbsnp_vcf_arg} \ -G StandardAnnotation \ --only-output-calls-starting-in-intervals \ -V ${INPUT_FILE} \ diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 1dcba6b7f..7f03840ac 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -2563,4 +2563,69 @@ task CreateIGVSession{ maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } +} + +task SplitContigToIntervals { + meta { + author: "Jonn Smith" + notes: "Splits the given contig into intervals of the given size." + } + + input { + File ref_dict + String contig + Int size = 200000 + + File ref_fasta + File ref_fasta_fai + File ref_dict + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 2 + + command <<< + set -euxo pipefail + + cat ~{ref_dict} | awk '{print $2,$3}' | grep '^SN' | sed -e 's@SN:@@' -e 's@LN:@@' | tr ' ' '\t' > genome.txt + grep "~{contig}" genome.txt > genome.contig.txt + + bedtools makewindows -g genome.contig.txt -w ~{size} > ~{contig}.~{size}bp_intervals.bed + + # Make individual bed files from each line: + while read line ; do + start=$(echo "${line}" | cut -d $'\t' -f 2) + end=$(echo "${line}" | cut -d $'\t' -f 3) + echo "${line}" > ~{contig}.${start}-${end}.single_interval.bed + done < ~{contig}.~{size}bp_intervals.bed + >>> + + output { + File full_bed_file = "~{contig}.~{size}bp_intervals.bed" + Array[File] individual_bed_files = glob("*.single_interval.bed") + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 2, + disk_gb: disk_size, + boot_disk_gb: 15, + preemptible_tries: 0, + max_retries: 1, + docker: "us.gcr.io/broad-dsp-lrma/lr-metrics:0.1.11" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } } \ No newline at end of file From 0cc4abd7851a1cbe1e64d015b8c28768dbf6c518 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 30 Nov 2023 15:31:47 -0500 Subject: [PATCH 256/297] Renaming a wdl. --- ...me_Pf_Niare_their_VQSR.wdl => SRWholeGenome_Pf_Niare_VQSR.wdl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename wdl/{SRWholeGenome_Pf_Niare_their_VQSR.wdl => SRWholeGenome_Pf_Niare_VQSR.wdl} (100%) diff --git a/wdl/SRWholeGenome_Pf_Niare_their_VQSR.wdl b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl similarity index 100% rename from wdl/SRWholeGenome_Pf_Niare_their_VQSR.wdl rename to wdl/SRWholeGenome_Pf_Niare_VQSR.wdl From b48b6691146ae4faa3044ef48f3a3c73ddbb0d1d Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 30 Nov 2023 15:37:41 -0500 Subject: [PATCH 257/297] Adding Niare et al. WDLs to dockstore. --- .dockstore.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.dockstore.yml b/.dockstore.yml index e14323241..8b333ef87 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -171,4 +171,20 @@ workflows: - name: PfalciparumDrugResistanceSummary subclass: wdl primaryDescriptorPath: /wdl/PfalciparumDrugResistanceSummary.wdl + testParameterFiles: +- name: SRWholeGenome_Pf_Niare_VQSR + subclass: wdl + primaryDescriptorPath: /wdl/SRWholeGenome_Pf_Niare_VQSR.wdl + testParameterFiles: +- name: SRWholeGenome_Pf_Niare_VETS + subclass: wdl + primaryDescriptorPath: /wdl/SRWholeGenome_Pf_Niare_VETS.wdl + testParameterFiles: +- name: SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VQSR + subclass: wdl + primaryDescriptorPath: /wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VQSR.wdl + testParameterFiles: +- name: SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VETS + subclass: wdl + primaryDescriptorPath: /wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VETS.wdl testParameterFiles: \ No newline at end of file From 061204dea3e616909bfdd2e86e7b7dd32f6828b4 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 30 Nov 2023 18:04:32 -0500 Subject: [PATCH 258/297] Removed manual monitoring instrumentation. --- wdl/SRJointCallGVCFsWithGenomicsDB.wdl | 3 +- wdl/TrainCnnFilters.wdl | 27 ------- wdl/tasks/AlignedMetrics.wdl | 9 --- wdl/tasks/DeepVariant.wdl | 10 --- wdl/tasks/FastQC.wdl | 9 --- wdl/tasks/Hail.wdl | 11 +-- wdl/tasks/HaplotypeCaller.wdl | 10 --- .../RemoveSingleOrganismContamination.wdl | 11 +-- wdl/tasks/SRJointGenotyping.wdl | 30 ------- wdl/tasks/SRUtils.wdl | 80 ------------------- wdl/tasks/Utils.wdl | 18 ----- wdl/tasks/VariantUtils.wdl | 70 ---------------- 12 files changed, 5 insertions(+), 283 deletions(-) diff --git a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl index 81d069681..6964c5315 100644 --- a/wdl/SRJointCallGVCFsWithGenomicsDB.wdl +++ b/wdl/SRJointCallGVCFsWithGenomicsDB.wdl @@ -314,7 +314,8 @@ workflow SRJointCallGVCFsWithGenomicsDB { ################################ # Finalize the regular output files: ############ - File keyfile = CreateHailMatrixTable.monitoring_log + + File keyfile = CreateHailMatrixTable.completion_file String recalibration_dir = outdir + "/recalibration_files" String recalibration_model_dir = outdir + "/recalibration_files/model" String recalibration_results_dir = outdir + "/recalibration_files/results" diff --git a/wdl/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl index 6e5480c34..58dbe0f90 100644 --- a/wdl/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -149,12 +149,6 @@ task Create1DReferenceTensors { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - gatk CNNVariantWriteTensors \ -R ~{ref_fasta} \ -V ~{vcf_input} \ @@ -168,12 +162,9 @@ task Create1DReferenceTensors { # No need to zip - the files are .hd5 formatted: tar -cf ~{prefix}_1D_tensor_dir.tar ~{prefix}_1D_tensor_dir - - kill $monitoring_pid >>> output { - File monitoring_log = "resources.log" File tensor_dir_tar = "~{prefix}_1D_tensor_dir.tar" } @@ -236,12 +227,6 @@ task Create2DReadTensors { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - gatk CNNVariantWriteTensors \ -R ~{ref_fasta} \ -V ~{vcf_input} \ @@ -288,12 +273,9 @@ CODE # No need to zip - the files are .hd5 formatted: tar -cf ~{prefix}_2D_tensor_dir.tar ~{prefix}_2D_tensor_dir - - kill $monitoring_pid >>> output { - File monitoring_log = "resources.log" File tensor_dir_tar = "~{prefix}_2D_tensor_dir.tar" } @@ -351,12 +333,6 @@ task TrainCnn { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # Must pre-process the given tensor_tars into a single folder: mkdir tensors cd tensors @@ -413,12 +389,9 @@ task TrainCnn { -model-name ~{prefix}_CNN_~{tensor_type}_model \ ls -la - - kill $monitoring_pid >>> output { - File monitoring_log = "resources.log" File model_hd5 = "~{prefix}_CNN_~{tensor_type}_model.hd5" File model_json = "~{prefix}_CNN_~{tensor_type}_model.json" } diff --git a/wdl/tasks/AlignedMetrics.wdl b/wdl/tasks/AlignedMetrics.wdl index 86e0826ce..2b8e46c82 100644 --- a/wdl/tasks/AlignedMetrics.wdl +++ b/wdl/tasks/AlignedMetrics.wdl @@ -395,12 +395,6 @@ task SamStatsMap { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') samtools stats -@${np} ~{bam} > ~{basename}.sam_stats.txt @@ -412,14 +406,11 @@ task SamStatsMap { sed 's/[\(\)]//g' | \ sed 's/[[:space:]]*#.*//' \ > map.txt - - kill $monitoring_pid >>> output { File sam_stats = "~{basename}.sam_stats.txt" Map[String, Float] stats_map = read_map("map.txt") - File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/DeepVariant.wdl b/wdl/tasks/DeepVariant.wdl index 59243c14b..c614f051d 100644 --- a/wdl/tasks/DeepVariant.wdl +++ b/wdl/tasks/DeepVariant.wdl @@ -84,12 +84,6 @@ task DV { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) mkdir -p "~{output_root}" @@ -104,8 +98,6 @@ task DV { find "~{output_root}/" -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g' \ > "~{output_root}/dir_structure.txt" - - kill $monitoring_pid >>> output { @@ -118,8 +110,6 @@ task DV { File gVCF_tbi = "~{output_root}/~{prefix}.g.vcf.gz.tbi" File visual_report_html = "~{output_root}/~{prefix}.visual_report.html" - - File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/FastQC.wdl b/wdl/tasks/FastQC.wdl index 40c765663..52c5a0bf4 100644 --- a/wdl/tasks/FastQC.wdl +++ b/wdl/tasks/FastQC.wdl @@ -17,12 +17,6 @@ task FastQC { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) fastqc -t $num_core --extract ~{bam} @@ -54,8 +48,6 @@ task FastQC { awk '{ a[i++]=$1; } END { x=int((i+1)/2); if (x < (i+1)/2) print (a[x-1]+a[x])/2; else print a[x-1]; }') echo $median_qual | awk '{ print "median_qual\t" $1 }' >> map.txt - - kill $monitoring_pid >>> output { @@ -63,7 +55,6 @@ task FastQC { File stats = "fastqc_data.txt" File report = "fastqc_report.html" - File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/Hail.wdl b/wdl/tasks/Hail.wdl index a05e7d393..2a9d8fc98 100644 --- a/wdl/tasks/Hail.wdl +++ b/wdl/tasks/Hail.wdl @@ -26,12 +26,6 @@ task ConvertToHailMT { command <<< set -x - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - python3 <>> output { String gcs_path = "~{outdir}/~{prefix}.mt" - - File monitoring_log = "resources.log" + File completion_file = "completion_file" } ######################### diff --git a/wdl/tasks/HaplotypeCaller.wdl b/wdl/tasks/HaplotypeCaller.wdl index f1fd8554f..7f237884d 100644 --- a/wdl/tasks/HaplotypeCaller.wdl +++ b/wdl/tasks/HaplotypeCaller.wdl @@ -183,12 +183,6 @@ task HaplotypeCaller_GATK4_VCF { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # We need at least 1 GB of available memory outside of the Java heap in order to execute native code, thus, limit # Java's memory by the total memory minus 1 GB. We need to compute the total memory as it might differ from # memory_size_gb because of Cromwell's retry with more memory feature. @@ -227,16 +221,12 @@ task HaplotypeCaller_GATK4_VCF { # Cromwell doesn't like optional task outputs, so we have to touch this file. touch ~{prefix}.bamout.bam - - kill $monitoring_pid >>> output { File output_vcf = "~{output_file_name}" File output_vcf_index = "~{output_file_name}.tbi" File bamout = "~{prefix}.bamout.bam" - - File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/RemoveSingleOrganismContamination.wdl b/wdl/tasks/RemoveSingleOrganismContamination.wdl index 88b637eee..1b224b2b5 100644 --- a/wdl/tasks/RemoveSingleOrganismContamination.wdl +++ b/wdl/tasks/RemoveSingleOrganismContamination.wdl @@ -163,7 +163,7 @@ workflow RemoveSingleOrganismContamination { ############################################ # Chosen because it's a relatively small file. - File keyfile = t_012_CreateFastqFromDecontaminatedReads.monitoring_log + File keyfile = t_012_CreateFastqFromDecontaminatedReads.fq_unpaired call FF.FinalizeToFile as t_013_FinalizeContaminatedBam { input: outdir = outdir, file = t_011_SortContaminatedReads.sorted_bam, keyfile = keyfile } call FF.FinalizeToFile as t_014_FinalizeDecontaminatedFq1 { input: outdir = outdir, file = t_012_CreateFastqFromDecontaminatedReads.fq_end1, keyfile = keyfile } @@ -268,22 +268,13 @@ task SortBamWithoutIndexing { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) samtools sort ~{extra_args} -@$num_core -o ~{prefix}.bam ~{input_bam} - - kill $monitoring_pid >>> output { File sorted_bam = "~{prefix}.bam" - File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/SRJointGenotyping.wdl b/wdl/tasks/SRJointGenotyping.wdl index 4b83b5ccf..936b79a83 100644 --- a/wdl/tasks/SRJointGenotyping.wdl +++ b/wdl/tasks/SRJointGenotyping.wdl @@ -129,12 +129,6 @@ task ImportGVCFs { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # Make sure that the output directory does not exist: [ -e ~{prefix} ] && rm -rf ~{prefix} @@ -161,14 +155,10 @@ task ImportGVCFs { --consolidate tar -cf ~{prefix}.genomicsDB.tar ~{prefix}.genomicsDB - - kill $monitoring_pid >>> output { File output_genomicsdb = "~{prefix}.genomicsDB.tar" - - File monitoring_log = "resources.log" } ######################### @@ -220,12 +210,6 @@ task ReblockGVCF { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - gatk --java-options "-Xms3000m -Xmx3000m" \ ReblockGVCF \ -R ~{ref_fasta} \ @@ -236,15 +220,11 @@ task ReblockGVCF { ~{annotations_to_keep_command} \ ~{"--tree-score-threshold-to-no-call " + tree_score_cutoff} \ -O ~{prefix}.reblocked.g.vcf.gz - - kill $monitoring_pid >>> output { File output_gvcf = "~{prefix}.reblocked.g.vcf.gz" File output_gvcf_index = "~{prefix}.reblocked.g.vcf.gz.tbi" - - File monitoring_log = "resources.log" } ######################### @@ -306,12 +286,6 @@ task GenotypeGVCFs { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # We must determine if our input variants are in a genomicsdb file or in a VCF. # The easiest way is to see if the input is a .tar file: @@ -343,15 +317,11 @@ task GenotypeGVCFs { # Removed for now: # -G AS_StandardAnnotation - - kill $monitoring_pid >>> output { File output_vcf = "~{prefix}.vcf.gz" File output_vcf_index = "~{prefix}.vcf.gz.tbi" - - File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/SRUtils.wdl b/wdl/tasks/SRUtils.wdl index 12f33ffb7..2862aabbe 100644 --- a/wdl/tasks/SRUtils.wdl +++ b/wdl/tasks/SRUtils.wdl @@ -15,27 +15,18 @@ task BamToFq { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - samtools sort -n ~{bam} | samtools bam2fq \ -n \ -s /dev/null \ -1 ~{prefix}.end1.fq.gz \ -2 ~{prefix}.end2.fq.gz \ -0 ~{prefix}.unpaired.fq.gz - - kill $monitoring_pid >>> output { File fq_end1 = "~{prefix}.end1.fq.gz" File fq_end2 = "~{prefix}.end2.fq.gz" File fq_unpaired = "~{prefix}.unpaired.fq.gz" - File monitoring_log = "resources.log" } ######################### @@ -189,12 +180,6 @@ task BwaMem2 { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # Make sure we use all our proocesors: np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') if [[ ${np} -gt 2 ]] ; then @@ -220,13 +205,10 @@ task BwaMem2 { ~{fq_end1} \ ~{fq_end2} | \ samtools view -1 - > ~{prefix}.bam - - kill $monitoring_pid >>> output { File bam = "~{prefix}.bam" - File monitoring_log = "resources.log" } ######################### @@ -274,12 +256,6 @@ task MergeBamAlignment { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # Make sure we use all our proocesors: np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') let np=${np}-1 @@ -311,13 +287,10 @@ task MergeBamAlignment { ALIGNER_PROPER_PAIR_FLAGS=true \ UNMAP_CONTAMINANT_READS=true \ ADD_PG_TAG_TO_READS=false - - kill $monitoring_pid >>> output { File bam = "~{prefix}.bam" - File monitoring_log = "resources.log" } ######################### @@ -367,13 +340,6 @@ task MarkDuplicates { # While query-grouped isn't actually query-sorted, it's good enough for MarkDuplicates with ASSUME_SORT_ORDER="queryname" command <<< - - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - tot_mem_mb=$(free -m | grep '^Mem' | awk '{print $2}') let java_memory_size_mb=${tot_mem_mb}-5120 @@ -389,12 +355,9 @@ task MarkDuplicates { ASSUME_SORT_ORDER="queryname" \ CLEAR_DT="false" \ ADD_PG_TAG_TO_READS=false - - kill $monitoring_pid >>> output { - File monitoring_log = "resources.log" File bam = "~{prefix}.bam" File metrics = "~{prefix}.metrics.txt" } @@ -455,11 +418,6 @@ task BaseRecalibrator { } command { - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! gatk --java-options "-XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10 -XX:+PrintFlagsFinal \ -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCDetails \ @@ -471,7 +429,6 @@ task BaseRecalibrator { -O ~{prefix}.txt \ --known-sites ~{known_sites_vcf} - kill $monitoring_pid } ######################### RuntimeAttr default_attr = object { @@ -495,7 +452,6 @@ task BaseRecalibrator { } output { File recalibration_report = "~{prefix}.txt" - File monitoring_log = "resources.log" } } @@ -536,12 +492,6 @@ task ApplyBQSR { command <<< - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - gatk --java-options "-XX:+PrintFlagsFinal -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps \ -XX:+PrintGCDetails -Xloggc:gc_log.log \ -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10 -Dsamjdk.compression_level=~{compression_level} -Xms8192m -Xmx~{java_memory_size_mb}m" \ @@ -562,8 +512,6 @@ task ApplyBQSR { np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') samtools index -@${np} ~{prefix}.bam - - kill $monitoring_pid >>> ######################### RuntimeAttr default_attr = object { @@ -588,7 +536,6 @@ task ApplyBQSR { output { File recalibrated_bam = "~{prefix}.bam" File recalibrated_bai = "~{prefix}.bam.bai" - File monitoring_log = "resources.log" } } @@ -609,12 +556,6 @@ task RevertSam { # https://gatk.broadinstitute.org/hc/en-us/articles/4403687183515--How-to-Generate-an-unmapped-BAM-from-FASTQ-or-aligned-BAM command { - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - java -Dsamjdk.compression_level=~{compression_level} -Xms~{java_memory_size_mb}m -jar /usr/picard/picard.jar \ RevertSam \ INPUT=~{input_bam} \ @@ -630,13 +571,10 @@ task RevertSam { RESTORE_ORIGINAL_QUALITIES=true \ REMOVE_DUPLICATE_INFORMATION=true \ REMOVE_ALIGNMENT_INFORMATION=true - - kill $monitoring_pid } output { File bam = "~{prefix}.bam" - File monitoring_log = "resources.log" } ######################### @@ -679,24 +617,16 @@ task ComputeBamStats { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - python3 /python/compute_sr_stats.py \ ~{qual_thresh_arg}~{default="" sep=" -q " qual_threshold} \ ~{bam_file} \ | tee ~{stats_file_name} - kill $monitoring_pid >>> output { Map[String, Float] results = read_map(stats_file_name) File results_file = stats_file_name - File monitoring_log = "resources.log" } ######################### @@ -839,12 +769,6 @@ task RevertBaseQualities { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # Check if the input bam has been run through `ApplyBQSR`. # If not, we can just return the input bam. samtools view -H ~{bam} | grep '^@PG' > header.pg.txt @@ -868,15 +792,11 @@ task RevertBaseQualities { cp ~{bai} ~{prefix}.bam.bai fi fi - - kill $monitoring_pid >>> output { File bam_out = "~{prefix}.bam" File bai_out = "~{prefix}.bam.bai" - - File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 7f03840ac..5d088966c 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -177,24 +177,15 @@ task SortBam { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - curl https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh > monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - num_core=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) samtools sort ~{extra_args} -@$num_core -o ~{prefix}.bam ~{input_bam} samtools index ~{prefix}.bam - - kill $monitoring_pid >>> output { File sorted_bam = "~{prefix}.bam" File sorted_bai = "~{prefix}.bam.bai" - File monitoring_log = "resources.log" } ######################### @@ -1899,24 +1890,15 @@ task ComputeGenomeLength { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - samtools dict ~{fasta} | \ grep '^@SQ' | \ awk '{ print $3 }' | \ sed 's/LN://' | \ awk '{ sum += $1 } END { print sum }' > length.txt - - kill $monitoring_pid >>> output { Float length = read_float("length.txt") - File monitoring_log = "resources.log" } ######################### diff --git a/wdl/tasks/VariantUtils.wdl b/wdl/tasks/VariantUtils.wdl index aeee71f8b..0f392a362 100644 --- a/wdl/tasks/VariantUtils.wdl +++ b/wdl/tasks/VariantUtils.wdl @@ -887,12 +887,6 @@ task IndelsVariantRecalibrator { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # We need to generate resource strings from the input arrays. # First we check that the arrays are the same length: if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ @@ -937,8 +931,6 @@ task IndelsVariantRecalibrator { --output-model ~{prefix}.model.report \ --max-gaussians ~{max_gaussians} \ ${resource_flags} - - kill $monitoring_pid >>> output { @@ -946,8 +938,6 @@ task IndelsVariantRecalibrator { File recalibration_index = "~{prefix}.recal.idx" File tranches = "~{prefix}.tranches" File model_report = "~{prefix}.model.report" - - File monitoring_log = "resources.log" } ######################### @@ -1020,12 +1010,6 @@ task SNPsVariantRecalibratorCreateModel { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # We need to generate resource strings from the input arrays. # First we check that the arrays are the same length: if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ @@ -1071,8 +1055,6 @@ task SNPsVariantRecalibratorCreateModel { --output-model ~{prefix}.model.report \ --max-gaussians ~{max_gaussians} \ ${resource_flags} - - kill $monitoring_pid >>> output { @@ -1080,8 +1062,6 @@ task SNPsVariantRecalibratorCreateModel { File recalibration_index = "~{prefix}.recal.idx" File tranches = "~{prefix}.tranches" File model_report = "~{prefix}.model.report" - - File monitoring_log = "resources.log" } ######################### @@ -1136,12 +1116,6 @@ task ApplyVqsr { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # Get amount of memory to use: mem_available=$(free -m | grep '^Mem' | awk '{print $2}') let mem_start=${mem_available}-2000 @@ -1168,15 +1142,11 @@ task ApplyVqsr { --truth-sensitivity-filter-level ~{snp_filter_level} \ --create-output-variant-index true \ -mode SNP - - kill $monitoring_pid >>> output { File recalibrated_vcf = "~{prefix}.recalibrated.vcf.gz" File recalibrated_vcf_index = "~{prefix}.recalibrated.vcf.gz.tbi" - - File monitoring_log = "resources.log" } ######################### @@ -1217,12 +1187,6 @@ task SelectVariants { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # Get amount of memory to use: mem_available=$(free -m | grep '^Mem' | awk '{print $2}') let mem_start=${mem_available}-2000 @@ -1233,15 +1197,11 @@ task SelectVariants { --exclude-filtered \ -V ~{vcf} \ -O ~{prefix}.vcf.gz - - kill $monitoring_pid >>> output { File vcf_out = "~{prefix}.vcf.gz" File vcf_out_index = "~{prefix}.vcf.gz.tbi" - - File monitoring_log = "resources.log" } ######################### @@ -1665,12 +1625,6 @@ task ExtractVariantAnnotations { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # We need to generate resource strings from the input arrays. # First we check that the arrays are the same length: if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ @@ -1707,8 +1661,6 @@ task ExtractVariantAnnotations { --maximum-number-of-unlabeled-variants ~{max_unlabeled_variants} \ ${resource_flags} \ -O ~{prefix}_extracted_annotations_~{mode} - - kill $monitoring_pid >>> output { @@ -1717,8 +1669,6 @@ task ExtractVariantAnnotations { File sites_only_vcf_index = "~{prefix}_extracted_annotations_~{mode}.vcf.gz.tbi" File? unlabeled_annotation_hdf5 = "~{prefix}_extracted_annotations_~{mode}.unlabeled.annot.hdf5" - - File monitoring_log = "resources.log" } ######################### @@ -1775,12 +1725,6 @@ task TrainVariantAnnotationsModel { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # Get amount of memory to use: mem_available=$(free -g | grep '^Mem' | awk '{print $2}') let mem_start=${mem_available}-2 @@ -1794,8 +1738,6 @@ task TrainVariantAnnotationsModel { ~{"--unlabeled-annotations-hdf5 " + unlabeled_annotation_hdf5} \ ~{cal_sense_arg} \ -O ~{prefix}_train_~{mode} - - kill $monitoring_pid >>> output { @@ -1805,8 +1747,6 @@ task TrainVariantAnnotationsModel { File? unlabeled_positive_model_scores = "~{prefix}_train_~{mode}.~{mode_lower}.unlabeledScores.hdf5" File? calibration_set_scores = "~{prefix}_train_~{mode}.~{mode_lower}.calibrationScores.hdf5" File? negative_model_scorer_pickle = "~{prefix}_train_~{mode}.~{mode_lower}.negative.scorer.pkl" - - File monitoring_log = "resources.log" } ######################### @@ -1884,12 +1824,6 @@ task ScoreVariantAnnotations { command <<< set -euxo pipefail - export MONITOR_MOUNT_POINT="/cromwell_root" - wget https://raw.githubusercontent.com/broadinstitute/long-read-pipelines/jts_kvg_sp_malaria/scripts/monitor/legacy/vm_local_monitoring_script.sh -O monitoring_script.sh - chmod +x monitoring_script.sh - ./monitoring_script.sh &> resources.log & - monitoring_pid=$! - # We need to generate resource strings from the input arrays. # First we check that the arrays are the same length: if [[ ~{length(known_reference_variants)} -ne ~{length(known_reference_variants_identifier)} ]] || \ @@ -1937,8 +1871,6 @@ task ScoreVariantAnnotations { --resource:extracted,extracted=true ~{sites_only_extracted_vcf} \ --${mode_lower}-calibration-sensitivity-threshold ~{calibration_sensitivity_threshold} \ -O ~{prefix}_scored - - kill $monitoring_pid >>> output { @@ -1947,8 +1879,6 @@ task ScoreVariantAnnotations { File? annotations_hdf5 = "~{prefix}_scored.annot.hdf5" File? scores_hdf5 = "~{prefix}_scored.scores.hdf5" - - File monitoring_log = "resources.log" } ######################### From 0cfad4ab955124841cec5211b41ed126086ae58d Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 30 Nov 2023 18:09:10 -0500 Subject: [PATCH 259/297] Fixing typo. --- wdl/SRWholeGenome_Pf_Niare_VQSR.wdl | 6 ++++-- wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl index 7f6ec0926..495492ddb 100644 --- a/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl +++ b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl @@ -137,6 +137,7 @@ workflow SRWholeGenome_Pf_Niare_VQSR { input_vcf = NormalizeVcfPreVqsr.output_vcf, input_vcf_index = NormalizeVcfPreVqsr.output_vcf_index, recal_file = VariantRecalibratorIndel.recalibration, + recal_file_index = VariantRecalibratorIndel.recalibration_index, recal_tranches = VariantRecalibratorIndel.tranches, prefix = participant_name + "." + contig_for_small_var + ".norm", } @@ -157,8 +158,9 @@ workflow SRWholeGenome_Pf_Niare_VQSR { input: input_vcf = ApplyVqsrIndel.output_vcf, input_vcf_index = ApplyVqsrIndel.output_vcf_index, - recal_file = VariantRecalibratorIndel.recalibration, - recal_tranches = VariantRecalibratorIndel.tranches, + recal_file = VariantRecalibratorSnp.recalibration, + recal_file_index = VariantRecalibratorSnp.recalibration_index, + recal_tranches = VariantRecalibratorSnp.tranches, prefix = participant_name + "." + contig_for_small_var + ".norm.indel_recal", } diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index b36969853..abf9ed833 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -771,6 +771,7 @@ task ApplyVqsrSnp { File input_vcf_index File recal_file + File recal_file_index File recal_tranches String prefix From fa0a79dc6c45864184443825f27fd4ebd22a542f Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 13 Nov 2023 21:29:27 +0000 Subject: [PATCH 260/297] Fixing bugs in `sr-malaria-niare-pipeline` dockerfile. --- docker/sr-malaria-niare-pipeline/Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docker/sr-malaria-niare-pipeline/Dockerfile b/docker/sr-malaria-niare-pipeline/Dockerfile index 25fce4796..76e6ef29a 100644 --- a/docker/sr-malaria-niare-pipeline/Dockerfile +++ b/docker/sr-malaria-niare-pipeline/Dockerfile @@ -33,7 +33,7 @@ RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.c && apt-get install google-cloud-sdk -y # Additional Dependencies: -RUN apt-get install -y curl wget datamash pkg-config zip unzip +RUN apt install -y curl wget datamash pkg-config zip unzip default-jre python ######################################################################################################################## # SOFTWARE: @@ -118,8 +118,9 @@ RUN wget http://www.usadellab.org/cms/uploads/supplementary/Trimmomatic/Trimmoma mkdir -p /opt && \ unzip Trimmomatic-0.36.zip && \ mv Trimmomatic-0.36 /opt/ && \ - echo -ne "#!/usr/local/env bash\n\njava -jar /opt/Trimmomatic-0.36/trimmomatic-0.36.jar $@\n\n" > /usr/local/bin/TrimmomaticPE && \ - chmod +x /usr/local/bin/TrimmomaticPE && \ + echo "#!/usr/bin/env bash\n\njava -jar /opt/Trimmomatic-0.36/trimmomatic-0.36.jar PE $@\n\n" > /usr/local/bin/TrimmomaticPE && \ + echo "#!/usr/bin/env bash\n\njava -jar /opt/Trimmomatic-0.36/trimmomatic-0.36.jar SE $@\n\n" > /usr/local/bin/TrimmomaticSE && \ + chmod +x /usr/local/bin/Trimmomatic*E && \ rm Trimmomatic-0.36.zip ######################################################################################################################## From 3ff4efa105a97fe4b7ecf239b45f92e534a36971 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 1 Dec 2023 16:09:05 +0000 Subject: [PATCH 261/297] Cleaned up docker image for niare paper. --- docker/sr-malaria-niare-pipeline/Dockerfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docker/sr-malaria-niare-pipeline/Dockerfile b/docker/sr-malaria-niare-pipeline/Dockerfile index 76e6ef29a..3b08549ad 100644 --- a/docker/sr-malaria-niare-pipeline/Dockerfile +++ b/docker/sr-malaria-niare-pipeline/Dockerfile @@ -127,6 +127,15 @@ RUN wget http://www.usadellab.org/cms/uploads/supplementary/Trimmomatic/Trimmoma ######################################################################################################################## ######################################################################################################################## +# Other utilities: +RUN apt-get clean +RUN apt install -y vim emacs nano +RUN apt-get clean + +######################################################################################################################## +######################################################################################################################## +######################################################################################################################## + # Might not need these: # gsl/2.7.1: From d1bb297f624a664c2a017412c9b1f6bf6f821dcc Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Fri, 1 Dec 2023 22:28:46 -0500 Subject: [PATCH 262/297] increasing resources for niare hc --- wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index abf9ed833..edc873ba6 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -121,8 +121,7 @@ task HaplotypeCaller_NIARE_GATK4_VCF { String output_file_name = prefix + ".g.vcf.gz" - Float ref_size = size(ref_fasta, "GiB") + size(ref_fasta_index, "GiB") + size(ref_dict, "GiB") - Int disk_size = 2*ceil(((size(input_bam, "GiB") + 30)) + ref_size) + 20 + Int disk_size = 2*ceil(size([ref_fasta, ref_fasta_index, ref_dict, input_bam], "GiB") + 50) parameter_meta { input_bam: { localization_optional: true } @@ -195,8 +194,8 @@ task HaplotypeCaller_NIARE_GATK4_VCF { ######################### RuntimeAttr default_attr = object { - cpu_cores: 2, - mem_gb: 16, + cpu_cores: 4, + mem_gb: 32, disk_gb: disk_size, boot_disk_gb: 15, preemptible_tries: 1, From 46c8aa315e3bbdb7cfec4fe173b0b85942f5669f Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sat, 2 Dec 2023 10:02:25 -0500 Subject: [PATCH 263/297] Fixing missing parameters in niare wdls --- wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index edc873ba6..33387fcb9 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -561,6 +561,10 @@ task VariantRecalibratorIndel { # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, # which do not rely on the output format of the `free` command. + available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') + let java_memory_size_mb=available_memory_mb-1024 + echo Total available memory: ${available_memory_mb} MB >&2 + echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ VariantRecalibrator \ @@ -642,6 +646,11 @@ task VariantRecalibratorSnp { # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, # which do not rely on the output format of the `free` command. + available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') + let java_memory_size_mb=available_memory_mb-1024 + echo Total available memory: ${available_memory_mb} MB >&2 + echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ VariantRecalibrator \ -R ~{ref_fasta} \ @@ -719,6 +728,11 @@ task ApplyVqsrIndel { # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, # which do not rely on the output format of the `free` command. + available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') + let java_memory_size_mb=available_memory_mb-1024 + echo Total available memory: ${available_memory_mb} MB >&2 + echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ ApplyVQSR \ -V ~{input_vcf} \ @@ -790,6 +804,11 @@ task ApplyVqsrSnp { # Note: In the future this should be done using Cromwell's ${MEM_SIZE} and ${MEM_UNIT} environment variables, # which do not rely on the output format of the `free` command. + available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') + let java_memory_size_mb=available_memory_mb-1024 + echo Total available memory: ${available_memory_mb} MB >&2 + echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ ApplyVQSR \ -V ~{input_vcf} \ @@ -864,6 +883,11 @@ task MergeMultiAllelicSitesPostRecalibration { # Make sure we use all our processors: np=$(cat /proc/cpuinfo | grep ^processor | tail -n1 | awk '{print $NF+1}') + available_memory_mb=$(free -m | awk '/^Mem/ {print $2}') + let java_memory_size_mb=available_memory_mb-1024 + echo Total available memory: ${available_memory_mb} MB >&2 + echo Memory reserved for Java: ${java_memory_size_mb} MB >&2 + gatk --java-options "-Xmx${java_memory_size_mb}m -Xms${java_memory_size_mb}m -XX:GCTimeLimit=50 -XX:GCHeapFreeLimit=10" \ SelectVariants \ -R ~{ref_fasta} \ From dc9b902991cc16831eefdf03a72421ccc490a3f4 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 3 Dec 2023 17:43:12 -0500 Subject: [PATCH 264/297] Removed R script from niare haplotypecaller. --- wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index 33387fcb9..d715110ca 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -578,7 +578,7 @@ task VariantRecalibratorIndel { -O ~{prefix}.indel_recal \ --output-model ~{prefix}.indel.model.report \ --tranches-file ~{prefix}.raw.indel.tranches \ - --rscript-file ~{prefix}.raw.indel.plots.R +# --rscript-file ~{prefix}.raw.indel.plots.R >>> @@ -663,7 +663,7 @@ task VariantRecalibratorSnp { -O ~{prefix}.snp_recal \ --tranches-file ~{prefix}.raw.snp.tranches \ --output-model ~{prefix}.snp.model.report \ - --rscript-file ~{prefix}.raw.snp.plots.R +# --rscript-file ~{prefix}.raw.snp.plots.R >>> From bff5cdf03a485c9c716070a5b7e276b3fbb34692 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 3 Dec 2023 18:04:07 -0500 Subject: [PATCH 265/297] Fixing outputs in Niare VETS workflow. --- wdl/SRWholeGenome_Pf_Niare_VETS.wdl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wdl/SRWholeGenome_Pf_Niare_VETS.wdl b/wdl/SRWholeGenome_Pf_Niare_VETS.wdl index e146c1883..d5e06ff58 100644 --- a/wdl/SRWholeGenome_Pf_Niare_VETS.wdl +++ b/wdl/SRWholeGenome_Pf_Niare_VETS.wdl @@ -245,8 +245,6 @@ workflow SRWholeGenome_Pf_Niare_VETS { # Finalize the reclibrated / filtered variants: call FF.FinalizeToFile as FinalizeHCRescoredVcf { input: outdir = smalldir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf } call FF.FinalizeToFile as FinalizeHCRescoredTbi { input: outdir = smalldir, keyfile = keyfile, file = ScoreIndelVariantAnnotations.scored_vcf_index } - call FF.FinalizeToFile as FinalizeHCRescoredFilteredVcf { input: outdir = smalldir, keyfile = keyfile, file = RemoveFilteredVariants.vcf_out } - call FF.FinalizeToFile as FinalizeHCRescoredFilteredTbi { input: outdir = smalldir, keyfile = keyfile, file = RemoveFilteredVariants.vcf_out_index } output { Boolean successfully_processed = true @@ -261,7 +259,5 @@ workflow SRWholeGenome_Pf_Niare_VETS { File? hc_raw_tbi = FinalizeRawHCTbi.gcs_path File? hc_rescored_vcf = FinalizeHCRescoredVcf.gcs_path File? hc_rescored_tbi = FinalizeHCRescoredVcf.gcs_path - File? hc_rescored_hard_filtered_vcf = FinalizeHCRescoredFilteredVcf.gcs_path - File? hc_rescored_hard_filtered_tbi = FinalizeHCRescoredFilteredTbi.gcs_path } } From 28cde270750d53b5f944a0dbb61c1334bf9fd0d8 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 3 Dec 2023 18:29:16 -0500 Subject: [PATCH 266/297] Removed R script output for niare vqsr --- wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 2 -- 1 file changed, 2 deletions(-) diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index d715110ca..9670e0d91 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -587,7 +587,6 @@ task VariantRecalibratorIndel { File recalibration_index = "~{prefix}.indel_recal.idx" File tranches = "~{prefix}.raw.indel.tranches" File model_report = "~{prefix}.indel.model.report" - File r_script_file = "~{prefix}.raw.indel.plots.R" } ######################### @@ -672,7 +671,6 @@ task VariantRecalibratorSnp { File recalibration_index = "~{prefix}.snp_recal.idx" File tranches = "~{prefix}.raw.snp.tranches" File model_report = "~{prefix}.snp.model.report" - File r_script_file = "~{prefix}.raw.snp.plots.R" } ######################### From f94642541b198d96734e2b3b866559cc4f9da97c Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Sun, 3 Dec 2023 19:37:22 -0500 Subject: [PATCH 267/297] Fixing syntax error. --- wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 1 - 1 file changed, 1 deletion(-) diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index 9670e0d91..af5a6b5c3 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -901,7 +901,6 @@ task MergeMultiAllelicSitesPostRecalibration { --threads ${np} tabix -p vcf ~{prefix}.pass.merged.vcf.gz -done >>> From 54c6fefbf374bf849ed188c85ac01ad3e876878b Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 4 Dec 2023 13:00:53 -0500 Subject: [PATCH 268/297] Fixing logic error in Niare VQSR pipeline. --- wdl/SRWholeGenome_Pf_Niare_VQSR.wdl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl index 495492ddb..fbaca3f02 100644 --- a/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl +++ b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl @@ -110,10 +110,18 @@ workflow SRWholeGenome_Pf_Niare_VQSR { scatter (c in SmallVariantsScatterPrep.chrs) { String contig_for_small_var = c[0] + call VARUTIL.SubsetVCF as GetHcCallsForContig { + input: + vcf_gz = RenameRawHcVcf.new_sample_name_vcf, + vcf_tbi = RenameRawHcVcf.new_sample_name_vcf_index, + locus = contig_for_small_var, + prefix = participant_name + "." + contig_for_small_var, + } + call Niare_HC.NormalizeVcfSplittingMultiallelics as NormalizeVcfPreVqsr { input: - input_vcf = RenameRawHcVcf.new_sample_name_vcf, - input_vcf_index = RenameRawHcVcf.new_sample_name_vcf_index, + input_vcf = GetHcCallsForContig.subset_vcf, + input_vcf_index = GetHcCallsForContig.subset_tbi, ref_fasta = ref_map['fasta'], ref_fasta_fai = ref_map['fai'], ref_dict = ref_map['dict'], From 932c3bce89a2eec6ab1c1e699ef2c56a7e8e3add Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 4 Dec 2023 13:51:25 -0500 Subject: [PATCH 269/297] Fixing issue where variants in Niare pipeline would be duplicated. --- wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index af5a6b5c3..859b76bf5 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -51,7 +51,7 @@ workflow CallVariantsWithHaplotypeCaller { ref_fasta = ref_fasta, ref_fasta_index = ref_fasta_fai, ref_dict = ref_dict, - interval_list = vcf_calling_interval_list + interval_list = contig_for_small_var } } From 34296443d4751bab8785318fd10880e6da08a1a0 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 4 Dec 2023 16:22:26 -0500 Subject: [PATCH 270/297] Fixing bad input in Niare workflows. --- wdl/SRWholeGenome_Pf_Niare_VETS.wdl | 1 - wdl/SRWholeGenome_Pf_Niare_VQSR.wdl | 1 - wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 1 - 3 files changed, 3 deletions(-) diff --git a/wdl/SRWholeGenome_Pf_Niare_VETS.wdl b/wdl/SRWholeGenome_Pf_Niare_VETS.wdl index d5e06ff58..1b6fb34ce 100644 --- a/wdl/SRWholeGenome_Pf_Niare_VETS.wdl +++ b/wdl/SRWholeGenome_Pf_Niare_VETS.wdl @@ -87,7 +87,6 @@ workflow SRWholeGenome_Pf_Niare_VETS { ref_fasta_fai = ref_map['fai'], ref_dict = ref_map['dict'], - vcf_calling_interval_list = vcf_calling_interval_list, genotype_gvcfs_intervals = genotype_gvcfs_intervals, prefix = participant_name + ".haplotype_caller", diff --git a/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl index fbaca3f02..320f302f8 100644 --- a/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl +++ b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl @@ -69,7 +69,6 @@ workflow SRWholeGenome_Pf_Niare_VQSR { ref_fasta_fai = ref_map['fai'], ref_dict = ref_map['dict'], - vcf_calling_interval_list = vcf_calling_interval_list, genotype_gvcfs_intervals = genotype_gvcfs_intervals, prefix = participant_name + ".haplotype_caller", diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index 859b76bf5..46a2cf3d9 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -22,7 +22,6 @@ workflow CallVariantsWithHaplotypeCaller { File ref_fasta_fai File ref_dict - File vcf_calling_interval_list File genotype_gvcfs_intervals Boolean call_vars_on_mitochondria = false From 00f01c1cc8d6c5db96e7bbb5e47768e10ffa0ae4 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 5 Dec 2023 12:57:08 -0500 Subject: [PATCH 271/297] Fixing file / string mixup in Niare HC. --- wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index 46a2cf3d9..8555a010e 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -113,7 +113,7 @@ task HaplotypeCaller_NIARE_GATK4_VCF { File ref_fasta File ref_fasta_index - File interval_list + String interval_list RuntimeAttr? runtime_attr_override } From 94a7bbe0418e2d8b70e63caac029a5d0d095b363 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 6 Dec 2023 11:28:19 -0500 Subject: [PATCH 272/297] Fixing mito contig name in Niare VQSR workflow. --- wdl/SRWholeGenome_Pf_Niare_VQSR.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl index 320f302f8..e2eb97c5e 100644 --- a/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl +++ b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl @@ -30,8 +30,8 @@ workflow SRWholeGenome_Pf_Niare_VQSR { File vqsr_sites_vcf_index Boolean call_vars_on_mitochondria = false - String mito_contig = "chrM" - Array[String] contigs_names_to_ignore = ["RANDOM_PLACEHOLDER_VALUE"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! + String mito_contig = "Pf3D7_MIT_v3" + Array[String] contigs_names_to_ignore = ["Pf3D7_API_v3"] ## Required for ignoring any filtering - this is kind of a hack - TODO: fix the task! } Map[String, String] ref_map = read_map(ref_map_file) From 39fa72ad2aafc981bf76144f0feb880f2b5191e9 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 6 Dec 2023 23:57:07 -0500 Subject: [PATCH 273/297] Removing contig sharding from VQSR in niare to fix variance issue. --- wdl/SRWholeGenome_Pf_Niare_VQSR.wdl | 220 ++++++++++++++++--------- wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 4 +- 2 files changed, 144 insertions(+), 80 deletions(-) diff --git a/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl index e2eb97c5e..3b9c96d16 100644 --- a/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl +++ b/wdl/SRWholeGenome_Pf_Niare_VQSR.wdl @@ -105,94 +105,158 @@ workflow SRWholeGenome_Pf_Niare_VQSR { filter = use_filter } - # Call over the scattered intervals: - scatter (c in SmallVariantsScatterPrep.chrs) { - String contig_for_small_var = c[0] - - call VARUTIL.SubsetVCF as GetHcCallsForContig { - input: - vcf_gz = RenameRawHcVcf.new_sample_name_vcf, - vcf_tbi = RenameRawHcVcf.new_sample_name_vcf_index, - locus = contig_for_small_var, - prefix = participant_name + "." + contig_for_small_var, - } - - call Niare_HC.NormalizeVcfSplittingMultiallelics as NormalizeVcfPreVqsr { - input: - input_vcf = GetHcCallsForContig.subset_vcf, - input_vcf_index = GetHcCallsForContig.subset_tbi, - ref_fasta = ref_map['fasta'], - ref_fasta_fai = ref_map['fai'], - ref_dict = ref_map['dict'], - prefix = participant_name + "." + contig_for_small_var + ".norm" - } - - call Niare_HC.VariantRecalibratorIndel as VariantRecalibratorIndel { - input: - input_vcf = NormalizeVcfPreVqsr.output_vcf, - input_vcf_index = NormalizeVcfPreVqsr.output_vcf_index, - ref_fasta = ref_map['fasta'], - ref_fasta_fai = ref_map['fai'], - ref_dict = ref_map['dict'], - sites_only_vcf = vqsr_sites_vcf, - sites_only_vcf_index = vqsr_sites_vcf_index, - prefix = participant_name + "." + contig_for_small_var + ".norm", - } +# # Call over the scattered intervals: +# scatter (c in SmallVariantsScatterPrep.chrs) { +# String contig_for_small_var = c[0] +# +# call VARUTIL.SubsetVCF as GetHcCallsForContig { +# input: +# vcf_gz = RenameRawHcVcf.new_sample_name_vcf, +# vcf_tbi = RenameRawHcVcf.new_sample_name_vcf_index, +# locus = contig_for_small_var, +# prefix = participant_name + "." + contig_for_small_var, +# } +# +# call Niare_HC.NormalizeVcfSplittingMultiallelics as NormalizeVcfPreVqsr { +# input: +# input_vcf = GetHcCallsForContig.subset_vcf, +# input_vcf_index = GetHcCallsForContig.subset_tbi, +# ref_fasta = ref_map['fasta'], +# ref_fasta_fai = ref_map['fai'], +# ref_dict = ref_map['dict'], +# prefix = participant_name + "." + contig_for_small_var + ".norm" +# } +# +# call Niare_HC.VariantRecalibratorIndel as VariantRecalibratorIndel { +# input: +# input_vcf = NormalizeVcfPreVqsr.output_vcf, +# input_vcf_index = NormalizeVcfPreVqsr.output_vcf_index, +# ref_fasta = ref_map['fasta'], +# ref_fasta_fai = ref_map['fai'], +# ref_dict = ref_map['dict'], +# sites_only_vcf = vqsr_sites_vcf, +# sites_only_vcf_index = vqsr_sites_vcf_index, +# prefix = participant_name + "." + contig_for_small_var + ".norm", +# } +# +# call Niare_HC.ApplyVqsrIndel as ApplyVqsrIndel { +# input: +# input_vcf = NormalizeVcfPreVqsr.output_vcf, +# input_vcf_index = NormalizeVcfPreVqsr.output_vcf_index, +# recal_file = VariantRecalibratorIndel.recalibration, +# recal_file_index = VariantRecalibratorIndel.recalibration_index, +# recal_tranches = VariantRecalibratorIndel.tranches, +# prefix = participant_name + "." + contig_for_small_var + ".norm", +# } +# +# call Niare_HC.VariantRecalibratorSnp as VariantRecalibratorSnp { +# input: +# input_vcf = ApplyVqsrIndel.output_vcf, +# input_vcf_index = ApplyVqsrIndel.output_vcf_index, +# ref_fasta = ref_map['fasta'], +# ref_fasta_fai = ref_map['fai'], +# ref_dict = ref_map['dict'], +# sites_only_vcf = vqsr_sites_vcf, +# sites_only_vcf_index = vqsr_sites_vcf_index, +# prefix = participant_name + "." + contig_for_small_var + ".norm", +# } +# +# call Niare_HC.ApplyVqsrSnp as ApplyVqsrSnp { +# input: +# input_vcf = ApplyVqsrIndel.output_vcf, +# input_vcf_index = ApplyVqsrIndel.output_vcf_index, +# recal_file = VariantRecalibratorSnp.recalibration, +# recal_file_index = VariantRecalibratorSnp.recalibration_index, +# recal_tranches = VariantRecalibratorSnp.tranches, +# prefix = participant_name + "." + contig_for_small_var + ".norm.indel_recal", +# } +# +# call Niare_HC.MergeMultiAllelicSitesPostRecalibration as MergeMultiAllelicSitesPostRecalibration { +# input: +# input_vcf = ApplyVqsrSnp.output_vcf, +# input_vcf_index = ApplyVqsrSnp.output_vcf_index, +# ref_fasta = ref_map['fasta'], +# ref_fasta_fai = ref_map['fai'], +# ref_dict = ref_map['dict'], +# prefix = participant_name + "." + contig_for_small_var, +# } +# } +# +# call SRUTIL.MergeVCFs as MergeVCFs { +# input: +# input_vcfs = MergeMultiAllelicSitesPostRecalibration.output_vcf, +# input_vcfs_indexes = MergeMultiAllelicSitesPostRecalibration.output_vcf_index, +# prefix = participant_name + ".recalibrated" +# } + + call Niare_HC.NormalizeVcfSplittingMultiallelics as NormalizeVcfPreVqsr { + input: + input_vcf = RenameRawHcVcf.new_sample_name_vcf, + input_vcf_index = RenameRawHcVcf.new_sample_name_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = participant_name + ".norm" + } - call Niare_HC.ApplyVqsrIndel as ApplyVqsrIndel { - input: - input_vcf = NormalizeVcfPreVqsr.output_vcf, - input_vcf_index = NormalizeVcfPreVqsr.output_vcf_index, - recal_file = VariantRecalibratorIndel.recalibration, - recal_file_index = VariantRecalibratorIndel.recalibration_index, - recal_tranches = VariantRecalibratorIndel.tranches, - prefix = participant_name + "." + contig_for_small_var + ".norm", - } + call Niare_HC.VariantRecalibratorIndel as VariantRecalibratorIndel { + input: + input_vcf = NormalizeVcfPreVqsr.output_vcf, + input_vcf_index = NormalizeVcfPreVqsr.output_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + sites_only_vcf = vqsr_sites_vcf, + sites_only_vcf_index = vqsr_sites_vcf_index, + prefix = participant_name + ".norm", + } - call Niare_HC.VariantRecalibratorSnp as VariantRecalibratorSnp { - input: - input_vcf = ApplyVqsrIndel.output_vcf, - input_vcf_index = ApplyVqsrIndel.output_vcf_index, - ref_fasta = ref_map['fasta'], - ref_fasta_fai = ref_map['fai'], - ref_dict = ref_map['dict'], - sites_only_vcf = vqsr_sites_vcf, - sites_only_vcf_index = vqsr_sites_vcf_index, - prefix = participant_name + "." + contig_for_small_var + ".norm", - } + call Niare_HC.ApplyVqsrIndel as ApplyVqsrIndel { + input: + input_vcf = NormalizeVcfPreVqsr.output_vcf, + input_vcf_index = NormalizeVcfPreVqsr.output_vcf_index, + recal_file = VariantRecalibratorIndel.recalibration, + recal_file_index = VariantRecalibratorIndel.recalibration_index, + recal_tranches = VariantRecalibratorIndel.tranches, + prefix = participant_name + ".norm", + } - call Niare_HC.ApplyVqsrSnp as ApplyVqsrSnp { - input: - input_vcf = ApplyVqsrIndel.output_vcf, - input_vcf_index = ApplyVqsrIndel.output_vcf_index, - recal_file = VariantRecalibratorSnp.recalibration, - recal_file_index = VariantRecalibratorSnp.recalibration_index, - recal_tranches = VariantRecalibratorSnp.tranches, - prefix = participant_name + "." + contig_for_small_var + ".norm.indel_recal", - } + call Niare_HC.VariantRecalibratorSnp as VariantRecalibratorSnp { + input: + input_vcf = ApplyVqsrIndel.output_vcf, + input_vcf_index = ApplyVqsrIndel.output_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + sites_only_vcf = vqsr_sites_vcf, + sites_only_vcf_index = vqsr_sites_vcf_index, + prefix = participant_name + ".norm", + } - call Niare_HC.MergeMultiAllelicSitesPostRecalibration as MergeMultiAllelicSitesPostRecalibration { - input: - input_vcf = ApplyVqsrSnp.output_vcf, - input_vcf_index = ApplyVqsrSnp.output_vcf_index, - ref_fasta = ref_map['fasta'], - ref_fasta_fai = ref_map['fai'], - ref_dict = ref_map['dict'], - prefix = participant_name + "." + contig_for_small_var, - } + call Niare_HC.ApplyVqsrSnp as ApplyVqsrSnp { + input: + input_vcf = ApplyVqsrIndel.output_vcf, + input_vcf_index = ApplyVqsrIndel.output_vcf_index, + recal_file = VariantRecalibratorSnp.recalibration, + recal_file_index = VariantRecalibratorSnp.recalibration_index, + recal_tranches = VariantRecalibratorSnp.tranches, + prefix = participant_name + ".norm.indel_recal", } - call SRUTIL.MergeVCFs as MergeVCFs { + call Niare_HC.MergeMultiAllelicSitesPostRecalibration as MergeMultiAllelicSitesPostRecalibration { input: - input_vcfs = MergeMultiAllelicSitesPostRecalibration.output_vcf, - input_vcfs_indexes = MergeMultiAllelicSitesPostRecalibration.output_vcf_index, - prefix = participant_name + ".recalibrated" + input_vcf = ApplyVqsrSnp.output_vcf, + input_vcf_index = ApplyVqsrSnp.output_vcf_index, + ref_fasta = ref_map['fasta'], + ref_fasta_fai = ref_map['fai'], + ref_dict = ref_map['dict'], + prefix = participant_name + ".recalibrated", } ################################################################################################ # Create a Keyfile for finalization: - File keyfile = MergeVCFs.output_vcf_index + File keyfile = MergeMultiAllelicSitesPostRecalibration.output_vcf_index # Finalize the raw Joint Calls: call FF.FinalizeToFile as FinalizeRawHCVcf { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcVcf.new_sample_name_vcf } @@ -201,8 +265,8 @@ workflow SRWholeGenome_Pf_Niare_VQSR { call FF.FinalizeToFile as FinalizeHCGTbi { input: outdir = smalldir, keyfile = keyfile, file = RenameRawHcGvcf.new_sample_name_vcf_index } call FF.FinalizeToFile as FinalizeHCBamOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout } call FF.FinalizeToFile as FinalizeHCBaiOut { input: outdir = smalldir, keyfile = keyfile, file = CallVariantsWithHaplotypeCaller.bamout_index } - call FF.FinalizeToFile as FinalizeRecalibratedVcf { input: outdir = smalldir, keyfile = keyfile, file = MergeVCFs.output_vcf } - call FF.FinalizeToFile as FinalizeRecalibratedVcfIndex { input: outdir = smalldir, keyfile = keyfile, file = MergeVCFs.output_vcf_index } + call FF.FinalizeToFile as FinalizeRecalibratedVcf { input: outdir = smalldir, keyfile = keyfile, file = MergeMultiAllelicSitesPostRecalibration.output_vcf } + call FF.FinalizeToFile as FinalizeRecalibratedVcfIndex { input: outdir = smalldir, keyfile = keyfile, file = MergeMultiAllelicSitesPostRecalibration.output_vcf_index } ################################ # Finalize the VETS files: diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index 8555a010e..35c9d8b1f 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -820,8 +820,8 @@ task ApplyVqsrSnp { >>> output { - File output_vcf = "~{prefix}.indel_recal.vcf.gz" - File output_vcf_index = "~{prefix}.indel_recal.vcf.gz.tbi" + File output_vcf = "~{prefix}.snp_recal.vcf.gz" + File output_vcf_index = "~{prefix}.snp_recal.vcf.gz.tbi" } ######################### From 4d617f56d92007ed1022f8175c45ee01da0d1ad9 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 7 Dec 2023 01:12:11 -0500 Subject: [PATCH 274/297] Fixing yet another issue in the vqsr stuff. --- wdl/tasks/Pf_Niare_HaplotypeCaller.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl index 35c9d8b1f..42807f27f 100644 --- a/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl +++ b/wdl/tasks/Pf_Niare_HaplotypeCaller.wdl @@ -815,7 +815,7 @@ task ApplyVqsrSnp { --lod-score-cutoff 0.0 \ --exclude-filtered false \ -mode SNP \ - -O ~{prefix}.indel_recal.vcf.gz + -O ~{prefix}.snp_recal.vcf.gz >>> From 4ae002b8d29323d90a985cb21e0e470989f10948 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 7 Dec 2023 12:57:30 -0500 Subject: [PATCH 275/297] MAde BQSR optional. --- wdl/SRFlowcell.wdl | 89 +++++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 8bfc8a928..d56c55da3 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -35,6 +35,8 @@ workflow SRFlowcell { String gcs_out_root_dir + Boolean perform_BQSR = true + Boolean DEBUG_MODE = false String platform = "illumina" @@ -182,36 +184,41 @@ workflow SRFlowcell { # TODO: Add Fingerprinting? - # Recalibrate Base Scores: - call SRUTIL.BaseRecalibrator as t_009_BaseRecalibrator { - input: - input_bam = t_008_SortAlignedDuplicateMarkedBam.sorted_bam, - input_bam_index = t_008_SortAlignedDuplicateMarkedBam.sorted_bai, + if (perform_BQSR) { + # Recalibrate Base Scores: + call SRUTIL.BaseRecalibrator as t_009_BaseRecalibrator { + input: + input_bam = t_008_SortAlignedDuplicateMarkedBam.sorted_bam, + input_bam_index = t_008_SortAlignedDuplicateMarkedBam.sorted_bai, - ref_fasta = ref_map["fasta"], - ref_fasta_index = ref_map["fai"], - ref_dict = ref_map["dict"], + ref_fasta = ref_map["fasta"], + ref_fasta_index = ref_map["fai"], + ref_dict = ref_map["dict"], - known_sites_vcf = ref_map["known_sites_vcf"], - known_sites_index = ref_map["known_sites_index"], + known_sites_vcf = ref_map["known_sites_vcf"], + known_sites_index = ref_map["known_sites_index"], - prefix = SM + ".baseRecalibratorReport" - } + prefix = SM + ".baseRecalibratorReport" + } - call SRUTIL.ApplyBQSR as t_010_ApplyBQSR { - input: - input_bam = t_008_SortAlignedDuplicateMarkedBam.sorted_bam, - input_bam_index = t_008_SortAlignedDuplicateMarkedBam.sorted_bai, + call SRUTIL.ApplyBQSR as t_010_ApplyBQSR { + input: + input_bam = t_008_SortAlignedDuplicateMarkedBam.sorted_bam, + input_bam_index = t_008_SortAlignedDuplicateMarkedBam.sorted_bai, - ref_fasta = ref_map["fasta"], - ref_fasta_index = ref_map["fai"], - ref_dict = ref_map["dict"], + ref_fasta = ref_map["fasta"], + ref_fasta_index = ref_map["fai"], + ref_dict = ref_map["dict"], - recalibration_report = t_009_BaseRecalibrator.recalibration_report, + recalibration_report = t_009_BaseRecalibrator.recalibration_report, - prefix = SM + ".aligned.merged.markDuplicates.sorted.BQSR" + prefix = SM + ".aligned.merged.markDuplicates.sorted.BQSR" + } } + File final_bam = select_first([t_010_ApplyBQSR.recalibrated_bam, t_008_SortAlignedDuplicateMarkedBam.sorted_bam]) + File final_bai = select_first([t_010_ApplyBQSR.recalibrated_bai, t_008_SortAlignedDuplicateMarkedBam.sorted_bai]) + ############################################# # __ __ _ _ # | \/ | ___| |_ _ __(_) ___ ___ @@ -223,24 +230,24 @@ workflow SRFlowcell { call AM.SamStatsMap as t_011_SamStats { input: - bam = t_010_ApplyBQSR.recalibrated_bam + bam = final_bam } - call FastQC.FastQC as t_012_FastQC { input: bam = t_010_ApplyBQSR.recalibrated_bam, bai = t_010_ApplyBQSR.recalibrated_bai } + call FastQC.FastQC as t_012_FastQC { input: bam = final_bam, bai = final_bai } call Utils.ComputeGenomeLength as t_013_ComputeGenomeLength { input: fasta = ref_map['fasta'] } - call SRUTIL.ComputeBamStats as t_014_ComputeBamStats { input: bam_file = t_010_ApplyBQSR.recalibrated_bam } + call SRUTIL.ComputeBamStats as t_014_ComputeBamStats { input: bam_file = final_bam } # Collect stats on aligned reads: - call SRUTIL.ComputeBamStats as t_015_ComputeBamStatsQ5 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 5 } - call SRUTIL.ComputeBamStats as t_016_ComputeBamStatsQ7 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 7 } - call SRUTIL.ComputeBamStats as t_017_ComputeBamStatsQ10 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 10 } - call SRUTIL.ComputeBamStats as t_018_ComputeBamStatsQ12 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 12 } - call SRUTIL.ComputeBamStats as t_019_ComputeBamStatsQ15 { input: bam_file = t_010_ApplyBQSR.recalibrated_bam, qual_threshold = 15 } + call SRUTIL.ComputeBamStats as t_015_ComputeBamStatsQ5 { input: bam_file = final_bam, qual_threshold = 5 } + call SRUTIL.ComputeBamStats as t_016_ComputeBamStatsQ7 { input: bam_file = final_bam, qual_threshold = 7 } + call SRUTIL.ComputeBamStats as t_017_ComputeBamStatsQ10 { input: bam_file = final_bam, qual_threshold = 10 } + call SRUTIL.ComputeBamStats as t_018_ComputeBamStatsQ12 { input: bam_file = final_bam, qual_threshold = 12 } + call SRUTIL.ComputeBamStats as t_019_ComputeBamStatsQ15 { input: bam_file = final_bam, qual_threshold = 15 } call AM.AlignedMetrics as PerFlowcellMetrics { input: - aligned_bam = t_010_ApplyBQSR.recalibrated_bam, - aligned_bai = t_010_ApplyBQSR.recalibrated_bai, + aligned_bam = final_bam, + aligned_bai = final_bai, ref_fasta = ref_map['fasta'], ref_dict = ref_map['dict'], gcs_output_dir = metrics_dir @@ -298,14 +305,14 @@ workflow SRFlowcell { call FF.FinalizeToFile as t_023_FinalizeAlignedBam { input: outdir = aligned_reads_dir, - file = t_010_ApplyBQSR.recalibrated_bam, + file = final_bam, keyfile = keyfile } call FF.FinalizeToFile as t_024_FinalizeAlignedBai { input: outdir = aligned_reads_dir, - file = t_010_ApplyBQSR.recalibrated_bai, + file = final_bai, keyfile = keyfile } @@ -316,7 +323,6 @@ workflow SRFlowcell { files = [ t_007_MarkDuplicates.metrics, - t_009_BaseRecalibrator.recalibration_report, t_011_SamStats.sam_stats, t_014_ComputeBamStats.results_file, t_015_ComputeBamStatsQ5.results_file, @@ -328,7 +334,18 @@ workflow SRFlowcell { keyfile = keyfile } - call FF.FinalizeToFile as t_026_FinalizeFastQCReport { + # Finalize BQSR Metrics if it was run: + if (perform_BQSR) { + call FF.FinalizeToDir as t_026_FinalizeBQSRMetrics { + input: + outdir = metrics_dir, + files = select_all([t_009_BaseRecalibrator.recalibration_report]), + keyfile = keyfile + } + + } + + call FF.FinalizeToFile as t_027_FinalizeFastQCReport { input: outdir = metrics_dir, file = t_012_FastQC.report @@ -398,6 +415,6 @@ workflow SRFlowcell { Float average_identity = 100.0 - (100.0*t_011_SamStats.stats_map['mismatches']/t_011_SamStats.stats_map['bases_mapped']) - File fastqc_report = t_026_FinalizeFastQCReport.gcs_path + File fastqc_report = t_027_FinalizeFastQCReport.gcs_path } } From ae5a5e630c314671c447da7cfedba7e42c2b0984 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 11 Dec 2023 22:39:42 -0500 Subject: [PATCH 276/297] Adding new WDL for expanded drug resistance markers. --- wdl/DrugResistanceMarkerCollection.wdl | 213 +++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 wdl/DrugResistanceMarkerCollection.wdl diff --git a/wdl/DrugResistanceMarkerCollection.wdl b/wdl/DrugResistanceMarkerCollection.wdl new file mode 100644 index 000000000..c4189b990 --- /dev/null +++ b/wdl/DrugResistanceMarkerCollection.wdl @@ -0,0 +1,213 @@ +version 1.0 + +import "tasks/Structs.wdl" +import "tasks/FunctionalAnnotation.wdl" as FUNK +import "tasks/Finalize.wdl" as FF + +workflow DrugResistanceMarkerCollection { + input { + String sample_name + + File vcf + File snpeff_db + File protein_drug_resistance_list + File gene_drug_resistance_list + + String dir_prefix + String gcs_out_root_dir + + Boolean do_functional_annotation = true + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/DrugResistanceMarkerCollection/~{dir_prefix}" + + if (do_functional_annotation) { + call FUNK.FunctionallyAnnotateVariants { input: vcf = vcf, snpeff_db = snpeff_db } + } + + call CallDrugResistanceMutations { + input: + vcf = select_first([FunctionallyAnnotateVariants.annotated_vcf, vcf]), + protein_drug_resistance_list = protein_drug_resistance_list, + gene_drug_resistance_list = gene_drug_resistance_list, + prefix = sample_name + } + + # Finalize data + String dir = outdir + "/reports" + + call FF.FinalizeToFile as FinalizeDRReportAllMarkers { input: outdir = dir, file = CallDrugResistanceMutations.all_markers } + call FF.FinalizeToFile as FinalizeDRReportProteinMarkers { input: outdir = dir, file = CallDrugResistanceMutations.protein_coding_markers } + + if (do_functional_annotation) { + call FF.FinalizeToFile as FinalizeAnnotatedVCF { input: outdir = dir, file = select_first([FunctionallyAnnotateVariants.annotated_vcf]) } + call FF.FinalizeToFile as FinalizeAnnotatedVCFIndex { input: outdir = dir, file = select_first([FunctionallyAnnotateVariants.annotated_vcf_index]) } + call FF.FinalizeToFile as FinalizeSnpEffSummary { input: outdir = dir, file = select_first([FunctionallyAnnotateVariants.snpEff_summary]) } + call FF.FinalizeToFile as FinalizeSnpEffGenes { input: outdir = dir, file = select_first([FunctionallyAnnotateVariants.snpEff_genes]) } + } + + output { + File drug_res_report_all = FinalizeDRReportAllMarkers.gcs_path + File drug_res_report_prot_only = FinalizeDRReportProteinMarkers.gcs_path + + File? annotated_vcf = FinalizeAnnotatedVCF.gcs_path + File? annotated_vcf_index = FinalizeAnnotatedVCFIndex.gcs_path + File? snpEff_summary = FinalizeSnpEffSummary.gcs_path + File? snpEff_genes = FinalizeSnpEffGenes.gcs_path + } +} + +task CallDrugResistanceMutations { + input { + File vcf + File protein_drug_resistance_list + File gene_drug_resistance_list + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 1 + 10*ceil(size([vcf, protein_drug_resistance_list, gene_drug_resistance_list], "GB")) + + command <<< + set -euxo pipefail + + python3 <') + annotation_fields = line[i1+len(needle):i2].replace("'", "").split(" | ") + continue + elif line.startswith("#"): + continue + chrom, pos, idd, ref, alt, qual, flt, info, gt_f, gt_d = line.strip().split("\t") + infos = {} + for i in info.split(";"): + if "=" in i: + k, v = i.split("=") + infos[k] = v + ann_dicts = make_ann_dict(infos["ANN"], annotation_fields) + + # Now check if our Gene level drug markers: + gene_annotations = [] + for gene_name, gene_id in gene_info: + for ann_dict in ann_dicts: + if gene_id in ann_dict["Gene_Name"] or gene_id in ann_dict["Gene_ID"]: + # is it protein coding? + if len(ann_dict["HGVS.p"]) > 0: + gene_annotations.append((ann_dict["Annotation_Impact"], gene_name, gene_id, ann_dict['HGVS.p'], ann_dict["Annotation"])) + else: + gene_annotations.append((ann_dict["Annotation_Impact"], gene_name, gene_id, ann_dict['HGVS.c'], ann_dict["Annotation"])) + + if len(gene_annotations) > 0: + # Sort gene annotations to make the highest / worst effect at the front: + gene_annotations = sorted(gene_annotations, key=ann_impact_sort_key) + # Only output the greatest effect annotation + # only output the fields from gene_name onward - we dont need the impact + annotations.append(gene_annotations[0][1:]) + + # Now check for our protein change string drug markers: + for gene_name, gene_id, prot_change in p_change_marker_info: + for ann_dict in ann_dicts: + if gene_id in ann_dict["Gene_Name"] or gene_id in ann_dict["Gene_ID"]: + if len(ann_dict["HGVS.p"]) > 0 and ann_dict["HGVS.p"] == prot_change: + annotations.append((gene_name, gene_id, ann_dict['HGVS.p'], ann_dict["Annotation"])) + + with open(gene_drug_report_all, 'w') as f: + f.write(f"Gene_Name\tGene_ID\tChange_String\tAnnotation_Type\n") + for a in annotations: + if a[3] != "synonymous_variant": + f.write("\t".join(a)) + f.write("\n") + + with open(gene_drug_report_prot, 'w') as f: + f.write(f"Gene_Name\tGene_ID\tChange_String\tAnnotation_Type\n") + for a in annotations: + if a[2].startswith("p.") and a[3] != "synonymous_variant": + f.write("\t".join(a)) + f.write("\n") + CODE + + >>> + + output { + File all_markers = "~{prefix}.expanded_drug_report.ALL.tsv" + File protein_coding_markers = "~{prefix}.expanded_drug_report.PROTEIN_CHANGES_ONLY.tsv" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 1, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "quay.io/biocontainers/snpeff:5.1d--hdfd78af_0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From 22a45e081b4b2d48dd4392e9b64c888aceef30df Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 11 Dec 2023 22:43:05 -0500 Subject: [PATCH 277/297] Adding ExpandedDrugResistanceMarkerExtraction to dockstore. --- .dockstore.yml | 4 ++++ ...lection.wdl => ExpandedDrugResistanceMarkerExtraction.wdl} | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) rename wdl/{DrugResistanceMarkerCollection.wdl => ExpandedDrugResistanceMarkerExtraction.wdl} (98%) diff --git a/.dockstore.yml b/.dockstore.yml index 8b333ef87..688abb0b1 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -187,4 +187,8 @@ workflows: - name: SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VETS subclass: wdl primaryDescriptorPath: /wdl/SRJointCallGVCFsWithGenomicsDB_Pf_Niare_VETS.wdl + testParameterFiles: +- name: ExpandedDrugResistanceMarkerExtraction + subclass: wdl + primaryDescriptorPath: /wdl/ExpandedDrugResistanceMarkerExtraction.wdl testParameterFiles: \ No newline at end of file diff --git a/wdl/DrugResistanceMarkerCollection.wdl b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl similarity index 98% rename from wdl/DrugResistanceMarkerCollection.wdl rename to wdl/ExpandedDrugResistanceMarkerExtraction.wdl index c4189b990..48af905e0 100644 --- a/wdl/DrugResistanceMarkerCollection.wdl +++ b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl @@ -4,7 +4,7 @@ import "tasks/Structs.wdl" import "tasks/FunctionalAnnotation.wdl" as FUNK import "tasks/Finalize.wdl" as FF -workflow DrugResistanceMarkerCollection { +workflow ExpandedDrugResistanceMarkerExtraction { input { String sample_name @@ -19,7 +19,7 @@ workflow DrugResistanceMarkerCollection { Boolean do_functional_annotation = true } - String outdir = sub(gcs_out_root_dir, "/$", "") + "/DrugResistanceMarkerCollection/~{dir_prefix}" + String outdir = sub(gcs_out_root_dir, "/$", "") + "/ExpandedDrugResistanceMarkerExtraction/~{dir_prefix}" if (do_functional_annotation) { call FUNK.FunctionallyAnnotateVariants { input: vcf = vcf, snpeff_db = snpeff_db } From af056ba81d07761d8bbcbc6fa5c62ae867ef5648 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Mon, 11 Dec 2023 23:13:02 -0500 Subject: [PATCH 278/297] Fixed issue in ExpandedDrugResistanceMarkerExtraction.wdl --- wdl/ExpandedDrugResistanceMarkerExtraction.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/ExpandedDrugResistanceMarkerExtraction.wdl b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl index 48af905e0..ae4c22294 100644 --- a/wdl/ExpandedDrugResistanceMarkerExtraction.wdl +++ b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl @@ -125,7 +125,7 @@ task CallDrugResistanceMutations { return s.index(x[0]) annotations = [] - with gzip.open(vcf, 'rt') as f: + with gzip.open("~{vcf}", 'rt') as f: for line in f: if line.startswith("##INFO= Date: Mon, 11 Dec 2023 23:43:54 -0500 Subject: [PATCH 279/297] Adding genotype info to drug resistance extraction. --- wdl/ExpandedDrugResistanceMarkerExtraction.wdl | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/wdl/ExpandedDrugResistanceMarkerExtraction.wdl b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl index ae4c22294..c980d9967 100644 --- a/wdl/ExpandedDrugResistanceMarkerExtraction.wdl +++ b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl @@ -143,6 +143,14 @@ task CallDrugResistanceMutations { infos[k] = v ann_dicts = make_ann_dict(infos["ANN"], annotation_fields) + # Get genotype info: + gt_f = gt_f.split(":") + gt_d = gt_d.split(":") + + i = gt_f.index("GT") + gt = gt_d[i] + gt_str = "hom" if (gt == "1/1" or gt=="1|1") else "het" + # Now check if our Gene level drug markers: gene_annotations = [] for gene_name, gene_id in gene_info: @@ -150,9 +158,9 @@ task CallDrugResistanceMutations { if gene_id in ann_dict["Gene_Name"] or gene_id in ann_dict["Gene_ID"]: # is it protein coding? if len(ann_dict["HGVS.p"]) > 0: - gene_annotations.append((ann_dict["Annotation_Impact"], gene_name, gene_id, ann_dict['HGVS.p'], ann_dict["Annotation"])) + gene_annotations.append((ann_dict["Annotation_Impact"], gene_name, gene_id, ann_dict['HGVS.p'], ann_dict["Annotation"], gt_str)) else: - gene_annotations.append((ann_dict["Annotation_Impact"], gene_name, gene_id, ann_dict['HGVS.c'], ann_dict["Annotation"])) + gene_annotations.append((ann_dict["Annotation_Impact"], gene_name, gene_id, ann_dict['HGVS.c'], ann_dict["Annotation"], gt_str)) if len(gene_annotations) > 0: # Sort gene annotations to make the highest / worst effect at the front: @@ -166,17 +174,17 @@ task CallDrugResistanceMutations { for ann_dict in ann_dicts: if gene_id in ann_dict["Gene_Name"] or gene_id in ann_dict["Gene_ID"]: if len(ann_dict["HGVS.p"]) > 0 and ann_dict["HGVS.p"] == prot_change: - annotations.append((gene_name, gene_id, ann_dict['HGVS.p'], ann_dict["Annotation"])) + annotations.append((gene_name, gene_id, ann_dict['HGVS.p'], ann_dict["Annotation"], gt_str)) with open(gene_drug_report_all, 'w') as f: - f.write(f"Gene_Name\tGene_ID\tChange_String\tAnnotation_Type\n") + f.write(f"Gene_Name\tGene_ID\tChange_String\tAnnotation_Type\tGT\n") for a in annotations: if a[3] != "synonymous_variant": f.write("\t".join(a)) f.write("\n") with open(gene_drug_report_prot, 'w') as f: - f.write(f"Gene_Name\tGene_ID\tChange_String\tAnnotation_Type\n") + f.write(f"Gene_Name\tGene_ID\tChange_String\tAnnotation_Type\tGT\n") for a in annotations: if a[2].startswith("p.") and a[3] != "synonymous_variant": f.write("\t".join(a)) From 62ccda93c169cce927afcc9ecffa9503554ab95d Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 12 Dec 2023 08:07:27 -0500 Subject: [PATCH 280/297] Adding ExpandedDrugResistanceMarkerAggregation.wdl --- .dockstore.yml | 4 + ...xpandedDrugResistanceMarkerAggregation.wdl | 132 ++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 wdl/ExpandedDrugResistanceMarkerAggregation.wdl diff --git a/.dockstore.yml b/.dockstore.yml index 688abb0b1..5aa060cd9 100644 --- a/.dockstore.yml +++ b/.dockstore.yml @@ -191,4 +191,8 @@ workflows: - name: ExpandedDrugResistanceMarkerExtraction subclass: wdl primaryDescriptorPath: /wdl/ExpandedDrugResistanceMarkerExtraction.wdl + testParameterFiles: +- name: ExpandedDrugResistanceMarkerAggregation + subclass: wdl + primaryDescriptorPath: /wdl/ExpandedDrugResistanceMarkerAggregation.wdl testParameterFiles: \ No newline at end of file diff --git a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl new file mode 100644 index 000000000..c2c27e6d7 --- /dev/null +++ b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl @@ -0,0 +1,132 @@ +version 1.0 + +import "tasks/Structs.wdl" +import "tasks/FunctionalAnnotation.wdl" as FUNK +import "tasks/Finalize.wdl" as FF + +workflow ExpandedDrugResistanceMarkerExtraction { + input { + Array[String] sample_names + Array[File] expanded_drug_res_markers + + String out_file_prefix + + String dir_prefix + String gcs_out_root_dir + } + + String outdir = sub(gcs_out_root_dir, "/$", "") + "/ExpandedDrugResistanceMarkerAggregation/~{dir_prefix}" + + call CombineExpandedDrugResistanceMarkers { + input: + expanded_drug_res_markers = expanded_drug_res_markers, + sample_names = sample_names, + prefix = out_file_prefix, + } + + + # Finalize data + String dir = outdir + "/reports" + + call FF.FinalizeToFile as FinalizeDRReportAllMarkers { input: outdir = dir, file = CombineExpandedDrugResistanceMarkers.combined_report } + + output { + File combined_expanded_markers = FinalizeDRReportAllMarkers.gcs_path + } +} + +task CombineExpandedDrugResistanceMarkers { + input { + Array[String] sample_names + Array[File] expanded_drug_res_markers + + String prefix + + RuntimeAttr? runtime_attr_override + } + + Int disk_size = 10 + 10*ceil(size([sample_names, expanded_drug_res_markers], "GB")) + + command <<< + set -euxo pipefail + + python3 <>> + + output { + File combined_report = "~{prefix}.expanded_drug_report_combined.tsv" + } + + ######################### + RuntimeAttr default_attr = object { + cpu_cores: 1, + mem_gb: 10, + disk_gb: disk_size, + boot_disk_gb: 10, + preemptible_tries: 2, + max_retries: 1, + docker: "quay.io/biocontainers/snpeff:5.1d--hdfd78af_0" + } + RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) + runtime { + cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) + memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " HDD" + bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) + preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) + maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) + docker: select_first([runtime_attr.docker, default_attr.docker]) + } +} From 7a7b1c5309096c3ab236513d89618f68d7121b82 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 12 Dec 2023 08:43:02 -0500 Subject: [PATCH 281/297] Fixing het value in aggregation table. --- wdl/ExpandedDrugResistanceMarkerAggregation.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl index c2c27e6d7..778786e30 100644 --- a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl +++ b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl @@ -82,7 +82,7 @@ task CombineExpandedDrugResistanceMarkers { for line in f: fields = line.strip().split() marker = "_".join(fields[:3]) - sample_marker_dict[sample_id][marker] = 1 if fields[4] == "hom" else -1 + sample_marker_dict[sample_id][marker] = 1 if fields[4] == "hom" else -2 # Make lists of the things we want to iterate over: marker_list_list = list([v.keys() for v in sample_marker_dict.values()]) From 32b6ae85418b93580bf0f5de8fa20c55731a2b8e Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 12 Dec 2023 11:24:25 -0500 Subject: [PATCH 282/297] Changing output format for ExpandedDrugResistanceMarkerExtraction.wdl --- wdl/ExpandedDrugResistanceMarkerExtraction.wdl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wdl/ExpandedDrugResistanceMarkerExtraction.wdl b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl index c980d9967..4e77a433a 100644 --- a/wdl/ExpandedDrugResistanceMarkerExtraction.wdl +++ b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl @@ -158,9 +158,9 @@ task CallDrugResistanceMutations { if gene_id in ann_dict["Gene_Name"] or gene_id in ann_dict["Gene_ID"]: # is it protein coding? if len(ann_dict["HGVS.p"]) > 0: - gene_annotations.append((ann_dict["Annotation_Impact"], gene_name, gene_id, ann_dict['HGVS.p'], ann_dict["Annotation"], gt_str)) + gene_annotations.append((ann_dict["Annotation_Impact"], chrom, pos, gene_name, gene_id, ann_dict["Feature_Type"], ann_dict["Feature_ID"], ann_dict['HGVS.p'], ann_dict["Annotation"], gt_str)) else: - gene_annotations.append((ann_dict["Annotation_Impact"], gene_name, gene_id, ann_dict['HGVS.c'], ann_dict["Annotation"], gt_str)) + gene_annotations.append((ann_dict["Annotation_Impact"], chrom, pos, gene_name, gene_id, ann_dict["Feature_Type"], ann_dict["Feature_ID"], ann_dict['HGVS.c'], ann_dict["Annotation"], gt_str)) if len(gene_annotations) > 0: # Sort gene annotations to make the highest / worst effect at the front: @@ -174,19 +174,19 @@ task CallDrugResistanceMutations { for ann_dict in ann_dicts: if gene_id in ann_dict["Gene_Name"] or gene_id in ann_dict["Gene_ID"]: if len(ann_dict["HGVS.p"]) > 0 and ann_dict["HGVS.p"] == prot_change: - annotations.append((gene_name, gene_id, ann_dict['HGVS.p'], ann_dict["Annotation"], gt_str)) + annotations.append((chrom, pos, gene_name, gene_id, ann_dict["Feature_Type"], ann_dict["Feature_ID"], ann_dict['HGVS.p'], ann_dict["Annotation"], gt_str)) with open(gene_drug_report_all, 'w') as f: - f.write(f"Gene_Name\tGene_ID\tChange_String\tAnnotation_Type\tGT\n") + f.write(f"Chrom\tPos\tGene_Name\tGene_ID\tFeatureType\tFeature_ID\tChange_String\tAnnotation_Type\tGT\n") for a in annotations: - if a[3] != "synonymous_variant": + if a[-2] != "synonymous_variant": f.write("\t".join(a)) f.write("\n") with open(gene_drug_report_prot, 'w') as f: - f.write(f"Gene_Name\tGene_ID\tChange_String\tAnnotation_Type\tGT\n") + f.write(f"Chrom\tPos\tGene_Name\tGene_ID\tFeatureType\tFeature_ID\tChange_String\tAnnotation_Type\tGT\n") for a in annotations: - if a[2].startswith("p.") and a[3] != "synonymous_variant": + if a[-3].startswith("p.") and a[-2] != "synonymous_variant": f.write("\t".join(a)) f.write("\n") CODE From a9899b8cc4071c003b54fe93cf3dc6dcea127fd9 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 12 Dec 2023 11:42:02 -0500 Subject: [PATCH 283/297] Updating format for ExpandedDrugResistanceMarkerExtraction.wdl again --- ...ExpandedDrugResistanceMarkerExtraction.wdl | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/wdl/ExpandedDrugResistanceMarkerExtraction.wdl b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl index 4e77a433a..ffb9f874e 100644 --- a/wdl/ExpandedDrugResistanceMarkerExtraction.wdl +++ b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl @@ -149,46 +149,42 @@ task CallDrugResistanceMutations { i = gt_f.index("GT") gt = gt_d[i] - gt_str = "hom" if (gt == "1/1" or gt=="1|1") else "het" + + base_out_data = [chrom, pos, ref, alt, gt] # Now check if our Gene level drug markers: gene_annotations = [] for gene_name, gene_id in gene_info: for ann_dict in ann_dicts: if gene_id in ann_dict["Gene_Name"] or gene_id in ann_dict["Gene_ID"]: - # is it protein coding? - if len(ann_dict["HGVS.p"]) > 0: - gene_annotations.append((ann_dict["Annotation_Impact"], chrom, pos, gene_name, gene_id, ann_dict["Feature_Type"], ann_dict["Feature_ID"], ann_dict['HGVS.p'], ann_dict["Annotation"], gt_str)) - else: - gene_annotations.append((ann_dict["Annotation_Impact"], chrom, pos, gene_name, gene_id, ann_dict["Feature_Type"], ann_dict["Feature_ID"], ann_dict['HGVS.c'], ann_dict["Annotation"], gt_str)) - - if len(gene_annotations) > 0: - # Sort gene annotations to make the highest / worst effect at the front: - gene_annotations = sorted(gene_annotations, key=ann_impact_sort_key) - # Only output the greatest effect annotation - # only output the fields from gene_name onward - we dont need the impact - annotations.append(gene_annotations[0][1:]) + gene_annotations.append(tuple(base_out_data + [ann_dict[a] for a in annotation_fields])) + + for g in gene_annotations: + annotations.append(g) # Now check for our protein change string drug markers: for gene_name, gene_id, prot_change in p_change_marker_info: for ann_dict in ann_dicts: if gene_id in ann_dict["Gene_Name"] or gene_id in ann_dict["Gene_ID"]: if len(ann_dict["HGVS.p"]) > 0 and ann_dict["HGVS.p"] == prot_change: - annotations.append((chrom, pos, gene_name, gene_id, ann_dict["Feature_Type"], ann_dict["Feature_ID"], ann_dict['HGVS.p'], ann_dict["Annotation"], gt_str)) + annotations.append(tuple(base_out_data + [ann_dict[a] for a in annotation_fields])) + header = "Chrom\tPos\tRef\tAlt\tGT\t" + "\t".join(annotation_fields) with open(gene_drug_report_all, 'w') as f: - f.write(f"Chrom\tPos\tGene_Name\tGene_ID\tFeatureType\tFeature_ID\tChange_String\tAnnotation_Type\tGT\n") + f.write(f"{header}\n") for a in annotations: - if a[-2] != "synonymous_variant": + if "synonymous_variant" not in a: f.write("\t".join(a)) f.write("\n") with open(gene_drug_report_prot, 'w') as f: - f.write(f"Chrom\tPos\tGene_Name\tGene_ID\tFeatureType\tFeature_ID\tChange_String\tAnnotation_Type\tGT\n") + f.write(f"{header}\n") for a in annotations: - if a[-3].startswith("p.") and a[-2] != "synonymous_variant": + if "synonymous_variant" not in a: f.write("\t".join(a)) f.write("\n") + + print('Done') CODE >>> From 3488be2199392428ffb3be62bfc5b79dcd1dd6d8 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 12 Dec 2023 13:05:49 -0500 Subject: [PATCH 284/297] Fixed ExpandedDrugResistanceMarkerAggregation.wdl for new formats. --- ...xpandedDrugResistanceMarkerAggregation.wdl | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl index 778786e30..b9c6fa3bc 100644 --- a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl +++ b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl @@ -78,11 +78,19 @@ task CombineExpandedDrugResistanceMarkers { for i, expanded_drug_report in enumerate(drug_res_files): sample_id = sample_names[i] with open(expanded_drug_report, 'r') as f: - next(f) + # NOTE: Header should be the same for all samples, so we can reuse it later + header = next(f).strip().split("\t") + + gt_field = header.index("GT") + error_field = header.index("ERRORS / WARNINGS / INFO") + for line in f: - fields = line.strip().split() - marker = "_".join(fields[:3]) - sample_marker_dict[sample_id][marker] = 1 if fields[4] == "hom" else -2 + fields = [fld.strip() for fld in line.split("\t")] + gt = fields[gt_field] + + marker = tuple([item for i, item in enumerate(fields) if i not in (gt_field, error_field)]) + + sample_marker_dict[sample_id][marker] = 1 if (gt=="1/1" or gt=="1|1") else -2 # Make lists of the things we want to iterate over: marker_list_list = list([v.keys() for v in sample_marker_dict.values()]) @@ -91,12 +99,15 @@ task CombineExpandedDrugResistanceMarkers { sample_list = sorted([k for k in sample_marker_dict.keys()]) # # Second pass to actually aggregate the markers: + out_header = [item for i, item in enumerate(header) if i not in (gt_field, error_field)] with open(aggregated_report, 'w') as f: # Write header: - f.write("Marker\t" + "\t".join(sample_list)) + f.write("\t".join(out_header) + "\t" + "\t".join(sample_list)) f.write("\n") for marker in marker_list: - f.write(marker + "\t") + # Markers are now tuples, so we need to write each element first: + f.write("\t".join(marker)) + f.write("\t") for sample in sample_list: f.write(str(sample_marker_dict[sample][marker])) f.write("\t") From b049ce4b1038059fd992ae473bf07c4ac4e7c4ce Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 12 Dec 2023 16:21:24 -0500 Subject: [PATCH 285/297] Fixing bug in ExpandedDrugResistanceMarkerAggregation.wdl --- wdl/ExpandedDrugResistanceMarkerAggregation.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl index b9c6fa3bc..f78ea1190 100644 --- a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl +++ b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl @@ -45,7 +45,7 @@ task CombineExpandedDrugResistanceMarkers { RuntimeAttr? runtime_attr_override } - Int disk_size = 10 + 10*ceil(size([sample_names, expanded_drug_res_markers], "GB")) + Int disk_size = 10 + 10*ceil(size(expanded_drug_res_markers, "GB")) command <<< set -euxo pipefail @@ -128,7 +128,7 @@ task CombineExpandedDrugResistanceMarkers { boot_disk_gb: 10, preemptible_tries: 2, max_retries: 1, - docker: "quay.io/biocontainers/snpeff:5.1d--hdfd78af_0" + docker: "us.gcr.io/broad-dsp-lrma/lr-utils:0.1.8" } RuntimeAttr runtime_attr = select_first([runtime_attr_override, default_attr]) runtime { From 31d492170fa0f530f0092bc00e791513c043664b Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 12 Dec 2023 16:55:35 -0500 Subject: [PATCH 286/297] Fixed a bug. --- wdl/ExpandedDrugResistanceMarkerAggregation.wdl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl index f78ea1190..76973b36e 100644 --- a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl +++ b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl @@ -54,8 +54,8 @@ task CombineExpandedDrugResistanceMarkers { aggregated_report = "~{prefix}.expanded_drug_report_combined.tsv" - sample_name_file = ~{write_lines(sample_names)} - drug_res_file = ~{write_lines(expanded_drug_res_markers)} + sample_name_file = "~{write_lines(sample_names)}" + drug_res_file = "~{write_lines(expanded_drug_res_markers)}" # Read in the sample names: sample_names = [] From 903276bad7bc5a7905c6c064fe42828ff34d6293 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Tue, 12 Dec 2023 19:12:20 -0500 Subject: [PATCH 287/297] Fixing another bug in ExpandedDrugResistanceMarkerAggregation.wdl --- wdl/ExpandedDrugResistanceMarkerAggregation.wdl | 1 + 1 file changed, 1 insertion(+) diff --git a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl index 76973b36e..0c7a0d62d 100644 --- a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl +++ b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl @@ -51,6 +51,7 @@ task CombineExpandedDrugResistanceMarkers { set -euxo pipefail python3 < Date: Thu, 14 Dec 2023 11:08:14 -0500 Subject: [PATCH 288/297] Now expanded drug reports contain synonymous mutations as well. --- wdl/ExpandedDrugResistanceMarkerAggregation.wdl | 1 - wdl/ExpandedDrugResistanceMarkerExtraction.wdl | 10 ++++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl index 0c7a0d62d..9eba060f3 100644 --- a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl +++ b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl @@ -1,7 +1,6 @@ version 1.0 import "tasks/Structs.wdl" -import "tasks/FunctionalAnnotation.wdl" as FUNK import "tasks/Finalize.wdl" as FF workflow ExpandedDrugResistanceMarkerExtraction { diff --git a/wdl/ExpandedDrugResistanceMarkerExtraction.wdl b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl index ffb9f874e..28949dddd 100644 --- a/wdl/ExpandedDrugResistanceMarkerExtraction.wdl +++ b/wdl/ExpandedDrugResistanceMarkerExtraction.wdl @@ -173,16 +173,14 @@ task CallDrugResistanceMutations { with open(gene_drug_report_all, 'w') as f: f.write(f"{header}\n") for a in annotations: - if "synonymous_variant" not in a: - f.write("\t".join(a)) - f.write("\n") + f.write("\t".join(a)) + f.write("\n") with open(gene_drug_report_prot, 'w') as f: f.write(f"{header}\n") for a in annotations: - if "synonymous_variant" not in a: - f.write("\t".join(a)) - f.write("\n") + f.write("\t".join(a)) + f.write("\n") print('Done') CODE From 25f8e5d2d4f9b4c803578993f059d7d010549a56 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 14 Dec 2023 12:53:20 -0500 Subject: [PATCH 289/297] Changed how file localization happens in ExpandedDrugResistanceMarkerAggregation.wdl --- wdl/ExpandedDrugResistanceMarkerAggregation.wdl | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl index 9eba060f3..f47057067 100644 --- a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl +++ b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl @@ -44,18 +44,33 @@ task CombineExpandedDrugResistanceMarkers { RuntimeAttr? runtime_attr_override } + parameter_meta { + expanded_drug_res_markers: { localization_optional: true } + } + Int disk_size = 10 + 10*ceil(size(expanded_drug_res_markers, "GB")) command <<< set -euxo pipefail + # Copy the files from the cloud to this machine here so it goes faster: + mkdir -p expanded_drug_reports + cd expanded_drug_reports + remote_sample_files=~{write_lines(expanded_drug_res_markers)} + cat ${remote_sample_files} | gsutil -m cp -I . + + # Create local file list: + cat ${remote_sample_files} | sed -e 's@^.*/@@' -e "s@^@$(pwd)@g" > local_sample_files.txt + + cd .. + python3 < Date: Thu, 14 Dec 2023 13:05:32 -0500 Subject: [PATCH 290/297] Fixing a typo in ExpandedDrugResistanceMarkerAggregation.wdl --- wdl/ExpandedDrugResistanceMarkerAggregation.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl index f47057067..2ac3a677b 100644 --- a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl +++ b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl @@ -60,7 +60,7 @@ task CombineExpandedDrugResistanceMarkers { cat ${remote_sample_files} | gsutil -m cp -I . # Create local file list: - cat ${remote_sample_files} | sed -e 's@^.*/@@' -e "s@^@$(pwd)@g" > local_sample_files.txt + cat ${remote_sample_files} | sed -e 's@^.*/@@' -e "s@^@$(pwd)/@g" > local_sample_files.txt cd .. From 39d70bdfb5457a99dda1dc753500c881afec9431 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Thu, 14 Dec 2023 16:59:13 -0500 Subject: [PATCH 291/297] Fixing trailing tab character. --- wdl/ExpandedDrugResistanceMarkerAggregation.wdl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl index 2ac3a677b..2885537b7 100644 --- a/wdl/ExpandedDrugResistanceMarkerAggregation.wdl +++ b/wdl/ExpandedDrugResistanceMarkerAggregation.wdl @@ -65,9 +65,10 @@ task CombineExpandedDrugResistanceMarkers { cd .. python3 <>> output { - File combined_report = "~{prefix}.expanded_drug_report_combined.tsv" + File combined_report = "~{prefix}.expanded_drug_report_combined.tsv.gz" } ######################### From 22ba3caca18f4b04c1a9490c2814296ec91df479 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 10 Jan 2024 10:26:55 -0500 Subject: [PATCH 292/297] Final cleanup commit. --- wdl/SRFlowcell.wdl | 5 +++-- wdl/TrainCnnFilters.wdl | 2 +- wdl/tasks/RemoveSingleOrganismContamination.wdl | 8 ++++---- wdl/tasks/Utils.wdl | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index d56c55da3..36d471c84 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -176,10 +176,11 @@ workflow SRFlowcell { } # Sort Duplicate Marked Bam: - call Utils.SortBam as t_008_SortAlignedDuplicateMarkedBam { + call Utils.SortSam as t_008_SortAlignedDuplicateMarkedBam { input: input_bam = t_007_MarkDuplicates.bam, - prefix = SM + ".aligned.merged.markDuplicates.sorted" + output_bam_basename = SM + ".aligned.merged.markDuplicates.sorted", + compression_level = 2 } # TODO: Add Fingerprinting? diff --git a/wdl/TrainCnnFilters.wdl b/wdl/TrainCnnFilters.wdl index 58dbe0f90..c991ee9a6 100644 --- a/wdl/TrainCnnFilters.wdl +++ b/wdl/TrainCnnFilters.wdl @@ -45,7 +45,7 @@ workflow TrainCnnFilters { # TODO: Validate that lengths of all inputs are the same: if ((length(vcfs) != length(vcf_indices)) || (length(vcfs) != length(vcf_indices)) || (length(vcfs) != length(vcf_indices)) || (length(vcfs) != length(vcf_indices)) || (length(vcfs) != length(vcf_indices))) { - call Utils.FailWithWarning {input: warning="Not all input arrays have the same length!"} + call Utils.StopWorkflow {input: reason="Not all input arrays have the same length!"} } # First create tensors for the input data: diff --git a/wdl/tasks/RemoveSingleOrganismContamination.wdl b/wdl/tasks/RemoveSingleOrganismContamination.wdl index 1b224b2b5..1210cc68c 100644 --- a/wdl/tasks/RemoveSingleOrganismContamination.wdl +++ b/wdl/tasks/RemoveSingleOrganismContamination.wdl @@ -56,13 +56,13 @@ workflow RemoveSingleOrganismContamination { # Some basic error handling: if (!defined(input_bam) && (!defined(fq_end1) || !defined(fq_end2))) { - call Utils.FailWithWarning as t_002_NoInputFileProvidedFailure { - input: warning = "No input file has been provided! You must provide either an input bam or input fastq1/fastq2 files." + call Utils.StopWorkflow as t_002_NoInputFileProvidedFailure { + input: reason = "No input file has been provided! You must provide either an input bam or input fastq1/fastq2 files." } } if (defined(input_bam) && (defined(fq_end1) || defined(fq_end2))) { - call Utils.FailWithWarning as t_003_TooManyInputsProvidedFailure { - input: warning = "Too many inputs provided! You must provide EITHER an input bam OR input fastq1/fastq2 files." + call Utils.StopWorkflow as t_003_TooManyInputsProvidedFailure { + input: reason = "Too many inputs provided! You must provide EITHER an input bam OR input fastq1/fastq2 files." } } diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 5d088966c..8cf9ee2c6 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -287,7 +287,7 @@ task MakeChrIntervalList { grep -v -e '^@HD' ~{true='-e' false='' length(filter) > 0} ~{sep=" -e " filter} | \ tee chrs.txt - cat chrs.txt | awk '{printf("%s:%d-%d\n", $1,$2,$3)}' > intervalList.intervals + cat chrs.txt | awk '{printf("%s:%d-%d\n", $1,$2,$3)}' > intervalList.intervals # Now make another output - a set of individual contig interval list files: while read line ; do @@ -2502,7 +2502,7 @@ task MapToTsv { } } -task CreateIGVSession{ +task CreateIGVSession { meta { description: "Create an IGV session given a list of IGV compatible file paths. Adapted / borrowed from https://github.com/broadinstitute/palantir-workflows/blob/mg_benchmark_compare/BenchmarkVCFs ." } From b4adfaad8c1f1d51231ef85c2a03d03f4dc8a263 Mon Sep 17 00:00:00 2001 From: Jonn Smith Date: Wed, 10 Jan 2024 10:28:05 -0500 Subject: [PATCH 293/297] Deprecating this branch. --- BRANCH_IS_DEPRECATED.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 BRANCH_IS_DEPRECATED.txt diff --git a/BRANCH_IS_DEPRECATED.txt b/BRANCH_IS_DEPRECATED.txt new file mode 100644 index 000000000..16a2e5bd5 --- /dev/null +++ b/BRANCH_IS_DEPRECATED.txt @@ -0,0 +1,7 @@ +THIS BRANCH IS DEPRECATED. + +AS OF 2024 Jan 09, ALL CODE IN THIS BRANCH HAS BEEN MERGED TO MAIN. + +THIS BRANCH SHALL CONTINUE TO EXIST AS A REFERENCE UNTIL +JONN IS BACK FROM PATERNITY LEAVE. + From fc35a15438a8ce462b9558c9af5731926b6e33c8 Mon Sep 17 00:00:00 2001 From: Kiran Garimella Date: Wed, 28 Feb 2024 11:10:09 -0500 Subject: [PATCH 294/297] Fixed an incorrect filename specification --- wdl/SRFlowcell.wdl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/wdl/SRFlowcell.wdl b/wdl/SRFlowcell.wdl index 36d471c84..8c63c9675 100644 --- a/wdl/SRFlowcell.wdl +++ b/wdl/SRFlowcell.wdl @@ -189,8 +189,8 @@ workflow SRFlowcell { # Recalibrate Base Scores: call SRUTIL.BaseRecalibrator as t_009_BaseRecalibrator { input: - input_bam = t_008_SortAlignedDuplicateMarkedBam.sorted_bam, - input_bam_index = t_008_SortAlignedDuplicateMarkedBam.sorted_bai, + input_bam = t_008_SortAlignedDuplicateMarkedBam.output_bam, + input_bam_index = t_008_SortAlignedDuplicateMarkedBam.output_bam_index, ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], @@ -204,8 +204,8 @@ workflow SRFlowcell { call SRUTIL.ApplyBQSR as t_010_ApplyBQSR { input: - input_bam = t_008_SortAlignedDuplicateMarkedBam.sorted_bam, - input_bam_index = t_008_SortAlignedDuplicateMarkedBam.sorted_bai, + input_bam = t_008_SortAlignedDuplicateMarkedBam.output_bam, + input_bam_index = t_008_SortAlignedDuplicateMarkedBam.output_bam_index, ref_fasta = ref_map["fasta"], ref_fasta_index = ref_map["fai"], @@ -217,8 +217,8 @@ workflow SRFlowcell { } } - File final_bam = select_first([t_010_ApplyBQSR.recalibrated_bam, t_008_SortAlignedDuplicateMarkedBam.sorted_bam]) - File final_bai = select_first([t_010_ApplyBQSR.recalibrated_bai, t_008_SortAlignedDuplicateMarkedBam.sorted_bai]) + File final_bam = select_first([t_010_ApplyBQSR.recalibrated_bam, t_008_SortAlignedDuplicateMarkedBam.output_bam]) + File final_bai = select_first([t_010_ApplyBQSR.recalibrated_bai, t_008_SortAlignedDuplicateMarkedBam.output_bam_index]) ############################################# # __ __ _ _ @@ -297,8 +297,8 @@ workflow SRFlowcell { t_005_AlignReads.bam, merged_bam, t_007_MarkDuplicates.bam, - t_008_SortAlignedDuplicateMarkedBam.sorted_bam, - t_008_SortAlignedDuplicateMarkedBam.sorted_bai, + t_008_SortAlignedDuplicateMarkedBam.output_bam, + t_008_SortAlignedDuplicateMarkedBam.output_bam_index, ], keyfile = keyfile } From 04db87560495cdaed36e76fd315bc3abcc091bee Mon Sep 17 00:00:00 2001 From: Shadi Zaheri Date: Thu, 11 Apr 2024 10:44:13 -0400 Subject: [PATCH 295/297] Streamlined BAM Index File Storage --- wdl/tasks/Finalize.wdl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wdl/tasks/Finalize.wdl b/wdl/tasks/Finalize.wdl index 56d0798cb..4402e1e38 100644 --- a/wdl/tasks/Finalize.wdl +++ b/wdl/tasks/Finalize.wdl @@ -22,7 +22,7 @@ task FinalizeToFile { outdir: "directory to which files should be uploaded" name: "name to set for uploaded file" } - + # Remove trailing slashes from a directory path. String gcs_output_dir = sub(outdir, "/+$", "") String gcs_output_file = gcs_output_dir + "/" + select_first([name, basename(file)]) From 64571bb00945ab881010b841738227ace7821317 Mon Sep 17 00:00:00 2001 From: Shadi Zaheri Date: Thu, 11 Apr 2024 10:47:12 -0400 Subject: [PATCH 296/297] Streamlined BAM Index File Storage (added the modified SRIndexBam workflow) --- wdl/SRIndexBam.wdl | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/wdl/SRIndexBam.wdl b/wdl/SRIndexBam.wdl index 4f376876c..1c16e17d9 100644 --- a/wdl/SRIndexBam.wdl +++ b/wdl/SRIndexBam.wdl @@ -6,12 +6,22 @@ import "tasks/Finalize.wdl" as FF workflow SRIndexBam { input { File bam - String outdir + String? outdir # Make outdir optional } call Utils.Index { input: bam = bam } + # If outdir is not provided, use the directory of the bam file as outdir. + # This uses a ternary conditional operator (if-else shorthand) to check if outdir is provided. + # If outdir is provided, it uses it; otherwise, it extracts the directory from the bam file path. + String finalOutdir = select_first([outdir, sub(bam, "/[^/]+$", "")]) - call FF.FinalizeToFile as FinalizeBamIndex { input: outdir = outdir, file = Index.bai } + # Call the FinalizeToFile task with the finalOutdir and the index file generated by the Utils.Index task + call FF.FinalizeToFile as FinalizeBamIndex { + input: + outdir = finalOutdir, + file = Index.bai + } + # call FF.FinalizeToFile as FinalizeBamIndex { input: outdir = outdir, file = Index.bai } output { File bai = FinalizeBamIndex.gcs_path From 84e00ce7fb2c0a67fe0433ccd8f7dbcb12dc7743 Mon Sep 17 00:00:00 2001 From: Shadi Zaheri <74751641+shadizaheri@users.noreply.github.com> Date: Thu, 11 Apr 2024 16:58:50 -0400 Subject: [PATCH 297/297] Update Utils.wdl - Local disk to SSD - samtools index -@ 2 ~{basename(prefix)} --- wdl/tasks/Utils.wdl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wdl/tasks/Utils.wdl b/wdl/tasks/Utils.wdl index 8cf9ee2c6..6fed2511b 100644 --- a/wdl/tasks/Utils.wdl +++ b/wdl/tasks/Utils.wdl @@ -1219,7 +1219,7 @@ task Index { set -euxo pipefail mv ~{bam} ~{prefix} - samtools index ~{basename(prefix)} + samtools index -@ 2 ~{basename(prefix)} >>> output { @@ -1240,7 +1240,7 @@ task Index { runtime { cpu: select_first([runtime_attr.cpu_cores, default_attr.cpu_cores]) memory: select_first([runtime_attr.mem_gb, default_attr.mem_gb]) + " GiB" - disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " LOCAL" + disks: "local-disk " + select_first([runtime_attr.disk_gb, default_attr.disk_gb]) + " SSD" bootDiskSizeGb: select_first([runtime_attr.boot_disk_gb, default_attr.boot_disk_gb]) preemptible: select_first([runtime_attr.preemptible_tries, default_attr.preemptible_tries]) maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) @@ -2610,4 +2610,4 @@ task SplitContigToIntervals { maxRetries: select_first([runtime_attr.max_retries, default_attr.max_retries]) docker: select_first([runtime_attr.docker, default_attr.docker]) } -} \ No newline at end of file +}