The dark-matter library (src/dark)¶
This documents the functionality under src/dark for developers who
want to write their own code against the library, as opposed to
doc/scripts.md, which documents the bin scripts for
people who just want to run existing tools. For each file: a short
overview, then a list of its public functions, classes (with their
important methods), and notable module-level data, plus a Used by
line naming which bin scripts directly import from that module
(not transitive usage through other dark modules).
1. Core reads & sequence I/O¶
The foundational Read/Reads class hierarchy and the
FASTA/FASTQ/PDB-FASTA readers built on it. See
doc/reads.md for an explanation of the Read class
hierarchy (what each class is for and how they relate to one another)
— the summary below is deliberately brief by comparison.
src/dark/reads.py¶
The core module of the library. Defines the Read class hierarchy (DNA, RNA,
amino-acid, secondary-structure, translated, and ORF reads), the Reads
collection class (and its filtering machinery), and the shared
addFASTACommandLineOptions/parseFASTACommandLineOptions helper pair used
by most bin scripts to read FASTA/FASTQ input.
parseSiteSettings(settings, oneBased=False)— parses an iterable ofsite:base[:quality]strings (as used by the--setSitesCLI option) into a list of(offset, base, quality)tuples, converting to 0-based offsets ifoneBasedis set. RaisesValueErroron malformed input.Read— holds a single sequence:id,sequence, and an optionalqualitystring (must matchsequence's length if given). Supports equality/ordering (@total_ordering),len(), hashing (based on an MD5 of id+sequence+quality), and slicing via__getitem__(returns a new instance of the same subclass).toString(format_="fasta")/toDict()/fromDict(d)— convert to/from FASTA or FASTQ text, or a plain dict (for JSON-style serialization).setSites(settings, oneBased=False)— mutates specific sequence (and quality) positions in place, givensite:base[:quality]strings or pre-parsed tuples.reverse()— reverses the sequence (not a complement).rotate(n, inPlace=True)— rotates the sequence left/right byncharacters.checkAlphabet(count=10)— checks that the firstcountcharacters (or all, ifNone) are a subset ofself.ALPHABET; raisesValueErrorif not.newFromSites(sites, exclude=False)— returns a new read containing only (or, ifexclude, excluding) the given 0-based site indices.find(pattern, start=0, end=False, caseSensitive=True, ignoreGaps=False, gapCharacter="-")— finds a literal (non-regex) subsequence, optionally ignoring gap characters during the search while still returning an offset into the original (gapped) sequence.getPrefixAndSuffixOffsets(prefix, suffix, ...)— finds the start offset ofprefixand the end offset ofsuffixin the sequence (each -1 if not given/found); used by the prefix/suffix-trimming and region-extraction tooling.walkHSP(hsp, includeWhiskers=True)— yields(subjectOffset, residue, inMatch)triples describing how this read aligns against a subject, as specified by anHSP(seedark.hsp); used for alignment-graph-style visualization.lowComplexityFraction()— fraction of bases that are lowercase (the convention used elsewhere in the codebase for marking low-complexity regions)._NucleotideRead(internal base forDNARead/RNARead) — addsreverseComplement()andtranslations()(yields all 6 reading-frameTranslatedReads).DNARead—ALPHABET = {A,T,C,G}; addsfindORF(offset, forward=True, requireStartCodon=True, allowGaps=True, untranslatable=None), which scans codon-by-codon fromoffsetand returns a dict describing the ORF found (length, whether a start/stop codon was found, nucleotide sequence, and its translation).RNARead—ALPHABET = {A,T,C,G,U}; otherwise likeDNAReadbut with an RNA complement table.DNAKozakRead— aDNAReadsubclass representing a detected Kozak consensus sequence; records theoriginalRead,start/stopoffsets, and akozakQualitypercentage. Constructed byfindKozakConsensus(indark.dna).AARead— amino-acid reads.properties()/propertyDetails()map each residue to property flags/details (fromdark.aaVars).ORFs(openORFs=False)scans the (already-translated) AA sequence for ORFs delimited byM...*, yieldingAAReadORFinstances; withopenORFs=True, ORFs that run off either end of the sequence (no start or no stop codon found) are also yielded.AAReadWithX—AAReadwhose alphabet also allows'X'.AAReadORF— anAAReadrepresenting one ORF found within a largerAARead; tracksstart/stopoffsets and whether the ORF isopenLeft/openRight(i.e., extends past the edge of the original read without a start/stop codon).SSAARead— anAAReadpaired with astructurestring of the same length (PDB secondary-structure annotation); overridestoStringto emit two FASTA records (sequence, then structure) in "fasta-ss" format.SSAAReadWithX—SSAAReadcombined with the'X'-extended alphabet.TranslatedRead— anAAReadrepresenting one DNA→AA translation of anoriginalRead, tagged with itsframe(0/1/2) and whether it came from thereverseComplementedstrand.maximumORFLength(openORFs=True)returns the longest ORF length found in it.ReadFilter— implements essentially all the filtering/editing logic behindfilter-fasta.pyand friends (length/complexity bounds, whitelist/blacklist by id or sequence (incl. regex and file-based lists), deduplication, random/fractional subsetting, site-based editing, id/sequence case changes, rotation, reverse/reverse-complement, quality-setting, arbitraryidLambda/readLambdafunctions, etc. — see its very long docstring for the full parameter list). Itsfilter(read)method applies all configured checks/edits in a fixed order and returns either the (possibly modified) read orFalseto drop it.readClassNameToClass— adictmapping read class names (e.g."DNARead") to the classes themselves; used by--readClassCLI options and for deserialization.getUnambiguousBases()returns a similar mapping from class name to its set of unambiguous bases/residues.Reads— a lazily-iterable collection ofReads, combining: reads added viaadd(), an optional wrappedinitialReadsiterable, and reads yielded by the (overridable)iter()method (used by file-backed subclasses likeFastaReads). Supports chained.filter(**kwargs)calls (each adds aReadFilter),save(filename, format_="fasta", mode="w"), and numerous analysis methods:summarizePosition(index)— per-site residue/base counts (Counter) across all reads.sitesMatching(targets, matchCase, any_)— the set of sites where any (or all) reads have one of thetargetsbases.variableSites(confirm=False, homogeneityLevel=1.0, unknownAreAmbiguous=False)— finds variable sites across equal-length reads, raisingReadLengthsNotIdenticalErrorotherwise.combineReads()— collapses equal-length reads into one ambiguity-coded consensus string.temporalBaseCounts(firstPostId, ...)— splits a time-ordered set of genomes into "pre"/"post" groups at a named id and reports newly-appearing bases and their frequencies in the "post" group.getPrefixAndSuffixOffsets(...)/getPrefixAndSuffixOffsetsForId(...)/extractRegion(...)— locate a prefix/suffix across a set of (usually aligned) reads and extract the enclosed region (raising on conflicting offsets between reads).mergeGapsInRegion(...)— within a prefix/suffix-delimited region, collapses all gap characters into one contiguous gap block (placed atgapOffset), used bymsa-merge-gapped-region.py; can optionally write a per-sequence TSV summary and/or return a human-readable summary string.unfilteredLength()— the true read count ignoring filters, but only available after the instance has been fully iterated once (raisesRuntimeErrorotherwise).ReadsInRAM— aReadssubclass that eagerly consumesinitialReadsinto a list, addinglen()/indexing/assignment (__len__,__getitem__,__setitem__) on top ofReads' lazy iteration semantics.addFASTACommandLineOptions(parser)/parseFASTACommandLineOptions(args)— the shared argparse wiring used by ~30 scripts inbin. Adds a positional input-file argument (or--fastaFile, kept for backward compatibility; giving both is an error) plus--readClassand a mutually exclusive--fasta/--fastq/--fasta-ssgroup. If none of those three format flags is given,_sniffFastaOrFastq(private) peeks at the input's first non-whitespace byte (looking through gzip/bz2 compression if present) to auto-detect FASTA (>) vs FASTQ (@) without consuming the stream — raisingSystemExitwith a clear message if the format can't be determined this way (PDB FASTA,--fasta-ss, is never auto-detected since it's syntactically identical to plain FASTA). Returns the appropriateFastaReads/FastqReads/SSFastaReadsinstance.getNoCoverageCounts(reads, noCoverageChars)— counts, per read id, how many sequence characters are in the given no-coverage character set.simpleReadSplitter(length, leftPrefix="", leftSuffix="", rightPrefix="", rightSuffix="")— returns a function that splits reads longer thanlengthinto left/right fragments (with distinguishing id prefixes/suffixes), or passes shorter reads through unchanged; used byfasta-split-by-length.py.
Used by: aa-to-dna.py, combine-sequences.py, compare-aa-sequences.py, compare-sequences.py, create-newick-relabeling-output.py, dna-to-aa.py, extract-ORFs.py, fasta-base-indices.py, fasta-count-chars.py, fasta-count.py, fasta-coverage.py, fasta-diff.py, fasta-find.py, fasta-identity-table.py, fasta-ids.py, fasta-join.py, fasta-lengths.py, fasta-match-offsets.py, fasta-sort.py, fasta-split-by-id.py, fasta-split-by-length.py, fasta-split.py, fasta-subset.py, fasta-to-phylip.py, fasta-translate.py, fasta-variable-sites.py, fastq-set-quality.py, filter-fasta.py, filter-hits-to-fasta.py, filter-sam.py, format-fasta.py, genome-protein-summary.py, msa-find-and-extract.py, msa-merge-gapped-region.py, plot-windowed-identity.py, randomize-fasta.py, reverse-complement.py, sam-coverage.py, sam-to-fasta-alignment.py, split-fasta-by-adaptors.py, summarize-fasta-bases.py, trim-primers.py, trim-reads.py, window-split-alignment.py.
src/dark/fasta.py¶
FASTA-specific reading, writing, de-duplication, and a disk-backed random-access index for FASTA sequences. Reading is done via the prseq package's FastaReader/read_fasta.
fastaToList(fastaFilename)— Read an entire FASTA file (by filename) into alist[Read], viaprseq.read_fasta.dedupFasta(reads)— Given aReadsinstance, yield only the firstReadseen for each distinct sequence (duplicates are detected by an MD5 hash of the sequence string, not the id).dePrefixAndSuffixFasta(sequences)— Given an iterable ofReads, yield only those that are not a prefix or suffix (nor a duplicate) of some other, longer sequence in the input. Works by hashing every prefix and suffix of each sequence (longest sequences first) and skipping any sequence whose full hash has already been seen.fastaSubtract(fastaFiles)— Given a list of FASTA file names/handles, remove (by id) from the first file's reads any id that also appears in any of the subsequent files. Returns an iterator of the remainingReads from the first file. RaisesIndexErrorif given an empty list; asserts (don't just silently ignore) that a duplicate id has the same sequence in both files.FastaReads— AReadssubclass (seedark/reads.py) that reads one or more FASTA files/handles (or standard input, iffiles=None) and yieldsReadinstances (of a configurablereadClass, defaultDNARead) via itsiter()method.upperCase=Trueuppercases each sequence as it's read.combineReads(filename, sequences, readClass, upperCase, idPrefix)— Build a singleReads/FastaReadsby combining the contents of an (optional) FASTA file with a list of raw sequence strings given on the command line (each of which may embed its own id, by having the id as the text before the final space in the string; otherwise an id is auto-generated usingidPrefixplus a counter). This is the standard waybinscripts that accept both a reference file and ad hoc sequences build their input.SqliteIndex— Build/use an sqlite3 database that maps FASTA sequence ids to(file name, byte offset)so that an arbitrarily large FASTA collection can be queried by id without loading it all into memory. (Unrelated to the differently-purposeddark.civ.proteins.SqliteIndex, which indexes genome/protein metadata rather than raw FASTA offsets.)addFile(filename)— Scan (and index) all the sequences in a FASTA file (optionally BGZF-compressed,.gz/.bgz; plain.bz2is explicitly rejected since offset-seeking into a bzip2 stream isn't supported), recording each id's file number and byte offset. RaisesValueErroron a duplicate file name or a duplicate sequence id (either within the file or across previously-added files).__getitem__(id_)— Look up a sequence by id and read just that one sequence's bytes from disk by seeking to its recorded offset, returning it as aReadinstance of the index'sreadClass. RaisesKeyErrorfor an unknown id.close()— Close the underlying sqlite3 connection.
Used by: alignments-per-read.py, alignment-panel-civ.py, check-fasta-json-blast-consistency.py, combine-sequences.py, compare-aa-sequences.py, fasta-subset.py, filter-fasta-by-taxonomy.py, fasta-diff.py, get-reads.py, local-align.py, make-consensus.py, make-fasta-database.py, filter-fasta-by-complexity.py, filter-reads-alignments.py, position-summary.py, filter-hits-to-fasta.py, randomize-fasta.py, noninteractive-alignment-panel.py, trim-primers.py, split-fasta-by-adaptors.py, sam-coverage-depth.py.
src/dark/fastq.py¶
A thin FASTQ-reading counterpart to dark.fasta.
FastqReads— aReadssubclass. Constructor takesfiles(a single filename/handle, a list/tuple of them, orNonefor standard input) and areadClass(defaultDNARead) to instantiate for each record.iter()reads each file withprseq.FastqReaderand yieldsreadClass(id, sequence, quality)for every record.
Used by: alignment-panel-civ.py, filter-reads-alignments.py, noninteractive-alignment-panel.py.
src/dark/fasta_ss.py¶
Reader for "PDB FASTA" files: pairs of FASTA records where the first holds an amino-acid sequence and the second (immediately following) holds its secondary-structure string of the same length.
SSFastaReads— aReadssubclass. Constructor takes_files(filename/handle, list/tuple of them, orNonefor stdin — thisNone-handling was recently added so it matchesFastaReads/FastqReads), areadClass(defaultSSAARead, must accept(id, sequence, structure)), andupperCase(uppercase both sequence and structure if set).iter()reads each file in sequence/structure record pairs viaprseq.FastaReader, raisingValueErrorif a file has an odd number of records or if a sequence/structure pair's lengths differ, yieldingreadClass(id, sequence, structure)for each valid pair.
Not used directly by any script in bin.
src/dark/idutils.py¶
A small generator-based utility for producing sequences of ids (e.g., id1, id2, ... or alphabetic/odometer-style ids), used to synthesize test or placeholder sequence ids.
ids(start, end=None, prefix=None, sep=" ", zeroes=False, maxResults=None)— The main entry point: a generator that yields a run of ids fromstartup to (and including)end.start/endcan each be a plain integer-like string (e.g.,"3") or an alphanumeric "odometer" string (e.g.,"aa","Az9"), in which case each character position is incremented independently like an odometer wheel (digits cycle 0-9, lowercase a-z, uppercase A-Z; non-alphanumeric characters are held constant). Ifendis omitted, counting continues indefinitely fromstartunlessmaxResultscaps it, or — if neitherendnormaxResultsis given — it is limited to incrementing only up to the next power of ten (to avoid an accidentally-infinite generator from plainids("3")). Ifprefixis not given and bothstart/endare given, their longest common string prefix is factored out automatically and treated as a fixed prefix;zeroes=Truezero-pads purely-numeric ids to the width ofend._commonPrefix,_doInts,_doStringsare private helpers implementing the above; not part of the public API.
Used by: ids.py.
src/dark/sequence.py¶
Sequence-string utilities for locating a primer/adaptor within a sequence, forward and reverse-complemented.
findPrimer(primer, seq)— Return alistof all (zero-based, non-overlapping) offsets inseqwhereprimeroccurs (case-insensitive).findPrimerBidi(primer, seq)— Return a(forward, reverse)tuple of two offset lists: whereprimermatchesseqdirectly, and where the reverse complement ofprimermatchesseq(i.e., where the original primer would match ifseqwere reverse complemented).findPrimerBidiLimits(primer, seq)— Given the forward/reverse offsets fromfindPrimerBidi, compute a(start, end)pair suitable for trimming all leading/trailing instances of the primer from a sequence, e.g.seq[start:end]:startis just past the last forward match (or0if none), andendis the first reverse match at or beyondstart(or the first reverse match / full sequence length if there's no forward match).
Used by: trim-primers.py.
src/dark/summarize.py¶
Two independent read-summarization helpers: overall per-file statistics, and a run-length-style categorization of a single sequence's characters.
summarizeReads(file_handle, file_type)— Reads an entire FASTA (file_type="fasta") or FASTQ (anything else) file viaFastaReads/FastqReadsand returns adictwithread_number,total_length,average_length,max_length,min_length,median_length(viadark.utils.median), andbase_counts(adefaultdict[str, int]of base → total count across all reads). All numeric fields are0if there are no reads.sequenceCategoryLengths(read, categories, defaultCategory=None, suppressedCategory="...", minLength=1)— Walks a read's sequence, mapping each character throughcategories(falling back todefaultCategoryfor unmapped characters), and returns alistof(category, runLength)tuples for each maximal contiguous run of the same category. Runs shorter thanminLengthare not emitted individually; they're accumulated and emitted instead as a single(suppressedCategory, totalSuppressedLength)entry wherever they occur. RaisesValueErrorifminLength < 1.
Used by: summarize-reads.py, summarize-fasta-bases.py.
src/dark/simplify.py¶
A single string-simplification helper for sequence titles/descriptions.
simplifyTitle(title, target)— Given a title string and atargetsubstring, return the prefix oftitle(as whole, space-separated words) up to and including the first word that containstarget(case-insensitive); the matched word itself is truncated right after the end of thetargetmatch. If no word containstarget, the whole title is returned unchanged. Intended for shortening verbose GenBank/NCBI-style sequence descriptions (e.g., trimming "Bovine polyomavirus DNA, complete genome" down to "Bovine polyomavirus" whentarget="virus").
Not used directly by any script in bin.
2. DNA & amino-acid biology¶
Ambiguity-code handling, consensus-base calling, codon/amino-acid lookups and properties, ORF-marker plotting, and SAM-CIGAR/BTOP alignment-encoding conversions.
src/dark/dna.py¶
Core DNA-sequence comparison, consensus, and ambiguity-code logic. Defines the IUPAC ambiguous-nucleotide table and builds most of the dark-matter DNA consensus/diff machinery on top of it.
AMBIGUOUS—dict[str, set[str]]mapping each of the 15 IUPAC nucleotide codes (A,C,G,T, and the ambiguity codesM,R,W,S,K,Y,V,H,D,B,N) to the set of unambiguous bases it can represent. This is the central table nearly everything else in this module (and much of the rest of the DNA-handling code in the package) is built on.BASES_TO_AMBIGUOUS— The reverse mapping: a sorted, concatenated base-set string (e.g.,"ACGT") to its single ambiguity code ("N").aaCompatible(s, aa)— Could the (possibly ambiguous) 3-character codon stringstranslate to amino acidaa? Checks both an exactREVERSE_CODONShit and, failing that, whethers's ambiguity is consistent with any ofaa's known codons.compatibleAAs(s)— Return thesetof amino acids a (possibly ambiguous) codonscould translate to, by expanding all the concrete codons implied bys's ambiguity codes and mapping each throughREVERSE_CODONS(unknown/stop codons map to"*").translatable(seq)— Is this nucleotide string cleanly translatable to protein (length a multiple of 3, no gaps/invalid bases, and no resulting"X"amino acid)? ReturnsFalse(never raises) for anything that fails.compareDNAReads(read1, read2, matchAmbiguous=True, gapChars="-", noCoverageChars=None, offsets=None)— The core pairwise DNA comparison function. Walks both sequences position by position (viazip_longest, so sequences of differing length are supported) and classifies each site as an identical match, an ambiguous match (ifmatchAmbiguous), a gap-involving mismatch, a "no coverage" site (characters innoCoverageChars, e.g. consensus sequences with uncovered regions), or a plain non-gap mismatch.offsets, if given, restricts comparison to just that set of positions. Returns a nesteddict(see the function's docstring for the exact key layout) recording counts and offset-lists for all of the above, plus adifferenceOffsetsdict keyed by"<char1><char2>"pairs. This return value is the input tomatchToString.matchToString(dnaMatch, read1, read2, matchAmbiguous=True, indent="", offsets=None, includeGapLocations=True, includeNoCoverageLocations=True, includeAmbiguousMatches=False, includeNonGapMismatches=False, includeDifferenceCounts=False, includeDifferenceLocations=False, includeNLocations=False)— Render thedictfromcompareDNAReadsas a human-readable multi-line report string (exact/ambiguous match counts and percentages, mismatch breakdown by category, per-read gap/no-coverage/ambiguous/N counts and optional 1-based locations, and optional difference-count/location tables). The variousinclude*flags control how much optional detail is appended. (This function's exact output format was recently revised — seetest/test_dna.py::TestMatchToStringfor the current, authoritative expected-output examples.)findKozakConsensus(read)— Scan aDNAReadfor Kozak consensus sequences (the pattern(gcc)gccRccATGGaround a start codon) and yield aDNAKozakRead(fromdark.reads) for eachATGGstart-codon context found, each annotated with a 0-100% "Kozak quality" score based on how many of the five variable positions match the preferred consensus base.FloatBaseCounts— Accumulate fractional base counts across a column of (possibly ambiguous) nucleotide codes, e.g. for building a consensus at one alignment position.- Constructor takes an iterable of codes; each ambiguous code's count is split fractionally across the bases it could represent (unknown codes count as fully-ambiguous
ACGTifunknownAreAmbiguous=True, else as"-"). mostFrequent()/highestFrequency()— the set of base(s) tied for the highest count, and that count's fraction of the total.homogeneous(level)— is the most frequent base's fraction>= level?variable(confirm=True)— is there more than one base present? Withconfirm=True, does a more careful check of whether the ambiguous codes could still all be consistent with a single real base (not just "multiple different symbols were seen").__len__,__str__— number of distinct bases seen; a human-readable"A:3 C:1 (0.750)"-style summary.sequenceToRegex(sequence, wildcards="-?")— Convert a (possibly ambiguous) DNA sequence into a regular expression string, expanding each ambiguous code to a[...]character class; characters inwildcardsthat aren't otherwise ambiguity codes become[ACGT]. RaisesKeyErroron a genuinely unknown character.leastAmbiguous(bases)— Given an iterable of (unambiguous) base characters, return the single IUPAC code that exactly represents that set (viaBASES_TO_AMBIGUOUS). RaisesKeyErrorfor an unknown base.leastAmbiguousFromCounts(bases, threshold)— Given adict/Counterof base → frequency count and a homogeneitythreshold(0-1), find the smallest set of (highest-count-first) bases whose cumulative frequency reachesthreshold, including any bases tied with the last one included, then return the ambiguity code for that set vialeastAmbiguous. This is the core of ambiguous-consensus-base calling. RaisesValueErrorfor a negative count or threshold.Bases— A mutable accumulator of(base, quality)observations at one genome site, used when building a consensus from aligned reads.append(base, quality)— record one observation (bases of"N"are ignored); returnsselffor chaining.consensus(threshold, minCoverage, lowCoverage, noCoverage)— call the consensus base for this site:noCoverageif nothing was observed,lowCoverageif fewer thanminCoverageobservations were made, otherwise the ambiguous/unambiguous code fromleastAmbiguousFromCountsat the giventhreshold.- Supports
+/+=(merging twoBasesinstances),__len__(observation count),__getitem__(count for one base), and equality.
Used by: compare-sequences.py, extract-ORFs.py, fasta-coverage.py, fasta-identity-table.py, fasta-count-chars.py.
src/dark/aa.py¶
The amino-acid analogue of dna.py: amino acid lookup/metadata and
pairwise amino-acid sequence comparison, built on the data tables in
aaVars.py.
AminoAcid— A plain data holder for one amino acid'sname,abbrev3,abbrev1,codons(tuple of its codons),properties(bitmask, seeaaVars.py),propertyDetails(per-property scaled float values), andpropertyClusters(per-property integer cluster numbers).propertySet()— expand thepropertiesbitmask into thesetof individual property flag values it contains.find(s)— Look up amino acid(s) by a flexible specifiers: a full name (case-insensitively, with multi-word names handled specially), a 3- or 1-letter abbreviation, or a DNA/RNA codon (3 letters;Us are treated asTs). Falls back to substring-matching against full names if nothing else matches. Yields zero, one, or (for an ambiguous partial name match) moreAminoAcidinstances.propertiesForSequence(sequence, propertyNames, missingAAValue=-1.1)/clustersForSequence(sequence, propertyNames, missingAAValue=0)— For anAARead, extract, for each named property, alistof that property's (scaled float, or cluster int) value at every sequence position, substitutingmissingAAValuefor unrecognized residues (e.g.X). Both delegate to the private_propertiesOrClustersForSequence, which validates that every requested property name is known (raisingValueErrorif not).matchToString(aaMatch, read1, read2, indent="", offsets=None)— The amino-acid counterpart ofdark.dna.matchToString: render thedictreturned bycompareAaReadsas a human-readable report (overall/non-gap match and mismatch counts and percentages, per-read gap counts and 1-based locations, extra-residue counts).compareAaReads(read1, read2, gapChars="-", offsets=None)— The amino-acid counterpart ofdark.dna.compareDNAReads: a simpler site-by-site comparison (no ambiguity handling, since amino acid ambiguity codes aren't modeled here) producing match/mismatch/gap counts and per-read gap offsets, in thedictshape consumed by this module'smatchToString.
Used by: aa-compare.py, aa-info.py, compare-aa-sequences.py.
src/dark/aaVars.py¶
Pure data: the amino acid reference tables that aa.py (and, indirectly, much of the rest of the package) is built on. Essentially no logic beyond a few reverse-mapping dict/tuple comprehensions at import time.
NAMES— 1-letter abbreviation → full amino acid name (e.g."A"→"Alanine").AA_LETTERSis the sorted list of its keys.NAMES_TO_ABBREV1is its reverse.ABBREV3— 1-letter → 3-letter abbreviation (e.g."A"→"Ala").ABBREV3_TO_ABBREV1is its reverse.HYDROPHOBIC,HYDROPHILIC,AROMATIC,SULPHUR,ALIPHATIC,HYDROXYLIC,TINY,SMALL,ACIDIC,BASIC_POSITIVE,NEGATIVE,POLAR,NONE— individual bit-flag constants (powers of two) for amino acid categorical properties.ALL_PROPERTIESis a tuple of all of them;PROPERTY_NAMESmaps each flag to a human-readable name.PROPERTIES— 1-letter amino acid code → bitwise-OR of the categorical property flags above that apply to it (e.g. alanine isHYDROPHOBIC | SMALL | TINY).CODONS— 1-letter amino acid code → tuple of the (DNA) codons that encode it (per the standard genetic code).REVERSE_CODONSis the reverse mapping (codon → amino acid).START_CODONis"ATG";STOP_CODONSis the 3-tuple of stop codons.PROPERTY_DETAILS— 1-letter code →dictof named physicochemical property → a scaled (-1 to 1) float value (aliphaticity, aromaticity, composition, hydrogenation, hydropathy, hydroxythiolation, iep, polar requirement, polarity, volume), drawn from several cited literature sources (see in-file comments for exact references).PROPERTY_DETAILS_RAWholds the same properties as their original, unscaled values.PROPERTY_CLUSTERS— 1-letter code →dictof the same named properties, but each mapped to a small integer cluster number (a coarser, discretized grouping of the raw/scaled values) rather than a continuous value.
Used by: aa-compare.py, aa-to-dna.py, aa-info.py, summarize-fasta-bases.py.
src/dark/codonDistance.py¶
Small helper module for comparing codons: how many nucleotide positions differ between two codons, and (for sets of codons encoding two amino acids) the distribution of those distances, optionally broken down into transitions vs. transversions.
findDistance(co1, co2)— Returns theintnumber of positions (0–3) at which two length-3 codon strings differ (simple positional Hamming distance, no alignment).codonInformation(codons1, codons2, countTransitions=False)— Given two lists of codons (e.g., all codons for amino acid A and all codons for amino acid B), computesfindDistancefor every pair and groups the results. Returns adictkeyed byintdistance (0–3), whose values are lists of(codon1, codon2)tuples, or(codon1, codon2, transitionCount)3-tuples ifcountTransitions=True(a "transition" is a purine↔purine or pyrimidine↔pyrimidine substitution, i.e. one of A↔G or C↔T, per the module-levelTRANSITIONSset).TRANSITIONS— module constant: asetof(from, to)nucleotide pairs considered transitions.
Used by: aa-compare.py.
src/dark/orfs.py¶
Matplotlib plotting helpers that overlay start/stop codon markers onto
a sequence plot, split by reading frame. This is visualization support
code (not an ORF-finder in the sense of extracting ORF sequences — see
dark.reads for that).
findCodons(seq, codons)— Generator over aBio.Seq.Seq(or similar) scanning in consecutive, non-overlapping triplets from offset 0, yielding theintstart offset of each triplet found in the givencodonsset. Note this only considers one fixed reading frame ofseqitself; callers sliceseq[frame:]first to scan the other two frames.addORFs(fig, seq, minX, maxX, offsetAdjuster)— Plots start (green) and stop (red) codon markers for all 3 forward reading frames ofseqonto a matplotlib axes/figurefig, usingoffsetAdjuster(typically adark.intervals.OffsetAdjuster.adjustOffset-like callable) to map sequence offsets to plot X coordinates. Sets axis limits/labels/title as a side effect.addReversedORFs(fig, seq, minX, maxX, offsetAdjuster)— Same asaddORFsbut for a reverse-complemented sequence, mirroring offsets viamaxX - offsetAdjuster(offset).START_CODONS/STOP_CODONS— module constants:{"ATG"}and{"TAA", "TAG", "TGA"}respectively.
Used by: Not used directly by any script in bin (it's dark.graphics that is the typical caller of these; no bin script imports dark.orfs directly).
src/dark/btop.py¶
Parsing and conversion for BLAST/DIAMOND BTOP strings (a compact alignment-difference encoding; format documented at https://www.ncbi.nlm.nih.gov/books/NBK279682/). The main practical use is turning a BTOP string into a SAM CIGAR string.
parseBtop(btopString)— Generator that tokenizes a BTOP string into a stream of items: a bareintfor a run of N identical/matching positions, or a(queryLetter, subjectLetter)2-tuple for a single mismatch or gap ("-"denotes a gap in query or subject). RaisesValueErrorfor malformed BTOP (e.g., a letter with no partner, two consecutive gaps, two identical consecutive letters, dangling trailing letter).countGaps(btopString)— Parses the string (viaparseBtop) and returns a(queryGaps, subjectGaps)2-tuple ofintcounts of"-"occurrences on each side.btop2cigar(btopString, concise=False, aa=False)— Generator yielding successive CIGAR-string pieces (e.g."12M") that together form a full CIGAR string (join with"".join(...)). Matches/mismatches become=/X(or both collapsed toMifconcise=True); a query gap ("-"on the query side) becomes a reference deletion (D); a subject gap becomes an insertion (I). Ifaa=True, the BTOP numbers are interpreted as amino-acid counts and multiplied by 3 to produce nucleotide-length CIGAR runs (used for DIAMOND protein-vs-DNA alignments); in that mode you cannot distinguish an exact nucleotide match from a synonymous codon difference, soconciseandaacannot both beTrue(raisesValueErrorif so).
Used by: Not used directly by any script in bin (consumed via dark.diamond.sam, which calls btop2cigar internally).
src/dark/cigar.py¶
Building and interrogating SAM CIGAR strings/operations, plus a couple
of helpers for working with pysam's get_aligned_pairs-style
(query-offset, reference-offset) pair lists. Imports
dark.sam.CONSUMES_REFERENCE, so this module sits "above" dark.sam
in the dependency order despite the name suggesting it's lower-level.
dna2cigar(s1, s2, concise=False)— Builds a CIGARstrfrom two already-aligned, equal-length DNA strings (no gaps handled — this is purely match/mismatch, so it only ever emits=/Xruns, or a singleMrun ifconcise=True). RaisesValueErrorif the sequences differ in length or are empty.makeCigar(reference, query, noEdgeInsertions=True)— Builds a CIGARstrfrom a pair of aligned strings that may contain'-'gap characters and leading/trailing space padding (space means "no base here for either side", used to represent one sequence extending beyond the other). ProducesM/I/Druns; ifnoEdgeInsertions=True(the default), leading/trailingIruns are converted into soft-clips (S) instead, which is the convention SAM expects for a query that overhangs the reference. RaisesValueErrorif either input is empty.cigarTuplesToOperations(tuples, includeHardClip=True)— Generator that expands a pysamcigartuples-style list of(operation, count)pairs into a flat per-base sequence of operation codes (one yielded value per base), optionally eliding hard-clip (CHARD_CLIP) operations.softClippedOffset(offset, pairs, cigarOperations)— Given the index (offset) of a soft-clipped base within a pysamget_aligned_pairs()result (pairs) and the corresponding flat per-basecigarOperationslist (e.g., fromcigarTuplesToOperations), works out and returns theintreference coordinate that soft-clipped base would align to, by looking at the nearest non-hard-clipped neighbour (preferring looking backwards, then forwards). Usesasserts to validate its preconditions and raisesValueErrorif no usable neighbour is found.insertionOffset(offset, pairs, cigarOperations)— The insertion analogue ofsoftClippedOffset: for a base that's part of an insertion (CIGARI), finds the reference offset of the base immediately after the insertion. Returns a(bool, int)tuple — theboolsays whether the answer was found by looking backward (and so should be read as "insertion occurs right after this reference position") vs. forward. RaisesValueErrorif no reference-consuming neighbour is found.- Module constants
CINS_STR,CDEL_STR,CMATCH_STR,CEQUAL_STR,CDIFF_STR,CHARD_CLIP_STR— the single-character SAM CIGAR operation codes (I,D,M,=,X,H) as strings, for use when building CIGAR text (as opposed to pysam's integer operation codes).
Used by: Not used directly by any script in bin (used internally by dark.sam, dark.diamond.sam, dark.btop, and dark.local_align).
src/dark/mutations.py¶
Older, fairly ad hoc code for visualizing per-base mismatches of
BLAST-matched reads against a subject, and for studying APOBEC-style
mutation signatures (C→T / G→A hypermutation patterns with
trinucleotide context) in those mismatches. Several functions here
have hard-coded assumptions (e.g., about title string formats) that
make them specific to older BLAST-based workflows; this module
predates the DIAMOND/dark.diamond code path.
basePlotter(blastHits, title)— For a given subjecttitle, builds a textual "dot alignment" display: one line for the subject sequence, followed by one line per matching read where matching bases are replaced by.and mismatches show the actual (read) base. RequiresblastHits.plotParams/.titles[title]['plotInfo']to already be populated (i.e.computePlotInfomust have been run on thedark.blast.BlastHits-like object first — asserts this). Returns alist[str], each TAB-containing.getAPOBECFrequencies(dotAlignment, orig, new, pattern)— Scans abasePlotter-style dot alignment for positions where the subject had baseorigand a read hadnewthere, and tallies those occurrences by the surrounding trinucleotide context, restricted to one of two fixed context sets (patternmust be'cPattern'or'tPattern', i.e. only contexts withCorTin the middle, matching APOBEC3 substrate motifs). Returns adict(really aCounter-likedefaultdict(int)) mapping trinucleotide context → count.getCompleteFreqs(blastHits)— DrivesbasePlotter+getAPOBECFrequenciesacross every title inblastHitsand all 6 possible single-base substitution types (C>A,C>G,C>T,T>A,T>C,T>G), returning a nesteddict:{title: {substitutionType: {context: count}, "numberOfReads": int, "bitScoreMax": float}}.makeFrequencyGraph(allFreqs, title, substitution, pattern, color='blue', createFigure=True, showFigure=True, readsAx=False)— Draws (via matplotlib) a bar chart of one substitution type's context frequencies (normalized by read count) for one title, either as a standalone figure or into a supplied subplotreadsAx. Returns thefloatmax bar height used, for axis-scaling by callers.makeFrequencyPanel(allFreqs, patientName)— Builds a grid ofmakeFrequencyGraphsubplots (one row per title, one column per substitution type) into a single matplotlib figure, normalizing all Y axes to a common scale, and returnsallFreqsunchanged (the useful side effect is the displayed figure).mutateString(original, n, replacements='acgt')— Returns a newstrwith exactlynrandomly-chosen positions oforiginalreplaced by a randomly-chosen different character fromreplacements. RaisesValueErroriforiginalis empty,nexceedslen(original),replacementshas duplicate characters, orreplacementshas length 1 and already appears inoriginal(making replacement impossible at some position).
Used by: Not used directly by any script in bin.
3. Sequence alignment & comparison¶
Dependency-free local alignment, wrappers around external aligners, and the score/HSP/windowed-identity machinery used to describe and compare alignments.
src/dark/local_align.py¶
A from-scratch Smith-Waterman local-alignment implementation for two
sequences (used where you want a dependency-free local alignment
instead of shelling out to an external aligner like
edlib/MAFFT/needle — see dark.aligners for those).
LocalAlignment(seq1, seq2, match=1, mismatch=-1, gap=-1, gapExtend=-1, gapExtendDecay=0.0)— Constructor takes twodark.reads.Read-like objects (uses.sequenceand.id) and scoring parameters (matchmust make sense as positive,mismatch/gapmust be negative,gapExtendmust not be positive — raisesValueErrorotherwise, as does passing either sequence as empty).gapExtendDecaylets the per-position gap-extension penalty shrink the longer a gap run gets (a float decay added togapExtendfor each extra position in the run, capped so the effective extension penalty never becomes positive).createAlignment(resultFormat=dict)— The main entry point: runs the DP fill-and-traceback and returns eitherNone(no alignment found — the whole table collapsed to zero) or adictwith keyscigar(a CIGAR-like string using=/X/I/D, not standard SAM CIGAR — note it uses=/Xalways, notM),sequence1Start/sequence1End,sequence2Start/sequence2End(1-based-looking indices from the internal DP table), andtext(a 3-line[seq1Line, matchLine, seq2Line]representation). IfresultFormat=str, returns a single formatted multi-line string instead. Internal/private helpers (_initialise,_fillAndTraceback,_cigarString,_formatAlignment,_alignmentToStr) implement the DP table, traceback, and output formatting.
Used by: local-align.py.
src/dark/aligners.py¶
Wraps three different ways of aligning sequences — the external tools
MAFFT and EMBOSS needle/stretcher (via dark.process.Executor,
i.e. shelling out), and the edlib Python binding for a fast,
dependency-light pairwise alignment — behind a common interface, plus
post-processing helpers to clean up edlib's output. This is the
module that bin/compare-sequences.py, bin/compare-aa-sequences.py,
bin/fasta-identity-table.py, bin/combine-sequences.py, and
bin/fasta-diff.py build on for their "run an aligner" option.
align(reads, aligner, alignerOptions=None, verbose=False, threads=<cpu_count>)— The main dispatch entry point: givenalignerof"mafft","needle", or"edlib", calls the corresponding function below with default options substituted whenalignerOptions is None, and returns the resultingReads.mafft(reads, verbose=False, options=None, threads=None, executor=None, dryRun=False)— Writesreadsto a temp FASTA file, shells out tomafft, and reads the aligned result back as aReadsinstance. IfdryRun=True, no command is actually executed and an emptyReadsis returned.needle(reads, verbose=False, options=None, executor=None, dryRun=False)— Aligns exactly the first two ofreadsusing EMBOSSneedle(global Needleman-Wunsch); ifneedlefails specifically because the sequences are too big, transparently falls back to running EMBOSSstretcherinstead.removeFirstUnnecessaryGaps(seq1, seq2, gapSymbol='-')— Finds the first stretch in two equal-length, already-aligned strings where one side has a gap-run immediately followed by non-gap characters that could be "slid" out to eliminate the gap run without making the alignment worse. Returns(True, newSeq1, newSeq2)or(False, "", "").removeUnnecessaryGaps(seq1, seq2, gapSymbol='-')— Repeatedly appliesremoveFirstUnnecessaryGapsuntil no more improving gap removals can be found.canonicalizeAllGaps(seq1, seq2)— Post-processes anedlib-produced alignment to merge many short gap runs into fewer, longer ones so the result looks more like what MAFFT would produce.edlibAlign(reads, gapSymbol='-', minimizeGaps=True, onlyTwoSequences=True, matchAmbiguous=True, canonicalizeGaps=True)— Aligns the first two ofreadsusingedlib's global ("NW") alignment mode, with ambiguous-nucleotide-code equalities taken into account ifmatchAmbiguous=True. RaisesValueErrorif more than two reads are passed whileonlyTwoSequences=True, or if either input sequence already containsgapSymbol.- Module constants:
MAFFT_DEFAULT_ARGS,NEEDLE_DEFAULT_ARGS,EDLIB_AMBIGUOUS.
Used by: fasta-identity-table.py, combine-sequences.py, fasta-diff.py, compare-sequences.py.
src/dark/windowedIdentity.py¶
Computes nucleotide identity between a reference sequence and a set of
other (aligned, equal-length) sequences in sliding windows along the
genome — the computational core behind
bin/plot-windowed-identity.py.
WindowedIdentity(reads: Iterable[DNARead])— Constructor just stores the (already-aligned) reads; validation is deferred to first use.getIdentity(referenceRegex, window, minWindow, jump, includeRegex='.', sort=False, strict=True, startOffset=0, stopOffset=None)— The main method. Picks exactly one read whose id matchesreferenceRegexas the reference (raisesValueErrorif zero or more than one match), picks reads matchingincludeRegexto compare against it, then slides a window of sizewindow(advancing byjump, never going belowminWindowat the end) along the reference. For each window and comparison read, computes identity viadark.dna.compareDNAReads. Returns(reference, windowStarts, identity)whereidentity[read]is the per-window identity-fraction list. RaisesValueErrorfor many invalid parameter combinations; all reads must be the same length (checked internally).addCommandLineOptions(parser)— Adds a standard set of argparse options (--reference,--include,--window,--minWindow,--jump,--sort,--startOffset,--stopOffset) mirroringgetIdentity's parameters.
Used by: plot-windowed-identity.py.
src/dark/hsp.py¶
Defines the data-holding classes for a single matched region
("high-scoring pair") between a read and a subject sequence, as
produced by alignment tools (BLAST, DIAMOND) and consumed throughout
the dark.alignments/dark.titles/dark.blast/dark.diamond code.
_Base— Private base class holding the common HSP/LSP fields:readStart,readEnd,readStartInSubject,readEndInSubject,readFrame,subjectStart,subjectEnd,subjectFrame,readMatchedSequence,subjectMatchedSequence,identicalCount,percentIdentical,positiveCount,percentPositive. Comparison is delegated toself.score.betterThan(score)— delegates to the score object's ownbetterThan.toDict()— returns a plaindictsnapshot of the fields above.HSP(score, **kwargs)—_Basesubclass for "numerically higher is better" scores (bit scores); wrapsscoreindark.score.HigherIsBetterScore.LSP(score, **kwargs)—_Basesubclass for "numerically lower is better" scores (e-values); wrapsscoreindark.score.LowerIsBetterScore.
Used by: Not used directly by any script in bin (used pervasively via dark.alignments, dark.titles, dark.blast, dark.diamond).
src/dark/score.py¶
Tiny wrapper classes that make "is this score better than that score"
comparisons polarity-agnostic, so code elsewhere (e.g. dark.hsp,
dark.titles) doesn't need to know whether higher-is-better (bit
scores) or lower-is-better (e-values) semantics apply.
HigherIsBetterScore(score: float)—</==compare numerically as normal;betterThan(score)isTrueif this instance's score is numerically greater.LowerIsBetterScore(score: float)— Same idea, inverted. Both classes only compare against instances of their own class (TypeErrorif you mix them).
Used by: Not used directly by any script in bin (used via dark.hsp.HSP/LSP).
src/dark/distance.py¶
A single general-purpose string-distance function.
levenshtein(source, target)— Computes theintLevenshtein (edit) distance between two strings, using a numpy-vectorized, two-row (not full-matrix) dynamic-programming implementation for memory efficiency.
Used by: adaptor-distances.py, split-fasta-by-adaptors.py.
4. Alignment-result containers, filtering & summarization¶
The application-agnostic layer above individual BLAST/DIAMOND HSPs: generic per-read/per-title match containers, filtering, and the shared read-filtering/editing argparse helpers.
src/dark/filter.py¶
General-purpose filtering primitives for sequence titles and
sequences, plus two pairs of argparse helper functions (mirroring
the addFASTACommandLineOptions/parseFASTACommandLineOptions
pattern in dark.reads) that let any bin/ script add a large,
standard set of "select/omit" and "edit" options for FASTA/FASTQ
reads. Used by both the BLAST and DIAMOND alignment-filtering code
(via dark.alignments) and directly by several bin/ scripts that
filter/edit raw reads.
TitleFilter(whitelist=None, blacklist=None, whitelistFile=None, blacklistFile=None, positiveRegex=None, negativeRegex=None, truncateAfter=None)— Decides whether a sequence title is acceptable.accept(title)— Returns one of three class constants:REJECT,WHITELIST_ACCEPT, orDEFAULT_ACCEPT. Checks, in order: exact whitelist, exact blacklist, a positive regex, a negative regex, and (iftruncateAfter) de-duplication by truncated title.SequenceFilter(whitelist=None, blacklist=None, whitelistFile=None, blacklistFile=None, positiveRegex=None, negativeRegex=None)— Same idea asTitleFilterbut applied to the sequence string itself.accept(sequence)— Same three-way return convention.ReadSetFilter(minNew)— Decides whether a new title's set of matching read ids is sufficiently different from previously-seen titles' read sets (e.g. to drop near-duplicate hits).accept(title, titleAlignments)—Trueif the fraction of new read ids is at leastminNew; records which titles were invalidated otherwise. RaisesAssertionErrorif the same title is passed twice.invalidates(title)— list of other titles rejected because their read sets overlapped too much withtitle's.addFASTAFilteringCommandLineOptions(parser)— Adds a large set of read-selection options:--minLength/--maxLength,--maxNFraction, title/sequence whitelist/blacklist (inline or file), regex filters,--keepSequences/--removeSequences,--head, deduplication options,--randomSubset/--trueLength,--sampleFraction,--sequenceNumbersFile.parseFASTAFilteringCommandLineOptions(args, reads)— Reads those option values and callsreads.filter(...), returning the filteredReads.addFASTAEditingCommandLineOptions(parser)— Adds options that change reads: site keep/remove ranges,--removeGaps,--truncateTitlesAfter,--removeDescriptions,--idLambda/--readLambda(eacheval'd),--reverse/--reverseComplement,--rotate,--setSite, upper/lower-case options,--setQuality.parseFASTAEditingCommandLineOptions(args, reads)— The parse half of the editing options, returning the editedReads.
Used by: fasta-identity-table.py, filter-fasta.py, genome-protein-summary.py, filter-sam.py, sam-coverage.py, sam-to-fasta-alignment.py.
src/dark/alignments.py¶
The application-agnostic core of dark matter's alignment-handling
machinery: generic containers for one subject match (Alignment) and
all of a read's matches (ReadAlignments), a generic
ReadsAlignments base class that BLAST- and DIAMOND-specific
subclasses extend, a composable filter (ReadsAlignmentsFilter) that
implements most of the actual filtering logic for matches, and two
small gap/offset-mapping helpers used when working with aligned
(gapped) sequences. Nothing here is specific to BLAST or DIAMOND — see
dark.blast.alignments.BlastReadsAlignments and
dark.diamond.alignments.DiamondReadsAlignments for the concrete
subclasses.
Alignment(subjectLength, subjectTitle)— Holds one subject sequence's length/title plus a growing list of HSPs/LSPs matched against it.addHsp(hsp)— Appends anHSPorLSP.ReadAlignments(read, alignments=None)— Alistsubclass holding all theAlignments found for one queryread.bestAlignment(readAlignments)— Returns theAlignmentwhose first HSP has the best score.ReadsAlignmentsParams(application, applicationParams=None, subjectIsNucleotides=True, scoreTitle="Score")— A plain data holder recording which application produced a set of alignments and how.ReadsAlignmentsFilter(limit=None, maxAlignmentsPerRead=None, minSequenceLen=None, maxSequenceLen=None, minStart=None, maxStop=None, oneAlignmentPerRead=False, maxHspsPerHit=None, scoreCutoff=None, percentageIdenticalCutoff=None, percentagePositiveCutoff=None, whitelist=None, blacklist=None, whitelistFile=None, blacklistFile=None, titleRegex=None, negativeTitleRegex=None, truncateTitlesAfter=None, taxonomy=None, readIdRegex=None)— Builds adark.filter.TitleFilterand/ordark.taxonomy.LineageFetcherinternally as needed.filter(readAlignments)— Applies, in a fixed order, a result-count limit, per-read alignment cap, read-id regex, title filter, sequence-length bounds, taxonomic lineage membership,oneAlignmentPerRead,maxHspsPerHit, score/percent cutoffs, and start/stop bounds. Returns the narrowedreadAlignments, orFalseif everything was filtered out.close()— Closes the internalLineageFetcher, if one was created.ReadsAlignments(reads, params, scoreClass=HigherIsBetterScore)— Base class for "a whole alignment-results file (or set of files)."getSubjectSequence(title)— Abstract; raisesNotImplementedErrorhere.hsps()— Generator yielding every HSP/LSP across every (filtered) alignment.__iter__()— Runs eachreadAlignmentsthrough every filter added viaself.filter(...).iter()— Placeholder; subclasses override to actually read alignment data.filter(**kwargs)— Constructs aReadsAlignmentsFilter(**kwargs)and chains it. Returnsself.getGappedOffsets(s, gapChar="-")— Maps each offset in the ungapped version ofsto the corresponding offset in the gapped strings.alignmentEnd(s, startOffset, length, gapChar="-")— Starting atstartOffsetin gapped sequences, scans forward untillengthnon-gap characters have been seen and returns the resulting (gapped) index.
Used by: Not used directly by any script in bin (scripts go
through the BLAST/DIAMOND-specific subclasses instead).
src/dark/titles.py¶
Groups and summarizes read alignments (HSPs) by the subject ("title")
they matched — the layer above individual HSPs and below the overall
ReadsAlignments abstraction, used for producing the per-subject
summaries and filtering seen in the BLAST/DIAMOND alignment-panel
scripts.
titleCounts(readsAlignments)— Returns adictmapping subject title →intcount of how many times that title was matched.TitleAlignment(read, hsps)— Pairs oneReadwith thelist[HSP]it matched a particular title with.TitleAlignments(subjectTitle, subjectLength)— Alistsubclass holding all theTitleAlignments for one subject title, plus subject metadata.addAlignment(alignment),reads()/readCount()/readIds(),hsps()/hspCount(),bestHsp()/worstHsp(),hasScoreBetterThan(score),medianScore().coverage()/coverageCounts()— fraction of the subject covered, or per-position coverage counts, viadark.intervals.ReadIntervals.coverageInfo()—dictkeyed by subject offset, a pileup-like list of(score, base)pairs.residueCounts(convertCaseTo='upper')—dictkeyed by subject offset, each value aCounterof residue→count.summary()/toDict()— compact and full serializations.TitlesAlignments(readsAlignments, scoreClass=None, readSetFilter=None)— Adictsubclass (title →TitleAlignments) built from aReadsAlignments-like object.filter(minMatchingReads=None, maxMatchingReads=None, minMedianScore=None, withScoreBetterThan=None, minNewReads=None, minCoverage=None, maxTitles=None, sortOn='maxScore', titleRegex=None, negativeTitleRegex=None)— mutates and returnsselfwith titles removed that fail any given criterion;minNewReadsuses a lazily-createddark.filter.ReadSetFilter;maxTitleskeeps only the best-scoring titles.sortTitles(by)— sorts titles by'length','maxScore','medianScore','readCount', or'title'.hsps(),summary(sortOn=None),tabSeparatedSummary(sortOn=None)(fixed field order — downstream code relies on it),toDict().
Used by: alignment-panel-civ.py, filter-hits-to-fasta.py, get-reads.py, noninteractive-alignment-panel.py.
src/dark/intervals.py¶
Tracks the (possibly overlapping) set of regions on a subject sequence
that are covered by matching reads, and derives coverage statistics
and plot-offset adjustments from that. Used by dark.titles (for
coverage()/coverageCounts()) and by graphics code that wants to
compress large unmatched regions when plotting (OffsetAdjuster).
ReadIntervals(targetLength: int)— Holds raw(start, end)intervals added viaadd.add(start, end),walk()(generator yielding(type, (start, end))covering the entire span with no gaps;typeisFULLorEMPTY),coverage()(fraction covered),coverageCounts()(per-base depthCounter).OffsetAdjuster(intervals=None, base=2.0)— Precomputes, from aReadIntervals' empty stretches, offset adjustments that compress those stretches logarithmically rather than linearly.adjustments(),adjustOffset(offset),adjustHSP(hsp)(mutates an HSP/LSP in place).
Used by: Not used directly by any script in bin (used via dark.titles.TitleAlignments.coverage/coverageCounts, and by dark.graphics/dark.orfs-style plotting code for OffsetAdjuster).
5. BLAST¶
Dark matter's own compact JSON encoding of BLAST results, and the BLAST-specific subclass of the generic alignments machinery.
src/dark/blast/records.py¶
A tiny debugging helper for dumping a raw BioPython BLAST record to stdout.
printBlastRecord(record)— Prints every top-level attribute of aBio.Blast.Record.Blastinstance (exceptalignments/descriptions/reference), then per-alignment description/alignment fields, then each HSP viadark.blast.hsp.printHSP.
Used by: Not used directly by any script in bin.
src/dark/blast/hacks.py¶
A single one-off, admittedly hacky function kept around from a specific past debugging session; not part of the supported API.
printBlastRecordForDerek(record)— Skips records with no alignments or whose best e-value is worse than1e-120; otherwise prints query name, each alignment's e-value, and (for the first HSP only) sense/half-genome info using a hard-coded coordinate. The docstring says it's called bybin/print-blast-xml-for-derek.py, but no such script currently exists inbin.
Used by: Not used directly by any script in bin (the script named in its own docstring does not currently exist).
src/dark/blast/params.py¶
A small consistency check for comparing the global BLAST parameters recorded in one results file against those from another, used when concatenating/combining multiple BLAST runs.
VARIABLE_PARAMS— parameter names expected to legitimately differ between files (and so excluded from comparison).checkCompatibleParams(initialParams, laterParams)— ReturnsNoneif compatible, else a human-readable summary of every difference found.
Used by: Not used directly by any script in bin (used internally by dark.blast.alignments.BlastReadsAlignments).
src/dark/blast/score.py¶
Pure-math conversions between BLAST bit scores and e-values, following NCBI's "BLAST Scoring Parameters" reference.
bitScoreToEValue(bitScore, dbSize, dbSequenceCount, queryLength, lengthAdjustment)— Converts a bit score to an e-value via an "effective" database size.eValueToBitScore(eValue, dbSize, dbSequenceCount, queryLength, lengthAdjustment)— The inverse.
Used by: bit-score-to-e-value.py, e-value-to-bit-score.py.
src/dark/blast/hsp.py¶
A debug-print helper plus the function that converts a raw (dict-form) BLAST HSP into the normalized, 0-based, Python-slicing-friendly offsets used throughout the rest of dark matter.
printHSP(hsp, indent="")— Prints a fixed set of HSP dict fields, one per line.normalizeHSP(hsp, readLen, blastApplication)— Takes a raw BLAST HSP (1-based offsets, aframe2-tuple) and returns adictwith 0-basedreadStart/readEnd/readStartInSubject/readEndInSubject/subjectStart/subjectEnd, all in the subject's coordinate system. Handles reverse-strand offset flipping,blastxnucleotide→protein coordinate conversion, and read-overhang computation. RaisesAssertionError(after dumping diagnostic info) on internal consistency failures.
Used by: Not used directly by any script in bin (used internally by dark.blast.conversion.JSONRecordsReader).
src/dark/blast/alignments.py¶
The BLAST-specific concrete subclass of
dark.alignments.ReadsAlignments: reads one or more BLAST result
files (dark matter's converted JSON) and makes their HSPs available as
filterable per-read alignment collections, with support for
e-value-based scoring quirks (zero e-values) and for looking up
subject sequences from several possible sources.
ZERO_EVALUE_UPPER_RANDOM_INCREMENT = 150— width of the random range used when spreading out zero-e-value HSPs for plotting.BlastReadsAlignments(reads, blastFilenames, databaseFilename=None, databaseDirectory=None, sqliteDatabaseFilename=None, scoreClass=HigherIsBetterScore, sortBlastFilenames=True, randomizeZeroEValues=True)—readsmust be the exactReadsgiven to BLAST, in output order.blastFilenamesmust be.json/.json.bz2(dark matter's format, produced bybin/convert-blast-xml-to-json.py) — raw XML is not accepted here.databaseFilename/sqliteDatabaseFilenameare mutually-exclusive subject-lookup sources; if neither given, falls back toblastdbcmdviadark.ncbidb.getSequence.iter()— yields oneReadAlignmentsper read.getSubjectSequence(title)— looks up and caches the subject sequence, returning anAAReadorDNAReaddepending on the BLAST application.adjustHspsForPlotting(titleAlignments)/adjustPlot(readsAx)— e-value →-log10conversion and zero-e-value placeholder handling, for plotting.
Used by: alignments-per-read.py, alignment-panel-civ.py, check-fasta-json-blast-consistency.py, filter-hits-to-fasta.py, get-reads.py, filter-reads-alignments.py, noninteractive-alignment-panel.py.
src/dark/blast/conversion.py¶
The two-way conversion between raw BLAST XML output and dark matter's own compact JSON encoding of BLAST results (one JSON object per line: a parameters line, then one record-dict line per query).
XMLRecordsReader(filename)— Reads BLAST XML (-outfmt 5) via BioPython'sBio.Blast.NCBIXML.parse.records()— generator yielding raw BioPython records; setsself.paramsfrom the first record.saveAsJSON(fp)— writes the JSON form: a params line, then one line per record (keeping only the fields dark matter actually uses).JSONRecordsReader(filename, scoreClass=HigherIsBetterScore)— Reads dark matter's JSON-encoded BLAST output;.bz2files are transparently decompressed. RaisesValueErrorif the file is empty, malformed, or in an old format missing"application".readAlignments(reads)— generator yielding oneReadAlignmentsper line, matched in lockstep againstreads; callsdark.blast.hsp.normalizeHSPfor each HSP. RaisesValueErroron a query/read id mismatch or a premature end of the reads iterator.
Used by: convert-blast-xml-to-json.py (XMLRecordsReader), read-blast-json.py (JSONRecordsReader).
6. DIAMOND¶
The DIAMOND-specific counterparts of the BLAST machinery above: tabular
output parsing, JSON conversion, DIAMOND-to-SAM conversion, and a small
wrapper for actually running the diamond executable.
src/dark/diamond/run.py¶
A convenience wrapper for actually invoking the external diamond
command-line tool from Python — building a temporary DIAMOND database
from a set of subject sequences, searching a set of query reads
against it, and parsing the tabular results back into Python
dicts. Mainly useful for tests and small ad hoc scripts; production
pipelines more commonly run DIAMOND externally and feed its output
through dark.diamond.conversion/dark.diamond.sam instead.
diamondInstalled()—Trueifdiamond helpruns successfully.DiamondExecutor(dryRun=False)— manages a temporary working directory holding subject/query FASTA and DIAMOND's tabular output; usable as a context manager.addSubject(subject),search(reads, fieldNames=None)(rebuilds the database if dirty, runsdiamond blastx --outfmt 6, yields onedictper result row),cleanup().SUBJECTS_FILENAME,QUERIES_FILENAME,OUTPUT_FILENAME— fixed temp-file basenames.
Used by: Not used directly by any script in bin (used by the test suite to drive real DIAMOND runs).
src/dark/diamond/alignments.py¶
Holds DiamondReadsAlignments, the DIAMOND-specific counterpart to
the BLAST ReadsAlignments machinery: it ties a set of query reads to
the (JSON-encoded) DIAMOND hits found for them, and supports the
plotting/filtering code built on top of ReadsAlignments.
DiamondReadsAlignments(reads, filenames, databaseFilename=None, databaseDirectory=None, sqliteDatabaseFilename=None, scoreClass=HigherIsBetterScore, sortFilenames=False, randomizeZeroEValues=True)— aReadsAlignmentssubclass.filenamesare one or more.json/.json.bz2files produced bybin/convert-diamond-to-json.py. Exactly one ofdatabaseFilename/sqliteDatabaseFilenamemust be given.iter()— oneReadAlignmentsper query read.getSubjectSequence(title)— looks up and caches theAAReadWithXfor a subject title; raisesKeyErrorif not found.adjustHspsForPlotting(titleAlignments)/adjustPlot(readsAx)— e-value-only plotting adjustments (no-ops for bit-score scoring).ZERO_EVALUE_UPPER_RANDOM_INCREMENT = 150.
Used by: alignment-panel-civ.py, filter-reads-alignments.py, noninteractive-alignment-panel.py.
src/dark/diamond/hsp.py¶
Converts a raw DIAMOND HSP (1-based offsets) into a normalized, 0-based form usable for display/plotting against a subject sequence, with internal sanity checks.
normalizeHSP(hsp, queryLen, diamondTask)— Given an HSPdict(1-basedquery_start/query_end/sbjct_start/sbjct_end,btop,frame) and query length, returns adictwith 0-basedreadStart/readEnd/readStartInSubject/readEndInSubject/subjectStart/subjectEnd. Handles DIAMOND'sblastxnucleotide→protein coordinate conversion and reversed-frame queries;readStartInSubject/readEndInSubjectcan fall outside[subjectStart, subjectEnd]if the query overhangs the subject. RaisesAssertionError(via_sanityCheck/_debugPrint) on internal inconsistency.
Used by: Not used directly by any script in bin (used internally by dark.diamond.conversion.JSONRecordsReader).
src/dark/diamond/sam.py¶
Converts DIAMOND tabular (--outfmt 6) matches into SAM output — this
is the engine behind bin/convert-diamond-to-sam.py. Note: that
script was recently flagged (with a startup stderr warning) as
incomplete and not fully trustworthy — treat this module's output with
the same caution until it's been reviewed further.
SAMWriter(ram=False)— accumulates SAM body lines (RAM or temp-file-backed) and tracks@SQreference lengths, raisingValueErroron a conflicting length for the same reference.addMatchLine(line, genome, fp),save(fp, tmpfp=None, headerLines=None)._DiamondSAMWriter(genomesProteins, mappingQuality=255, ram=False, keepDescriptions=False)— abstract base class;genomesProteinsis adark.civ.proteins.SqliteIndexinstance (built bybin/make-protein-database.py)._preprocessMatch(diamondStr)— parses a tabular line and looks up its genome/protein, raisingValueErroron a failed lookup._SAMLine(match, protein, genome)— builds the TAB-separated SAM line (reverse-complementing as needed, building the CIGAR viadark.btop.btop2cigar, converting the proteinsstartto a nucleotidePOS, adding anAS:i:<bitscore>tag).addMatch(diamondStr)/save(...)— abstract.SimpleDiamondSAMWriter— writes all matches to one combined SAM output.PerReferenceDiamondSAMWriter(genomesProteins, baseFilenameFunc=None, ..., fpcMaxsize=0)— writes one<basename>.samfile per matched reference, using adark.fpcache.FilePointerCacheto stay under the OS open-file-descriptor limit.
Used by: convert-diamond-to-sam.py.
src/dark/diamond/conversion.py¶
Defines dark-matter's DIAMOND tabular ↔ internal-dict ↔ JSON
conversions: reading raw DIAMOND --outfmt 6 output, converting it to
the project's compact per-line JSON alignment format, and reading that
JSON format back for use as ReadAlignments.
FIELDS— the--outfmt 6field-name string DIAMOND must be run with for its output to be usable bybin/convert-diamond-to-sam.py.DIAMOND_FIELD_CONVERTER— maps known DIAMOND field names to their value-conversion function.DiamondTabularFormat(fieldNames=None)— generic reader/converter for arbitrary--outfmt 6column layouts.diamondFieldsToDict(line),diamondTabularFormatToDicts(filename).DiamondTabularFormatReader(filename)— reads raw DIAMOND--outfmt 6output in one of three older fixed column orders (a different convention thanFIELDSabove) and groups consecutive same-query lines into per-query alignment records.records(),saveAsJSON(fp, writeBytes=False).JSONRecordsReader(filename, scoreClass=HigherIsBetterScore)— reads back dark-matter's per-line JSON DIAMOND format, transparently handling.bz2.readAlignments(reads)— oneReadAlignmentsper entry inreads, matched by id (tolerating truncated ids).
Used by: convert-diamond-to-json.py, convert-diamond-to-sam.py (for FIELDS).
7. SAM/BAM, read mapping & consensus building¶
The pysam-based core of SAM/BAM handling, the external-tool wrappers that produce BAM files in the first place, and the (pure-Python) consensus-calling engine built on top of it.
src/dark/sam.py¶
Core SAM/BAM file handling: reference inspection, coverage-depth
computation, a flexible alignment filter (SAMFilter), conversion of
CIGAR-aligned reads to padded (gapped) FASTA (PaddedSAM), a
reference-to-reference distance matrix built from shared read matches
(DistanceMatrix), reference/consensus-building helpers, and summary
statistics over all reads in a SAM/BAM file. Built on top of pysam.
SamError/UnequalReferenceLengthError/UnknownReference/UnspecifiedReference/ReferenceNameMismatchError/InvalidSAM— a small exception hierarchy (all subclassSamError).CONSUMES_QUERY/CONSUMES_REFERENCE—sets ofpysamCIGAR operation codes that consume query/reference bases, used when walking CIGAR strings.samfile(filename)—@contextmanageropening apysam.AlignmentFile, guaranteed closed.samtoolsInstalled(),samReferences(filenameOrSamfile),samReferenceLengths(filenameOrSamfile),samReferencesToStr(filenameOrSamfile, indent=0).coverageDepth(filename, reference=None)— per-base coverage depth for one reference (auto-detected if not given, raisingUnspecifiedReference/UnknownReferenceas appropriate). Returns(referenceName, depths).SAMFilter— the central class for reading and filtering alignments from a SAM/BAM file.__init__(filename, filterRead=None, referenceIds=None, storeQueryIds=False, dropUnmapped=False, dropSecondary=False, dropSupplementary=False, dropDuplicates=False, keepQCFailures=False, minScore=None, maxScore=None, scoreTag="AS").addFilteringOptions(parser, samfileIsPositional=False, ...)(@staticmethod) — adds the standard--samfile/positional,--referenceId, drop-flags, score-range options to anargparse.ArgumentParser.parseFilteringOptions(args, filterRead=None, storeQueryIds=False)(@classmethod) — builds aSAMFilterfrom parsed args.filterAlignment(alignment)— tests onepysamalignment against all configured criteria.alignments()— generator of passing alignments; repairs*-SEQ secondary/supplementary records per the SAM spec. RaisesInvalidSAMif no preceding alignment is available to borrow a sequence from.referenceLengths(getAllReferences=False).PaddedSAM— turns SAM/BAM queries into FASTA-style reads padded with gap characters so they all line up against one reference.__init__(samFilter)— requires all references to have the same length (raisesUnequalReferenceLengthErrorotherwise).queries(rcSuffix="", rcNeeded=False, padChar="-", queryInsertionChar="N", unknownQualityChar="!", allowDuplicateIds=False, addAlignment=False)— generator of paddedReads, walking the CIGAR to handle matches/insertions/deletions/clipping, handling reverse-strand reads and duplicate-id suffixing.DistanceMatrix— accumulates read/reference match scores from one or more SAM files and computes inter-reference distance/similarity matrices.addFile(filename, scoreTag=None),score(referenceId, queryId),jaccardDistance(...)/soergelDistance(...),matrix(referenceIds=None, metric="soergel", similarity=False, returnDict=False),save(fp)/load(fp).getReferenceInfo(bam, bamFilename, bamId=None, referenceFasta=None, fastaId=None, quiet=False)— resolves "which reference, and how long is it" for consensus-building, cross-checking a BAM's own reference info against an optional separate reference FASTA. Returns(bamId, referenceRead_or_None, referenceLength).ReferenceReads— per-reference bookkeeping of which reads matched it and how (primary/secondary/supplementary/duplicate/QC-fail, covered offsets);coverage().ReadsSummary— reads a whole SAM/BAM file and builds aReferenceReadsper matched reference.toString(excludeZeroes=False, excludeIfNoAdditional=False, sortBy="count"),sortReferences(sortBy="count").
Used by: filter-sam.py, sam-references.py, sam-reference-read-counts.py, sam-coverage.py, sam-coverage-depth.py, plot-coverage-depth.py, sam-to-fasta-alignment.py, genome-protein-summary.py, simple-consensus.py, reference-read-scores-to-JSON.py, plot-references-by-inter-read-distance.py.
src/dark/consensus.py¶
The core (pure-Python) BAM-to-consensus-sequence engine used by
bin/simple-consensus.py (via consensusFromBAM); handles per-site
base counting, insertions, deletions, and coverage-dependent symbol
choice. Note: bin/simple-consensus.py was recently flagged (with
a startup stderr warning) as incomplete and not fully trustworthy —
bear that in mind when relying on this module.
ConsensusError— raised for consensus-making problems (e.g. an unknownstrategy).Insertion(insertionOffset)— accumulates per-read insertion data at a single reference offset.start(anchorOffset),append(base, quality),readCount(),resolve()(returns alist[dark.dna.Bases], one per inserted position).basesToConsensus(offsetBases, otherBases, originalOffsets, reference, referenceLength, threshold, minCoverage, lowCoverage, noCoverage, deletionSymbol, progress)— turns per-siteBasestallies into the final consensusstr.getConsensusId(bamId, idLambda)— builds the consensus id (f"{bamId}-consensus"by default, or aneval'd user lambda).consensusFromBAM(bamFilename, bamId=None, referenceFasta=None, fastaId=None, consensusId=None, idLambda=None, threshold=0.8, minCoverage=1, lowCoverage="n", noCoverage="N", deletionSymbol="-", deletionThreshold=0.5, ignoreQuality=False, insertionCountThreshold=5, strategy="fetch", includeSoftClipped=False, compareWithPileupFile=None, progress=False, quiet=False)— the main entry point: resolves the reference (viadark.sam.getReferenceInfo), collects per-site data, builds and returns a consensusDNARead. RaisesUnspecifiedReference/UnknownReference/UnequalReferenceLengthError/ReferenceNameMismatchError(fromdark.sam) orConsensusErrorfor an unsupportedstrategy(only"fetch"is implemented).getPairs(bam, bamId, referenceLength, ignoreQuality, includeSoftClipped, progress)— builds per-sitecorrespondences/deletions/insertionsby delegating toaddPairsInfofor each read.addPairsInfo(pairs, cigarOperations, queryId, query, qualities, referenceLength, includeSoftClipped, correspondences, deletions, insertions)— per-read helper; raisesValueErroron mismatched pairs/CIGAR lengths.fetchConsensus(bam, correspondences, deletions, insertions, reference, referenceLength, noCoverage, deletionThreshold, ignoreQuality, insertionCountThreshold, includeSoftClipped, progress)— turns raw per-site data into the offset-indexed structuresbasesToConsensusconsumes.pileupCorrespondences(bam, bamId, referenceLength, includeSoftClipped, progress)— an alternative, pysampileup-based way of collecting per-site data, used only for cross-checking.compareCorrespondences(fp, fetch, pileup, threshold, minCoverage)— diagnostic comparison of the two collection strategies, wired up viacompareWithPileupFile.
Used by: simple-consensus.py.
src/dark/bowtie2.py¶
A thin, stateful wrapper for driving the
bowtie2/samtools/GATK/Picard/bcftools/ivar command-line toolchain
to align, post-process, and call variants from a FASTQ dataset, via
dark.process.Executor. All the real work happens in external shell
commands; this class just sequences them and tracks what stage of the
pipeline it's in.
Bowtie2(executor=None, threads=None, verboseFp=None, dryRun=False, reference=None, tempdir=None, tmpChmod=None)(@final) — creates a private temp directory for intermediate files.buildIndex(index)— auto-detects whetherindexis a pre-built bowtie2 index, a FASTA filename, or an NCBI accession, and arranges to use/build accordingly. Must run beforealign.align(bowtie2Args="--no-unal", fastq1=None, fastq2=None, threads=None, discardSAM=False, readGroup="orig", sampleName="orig")— raisesValueErrorifbuildIndexhasn't run or neither FASTQ is given.makeBAM(...)/sort(byName=False)/indexBAM()/removeDuplicates()/markDuplicatesPicard(picardFile)/markDuplicatesGATK(threads=None)/removePrimers(bedFile)— successive post-processing steps, several requiringmakeBAMto have run first.callHaplotypesGATK(picardJar, vcfFile=None, referenceFasta=None)/callHaplotypesBcftools(vcfFile=None, referenceFasta=None)— variant-calling into a VCF.isEmpty(),mappedAndUnmappedCounts(),outputFile(),close().
Used by: run-bowtie2.py.
src/dark/process.py¶
A single utility class for running (or dry-run logging) shell commands
uniformly across the various bin/run-*/bin/make-consensus.py-style
scripts, with consistent error reporting and an append-only log of
everything that was (or would have been) executed.
Executor(dryRun=False, stdout=None, stderr=None)(@final) —self.logaccumulates a human-readable record of every command run.execute(command, dryRun=None, useStderr=True, stdout=False, stderr=False, **kwargs)— runscommandviasubprocess.run(..., check=True), logging it either way; in dry-run mode returnsNonewithout executing. OnCalledProcessError, optionally prints the error/output to stderr (peruseStderr) before re-raising.
Used by: compare-aa-sequences.py, compare-consensuses.py, run-bowtie2.py, run-bwa.py, extract-reference-from-fasta-and-bam.py, make-consensus.py.
src/dark/extract_reference.py¶
Implements the two halves of
bin/extract-reference-from-fasta-and-bam.py: pulling a single named
reference sequence out of a FASTA file, and filtering a SAM/BAM file
down to just the records aligned against it.
extract_fasta(fasta, ref_id, out_dir, index_fasta, quiet, force, keep_description, e)— findsref_idinfasta, writes it to<out_dir>/<ref_id>.fasta, optionally indexes it withsamtools faidx, returns its length. Callssys.exit(...)(not an exception) on a pre-existing output file (unlessforce) or an unfindable reference.extract_sam(bam, ref_id, reference_len, out_dir, keep_sam, force, e)— converts to SAM if needed, copies the matching@SQ/body lines, sorts/indexes back to BAM. Returns the matching-read count;sys.exits on zero matches, pre-existing outputs, or an unrecognized file suffix.
Used by: extract-reference-from-fasta-and-bam.py.
src/dark/analyze_reads.py¶
Small helper module backing bin/trim-reads.py: finds a prefix/suffix
common to every read in a FASTA file (e.g. leftover primer/adaptor
sequence) and trims it off.
getPrefixAndSuffix(file_handle)— returns(prefixLength, suffixLength): the longest prefix/suffix common to every read in the file. Returns(0, 0)if any two reads share none, or if there are no reads.trimReads(prefix, suffix, file_handle)— generator yielding each read withprefix/suffixbases removed._longestPrefixOfTwoSeqs(a, b)— internal helper, not public API.
Used by: trim-reads.py.
src/dark/fpcache.py¶
A small LFU (least-frequently-used) file-pointer cache, letting code hold many "simultaneously open" files for writing/reading without exceeding the OS's per-process open-file-descriptor limit — files that fall out of the cache are transparently closed, and reopened (in a different mode) if touched again.
FilePointerCache— the public wrapper class.__init__(maxsize=32, openArgs=None, reopenArgs=None)—openArgs/reopenArgscontrol the kwargs used for the first open vs. a reopen after cache eviction.open(filename)— returns an open file pointer, opening/evicting as needed.close()— closes every currently-open file. Usable as a context manager.
Used by: Not used directly by any script in bin (used internally by dark.diamond.sam.PerReferenceDiamondSAMWriter, reached via bin/convert-diamond-to-sam.py's --perReferenceOutput option).
src/dark/progress.py¶
A one-function wrapper around the progressbar package that makes an
optional, terminal-aware progress bar easy to use with a single with
statement.
maybeProgressBar(show, maxValue, prefix)—@contextmanager. Ifshowand stderr is a TTY, yields a configuredprogressbar.ProgressBar; otherwise yields a no-op stand-in with a do-nothingupdatemethod, so callers never need to branch.
Used by: Not used directly by any script in bin.
8. Protein/genome databases, GenBank & taxonomy¶
Two parallel sets of "protein/genome database" code exist in this codebase —
the plain dark.proteins/dark.genomes modules (built around NCBI-style
[pathogen name]-bracketed titles) and the dark.civ.proteins module (built
around dark matter's own "civ"-prefixed sequence id format and its own
SQLite schema). They are not interchangeable; several bin scripts'
import bugs (see below) have come from confusing the two.
src/dark/proteins.py¶
Groups DIAMOND/BLAST protein-match results by the pathogen each
matched protein comes from, and renders that grouping as plain text or
as a browsable HTML report (with optional per-pathogen read-count
panel plots). This is the library behind proteins-to-pathogens.py's
pathogen-centric view of noninteractive-alignment-panel.py
output. Note: this module is unrelated to
src/dark/civ/proteins.py below.
splitNames(names)— Splits an NCBI-style id of the form"Protein name [pathogen name]"into(proteinName, pathogenName)using the last[...]section.getPathogenProteinCounts(filenames)— Returns aCounterof pathogen name → protein count across the given FASTA files.PathogenSampleFiles— Caches, for each (pathogen, sample) pair, a de-duplicated FASTA/FASTQ of the reads that matched that pathogen in that sample, plus sample/pathogen index files.__init__(proteinGrouper, format_="fasta"),add(pathogenName, sampleName),lookup(pathogenName, sampleName)(raisesKeyErrorif not yet added),writeSampleIndex(fp)/writePathogenIndex(fp),pathogenIndex(pathogenName).ProteinGrouper— accumulates per-sample protein-match summary lines and groups them by pathogen.__init__(assetDir="out", sampleName=None, sampleNameRegex=None, format_="fasta", proteinFastaFilenames=None, saveReadLengths=False, titleRegex=None, negativeTitleRegex=None, pathogenDataDir="pathogen-data").addFile(filename, fp)— parses one summary file's lines; raisesValueErroron a duplicate pathogen/sample/protein combination.toStr(title="Summary of pathogens", preamble=None),toHTML(pathogenPanelFilename=None, minProteinFraction=0.0, minProteinCount=0, pathogenType="viral", ...). ItsminProteinFraction > 0.0 or minProteinCount > 0filtering branch previously referencedself.genomeAccessions/self._db(leftover code copied fromciv/proteins.py's similarly-named class, neither attribute existing on this non-civ class) and would raiseAttributeErrorif exercised — fixed (2026-10-24) to use this class's actual data model,self.pathogenNames/self._pathogenProteinCount, and verified with both aminProteinCountand aminProteinFractionend-to-end test (sample kept when it passes the threshold, deleted when it doesn't).pathogenPanel(filename)— saves a per-pathogen read-count-vs-sample bar-plot panel.
Used by: proteins-to-pathogens.py imports ProteinGrouper. subset-protein-database.py previously imported a nonexistent SqliteIndex from this module (from dark.proteins import SqliteIndex — this module defines no such class, so the import raised ImportError); fixed (2026-10-24) to from dark.civ.proteins import SqliteIndex (the same stale-import bug pattern found and fixed in bin/convert-diamond-to-sam.py) and verified end-to-end against a real database built with SqliteIndexWriter.
src/dark/genomes.py¶
Correlates a genome's annotated proteins (from a civ-style protein/genome database) with read coverage from one or more SAM files, to compute per-protein coverage statistics.
GenomeProteinInfo— Holds, for one genome accession, its proteins (and nucleotide offset ranges) and which read ids cover which genome offsets.__init__(genomeAccession, proteinGenomeDB, checkTranslations=True)—proteinGenomeDBmust duck-type as adark.civ.proteins.SqliteIndex. Verifies (ifcheckTranslations) that each protein's stored translation matches the genome nucleotides. Raisesdark.errors.NoSuchGenomeErrorif the accession isn't found.addSAM(filename, filterAlignment=None)— reads a SAM/BAM file and records covering read ids per genome offset and per protein.proteinCoverageInfo(proteinAccession, minReadOffsetCount=None)— coverage stats for one protein; raisesKeyError/ValueErroras appropriate.readIdsForAllProteins().
Used by: genome-protein-summary.py.
src/dark/genbank.py¶
Parses the odd little location-range mini-language BioPython produces
when it reads GenBank flat files (e.g. "join{[126386:126881](-),
[125941:126232](+)}"), and extracts genome/CDS feature metadata from
BioPython GenBank records.
GenomeRanges— Parses a GenBank/BioPython range string into(start, stop, forward)0-based ranges (merging contiguous ones). RaisesValueErroron malformed input.circular(genomeLength)—Trueif the ranges wrap around the genome's origin.startInGenome(match)— converts a DIAMOND match's 1-based proteinsstartto a 0-based genome nucleotide offset, accounting for spliced (multi-range) proteins.orientations(),distinctRangeCount(genomeLength).getSourceInfo(genome, keys=("host", "note", "organism", "mol_type"), logfp=None, indent="")— extracts qualifier values from a GenBank record'ssourcefeature.getCDSInfo(genome, feature, keys=(...))— extracts and validates a CDS feature's protein id, translation, gene/note/product, parsedGenomeRanges, circularity, strand. ReturnsNone(with a warning) for CDS features that fail validation — treated as "skip", not a hard error.
Used by: genome-protein-summary.py.
src/dark/database.py¶
A single helper, getDatabaseConnection(), that opens a MySQL
connection (via mysql.connector) to a local ncbi_taxonomy
database, using the DBI_USER/USER and DBI_PASSWORD environment
variables for credentials. This is the (older, MySQL-based) taxonomy
backend used by dark.taxonomy.LineageFetcher; the newer
dark.taxonomy.Taxonomy class uses an sqlite3 database instead.
Not used directly by any script in bin (used only indirectly, via dark.taxonomy.LineageFetcher, which itself isn't imported by anything in bin either).
src/dark/sqlite3.py¶
sqliteConnect(filename)— A thin wrapper aroundsqlite3.connectthat, on failure, re-raisessqlite3.OperationalErrorwith the filename included in the message.
Not used directly by any script in bin (used internally by several other dark modules, e.g. dark.taxonomy, dark.civ.proteins, dark.fasta).
src/dark/ncbidb.py¶
getSequence(title, db="nt")— Given a BLAST-hit-style title, shells out toblastdbcmdto fetch the corresponding sequence from a local BLAST database. Requiresblastdbcmdand the named database to exist locally.
Not used directly by any script in bin.
src/dark/entrez.py¶
getSequence(title, db="nucleotide")— The network equivalent ofdark.ncbidb.getSequence: fetches the full GenBank record from NCBI over the network viaBio.Entrez.efetch, returningNoneon failure (e.g. offline). SetsEntrez.emailas NCBI requires; subject to NCBI's rate limits.
Not used directly by any script in bin (used by dark.features.FeatureList as the default sequence fetcher).
src/dark/civ/proteins.py¶
This is the core of the "civ" protein/genome database machinery: it
defines the SQLite schema and read/write access classes for a database
of genome and protein records (built from GenBank or JSON genome
records), plus the ProteinGrouper/PathogenSampleFiles classes that
turn per-sample alignment-panel protein-match summaries into grouped,
per-pathogen HTML/text/plot reports. The "civ" sequence id format this
module defines and consumes is
civ|GENBANK|<protein-accession>|GENBANK|<genome-accession>|<product>
[<pathogen-name>] (field 2 is the protein accession, field 4 is the
genome accession). This whole module is the backbone that
make-protein-database.py writes to and that
alignment-panel-civ.py, describe-protein-database.py,
convert-diamond-to-sam.py, genome-protein-summary.py, and
proteins-to-pathogens-civ.py all read from.
PathogenSampleFiles— a cache/writer of per-(pathogen, sample) de-duplicated FASTA/FASTQ files, used while building aProteinGrouperHTML report.add(genomeAccession, sampleName)— gathers every read matching any protein of that pathogen in that sample, de-duplicates by id, writes the combined reads topathogen-<genomeAccession>-sample-<index>.<format>, records the de-duplicated count back into the owningProteinGrouper, and returns the filename (cached on subsequent calls).lookup(genomeAccession, sampleName)— cache-only lookup; raisesKeyErrorif not yet added.writeSampleIndex(fp).ProteinGrouper— reads alignment-panel protein-summary text output and groups it by pathogen (genome accession) and sample, then renders plain text, HTML, or a per-pathogen read-count plot panel. Constructor requires aSqliteIndex-likeproteinGenomeDatabaseand a taxonomy database.addFile(filename, fp)— ingests one summary file intoself.genomeAccessions(a nestedgenomeAccession -> sampleName -> {"proteins": {...}, "uniqueReadCount": None}dict). RaisesValueErrorif a protein can't be found in the database (commonly means the summary was generated against a different database version) or on a duplicate combination.toStr(title=None, preamble=None, pathogenType="viral")— plain-text summary (no de-duplicated read counts — those needtoHTML's setup).toHTML(...)— the main interactive HTML report (collapsible sections, taxonomy links, optional read-count panel image, optional taxonomy tree); internally calls_computeUniqueReadCountsfirst.pathogenPanel(filename)— the per-pathogen read-count-vs-sample plot panel image, usable standalone or viatoHTML.- Class constants:
VIRALZONE/ICTVlink prefixes,READCOUNT_MARKER,READ_AND_HSP_COUNT_STR_SEP. _Genome/_GenomeLocation/_GenomeFeature— small adapters wrapping a plaindict(as loaded from JSON) so it exposes the same attributes a BioPythonSeqIO.parseGenBank record would, letting the rest of the module treat JSON-sourced and GenBank-sourced genomes identically.SqliteIndexWriter— creates/updates the SQLite database of genomes and proteins (tablesgenomes/proteins), simultaneously writing protein FASTA (with "civ"-format ids) to a file pointer. Constructor takes a filename (or":memory:") and an optionalfastaFp(defaultsys.stdout).addGenBankFile(filename, taxonomyDatabase, dnaOnly=False, rnaOnly=False, allowedTaxonomicRanks=None, minGenomeLength=None, maxGenomeLength=None, excludeExclusiveHosts=None, excludeFungusOnlyViruses=False, excludePlantOnlyViruses=False, databaseName=None, proteinSource="GENBANK", genomeSource="GENBANK", duplicationPolicy="error", logfp=None)— parses a GenBank flat file and adds all its genomes/proteins; returns(genomeSequencesInFile, genomesAdded, proteinsAdded). This is whatmake-protein-database.pycalls for GenBank-sourced data.addJSONFile(filename, taxonomyDatabase, ...)— same, for a single genome supplied as JSON.addGenome(genome, source, taxonomyId, proteinCount, databaseName, duplicationPolicy="error", logfp=None)— raisesDatabaseDuplicationErroron a duplicate accession with"error"policy.addProteins(genome, source, ...)— extracts every CDS feature (viadark.genbank.getCDSInfo), writes each translation to the FASTA file with a civ-format id, and callsaddProteinfor each.addProtein(accession, genomeAccession, sequence, offsets, forward, circular, rangeCount, gene=None, note=None, product=None, duplicationPolicy="error", logfp=None).close()— creates unique indices, commits, closes. Also a context manager.- Class constants:
PROTEIN_ACCESSION_FIELD = 2,GENOME_ACCESSION_FIELD = 4,SEQUENCE_ID_PREFIX = "civ",SEQUENCE_ID_SEPARATOR = "|". SqliteIndex— read-only lookup access to a database built bySqliteIndexWriter. This is the class most "civ"binscripts need asSqliteIndex(notablyconvert-diamond-to-sam.py, which originally had a stale import pointing at an unrelated same-named class). Constructor takes a filename or an opensqlite3.Connection, plus an LRUlookupCacheSize(default 1024).genomeAccession(id_)/proteinAccession(id_)— extract the genome/protein accession from a civ-format id; raiseIndexErrorif too few fields.findGenome(id_)/findProtein(id_)— look up a row (as adict) by full id or accession;Noneif not found (memoized).findProteinsForGenome(id_)— generator of all protein rows for a genome.execute(query, *args),proteinCount(),genomeCount(),close()(closes the connection only if this instance opened it itself).
Used by: alignment-panel-civ.py, convert-diamond-to-sam.py, describe-protein-database.py, genome-protein-summary.py, make-protein-database.py, proteins-to-pathogens-civ.py.
src/dark/taxonomy.py¶
Provides lineage/host/classification lookups against a local NCBI
taxonomy database (built by the separate
ncbi-taxonomy-database
project), plus heuristics for classifying viruses as plant-only,
fungus-only, RNA, DNA, or retroviruses, and argparse helpers for
scripts that need a taxonomy database path.
LineageFetcher— The older, MySQL-backed lineage lookup, keyed by BLAST-hit gi number;lineage(title)walks up the taxonomy tree (cached in memory);close(). Superseded byTaxonomybelow.Taxonomy— The modern, sqlite3-backed taxonomy interface; usable as a context manager.__init__(dbFilenameOrConnection).lineageFromTaxid(taxid, skipFunc=None, stopFunc=None)— walksnodes/namesup to the root, returning atupleofLineageElement(taxid, name, rank). RaisesValueErroron a missing taxid.lineage(id_, skipFunc=None, stopFunc=None)(memoized) — like above, butid_may be aninttaxid, astraccession, or astrname.hostsFromTaxid(taxid)(memoized),taxIdFromName(name),hosts(id_).isFungusOnlyVirus(lineage, title=None)/isPlantOnlyVirus(lineage, title=None)(both memoized) — heuristic classification combining family/genus/order name lists, a title regex, and (slower) ahosts()lookup.subsetLineageByRanks(lineage, func)(static),close().isRetrovirus(lineage),isRNAVirus(lineage),isDNAVirus(lineage)— free functions testing a lineage against fixed name/rank markers.isAllowedTaxonomicRank(allowedTaxonomicRanks, lineage),formatLineage(lineage, namesOnly=False, separator=", ", prefix=""),lineageTaxonomyLinks(lineage).Hierarchy/_HierarchyNode— build a lineage tree (rooted at"Viruses"by default) and serialize tojstree-compatible JSON viatoJSON().addTaxonomyDatabaseCommandLineOptions(parser)/parseTaxonomyDatabaseCommandLineOptions(args, parser)— argparse helper pair adding/resolving--taxonomyDatabase(falling back to theDARK_MATTER_TAXONOMY_DATABASEenvironment variable).
Used by: make-protein-database.py, get-taxonomy.py, proteins-to-pathogens-civ.py, get-hosts.py, filter-fasta-by-taxonomy.py.
9. Phylogenetic trees¶
src/dark/trees.py¶
Two independent phylogenetic-tree utilities: one for cleaning up
iqtree2-produced Newick trees that mix bootstrap-support and aLRT
values on node labels (using dendropy), and one for re-rooting an
ete4 tree on the branch leading to the MRCA of a given set of tips.
removeAlrt(treeFile, supportThreshold, alrtThreshold, adjustSupportValues, replacementSupportValue, expectedUnlabeledNodeCount, verbose=False, verboseFp=sys.stderr)— reads a Newick tree whose internal node labels are"<alrt>/<support>"(asiqtree2produces when both values are requested — a format onlydendropycan parse among the tools tried), strips the aLRT part, and (ifadjustSupportValues) replaces the support label withreplacementSupportValueon branches whose support meetssupportThresholdbut aLRT falls belowalrtThreshold. Returns the resulting Newickstr. RaisesValueErroron a malformed label or an unexpected count of unlabeled internal nodes (pass-1to skip that check).ete4root(tree, tipNames, detach=False, scale=1.0)— re-roots anete4Treeon the branch leading into the MRCA oftipNames(sys.exits with a clear message if a name matches zero or more than one node); handles an already-trifurcating root by re-rooting on the excluded branch instead, and no-ops (with a stderr note) if the MRCA is already the root.scaledivides the new root's two child-branch lengths by the given factor. Ifdetach=True, the outgroup subtree is detached after rerooting and the remaining subtree is returned instead.ete3rootis a backward-compatible alias.
Used by: curate-tree-ete.py (ete4root), remove-alrt-from-tree.py (removeAlrt).
10. Visualization: plotting & HTML¶
Two near-duplicate pairs of plotting/HTML code exist here: the general-purpose
dark.graphics/dark.html and the "civ"-database-specific
dark.civ.graphics/dark.civ.html. Both build BLAST/DIAMOND alignment-panel
figures and HTML index pages; the civ versions look up subject accession
numbers through a local protein/genome database rather than online.
src/dark/civ/graphics.py¶
Plotting code for the "civ" pathogen-grouping pipeline: draws
BLAST/DIAMOND alignment graphs and panels of them against a
protein/genome SQLite database (dark.civ.proteins.SqliteIndex),
using matplotlib, and can emit an HTML summary via
dark.civ.html.AlignmentPanelHTMLWriter. It is a near-duplicate of
dark.graphics (see below) specialized to accept a proteinGenomeDB
and look up accession numbers through it rather than through online
feature lookup.
report(msg)— print a timestamped progress message to stderr.alignmentGraph(titlesAlignments, title, accession, addQueryLines=True, showFeatures=True, logLinearXAxis=False, logBase=1.1, rankScores=False, createFigure=True, showFigure=True, readsAx=None, imageFile=None, quiet=False, idList=False, xRange="subject", maxBluePlotReads=None)— draws a single alignment graph for one matched subjecttitle: whisker lines for each query's full extent plus a solid HSP-match line, at score height, optionally colored per-read viaidList.logLinearXAxis=Truelogarithmically compresses large gap regions on the X axis (viadark.intervals.OffsetAdjuster);maxBluePlotReadscaps drawn lines. Returns adictwithadjustOffset/features/minX/maxX/minY/maxY— needed by callers (e.g.alignmentPanel) for consistent alignment/scaling across multiple graphs.alignmentPanel(titlesAlignments, proteinGenomeDB, sortOn="maxScore", idList=False, equalizeXAxes=False, xRange="subject", logLinearXAxis=False, rankScores=False, showFeatures=True, logBase=1.1, subjectType="protein", maxBluePlotReads=None)— a 5-column grid of smallalignmentGraphsubplots, one per matched subject title, sorted bysortOn. Accession numbers come fromproteinGenomeDB.proteinAccession/.genomeAccessiondepending onsubjectType. Shows the figure. RaisesValueErrorfor an invalidxRange.alignmentPanelHTML(titlesAlignments, proteinGenomeDB, sortOn="maxScore", outputDir=None, idList=False, equalizeXAxes=False, xRange="subject", logLinearXAxis=False, logBase=1.1, rankScores=False, showFeatures=True, subjectType="protein", maxBluePlotReads=None)— likealignmentPanel, but saves one PNG per subject intooutputDirand writes anindex.htmlviaAlignmentPanelHTMLWriter.outputDiris required;equalizeXAxes=TrueraisesNotImplementedError.- Module constants:
SMALLEST_LOGGED_GAP_TO_DISPLAY = 20,Y_AXIS_UPPER_PADDING = 1.01,DEFAULT_LOG_LINEAR_X_AXIS_BASE = 1.1.
Used by: alignment-panel-civ.py.
src/dark/graphics.py¶
The general-purpose (non-civ) counterpart of dark.civ.graphics:
BLAST/DIAMOND alignment-graph plotting plus a few standalone
sequence-visualization helpers (dot-plot alignment, amino-acid
property/cluster plots). Subject feature/accession information comes
from online lookups (via dark.features) rather than a local civ
database, and it additionally supports drawing open reading frames and
per-base-colored query rendering, which dark.civ.graphics does not.
report(msg)— same timestamped stderr progress printer.alignmentGraph(titlesAlignments, title, addQueryLines=True, showFeatures=True, logLinearXAxis=False, logBase=1.1, rankScores=False, colorQueryBases=False, createFigure=True, showFigure=True, readsAx=None, imageFile=None, quiet=False, idList=False, xRange="subject", showOrfs=True)— same core behavior as the civ version, plus:showOrfs=Trueadds ORF tracks (viadark.orfs, auto-disabled for protein subjects);colorQueryBases=Truerenders each query as a per-nucleotide-coloreddark.baseimage.BaseImagegrid instead of a line. NoproteinGenomeDB/accession parameter — the title itself is used for display and feature lookup.alignmentPanel(titlesAlignments, sortOn="maxScore", idList=False, equalizeXAxes=False, xRange="subject", logLinearXAxis=False, rankScores=False, showFeatures=True, logBase=1.1)— same grid structure as the civ version, without accession lookups (ORFs/colored bases disabled in panel mode).alignmentPanelHTML(titlesAlignments, sortOn="maxScore", outputDir=None, idList=False, equalizeXAxes=False, xRange="subject", logLinearXAxis=False, logBase=1.1, rankScores=False, showFeatures=True, showOrfs=True)— HTML-with-per-subject-PNGs variant usingdark.html.AlignmentPanelHTMLWriter.scoreGraph(titlesAlignments, find=None, showTitles=False, figureWidth=5, figureHeight=5)— grid of sorted per-title score line plots, optionally highlighting titles matching afindpredicate. The docstring flags this as "probably bit rotted."scatterAlign(seq1, seq2, window=7)— quick dot-plot alignment visualization: scatter-plots every position pair where the samewindow-sized substring occurs in both sequences.plotAAProperties(sequence, propertyNames, showLines=True, showFigure=True)— plots (viadark.aa.propertiesForSequence) per-residue property values; always returns the computeddicteven if not shown. RaisesValueErrorfor an unknown property.plotAAClusters(sequence, propertyNames, showLines=True, showFigure=True)— same, for property cluster numbers (viadark.aa.clustersForSequence).- Module constants:
QUERY_COLORS,DEFAULT_BASE_COLOR,TABLEAU20,SMALLEST_LOGGED_GAP_TO_DISPLAY,Y_AXIS_UPPER_PADDING,DEFAULT_LOG_LINEAR_X_AXIS_BASE.
Used by: noninteractive-alignment-panel.py.
src/dark/html.py¶
HTML/IPython-display rendering helpers for summarizing BLAST/DIAMOND
alignment results — both for Jupyter-notebook inline display and for
the standalone-HTML alignment-panel output written by
dark.graphics.alignmentPanelHTML.
NCBISequenceLinkURL(title, field=None, delim="|")— extracts a field from a title and builds an NCBI nucleotide-database URL for it. RaisesIndexErrorif the field doesn't exist.NCBISequenceLink(title, field=None, delim="|")— same, wrapped in an HTML<a>tag.summarizeTitlesByTitle/ByCount/ByLength/ByMaxScore/ByMedianScore(titlesAlignments, limit=None)— each sorts adark.titles.TitlesAlignmentsby the named attribute and returns anIPython.display.HTMLlisting read count, length, best/median score, and an NCBI link per title.AlignmentPanelHTMLWriter— accumulates one PNG-plus-metadata entry per subject (addImage) and, onclose(), writesindex.html(thumbnail table + detail sections, each with a matching-reads FASTA/FASTQ link and an optional features link) plusstyle.css.__init__(outputDir, titlesAlignments),addImage(imageBasename, title, graphInfo),close().readCountText(readCountColors, count, linkText=None)— produces a CSS-classed HTML<span>for a count per adark.colors.ColorsForCountsinstance (or plain text ifreadCountColors is None).
Used by: Not used directly by any script in bin (imported by dark.graphics, which bin/noninteractive-alignment-panel.py actually uses).
src/dark/civ/html.py¶
A small module providing the HTML writer that
dark.graphics/dark.civ.graphics use to produce the multi-image
"alignment panel" index page (a grid of thumbnail graphs, each linking
to a full-size version with match details, a link to the reads that
produced it, and, where available, feature/NCBI-link annotations).
AlignmentPanelHTMLWriter— accumulates one entry per matched subject (viaaddImage), then writes outindex.htmlandstyle.css(viaclose). Constructor takes the output directory and adark.titles.TitlesAlignmentsinstance.addImage(imageBasename, accession, title, graphInfo).close()— writesindex.html(thumbnail table, detail sections each with a reads-file link that saves the matching reads as gzipped FASTA/FASTQ, and an optional features-file link) andstyle.css.
Used by: Not used directly by any script in bin — used internally by src/dark/graphics.py and src/dark/civ/graphics.py, which in turn back bin/alignment-panel-civ.py and bin/noninteractive-alignment-panel.py.
src/dark/colors.py¶
A small utility for mapping integer counts to colors/CSS class names
based on user-specified thresholds, used to color-code read counts in
HTML/plot output (e.g. in dark.html.readCountText).
ColorsForCounts— holds a sorted (highest threshold first) list of(threshold, color)pairs built from an iterable of"value color"strings; a(0, defaultColor)entry is auto-appended if no zero-threshold entry was given. RaisesValueErroron malformed entries.thresholdToCssName(threshold),thresholdForCount(count),colorForCount(count).
Used by: proteins-to-pathogens-civ.py.
src/dark/dimension.py¶
A single generic multi-dimensional-counter (odometer-style) iterator, used to walk row/column coordinates when laying out grid panels of plots.
dimensionalIterator(dimensions, maxItems=-1, start=None)— given a list ofnpositive integers (or'*'for unbounded), yieldsn-tuples enumerating every combination in odometer order.maxItemscaps output (-1means unlimited — required if any dimension is'*');startresumes from a specific coordinate. RaisesValueErrorfor a non-positive dimension or mismatchedstartlength.
Used by: Not used directly by any script in bin (used internally
by dark.graphics/dark.civ.graphics to lay out panel-of-plots
grids).
src/dark/baseimage.py¶
A tiny helper for building an RGB pixel grid representing per-base
coloring of aligned sequence data, used by
dark.graphics.alignmentGraph's colorQueryBases option to render a
query sequence base-by-base instead of as a single-color line.
BaseImage— a 2D array of RGB triples (numpy, all-ones/white initially) representing anxRange×yRangegrid, each optionally "blown up" into anxScale×yScalepixel block..datais the raw array (suitable formatplotlib.pyplot.imshow).set(x, y, value)— color the block for grid position(x, y).
Used by: Not used directly by any script in bin (used internally by dark.graphics.alignmentGraph).
src/dark/features.py¶
Fetches and plots GenBank sequence features (CDS, mat_peptide, etc.) alongside BLAST alignment-panel figures, annotating a matplotlib figure with colored bars for each feature and a legend describing them.
Feature— Wraps a single BioPython GenBank feature with 0-basedstart/end, an assignable plotcolor, andlegendLabel()(a human-readable description).FeatureList(list)— fetches a sequence's GenBank record (via asequenceFetcher, defaultdark.entrez.getSequence) and populates itself withFeatures for whichever features have a type inwantedTypes, colored along a "coolwarm" colormap. Setsself.offline = True(and stays empty) if there's no network access._FeatureAdder— abstract base coordinating "fetch features, then plot them, unless there are too many or we're offline."add(fig, title, minX, maxX, offsetAdjuster=None, sequenceFetcher=None); caps display atMAX_FEATURES_TO_DISPLAY = 50.ProteinFeatureAdder/NucleotideFeatureAdder— concrete subclasses with differentDATABASE/WANTED_TYPESand layouts (nucleotide features laid out by reading frame).
Not used directly by any script in bin (used internally by dark.graphics, which several scripts/notebooks use for alignment-panel plotting).
11. General utilities¶
Grab-bag helpers used throughout the rest of the package.
src/dark/utils.py¶
A grab-bag of generic helpers used across the codebase: file-name
sorting, transparent compressed-file opening, numeric range-string
parsing, formatted count/percentage strings, interval compression, and
a copy of scikit-learn's entropy function.
numericallySortFilenames(names)— Sorts filenames by the numeric prefix of their basename (so"10.json.bz2"sorts after"1.json.bz2"but before"2.json.bz2"numerically, not lexically).median(numbers)— Likestatistics.median, but raisesValueError("arg is an empty sequence")on empty input.asHandle(fileNameOrHandle, mode="rt", encoding="UTF-8")(context manager) — The central "open this, whatever it is" helper: opens astr/Path(transparently usinggzip.open/bz2.openfor.gz/.bz2), or yields an already-open file-like object unchanged (so it is not closed on exit in that case).parseRangeString(s, convertToZeroBased=False, asList=False)— Parses"1-5,12,100-200"into aset(or orderedlist) ofints. RaisesValueErroron malformed input.parseRangeExpression(s, convertToZeroBased=False)— A more powerful sibling also understanding&,|, and()around range groups, by substituting ranges with Pythonsetliterals andeval-ing the result. Returnsset()for empty input. Security note: usesevalon input-derived text — safe for its intended shape, not for untrusted input.parseStartEndLabelColor(regionStr)— Parses a 1-based"start:end[:label[:color]]"region spec into(start, end, label, color)withstartconverted to 0-based. RaisesValueErroron invalid bounds.baseCountsToStr(counts)/nucleotidesToStr(nucleotides, prefix="")— Format base→count data as human-readable strings.countPrint(mesg, count, len1, len2=None)— the formatting helper behinddark.dna.matchToString's per-field output lines ("<mesg>: <count>"or"<mesg>: <count>/<len1> (<pct>%)", two-length variant iflen1 != len2).pct(a, b, commas=False, places=3, denom=True)— Formats"<a>/<b> (<pct>%)". Asserts0 <= a <= b.cd(newdir)(context manager) — Temporarilychdirs, restoring on exit.take(iterator, n)— generator of successive (up to)n-item chunks.readLabels(fp)— Reads aname<TAB>replacementfile into adict(for Newick tree relabeling).matchOffset(leftPaddedReference, leftPaddedQuery)— the 0-based offset into the unpadded reference where the query's real content begins.openOr(filename, mode="r", defaultFp=None, specialCaseHyphen=True)(context manager) — yieldsdefaultFpiffilenameisNone(or"-", ifspecialCaseHyphen), else opensfilename.intsToIntervals(numbers)— sortedlistof(start, end)tuples for each maximal run of consecutive integers.intsToStringIntervals(numbers, sep="-", minimize=False)—strform ("5"/"5-9");minimize=Truefurther abbreviates same-length ranges (e.g."2016-20"). This is what produces the compressed gap/no-coverage location lists indark.dna.matchToString's output.gmt()— current UTC time as"YYYY-MM-DD HH:MM:SS (UTC)".entropy(labels)— a vendored copy of scikit-learn's now-renamed/deprecated entropy helper (natural log).entropy2(labels)— the same, base-2.assignRegionRows(regions)— greedy interval-scheduling layout: assigns each(start, end, label, color)region to the lowest-numbered non-overlapping row, for stacking plot annotations.
Used by: alignment-panel-civ.py, compare-sequences.py, compare-aa-sequences.py, fasta-split.py, msa-find-and-extract.py, genome-protein-summary.py, plot-coverage-depth.py, plot-references-by-inter-read-distance.py, sam-coverage-depth.py, sam-match-lengths.py, noninteractive-alignment-panel.py, sam-to-fasta-alignment.py.
src/dark/errors.py¶
Four tiny Exception subclasses used as specific error types elsewhere in the package: DatabaseDuplicationError, NoSuchGenomeError, NoSuchProteinError, ReadLengthsNotIdenticalError. Each is a one-line docstring with no additional behavior.
Used by: fasta-variable-sites.py, genome-protein-summary.py.
src/dark/__init__.py¶
The package root module. Defines __version__ (read from installed
package metadata, falling back to "unknown"), raises an Exception
at import time if running on Python older than 3.10, and defines the
File type alias (TextIO | BinaryIO | str | bytes | Path) used
throughout the codebase's type hints to mean "anything that can be
treated as a readable/writable file: an open handle, a filename, or
raw bytes."
Used by: dark-matter-version.py (for __version__), fasta-subtraction.py, simple-consensus.py (also for __version__).
src/dark/ipynb.py¶
Not a real module of its own functionality — just a convenience
re-export list, meant to be used as from dark.ipynb import * inside
an IPython/Jupyter notebook to pull in the most commonly-needed names
(FastaReads, BlastReadsAlignments, TitlesAlignments,
titleCounts, the summarizeTitlesBy* family, and the
alignmentGraph/alignmentPanel/scoreGraph plotting functions) in
one line. Defines __all__ explicitly so the star-import only brings
in those names.
Not used directly by any script in bin (it's specifically for
interactive notebook use, not the bin scripts).