Source code for lenstest.foucault

# pylint: disable=invalid-name
# pylint: disable=too-many-arguments
# pylint: disable=consider-using-f-string
# pylint: disable=too-many-locals

"""
Generate Foucault Knife Edge images (Foucaugrams) for mirror/lens testing.

Documentation and examples are available at <https://lenstest.readthedocs.io>
"""

import numpy as np
import matplotlib.pyplot as plt
import lenstest.lenstest

__all__ = ('gram',
           'plot_gram',
           'plot_knife_and_screen',
           'plot_lens_layout',
           'plot_mirror_layout')


def _transmitted(RoC, x_offset, z_offset, X, Y, conic=0, phi=0, mask=False, A=0):
    """
    Determine if X, Y points are transmitted past knife edge.

    This assumes that the point source of light is located at the center
    of the mirror madius of curvature, RoC (or two focal lengths from a lens).

    Args:
        RoC: radius of curvature of mirror [mm]
        x_offset: transverse knife edge displacement from optical axis [mm]
        z_offset: axial knife edge offset from paraxial focus [mm]
        X, Y: grid of points to evaluate [mm]
        conic: conic constant or Schwartzchild constant [-]
        phi: CCW rotation of knife edge from vertical [radians]
        mask: show knife edge without lens/mirror effects
        A: coefficient of spherical aberration
    Returns:
        1D boolean array describing points blocked by knife edge
    """
    if mask:
        sagitta = 0
    else:
        sagitta = lenstest.lenstest.sagitta(RoC, conic, X, Y, A=A)

    # rotate points
    Xr = X * np.cos(phi) + Y * np.sin(phi)

    # x values of rays intersecting plane of knife edge
    Lx = Xr * (-z_offset - sagitta * conic) / (RoC + sagitta * conic)

    # True/False array if points miss knife edge
    T_mask = Lx < x_offset

    return T_mask


[docs] def gram(D, RoC, x_offset, z_offset, conic=0, phi=0, N=100000, invert=False, on_grid=False, A=0, mask=False): """ Create points that miss the knife edge. This assumes that the point source of light is located at the center of the mirror madius of curvature, RoC. The knife edge is displaced from the center of the mirror by x_offset. The knife edge is oriented its edge is vertical when phi=0. The conic section is specified by conic: conic = ∞ for surface that is flat conic > 0 for surface that is an oblate spheroid conic = 0 for surface that is a sphere 0<conic<-1 for surface that is a prolate spheroid conic = -1 for surface that is a paraboloid conic < -1 for surface that is a hyperboloid Args: D: diameter of mirror [mm] RoC: radius of curvature of mirror [mm] x_offset: transverse knife edge displacement from optical axis [mm] z_offset: axial knife edge offset from paraxial focus [mm] conic: conic constant or Schwartzchild constant [-] phi: CCW rotation of knife edge from vertical [radians] N: number of points to generate invert: boolean to draw dark or light areas on_grid: if False generate points on a grid mask: show Ronchi ruling without lens/mirror effects A: spherical aberration [1/mm³] Returns: x, y: masked listed of points to plot """ X, Y = lenstest.lenstest.XY_test_points(D, N=N, on_grid=on_grid) T_mask = _transmitted(RoC, x_offset, z_offset, X, Y, conic=conic, phi=phi, A=A, mask=mask) if invert: T_mask = np.logical_not(T_mask) # hide all points that will appear dark x_image = np.ma.masked_where(T_mask, X) y_image = np.ma.masked_where(T_mask, Y) return x_image, y_image
[docs] def plot_gram(D, RoC, x_offset, z_offset, conic=0, phi=0, A=0, on_grid=False): """ Plot the Foucault knife edge image. The idea is to graph both the beam on the knife edge and the expected projection on a screen located at the radius-of-curvature away from the focus. This allows rapid visualization or roughly how much of the knife edge interacts with the screen. The beam size is limited by the diffraction focus limit of the beam (assuming a 1000nm wavelength). Args: D: diameter of mirror [mm] RoC: radius of curvature of mirror [mm] x_offset: transverse knife edge offset from optical axis [mm] z_offset: axial knife edge offset from paraxial focus [mm] conic: conic or Schwartzchild constant [-] phi: CCW rotation of Ronchi ruling from vertical [radians] init: set to False to allow updating plots on_grid: if False generate points on a grid invert: set to True to invert ruling Returns: fig: matplotlib Figure object representing the plot ax: matplotlib Axes object representing the plot """ x, y = gram(D, RoC, x_offset, z_offset, conic=conic, phi=phi, A=A, on_grid=on_grid) lenstest.lenstest.draw_circle(D / 2, color='blue') plt.plot(x, y, 'o', markersize=0.1, color='white') plt.ylim(-D / 2 * 1.2, D / 2 * 1.2) plt.xlim(-D / 2 * 1.2, D / 2 * 1.2) plt.xlabel("(mm)") plt.ylabel("(mm)") plt.text(0.5, 0.97, "Mirror/Lens is %s " % lenstest.lenstest.conic_string(conic), color='white', ha='center', va='top', transform=plt.gca().transAxes ) plt.title("Screen is %.0f mm from Focus" % RoC) plt.gca().set_aspect('equal') plt.gca().set_facecolor("black")
[docs] def plot_knife_and_screen(D, RoC, x_offset, z_offset, conic=0, phi=0, init=True, A=0, on_grid=False): """ Plot the Foucault knife edge image. The idea is to graph both the beam on the knife edge and the expected projection on a screen located at the radius-of-curvature away from the focus. This allows rapid visualization or roughly how much of the knife edge interacts with the screen. The beam size is limited by the diffraction focus limit of the beam (assuming a 1000nm wavelength). Args: D: diameter of mirror [mm] RoC: radius of curvature of mirror [mm] x_offset: transverse knife edge offset from optical axis [mm] z_offset: axial knife edge offset from paraxial focus [mm] conic: conic or Schwartzchild constant [-] phi: CCW rotation of Ronchi ruling from vertical [radians] init: set to False to allow updating plots on_grid: if False generate points on a grid Returns: fig: matplotlib Figure object representing the plot ax: matplotlib Axes object representing the plot """ if init: fig, ax = plt.subplots(1, 2, figsize=(10, 4)) else: fig, ax = None, None # plot in the plane of the knife edge plt.subplot(1, 2, 1) plt.gca().set_aspect('equal') # draw the knife x, y = lenstest.lenstest.knife_polygon(D / 2, phi, x_offset) plt.fill(x, y, color='silver', alpha=0.8) plt.plot(0, 0, 'b+', markersize=5) # Draw circle showing spotsize in the knife edge plane r_geometric = abs(D / 2 / RoC * z_offset) # mm r_diffraction = 0.5 * 1e-6 * (RoC / 2) / D # mm r_spot = max(r_geometric, r_diffraction) lenstest.lenstest.draw_circle(r_spot, color='blue') # we always want to see the edge of the knife size = max(r_spot * 2.2, abs(2 * x_offset)) plt.ylim(-size, size) plt.xlim(-size, size) # plt.text(0.5, 0.96, # 'Δx=%.2fmm, ϕ=%.0f°' % (x_offset, np.degrees(phi)), # color='white', # ha='center', va='top', # transform=plt.gca().transAxes # ) # label the plot plt.xlabel("Knife x (mm)") plt.ylabel("Knife y (mm)") ks = 'at' if z_offset < 0: ks = '%.3fmm before' % abs(z_offset) if z_offset > 0: ks = '%.3fmm after' % z_offset plt.title("%s focus" % ks) # plot in the plane of the screen plt.subplot(1, 2, 2) plt.gca().set_facecolor("black") plt.gca().set_aspect('equal') # knife edge result x, y = gram(D, RoC, x_offset, z_offset, conic=conic, phi=phi, A=A, on_grid=on_grid) plt.plot(x, y, 'o', markersize=0.1, color='white') # the circle for the projected circle from mirror lenstest.lenstest.draw_circle(D / 2, color='blue') size = D / 2 * 1.2 plt.ylim(-size, size) plt.xlim(-size, size) # add labels plt.title("D=%.1fmm, RoC=%.1fmm, K=%.2f" % (D, RoC, conic)) plt.xlabel("Screen x (mm)") plt.ylabel("Screen y (mm)") cs = lenstest.lenstest.conic_string(conic) plt.text(0.5, 0.97, "Mirror/Lens is %s " % cs, color='white', ha='center', va='top', transform=plt.gca().transAxes ) plt.title("%.0fmm from Focus" % RoC) return fig, ax
[docs] def plot_lens_layout(D, RoC, x_offset, z_offset): """ Plot the Foucault knife edge experiment. Args: D: diameter of mirror or lens [mm] RoC: radius of curvature of mirror [mm] x_offset: transverse knife edge offset from optical axis [mm] z_offset: axial knife edge offset from paraxial focus [mm] Returns: fig: matplotlib Figure object representing the plot ax: matplotlib Axes object representing the plot """ fig, ax = plt.subplots(figsize=(10, 5)) # slope of marginal ray and ray touching knife m2 = (D / 2) / RoC if z_offset == 0: m = 0 else: m = x_offset / z_offset # account for knife completely missing or blocking beam m = min(m, m2) m = max(m, -m2) # marginal rays plt.plot([-2 * RoC, -RoC, RoC], [0, D / 2, -D / 2], color='black', linewidth=1) plt.plot([-2 * RoC, -RoC, RoC], [0, -D / 2, D / 2], color='black', linewidth=1) plt.plot(-2 * RoC, 0, 'ok', markersize=5) # ray touching the knife edge plt.plot([-2 * RoC, -RoC, RoC], [0, -m * RoC, m * RoC], color='black', linewidth=1, linestyle='--') # shade blocked light if z_offset < 0: plt.fill_between([z_offset, RoC], [m * z_offset, m * RoC], [z_offset * m2, D / 2], color='darkgray') else: plt.fill_between([z_offset, RoC], [m * z_offset, m * RoC], [-z_offset * m2, -D / 2], color='darkgray') # draw the lens lenstest.lenstest.draw_lens(D, RoC, -RoC) plt.text(-RoC, -D / 2, 'lens / mirror', ha='right', rotation=90, color='blue', clip_on=True) # focus plane # plt.text(0, D / 2, ' focus', ha='left') # plt.axvline(0, color='black', linewidth = 1) # optical axis plt.axhline(0, color='blue', linewidth=1) plt.text(-RoC * 0.96, 0, 'optical axis ', ha='left', va='center', color='blue', bbox={"facecolor": "white", "edgecolor": "white"}, clip_on=True) # knife # plt.axvline(z_offset, color='black', linewidth = 0.5) plt.plot([z_offset, z_offset], [x_offset, -D / 2], lw=2, color='black') plt.text(z_offset, -D / 2, 'knife', ha='center', rotation=90, color='black', bbox={"facecolor": "white", "edgecolor": "white"}, clip_on=True) # screen plt.axvline(RoC, color='blue', linewidth=2) plt.text(RoC, -D / 2, 'projection screen', ha='left', rotation=90, color='blue', clip_on=True) plt.xlabel('Distance from focus (mm)') plt.ylabel('Height above optical axis (mm)') plt.title('Foucault Knife Test Showing Shading of Screen') return fig, ax
[docs] def plot_mirror_layout(D, RoC, x_offset, z_offset): """ Plot the Foucault knife edge experiment. Args: D: diameter of mirror or lens [mm] RoC: radius of curvature of mirror [mm] x_offset: transverse offset of knife edge from optical axis [mm] z_offset: axial knife edge offset from paraxial focus [mm] Returns: fig: matplotlib Figure object representing the plot ax: matplotlib Axes object representing the plot """ # slope of marginal ray and ray touching knife fig, ax = plt.subplots(figsize=(10, 5)) m2 = (D / 2) / RoC if z_offset == 0: m = 0 else: m = x_offset / z_offset # account for knife completely missing or blocking beam m = min(m, m2) m = max(m, -m2) # marginal rays plt.plot(0, D / 20, 'ok', markersize=5) plt.plot([0, -RoC, RoC], [D / 20, D / 2, -D / 2], color='black', linewidth=1) plt.plot([0, -RoC, RoC], [D / 20, -D / 2, D / 2], color='black', linewidth=1) # ray touching the knife edge plt.plot([0, -RoC, RoC], [D / 20, -m * RoC, m * RoC], color='black', linewidth=1, linestyle='--') # shade blocked light if z_offset < 0: plt.fill_between([z_offset, RoC], [m * z_offset, m * RoC], [z_offset * m2, D / 2], color='darkgray') else: plt.fill_between([z_offset, RoC], [m * z_offset, m * RoC], [-z_offset * m2, -D / 2], color='darkgray') # draw the lens lenstest.lenstest.draw_mirror(D, RoC, vertex=-RoC) plt.text(-RoC, -D / 2, 'lens / mirror', ha='right', rotation=90, color='blue', clip_on=True) # focus plane # plt.text(0, D / 2, ' focus', ha='left') # plt.axvline(0, color='black', linewidth = 1) # optical axis plt.axhline(0, color='blue', linewidth=1) plt.text(-RoC * 0.96, 0, 'optical axis ', ha='left', va='center', color='blue', bbox={"facecolor": "white", "edgecolor": "white"}, clip_on=True) # knife # plt.axvline(z_offset, color='black', linewidth = 0.5) plt.plot([z_offset, z_offset], [x_offset, -D / 2], lw=2, color='black') plt.text(z_offset, -D / 2, 'knife', ha='center', rotation=90, color='black', bbox={"facecolor": "white", "edgecolor": "white"}, clip_on=True) # screen plt.axvline(RoC, color='blue', linewidth=2) plt.text(RoC, -D / 2, 'projection screen', ha='left', rotation=90, color='blue', clip_on=True) plt.xlabel('Distance from focus (mm)') plt.ylabel('Height above optical axis (mm)') plt.title('Foucault Knife Test Showing Shading of Screen') return fig, ax