joduor's picture
Deploy AlphaFold Adaptive API with morphic simulations and ESM-2
17b2088 verified
Raw
History Blame Contribute Delete
5.87 kB
"""
Morphic simulation engine.
Generates Gray-Scott reaction-diffusion patterns that self-assemble
based on protein properties — each protein produces a unique visual fingerprint.
"""
from __future__ import annotations
import hashlib, io, math
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
# ── Protein-property → Gray-Scott parameter mapping ───────────────────────────
FUNCTION_PALETTES = {
"enzyme": [(0.02, 0.055), "#00f5ff", "#0a0a2e"],
"receptor": [(0.035, 0.065), "#c084fc", "#0d0520"],
"transporter": [(0.025, 0.06), "#10b981", "#031a0e"],
"structural": [(0.04, 0.063), "#fbbf24", "#1a1000"],
"transcription": [(0.03, 0.057), "#f97316", "#1a0a00"],
"signaling": [(0.022, 0.051), "#3b82f6", "#00020f"],
"unknown": [(0.028, 0.058), "#94a3b8", "#0c0c14"],
}
def _protein_to_gs_params(
uniprot_id: str,
plddt: float,
length: int,
function: str,
) -> dict:
"""Map protein properties to Gray-Scott f/k parameters and palette."""
palette_key = function.lower() if function.lower() in FUNCTION_PALETTES else "unknown"
(f_base, k_base), fg_hex, bg_hex = FUNCTION_PALETTES[palette_key]
# pLDDT (0–100) shifts f: high confidence → more stable patterns
f = f_base + (plddt / 100 - 0.5) * 0.008
# Protein length shifts k: longer → finer pattern
k = k_base + min(length / 10000, 0.006)
# Unique seed from UniProt ID
seed = int(hashlib.md5(uniprot_id.encode()).hexdigest()[:8], 16) % 10000
return {"f": f, "k": k, "fg": fg_hex, "bg": bg_hex, "seed": seed}
def _run_gs(f: float, k: float, seed: int, steps: int = 3000, N: int = 128) -> np.ndarray:
"""Run Gray-Scott PDE on an N×N grid, return V concentration."""
rng = np.random.default_rng(seed)
U = np.ones((N, N), dtype=np.float32)
V = np.zeros((N, N), dtype=np.float32)
# Seed perturbation
cx, cy = N // 2, N // 2
r = max(4, N // 16)
U[cx - r:cx + r, cy - r:cy + r] = 0.5
V[cx - r:cx + r, cy - r:cy + r] = 0.25
V += rng.uniform(0, 0.01, (N, N)).astype(np.float32)
Du, Dv, dt = 0.16, 0.08, 1.0
for _ in range(steps):
Lu = (np.roll(U, 1, 0) + np.roll(U, -1, 0) +
np.roll(U, 1, 1) + np.roll(U, -1, 1) - 4 * U)
Lv = (np.roll(V, 1, 0) + np.roll(V, -1, 0) +
np.roll(V, 1, 1) + np.roll(V, -1, 1) - 4 * V)
uvv = U * V * V
U += dt * (Du * Lu - uvv + f * (1 - U))
V += dt * (Dv * Lv + uvv - (f + k) * V)
np.clip(U, 0, 1, out=U)
np.clip(V, 0, 1, out=V)
return V
def _hex_to_rgb(h: str) -> tuple:
h = h.lstrip("#")
return tuple(int(h[i:i+2], 16) / 255.0 for i in (0, 2, 4))
def generate_morphic_image(
uniprot_id: str,
plddt: float = 75.0,
length: int = 300,
function: str = "unknown",
protein_name: str = "",
width: int = 800,
height: int = 800,
) -> bytes:
"""
Return PNG bytes of a morphic simulation unique to this protein.
The pattern self-assembles based on protein properties.
"""
params = _protein_to_gs_params(uniprot_id, plddt, length, function)
V = _run_gs(params["f"], params["k"], params["seed"])
# Build custom colormap from protein palette
bg = _hex_to_rgb(params["bg"])
fg = _hex_to_rgb(params["fg"])
mid = tuple(min(1.0, c * 1.6) for c in fg)
cmap = LinearSegmentedColormap.from_list(
"protein", [bg, params["bg"], fg, mid, "#ffffff"], N=512
)
fig, ax = plt.subplots(figsize=(width / 100, height / 100), dpi=100)
fig.patch.set_facecolor(params["bg"])
ax.set_facecolor(params["bg"])
ax.imshow(V, cmap=cmap, interpolation="bilinear", aspect="auto", vmin=0, vmax=V.max())
ax.axis("off")
# Overlay protein ID and pLDDT badge
label = f"{uniprot_id} · pLDDT {plddt:.1f} · {length} aa"
ax.text(0.5, 0.03, label, transform=ax.transAxes, color=params["fg"],
fontsize=10, ha="center", va="bottom",
fontfamily="monospace", alpha=0.85)
if protein_name:
ax.text(0.5, 0.96, protein_name, transform=ax.transAxes, color="#ffffff",
fontsize=13, ha="center", va="top", fontweight="bold", alpha=0.9)
buf = io.BytesIO()
plt.savefig(buf, format="png", bbox_inches="tight", pad_inches=0,
facecolor=params["bg"])
plt.close(fig)
buf.seek(0)
return buf.read()
def generate_confidence_heatmap(
residue_scores: list[float],
uniprot_id: str,
protein_name: str = "",
width: int = 900,
height: int = 280,
) -> bytes:
"""Per-residue pLDDT heatmap with adaptive colour coding."""
arr = np.array(residue_scores, dtype=np.float32).reshape(1, -1)
cmap = LinearSegmentedColormap.from_list(
"plddt", ["#f97316", "#eab308", "#22c55e", "#1d4ed8"], N=256
)
fig, ax = plt.subplots(figsize=(width / 100, height / 100), dpi=100)
fig.patch.set_facecolor("#020817")
ax.set_facecolor("#020817")
im = ax.imshow(arr, cmap=cmap, aspect="auto", vmin=0, vmax=100)
ax.set_yticks([])
ax.tick_params(colors="#64748b", labelsize=8)
ax.set_xlabel("Residue index", color="#64748b", fontsize=9)
title = f"{protein_name} ({uniprot_id}) — per-residue pLDDT" if protein_name else f"{uniprot_id} — per-residue pLDDT"
ax.set_title(title, color="#94a3b8", fontsize=10, pad=8)
plt.colorbar(im, ax=ax, orientation="horizontal", fraction=0.04, pad=0.18,
label="pLDDT confidence").ax.xaxis.label.set_color("#64748b")
buf = io.BytesIO()
plt.savefig(buf, format="png", bbox_inches="tight", facecolor="#020817")
plt.close(fig)
buf.seek(0)
return buf.read()