use crate::collation::BarcodeRole;
use crate::io::{
NewI8, NewI16, NewI32, NewI64, NewI128, NewU8, NewU16, NewU32, NewU64, NewU128, TryWrapper,
};
use crate::{
io as rad_io,
rad_types::{
MappedFragmentOrientation, MappingType, PrimitiveInteger, RadIntId, RadType, TagSection,
TagValue,
},
utils,
};
use anyhow::{self, Context, bail};
use bio_types::strand::{Same, Strand};
use libradicl_macros::UmiTagged;
use scroll::Pread;
use smallvec::SmallVec;
use std::io::{Read, Write};
use std::mem;
fn argsort<T: Ord>(data: &[T]) -> Vec<usize> {
let mut indices = (0..data.len()).collect::<Vec<_>>();
indices.sort_unstable_by_key(|&i| &data[i]);
indices
}
#[allow(unused)]
fn argsort_by<T, F>(data: &[T], mut compare: F) -> Vec<usize>
where
F: FnMut(&T, &T) -> std::cmp::Ordering,
{
let mut indices: Vec<usize> = (0..data.len()).collect();
indices.sort_unstable_by(|&i, &j| compare(&data[i], &data[j]));
indices
}
fn reorder_in_place<T>(data: &mut [T], indices: &[usize]) {
let mut visited = vec![false; data.len()];
for start in 0..data.len() {
if visited[start] {
continue;
}
let mut current = start;
let mut next = indices[current];
while next != start {
visited[current] = true;
data.swap(current, next);
current = next;
next = indices[next];
}
visited[current] = true;
}
}
pub type AlevinFryReadRecord = AlevinFryReadRecordT<u64>;
pub type AlevinFryReadRecordU64 = AlevinFryReadRecordT<u64>;
pub type AlevinFryReadRecordU128 = AlevinFryReadRecordT<u128>;
pub type ScLongReadRecord = ScLongReadRecordT<u64>;
pub type ScLongReadRecordU64 = ScLongReadRecordT<u64>;
pub type ScLongReadRecordU128 = ScLongReadRecordT<u128>;
pub type AlevinFryReadRecordWithPosition = AlevinFryReadRecordWithPositionT<u64>;
pub type AlevinFryReadRecordWithPositionU64 = AlevinFryReadRecordWithPositionT<u64>;
pub type AlevinFryReadRecordWithPositionU128 = AlevinFryReadRecordWithPositionT<u128>;
pub trait RecordHeader {
type RecordType: MappedRecord;
fn naln(&self) -> u32;
}
pub trait CollatableRecordHeader<B: ConvertiblePrimitiveInteger>: RecordHeader {
fn collate_key(&self) -> B;
fn collation_group_key(
&self,
_ctx: &<<Self as RecordHeader>::RecordType as MappedRecord>::ParsingContext,
) -> u64
where
u64: From<B>,
{
self.collate_key().into()
}
fn write_fields<W: Write>(
&self,
writer: &mut W,
_ctx: &<<Self as RecordHeader>::RecordType as MappedRecord>::ParsingContext,
) -> anyhow::Result<()>;
}
pub struct AlevinFryReadRecordHeader<B: ConvertiblePrimitiveInteger> {
pub naln: u32,
pub bc: B,
pub umi: u64,
}
impl<B: ConvertiblePrimitiveInteger> RecordHeader for AlevinFryReadRecordHeader<B> {
type RecordType = AlevinFryReadRecordT<B>;
fn naln(&self) -> u32 {
self.naln
}
}
impl<B: ConvertiblePrimitiveInteger> CollatableRecordHeader<B> for AlevinFryReadRecordHeader<B> {
fn collate_key(&self) -> B {
self.bc
}
fn write_fields<W: Write>(
&self,
writer: &mut W,
ctx: &<<Self as RecordHeader>::RecordType as MappedRecord>::ParsingContext,
) -> anyhow::Result<()> {
let na: u32 = self.naln();
RadIntId::U32
.write_to(na, writer)
.context("couldn't write number of alignments for record")?;
ctx.bct
.write_to(self.bc, writer)
.context("couldn't write bc field for record")?;
ctx.umit
.write_to(self.umi, writer)
.context("couldn't write umi field for record")?;
Ok(())
}
}
pub struct ScLongReadRecordHeader<B: ConvertiblePrimitiveInteger> {
pub naln: u32,
pub bc: B,
pub umi: u64,
}
impl<B: ConvertiblePrimitiveInteger> RecordHeader for ScLongReadRecordHeader<B> {
type RecordType = ScLongReadRecordT<B>;
fn naln(&self) -> u32 {
self.naln
}
}
impl<B: ConvertiblePrimitiveInteger> CollatableRecordHeader<B> for ScLongReadRecordHeader<B> {
fn collate_key(&self) -> B {
self.bc
}
fn write_fields<W: Write>(
&self,
writer: &mut W,
ctx: &<<Self as RecordHeader>::RecordType as MappedRecord>::ParsingContext,
) -> anyhow::Result<()> {
let na: u32 = self.naln();
RadIntId::U32
.write_to(na, writer)
.context("couldn't write number of alignments for record")?;
ctx.bct
.write_to(self.bc, writer)
.context("couldn't write bc field for record")?;
ctx.umit
.write_to(self.umi, writer)
.context("couldn't write umi field for record")?;
Ok(())
}
}
pub struct AtacSeqReadRecordHeader {
pub naln: u32,
pub bc: u64,
}
impl RecordHeader for AtacSeqReadRecordHeader {
type RecordType = AtacSeqReadRecord;
fn naln(&self) -> u32 {
self.naln
}
}
impl CollatableRecordHeader<u64> for AtacSeqReadRecordHeader {
fn collate_key(&self) -> u64 {
self.bc
}
fn write_fields<W: Write>(
&self,
writer: &mut W,
ctx: &<<Self as RecordHeader>::RecordType as MappedRecord>::ParsingContext,
) -> anyhow::Result<()> {
let na: u32 = self.naln();
RadIntId::U32
.write_to(na, writer)
.context("couldn't write number of alignments for record")?;
ctx.bct
.write_to(self.bc, writer)
.context("couldn't write bc field for record")?;
Ok(())
}
}
#[allow(unused)]
struct PiscemBulkReadRecordHeader {
pub na: u32,
}
impl RecordHeader for PiscemBulkReadRecordHeader {
type RecordType = PiscemBulkReadRecord;
fn naln(&self) -> u32 {
self.na
}
}
#[allow(unused)]
struct GenericReadRecordHeader {
pub na: u32,
}
impl RecordHeader for GenericReadRecordHeader {
type RecordType = GenericReadRecord;
fn naln(&self) -> u32 {
self.na
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GenericReadRecord {
pub naln: u32,
pub naln_tags: u32,
pub rtags: Vec<TagValue>,
pub atags: Vec<TagValue>,
}
impl GenericReadRecord {
pub fn fmt_with_context(
&self,
ctx: &GenericReadRecordContext,
f: &mut impl Write,
) -> std::io::Result<()> {
f.write_all(
format!(
"GenericReadRecord{{ naln: {}, naln_tags: {},\nrtags: {},\natags: {} }}\n",
self.naln,
self.naln_tags,
ctx.read_tags
.iter_desc()
.zip(self.rtags.iter())
.map(|(td, tv)| format!("{} : [{:?}]", td.name, tv))
.collect::<Vec<_>>()
.join(", "),
self.atags
.chunks_exact(self.naln_tags as usize)
.map(|vchunk| {
ctx.aln_tags
.iter_desc()
.zip(vchunk.iter())
.map(|(td, tv)| format!("{} : [{:?}]", td.name, tv))
.collect::<Vec<_>>()
.join(", ")
})
.collect::<Vec<_>>()
.join("\n\t")
)
.as_bytes(),
)
}
}
#[derive(Debug, Clone)]
pub struct GenericReadRecordContext {
pub read_tags: TagSection,
pub aln_tags: TagSection,
}
pub trait KnownSize {
fn nbytes(na: u32, ctx: &<Self as MappedRecord>::ParsingContext) -> usize
where
Self: MappedRecord;
fn nbytes_aln(ctx: &<Self as MappedRecord>::ParsingContext) -> usize
where
Self: MappedRecord;
}
impl<B: ConvertiblePrimitiveInteger> KnownSize for AlevinFryReadRecordT<B> {
fn nbytes(na: u32, ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>() +
ctx.bct.bytes_for_type() +
ctx.umit.bytes_for_type() +
(na as usize * Self::nbytes_aln(ctx))
}
fn nbytes_aln(_ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>()
}
}
impl<B: ConvertiblePrimitiveInteger> KnownSize for AlevinFryReadRecordWithPositionT<B> {
fn nbytes(na: u32, ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>() +
ctx.bct.bytes_for_type() +
ctx.umit.bytes_for_type() +
(na as usize * Self::nbytes_aln(ctx))
}
fn nbytes_aln(_ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>()
+ std::mem::size_of::<u32>()
}
}
impl KnownSize for PiscemBulkReadRecord {
fn nbytes(na: u32, ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>() +
ctx.frag_map_t.bytes_for_type() +
(na as usize * Self::nbytes_aln(ctx))
}
fn nbytes_aln(_ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>() + std::mem::size_of::<u32>() + std::mem::size_of::<u16>()
}
}
impl<B: ConvertiblePrimitiveInteger> KnownSize for ScLongReadRecordT<B> {
fn nbytes(na: u32, ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>() +
ctx.bct.bytes_for_type() +
ctx.umit.bytes_for_type() +
(na as usize * Self::nbytes_aln(ctx))
}
fn nbytes_aln(_ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>()
+ std::mem::size_of::<u32>()
+ std::mem::size_of::<u32>()
+ std::mem::size_of::<i32>()
+ std::mem::size_of::<u32>()
}
}
impl KnownSize for AtacSeqReadRecord {
fn nbytes(na: u32, ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>() +
ctx.bct.bytes_for_type() +
(na as usize * Self::nbytes_aln(ctx))
}
fn nbytes_aln(_ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>()
+ std::mem::size_of::<u32>()
+ std::mem::size_of::<u16>()
+ std::mem::size_of::<u8>()
}
}
pub trait UmiTaggedRecord {
fn umi(&self) -> u64;
}
#[derive(Clone, Debug, PartialEq, Eq, UmiTagged)]
pub struct AlevinFryReadRecordT<B: ConvertiblePrimitiveInteger> {
pub bc: B,
pub umi: u64,
pub dirs: Vec<bool>,
pub refs: Vec<u32>,
}
#[derive(Clone, Debug, PartialEq, Eq, UmiTagged)]
pub struct AlevinFryReadRecordWithPositionT<B: ConvertiblePrimitiveInteger> {
pub bc: B,
pub umi: u64,
pub dirs: Vec<bool>,
pub refs: Vec<u32>,
pub pos: Vec<u32>,
}
#[derive(Debug)]
pub struct PiscemBulkReadRecord {
pub frag_type: u8,
pub dirs: Vec<MappedFragmentOrientation>,
pub refs: Vec<u32>,
pub positions: Vec<u32>,
pub frag_lengths: Vec<u16>,
}
#[derive(Clone, Debug, PartialEq, Eq, UmiTagged)]
pub struct ScLongReadRecordT<B: ConvertiblePrimitiveInteger> {
pub bc: B,
pub umi: u64,
pub dirs: Vec<bool>,
pub refs: Vec<u32>,
pub as_scores: Vec<i32>,
pub starts: Vec<u32>,
pub ends: Vec<u32>,
pub tlens: Vec<u32>,
}
#[derive(Debug)]
pub struct AtacSeqReadRecord {
pub bc: u64,
pub start_pos: Vec<u32>,
pub refs: Vec<u32>,
pub frag_lengths: Vec<u16>,
pub map_type: Vec<u8>,
}
pub trait CollatableMappedRecord<B: ConvertiblePrimitiveInteger>: MappedRecord
where
<Self as CollatableMappedRecord<B>>::CollatableRecordHeader: RecordHeader,
<<Self as CollatableMappedRecord<B>>::CollatableRecordHeader as RecordHeader>::RecordType:
MappedRecord<ParsingContext = Self::ParsingContext>,
{
type CollatableRecordHeader: CollatableRecordHeader<B>;
fn from_bytes_with_header_retain_ori<T: Read>(
reader: &mut T,
hdr: &mut Self::CollatableRecordHeader,
ctx: &<Self as MappedRecord>::ParsingContext,
expected_ori: &MappedFragmentOrientation,
) -> Self;
fn set_collate_key(&mut self, k: B);
fn collate_key(&self) -> B;
fn from_bytes_collatable_header<T: Read>(
reader: &mut T,
context: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader>;
fn peek_collatable_header(
reader: &[u8],
context: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader>;
}
pub trait MappedRecord {
type ParsingContext;
type PeekResult;
fn peek_record(buf: &[u8], ctx: &Self::ParsingContext) -> Self::PeekResult;
fn from_bytes_with_context<T: Read>(reader: &mut T, ctx: &Self::ParsingContext) -> Self;
fn write<W: Write>(&self, writer: &mut W, ctx: &Self::ParsingContext) -> anyhow::Result<()>;
fn is_empty(&self) -> bool;
fn num_aln(&self) -> usize;
fn refs(&self) -> &[u32];
fn has_alignment_on_strand(&self, s: Strand) -> bool;
}
pub trait RecordContext {
fn get_context_from_tag_section(
ft: &TagSection,
rt: &TagSection,
at: &TagSection,
) -> anyhow::Result<Self>
where
Self: Sized;
}
impl RecordContext for GenericReadRecordContext {
fn get_context_from_tag_section(
_ft: &TagSection,
rt: &TagSection,
at: &TagSection,
) -> anyhow::Result<Self> {
Ok(Self {
read_tags: rt.clone(),
aln_tags: at.clone(),
})
}
}
#[derive(Debug, Clone)]
pub struct AlevinFryRecordContext {
pub bct: RadIntId,
pub umit: RadIntId,
}
impl RecordContext for AlevinFryRecordContext {
fn get_context_from_tag_section(
_ft: &TagSection,
rt: &TagSection,
_at: &TagSection,
) -> anyhow::Result<Self> {
let bct = rt
.get_tag_type("b")
.expect("alevin-fry record context requires a \'b\' read-level tag");
let umit = rt
.get_tag_type("u")
.expect("alevin-fry record context requires a \'u\' read-level tag");
if let (RadType::Int(x), RadType::Int(y)) = (bct, umit) {
Ok(Self { bct: x, umit: y })
} else {
bail!("alevin-fry record context requires that b and u tags are of type RadType::Int");
}
}
}
impl AlevinFryRecordContext {
pub fn from_bct_umit(bct: RadIntId, umit: RadIntId) -> Self {
Self { bct, umit }
}
}
#[derive(Debug, Clone)]
pub struct PiscemBulkRecordContext {
pub frag_map_t: RadIntId,
}
impl RecordContext for PiscemBulkRecordContext {
fn get_context_from_tag_section(
_ft: &TagSection,
rt: &TagSection,
_at: &TagSection,
) -> anyhow::Result<Self> {
let frag_map_t = rt
.get_tag_type("frag_map_type")
.expect("psicem bulk record context requires a \"frag_map_type\" read-level tag");
if let RadType::Int(x) = frag_map_t {
Ok(Self { frag_map_t: x })
} else {
bail!(
"piscem bulk record context requries that \"frag_map_type\" tag is of type RadType::Int"
);
}
}
}
impl MappedRecord for PiscemBulkReadRecord {
type ParsingContext = PiscemBulkRecordContext;
type PeekResult = Option<u64>;
fn is_empty(&self) -> bool {
self.refs.is_empty()
}
fn num_aln(&self) -> usize {
self.refs.len()
}
fn refs(&self) -> &[u32] {
&self.refs
}
fn has_alignment_on_strand(&self, s: Strand) -> bool {
match s {
Strand::Unknown => !self.refs.is_empty(),
Strand::Forward => self.dirs.iter().any(|&x| {
matches!(
x,
MappedFragmentOrientation::Forward
| MappedFragmentOrientation::ForwardReverse
| MappedFragmentOrientation::ForwardForward
| MappedFragmentOrientation::Unknown
)
}),
Strand::Reverse => self.dirs.iter().any(|&x| {
matches!(
x,
MappedFragmentOrientation::Reverse
| MappedFragmentOrientation::ReverseForward
| MappedFragmentOrientation::ReverseReverse
| MappedFragmentOrientation::Unknown
)
}),
}
}
#[inline]
fn from_bytes_with_context<T: Read>(reader: &mut T, ctx: &Self::ParsingContext) -> Self {
const MASK_LOWER_30_BITS: u32 = 0xC0000000;
const MASK_UPPER_2_BITS: u32 = 0x3FFFFFFF;
let mut rbuf = [0u8; 255];
reader.read_exact(&mut rbuf[0..4]).unwrap();
let na = rbuf.pread::<u32>(0).unwrap();
let fmt = rad_io::read_into_u64(reader, &ctx.frag_map_t);
let f = MappingType::from_u8(fmt as u8);
let mut rec = Self {
frag_type: fmt as u8,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
positions: Vec::with_capacity(na as usize),
frag_lengths: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let dir_int = (v & MASK_LOWER_30_BITS) >> 30;
let dir = MappedFragmentOrientation::from_u32_paired_status(dir_int, f);
rec.dirs.push(dir);
rec.refs.push(v & MASK_UPPER_2_BITS);
reader.read_exact(&mut rbuf[0..4]).unwrap();
let pos = rbuf.pread::<u32>(0).unwrap();
rec.positions.push(pos);
reader.read_exact(&mut rbuf[0..2]).unwrap();
let flen = rbuf.pread::<u16>(0).unwrap();
rec.frag_lengths.push(flen);
}
rec
}
#[inline]
fn peek_record(_buf: &[u8], _ctx: &Self::ParsingContext) -> Self::PeekResult {
unimplemented!(
"Currently there is no implementation for peek_record for PiscemBulkReadRecord. This should not be needed"
);
}
#[inline]
fn write<W: Write>(&self, writer: &mut W, _ctx: &Self::ParsingContext) -> anyhow::Result<()> {
let na: u32 = self.refs.len().try_into()?;
writer
.write_all(&na.to_le_bytes())
.context("couldn't write number of alignments for record")?;
let fmt: u8 = self.frag_type;
writer
.write_all(&fmt.to_le_bytes())
.context("couldn't write frag_map_t for the record")?;
for (dir, ref_idx, pos, length) in
itertools::izip!(&self.dirs, &self.refs, &self.positions, &self.frag_lengths)
{
let encoded_dir: u32 = (*dir).into();
let encoded_dir_idx: u32 = (encoded_dir << 30) | ref_idx;
writer
.write_all(&encoded_dir_idx.to_le_bytes())
.context("couldn't write frag_map_type and ref for record")?;
writer
.write_all(&pos.to_le_bytes())
.context("couldn't write position for record")?;
writer
.write_all(&length.to_le_bytes())
.context("couldn't write fragment length for record")?;
}
Ok(())
}
}
impl<B: ConvertiblePrimitiveInteger> CollatableMappedRecord<B> for AlevinFryReadRecordT<B> {
type CollatableRecordHeader = AlevinFryReadRecordHeader<B>;
#[inline]
fn from_bytes_with_header_retain_ori<T: Read>(
reader: &mut T,
hdr: &mut Self::CollatableRecordHeader,
_ctx: &<Self as MappedRecord>::ParsingContext,
expected_ori: &MappedFragmentOrientation,
) -> Self {
let rec = AlevinFryReadRecordT::<B>::from_bytes_with_header_keep_ori(
reader,
hdr.bc,
hdr.umi,
hdr.naln,
expected_ori.into(),
);
hdr.naln = rec.refs.len() as u32;
rec
}
fn set_collate_key(&mut self, k: B) {
self.bc = k;
}
fn collate_key(&self) -> B {
self.bc
}
fn from_bytes_collatable_header<T: Read>(
reader: &mut T,
context: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader> {
let mut rbuf = [0u8; 4];
reader.read_exact(&mut rbuf).unwrap();
let na = u32::from_le_bytes(rbuf);
let bc = rad_io::read_into::<T, B>(reader, &context.bct);
let umi = rad_io::read_into_u64(reader, &context.umit);
Ok(Self::CollatableRecordHeader { naln: na, bc, umi })
}
fn peek_collatable_header(
buf: &[u8],
ctx: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader> {
let na_size = mem::size_of::<u32>();
let bc_size = ctx.bct.bytes_for_type();
let na = buf.pread::<u32>(0).unwrap();
let bc: B = match ctx.bct {
RadIntId::U8 => NewU8(buf.pread::<u8>(na_size).unwrap()).into(),
RadIntId::U16 => NewU16(buf.pread::<u16>(na_size).unwrap()).into(),
RadIntId::U32 => NewU32(buf.pread::<u32>(na_size).unwrap()).into(),
RadIntId::U64 => NewU64(buf.pread::<u64>(na_size).unwrap()).into(),
RadIntId::U128 => NewU128(buf.pread::<u128>(na_size).unwrap()).into(),
_ => panic!("signed barcode integer encodings are not supported"),
};
let umi = match ctx.umit {
RadIntId::U8 => buf.pread::<u8>(na_size + bc_size).unwrap() as u64,
RadIntId::U16 => buf.pread::<u16>(na_size + bc_size).unwrap() as u64,
RadIntId::U32 => buf.pread::<u32>(na_size + bc_size).unwrap() as u64,
RadIntId::U64 => buf.pread::<u64>(na_size + bc_size).unwrap(),
RadIntId::U128 => panic!("u128 is currently not supported as a umi type"),
_ => panic!("signed umi integer encodings are not supported"),
};
Ok(Self::CollatableRecordHeader { naln: na, bc, umi })
}
}
impl<B: ConvertiblePrimitiveInteger> CollatableMappedRecord<B>
for AlevinFryReadRecordWithPositionT<B>
{
type CollatableRecordHeader = AlevinFryReadRecordHeader<B>;
#[inline]
fn from_bytes_with_header_retain_ori<T: Read>(
reader: &mut T,
hdr: &mut Self::CollatableRecordHeader,
_ctx: &<Self as MappedRecord>::ParsingContext,
expected_ori: &MappedFragmentOrientation,
) -> Self {
let rec = AlevinFryReadRecordWithPositionT::<B>::from_bytes_with_header_keep_ori(
reader,
hdr.bc,
hdr.umi,
hdr.naln,
expected_ori.into(),
);
hdr.naln = rec.refs.len() as u32;
rec
}
fn set_collate_key(&mut self, k: B) {
self.bc = k;
}
fn collate_key(&self) -> B {
self.bc
}
fn from_bytes_collatable_header<T: Read>(
reader: &mut T,
context: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader> {
let mut rbuf = [0u8; 4];
reader.read_exact(&mut rbuf).unwrap();
let na = u32::from_le_bytes(rbuf);
let bc = rad_io::read_into::<T, B>(reader, &context.bct);
let umi = rad_io::read_into_u64(reader, &context.umit);
Ok(Self::CollatableRecordHeader { naln: na, bc, umi })
}
fn peek_collatable_header(
buf: &[u8],
ctx: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader> {
let na_size = mem::size_of::<u32>();
let bc_size = ctx.bct.bytes_for_type();
let na = buf.pread::<u32>(0).unwrap();
let bc: B = match ctx.bct {
RadIntId::U8 => NewU8(buf.pread::<u8>(na_size).unwrap()).into(),
RadIntId::U16 => NewU16(buf.pread::<u16>(na_size).unwrap()).into(),
RadIntId::U32 => NewU32(buf.pread::<u32>(na_size).unwrap()).into(),
RadIntId::U64 => NewU64(buf.pread::<u64>(na_size).unwrap()).into(),
RadIntId::U128 => NewU128(buf.pread::<u128>(na_size).unwrap()).into(),
_ => panic!("signed barcode integer encodings are not supported"),
};
let umi = match ctx.umit {
RadIntId::U8 => buf.pread::<u8>(na_size + bc_size).unwrap() as u64,
RadIntId::U16 => buf.pread::<u16>(na_size + bc_size).unwrap() as u64,
RadIntId::U32 => buf.pread::<u32>(na_size + bc_size).unwrap() as u64,
RadIntId::U64 => buf.pread::<u64>(na_size + bc_size).unwrap(),
RadIntId::U128 => panic!("u128 is currently not supported as a umi type"),
_ => panic!("signed umi integer encodings are not supported"),
};
Ok(Self::CollatableRecordHeader { naln: na, bc, umi })
}
}
impl<B: ConvertiblePrimitiveInteger> MappedRecord for AlevinFryReadRecordT<B> {
type ParsingContext = AlevinFryRecordContext;
type PeekResult = (B, u64);
fn is_empty(&self) -> bool {
self.refs.is_empty()
}
fn num_aln(&self) -> usize {
self.refs.len()
}
fn refs(&self) -> &[u32] {
&self.refs
}
fn has_alignment_on_strand(&self, s: Strand) -> bool {
match s {
Strand::Unknown => !self.refs.is_empty(),
Strand::Forward => self.dirs.iter().any(|&x| x),
Strand::Reverse => self.dirs.iter().any(|&x| !x),
}
}
#[inline]
fn peek_record(buf: &[u8], ctx: &Self::ParsingContext) -> Self::PeekResult {
let na_size = mem::size_of::<u32>();
let bc_size = ctx.bct.bytes_for_type();
let _na = buf.pread::<u32>(0).unwrap();
let bc: B = match ctx.bct {
RadIntId::U8 => NewU8(buf.pread::<u8>(na_size).unwrap()).into(),
RadIntId::U16 => NewU16(buf.pread::<u16>(na_size).unwrap()).into(),
RadIntId::U32 => NewU32(buf.pread::<u32>(na_size).unwrap()).into(),
RadIntId::U64 => NewU64(buf.pread::<u64>(na_size).unwrap()).into(),
RadIntId::U128 => NewU128(buf.pread::<u128>(na_size).unwrap()).into(),
_ => panic!("signed barcode integer encodings are not supported"),
};
let umi = match ctx.umit {
RadIntId::U8 => buf.pread::<u8>(na_size + bc_size).unwrap() as u64,
RadIntId::U16 => buf.pread::<u16>(na_size + bc_size).unwrap() as u64,
RadIntId::U32 => buf.pread::<u32>(na_size + bc_size).unwrap() as u64,
RadIntId::U64 => buf.pread::<u64>(na_size + bc_size).unwrap(),
RadIntId::U128 => panic!("u128 is currently not supported as a umi type"),
_ => panic!("signed umi integer encodings are not supported"),
};
(bc, umi)
}
#[inline]
fn from_bytes_with_context<T: Read>(reader: &mut T, ctx: &Self::ParsingContext) -> Self {
let mut rbuf = [0u8; 255];
let (bc, umi, na) = Self::from_bytes_record_header(reader, &ctx.bct, &ctx.umit);
let mut rec = Self {
bc,
umi,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let dir = (v & utils::MASK_LOWER_31_U32) != 0;
rec.dirs.push(dir);
rec.refs.push(v & utils::MASK_TOP_BIT_U32);
}
rec
}
#[inline]
fn write<W: Write>(&self, writer: &mut W, ctx: &Self::ParsingContext) -> anyhow::Result<()> {
let na: u32 = self.refs.len() as u32;
RadIntId::U32
.write_to(na, writer)
.context("couldn't write number of alignments for record")?;
ctx.bct
.write_to(self.bc, writer)
.context("couldn't write bc field for record")?;
ctx.umit
.write_to(self.umi, writer)
.context("couldn't write umi field for record")?;
let dir_iter = self.dirs.iter();
for (dir, ref_idx) in
itertools::izip!(dir_iter.chain(std::iter::repeat(&false)), &self.refs)
{
let encoded_dir: u32 = if *dir { 1_u32 << 31 } else { 0_u32 };
let encoded_dir_ref: u32 = ref_idx | encoded_dir;
writer
.write_all(&encoded_dir_ref.to_le_bytes())
.context("couldn't write compressed_ori_refid for record")?;
}
Ok(())
}
}
impl<B: ConvertiblePrimitiveInteger> MappedRecord for AlevinFryReadRecordWithPositionT<B> {
type ParsingContext = AlevinFryRecordContext;
type PeekResult = (B, u64);
fn is_empty(&self) -> bool {
self.refs.is_empty()
}
fn num_aln(&self) -> usize {
self.refs.len()
}
fn refs(&self) -> &[u32] {
&self.refs
}
fn has_alignment_on_strand(&self, s: Strand) -> bool {
match s {
Strand::Unknown => !self.refs.is_empty(),
Strand::Forward => self.dirs.iter().any(|&x| x),
Strand::Reverse => self.dirs.iter().any(|&x| !x),
}
}
#[inline]
fn peek_record(buf: &[u8], ctx: &Self::ParsingContext) -> Self::PeekResult {
let na_size = mem::size_of::<u32>();
let bc_size = ctx.bct.bytes_for_type();
let _na = buf.pread::<u32>(0).unwrap();
let bc: B = match ctx.bct {
RadIntId::U8 => NewU8(buf.pread::<u8>(na_size).unwrap()).into(),
RadIntId::U16 => NewU16(buf.pread::<u16>(na_size).unwrap()).into(),
RadIntId::U32 => NewU32(buf.pread::<u32>(na_size).unwrap()).into(),
RadIntId::U64 => NewU64(buf.pread::<u64>(na_size).unwrap()).into(),
RadIntId::U128 => NewU128(buf.pread::<u128>(na_size).unwrap()).into(),
_ => panic!("signed barcode integer encodings are not supported"),
};
let umi = match ctx.umit {
RadIntId::U8 => buf.pread::<u8>(na_size + bc_size).unwrap() as u64,
RadIntId::U16 => buf.pread::<u16>(na_size + bc_size).unwrap() as u64,
RadIntId::U32 => buf.pread::<u32>(na_size + bc_size).unwrap() as u64,
RadIntId::U64 => buf.pread::<u64>(na_size + bc_size).unwrap(),
RadIntId::U128 => panic!("u128 is currently not supported as a umi type"),
_ => panic!("signed umi integer encodings are not supported"),
};
(bc, umi)
}
#[inline]
fn from_bytes_with_context<T: Read>(reader: &mut T, ctx: &Self::ParsingContext) -> Self {
let mut rbuf = [0u8; 255];
let (bc, umi, na) = Self::from_bytes_record_header(reader, &ctx.bct, &ctx.umit);
let mut rec = Self {
bc,
umi,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
pos: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..8]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let dir = (v & utils::MASK_LOWER_31_U32) != 0;
rec.dirs.push(dir);
rec.refs.push(v & utils::MASK_TOP_BIT_U32);
let pos = rbuf.pread::<u32>(std::mem::size_of::<u32>()).unwrap();
rec.pos.push(pos);
}
rec
}
#[inline]
fn write<W: Write>(&self, writer: &mut W, ctx: &Self::ParsingContext) -> anyhow::Result<()> {
let na: u32 = self.refs.len() as u32;
RadIntId::U32
.write_to(na, writer)
.context("couldn't write number of alignments for record")?;
ctx.bct
.write_to(self.bc, writer)
.context("couldn't write bc field for record")?;
ctx.umit
.write_to(self.umi, writer)
.context("couldn't write umi field for record")?;
let dir_iter = self.dirs.iter();
for (dir, ref_idx, pos) in itertools::izip!(
dir_iter.chain(std::iter::repeat(&false)),
&self.refs,
&self.pos
) {
let encoded_dir: u32 = if *dir { 1_u32 << 31 } else { 0_u32 };
let encoded_dir_ref: u32 = ref_idx | encoded_dir;
writer
.write_all(&encoded_dir_ref.to_le_bytes())
.context("couldn't write compressed_ori_refid for record")?;
writer
.write_all(&pos.to_le_bytes())
.context("couldn't write position for record")?;
}
Ok(())
}
}
impl MappedRecord for GenericReadRecord {
type ParsingContext = GenericReadRecordContext;
type PeekResult = Option<u64>;
fn is_empty(&self) -> bool {
self.atags.is_empty()
}
fn num_aln(&self) -> usize {
self.naln as usize
}
fn has_alignment_on_strand(&self, _s: Strand) -> bool {
unimplemented!("no implementation of has_alignment_on_strand for GenericReadRecord")
}
fn refs(&self) -> &[u32] {
unimplemented!("no implementation of refs() for GenericReadRecord yet")
}
#[inline]
fn from_bytes_with_context<T: Read>(reader: &mut T, ctx: &Self::ParsingContext) -> Self {
let mut rbuf = [0u8; 255];
reader.read_exact(&mut rbuf[0..4]).unwrap();
let na = rbuf.pread::<u32>(0).unwrap();
let rtags: Vec<TagValue> = ctx
.read_tags
.iter_desc()
.map(|td| td.value_from_bytes(reader))
.collect();
let naln_tags = &ctx.aln_tags.iter_desc().len();
let mut atags = Vec::<TagValue>::new();
for _ in 0..(na as usize) {
let aln_tags: Vec<TagValue> = ctx
.aln_tags
.iter_desc()
.map(|td| td.value_from_bytes(reader))
.collect();
atags.extend(aln_tags);
}
Self {
naln: na,
naln_tags: *naln_tags as u32,
rtags,
atags,
}
}
#[inline]
fn peek_record(_buf: &[u8], _ctx: &Self::ParsingContext) -> Self::PeekResult {
unimplemented!(
"Currently there is no implementation for peek_record for GenericRecord. This should not be needed"
);
}
#[inline]
fn write<W: Write>(&self, _writer: &mut W, _ctx: &Self::ParsingContext) -> anyhow::Result<()> {
unimplemented!("Currently there is no implementation for write for the GenericReadRecord");
}
}
impl From<NewI8> for u64 {
fn from(_x: NewI8) -> Self {
unimplemented!()
}
}
impl From<NewI16> for u64 {
fn from(_x: NewI16) -> Self {
unimplemented!()
}
}
impl From<NewI32> for u64 {
fn from(_x: NewI32) -> Self {
unimplemented!()
}
}
impl From<NewI64> for u64 {
fn from(_x: NewI64) -> Self {
unimplemented!()
}
}
impl From<NewI128> for u64 {
fn from(_x: NewI128) -> Self {
unimplemented!()
}
}
impl From<TryWrapper<NewI8>> for u64 {
fn from(_x: TryWrapper<NewI8>) -> Self {
unimplemented!()
}
}
impl From<TryWrapper<NewI16>> for u64 {
fn from(_x: TryWrapper<NewI16>) -> Self {
unimplemented!()
}
}
impl From<TryWrapper<NewI32>> for u64 {
fn from(_x: TryWrapper<NewI32>) -> Self {
unimplemented!()
}
}
impl From<TryWrapper<NewI64>> for u64 {
fn from(_x: TryWrapper<NewI64>) -> Self {
unimplemented!()
}
}
impl From<TryWrapper<NewI128>> for u64 {
fn from(_x: TryWrapper<NewI128>) -> Self {
unimplemented!()
}
}
pub trait ConvertiblePrimitiveInteger:
PrimitiveInteger
+ std::convert::From<NewU8>
+ std::convert::From<NewU16>
+ std::convert::From<NewU32>
+ std::convert::From<NewU64>
+ std::convert::From<NewU128>
+ std::convert::TryFrom<TryWrapper<NewU8>>
+ std::convert::TryFrom<TryWrapper<NewU16>>
+ std::convert::TryFrom<TryWrapper<NewU32>>
+ std::convert::TryFrom<TryWrapper<NewU64>>
+ std::convert::TryFrom<TryWrapper<NewU128>>
+ std::convert::From<NewI8>
+ std::convert::From<NewI16>
+ std::convert::From<NewI32>
+ std::convert::From<NewI64>
+ std::convert::From<NewI128>
+ std::convert::TryFrom<TryWrapper<NewI8>>
+ std::convert::TryFrom<TryWrapper<NewI16>>
+ std::convert::TryFrom<TryWrapper<NewI32>>
+ std::convert::TryFrom<TryWrapper<NewI64>>
+ std::convert::TryFrom<TryWrapper<NewI128>>
+ std::convert::TryFrom<TryWrapper<NewI128>>
{
}
impl<
T: PrimitiveInteger
+ std::convert::From<NewU8>
+ std::convert::From<NewU16>
+ std::convert::From<NewU32>
+ std::convert::From<NewU64>
+ std::convert::From<NewU128>
+ std::convert::TryFrom<TryWrapper<NewU8>>
+ std::convert::TryFrom<TryWrapper<NewU16>>
+ std::convert::TryFrom<TryWrapper<NewU32>>
+ std::convert::TryFrom<TryWrapper<NewU64>>
+ std::convert::TryFrom<TryWrapper<NewU128>>
+ std::convert::From<NewI8>
+ std::convert::From<NewI16>
+ std::convert::From<NewI32>
+ std::convert::From<NewI64>
+ std::convert::From<NewI128>
+ std::convert::TryFrom<TryWrapper<NewI8>>
+ std::convert::TryFrom<TryWrapper<NewI16>>
+ std::convert::TryFrom<TryWrapper<NewI32>>
+ std::convert::TryFrom<TryWrapper<NewI64>>
+ std::convert::TryFrom<TryWrapper<NewI128>>
+ std::convert::TryFrom<TryWrapper<NewI128>>,
> ConvertiblePrimitiveInteger for T
{
}
impl<B: ConvertiblePrimitiveInteger> AlevinFryReadRecordT<B> {
pub fn from_bytes<T: Read>(reader: &mut T, bct: &RadIntId, umit: &RadIntId) -> Self {
let mut rbuf = [0u8; 255];
let (bc, umi, na) = Self::from_bytes_record_header(reader, bct, umit);
let mut rec = Self {
bc,
umi,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let dir = (v & utils::MASK_LOWER_31_U32) != 0;
rec.dirs.push(dir);
rec.refs.push(v & utils::MASK_TOP_BIT_U32);
}
rec
}
#[inline]
pub fn from_bytes_record_header<T: Read>(
reader: &mut T,
bct: &RadIntId,
umit: &RadIntId,
) -> (B, u64, u32) {
let mut rbuf = [0u8; 4];
reader.read_exact(&mut rbuf).unwrap();
let na = u32::from_le_bytes(rbuf);
let bc = rad_io::read_into::<T, B>(reader, bct);
let umi = rad_io::read_into_u64(reader, umit);
(bc, umi, na)
}
#[inline]
pub fn from_bytes_with_header_keep_ori<T: Read>(
reader: &mut T,
bc: B,
umi: u64,
na: u32,
expected_ori: &Strand,
) -> Self {
let mut rbuf = [0u8; 255];
let mut rec = Self {
bc,
umi,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let strand = if (v & utils::MASK_LOWER_31_U32) > 0 {
Strand::Forward
} else {
Strand::Reverse
};
if expected_ori.same(&strand) || expected_ori.is_unknown() {
let dir = (v & utils::MASK_LOWER_31_U32) != 0;
rec.dirs.push(dir);
rec.refs.push(v & utils::MASK_TOP_BIT_U32);
}
}
let indices = argsort(&rec.refs);
reorder_in_place(&mut rec.refs, &indices);
reorder_in_place(&mut rec.dirs, &indices);
rec
}
#[inline]
pub fn from_bytes_keep_ori<T: Read>(
reader: &mut T,
bct: &RadIntId,
umit: &RadIntId,
expected_ori: &Strand,
) -> Self {
let (bc, umi, na) = Self::from_bytes_record_header(reader, bct, umit);
Self::from_bytes_with_header_keep_ori(reader, bc, umi, na, expected_ori)
}
}
impl<B: ConvertiblePrimitiveInteger> AlevinFryReadRecordWithPositionT<B> {
pub fn from_bytes<T: Read>(reader: &mut T, bct: &RadIntId, umit: &RadIntId) -> Self {
let mut rbuf = [0u8; 255];
let (bc, umi, na) = Self::from_bytes_record_header(reader, bct, umit);
let mut rec = Self {
bc,
umi,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
pos: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..8]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let dir = (v & utils::MASK_LOWER_31_U32) != 0;
rec.dirs.push(dir);
rec.refs.push(v & utils::MASK_TOP_BIT_U32);
let pos = rbuf.pread::<u32>(std::mem::size_of::<u32>()).unwrap();
rec.pos.push(pos);
}
rec
}
#[inline]
pub fn from_bytes_record_header<T: Read>(
reader: &mut T,
bct: &RadIntId,
umit: &RadIntId,
) -> (B, u64, u32) {
let mut rbuf = [0u8; 4];
reader.read_exact(&mut rbuf).unwrap();
let na = u32::from_le_bytes(rbuf);
let bc = rad_io::read_into::<T, B>(reader, bct);
let umi = rad_io::read_into_u64(reader, umit);
(bc, umi, na)
}
#[inline]
pub fn from_bytes_with_header_keep_ori<T: Read>(
reader: &mut T,
bc: B,
umi: u64,
na: u32,
expected_ori: &Strand,
) -> Self {
let mut rbuf = [0u8; 255];
let mut rec = Self {
bc,
umi,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
pos: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..8]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let strand = if (v & utils::MASK_LOWER_31_U32) > 0 {
Strand::Forward
} else {
Strand::Reverse
};
if expected_ori.same(&strand) || expected_ori.is_unknown() {
let pos = rbuf.pread::<u32>(std::mem::size_of::<u32>()).unwrap();
let dir = (v & utils::MASK_LOWER_31_U32) != 0;
rec.dirs.push(dir);
rec.refs.push(v & utils::MASK_TOP_BIT_U32);
rec.pos.push(pos);
}
}
let indices = argsort(&rec.refs);
reorder_in_place(&mut rec.refs, &indices);
reorder_in_place(&mut rec.dirs, &indices);
reorder_in_place(&mut rec.pos, &indices);
rec
}
#[inline]
pub fn from_bytes_keep_ori<T: Read>(
reader: &mut T,
bct: &RadIntId,
umit: &RadIntId,
expected_ori: &Strand,
) -> Self {
let (bc, umi, na) = Self::from_bytes_record_header(reader, bct, umit);
Self::from_bytes_with_header_keep_ori(reader, bc, umi, na, expected_ori)
}
}
#[derive(Debug, Clone)]
pub struct AtacSeqRecordContext {
pub bct: RadIntId,
}
impl RecordContext for AtacSeqRecordContext {
fn get_context_from_tag_section(
_ft: &TagSection,
rt: &TagSection,
_at: &TagSection,
) -> anyhow::Result<Self> {
let bct = rt
.get_tag_type("barcode")
.or_else(|| rt.get_tag_type("b"))
.ok_or_else(|| {
anyhow::anyhow!(
"atac-reader record context requires a 'barcode' (or 'b') read-level tag"
)
})?;
if let RadType::Int(x) = bct {
Ok(Self { bct: x })
} else {
bail!("atac-reader record context requires that barcode tags are of type RadType::Int");
}
}
}
impl AtacSeqRecordContext {
pub fn from_bct(bct: RadIntId) -> Self {
Self { bct }
}
}
impl CollatableMappedRecord<u64> for AtacSeqReadRecord {
type CollatableRecordHeader = AtacSeqReadRecordHeader;
fn from_bytes_collatable_header<T: Read>(
reader: &mut T,
context: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader> {
let mut rbuf = [0u8; 4];
reader.read_exact(&mut rbuf).unwrap();
let na = u32::from_le_bytes(rbuf);
let bc = rad_io::read_into_u64(reader, &context.bct);
Ok(Self::CollatableRecordHeader { naln: na, bc })
}
fn peek_collatable_header(
buf: &[u8],
ctx: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader> {
let na_size = mem::size_of::<u32>();
let na = buf.pread::<u32>(0).unwrap();
let bc: u64 = match ctx.bct {
RadIntId::U8 => NewU8(buf.pread::<u8>(na_size).unwrap()).into(),
RadIntId::U16 => NewU16(buf.pread::<u16>(na_size).unwrap()).into(),
RadIntId::U32 => NewU32(buf.pread::<u32>(na_size).unwrap()).into(),
RadIntId::U64 => NewU64(buf.pread::<u64>(na_size).unwrap()).into(),
RadIntId::U128 => NewU128(buf.pread::<u128>(na_size).unwrap()).into(),
_ => panic!("signed barcode integer encodings are not supported"),
};
Ok(Self::CollatableRecordHeader { naln: na, bc })
}
fn set_collate_key(&mut self, k: u64) {
self.bc = k;
}
fn collate_key(&self) -> u64 {
self.bc
}
#[inline]
fn from_bytes_with_header_retain_ori<T: Read>(
reader: &mut T,
hdr: &mut Self::CollatableRecordHeader,
_ctx: &<Self as MappedRecord>::ParsingContext,
_expected_ori: &MappedFragmentOrientation,
) -> Self {
let mut rbuf = [0u8; 255];
let na = hdr.naln;
let bc = hdr.bc;
let mut rec = Self {
bc,
refs: Vec::with_capacity(na as usize),
map_type: Vec::with_capacity(na as usize),
start_pos: Vec::with_capacity(na as usize),
frag_lengths: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let ref_id = rbuf.pread::<u32>(0).unwrap();
rec.refs.push(ref_id);
reader.read_exact(&mut rbuf[0..1]).unwrap();
let map_type = rbuf.pread::<u8>(0).unwrap();
rec.map_type.push(map_type);
reader.read_exact(&mut rbuf[0..4]).unwrap();
let start_pos = rbuf.pread::<u32>(0).unwrap();
rec.start_pos.push(start_pos);
reader.read_exact(&mut rbuf[0..2]).unwrap();
let frag_length = rbuf.pread::<u16>(0).unwrap();
rec.frag_lengths.push(frag_length);
}
hdr.naln = rec.refs.len() as u32;
rec
}
}
impl MappedRecord for AtacSeqReadRecord {
type ParsingContext = AtacSeqRecordContext;
type PeekResult = u64;
fn is_empty(&self) -> bool {
self.refs.is_empty()
}
fn num_aln(&self) -> usize {
self.refs.len()
}
fn has_alignment_on_strand(&self, _s: Strand) -> bool {
!self.refs.is_empty()
}
fn refs(&self) -> &[u32] {
&self.refs
}
#[inline]
fn peek_record(buf: &[u8], ctx: &Self::ParsingContext) -> Self::PeekResult {
let na_size = mem::size_of::<u32>();
let _na = buf.pread::<u32>(0).unwrap();
match ctx.bct {
RadIntId::U8 => buf.pread::<u8>(na_size).unwrap() as u64,
RadIntId::U16 => buf.pread::<u16>(na_size).unwrap() as u64,
RadIntId::U32 => buf.pread::<u32>(na_size).unwrap() as u64,
RadIntId::U64 => buf.pread::<u64>(na_size).unwrap(),
RadIntId::U128 => panic!("u128 is currently not supported as a barcode type"),
_ => panic!("signed integer types are not supported as a barcode type"),
}
}
#[inline]
fn from_bytes_with_context<T: Read>(reader: &mut T, ctx: &Self::ParsingContext) -> Self {
let mut rbuf = [0u8; 255];
let (bc, na) = Self::from_bytes_record_header(reader, &ctx.bct);
let mut rec = Self {
bc,
refs: Vec::with_capacity(na as usize),
map_type: Vec::with_capacity(na as usize),
start_pos: Vec::with_capacity(na as usize),
frag_lengths: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let ref_id = rbuf.pread::<u32>(0).unwrap();
rec.refs.push(ref_id);
reader.read_exact(&mut rbuf[0..1]).unwrap();
let map_type = rbuf.pread::<u8>(0).unwrap();
rec.map_type.push(map_type);
reader.read_exact(&mut rbuf[0..4]).unwrap();
let start_pos = rbuf.pread::<u32>(0).unwrap();
rec.start_pos.push(start_pos);
reader.read_exact(&mut rbuf[0..2]).unwrap();
let frag_length = rbuf.pread::<u16>(0).unwrap();
rec.frag_lengths.push(frag_length);
}
rec
}
#[inline]
fn write<W: Write>(&self, writer: &mut W, ctx: &Self::ParsingContext) -> anyhow::Result<()> {
let na: u32 = self.refs.len() as u32;
RadIntId::U32
.write_to(na, writer)
.context("couldn't write number of alignments for AtacSeq record")?;
ctx.bct
.write_to(self.bc, writer)
.context("couldn't write bc field for AtacSeq record")?;
for i in 0..(na as usize) {
writer
.write_all(&self.refs[i].to_le_bytes())
.context("couldn't write ref for AtacSeq alignment")?;
writer
.write_all(&self.map_type[i].to_le_bytes())
.context("couldn't write map_type for AtacSeq alignment")?;
writer
.write_all(&self.start_pos[i].to_le_bytes())
.context("couldn't write start_pos for AtacSeq alignment")?;
writer
.write_all(&self.frag_lengths[i].to_le_bytes())
.context("couldn't write frag_length for AtacSeq alignment")?;
}
Ok(())
}
}
impl AtacSeqReadRecord {
pub fn from_bytes<T: Read>(reader: &mut T, bct: &RadIntId) -> Self {
let mut rbuf = [0u8; 255];
let (bc, na) = Self::from_bytes_record_header(reader, bct);
let mut rec = Self {
bc,
refs: Vec::with_capacity(na as usize),
map_type: Vec::with_capacity(na as usize),
start_pos: Vec::with_capacity(na as usize),
frag_lengths: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let ref_id = rbuf.pread::<u32>(0).unwrap();
rec.refs.push(ref_id);
reader.read_exact(&mut rbuf[0..1]).unwrap();
let map_type = rbuf.pread::<u8>(0).unwrap();
rec.map_type.push(map_type);
reader.read_exact(&mut rbuf[0..4]).unwrap();
let start_pos = rbuf.pread::<u32>(0).unwrap();
rec.start_pos.push(start_pos);
reader.read_exact(&mut rbuf[0..2]).unwrap();
let frag_length = rbuf.pread::<u16>(0).unwrap();
rec.frag_lengths.push(frag_length);
}
rec
}
#[inline]
pub fn from_bytes_record_header<T: Read>(reader: &mut T, bct: &RadIntId) -> (u64, u32) {
let mut rbuf = [0u8; 4];
reader.read_exact(&mut rbuf).unwrap();
let na = u32::from_le_bytes(rbuf); let bc = rad_io::read_into_u64(reader, bct);
(bc, na)
}
pub fn from_bytes_with_header<T: Read>(reader: &mut T, bc: u64, na: u32) -> Self {
let mut rbuf = [0u8; 255];
let mut rec = Self {
bc,
refs: Vec::with_capacity(na as usize),
map_type: Vec::with_capacity(na as usize),
start_pos: Vec::with_capacity(na as usize),
frag_lengths: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let ref_id = rbuf.pread::<u32>(0).unwrap();
rec.refs.push(ref_id);
reader.read_exact(&mut rbuf[0..1]).unwrap();
let map_type = rbuf.pread::<u8>(0).unwrap();
rec.map_type.push(map_type);
reader.read_exact(&mut rbuf[0..4]).unwrap();
let start_pos = rbuf.pread::<u32>(0).unwrap();
rec.start_pos.push(start_pos);
reader.read_exact(&mut rbuf[0..2]).unwrap();
let frag_length = rbuf.pread::<u16>(0).unwrap();
rec.frag_lengths.push(frag_length);
}
let indices = argsort(&rec.refs);
reorder_in_place(&mut rec.refs, &indices);
reorder_in_place(&mut rec.map_type, &indices);
reorder_in_place(&mut rec.start_pos, &indices);
reorder_in_place(&mut rec.frag_lengths, &indices);
rec
}
}
#[derive(Debug, Clone)]
pub struct ScLongReadRecordContext {
pub bct: RadIntId,
pub umit: RadIntId,
}
impl RecordContext for ScLongReadRecordContext {
fn get_context_from_tag_section(
_ft: &TagSection,
rt: &TagSection,
_at: &TagSection,
) -> anyhow::Result<Self> {
let bct = rt
.get_tag_type("b")
.expect("scLongRead record requires a 'b' barcode tag");
let umit = rt
.get_tag_type("u")
.expect("scLongRead record requires a 'u' umi tag");
match (bct, umit) {
(RadType::Int(bct), RadType::Int(umit)) => Ok(Self { bct, umit }),
_ => bail!("barcode/umi must be RadType::Int"),
}
}
}
impl ScLongReadRecordContext {
pub fn from_bct_umit(bct: RadIntId, umit: RadIntId) -> Self {
Self { bct, umit }
}
}
impl<B: ConvertiblePrimitiveInteger> CollatableMappedRecord<B> for ScLongReadRecordT<B> {
type CollatableRecordHeader = ScLongReadRecordHeader<B>;
fn from_bytes_collatable_header<T: Read>(
reader: &mut T,
context: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader> {
let mut rbuf = [0u8; 4];
reader.read_exact(&mut rbuf).unwrap();
let na = u32::from_le_bytes(rbuf);
let bc = rad_io::read_into::<T, B>(reader, &context.bct);
let umi = rad_io::read_into_u64(reader, &context.umit);
Ok(Self::CollatableRecordHeader { naln: na, bc, umi })
}
fn peek_collatable_header(
buf: &[u8],
ctx: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader> {
let na_size = mem::size_of::<u32>();
let bc_size = ctx.bct.bytes_for_type();
let na = buf.pread::<u32>(0).unwrap();
let bc: B = match ctx.bct {
RadIntId::U8 => NewU8(buf.pread::<u8>(na_size).unwrap()).into(),
RadIntId::U16 => NewU16(buf.pread::<u16>(na_size).unwrap()).into(),
RadIntId::U32 => NewU32(buf.pread::<u32>(na_size).unwrap()).into(),
RadIntId::U64 => NewU64(buf.pread::<u64>(na_size).unwrap()).into(),
RadIntId::U128 => NewU128(buf.pread::<u128>(na_size).unwrap()).into(),
_ => panic!("signed barcode integer encodings are not supported"),
};
let umi = match ctx.umit {
RadIntId::U8 => buf.pread::<u8>(na_size + bc_size).unwrap() as u64,
RadIntId::U16 => buf.pread::<u16>(na_size + bc_size).unwrap() as u64,
RadIntId::U32 => buf.pread::<u32>(na_size + bc_size).unwrap() as u64,
RadIntId::U64 => buf.pread::<u64>(na_size + bc_size).unwrap(),
RadIntId::U128 => panic!("u128 is currently not supported as a umi type"),
_ => panic!("signed umi integer encodings are not supported"),
};
Ok(Self::CollatableRecordHeader { naln: na, bc, umi })
}
fn set_collate_key(&mut self, k: B) {
self.bc = k;
}
fn collate_key(&self) -> B {
self.bc
}
#[inline]
fn from_bytes_with_header_retain_ori<T: Read>(
reader: &mut T,
hdr: &mut Self::CollatableRecordHeader,
_ctx: &<Self as MappedRecord>::ParsingContext,
expected_ori: &MappedFragmentOrientation,
) -> Self {
let na = hdr.naln;
let bc = hdr.bc;
let umi = hdr.umi;
let mut rbuf = [0u8; 255];
let mut rec = Self {
bc,
umi,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
as_scores: Vec::with_capacity(na as usize),
starts: Vec::with_capacity(na as usize),
ends: Vec::with_capacity(na as usize),
tlens: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let dir = (v & utils::MASK_LOWER_31_U32) != 0;
let ref_id = v & utils::MASK_TOP_BIT_U32;
reader.read_exact(&mut rbuf[0..4]).unwrap();
let as_score = rbuf.pread::<i32>(0).unwrap();
reader.read_exact(&mut rbuf[0..4]).unwrap();
let start = rbuf.pread::<u32>(0).unwrap();
reader.read_exact(&mut rbuf[0..4]).unwrap();
let end = rbuf.pread::<u32>(0).unwrap();
reader.read_exact(&mut rbuf[0..4]).unwrap();
let tlen = rbuf.pread::<u32>(0).unwrap();
let strand = if (v & utils::MASK_LOWER_31_U32) > 0 {
Strand::Forward
} else {
Strand::Reverse
}
.into();
if expected_ori.same(&strand) || expected_ori.is_unknown() {
rec.dirs.push(dir);
rec.refs.push(ref_id);
rec.as_scores.push(as_score);
rec.starts.push(start);
rec.ends.push(end);
rec.tlens.push(tlen);
}
}
hdr.naln = rec.refs.len() as u32;
let indices = argsort(&rec.refs);
reorder_in_place(&mut rec.dirs, &indices);
reorder_in_place(&mut rec.refs, &indices);
reorder_in_place(&mut rec.as_scores, &indices);
reorder_in_place(&mut rec.starts, &indices);
reorder_in_place(&mut rec.ends, &indices);
reorder_in_place(&mut rec.tlens, &indices);
rec
}
}
impl<B: ConvertiblePrimitiveInteger> MappedRecord for ScLongReadRecordT<B> {
type ParsingContext = ScLongReadRecordContext;
type PeekResult = (B, u64);
fn is_empty(&self) -> bool {
self.refs.is_empty()
}
fn num_aln(&self) -> usize {
self.refs.len()
}
fn refs(&self) -> &[u32] {
&self.refs
}
fn has_alignment_on_strand(&self, s: Strand) -> bool {
match s {
Strand::Unknown => !self.refs.is_empty(),
Strand::Forward => self.dirs.iter().any(|&x| x),
Strand::Reverse => self.dirs.iter().any(|&x| !x),
}
}
#[inline]
fn peek_record(buf: &[u8], ctx: &Self::ParsingContext) -> Self::PeekResult {
let na_size = mem::size_of::<u32>();
let bc_size = ctx.bct.bytes_for_type();
let _na = buf.pread::<u32>(0).unwrap();
let bc: B = match ctx.bct {
RadIntId::U8 => NewU8(buf.pread::<u8>(na_size).unwrap()).into(),
RadIntId::U16 => NewU16(buf.pread::<u16>(na_size).unwrap()).into(),
RadIntId::U32 => NewU32(buf.pread::<u32>(na_size).unwrap()).into(),
RadIntId::U64 => NewU64(buf.pread::<u64>(na_size).unwrap()).into(),
RadIntId::U128 => NewU128(buf.pread::<u128>(na_size).unwrap()).into(),
_ => panic!("signed barcode integer encodings are not supported"),
};
let umi = match ctx.umit {
RadIntId::U8 => buf.pread::<u8>(na_size + bc_size).unwrap() as u64,
RadIntId::U16 => buf.pread::<u16>(na_size + bc_size).unwrap() as u64,
RadIntId::U32 => buf.pread::<u32>(na_size + bc_size).unwrap() as u64,
RadIntId::U64 => buf.pread::<u64>(na_size + bc_size).unwrap(),
RadIntId::U128 => panic!("u128 is currently not supported as a umi type"),
_ => panic!("signed umi integer encodings are not supported"),
};
(bc, umi)
}
#[inline]
fn from_bytes_with_context<T: Read>(reader: &mut T, ctx: &Self::ParsingContext) -> Self {
let mut rbuf = [0u8; 255];
let (bc, umi, na) = Self::from_bytes_record_header(reader, &ctx.bct, &ctx.umit);
let mut rec = Self {
bc,
umi,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
as_scores: Vec::with_capacity(na as usize),
starts: Vec::with_capacity(na as usize),
ends: Vec::with_capacity(na as usize),
tlens: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let dir = (v & utils::MASK_LOWER_31_U32) != 0;
let ref_id = v & utils::MASK_TOP_BIT_U32;
rec.dirs.push(dir);
rec.refs.push(ref_id);
reader.read_exact(&mut rbuf[0..4]).unwrap();
let as_score = rbuf.pread::<i32>(0).unwrap();
rec.as_scores.push(as_score);
reader.read_exact(&mut rbuf[0..4]).unwrap();
let start = rbuf.pread::<u32>(0).unwrap();
rec.starts.push(start);
reader.read_exact(&mut rbuf[0..4]).unwrap();
let end = rbuf.pread::<u32>(0).unwrap();
rec.ends.push(end);
reader.read_exact(&mut rbuf[0..4]).unwrap();
let tlen = rbuf.pread::<u32>(0).unwrap();
rec.tlens.push(tlen);
}
rec
}
#[inline]
fn write<W: Write>(&self, writer: &mut W, ctx: &Self::ParsingContext) -> anyhow::Result<()> {
let na: u32 = self.refs.len() as u32;
RadIntId::U32
.write_to(na, writer)
.context("couldn't write number of alignments for record")?;
ctx.bct
.write_to(self.bc, writer)
.context("couldn't write bc field for record")?;
ctx.umit
.write_to(self.umi, writer)
.context("couldn't write umi field for record")?;
for i in 0..(na as usize) {
let ref_idx = self.refs[i];
let dir = self.dirs[i];
let as_i32 = self.as_scores[i];
let start = self.starts[i];
let end = self.ends[i];
let tlen = self.tlens[i];
let encoded_dir: u32 = if dir { 1_u32 << 31 } else { 0_u32 };
let encoded_dir_ref: u32 = ref_idx | encoded_dir;
writer
.write_all(&encoded_dir_ref.to_le_bytes())
.context("couldn't write compressed_ori_refid for record")?;
writer
.write_all(&as_i32.to_le_bytes())
.context("couldn't write AS for record")?;
writer
.write_all(&start.to_le_bytes())
.context("couldn't write start for record")?;
writer
.write_all(&end.to_le_bytes())
.context("couldn't write end for record")?;
writer
.write_all(&tlen.to_le_bytes())
.context("couldn't write tlen for record")?;
}
Ok(())
}
}
impl<B: ConvertiblePrimitiveInteger> ScLongReadRecordT<B> {
pub fn from_bytes<T: Read>(reader: &mut T, bct: &RadIntId, umit: &RadIntId) -> Self {
let ctx = ScLongReadRecordContext::from_bct_umit(*bct, *umit);
Self::from_bytes_with_context(reader, &ctx)
}
#[inline]
pub fn from_bytes_record_header<T: Read>(
reader: &mut T,
bct: &RadIntId,
umit: &RadIntId,
) -> (B, u64, u32) {
let mut rbuf = [0u8; 4];
reader.read_exact(&mut rbuf).unwrap();
let na = u32::from_le_bytes(rbuf); let bc: B = rad_io::read_into(reader, bct);
let umi = rad_io::read_into_u64(reader, umit);
(bc, umi, na)
}
pub fn from_bytes_with_header<T: Read>(_reader: &mut T, _bc: u64, _umi: u64, _na: u32) -> Self {
unimplemented!("from_bytes_with_header is not implemented for ScLongReadRecordT");
}
}
pub type MultiBarcodeReadRecord = MultiBarcodeReadRecordT<u64>;
pub type MultiBarcodeReadRecordU64 = MultiBarcodeReadRecordT<u64>;
pub type MultiBarcodeReadRecordU128 = MultiBarcodeReadRecordT<u128>;
pub const MAX_INLINE_BARCODES: usize = 4;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MultiBarcodeReadRecordT<B: ConvertiblePrimitiveInteger> {
pub barcodes: SmallVec<[B; MAX_INLINE_BARCODES]>,
pub umi: u64,
pub dirs: Vec<bool>,
pub refs: Vec<u32>,
}
impl<B: ConvertiblePrimitiveInteger> UmiTaggedRecord for MultiBarcodeReadRecordT<B> {
fn umi(&self) -> u64 {
self.umi
}
}
#[derive(Debug, Clone)]
pub struct MultiBarcodeRecordContext {
pub bc_types: SmallVec<[RadIntId; MAX_INLINE_BARCODES]>,
pub umit: RadIntId,
pub roles: SmallVec<[BarcodeRole; MAX_INLINE_BARCODES]>,
}
impl RecordContext for MultiBarcodeRecordContext {
fn get_context_from_tag_section(
ft: &TagSection,
rt: &TagSection,
_at: &TagSection,
) -> anyhow::Result<Self> {
let num_barcodes_tag = ft.get_tag_type("num_barcodes");
let num_barcodes = if let Some(RadType::Int(_)) = num_barcodes_tag {
let mut count = 0usize;
while rt.get_tag_type(&format!("b{}", count)).is_some() {
count += 1;
}
if count == 0 {
bail!(
"multi-barcode record context: num_barcodes file tag present but no bN read-level tags found"
);
}
count
} else {
bail!("multi-barcode record context requires a 'num_barcodes' file-level tag");
};
let mut bc_types = SmallVec::new();
for i in 0..num_barcodes {
let tag_name = format!("b{}", i);
let bct = rt.get_tag_type(&tag_name).ok_or_else(|| {
anyhow::anyhow!(
"multi-barcode record context requires a '{}' read-level tag",
tag_name
)
})?;
if let RadType::Int(x) = bct {
bc_types.push(x);
} else {
bail!(
"multi-barcode record context requires that '{}' tag is of type RadType::Int",
tag_name
);
}
}
let umit = rt
.get_tag_type("u")
.expect("multi-barcode record context requires a 'u' read-level tag");
let umit = if let RadType::Int(x) = umit {
x
} else {
bail!("multi-barcode record context requires that 'u' tag is of type RadType::Int");
};
let roles = Self::parse_roles_or_default(ft, num_barcodes)?;
Ok(Self {
bc_types,
umit,
roles,
})
}
}
impl MultiBarcodeRecordContext {
pub fn new(
bc_types: SmallVec<[RadIntId; MAX_INLINE_BARCODES]>,
umit: RadIntId,
roles: SmallVec<[BarcodeRole; MAX_INLINE_BARCODES]>,
) -> Self {
Self {
bc_types,
umit,
roles,
}
}
pub fn num_barcodes(&self) -> usize {
self.bc_types.len()
}
pub fn total_bc_bytes(&self) -> usize {
self.bc_types.iter().map(|t| t.bytes_for_type()).sum()
}
fn parse_roles_or_default(
ft: &TagSection,
num_barcodes: usize,
) -> anyhow::Result<SmallVec<[BarcodeRole; MAX_INLINE_BARCODES]>> {
let _ = ft;
let mut roles = SmallVec::new();
for i in 0..num_barcodes {
if i == 0 && num_barcodes > 1 {
roles.push(BarcodeRole::Sample);
} else {
roles.push(BarcodeRole::Cell);
}
}
Ok(roles)
}
}
pub struct MultiBarcodeReadRecordHeader<B: ConvertiblePrimitiveInteger> {
pub naln: u32,
pub barcodes: SmallVec<[B; MAX_INLINE_BARCODES]>,
pub umi: u64,
}
impl<B: ConvertiblePrimitiveInteger> RecordHeader for MultiBarcodeReadRecordHeader<B> {
type RecordType = MultiBarcodeReadRecordT<B>;
fn naln(&self) -> u32 {
self.naln
}
}
impl<B: ConvertiblePrimitiveInteger> CollatableRecordHeader<B> for MultiBarcodeReadRecordHeader<B> {
fn collate_key(&self) -> B {
*self
.barcodes
.last()
.expect("multi-barcode header must have at least one barcode")
}
fn collation_group_key(
&self,
ctx: &<<Self as RecordHeader>::RecordType as MappedRecord>::ParsingContext,
) -> u64
where
u64: From<B>,
{
if self.barcodes.len() < 2 {
return self.collate_key().into();
}
let cell_bits = (ctx
.bc_types
.last()
.expect("multi-barcode context must have at least one barcode type")
.bytes_for_type()
* 8) as u32;
let sample = u64::from(self.barcodes[0]);
let cell = u64::from(
*self
.barcodes
.last()
.expect("multi-barcode header must have at least one barcode"),
);
if cell_bits >= 64 {
cell
} else {
(sample << cell_bits) | (cell & ((1_u64 << cell_bits) - 1))
}
}
fn write_fields<W: Write>(
&self,
writer: &mut W,
ctx: &<<Self as RecordHeader>::RecordType as MappedRecord>::ParsingContext,
) -> anyhow::Result<()> {
let na: u32 = self.naln();
RadIntId::U32
.write_to(na, writer)
.context("couldn't write number of alignments for multi-barcode record")?;
for (bc, bct) in self.barcodes.iter().zip(ctx.bc_types.iter()) {
bct.write_to(*bc, writer)
.context("couldn't write barcode field for multi-barcode record")?;
}
ctx.umit
.write_to(self.umi, writer)
.context("couldn't write umi field for multi-barcode record")?;
Ok(())
}
}
impl<B: ConvertiblePrimitiveInteger> KnownSize for MultiBarcodeReadRecordT<B> {
fn nbytes(na: u32, ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>()
+ ctx.total_bc_bytes()
+ ctx.umit.bytes_for_type()
+ (na as usize * Self::nbytes_aln(ctx))
}
fn nbytes_aln(_ctx: &<Self as MappedRecord>::ParsingContext) -> usize {
std::mem::size_of::<u32>()
}
}
impl<B: ConvertiblePrimitiveInteger> MappedRecord for MultiBarcodeReadRecordT<B> {
type ParsingContext = MultiBarcodeRecordContext;
type PeekResult = (SmallVec<[B; MAX_INLINE_BARCODES]>, u64);
fn is_empty(&self) -> bool {
self.refs.is_empty()
}
fn num_aln(&self) -> usize {
self.refs.len()
}
fn refs(&self) -> &[u32] {
&self.refs
}
fn has_alignment_on_strand(&self, s: Strand) -> bool {
match s {
Strand::Unknown => !self.refs.is_empty(),
Strand::Forward => self.dirs.iter().any(|&x| x),
Strand::Reverse => self.dirs.iter().any(|&x| !x),
}
}
#[inline]
fn peek_record(buf: &[u8], ctx: &Self::ParsingContext) -> Self::PeekResult {
let na_size = mem::size_of::<u32>();
let _na = buf.pread::<u32>(0).unwrap();
let mut offset = na_size;
let mut barcodes = SmallVec::new();
for bct in &ctx.bc_types {
let bc: B = match bct {
RadIntId::U8 => NewU8(buf.pread::<u8>(offset).unwrap()).into(),
RadIntId::U16 => NewU16(buf.pread::<u16>(offset).unwrap()).into(),
RadIntId::U32 => NewU32(buf.pread::<u32>(offset).unwrap()).into(),
RadIntId::U64 => NewU64(buf.pread::<u64>(offset).unwrap()).into(),
RadIntId::U128 => NewU128(buf.pread::<u128>(offset).unwrap()).into(),
_ => panic!("signed barcode integer encodings are not supported"),
};
barcodes.push(bc);
offset += bct.bytes_for_type();
}
let umi = match ctx.umit {
RadIntId::U8 => buf.pread::<u8>(offset).unwrap() as u64,
RadIntId::U16 => buf.pread::<u16>(offset).unwrap() as u64,
RadIntId::U32 => buf.pread::<u32>(offset).unwrap() as u64,
RadIntId::U64 => buf.pread::<u64>(offset).unwrap(),
RadIntId::U128 => panic!("u128 is currently not supported as a umi type"),
_ => panic!("signed umi integer encodings are not supported"),
};
(barcodes, umi)
}
#[inline]
fn from_bytes_with_context<T: Read>(reader: &mut T, ctx: &Self::ParsingContext) -> Self {
let mut rbuf = [0u8; 255];
reader.read_exact(&mut rbuf[0..4]).unwrap();
let na = u32::from_le_bytes([rbuf[0], rbuf[1], rbuf[2], rbuf[3]]);
let mut barcodes = SmallVec::new();
for bct in &ctx.bc_types {
let bc: B = rad_io::read_into(reader, bct);
barcodes.push(bc);
}
let umi = rad_io::read_into_u64(reader, &ctx.umit);
let mut rec = Self {
barcodes,
umi,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let dir = (v & utils::MASK_LOWER_31_U32) != 0;
rec.dirs.push(dir);
rec.refs.push(v & utils::MASK_TOP_BIT_U32);
}
rec
}
#[inline]
fn write<W: Write>(&self, writer: &mut W, ctx: &Self::ParsingContext) -> anyhow::Result<()> {
let na: u32 = self.refs.len() as u32;
RadIntId::U32
.write_to(na, writer)
.context("couldn't write number of alignments for multi-barcode record")?;
for (bc, bct) in self.barcodes.iter().zip(ctx.bc_types.iter()) {
bct.write_to(*bc, writer)
.context("couldn't write barcode field for multi-barcode record")?;
}
ctx.umit
.write_to(self.umi, writer)
.context("couldn't write umi field for multi-barcode record")?;
let dir_iter = self.dirs.iter();
for (dir, ref_idx) in
itertools::izip!(dir_iter.chain(std::iter::repeat(&false)), &self.refs)
{
let encoded_dir: u32 = if *dir { 1_u32 << 31 } else { 0_u32 };
let encoded_dir_ref: u32 = ref_idx | encoded_dir;
writer
.write_all(&encoded_dir_ref.to_le_bytes())
.context("couldn't write compressed_ori_refid for multi-barcode record")?;
}
Ok(())
}
}
impl<B: ConvertiblePrimitiveInteger> CollatableMappedRecord<B> for MultiBarcodeReadRecordT<B> {
type CollatableRecordHeader = MultiBarcodeReadRecordHeader<B>;
#[inline]
fn from_bytes_with_header_retain_ori<T: Read>(
reader: &mut T,
hdr: &mut Self::CollatableRecordHeader,
_ctx: &<Self as MappedRecord>::ParsingContext,
expected_ori: &MappedFragmentOrientation,
) -> Self {
let rec = MultiBarcodeReadRecordT::<B>::from_bytes_with_header_keep_ori(
reader,
hdr.barcodes.clone(),
hdr.umi,
hdr.naln,
expected_ori.into(),
);
hdr.naln = rec.refs.len() as u32;
rec
}
fn set_collate_key(&mut self, k: B) {
if let Some(last) = self.barcodes.last_mut() {
*last = k;
}
}
fn collate_key(&self) -> B {
*self
.barcodes
.last()
.expect("multi-barcode record must have at least one barcode")
}
fn from_bytes_collatable_header<T: Read>(
reader: &mut T,
context: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader> {
let mut rbuf = [0u8; 4];
reader.read_exact(&mut rbuf).unwrap();
let na = u32::from_le_bytes(rbuf);
let mut barcodes = SmallVec::new();
for bct in &context.bc_types {
let bc: B = rad_io::read_into(reader, bct);
barcodes.push(bc);
}
let umi = rad_io::read_into_u64(reader, &context.umit);
Ok(Self::CollatableRecordHeader {
naln: na,
barcodes,
umi,
})
}
fn peek_collatable_header(
buf: &[u8],
ctx: &<Self as MappedRecord>::ParsingContext,
) -> anyhow::Result<Self::CollatableRecordHeader> {
let na_size = mem::size_of::<u32>();
let na = buf.pread::<u32>(0).unwrap();
let mut offset = na_size;
let mut barcodes = SmallVec::new();
for bct in &ctx.bc_types {
let bc: B = match bct {
RadIntId::U8 => NewU8(buf.pread::<u8>(offset).unwrap()).into(),
RadIntId::U16 => NewU16(buf.pread::<u16>(offset).unwrap()).into(),
RadIntId::U32 => NewU32(buf.pread::<u32>(offset).unwrap()).into(),
RadIntId::U64 => NewU64(buf.pread::<u64>(offset).unwrap()).into(),
RadIntId::U128 => NewU128(buf.pread::<u128>(offset).unwrap()).into(),
_ => panic!("signed barcode integer encodings are not supported"),
};
barcodes.push(bc);
offset += bct.bytes_for_type();
}
let umi = match ctx.umit {
RadIntId::U8 => buf.pread::<u8>(offset).unwrap() as u64,
RadIntId::U16 => buf.pread::<u16>(offset).unwrap() as u64,
RadIntId::U32 => buf.pread::<u32>(offset).unwrap() as u64,
RadIntId::U64 => buf.pread::<u64>(offset).unwrap(),
RadIntId::U128 => panic!("u128 is currently not supported as a umi type"),
_ => panic!("signed umi integer encodings are not supported"),
};
Ok(Self::CollatableRecordHeader {
naln: na,
barcodes,
umi,
})
}
}
pub trait HierarchicallyCollatable<B: ConvertiblePrimitiveInteger>:
CollatableMappedRecord<B>
{
fn num_collation_levels(&self) -> usize;
fn collation_key_at_level(&self, level: usize) -> B;
fn set_collation_key_at_level(&mut self, level: usize, k: B);
}
impl<B: ConvertiblePrimitiveInteger> HierarchicallyCollatable<B> for MultiBarcodeReadRecordT<B> {
fn num_collation_levels(&self) -> usize {
self.barcodes.len()
}
fn collation_key_at_level(&self, level: usize) -> B {
self.barcodes[level]
}
fn set_collation_key_at_level(&mut self, level: usize, k: B) {
self.barcodes[level] = k;
}
}
impl<B: ConvertiblePrimitiveInteger> MultiBarcodeReadRecordT<B> {
pub fn from_bytes_record_header<T: Read>(
reader: &mut T,
bc_types: &[RadIntId],
umit: &RadIntId,
) -> (SmallVec<[B; MAX_INLINE_BARCODES]>, u64, u32) {
let mut rbuf = [0u8; 4];
reader.read_exact(&mut rbuf).unwrap();
let na = u32::from_le_bytes(rbuf);
let mut barcodes = SmallVec::new();
for bct in bc_types {
let bc: B = rad_io::read_into(reader, bct);
barcodes.push(bc);
}
let umi = rad_io::read_into_u64(reader, umit);
(barcodes, umi, na)
}
#[inline]
pub fn from_bytes_with_header_keep_ori<T: Read>(
reader: &mut T,
barcodes: SmallVec<[B; MAX_INLINE_BARCODES]>,
umi: u64,
na: u32,
expected_ori: &Strand,
) -> Self {
let mut rbuf = [0u8; 255];
let mut rec = Self {
barcodes,
umi,
dirs: Vec::with_capacity(na as usize),
refs: Vec::with_capacity(na as usize),
};
for _ in 0..(na as usize) {
reader.read_exact(&mut rbuf[0..4]).unwrap();
let v = rbuf.pread::<u32>(0).unwrap();
let strand = if (v & utils::MASK_LOWER_31_U32) > 0 {
Strand::Forward
} else {
Strand::Reverse
};
if expected_ori.same(&strand) || expected_ori.is_unknown() {
let dir = (v & utils::MASK_LOWER_31_U32) != 0;
rec.dirs.push(dir);
rec.refs.push(v & utils::MASK_TOP_BIT_U32);
}
}
let indices = argsort(&rec.refs);
reorder_in_place(&mut rec.refs, &indices);
reorder_in_place(&mut rec.dirs, &indices);
rec
}
}
#[cfg(test)]
mod tests {
use crate::rad_types::{RadIntId, TagSection, TagSectionLabel};
use crate::rad_types::{RadType, TagDesc};
use crate::record::{AlevinFryReadRecord, AlevinFryRecordContext, MappedRecord, RecordContext};
use std::io::Cursor;
#[test]
fn can_write_af_record() {
let rec = AlevinFryReadRecord {
bc: 12345_u64,
umi: 6789_u64,
dirs: vec![true, true, true, false],
refs: vec![123, 456, 78, 910],
};
let ft = TagSection::new_with_label(TagSectionLabel::FileTags);
let mut rt = TagSection::new_with_label(TagSectionLabel::ReadTags);
rt.add_tag_desc(TagDesc {
name: "b".to_string(),
typeid: RadType::Int(RadIntId::U32),
});
rt.add_tag_desc(TagDesc {
name: "u".to_string(),
typeid: RadType::Int(RadIntId::U32),
});
let at = TagSection::new_with_label(TagSectionLabel::AlignmentTags);
let ctx = AlevinFryRecordContext::get_context_from_tag_section(&ft, &rt, &at).unwrap();
let mut buf: Vec<u8> = Vec::new();
rec.write(&mut buf, &ctx).expect("couldn't write record");
let mut cursor = Cursor::new(buf);
let new_rec = AlevinFryReadRecord::from_bytes_with_context(&mut cursor, &ctx);
assert_eq!(rec, new_rec);
}
#[test]
fn can_write_multi_barcode_record() {
use crate::collation::BarcodeRole;
use crate::record::{MultiBarcodeReadRecord, MultiBarcodeRecordContext};
use smallvec::smallvec;
let rec = MultiBarcodeReadRecord {
barcodes: smallvec![42_u64, 12345_u64],
umi: 6789_u64,
dirs: vec![true, false, true],
refs: vec![100, 200, 300],
};
let ctx = MultiBarcodeRecordContext {
bc_types: smallvec![RadIntId::U32, RadIntId::U32],
umit: RadIntId::U32,
roles: smallvec![BarcodeRole::Sample, BarcodeRole::Cell],
};
let mut buf: Vec<u8> = Vec::new();
rec.write(&mut buf, &ctx)
.expect("couldn't write multi-barcode record");
let mut cursor = Cursor::new(buf);
let new_rec = MultiBarcodeReadRecord::from_bytes_with_context(&mut cursor, &ctx);
assert_eq!(rec, new_rec);
}
#[test]
fn multi_barcode_collation_key_is_last_barcode() {
use crate::record::{
CollatableMappedRecord, HierarchicallyCollatable, MultiBarcodeReadRecord,
};
use smallvec::smallvec;
let rec = MultiBarcodeReadRecord {
barcodes: smallvec![42_u64, 12345_u64],
umi: 6789_u64,
dirs: vec![true],
refs: vec![100],
};
assert_eq!(rec.collate_key(), 12345_u64);
assert_eq!(rec.collation_key_at_level(0), 42_u64);
assert_eq!(rec.collation_key_at_level(1), 12345_u64);
assert_eq!(rec.num_collation_levels(), 2);
}
#[test]
fn multi_barcode_header_grouping_key_includes_sample() {
use crate::collation::BarcodeRole;
use crate::record::{
CollatableRecordHeader, MultiBarcodeReadRecordHeader, MultiBarcodeRecordContext,
};
use smallvec::smallvec;
let ctx = MultiBarcodeRecordContext {
bc_types: smallvec![RadIntId::U32, RadIntId::U32],
umit: RadIntId::U32,
roles: smallvec![BarcodeRole::Sample, BarcodeRole::Cell],
};
let hdr_a = MultiBarcodeReadRecordHeader {
naln: 1,
barcodes: smallvec![1_u64, 12345_u64],
umi: 7,
};
let hdr_b = MultiBarcodeReadRecordHeader {
naln: 1,
barcodes: smallvec![2_u64, 12345_u64],
umi: 8,
};
assert_eq!(hdr_a.collate_key(), 12345_u64);
assert_eq!(hdr_b.collate_key(), 12345_u64);
assert_ne!(
hdr_a.collation_group_key(&ctx),
hdr_b.collation_group_key(&ctx)
);
}
}