def set_phase_map(self, z_coef, rho_zern=1., PV_rescale=True, PV_goal=50): """ Generates a phase map based on Zernike polynomials This usually represents an NCPA map Uses the fast methods implemented in zern_core.py :param z_coef: coefficient of the Zernike series expansion :param PV_goal: desired Peak-To-Valley [nm] of the phase map """ # Construct the base phase map self.zern_model = zern.ZernikeNaive(mask=self.pupil_mask) phase_map = self.zern_model(coef=z_coef, rho=self.rho_m / rho_zern, theta=self.theta_m, normalize_noll=False, mode='Jacobi', print_option='Silent') if PV_rescale: # Compute the current PV and rescale the coefficients current_pv = np.max(phase_map[np.nonzero(phase_map)]) - np.min( phase_map[np.nonzero(phase_map)]) # Remember that at this point the phase map is a 1D array (a masked 2D) phase_map = zern.rescale_phase_map(phase_map, peak=PV_goal / 2) self.ncpa_coef = self.zern_model.coef * (PV_goal / current_pv ) # Save the coefficients self.phase_map = zern.invert_mask(phase_map, self.pupil_mask) else: self.phase_map = zern.invert_mask(phase_map, self.pupil_mask) self.ncpa_coef = self.zern_model.coef
def callback_function(self, coef): """ Callback to print intermediate results at each iteration """ cost_now = self.cost(coef) self.cost_array.append(cost_now) coef_copy = coef coef_copy[0] = 0 #remove piston print('\nAt iteration %d :' % self.counter) r = self.rho_m * (self.rho_zern / self.rho_aper) nominal_map = self.zern_model(coef=coef_copy, rho=r, theta=self.theta_m, normalize_noll=False, mode='Jacobi', print_option='Silent') nominal_map = zern.invert_mask(nominal_map, self.pupil_mask) try: p0 = self.true_phase PV = compute_PV_2maps(phase_ref=self.true_phase, phase_guess=nominal_map) self.PV_array.append(PV) RMS = compute_rms((self.true_phase - nominal_map)[self.pupil_mask]) self.RMS_array.append(RMS) print('Merit Function: %.3E' % cost_now) print('PV : %.3f' % PV) print('RMS: %.3f' % RMS) except AttributeError: pass # cost_at_iter = self.cost(coef) # print 'Merit function = %e' %cost_at_iter self.guesses[:, :, self.counter] = nominal_map self.counter += 1
def rand_zern(randker): N = 48 N_zern = 10 rho_max = 0.9 eps_rho = 1.4 randgen = RandomState(randker) extents = [-1, 1, -1, 1] # Construct the coordinates x = np.linspace(-rho_max, rho_max, N) rho_spacing = x[1] - x[0] xx, yy = np.meshgrid(x, x) rho = np.sqrt(xx ** 2 + yy ** 2) theta = np.arctan2(xx, yy) aperture_mask = rho <= eps_rho * rho_max rho, theta = rho[aperture_mask], theta[aperture_mask] rho_max = np.max(rho) extends = [-rho_max, rho_max, -rho_max, rho_max] # Compute the Zernike series coef = randgen.normal(size=N_zern) z = zern.ZernikeNaive(mask=aperture_mask) phase_map = z(coef=coef, rho=rho, theta=theta, normalize_noll=False, mode='Jacobi', print_option='Silent') phase_map = zern.rescale_phase_map(phase_map, peak=1) phase_2d = zern.invert_mask(phase_map, aperture_mask) return phase_2d
def set_true_phase(self, ncpa_coef): r = self.rho_m * (self.rho_zern / self.rho_aper) nominal_map = self.zern_model(coef=ncpa_coef, rho=r, theta=self.theta_m, normalize_noll=False, mode='Jacobi', print_option='Silent') self.true_phase = zern.invert_mask(nominal_map, self.pupil_mask)
def evaluate_phase(self, zern_coef): _phase = self.zern_model(coef=zern_coef, rho=self.rho, theta=self.theta, normalize_noll=False, mode='Jacobi', print_option='Silent') phase = zern.invert_mask(_phase, self.aper_mask) return phase
def evaluate_phase(self, zern_coef): r = self.rho_m * (self.rho_zern / self.rho_aper) nominal_map = self.zern_model(coef=zern_coef, rho=r, theta=self.theta_m, normalize_noll=False, mode='Jacobi', print_option='Silent') nominal_map = zern.invert_mask(nominal_map, self.pupil_mask) # plt.figure() # plt.imshow(nominal_map) # plt.colorbar() return nominal_map
def callback_function(self, coef): """ Callback to print intermediate results at each iteration """ cost_now = self.cost(coef) self.cost_array.append(cost_now) grad = self.grad_analytic(coef) coef_copy = coef print('\nAt iteration %d :' % self.counter) print('Merit Function: %.3E' % cost_now) print('Grad Norm: %e' % (np.linalg.norm(grad))) nominal_map = self.zern_model(coef=coef_copy, rho=self.rho, theta=self.theta, normalize_noll=False, mode='Jacobi', print_option='Silent') nominal_map = zern.invert_mask(nominal_map, self.aper_mask) self.guesses[:, :, self.counter] = nominal_map self.counter += 1
def set_phase_diversity(self, n=2, m=0, rho_zern=1., ratio=10): """ Creates the Phase Diversity map which will be used to 'defocus' the images. Although the common thing is to use a pure defocus term (n=2, m=0) any Zernike polynomial is possible The Phase Diversity map is rescaled according to a desired PV which is 'ratio' times the PV of the NCPA """ diversity = self.zern_model.Z_nm(n=n, m=m, rho=self.rho_m / rho_zern, theta=self.theta_m, normalize_noll=False, mode='Jacobi') pv_diversity = np.max(diversity) - np.min(diversity) pv_phase = np.max(self.phase_map) - np.min(self.phase_map) phase_diversity = (ratio * pv_phase) * (diversity / pv_diversity) # Remember that at this point the phase diversity is a 1D array (a masked 2D) self.phase_diversity = zern.invert_mask(phase_diversity, self.pupil_mask)
def cost(self, zern_coef): norm_pix = 1. / (self.N_pix) r = self.rho_m * (self.rho_zern / self.rho_aper) nominal_map = self.zern_model(coef=zern_coef, rho=r, theta=self.theta_m, normalize_noll=False, mode='Jacobi', print_option='Silent') nominal_map = zern.invert_mask(nominal_map, self.pupil_mask) # Nominal Image pupil_nominal = complex_function(self.pupil_mask, nominal_map, self.wave) propagated_nominal = norm_pix * fftshift(fft2(pupil_nominal)) image_nominal = self.t_exp * (np.abs(propagated_nominal))**2 J_nominal = (self.image_nominal - image_nominal)**2 # + Phase Diversity pupil_plus_defocus = complex_function( self.pupil_mask, (nominal_map + self.phase_diversity), self.wave) propagated_plus_defocus = norm_pix * fftshift(fft2(pupil_plus_defocus)) image_plus_defocus = self.t_exp * (np.abs(propagated_plus_defocus))**2 J_plus = (self.image_plus_defocus - image_plus_defocus)**2 if self.N_images == 3: # - Phase Diversity pupil_minus_defocus = complex_function( self.pupil_mask, (nominal_map - self.phase_diversity), self.wave) propagated_minus_defocus = norm_pix * fftshift( fft2(pupil_minus_defocus)) image_minus_defocus = self.t_exp * ( np.abs(propagated_minus_defocus))**2 J_minus = (self.image_minus_defocus - image_minus_defocus)**2 return np.sum(J_nominal + J_plus + J_minus) / (self.t_exp**2) else: return np.sum(J_nominal + J_plus) / (self.t_exp**2)
rho, theta = rho[aperture_mask], theta[aperture_mask] rho_max = np.max(rho) extends = [-rho_max, rho_max, -rho_max, rho_max] # Compute the Zernike series coef = randgen.normal(size=N_zern) z = zern.ZernikeNaive(mask=aperture_mask) phase_map = z(coef=coef, rho=rho, theta=theta, normalize_noll=False, mode='Jacobi', print_option=None) # phase_map = zern.rescale_phase_map(phase_map, peak=1) phase_2d = zern.invert_mask(phase_map, aperture_mask) # Introduce some noise in the map noised_phase_map = phase_map + 0.5 * np.random.normal(size=phase_map.shape[0]) noised_2d = zern.invert_mask(noised_phase_map, aperture_mask) plt.figure() plt.imshow(phase_2d, extent=extends, cmap='jet') plt.title("Zernike Series (%d polynomials)" % N_zern) plt.xlabel('x') plt.ylabel('y') plt.colorbar() plt.figure() plt.imshow(noised_2d, extent=extends, cmap='jet') plt.title("Zernike Series with Noise")
def reshape_model_matrix(matrix, mask): # Reshape the Zernike model matrix from flattened to square H = [zern.invert_mask(matrix[:, i], mask) for i in range(matrix.shape[-1])] H = np.array(H) H_new = np.moveaxis(H, 0, -1) return H_new
def grad_analytic(self, zern_coef): """ Analytic gradient for speed purposes """ grad_start = timer() N_zern = zern_coef.shape[0] wave_factor = 2 * np.pi / self.wave * 1j norm_pix = 1. / (self.N_pix) r = self.rho_m * (self.rho_zern / self.rho_aper) nominal_map = self.zern_model(coef=zern_coef, rho=r, theta=self.theta_m, normalize_noll=False, mode='Jacobi', print_option='Silent') nominal_map = zern.invert_mask(nominal_map, self.pupil_mask) # Nominal Image pupil_nominal = complex_function(self.pupil_mask, nominal_map, self.wave) propagated_nominal = norm_pix * fftshift(fft2(pupil_nominal)) image_nominal = self.t_exp * (np.abs(propagated_nominal))**2 # + Phase Diversity pupil_plus_defocus = complex_function( self.pupil_mask, (nominal_map + self.phase_diversity), self.wave) propagated_plus_defocus = norm_pix * fftshift(fft2(pupil_plus_defocus)) image_plus_defocus = self.t_exp * (np.abs(propagated_plus_defocus))**2 # - Phase Diversity pupil_minus_defocus = complex_function( self.pupil_mask, (nominal_map - self.phase_diversity), self.wave) propagated_minus_defocus = norm_pix * fftshift( fft2(pupil_minus_defocus)) image_minus_defocus = self.t_exp * ( np.abs(propagated_minus_defocus))**2 base_factor_nom = 2 * (self.image_nominal - image_nominal) base_factor_plus = 2 * (self.image_plus_defocus - image_plus_defocus) base_factor_minus = 2 * (self.image_minus_defocus - image_minus_defocus) # Helper stuff Ec_nom, Ec_plus, Ec_minus = np.conj(pupil_nominal), np.conj( pupil_plus_defocus), np.conj(pupil_minus_defocus) FE_nom = norm_pix * fftshift(fft2(pupil_nominal)) FE_plus = norm_pix * fftshift(fft2(pupil_plus_defocus)) FE_minus = norm_pix * fftshift(fft2(pupil_minus_defocus)) FEc_nom, FEc_plus, FEc_minus = np.conj(FE_nom), np.conj( FE_plus), np.conj(FE_minus) # print('Common: %f sec' %(timer() - grad_start)) g = np.zeros(N_zern) for k in range(N_zern): Z_k = self.model_matrix[:, :, k] fourier_factor_nom = self.helper_grad(Z_k, pupil_nominal, Ec_nom, FE_nom, FEc_nom) fourier_factor_plus = self.helper_grad(Z_k, pupil_plus_defocus, Ec_plus, FE_plus, FEc_plus) fourier_factor_minus = self.helper_grad(Z_k, pupil_minus_defocus, Ec_minus, FE_minus, FEc_minus) grad_nom = np.sum(base_factor_nom * self.t_exp * fourier_factor_nom) grad_plus = np.sum(base_factor_plus * self.t_exp * fourier_factor_plus) grad_minus = np.sum(base_factor_minus * self.t_exp * fourier_factor_minus) g[k] = np.real(grad_nom + grad_plus + grad_minus) / (self.t_exp**2) return g
print('\nML model guesses:') print(guessed[0, :]) print('\nTrue values are :') print(true_coef) """ Compare the guess and the true NCPA """ _true_ncpa = np.dot(H_matrix, zern_coef) guessed_coef = np.zeros_like(zern_coef) guessed_coef[1:] = guessed _guessed_ncpa = np.dot(H_matrix, guessed_coef) _res_ncpa = _true_ncpa - _guessed_ncpa PV = np.max(_res_ncpa) - np.min(_res_ncpa) mu = np.mean(_res_ncpa) nn = _res_ncpa.shape[0] RMS = np.sqrt(1. / nn * np.sum((_res_ncpa - mu)**2)) true_ncpa = 1e3 * zern.invert_mask(_true_ncpa, pupil_mask) guessed_ncpa = 1e3 * zern.invert_mask(_guessed_ncpa, pupil_mask) res_ncpa = true_ncpa - guessed_ncpa print('\nDefocus = %.1f [mm]' % defocus) print('PV residual NPCA = %.1f [nm]' % (1e3 * PV)) print('RMS residual NPCA = %.1f [nm]\n' % (1e3 * RMS)) plt.figure() plt.imshow(true_ncpa, cmap='jet') plt.colorbar() plt.title('True NCPA map [nm]') plt.figure() plt.imshow(guessed_ncpa, cmap='jet') plt.colorbar()
plt.subplot(122) plt.imshow(pop.crop_array(image, N_pix), extent=extends, cmap='jet') plt.xlabel('X [mm]') plt.title('Ideal PSF') # Show an example of NCPA map zern_model = zern.ZernikeNaive(mask=aper_mask) zern_coef = np.zeros(10) zern_coef[4] = 0.25 zz = zern_model(coef=zern_coef, rho=rho, theta=theta, mode='Standard', print_option=None) phase = zern.invert_mask(zz, aper_mask) H = zern_model.model_matrix plt.figure() plt.imshow(zern.invert_mask(H[:, 4], aper_mask), extent=[-1, 1, -1, 1]) plt.xlim([-1.1 * eps, 1.1 * eps]) plt.ylim([-1.1 * eps, 1.1 * eps]) plt.colorbar() # Investigate why there's a difference in Cost # when you use a slightly different defocus than # the one Zemax says we used j = [] focus = np.linspace(0.1, 0.2, 20)
rho, theta = rho[aperture_mask], theta[aperture_mask] rho_max = np.max(rho) extends = [-rho_max, rho_max, -rho_max, rho_max] # Compute the Zernike series coef = randgen.normal(size=N_zern) z = zern.ZernikeNaive(mask=aperture_mask) phase_map = z(coef=coef, rho=rho, theta=theta, normalize_noll=False, mode='Jacobi', print_option=None) phase_map = zern.rescale_phase_map(phase_map, peak=1) phase_2d = zern.invert_mask(phase_map, aperture_mask) plt.figure() plt.imshow(phase_2d, extent=extends, cmap='jet') plt.title("Zernike Series (%d polynomials)" % N_zern) plt.xlabel('x') plt.ylabel('y') plt.colorbar() # Compute the Power Spectral Density of the Zernike map phase_f = fftshift(fft2(phase_2d)) power_f = (np.abs(phase_f) / N / N)**2 spatial_frequencies = fftfreq(N, d=rho_spacing) freq_plus = spatial_frequencies[:N // 2] # Slice the 2D result for all 4 sub-axis: +X, +Y, -X, -Y