codexphase-contrast-microscope-coherent-model.py
phase-contrast-microscope-coherent-model.py
"""Original zero-mode phase-contrast calculation; PNG is written to the CWD.
Periodic, lossless, coherent specimen; only the zero Fourier coefficient is
changed. This is not an annular-instrument simulation or historical data.
Tested with Python 3.14, matplotlib 3.10 and numpy 2.3.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def model(n=256, alpha=.5):
x = np.arange(n)/n
xx, yy = np.meshgrid(x, x)
phase = .06*(np.cos(2*np.pi*xx)+.4*np.cos(2*np.pi*yy))
field = np.exp(1j*phase)
c0 = field.mean()
image = lambda theta: abs(field+(alpha*np.exp(1j*theta)-1)*c0)**2
return x, phase, field, c0, image
def main():
alpha = .5
x, phase, field, c0, image = model(alpha=alpha)
bright = abs(field)**2
plus, minus = image(np.pi/2), image(-np.pi/2)
assert np.max(abs(bright-1)) < 1e-14
assert np.max(abs(phase)) <= .084000000001
expected_mean = 1-(1-alpha**2)*abs(c0)**2
assert abs(plus.mean()-expected_mean) < 1e-14
assert abs(minus.mean()-expected_mean) < 1e-14
plt.rcParams.update({"font.size": 10, "axes.spines.top": False,
"axes.spines.right": False})
fig = plt.figure(figsize=(12.6, 7.9), facecolor="white")
gs = fig.add_gridspec(2, 4, left=.06, right=.93, bottom=.12, top=.83,
height_ratios=[1, 1.02], hspace=.62, wspace=.50)
axes = [fig.add_subplot(gs[0, j]) for j in range(4)]
pm = axes[0].imshow(phase, origin="lower", extent=(0, 1, 0, 1),
cmap="RdBu_r", vmin=-.084, vmax=.084)
fig.colorbar(pm, ax=axes[0], fraction=.046, pad=.03)
axes[0].set_title("Chosen phase (rad)")
for ax, data, title in zip(axes[1:], [bright, plus/alpha**2, minus/alpha**2],
["Ideal bright field", r"Reference $\theta=+\pi/2$", r"Reference $\theta=-\pi/2$"]):
im = ax.imshow(data, origin="lower", extent=(0, 1, 0, 1),
cmap="gray", vmin=.6, vmax=1.4)
ax.set_title(title)
fig.colorbar(im, ax=axes[1:], fraction=.019, pad=.025, label="Relative intensity")
for j, ax in enumerate(axes):
ax.set(xlabel="x (periods)", ylabel="y (periods)" if j == 0 else "",
xticks=[0, .5, 1], yticks=[0, .5, 1])
cut = fig.add_subplot(gs[1, :2])
cut.plot(x, plus[0]/alpha**2, color="#265b9a", label=r"Exact $\theta=+\pi/2$")
cut.plot(x, 1+2*phase[0]/alpha, "--", color="#d38324", label=r"First order $1+2\phi/a$")
cut.plot(x, minus[0]/alpha**2, color="#7c3e82", label=r"Exact $\theta=-\pi/2$")
cut.axhline(1, color=".45", lw=1, label="Ideal bright field")
cut.set(xlabel="x along y = 0 (periods)", ylabel="Relative intensity", xlim=(0, 1), ylim=(.6, 1.42))
cut.set_title("Image line cut: the same phase pattern, opposite contrast")
cut.legend(fontsize=9, loc="lower center", ncol=2)
response = fig.add_subplot(gs[1, 2:])
theta = np.linspace(-np.pi, np.pi, 401)
exact = abs(field[0, 0]+(alpha*np.exp(1j*theta)-1)*c0)**2/alpha**2
response.plot(theta, exact, color="#265b9a", label="Exact, pattern maximum")
response.plot(theta, 1+2*.084*np.sin(theta)/alpha, "--", color="#d38324", label="First order")
response.axhline(1, color=".65", lw=.8)
response.set(xlabel=r"Reference shift $\theta$", ylabel="Relative intensity", xlim=(-np.pi, np.pi), ylim=(.6, 1.42))
response.set_xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
[r"$-\pi$", r"$-\pi/2$", "0", r"$\pi/2$", r"$\pi$"])
response.set_title("A quarter-cycle shift gives the largest linear response")
response.legend(fontsize=9, loc="lower center")
fig.suptitle("Turning phase variations into brightness", fontsize=18, y=.97)
fig.text(.5, .915, r"$\phi=0.06[\cos(2\pi x)+0.4\cos(2\pi y)]$, $a=0.5$, $E=e^{i\phi}+(ae^{i\theta}-1)\langle e^{i\phi}\rangle$",
ha="center", fontsize=12)
fig.text(.5, .875, "Original coherent zero-mode model; synthetic pattern, no absorption in the specimen and no measured data",
ha="center", color=".3", fontsize=10)
fig.text(.5, .035, "Brightness is normalized to the empty-field intensity: 1 for bright field, a² = 0.25 with the reference plate.",
ha="center", fontsize=10)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=110, facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()