def nm_to_name(n, m): """Convert an (n,m) index into a human readable name. Parameters ---------- n : int radial polynomial order m : int azimuthal polynomial order Returns ------- str a name, np.g. Piston or Primary Spherical """ # piston, tip tilt, az invariant order if n == 0: return 'Piston' if n == 1: if sign(m) == 1: return 'Tilt X' else: return 'Tilt Y' if n == 2 and m == 0: return 'Defocus' if m == 0: accessor = int((n / 2) - 1) prefix = _names.get(accessor, f'{accessor}th') return f'{prefix} Spherical' return _name_helper(n, m)
def __repr__(self): ''' Pretty-print pupil description. ''' if self.normalize is True: header = 'rms normalized Fringe Zernike description with:\n\t' else: header = 'Fringe Zernike description with:\n\t' strs = [] for number, (coef, func) in enumerate(zip(self.coefs, z.zernikes)): # skip 0 terms if coef == 0: continue # positive coefficient, prepend with + if m.sign(coef) == 1: _ = '+' + f'{coef:.3f}' # negative, sign comes from the value else: _ = f'{coef:.3f}' # create the name name = f'Z{number+self.base} - {func.name}' strs.append(' '.join([_, name])) body = '\n\t'.join(strs) footer = f'\n\t{self.pv:.3f} PV, {self.rms:.3f} RMS' return f'{header}{body}{footer}'
def __repr__(self): '''Pretty-print pupil description ''' header = 'Standard Zernike description with:\n\t' strs = [] for number, (coef, name) in enumerate(zip(self.coefs, _names)): # skip 0 terms if coef == 0: continue # positive coefficient, prepend with + if m.sign(coef) == 1: _ = '+' + f'{coef:.3f}' # negative, sign comes from the value else: _ = f'{coef:.3f}' # adjust term numbers if self.base is 1: if number > 9: # two-digit term name_lcl = ''.join( [name[0], str(int(name[1:3]) + 1), name[3:]]) else: name_lcl = ''.join( [name[0], str(int(name[1]) + 1), name[2:]]) else: name_lcl = name strs.append(' '.join([_, name_lcl])) body = '\n\t'.join(strs) footer = f'\n\t{self.pv:.3f} PV, {self.rms:.3f} RMS' return f'{header}{body}{footer}'
def __repr__(self): """Pretty-print pupil description.""" if self.normalize is True: header = f'rms normalized {self._name} Zernike description with:\n\t' else: header = f'{self._name} Zernike description with:\n\t' strs = [] for number, coef in enumerate(self.coefs): # skip 0 terms if coef == 0: continue # positive coefficient, prepend with + if m.sign(coef) == 1: _ = '+' + f'{coef:.3f}' # negative, sign comes from the value else: _ = f'{coef:.3f}' # create the name idx = self._map[number] name = f'Z{number+self.base} - {zernikes[idx].name}' strs.append(' '.join([_, name])) body = '\n\t'.join(strs) footer = f'\n\t{self.pv:.3f} PV, {self.rms:.3f} RMS [{self.phase_unit}]' return f'{header}{body}{footer}'
def _name_helper(n, m): accessor = _name_accessor(n, m) prefix = _names.get(accessor, f'{accessor}th') name = _names_m.get(abs(m), f'{abs(m)}-foil') if n == 1: name = 'Tilt' if is_odd(m): if sign(m) == 1: suffix = 'X' else: suffix = 'Y' else: if sign(m) == 1: suffix = '00°' else: suffix = '45°' return f'{prefix} {name} {suffix}'
def Q2d(n, m, r, t): """2D Q polynomial, aka the Forbes polynomials. Parameters ---------- n : int radial polynomial order m : int azimuthal polynomial order r : numpy.ndarray radial coordinate, slope orthogonal in [0,1] t : numpy.ndarray azimuthal coordinate, radians Returns ------- numpy.ndarray array containing Q2d_n^m(r,t) the leading coefficient u^m or u^2 (1 - u^2) and sines/cosines are included in the return """ # Q polynomials have auxiliary polynomials "P" # which are scaled jacobi polynomials under the change of variables # x => 2x - 1 with alpha = -3/2, beta = m-3/2 # the scaling prefix may be found in A.4 of oe-20-3-2483 # impl notes: # Pn is computed using a recurrence over order n. The recurrence is for # a single value of m, and the 'seed' depends on both m and n. # # in general, Q_n^m = [P_n^m(x) - g_n-1^m Q_n-1^m] / f_n^m # for the sake of consistency, this function takes args of (r,t) # but the papers define an argument of u (really, u^2...) # which is what I call rho (or r). # for the sake of consistency of impl, I alias r=>u # and compute x = u**2 to match the papers u = r x = u**2 if m == 0: return Qbfs(n, r) # m == 0 already was short circuited, so we only # need to consider the m =/= 0 case for azimuthal terms if sign(m) == -1: m = abs(m) prefix = u**m * np.sin(m * t) else: prefix = u**m * np.cos(m * t) m = abs(m) P0 = 1 / 2 if m == 1: P1 = 1 - x / 2 else: P1 = (m - .5) + (1 - m) * x f0 = f_q2d(0, m) Q0 = 1 / (2 * f0) if n == 0: return Q0 * prefix g0 = g_q2d(0, m) f1 = f_q2d(1, m) Q1 = (P1 - g0 * Q0) * (1 / f1) if n == 1: return Q1 * prefix # everything above here works, or at least everything in the returns works if m == 1: P2 = (3 - x * (12 - 8 * x)) / 6 P3 = (5 - x * (60 - x * (120 - 64 * x))) / 10 g1 = g_q2d(1, m) f2 = f_q2d(2, m) Q2 = (P2 - g1 * Q1) * (1 / f2) g2 = g_q2d(2, m) f3 = f_q2d(3, m) Q3 = (P3 - g2 * Q2) * (1 / f3) # Q2, Q3 correct if n == 2: return Q2 * prefix elif n == 3: return Q3 * prefix Pnm2, Pnm1 = P2, P3 Qnm1 = Q3 min_n = 4 else: Pnm2, Pnm1 = P0, P1 Qnm1 = Q1 min_n = 2 for nn in range(min_n, n + 1): A, B, C = abc_q2d(nn - 1, m) Pn = (A + B * x) * Pnm1 - C * Pnm2 gnm1 = g_q2d(nn - 1, m) fn = f_q2d(nn, m) Qn = (Pn - gnm1 * Qnm1) * (1 / fn) Pnm2, Pnm1 = Pnm1, Pn Qnm1 = Qn # flake8 can't prove that the branches above the loop guarantee that we # enter the loop and Qn is defined return Qn * prefix # NOQA
def nm_to_fringe(n, m): """Convert (n,m) two term index to Fringe index.""" term1 = (1 + (n + abs(m)) / 2)**2 term2 = 2 * abs(m) term3 = (1 + sign(m)) / 2 return int(term1 - term2 - term3) + 1 # shift 0 base to 1 base