Zernikes

Prysm supports several flavors of Zernike polynomial: - Fringe - Noll - ANSI “j” - ANSI “n,m”

The single index notations have identical interfaces, while the (n,m) notation is slightly different.

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

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

There are additional keyword arguments for each term. The polynomials are orthogonal, having unit amplitude (zero-to-peak). They can also be used with unit variance via the norm keyword argument, in which case they are orthonormal. The polynomial classes have friendly print statements:

[2]:
p2 = FringeZernike(Z4=1, Z9=1, Z98=1, norm=True)
print(p2)
rms normalized Fringe Zernike description with:
        +1.000 Z4 - Defocus
        +1.000 Z9 - Primary Spherical
        +1.000 Z98 - 8th Coma Y
        11.758 PV, 1.718 RMS [nm]

Notice that we include a 98th term which you likely will not find in most other programs, and that the RMS value is equal to sqrt(1^2 + 1^2 + 1^2) = sqrt(3) = 1.718 ~= 1.722. The difference 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)
print(fz3)
Fringe Zernike description with:
        +0.237 Z1 - Piston
        +0.422 Z2 - Tilt Y
        +0.150 Z3 - Tilt X
        +0.859 Z4 - Defocus
        +0.073 Z5 - Primary Astigmatism 00°
        +0.757 Z6 - Primary Astigmatism 45°
        +0.475 Z7 - Primary Coma Y
        +0.522 Z8 - Primary Coma X
        +0.579 Z9 - Primary Spherical
        +0.198 Z10 - Primary Trefoil Y
        +0.832 Z11 - Primary Trefoil X
        +0.588 Z12 - Secondary Astigmatism 00°
        +0.527 Z13 - Secondary Astigmatism 45°
        +0.916 Z14 - Secondary Coma Y
        +0.381 Z15 - Secondary Coma X
        +0.202 Z16 - Secondary Spherical
        +0.825 Z17 - Primary Quadrafoil 00°
        60.920 PV, 4.473 RMS [nm]
[5]:
fz3.top_n(5)
[5]:
[(0.91562805153131, 14, 'Secondary Coma Y'),
 (0.8587566090731925, 4, 'Defocus'),
 (0.8315043074143359, 11, 'Primary Trefoil X'),
 (0.8247641831374353, 17, 'Primary Quadrafoil 00°'),
 (0.7565978303965777, 6, 'Primary Astigmatism 45°')]

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

[6]:
fz3.magnitudes
[6]:
{'Piston': (0.23745919086319245, 0),
 'Tilt': (0.44849286815677, 70.39346804745544),
 'Defocus': (0.8587566090731925, 0),
 'Primary Astigmatism': (0.7600997704834245, 5.502037764188377),
 'Primary Coma': (0.7061017606036157, 42.27652344562817),
 'Primary Spherical': (0.5791772079761679, 0),
 'Primary Trefoil': (0.8546972533464932, 13.378161550297573),
 'Secondary Astigmatism': (0.7898356917600361, 48.13921037292902),
 'Secondary Coma': (0.9916106766946162, 67.42451186641055),
 'Secondary Spherical': (0.20220097264202486, 0),
 'Primary Quadrafoil': (0.8247641831374353, 0)}

Magnitudes is a dict keyed by the name with values of (mag, ang). The angle is always zero if the term is rotationally invariant.

These things may be (bar) plotted;

[7]:
fz3.barplot(orientation='h', buffer=1, zorder=3)
fz3.barplot_magnitudes(orientation='h', buffer=1, zorder=3)
fz3.barplot_topn(n=5, orientation='h', buffer=1, zorder=3)
[7]:
(<Figure size 432x288 with 1 Axes>,
 <matplotlib.axes._subplots.AxesSubplot at 0x7f7750984eb8>)
../_images/user_guide_Zernikes_13_1.png
../_images/user_guide_Zernikes_13_2.png
../_images/user_guide_Zernikes_13_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, the first few polynomials are provided as explicit functions and can be imported:

[8]:
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,

[9]:
from prysm.zernike import zcachemn
# 8 is the index into prysm.zernike.zernikes, which loosely follows the Fringe ordering
zcachemn(7, 7, norm=True, samples=128)
zcachemn.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,

[10]:
from prysm.zernike import ZCacheMN
my_zcache = ZCacheMN()

See Pupils for information about general pupil functions.