def fit(data, num_terms=16, rms_norm=False, round_at=6): ''' Fits a number of Zernike coefficients to provided data by minimizing the root sum square between each coefficient and the given data. The data should be uniformly sampled in an x,y grid. Args: data (`numpy.ndarray`): data to fit to. num_terms (`int`): number of terms to fit, fits terms 0~num_terms. rms_norm (`bool`): if true, normalize coefficients to unit RMS value. round_at (`int`): decimal place to round values at. Returns: numpy.ndarray: an array of coefficients matching the input data. ''' if num_terms > len(zernmap): raise ValueError(f'number of terms must be less than {len(zernmap)}') # precompute the valid indexes in the original data pts = m.isfinite(data) # set up an x/y rho/phi grid to evaluate Zernikes on x, y = m.linspace(-1, 1, data.shape[1]), m.linspace(-1, 1, data.shape[0]) xx, yy = m.meshgrid(x, y) rho = m.sqrt(xx**2 + yy**2)[pts].flatten() phi = m.arctan2(xx, yy)[pts].flatten() # compute each Zernike term zernikes = [] for i in range(num_terms): func = z.zernikes[zernmap[i]] base_zern = func(rho, phi) if rms_norm: base_zern *= func.norm zernikes.append(base_zern) zerns = m.asarray(zernikes).T # use least squares to compute the coefficients coefs = m.lstsq(zerns, data[pts].flatten(), rcond=None)[0] return coefs.round(round_at)
def image_dist_epd_to_na(image_distance, epd): """Compute the NA from an image distance and entrance pupil diameter. Parameters ---------- image_distance : `float` distance from the image to the entrance pupil epd : `float` diameter of the entrance pupil Returns ------- `float` numerical aperture. The NA of the system. """ image_distance = guarantee_array(image_distance) rho = epd / 2 marginal_ray_angle = abs(m.arctan2(rho, image_distance)) return marginal_ray_angle
def cart_to_polar(x, y): '''Return the (rho,phi) coordinates of the (x,y) input points. Parameters ---------- x : `numpy.ndarray` or number x coordinate y : `numpy.ndarray` or number y coordinate Returns ------- rho : `numpy.ndarray` or number radial coordinate phi : `numpy.ndarray` or number azimuthal coordinate ''' rho = m.sqrt(x**2 + y**2) phi = m.arctan2(y, x) return rho, phi
def zernikes_to_magnitude_angle(coefs, namer): """Convert Fringe Zernike polynomial set to a magnitude and phase representation.""" def mkary(): # default for defaultdict return m.zeros(2) # make a list of names to go with the coefficients names = [namer(i, base=0) for i in range(len(coefs))] combinations = defaultdict(mkary) # for each name and coefficient, make a len 2 array. Put the Y or 0 degree values in the first slot for coef, name in zip(coefs, names): if name.endswith(('X', 'Y', '°')): newname = ' '.join(name.split(' ')[:-1]) if name.endswith('Y'): combinations[newname][0] = coef elif name.endswith('X'): combinations[newname][1] = coef elif name[-2] == '5': # 45 degree case combinations[newname][1] = coef else: combinations[newname][0] = coef else: combinations[name][0] = coef # now go over the combinations and compute the L2 norms and angles for name in combinations: ovals = combinations[name] magnitude = m.sqrt((ovals**2).sum()) if 'Spheric' in name or 'focus' in name or 'iston' in name: phase = 0 else: phase = m.degrees(m.arctan2(*ovals)) values = (magnitude, phase) combinations[name] = values return dict(combinations) # cast to regular dict for return
def test_cart_to_polar(x, y): rho, phi = coordinates.cart_to_polar(x, y) assert np.allclose(rho, sqrt(x**2 + y**2)) assert np.allclose(phi, arctan2(y, x))