{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lens MTF Model\n", "\n", "In this tutorial, we will show how to use prysm to model the MTF of a lens based on a polynomial model of its aberrations. We will utilize the concepts from the [First Diffraction Model](./First-Diffraction-Model.ipynb) tutorial in constructing the forward model.\n", "\n", "MTF is defined as the magnitude of the Fourier transform of the Point Spread Function (PSF), normalized by its value at the origin. Without writing the normalization, that is simply:\n", "\n", "$$ \\text{MTF}\\left(\\nu_x,\\nu_y\\right) = \\left| \\mathfrak{F}\\left[\\text{PSF}\\left(x,y\\right)\\right] \\right| $$\n", "\n", "To make this tutorial a bit more interesting, we will use an N-sided aperture, as if our lens were stopped down and has a finite number of aperture blades. We will also assume no vignetting. Instead of Hopkins' polynomials as used previously, we will use Zernike polynomials which are orthogonal over the unit disk. Everything scales with F/#, but we'll assume its 8 and the focal length is 50 mm as reasonable photographic examples.\n", "\n", "The first step in this model is to form the aperture:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from prysm.coordinates import make_xy_grid, cart_to_polar\n", "from prysm.geometry import regular_polygon\n", "\n", "from matplotlib import pyplot as plt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "efl = 50\n", "fno = 8\n", "\n", "x, y = make_xy_grid(256, diameter=efl/fno)\n", "dx = x[0,1]-x[0,0]\n", "r, t = cart_to_polar(x, y)\n", "radius = efl/fno/2\n", "rho = r / radius\n", "n_sides = 14\n", "\n", "aperture = regular_polygon(n_sides, radius, x, y)\n", "\n", "plt.imshow(aperture, origin='lower')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once we have our model of the aperture, we can model its phase error and compute the PSF. We will assume for the moment that the illumination is monochromatic, as a separate tutorial deals with polychromatic propagation. We'll assume, too, that there's $\\lambda/14$ RMS of wavefront error; the lens just meets the Marechal Criteria for \"diffraction limited.\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from prysm.polynomials import zernike_nm\n", "from prysm.propagation import Wavefront\n", "wvl = 0.55 # mid visible band, um\n", "\n", "wfe_nm_rms = wvl/14*1e3 # nm, 3/4 of a wave, 1e3 = um to nm\n", "mode = zernike_nm(4, 0, rho, t)\n", "opd = mode * wfe_nm_rms\n", "pup = Wavefront.from_amp_and_phase(aperture, opd, wvl, dx)\n", "coherent_psf = pup.focus(efl, Q=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At this point, we are in posession of the coherent PSF, which we will recall can be converted to the incoherent PSF with the `.intensity` computed property. From there, we simply use the `mtf_from_psf` function to compute the MTF." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from prysm.otf import mtf_from_psf, diffraction_limited_mtf\n", "psf = coherent_psf.intensity\n", "mtf = mtf_from_psf(psf)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the diffraction limited MTF for a circular aperture, but it's close enough for the 14-gon example.\n", "\n", "We can start by plotting the X and Y slices of the MTF. If we are on axis, or aligned to a cartesian axis of the image plane, these are the tangential and sagittal MTFs." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fx, _ = mtf.slices().x\n", "fig, ax = mtf.slices().plot(['x', 'y', 'azavg'], xlim=(0,200))\n", "difflim = diffraction_limited_mtf(fno, wvl, fx)\n", "\n", "ax.plot(fx, difflim, ls=':', c='k', alpha=0.75, zorder=1)\n", "ax.set(xlabel='Spatial frequency, cy/mm', ylabel='MTF')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see the lens has a bit lower MTF than the diffraction limit. In other words, _the Marechal criteria does not mean lens MTF == diffraction limit_, even thought the lens is \"diffraction limited.\" We can also see the x and y MTFs are identical, since spherical aberration, $Z_4^0$ is rotationally invariant. What if the lens had an equivalent amount of coma?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wfe_nm_rms = wvl/14*1e3\n", "mode = zernike_nm(3, 1, rho, t) # only this line changed\n", "opd = mode * wfe_nm_rms\n", "pup = Wavefront.from_amp_and_phase(aperture, opd, wvl, dx)\n", "coherent_psf = pup.focus(efl, Q=2)\n", "psf = coherent_psf.intensity\n", "mtf = mtf_from_psf(psf, psf.dx)\n", "\n", "fig, ax = mtf.slices().plot(['x', 'y', 'azavg'], xlim=(0,200))\n", "\n", "ax.plot(fx, difflim, ls=':', c='k', alpha=0.75, zorder=1)\n", "ax.set(xlabel='Spatial frequency, cy/mm', ylabel='MTF')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can see a similar level of departure from the diffraction limit, and it varies as a function of the angle in k-space (\"tangential\" and \"sagittal,\" in this case)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you were interested in the phase transfer function or the OTF itself, the functions are `ptf_from_psf` and `otf_from_psf`, and they work the same way.\n", "\n", "In summary, to model the MTF of a system:\n", "\n", "- create a model of the pupil\n", "\n", "- create a model of the OPD within the pupil\n", "\n", "- propagate the pupil to a PSF plane and take its intensity (for incoherent systems)\n", "\n", "- use `mtf_from_psf` to compute the MTF" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }