Calculation of real Zernike polynomials values, associated PSFs, plotting of their profiles in polar coordinates
Project description
zernpy
Python package for:
- Calculating real-valued Zernike polynomials based on exact (factorial) and recursive equations;
- Converting between radial (n) + azimuthal (m) orders, OSA, Noll, and Fringe indices;
- Fitting phase profiles with Zernike polynomials;
- Generating and visualizing wavefronts formed by single or sums of polynomials;
- Computing 2D PSF kernels in the imaging plane of an optical system for individual polynomials and polynomial sums.
Features
- Real-valued Zernike polynomial computation in analytical and recursive forms;
- Derivatives computation (radial and azimuthal);
- Orders / OSA / Noll / Fringe index conversions;
- Wavefront fitting;
- 2D PSF kernel simulation (image plane);
- Optional Numba acceleration of computations;
- Visualization (plotting) utilities.
Full API documentation: sklykov.github.io/zernpy
The recursive form of the equations is used by default. It becomes valuable for high-order polynomials
(n > 40) due to improved numerical stability.
Setup instructions
Installation
pip install zernpy
To upgrade:
pip install -U zernpy
Requirements
The package requires numpy, scipy and matplotlib with no specific version constraints.
The optional dependency is numba>=0.57.1.
Tests are run with pytest. Linting and formatting use ruff; non-strict type checking uses mypy.
Examples
Minimal Example
from zernpy import ZernPol
from math import pi
zp = ZernPol(m=0, n=4) # Spherical polynomial initialization
# Similar init. forms: ZernPol(osa=12), ZernPol(noll=11), ZernPol(fringe=9)
indices = zp.get_indices() # returns tuple: ((m, n), OSA, Noll, Fringe) orders / indices
naming = zp.get_polynomial_name() # returns str with polynomial name (up to 7th order)
# Coordinates can be provided as real numbers or NumPy arrays
r = 0.5; theta = pi*0.25 # as example of a single polar point
value = zp.polynomial_value(r, theta) # polynomial value(-s) for radial coordinates
value_r = zp.radial(r) # radial polynomial value(-s)
value_dr = zp.radial_dr(r) # derivative on r of radial polynomial value(-s)
value_ang = zp.triangular(theta) # angular polynomial value(-s)
value_ang_dth = zp.triangular_dtheta(theta) # derivative on theta of angular value(-s)
normalization = zp.normf() # OSA or Var(ZP)=1 normalization factor
Example of a few useful static methods
from zernpy import ZernPol
# Notations conversion
m, n = ZernPol.index2orders(osa_index=10) # Get azimuthal, radial orders. Same for noll_index, fringe_index
noll = ZernPol.osa2noll(10) # Also available: noll2osa, osa2fringe, fringe2osa
ZernPol.plot_profile(ZernPol(fringe=11)) # interactive plotting of a single polynomial
# Sum of Zernike polynomials as a surface
zerns = ZernPol(osa=3), ZernPol(osa=6)
zerns_sum_surface = ZernPol.gen_zernikes_surface(coefficients=[0.1, -0.1], polynomials=zerns)
ZernPol.plot_zernikes_surface(zerns_sum_surface) # interactive matplotlib plot
Note
Images below are visible on GitHub but not on the PyPI project page.
Fitting Zernike polynomials to a 2D phase profile
There are methods implemented for fitting a provided phase profile to a provided set of polynomials. Minimal example:
import matplotlib.pyplot as plt
from zernpy import ZernPol, generate_phases_image, fit_polynomials
polynomials = (ZernPol(m=-1, n=5), ZernPol(m=3, n=3), ZernPol(m=0, n=2))
pols_coeffs = (-0.114, 0.245, 0.403)
phases_image = generate_phases_image(polynomials=polynomials, polynomials_amplitudes=pols_coeffs,
img_height=401, img_width=401)
plt.figure("Initial Phase Profile"); plt.imshow(phases_image, cmap="jet")
plt.axis("off"); plt.tight_layout()
polynomials_amplitudes, cropped_img = fit_polynomials(phases_image, polynomials, return_cropped_image=True)
plt.figure("Fitted Phase Profile"); plt.imshow(cropped_img, cmap="jet")
plt.axis("off"); plt.tight_layout()
Initially generated profile:
Fitted profile:
2D PSF kernel calculation
The 2D PSF kernel is calculated from the diffraction integral over the round pupil plane and described as Zernike polynomial phase distribution for the focal point (no Z-axis dependency).
Initialization and usage of the class instance (basic usage with default calculation parameters, such as the kernel size):
from zernpy import ZernPSF, ZernPol
zpsf = ZernPSF(ZernPol(m=1, n=3)) # horizontal coma
NA = 0.4; wavelength = 0.55; pixel_physical_size = 0.24*wavelength; expansion_coeff = -0.1
zpsf.set_physical_props(NA, wavelength, expansion_coeff, pixel_physical_size)
kernel = zpsf.calculate_psf_kernel(normalized=True) # get a kernel as a squared matrix
The PSF kernel obtained for horizontal coma with the above parameters is shown below:
Check the API documentation for other available methods.
PSF kernel for several polynomials
Similarly to the code above, it's possible to calculate a PSF kernel associated with a sum profile of several polynomials:
from zernpy import ZernPSF, ZernPol
zp1 = ZernPol(m=-1, n=3); zp2 = ZernPol(m=2, n=4); zp3 = ZernPol(m=0, n=4)
pols = (zp1, zp2, zp3); coeffs = (0.5, 0.21, 0.15)
zpsf_pic = ZernPSF(pols); zpsf_pic.set_physical_props(NA=0.65, wavelength=0.6, expansion_coeff=coeffs,
pixel_physical_size=0.6/5.0)
zpsf_pic.calculate_psf_kernel(); zpsf_pic.plot_kernel("Sum of Polynomials Profile")
The resulting profile is:
Acceleration of kernel calculation by numba
It's possible to accelerate the calculation of a kernel by installing the numba library in the same Python environment and providing the appropriate flags in a calculation method, similar to the following code snippet:
from zernpy import force_get_psf_compilation, ZernPol, ZernPSF
force_get_psf_compilation() # optional precompilation for saving and reusing compiled code
NA = 0.95; wavelength = 0.55; pixel_size = wavelength / 4.6; ampl = -0.12
zp = ZernPol(m=0, n=2); zpsf = ZernPSF(zp)
zpsf.set_physical_props(NA, wavelength, ampl, pixel_size)
zpsf.calculate_psf_kernel(accelerated=True)
Cropping kernel
By default, the kernel size is automatically estimated and may be overestimated to guarantee that all significant points of kernel will be calculated. Also, kernel size is growing with the polynomial orders and its amplitude. To reduce the size of kernel, it's possible to call the method crop_kernel, e.g.:
from zernpy import ZernPSF, ZernPol
zpsf = ZernPSF(zernpol=(ZernPol(m=0, n=4))) # Spherical aberration
zpsf.set_physical_props(NA=1.25, wavelength=0.5, expansion_coeff=0.25, pixel_physical_size=0.5/5.0)
zpsf.calculate_psf_kernel(accelerated=True, verbose_info=True); zpsf.plot_kernel("Not Cropped")
zpsf.crop_kernel(min_part_of_max=0.025) # 2.5% of max kernel value is used for a threshold
zpsf.plot_kernel("Cropped")
Originally used (estimated) kernel with size (77, 77) for Spherical aberration:
Cropped kernel with size (33, 33) for Spherical aberration:
If a warning is encountered that estimated kernel size isn't enough for kernel calculation (it can be thrown in the end of computation), it's possible to manually increase kernel size for next computation, e.g.:
zpsf.set_calculation_props(kernel_size=51) # as an example
References
The recursive and tabular equations, along with references to the essential information about Zernike polynomials, are sourced from:
The equations for calculation of PSF are sourced from:
- Principles of Optics, by M. Born and E. Wolf, 4 ed., 1968
- Open Source Articles: Lecture, Thesis.
- Mahajan and Díaz 2013
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zernpy-0.1.2.tar.gz.
File metadata
- Download URL: zernpy-0.1.2.tar.gz
- Upload date:
- Size: 61.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
053ea624e3c5c900c3894a24f1498244e897acad221c8a082ebb1b581f436287
|
|
| MD5 |
8dbb7e7eaf8ac870e6a1daec63a9bad6
|
|
| BLAKE2b-256 |
cccdb21638d49b1517c3dbe03fa3f9b30b235e944b7c5252c8637a27e0ca93fd
|
Provenance
The following attestation bundles were made for zernpy-0.1.2.tar.gz:
Publisher:
publish.yaml on sklykov/zernpy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zernpy-0.1.2.tar.gz -
Subject digest:
053ea624e3c5c900c3894a24f1498244e897acad221c8a082ebb1b581f436287 - Sigstore transparency entry: 1940251094
- Sigstore integration time:
-
Permalink:
sklykov/zernpy@af7804f72439c3b4a5b5afa8848f3be241873303 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/sklykov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@af7804f72439c3b4a5b5afa8848f3be241873303 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zernpy-0.1.2-py3-none-any.whl.
File metadata
- Download URL: zernpy-0.1.2-py3-none-any.whl
- Upload date:
- Size: 64.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce2a6a348ac7cab61083dc9475bfd8d6b0d2791f110329a06f3cbb2ab96746f3
|
|
| MD5 |
8c3fcfabe4244332b54c2f74b738412e
|
|
| BLAKE2b-256 |
c47516f92807401d0238b6a5fa9aca757a63ba34931c4404e4c5ed3dc439d819
|
Provenance
The following attestation bundles were made for zernpy-0.1.2-py3-none-any.whl:
Publisher:
publish.yaml on sklykov/zernpy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zernpy-0.1.2-py3-none-any.whl -
Subject digest:
ce2a6a348ac7cab61083dc9475bfd8d6b0d2791f110329a06f3cbb2ab96746f3 - Sigstore transparency entry: 1940251256
- Sigstore integration time:
-
Permalink:
sklykov/zernpy@af7804f72439c3b4a5b5afa8848f3be241873303 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/sklykov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@af7804f72439c3b4a5b5afa8848f3be241873303 -
Trigger Event:
push
-
Statement type: