Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
andykee committed Jan 21, 2022
1 parent 4fcc8ee commit 95bcea4
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 115 deletions.
42 changes: 42 additions & 0 deletions docs/_img/python/focus_images.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import matplotlib.pyplot as plt
import numpy as np
import lentil

import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (4, 4)

amp = np.zeros((256, 256))
amp += lentil.circlemask((256, 256), 64, shift=(32, 8))
amp -= lentil.circlemask((256, 256), 40, shift=(32, 8))
amp[:,0:128+8] = 0
amp[32:225,104-16:128-16] = 1
amp[96:120, 128-16:144-8] = 1
amp[201:225, 128-16:144-8] = 1

focus = lentil.zernike(mask=np.ones((256,256)), index=4)

pupil_neg = lentil.Pupil(amplitude=amp, pixelscale=1/240, focal_length=10)
pupil_neg.phase = -6e-6 * focus
w_neg = lentil.Wavefront(650e-9)
w_neg *= pupil_neg
w_neg = w_neg.propagate_image(pixelscale=5e-6, npix=256, oversample=2)

pupil_pos = lentil.Pupil(amplitude=amp, pixelscale=1/240, focal_length=10)
pupil_pos.phase = 6e-6 * focus
w_pos = lentil.Wavefront(650e-9)
w_pos *= pupil_pos
w_pos = w_pos.propagate_image(pixelscale=5e-6, npix=256, oversample=2)

plt.subplot(121)
plt.imshow(w_neg.intensity, origin='lower')
plt.title('Negative focus')
plt.xticks(np.linspace(0, 512, 5), labels=np.linspace(-1, 1, 5))
plt.yticks(np.linspace(0, 512, 5), labels=np.linspace(-1, 1, 5))

plt.subplot(122)
plt.imshow(w_pos.intensity, origin='lower')
plt.title('Positive focus')
plt.xticks(np.linspace(0, 512, 5), labels=np.linspace(-1, 1, 5))
plt.yticks(np.linspace(0, 512, 5), labels=np.linspace(-1, 1, 5))

plt.tight_layout()
11 changes: 7 additions & 4 deletions docs/_img/python/npix_prop.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
opd = lentil.zernike(amp, 4) * 1e-6
pupil = lentil.Pupil(amplitude=amp, phase=opd, pixelscale=1 / 240, focal_length=10)

w = lentil.Wavefront(wavelength=500e-9)
w *= pupil
w1 = w.propagate_image(pixelscale=5e-6, npix=128, npix_prop=128, oversample=5)
w2 = w.propagate_image(pixelscale=5e-6, npix=128, npix_prop=48, oversample=5)
w1 = lentil.Wavefront(wavelength=500e-9)
w1 *= pupil
w1 = w1.propagate_image(pixelscale=5e-6, npix=128, npix_prop=128, oversample=5)

w2 = lentil.Wavefront(wavelength=500e-9)
w2 *= pupil
w2 = w2.propagate_image(pixelscale=5e-6, npix=128, npix_prop=48, oversample=5)

plt.subplot(1, 2, 1)
plt.imshow(w1.intensity, origin='lower')
Expand Down
4 changes: 2 additions & 2 deletions docs/_img/python/segmask.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@
ax2.plot(xss, yss, offset[0], linewidth=1, color='k', zorder=100)

ax2.axis('off')
ax2.set_title('segmented_mask', fontname='monospace')
ax2.set_title('segmask', fontname='monospace')

ax3 = fig.add_subplot(133)
ax3.imshow(segmask[0])
ax3.set_title('segmented_mask[0]', fontname='monospace')
ax3.set_title('segmask[0]', fontname='monospace')
ax3.axis('off')
47 changes: 47 additions & 0 deletions docs/_img/python/tilt_images.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import matplotlib.pyplot as plt
import numpy as np
import lentil

import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (3.5, 3.5)

amp = lentil.circle((256, 256), 120)
x_tilt = 8e-6 * lentil.zernike(amp, 3) # +x tilt
y_tilt = 8e-6 * lentil.zernike(amp, 2) # +y tilt

py = lentil.Pupil(focal_length=10, pixelscale=1 / 240, amplitude=amp, phase=y_tilt)
wy = lentil.Wavefront(650e-9)
wy *= py
wy = wy.propagate_image(pixelscale=5e-6, npix=256, oversample=5)

px = lentil.Pupil(focal_length=10, pixelscale=1 / 240, amplitude=amp, phase=x_tilt)
wx = lentil.Wavefront(650e-9)
wx *= px
wx = wx.propagate_image(pixelscale=5e-6, npix=256, oversample=5)

plt.subplot(2, 2, 1)
plt.imshow(x_tilt, origin='lower')
plt.title('Pupil plane [$+R_x$]')
plt.xticks(np.linspace(0, 256, 5), labels=np.linspace(-1, 1, 5))
plt.yticks(np.linspace(0, 256, 5), labels=np.linspace(-1, 1, 5))
# plt.grid()

plt.subplot(2, 2, 2)
plt.imshow(y_tilt, origin='lower')
plt.title('Pupil plane [$+R_y$]')
plt.xticks(np.linspace(0, 256, 5), labels=np.linspace(-1, 1, 5))
plt.yticks(np.linspace(0, 256, 5), labels=np.linspace(-1, 1, 5))
# plt.grid()

plt.subplot(2, 2, 3)
plt.imshow(wx.intensity ** 0.2, origin='lower')
plt.title('Image plane [$+R_x$]')
plt.xticks(np.linspace(0, 256 * 5, 5), labels=np.linspace(-1, 1, 5))
plt.yticks(np.linspace(0, 256 * 5, 5), labels=np.linspace(-1, 1, 5))
plt.subplot(2, 2, 4)
plt.imshow(wy.intensity ** 0.2, origin='lower')
plt.title('Image plane [$+R_y$]')
plt.xticks(np.linspace(0, 256 * 5, 5), labels=np.linspace(-1, 1, 5))
plt.yticks(np.linspace(0, 256 * 5, 5), labels=np.linspace(-1, 1, 5))

plt.tight_layout()
50 changes: 1 addition & 49 deletions docs/user_guide/coordinates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,57 +26,9 @@ system defined above:
* Rotations in +y rotate the xz plane counter-clockwise about the y-axis
* Rotations in +z rotate the xy plane counter-clockwise about the z-axis

.. plot::
.. plot:: _img/python/tilt_images.py
:scale: 50

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

import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (3.5, 3.5)

amp = lentil.circle((256, 256), 120)
x_tilt = 8e-6 * lentil.zernike(amp, 3) # +x tilt
y_tilt = 8e-6 * lentil.zernike(amp, 2) # +y tilt

py = lentil.Pupil(focal_length=10, pixelscale=1/240, amplitude=amp, phase=y_tilt)
wy = lentil.Wavefront(650e-9)
wy *= py
wy = wy.propagate_image(pixelscale=5e-6, npix=256, oversample=5)
px = lentil.Pupil(focal_length=10, pixelscale=1/240, amplitude=amp, phase=x_tilt)
wx = lentil.Wavefront(650e-9)
wx *= px
wx = wx.propagate_image(pixelscale=5e-6, npix=256, oversample=5)
plt.subplot(2, 2, 1)
plt.imshow(x_tilt, origin='lower')
plt.title('Pupil plane [$+R_x$]')
plt.xticks(np.linspace(0, 256, 5), labels=np.linspace(-1, 1, 5))
plt.yticks(np.linspace(0, 256, 5), labels=np.linspace(-1, 1, 5))
#plt.grid()

plt.subplot(2, 2, 2)
plt.imshow(y_tilt, origin='lower')
plt.title('Pupil plane [$+R_y$]')
plt.xticks(np.linspace(0, 256, 5), labels=np.linspace(-1, 1, 5))
plt.yticks(np.linspace(0, 256, 5), labels=np.linspace(-1, 1, 5))
#plt.grid()

plt.subplot(2, 2, 3)
plt.imshow(wx.intensity**0.2, origin='lower')
plt.title('Image plane [$+R_x$]')
plt.xticks(np.linspace(0, 256*5, 5), labels=np.linspace(-1, 1, 5))
plt.yticks(np.linspace(0, 256*5, 5), labels=np.linspace(-1, 1, 5))
plt.subplot(2, 2, 4)
plt.imshow(wy.intensity**0.2, origin='lower')
plt.title('Image plane [$+R_y$]')
plt.xticks(np.linspace(0, 256*5, 5), labels=np.linspace(-1, 1, 5))
plt.yticks(np.linspace(0, 256*5, 5), labels=np.linspace(-1, 1, 5))

plt.tight_layout()

.. note::

Matplotlib's ``imshow()`` method (and MATLAB's ``imagesc()`` method) place
Expand Down
2 changes: 1 addition & 1 deletion docs/user_guide/diffraction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -317,4 +317,4 @@ Differences for segmented apertures



.. [1] Goodman, *Introduction to Fourier Optics*.:w
.. [1] Goodman, *Introduction to Fourier Optics*.
135 changes: 78 additions & 57 deletions docs/user_guide/optical_systems.rst
Original file line number Diff line number Diff line change
@@ -1,91 +1,112 @@
.. _user_guide.optical_systems:

.. currentmodule:: lentil

**************************
Describing optical systems
**************************

.. |Pupil| replace:: :class:`~lentil.Pupil`
.. |Image| replace:: :class:`~lentil.Image`
.. |Detector| replace:: :class:`~lentil.Detector`
.. |Plane| replace:: :class:`~lentil.Plane`
.. |Wavefront| replace:: :class:`~lentil.Wavefront`
.. |Tilt| replace:: :class:`~lentil.Tilt`
.. |Pupil| replace:: :class:`Pupil`
.. |Image| replace:: :class:`Image`
.. |Detector| replace:: :class:`Detector`
.. |Plane| replace:: :class:`Plane`
.. |Wavefront| replace:: :class:`Wavefront`
.. |Tilt| replace:: :class:`Tilt`

Lentil uses |Plane| objects to represent discretely sampled planes in an
optical system and Fourier transform-based algorithms to numerically model
the propagation of a monochromatic |Wavefront| from plane to plane through
the optical system. The basic propagation flow is:
It is common in physical optics modeling to represent an optical system in its
"unfolded" state where all optical elements are arranged along a straight line (the
optical axis). This approach assumes the following:

1. Create a new |Wavefront|
2. Propagate the wavefront through the first plane in the optical system
3. Propagate the wavefront to the next plane in the optical system
4. Repeat steps 2 and 3 until reaching the final plane in the optical system
* The beam is `paraxial <https://en.wikipedia.org/wiki/Paraxial_approximation>`_
* Powered mirrors are represented as equivalent ideal thin lenses
* The beam does not change dimensions across a lens
* A lens has no thickness so all phase changes occur in a plane

This process may be repeated a number of times (once per discrete wavelength being
represented) to simulate the propagation of broadband light through the system.
Lentil uses |Plane| objects to represent discretely sampled planes in an optical system
and |Wavefront| objects to represent discretely sampled electromagnetic fields as they
propagate through an optical system.

.. _user_guide.optical_systems.plane_wavefront:

How a plane affects a wavefront
===============================
An optical plane generally has some effect on a wavefront as it propagates
through the plane. A plane may change a propagating wavefront's amplitude, phase,
or physical extent. This |Plane|-|Wavefront| interaction is governed by the
plane's :func:`~lentil.Plane.multiply` method. This method constructs a complex
phasor from the plane's :attr:`~lentil.Plane.amplitude` and
:attr:`~lentil.Plane.phase` attributes and the |Wavefront| wavelength. The plane
complex phasor is then multiplied element-wise with the wavefront's complex data
array:
and/or physical extent. This |Plane|-|Wavefront| interaction is performed by the
plane's :func:`~lentil.Plane.multiply` method. A |Plane| and |Wavefront| can be
multiplied in two ways:

* By calling :func:`Plane.multiply` directly:

.. code:: pycon
>>> w1 = plane.multiply(w0)
* By using the built-in multiplication operator (which in turn calls
:func:`Plane.multiply`):

.. code:: pycon
>>> w1 = plane * w0
The :func:`~Plane.multiply` method constructs a complex phasor from the plane's
:attr:`~lentil.Plane.amplitude` and :attr:`~lentil.Plane.phase` attributes and the
|Wavefront| wavelength. The plane complex phasor is then multiplied element-wise with
the wavefront's complex data array:

.. math::
\mathbf{W} = \mathbf{A} \exp(\frac{-2\pi j}{\lambda} \mathbf{\theta}) \circ \mathbf{W}
\mathbf{W_1} = \mathbf{A} \exp(\frac{-2\pi j}{\lambda} \mathbf{\theta}) \circ \mathbf{W_0}
The plane's :func:`~lentil.Plane.multiply` method also accepts an ``inplace`` argument
that governs whether the multiplication operation is performed on the wavefront in-place
or using a copy:

If the |Plane| :attr:`~lentil.Plane.tilt` attribute is not empty, its contents are appended
to the |Wavefront|. See :ref:`user_guide.planes.fit_tilt` and :ref:`user_guide.diffraction.tilt`
for additional details.
.. code:: pycon
>>> w1 = plane.multiply(w0, inplace=True)
>>> w1 is w0
True
The in-place multiplication operator can also be used:

.. code:: pycon
>>> w *= plane
.. If the |Plane| :attr:`~lentil.Plane.tilt` attribute is not empty, its contents are appended
.. to the |Wavefront|. See :ref:`user_guide.planes.fit_tilt` and :ref:`user_guide.diffraction.tilt`
.. for additional details.
Planes in a simple optical system
=================================
Most optical systems can be adequately modeled by far-field propagation between a pupil
and image plane. This includes most cameras, telescopes, and imaging instruments. In
these models, all of the optics in a system are represented by a single
:class:`~lentil.Pupil` plane. The results of the diffraction propagation (assuming the
optical system is operating near focus) can be viewed using either an
:class:`~lentil.Image` or :class:`~lentil.Detector` plane. Because of the optimizations
mentioned above, the :class:`~lentil.Detector` plane should be used to most efficiently
compute image plane intensity.

.. image:: /_static/img/cassegrain.png
:width: 550px
:align: center

.. image:: /_static/img/simple_optical_system.png
:width: 375px
:align: center

If the optical system depicted above has a 1m diameter primary mirror, a secondary
mirror obscuration of 0.33m centered over the primary, a focal length of 10m, and
a focal plane with 5um pixels. We describe this optical system using a |Pupil| and
|Detector| plane as follows:

.. code-block:: pycon
>>> amplitude = lentil.circle(shape=(256, 256), radius=128) -
... lentil.circle(shape=(256, 256), radius=128/3)
Most optical systems can be adequately modeled by a single far-field propagation
between a :ref:`user_guide.planes.pupil` and image plane. This includes most cameras,
telescopes, and imaging instruments. In these models, all of the optics in a system are
represented by a single |Pupil| plane:

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

>>> import matplotlib.pyplot as plt
>>> import lentil
>>> amplitude = lentil.circle(shape=(256, 256), radius=120)
>>> opd = lentil.zernike_compose(mask=amplitude,
... coeffs=[0, 0, 0, 100e-9, 300e-9, 0, -100e-9])
>>> pupil = lentil.Pupil(amplitude=amplitude, phase=opd, focal_length=10,
... pixelscale=1/256)
>>> detector = lentil.Detector(pixelscale=5e-6)
... pixelscale=1/240)
>>> plt.imshow(pupil.phase, origin='lower')

Segmented optical systems
=========================
Creating a model of a segmented aperture optical system in Lentil doesn't require any
special treatment. The |Plane| and |Pupil| objects work just as well with sparse or
special treatment. The |Plane| and |Pupil| objects work the same with sparse or
segmented amplitude, phase, and mask attributes as with monolithic ones.

That being said, it is advantageous from a performance point of view to supply a
3-dimensional "segment mask" when specifying a Plane's :attr:`~lentil.Plane.mask`
attribute rather than a flattened 2-dimensional mask when working
3-dimensional `segment mask` when specifying a Plane's :attr:`~lentil.Plane.mask`
attribute rather than a flattened 2-dimensional `global mask` when working
with a segmented aperture, as depicted below:

.. plot:: _img/python/segmask.py
Expand Down
28 changes: 26 additions & 2 deletions docs/user_guide/wavefront_error.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,39 @@ Lentil to a Numpy array.
For :class:`~lentil.Pupil` planes, the :attr:`~lentil.Pupil.phase` attribute represents the optical
path difference (OPD) relative to the pupil's reference sphere.

Sign conventions
================
Lentil adopts the convention that a positive phase or OPD leads an ideal reference
wavefront and a negative phase or OPD lags an ideal reference wavefront. From the
figure below, a positive phase or OPD produces a negative aberration (focus, in the
case of the figure) while a negative phase or OPD produces a positive aberration.
figure below, a positive phase or OPD produces a negative aberration (focus, in this
case) while a negative phase or OPD produces a positive aberration.

.. image:: /_static/img/focus_direction.png
:width: 450px
:align: center

Tip/tilt
--------
A positive x-tilt rotates the yz plane counter-clockwise about the x-axis resulting in
vertical image plane motion in the negative y direction. A positive y-tilt rotates the
xz plane counter-clockwise about the y-axis resulting in horizontal image plane motion
in the positive x direction.

.. plot:: _img/python/tilt_images.py
:scale: 50

Focus
-----
A positive focus has the effect of shifting the image plane in front of focus while a
negative focus has the effect of shifting the image plane behind focus. We can test this
by observing +/- defocused point spread functions of an imaging system with an
asymmetric exit pupil (for example, in the shape of the letter P). We expect the
negative focus image to have the same orientation as the exit pupil (consistent with
being observed before focus) and the positive focus image to be flipped about both axes
relative to the exit pupil (consistent with being observed after passing through focus).

.. plot:: _img/python/focus_images.py
:scale: 50

Static Errors
=============
Expand Down

0 comments on commit 95bcea4

Please sign in to comment.