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.955 Z1 - Piston
+0.879 Z2 - Tilt Y
+0.776 Z3 - Tilt X
+0.705 Z4 - Defocus
+0.732 Z5 - Primary Astigmatism 00°
+0.257 Z6 - Primary Astigmatism 45°
+0.600 Z7 - Primary Coma Y
+0.698 Z8 - Primary Coma X
+0.266 Z9 - Primary Spherical
+0.590 Z10 - Primary Trefoil Y
+0.114 Z11 - Primary Trefoil X
+0.019 Z12 - Secondary Astigmatism 00°
+0.990 Z13 - Secondary Astigmatism 45°
+0.390 Z14 - Secondary Coma Y
+0.257 Z15 - Secondary Coma X
+0.549 Z16 - Secondary Spherical
+0.704 Z17 - Primary Quadrafoil 00°
67.421 PV, 5.584 RMS [nm]
[5]:
fz3.top_n(5)
[5]:
[(0.9897696655966812, 13, 'Secondary Astigmatism 45°'),
(0.954654808201971, 1, 'Piston'),
(0.8788744913189528, 2, 'Tilt Y'),
(0.7757020181441167, 3, 'Tilt X'),
(0.7323050708346519, 5, 'Primary Astigmatism 00°')]
or the terms listed by their pairwise magnitudes and clocking angles,
[6]:
fz3.magnitudes
[6]:
{'Piston': (0.954654808201971, 0),
'Tilt': (1.1722346149316711, 48.5681054763325),
'Defocus': (0.7053046706124283, 0),
'Primary Astigmatism': (0.7761654526285778, 70.64632985233843),
'Primary Coma': (0.9199310338976827, 40.672119822341074),
'Primary Spherical': (0.26631457878325804, 0),
'Primary Trefoil': (0.6006862836883362, 79.04020898600335),
'Secondary Astigmatism': (0.9899512332479693, 1.0973789004428935),
'Secondary Coma': (0.4672182811703899, 56.59991456593365),
'Secondary Spherical': (0.5485796066693523, 0),
'Primary Quadrafoil': (0.7043664001503012, 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>,
<AxesSubplot:ylabel='OPD [$\\mathrm{nm}$]'>)
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.