Skip to content

Commit

Permalink
Update examples, introduction
Browse files Browse the repository at this point in the history
  • Loading branch information
andykee committed Jan 31, 2024
1 parent 8aa4f59 commit aec0280
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 139 deletions.
30 changes: 30 additions & 0 deletions docs/examples/broadband.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.. _examples.broadband:

********************************
Broadband diffraction simulation
********************************

.. plot::
:context: reset
:include-source:
:scale: 50

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

amp = lentil.circle(shape=(256,256), radius=120)
coef = [0, 0, 0, 150e-9, -50e-9, 0, 0, 0, 35e-9]
opd = lentil.zernike_compose(mask=amp, coeffs=coef)
pupil = lentil.Pupil(amplitude=amp, opd=opd, pixelscale=1/240,
focal_length=20)
psf = 0
for wl in np.arange(450e-9, 650e-9, 5e-9):
w0 = lentil.Wavefront(wavelength=wl)
w1 = w0 * pupil
w2 = lentil.propagate_dft(w1, shape=(128,128), pixelscale=5e-6, oversample=3)
psf += w2.intensity

fig, ax = plt.subplots()
ax.imshow(psf, norm='log')
ax.axis('off')
9 changes: 9 additions & 0 deletions docs/examples/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ End-to-end simulations
Simple diffraction simulation

.. grid-item-card::
:link: broadband
:link-type: doc
:margin: 2 2 0 0

.. image:: /examples/_thumbnails/broadband_diffraction.png
Expand All @@ -34,6 +36,8 @@ End-to-end simulations
Broadband diffraction simulation

.. grid-item-card::
:link: segmented
:link-type: doc
:margin: 2 2 0 0

.. image:: /examples/_thumbnails/segmented_diffraction.png
Expand All @@ -43,6 +47,8 @@ End-to-end simulations
Segmented aperture diffraction simulation

.. grid-item-card::
:link: tilt
:link-type: doc
:margin: 2 2 0 0

Diffraction simulation with large tilt
Expand All @@ -68,6 +74,9 @@ End-to-end simulations
:hidden:

simple
broadband
segmented
tilt


.. _examples.useful_patterns:
Expand Down
48 changes: 48 additions & 0 deletions docs/examples/segmented.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.. _examples.segmented:

*****************************************
Segmented aperture diffraction simulation
*****************************************

.. plot::
:context: reset
:include-source:
:scale: 50

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

segmask = lentil.hex_segments(rings=2, seg_radius=64, seg_gap=2, flatten=False)

# the amplitude is the flattened set of segmasks
amp = np.squeeze(np.sum(segmask, axis=0, keepdims=True))

# generate random segment tilts
opd = 0
for seg in segmask:
coeff = np.random.uniform(-1, 1, 2)*3e-6
opd += lentil.zernike_compose(seg, [0, *coeff])
# do the propagation
p = lentil.Pupil(amplitude=amp, opd=opd, focal_length=10, pixelscale=1/240)
w = lentil.Wavefront(650e-9)
w = w * p
f = lentil.propagate_dft(w, pixelscale=5e-6, shape=(128, 128), oversample=2)
psf = f.intensity

# set up the OPD colormap to display NaNs as light gray
opd[np.where(opd == 0)] = np.nan
opd_cmap = matplotlib.colormaps.get_cmap('RdBu_r')
opd_cmap.set_bad(color='#dddddd')

fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(5,3))

ax1.imshow(opd, cmap=opd_cmap)
ax1.set_title('OPD')
ax1.axis('off')

ax2.imshow(psf, cmap='inferno', norm='log', vmin=5e-3)
ax2.set_title('PSF')
ax2.axis('off')
141 changes: 33 additions & 108 deletions docs/examples/simple.rst
Original file line number Diff line number Diff line change
@@ -1,134 +1,59 @@
.. _examples.simple_example:
.. _examples.simple:

*****************************
Simple diffraction simulation
*****************************
This is an example that demonstrates the core functionality of Lentil. It
is mainly written for new users. More detailed examples and specific design
patterns are available :ref:`here<examples>`.

First, we'll import Lentil:

.. code-block:: pycon
>>> import lentil
We'll also import `Matplotlib <https://matplotlib.org>`_ to visualize results:

.. code-block:: pycon
>>> import matplotlib.pyplot as plt
Creating planes
===============
Most simple diffraction simulations can be represented by a single far-field
propagation from a pupil plane to an image plane. First, we'll create a
pupil amplitude map and corresponding optical path difference (OPD) map which
represents the wavefront error of the system. We'll construct the OPD map from
a combination of Zernike modes:

.. plot::
:context: reset
:include-source:
:scale: 50

>>> amp = lentil.circle(shape=(256,256), radius=120)
>>> coef = [0, 0, 0, 300e-9, 50e-9, -100e-9, 50e-9]
>>> opd = lentil.zernike_compose(mask=amp, coeffs=coef)
>>> fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(5, 4))
>>> ax1.imshow(amp)
>>> ax1.set_title('Amplitude')
>>> ax2.imshow(opd)
>>> ax2.set_title('OPD')

Now we can use the amplitude and OPD maps to construct a |Pupil| plane with
a focal length of 20 meters and a diameter of 1 meter:

.. plot::
:context: close-figs
:include-source:

>>> pupil = lentil.Pupil(amplitude=amp, opd=opd, pixelscale=1/240,
... focal_length=20)

Note the diameter is implicitly defined via the
:attr:`~lentil.Pupil.pixelscale` attribute:

.. image:: /_static/img/pixelscale.png
:width: 500px
:align: center

.. note::

Lentil is "unitless" in the sense that it doesn't enforce a specific base
unit. All calculations are well behaved for both metric and imperial units.
It is important that units are consistent however, and this task is left to
the user.

That being said, it is recommended that all calculations be performed in
terms of either meters, millimeters, or microns.

Diffraction
===========
import matplotlib.pyplot as plt
import numpy as np
import lentil

Pupil to image plane propagation
--------------------------------
The simplest diffraction propagation is from a pupil to image plane. Here, we
construct a |Wavefront| with wavelength of 500 nm, again represented
in meters:
amp = lentil.circle(shape=(256,256), radius=120)
coef = [0, 0, 0, 300e-9, 50e-9, -100e-9, 50e-9]
opd = lentil.zernike_compose(mask=amp, coeffs=coef)

.. plot::
:context:
:include-source:

>>> w0 = lentil.Wavefront(wavelength=500e-9)

Next, we'll propagate the wavefront through the pupil plane we defined above.
Lentil uses multiplication represent the interaction between a |Plane| and
|Wavefront|:

.. plot::
:context:
:include-source:

>>> w1 = w0 * pupil
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(5, 3))
ax1.imshow(amp)
ax1.set_title('Amplitude')
ax2.imshow(opd)
ax2.set_title('OPD')

Finally, we'll propagate the wavefront to a discreetly sampled image plane
using :func:`~lentil.propagate_dft`. In this case, we'll sample
the result on a grid with spacing of 5e-6 meters and perform the propagation
2 times oversampled:

.. plot::
:context:
:context: close-figs
:include-source:
:scale: 50

>>> w2 = lentil.propagate_dft(w1, shape=(64,64), pixelscale=5e-6, oversample=2)

The resulting intensity (point spread function) can now be observed:
# pupil model of the optical system
pupil = lentil.Pupil(amplitude=amp, opd=opd, pixelscale=1/240,
focal_length=20)

# create a plane wave
w0 = lentil.Wavefront(wavelength=500e-9)

# propagate wavefront through pupil plane
w1 = w0 * pupil

.. plot::
:context:
:include-source:
:scale: 50
# propagate the wavefront to the image plane
w2 = lentil.propagate_dft(w1, shape=(64,64), pixelscale=5e-6, oversample=2)

>>> plt.imshow(w2.intensity)
# plot the oversampled PSF
fig, ax = plt.subplots(figsize=(2.5, 2.5))
ax.imshow(w2.intensity)

Finally, we will rescale the oversampled image to native sampling and include the
blurring effects due to the discrete pixel sampling of the image plane:

.. plot::
:context: close-figs
:include-source:
:scale: 50

>>> img = lentil.detector.pixelate(w2.intensity, oversample=2)
>>> plt.imshow(img)

.. Focal planes
.. ============
.. Radiometry
.. ==========
# convolve the oversampled PSF with the pixel MTF and rebin to
# native sampling
img = lentil.detector.pixelate(w2.intensity, oversample=2)
fig, ax = plt.subplots(figsize=(2.5, 2.5))
ax.imshow(img)
45 changes: 45 additions & 0 deletions docs/examples/tilt.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
.. _examples.tilt:

**************************************
Diffraction simulation with large tilt
**************************************

.. plot::
:context: reset
:include-source:
:scale: 50

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

amp = lentil.circle((256,256), 120)
opd = lentil.zernike(amp, 2)*15e-6

p = lentil.Pupil(amplitude=amp, opd=opd, focal_length=10, pixelscale=1/240)
w = lentil.Wavefront(500e-9)
w *= p
w = lentil.propagate_dft(w, pixelscale=5e-6, shape=(256, 256))
psf = w.intensity/np.max(w.intensity)

fig, ax = plt.subplots(figsize=(2.5, 2.5))
ax.imshow(psf**0.1, cmap='inferno', vmin=0.15)
ax.set_title('Large tilt exposes periodic wraparound')

.. plot::
:context: close-figs
:include-source:
:scale: 50

p_geom_tilt = p.fit_tilt()

w = lentil.Wavefront(500e-9)
w *= p_geom_tilt
w = lentil.propagate_dft(w, pixelscale=5e-6, shape=(256, 256))
psf = w.intensity/np.max(w.intensity)

fig, ax = plt.subplots(figsize=(2.5, 2.5))
ax.imshow(psf**0.1, cmap='inferno', vmin=0.15)
ax.set_title('Geometric tilt handling eliminates wraparound')
Loading

0 comments on commit aec0280

Please sign in to comment.