mod cli;
mod utils;
mod bed;
mod reads;
mod cigar;
mod paf;
use std::fs::File;
use std::io::BufWriter;
use std::fs::OpenOptions;
use anyhow::Result;
use anyhow::Ok;
use clap::Parser;
use noodles::bam;
use cli::Opts;
use reads::get_bam_reads;
use utils::{read_bed, write_fasta_record};
use paf::PafIndex;
use crate::paf::read_paf_record_at_offset;
use crate::reads::ToCigarOps;
use crate::utils::extract_from_fasta_coords;
use crate::utils::get_read_cuts;
use crate::utils::write_fastq_record;
fn main() -> Result<()>{
let opts: Opts = Opts::parse();
eprintln!("{:#?}", opts);
crate::cli::check_option_values(&opts);
crate::cli::check_inputs_exist(&opts);
eprintln!("Reading bed file");
let regions: Vec<(noodles::core::Region, String, String)> = read_bed(&opts);
let output_file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&opts.output)?;
let mut read_writer: BufWriter<File> = BufWriter::new(output_file);
if opts.bam.to_str() != Some("None"){
eprintln!("Bam mode");
eprintln!("Extracting sequences");
extract_from_bam(&opts, regions, &mut read_writer);
}
else if opts.paf.to_str() != Some("None") && opts.query_ref.to_str() != Some("None"){
eprintln!("paf mode");
eprintln!("Extracting sequences");
extract_from_paf(&opts, regions, &mut read_writer);
}
eprintln!("Done");
Ok(())
}
pub fn extract_from_bam(opts: &Opts, regions: Vec<(noodles::core::Region, String, String)>, read_writer: &mut BufWriter<File>) {
for (region, region_name, chr) in regions.iter() {
eprintln!("===============================");
eprintln!("Analysing region: {}, {}",region, region_name);
eprintln!("===============================");
if region.name().contains(&('#' as u8)) {
eprintln!("Region {} has a #, skipping", region_name);
continue;
}
let mut reader= bam::io::indexed_reader::Builder::default().build_from_path(&opts.bam).expect("Couldn't read bam");
let header: noodles::sam::Header = reader.read_header().expect("Couldn't read header");
let query: bam::io::reader::Query<File> = reader.query(&header, ®ion).expect("Couldn't find query");
let overlapping_reads: Vec<(String, Vec<u8>, String, usize, usize)> = get_bam_reads(&opts, query, ®ion);
if overlapping_reads.len() == 0 {
eprintln!("No reads found for region in bam file. Skipping region: {}", region_name);
continue;
}
let region_start = usize::from(region.interval().start().unwrap());
let region_end = usize::from(region.interval().end().unwrap());
for (name, subseq, subqual, _ref_start, _ref_end) in overlapping_reads {
let head = format!("{}|{}:{:?}-{:?}|{}", name, chr, region_start, region_end, region_name);
if opts.fastq {
write_fastq_record(read_writer, &head, &std::str::from_utf8(&subseq).expect("unexpected utf8 in sequence"), &subqual).expect("Couldn't write fastq record");
}
else {
write_fasta_record(read_writer, &head, &std::str::from_utf8(&subseq).expect("unexpected utf8 in sequence")).expect("Couldn't write fasta record");
}
}
}
}
pub fn extract_from_paf(opts: &Opts, regions: Vec<(noodles::core::Region, String, String)>, read_writer: &mut BufWriter<File>) {
let paf_path: &str = opts.paf.to_str().expect("couldn't get paf string");
let query_ref = opts.query_ref.to_str().expect("Couldn't get query_ref string");
let index_path = format!("{}.idx", paf_path);
let index = if opts.use_paf_index {
if std::path::Path::new(&index_path).exists() {
eprintln!("Loading PAF index from {}", index_path);
PafIndex::load(&index_path).expect("couldn't load index")
} else {
eprintln!("Building PAF index...");
let index = PafIndex::build(paf_path).expect("couldn't build paf index");
index.save(&index_path).expect("Failed to save paf index");
eprintln!("Index saved to {}", index_path);
index
}
} else {
PafIndex::build(paf_path).expect("Couldn't build paf index")
};
for (region, region_name, chr) in regions.iter() {
eprintln!("===============================");
eprintln!("Analysing region: {}, {}",region, region_name);
eprintln!("===============================");
if region.name().contains(&('#' as u8)) {
eprintln!("Region {} has a #, skipping", region_name);
continue;
}
let region_start = usize::from(region.interval().start().unwrap());
let region_end = usize::from(region.interval().end().unwrap());
let overlapping_entries = index.query(chr, region_start, region_end);
eprintln!("Found {} overlapping alignments", overlapping_entries.len());
for entry in overlapping_entries {
let paf_record = read_paf_record_at_offset(paf_path, entry.offset).unwrap();
if paf_record.target_start > region_start {
eprintln!("Warning: Alignment starts after region start, may be incomplete");
}
if paf_record.target_end < region_end {
eprintln!("Warning: Alignment ends before region end, may be incomplete");
}
if let Some(cigar_str) = &paf_record.cigar {
let cigar_ops = cigar_str.as_str().to_cigar_ops();
let cuts = get_read_cuts(&cigar_ops, paf_record.target_start, region_start, region_end);
if cuts.read_start == 0 && cuts.read_end == 0 {
eprintln!("Warning: No valid overlap found, skipping");
continue;
}
if cuts.read_start >= cuts.read_end {
eprintln!("Warning: Invalid coordinates (start {} >= end {}), skipping",
cuts.read_start, cuts.read_end);
continue;
}
let query_start = paf_record.query_start + cuts.read_start;
let query_end = paf_record.query_start + cuts.read_end;
eprintln!("Query coords: {}:{}-{}", paf_record.query_name, query_start, query_end);
let sequence = extract_from_fasta_coords(query_ref, &paf_record.query_name, query_start, query_end).expect("couldn't extract fasta sequence");
let header = format!("{}|ref_{}:{}-{}|query_{}:{}-{}",
paf_record.query_name,
region_name,
region_start,
region_end,
paf_record.query_name,
query_start,
query_end);
write_fasta_record(read_writer, &header, &sequence).expect("Failed to write fasta");
}
}
}
}