version 1.0 workflow GDC_Samtools_Fastq { input { File input_bam # DEFAULT FOR TERRA: Use the public, pinned version String docker_image = "staphb/samtools:1.22" } # 1. Index BAM file call IndexBam { input: bam = input_bam, docker = docker_image } # 2. Split the BAM by Read Group (RG) # This is critical for GDC BAMs which often contain multiple lanes call SplitBamByRG { input: bam = input_bam, bai = IndexBam.bai, docker = docker_image } # 3. Convert each split BAM into FASTQ pairs (Parallel Scatter) scatter (split_bam in SplitBamByRG.split_bams) { call BamToFastq { input: bam = split_bam, docker = docker_image } } # 4. Merge the scattered FASTQ files into a single pairs call MergeFastqs { input: r1_files = BamToFastq.r1, r2_files = BamToFastq.r2, base_name = basename(input_bam, ".bam"), docker = docker_image } output { File merged_r1 = MergeFastqs.merged_r1 File merged_r2 = MergeFastqs.merged_r2 } } # TASK 1: Index BAM file task IndexBam { input { File bam String docker } # Calculate basename so we can symlink with the correct original name String filename = basename(bam) command <<< set -e # 1. Symlink input to current working directory # This solves the issue of read-only input directories or missing sidecar files ln -s "~{bam}" "~{filename}" # 2. Index the local symlink samtools index "~{filename}" >>> runtime { docker: docker memory: "4 GB" cpu: 1 disks: "local-disk 50 HDD" # Ensure enough disk space for the BAM } output { # samtools index creates .bam.bai File bai = "~{filename}.bai" } } # TASK 2: Split BAM by Read Group task SplitBamByRG { input { File bam File bai String docker } String filename = basename(bam) command <<< set -e # 1. Symlink BAM and BAI to current directory # This forces them to exist side-by-side, which samtools requires ln -s "~{bam}" "~{filename}" ln -s "~{bai}" "~{filename}.bai" # 2. Split using the local symlink # -f: Naming format (%* = original basename, %# = Read Group Index) # -u: Unaccounted reads (if any) go here samtools split -f '%*_%#.bam' -u unassigned.bam "~{filename}" >>> runtime { docker: docker memory: "4 GB" cpu: 2 disks: "local-disk 100 HDD" } output { # Capture all BAMs generated by the split Array[File] split_bams = glob("*_*.bam") } } # TASK 3: Convert BAM to FASTQ task BamToFastq { input { File bam String docker } # Extract the filename base (e.g., "sample_RG1.bam" -> "sample_RG1") String basename = basename(bam, ".bam") command <<< set -e # PIPELINE EXPLANATION: # 1. collate: Shuffles reads so pairs are together (required for fastq) # -u: Uncompressed output (faster for pipe) # -O: Output to stdout # 2. fastq: Converts to FASTQ format # -1/-2: Paired output files # -0/-s: Discard singletons/orphans (GDC recommendation) # -n: Use 'standard' /1 /2 suffixes in header # -O: RESTORE ORIGINAL QUALITIES (Critical GDC Requirement) samtools collate -u -O ~{bam} | \ samtools fastq \ -1 ~{basename}_R1.fastq.gz \ -2 ~{basename}_R2.fastq.gz \ -0 /dev/null \ -s /dev/null \ -n \ -O \ - >>> runtime { docker: docker memory: "8 GB" # Increased for safety with larger GDC files cpu: 2 disks: "local-disk 50 HDD" } output { File r1 = "~{basename}_R1.fastq.gz" File r2 = "~{basename}_R2.fastq.gz" } } # TASK 4: Merge Fastq files task MergeFastqs { input { Array[File] r1_files Array[File] r2_files String base_name String docker } command <<< set -e # Concatenate the gzipped files (valid for bgzip/gzip) cat ~{sep=" " r1_files} > "~{base_name}_R1.fastq.gz" cat ~{sep=" " r2_files} > "~{base_name}_R2.fastq.gz" >>> runtime { docker: docker memory: "4 GB" cpu: 1 disks: "local-disk 100 HDD" } output { File merged_r1 = "~{base_name}_R1.fastq.gz" File merged_r2 = "~{base_name}_R2.fastq.gz" } }