mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
zernikeCovariance.hpp
Go to the documentation of this file.
1/** \file zernikeCovariance.hpp
2 * \author Jared R. Males (jaredmales@gmail.com)
3 * \brief Calculation of the modal covariance in the zernike basis.
4 * \ingroup mxAO_files
5 *
6 */
7
8#ifndef zernikeCovariance_hpp
9#define zernikeCovariance_hpp
10
11#include <gsl/gsl_integration.h>
12#include <gsl/gsl_errno.h>
13
14#include "../../math/constants.hpp"
15#include "../../math/func/jinc.hpp"
16#include "../../sigproc/zernike.hpp"
17#include "../../ioutils/fits/fitsFile.hpp"
18#include "../../improc/eigenImage.hpp"
19#include "../../improc/eigenCube.hpp"
20#include "../../mxlib_uncomp_version.h"
21#include "../../ipc/ompLoopWatcher.hpp"
22#include "../../sys/timeUtils.hpp"
23#include "../../math/eigenLapack.hpp"
24
25#include "../../math/func/airyPattern.hpp"
26
27#include "aoAtmosphere.hpp"
28#include "aoPSDs.hpp"
29#include "aoSystem.hpp"
30#include "varmapToImage.hpp"
31
32namespace mx
33{
34namespace AO
35{
36namespace analysis
37{
38
39/// Structure to manage the zernike mode covariance calculation, passed to integration functions
40/**
41 * \tparam realT a floating point type used for all calculations. As of Dec 2023 must be double due to gsl_integration.
42 * \tparam aosysT the type of the AO system structure
43 */
44template <typename realT, typename aosysT>
46{
47 /// Pointer to an AO system, which contains the relevant spatial PSD of turbulence.
48 aosysT *m_aosys{ nullptr };
49
50 /// The n-index of the unprimed mode.
51 realT m_n;
52
53 /// The m-index of the unprimed mode.
54 realT m_m;
55
56 /// The n-indexof the primed mode, corresponding to the \f$ k_v = n/D \f$ component of spatial frequency.
57 realT m_np;
58
59 /// The m-index of the primed mode, corresponding to the \f$ k_u = m/D \f$ component of spatial frequency.
60 realT m_mp;
61
62 /// Spatial frequency being calculated, passed for use in the integrand worker functions.
63 realT m_k;
64
65 /// Absolute tolerance for the radial integral. Default is 1e-10.
66 realT m_kIntEpsAbs{ 1e-10 };
67
68 /// Relative tolerance for the radial integral. Default is 0, meaning absolute is used.
69 realT m_kIntEpsRel{ 0 };
70
71 /// Absolute tolerance for the azimuthal integral. Default is 1e-10.
72 realT m_phiIntEpsAbs{ 1e-10 };
73
74 /// Relative tolerance for the azimuthal integral. Default is 0, meaning absolute is used.
75 realT m_phiIntEpsRel{ 0 };
76
77 protected:
78 size_t m_workspaceSize{ 1000000 };
79
80 /// Working memory for the azimuthal integral.
81 gsl_integration_workspace *m_workspacePhi{ nullptr };
82
83 /// Working memory for the radial integral.
84 gsl_integration_workspace *m_workspaceK{ nullptr };
85
86 public:
87 /// Constructor
89 {
90 m_workspacePhi = gsl_integration_workspace_alloc( m_workspaceSize );
91 m_workspaceK = gsl_integration_workspace_alloc( m_workspaceSize );
92 }
93
94 /// Destructor
96 {
97 gsl_integration_workspace_free( m_workspacePhi );
98 gsl_integration_workspace_free( m_workspaceK );
99 }
100
101 void workspaceSize( size_t wsz )
102 {
103 gsl_integration_workspace_free( m_workspacePhi );
104 gsl_integration_workspace_free( m_workspaceK );
105
106 m_workspacePhi = gsl_integration_workspace_alloc( m_workspaceSize );
107 m_workspaceK = gsl_integration_workspace_alloc( m_workspaceSize );
108 }
109
110 size_t workspaceSize()
111 {
112 return m_workspaceSize;
113 }
114
115 gsl_integration_workspace *workspacePhi()
116 {
117 return m_workspacePhi;
118 }
119
120 /// Calculate the covariance between the two modes.
121 /** \todo document me
122 * \todo handle gsl errors
123 */
124 realT getCovariance( realT &error )
125 {
126 realT result;
127
128 if( m_aosys == nullptr )
129 {
130 mxThrowException(
131 err::paramnotset, "zernikeCovariance::getCovariance", "AO system not setup (aosys is nullptr)" );
132 }
133
134 gsl_function func;
135 func.function = &kInt;
136 func.params = this;
137
138 gsl_set_error_handler_off();
139
140 int ec = gsl_integration_qagiu(
141 &func, 0, m_kIntEpsAbs, m_kIntEpsRel, m_workspaceSize, m_workspaceK, &result, &error );
142
143 return result;
144 }
145
146 /// Worker function for the radial integral in the covariance calculation
147 /**
148 * \returns the value of the integrand at spatial frequency \p k.
149 */
150 static realT kInt( realT k, ///< [in] the spatial frequency at which to evaluate the integrand, meters
151 void *params ///< [in] a pointer to a object of type zernikeCovariance<realT, aosyT>
152 );
153
154 /// Worker for the azimuthal integral (in phi) for the zernike mode covariance.
155 /**
156 * \return the value of the integrand at the angle \p phi
157 */
158 static realT phiInt( realT phi, ///< [in] the angle at which to evaluate the integrand, radians
159 void *params ///< [in] a pointer to a object of type zernikeCovariance<realT, aosyT>
160 );
161};
162
163template <typename realT, typename aosysT>
164realT zernikeCovariance<realT, aosysT>::kInt( realT k, void *params )
165{
167
168 realT result, error;
169
170 gsl_function func;
171
172 func.function = &phiInt;
173
174 func.params = Pp;
175
176 Pp->m_k = k;
177
178 gsl_integration_qag( &func,
179 0.,
180 math::two_pi<double>(),
181 Pp->m_phiIntEpsAbs,
182 Pp->m_phiIntEpsRel,
183 Pp->workspaceSize(),
184 GSL_INTEG_GAUSS61,
185 Pp->workspacePhi(),
186 &result,
187 &error );
188
189 return result;
190}
191
192template <typename realT, typename aosysT>
193realT zernikeCovariance<realT, aosysT>::phiInt( realT phi, void *params )
194{
196
197 realT D = Pp->m_aosys->D();
198
199 realT k = Pp->m_k;
200
201 /*** no prime ***/
202 realT m = Pp->m_m;
203 realT n = Pp->m_n;
204
205 std::complex<realT> Q_mn = zernikeQ( k * D / 2.0, phi, n, m );
206
207 /*** primed ***/
208 realT mp = Pp->m_mp;
209 realT np = Pp->m_np;
210
211 std::complex<realT> Q_mpnp = zernikeQ( k * D / 2.0, phi, np, mp );
212
213 /*** The product ***/
214 realT QQ = real( conj( Q_mn ) * Q_mpnp );
215
216 realT P = Pp->m_aosys->psd( Pp->m_aosys->atm, 0, k, Pp->m_aosys->lam_sci(), Pp->m_aosys->lam_wfs(), 1.0 );
217
218 return P * k * QQ;
219}
220
221#if 0
222
223///Calculate a vector of zernike mode variances.
224template<typename realT, typename aosysT>
225int zernikeVarVec( const std::string & fname,
226 int N,
227 aosysT & aosys,
228 realT absTol,
229 realT relTol,
230 bool modifed=true
231 )
232{
233
234 std::vector<realT> var(N,0);
235
236 ipc::ompLoopWatcher<> watcher(N, std::cerr);
237
238 realT mnCon = 0;
239 if( aosys.d_min(0) > 0)
240 {
241 mnCon = floor( aosys.D()/aosys.d_min(0)/2.0);
242 }
243
244#pragma omp parallel
245 {
247 Pp.absTol = absTol;
248 Pp.relTol = relTol;
249 Pp.aosys = &aosys;
250
251 Pp.mnCon = mnCon;
252
253 realT result, error;
254
255#pragma omp for schedule( dynamic, 5 )
256 for(int i=0; i< N; ++i)
257 {
258 Pp.p = +1;
259 Pp.m = i+1;
260 Pp.n = 0;
261
262 Pp.pp = +1;
263 Pp.mp = i+1;
264 Pp.np = 0;
265
266 result = Pp.getVariance(error);
267
268 var[i] = result;
269
270 watcher.incrementAndOutputStatus();
271 }
272 }
273
274 std::ofstream fout;
275 fout.open(fname);
276
277 realT D = aosys.D();
278 realT r_0 = aosys.atm.r_0();
279 realT tot = 1.0299*pow(D/r_0, 5./3.);
280
281 realT sum = 0;
282
283 std::cout << 0 << " " << 0 << " " << 0 << " " << tot << "\n";
284 for(int i=0; i<N; ++i)
285 {
286 realT k = (i+1)/D;
287
288 realT P = aosys.psd(aosys.atm, 0, k, 1.0);// aosys.lam_sci(), 0, aosys.lam_wfs(), 1.0);
289
290 if(mnCon > 0 )
291 {
292 if( k*D < mnCon )
293 {
294 P *= pow(math::two_pi<realT>()*aosys.atm.v_wind()* k * (aosys.minTauWFS(0)+aosys.deltaTau()),2);
295 }
296 }
297
298 sum += var[i];
299 fout << i+1 << " " << var[i] << " " << P/pow(D,2) << " " << tot-sum << "\n";
300 }
301
302 fout.close();
303
304 return 0;
305}
306
307///Calculate a map of zernike variances by convolution with the PSD
308/** Uses the Airy pattern for the circularly unobstructed aperture.
309 *
310 * \returns 0 on success
311 * \returns -1 on error
312 */
313template<typename realT, typename aosysT>
314int zernikePSDMap( improc::eigenImage<realT> & var, ///< [out] The variance estimated by convolution with the PSD
315 improc::eigenImage<realT> & psd, ///< [out] the PSD map
316 int N, ///< [in] the number of components to analyze
317 int overSample,
318 aosysT & aosys ///< [in[ the AO system defining the PSD characteristics.
319 )
320{
321 N *= overSample;
322 psd.resize(2*N + 1, 2*N+1);
323
324 realT mnCon = 0;
325 if( aosys.d_min() > 0)
326 {
327 mnCon = floor( aosys.D()/aosys.d_min()/2.0);
328 }
329
330 for(int i=0; i<=N; ++i)
331 {
332 for(int j=-N; j<=N; ++j)
333 {
334
335 realT D = aosys.D();
336 realT k = sqrt( pow(i,2) + pow(j,2))/D/overSample;
337
338 realT P = aosys.psd(aosys.atm, k, aosys.lam_sci(), 0, aosys.lam_wfs(), 1.0);
339
340 if(mnCon > 0 )
341 {
342 if( k*D < mnCon )
343 {
344 P *= pow(math::two_pi<realT>()*aosys.atm.v_wind()* k * (aosys.minTauWFS()+aosys.deltaTau()),2);
345 }
346 }
347
348 psd(N+i, N + j) = P/pow(D*overSample,2);
349 psd(N-i, N - j) = P/pow(D*overSample,2);
350 }
351 }
352
353 //Create Airy PSF for convolution with PSD psd.
354 Eigen::Array<realT, -1,-1> psf;
355 psf.resize(2*N+3,2*N+3);
356 for(int i=0;i<psf.rows();++i)
357 {
358 for(int j=0;j<psf.cols();++j)
359 {
360 psf(i,j) = mx::math::func::airyPattern(sqrt( pow( i-floor(.5*psf.rows()),2) + pow(j-floor(.5*psf.cols()),2))/overSample);
361 }
362 }
363
364 mx::AO::analysis::varmapToImage(var, psd, psf);
365
366 return 0;
367}
368
369template<typename realT>
370int zernikeCovarMap( const std::string & fname, ///< [out] the path where the output FITS file will be written
371 int N, ///< [in] the linear number of zernike modes across the aperture. The Nyquist frequency is set by N/2.
372 realT D,
373 realT L_0,
374 bool subPist,
375 bool subTilt,
376 realT absTol,
377 realT relTol,
378 bool modified=true
379 )
380{
381 std::vector<mx::sigproc::zernikeModeDef> ml;
382 mx::sigproc::makezernikeModeFreqs_Rect(ml, N);
383
384
386
387 aosys.loadMagAOX();
388
389 //This is just a normalization parameter in this context.
390 aosys.atm.r_0(1.0, 0.5e-6);
391
392 aosys.D( D );
393 aosys.atm.L_0( L_0 );
394 aosys.psd.subPiston( subPist );
395 aosys.psd.subTipTilt( subTilt );
396
397 int psz = ml.size();
398
399 Eigen::Array<realT,-1,-1> covar( psz, psz);
400 covar.setZero();
401
402 //int ncalc = 0.5*( psz*psz - psz);
403
404 ipc::ompLoopWatcher<> watcher(psz, std::cout);
405
406 std::cerr << "Starting . . .\n";
407#pragma omp parallel
408 {
410 Pp.absTol = absTol;
411 Pp.relTol = relTol;
412 Pp.aosys = &aosys;
413
414 if(!modified) Pp.useBasic = true;
415
416 realT result, error;
417
418#pragma omp for schedule( static, 5 )
419 for(int i=0; i< psz; ++i)
420 {
421 for(int j=i; j< psz; ++j)
422 {
423 Pp.p = ml[i].p;
424 Pp.m = ml[i].m;
425 Pp.n = ml[i].n;
426
427 Pp.pp = ml[j].p;
428 Pp.mp = ml[j].m;
429 Pp.np = ml[j].n;
430 result = Pp.getVariance(error);
431
432 covar(i,j) = result;
433
434 }
435 watcher.incrementAndOutputStatus();
436 }
437 }
438
439 fits::fitsHeader head;
440 head.append("DIAMETER", aosys.D(), "Diameter in meters");
441 head.append("NSUBAP", N, "Linear number of s.f. sampled");
442 head.append("L0", aosys.atm.L_0(0), "Outer scale (L_0) in meters");
443 head.append("SUBPIST", aosys.psd.subPiston(), "Piston subtractioon true/false flag");
444 head.append("SUBTILT", aosys.psd.subTipTilt(), "Tip/Tilt subtractioon true/false flag");
445 head.append("ABSTOL", absTol, "Absolute tolerance in qagiu");
446 head.append("RELTOL", relTol, "Relative tolerance in qagiu");
447
448 fitsHeaderGitStatus(head, "mxlib_uncomp", MXLIB_UNCOMP_CURRENT_SHA1, MXLIB_UNCOMP_REPO_MODIFIED);
449
450
452 ff.write(fname + ".fits", covar, head);
453
454 return 0;
455}
456
457
458
459
460template<typename realT, typename aosysT>
461int zernikeCovarMapSeparated( const std::string & fname,
462 int N,
463 aosysT & aosys,
464 realT absTol,
465 realT relTol,
466 bool modified=true)
467{
468 std::vector<mx::sigproc::zernikeModeDef> ml;
469 mx::sigproc::makezernikeModeFreqs_Rect(ml, N);
470
471 int psz = 0.5*ml.size();
472
473 Eigen::Array<realT,-1,-1> covar_pp( (int) (0.5*psz), (int)(.5*psz)), covar_ppp( (int) (0.5*psz), (int)(0.5*psz));
474 covar_pp.setZero();
475 covar_ppp.setZero();
476
477 realT mnCon = 0;
478 if( aosys.d_min() > 0)
479 {
480 mnCon = floor( aosys.D()/aosys.d_min()/2.0);
481 }
482
483 ipc::ompLoopWatcher<> watcher((psz+1)*0.125*(psz+1)*2, std::cout);
484
485#pragma omp parallel
486 {
487
488 zernikeCovariance<realT, aosysT > Pp;
489 Pp.absTol = absTol;
490 Pp.relTol = relTol;
491 Pp.aosys = &aosys;
492
493 if(!modified) Pp.useBasic = true;
494
495 Pp.mnCon = mnCon;
496
497 realT result, error;
498
499#pragma omp for schedule( dynamic, 5 )
500 for(int i=0; i< psz; i+=2)
501 {
502 for(int j=0; j<= 0.5*i; ++j)
503 {
504 for(int k=0; k< 2; ++k)
505 {
506 Pp.p = ml[i].p;
507 Pp.m = ml[i].m;
508 Pp.n = ml[i].n;
509
510 Pp.pp = ml[2*j + k].p;
511 Pp.mp = ml[2*j + k].m;
512 Pp.np = ml[2*j + k].n;
513 result = Pp.getVariance(error);
514
515 if( Pp.p == Pp.pp)
516 {
517 covar_pp(i/2, j) = result;
518 }
519 else
520 {
521 covar_ppp(i/2, j) = result;
522 }
523 watcher.incrementAndOutputStatus();
524 }
525 }
526 }
527 }
528
529 fits::fitsHeader head;
530 head.append("DIAMETER", aosys.D(), "Diameter in meters");
531 head.append("L0", aosys.atm.L_0(), "Outer scale (L_0) in meters");
532 head.append("SUBPIST", aosys.psd.subPiston(), "Piston subtractioon true/false flag");
533 head.append("SUBTILT", aosys.psd.subTipTilt(), "Tip/Tilt subtractioon true/false flag");
534 head.append("ABSTOL", absTol, "Absolute tolerance in qagiu");
535 head.append("RELTOL", relTol, "Relative tolerance in qagiu");
536
537
538 fitsHeaderGitStatus(head, "mxlib_uncomp", MXLIB_UNCOMP_CURRENT_SHA1, MXLIB_UNCOMP_REPO_MODIFIED);
539
540 fits::fitsFile<realT> ff;
541 ff.write(fname + "_pp.fits", covar_pp, head);
542 ff.write(fname + "_ppp.fits", covar_ppp, head);
543
544 return 0;
545}
546
547template<typename realT>
548void calcKLCoeffs( const std::string & outFile,
549 const std::string & cvFile )
550{
551 fits::fitsFile<realT> ff;
552
553 Eigen::Array<realT,-1,-1> cvT, cv, evecs, evals;
554
555 ff.read(cv, cvFile);
556
557 //cvT = cv.block(0,0, 1000,1000);//.transpose();
558
559 std::cerr << cvT.rows() << " " << cvT.cols() << "\n";
560 std::cerr << "1\n";
562
563 double t0 = sys::get_curr_time();
564
565 int info = math::eigenSYEVR<double,double>(evecs, evals, cv, 0, -1, 'U', &mem);
566
567 double t1 = sys::get_curr_time();
568
569 std::cerr << "2\n";
570
571 if(info !=0 )
572 {
573 std::cerr << "info =" << info << "\n";
574 exit(0);
575 }
576
577 std::cerr << "Time = " << t1-t0 << " secs\n";
578
579 //Normalize the eigenvectors
580 for(int i=0;i< evecs.cols(); ++i)
581 {
582 evecs.col(i) = evecs.col(i)/sqrt(fabs(evals(i)));
583 }
584
585 ff.write(outFile, evecs);
586}
587
588template<typename eigenArrT1, typename eigenArrT2, typename eigenArrT3>
589void makeKL( eigenArrT1 & kl,
590 eigenArrT2 & evecs,
591 eigenArrT3 && rvecs )
592{
593
594 int tNims = evecs.rows();
595 int tNpix = rvecs.rows();
596
597 int n_modes = tNims;
598
599 //Now calculate KL images
600 /*
601 * KL = E^T * R ==> C = A^T * B
602 */
603 math::gemm<typename eigenArrT1::Scalar>(CblasColMajor, CblasTrans, CblasTrans, n_modes, tNpix,
604 tNims, 1., evecs.data(), evecs.rows(), rvecs.data(), rvecs.rows(),
605 0., kl.data(), kl.rows());
606
607}
608
609template<typename realT>
610void makeFKL( const std::string & outFile,
611 const std::string & coeffs,
612 int N,
613 int pupSize )
614{
615 fits::fitsFile<realT> ff;
616 Eigen::Array<realT, -1, -1> evecs;
617
618 ff.read(evecs, coeffs);
619
620 improc::eigenCube<realT> Rims;
621 sigproc::makezernikeBasis_Rect(Rims, pupSize, N, MX_zernike_MODIFIED);
622
623 std::cout << Rims.planes() << " " << evecs.cols() << "\n";
624
625 Eigen::Array<realT,-1,-1> kl;
626 kl.resize( Rims.planes(), Rims.rows()*Rims.cols());
627
628 std::cerr << 1 << "\n";
629 makeKL( kl, evecs, Rims.cube());
630 std::cerr << 2 << "\n";
631
632 Eigen::Array<realT,-1,-1> klT = kl.transpose();
633 //kl.resize(0,0);
634 //Rims.resize(0,0);
635 //evecs.resize(0,0);
636
637 improc::eigenCube<realT> klims(klT.data(), Rims.rows(), Rims.cols(), Rims.planes());
638
639 improc::eigenCube<realT> klimsR;
640 klimsR.resize( klims.rows(), klims.cols(), klims.planes());
641
642 for(int i=0; i< klims.planes(); ++i)
643 {
644 klimsR.image(i) = klims.image(klims.planes()-1-i);
645 }
646
647 ff.write(outFile, klimsR);
648 std::cerr << 3 << "\n";
649}
650
651#endif
652
653} // namespace analysis
654} // namespace AO
655} // namespace mx
656
657#endif // zernikeCovariance_hpp
Provides a class to specify atmosphere parameters.
Spatial power spectra used in adaptive optics.
Declares and defines an analytical AO system.
realT L_0(const size_t &n)
Get the value of the outer scale for a single layer.
realT r_0()
Get the value of Fried's parameter r_0 at the reference wavelength lam_0.
Describes an analytic adaptive optics (AO) system.
Definition aoSystem.hpp:65
void loadMagAOX()
Load parameters corresponding to the MagAO-X system.
void D(realT nD)
Set the value of the primary mirror diameter.
mxException for parameters which aren't set
Class to manage interactions with a FITS file.
Definition fitsFile.hpp:52
int write(const dataT *im, int d1, int d2, int d3, fitsHeader *head)
Write the contents of a raw array to the FITS file.
Class to manage a complete fits header.
void append(fitsHeaderCard card)
Append a fitsHeaderCard to the end of the header.
A class to track the number of iterations in an OMP parallelized loop.
void incrementAndOutputStatus()
Increment and output status.
@ modified
The modified Fourier basis from .
Eigen::Array< scalarT, -1, -1 > eigenImage
Definition of the eigenImage type, which is an alias for Eigen::Array.
void fitsHeaderGitStatus(fitsHeader &head, const std::string &repoName, const char *sha1, int modified)
Write the status of a Git repository to HISTORY in a FITS header.
realT airyPattern(realT x)
The classical Airy pattern.
constexpr floatT six_fifths()
Return 6/5 in the specified precision.
void varmapToImage(imageT &im, imageT &varmap, imageT &psf)
Convert a wavefront variance map to an intensity image by convolving with the PSF.
typeT get_curr_time()
Get the current system time in seconds.
Definition timeUtils.hpp:90
The mxlib c++ namespace.
Definition mxError.hpp:106
Structure to manage the zernike mode covariance calculation, passed to integration functions.
realT m_kIntEpsAbs
Absolute tolerance for the radial integral. Default is 1e-10.
aosysT * m_aosys
Pointer to an AO system, which contains the relevant spatial PSD of turbulence.
realT m_k
Spatial frequency being calculated, passed for use in the integrand worker functions.
realT m_m
The m-index of the unprimed mode.
realT m_mp
The m-index of the primed mode, corresponding to the component of spatial frequency.
realT m_np
The n-indexof the primed mode, corresponding to the component of spatial frequency.
gsl_integration_workspace * m_workspacePhi
Working memory for the azimuthal integral.
realT m_phiIntEpsRel
Relative tolerance for the azimuthal integral. Default is 0, meaning absolute is used.
static realT phiInt(realT phi, void *params)
Worker for the azimuthal integral (in phi) for the zernike mode covariance.
realT getCovariance(realT &error)
Calculate the covariance between the two modes.
realT m_n
The n-index of the unprimed mode.
realT m_phiIntEpsAbs
Absolute tolerance for the azimuthal integral. Default is 1e-10.
gsl_integration_workspace * m_workspaceK
Working memory for the radial integral.
realT m_kIntEpsRel
Relative tolerance for the radial integral. Default is 0, meaning absolute is used.
static realT kInt(realT k, void *params)
Worker function for the radial integral in the covariance calculation.
A utility to convert a wavefront variance map to an intensity image.
int zernikeCovarMap(const std::string &fname, int N, realT D, realT L_0, bool subPist, bool subTilt, realT absTol, realT relTol, bool modified=true)
int zernikeVarVec(const std::string &fname, int N, aosysT &aosys, realT absTol, realT relTol, bool modifed=true)
Calculate a vector of zernike mode variances.
int zernikePSDMap(improc::eigenImage< realT > &var, improc::eigenImage< realT > &psd, int N, int overSample, aosysT &aosys)
Calculate a map of zernike variances by convolution with the PSD.