codexmicroscope-objective-eyepiece-conjugates.py
microscope-objective-eyepiece-conjugates.py
"""Draw an original finite-conjugate thin-lens microscope; Python 3.14.
Run from _media; output is the generator basename with a .png suffix.
Heights are enlarged by the same factor for both image and object.
"""
from pathlib import Path
import matplotlib.pyplot as plt
def main():
plt.rcParams.update({'font.size': 11, 'font.family': 'DejaVu Sans'})
fig, ax = plt.subplots(figsize=(11, 5.6), dpi=100, facecolor='white')
ax.set_facecolor('white')
ax.set_xlim(-24, 118)
ax.set_ylim(-12, 13)
ax.axis('off')
fig.suptitle('A real intermediate image, then relaxed-eye viewing', fontsize=16, y=0.96)
ax.axhline(0, color='#737373', linewidth=1)
ax.text(113, 0.5, 'Optical\naxis', ha='center', fontsize=10)
for x, label, focal in [(0, 'Objective', 10), (85, 'Eyepiece', 25)]:
ax.annotate('', xy=(x, 8.6), xytext=(x, -8.6),
arrowprops={'arrowstyle': '<->', 'lw': 2, 'color': '#34495e'})
ax.text(x, 10.6, label, ha='center', fontsize=12, fontweight='bold')
ax.text(x, 9.1, f'f = {focal} mm', ha='center')
for x in [-10, 10, 60]:
ax.plot(x, 0, 'o', color='#34495e', markersize=4)
ax.annotate('', xy=(-12, 1), xytext=(-12, 0),
arrowprops={'arrowstyle': '-|>', 'lw': 2.5, 'color': '#207a47'})
ax.text(-15, 3.5, 'Object', ha='center', color='#207a47', fontweight='bold')
ax.axvline(60, ymin=0.14, ymax=0.64, color='#207a47', linestyle=':', lw=1.5)
ax.annotate('', xy=(60, -5), xytext=(60, 0),
arrowprops={'arrowstyle': '-|>', 'lw': 2.5, 'color': '#207a47'})
ax.text(59, 5.9, 'Real intermediate image\n5 times taller, inverted',
ha='center', color='#207a47', fontsize=11)
# Two independent paraxial rays from the same object point.
# Thin-lens slope update is t_out = t_in - y/f.
for intercept, color in [(1.0, '#c34737'), (0.0, '#2868ac')]:
slope_in = (intercept - 1) / 12
slope_obj = slope_in - intercept / 10
y_image = intercept + slope_obj * 60
y_eye = intercept + slope_obj * 85
slope_eye = slope_obj - y_eye / 25
ax.plot([-12, 0, 60, 85, 112],
[1, intercept, y_image, y_eye, y_eye + slope_eye * 27],
color=color, linewidth=2)
ax.annotate('', xy=(108, y_eye + slope_eye * 23),
xytext=(101, y_eye + slope_eye * 16),
arrowprops={'arrowstyle': '->', 'color': color, 'lw': 2})
ax.text(103, -9.2, 'Parallel rays from\none image point', ha='center', fontsize=10)
for left, right, y, label in [(-12, 0, -10.6, '12 mm'),
(0, 60, -10.6, '60 mm'),
(60, 85, -10.6, '25 mm')]:
ax.annotate('', xy=(left, y), xytext=(right, y),
arrowprops={'arrowstyle': '<->', 'color': '#555555', 'lw': 1})
ax.text((left + right) / 2, y - 0.7, label, ha='center', va='top')
fig.text(0.5, 0.095, 'Detector at the intermediate image: m = −5. Eyepiece viewing: Mₑ = 250/25 = 10.',
ha='center', fontsize=11)
fig.text(0.5, 0.049, 'Total signed angular magnification: −50. Toy geometry; vertical heights enlarged.',
ha='center', fontsize=11)
fig.subplots_adjust(left=0.035, right=0.97, bottom=0.19, top=0.86)
fig.savefig(Path(__file__).stem + '.png', facecolor='white', transparent=False)
plt.close(fig)
if __name__ == '__main__':
main()