def __init__(self, n_beta): r"""New `CPsf` with Zernike polynomials up to radial order `n_beta`. The number of polynomials is `self.czern.nk`, i.e., :math:`N_\beta = (n_\beta + 1)(n_\beta + 2)/2`. """ self.czern = CZern(n_beta)
def test_normalisations_complex(self): log = logging.getLogger('TestZern.test_normalisations_complex') n_beta = 6 L, K = 400, 393 # polar grid pol = CZern(n_beta) fitBeta = FitZern(pol, L, K) t1 = time() pol.make_pol_grid(fitBeta.rho_j, fitBeta.theta_i) t2 = time() log.debug('make pol grid {:.6f}'.format(t2 - t1)) # cartesian grid cart = CZern(n_beta) dd = np.linspace(-1.0, 1.0, max(L, K)) xx, yy = np.meshgrid(dd, dd) t1 = time() cart.make_cart_grid(xx, yy) t2 = time() log.debug('make cart grid {:.6f}'.format(t2 - t1)) smap = np.isfinite(cart.eval_grid(np.zeros(cart.nk))) scale = (1.0/np.sum(smap)) log.debug('') log.debug('{} modes, {} x {} grid'.format(n_beta, L, K)) for i in range(pol.nk): a = np.zeros(pol.nk) a[i] = 1.0 Phi_a = cart.eval_grid(a) for j in range(pol.nk): b = np.zeros(pol.nk) b[j] = 1.0 Phi_b = cart.eval_grid(b) ip = scale*np.sum(Phi_a[smap]*(Phi_b[smap].conj())) if i == j: eip = 1.0 else: eip = 0.0 iperr = abs(ip - eip) log.debug('<{:02},{:02}> = {:+e} {:+e}'.format( i + 1, j + 1, ip, iperr)) self.assertTrue(iperr < self.max_ip_err)
def test_fit_complex_numpy(self): log = logging.getLogger('TestFitZern.test_fit_complex_numpy') z = CZern(4) F = FitZern(z, self.L, self.K) theta_i = F.theta_i rho_j = F.rho_j c = normal(size=z.nk) + 1j * normal(size=z.nk) Phi = [z.eval_a(c, rh, th) for rh in rho_j for th in theta_i] time1 = time() ce = F._fit_slow(Phi) time2 = time() log.debug('elapsed FIT_LIST {:.6f}'.format(time2 - time1)) PhiN = np.array(Phi, order='F') time1 = time() ce2 = F.fit(PhiN) time2 = time() log.debug('elapsed FIT_NUMPY {:.6f}'.format(time2 - time1)) enorm = norm(ce2 - np.array(ce, order='F')) log.debug('enorm {:e}'.format(enorm)) self.assertTrue(enorm < self.max_enorm)
def test_fit_complex(self): log = logging.getLogger('TestFitZern.test_fit_complex') z = CZern(4) F = FitZern(z, self.L, self.K) theta_i = F.theta_i rho_j = F.rho_j c = normal(size=z.nk) + 1j * normal(size=z.nk) time1 = time() Phi = [z.eval_a(c, rh, th) for rh in rho_j for th in theta_i] time2 = time() log.debug('eval Phi {:.4f}'.format(time2 - time1)) time1 = time() ce = F._fit_slow(Phi) time2 = time() log.debug('elapsed time {:.4f}'.format(time2 - time1)) err1 = np.sqrt(sum([abs(c[i] - ce[i])**2 for i in range(z.nk)])) max1 = max([abs(c[i] - ce[i]) for i in range(z.nk)]) log.debug('err1 {:e} max1 {:e} max {:e}'.format( err1, max1, self.max_fit_norm)) self.assertTrue(err1 < self.max_fit_norm)
def test_fit_complex_numpy(self): log = logging.getLogger('TestFitZern.test_fit_complex_numpy') z = CZern(4) F = FitZern(z, self.L, self.K) theta_i = F.theta_i rho_j = F.rho_j c = normal(size=z.nk) + 1j*normal(size=z.nk) Phi = [z.eval_a(c, rh, th) for rh in rho_j for th in theta_i] time1 = time() ce = F._fit_slow(Phi) time2 = time() log.debug('elapsed FIT_LIST {:.6f}'.format(time2 - time1)) PhiN = np.array(Phi, order='F') time1 = time() ce2 = F.fit(PhiN) time2 = time() log.debug('elapsed FIT_NUMPY {:.6f}'.format(time2 - time1)) enorm = norm(ce2 - np.array(ce, order='F')) log.debug('enorm {:e}'.format(enorm)) self.assertTrue(enorm < self.max_enorm)
def test_fit_complex(self): log = logging.getLogger('TestFitZern.test_fit_complex') z = CZern(4) F = FitZern(z, self.L, self.K) theta_i = F.theta_i rho_j = F.rho_j c = normal(size=z.nk) + 1j*normal(size=z.nk) time1 = time() Phi = [z.eval_a(c, rh, th) for rh in rho_j for th in theta_i] time2 = time() log.debug('eval Phi {:.4f}'.format(time2 - time1)) time1 = time() ce = F._fit_slow(Phi) time2 = time() log.debug('elapsed time {:.4f}'.format(time2 - time1)) err1 = np.sqrt(sum([abs(c[i] - ce[i])**2 for i in range(z.nk)])) max1 = max([abs(c[i] - ce[i]) for i in range(z.nk)]) log.debug('err1 {:e} max1 {:e} max {:e}'.format( err1, max1, self.max_fit_norm)) self.assertTrue(err1 < self.max_fit_norm)
def __init__(self, unparsed): super().__init__() args = self.do_cmdline(unparsed) # plot objects phaseplot = PhasePlot(n=args.n_alpha) # to plot beta and the PSF betaplot = BetaPlot(args) # to plot the phase # complex-valued Zernike polynomials for the GPF ip = FitZern(CZern(args.n_beta), args.fit_L, args.fit_K) # real-valued Zernike polynomials for the phase phase_pol = RZern(args.n_alpha) phase_pol.make_pol_grid(ip.rho_j, ip.theta_i) # make a polar grid # real-valued Zernike coefficients alpha = np.zeros(phase_pol.nk) # set the alpha coefficients randomly if args.random: alpha1 = normal(size=alpha.size - 1) alpha1 = (args.rms / norm(alpha1)) * alpha1 alpha[1:] = alpha1 del alpha1 self.rms = args.rms self.alpha = alpha self.phase_pol = phase_pol self.ip = ip self.betaplot = betaplot self.phaseplot = phaseplot # fit beta coefficients from alpha coefficients self.alpha2beta() # make gui self.make_gui()
def load_h5py(cls, f, prepend=None): """Load object contents from an opened HDF5 file object.""" z = cls(1) prefix = cls.__name__ + '/' if prepend is not None: prefix = prepend + prefix z.czern = CZern.load_h5py(f, prepend=prepend) try: z.Ugrid = f[prefix + 'Ugrid'].value except ValueError: pass try: z.Vnm = f[prefix + 'Vnm'].value except ValueError: pass try: z.Cnm = f[prefix + 'Cnm'].value except ValueError: pass return z
def test_normalisations_complex(self): log = logging.getLogger('TestZern.test_normalisations_complex') n_beta = 6 L, K = 400, 393 # polar grid pol = CZern(n_beta) fitBeta = FitZern(pol, L, K) t1 = time() pol.make_pol_grid(fitBeta.rho_j, fitBeta.theta_i) t2 = time() log.debug('make pol grid {:.6f}'.format(t2 - t1)) # cartesian grid cart = CZern(n_beta) dd = np.linspace(-1.0, 1.0, max(L, K)) xx, yy = np.meshgrid(dd, dd) t1 = time() cart.make_cart_grid(xx, yy) t2 = time() log.debug('make cart grid {:.6f}'.format(t2 - t1)) smap = np.isfinite(cart.eval_grid(np.zeros(cart.nk))) scale = (1.0 / np.sum(smap)) log.debug('') log.debug('{} modes, {} x {} grid'.format(n_beta, L, K)) for i in range(pol.nk): a = np.zeros(pol.nk) a[i] = 1.0 Phi_a = cart.eval_grid(a) for j in range(pol.nk): b = np.zeros(pol.nk) b[j] = 1.0 Phi_b = cart.eval_grid(b) ip = scale * np.sum(Phi_a[smap] * (Phi_b[smap].conj())) if i == j: eip = 1.0 else: eip = 0.0 iperr = abs(ip - eip) log.debug('<{:02},{:02}> = {:+e} {:+e}'.format( i + 1, j + 1, ip, iperr)) self.assertTrue(iperr < self.max_ip_err)
References ---------- .. [A2015] Jacopo Antonello and Michel Verhaegen, "Modal-based phase retrieval for adaptive optics," J. Opt. Soc. Am. A 32, 1160-1170 (2015) . `url <http://dx.doi.org/10.1364/JOSAA.32.001160>`__. """ if __name__ == '__main__': # grid sizes L, K = 95, 105 # complex-valued Zernike polynomials up to the 4-th radial order gpf_pol = CZern(4) # to approximate the GPF # FitZern computes the approximate inner products, see Eq. (B4) in [A2015] ip = FitZern(gpf_pol, L, K) gpf_pol.make_pol_grid(ip.rho_j, ip.theta_i) # make a polar grid # random vector of Zernike coefficients to be estimated beta_true = normal(size=gpf_pol.nk) + 1j * normal(size=gpf_pol.nk) # random generalised pupil function P P = gpf_pol.eval_grid(beta_true) # estimate the random vector from the GPF grid beta_hat = ip.fit(P) # plot the results
'--rms', type=float, default=1.0, help='Rms of the alpha aberration.') parser.add_argument( '--random', action='store_true', help='Make a random alpha aberration.') parser.add_argument( '--fit-L', type=int, default=95, metavar='L', help='Grid size for the inner products.') parser.add_argument( '--fit-K', type=int, default=105, metavar='K', help='Grid size for the inner products.') args = parser.parse_args() # complex-valued Zernike polynomials for the GPF ip = FitZern(CZern(args.n_beta), args.fit_L, args.fit_K) # real-valued Zernike polynomials for the phase phase_pol = RZern(args.n_alpha) phase_pol.make_pol_grid(ip.rho_j, ip.theta_i) # make a polar grid # real-valued Zernike coefficients alpha = np.zeros(phase_pol.nk) # nm to linear index conversion nmlist = list(zip(phase_pol.ntab, phase_pol.mtab)) # set an alpha coefficient using the (n, m) indeces if args.nm[0] != -1 and args.nm[0] != -1: try: k = nmlist.index(tuple(args.nm))
class CPsf: r"""Complex point-spread function object. A finite set of complex-valued Zernike polynomials are used to approximate the generalised pupil function. The polynomials are ordered and normalised as outlined in Appendix A of [A2015]_. The complex point-spread function is computed using the extended Nijboer-Zernike formulas found in [B2008]_. The coordinates in the image plane :math:`(r, \phi)` are normalised by the diffraction unit, see [B2008]_, [J2002]_, and [H2010]_. References ---------- .. [J2002] A. J. E. M. Janssen, "Extended Nijboer–Zernike approach for the computation of optical point-spread functions," J. Opt. Soc. Am. A 19, 849–857 (2002). `doi <http://dx.doi.org/10.1364/JOSAA.19.000849>`__. .. [B2008] J. Braat, S. van Haver, A. Janssen, P. Dirksen, Chapter 6 Assessment of optical systems by means of point-spread functions, In: E. Wolf, Editor(s), Progress in Optics, Elsevier, 2008, Volume 51, Pages 349-468, ISSN 0079-6638, ISBN 9780444532114. `doi <http://dx.doi.org/10.1016/S0079-6638(07)51006-1>`__. .. [H2010] S. van Haver, The Extended Nijboer-Zernike Diffraction Theory and its Applications (Ph.D. thesis, Delft University of Technology, The Netherlands, 2010). `doi <http://resolver.tudelft.nl/uuid:8d96ba75-24da-4e31-a750-1bc348155061>`__. .. [A2015] Jacopo Antonello and Michel Verhaegen, "Modal-based phase retrieval for adaptive optics," J. Opt. Soc. Am. A 32, 1160-1170 (2015). `url <http://dx.doi.org/10.1364/JOSAA.32.001160>`__. """ def __init__(self, n_beta): r"""New `CPsf` with Zernike polynomials up to radial order `n_beta`. The number of polynomials is `self.czern.nk`, i.e., :math:`N_\beta = (n_\beta + 1)(n_\beta + 2)/2`. """ self.czern = CZern(n_beta) def P(self, beta, rho, theta): r"""Evaluate the generalised pupil function at a point. The generalised pupil function is .. math:: P(\rho, \theta) = \sum_{n, m} \beta_n^m \sqrt{n + 1}R_n^{|m|}(\rho) exp(i m \theta). The summation extends over :math:`N_beta` = `self.czern.nk` addends. The support of :math:`P(\rho, \theta)` is the unit disk. See Eq. (2) in [A2015]_. Parameters ---------- - `beta`: list of complex-valued Zernike coefficients :math:`\beta_n^m` - `rho`: float for the radial coordinate :math:`\rho` - `theta`: float for the azimuthal coordinate :math:`\theta` Returns ------- - `P`: complex value of :math:`P(\rho, \theta)` References ---------- .. [A2015] Jacopo Antonello and Michel Verhaegen, "Modal-based phase retrieval for adaptive optics," J. Opt. Soc. Am. A 32, 1160-1170 (2015). `url <http://dx.doi.org/10.1364/JOSAA.32.001160>`__. """ return self.czern.eval_a(beta, rho, theta) def _numpify(self, a, dtype): if isinstance(a, np.ndarray): return a.ravel().astype(dtype, order='F', copy=False) elif isinstance(a, list): return np.array(a, order='F', dtype=dtype) else: return np.array([a], order='F', dtype=dtype) def V(self, n, m, r, f, L_max=35, ncpus=-1): r"""Evaluate :math:`V_n^m(r, f)`. Compute .. math:: V_n^m(r, f) = \epsilon_m \exp(if) \sum_{l=1}^{L_max}(-2if)^{l - 1} \sum_{j=0}^{(n - |m|)/2} v_{l,j} (1/l(2\pi r)^l)J_{|m| + l + 2j}(2\pi r). See Eq. (2.48) in [B2008]_. Parameters ---------- - `n`: `numpy` vector of integers for the radial orders `n` - `m`: `numpy` vector of integers for the azimuthal frequencies `m` - `r`: `numpy` vector of doubles for the radial coordinate :math:`r`, which is normalised to the diffraction unit `wavelength/NA` - `f`: `numpy` vector of complex numbers for the defocus parameter, see [J2002]_, [B2008]_, and [H2010]_ - `L_max`: optional `int` for the truncation order of the series, see [J2002]_, [B2008]_, and [H2010]_. `L_max <= 0` uses the default value of `35`. - `ncpus` : optional `int` for the number of threads. `-1` chooses all available cpus Returns ------- - `vnm`: `numpy` array of shape `(r.size, f.size, n.size)` for :math:`V_n^m(r, f)` References ---------- .. [J2002] A. J. E. M. Janssen, "Extended Nijboer–Zernike approach for the computation of optical point-spread functions," J. Opt. Soc. Am. A 19, 849–857 (2002). `doi <http://dx.doi.org/10.1364/JOSAA.19.000849>`__. .. [B2008] J. Braat, S. van Haver, A. Janssen, P. Dirksen, Chapter 6 Assessment of optical systems by means of point-spread functions, In: E. Wolf, Editor(s), Progress in Optics, Elsevier, 2008, Volume 51, Pages 349-468, ISSN 0079-6638, ISBN 9780444532114. `doi <http://dx.doi.org/10.1016/S0079-6638(07)51006-1>`__. .. [H2010] S. van Haver, The Extended Nijboer-Zernike Diffraction Theory and its Applications (Ph.D. thesis, Delft University of Technology, The Netherlands, 2010). `doi <http://resolver.tudelft.nl/uuid:8d96ba75-24da-4e31-a750-1bc348155061>`__. """ n = self._numpify(n, np.int) m = self._numpify(m, np.int) r = self._numpify(r, np.float) f = self._numpify(f, np.complex) Vnm = vnmpocnp(r, f, n, m, L_max=L_max, ncpus=ncpus) if Vnm.size == 1: return complex(Vnm[0, 0, 0]) else: return Vnm def U(self, beta, r, phi, f): r"""Evaluate the complex point spread function at a point. The complex point-spread function is .. math:: U(r, \phi, f) = 2 \sum_{n, m} \beta_{n}^{m} \sqrt{n + 1} i^{m} V_n^m(r, f) \exp(im\phi). See Eq. (4) in [A2015]_. Parameters ---------- - `beta`: `list` of the complex Zernike coefficients :math:`\beta_n^m` - `r`: float for the radial coordinate :math:`r`, normalised to the diffraction unit `wavelength/NA` - `phi`: float for the azimuthal coordinate :math:`\phi` - `f`: complex-valued defocus parameter, see [J2002]_, [B2008]_, and [H2010]_ Returns ------- - `U`: complex value of :math:`U(r, \phi, f)` References ---------- .. [A2015] Jacopo Antonello and Michel Verhaegen, "Modal-based phase retrieval for adaptive optics," J. Opt. Soc. Am. A 32, 1160-1170 (2015). `url <http://dx.doi.org/10.1364/JOSAA.32.001160>`__. .. [J2002] A. J. E. M. Janssen, "Extended Nijboer–Zernike approach for the computation of optical point-spread functions," J. Opt. Soc. Am. A 19, 849–857 (2002). `doi <http://dx.doi.org/10.1364/JOSAA.19.000849>`__. .. [B2008] J. Braat, S. van Haver, A. Janssen, P. Dirksen, Chapter 6 Assessment of optical systems by means of point-spread functions, In: E. Wolf, Editor(s), Progress in Optics, Elsevier, 2008, Volume 51, Pages 349-468, ISSN 0079-6638, ISBN 9780444532114. `doi <http://dx.doi.org/10.1016/S0079-6638(07)51006-1>`__. .. [H2010] S. van Haver, The Extended Nijboer-Zernike Diffraction Theory and its Applications (Ph.D. thesis, Delft University of Technology, The Netherlands, 2010). `doi <http://resolver.tudelft.nl/uuid:8d96ba75-24da-4e31-a750-1bc348155061>`__. """ U = complex(0.0) r = self._numpify(r, np.float) f = self._numpify(f, np.complex) assert (r.size == 1) assert (f.size == 1) n, m = np.array([0]), np.array([0]) for i in range(self.czern.nk): n[0], m[0] = self.czern.ntab[i], self.czern.mtab[i] cf = self.czern.coefnorm[i] U += complex(beta[i] * cf * ((1j)**m[0]) * vnmpocnp(r, f, n, m)[0][0][0] * cmath.exp(1j * m[0] * phi)) return 2.0 * U def _trim_r(self, r_sp, min_r): if np.abs(r_sp).min() < min_r: r_sp[np.abs(r_sp) <= min_r] = min_r assert (np.abs(r_sp).min() >= min_r) return r_sp def make_cart_grid(self, x_sp=None, y_sp=None, f_sp=None, min_r=1e-9): r"""Make a cartesian grid for the complex point-spread function. The complex point-spread function is .. math:: U(r, \phi, f) = 2 \sum_{n, m} \beta_{n}^{m} \sqrt{n + 1} i^{m} V_n^m(r, f) \exp(im\phi). See Eq. (4) in [A2015]_. Parameters ---------- - `x_sp`: `numpy` array of the `x` coordinates in the image plane - `y_sp`: `numpy` array of the `y` coordinates in the image plane - `f_sp`: `numpy` array of complex numbers for the defocus parameter, see [J2002]_, [B2008]_, [H2010]_ - `min_r`: float, optional threshold to avoid division by zero in the image plane References ---------- .. [A2015] Jacopo Antonello and Michel Verhaegen, "Modal-based phase retrieval for adaptive optics," J. Opt. Soc. Am. A 32, 1160-1170 (2015). `url <http://dx.doi.org/10.1364/JOSAA.32.001160>`__. .. [J2002] A. J. E. M. Janssen, "Extended Nijboer–Zernike approach for the computation of optical point-spread functions," J. Opt. Soc. Am. A 19, 849–857 (2002). `doi <http://dx.doi.org/10.1364/JOSAA.19.000849>`__. .. [B2008] J. Braat, S. van Haver, A. Janssen, P. Dirksen, Chapter 6 Assessment of optical systems by means of point-spread functions, In: E. Wolf, Editor(s), Progress in Optics, Elsevier, 2008, Volume 51, Pages 349-468, ISSN 0079-6638, ISBN 9780444532114. `doi <http://dx.doi.org/10.1016/S0079-6638(07)51006-1>`__. .. [H2010] S. van Haver, The Extended Nijboer-Zernike Diffraction Theory and its Applications (Ph.D. thesis, Delft University of Technology, The Netherlands, 2010). `doi <http://resolver.tudelft.nl/uuid:8d96ba75-24da-4e31-a750-1bc348155061>`__. """ x_sp = self._numpify(x_sp, np.float).ravel(order='F') y_sp = self._numpify(y_sp, np.float).ravel(order='F') f_sp = self._numpify(f_sp, np.complex).ravel(order='F') xx, yy = np.meshgrid(x_sp, y_sp) r_sp = np.sqrt(np.square(xx) + np.square(yy)).ravel(order='F') # remove nans due to r = 0.0 r_sp = self._trim_r(r_sp, min_r) ph_sp = np.arctan2(yy, xx).ravel(order='F') Ugrid = np.zeros((x_sp.size, y_sp.size, f_sp.size, self.czern.nk), order='F', dtype=np.complex) Vnm = vnmpocnp(r_sp, f_sp, self.czern.ntab, self.czern.mtab) assert (np.all(np.isfinite(Vnm))) Cnm = np.zeros((ph_sp.size, self.czern.nk), order='F', dtype=np.complex) for k in range(self.czern.nk): m = self.czern.mtab[k] Cnm[:, k] = 2.0 * self.czern.coefnorm[k] * ( (1j)**m) * np.exp(1j * m * ph_sp) for k in range(self.czern.nk): for f in range(f_sp.size): Ugrid[:, :, f, k] = Vnm[:, f, k].reshape( (x_sp.size, y_sp.size), order='F') * Cnm[:, k].reshape( (x_sp.size, y_sp.size), order='F') assert (np.all(np.isfinite(Ugrid))) self.Ugrid = Ugrid self.Vnm = Vnm self.Cnm = Cnm def make_pol_grid(self, r_sp=None, ph_sp=None, f_sp=None, min_r=1e-9): r"""Make a polar grid for the complex point-spread function. The complex point-spread function is .. math:: U(r, \phi, f) = 2 \sum_{n, m} \beta_{n}^{m} \sqrt{n + 1} i^{m} V_n^m(r, f) \exp(im\phi). See Eq. (4) in [A2015]_. Parameters ---------- - `r_sp`: `numpy` array of the radial coordinate :math:`r` - `ph_sp`: `numpy` array of the azimuthal coordinate :math:`\phi` - `f_sp`: `numpy` array of complex numbers for the defocus parameter, see [J2002]_, [B2008]_, [H2010]_ - `min_r`: float, optional threshold to avoid division by zero in the image plane References ---------- .. [A2015] Jacopo Antonello and Michel Verhaegen, "Modal-based phase retrieval for adaptive optics," J. Opt. Soc. Am. A 32, 1160-1170 (2015). `url <http://dx.doi.org/10.1364/JOSAA.32.001160>`__. .. [J2002] A. J. E. M. Janssen, "Extended Nijboer–Zernike approach for the computation of optical point-spread functions," J. Opt. Soc. Am. A 19, 849–857 (2002). `doi <http://dx.doi.org/10.1364/JOSAA.19.000849>`__. .. [B2008] J. Braat, S. van Haver, A. Janssen, P. Dirksen, Chapter 6 Assessment of optical systems by means of point-spread functions, In: E. Wolf, Editor(s), Progress in Optics, Elsevier, 2008, Volume 51, Pages 349-468, ISSN 0079-6638, ISBN 9780444532114. `doi <http://dx.doi.org/10.1016/S0079-6638(07)51006-1>`__. .. [H2010] S. van Haver, The Extended Nijboer-Zernike Diffraction Theory and its Applications (Ph.D. thesis, Delft University of Technology, The Netherlands, 2010). `doi <http://resolver.tudelft.nl/uuid:8d96ba75-24da-4e31-a750-1bc348155061>`__. """ r_sp = self._numpify(r_sp, np.float).ravel(order='F') ph_sp = self._numpify(ph_sp, np.float).ravel(order='F') f_sp = self._numpify(f_sp, np.complex).ravel(order='F') # remove nans due to r = 0.0 r_sp = self._trim_r(r_sp, min_r) Ugrid = np.zeros((r_sp.size, ph_sp.size, f_sp.size, self.czern.nk), order='F', dtype=np.complex) Vnm = vnmpocnp(r_sp, f_sp, self.czern.ntab, self.czern.mtab) Cnm = np.zeros((ph_sp.size, self.czern.nk), order='F', dtype=np.complex) for k in range(self.czern.nk): m = self.czern.mtab[k] Cnm[:, k] = 2.0 * self.czern.coefnorm[k] * ( (1j)**m) * np.exp(1j * m * ph_sp) for k in range(self.czern.nk): for f in range(f_sp.size): v = Vnm[:, f, k].reshape((Vnm.shape[0], 1)) c = Cnm[:, k].reshape((1, Cnm.shape[0])) Ugrid[:, :, f, k] = np.kron(c, v) assert (np.all(np.isfinite(Ugrid))) self.Ugrid = Ugrid self.Vnm = Vnm self.Cnm = Cnm def eval_grid(self, beta, i=0, j=0, f_k=0): r"""Evaluate the complex point-spread function at a point in a grid. The complex point-spread function is .. math:: U(r, \phi, f) = 2 \sum_{n, m} \beta_{n}^{m} \sqrt{n + 1} i^{m} V_n^m(r, f) \exp(im\phi). See Eq. (4) in [A2015]_. Parameters ---------- - `beta`: `numpy` vector for the Zernike coefficients :math:`\beta_n^m` - `i`: `int` first slicing index - `j`: `int` second slicing index - `f_k`: `int` slicing index for the defocus parameter Returns ------- - `U`: complex value of :math:`U(r, \phi, f)` References ---------- .. [A2015] Jacopo Antonello and Michel Verhaegen, "Modal-based phase retrieval for adaptive optics," J. Opt. Soc. Am. A 32, 1160-1170 (2015). `url <http://dx.doi.org/10.1364/JOSAA.32.001160>`__. """ return np.dot(self.Ugrid[i, j, f_k, :], self._numpify(beta, np.complex)) def eval_grid_f(self, beta, f_k=0, const_phase=0.0): r"""Slice the complex point-spread function grid at defocus `f_k`. The complex point-spread function is .. math:: U(r, \phi, f) = 2 \sum_{n, m} \beta_{n}^{m} \sqrt{n + 1} i^{m} V_n^m(r, f) \exp(im\phi). See Eq. (4) in [A2015]_. Parameters ---------- - `beta`: `numpy` vector for the Zernike coefficients :math:`\beta_n^m` - `f_k`: `int` slicing index for the defocus parameter - `const_phase`: optional `float` to add a constant phase Returns ------- - `U`: A `numpy` array of shape `self.Ugrid.shape[:2]` Examples -------- .. code:: python import math import numpy as np import matplotlib.pyplot as p import enzpy.enz as enz from time import time from enzpy.enz import CPsf wavelength=632.8e-9 # wavelength in [m] aperture_radius=0.002 # aperture radius in [m] focal_length=500e-3 # focal length in [m] pixel_size=7.4e-6 # pixel size in [m] image_width=75 # [pixels], odd to get the origin as well image_height=151 # [pixels] fspace=np.linspace( # defocus planes specified by an array of -3.0, 2.0, 6) # defocus parameters n_beta=4 # max radial order for the Zernikes # compute the diffraction unit (lambda/NA) fu = enz.get_field_unit( wavelength, aperture_radius, focal_length) def make_space(w, p, fu): if w % 2 == 0: return np.linspace(-(w/2 - 0.5), w/2 - 0.5, w)*p/fu else: return np.linspace(-(w - 1)/2, (w - 1)/2, w)*p/fu # image side space xspace = make_space(image_width, pixel_size, fu) yspace = make_space(image_height, pixel_size, fu) image_size = (image_height, image_width) # consider complex-valued Zernike polynomials up to the radial # order n_beta to approximate the PSF, see Eq. (1) and Eq. (2) in # [A2015]_ cpsf = CPsf(n_beta) # make a cartesian grid to evaluate the PSF t1 = time() cpsf.make_cart_grid(x_sp=xspace, y_sp=yspace, f_sp=fspace) t2 = time() print('make_cart_grid {:.6f} sec'.format(t2 - t1)) def plot_psf(U, interpolation='nearest', vmin=None, vmax=None): # evaluate the modulus squared mypsf = np.square(np.abs(U)).reshape(image_size, order='F') p.imshow( mypsf, interpolation=interpolation, vmin=vmin, vmax=vmax, origin='lower') p.axis('off') def plot_beta_f(beta, fi): U = cpsf.eval_grid_f(beta, fi) plot_psf(U) # beta (diffraction-limited), N_beta = cpsf.czern.nk beta = np.zeros(cpsf.czern.nk, dtype=np.complex) beta[0] = 1.0 # or a random beta beta = ( np.random.normal(size=cpsf.czern.nk) + 1j*np.random.normal(size=cpsf.czern.nk)) beta[0] = 1.0 # plot the results nn, mm = 2, math.ceil(fspace.size//2) p.figure(1) for fi, f in enumerate(fspace): ax = p.subplot(nn, mm, fi + 1) # plot the psf plot_beta_f(beta, fi) p.colorbar() # defocus in rad p.title('d={:.1f}'.format(enz.get_defocus(f))) p.tight_layout() p.show() References ---------- .. [A2015] Jacopo Antonello and Michel Verhaegen, "Modal-based phase retrieval for adaptive optics," J. Opt. Soc. Am. A 32, 1160-1170 (2015). `url <http://dx.doi.org/10.1364/JOSAA.32.001160>`__. """ return np.exp(-1j * const_phase) * (np.dot( self.Ugrid[:, :, f_k], self._numpify(beta, np.complex)).ravel(order='F')) def save(self, filename, prepend=None, params=HDF5_options, libver='latest'): """Save object into an HDF5 file.""" f = h5py.File(filename, 'w', libver=libver) self.save_h5py(f, prepend=prepend, params=params) f.close() def save_h5py(self, f, prepend=None, params=HDF5_options): """Dump object contents into an opened HDF5 file object.""" prefix = self.__class__.__name__ + '/' if prepend is not None: prefix = prepend + prefix try: params['data'] = self.Ugrid f.create_dataset(prefix + 'Ugrid', **params) except ValueError: pass try: params['data'] = self.Vnm f.create_dataset(prefix + 'Vnm', **params) except ValueError: pass try: params['data'] = self.Cnm f.create_dataset(prefix + 'Cnm', **params) except ValueError: pass self.czern.save_h5py(f, prepend=prepend) @classmethod def load(cls, filename, prepend=None): """Load object from an HDF5 file.""" f = h5py.File(filename, 'r') z = cls.load_h5py(f, prepend=prepend) f.close() return z @classmethod def load_h5py(cls, f, prepend=None): """Load object contents from an opened HDF5 file object.""" z = cls(1) prefix = cls.__name__ + '/' if prepend is not None: prefix = prepend + prefix z.czern = CZern.load_h5py(f, prepend=prepend) try: z.Ugrid = f[prefix + 'Ugrid'].value except ValueError: pass try: z.Vnm = f[prefix + 'Vnm'].value except ValueError: pass try: z.Cnm = f[prefix + 'Cnm'].value except ValueError: pass return z
References ---------- .. [A2015] Jacopo Antonello and Michel Verhaegen, "Modal-based phase retrieval for adaptive optics," J. Opt. Soc. Am. A 32, 1160-1170 (2015) . `url <http://dx.doi.org/10.1364/JOSAA.32.001160>`__. """ if __name__ == '__main__': # grid sizes L, K = 95, 105 # complex-valued Zernike polynomials up to the 4-th radial order gpf_pol = CZern(4) # to approximate the GPF # FitZern computes the approximate inner products, see Eq. (B4) in [A2015] ip = FitZern(gpf_pol, L, K) gpf_pol.make_pol_grid(ip.rho_j, ip.theta_i) # make a polar grid # random vector of Zernike coefficients to be estimated beta_true = normal(size=gpf_pol.nk) + 1j*normal(size=gpf_pol.nk) # random generalised pupil function P P = gpf_pol.eval_grid(beta_true) # estimate the random vector from the GPF grid beta_hat = ip.fit(P) # plot the results