def get_perfect_wfs(self, wl=None): """compute and save perfect pupil plane and focal plane wavefronts""" if wl is None: wf_pupil = hc.Wavefront(self.ap, wavelength=self.wavelength) wf_pupil_hires = hc.Wavefront(self.ap_hires, wavelength=self.wavelength) else: wf_pupil = hc.Wavefront(self.ap, wavelength=wl) wf_pupil_hires = hc.Wavefront(self.ap_hires, wavelength=wl) wf_pupil.total_power = 1 wf_pupil_hires.total_power = 1 wf_focal = wf_pupil_hires.copy() if self.collimate is not None: wf_focal = self.collimate(wf_focal) if self.apodize is not None: wf_focal = self.apodize(wf_focal) wf_focal = self.propagator.forward(wf_focal) # note that the total power in the focal plane is not 1. some power gets deflected # away due to turbulence. self.wf_pupil, self.wf_pupil_hires, self.wf_focal = wf_pupil, wf_pupil_hires, wf_focal return wf_pupil, wf_pupil_hires, wf_focal
def build_PYWFS(grid, aperture, DM=None, basis=None, wavelength=wavelength_wfs): ''' Builds the Pyramid, the camera reading the pyramid, and optionally the control matrix for a DM OUTPUTS: pywfs: the pyramid that you pass a wavefront to to generate 4 pupils camera_py: the camera that reads out the pywfs output img_ref: the reference image of a flat wavefront IM_prime: the matrix that converts pywfs measurements to DM actuator space ''' pywfs = hci.PyramidWavefrontSensorOptics(grid, wavelength_0=wavelength) camera_py = hci.NoiselessDetector(pywfs.output_grid) wf = hci.Wavefront(aperture, wavelength) wf.total_power = 1 camera_py.integrate(pywfs.forward(wf), 1) img_ref = camera_py.read_out() img_ref /= img_ref.sum() if DM is None: return pywfs, camera_py, img_ref, None else: IM_prime = build_IM(pywfs, camera_py, img_ref, wf, DM, basis) return pywfs, camera_py, img_ref, IM_prime
def calibrate_DM(self, rcond=0.1, fname_append=None, force_new=False): """Calculate and save the imagae reconstruction matrix for the AO system""" from os import path if fname_append is not None: fname = "rmat_" + str( self.DM.num_actuators) + "_" + str(rcond).replace( ".", "") + "_" + fname_append else: fname = "rmat_" + str( self.DM.num_actuators) + "_" + str(rcond).replace(".", "") #first we make a reference image for an unaberrated wavefront passing through the pwfs wf = hc.Wavefront(self.ap, wavelength=self.reference_wavelength) wf.total_power = 1 wf_pwfs = self.pwfs.forward(wf) self.ref_image = self.read_out(wf_pwfs, poisson=False) if path.exists(fname + ".npy") and not force_new: print("trying to load reconstruction matrix from " + fname) rmat = np.load(fname + str(".npy")) print("loaded cached rmat") else: print("computing reconstruction matrix") #compute the interaction matrix relating incoming abberations to wfs response probe_disp = 0.01 * self.wavelength slopes = [] for i in range(self.DM.num_actuators): printProgressBar(i, self.DM.num_actuators) slope = 0 for s in (-1, 1): disps = np.zeros((self.DM.num_actuators, )) disps[i] = s * probe_disp self.DM.actuators = disps wf_dm = self.DM.forward(wf) #pass through DM wf_dm_pwfs = self.pwfs.forward(wf_dm) #pass through wfs image = self.read_out(wf_dm_pwfs) slope += s * (image - self.ref_image) / (2 * probe_disp) slopes.append(slope) print("matrix inversion...") basis = hc.ModeBasis(slopes) rmat = hc.inverse_tikhonov(basis.transformation_matrix, rcond=rcond, svd=None) np.save(fname, rmat) self.DM.actuators = np.zeros((self.DM.num_actuators, )) self.rmat = rmat return rmat
def remove_pad(wf, pad): res = wf.electric_field.shaped.shape[0] radius = wf.electric_field.grid[-1, 0] beamres = int(res / pad) padpix = int((pad - 1) * beamres / 2) field = wf.electric_field.shaped[padpix:-padpix, padpix:-padpix] grid = hc.make_pupil_grid(beamres, 2 * radius) return hc.Wavefront(hc.Field(field.flatten(), grid), wavelength=wf.wavelength)
def score_matrix3(aberrations, show=False): # Make segmented mirror aper, segments = hcipy.make_hexagonal_segmented_aperture(num_rings, segment_flat_to_flat, gap_size, starting_ring=1, return_segments=True) aper = hcipy.evaluate_supersampled(aper, pupil_grid, 1) segments = hcipy.evaluate_supersampled(segments, pupil_grid, 1) # Instantiate the segmented mirror hsm = hcipy.SegmentedDeformableMirror(segments) # Make a pupil plane wavefront from aperture wf = hcipy.Wavefront(aper, wavelength) hsm.flatten() # Get poke segments for i in range(0,len(aberrations)): if float(aberrations[i]) != 0: hsm.set_segment_actuators(i, aber_to_opd(aber_rad, wavelength) / (1/float(aberrations[i])), 0, 0) if show: plt.figure(figsize=(8,8)) plt.title('OPD for HCIPy SM') hcipy.imshow_field(hsm.surface * 2, mask=aper, cmap='RdBu_r', vmin=-5e-7, vmax=5e-7) plt.colorbar() plt.show() ### PROPAGATE AND SCORE ### wf_fp_pistoned = hsm(wf) # Propagate from SM to image plane im_pistoned_hc = prop(wf_fp_pistoned) norm_hc = np.max(im_pistoned_hc.intensity) if show: # Display intensity in image plane hcipy.imshow_field(np.log10(im_pistoned_hc.intensity / norm_hc), cmap='inferno', vmin=-9) plt.title('Image plane after SM') plt.colorbar() plt.show() hcipy.imshow_field(np.log10((im_pistoned_hc.intensity / norm_hc)*(annulus)), cmap='inferno', vmin=-9) plt.title('Annular image plane region') plt.colorbar() plt.show() interested_field = (im_pistoned_hc.intensity / norm_hc)*(1-annulus) score = float(hcipy.Field.sum(interested_field)/hcipy.Field.sum(im_pistoned_hc.intensity / norm_hc))*100 return score
def __init__(self, aper, indexed_aperture, seg_pos, apod, lyotst, fpm, focal_grid, params): self.sm = SegmentedMirror(indexed_aperture=indexed_aperture, seg_pos=seg_pos) self.aper = aper self.apodizer = apod self.lyotstop = lyotst self.fpm = fpm self.wvln = params['wavelength'] self.diam = params['diameter'] self.imlamD = params['imlamD'] self.fpm_rad = params['fpm_rad'] self.lamDrad = self.wvln / self.diam self.coro = hc.LyotCoronagraph(indexed_aperture.grid, fpm, lyotst) self.prop = hc.FraunhoferPropagator(indexed_aperture.grid, focal_grid) self.coro_no_ls = hc.LyotCoronagraph(indexed_aperture.grid, fpm) self.wf_aper = hc.Wavefront(aper, wavelength=self.wvln) self.focal_det = focal_grid
def coupling_vs_r_pupilplane_single(tele: AOtele.AOtele, pupilfields, rcore, ncore, nclad, wl0): k0 = 2 * np.pi / wl0 fieldshape = pupilfields[0].shape #need to generate the available modes V = LPmodes.get_V(k0, rcore, ncore, nclad) modes = LPmodes.get_modes(V) lpfields = [] for mode in modes: if mode[0] == 0: lpfields.append( normalize( LPmodes.lpfield(xg, yg, mode[0], mode[1], rcore, wl0, ncore, nclad))) else: lpfields.append( normalize( LPmodes.lpfield(xg, yg, mode[0], mode[1], rcore, wl0, ncore, nclad, "cos"))) lpfields.append( normalize( LPmodes.lpfield(xg, yg, mode[0], mode[1], rcore, wl0, ncore, nclad, "sin"))) lppupilfields = [] #then backpropagate for field in lpfields: wf = hc.Wavefront(hc.Field(field.flatten(), tele.focalgrid), wavelength=wl0 * 1.e-6) pupil_wf = tele.back_propagate(wf, True) lppupilfields.append(pupil_wf.electric_field.reshape(fieldshape)) lppupilfields = np.array(lppupilfields) pupilfields = np.array(pupilfields) #compute total overlap powers = np.sum(pupilfields * lppupilfields, axes=(1, 2)) return np.mean(powers), np.stddev(powers)
def _inner_(wf): _power = wf.total_power reals, imags = wf.real, wf.imag reals = np.reshape(reals, (self.hi_pupil_res, self.hi_pupil_res)) imags = np.reshape(imags, (self.hi_pupil_res, self.hi_pupil_res)) if self.hi_pupil_res != col_res: reals = resize2(reals, (col_res, col_res)).flatten() imags = resize2(imags, (col_res, col_res)).flatten() else: reals = reals.flatten() imags = imags.flatten() new_wf = hc.Wavefront( hc.Field(reals + 1.j * imags, self.collimator_grid), wf.wavelength) # make sure power is conserved new_wf.total_power = _power return new_wf
def seg_mirror_test(): """ Testing the integrated energy of images produced by HCIPy vs Poppy segmented DMs. This is now deprecated as I am using directly the hcipy SM, but specifically from an older commit: from hcipy.optics.segmented_mirror import SegmentedMirror """ # Parameters which_tel = CONFIG_PASTIS.get('telescope', 'name') NPIX = CONFIG_PASTIS.getint('numerical', 'tel_size_px') PUP_DIAMETER = CONFIG_PASTIS.getfloat(which_tel, 'diameter') GAPSIZE = CONFIG_PASTIS.getfloat(which_tel, 'gaps') FLATTOFLAT = CONFIG_PASTIS.getfloat(which_tel, 'flat_to_flat') wvln = 638e-9 lamD = 20 samp = 4 norm = False fac = 6.55 # --------------------------------- # #aber_rad = 6.2 aber_array = np.linspace(0, 2 * np.pi, 50, True) log.info('Aber in rad: \n{}'.format(aber_array)) log.info('Aber in m: \n{}'.format(util.aber_to_opd(aber_array, wvln))) # --------------------------------- # ### HCIPy SM # HCIPy grids and propagator pupil_grid = hcipy.make_pupil_grid(dims=NPIX, diameter=PUP_DIAMETER) focal_grid = hcipy.make_focal_grid(pupil_grid, samp, lamD, wavelength=wvln) prop = hcipy.FraunhoferPropagator(pupil_grid, focal_grid) # Generate an aperture aper, seg_pos = get_atlast_aperture(normalized=norm) aper = hcipy.evaluate_supersampled(aper, pupil_grid, 1) # Instantiate the segmented mirror hsm = SegmentedMirror(aper, seg_pos) # Make a pupil plane wavefront from aperture wf = hcipy.Wavefront(aper, wavelength=wvln) ### Poppy SM psm = poppy.dms.HexSegmentedDeformableMirror(name='Poppy SM', rings=3, flattoflat=FLATTOFLAT * u.m, gap=GAPSIZE * u.m, center=False) ### Apply pistons hc_ims = [] pop_ims = [] for aber_rad in aber_array: # Flatten both SMs hsm.flatten() psm.flatten() # HCIPy for i in [19, 28]: hsm.set_segment(i, util.aber_to_opd(aber_rad, wvln) / 2, 0, 0) # Poppy for i in [34, 25]: psm.set_actuator(i, util.aber_to_opd(aber_rad, wvln) * u.m, 0, 0) # 34 in poppy is 19 in HCIPy ### Propagate to image plane ### HCIPy # Apply SM to pupil plane wf wf_fp_pistoned = hsm(wf) # Propagate from SM to image plane im_pistoned_hc = prop(wf_fp_pistoned) ### Poppy # Make an optical system with the Poppy SM and a detector osys = poppy.OpticalSystem() osys.add_pupil(psm) pxscle = 0.0031 * fac # I'm tweaking pixelscale and fov_arcsec to match the HCIPy image fovarc = 0.05 * fac osys.add_detector(pixelscale=pxscle, fov_arcsec=fovarc, oversample=10) # Calculate the PSF psf = osys.calc_psf(wvln) # Get the PSF as an array im_pistoned_pop = psf[0].data hc_ims.append(im_pistoned_hc.intensity.shaped / np.max(im_pistoned_hc.intensity)) pop_ims.append(im_pistoned_pop / np.max(im_pistoned_pop)) ### Trying to do it with numbers hc_ims = np.array(hc_ims) pop_ims = np.array(pop_ims) sum_hc = np.sum(hc_ims, axis=(1, 2)) sum_pop = np.sum( pop_ims, axis=(1, 2) ) - 1.75 # the -1.75 is just there because I didn't bother about image normalization too much plt.suptitle('Image degradation of SMs') plt.plot(aber_array, sum_hc, label='HCIPy SM') plt.plot(aber_array, sum_pop, label='Poppy SM') plt.xlabel('rad') plt.ylabel('image sum') plt.legend() plt.show()
def gen_wf(self, wl=None): if wl is None: wl = self.wavelength return hc.Wavefront(self.ap, wl)
def make_wf(phase, aper, wavelength=wavelength_wfs): wf = hci.Wavefront(np.exp(1j * phase) * aper, wavelength) return wf
# Create SM # Read pupil and indexed pupil inputdir = '/Users/ilaginja/Documents/LabWork/ultra/LUVOIR_delivery_May2019/' aper_path = 'inputs/TelAp_LUVOIR_gap_pad01_bw_ovsamp04_N1000.fits' aper_ind_path = 'inputs/TelAp_LUVOIR_gap_pad01_bw_ovsamp04_N1000_indexed.fits' aper_read = hc.read_fits(os.path.join(inputdir, aper_path)) aper_ind_read = hc.read_fits(os.path.join(inputdir, aper_ind_path)) # Sample them on a pupil grid and make them hc.Fields pupil_grid = hc.make_pupil_grid(dims=aper_ind_read.shape[0], diameter=15) aper = hc.Field(aper_read.ravel(), pupil_grid) aper_ind = hc.Field(aper_ind_read.ravel(), pupil_grid) # Create the wavefront on the aperture wf_aper = hc.Wavefront(aper, wvln) # Load segment positions from fits header hdr = fits.getheader(os.path.join(inputdir, aper_ind_path)) poslist = [] for i in range(nseg): segname = 'SEG' + str(i + 1) xin = hdr[segname + '_X'] yin = hdr[segname + '_Y'] poslist.append((xin, yin)) poslist = np.transpose(np.array(poslist)) seg_pos = hc.CartesianGrid(poslist) # Instantiate SM sm = SegmentedMirror(aper_ind, seg_pos)
def gen_atmos(plot=False): """ generates atmospheric phase distortions using hcipy (updated from original using CAOS) read more on HCIpy here: https://hcipy.readthedocs.io/en/latest/index.html In hcipy, the atmosphere evolves as a function of time, specified by the user. User can thus specify the timescale of evolution through both velocity of layer and time per step in the obs_sequence, in loop for medis_main.gen_timeseries(). :param plot: turn plotting on or off :return: """ dprint("Making New Atmosphere Model") # Saving Parameters # np.savetxt(iop.atmosconfig, ['Grid Size', 'Wvl Range', 'Number of Frames', 'Layer Strength', 'Outer Scale', 'Velocity', 'Scale Height', cp.model]) # np.savetxt(iop.atmosconfig, ['ap.grid_size', 'ap.wvl_range', 'ap.numframes', 'atmp.cn_sq', 'atmp.L0', 'atmp.vel', 'atmp.h', 'cp.model']) # np.savetxt(iop.atmosconfig, [ap.grid_size, ap.wvl_range, ap.numframes, atmp.cn_sq, atmp.L0, atmp.vel, atmp.h, cp.model], fmt='%s') wsamples = np.linspace(ap.wvl_range[0], ap.wvl_range[1], ap.n_wvl_init) wavefronts = [] ################################## # Initiate HCIpy Atmosphere Type ################################## pupil_grid = hcipy.make_pupil_grid(sp.grid_size, tp.entrance_d) if atmp.model == 'single': layers = [ hcipy.InfiniteAtmosphericLayer(pupil_grid, atmp.cn_sq, atmp.L0, atmp.vel, atmp.h, 2) ] elif atmp.model == 'hcipy_standard': # Make multi-layer atmosphere layers = hcipy.make_standard_atmospheric_layers( pupil_grid, atmp.outer_scale) elif atmp.model == 'evolving': raise NotImplementedError atmos = hcipy.MultiLayerAtmosphere(layers, scintilation=False) for wavelength in wsamples: wavefronts.append( hcipy.Wavefront(hcipy.Field(np.ones(pupil_grid.size), pupil_grid), wavelength)) ########################################### # Evolving Wavefront using HCIpy tools ########################################### for it, t in enumerate( np.arange(0, sp.numframes * sp.sample_time, sp.sample_time)): atmos.evolve_until(t) for iw, wf in enumerate(wavefronts): wf2 = atmos.forward(wf) filename = get_filename(it, wsamples[iw]) dprint(f"atmos file = {filename}") hdu = fits.ImageHDU(wf2.phase.reshape(sp.grid_size, sp.grid_size)) hdu.header['PIXSIZE'] = tp.entrance_d / sp.grid_size hdu.writeto(filename, overwrite=True) if plot and iw == 0: import matplotlib.pyplot as plt from medis.twilight_colormaps import sunlight plt.figure() plt.title( f"Atmosphere Phase Map t={t} lambda={eformat(wsamples[iw], 3, 2)}" ) hcipy.imshow_field(wf2.phase, cmap=sunlight) plt.colorbar() plt.show(block=True)
def compute_coupling_vs_wl_parallel(tele, DMshapes, ts, wls, wl0, rcore0, fname): """ compute avg coupling vs wavelength. Since psfs change with wavelength, the psfs are generated on the fly from saved DM positions and atmospheric turbulence states """ #to ensure accurate psf generation, an AOtele object identical to the one used to generate DMshapes must be passed in. ncores = 8 ## set up parallel stuff client = Client(threads_per_worker=1, n_workers=ncores) # get a perfect psf for Strehl calculations wf_pupil = hc.Wavefront(tele.ap, wavelength=wl0 * 1.e-6) wf_focus_perfect = tele.propagator.forward(wf_pupil) u_focus_perfect = AOtele.get_u(wf_focus_perfect) out = [] #psf focal plane physical size w = None j = 0 import copy for i in range(len(ts)): if i % 100 == 0 and i != 0: print("psfs completed: ", j) tele.atmos.t = ts[i] tele.DM.actuators = DMshapes[i] # we need to select a psf to do F# opt # by default let's just use #100, the first psf we analyze if i == 100: u = tele.get_PSF(wf_pupil) print("first psf @ 1um has Strehl", AOtele.get_strehl(u, u_focus_perfect)) plt.imshow(np.abs(u)) plt.show() ncore = get_IOR(wl0) V = LPmodes.get_V(2 * np.pi / wl0, rcore0, ncore, ncore - 5.5e-3) modes = LPmodes.get_modes(V) r, p, focal_plane_width = scale_u0_all(u, modes, rcore0, ncore, nclad, wl0) print('optimal psf size: ', focal_plane_width) couplings = [] # most likely can be sped up. I don't know how to parallelize the psf creation since the tele object can't be shared between threads. for wl in wls: wf = hc.Wavefront(tele.ap, wavelength=wl * 1.e-6) u = tele.get_PSF(wf) c = dask.delayed(compute_coupling_at_wl)(u, focal_plane_width, rcore0, wl) couplings.append(c) futures = dask.persist(*couplings) results = np.array(dask.compute(*futures)) out.append(results) j += 1 out = np.array(out) # save to file ncores = get_IOR(wls) with h5py.File(fname + ".hdf5", "w") as f: f.create_dataset("coupling", data=out) f.create_dataset("rcore0", data=rcore0) f.create_dataset("ncores", data=ncores) f.create_dataset("nclads", data=ncores - 5.5e-3) f.create_dataset("wls", data=wls) f.create_dataset("wl0", data=wl0) f.create_dataset("w", data=focal_plane_width) # show prelim results avgs = np.mean(out, axis=0) stds = np.std(out, axis=0) plt.plot(wls, avgs, color='k', ls='None', marker='.') plt.fill_between(wls, avgs - stds, avgs + stds, color='0.75', alpha=0.5) plt.axvspan(1.02 - 0.06, 1.02 + 0.06, alpha=0.35, color="burlywood", zorder=-100) plt.axvspan(1.220 - 0.213 / 2, 1.220 + 0.213 / 2, alpha=0.25, color="indianred", zorder=-100) plt.xlim(0.9, 1.4) plt.ylim(0., 1.0) plt.xlabel("wavelength (um)") plt.ylabel("coupling efficiency") plt.show()
def gen_atmos(plot=False, debug=True): """ generates atmospheric phase distortions using hcipy (updated from original using CAOS) read more on HCIpy here: https://hcipy.readthedocs.io/en/latest/index.html In hcipy, the atmosphere evolves as a function of time, specified by the user. User can thus specify the timescale of evolution through both velocity of layer and time per step in the obs_sequence, in loop for medis_main.gen_timeseries(). :param plot: turn plotting on or off :return: todo add simple mp.Pool.map code to make maps in parrallel """ if tp.use_atmos is False: pass # only make new atmosphere map if using the atmosphere else: if sp.verbose: dprint("Making New Atmosphere Model") # Saving Parameters # np.savetxt(iop.atmosconfig, ['Grid Size', 'Wvl Range', 'Number of Frames', 'Layer Strength', 'Outer Scale', 'Velocity', 'Scale Height', cp.model]) # np.savetxt(iop.atmosconfig, ['ap.grid_size', 'ap.wvl_range', 'ap.numframes', 'atmp.cn_sq', 'atmp.L0', 'atmp.vel', 'atmp.h', 'cp.model']) # np.savetxt(iop.atmosconfig, [ap.grid_size, ap.wvl_range, ap.numframes, atmp.cn_sq, atmp.L0, atmp.vel, atmp.h, cp.model], fmt='%s') wsamples = np.linspace(ap.wvl_range[0], ap.wvl_range[1], ap.n_wvl_init) wavefronts = [] ################################## # Initiate HCIpy Atmosphere Type ################################## pupil_grid = hcipy.make_pupil_grid(sp.grid_size, tp.entrance_d) if atmp.model == 'single': layers = [ hcipy.InfiniteAtmosphericLayer(pupil_grid, atmp.cn_sq, atmp.L0, atmp.vel, atmp.h, 2) ] elif atmp.model == 'hcipy_standard': # Make multi-layer atmosphere # layers = hcipy.make_standard_atmospheric_layers(pupil_grid, atmp.L0) heights = np.array([500, 1000, 2000, 4000, 8000, 16000]) velocities = np.array([10, 10, 10, 10, 10, 10]) Cn_squared = np.array( [0.2283, 0.0883, 0.0666, 0.1458, 0.3350, 0.1350]) * 3.5e-12 layers = [] for h, v, cn in zip(heights, velocities, Cn_squared): layers.append( hcipy.InfiniteAtmosphericLayer(pupil_grid, cn, atmp.L0, v, h, 2)) elif atmp.model == 'evolving': raise NotImplementedError atmos = hcipy.MultiLayerAtmosphere(layers, scintilation=False) for wavelength in wsamples: wavefronts.append( hcipy.Wavefront( hcipy.Field(np.ones(pupil_grid.size), pupil_grid), wavelength)) if atmp.correlated_sampling: # Damage Detection and Localization from Dense Network of Strain Sensors # fancy sampling goes here normal = corrsequence(sp.numframes, atmp.tau / sp.sample_time)[1] * atmp.std uniform = (special.erf(normal / np.sqrt(2)) + 1) times = np.cumsum(uniform) * sp.sample_time if debug: import matplotlib.pylab as plt plt.plot(normal) plt.figure() plt.plot(uniform) plt.figure() plt.hist(uniform) plt.figure() plt.plot( np.arange(0, sp.numframes * sp.sample_time, sp.sample_time)) plt.plot(times) plt.show() else: times = np.arange(0, sp.numframes * sp.sample_time, sp.sample_time) ########################################### # Evolving Wavefront using HCIpy tools ########################################### for it, t in enumerate(times): atmos.evolve_until(t) for iw, wf in enumerate(wavefronts): wf2 = atmos.forward(wf) filename = get_filename( it, wsamples[iw], (iop.atmosdir, sp.sample_time, atmp.model)) if sp.verbose: dprint(f"atmos file = {filename}") hdu = fits.ImageHDU( wf2.phase.reshape(sp.grid_size, sp.grid_size)) hdu.header['PIXSIZE'] = tp.entrance_d / sp.grid_size hdu.writeto(filename, overwrite=True) if plot and iw == 0: import matplotlib.pyplot as plt from medis.twilight_colormaps import sunlight plt.figure() plt.title( f"Atmosphere Phase Map t={t} lambda={eformat(wsamples[iw], 3, 2)}" ) hcipy.imshow_field(wf2.phase, cmap=sunlight) plt.colorbar() plt.show(block=True)
def calc_psf(self, ref=False, display_intermediate=False, return_intermediate=None): """Calculate the PSF of the segmented telescope, normalized to contrast units. Parameters: ---------- ref : bool Keyword for additionally returning the refrence PSF without the FPM. display_intermediate : bool Keyword for display of all planes. return_intermediate : string Either 'intensity', return the intensity in all planes; except phase on the SM (first plane) or 'efield', return the E-fields in all planes. Default none. Returns: -------- wf_im_coro.intensity : Field Coronagraphic image, normalized to contrast units by max of reference image (even when ref not returned). wf_im_ref.intensity : Field, optional Reference image without FPM. intermediates : dict of Fields, optional Intermediate plane intensity images; except for full wavefront on segmented mirror. wf_im_coro : Wavefront Wavefront in last focal plane. wf_im_ref : Wavefront, optional Wavefront of reference image without FPM. intermediates : dict of Wavefronts, optional Intermediate plane E-fields; except intensity in focal plane after FPM. """ # Create fake FPM for plotting fpm_plot = 1 - hc.circular_aperture(2 * self.fpm_rad * self.lamDrad)( self.focal_det) # Create apodozer as hc.Apodizer() object to be able to propagate through it apod_prop = hc.Apodizer(self.apodizer) # Calculate all wavefronts of the full propagation wf_sm = self.sm(self.wf_aper) wf_apod = apod_prop(wf_sm) wf_lyot = self.coro(wf_apod) wf_im_coro = self.prop(wf_lyot) # Wavefronts in extra planes wf_before_fpm = self.prop(wf_apod) int_after_fpm = np.log10( wf_before_fpm.intensity / wf_before_fpm.intensity.max() ) * fpm_plot # this is the intensity straight wf_before_lyot = self.coro_no_ls(wf_apod) # Wavefronts of the reference propagation wf_ref_pup = hc.Wavefront(self.apodizer * self.lyotstop, wavelength=self.wvln) wf_im_ref = self.prop(wf_ref_pup) # Display intermediate planes if display_intermediate: plt.figure(figsize=(15, 15)) plt.subplot(331) hc.imshow_field(wf_sm.phase, mask=self.aper, cmap='RdBu') plt.title('Seg aperture phase') plt.subplot(332) hc.imshow_field(wf_apod.intensity, cmap='inferno') plt.title('Apodizer') plt.subplot(333) hc.imshow_field(wf_before_fpm.intensity / wf_before_fpm.intensity.max(), norm=LogNorm(), cmap='inferno') plt.title('Before FPM') plt.subplot(334) hc.imshow_field(int_after_fpm / wf_before_fpm.intensity.max(), cmap='inferno') plt.title('After FPM') plt.subplot(335) hc.imshow_field(wf_before_lyot.intensity / wf_before_lyot.intensity.max(), norm=LogNorm(vmin=1e-3, vmax=1), cmap='inferno') plt.title('Before Lyot stop') plt.subplot(336) hc.imshow_field(wf_lyot.intensity / wf_lyot.intensity.max(), norm=LogNorm(vmin=1e-3, vmax=1), cmap='inferno', mask=self.lyotstop) plt.title('After Lyot stop') plt.subplot(337) hc.imshow_field(wf_im_coro.intensity / wf_im_ref.intensity.max(), norm=LogNorm(vmin=1e-10, vmax=1e-3), cmap='inferno') plt.title('Final image') plt.colorbar() if return_intermediate == 'intensity': # Return the intensity in all planes; except phase on the SM (first plane) intermediates = { 'seg_mirror': wf_sm.phase, 'apod': wf_apod.intensity, 'before_fpm': wf_before_fpm.intensity / wf_before_fpm.intensity.max(), 'after_fpm': int_after_fpm / wf_before_fpm.intensity.max(), 'before_lyot': wf_before_lyot.intensity / wf_before_lyot.intensity.max(), 'after_lyot': wf_lyot.intensity / wf_lyot.intensity.max() } if ref: return wf_im_coro.intensity, wf_im_ref.intensity, intermediates else: return wf_im_coro.intensity, intermediates if return_intermediate == 'efield': # Return the E-fields in all planes; except intensity in focal plane after FPM intermediates = { 'seg_mirror': wf_sm, 'apod': wf_apod, 'before_fpm': wf_before_fpm, 'after_fpm': int_after_fpm, 'before_lyot': wf_before_lyot, 'after_lyot': wf_lyot } if ref: return wf_im_coro, wf_im_ref, intermediates else: return wf_im_coro, intermediates if ref: return wf_im_coro.intensity, wf_im_ref.intensity return wf_im_coro.intensity
def analytical_model(zernike_pol, coef, cali=False): """ :param zernike_pol: :param coef: :param cali: bool; True if we already have calibration coefficients to use. False if we still need to create them. :return: """ #-# Parameters dataDir = os.path.join(CONFIG_INI.get('local', 'local_data_path'), 'active') telescope = CONFIG_INI.get('telescope', 'name') nb_seg = CONFIG_INI.getint(telescope, 'nb_subapertures') tel_size_m = CONFIG_INI.getfloat(telescope, 'diameter') * u.m real_size_seg = CONFIG_INI.getfloat( telescope, 'flat_to_flat' ) # in m, size in meters of an individual segment flatl to flat size_seg = CONFIG_INI.getint( 'numerical', 'size_seg') # pixel size of an individual segment tip to tip wvln = CONFIG_INI.getint(telescope, 'lambda') * u.nm inner_wa = CONFIG_INI.getint(telescope, 'IWA') outer_wa = CONFIG_INI.getint(telescope, 'OWA') tel_size_px = CONFIG_INI.getint( 'numerical', 'tel_size_px') # pupil diameter of telescope in pixels im_size_pastis = CONFIG_INI.getint( 'numerical', 'im_size_px_pastis') # image array size in px sampling = CONFIG_INI.getfloat('numerical', 'sampling') # sampling size_px_tel = tel_size_m / tel_size_px # size of one pixel in pupil plane in m px_sq_to_rad = (size_px_tel * np.pi / tel_size_m) * u.rad zern_max = CONFIG_INI.getint('zernikes', 'max_zern') sz = CONFIG_INI.getint('numerical', 'im_size_lamD_hcipy') # Create Zernike mode object for easier handling zern_mode = util.ZernikeMode(zernike_pol) #-# Mean subtraction for piston if zernike_pol == 1: coef -= np.mean(coef) #-# Generic segment shapes if telescope == 'JWST': # Load pupil from file pupil = fits.getdata( os.path.join(dataDir, 'segmentation', 'pupil.fits')) # Put pupil in randomly picked, slightly larger image array pup_im = np.copy(pupil) # remove if lines below this are active #pup_im = np.zeros([tel_size_px, tel_size_px]) #lim = int((pup_im.shape[1] - pupil.shape[1])/2.) #pup_im[lim:-lim, lim:-lim] = pupil # test_seg = pupil[394:,197:315] # this is just so that I can display an individual segment when the pupil is 512 # test_seg = pupil[:203,392:631] # ... when the pupil is 1024 # one_seg = np.zeros_like(test_seg) # one_seg[:110, :] = test_seg[8:, :] # this is the centered version of the individual segment for 512 px pupil # Creat a mini-segment (one individual segment from the segmented aperture) mini_seg_real = poppy.NgonAperture( name='mini', radius=real_size_seg ) # creating real mini segment shape with poppy #test = mini_seg_real.sample(wavelength=wvln, grid_size=flat_diam, return_scale=True) # fix its sampling with wavelength mini_hdu = mini_seg_real.to_fits(wavelength=wvln, npix=size_seg) # make it a fits file mini_seg = mini_hdu[ 0].data # extract the image data from the fits file elif telescope == 'ATLAST': # Create mini-segment pupil_grid = hcipy.make_pupil_grid(dims=tel_size_px, diameter=real_size_seg) focal_grid = hcipy.make_focal_grid( pupil_grid, sampling, sz, wavelength=wvln.to( u.m).value) # fov = lambda/D radius of total image prop = hcipy.FraunhoferPropagator(pupil_grid, focal_grid) mini_seg_real = hcipy.hexagonal_aperture(circum_diameter=real_size_seg, angle=np.pi / 2) mini_seg_hc = hcipy.evaluate_supersampled( mini_seg_real, pupil_grid, 4 ) # the supersampling number doesn't really matter in context with the other numbers mini_seg = mini_seg_hc.shaped # make it a 2D array # Redefine size_seg if using HCIPy size_seg = mini_seg.shape[0] # Make stand-in pupil for DH array pupil = fits.getdata( os.path.join(dataDir, 'segmentation', 'pupil.fits')) pup_im = np.copy(pupil) #-# Generate a dark hole mask #TODO: simplify DH generation and usage dh_area = util.create_dark_hole( pup_im, inner_wa, outer_wa, sampling ) # this might become a problem if pupil size is not same like pastis image size. fine for now though. if telescope == 'ATLAST': dh_sz = util.zoom_cen(dh_area, sz * sampling) #-# Import information form segmentation script Projection_Matrix = fits.getdata( os.path.join(dataDir, 'segmentation', 'Projection_Matrix.fits')) vec_list = fits.getdata( os.path.join(dataDir, 'segmentation', 'vec_list.fits')) # in pixels NR_pairs_list = fits.getdata( os.path.join(dataDir, 'segmentation', 'NR_pairs_list_int.fits')) # Figure out how many NRPs we're dealing with NR_pairs_nb = NR_pairs_list.shape[0] #-# Chose whether calibration factors to do the calibraiton with if cali: filename = 'calibration_' + zern_mode.name + '_' + zern_mode.convention + str( zern_mode.index) ck = fits.getdata( os.path.join(dataDir, 'calibration', filename + '.fits')) else: ck = np.ones(nb_seg) coef = coef * ck #-# Generic coefficients # the coefficients in front of the non redundant pairs, the A_q in eq. 13 in Leboulleux et al. 2018 generic_coef = np.zeros( NR_pairs_nb ) * u.nm * u.nm # setting it up with the correct units this will have for q in range(NR_pairs_nb): for i in range(nb_seg): for j in range(i + 1, nb_seg): if Projection_Matrix[i, j, 0] == q + 1: generic_coef[q] += coef[i] * coef[j] #-# Constant sum and cosine sum - calculating eq. 13 from Leboulleux et al. 2018 if telescope == 'JWST': i_line = np.linspace(-im_size_pastis / 2., im_size_pastis / 2., im_size_pastis) tab_i, tab_j = np.meshgrid(i_line, i_line) cos_u_mat = np.zeros( (int(im_size_pastis), int(im_size_pastis), NR_pairs_nb)) elif telescope == 'ATLAST': i_line = np.linspace(-(2 * sz * sampling) / 2., (2 * sz * sampling) / 2., (2 * sz * sampling)) tab_i, tab_j = np.meshgrid(i_line, i_line) cos_u_mat = np.zeros((int((2 * sz * sampling)), int( (2 * sz * sampling)), NR_pairs_nb)) # Calculating the cosine terms from eq. 13. # The -1 with each NR_pairs_list is because the segment names are saved starting from 1, but Python starts # its indexing at zero, so we have to make it start at zero here too. for q in range(NR_pairs_nb): # cos(b_q <dot> u): b_q with 1 <= q <= NR_pairs_nb is the basis of NRPS, meaning the distance vectors between # two segments of one NRP. We can read these out from vec_list. # u is the position (vector) in the detector plane. Here, those are the grids tab_i and tab_j. # We need to calculate the dot product between all b_q and u, so in each iteration (for q), we simply add the # x and y component. cos_u_mat[:, :, q] = np.cos( px_sq_to_rad * (vec_list[NR_pairs_list[q, 0] - 1, NR_pairs_list[q, 1] - 1, 0] * tab_i) + px_sq_to_rad * (vec_list[NR_pairs_list[q, 0] - 1, NR_pairs_list[q, 1] - 1, 1] * tab_j)) * u.dimensionless_unscaled sum1 = np.sum( coef**2 ) # sum of all a_{k,l} in eq. 13 - this works only for single Zernikes (l fixed), because np.sum would sum over l too, which would be wrong. if telescope == 'JWST': sum2 = np.zeros( (int(im_size_pastis), int(im_size_pastis)) ) * u.nm * u.nm # setting it up with the correct units this will have elif telescope == 'ATLAST': sum2 = np.zeros( (int(2 * sz * sampling), int(2 * sz * sampling))) * u.nm * u.nm for q in range(NR_pairs_nb): sum2 = sum2 + generic_coef[q] * cos_u_mat[:, :, q] #-# Local Zernike if telescope == 'JWST': # Generate a basis of Zernikes with the mini segment being the support isolated_zerns = zern.hexike_basis(nterms=zern_max, npix=size_seg, rho=None, theta=None, vertical=False, outside=0.0) # Calculate the Zernike that is currently being used and put it on one single subaperture, the result is Zer # Apply the currently used Zernike to the mini-segment. if zernike_pol == 1: Zer = np.copy(mini_seg) elif zernike_pol in range(2, zern_max - 2): Zer = np.copy(mini_seg) Zer = Zer * isolated_zerns[zernike_pol - 1] # Fourier Transform of the Zernike - the global envelope mf = mft.MatrixFourierTransform() ft_zern = mf.perform(Zer, im_size_pastis / sampling, im_size_pastis) elif telescope == 'ATLAST': isolated_zerns = hcipy.make_zernike_basis(num_modes=zern_max, D=real_size_seg, grid=pupil_grid, radial_cutoff=False) Zer = hcipy.Wavefront(mini_seg_hc * isolated_zerns[zernike_pol - 1], wavelength=wvln.to(u.m).value) # Fourier transform the Zernike ft_zern = prop(Zer) #-# Final image if telescope == 'JWST': # Generating the final image that will get passed on to the outer scope, I(u) in eq. 13 intensity = np.abs(ft_zern)**2 * (sum1.value + 2. * sum2.value) elif telescope == 'ATLAST': intensity = ft_zern.intensity.shaped * (sum1.value + 2. * sum2.value) # PASTIS is only valid inside the dark hole, so we cut out only that part if telescope == 'JWST': tot_dh_im_size = sampling * (outer_wa + 3) intensity_zoom = util.zoom_cen( intensity, tot_dh_im_size ) # zoom box is (owa + 3*lambda/D) wide, in terms of lambda/D dh_area_zoom = util.zoom_cen(dh_area, tot_dh_im_size) dh_psf = dh_area_zoom * intensity_zoom elif telescope == 'ATLAST': dh_psf = dh_sz * intensity """ # Create plots. plt.subplot(1, 3, 1) plt.imshow(pupil, origin='lower') plt.title('JWST pupil and diameter definition') plt.plot([46.5, 464.5], [101.5, 409.5], 'r-') # show how the diagonal of the pupil is defined plt.subplot(1, 3, 2) plt.imshow(mini_seg, origin='lower') plt.title('JWST individual mini-segment') plt.subplot(1, 3, 3) plt.imshow(dh_psf, origin='lower') plt.title('JWST dark hole') plt.show() """ # dh_psf is the image of the dark hole only, the pixels outside of it are zero # intensity is the entire final image return dh_psf, intensity