Skip to content

Commit

Permalink
Added test for a few pol-s, prepared info for the new release
Browse files Browse the repository at this point in the history
  • Loading branch information
sklykov committed Nov 9, 2024
1 parent ad870f0 commit 19ecbb5
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 11 deletions.
18 changes: 14 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.13] - 2024-06-13
### [0.0.14] - 2024-11-09
#### Added
- PSF kernel calculation for several polynomials;
- Acceleration by numba compilation;

#### Fixed
- Issue with type hints for Python 3.8 (tested in the environment with Python 3.8.20).


### [0.0.13] - 2024-06-13
Added **ZernPSF** class with methods for calculation, visualization and convolution with an image 2D PSF kernel, which
should correspond to the Zernike polynomial.

## [0.0.12] - 2023-12-11

### [0.0.12] - 2023-12-11
Added this file for storing changes between package versions.

### Added
#### Added
- Fitting using accounting of the Piston polynomial;
- A few more tests of new fitting procedure;
- ***functools.lru_cache*** annotation for speeding up of recursive calculation of polynomial coefficients.

### Fixed
#### Fixed
- Several docstrings of the methods.
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,34 @@ The sample of calculated PSF for Vertical Trefoil:
![Vertical Trefoil Kernel](./src/zernpy/readme_images/(-3,_3)_Vert._3foil_0.85.png "Vertical Trefoil Kernel")
Initialization and usage of the class instance (basic usage with default calculation parameters, such as the kernel size):
```python # code block for Python code
from zernpy import ZernPSF
from zernpy import ZernPSF, ZernPol
zpsf = ZernPSF(ZernPol(m=1, n=3)) # horizontal coma
NA = 0.95; wavelength = 0.55; expansion_coeff = -0.26; pixel_physical_size = 0.2*wavelength # example of physical properties
zpsf.set_physical_properties(NA, wavelength, expansion_coeff, pixel_physical_size) # provide physical properties of the system
kernel = zpsf.calculate_psf_kernel(normalized=True) # get the kernel as the square normalized matrix
```
Check the API documentation for other available methods.
Check the API documentation for other available methods.

#### PSF kernel for several polynomials
Similarly to the code above, it's possible to calculate the PSF associated with the sum profile of several polynomials:
```python
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:

![3 Polynomials Kernel Plot](./src/zernpy/readme_images/Kernel_Sum_Vert_Coma_2nd_Astigm_Spher.png "3 Polynomials Kernel Plot")

#### Acceleration of kernel calculation by numba
It's possible to accelerate the calculation of a kernel by installing the [numba](https://numba.pydata.org/) library in the
same Python environment and providing the appropriate flags in a calculation method, similar to the following code snippet:
```python
from zernpy import *
force_get_psf_compilation() # optional precompilation of calculation methods for further using of their compiled forms
NA = 0.95; wavelength = 0.55; pixel_size = wavelength / 4.6; ampl = -0.16
zp = ZernPol(m=0, n=2); zpsf = ZernPSF(zp)
zpsf.set_physical_props(NA, wavelength, ampl, pixel_size)
zpsf.calculate_psf_kernel(accelerated=True)
```
6 changes: 3 additions & 3 deletions src/zernpy/calculations/calc_psfs_numba.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,8 @@ def get_psf_kernel_comp(zernike_pol, len2pixels: float, alpha: Union[float, np.n
if size % 2 == 0:
size += 1
# Provide performance tip if the provided kernel size is quite big for calculations
if size > 45 and not suppress_warns:
__warn_message = f"Calculation of provided {size} will take more than 20 seconds"
if size > 85 and not suppress_warns:
__warn_message = f"\nCalculation of provided {size} may take more than 20 seconds"
warnings.warn(__warn_message); __warn_message = ""
kernel = np.zeros(shape=(size, size)); i_center = size//2; j_center = size//2
# Get the orders of polynomial and check if the equation for compilation was implemented
Expand All @@ -348,7 +348,7 @@ def get_psf_kernel_comp(zernike_pol, len2pixels: float, alpha: Union[float, np.n
# Check that the calibration coefficient is sufficient for calculation
pixel_size_nyquist = 0.5*0.61*wavelength/NA
if len2pixels > pixel_size_nyquist and not suppress_warns:
__warn_message = f"Provided calibration coefficient {len2pixels} {um_char}/pixels isn't sufficient enough"
__warn_message = f"\nProvided calibration coefficient {len2pixels} {um_char}/pixels isn't sufficient enough"
__warn_message += f" (defined by the relation between Nyquist freq. and the optical resolution: 0.61{lambda_char}/NA)"
warnings.warn(__warn_message); __warn_message = ""
# Calculate the PSF kernel for usage in convolution operation
Expand Down
6 changes: 6 additions & 0 deletions src/zernpy/tests/test_calc_psf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ def test_psf_kernel_calc():
diff_airy = np.abs(airy_int - airy_p)
assert np.max(diff_airy) < 0.01, print("Difference between exact Airy pattern and calculated by the numerical integral is bigger than 1%:",
np.max(diff_airy))
# Test normal calculation of several polynomials
pols = (ZernPol(osa=10), ZernPol(osa=15)); coeffs = (0.3, -0.27)
zpsf = ZernPSF(pols); zpsf.set_physical_props(NA, wavelength, expansion_coeff=coeffs, pixel_physical_size=wavelength / 3.5)
zpsf.set_calculation_props(kernel_size=25, n_integration_points_r=200, n_integration_points_phi=180)
psf_kernel = zpsf.calculate_psf_kernel()
assert np.max(psf_kernel) > 0.5 and np.min(psf_kernel) > -1E-5, "Check calculation of a kernel for several polynomials"


def test_zernpsf_usage():
Expand Down
10 changes: 8 additions & 2 deletions src/zernpy/zernpsf.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ def calculate_psf_kernel(self, suppress_warnings: bool = False, normalized: bool
if len(self.__warn_message) > 0 and not suppress_warnings:
warnings.warn(self.__warn_message)
if not self.__physical_props_set and not suppress_warnings:
self.__warn_message = "\nPhysical properties for calculation haven't been set, the default values will be used"
self.kernel_size = 19; self.__warn_message = "\nPhysical properties haven't been set before, the default ones will be used"
warnings.warn(self.__warn_message); self.__warn_message = ""
# Check if accelerated flag set to True but no numba installed
global numba_installed # access the flag
Expand Down Expand Up @@ -759,7 +759,7 @@ def force_get_psf_compilation(verbose_report: bool = False) -> Union[tuple, None
if __name__ == "__main__":
plt.close("all") # close all opened before figures
check_other_pols = False; check_small_na_wl = False # flag for checking some other polynomials PSFs
check_airy = False; check_common_psf = False; check_io_kernel = False; check_parallel_calculation = False
check_airy = False; check_common_psf = False; check_io_kernel = False; check_parallel_calculation = False; check_test = False
check_faster_airy = False; check_test_conditions = False; check_test_conditions2 = False; check_several_pols = False
check_edge_conditions = False; test_acceleration_single_pol = False; test_acceleration_few_pol = False
prepare_pic_readme = False # for plotting the sum of polynomials produced profile
Expand Down Expand Up @@ -863,3 +863,9 @@ def force_get_psf_compilation(verbose_report: bool = False) -> Union[tuple, None
zpsf_norm.set_physical_props(NA=0.43, wavelength=0.6, expansion_coeff=coeffs10, pixel_physical_size=0.6/3.0)
kern_acc = zpsf_acc.calculate_psf_kernel(normalized=True, accelerated=True); kern_norm = zpsf_norm.calculate_psf_kernel(normalized=True)
kern_diff = np.round(kern_acc - kern_norm, 9) # for checking the difference in calculations

if check_test:
pols = (ZernPol(osa=10), ZernPol(osa=15)); coeffs = (0.28, -0.33); NA = 0.35; wavelength = 0.55
zpsf = ZernPSF(pols); zpsf.set_physical_props(NA, wavelength, expansion_coeff=coeffs, pixel_physical_size=wavelength / 3.5)
zpsf.set_calculation_props(kernel_size=25, n_integration_points_r=200, n_integration_points_phi=180)
psf_kernel = zpsf.calculate_psf_kernel(normalized=False); zpsf.plot_kernel()

0 comments on commit 19ecbb5

Please sign in to comment.