codexgouy-four-step-interferometry.py
gouy-four-step-interferometry.py
"""Synthetic phase-stepped Gaussian interferometry; save the PNG to cwd."""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def frames(zeta):
zeta = np.asarray(zeta)
phase = -np.arctan(zeta)
signal = 1 / (1 + zeta**2)
background = 1 + signal
amplitude = 2 * np.sqrt(signal)
steps = np.arange(4) * np.pi / 2
return background[..., None] + amplitude[..., None] * np.cos(phase[..., None] + steps)
def recover(intensities):
return np.arctan2(intensities[..., 3] - intensities[..., 1],
intensities[..., 0] - intensities[..., 2])
def main():
zeta = np.linspace(-3, 3, 601)
intensities = frames(zeta)
np.testing.assert_allclose(recover(intensities), -np.arctan(zeta), atol=1e-14)
np.testing.assert_allclose(frames(1), [2.5, 2.5, .5, .5], atol=1e-14)
np.testing.assert_allclose(frames(-1), [2.5, .5, .5, 2.5], atol=1e-14)
assert np.all(intensities >= -1e-14)
np.testing.assert_allclose(intensities[:, 0], intensities[::-1, 0], atol=1e-14)
plt.rcParams.update({"font.size": 11, "figure.facecolor": "white", "axes.facecolor": "white"})
fig, axes = plt.subplots(1, 2, figsize=(9.6, 4.2), dpi=100)
colors = ["#276FBF", "#BD5C18", "#518548", "#8E4C96"]
labels = ["δ = 0", "δ = π/2", "δ = π", "δ = 3π/2"]
for i, (color, label) in enumerate(zip(colors, labels)):
axes[0].plot(zeta, intensities[:, i], color=color, linewidth=1.8, label=label)
axes[0].set_title("Four phase-stepped intensities", fontsize=12)
axes[0].set_ylabel("Normalized detector intensity")
axes[0].set_ylim(-.12, 4.25)
axes[0].legend(fontsize=9, loc="upper right", framealpha=.95)
axes[1].plot(zeta, -np.arctan(zeta), color="#343434", linewidth=2,
label="Model: −atan(z/zR)")
sample = np.linspace(-3, 3, 25)
axes[1].scatter(sample, recover(frames(sample)), marker="o", s=24,
facecolor="white", edgecolor="#276FBF", zorder=3,
label="Retrieved from four frames")
for location in [-1, 1]:
axes[1].axvline(location, color="#777777", linestyle=":", linewidth=.8)
axes[1].set_yticks([-np.pi / 2, -np.pi / 4, 0, np.pi / 4, np.pi / 2],
["−π/2", "−π/4", "0", "π/4", "π/2"])
axes[1].set_ylim(-1.65, 1.65)
axes[1].set_title("Recovered relative phase", fontsize=12)
axes[1].set_ylabel("Phase (rad)")
axes[1].legend(fontsize=9, loc="upper right")
for ax in axes:
ax.set_xlim(-3, 3)
ax.set_xlabel("Axial coordinate z/zR")
ax.grid(alpha=.18)
ax.spines[["top", "right"]].set_visible(False)
fig.suptitle("Synthetic Gaussian/reference interferometry", fontsize=13, y=.98)
fig.text(.5, .025, "The δ = 0 frame is identical at z = ±zR; the quadratures determine the phase sign.",
ha="center", fontsize=10)
fig.subplots_adjust(left=.075, right=.98, top=.84, bottom=.18, wspace=.30)
output = Path(__file__).with_suffix(".png").name
fig.savefig(output, dpi=100, facecolor="white")
plt.close(fig)
print(f"{output}: 960×420; all phase-retrieval and frame-symmetry checks passed")
if __name__ == "__main__":
main()