Skip to main content

ciri_toolkit/
fasta.rs

1//! FASTA module: High-speed sequence loading with Needletail.
2//!
3//! This module provides the `FastaReader` struct for loading reference genome sequences
4//! into memory for rapid retrieval during junction validation.
5
6use anyhow::Result;
7use needletail::parse_fastx_file;
8use std::collections::HashMap;
9
10/// In-memory reference FASTA store.
11///
12/// The full chromosome sequence is kept as uppercase text because both Scan1 and
13/// Scan2 perform many short substring comparisons, and the reference size used by
14/// CIRI is small enough that this tradeoff is simpler and faster than on-demand I/O.
15pub struct FastaReader {
16    /// Maps from chromosome name to its sequence length.
17    pub chr_len_map: HashMap<String, usize>,
18    /// Maps from chromosome name (e.g., "chr1") to its full uppercase sequence string.
19    pub chr_tcga_map: HashMap<String, String>,
20}
21
22impl FastaReader {
23    /// Initializes an empty FASTA store.
24    pub fn new() -> Self {
25        Self {
26            chr_len_map: HashMap::new(),
27            chr_tcga_map: HashMap::new(),
28        }
29    }
30
31    /// Reads reference sequences from a FASTA file.
32    ///
33    /// `needletail` handles parsing, while this wrapper normalizes names and case
34    /// so later junction-validation code can use cheap byte/substring comparisons.
35    ///
36    /// # Arguments
37    /// * `fasta_file` - Path to the reference genome FASTA file.
38    pub fn read_fasta(&mut self, fasta_file: &str) -> Result<()> {
39        let mut reader = parse_fastx_file(fasta_file)?;
40        while let Some(record) = reader.next() {
41            let seq_record = record?;
42            // Use the first whitespace-separated part of the ID as the chromosome name.
43            let chr_name = String::from_utf8_lossy(
44                seq_record.id().split(|&b| b == b' ').next().unwrap_or(b"*"),
45            )
46            .to_string();
47            let seq = String::from_utf8_lossy(seq_record.seq().as_ref())
48                .to_string()
49                .to_uppercase();
50
51            self.chr_len_map.insert(chr_name.clone(), seq.len());
52            self.chr_tcga_map.insert(chr_name, seq);
53        }
54        Ok(())
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use std::io::Write;
62    use tempfile::NamedTempFile;
63
64    #[test]
65    fn test_fasta_reader() {
66        let mut file = NamedTempFile::new().unwrap();
67        writeln!(file, ">chr1 test\nATGCATGC\n>chr2\nGGGGCCCC").unwrap();
68
69        let mut reader = FastaReader::new();
70        reader.read_fasta(file.path().to_str().unwrap()).unwrap();
71
72        assert_eq!(reader.chr_tcga_map.get("chr1").unwrap(), "ATGCATGC");
73        assert_eq!(reader.chr_tcga_map.get("chr2").unwrap(), "GGGGCCCC");
74        assert_eq!(reader.chr_tcga_map.len(), 2);
75    }
76}