//! Converts a BAM file to the FASTQ format. //! //! This example only supports single segment reads. //! //! The result matches the output of `samtools fastq <src>`. use std::{ env, io::{self, BufWriter, Write}, }; use noodles_bam as bam; use noodles_sam::alignment::record::Flags; const FILTERS: Flags = Flags::SECONDARY.union(Flags::SUPPLEMENTARY); fn main() -> io::Result<()> { let src = env::args().nth(1).expect("missing src"); let mut reader = bam::io::reader::Builder.build_from_path(src)?; reader.read_header()?; let stdout = io::stdout().lock(); let mut writer = BufWriter::new(stdout); for result in reader.records() { let record = result?; let flags = record.flags(); if flags.is_segmented() { return Err(io::Error::new( io::ErrorKind::InvalidData, "this example only supports single segment reads", )); } else if flags.intersects(FILTERS) { continue; } write_record(&mut writer, &record)?; } Ok(()) } fn write_record<W>(writer: &mut W, record: &bam::Record) -> io::Result<()> where W: Write, { const DEFINITION_PREFIX: u8 = b'@'; const MISSING_NAME: &[u8] = b"*"; const SEPARATOR: u8 = b'+'; const LINE_FEED: u8 = b'\n'; writer.write_all(&[DEFINITION_PREFIX])?; let name = record .name() .map(|name| name.as_ref()) .unwrap_or(MISSING_NAME); writer.write_all(name)?; writer.write_all(&[LINE_FEED])?; let is_reversed_complemented = record.flags().is_reverse_complemented(); let bases: Vec<_> = record.sequence().iter().collect(); if is_reversed_complemented { for base in bases.into_iter().rev().map(complement_base) { writer.write_all(&[base])?; } } else { for base in bases { writer.write_all(&[base])?; } } writer.write_all(&[LINE_FEED])?; writer.write_all(&[SEPARATOR, LINE_FEED])?; let quality_scores = record.quality_scores(); let scores = quality_scores.as_ref().iter().copied().map(encode_score); if is_reversed_complemented { for n in scores.rev() { writer.write_all(&[n])?; } } else { for n in scores { writer.write_all(&[n])?; } } writer.write_all(&[LINE_FEED])?; Ok(()) } fn complement_base(b: u8) -> u8 { match b { b'A' => b'T', b'C' => b'G', b'G' => b'C', b'T' => b'A', b'U' => b'A', b'W' => b'W', b'S' => b'S', b'M' => b'K', b'K' => b'M', b'R' => b'Y', b'Y' => b'R', b'B' => b'V', b'D' => b'H', b'H' => b'D', b'V' => b'B', b'N' => b'N', _ => unreachable!(), } } fn encode_score(n: u8) -> u8 { const OFFSET: u8 = b'!'; n.checked_add(OFFSET).expect("attempt to add with overflow") }