codex/replica-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()