Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions crates/fgumi-umi/src/assigner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ pub fn count_mismatches(a: &str, b: &str) -> usize {
if a.len() != b.len() {
return usize::MAX;
}
a.chars().zip(b.chars()).filter(|(x, y)| x != y).count()
a.as_bytes().iter().zip(b.as_bytes()).filter(|(x, y)| x != y).count()
}

/// Check if two UMI sequences match within a maximum mismatch threshold.
Expand Down Expand Up @@ -855,27 +855,22 @@ impl UmiAssigner for IdentityUmiAssigner {
return Vec::new();
}

// Map each UMI to its canonical (uppercase) form and track unique canonicals
let mut canonical_to_id: HashMap<String, MoleculeId> = HashMap::new();
let mut unique_canonicals: Vec<String> = Vec::new();

for umi in raw_umis {
let canonical = umi.to_uppercase();
if !canonical_to_id.contains_key(&canonical) {
unique_canonicals.push(canonical);
}
}
// Uppercase each UMI once and collect unique canonicals
let canonicals: Vec<String> = raw_umis.iter().map(|umi| umi.to_uppercase()).collect();
let mut unique_canonicals: Vec<&str> =
canonicals.iter().map(String::as_str).collect::<HashSet<_>>().into_iter().collect();

// Sort for deterministic ID assignment
unique_canonicals.sort();
unique_canonicals.sort_unstable();

// Assign IDs in sorted order
for canonical in unique_canonicals {
canonical_to_id.insert(canonical, MoleculeId::Single(self.next_id()));
}
let canonical_to_id: HashMap<&str, MoleculeId> = unique_canonicals
.into_iter()
.map(|c| (c, MoleculeId::Single(self.next_id())))
.collect();

// Build result Vec indexed by input position
raw_umis.iter().map(|umi| canonical_to_id[&umi.to_uppercase()]).collect()
canonicals.iter().map(|c| canonical_to_id[c.as_str()]).collect()
}

fn as_any(&self) -> &dyn Any {
Expand Down
42 changes: 10 additions & 32 deletions crates/fgumi-umi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,37 +144,17 @@ impl std::fmt::Display for MoleculeId {
pub struct TagSets;

impl TagSets {
/// Returns the list of Consensus tags that should be reversed
/// Consensus tags that should be reversed.
///
/// These are per-base tags from fgbio consensus callers that need to be
/// reversed when the read is mapped to the negative strand.
///
/// # Returns
///
/// Vector of tag names: `["ad", "ae", "bd", "be", "cd"]`
#[must_use]
pub fn consensus_reverse() -> Vec<String> {
vec![
"ad".to_string(),
"ae".to_string(),
"bd".to_string(),
"be".to_string(),
"cd".to_string(),
]
}
pub const CONSENSUS_REVERSE: &[&str] = &["ad", "ae", "bd", "be", "cd"];

/// Returns the list of Consensus tags that should be reverse complemented
/// Consensus tags that should be reverse complemented.
///
/// These are per-base sequence tags from fgbio consensus callers that need
/// to be reverse complemented when the read is mapped to the negative strand.
///
/// # Returns
///
/// Vector of tag names: `["aD", "bD", "cD"]`
#[must_use]
pub fn consensus_revcomp() -> Vec<String> {
vec!["aD".to_string(), "bD".to_string(), "cD".to_string()]
}
pub const CONSENSUS_REVCOMP: &[&str] = &["aD", "bD", "cD"];
}

/// Information about which tags to remove, reverse, or reverse complement
Expand Down Expand Up @@ -220,15 +200,15 @@ impl TagInfo {

for tag in reverse {
if tag == "Consensus" {
reverse_set.extend(TagSets::consensus_reverse());
reverse_set.extend(TagSets::CONSENSUS_REVERSE.iter().map(|&s| s.to_owned()));
} else {
reverse_set.insert(tag);
}
}

for tag in revcomp {
if tag == "Consensus" {
revcomp_set.extend(TagSets::consensus_revcomp());
revcomp_set.extend(TagSets::CONSENSUS_REVCOMP.iter().map(|&s| s.to_owned()));
} else {
revcomp_set.insert(tag);
}
Expand Down Expand Up @@ -339,16 +319,14 @@ mod tests {

#[test]
fn test_consensus_reverse_returns_correct_tags() {
let tags = TagSets::consensus_reverse();
assert_eq!(tags.len(), 5);
assert_eq!(tags, vec!["ad", "ae", "bd", "be", "cd"]);
assert_eq!(TagSets::CONSENSUS_REVERSE.len(), 5);
assert_eq!(TagSets::CONSENSUS_REVERSE, &["ad", "ae", "bd", "be", "cd"]);
}

#[test]
fn test_consensus_revcomp_returns_correct_tags() {
let tags = TagSets::consensus_revcomp();
assert_eq!(tags.len(), 3);
assert_eq!(tags, vec!["aD", "bD", "cD"]);
assert_eq!(TagSets::CONSENSUS_REVCOMP.len(), 3);
assert_eq!(TagSets::CONSENSUS_REVCOMP, &["aD", "bD", "cD"]);
}

#[test]
Expand Down
Loading