Skip to content

Sample vs. reference signatures

Every signature snipe writes carries a type. A signature is either a Sample — the k-mers of a sequencing run — or a Reference — the k-mers of a genome you are measuring that run against. snipe records this type in the .snipesig header and uses it to decide which comparisons are meaningful and which it will reject.

The type is an enum with exactly two values, and Sample is the default:

  • Sample — a signature built from sequencing data (WGS, RNA-seq, and the like). This is what sketch --sample produces, and what you get whenever you sketch reads.
  • Reference — a signature built from a reference genome or database. This is what sketch --genome produces; it also stores per-k-mer genomic coordinates alongside the edgemers.

Both roles use the same .snipesig container and the same edgemer machinery — the type is metadata that travels with the signature, not a different file format. You can see it any time with info, which prints Type: Sample or Type: Reference. On export, the type is carried into the signature name: a sample gets a -snipesample suffix, a reference -snipereference.

snipe’s quality control scores a sample against a reference — without aligning the reads. qc takes one --reference and one or more --sample signatures, then asks how much of the sample’s content the reference explains, and vice versa. The two directions are different questions:

  • Reference coverage — of the reference’s unique edgemers, how many appear in the sample. This measures how completely your run covers the genome.
  • Sample coverage — of the sample’s unique edgemers, how many appear in the reference. This measures how much of your run is on-target versus contamination or novel content.

Both numbers fall out of the same intersection, but they only make sense because snipe knows which signature is the yardstick and which is the thing being measured. That asymmetry is what the type captures. The edgemer-based metrics — sequencing-error rate, mutation rate, and the rest — are built on this sample-against-reference framing.

Set operations gate on type. snipe permits the combinations that carry a coherent meaning and rejects the ones that would silently produce confused output. There are three regimes.

Intersection accepts every combination of types. The canonical case is Sample ∩ Reference — “what fraction of my sample is in the reference” — but Reference ∩ Sample, Sample ∩ Sample, and Reference ∩ Reference are all allowed too.

Union, sum, and merge require the same type on both sides. These operations gather edgemers from both inputs into one pooled signature, so combining a sample with a reference would blend measurements with a yardstick — semantically confused. Pool samples with samples, references with references.

Difference is first-signature-wins (“keep what is in the first, drop what the rest contain”), and it deliberately allows cross-type pairs. sample.difference(reference) is the canonical contamination / off-target detection pattern: strip everything the reference explains and inspect what remains.

OperationSame typeSample vs. ReferenceShape
intersectallowedallowedkeep shared edgemers
union / sum / mergeallowedrejectedpool both inputs
differenceallowedallowedfirst minus the rest

When you attempt a rejected pairing, snipe fails loudly rather than guessing — a union of a sample and a reference stops with a clear Incompatible signature types error naming both types and telling you the operation needs the same type. (All operations also still require matching k1, k2, and scale; the type check runs on top of those.)