seqtk_rs/trim.rs
1use crate::io_utils::{FqReader, FxWriter};
2/// Trims low-quality bases from a FASTQ data based on a quality threshold `Q`.
3/// Outputs the FASTQ to [`std::io::stdout()`].
4///
5/// The algorithm:
6///
7/// (1) Scan the read from 5' to 3' to find the first base with `quality >= Q`.
8/// This is the starting position of the trimmed read.
9/// (2) Compute a running score from 5' to 3'
10/// `score(i) = score(i-1) + quality(i) - Q`
11/// where the initial score is `score(start) = quality(start) - Q`.
12/// (3) Find out the maximun score. This is the end of the trimmed read.
13///
14/// Note:
15/// (1) If all base quality are less than `Q`, the read is discarded.
16/// (2) If the trimmed read is shorter than `min_len`, we first extend it from the 3' end. If still too short, extend form the 5' end until `min_len` is reached or no more bases are available.
17///
18///
19/// # Arguments
20///
21/// * `path` - FASTQ path
22/// * `q_plus_ascii` - The sum of quality threshold and asciibase.
23/// * `minilen` - The minimum length of read.
24///
25/// # Errors
26///
27/// Return an error if the operation cannot be completed.
28///
29/// # Notes
30/// Quality trimming is no longer necessary in most modern sequencing pipelines.
31/// Its usefulness depends on the sequencing technology and the goals of your downstream analysis.
32///
33/// For example:
34/// * Illumina reads may benefit from light trimming low-quality tails at 3' end.
35/// * Long-read technologies are generally not necessary to do quality trimming, because it may remove informative regions and reduce read length unnecessarily.
36///
37pub fn trimfq(fq_path: &str, q_plus_ascii: u8, minlen: usize) -> Result<(), std::io::Error> {
38 let reader = FqReader::new(fq_path)?;
39 let mut writer = FxWriter::new(false);
40 let (mut start, mut end, mut size): (usize, usize, usize);
41 for record in reader.records() {
42 let read = record.unwrap();
43 (start, end) = trim_read_by_q(read.qual(), q_plus_ascii);
44
45 // discard read if start == end (Q < q_threshold for all bases)
46 if start < end {
47 size = read.qual().len();
48
49 if size < minlen {
50 // write full length is size < minlen
51 (start, end) = (0, size - 1);
52 } else if minlen > (end - start + 1) {
53 if size - start >= minlen {
54 // append 3' seq to reach the minlen
55 end = start + minlen - 1;
56 } else {
57 // append 5' seq if the whole 3' end is not enough
58 end = size - 1;
59 start = size - minlen;
60 }
61 }
62 writer.write(
63 read.id(),
64 &read.seq()[start..(end + 1)],
65 read.desc(),
66 &read.qual()[start..(end + 1)],
67 )?;
68 }
69 }
70 Ok(())
71}
72
73/// return start, end
74fn trim_read_by_q(qual: &[u8], q_plus_ascii: u8) -> (usize, usize) {
75 let mut start: usize = 0;
76 let len = qual.len();
77 for &q in qual {
78 if q >= q_plus_ascii {
79 break;
80 } else {
81 start += 1;
82 }
83 }
84 if start == len {
85 return (len, len);
86 }
87
88 let qthd_add_ascii_i = q_plus_ascii as isize;
89 let mut err_sum: Vec<isize> = vec![0; len];
90
91 err_sum[start] = qual[start] as isize - qthd_add_ascii_i;
92 let (mut max_idx, mut max_val) = (start, err_sum[start]);
93
94 ((start + 1)..len).for_each(|i| {
95 err_sum[i] = err_sum[i - 1] + qual[i] as isize - qthd_add_ascii_i;
96 if err_sum[i] >= max_val {
97 max_idx = i;
98 max_val = err_sum[i];
99 }
100 });
101
102 // println!("[!] thres: {} sum: {:?}", qthd_add_ascii_i, &err_sum[start..]);
103
104 (start, max_idx)
105}
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_trim_read() {
112 let qual = b"&&&&&...&...++.&.++...&...''..'.&..+..++++.++";
113 let (start, end) = trim_read_by_q(qual, 33 + 10);
114 assert_eq!(end - start + 1, 40);
115 let qual= b"?@<DDD;2A<><FIBHB?FCGAHHEBEHAFCBEFGGB@4CCA@?*?DH9?B?BDC/?81.8BFFEHG@>@G=DGCECEHEHF>>;?;?B=3@CC:(,(98',>5(82)<5>>223@4+4>@+5855>>>@:AA@>:43>@##########";
116
117 let (start, end) = trim_read_by_q(qual, 33 + 20);
118 assert_eq!(end - start + 1, 140);
119 }
120}