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');
_images/available_optics_5_0.png

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');
_images/available_optics_7_0.png

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');
_images/available_optics_9_0.png

HexagonAperture

For instance, one segment of a segmented mirror.

[6]:
optic = poppy.HexagonAperture(side=1.0)
optic.display(what='both');
_images/available_optics_11_0.png

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');
_images/available_optics_13_0.png

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');
_images/available_optics_15_0.png

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');
_images/available_optics_17_0.png

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');
_images/available_optics_19_0.png

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');
_images/available_optics_21_0.png

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');
_images/available_optics_23_0.png

KnifeEdge

A knife edge is an infinite opaque half-plane.

[13]:
optic = poppy.optics.KnifeEdge(rotation=0)
optic.display(what='both');
_images/available_optics_25_0.png

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');
_images/available_optics_28_0.png

RectangularFieldStop

This class can be used to implement a spectrograph slit, for instance.

[15]:
optic = poppy.RectangularFieldStop()
optic.display(what='both');
_images/available_optics_30_0.png

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');
_images/available_optics_32_0.png

HexagonFieldStop

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

CircularOcculter

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

BarOcculter

This is an opaque bar or line.

[19]:
optic = poppy.optics.BarOcculter(width=1, height=10)
optic.display(what='both');
_images/available_optics_38_0.png

BandLimitedCoronagraph

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

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');
_images/available_optics_42_0.png

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');
_images/available_optics_45_0.png

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)
_images/available_optics_47_0.png

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);
_images/available_optics_50_0.png

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);
_images/available_optics_52_0.png

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);
_images/available_optics_54_0.png

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);
_images/available_optics_56_0.png

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);
_images/available_optics_59_0.png

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');
_images/available_optics_61_0.png

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');
_images/available_optics_64_0.png

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');
_images/available_optics_66_0.png

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')
_images/available_optics_68_1.png
_images/available_optics_68_2.png

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>)
_images/available_optics_70_1.png

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>)
_images/available_optics_72_1.png
[ ]: