Interferograms

Prysm offers rich features for analysis of interferometric data. Interferogram objects are conceptually similar to Pupils and both inherit from the same base class, as they both have to do with optical phase. We begin by performing a few imports:

[1]:
import numpy as np
from prysm import Interferogram, sample_files

from matplotlib import pyplot as plt
%matplotlib inline

The construction of an Interferogram requires only a few parameters:

[2]:
x = y = np.arange(129)
z = np.random.rand(128, 128)
interf = Interferogram(phase=z, intensity=None, x=x, y=y, meta=dict(), xy_unit=None, z_unit=None, labels=None)
print(interf)
Interferogram with:
Units: xy:: mm, z:: nm
Size: (128.000x128.000), (128x128) px
Height: 1.000 PV, 0.574 RMS [nm]

xy/z_units and labels have default values of None and control the units the data is in as well as the labels used for plotting. For more information, see the documentation on units and labels meta is a dictionary to store metadata. Interferograms are usually created from Zygo dat files. One is provided as a sample file:

[3]:
interf = Interferogram.from_zygo_dat(sample_files('dat'))
interf.plot2d()
[3]:
(<Figure size 432x288 with 2 Axes>,
 <AxesSubplot:xlabel=' X [$\\mathrm{mm}$]', ylabel=' Y [$\\mathrm{mm}$]'>)
../_images/user_guide_Interferograms_5_1.png

and both the dat and datx format from Zygo are supported. Dat carries no dependencies, while datx requries the installation of h5py. In addition to properties inherited from the OpticalPhase class (pv, rms, Sa, std), Interferograms have a PVr property, for C. Evan’s Robust PV metric, and dropout_percentage property, which gives the percentage of NaN values within the phase array. These NaNs may be filled,

[4]:
interf.fill(_with=0)
interf.plot2d()
[4]:
(<Figure size 432x288 with 2 Axes>,
 <AxesSubplot:xlabel=' X [$\\mathrm{mm}$]', ylabel=' Y [$\\mathrm{mm}$]'>)
../_images/user_guide_Interferograms_7_1.png

with 0 as a default value; only constants are supported. The modification is done in-place and the method returns self. Piston, tip-tilt, and power may be removed:

[5]:
# no plot here - the sample file already has this processing done
interf.remove_piston().remove_tiptilt().remove_power()
[5]:
<prysm.interferogram.Interferogram at 0x7f53a52d7cc0>

again done in-place and returning self, so methods can be chained. You should remove these terms (or, indeed do anything with Zernikes) before filling NaNs. One line convenience wrappers exist:

[6]:
interf.remove_piston_tiptilt()
interf.remove_piston_tiptilt_power()
[6]:
<prysm.interferogram.Interferogram at 0x7f53a52d7cc0>

spikes may also be clipped,

[7]:
interf.spike_clip(nsigma=3)  # default is 3
interf.plot2d()
[7]:
(<Figure size 432x288 with 2 Axes>,
 <AxesSubplot:xlabel=' X [$\\mathrm{mm}$]', ylabel=' Y [$\\mathrm{mm}$]'>)
../_images/user_guide_Interferograms_13_1.png

setting points with a value more than nsigma standard deviations from the mean to NaN.

Lateral calibrations can be stripped and applied:

[8]:
# this is not going to do what you want if your data is already calibrated.
interf.strip_latcal()
interf.latcal(plate_scale=0.1, unit='mm')
[8]:
<prysm.interferogram.Interferogram at 0x7f53a52d7cc0>

Masks may be applied:

[9]:
your_mask = np.ones(interf.phase.shape)
interf.mask(your_mask)
interf.mask('circle', diameter=40)  # 30 interf.units.x diameter circle
interf.plot2d()
[9]:
(<Figure size 432x288 with 2 Axes>,
 <AxesSubplot:xlabel=' X [$\\mathrm{mm}$]', ylabel=' Y [$\\mathrm{mm}$]'>)
../_images/user_guide_Interferograms_17_1.png

The truecircle mask should not be used on interferometric data. the phase is deleted (replaced with NaN) wherever the mask is equal to zero.

Interferograms may be cropped, deleting empty (NaN) regions around a measurment;

[10]:
interf.crop()
[10]:
<prysm.interferogram.Interferogram at 0x7f53a52d7cc0>

Or padded,

[11]:
interf.pad(3)  # this works out to a 10% of diameter on each side pad
interf.plot2d()
[11]:
(<Figure size 432x288 with 2 Axes>,
 <AxesSubplot:xlabel=' X [$\\mathrm{mm}$]', ylabel=' Y [$\\mathrm{mm}$]'>)
../_images/user_guide_Interferograms_21_1.png

Convenience properties are provided for data size,

[12]:
interf.shape, interf.size, interf.diameter_x, interf.diameter_y, interf.diameter, interf.semidiameter
[12]:
((459, 459),
 210681,
 45.80000000000004,
 45.80000000000004,
 45.80000000000004,
 22.90000000000002)

shape and size mirrors the underlying interf.phase ndarray. The x and y diameters are in units of interf.spatial_unit and diameter is the greater of the two.

The two dimensional Power Spectral Density (PSD) may be computed. The data may not contain NaNs, and piston tip and tilt should be removed prior. A 2D Welch window is used for circular data and a 2D Hanning window for rectangular data, so there is no need for concern about zero values creating a discontinuity at the edge of circular or other nonrectangular apertures.

[13]:
interf.crop().remove_piston_tiptilt_power().fill()
psd = interf.psd()

The psd variable is a PSD (RichData) object, so it can be used as any other, e.g. for slicing or plotting

When plotting PSD slices, power law models can be overlain:

[14]:
from prysm.plotting import add_psd_model

# a, b, and c given => abc model
fig, ax = psd.slices().plot('azavg', invert_x=True, ylim=(1e-4, 1e4))
fig, ax = add_psd_model(psd, fig=fig, ax=ax, invert_x=True,
                        lw=3, ls='--', color='r', alpha=0.6,
                        a=3e3, b=2/10, c=4)

# a, b given => ab model
fig, ax = add_psd_model(psd, fig=fig, ax=ax, invert_x=True,
                        color='g', alpha=0.6, lw=2, ls=':',
                        a=1, b=3.5)

# you can also pass in your own model via the `psd_fcn` keyword argument.
# Its signature should look like:
def your_own_psd_fcn(nu, **kwargs):
    pass
../_images/user_guide_Interferograms_28_0.png

(slices of) the PSD can be fit to these analytic functions:

[15]:
from prysm.interferogram import fit_psd

a, b, c = fit_psd(*psd.slices().azavg, guess=(2e3, 2/10, 3.75))

print(a, b, c)

fig, ax = psd.slices().plot('azavg', invert_x=True, ylim=(1e-4, 1e4))
fig, ax = add_psd_model(psd, fig=fig, ax=ax, invert_x=True, a=a, b=b, c=c, alpha=0.5)
1999.3278606328984 -0.03711507483250623 3.034687309612286
../_images/user_guide_Interferograms_30_1.png

A bandlimited RMS value derived from the 2D PSD may also be evaluated,

[16]:
interf.bandlimited_rms(wllow=1, wlhigh=10, flow=1, fhigh=10)
[16]:
5.094636748733854

only one of wavelength (wl; spatial period) or frequency (f) should be provided. f will clobber wavelength.

For details on plotting or slicing interferograms, see plotting and slicing