use log::{error, info, trace, warn};
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::process;
use flate2::write::GzEncoder;
use flate2::Compression;
use noodles::cram::record::Record as CramRecord;
use noodles::sam::alignment::record::data::field::Tag;
use seq_io::fastq::Position;
use serde::{Deserialize, Serialize};
use super::cram_stats;
use noodles::cram;
#[derive(Serialize, Deserialize)]
pub struct Index {
pub header: Header,
pub content: Droplets,
}
#[derive(Serialize, Deserialize)]
pub struct Header {
pub magic: u32, pub is_sorted: bool, }
#[derive(Serialize, Deserialize, Debug)]
pub struct Droplets {
pub droplets: HashMap<String, CramDroplet>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct UnsortedDroplet {
pub n_reads: u32,
pub read_names: Vec<String>,
pub f_pos: Vec<u64>,
pub r_pos: Vec<u64>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SortedDroplet {
pub n_reads: u32,
pub read_names: Vec<String>,
pub f_start_pos: u64, pub f_end_pos: u64,
pub r_start_pos: u64,
pub r_end_pos: u64,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum Droplet {
Unsorted(UnsortedDroplet),
Sorted(SortedDroplet),
}
impl Droplet {
pub fn get_n_reads(&self) -> u32 {
match self {
Droplet::Unsorted(droplet) => droplet.n_reads,
Droplet::Sorted(droplet) => droplet.n_reads,
}
}
pub fn get_reads_names(&self) -> &Vec<String> {
match self {
Droplet::Unsorted(droplet) => &droplet.read_names,
Droplet::Sorted(droplet) => &droplet.read_names,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CramChunk {
pub pos: u64, pub reads_start: u32,
pub reads_end: u32,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CramDroplet {
pub n_reads: u32,
pub chunks: Vec<CramChunk>,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "seq_io::fastq::Position")]
struct PositionDef {
#[serde(getter = "Position::line")]
line: u64,
#[serde(getter = "Position::byte")]
byte: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Adapter {
#[serde(with = "PositionDef")]
pub pos: Position,
}
impl From<PositionDef> for Position {
fn from(pos: PositionDef) -> Position {
Position::new(pos.line, pos.byte)
}
}
pub fn index(cram: &PathBuf) {
warn!("Experimental function on cram files");
let mut index = Index {
header: Header {
magic: 0xF09FA6A0,
is_sorted: true,
},
content: Droplets {
droplets: HashMap::new(),
},
};
let mut cram_reader = cram::io::reader::Builder::default()
.build_from_path(cram)
.unwrap();
let _header = cram_reader.read_header().expect("bad header");
let mut previous_barcode: String = String::new();
let mut previous_position = cram_reader.position().unwrap();
while let Some(container) = cram_reader
.read_container() .expect("error reading cram")
{
let current_position = cram_reader.position().unwrap();
trace!("Reading data container at position {}", current_position);
let slices = container.slices();
for slice in slices {
let mut n_reads_iterated_over = 0;
let rs = slice.records(container.compression_header()).unwrap();
let chunks: Vec<Vec<_>> = rs.chunks(2).map(|r| r.to_vec()).collect();
let mut chunks_iterator = chunks.iter();
while let Some(pair) = chunks_iterator.next() {
validate_read_pair(pair);
let bc_tag = pair[0].tags().get(&Tag::CELL_BARCODE_ID);
let current_barcode: String = cram_stats::reformat_value_string(
cram_stats::BCValue(bc_tag.unwrap().clone()).to_string(),
);
if current_barcode != previous_barcode {
trace!("Found a new barcode: {:?}", ¤t_barcode);
index.content.droplets.insert(
current_barcode.clone(),
CramDroplet {
n_reads: 1,
chunks: vec![CramChunk {
pos: current_position,
reads_start: n_reads_iterated_over,
reads_end: n_reads_iterated_over + 1,
}],
},
);
previous_barcode = current_barcode;
previous_position = current_position;
} else {
if current_position == previous_position {
if let Some(droplet) = index.content.droplets.get_mut(¤t_barcode) {
trace!("Updating existing barcode: {:?}", ¤t_barcode);
droplet.n_reads += 1;
let chunk = droplet
.chunks
.iter_mut()
.find(|c| c.pos == current_position)
.unwrap();
chunk.reads_end = n_reads_iterated_over + 1;
}
} else {
if let Some(droplet) = index.content.droplets.get_mut(¤t_barcode) {
trace!(
"Updating existing barcode (new chunk): {:?}",
¤t_barcode
);
droplet.n_reads += 1;
droplet.chunks.push(CramChunk {
pos: current_position,
reads_start: n_reads_iterated_over,
reads_end: n_reads_iterated_over + 1,
});
}
previous_position = current_position;
}
}
n_reads_iterated_over += 2;
}
}
previous_position = current_position;
}
let mut index_path = cram.clone();
index_path.set_extension("idx");
let index_file = match File::create(&index_path) {
Ok(file) => file,
Err(_) => {
error!("Could not create index file {}", &index_path.display());
process::exit(1)
}
};
let index_vec = match bincode::serialize(&index) {
Ok(v) => v,
Err(_) => {
error!("Could not serialize index structure to vector");
process::exit(1)
}
};
let mut encoder = GzEncoder::new(index_file, Compression::default());
encoder.write_all(&index_vec).expect("Error writing index");
info!("Written sorted index to {}", index_path.display());
}
fn validate_read_pair(pair: &Vec<CramRecord>) {
if pair.len() != 2 {
trace!("{:?}", pair);
error!("Read pair does not have 2 reads. Exiting.");
process::exit(1);
}
if pair[0].name() != pair[1].name() {
trace!("{:?}", pair);
error!("Reads in a pair do not have the same name. Exiting.");
process::exit(1);
}
if !pair[0].bam_flags().is_first_segment() || !pair[1].bam_flags().is_last_segment() {
trace!("{:?}", pair);
error!("Read pair does not have the correct BAM flags. Exiting.");
process::exit(1);
}
let fw_barcodes = pair[0].tags().get(&Tag::CELL_BARCODE_ID);
let rv_barcodes = pair[1].tags().get(&Tag::CELL_BARCODE_ID);
if fw_barcodes != rv_barcodes {
trace!("{:?}", pair);
error!("Reads in a pair do not have the same barcode. Exiting.");
process::exit(1);
}
}