codextem-objective-aperture-bright-dark-paths.py
tem-objective-aperture-bright-dark-paths.py
"""Functional TEM aperture selection, not a ray trace or measured apparatus.
Tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
Run from _media; writes the script basename with a .png extension to CWD.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, Rectangle
CENTRAL = '#1769aa'
DIFFRACTED = '#c26413'
GREY = '#687380'
def arrow(ax, start, end, color, **kwargs):
ax.annotate('', xy=end, xytext=start,
arrowprops=dict(arrowstyle='->', lw=2.0, color=color, **kwargs))
def panel(ax, dark):
ax.set_xlim(-2.5, 3.0)
ax.set_ylim(0.0, 10.5)
ax.axis('off')
ax.set_title('Dark field' if dark else 'Bright field', fontsize=15, weight='bold')
arrow(ax, (0, 10.1), (0, 9.4), GREY)
ax.text(0.25, 9.85, 'Incident beam', fontsize=10, va='center')
ax.add_patch(Rectangle((-1.3, 9.0), 2.6, 0.25,
facecolor='#e1e7e9', edgecolor=GREY))
ax.text(-2.4, 9.13, 'Specimen', fontsize=10, va='center')
arrow(ax, (0, 9.0), (0, 8.35), GREY)
ax.add_patch(FancyBboxPatch((-1.3, 7.3), 2.6, 0.9,
boxstyle='round,pad=0.1',
facecolor='#f1f4f7', edgecolor=GREY))
ax.text(0, 7.75, 'Objective lens\nangles → positions',
ha='center', va='center', fontsize=10)
ax.plot([0, 0], [7.2, 5.05], color=CENTRAL, lw=2.2)
ax.plot([0.35, 1.65], [7.2, 5.05], color=DIFFRACTED, lw=2.2)
ax.text(-0.15, 6.25, '0', color=CENTRAL, ha='right', fontsize=12)
ax.text(1.1, 6.25, 'g', color=DIFFRACTED, fontsize=12)
# Schematic cross-section of the physical aperture at a diffraction plane.
ax.axhline(4.9, color='#bcc3ca', lw=1, ls=':', zorder=0)
ax.text(-2.4, 5.45, 'Back focal\nplane', fontsize=10, va='center')
passed = 1.65 if dark else 0.0
rejected = 0.0 if dark else 1.65
opening = 0.28
for lo, hi in [(-1.1, passed - opening), (passed + opening, 2.35)]:
ax.add_patch(Rectangle((lo, 4.72), hi - lo, 0.35,
facecolor=GREY, edgecolor=GREY, zorder=5))
ax.scatter([0, 1.65], [4.9, 4.9], s=38,
color=[CENTRAL, DIFFRACTED], zorder=6)
ax.text(2.55, 4.9, 'Aperture', fontsize=10, va='center', rotation=90)
ax.scatter([rejected], [4.47], marker='x', s=65, color='#a02d2d', zorder=7)
ax.text(rejected - 0.12, 4.0, 'Blocked', color='#a02d2d',
fontsize=10, ha='right' if dark else 'center')
color = DIFFRACTED if dark else CENTRAL
arrow(ax, (passed, 4.68), (passed, 3.35), color)
ax.add_patch(FancyBboxPatch((-0.8, 2.45), 3.0, 0.7,
boxstyle='round,pad=0.1',
facecolor='white', edgecolor=color))
ax.text(0.7, 2.8, 'Image relay', fontsize=11, ha='center', va='center')
arrow(ax, (0.7, 2.35), (0.7, 1.65), color)
ax.add_patch(Rectangle((-0.8, 0.85), 3.0, 0.65,
facecolor='#fff8ee' if dark else '#edf5fc',
edgecolor=color))
ax.text(0.7, 1.18, 'Specimen image from g' if dark else 'Specimen image from 0',
fontsize=10, color=color, ha='center', va='center')
def main():
plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 11})
fig, axes = plt.subplots(1, 2, figsize=(10, 6.3), dpi=120, facecolor='white')
panel(axes[0], dark=False)
panel(axes[1], dark=True)
fig.suptitle('The aperture selects a beam direction', fontsize=17, y=0.98)
fig.text(0.5, 0.025, 'Functional paths only • 0: central beam; g: chosen diffracted beam • not to scale',
ha='center', fontsize=10, color=GREY)
fig.subplots_adjust(top=0.86, bottom=0.065, left=0.025, right=0.98, wspace=0.12)
fig.savefig(Path(__file__).stem + '.png', facecolor='white', transparent=False)
plt.close(fig)
if __name__ == '__main__':
main()