How to Build Plasmid Engineering Workbench with Circular Mapping, Restriction Analysis, Virtual Gels, and Primer Design
In this tutorial, we construct a Google Colab-native plasmid workbench that recreates the core concepts of SpliceCraft inside an interactive pocket book surroundings. Instead of counting on a terminal-based TUI, we use Biopython, NumPy, and Matplotlib to load plasmid information, normalize annotated genomic options, render round and linear plasmid maps, compute sequence statistics, analyze restriction enzyme reduce websites, simulate digital digests, scan open studying frames, translate CDS options, design primers, and apply sequence edits programmatically. We start with an artificial offline plasmid so the workflow runs reliably with out exterior dependencies, whereas nonetheless supporting non-obligatory NCBI GenBank fetching and native GenBank uploads.
strive:
import Bio
besides ImportError:
import subprocess, sys
subprocess.run([sys.executable, "-m", "pip", "install", "-q", "biopython"], verify=True)
import math, io, textwrap, random
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
from Bio import Entrez, SeqIO
from Bio.Seq import Seq
from Bio.SeqFile import SeqFile
from Bio.SeqCharacteristic import SeqCharacteristic, FeatureLocation
from Bio.Restriction import RestrictionBatch, Analysis, CommOnly
strive:
from Bio.SeqUtils import MeltingTemp as _mt
besides Exception:
_mt = None
EMAIL = "[email protected]"
ACCESSION = "L09137"
USE_NCBI = False
Entrez.e mail = EMAIL
TYPE_COLORS = {
"CDS": "#d1495b", "gene": "#c1666b", "rep_origin": "#2e86ab",
"promoter": "#e3a008", "terminator": "#8d99ae", "misc_feature": "#6a994e",
"primer_bind": "#9d4edd", "protein_bind": "#457b9d", "supply": "#adb5bd",
}
def _color(ftype): return TYPE_COLORS.get(ftype, "#6a994e")
MCS = "GAATTCGAGCTCGGTACCCGGGGATCCTCTAGAGTCGACCTGCAGGCATGCAAGCTT"
def _synthetic_plasmid():
random.seed(19)
physique = "".be part of(random.alternative("ACGT") for _ in vary(2686))
seq = physique[:400] + MCS + physique[400:]
rec = SeqFile(Seq(seq), id="pDEMO", title="pDEMO",
description="Synthetic offline demo plasmid (SpliceCraft-Colab)")
rec.annotations["topology"] = "round"
rec.annotations["molecule_type"] = "ds-DNA"
def feat(s, e, strand, ftype, label):
f = SeqCharacteristic(FeatureLocation(s, e, strand=strand), sort=ftype)
f.qualifiers["label"] = [label]; return f
rec.options += [
feat(400, 400 + len(MCS), 1, "misc_feature", "MCS"),
feat(700, 1561, 1, "CDS", "AmpR"),
feat(1700, 2260, -1, "rep_origin", "ori"),
feat(2350, 2686, 1, "CDS", "lacZ-alpha"),
]
rec.options[1].qualifiers["product"] = ["beta-lactamase (demo)"]
return rec
def load_record():
if USE_NCBI and "@" in EMAIL and not EMAIL.startswith("you@"):
strive:
h = Entrez.efetch(db="nucleotide", id=ACCESSION,
rettype="gbwithparts", retmode="textual content")
rec = SeqIO.learn(h, "genbank"); h.shut()
print(f"✓ Fetched {ACCESSION} from NCBI: {rec.description}")
return rec
besides Exception as e:
print(f"
NCBI fetch failed ({e}); utilizing artificial demo plasmid.")
else:
print("
USE_NCBI is off (or EMAIL not set) — utilizing artificial demo plasmid.")
return _synthetic_plasmid()
def upload_gb():
from google.colab import information
up = information.add()
title = subsequent(iter(up))
return SeqIO.learn(io.StringIO(up[name].decode()), "genbank")
def label_of(f):
for okay in ("label", "gene", "product", "notice"):
if okay in f.qualifiers: return f.qualifiers[k][0]
return f.sort
def norm_features(rec, skip_source=True, min_len=0):
L = len(rec.seq); out = []
for f in rec.options:
if skip_source and f.sort in ("supply",): proceed
s = int(f.location.begin); e = int(f.location.finish)
if (e - s) < min_len: proceed
out.append(dict(begin=s % L, finish=e % L, strand=f.location.strand or 1,
sort=f.sort, label=label_of(f), colour=_color(f.sort)))
return out
We arrange the pocket book surroundings, set up Biopython when wanted, and import the scientific, plotting, and sequence-analysis libraries required for the workflow. We outline the plasmid configuration, function colour palette, a number of cloning web site, and fallback artificial plasmid so the tutorial works even with out an web connection. We additionally create helper capabilities to load information from NCBI or GenBank information and normalize annotated sequence options into drawable metadata.
def _ang(bp, L): return math.pi/2 - 2*math.pi*(bp/L)
def _pt(bp, r, L):
a = _ang(bp, L); return r*math.cos(a), r*math.sin(a)
def _arc(s, e, r, L, n=240):
if e < s: e += L
bps = np.linspace(s, e, max(2, int(n*(e-s)/L)+2))
return ([r*math.cos(_ang(b, L)) for b in bps],
[r*math.sin(_ang(b, L)) for b in bps])
def gc_percent(seq):
s = str(seq).higher(); n = len(s) or 1
return 100.0 * (s.depend("G") + s.depend("C")) / n
def circular_map(rec, title=None, show_gc=True):
L = len(rec.seq); feats = norm_features(rec)
fig, ax = plt.subplots(figsize=(8, 8)); R = 1.0
if show_gc:
w = max(30, L // 120); step = max(1, w // 2); imply = gc_percent(rec.seq)
s = str(rec.seq).higher()
for i in vary(0, L, step):
win = s[i:i+w] or s[i:] + s[:(i+w) % L]
dev = (gc_percent(win) - imply) / 100.0
rr = 0.72 + dev * 0.9
x0, y0 = _pt(i, 0.72, L); x1, y1 = _pt(i, rr, L)
ax.plot([x0, x1], [y0, y1],
colour="#2e86ab" if dev >= 0 else "#d1495b", lw=1, alpha=0.5, zorder=1)
xs, ys = _arc(0, L, R, L); ax.plot(xs, ys, colour="#333", lw=2.5, zorder=2)
step = max(1, spherical(L/12/100)*100) or max(1, L//12)
for t in vary(0, L, step):
x0, y0 = _pt(t, R*1.015, L); x1, y1 = _pt(t, R*1.045, L)
ax.plot([x0, x1], [y0, y1], colour="#999", lw=1, zorder=2)
lx, ly = _pt(t, R*1.10, L)
ax.textual content(lx, ly, f"{t:,}", ha="middle", va="middle", fontsize=7, colour="#777")
for f in feats:
outer = f["strand"] >= 0
rr = R + 0.09 if outer else R - 0.09
xs, ys = _arc(f["start"], f["end"], rr, L)
ax.plot(xs, ys, colour=f["color"], lw=10, solid_capstyle="butt", alpha=0.9, zorder=3)
tip = f["end"] if outer else f["start"]
a = _ang(tip, L); tx, ty = rr*math.cos(a), rr*math.sin(a)
d = -1 if outer else 1
tanx, tany = -math.sin(a)*d, math.cos(a)*d
px, py = math.cos(a), math.sin(a)
ln, wd = 0.055, 0.052
ax.add_patch(Polygon([(tx+tanx*ln, ty+tany*ln),
(tx+px*wd, ty+py*wd),
(tx-px*wd, ty-py*wd)],
colour=f["color"], zorder=4))
span = (f["end"] - f["start"]) % L
mid = (f["start"] + span/2) % L
lx, ly = _pt(mid, (rr + 0.16) if outer else (rr - 0.16), L)
ax.textual content(lx, ly, f["label"], ha="middle", va="middle",
fontsize=8.5, colour=f["color"], weight="daring", zorder=5)
circ = "round" if rec.annotations.get("topology") == "round" else "linear"
ax.textual content(0, 0.05, title or rec.title, ha="middle", va="middle", fontsize=15, weight="daring")
ax.textual content(0, -0.07, f"{L:,} bp · {gc_percent(rec.seq):.1f}% GC · {circ}",
ha="middle", va="middle", fontsize=9.5, colour="#555")
ax.set_xlim(-1.45, 1.45); ax.set_ylim(-1.45, 1.45)
ax.set_aspect("equal"); ax.axis("off"); plt.tight_layout(); plt.present()
We implement the round plasmid visualization by mapping base-pair positions to angular coordinates on a clockwise ring. We calculate GC content material, draw the plasmid spine, add tick marks, render function arcs, and connect directional arrowheads to clearly point out strand orientation. We additionally add labels and central sequence metadata in order that the map is each biologically informative and visually readable in Colab.
def linear_map(rec):
L = len(rec.seq); feats = norm_features(rec)
fig, ax = plt.subplots(figsize=(11, 2.6))
ax.hlines(0, 0, L, colour="#333", lw=2)
for f in feats:
y = 0.35 if f["strand"] >= 0 else -0.35
s, e = f["start"], f["end"]
segs = [(s, e)] if e >= s else [(s, L), (0, e)]
for a, b in segs:
ax.annotate("", xy=(b if f["strand"] >= 0 else a, y),
xytext=(a if f["strand"] >= 0 else b, y),
arrowprops=dict(arrowstyle="-|>", colour=f["color"], lw=7, alpha=0.85))
ax.textual content((s + ((e - s) % L)/2) % L, y + (0.28 if y > 0 else -0.28), f["label"],
ha="middle", va="middle", fontsize=8.5, colour=f["color"], weight="daring")
ax.set_xlim(-L*0.02, L*1.02); ax.set_ylim(-1, 1)
ax.set_yticks([]); ax.set_xlabel("place (bp)")
ax.set_title(f"{rec.title} — linear view", fontsize=11)
for sp in ("prime", "left", "proper"): ax.spines[sp].set_visible(False)
plt.tight_layout(); plt.present()
def gc_skew_plot(rec, window=None):
s = str(rec.seq).higher(); L = len(s)
window = window or max(50, L // 100); skew = []; run = 0
for i in vary(0, L, window):
w = s[i:i+window]; g, c = w.depend("G"), w.depend("C")
run += (g - c) / max(1, g + c); skew.append(run)
fig, ax = plt.subplots(figsize=(11, 2.6))
ax.plot(np.arange(len(skew))*window, skew, colour="#2e86ab")
ax.axhline(0, colour="#aaa", lw=0.8)
ax.set_title(f"Cumulative GC-skew — {rec.title} (min≈replication origin, max≈terminus)",
fontsize=10)
ax.set_xlabel("place (bp)"); ax.set_ylabel("cumulative skew")
plt.tight_layout(); plt.present()
def stats(rec):
seq = str(rec.seq).higher(); L = len(seq)
print(f"── {rec.title} ─────────────────────────────")
print(f"size : {L:,} bp")
print(f"GC content material : {gc_percent(seq):.2f} %")
print(f"A/T/G/C : {seq.depend('A')}/{seq.depend('T')}/{seq.depend('G')}/{seq.depend('C')}")
print(f"topology : {rec.annotations.get('topology', 'unknown')}")
print(f"options : {len(norm_features(rec))} annotated")
def _is_circular(rec): return rec.annotations.get("topology") == "round"
def find_sites(rec, enzymes=None):
batch = RestrictionBatch(enzymes) if enzymes else CommOnly
ana = Analysis(batch, rec.seq, linear=not _is_circular(rec))
return {str(okay): v for okay, v in ana.full().objects()}
def unique_cutters(rec, enzymes=None):
hits = find_sites(rec, enzymes)
return sorted([e for e, pos in hits.items() if len(pos) == 1])
def digest_fragments(rec, enzymes):
hits = find_sites(rec, enzymes)
cuts = sorted({p for pos in hits.values() for p in pos})
L = len(rec.seq)
if not cuts: return [L]
if _is_circular(rec):
frags = [cuts[i+1] - cuts[i] for i in vary(len(cuts)-1)]
frags.append(L - cuts[-1] + cuts[0])
else:
frags = [cuts[0]] + [cuts[i+1]-cuts[i] for i in vary(len(cuts)-1)] + [L - cuts[-1]]
return sorted((f for f in frags if f > 0), reverse=True)
def restriction_report(rec, enzymes=None):
hits = find_sites(rec, enzymes)
reducing = {e: p for e, p in hits.objects() if p}
print(f"── Restriction map of {rec.title} "
f"({'round' if _is_circular(rec) else 'linear'}) ──")
print(f"distinctive (single) cutters: {', '.be part of(unique_cutters(rec, enzymes)) or 'none'}")
print("reducing enzymes (web site depend):")
for e in sorted(reducing, key=lambda x: len(reducing[x])):
print(f" {e:<10} {len(reducing[e])}x at {reducing[e]}")
We construct a linear plasmid map that represents the identical annotated options alongside a base-pair axis for simpler positional inspection. We then compute the cumulative GC-skew to observe directional nucleotide bias that may point out replication-related construction. We additionally add sequence statistics and restriction-analysis utilities to summarize plasmid composition, enzyme reduce websites, distinctive cutters, and anticipated digest fragments.
def virtual_gel(named_digests):
lanes = [("Ladder", LADDER)] + checklist(named_digests.objects())
fig, ax = plt.subplots(figsize=(1.4*len(lanes)+1, 6))
ax.set_facecolor("#0b1021")
def y(sz): return math.log10(max(sz, 50))
for i, (title, frags) in enumerate(lanes):
for f in frags:
ax.hlines(y(f), i+0.62, i+1.38, colour="#e6f1ff",
lw=2 + min(4, f/1500), alpha=0.9)
if title == "Ladder":
ax.textual content(i+0.5, y(f), f"{f}", colour="#9db4d0", ha="proper",
va="middle", fontsize=7)
ax.textual content(i+1, y(max(LADDER))+0.15, title, colour="#e6f1ff",
ha="middle", fontsize=9, rotation=0)
ax.set_xlim(0.2, len(lanes)+0.5); ax.set_ylim(y(80), y(12000))
ax.invert_yaxis(); ax.axis("off")
ax.set_title("Virtual agarose gel", colour="#333", fontsize=11)
plt.tight_layout(); plt.present()
def find_orfs(rec, min_aa=50):
seq = rec.seq; L = len(seq); orfs = []
for strand, s in ((+1, seq), (-1, seq.reverse_complement())):
s = str(s)
for body in vary(3):
i = body
whereas i < L - 2:
if s[i:i+3] == "ATG":
j = i
whereas j < L - 2:
if s[j:j+3] in ("TAA", "TAG", "TGA"):
if (j - i)//3 >= min_aa:
orfs.append((strand, body, i, j+3, (j-i)//3))
i = j; break
j += 3
i += 3
return sorted(orfs, key=lambda o: -o[4])
def translate_feature(rec, label):
for f in rec.options:
if label.decrease() in label_of(f).decrease() and f.sort in ("CDS", "gene"):
sub = f.location.extract(rec.seq)
prot = sub.translate(desk=11, to_stop=True)
return str(prot)
return None
def _tm(primer):
if _mt just isn't None:
strive: return float(_mt.Tm_NN(Seq(primer)))
besides Exception: cross
p = primer.higher()
return 2*(p.depend("A")+p.depend("T")) + 4*(p.depend("G")+p.depend("C"))
def design_primers(rec, begin, finish, target_tm=60.0, lo=18, hello=30):
seq = str(rec.seq)
def tune(sub, rev=False):
finest = None
for n in vary(lo, hello+1):
p = sub[-n:] if rev else sub[:n]
if rev: p = str(Seq(p).reverse_complement())
rating = abs(_tm(p) - target_tm)
if finest is None or rating < finest[0]: finest = (rating, p)
return finest[1]
fwd = tune(seq[start:start+hi])
rev = tune(seq[end-hi:end], rev=True)
print(f"── Primers to amplify {begin}–{finish} ({end-start} bp) ──")
print(f" FWD 5'-{fwd}-3' len {len(fwd)} Tm {_tm(fwd):.1f}°C GC {gc_percent(fwd):.0f}%")
print(f" REV 5'-{rev}-3' len {len(rev)} Tm {_tm(rev):.1f}°C GC {gc_percent(rev):.0f}%")
print(f" amplicon: {end-start} bp")
return fwd, rev
We simulate a digital agarose gel by plotting digest fragment sizes beside a DNA ladder on a log-scaled migration axis. We scan the plasmid sequence in all six studying frames to establish lengthy open studying frames and rank them by amino-acid size. We additionally translate annotated CDS options and design primers round a specific area by tuning primer size towards a goal melting temperature.
def edit_sequence(rec, pos, delete=0, insert=""):
s = str(rec.seq)
new = s[:pos] + insert + s[pos+delete:]
out = SeqFile(Seq(new), id=rec.id, title=rec.title + "_edit",
description=rec.description + f" [edit @{pos}:-{delete}+{len(insert)}]")
out.annotations = dict(rec.annotations)
shift = len(insert) - delete
for f in rec.options:
s0, e0 = int(f.location.begin), int(f.location.finish)
if s0 >= pos: s0 += shift
if e0 > pos: e0 += shift
if 0 <= s0 < e0 <= len(new):
g = SeqCharacteristic(FeatureLocation(s0, e0, strand=f.location.strand), sort=f.sort)
g.qualifiers = dict(f.qualifiers); out.options.append(g)
return out
LIBRARY = {}
def add_to_library(rec): LIBRARY[rec.name] = rec; print(f"+ added {rec.title} (now {len(LIBRARY)} in library)")
def show_library():
for title, r in LIBRARY.objects():
print(f" {title:<16} {len(r.seq):>7,} bp {gc_percent(r.seq):.1f}% GC")
We implement a pure sequence-editing operate that helps insertion, deletion, and alternative whereas returning a brand new edited SeqFile. We protect plasmid annotations and shift downstream function coordinates so the edited assemble stays internally constant after sequence modifications. We additionally create a light-weight in-notebook plasmid library that lets us retailer and checklist authentic and edited information through the workflow.
if __name__ == "__main__":
rec = load_record()
add_to_library(rec)
print("n[1] Sequence statistics")
stats(rec)
print("n[2] Circular map (the signature SpliceCraft view)")
circular_map(rec)
print("n[3] Linear map + GC-skew")
linear_map(rec)
gc_skew_plot(rec)
print("n[4] Restriction evaluation")
restriction_report(rec, enzymes=["EcoRI", "BamHI", "HindIII", "PstI",
"SalI", "XbaI", "KpnI", "SacI", "SphI"])
print("n[5] Virtual digests on a gel")
virtual_gel({
"EcoRI": digest_fragments(rec, ["EcoRI"]),
"EcoRI+HindIII": digest_fragments(rec, ["EcoRI", "HindIII"]),
"BamHI+SalI": digest_fragments(rec, ["BamHI", "SalI"]),
})
print("n[6] Six-frame ORF scan (prime 5 by size)")
for strand, body, s, e, aa in find_orfs(rec, min_aa=40)[:5]:
print(f" strand {strand:+d} body {body} {s}-{e} {aa} aa")
print("n[7] Translate a CDS function")
prot = translate_feature(rec, "AmpR")
if prot: print(f" AmpR protein ({len(prot)} aa): {prot[:60]}...")
print("n[8] Design primers to amplify a area")
design_primers(rec, 400, 900)
print("n[9] Edit the sequence (insert a 6-bp tag) and re-map")
edited = edit_sequence(rec, pos=456, insert="CATCATCAT")
add_to_library(edited)
print("n[10] Library"); show_library()
print("nDone. Call any operate instantly, e.g. circular_map(edited).")
We run the complete guided demo by loading a plasmid file, including it to the library, and printing sequence statistics. We generate the round map, linear map, GC-skew plot, restriction report, digital gel, ORF scan, CDS translation, and primer design output in sequence. We end by enhancing the plasmid, storing the edited assemble, and exhibiting how the pocket book can act as a reusable plasmid workbench.
In conclusion, we accomplished the tutorial by turning a single pocket book right into a compact but useful plasmid-engineering workspace. We moved from sequence loading and function extraction to graphical plasmid visualization, GC-skew evaluation, restriction mapping, gel simulation, ORF discovery, primer design, and editable sequence manipulation. By maintaining every operation as a reusable Python operate, we made the workflow modular sufficient to examine actual GenBank information, modify plasmid sequences, evaluate edited constructs, and prolong the pocket book with extra cloning or annotation utilities. Overall, we demonstrated how we will translate the conduct of a terminal plasmid instrument right into a reproducible, visible, and notebook-friendly bioinformatics workflow.
Check out the FULL CODES here. Also, be happy to comply with us on Twitter and don’t overlook to be part of our 150k+ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.
Need to associate with us for selling your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar and so forth.? Connect with us
The submit How to Build Plasmid Engineering Workbench with Circular Mapping, Restriction Analysis, Virtual Gels, and Primer Design appeared first on MarkTechPost.
