def getNoise(self, i, rng=None, gsparams=None): """Returns the noise cf at index `i` as a CorrelatedNoise object. """ if self.noise_file_name is None: cf = galsim.UncorrelatedNoise(rng, self.pixel_scale[i], self.variance[i], gsparams) else: if i >= len(self.noise_file_name): raise IndexError( 'index %d given to getNoise is out of range (0..%d)'%( i,len(self.noise_file_name)-1)) if self.noise_file_name[i] in self.saved_noise_im: im = self.saved_noise_im[self.noise_file_name[i]] else: import pyfits import os import numpy file_name = os.path.join(self.noise_dir,self.noise_file_name[i]) array = pyfits.getdata(file_name) im = galsim.ImageViewD(numpy.ascontiguousarray(array.astype(numpy.float64))) self.saved_noise_im[self.noise_file_name[i]] = im cf = galsim.correlatednoise._BaseCorrelatedNoise( rng, galsim.InterpolatedImage(im, dx=self.pixel_scale[i], normalization="sb", calculate_stepk=False, calculate_maxk=False, x_interpolant='linear', gsparams=gsparams)) cf.setVariance(self.variance[i]) return cf
def applyTo(self, image): """Apply this correlated Gaussian random noise field to an input Image. Calling ------- To add deviates to every element of an image, the syntax >>> image.addNoise(correlated_noise) is preferred. However, this is equivalent to calling this instance's .applyTo() method as follows >>> correlated_noise.applyTo(image) On output the Image instance `image` will have been given additional noise according to the given CorrelatedNoise instance `correlated_noise`. image.getScale() is used to determine the input image pixel separation, and if image.getScale() <= 0 a pixel scale of 1 is assumed. Note that the correlated noise field in `image` will be periodic across its boundaries: this is due to the fact that the internals of the CorrelatedNoise currently use a relatively simple implementation of noise generation using the Fast Fourier Transform. If you wish to avoid this property being present in your final `image` you should .applyTo() an `image` of greater extent than you need, and take a subset. @param image The input Image object. """ # Note that this uses the (fast) method of going via the power spectrum and FFTs to generate # noise according to the correlation function represented by this instance. An alternative # would be to use the covariance matrices and eigendecomposition. However, it is O(N^6) # operations for an NxN image! FFT-based noise realization is O(2 N^2 log[N]) so we use it # for noise generation applications. # Check that the input has defined bounds if not hasattr(image, "bounds"): raise ValueError( "Input image argument does not have a bounds attribute, it must be a galsim.Image "+ "or galsim.ImageView-type object with defined bounds.") # If the profile has changed since last time (or if we have never been here before), # clear out the stored values. if self._profile_for_stored is not self._profile: self._rootps_store = [] self._rootps_whitening_store = [] self._variance_stored = None # Set profile_for_stored for next time. self._profile_for_stored = self._profile # Then retrieve or redraw the sqrt(power spectrum) needed for making the noise field rootps = self._get_update_rootps(image.array.shape, image.getScale()) # Finally generate a random field in Fourier space with the right PS noise_array = _generate_noise_from_rootps(self.getRNG(), rootps) # Add it to the image image += galsim.ImageViewD(noise_array) return image
def getPSF(self, i): """Returns the PSF at index `i` as an ImageViewD object. """ if i >= len(self.PSF_file_name): raise IndexError( 'index %d given to getPSF is out of range (0..%d)'%(i,len(self.PSF_file_name)-1)) import pyfits import os import numpy if self.do_preload and not self.preloaded: self.preload() if self.preloaded: array = self.loaded_files[self.PSF_file_name[i]][self.PSF_hdu[i]].data else: file_name = os.path.join(self.image_dir,self.PSF_file_name[i]) array = pyfits.getdata(file_name,self.PSF_hdu[i]) return galsim.ImageViewD(numpy.ascontiguousarray(array.astype(numpy.float64)))
def kolmogorov_psf_image( array_shape=(256, 256), dx=1., lam_over_r0=1., flux=1.): """Return long exposure Kolmogorov PSF as an ImageViewD. The ImageView output can be used to directly instantiate an SBInterpolatedImage, and its scale will reflect the spacing of the output grid in the system of units adopted for lam_over_diam. @param array_shape the NumPy array shape desired for the array view of the ImageViewD. @param dx grid spacing of PSF in real space units @param lam_over_r0 lambda / r0 in the physical units adopted for dx (user responsible for consistency). r0 is the Fried parameter. Typical values for the Fried parameter are on the order of 10 cm for most observatories. @param flux total flux of the profile [default flux=1.] """ array = kolmogorov_psf(array_shape=array_shape, dx=dx, lam_over_r0=lam_over_r0, flux=flux) return galsim.ImageViewD(array.astype(np.float64))
def kolmogorov_mtf_image(array_shape=(256, 256), dx=1., lam_over_r0=1.): """Return the atmospheric modulation transfer function for long exposures with Kolmogorov turbulence as an ImageViewD. The ImageView output can be used to directly instantiate an SBInterpolatedImage, and its scale will reflect the spacing of the output grid in the system of units adopted for lam_over_r0. @param array_shape the NumPy array shape desired for the array view of the ImageViewD. @param dx grid spacing of PSF in real space units @param lam_over_r0 lambda / r0 in the physical units adopted for dx (user responsible for consistency), where r0 is the Fried parameter. The FWHM of the Kolmogorov PSF is ~0.976 lambda/r0 (e.g., Racine 1996, PASP 699, 108). Typical values for the Fried parameter are on the order of 10 cm for most observatories and up to 20 cm for excellent sites. The values are usually quoted at lambda = 500 nm and r0 depends weakly on wavelength [r0 ~ lambda^(-6/5)]. """ amtf = kolmogorov_mtf(array_shape=array_shape, dx=dx, lam_over_r0=lam_over_r0) return galsim.ImageViewD(amtf.astype(np.float64))
# function that is NPIX by NPIX if not os.path.isfile( CFIMFILE ): # If the CFIMFILE already exists skip straight through to the plots # Read in the pickled images noiseims = cPickle.load(open(NOISEIMFILE, 'rb')) # Loop through the images and sum the correlation functions hst_ncf = None bd = galsim.BaseDeviate(12345) # Seed is basically unimportant here for noiseim in noiseims: noiseim = noiseim.astype(np.float64) if hst_ncf is None: # Initialize the HST noise correlation function using the first image hst_ncf = galsim.CorrelatedNoise(bd, galsim.ImageViewD(noiseim), correct_periodicity=True, subtract_mean=SUBTRACT_MEAN) else: hst_ncf += galsim.CorrelatedNoise(bd, galsim.ImageViewD(noiseim), correct_periodicity=True, subtract_mean=SUBTRACT_MEAN) hst_ncf /= float(len(noiseims)) # Draw and plot an output image of the resulting correlation function cfimage = galsim.ImageD(NPIX, NPIX) hst_ncf.draw(cfimage, dx=1.) # Save this to the output filename specified in the script header cfimage.write(CFIMFILE) else: cfimage = galsim.fits.read(CFIMFILE)
def __init__( self, position_list_filename="ground_optical_psf_zernike_coefficients_41x41/ZEMAXInput.dat", lam=800., diameter=4.0, obscuration=0.35, nstruts=0, strut_thick=0.01, strut_angle=0. * galsim.degrees, pad_factor=None, dz=0., dx=0., dy=0., tx=0., ty=0., dz0=0.05, interpolant2d=None): """ Inputs - position_list_filename: filename of position list used for reading ZEMAX output files. - lam: wavelength [nm] - diameter: diameter of telescope [m] - obscuration: central obscuration [ratio between mirror diameter and obscuration diameter] - nstruts: number of radial support struts to add to the central obscuration - strut_thick: thickness of support struts as a fraction of pupil diameter - strut_angle: angle made between the vertical and the strut starting closest to it, defined to be positive in the counter-clockwise direction; must be a galsim.Angle instance - pad_factor: optional padding specification if 1.5 is not good enough - dz: defocus [mm] - dx: decenter along x axis [mm] - dy: decenter along y axis [mm] - tx: tilt about x [arcsec] - tx: tilt about y [arcsec] - dz0: offset to defocus [mm] - interpolant2d: galsim._galsim.InterpolantXY If None, galsim.InterpolantXY(galsim.Quintic()) Notes of system convention (dx, dy, dz): right-handed system where z-axis is along the light coming into mirror. (tx, ty): + is the rotation by which a right screw goes into the direction of an axis. """ self.lam = lam * 1e-9 # meters self.lam_over_diam = self.lam / diameter * 206265 # arcsec self.obscuration = obscuration self.nstruts = nstruts self.strut_thick = strut_thick self.strut_angle = strut_angle self.pad_factor = pad_factor # read position information from the list data = np.loadtxt(position_list_filename) x = data[:, 0] y = data[:, 1] n = int(np.sqrt(len(x))) d = y[1] - y[0] self.xmin = x.min() self.xmax = x.max() self.ymin = y.min() self.ymax = y.max() # read coefficients from ZEMAX file self.ncoefs = 8 # defocus, a1, a2, c1, c2, t1, t2, spher coefs = np.zeros((self.ncoefs, n, n)) for i, (_x, _y) in enumerate(zip(x, y)): zernike_filename = os.path.join( os.path.dirname(position_list_filename), "%.4f_%.4f.txt" % (_x, _y)) wavelength, coefs_tmp = loadZernikeCoefficients(zernike_filename) i_x = int(i / n) i_y = i % n # need to flip sign of x and y, since definition of axes in ZEMAX and GalSim seem #different. # We have to convert Zernike coefficients in units of wavelength we want. coefs[:, n - i_y - 1, n - i_x - 1] = coefs_tmp * wavelength * 1e-6 / self.lam # get interpolated images self.interpolated_coefficients = list() for coef in coefs: im_coef = galsim.ImageViewD(coef) im_coef.setScale(d) if interpolant2d == None: interpolant2d = galsim.InterpolantXY(galsim.Quintic()) self.interpolated_coefficients.append( galsim.InterpolatedImage( im_coef, x_interpolant=interpolant2d, normalization="sb", calculate_stepk=False, calculate_maxk=False, )) # prepare for misalignment self.optical_psf_misalignment = OpticalPSFMisalignment( self.lam * 1e9, dz + dz0, dx, dy, tx, ty)
test_image2 = galsim.ImageD(TESTDIM, TESTDIM) failed1 = 0 failed2 = 0 failed3 = 0 failed4 = 0 failed5 = 0 failed6 = 0 failed7 = 0 TESTSTART = 25 TESTEND = 125 for dim in range(TESTSTART, TESTEND): noise_array = np.random.randn(dim, dim) noise_image = galsim.ImageViewD(noise_array) cn1 = galsim.CorrelatedNoise(rng, noise_image) cn2 = galsim.CorrelatedNoise(rng, noise_image, correct_periodicity=False) # First try with (default), then without, periodicity correction test_image1.setZero() try: cn1.applyTo(test_image1) except RuntimeError: failed1 += 1 test_image2.setZero() try: cn2.applyTo(test_image2) except RuntimeError: failed2 += 1 # Then try calculating the PS by hand, in the same manner as the CorrelatedNoise internals noiseft = np.fft.fft2(noise_array)
def ptf_image(array_shape=(256, 256), dx=1., lam_over_diam=2., defocus=0., astig1=0., astig2=0., coma1=0., coma2=0., trefoil1=0., trefoil2=0., spher=0., circular_pupil=True, obscuration=0.): """Return the PTF [radians] of a circular (default) or square pupil with low-order aberrations as an ImageViewD. PTF array element ordering follows the DFT standard of kxky(array_shape), and has ptf[0, 0] = 0. by default. The scale of the output ImageViewD is correct in k space units. The ImageView output can be used to directly instantiate an SBInterpolatedImage, and its .getScale() method will reflect the spacing of the output grid in the system of units adopted for lam_over_diam. To ensure properly Nyquist sampled output any user should set lam_over_diam >= 2. * dx. @param array_shape the NumPy array shape desired for the array view of the ImageViewD. @param dx grid spacing of PSF in real space units @param lam_over_diam lambda / telescope diameter in the physical units adopted for dx (user responsible for consistency). @param defocus defocus in units of incident light wavelength. @param astig1 astigmatism (like e2) in units of incident light wavelength. @param astig2 astigmatism (like e1) in units of incident light wavelength. @param coma1 coma along y in units of incident light wavelength. @param coma2 coma along x in units of incident light wavelength. @param trefoil1 trefoil (one of the arrows along y) in units of incident light wavelength. @param trefoil2 trefoil (one of the arrows along x) in units of incident light wavelength. @param spher spherical aberration in units of incident light wavelength. @param circular_pupil adopt a circular pupil? @param obscuration linear dimension of central obscuration as fraction of pupil linear dimension, [0., 1.) """ array = ptf(array_shape=array_shape, dx=dx, lam_over_diam=lam_over_diam, defocus=defocus, astig1=astig1, astig2=astig2, coma1=coma1, coma2=coma2, trefoil1=trefoil1, trefoil2=trefoil2, spher=spher, circular_pupil=circular_pupil, obscuration=obscuration) im = galsim.ImageViewD(array.astype(np.float64)) if array_shape[0] != array_shape[1]: import warnings warnings.warn( "PTF Image scale will not be correct in both directions for non-square arrays, only " + "square grids currently supported by galsim.Images.") im.setScale(2. * np.pi / array_shape[0]) return im
CFLOGPLOTFILE = "acs_I_unrot_sci_20_log10cf.png" # Plot (log) of the output CF NPIX = 81 # Make an image of the final correlation # function that is NPIX by NPIX if not os.path.isfile(CFIMFILE): # If the CFIMFILE already exists skip straight through to the plots # Read in the pickled images noiseims = cPickle.load(open(NOISEIMFILE, 'rb')) # Loop through the images and sum the correlation functions hst_ncf = None bd = galsim.BaseDeviate(12345) # Seed is basically unimportant here for noiseim in noiseims: noiseim = noiseim.astype(np.float64) if hst_ncf is None: # Initialize the HST noise correlation function using the first image hst_ncf = galsim.CorrelatedNoise( bd, galsim.ImageViewD(noiseim), correct_periodicity=True, subtract_mean=SUBTRACT_MEAN) else: hst_ncf += galsim.CorrelatedNoise( bd, galsim.ImageViewD(noiseim), correct_periodicity=True, subtract_mean=SUBTRACT_MEAN) hst_ncf /= float(len(noiseims)) # Draw and plot an output image of the resulting correlation function cfimage = galsim.ImageD(NPIX, NPIX) hst_ncf.draw(cfimage, dx=1.) # Save this to the output filename specified in the script header cfimage.write(CFIMFILE) else: cfimage = galsim.fits.read(CFIMFILE) # Then make nice plots
def wavefront_image(array_shape=(256, 256), dx=1., lam_over_diam=2., defocus=0., astig1=0., astig2=0., coma1=0., coma2=0., trefoil1=0., trefoil2=0., spher=0., circular_pupil=True, obscuration=0.): """Return wavefront as a (real, imag) tuple of ImageViewD objects rather than complex NumPy array. Outputs a circular pupil wavefront of unit amplitude that can be easily transformed to produce an optical PSF with lambda/diam = lam_over_diam on an output grid of spacing dx. The ImageView output can be used to directly instantiate an SBInterpolatedImage, and its .getScale() method will reflect the spacing of the output grid in the system of units adopted for lam_over_diam. To ensure properly Nyquist sampled output any user should set lam_over_diam >= 2. * dx. The pupil sample locations are arranged in standard DFT element ordering format, so that (kx, ky) = (0, 0) is the [0, 0] array element. The scale of the output ImageViewD is correct in k space units. Input aberration coefficients are assumed to be supplied in units of wavelength, and correspond to the Zernike polynomials in the Noll convention definined in Noll, J. Opt. Soc. Am. 66, 207-211(1976). For a brief summary of the polynomials, refer to http://en.wikipedia.org/wiki/Zernike_polynomials#Zernike_polynomials. @param array_shape the NumPy array shape desired for the output array. @param dx grid spacing of PSF in real space units @param lam_over_diam lambda / telescope diameter in the physical units adopted for dx (user responsible for consistency). @param defocus defocus in units of incident light wavelength. @param astig1 astigmatism (like e2) in units of incident light wavelength. @param astig2 astigmatism (like e1) in units of incident light wavelength. @param coma1 coma along y in units of incident light wavelength. @param coma2 coma along x in units of incident light wavelength. @param trefoil1 trefoil (one of the arrows along y) in units of incident light wavelength. @param trefoil2 trefoil (one of the arrows along x) in units of incident light wavelength. @param spher spherical aberration in units of incident light wavelength. @param circular_pupil adopt a circular pupil? @param obscuration linear dimension of central obscuration as fraction of pupil linear dimension, [0., 1.) """ array = wavefront(array_shape=array_shape, dx=dx, lam_over_diam=lam_over_diam, defocus=defocus, astig1=astig1, astig2=astig2, coma1=coma1, coma2=coma2, trefoil1=trefoil1, trefoil2=trefoil2, spher=spher, circular_pupil=circular_pupil, obscuration=obscuration) imreal = galsim.ImageViewD( np.ascontiguousarray(array.real.astype(np.float64))) imimag = galsim.ImageViewD( np.ascontiguousarray(array.imag.astype(np.float64))) if array_shape[0] != array_shape[1]: import warnings warnings.warn( "Wavefront Images' scales will not be correct in both directions for non-square " + "arrays, only square grids currently supported by galsim.Images.") imreal.setScale(2. * np.pi / array_shape[0]) imimag.setScale(2. * np.pi / array_shape[0]) return (imreal, imimag)
def __init__(self, rng, image, dx=0., x_interpolant=None, correct_periodicity=True, subtract_mean=False): # Check that the input image is in fact a galsim.ImageSIFD class instance if not isinstance(image, ( galsim.BaseImageD, galsim.BaseImageF, galsim.BaseImageS, galsim.BaseImageI)): raise TypeError( "Input image not a galsim.Image class object (e.g. ImageD, ImageViewS etc.)") # Build a noise correlation function (CF) from the input image, using DFTs # Calculate the power spectrum then a (preliminary) CF ft_array = np.fft.fft2(image.array) ps_array = (ft_array * ft_array.conj()).real if subtract_mean: # Quickest non-destructive way to make the PS correspond to the # mean-subtracted case ps_array[0, 0] = 0. # Note need to normalize due to one-directional 1/N^2 in FFT conventions cf_array_prelim = (np.fft.ifft2(ps_array)).real / np.product(image.array.shape) store_rootps = True # Currently the ps_array above corresponds to cf, but this may change... # Apply a correction for the DFT assumption of periodicity unless user requests otherwise if correct_periodicity: cf_array_prelim *= _cf_periodicity_dilution_correction(cf_array_prelim.shape) store_rootps = False # Roll CF array to put the centre in image centre. Remember that numpy stores data [y,x] cf_array_prelim = utilities.roll2d( cf_array_prelim, (cf_array_prelim.shape[0] / 2, cf_array_prelim.shape[1] / 2)) # The underlying C++ object is expecting the CF to be represented by an odd-dimensioned # array with the central pixel denoting the zero-distance correlation (variance), even # even if the input image was even-dimensioned on one or both sides. # We therefore copy-paste and zero pad the CF calculated above to ensure that these # expectations are met. # # Determine the largest dimension of the input image, and use it to generate an empty CF # array for final output, padding by one to make odd if necessary: cf_array = np.zeros(( 1 + 2 * (cf_array_prelim.shape[0] / 2), 1 + 2 * (cf_array_prelim.shape[1] / 2))) # using integer division # Then put the data from the prelim CF into this array cf_array[0:cf_array_prelim.shape[0], 0:cf_array_prelim.shape[1]] = cf_array_prelim # Then copy-invert-paste data from the leftmost column to the rightmost column, and lowest # row to the uppermost row, if the original CF had even dimensions in the x and y # directions, respectively (remembering again that NumPy stores data [y,x] in arrays) if cf_array_prelim.shape[1] % 2 == 0: # first do x lhs_column = cf_array[:, 0] cf_array[:, cf_array_prelim.shape[1]] = lhs_column[::-1] # inverts order as required if cf_array_prelim.shape[0] % 2 == 0: # then do y bottom_row = cf_array[0, :] cf_array[cf_array_prelim.shape[0], :] = bottom_row[::-1] # inverts order as required # Wrap correlation function in an image cf_image = galsim.ImageViewD(np.ascontiguousarray(cf_array)) # Correctly record the original image scale if set if dx > 0.: cf_image.setScale(dx) elif image.getScale() > 0.: cf_image.setScale(image.getScale()) else: # sometimes Images are instantiated with scale=0, in which case we will assume unit # pixel scale cf_image.setScale(1.) # If x_interpolant not specified on input, use bilinear if x_interpolant == None: linear = galsim.Linear(tol=1.e-4) x_interpolant = galsim.InterpolantXY(linear) else: x_interpolant = utilities.convert_interpolant_to_2d(x_interpolant) # Then initialize... cf_object = galsim.InterpolatedImage( cf_image, x_interpolant=x_interpolant, dx=cf_image.getScale(), normalization="sb", calculate_stepk=False, calculate_maxk=False) # these internal calculations do not seem # to do very well with often sharp-peaked # correlation function images... _BaseCorrelatedNoise.__init__(self, rng, cf_object) if store_rootps: # If it corresponds to the CF above, store useful data as a (rootps, dx) tuple for # efficient later use: self._profile_for_stored = self._profile self._rootps_store.append((np.sqrt(ps_array), cf_image.getScale()))
def applyWhiteningTo(self, image): """Apply noise designed to whiten correlated Gaussian random noise in an input Image. On output the Image instance `image` will have been given additional noise according to a specified CorrelatedNoise instance, designed to whiten any correlated noise that may have originally existed in `image`. Calling ------- >>> correlated_noise.applyWhiteningTo(image) If the `image` originally contained noise with a correlation function described by the `correlated_noise` instance, the combined noise after using the applyWhiteningTo() method will be approximately uncorrelated. Tests using COSMOS noise fields suggest ~0.3% residual off-diagonal covariances after whitening, relative to the variance, although results may vary depending on the precise correlation function of the noise field. (See `devel/external/hst/compare_whitening_subtraction.py` for the COSMOS tests.) Note that the code doesn't check that the "if" above is true: the user MUST make sure this is the case for the final noise to be uncorrelated. image.getScale() is used to determine the input image pixel separation, and if image.getScale() <= 0 a pixel scale of 1 is assumed. If you are interested in a theoretical calculation of the variance in the final noise field after whitening, the applyWhiteningTo() method in fact returns this variance. For example: >>> variance = correlated_noise.applyWhiteningTo(image) Example ------- To see noise whitening in action, let us use a model of the correlated noise in COSMOS as returned by the getCOSMOSNoise() function. Let's initialize and add noise to an image: >>> cosmos_file='YOUR/REPO/PATH/GalSim/examples/data/acs_I_unrot_sci_20_cf.fits' >>> cn = galsim.getCOSMOSNoise(galsim.BaseDeviate(), cosmos_file) >>> image = galsim.ImageD(256, 256) >>> image.setScale(0.03) # Should match the COSMOS default since didn't specify another >>> image.addNoise(cn) The `image` will then contain a realization of a random noise field with COSMOS-like correlation. Using the applyWhiteningTo() method, we can now add more noise to `image` with a power spectrum specifically designed to make the combined noise fields uncorrelated: >>> cn.applyWhiteningTo(image) Of course, this whitening comes at the cost of adding further noise to the image, but the algorithm is designed to make this additional noise (nearly) as small as possible. @param image The input Image object. @return variance A float containing the theoretically calculated variance of the combined noise fields in the updated image. """ # Note that this uses the (fast) method of going via the power spectrum and FFTs to generate # noise according to the correlation function represented by this instance. An alternative # would be to use the covariance matrices and eigendecomposition. However, it is O(N^6) # operations for an NxN image! FFT-based noise realization is O(2 N^2 log[N]) so we use it # for noise generation applications. # Check that the input has defined bounds if not hasattr(image, "bounds"): raise ValueError( "Input image argument does not have a bounds attribute, it must be a galsim.Image "+ "or galsim.ImageView-type object with defined bounds.") # If the profile has changed since last time (or if we have never been here before), # clear out the stored values. if self._profile_for_stored is not self._profile: self._rootps_store = [] self._rootps_whitening_store = [] self._variance_stored = None # Set profile_for_stored for next time. self._profile_for_stored = self._profile # Then retrieve or redraw the sqrt(power spectrum) needed for making the whitening noise, # and the total variance of the combination rootps_whitening, variance = self._get_update_rootps_whitening( image.array.shape, image.getScale()) # Finally generate a random field in Fourier space with the right PS and add to image noise_array = _generate_noise_from_rootps(self.getRNG(), rootps_whitening) image += galsim.ImageViewD(noise_array) # Return the variance to the interested user return variance
# 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the disclaimer given in the documentation # and/or other materials provided with the distribution. # # Just some temporary testing stuff if __name__ == "__main__": import numpy as np import matplotlib.pyplot as plt import galsim IMSIZE = 40 # First make a noise field with an even number of elements enoise = galsim.ImageViewD(np.random.randn(IMSIZE, IMSIZE)) encf = galsim.correlatednoise.CorrFunc(enoise) print encf.SBProfile.xValue(galsim.PositionD(0, 0)) # Then make a noise field with an odd number of elements onoise = galsim.ImageViewD(np.random.randn(IMSIZE + 1, IMSIZE + 1)) oncf = galsim.correlatednoise.CorrFunc(onoise) print oncf.SBProfile.xValue(galsim.PositionD(0, 0)) testim = galsim.ImageD(10, 10) cv = encf.SBProfile.getCovarianceMatrix(testim.view(), dx=1.) #plt.pcolor(cv.array); plt.colorbar() # Construct an image with noise correlated in the y direction plt.figure() ynoise = galsim.ImageViewD(enoise.array[:, :] +
def psf_image(array_shape=(256, 256), dx=1., lam_over_diam=2., defocus=0., astig1=0., astig2=0., coma1=0., coma2=0., trefoil1=0., trefoil2=0., spher=0., circular_pupil=True, obscuration=0., flux=1.): """Return circular (default) or square pupil PSF with low-order aberrations as an ImageViewD. The PSF is centred on the array[array_shape[0] / 2, array_shape[1] / 2] pixel by default, and uses surface brightness rather than flux units for pixel values, matching SBProfile. The ImageView output can be used to directly instantiate an SBInterpolatedImage, and its .getScale() method will reflect the spacing of the output grid in the system of units adopted for lam_over_diam. To ensure properly Nyquist sampled output any user should set lam_over_diam >= 2. * dx. @param array_shape the NumPy array shape desired for the array view of the ImageViewD. @param dx grid spacing of PSF in real space units @param lam_over_diam lambda / telescope diameter in the physical units adopted for dx (user responsible for consistency). @param defocus defocus in units of incident light wavelength. @param astig1 astigmatism (like e2) in units of incident light wavelength. @param astig2 astigmatism (like e1) in units of incident light wavelength. @param coma1 coma along y in units of incident light wavelength. @param coma2 coma along x in units of incident light wavelength. @param trefoil1 trefoil (one of the arrows along y) in units of incident light wavelength. @param trefoil2 trefoil (one of the arrows along x) in units of incident light wavelength. @param spher spherical aberration in units of incident light wavelength. @param circular_pupil adopt a circular pupil? @param obscuration linear dimension of central obscuration as fraction of pupil linear dimension, [0., 1.) @param flux total flux of the profile [default flux=1.] """ array = psf(array_shape=array_shape, dx=dx, lam_over_diam=lam_over_diam, defocus=defocus, astig1=astig1, astig2=astig2, coma1=coma1, coma2=coma2, trefoil1=trefoil1, trefoil2=trefoil2, spher=spher, circular_pupil=circular_pupil, obscuration=obscuration, flux=flux) im = galsim.ImageViewD(array.astype(np.float64)) im.setScale(dx) return im
RSEED = 12334566 ud = galsim.UniformDeviate(RSEED) # Case 1: subtract_mean=True; Case 2: subtract_mean=False cn1 = galsim.getCOSMOSNoise(ud, CFIMFILE_SUB, dx_cosmos=1.) cn2 = galsim.getCOSMOSNoise(ud, CFIMFILE_UNS, dx_cosmos=1.) testim1 = galsim.ImageD(7, 7) testim2 = galsim.ImageD(7, 7) var1 = 0. var2 = 0. noisearrays = cPickle.load(open(NOISEIMFILE, 'rb')) for noisearray, i in zip(noisearrays, range(len(noisearrays))): noise1 = galsim.ImageViewD((noisearray.copy()).astype(np.float64), scale=1.) noise2 = galsim.ImageViewD((noisearray.copy()).astype(np.float64), scale=1.) cn1.applyWhiteningTo(noise1) cn2.applyWhiteningTo(noise2) var1 += noise1.array.var() var2 += noise2.array.var() cntest1 = galsim.CorrelatedNoise(ud, noise1) cntest2 = galsim.CorrelatedNoise(ud, noise2) cntest1.draw(testim1, dx=1., add_to_image=True) cntest2.draw(testim2, dx=1., add_to_image=True) print "Done "+str(i + 1)+"/"+str(len(noisearrays)) testim1 /= len(noisearrays) testim2 /= len(noisearrays) var1 /= len(noisearrays) var2 /= len(noisearrays)
def __init__( self, filename="afta_wfirst_example_psf_exaggerated.fields_and_coefs.fits", lam=1000., diameter=2.4, obscuration=0.28, nstruts=6, strut_thick=0.01, strut_angle=0. * galsim.degrees, pad_factor=None, rms=0.075, interpolant2d=None, seed=None): """ Inputs - filename: filename of fits file with information of optics. - lam: wavelength [nm] - diameter: diameter of telescope [m] - obscuration: central obscuration [ratio between mirror diameter and obscuration diameter] - nstruts: number of radial support struts to add to the central obscuration - strut_thick: thickness of support struts as a fraction of pupil diameter - strut_angle: angle made between the vertical and the strut starting closest to it, defined to be positive in the counter-clockwise direction; must be a galsim.Angle instance - pad_factor: optional padding specification if 1.5 is not good enough - rms: total rms of the random Zernike coefficients [wavelength] - interpolant2d: galsim._galsim.InterpolantXY If None, galsim.InterpolantXY(galsim.Quintic()) - seed: random seed to use for numpy routines that make random additional aberrations (if None, then let numpy seed routines based on time) """ self.lam = lam * 1e-9 # meters self.lam_over_diam = self.lam / diameter * 206265 # arcsec self.obscuration = obscuration self.nstruts = nstruts self.strut_thick = strut_thick self.strut_angle = strut_angle self.pad_factor = pad_factor # read file hdulist = pyfits.open(filename) primary_hdu, fields, coefs = hdulist # note that fields.data['x'] is actually y-axis and fields.data['y'] is x-axis fields_1d = np.array(zip(fields.data['x'], fields.data['y'])) n = int(np.sqrt(fields_1d.shape[0])) self.dy = sorted(fields.data['x'])[n] - sorted(fields.data['x'])[n - 1] self.dx = sorted(fields.data['y'])[n] - sorted(fields.data['y'])[n - 1] self.xmin = fields.data['y'].min() self.xmax = fields.data['y'].max() self.ymin = fields.data['x'].min() self.ymax = fields.data['x'].max() sorted_coordinates_raster = np.array([ row[2] for row in sorted([(r[0], r[1], i) for i, r in enumerate(fields_1d)]) ]) field_mapping_index = sorted_coordinates_raster.reshape(n, n) # Zernike coefficients in the input file is 10 times larger than actual ones mapped_coefs = coefs.data[field_mapping_index] / 10. # interpolate coefficients self.n_coefs = 8 # defocus, a1, a2, c1, c2, t1, t2, spher self.interpolated_coefficients = list() for i_coefs in range(self.n_coefs): im_coef = galsim.ImageViewD( np.ascontiguousarray(mapped_coefs[:, :, i_coefs + 3])) im_coef.setScale(1.) if interpolant2d == None: interpolant2d = galsim.InterpolantXY(galsim.Quintic()) self.interpolated_coefficients.append( galsim.InterpolatedImage( im_coef, x_interpolant=interpolant2d, normalization="sb", calculate_stepk=False, calculate_maxk=False, )) # generate aberration errors if rms != 0.: if seed is not None: np.random.seed(seed) self.aberration_errors = np.random.normal( 0., rms / np.sqrt(self.n_coefs), self.n_coefs) else: self.aberration_errors = np.zeros(self.n_coefs) hdulist.close()