Bio core chromatogram analysis
Skill Pavel-Kravchenko/Bioinformatics/Skills/bio-core-chromatogram-analysis
Parse Sanger .ab1/.abi chromatograms with BioPython, extract Phred quality/trace channels, plot traces, quality-trim, and flag het double-peaks. Use for .ab1/.scf files or detecting het SNPs/mixed peaks.From its SKILL.md
npx -y skills add Pavel-Kravchenko/Bioinformatics --skill bio-core-chromatogram-analysisAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
8.1 KB, ~2.2k tokens by cl100k_base, as published. Nobody here has run it
Chromatogram Analysis: Sanger Sequencing
When to Use
- Verifying a plasmid, PCR product, or CRISPR edit from a Sanger
.ab1trace file - Trimming low-quality primer/dye-blob regions before using a Sanger read downstream
- Visualizing the four-color chromatogram trace alongside base calls
- Screening a trace for heterozygous positions (double peaks) or contamination
- Building a consensus sequence from overlapping forward/reverse Sanger reads
Version Compatibility
- biopython ≥ 1.81 (
Bio.SeqIOABI parser), numpy ≥ 1.24, matplotlib ≥ 3.7, Python ≥ 3.10
Prerequisites
pip install biopython numpy matplotlib- Familiarity with Phred quality scores and basic sequence I/O (see
bio-sequence-io-read-sequences)
Phred Quality Score Reference
| Q Score | Error Probability | Accuracy | Interpretation |
|---|---|---|---|
| 10 | 1/10 | 90% | Poor — avoid |
| 20 | 1/100 | 99% | Minimum acceptable |
| 30 | 1/1,000 | 99.9% | High confidence |
| 40 | 1/10,000 | 99.99% | Excellent |
Formula: Q = -10 × log₁₀(P_error). Practical thresholds: Q<20 = trim/re-sequence; Q20–29 = acceptable; Q≥30 = high confidence.
Sanger read quality profile: bases 1–25 are poor (primer artifact, dye blobs) — always trim; bases 25–700 form a high-quality plateau (Q>30 typical); bases 700+ decay sharply — trim the tail. Max practical read length is ~800 bp.
Goal: load an .ab1 trace and get sequence + per-base quality + raw fluorescence channels.
Approach: SeqIO.read(..., "abi") parses the ABIF container into a SeqRecord; quality lives in letter_annotations, raw trace data lives in annotations["abif_raw"] under standard ABIF tags.
from Bio import SeqIO
import numpy as np
def read_ab1(filepath):
"""Read an .ab1 chromatogram and return (SeqRecord, quality array)."""
record = SeqIO.read(filepath, "abi")
quals = np.array(record.letter_annotations["phred_quality"])
print(f"{filepath}: {len(record.seq)} bp, mean Q={quals.mean():.1f}, median Q={np.median(quals):.0f}")
return record, quals
def extract_traces(record):
"""Pull processed four-color trace channels and peak locations from an .ab1 SeqRecord."""
raw = record.annotations["abif_raw"]
base_order = raw.get("FWO_1", b"GATC").decode() # e.g. "GATC"
channels = {base: np.array(raw[f"DATA{9 + i}"]) for i, base in enumerate(base_order)} # DATA9-12 = processed
peak_locs = list(raw["PLOC1"]) # scan position of each called base
return channels, peak_locs
Key .ab1 Tags
| Tag | Content |
|---|---|
DATA9–DATA12 | Processed trace channels (baseline-corrected, order from FWO_1) |
DATA1–DATA4 | Raw trace channels |
PLOC1 / PLOC2 | Peak locations (scan numbers) |
PBAS1 / PBAS2 | Called bases (raw / edited) |
PCON1 / PCON2 | Quality values |
FWO_1 | Base order for channels (e.g. b'GATC') |
Note: .ab1 and .abi are the same format; .scf is an older MRC-LMB format. BioPython reads both via "abi".
Goal: visualize a chromatogram and trim low-quality ends. Approach: plot each color channel over a scan-position window, label called bases at their peak positions; trim by sliding a window of quality scores in from each end until the mean clears the threshold.
import matplotlib.pyplot as plt
TRACE_COLORS = {"A": "green", "C": "blue", "G": "black", "T": "red"}
def plot_chromatogram(channels, peak_locs, sequence, start=0, end=50, title="Chromatogram"):
"""Plot a four-color Sanger trace between base indices [start, end)."""
end = min(end, len(peak_locs))
scan_start = max(0, peak_locs[start] - 10)
scan_end = min(len(channels["A"]), peak_locs[end - 1] + 10)
x = np.arange(scan_start, scan_end)
fig, ax = plt.subplots(figsize=(14, 3.5))
for base, color in TRACE_COLORS.items():
ax.plot(x, channels[base][scan_start:scan_end], color=color, lw=0.8, label=base)
for i in range(start, end):
base = sequence[i]
ax.text(peak_locs[i], 0, base, ha="center", fontsize=7, fontweight="bold",
color=TRACE_COLORS.get(base, "gray"))
ax.legend(ncol=4, fontsize=8)
ax.set_xlabel("Scan position"); ax.set_ylabel("Fluorescence"); ax.set_title(title)
plt.tight_layout(); plt.show()
def trim_by_quality(sequence, quals, min_q=20, window=10):
"""Trim low-quality 5'/3' ends using a sliding-window mean quality (Sanger convention)."""
quals = np.asarray(quals)
n = len(quals)
start = 0
for i in range(n - window + 1):
if quals[i:i + window].mean() >= min_q:
start = i
break
end = n
for i in range(n - window, -1, -1):
if quals[i:i + window].mean() >= min_q:
end = i + window
break
return sequence[start:end], quals[start:end]
Goal: flag heterozygous positions (double peaks) directly from trace intensities. Approach: at each called base's peak scan position, compare the fluorescence of the called (primary) channel to the other three; if a secondary channel's intensity exceeds a ratio threshold of the primary, report it and map the base pair to an IUPAC ambiguity code.
IUPAC_AMBIGUITY = {
frozenset("AG"): "R", frozenset("CT"): "Y", frozenset("GC"): "S",
frozenset("AT"): "W", frozenset("GT"): "K", frozenset("AC"): "M",
}
def detect_heterozygous(channels, peak_locs, sequence, ratio_threshold=0.35):
"""Flag positions with a significant secondary peak (candidate het SNP or contamination)."""
results = []
for i, (pos, base) in enumerate(zip(peak_locs, sequence.upper())):
intensities = {b: channels[b][min(pos, len(channels[b]) - 1)] for b in "ATGC"}
primary = intensities.get(base, 0)
if primary == 0:
continue
for alt_base, alt_val in intensities.items():
if alt_base == base:
continue
ratio = alt_val / primary
if ratio >= ratio_threshold:
iupac = IUPAC_AMBIGUITY.get(frozenset([base, alt_base]), "N")
results.append({"position": i + 1, "called": base, "alt": alt_base,
"ratio": ratio, "iupac": iupac})
return results
Pitfalls
- Double peaks = heterozygosity OR contamination: two overlapping peaks of comparable height at one position — check surrounding context; genuine het SNPs form a consistent pattern across the read; contamination creates irregular double peaks throughout
- First 20–30 bases always low quality: this is normal (primer signal + dye artifacts) — never report variants from this region
- Why 4 dyes: each ddNTP (ddA, ddC, ddG, ddT) carries a different fluorescent dye; capillary electrophoresis separates by size, laser detects dye — the instrument reconstructs sequence from size+color
.ab1stores raw fluorescence, not just sequence: base calls are computed by KB Basecaller software; you can re-call from raw traces with third-party toolsDATA9–DATA12are processed;DATA1–DATA4are raw: use processed channels for visualization (baseline correction and color deconvolution already applied)- Sanger max read length ~800 bp: beyond this, band resolution collapses — design primers to place your region of interest in the Q>30 window (bases ~30–700)
ratio_thresholdindetect_heterozygousneeds tuning per instrument: 0.35 works for typical ABI 3730 traces but noisy runs may need 0.4–0.5 to avoid false positives
See Also
bio-sequence-io-read-sequences— general sequence file I/O with BioPythonbio-primer-design-primer-validation— validating primers used to generate the Sanger readbio-variant-calling-vcf-basics— downstream representation once a het call is confirmedbio-sequence-manipulation-seq-objects— working with the resultingSeq/SeqRecordobjects
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.