Analysis of Interferometric Wavefront Data

In this example, we will see how to use prysm to almost entirely supplant the software that comes with a commerical interferometer to analyze the wavefront of an optic. We begin by importing the relevant classes and setting some aesthetics for matplotlib.

from prysm import Interferogram, FringeZernike, sample_files, zernikefit

from matplotlib import pyplot as plt'bmh')

We point prysm to the file, create a new interferogram, mask it to a circular region 100 mm across, subtract piston, tip/tilt and power, and evalute the PV and RMS wavefront error. We also plot the wavefront to make sure all has gone well

p = sample_files('dat')  # sample Zygo .dat file, will be downloaded on demand and saved locally
i = Interferogram.from_zygo_dat(p)
i.crop().mask('circle', 40).crop()
print(i.pv, i.rms)
i.plot2d(clim=100, interp_method='bilinear')  # +/- 100 nm
83.33634959318245 15.245987053549815

The interferogram is cropped twice – once to enclose the valid data, then again to apply a mask centered on that region. For relatively conventional interferometry, you may want to stop here. If you want to use a different unit, that is easy enough,

1/i.pv, 1/i.rms  # print reciprocal -- "one over xxx waves"
(7.593324934352549, 41.50600279806053)

There is no need to crop again since the outer bound has not changed. Perhaps you wish to evaluated the RMS within the 1 - 10 mm spatial periods,


This value is derived from the PSD, so you must call fill first. Do not worry about the corners of the array containing data - it will be windowed out. If you do this on a part which has a central obscuration, the result will be incorrect. Otherwise, the value tends to agree to within +/- 5% of Zygo’s Mx ® software, though the authors of prysm do not believe they are calculated at all the same way.

If you wish to decompose the wavefront into Zernike polynomials, that is easy enough.

# do this on data which has not been filled to avoid errors introduced by the fill value.
coefficients = zernikefit(i.phase, terms=36, norm=True, map_='fringe')
fz = FringeZernike(coefficients, dia=i.diameter, opd_unit=i.phase_unit, norm=True)
<prysm.zernike.FringeZernike at 0x7fb499db40b8>

This print might be a bit daunting, one may prefer to see the top few terms by magnitude,

[(6.296543, 16, 'Secondary Spherical'),
 (3.035946, 10, 'Primary Trefoil Y'),
 (-2.779492, 25, 'Tertiary Spherical'),
 (2.38442, 14, 'Secondary Coma Y'),
 (-2.171752, 4, 'Defocus')]

or a barplot of all terms,

fz.barplot_magnitudes(orientation='v', sort=True)
(<Figure size 432x288 with 1 Axes>,
 <matplotlib.axes._subplots.AxesSubplot at 0x7fb499db49e8>)

The sample data has a ciruclar clear aperture, but if it had a central obscuration (such as transmitted wavefront data for a telescope) that would be easy to mask too. PSD, and by extension bandlimited RMS values for data with annular support will be nonsensical.

i.mask('invertedcircle', 7)
i.plot2d(clim=100)  # +/- 100 nm
[ ]: