fibertools_rs/utils/
platform.rs1use lazy_static::lazy_static;
6use regex::Regex;
7use rust_htslib::bam::Record;
8
9const PACBIO_READ_TAGS: [&[u8; 2]; 4] = [b"zm", b"np", b"rq", b"sn"];
13
14const ONT_READ_TAGS: [&[u8; 2]; 5] = [b"mx", b"ch", b"st", b"du", b"ts"];
19
20lazy_static! {
21 static ref PACBIO_QNAME: Regex = Regex::new(r"^m\w+/\d+/").unwrap();
25 static ref ONT_QNAME: Regex = Regex::new(
29 r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
30 ).unwrap();
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum SeqPlatform {
36 PacBio,
37 Ont,
38 Unknown,
40}
41
42pub fn platform_from_record(rec: &Record) -> SeqPlatform {
45 match platform_from_aux(rec) {
46 SeqPlatform::Unknown => platform_from_qname(rec.qname()),
47 known => known,
48 }
49}
50
51pub fn platform_from_aux(rec: &Record) -> SeqPlatform {
54 let has_any = |tags: &[&[u8; 2]]| tags.iter().any(|t| rec.aux(&t[..]).is_ok());
55 let pacbio = has_any(&PACBIO_READ_TAGS);
56 let ont = has_any(&ONT_READ_TAGS);
57 match (pacbio, ont) {
58 (true, false) => SeqPlatform::PacBio,
59 (false, true) => SeqPlatform::Ont,
60 _ => SeqPlatform::Unknown,
62 }
63}
64
65pub fn platform_from_qname(qname: &[u8]) -> SeqPlatform {
68 let name = String::from_utf8_lossy(qname);
69 if PACBIO_QNAME.is_match(&name) {
70 SeqPlatform::PacBio
71 } else if ONT_QNAME.is_match(&name) {
72 SeqPlatform::Ont
73 } else {
74 SeqPlatform::Unknown
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use rust_htslib::bam::{self, Read};
82
83 fn first_record(path: &str) -> Record {
84 let mut reader = bam::Reader::from_path(path).unwrap();
85 let mut rec = Record::new();
86 reader
87 .read(&mut rec)
88 .expect("fixture has at least one record")
89 .expect("read ok");
90 rec
91 }
92
93 const PACBIO_FIXTURES: [&str; 5] = [
94 "tests/data/revio.bam",
95 "tests/data/two_two.bam",
96 "tests/data/three_two.bam",
97 "tests/data/all.bam",
98 "tests/data/ctcf.bam",
99 ];
100 const ONT_FIXTURE: &str = "tests/data/ONT.NAPA.bam";
101
102 #[test]
103 fn aux_detects_pacbio() {
104 for f in PACBIO_FIXTURES {
105 assert_eq!(
106 platform_from_aux(&first_record(f)),
107 SeqPlatform::PacBio,
108 "{f}"
109 );
110 }
111 }
112
113 #[test]
114 fn aux_detects_ont() {
115 assert_eq!(
116 platform_from_aux(&first_record(ONT_FIXTURE)),
117 SeqPlatform::Ont
118 );
119 }
120
121 #[test]
122 fn qname_detects_pacbio() {
123 for f in PACBIO_FIXTURES {
124 assert_eq!(
125 platform_from_qname(first_record(f).qname()),
126 SeqPlatform::PacBio,
127 "{f}"
128 );
129 }
130 }
131
132 #[test]
133 fn qname_detects_ont() {
134 assert_eq!(
135 platform_from_qname(first_record(ONT_FIXTURE).qname()),
136 SeqPlatform::Ont
137 );
138 }
139
140 #[test]
141 fn qname_recovers_platform_after_aux_stripping() {
142 let mut rec = first_record(ONT_FIXTURE);
144 for t in ONT_READ_TAGS {
145 rec.remove_aux(t).ok();
146 }
147 assert_eq!(platform_from_aux(&rec), SeqPlatform::Unknown);
148 assert_eq!(platform_from_record(&rec), SeqPlatform::Ont);
149
150 let mut rec = first_record("tests/data/revio.bam");
151 for t in PACBIO_READ_TAGS {
152 rec.remove_aux(t).ok();
153 }
154 assert_eq!(platform_from_aux(&rec), SeqPlatform::Unknown);
155 assert_eq!(platform_from_record(&rec), SeqPlatform::PacBio);
156 }
157
158 #[test]
159 fn qname_patterns_and_non_matches() {
160 assert_eq!(
161 platform_from_qname(b"m84081_231207_200206_s1/240456095/ccs"),
162 SeqPlatform::PacBio
163 );
164 assert_eq!(
165 platform_from_qname(b"m54329U_210810_004956/120523046/0_5000"),
166 SeqPlatform::PacBio
167 );
168 assert_eq!(
169 platform_from_qname(b"d78c9b31-cec7-4381-bc1c-5f1513d070b9"),
170 SeqPlatform::Ont
171 );
172 assert_eq!(platform_from_qname(b"read_12345"), SeqPlatform::Unknown);
173 }
174}