Available Optical Element Classes
There are many available predefined types of optical elements in poppy. In addition you can easily specify your own custom optics.
Pupils and Aperture Stops
These optics have dimensions specified in meters, or other units of length. They can be used in the pupil planes of a Fraunhofer optical system, or any plane of a Fresnel optical system. All of these may be translated or rotated around in the plane by setting the rotation
, shift_x
or shift_y
parameters.
CircularAperture
A basic aperture stop.
[3]:
optic = poppy.CircularAperture(radius=1)
optic.display(what='both');

SquareAperture
A square stop. The specified size is the length across any one side.
[4]:
optic = poppy.SquareAperture(size=1.0)
optic.display(what='both');

RectangularAperture
Specify the width and height to define a rectangle.
[5]:
optic = poppy.RectangleAperture(width=0.5*u.m, height=1.0*u.m)
optic.display(what='both');

HexagonAperture
For instance, one segment of a segmented mirror.
[6]:
optic = poppy.HexagonAperture(side=1.0)
optic.display(what='both');

MultiHexagonAperture
Arbitrarily many hexagons, in rings. You can adjust the size of each hex, the gap width between them, and whether any hexes are missing (in particular the center one).
[7]:
optic = poppy.MultiHexagonAperture(side=1, rings=1, gap=0.05, center=True)
optic.display(what='both');

NgonAperture
Triangular apertures or other regular polygons besides hexagons are uncommon, but a generalized N-gon aperture allows modeling them if needed.
[8]:
optic = poppy.NgonAperture(nsides=5)
optic.display(what='both');

SecondaryObscuration
This class adds an obstruction which is supported by a regular evenly-spaced grid of identical struts, or set n_supports=0
for a free-standing obscuration.
[9]:
optic = poppy.SecondaryObscuration(secondary_radius=1.0,
n_supports=4,
support_width=5*u.cm)
optic.display(what='both');

AsymmetricSecondaryObscuration
This class allows making more complex “spider” support patterns than the SecondaryObscuration class. Each strut may individually be adjusted in angle, width, and offset in x and y from the center of the aperture. Angles are given in a convention such that the +Y axis is 0 degrees, and increase counterclockwise (based on the typical astronomical convention that north is the origin for position angles, increasing towards east.)
[10]:
optic = poppy.AsymmetricSecondaryObscuration(secondary_radius=0.3*u.m,
support_angle=(40, 140, 220, 320),
support_width=[0.05, 0.03, 0.03, 0.05],
support_offset_x=[0, -0.2, 0.2, 0],
name='Complex secondary')
optic.display(what='both');

ThinLens
This models a lens or powered mirror in the thin-lens approximation, with the retardance specified in terms of a number of waves at a given wavelength. The lens is perfectly achromatic.
[11]:
optic = poppy.ThinLens(nwaves=1, reference_wavelength=1e-6*u.m, radius=10*u.cm)
optic.display(what='both');

GaussianAperture
This Gaussian profile can be used for instance to model an apodizer in the pupil plane, or to model a beam launched from a fiber optic.
[12]:
optic = poppy.GaussianAperture(fwhm=1*u.m)
optic.display(what='both');

KnifeEdge
A knife edge is an infinite opaque half-plane.
[13]:
optic = poppy.optics.KnifeEdge(rotation=0)
optic.display(what='both');

Image Plane Elements
Image plane classes have dimensions specified in units of arcseconds projected onto the sky. These classes can be placed in image planes in either Fraunhofer or Fresnel optical systems.
SquareFieldStop
[14]:
optic = poppy.SquareFieldStop()
optic.display(what='both');

RectangularFieldStop
This class can be used to implement a spectrograph slit, for instance.
[15]:
optic = poppy.RectangularFieldStop()
optic.display(what='both');

AnnularFieldStop
You can also use this as a circular field stop by setting radius_inner=0
.
[16]:
optic = poppy.optics.AnnularFieldStop(radius_inner=1, radius_outer=3)
optic.display(what='both');

HexagonFieldStop
[17]:
optic = poppy.optics.HexagonFieldStop()
optic.display(what='both');

CircularOcculter
[18]:
optic = poppy.optics.CircularOcculter()
optic.display(what='both');

BarOcculter
This is an opaque bar or line.
[19]:
optic = poppy.optics.BarOcculter(width=1, height=10)
optic.display(what='both');

BandLimitedCoronagraph
[20]:
optic = poppy.BandLimitedCoronagraph()
optic.display(what='both');

Four Quadrant Phase Mask
The class is named IdealFQPM
because this implements a notionally perfect 4QPM at some given wavelength; it has precisely half a wave retardance at the reference wavelength.
[21]:
optic = poppy.IdealFQPM(wavelength=1*u.micron)
optic.display(what='both');

General Purpose Elements
ScalarTransmission
This class implements a uniformly multiplicative transmission factor, i.e. a neutral density filter.
[22]:
optic = poppy.ScalarTransmission(transmission=0.85)
optic.display(what='both');

Inverse Transmission
This optic acts on another to flip the transmission: Areas which were 0 become 1 and vice versa. This operation is not meant as a representative of some real physical process itself, but can be useful in building certain types of compound optics.
[23]:
circ = poppy.CircularAperture(radius=1)
ax1= plt.subplot(121)
circ.display(what='amplitude', ax=ax1)
ax2= plt.subplot(122)
inverted_circ = poppy.InverseTransmission(circ)
inverted_circ.display(grid_size=3, what='amplitude', ax=ax2)

Wavefront Errors
Wavefront error classes can be used to represent various forms of phase delay, typically at a pupil plane. Further documentation on these classes can be found here.
ZernikeWFE
Wavefront errors can be specified by giving a list of Zernike coefficients, which are ordered using the Noll indexing convention. The different Zernikes are then added together to make an overall wavefront map.
Note that while the Zernikes are defined with respect to some notional circular aperture of a given radius, by default this class just implements the wavefront error part, not the aperture stop.
[24]:
optic = poppy.ZernikeWFE(radius=1*u.cm,
coefficients=[0.1e-6, 3e-6, -3e-6, 1e-6, -7e-7, 0.4e-6, -2e-6],
aperture_stop=False)
optic.display(what='both', opd_vmax=1e-5, grid_size=0.03);

You can optionally set the parameter aperture_stop=True
to ZernikeWFE if you want it to also act as a circular aperture stop.
[25]:
optic = poppy.ZernikeWFE(radius=1*u.cm,
coefficients=[0, 2e-6, 3e-6, 2e-6, 0, 0.4e-6, 1e-6],
aperture_stop=True)
optic.display(what='both', opd_vmax=1e-5, grid_size=0.03);

SineWaveWFE
A sinusoidal ripple across a mirror, for instance a deformable mirror. Specify the ripple by the spatial frequency (i.e. cycles per meter). Use the amplitude
, rotation
and phaseoffset
parameters to adjust the sine wave.
[26]:
optic = poppy.SineWaveWFE(spatialfreq=5/u.meter,
amplitude=1*u.micron,
rotation=20)
optic.display(what='both', opd_vmax=1*u.micron);

StatisticalPSDWFE
A wavefront error from a random phase with a power spectral density from a power law of index -index
. The seed
parameter allows to reinitialize the pseudo-random number generator
[27]:
optic = poppy.wfe.StatisticalPSDWFE(index=3.0, wfe=50*u.nm, radius=7*u.mm, seed=1234)
optic.display(what='both', opd_vmax=150*u.nm);

Deformable Mirrors and other Active Optics
Continuous Deformable Mirrors
Represents a continuous phase-sheet DM, such as from Boston Micromachines or AOA Xinetics. Individual actuators in the DM can be addressed and poked with the set_actuator
function. That function takes 3 parameters: set_actuator(x_act, y_act, piston)
.
[28]:
dm = poppy.dms.ContinuousDeformableMirror(dm_shape=(16,16), actuator_spacing=0.3*u.mm, radius=2.3*u.mm)
dm.set_actuator(2, 4, 1e-6)
dm.set_actuator(12, 12, -1e-6)
dm.display(what='both', opd_vmax=1*u.micron);

You can also set all actuators in the entire DM surface at once by calling set_surface
with a suitably-sized array.
[29]:
dm = poppy.dms.ContinuousDeformableMirror(dm_shape=(32,32), actuator_spacing=0.3*u.mm, radius=4.9*u.mm)
target_surf = fits.getdata('dm_example_surface.fits')
dm.set_surface(target_surf);
dm.display(what='both');

The DM class has much more functionality than currently shown here, including loading measured influence functions and models of high-spatial-frequency actuator print-through. These features are not yet fully documented but please contact Marshall if you’re interested.
Hexagonally Segmented Deformable Mirrors
For instance, one of the devices made by Iris AO.
In this case, the set_actuator
function takes 4 parameters: set_actuator(segment_number, piston, tip, tilt)
.
[30]:
hexdm = poppy.dms.HexSegmentedDeformableMirror(rings=3)
hexdm.set_actuator(12, 0.5*u.micron, 0, 0)
hexdm.set_actuator(18, -0.25*u.micron, 0, 0)
hexdm.set_actuator(6, 0, -0.25*u.microradian, 0)
for i in range (19, 37, 3): hexdm.set_actuator(i, 0, 0, 2*u.microradian)
hexdm.display(what='both');

Circularly Segmented Deformable Mirrors
A collection of circular apertures that can be individually controlled.
Like HexSegmentedDeformableMirror, the set_actuator
function takes 4 parameters: set_actuator(segment_number, piston, tip, tilt)
.
[31]:
gmt = poppy.dms.CircularSegmentedDeformableMirror(rings=1, segment_radius=8.4*u.m/2, gap=0.1*u.m)
gmt.set_actuator(1, 0, 0, 0.1*u.microradian)
gmt.set_actuator(2, -0.25*u.micron, 0, 0)
gmt.set_actuator(5, 0, -0.05*u.microradian, 0)
gmt.display(what='both');

Tip Tilt Stage
This simulates a stage that can be used to adjust the tilt of some arbitrary optic. Create that optic first, then provide it as an input to this stage.
The set_tip_tilt
function takes 2 parameters: set_tip_tilt(tip, tilt)
to specify where the PSF should be tilted towards. Assuming the input beam is a flat wavefront with 0 initial tilts, the resulting PSF should be located at the specified offset.
[32]:
aperture = ap = poppy.HexagonAperture(side= 10*u.cm)
ttstage = poppy.TipTiltStage(aperture, radius=10*u.cm)
ttstage.set_tip_tilt(20*u.arcsec, -10*u.arcsec)
ttstage.display(what='both', opd_vmax=1e-5)
# Demonstrate the effect on a wavefront by this stage
w = poppy.Wavefront(diam=20*u.cm)
w *= ttstage
w.propagate_to(poppy.Detector(pixelscale=0.5*u.arcsec/u.pixel, fov_arcsec=50))
plt.figure()
w.display(imagecrop=50)
plt.title("PSF with tip tilt (20,-10) arcsec applied")
[32]:
Text(0.5, 1, 'PSF with tip tilt (20,-10) arcsec applied')


Supplying Custom Optics from Files or Arrays
If you want to set some more complicated shape, you can do so by directly specifying an array of values and loading those into an ArrayOpticalElement
.
[33]:
# Define arrays
npix = 128
trans = np.zeros((npix, npix))
opd = np.zeros((npix, npix)) - 1e-7
# Load values into those arrays
opd[35:45, 35:87] = 2e-7
y, x = np.indices((npix, npix))
r = np.sqrt((x-npix/2)**2 + (y-npix/2)**2)
trans[r<50] = 1
for x in [35, 75]:
trans[70:85, x:x+12] = 0
opd[45:52, x:x+12] = 2e-7
# Create an optic using those arrays
complex_optic = poppy.ArrayOpticalElement(transmission=trans,
opd=opd,
name='Friendly Optic',
pixelscale=1*u.mm/u.pixel)
complex_optic.display(what='both')
[33]:
(<matplotlib.axes._subplots.AxesSubplot at 0x7fb0f09a2790>,
<matplotlib.axes._subplots.AxesSubplot at 0x7fb0f09b0e10>)

You may also load arrays directly from FITS files to specify arbitrarily complicated optics using data defined by some external software, or measured data from an instrument.
[34]:
optic = poppy.FITSOpticalElement(transmission='example_flower.fits',
pixelscale=0.01,
opd='example_flower_opd.fits',
opdunits='nanometers')
optic.display(what='both')
[34]:
(<matplotlib.axes._subplots.AxesSubplot at 0x7fb0f121a750>,
<matplotlib.axes._subplots.AxesSubplot at 0x7fb0f1228b50>)

[ ]: