def test_FITSOpticalElement(tmpdir): circ_fits = poppy.CircularAperture().to_fits(grid_size=3, npix=10) fn = str(tmpdir / "circle.fits") circ_fits.writeto(fn, overwrite=True) # Test passing aperture via file on disk foe = poppy.FITSOpticalElement(transmission=fn) assert foe.amplitude_file == fn assert np.allclose(foe.amplitude, circ_fits[0].data) # Test passing OPD via FITS object, along with unit conversion circ_fits[0].header['BUNIT'] = 'micron' # need unit for OPD foe = poppy.FITSOpticalElement(opd=circ_fits) assert foe.opd_file == 'supplied as fits.HDUList object' assert np.allclose(foe.opd, circ_fits[0].data * 1e-6) # make a cube rect_mask = poppy.RectangleAperture().sample(grid_size=3, npix=10) circ_mask = circ_fits[0].data circ_fits[0].data = np.stack([circ_mask, rect_mask]) circ_fits[0].header['BUNIT'] = 'nm' # need unit for OPD fn2 = str(tmpdir / "cube.fits") circ_fits.writeto(fn2, overwrite=True) # Test passing OPD as cube, with slice default, units of nanometers foe = poppy.FITSOpticalElement(opd=fn2) assert foe.opd_file == fn2 assert foe.opd_slice == 0 assert np.allclose(foe.opd, circ_mask * 1e-9) # Same cube but now we ask for the next slice foe = poppy.FITSOpticalElement(opd=(fn2, 1)) assert foe.opd_file == fn2 assert foe.opd_slice == 1 assert np.allclose(foe.opd, rect_mask * 1e-9)
def WFIRSTSPC(npix=256, ratio=0.25): Tel_fname = os.path.join(os.environ['WEBBPSF_PATH'], "AFTA_CGI_C5_Pupil_onax_256px_flip.fits") SP_fname = os.path.join(os.environ['WEBBPSF_PATH'], "CGI/optics/CHARSPC_SP_256pix.fits.gz") FPM_fname = os.path.join( os.environ['WEBBPSF_PATH'], "CGI/optics/CHARSPC_FPM_25WA90_2x65deg_-_FP1res4_evensamp_D072_F770.fits.gz" ) LS_fname = os.path.join(os.environ['WEBBPSF_PATH'], "CGI/optics/SPC_LS_30D88_256pix.fits.gz") D_prim = 2.37 * u.m D_relay = 20 * u.mm fr_pri = 7.8 fl_pri = D_prim * fr_pri fl_m2 = fl_pri * D_relay / D_prim fr_m3 = 20. fl_m3 = fr_m3 * D_relay oversamp = 4 wfirst_optsys = poppy.FresnelOpticalSystem(pupil_diameter=D_prim, beam_ratio=ratio, npix=npix) telap = poppy.FITSOpticalElement(transmission=Tel_fname) SP = poppy.FITSOpticalElement(transmission=SP_fname) #default FPM pixelscale is in arcsecs FPM = poppy.FITSOpticalElement( transmission=FPM_fname, planetype=poppy.poppy_core.PlaneType.intermediate, pixelscale=0.005) SP.pixelscale = 0.5 * u.cm / SP.shape[0] / u.pix FPM.pixelscale = 0.5 * u.cm / SP.shape[0] / u.pix m1 = poppy.QuadraticLens(fl_pri, name='Primary') m2 = poppy.QuadraticLens(fl_m2, name='M2') m3 = poppy.QuadraticLens(fl_m3, name='M3') m4 = poppy.QuadraticLens(fl_m3, name='M4') m5 = poppy.QuadraticLens(fl_m3, name='M5') m6 = poppy.QuadraticLens(fl_m3, name='M6') wfirst_optsys.add_optic(telap) wfirst_optsys.add_optic(m1) wfirst_optsys.add_optic(m2, distance=fl_pri + fl_m2) wfirst_optsys.add_optic(m3, distance=fl_m2 + fl_m3) wfirst_optsys.add_optic(m4, distance=2 * fl_m3) wfirst_optsys.add_optic(SP, distance=fl_m3) wfirst_optsys.add_optic(m5, distance=fl_m3) wfirst_optsys.add_optic(FPM, distance=fl_m3) wfirst_optsys.add_optic(m5, distance=2 * fl_m3) wfirst_optsys.add_optic(poppy.ScalarTransmission( planetype=poppy.poppy_core.PlaneType.intermediate, name='focus', ), distance=fl_m3 + 0.39999923 * u.m) return wfirst_optsys
def surfFITS(file_loc, optic_type, opdunit, name): optic_fits = fits.open(file_loc) optic_fits[0].data = np.float_( optic_fits[0].data) # typecasting for POPPY workaround if optic_type == 'opd': optic_surf = poppy.FITSOpticalElement(name=name, opd=optic_fits, opdunits=opdunit) else: optic_surf = poppy.FITSOpticalElement(name=name, transmission=optic_fits) return optic_surf
def test_shift_rotation_consistency(npix=30, grid_size = 1.5, angle=35, display=False): """Test shifting & rotation together for FITS and Analytic optics Do we get consistent behavior from each? Are the signs and order of operations consistent? Tests the fix for issue #275. """ import poppy if npix < 30: raise ValueError("Need npix>=30 for enough resolution for this test") # Create rectangle, rotated rect = poppy.RectangleAperture(shift_x=0.25, rotation=angle) rect_samp = rect.sample(grid_size=grid_size, npix=npix) # Create rectangle, turn into FITS, then rotate rect_fits = poppy.RectangleAperture().to_fits(grid_size=grid_size, npix=npix) rect2 = poppy.FITSOpticalElement(transmission=rect_fits, shift_x=0.25, rotation=angle) # Compare that they are consistent enough, meaning # no more than 1% pixel difference. That tolerance allows for the # imprecision of rotating low-res binary masks. diff = np.round(rect2.amplitude)-rect_samp assert np.abs(diff).sum() <= 0.01*rect_samp.sum(), "Shift and rotations differ unexpectedly" if display: plt.figure() plt.subplot(131) plt.imshow(rect_samp) plt.subplot(132) plt.imshow(np.round(rect2.amplitude)) plt.subplot(133) plt.imshow(diff)
def test_OPD_in_waves_for_FITSOpticalElement(): pupil_radius = 1 * u.m pupil = poppy.CircularAperture(radius=pupil_radius) reference_wavelength = 1 * u.um npix = 16 single_wave_1um_lens = poppy.ThinLens( name='Defocus', nwaves=1, reference_wavelength=reference_wavelength, radius=pupil_radius) osys = poppy.OpticalSystem(oversample=1, npix=npix) osys.add_pupil(pupil) osys.add_pupil(single_wave_1um_lens) osys.add_detector(0.01 * u.arcsec / u.pixel, fov_pixels=3) # We have applied 1 wave of defocus at 1 um, so verify the center # has lower flux than at 2 um (it should be the 'hole' of the donut) psf_1um = osys.calc_psf(reference_wavelength) center_pixel_value = psf_1um[0].data[1, 1] psf_2um = osys.calc_psf(2 * reference_wavelength) assert psf_2um[0].data[1, 1] > psf_1um[0].data[1, 1] # Now, use the single_wave_1um_lens optic to make a # wavelength-independent 1 wave defocus lens_as_fits = single_wave_1um_lens.to_fits(what='opd', npix=3 * npix // 2) lens_as_fits[0].header['BUNIT'] = 'radian' lens_as_fits[0].data *= 2 * np.pi / reference_wavelength.to(u.m).value thin_lens_wl_indep = poppy.FITSOpticalElement(opd=lens_as_fits, opdunits='radian') # We expect identical peak flux for all wavelengths, so check at 0.5x and 2x for prefactor in (0.5, 1.0, 2.0): osys = poppy.OpticalSystem(oversample=1, npix=npix) osys.add_pupil(pupil) osys.add_pupil(thin_lens_wl_indep) osys.add_detector(prefactor * 0.01 * u.arcsec / u.pixel, fov_pixels=3) psf = osys.calc_psf(wavelength=prefactor * u.um) assert np.isclose(center_pixel_value, psf[0].data[1, 1])
def test_shifting_optics( npix=30, grid_size = 3, display=False): """Test shifting (translation) of Analytic and FITS Optical elements. Does shifting work as expected? Is it consistent between the two classes? Tests the fix for #247 """ import poppy pixsize =grid_size/npix shift_size = np.round(0.2/pixsize)*pixsize # by construction, an integer # of pixels # Create a reference array circ = poppy.CircularAperture() circ_samp = circ.sample(npix=npix, grid_size=grid_size) # Create a shifted version, and make sure it's different circ_shift = poppy.CircularAperture( shift_x=shift_size) circ_shift_samp = circ_shift.sample(npix=npix, grid_size=grid_size) if display: plt.imshow(circ_samp-circ_shift_samp) assert np.allclose(circ_samp, circ_shift_samp) is False, "Shift didn't change array" # Make a FITS element. circ_fits = circ.to_fits(npix=npix, grid_size=grid_size) # Show we can shift that and get the same result as shifting the analytic element fits_shifted = poppy.FITSOpticalElement(transmission=circ_fits, shift_x=shift_size) np.testing.assert_allclose(fits_shifted.amplitude, circ_shift_samp, atol=1e-9, err_msg="Shifting Analytic and FITS versions are not consistent (v1, via shift_x)") # FITSOpticalElement also lets you specify shifts via fraction of the array. Let's # show that is consistent. This is older syntax that is discouraged, and may be # deprecated and removed eventually. But while available it should be correct. array_frac = shift_size/grid_size fits_shifted_v2 = poppy.FITSOpticalElement(transmission=circ_fits, shift=(array_frac, 0)) np.testing.assert_allclose(fits_shifted.amplitude, fits_shifted_v2.amplitude, atol=1e-9, err_msg="Shifting FITS via shift/shift_x are not consistent") np.testing.assert_allclose(fits_shifted.amplitude, circ_shift_samp, atol=1e-9, err_msg="Shifting Analytic and FITS versions are not consistent (v2, via shift)") # Check in a 1D cut that the amount of shift is as expected - # this is implicitly also checked above via the match of Analytic and FITS # which use totally different methods to perform the shift. shift_in_pixels = int(shift_size/pixsize) assert np.allclose(np.roll(circ_samp[npix//2], shift_in_pixels), circ_shift_samp[npix//2])
def test_fits_rot90_vs_ndimagerotate_consistency(plot=False): """Test that rotating a FITS HDUList via either of the two methods yields consistent results. This compares an exact 90 degree rotation and an interpolating not-quite-90-deg rotation. Both methods should rotate counterclockwise and consistently. """ letterf_hdu = poppy.optics.LetterFAperture().to_fits(npix=128) opt1 = poppy.FITSOpticalElement(transmission=letterf_hdu, rotation=90) opt2 = poppy.FITSOpticalElement(transmission=letterf_hdu, rotation=89.99999) assert np.allclose(opt1.amplitude, opt2.amplitude, atol=1e-5) if plot: fig, axes = plt.subplots(figsize=(10, 5), ncols=2) axes[0].imshow(opt1.amplitude) axes[0].set_title("Rot90") axes[1].imshow(opt2.amplitude) axes[1].set_title("ndimage rotate(89.9999)")
def test_analytic_vs_FITS_rotation_consistency(plot=False): """Test that rotating an AnalyticOpticalElement vs rotating a discretized version as a FITSOpticalElement are consistent in rotation direction (counterclockwise) and amount""" opt1 = poppy.optics.LetterFAperture(rotation=90) letterf_hdu = poppy.optics.LetterFAperture().to_fits(npix=128) opt2 = poppy.FITSOpticalElement(transmission=letterf_hdu, rotation=90) if plot: opt1.display() plt.figure() opt2.display() array1 = opt1.sample(npix=128) array2 = opt2.amplitude assert np.allclose(array1, array2)
def create_image(self, coefficient_set_init=None, input_noise=None): # This is the function that creates the image (will probably call the config file) from some zernike coefficients # Copy paste the code that creates the image here import poppy from astropy.io import fits pupil_diameter = 6.559 # (in meter) As used in WebbPSF pupil_radius = pupil_diameter / 2 osys = poppy.OpticalSystem() transmission = '/Users/mygouf/Python/webbpsf/webbpsf-data4/jwst_pupil_RevW_npix1024.fits.gz' #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data/NIRCam/OPD/OPD_RevV_nircam_115.fits' opd = '/Users/mygouf/Python/webbpsf/webbpsf-data4/NIRCam/OPD/OPD_RevW_ote_for_NIRCam_requirements.fits.gz' hdul = fits.open(opd) hdul2 = fits.open(transmission) # Create wavefront map #print(coefficient_set_init) zernike_coefficients = np.append(0, coefficient_set_init) #zernike_coefficients *= 1e6 # conversion from meters to microns #wavefront_map = poppy.ZernikeWFE(radius=pupil_radius, # coefficients=zernike_coefficients, # aperture_stop=False) #print(zernike_coefficients) wavefront_map = poppy.zernike.opd_from_zernikes( zernike_coefficients, npix=1024, basis=poppy.zernike.zernike_basis_faster) wavefront_map = np.nan_to_num(wavefront_map) * hdul2[0].data fits.writeto('wavefront_map.fits', wavefront_map, hdul[0].header, overwrite=True) #opd = wavefront_map opd = 'wavefront_map.fits' #myoptic = poppy.FITSOpticalElement(transmission='transfile.fits', opd='opdfile.fits', pupilscale="PIXELSCL") #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data4/NIRCam/OPD/OPD_RevW_ote_for_NIRCam_requirements.fits.gz' jwst_opd = poppy.FITSOpticalElement(transmission=transmission, opd=opd) #jwst_opd = poppy.FITSOpticalElement(transmission=transmission) osys.add_pupil(jwst_opd) # JWST pupil osys.add_detector( pixelscale=0.063, fov_arcsec=self.fov_arcsec, oversample=4) # image plane coordinates in arcseconds psf = osys.calc_psf(4.44e-6) # wavelength in microns psf_poppy = np.array(psf[0].data) poppy.display_psf(psf, title='JWST NIRCam test') #psf1 = osys.calc_psf(4.44e-6) # wavelength in microns #psf2 = osys.calc_psf(2.50e-6) # wavelength in microns #psf_poppy = psf1[0].data/2 + psf2[0].data/2 psf_poppy = psf_poppy * 1e7 / np.max(psf_poppy) # Adding photon noise #image = np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy)) if np.all(input_noise) == None: #noise = np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>1000)) #noise = self.rs.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>np.max(psf_poppy)/1000)) noise = np.random.normal( loc=psf_poppy, scale=np.sqrt(psf_poppy > np.max(psf_poppy) / 1000)) #noise = rs.poisson(psf_poppy) #print('Estimated Noise', np.mean(noise)) else: noise = input_noise #print('Input Noise', np.mean(noise)) image = psf_poppy + noise dict_ = { 'image': image, 'noise': noise, 'wavefront_map': wavefront_map } #print(np.mean(image),np.mean(noise)) return dict_
def create_image_from_opd_file(self, opd=None, input_noise=None): # This is the function that creates the image (will probably call the config file) from some zernike coefficients # Copy paste the code that creates the image here import poppy from astropy.io import fits pupil_diameter = 6.559 # (in meter) As used in WebbPSF pupil_radius = pupil_diameter / 2 osys = poppy.OpticalSystem() transmission = '/Users/mygouf/Python/webbpsf/webbpsf-data4/jwst_pupil_RevW_npix1024.fits.gz' #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data/NIRCam/OPD/OPD_RevV_nircam_115.fits' #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data4/NIRCam/OPD/OPD_RevW_ote_for_NIRCam_requirements.fits.gz' hdul = fits.open(opd) hdul2 = fits.open(transmission) wavefront_map = hdul[0].data jwst_opd = poppy.FITSOpticalElement(transmission=transmission, opd=opd) #jwst_opd = poppy.FITSOpticalElement(transmission=transmission) osys.add_pupil(jwst_opd) # JWST pupil osys.add_detector( pixelscale=0.063, fov_arcsec=self.fov_arcsec, oversample=4) # image plane coordinates in arcseconds psf = osys.calc_psf(4.44e-6) # wavelength in microns psf_poppy = np.array(psf[0].data) poppy.display_psf(psf, title='JWST NIRCam test') #psf1 = osys.calc_psf(4.44e-6) # wavelength in microns #psf2 = osys.calc_psf(2.50e-6) # wavelength in microns #psf_poppy = psf1[0].data/2 + psf2[0].data/2 psf_poppy = psf_poppy * 1e7 / np.max(psf_poppy) # Adding photon noise #image = np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy)) if np.all(input_noise) == None: #noise = np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>1000)) #noise = self.rs.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>np.max(psf_poppy)/1000)) noise = np.random.normal( loc=psf_poppy, scale=np.sqrt(psf_poppy > np.max(psf_poppy) / 1000)) #noise = rs.poisson(psf_poppy) #print('Estimated Noise', np.mean(noise)) else: noise = input_noise #print('Input Noise', np.mean(noise)) image = psf_poppy + noise dict_ = { 'image': image, 'noise': noise, 'wavefront_map': wavefront_map } #print(np.mean(image),np.mean(noise)) return dict_