-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
205 additions
and
139 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
Oops, something went wrong.