mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
zernike.hpp
Go to the documentation of this file.
1/** \file zernike.hpp
2 * \author Jared R. Males (jaredmales@gmail.com)
3 * \brief Working with the Zernike polynomials.
4 *
5 * \todo the basic zernike polys should be in math::func.
6 *
7 * \ingroup signal_processing_files
8 *
9 */
10
11//***********************************************************************//
12// Copyright 2015-2020 Jared R. Males (jaredmales@gmail.com)
13//
14// This file is part of mxlib.
15//
16// mxlib is free software: you can redistribute it and/or modify
17// it under the terms of the GNU General Public License as published by
18// the Free Software Foundation, either version 3 of the License, or
19// (at your option) any later version.
20//
21// mxlib is distributed in the hope that it will be useful,
22// but WITHOUT ANY WARRANTY; without even the implied warranty of
23// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24// GNU General Public License for more details.
25//
26// You should have received a copy of the GNU General Public License
27// along with mxlib. If not, see <http://www.gnu.org/licenses/>.
28//***********************************************************************//
29
30#ifndef math_zernike_hpp
31#define math_zernike_hpp
32
33#include <cmath>
34#include <complex>
35#include <vector>
36
37#include "../mxlib.hpp"
38#include "../math/func/bessel.hpp"
39#include "../math/func/jinc.hpp"
40#include "../math/func/factorial.hpp"
41#include "../math/func/sign.hpp"
42#include "../math/constants.hpp"
43
44namespace mx
45{
46namespace sigproc
47{
48
49/**
50 * \ingroup zernike_basis
51 * @{
52 */
53
54/// Get the Zernike coefficients n,m corrresponding the Noll index j.
55/** Calculates the values of (n,m) for an index j following Noll (1976) \cite noll_1976
56 * See also: http://en.wikipedia.org/wiki/Zernike_polynomials
57 *
58 * If j is odd, this returns m <= 0.
59 *
60 *
61 * \retval 0 on success
62 * \retval -1 on error (j < 1)
63 *
64 * \test Scenario: testing noll_nm \ref tests_sigproc_zernike_noll_nm "[test doc]"
65 */
66int noll_nm( int &n, ///< [out] n the radial index of the Zernike polynomial
67 int &m, ///< [out] m the azimuthal index of the Zernnike polynomial. m < 0 if j odd.
68 int j ///< [in] j the Noll index, j > 0.
69);
70
71/// Get the Noll index j corresponding to Zernike coefficients n,m
72/** Calculates the value j for(n,m) following Noll (1976) \cite noll_1976
73 * See also: http://en.wikipedia.org/wiki/Zernike_polynomials
74 *
75 * \retval >= 0 on success
76 * \retval -1 on error (n-m odd)
77 *
78 */
79int noll_j( unsigned n, ///< [in] n the radial index of the Zernike polynomial
80 int m ///< [in] m the azimuthal index of the Zernnike polynomial.
81);
82
83/// Get the number of Zernikes up to and including a radial order.
84/** Calculates the total number of Zernike polynomials through radial order \p n. See Noll (1976) \cite noll_1976
85 * See also: http://en.wikipedia.org/wiki/Zernike_polynomials
86 *
87 * \retval the number of
88 * \retval -1 on error (n-m odd)
89 *
90 */
91int nZernRadOrd( unsigned n /**< [n] the radial order */ );
92
93/// Calculate the coefficients of a Zernike radial polynomial
94/**
95 * \retval 0 on success
96 * \retval -1 on error
97 *
98 * \tparam realT is a real floating type
99 */
100template <typename realT>
102 std::vector<realT> &c, ///< [out] allocated to length \f$ 0.5(n-m)+1\f$ and filled with the coefficients.
103 int n, ///< [in] the radial index of the Zernike polynomial.
104 int m ///< [in] the azimuthal index of the Zernike polynomial.
105)
106{
107 m = abs( m );
108
109 if( n < m )
110 {
111 internal::mxlib_error_report( error_t::invalidarg, "n cannot be less than m in the Zernike polynomials" );
112 return -1;
113 }
114
115 // If odd, it's 0.
116 if( ( n - m ) % 2 > 0 )
117 {
118 c.resize( 1, 0 );
119 return 0;
120 }
121
122 int ul = 0.5 * ( n - m ) + 1;
123
124 c.resize( ul );
125
126 for( int k = 0; k < ul; ++k )
127 {
128 c[k] = pow( -1.0, k ) * math::func::factorial<realT>( n - k ) /
129 ( math::func::factorial<realT>( k ) * math::func::factorial<realT>( 0.5 * ( n + m ) - k ) *
130 math::func::factorial<realT>( 0.5 * ( n - m ) - k ) );
131 }
132
133 return 0;
134}
135
136// Explicit instantiations:
137extern template int zernikeRCoeffs<float>( std::vector<float> &c, int n, int m );
138
139extern template int zernikeRCoeffs<double>( std::vector<double> &c, int n, int m );
140
141extern template int zernikeRCoeffs<long double>( std::vector<long double> &c, int n, int m );
142
143#ifdef HASQUAD
144extern template int zernikeRCoeffs<__float128>( std::vector<__float128> &c, int n, int m );
145#endif
146
147/// Calculate the value of a Zernike radial polynomial at a given separation.
148/**
149 *
150 * \retval -9999 indicates a possible error
151 * \retval R the value of the Zernike radial polynomial otherwise
152 *
153 * \tparam realT is a real floating type
154 * \tparam calcRealT is a real floating type used for internal calcs, should be at least double.
155 */
156template <typename realT, typename calcRealT>
157realT zernikeR( realT rho, ///< [in] the radial coordinate, \f$ 0 \le \rho \le 1 \f$.
158 int n, ///< [in] the radial index of the Zernike polynomial.
159 int m, ///< [in] the azimuthal index of the Zernike polynomial.
160 std::vector<calcRealT> &c /**< [in] contains the radial polynomial coeeficients,
161 and must be of length \f$ 0.5(n-m)+1\f$. */
162)
163{
164 m = abs( m );
165
166 // If odd, it's 0.
167 if( ( n - m ) % 2 > 0 )
168 {
169 c.resize( 1, 0 );
170 return 0.0;
171 }
172
173 if( c.size() != 0.5 * ( n - m ) + 1 )
174 {
175 internal::mxlib_error_report( error_t::invalidarg, "c vector has incorrect length for n and m." );
176 return -9999;
177 }
178
179 realT R = 0.0;
180 for( size_t k = 0; k < c.size(); ++k )
181 {
182 R += c[k] * pow( rho, n - 2 * k );
183 }
184
185 return R;
186}
187
188extern template float zernikeR<float, double>( float rho, int n, int m, std::vector<double> &c );
189
190extern template double zernikeR<double, double>( double rho, int n, int m, std::vector<double> &c );
191
192extern template long double
193zernikeR<long double, long double>( long double rho, int n, int m, std::vector<long double> &c );
194
195#ifdef HASQUAD
196extern template __float128 zernikeR<__float128, __float128>( __float128 rho, int n, int m, std::vector<__float128> &c );
197#endif
198
199/// Calculate the value of a Zernike radial polynomial at a given separation.
200/**
201 * \retval -9999 indicates a possible error
202 * \retval R the value of the Zernike radial polynomial otherwise
203 *
204 * \tparam realT is a real floating type
205 * \tparam calcRealT is a real floating type used for internal calculations, should be at least double
206 */
207template <typename realT, typename calcRealT>
208realT zernikeR( realT rho, ///< [in] the radial coordinate, \f$ 0 \le \rho \le 1 \f$.
209 int n, ///< [in] the radial index of the Zernike polynomial.
210 int m ///< [in] the azimuthal index of the Zernike polynomial.
211)
212{
213 m = abs( m );
214
215 // If odd, it's 0.
216 if( ( n - m ) % 2 > 0 )
217 {
218 return 0.0;
219 }
220
221 std::vector<calcRealT> c;
222
223 if( zernikeRCoeffs( c, n, m ) < 0 )
224 return -9999;
225
226 return zernikeR<realT, calcRealT>( rho, n, m, c );
227}
228
229extern template float zernikeR<float, double>( float rho, int n, int m );
230
231extern template double zernikeR<double, double>( double rho, int n, int m );
232
233extern template long double zernikeR<long double, long double>( long double rho, int n, int m );
234
235#ifdef HASQUAD
236extern template __float128 zernikeR<__float128, __float128>( __float128 rho, int n, int m );
237#endif
238
239/// Calculate the value of a Zernike radial polynomial at a given radius and angle.
240/**
241 * \retval -9999 indicates a possible error
242 * \retval R the value of the Zernike radial polynomial otherwise
243 *
244 * \tparam realT is a real floating type
245 * \tparam calcRealT is a real floating type used for internal calculations, should be at least double
246 */
247template <typename realT, typename calcRealT>
248realT zernike( realT rho, /**< [in] the radial coordinate, \f$ 0 \le \rho \le 1 \f$.*/
249 realT phi, /**< [in] the azimuthal angle (in radians)*/
250 int n, /**< [in] the radial index of the Zernike polynomial.*/
251 int m, /**< [in] the azimuthal index of the Zernike polynomial.*/
252 std::vector<calcRealT> &c /**< [in] contains the radial polynomial coeeficients, and
253 must be of length \f$ 0.5(n-m)+1\f$.*/
254)
255{
256 realT azt;
257
258 if( n == 0 && m == 0 )
259 {
260 return 1.0;
261 }
262
263 if( m < 0 )
264 {
265 azt = math::root_two<realT>() * sin( -m * phi );
266 }
267 else if( m > 0 )
268 {
269 azt = math::root_two<realT>() * cos( m * phi );
270 }
271 else
272 {
273 azt = 1.0;
274 }
275
276 return sqrt( (realT)n + 1 ) * zernikeR<realT, calcRealT>( rho, n, m, c ) * azt;
277}
278
279extern template float zernike<float, double>( float rho, float phi, int n, int m, std::vector<double> &c );
280
281extern template double zernike<double, double>( double rho, double phi, int n, int m, std::vector<double> &c );
282
283extern template long double
284zernike<long double, long double>( long double rho, long double phi, int n, int m, std::vector<long double> &c );
285
286#ifdef HASQUAD
287extern template __float128
288zernike<__float128, __float128>( __float128 rho, __float128 phi, int n, int m, std::vector<__float128> &c );
289#endif
290
291/// Calculate the value of a Zernike radial polynomial at a given radius and angle.
292/**
293 *
294 * \retval -9999 indicates a possible error
295 * \retval R the value of the Zernike radial polynomial otherwise
296 *
297 * \tparam realT is a real floating type
298 * \tparam calcRealT is a real floating type used for internal calculations, should be at least double
299 */
300template <typename realT, typename calcRealT>
301realT zernike( realT rho, ///< [in] the radial coordinate, \f$ 0 \le \rho \le 1 \f$.
302 realT phi, ///< [in] the azimuthal angle (in radians)
303 int n, ///< [in] the radial index of the Zernike polynomial.
304 int m ///< [in] the azimuthal index of the Zernike polynomial.
305)
306{
307
308 std::vector<calcRealT> c;
309
310 if( zernikeRCoeffs<calcRealT>( c, n, m ) < 0 )
311 return -9999;
312
313 return zernike<realT, calcRealT>( rho, phi, n, m, c );
314}
315
316extern template float zernike<float, double>( float rho, float phi, int n, int m );
317
318extern template double zernike<double, double>( double rho, double phi, int n, int m );
319
320extern template long double zernike<long double, long double>( long double rho, long double phi, int n, int m );
321
322#ifdef HASQUAD
323extern template __float128 zernike<__float128, __float128>( __float128 rho, __float128 phi, int n, int m );
324#endif
325
326/// Calculate the value of a Zernike radial polynomial at a given radius and angle.
327/**
328 * \retval -9999 indicates a possible error
329 * \retval R the value of the Zernike radial polynomial otherwise
330 *
331 * \tparam realT is a real floating type
332 * \tparam calcRealT is a real floating type used for internal calculations, should be at least double
333 */
334template <typename realT, typename calcRealT>
335realT zernike( realT rho, ///< [in] the radial coordinate, \f$ 0 \le \rho \le 1 \f$.
336 realT phi, ///< [in] the azimuthal angle (in radians)
337 int j ///< [in] the Noll index of the Zernike polynomial.
338)
339{
340 int n, m;
341
342 // Get n and m from j
343 if( noll_nm( n, m, j ) < 0 )
344 return -9999;
345
346 return zernike<realT, calcRealT>( rho, phi, n, m );
347}
348
349extern template float zernike<float, double>( float rho, float phi, int j );
350
351extern template double zernike<double, double>( double rho, double phi, int j );
352
353extern template long double zernike<long double, long double>( long double rho, long double phi, int j );
354
355#ifdef HASQUAD
356extern template __float128 zernike<__float128, __float128>( __float128 rho, __float128 phi, int j );
357#endif
358
359/// Fill in an Eigen-like array with a Zernike polynomial
360/** Sets any pixel which is at rad <= r < rad+0.5 pixels to rho = 1, to be consistent with mx::circularPupil
361 *
362 * \tparam realT is a real floating type
363 * \tparam calcRealT is a real floating type used for internal calculations, should be at least double
364 */
365template <typename arrayT, typename calcRealT, int overscan = 2>
366int zernike( arrayT &arr, /**< [out] allocated array with an Eigen-like interface. The rows()
367 and cols() members are used to size the polynomial. */
368 int n, /**< [in] the radial index of the polynomial*/
369 int m, /**< [in] the azimuthal index of the polynomial*/
370 typename arrayT::Scalar xcen, /**< [in] the x coordinate of the desired center of the polynomial,
371 in pixels*/
372 typename arrayT::Scalar ycen, /**< [in] the y coordinate of the desired center of the polynomial,
373 in pixels*/
374 typename arrayT::Scalar rad = -1 /**< [in] the desired radius. If rad <= 0, then the maximum radius
375 based on dimensions of m is used.*/
376)
377{
378 typedef typename arrayT::Scalar realT;
379 realT x;
380 realT y;
381 realT r, rho;
382 realT phi;
383
384 std::vector<calcRealT> c;
385
386 if( zernikeRCoeffs( c, n, m ) < 0 )
387 return -1;
388
389 size_t l0 = arr.rows();
390 size_t l1 = arr.cols();
391
392 if( rad <= 0 )
393 rad = 0.5 * std::min( l0 - 1, l1 - 1 );
394
395 for( size_t i = 0; i < l0; ++i )
396 {
397 for( size_t j = 0; j < l1; ++j )
398 {
399 x = i - xcen;
400 y = j - ycen;
401
402 r = std::sqrt( x * x + y * y );
403
404 // This is to be consistent with mx::circularPupil while still respecting the Zernike rules
405 if( r > rad && r <= rad + ( 1.0 / overscan ) )
406 r = rad;
407
408 rho = r / rad;
409
410 if( rho <= 1.0 )
411 {
412 phi = std::atan2( y, x );
413 arr( i, j ) = zernike( rho, phi, n, m, c );
414 }
415 else
416 {
417 arr( i, j ) = 0.0;
418 }
419 }
420 }
421 return 0;
422}
423
424/// Fill in an Eigen-like array with a Zernike polynomial
425/** Sets any pixel which is at rad <= r <= rad+0.5 pixels to rho = 1, to be consistent with mx::circularPupil
426 *
427 *
428 * \tparam realT is a real floating type
429 * \tparam calcRealT is a real floating type used for internal calculations, should be at least double
430 */
431template <typename arrayT, typename calcRealT>
433 arrayT &arr, /**< [out] is the allocated array with an Eigen-like interface. The rows() and
434 cols() members are used to size the polynomial.*/
435 int j, /**< [in] is the Noll index of the polynomial*/
436 typename arrayT::Scalar xcen, /**< [in] is the x coordinate of the desired center of the polynomial, in pixels */
437 typename arrayT::Scalar ycen, /**< [in] is the y coordinate of the desired center of the polynomial, in pixels*/
438 typename arrayT::Scalar rad = -1 /**< [in] is the desired radius. If rad <= 0, then the maximum radius
439 based on dimensions of m is used.*/
440)
441{
442 typedef typename arrayT::Scalar realT;
443
444 int n, m;
445
446 if( noll_nm( n, m, j ) < 0 )
447 return -1;
448
449 return zernike<arrayT, calcRealT>( arr, n, m, xcen, ycen, rad );
450}
451
452/// Fill in an Eigen-like array with a Zernike polynomial
453/** The geometric center of the array, 0.5*(arr.rows()-1), 0.5*(arr.cols()-1), is used as the center.
454 * Sets any pixel which is at rad <= r < rad+0.5 pixels to rho = 1, to be consistent with mx::circularPupil
455 *
456 * \tparam realT is a real floating type
457 * \tparam calcRealT is a real floating type used for internal calculations, should be at least double
458 */
459template <typename arrayT, typename calcRealT>
460int zernike( arrayT &arr, /**< [out] allocated array with an Eigen-like interface.
461 The rows() and cols() members are used to size
462 the polynomial.*/
463 int n, /**< [in] the radial index of the polynomial*/
464 int m, /**< [in] the azimuthal index of the polynomial*/
465 typename arrayT::Scalar rad = -1 /**< [in] [opt] the desired radius. If rad <= 0, then the
466 maximum radius based on dimensions of m is used.*/
467)
468{
469 typename arrayT::Scalar xcen = 0.5 * ( arr.rows() - 1.0 );
470 typename arrayT::Scalar ycen = 0.5 * ( arr.cols() - 1.0 );
471
472 return zernike<arrayT, calcRealT>( arr, n, m, xcen, ycen, rad );
473}
474
475/// Fill in an Eigen-like array with a Zernike polynomial
476/** The geometric center of the array, 0.5*(arr.rows()-1), 0.5*(arr.cols()-1), is used as the center.
477 * Sets any pixel which is at rad <= r < rad+0.5 pixels to rho = 1, to be consistent with mx::circularPupil
478 *
479 * \tparam arrayT is an Eigen-like array of real floating type
480 * \tparam calcRealT is a real floating type used for internal calculations, should be at least double
481 */
482template <typename arrayT, typename calcRealT>
483int zernike( arrayT &arr, /**< [out] the allocated array with an Eigen-like interface.
484 The rows() and cols() members are used to size
485 the polynomial.*/
486 int j, /**< [in] the Noll index of the polynomial*/
487 typename arrayT::Scalar rad = -1 /**< [in] [opt] the desired radius. If rad <= 0, then the maximum
488 radius based on dimensions of m is used.*/
489)
490{
491 typename arrayT::Scalar xcen = 0.5 * ( arr.rows() - 1.0 );
492 typename arrayT::Scalar ycen = 0.5 * ( arr.cols() - 1.0 );
493
494 return zernike<arrayT, calcRealT>( arr, j, xcen, ycen, rad );
495}
496
497/// Fill in an Eigencube-like array with Zernike polynomials in Noll order
498/** The cube is pre-allocated to set the image size and the number of modes.
499 *
500 * \returns 0 on success
501 * \returns -1 on error
502 *
503 * \tparam cubeT is an Eigencube-like array with real floating point type
504 * \tparam calcRealT is a real floating type used for internal calculations, should be at least double
505 */
506template <typename cubeT, typename calcRealT>
507int zernikeBasis( cubeT &cube, /**< [in/out] the pre-allocated cube which will be filled
508 with the Zernike basis*/
509 typename cubeT::Scalar rad = -1, /**< [in] [opt] the radius of the aperture. If -1 then the full
510 image size is used.*/
511 int minj = 2 /**< [in] [opt] the minimum j value to include. The default is j=2,
512 which skips piston (j=1).*/
513)
514{
515 typedef typename cubeT::imageT arrayT;
516
517 typename cubeT::imageT im;
518
519 im.resize( cube.rows(), cube.cols() );
520
521 int rv;
522 for( int i = 0; i < cube.planes(); ++i )
523 {
524 rv = zernike<arrayT, calcRealT>( im, minj + i, rad );
525
526 if( rv < 0 )
527 {
528 return rv;
529 }
530 cube.image( i ) = im;
531 }
532
533 return 0;
534}
535
536/// Calculate the square-normed Fourier transform of a Zernike polynomial at position (k,phi)
537/** Implements Equation (8) of Noll (1976) \cite noll_1976.
538 *
539 * \todo need a more robust jinc_n function for n > 1
540 *
541 * \test Scenario: testing zernikeQNorm \ref tests_sigproc_zernike_zernikeQNorm "[test doc]"
542 *
543 * \returns the value of |Q(k,phi)|^2
544 *
545 * \tparam realT is the floating point type used for arithmetic
546 */
547template <typename realT>
548std::complex<realT> zernikeQ( realT k, /**< [in] the radial coordinate of normalized spatial frequency. This is in the
549 \cite noll_1976 convention of cycles-per-radius.*/
550 realT phi, ///< [in] the azimuthal coordinate of normalized spatial frequency
551 int n, ///< [in] the Zernike polynomial n
552 int m ///< [in] the Zernike polynomial m
553)
554{
555
556 std::complex<realT> Q;
557
558 // sloppy implementation of jinc_n for k ~ 0
559 if( k < 1e-12 )
560 {
561 if( n == 0 )
562 Q = 1.0;
563 else
564 Q = 0.0;
565 }
566 else
567 {
568 Q = math::func::bessel_j( n + 1, math::two_pi<realT>() * k ) / ( math::pi<realT>() * k );
569 }
570
571 Q = sqrt( n + 1 ) * Q;
572
573 if( m > 0 ) // Even j (see Noll 1976)
574 {
575 Q = Q * pow( -1, 0.5 * ( n - m ) ) * pow( std::complex<realT>( { 0, 1 } ), m ) * sqrt( 2 ) * cos( m * phi );
576 }
577 else if( m < 0 ) // Odd j (see Noll 1976) , but m can't really be neg
578 {
579 Q = Q * pow( -1, 0.5 * ( n + m ) ) * pow( std::complex<realT>( { 0, 1 } ), -m ) * sqrt( 2 ) * sin( -m * phi );
580 }
581 else
582 {
583 Q = Q * pow( -1, 0.5 * n );
584 }
585
586 return Q;
587}
588
589/// Calculate the square-normed Fourier transform of a Zernike polynomial at position (k,phi)
590/** Implements Equation (8) of Noll (1976) \cite noll_1976.
591 *
592 * \todo need a more robust jinc_n function for n > 1
593 *
594 * \test Scenario: testing zernikeQNorm \ref tests_sigproc_zernike_zernikeQNorm "[test doc]"
595 *
596 * \returns the value of |Q(k,phi)|^2
597 *
598 * \tparam realT is the floating point type used for arithmetic
599 */
600template <typename realT>
601realT zernikeQNorm( realT k, /**< [in] the radial coordinate of normalized spatial frequency. This is in the
602 \cite noll_1976 convention of cycles-per-radius.*/
603 realT phi, ///< [in] the azimuthal coordinate of normalized spatial frequency
604 int n, ///< [in] the Zernike polynomial n
605 int m ///< [in] the Zernike polynomial m
606)
607{
608
609 realT B;
610
611 // sloppy implementation of jinc_n for k ~ 0
612 if( k < 0.00001 )
613 {
614 if( n == 0 )
615 {
616 B = 1.0;
617 }
618 else
619 {
620 B = 0.0;
621 }
622 }
623 else
624 {
625 B = math::func::bessel_j( n + 1, math::two_pi<realT>() * k ) / ( math::pi<realT>() * k );
626 }
627
628 realT Q2 = ( n + 1 ) * ( B * B );
629
630 if( m > 0 ) // Even j (see Noll 1976)
631 {
632 Q2 = 2 * Q2 * pow( cos( m * phi ), 2 );
633 }
634 else if( m < 0 ) // Odd j (see Noll 1976)
635 {
636 Q2 = 2 * Q2 * pow( sin( -m * phi ), 2 );
637 }
638
639 return Q2;
640}
641
642extern template float zernikeQNorm<float>( float k, float phi, int n, int m );
643
644extern template double zernikeQNorm<double>( double k, double phi, int n, int m );
645
646extern template long double zernikeQNorm<long double>( long double k, long double phi, int n, int m );
647
648#ifdef HASQUAD
649extern template __float128 zernikeQNorm<__float128>( __float128 k, __float128 phi, int n, int m );
650#endif
651
652/// Calculate the square-normed Fourier transform of a Zernike polynomial at position (k,phi)
653/** Implements Equation (8) of Noll (1976) \cite noll_1976.
654 *
655 * \returns the value of |Q(k,phi)|^2
656 *
657 * \tparam realT is the floating point type used for arithmetic
658 *
659 */
660template <typename realT>
661realT zernikeQNorm( realT k, /**< [in] the radial coordinate of normalized spatial frequency. This is in the
662 \cite noll_1976 convention of cycles-per-radius.*/
663 realT phi, ///< [in] the azimuthal coordinate of normalized spatial frequency
664 int j ///< [in] the Zernike polynomial index j (Noll convention)
665)
666{
667 int n, m;
668
669 noll_nm( n, m, j );
670
671 return zernikeQNorm( k, phi, n, m );
672}
673
674/// Fill in an Eigen-like array with the square-normed Fourier transform of a Zernike polynomial
675/** The array is filled in with the values of |Q(k,phi)|^2 according to Equation (8) of Noll (1976) \cite noll_1976.
676 *
677 * \test Scenario: testing zernikeQNorm \ref tests_sigproc_zernike_zernikeQNorm "[test doc]"
678 *
679 * \returns 0 on success
680 * \returns -1 on error
681 *
682 * \tparam arrayT is the Eigen-like array type. Arithmetic will be done in arrayT::Scalar.
683 */
684template <typename arrayT>
685int zernikeQNorm( arrayT &arr, /**< [out] the allocated array. The rows() and cols() members are used to size
686 the transform.*/
687 arrayT &k, /**< [in] the normalized spatial frequency magnitude at each pixel. This is in the \cite
688 noll_1976 convention of cycles-per-radius.*/
689 arrayT &phi, ///< [in] the spatial frequency angle at each pixel
690 int j ///< [in] the polynomial index in the Noll convention \cite noll_1976
691)
692{
693 if( arr.rows() != k.rows() || arr.cols() != k.cols() )
694 {
695 internal::mxlib_error_report( error_t::invalidarg, "output array and input k are not the same size" );
696 return -1;
697 }
698
699 if( arr.rows() != phi.rows() || arr.cols() != phi.cols() )
700 {
701 internal::mxlib_error_report( error_t::invalidarg, "output array and input phi are not the same size" );
702 return -1;
703 }
704
705 int n, m;
706 if( noll_nm( n, m, j ) < 0 )
707 return -1; // noll_nm will explain error
708
709 for( size_t i = 0; i < arr.rows(); ++i )
710 {
711 for( size_t j = 0; j < arr.cols(); ++j )
712 {
713 arr( i, j ) = zernikeQNorm( k( i, j ), phi( i, j ), n, m );
714 }
715 }
716 return 0;
717}
718
719/// Calculate the spatial power spectrum of Piston
720template <typename realT>
721realT zernikePPiston( const realT &kD /**< [in] Spatial frequency in diameter units, i.e. cycles per aperture.*/ )
722{
723 return 4 * pow( math::func::jinc( math::pi<realT>() * kD ), 2 );
724}
725
726/// Calculate the spatial power spectrum of Tip \& Tilt
727template <typename realT>
728realT zernikePTipTilt( const realT &kD /**< [in] Spatial frequency in diameter units, i.e. cycles per aperture.*/ )
729{
730 return 16 * pow( math::func::jincN( 2, math::pi<realT>() * kD ), 2 );
731}
732
733/// Calculate the spatial power spectrum of Defocus
734template <typename realT>
735realT zernikePDefocus( const realT &kD /**< [in] Spatial frequency in diameter units, i.e. cycles per aperture.*/ )
736{
737 return 12 * pow( math::func::jincN( 3, math::pi<realT>() * kD ), 2 );
738}
739
740/// Calculate the spatial power spectrum of Astigmatism
741template <typename realT>
742realT zernikePAstig( const realT &kD /**< [in] Spatial frequency in diameter units, i.e. cycles per aperture.*/ )
743{
744 return 24 * pow( math::func::jincN( 3, math::pi<realT>() * kD ), 2 );
745}
746
747/// Calculate the spatial power spectrum of Coma
748template <typename realT>
749realT zernikePComa( const realT &kD /**< [in] Spatial frequency in diameter units, i.e. cycles per aperture.*/ )
750{
751 return 32 * pow( math::func::jincN( 4, math::pi<realT>() * kD ), 2 );
752}
753
754/// Calculate the spatial power spectrum of Trefoil
755template <typename realT>
756realT zernikePTrefoil( const realT &kD /**< [in] Spatial frequency in diameter units, i.e. cycles per aperture.*/ )
757{
758 return 32 * pow( math::func::jincN( 4, math::pi<realT>() * kD ), 2 );
759}
760
761///@} signal_processing
762
763} // namespace sigproc
764} // namespace mx
765
766#endif // math_zernike_hpp
@ invalidarg
An argument was invalid.
error_t mxlib_error_report(const error_t &code, const std::string &expl, const std::source_location &loc=std::source_location::current())
Print a report to stderr given an mxlib error_t code and explanation and return the code.
Definition error.hpp:331
T2 bessel_j(T1 v, T2 x)
Bessel Functions of the First Kind.
Definition bessel.hpp:49
T2 jincN(const T1 &v, const T2 &x)
The JincN function.
Definition jinc.hpp:112
T jinc(const T &x)
The Jinc function.
Definition jinc.hpp:61
constexpr floatT six_fifths()
Return 6/5 in the specified precision.
int noll_j(unsigned n, int m)
Get the Noll index j corresponding to Zernike coefficients n,m.
realT zernikeR(realT rho, int n, int m, std::vector< calcRealT > &c)
Calculate the value of a Zernike radial polynomial at a given separation.
Definition zernike.hpp:157
realT zernikePPiston(const realT &kD)
Calculate the spatial power spectrum of Piston.
Definition zernike.hpp:721
realT zernikePDefocus(const realT &kD)
Calculate the spatial power spectrum of Defocus.
Definition zernike.hpp:735
realT zernikePTrefoil(const realT &kD)
Calculate the spatial power spectrum of Trefoil.
Definition zernike.hpp:756
realT zernike(realT rho, realT phi, int n, int m, std::vector< calcRealT > &c)
Calculate the value of a Zernike radial polynomial at a given radius and angle.
Definition zernike.hpp:248
int nZernRadOrd(unsigned n)
Get the number of Zernikes up to and including a radial order.
realT zernikePTipTilt(const realT &kD)
Calculate the spatial power spectrum of Tip & Tilt.
Definition zernike.hpp:728
int noll_nm(int &n, int &m, int j)
Get the Zernike coefficients n,m corrresponding the Noll index j.
Definition zernike.cpp:35
realT zernikeQNorm(realT k, realT phi, int n, int m)
Calculate the square-normed Fourier transform of a Zernike polynomial at position (k,...
Definition zernike.hpp:601
std::complex< realT > zernikeQ(realT k, realT phi, int n, int m)
Calculate the square-normed Fourier transform of a Zernike polynomial at position (k,...
Definition zernike.hpp:548
realT zernikePAstig(const realT &kD)
Calculate the spatial power spectrum of Astigmatism.
Definition zernike.hpp:742
realT zernikePComa(const realT &kD)
Calculate the spatial power spectrum of Coma.
Definition zernike.hpp:749
int zernikeRCoeffs(std::vector< realT > &c, int n, int m)
Calculate the coefficients of a Zernike radial polynomial.
Definition zernike.hpp:101
int zernikeBasis(cubeT &cube, typename cubeT::Scalar rad=-1, int minj=2)
Fill in an Eigencube-like array with Zernike polynomials in Noll order.
Definition zernike.hpp:507
The mxlib c++ namespace.
Definition mxlib.hpp:37