Zernikes

Prysm supports two flavors of Zernike polynomials; the Fringe set up to the 49th term, and the Noll set up to the 36th term. They have identical interfaces, so only one will be shown here.

Zernike notations are a subclass of Pupil, so they support the same arguments to __init__,

[1]:
%matplotlib inline
from prysm import FringeZernike, NollZernike
p = FringeZernike(samples=123, dia=456.7, wavelength=1.0, z_unit='nm', phase_mask='dodecagon')

There are additional keyword arguments for each term, and the base (0 or 1) can be supplied. With base 0, the terms start at Z0 and range to Z48. With base 1, they start at Z1 and range to Z49 (or Z48, for Standard Zernikes). The Fringe set can also be used with unit variance via the norm keyword argument. Both notations also have nice print statements,

[2]:
p2 = FringeZernike(Z4=1, Z9=1, Z48=1, base=0, norm=True)
print(p2)
rms normalized Fringe Zernike description with:
        +1.000 Z4 - Primary Astigmatism 0°
        +1.000 Z9 - Primary Trefoil Y
        +1.000 Z48 - Quinternary Spherical
        13.191 PV, 1.723 RMS [nm]

Notice that the RMS value is equal to sqrt(1^2 + 1^2 + 1^2) = sqrt(3) = 1.732 ~= 1.722. The difference of ~1% is due to the array sizes used by prysm by default, if increased, e.g. by adding samples=1204 to the constructor, the value would converge to the analytical one.

A Zernike pupil can also be initalized with an iterable (list, tuple…) of coefficients,

[3]:
import numpy as np
terms = np.random.rand(49)  # 49 zernike coefficients, e.g. from a wavefront sensor
fz3 = FringeZernike(terms)

Zernike instances have a truncate method which discards terms with indices higher than n. For example,

[4]:
fz3.truncate(16)
[4]:
<prysm.zernike.FringeZernike at 0x7fab74e1c940>

this is less efficient, however, than simply slicing the coefficient vector,

[5]:
fz4 = FringeZernike(terms[:16])

and this slicing alternative should be used when one is sensitive to performance.

The top few terms may be extracted,

[6]:
fz4.top_n(5)
[6]:
[(0.8759299118677433, 1, 'Piston'),
 (0.8476086901192625, 9, 'Primary Spherical'),
 (0.8447620421672819, 12, 'Secondary Astigmatism 0°'),
 (0.8378075536899323, 5, 'Primary Astigmatism 0°'),
 (0.8190418030343746, 3, 'Tilt X')]

or the terms listed by their pairwise magnitudes and clocking angles,

[7]:
fz4.magnitudes
[7]:
{'Piston': (0.8759299118677433, 0),
 'Tilt': (0.83008221810879, 9.35518350586029),
 'Defocus': (0.6327262401733448, 0),
 'Primary Astigmatism': (0.8757349109756102, 73.07582149178343),
 'Primary Coma': (0.6339912921644918, 28.287494975342167),
 'Primary Spherical': (0.8476086901192625, 0),
 'Primary Trefoil': (0.5542249162849403, 72.4352534264586),
 'Secondary Astigmatism': (0.8500097463808574, 83.63007911349237),
 'Secondary Coma': (0.7035585879114905, 72.29938814536897),
 'Secondary Spherical': (0.09563436795119862, 0)}

These things may be (bar) plotted;

[8]:
fz4.barplot(orientation='h', buffer=1, zorder=3)
fz4.barplot_magnitudes(orientation='h', buffer=1, zorder=3)
fz4.barplot_topn(n=5, orientation='h', buffer=1, zorder=3)
[8]:
(<Figure size 432x288 with 1 Axes>,
 <matplotlib.axes._subplots.AxesSubplot at 0x7fab45333208>)
../_images/user_guide_Zernikes_15_1.png
../_images/user_guide_Zernikes_15_2.png
../_images/user_guide_Zernikes_15_3.png

orientation controls the axis on which the terms are enumerated. h results in vertical bars, v is also accepted, as are horizontal and vertical. buffer is the number of terms’ worth of spaces left on each side. The default of 1 leaves a one bar margin. zorder is passed to matplotlib – the default of 3 places the bars above any gridlines, which is an aesthetic choice. Matplotlib has a general default of 1.

If you would like direct access to the underlying functions, there are two paths. prysm._zernike contains functions for the first 49 (Fringe ordered) Zernike polynomials, for example

[9]:
from prysm.zernike import defocus

each of these takes arguments of (rho, phi). They have names which end with _x, or _00 and _45 for the terms which have that naming convention.

Zernike functions are cached by prysm’s internal routines,

[10]:
from prysm.zernike import zcache
# 8 is the index into prysm.zernike.zernikes, which loosely follows the Fringe ordering
zcache(8, norm=True, samples=128)
zcache.clear()  # you should never have to do this unless you want to release memory

This cache instance is used internally by prysm, if you modify the returned arrays in-place, you probably won’t like the result. You can create your own independent instance,

[11]:
from prysm.zernike import ZCache
my_zcache = ZCache()

See Pupils for information about general pupil functions.