fastqc_rust/utils/
phred.rs1#[derive(Debug, Clone, Copy)]
6pub struct PhredEncoding {
7 pub name: &'static str,
8 pub offset: u8,
9}
10
11const SANGER_ENCODING_OFFSET: u8 = 33;
14const ILLUMINA_1_3_ENCODING_OFFSET: u8 = 64;
15
16impl PhredEncoding {
17 pub const SANGER: PhredEncoding = PhredEncoding {
23 name: "Sanger / Illumina 1.9",
24 offset: SANGER_ENCODING_OFFSET,
25 };
26}
27
28impl PhredEncoding {
29 pub const PHRED64: PhredEncoding = PhredEncoding {
32 name: "Illumina 1.5",
33 offset: ILLUMINA_1_3_ENCODING_OFFSET,
34 };
35}
36
37pub const NO_QUALITY_SEEN: u16 = 1000;
40
41pub fn resolve(hint: Option<PhredEncoding>, lowest_char: u16) -> Result<PhredEncoding, String> {
47 match hint {
48 Some(e) if e.offset == ILLUMINA_1_3_ENCODING_OFFSET => detect(lowest_char, true),
49 Some(e) => Ok(e),
50 None => detect(lowest_char, false),
51 }
52}
53
54pub fn detect(lowest_char: u16, phred64: bool) -> Result<PhredEncoding, String> {
57 let c = char::from_u32(lowest_char as u32).unwrap_or('?');
58 if lowest_char < 33 {
59 return Err(format!(
60 "No known encodings with chars < 33 (Yours was '{}' with value {})",
61 c, lowest_char
62 ));
63 }
64 if !phred64 {
65 return Ok(PhredEncoding::SANGER);
66 }
67 if lowest_char < ILLUMINA_1_3_ENCODING_OFFSET as u16 {
68 return Err(format!(
69 "Phred64 encoding is incompatible with having ASCII char '{}' with value {}) in the file",
70 c, lowest_char
71 ));
72 }
73 if lowest_char == ILLUMINA_1_3_ENCODING_OFFSET as u16 + 1 {
75 Ok(PhredEncoding {
76 name: "Illumina 1.3",
77 offset: ILLUMINA_1_3_ENCODING_OFFSET,
78 })
79 } else {
80 Ok(PhredEncoding::PHRED64)
81 }
82}
83
84pub fn phred64_suspicion(lowest_char: u16) -> Option<String> {
87 (lowest_char >= ILLUMINA_1_3_ENCODING_OFFSET as u16 && lowest_char != NO_QUALITY_SEEN).then(
88 || {
89 format!(
90 "Using Phred33 encoding your lowest quality is {} could this file be Phred64 encoded?",
91 lowest_char - SANGER_ENCODING_OFFSET as u16
92 )
93 },
94 )
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_phred33_default() {
103 assert_eq!(detect(b'!' as u16, false).unwrap().offset, 33);
104 assert_eq!(
106 detect(b'I' as u16, false).unwrap().name,
107 "Sanger / Illumina 1.9"
108 );
109 assert_eq!(detect(200, false).unwrap().offset, 33);
110 assert_eq!(detect(NO_QUALITY_SEEN, false).unwrap().offset, 33);
111 }
112
113 #[test]
114 fn test_phred64() {
115 assert_eq!(detect(65, true).unwrap().name, "Illumina 1.3");
116 assert_eq!(detect(66, true).unwrap().name, "Illumina 1.5");
117 assert_eq!(detect(66, true).unwrap().offset, 64);
118 assert!(detect(63, true)
119 .unwrap_err()
120 .contains("Phred64 encoding is incompatible"));
121 }
122
123 #[test]
124 fn test_error_below_33() {
125 assert!(detect(20, false).unwrap_err().contains("< 33"));
126 }
127
128 #[test]
129 fn test_resolve() {
130 let enc = resolve(Some(PhredEncoding::SANGER), b'I' as u16).unwrap();
131 assert_eq!(enc.name, "Sanger / Illumina 1.9");
132 assert_eq!(
133 resolve(Some(PhredEncoding::PHRED64), 65).unwrap().name,
134 "Illumina 1.3"
135 );
136 assert_eq!(resolve(None, b'I' as u16).unwrap().offset, 33);
137 }
138
139 #[test]
140 fn test_phred64_suspicion() {
141 assert!(phred64_suspicion(b'I' as u16)
142 .unwrap()
143 .contains("lowest quality is 40"));
144 assert!(phred64_suspicion(b'?' as u16).is_none());
145 assert!(phred64_suspicion(NO_QUALITY_SEEN).is_none());
146 }
147}