rust_lib_reference_genome/
reference_genome.rs1
2use bio::io::fasta;
3use flate2::bufread::MultiGzDecoder;
4use log::{debug, warn};
5use rustc_hash::FxHashMap as HashMap;
6use simple_error::{bail, SimpleError};
7use std::io::{BufRead, BufReader};
8use std::path::{Path, PathBuf};
9
10pub struct ReferenceGenome {
12 filename: PathBuf,
14 contig_keys: Vec<String>,
16 contig_map: HashMap<String, Vec<u8>>
18}
19
20impl ReferenceGenome {
21 pub fn empty_reference() -> Self {
23 Self {
24 filename: PathBuf::from(""),
25 contig_keys: vec![],
26 contig_map: Default::default()
27 }
28 }
29
30 pub fn from_fasta(fasta_fn: &Path) -> Result<ReferenceGenome, Box<dyn std::error::Error>> {
37 debug!("Loading {:?}...", fasta_fn);
38 let mut contig_keys: Vec<String> = Default::default();
39 let mut contig_map: HashMap<String, Vec<u8>> = Default::default();
40
41 let fasta_file: std::fs::File = std::fs::File::open(fasta_fn)?;
43 let file_reader = BufReader::new(fasta_file);
44 let fasta_reader: fasta::Reader<Box<dyn BufRead>> = if fasta_fn.extension().unwrap_or_default() == "gz" {
45 debug!("Detected gzip extension, loading reference with MultiGzDecoder...");
46 let gz_decoder = MultiGzDecoder::new(file_reader);
47 let bufreader = BufReader::new(gz_decoder);
48 fasta::Reader::from_bufread(Box::new(bufreader))
49 } else {
50 debug!("Loading reference as plain-text file...");
51 fasta::Reader::from_bufread(Box::new(file_reader))
52 };
53
54 for entry in fasta_reader.records() {
55 let record: fasta::Record = entry?;
56 let seq_id: String = record.id().to_string();
57 let sequence: Vec<u8> = record.seq().to_ascii_uppercase();
58
59 contig_keys.push(seq_id.clone());
60 contig_map.insert(seq_id, sequence);
61 }
62 debug!("Finished loading {} contigs.", contig_map.len());
63
64 Ok(ReferenceGenome {
65 filename: fasta_fn.to_path_buf(),
66 contig_keys,
67 contig_map
68 })
69 }
70
71 pub fn add_contig(&mut self, contig_key: String, contig_sequence: &str) -> Result<(), SimpleError> {
76 if self.contig_map.contains_key(&contig_key) {
77 bail!("Contig key \"{contig_key}\" is already in the reference genome");
78 }
79
80 let byte_form = contig_sequence.to_ascii_uppercase().into_bytes();
82
83 self.contig_keys.push(contig_key.clone());
85 self.contig_map.insert(contig_key, byte_form);
86 Ok(())
87 }
88
89 pub fn filename(&self) -> &Path {
90 &self.filename
91 }
92
93 pub fn contig_keys(&self) -> &[String] {
94 &self.contig_keys
95 }
96
97 pub fn get_slice(&self, chromosome: &str, start: usize, end: usize) -> &[u8] {
107 let full_contig = self.contig_map.get(chromosome).expect("a chromosome from the reference file");
108 assert!(start <= end, "start > end: {start} > {end}");
109 let truncated_start = if start <= full_contig.len() { start } else {
110 warn!("Received get_slice({:?}, {}, {}), truncated start to {}", chromosome, start, end, full_contig.len());
111 full_contig.len()
112 };
113 let truncated_end = if end <= full_contig.len() { end } else {
114 warn!("Received get_slice({:?}, {}, {}), truncated end to {}", chromosome, start, end, full_contig.len());
115 full_contig.len()
116 };
117 &full_contig[truncated_start..truncated_end]
118 }
119
120 pub fn get_full_chromosome(&self, chromosome: &str) -> &[u8] {
126 let full_contig = self.contig_map.get(chromosome).expect("a chromosome from the reference file");
127 full_contig
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use std::path::PathBuf;
135 #[test]
136 fn test_simple_reference() {
137 let references = vec![
138 "./test_data/test_reference.fa",
139 "./test_data/test_reference.fa.gz"
140 ];
141 for &reference_fn in references.iter() {
142 let simple_reference_fn: PathBuf = PathBuf::from(reference_fn);
143 let reference_genome = ReferenceGenome::from_fasta(&simple_reference_fn).unwrap();
144
145 assert_eq!(reference_genome.contig_keys(), &[
146 "chr1".to_string(),
147 "chr2".to_string()
148 ]);
149
150 let chr1_string: Vec<u8> = "ACGTACGT".as_bytes().to_vec();
152 for i in 0..8 {
153 assert_eq!(reference_genome.get_slice(&"chr1", i, 8), &chr1_string[i..]);
154 }
155
156 let chr1_string: Vec<u8> = "ACCATGTA".as_bytes().to_vec();
158 assert_eq!(reference_genome.get_slice(&"chr2", 0, 8), chr1_string);
159 }
160 }
161
162 #[test]
163 fn test_add_contig() {
164 let mut reference_genome = ReferenceGenome::empty_reference();
165 reference_genome.add_contig("test".to_string(), "Acgt").unwrap();
166 reference_genome.add_contig("test2".to_string(), "TGNA").unwrap();
167
168 assert_eq!(reference_genome.contig_keys(), &["test".to_string(), "test2".to_string()]);
169 assert_eq!(reference_genome.get_full_chromosome("test"), b"ACGT");
170 assert_eq!(reference_genome.get_full_chromosome("test2"), b"TGNA");
171 }
172}