55from concurrent .futures import ProcessPoolExecutor , as_completed
66
77import enlighten
8+ from Bio .Data import IUPACData
89from Bio .Seq import reverse_complement
910from helperlibs .bio import seqio
1011
3839
3940logger = logging .getLogger ("geneml" )
4041
42+ AMBIGUOUS_DNA_LETTERS = frozenset (IUPACData .ambiguous_dna_letters ) - frozenset ('ACGTN' )
43+ AMBIGUOUS_DNA_TO_N = str .maketrans ({base : 'N' for base in AMBIGUOUS_DNA_LETTERS })
44+
4145
4246def parse_contigs (inpath : str , contigs_filter : list [str ] | None ) -> tuple [dict [str , str ], int ]:
4347 """Parse and validate contig sequences from an input file.
4448
4549 Reads genome records, restricted to IDs in contigs_filter if provided.
46- Sequences are converted to uppercase and validated to contain only valid nucleotide characters.
50+ Sequences are converted to uppercase, ambiguous IUPAC DNA codes are mapped to N,
51+ and the result is validated to contain only valid nucleotide characters.
4752
4853 Args:
4954 inpath: Path to an input sequence file in FASTA/GenBank/EMBL format
@@ -64,6 +69,15 @@ def parse_contigs(inpath: str, contigs_filter: list[str] | None) -> tuple[dict[s
6469 to_process .discard (record .id )
6570
6671 seq = str (record .seq ).upper ()
72+ ambiguous_dna_letters = sorted (set (seq ) & AMBIGUOUS_DNA_LETTERS )
73+ if ambiguous_dna_letters :
74+ logger .warning (
75+ 'Contig %s contains ambiguous DNA codes %s; converting them to N.' ,
76+ record .id ,
77+ ', ' .join (ambiguous_dna_letters ),
78+ )
79+
80+ seq = seq .translate (AMBIGUOUS_DNA_TO_N )
6781 # Check if sequence is valid
6882 if not seq :
6983 raise ValueError (f"Contig { record .id } has no sequence." )
0 commit comments