codexreplica-plating-colony-map-retrieval.py
replica-plating-colony-map-retrieval.py
"""Original colony-map logic. Run from _media; Python 3.14, matplotlib 3.10.7."""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Polygon, Rectangle
BLUE = "#236c91"
ORANGE = "#bb6615"
RED = "#b44b52"
GRAY = "#66727a"
ALL_SITES = {f"{r}{c}" for r in "ABCD" for c in range(1, 5)}
ABSENT = {"A4", "D1", "D4"}
MASTER = ALL_SITES - ABSENT
UNRESOLVED = {"B2", "C3"}
CONTROL = MASTER - UNRESOLVED
CANDIDATES = {"A2", "C1"}
OMISSION = CONTROL - CANDIDATES
def panel(ax, x, title, growing, index):
ax.text(x + 1.5, 5.60, title, ha="center", fontsize=13, weight="bold")
ax.text(x + 1.5, 5.27, f"{len(growing)} growing sites", ha="center", fontsize=11)
ax.add_patch(Rectangle((x - .45, .97), 3.9, 3.95, fc="#fafcfd", ec=GRAY, lw=1.3))
ax.add_patch(Polygon([(x - .3, 5.05), (x -.1, 5.05), (x -.2, 4.84)], color=GRAY))
for c in range(1, 5):
ax.text(x + c - 1, 4.57, str(c), ha="center", color=GRAY)
for row, r in enumerate("ABCD"):
y = 4.12 - .85 * row
ax.text(x - .70, y, r, ha="center", va="center", color=GRAY)
for c in range(1, 5):
site, cx = f"{r}{c}", x + c - 1
ax.add_patch(Circle((cx, y), .21, fc="white", ec="#dce2e6", lw=1))
if site in growing:
ax.add_patch(Circle((cx, y), .17, fc=BLUE, ec="white", lw=.5))
elif site in ABSENT:
ax.plot([cx -.11, cx +.11], [y, y], color=GRAY, lw=2)
elif site in UNRESOLVED and index > 0:
ax.plot([cx -.11, cx +.11], [y -.11, y +.11], color=RED, lw=2)
ax.plot([cx -.11, cx +.11], [y +.11, y -.11], color=RED, lw=2)
if site in CANDIDATES:
ax.add_patch(Circle((cx, y), .27, fc="none", ec=ORANGE, lw=2))
def main():
assert len(MASTER) == 13 and len(CONTROL) == 11 and len(OMISSION) == 9
assert CONTROL - OMISSION == CANDIDATES
assert MASTER - CONTROL == UNRESOLVED
plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 11})
fig = plt.figure(figsize=(11, 7.1), dpi=110, facecolor="white")
ax = fig.add_axes([.04, .04, .93, .84])
ax.set(xlim=(-.9, 12.5), ylim=(-2.05, 5.95), aspect="equal")
ax.axis("off")
fig.suptitle("An empty replica site has more than one explanation", y=.97, fontsize=18)
fig.text(.5, .92, "Constructed 4 × 4 example • matching orientation • X is an assumed nutrient",
ha="center", fontsize=11, color=GRAY)
panel(ax, 0, "Master M", MASTER, 0)
panel(ax, 4.45, "Control C", CONTROL, 1)
panel(ax, 8.90, "Without X: S", OMISSION, 2)
ax.text(5.9, .57, "Blue disc = growth Orange ring = retrieval candidate ▲ = orientation mark",
ha="center", fontsize=10)
rows = [
(ORANGE, "A2, C1", "Present on master; grows on control; fails without X",
"Recover MASTER source; retest with and without X"),
(RED, "B2, C3", "Present on master; no growth even on control",
"Unresolved transfer or viability; not an X-requirement call"),
(GRAY, "A4, D1, D4", "Empty on master and both replicas",
"No source colony to recover at that location"),
]
for i, (color, sites, evidence, action) in enumerate(rows):
y = -.10 - .63 * i
ax.plot([-.6, 12.35], [y +.29, y +.29], color="#e0e5e8", lw=1)
ax.text(-.5, y, sites, color=color, weight="bold", va="center", fontsize=11)
ax.text(2.05, y +.06, evidence, va="center", fontsize=10)
ax.text(2.05, y -.19, action, va="center", fontsize=10, color=color)
ax.text(5.9, -1.94, "7 empty omission sites = 2 candidates + 2 unresolved + 3 absent sources",
ha="center", fontsize=11)
fig.savefig(Path.cwd() / f"{Path(__file__).stem}.png", facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()