def test_Quintic_ref(): """Test use of Quintic interpolant against some reference values """ import time t1 = time.time() interp = galsim.Quintic(tol=1.e-4) testobj = galsim.InterpolatedImage(ref_image.view(), x_interpolant=interp, dx=dx, normalization='sb') testKvals = np.zeros(len(KXVALS)) # Make test kValues for i in xrange(len(KXVALS)): posk = galsim.PositionD(KXVALS[i], KYVALS[i]) testKvals[i] = np.abs(testobj.kValue(posk)) # Compare with saved array refKvals = np.loadtxt(os.path.join(TESTDIR, "absfKQuintic_test.txt")) print 'ref = ', refKvals print 'test = ', testKvals np.testing.assert_array_almost_equal( refKvals / testKvals, 1., 5, err_msg="kValues do not match reference values for Quintic interpolant." ) t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def __init__(self, real_kimage, imag_kimage, k_interpolant=None, stepk=None, gsparams=None): # make sure real_kimage, imag_kimage are really `Image`s, are floats, and are congruent. if not isinstance(real_kimage, galsim.Image) or not isinstance(imag_kimage, galsim.Image): raise ValueError("Supplied kimage is not an Image instance") if ((real_kimage.dtype != np.float32 and real_kimage.dtype != np.float64) or (imag_kimage.dtype != np.float32 and imag_kimage.dtype != np.float64)): raise ValueError("Supplied image does not have dtype of float32 or float64!") if real_kimage.bounds != imag_kimage.bounds: raise ValueError("Real and Imag kimages must have same bounds.") if real_kimage.scale != imag_kimage.scale: raise ValueError("Real and Imag kimages must have same scale.") # Make sure any `wcs`s are `PixelScale`s. if ((real_kimage.wcs is not None and not real_kimage.wcs.isPixelScale()) or (imag_kimage.wcs is not None and not imag_kimage.wcs.isPixelScale())): raise ValueError("Real and Imag kimage wcs's must be PixelScale's or None.") # Check for Hermitian symmetry properties of real_kimage and imag_kimage shape = real_kimage.array.shape # If image is even-sized, ignore first row/column since in this case not every pixel has # a symmetric partner to which to compare. bd = galsim.BoundsI(real_kimage.xmin + (1 if shape[1]%2==0 else 0), real_kimage.xmax, real_kimage.ymin + (1 if shape[0]%2==0 else 0), real_kimage.ymax) if not (np.allclose(real_kimage[bd].array, real_kimage[bd].array[::-1,::-1]) and np.allclose(imag_kimage[bd].array, -imag_kimage[bd].array[::-1,::-1])): raise ValueError("Real and Imag kimages must form a Hermitian complex matrix.") if stepk is None: stepk = real_kimage.scale else: if stepk < real_kimage.scale: import warnings warnings.warn( "Provided stepk is smaller than kimage.scale; overriding with kimage.scale.") stepk = real_kimage.scale self._real_kimage = real_kimage self._imag_kimage = imag_kimage self._stepk = stepk self._gsparams = gsparams # set up k_interpolant if none was provided by user, or check that the user-provided one # is of a valid type if k_interpolant is None: self.k_interpolant = galsim.Quintic(tol=1e-4) else: self.k_interpolant = galsim.utilities.convert_interpolant(k_interpolant) GSObject.__init__(self, galsim._galsim.SBInterpolatedKImage( self._real_kimage.image, self._imag_kimage.image, self._real_kimage.scale, self._stepk, self.k_interpolant, gsparams))
def __init__(self, lam_over_r0=None, fwhm=None, interpolant=None, oversampling=1.5, flux=1., gsparams=None): # The FWHM of the Kolmogorov PSF is ~0.976 lambda/r0 (e.g., Racine 1996, PASP 699, 108). if lam_over_r0 is None: if fwhm is not None: lam_over_r0 = fwhm / 0.976 else: raise TypeError( "Either lam_over_r0 or fwhm must be specified for AtmosphericPSF" ) else: if fwhm is None: fwhm = 0.976 * lam_over_r0 else: raise TypeError( "Only one of lam_over_r0 and fwhm may be specified for AtmosphericPSF" ) # Set the lookup table sample rate via FWHM / 2 / oversampling (BARNEY: is this enough??) dx_lookup = .5 * fwhm / oversampling # Fold at 10 times the FWHM stepk_kolmogorov = np.pi / (10. * fwhm) # Odd array to center the interpolant on the centroid. Might want to pad this later to # make a nice size array for FFT, but for typical seeing, arrays will be very small. npix = 1 + 2 * (np.ceil(np.pi / stepk_kolmogorov)).astype(int) atmoimage = kolmogorov_psf_image(array_shape=(npix, npix), dx=dx_lookup, lam_over_r0=lam_over_r0, flux=flux) # Run checks on the interpolant and build default if None if interpolant is None: quintic = galsim.Quintic(tol=1e-4) self.interpolant = galsim.InterpolantXY(quintic) else: self.interpolant = galsim.utilities.convert_interpolant_to_2d( interpolant) # Then initialize the SBProfile GSObject.__init__( self, galsim.SBInterpolatedImage(atmoimage, xInterp=self.interpolant, dx=dx_lookup, gsparams=gsparams)) # The above procedure ends up with a larger image than we really need, which # means that the default stepK value will be smaller than we need. # Thus, we call the function calculateStepK() to refine the value. self.SBProfile.calculateStepK() self.SBProfile.calculateMaxK()
def test_sbinterpolatedimage(): """Test that we can make SBInterpolatedImages from Images of various types, and convert back. """ import time t1 = time.time() # for each type, try to make an SBInterpolatedImage, and check that when we draw an image from # that SBInterpolatedImage that it is the same as the original lan3 = galsim.Lanczos(3, True, 1.E-4) lan3_2d = galsim.InterpolantXY(lan3) ftypes = [np.float32, np.float64] ref_array = np.array([[0.01, 0.08, 0.07, 0.02], [0.13, 0.38, 0.52, 0.06], [0.09, 0.41, 0.44, 0.09], [0.04, 0.11, 0.10, 0.01]]) for array_type in ftypes: image_in = galsim.ImageView[array_type](ref_array.astype(array_type)) np.testing.assert_array_equal( ref_array.astype(array_type), image_in.array, err_msg= "Array from input Image differs from reference array for type %s" % array_type) sbinterp = galsim.SBInterpolatedImage(image_in, lan3_2d, dx=1.0) test_array = np.zeros(ref_array.shape, dtype=array_type) image_out = galsim.ImageView[array_type](test_array, scale=1.0) sbinterp.draw(image_out.view()) np.testing.assert_array_equal( ref_array.astype(array_type), image_out.array, err_msg= "Array from output Image differs from reference array for type %s" % array_type) # Lanczos doesn't quite get the flux right. Wrong at the 5th decimal place. # Gary says that's expected -- Lanczos isn't technically flux conserving. # He applied the 1st order correction to the flux, but expect to be wrong at around # the 10^-5 level. # Anyway, Quintic seems to be accurate enough. quint = galsim.Quintic(1.e-4) quint_2d = galsim.InterpolantXY(quint) sbinterp = galsim.SBInterpolatedImage(image_in, quint_2d, dx=1.0) sbinterp.setFlux(1.) do_shoot(galsim.GSObject(sbinterp), image_out, "InterpolatedImage") # Test kvalues do_kvalue(galsim.GSObject(sbinterp), "InterpolatedImage") t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def test_roundtrip(): """Test round trip from Image to InterpolatedImage back to Image. """ # Based heavily on test_sbinterpolatedimage() in test_SBProfile.py! import time t1 = time.time() # for each type, try to make an SBInterpolatedImage, and check that when we draw an image from # that SBInterpolatedImage that it is the same as the original ftypes = [np.float32, np.float64] ref_array = np.array([[0.01, 0.08, 0.07, 0.02], [0.13, 0.38, 0.52, 0.06], [0.09, 0.41, 0.44, 0.09], [0.04, 0.11, 0.10, 0.01]]) for array_type in ftypes: image_in = galsim.ImageView[array_type](ref_array.astype(array_type)) np.testing.assert_array_equal( ref_array.astype(array_type), image_in.array, err_msg= "Array from input Image differs from reference array for type %s" % array_type) interp = galsim.InterpolatedImage(image_in, dx=test_dx) test_array = np.zeros(ref_array.shape, dtype=array_type) image_out = galsim.ImageView[array_type](test_array) image_out.setScale(test_dx) interp.draw(image_out) np.testing.assert_array_equal( ref_array.astype(array_type), image_out.array, err_msg= "Array from output Image differs from reference array for type %s" % array_type) # Lanczos doesn't quite get the flux right. Wrong at the 5th decimal place. # Gary says that's expected -- Lanczos isn't technically flux conserving. # He applied the 1st order correction to the flux, but expect to be wrong at around # the 10^-5 level. # Anyway, Quintic seems to be accurate enough. quint = galsim.Quintic(1.e-4) quint_2d = galsim.InterpolantXY(quint) interp = galsim.InterpolatedImage(image_in, x_interpolant=quint_2d, dx=test_dx, flux=1.) do_shoot(interp, image_out, "InterpolatedImage") t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def test_Quintic_spline(): """Test the spline tabulation of the k space Quintic interpolant. """ import time t1 = time.time() interp = galsim.InterpolantXY(galsim.Quintic(tol=1.e-4)) testobj = galsim.SBInterpolatedImage(image.view(), interp, dx=dx) testKvals = np.zeros(len(KXVALS)) # Make test kValues for i in xrange(len(KXVALS)): posk = galsim.PositionD(KXVALS[i], KYVALS[i]) testKvals[i] = np.abs(testobj.kValue(posk)) # Compare with saved array refKvals = np.loadtxt(os.path.join(TESTDIR, "absfKQuintic_test.txt")) np.testing.assert_array_almost_equal( refKvals, testKvals, DECIMAL, err_msg="Spline-interpolated kValues do not match saved " + "data for k space Quintic interpolant.") t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, rng=None, x_interpolant=None, k_interpolant=None, flux=None, pad_factor=0, noise_pad=False, pad_image=None, use_cache=True, gsparams=None): import pyfits import numpy as np # Code block below will be for galaxy selection; not all are currently implemented. Each # option must return an index within the real_galaxy_catalog. if index is not None: if id is not None or random is True: raise AttributeError( 'Too many methods for selecting a galaxy!') use_index = index elif id is not None: if random is True: raise AttributeError( 'Too many methods for selecting a galaxy!') use_index = real_galaxy_catalog._get_index_for_id(id) elif random is True: if rng is None: uniform_deviate = galsim.UniformDeviate() elif isinstance(rng, galsim.BaseDeviate): uniform_deviate = galsim.UniformDeviate(rng) else: raise TypeError( "The rng provided to RealGalaxy constructor is not a BaseDeviate" ) use_index = int(real_galaxy_catalog.nobjects * uniform_deviate()) else: raise AttributeError('No method specified for selecting a galaxy!') # read in the galaxy, PSF images; for now, rely on pyfits to make I/O errors. Should # consider exporting this code into fits.py in some function that takes a filename and HDU, # and returns an ImageView gal_image = real_galaxy_catalog.getGal(use_index) PSF_image = real_galaxy_catalog.getPSF(use_index) # choose proper interpolant if x_interpolant is None: lan5 = galsim.Lanczos(5, conserve_flux=True, tol=1.e-4) self.x_interpolant = galsim.InterpolantXY(lan5) else: self.x_interpolant = galsim.utilities.convert_interpolant_to_2d( x_interpolant) if k_interpolant is None: self.k_interpolant = galsim.InterpolantXY( galsim.Quintic(tol=1.e-4)) else: self.k_interpolant = galsim.utilities.convert_interpolant_to_2d( k_interpolant) # read in data about galaxy from FITS binary table; store as normal attributes of RealGalaxy # save any other relevant information as instance attributes self.catalog_file = real_galaxy_catalog.file_name self.index = use_index self.pixel_scale = float(real_galaxy_catalog.pixel_scale[use_index]) # handle padding by an image specify_size = False padded_size = gal_image.getPaddedSize(pad_factor) if pad_image is not None: specify_size = True if isinstance(pad_image, str): pad_image = galsim.fits.read(pad_image) if (not isinstance(pad_image, galsim.BaseImageF) and not isinstance(pad_image, galsim.BaseImageD)): raise ValueError( "Supplied pad_image is not one of the allowed types!") # If an image was supplied directly or from a file, check its size: # Cannot use if too small. # Use to define the final image size otherwise. deltax = ((1 + pad_image.getXMax() - pad_image.getXMin()) - (1 + gal_image.getXMax() - gal_image.getXMin())) deltay = ((1 + pad_image.getYMax() - pad_image.getYMin()) - (1 + gal_image.getYMax() - gal_image.getYMin())) if deltax < 0 or deltay < 0: raise RuntimeError("Image supplied for padding is too small!") if pad_factor != 1. and pad_factor != 0.: import warnings msg = "Warning: ignoring specified pad_factor because user also specified\n" msg += " an image to use directly for the padding." warnings.warn(msg) else: if isinstance(gal_image, galsim.BaseImageF): pad_image = galsim.ImageF(padded_size, padded_size) if isinstance(gal_image, galsim.BaseImageD): pad_image = galsim.ImageD(padded_size, padded_size) # Set up the GaussianDeviate if not provided one, or check that the user-provided one # is of a valid type. Note if random was selected, we can use that uniform_deviate safely. if random is True: gaussian_deviate = galsim.GaussianDeviate(uniform_deviate) else: if rng is None: gaussian_deviate = galsim.GaussianDeviate() elif isinstance(rng, galsim.BaseDeviate): # Even if it's already a GaussianDeviate, we still want to make a new Gaussian # deviate that would generate the same sequence, because later we change the sigma # and we don't want to change it for the original one that was passed in. So don't # distinguish between GaussianDeviate and the other BaseDeviates here. gaussian_deviate = galsim.GaussianDeviate(rng) else: raise TypeError( "rng provided to RealGalaxy constructor is not a BaseDeviate" ) # handle noise-padding options try: noise_pad = galsim.config.value._GetBoolValue(noise_pad, '') except: pass if noise_pad: self.pad_variance = float(real_galaxy_catalog.variance[use_index]) # Check, is it "True" or something else? If True, we use Gaussian uncorrelated noise # using the stored variance in the catalog. Otherwise, if it's a CorrelatedNoise we use # it directly; if it's an Image of some sort we use it to make a CorrelatedNoise; if # it's a string, we read in the image from file and make a CorrelatedNoise. if type(noise_pad) is not bool: if isinstance(noise_pad, galsim.correlatednoise._BaseCorrelatedNoise): cn = noise_pad.copy() if rng: # Let user supplied RNG take precedence over that in user CN cn.setRNG(gaussian_deviate) # This small patch may have different overall variance, so rescale while # preserving the correlation structure by default cn.setVariance(self.pad_variance) elif (isinstance(noise_pad, galsim.BaseImageF) or isinstance(noise_pad, galsim.BaseImageD)): cn = galsim.CorrelatedNoise(gaussian_deviate, noise_pad) elif use_cache and noise_pad in RealGalaxy._cache_noise_pad: cn = RealGalaxy._cache_noise_pad[noise_pad] # Make sure that we are using the desired RNG by resetting that in this cached # CorrelatedNoise instance if rng: cn.setRNG(gaussian_deviate) # This small patch may have different overall variance, so rescale while # preserving the correlation structure cn.setVariance(self.pad_variance) elif isinstance(noise_pad, str): tmp_img = galsim.fits.read(noise_pad) cn = galsim.CorrelatedNoise(gaussian_deviate, tmp_img) if use_cache: RealGalaxy._cache_noise_pad[noise_pad] = cn # This small patch may have different overall variance, so rescale while # preserving the correlation structure cn.setVariance(self.pad_variance) else: raise RuntimeError( "noise_pad must be either a bool, CorrelatedNoise, Image, " + "or a filename for reading in an Image") # Set the GaussianDeviate sigma gaussian_deviate.setSigma(np.sqrt(self.pad_variance)) # populate padding image with noise field if type(noise_pad) is bool: pad_image.addNoise(galsim.DeviateNoise(gaussian_deviate)) else: pad_image.addNoise(cn) else: self.pad_variance = 0. # Now we have to check: was the padding determined using pad_factor? Or by passing in an # image for padding? Treat these cases differently: # (1) If the former, then we can simply have the C++ handle the padding process. # (2) If the latter, then we have to do the padding ourselves, and pass the resulting image # to the C++ with pad_factor explicitly set to 1. if specify_size is False: # Make the SBInterpolatedImage out of the image. self.original_image = galsim.SBInterpolatedImage( gal_image, xInterp=self.x_interpolant, kInterp=self.k_interpolant, dx=self.pixel_scale, pad_factor=pad_factor, pad_image=pad_image, gsparams=gsparams) else: # Leave the original image as-is. Instead, we shift around the image to be used for # padding. Find out how much x and y margin there should be on lower end: x_marg = int(np.round(0.5 * deltax)) y_marg = int(np.round(0.5 * deltay)) # Now reset the pad_image to contain the original image in an even way pad_image = pad_image.view() pad_image.setScale(self.pixel_scale) pad_image.setOrigin(gal_image.getXMin() - x_marg, gal_image.getYMin() - y_marg) # Set the central values of pad_image to be equal to the input image pad_image[gal_image.bounds] = gal_image self.original_image = galsim.SBInterpolatedImage( pad_image, xInterp=self.x_interpolant, kInterp=self.k_interpolant, dx=self.pixel_scale, pad_factor=1., gsparams=gsparams) # also make the original PSF image, with far less fanfare: we don't need to pad with # anything interesting. self.original_PSF = galsim.SBInterpolatedImage( PSF_image, xInterp=self.x_interpolant, kInterp=self.k_interpolant, dx=self.pixel_scale, gsparams=gsparams) # recalculate Fourier-space attributes rather than using overly-conservative defaults self.original_image.calculateStepK() self.original_image.calculateMaxK() self.original_PSF.calculateStepK() self.original_PSF.calculateMaxK() if flux != None: self.original_image.setFlux(flux) self.original_image.__class__ = galsim.SBTransform # correctly reflect SBProfile change self.original_PSF.setFlux(1.0) self.original_PSF.__class__ = galsim.SBTransform # correctly reflect SBProfile change # Calculate the PSF "deconvolution" kernel psf_inv = galsim.SBDeconvolve(self.original_PSF, gsparams=gsparams) # Initialize the SBProfile attribute GSObject.__init__( self, galsim.SBConvolve([self.original_image, psf_inv], gsparams=gsparams))
def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization='flux', scale=None, wcs=None, flux=None, pad_factor=4., noise_pad_size=0, noise_pad=0., rng=None, pad_image=None, calculate_stepk=True, calculate_maxk=True, use_cache=True, use_true_center=True, offset=None, gsparams=None, dx=None, _force_stepk=0., _force_maxk=0., _serialize_stepk=None, _serialize_maxk=None, hdu=None): # Check for obsolete dx parameter if dx is not None and scale is None: from galsim.deprecated import depr depr('dx', 1.1, 'scale') scale = dx # If the "image" is not actually an image, try to read the image as a file. if not isinstance(image, galsim.Image): image = galsim.fits.read(image, hdu=hdu) # make sure image is really an image and has a float type if image.dtype != np.float32 and image.dtype != np.float64: raise ValueError( "Supplied image does not have dtype of float32 or float64!") # it must have well-defined bounds, otherwise seg fault in SBInterpolatedImage constructor if not image.bounds.isDefined(): raise ValueError("Supplied image does not have bounds defined!") # check what normalization was specified for the image: is it an image of surface # brightness, or flux? if not normalization.lower() in ("flux", "f", "surface brightness", "sb"): raise ValueError(( "Invalid normalization requested: '%s'. Expecting one of 'flux', " + "'f', 'surface brightness', or 'sb'.") % normalization) # set up the interpolants if none was provided by user, or check that the user-provided ones # are of a valid type if x_interpolant is None: self.x_interpolant = galsim.Quintic(tol=1e-4) else: self.x_interpolant = galsim.utilities.convert_interpolant( x_interpolant) if k_interpolant is None: self.k_interpolant = galsim.Quintic(tol=1e-4) else: self.k_interpolant = galsim.utilities.convert_interpolant( k_interpolant) # Store the image as an attribute and make sure we don't change the original image # in anything we do here. (e.g. set scale, etc.) self.image = image.view() self.use_cache = use_cache # Set the wcs if necessary if scale is not None: if wcs is not None: raise TypeError( "Cannot provide both scale and wcs to InterpolatedImage") self.image.wcs = galsim.PixelScale(scale) elif wcs is not None: if not isinstance(wcs, galsim.BaseWCS): raise TypeError( "wcs parameter is not a galsim.BaseWCS instance") self.image.wcs = wcs elif self.image.wcs is None: raise ValueError( "No information given with Image or keywords about pixel scale!" ) # Set up the GaussianDeviate if not provided one, or check that the user-provided one is # of a valid type. if rng is None: if noise_pad: rng = galsim.BaseDeviate() elif not isinstance(rng, galsim.BaseDeviate): raise TypeError( "rng provided to InterpolatedImage constructor is not a BaseDeviate" ) # Check that given pad_image is valid: if pad_image: if isinstance(pad_image, str): pad_image = galsim.fits.read(pad_image) if not isinstance(pad_image, galsim.Image): raise ValueError("Supplied pad_image is not an Image!") if pad_image.dtype != np.float32 and pad_image.dtype != np.float64: raise ValueError( "Supplied pad_image is not one of the allowed types!") # Check that the given noise_pad is valid: try: noise_pad = float(noise_pad) except: pass if isinstance(noise_pad, float): if noise_pad < 0.: raise ValueError("Noise variance cannot be negative!") # There are other options for noise_pad, the validity of which will be checked in # the helper function self.buildNoisePadImage() # This will be passed to SBInterpolatedImage, so make sure it is the right type. pad_factor = float(pad_factor) if pad_factor <= 0.: raise ValueError("Invalid pad_factor <= 0 in InterpolatedImage") if use_true_center: im_cen = self.image.bounds.trueCenter() else: im_cen = self.image.bounds.center() local_wcs = self.image.wcs.local(image_pos=im_cen) self.min_scale = local_wcs.minLinearScale() self.max_scale = local_wcs.maxLinearScale() # Make sure the image fits in the noise pad image: if noise_pad_size: import math # Convert from arcsec to pixels according to the local wcs. # Use the minimum scale, since we want to make sure noise_pad_size is # as large as we need in any direction. noise_pad_size = int(math.ceil(noise_pad_size / self.min_scale)) # Round up to a good size for doing FFTs noise_pad_size = galsim._galsim.goodFFTSize(noise_pad_size) if noise_pad_size <= min(self.image.array.shape): # Don't need any noise padding in this case. noise_pad_size = 0 elif noise_pad_size < max(self.image.array.shape): noise_pad_size = max(self.image.array.shape) # See if we need to pad out the image with either a pad_image or noise_pad if noise_pad_size: new_pad_image = self.buildNoisePadImage(noise_pad_size, noise_pad, rng) if pad_image: # if both noise_pad and pad_image are set, then we need to build up a larger # pad_image and place the given pad_image in the center. # We will change the bounds here, so make a new view to avoid modifying the # input pad_image. pad_image = pad_image.view() pad_image.setCenter(0, 0) new_pad_image.setCenter(0, 0) if new_pad_image.bounds.includes(pad_image.bounds): new_pad_image[pad_image.bounds] = pad_image else: new_pad_image = pad_image pad_image = new_pad_image elif pad_image: # Just make sure pad_image is the right type pad_image = galsim.Image(pad_image, dtype=image.dtype) # Now place the given image in the center of the padding image: if pad_image: pad_image.setCenter(0, 0) self.image.setCenter(0, 0) if pad_image.bounds.includes(self.image.bounds): pad_image[self.image.bounds] = self.image pad_image.wcs = self.image.wcs else: # If padding was smaller than original image, just use the original image. pad_image = self.image else: pad_image = self.image # GalSim cannot automatically know what stepK and maxK are appropriate for the # input image. So it is usually worth it to do a manual calculation (below). # # However, there is also a hidden option to force it to use specific values of stepK and # maxK (caveat user!). The values of _force_stepk and _force_maxk should be provided in # terms of physical scale, e.g., for images that have a scale length of 0.1 arcsec, the # stepK and maxK should be provided in units of 1/arcsec. Then we convert to the 1/pixel # units required by the C++ layer below. Also note that profile recentering for even-sized # images (see the ._fix_center step below) leads to automatic reduction of stepK slightly # below what is provided here, while maxK is preserved. if _force_stepk > 0.: calculate_stepk = False _force_stepk *= self.min_scale if _force_maxk > 0.: calculate_maxk = False _force_maxk *= self.max_scale # Due to floating point rounding errors, for pickling it's necessary to store the exact # _force_maxk and _force_stepk used to create the SBInterpolatedImage, as opposed to the # values before being scaled by self.min_scale and self.max_scale. So we do that via the # _serialize_maxk and _serialize_stepk hidden kwargs, which should only get used during # pickling. if _serialize_stepk is not None: calculate_stepk = False _force_stepk = _serialize_stepk if _serialize_maxk is not None: calculate_maxk = False _force_maxk = _serialize_maxk # Save these values for pickling self._pad_image = pad_image self._pad_factor = pad_factor self._gsparams = gsparams # Make the SBInterpolatedImage out of the image. sbii = galsim._galsim.SBInterpolatedImage(pad_image.image, self.x_interpolant, self.k_interpolant, pad_factor, _force_stepk, _force_maxk, gsparams) # I think the only things that will mess up if getFlux() == 0 are the # calculateStepK and calculateMaxK functions, and rescaling the flux to some value. if (calculate_stepk or calculate_maxk or flux is not None) and sbii.getFlux() == 0.: raise RuntimeError( "This input image has zero total flux. " "It does not define a valid surface brightness profile.") if calculate_stepk: if calculate_stepk is True: sbii.calculateStepK() else: # If not a bool, then value is max_stepk sbii.calculateStepK(max_stepk=calculate_stepk) if calculate_maxk: if calculate_maxk is True: sbii.calculateMaxK() else: # If not a bool, then value is max_maxk sbii.calculateMaxK(max_maxk=calculate_maxk) # If the user specified a surface brightness normalization for the input Image, then # need to rescale flux by the pixel area to get proper normalization. if flux is None and normalization.lower() in [ 'surface brightness', 'sb' ]: flux = sbii.getFlux() * local_wcs.pixelArea() # Save this intermediate profile self._sbii = sbii self._stepk = sbii.stepK() / self.min_scale self._maxk = sbii.maxK() / self.max_scale self._flux = flux self._serialize_stepk = sbii.stepK() self._serialize_maxk = sbii.maxK() prof = GSObject(sbii) # Make sure offset is a PositionD offset = prof._parse_offset(offset) # Apply the offset, and possibly fix the centering for even-sized images # Note reverse=True, since we want to fix the center in the opposite sense of what the # draw function does. prof = prof._fix_center(self.image.array.shape, offset, use_true_center, reverse=True) # Save the offset we will need when pickling. if hasattr(prof, 'offset'): self._offset = -prof.offset else: self._offset = None # Bring the profile from image coordinates into world coordinates prof = local_wcs.toWorld(prof) # If the user specified a flux, then set to that flux value. if flux is not None: prof = prof.withFlux(float(flux)) # Now, in order for these to pickle correctly if they are the "original" object in a # Transform object, we need to hide the current transformation. An easy way to do that # is to hide the SBProfile in an SBAdd object. sbp = galsim._galsim.SBAdd([prof.SBProfile]) GSObject.__init__(self, sbp)
def test_log(): """Some simple tests of interpolation using logs.""" # Set up some test vectors that are strictly positive, and others that are negative. x = 0.01 * np.arange(1000) + 0.01 y = 1. * x x_neg = -1. * x y_neg = 1. * x_neg # Check that interpolation agrees for the positive ones when using log interpolation (for some # reasonable tolerance). tab_1 = galsim.LookupTable(x=x, f=y) tab_2 = galsim.LookupTable(x=x, f=y, x_log=True, f_log=True) tab_3 = galsim.LookupTable(x=x, f=y, x_log=True) tab_4 = galsim.LookupTable(x=x, f=y, f_log=True) test_x_vals = [2.641, 3.985, 8.123125] for test_val in test_x_vals: result_1 = tab_1(test_val) result_2 = tab_2(test_val) result_3 = tab_3(test_val) result_4 = tab_4(test_val) print(result_1, result_2, result_3, result_4) np.testing.assert_almost_equal( result_2, result_1, decimal=3, err_msg='Disagreement when interpolating in log(f) and log(x)') np.testing.assert_almost_equal( result_3, result_1, decimal=3, err_msg='Disagreement when interpolating in log(x)') np.testing.assert_almost_equal( result_4, result_1, decimal=3, err_msg='Disagreement when interpolating in log(f)') with assert_raises(galsim.GalSimRangeError): tab_2(-1) with assert_raises(galsim.GalSimRangeError): tab_3(-1) with assert_raises(galsim.GalSimRangeError): tab_2(x_neg) with assert_raises(galsim.GalSimRangeError): tab_3(x_neg) # Check picklability do_pickle(tab_1) do_pickle(tab_2) do_pickle(tab_3) do_pickle(tab_4) # Check storage of args and vals for log vs. linear, which should be the same to high precision. np.testing.assert_array_almost_equal( tab_1.getArgs(), tab_3.getArgs(), decimal=12, err_msg='Args differ for linear vs. log storage') np.testing.assert_array_almost_equal( tab_1.getVals(), tab_4.getVals(), decimal=12, err_msg='Vals differ for linear vs. log storage') # Check other properties assert not tab_1.x_log assert not tab_1.f_log assert tab_2.x_log assert tab_2.f_log assert tab_3.x_log assert not tab_3.f_log assert not tab_1.isLogX() assert not tab_1.isLogF() assert tab_2.isLogX() assert tab_2.isLogF() assert tab_3.isLogX() assert not tab_3.isLogF() # Check that an appropriate exception is thrown when trying to do interpolation using negative # ones. assert_raises(ValueError, galsim.LookupTable, x=x_neg, f=y_neg, x_log=True) assert_raises(ValueError, galsim.LookupTable, x=x_neg, f=y_neg, f_log=True) assert_raises(ValueError, galsim.LookupTable, x=x_neg, f=y_neg, x_log=True, f_log=True) # Check that doing log transform explicitly matches expected behavior of x_log and f_log. expx = np.exp(x) expy = np.exp(y) for interpolant in ['linear', 'spline', galsim.Quintic()]: tab_1 = galsim.LookupTable(x, y, interpolant=interpolant) tab_2 = galsim.LookupTable(expx, y, x_log=True, interpolant=interpolant) tab_3 = galsim.LookupTable(x, expy, f_log=True, interpolant=interpolant) tab_4 = galsim.LookupTable(expx, expy, x_log=True, f_log=True, interpolant=interpolant) for test_val in test_x_vals: result_1 = tab_1(test_val) result_2 = tab_2(np.exp(test_val)) result_3 = np.log(tab_3(test_val)) result_4 = np.log(tab_4(np.exp(test_val))) np.testing.assert_almost_equal( result_2, result_1, decimal=10, err_msg='Disagreement when interpolating in log(x)') np.testing.assert_almost_equal( result_3, result_1, decimal=10, err_msg='Disagreement when interpolating in log(f)') np.testing.assert_almost_equal( result_4, result_1, decimal=10, err_msg='Disagreement when interpolating in log(f) and log(x)') # Verify for exception when using x_log with non-equal-spaced galsim.Interpolant galsim.LookupTable(x, y, x_log=True, interpolant='linear') # works fine assert_raises(ValueError, galsim.LookupTable, x, y, x_log=True, interpolant=galsim.Linear())
dx = 0.4 final.draw(image=image, dx=dx) dir = '../../../tests/interpolant_comparison_files' # First make a Cubic interpolant interp = galsim.InterpolantXY(galsim.Cubic(tol=1.e-4)) testobj = galsim.SBInterpolatedImage(image.view(), interp, dx=dx) for i in xrange(len(kxvals)): posk = galsim.PositionD(kxvals[i], kyvals[i]) absoutk[i] = np.abs(testobj.kValue(posk)) print absoutk np.savetxt(os.path.join(dir,'absfKCubic_test.txt'), absoutk) # Then make a Quintic interpolant interp = galsim.InterpolantXY(galsim.Quintic(tol=1.e-4)) testobj = galsim.SBInterpolatedImage(image.view(), interp, dx=dx) for i in xrange(len(kxvals)): posk = galsim.PositionD(kxvals[i], kyvals[i]) absoutk[i] = np.abs(testobj.kValue(posk)) print absoutk np.savetxt(os.path.join(dir,'absfKQuintic_test.txt'), absoutk) # Then make a Lanczos5 interpolant interp = galsim.InterpolantXY(galsim.Lanczos(5, conserve_flux=False, tol=1.e-4)) testobj = galsim.SBInterpolatedImage(image.view(), interp, dx=dx) for i in xrange(len(kxvals)): posk = galsim.PositionD(kxvals[i], kyvals[i]) absoutk[i] = np.abs(testobj.kValue(posk)) print absoutk np.savetxt(os.path.join(dir,'absfKLanczos5_test.txt'), absoutk)
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)
""" import cPickle import numpy as np import galsim import test_interpolants SERSIC_IMAGE_SIZE = 512 # For initial image of the Sersic at Hubble resolution, make nice and large TEST_IMAGE_SIZE = SERSIC_IMAGE_SIZE # For speed could make this smaller # Dictionary for parsing the test_interpolants.interpolant_list into galsim Interpolants INTERPOLANT_DICT = { "nearest": galsim.Nearest(), "sinc": galsim.SincInterpolant(), "linear": galsim.Linear(), "cubic": galsim.Cubic(), "quintic": galsim.Quintic(), "lanczos3": galsim.Lanczos(3), "lanczos4": galsim.Lanczos(4), "lanczos5": galsim.Lanczos(5), "lanczos7": galsim.Lanczos(7) } # Output filenames DELTA_FILENAME = 'interpolant_test_parametric_output_delta.dat' ORIGINAL_FILENAME = 'interpolant_test_parametric_output_original.dat' NITEMS = 30 # For now, look at a few only LAM_OVER_DIAM_COSMOS = 814.e-9 / 2.4 # All the original images in Melanie's tests were from COSMOS # F814W, so this is a crude approximation to the PSF scale in # radians, ~0.07 arcsec
def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, real_kimage=None, imag_kimage=None, real_hdu=None, imag_hdu=None): if isinstance(kimage, galsim.Image) and isinstance( k_interpolant, galsim.Image): from .deprecated import depr depr( 'InterpolatedKImage(re,im,...)', 1.5, 'either InterpolatedKImage(re + 1j * im, ...) or ' 'InterpolatedKImage(real_kimage=re, imag_kimage=im)') # This won't work if they call InterpolatedKImage(re,im, k_interpolant=kinterp) # But I don't see an easy way around that, so I guess that use case is not # backwards compatible. Sorry.. real_kimage = kimage imag_kimage = k_interpolant kimage = None k_interpolant = None if kimage is None: if real_kimage is None or imag_kimage is None: raise ValueError( "Must provide either kimage or real_kimage/imag_kimage") # If the "image" is not actually an image, try to read the image as a file. if not isinstance(real_kimage, galsim.Image): real_kimage = galsim.fits.read(real_kimage, hdu=real_hdu) if not isinstance(imag_kimage, galsim.Image): imag_kimage = galsim.fits.read(imag_kimage, hdu=imag_hdu) # make sure real_kimage, imag_kimage are really `Image`s, are floats, and are # congruent. if not isinstance(real_kimage, galsim.Image): raise ValueError( "Supplied real_kimage is not an Image instance") if not isinstance(imag_kimage, galsim.Image): raise ValueError( "Supplied imag_kimage is not an Image instance") if real_kimage.bounds != imag_kimage.bounds: raise ValueError( "Real and Imag kimages must have same bounds.") if real_kimage.wcs != imag_kimage.wcs: raise ValueError( "Real and Imag kimages must have same scale/wcs.") kimage = real_kimage + 1j * imag_kimage else: if real_kimage is not None or imag_kimage is not None: raise ValueError( "Cannot provide both kimage and real_kimage/imag_kimage") if not kimage.iscomplex: raise ValueError("Supplied kimage is not an ImageC") # Make sure wcs is a PixelScale. if kimage.wcs is not None and not kimage.wcs.isPixelScale(): raise ValueError("kimage wcs must be PixelScale or None.") # Check for Hermitian symmetry properties of kimage shape = kimage.array.shape # If image is even-sized, ignore first row/column since in this case not every pixel has # a symmetric partner to which to compare. bd = galsim.BoundsI(kimage.xmin + (1 if shape[1] % 2 == 0 else 0), kimage.xmax, kimage.ymin + (1 if shape[0] % 2 == 0 else 0), kimage.ymax) if not (np.allclose(kimage[bd].real.array, kimage[bd].real.array[::-1, ::-1]) and np.allclose(kimage[bd].imag.array, -kimage[bd].imag.array[::-1, ::-1])): raise ValueError( "Real and Imag kimages must form a Hermitian complex matrix.") if stepk is None: stepk = kimage.scale else: if stepk < kimage.scale: import warnings warnings.warn( "Provided stepk is smaller than kimage.scale; overriding with kimage.scale." ) stepk = kimage.scale self._kimage = kimage self._stepk = stepk self._gsparams = gsparams # set up k_interpolant if none was provided by user, or check that the user-provided one # is of a valid type if k_interpolant is None: self.k_interpolant = galsim.Quintic(tol=1e-4) else: self.k_interpolant = galsim.utilities.convert_interpolant( k_interpolant) GSObject.__init__( self, galsim._galsim.SBInterpolatedKImage(self._kimage.image, self._kimage.scale, self._stepk, self.k_interpolant, gsparams))
def __init__(self, image, x_interpolant = None, k_interpolant = None, normalization = 'flux', dx = None, flux = None, pad_factor = 0., noise_pad = 0., rng = None, pad_image = None, calculate_stepk=True, calculate_maxk=True, use_cache=True, use_true_center=True, gsparams=None): import numpy as np # first try to read the image as a file. If it's not either a string or a valid # pyfits hdu or hdulist, then an exception will be raised, which we ignore and move on. try: image = galsim.fits.read(image) except: pass # make sure image is really an image and has a float type if not isinstance(image, galsim.BaseImageF) and not isinstance(image, galsim.BaseImageD): raise ValueError("Supplied image is not an image of floats or doubles!") # it must have well-defined bounds, otherwise seg fault in SBInterpolatedImage constructor if not image.getBounds().isDefined(): raise ValueError("Supplied image does not have bounds defined!") # check what normalization was specified for the image: is it an image of surface # brightness, or flux? if not normalization.lower() in ("flux", "f", "surface brightness", "sb"): raise ValueError(("Invalid normalization requested: '%s'. Expecting one of 'flux', "+ "'f', 'surface brightness', or 'sb'.") % normalization) # set up the interpolants if none was provided by user, or check that the user-provided ones # are of a valid type if x_interpolant is None: self.x_interpolant = galsim.InterpolantXY(galsim.Quintic(tol=1e-4)) else: self.x_interpolant = galsim.utilities.convert_interpolant_to_2d(x_interpolant) if k_interpolant is None: self.k_interpolant = galsim.InterpolantXY(galsim.Quintic(tol=1e-4)) else: self.k_interpolant = galsim.utilities.convert_interpolant_to_2d(k_interpolant) # Check for input dx, and check whether Image already has one set. At the end of this # code block, either an exception will have been raised, or the input image will have a # valid scale set. if dx is None: dx = image.scale if dx == 0: raise ValueError("No information given with Image or keywords about pixel scale!") else: if type(dx) != float: dx = float(dx) # Don't change the original image. Make a new view if we need to set the scale. image = image.view() image.setScale(dx) if dx == 0.0: raise ValueError("dx may not be 0.0") # Set up the GaussianDeviate if not provided one, or check that the user-provided one is # of a valid type. if rng is None: gaussian_deviate = galsim.GaussianDeviate() elif isinstance(rng, galsim.BaseDeviate): # Even if it's already a GaussianDeviate, we still want to make a new Gaussian deviate # that would generate the same sequence, because later we change the sigma and we don't # want to change it for the original one that was passed in. So don't distinguish # between GaussianDeviate and the other BaseDeviates here. gaussian_deviate = galsim.GaussianDeviate(rng) else: raise TypeError("rng provided to InterpolatedImage constructor is not a BaseDeviate") # decide about deterministic image padding specify_size = False padded_size = image.getPaddedSize(pad_factor) if pad_image: specify_size = True if isinstance(pad_image, str): pad_image = galsim.fits.read(pad_image) if ( not isinstance(pad_image, galsim.BaseImageF) and not isinstance(pad_image, galsim.BaseImageD) ): raise ValueError("Supplied pad_image is not one of the allowed types!") # If an image was supplied directly or from a file, check its size: # Cannot use if too small. # Use to define the final image size otherwise. deltax = (1+pad_image.getXMax()-pad_image.getXMin())-(1+image.getXMax()-image.getXMin()) deltay = (1+pad_image.getYMax()-pad_image.getYMin())-(1+image.getYMax()-image.getYMin()) if deltax < 0 or deltay < 0: raise RuntimeError("Image supplied for padding is too small!") if pad_factor != 1. and pad_factor != 0.: import warnings msg = "Warning: ignoring specified pad_factor because user also specified\n" msg += " an image to use directly for the padding." warnings.warn(msg) elif noise_pad: if isinstance(image, galsim.BaseImageF): pad_image = galsim.ImageF(padded_size, padded_size) if isinstance(image, galsim.BaseImageD): pad_image = galsim.ImageD(padded_size, padded_size) # now decide about noise padding # First, see if the input is consistent with a float. # i.e. it could be an int, or a str that converts to a number. try: noise_pad = float(noise_pad) except: pass if isinstance(noise_pad, float): if noise_pad < 0.: raise ValueError("Noise variance cannot be negative!") elif noise_pad > 0.: # Note: make sure the sigma is properly set to sqrt(noise_pad). gaussian_deviate.setSigma(np.sqrt(noise_pad)) pad_image.addNoise(galsim.DeviateNoise(gaussian_deviate)) else: if isinstance(noise_pad, galsim.correlatednoise._BaseCorrelatedNoise): cn = noise_pad.copy() if rng: # Let a user supplied RNG take precedence over that in user CN cn.setRNG(gaussian_deviate) elif isinstance(noise_pad,galsim.BaseImageF) or isinstance(noise_pad,galsim.BaseImageD): cn = galsim.CorrelatedNoise(gaussian_deviate, noise_pad) elif use_cache and noise_pad in InterpolatedImage._cache_noise_pad: cn = InterpolatedImage._cache_noise_pad[noise_pad] if rng: # Make sure that we are using a specified RNG by resetting that in this cached # CorrelatedNoise instance, otherwise preserve the cached RNG cn.setRNG(gaussian_deviate) elif isinstance(noise_pad, str): cn = galsim.CorrelatedNoise(gaussian_deviate, galsim.fits.read(noise_pad)) if use_cache: InterpolatedImage._cache_noise_pad[noise_pad] = cn else: raise ValueError( "Input noise_pad must be a float/int, a CorrelatedNoise, Image, or filename "+ "containing an image to use to make a CorrelatedNoise!") pad_image.addNoise(cn) # Now we have to check: was the padding determined using pad_factor? Or by passing in an # image for padding? Treat these cases differently: # (1) If the former, then we can simply have the C++ handle the padding process. # (2) If the latter, then we have to do the padding ourselves, and pass the resulting image # to the C++ with pad_factor explicitly set to 1. if specify_size is False: # Make the SBInterpolatedImage out of the image. sbinterpolatedimage = galsim.SBInterpolatedImage( image, xInterp=self.x_interpolant, kInterp=self.k_interpolant, dx=dx, pad_factor=pad_factor, pad_image=pad_image, gsparams=gsparams) self.x_size = padded_size self.y_size = padded_size else: # Leave the original image as-is. Instead, we shift around the image to be used for # padding. Find out how much x and y margin there should be on lower end: x_marg = int(np.round(0.5*deltax)) y_marg = int(np.round(0.5*deltay)) # Now reset the pad_image to contain the original image in an even way pad_image = pad_image.view() pad_image.setScale(dx) pad_image.setOrigin(image.getXMin()-x_marg, image.getYMin()-y_marg) # Set the central values of pad_image to be equal to the input image pad_image[image.bounds] = image sbinterpolatedimage = galsim.SBInterpolatedImage( pad_image, xInterp=self.x_interpolant, kInterp=self.k_interpolant, dx=dx, pad_factor=1., gsparams=gsparams) self.x_size = 1+pad_image.getXMax()-pad_image.getXMin() self.y_size = 1+pad_image.getYMax()-pad_image.getYMin() # GalSim cannot automatically know what stepK and maxK are appropriate for the # input image. So it is usually worth it to do a manual calculation here. if calculate_stepk: sbinterpolatedimage.calculateStepK() if calculate_maxk: sbinterpolatedimage.calculateMaxK() # If the user specified a flux, then set to that flux value. if flux != None: if type(flux) != flux: flux = float(flux) sbinterpolatedimage.setFlux(flux) # If the user specified a flux normalization for the input Image, then since # SBInterpolatedImage works in terms of surface brightness, have to rescale the values to # get proper normalization. elif flux is None and normalization.lower() in ['flux','f'] and dx != 1.: sbinterpolatedimage.scaleFlux(1./(dx**2)) # If the input Image normalization is 'sb' then since that is the SBInterpolated default # assumption, no rescaling is needed. # Initialize the SBProfile GSObject.__init__(self, sbinterpolatedimage) # Fix the center to be in the right place. # Note the minus sign in front of image.scale, since we want to fix the center in the # opposite sense of what the draw function does. if use_true_center: prof = self._fix_center(image, -image.scale) GSObject.__init__(self, prof.SBProfile)
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()
def __init__(self, lam_over_diam, defocus=0., astig1=0., astig2=0., coma1=0., coma2=0., trefoil1=0., trefoil2=0., spher=0., circular_pupil=True, obscuration=0., interpolant=None, oversampling=1.5, pad_factor=1.5, flux=1., gsparams=None): # Currently we load optics, noise etc in galsim/__init__.py, but this might change (???) import galsim.optics # Choose dx for lookup table using Nyquist for optical aperture and the specified # oversampling factor dx_lookup = .5 * lam_over_diam / oversampling # We need alias_threshold here, so don't wait to make this a default GSParams instance # if the user didn't specify anything else. if not gsparams: gsparams = galsim.GSParams() # Use a similar prescription as SBAiry to set Airy stepK and thus reference unpadded image # size in physical units stepk_airy = min( gsparams.alias_threshold * .5 * np.pi**3 * (1. - obscuration) / lam_over_diam, np.pi / 5. / lam_over_diam) # Boost Airy image size by a user-specifed pad_factor to allow for larger, aberrated PSFs, # also make npix always *odd* so that opticalPSF lookup table array is correctly centred: npix = 1 + 2 * (np.ceil(pad_factor * (np.pi / stepk_airy) / dx_lookup)).astype(int) # Make the psf image using this dx and array shape optimage = galsim.optics.psf_image(lam_over_diam=lam_over_diam, dx=dx_lookup, array_shape=(npix, npix), defocus=defocus, astig1=astig1, astig2=astig2, coma1=coma1, coma2=coma2, trefoil1=trefoil1, trefoil2=trefoil2, spher=spher, circular_pupil=circular_pupil, obscuration=obscuration, flux=flux) # If interpolant not specified on input, use a Quintic interpolant if interpolant is None: quintic = galsim.Quintic(tol=1e-4) self.interpolant = galsim.InterpolantXY(quintic) else: self.interpolant = galsim.utilities.convert_interpolant_to_2d( interpolant) # Initialize the SBProfile GSObject.__init__( self, galsim.SBInterpolatedImage(optimage, xInterp=self.interpolant, dx=dx_lookup, gsparams=gsparams)) # The above procedure ends up with a larger image than we really need, which # means that the default stepK value will be smaller than we need. # Thus, we call the function calculateStepK() to refine the value. self.SBProfile.calculateStepK() self.SBProfile.calculateMaxK()
def __init__(self, image, x_interpolant = None, k_interpolant = None, normalization = 'flux', dx = None, flux = None, pad_factor = 0., noise_pad = 0., rng = None, pad_image = None, calculate_stepk=True, calculate_maxk=True, use_cache=True, use_true_center=True, offset=None, gsparams=None): # first try to read the image as a file. If it's not either a string or a valid # pyfits hdu or hdulist, then an exception will be raised, which we ignore and move on. try: image = galsim.fits.read(image) except: pass # make sure image is really an image and has a float type if not isinstance(image, galsim.BaseImageF) and not isinstance(image, galsim.BaseImageD): raise ValueError("Supplied image is not an image of floats or doubles!") # it must have well-defined bounds, otherwise seg fault in SBInterpolatedImage constructor if not image.bounds.isDefined(): raise ValueError("Supplied image does not have bounds defined!") # check what normalization was specified for the image: is it an image of surface # brightness, or flux? if not normalization.lower() in ("flux", "f", "surface brightness", "sb"): raise ValueError(("Invalid normalization requested: '%s'. Expecting one of 'flux', "+ "'f', 'surface brightness', or 'sb'.") % normalization) # set up the interpolants if none was provided by user, or check that the user-provided ones # are of a valid type if x_interpolant is None: self.x_interpolant = galsim.InterpolantXY(galsim.Quintic(tol=1e-4)) else: self.x_interpolant = galsim.utilities.convert_interpolant_to_2d(x_interpolant) if k_interpolant is None: self.k_interpolant = galsim.InterpolantXY(galsim.Quintic(tol=1e-4)) else: self.k_interpolant = galsim.utilities.convert_interpolant_to_2d(k_interpolant) # Check for input dx, and check whether Image already has one set. At the end of this # code block, either an exception will have been raised, or the input image will have a # valid scale set. if dx is None: dx = image.scale if dx == 0: raise ValueError("No information given with Image or keywords about pixel scale!") else: if type(dx) != float: dx = float(dx) if dx <= 0.0: raise ValueError("dx may not be <= 0.0") # Don't change the original image. Make a new view if we need to set the scale. image = image.view() image.scale = dx # Set up the GaussianDeviate if not provided one, or check that the user-provided one is # of a valid type. if rng is None: if noise_pad: rng = galsim.BaseDeviate() elif not isinstance(rng, galsim.BaseDeviate): raise TypeError("rng provided to InterpolatedImage constructor is not a BaseDeviate") # Check that given pad_image is valid: if pad_image: if isinstance(pad_image, str): pad_image = galsim.fits.read(pad_image) if ( not isinstance(pad_image, galsim.BaseImageF) and not isinstance(pad_image, galsim.BaseImageD) ): raise ValueError("Supplied pad_image is not one of the allowed types!") # Check that the given noise_pad is valid: try: noise_pad = float(noise_pad) except: pass if isinstance(noise_pad, float): if noise_pad < 0.: raise ValueError("Noise variance cannot be negative!") # There are other options for noise_pad, the validity of which will be checked in # the helper function self.buildNoisePadImage() # This will be passed to SBInterpolatedImage, so make sure it is the right type. pad_factor = float(pad_factor) # Store the image as an attribute self.orig_image = image self.use_cache = use_cache # See if we need to build a pad_image if noise_pad and pad_image: # if both noise_pad and pad_image are set, then we need to build up a larger # pad_image and place the given pad_image in the center. new_pad_image = self.buildNoisePadImage(pad_factor, noise_pad, rng) # We will change the bounds here, so make a new view to avoid modifying the # input pad_image. pad_image = pad_image.view() pad_image.setCenter(0,0) new_pad_image.setCenter(0,0) if not new_pad_image.bounds.includes(pad_image.bounds): raise ValueError("pad_factor is too small to fit the provided pad_image.") new_pad_image[pad_image.bounds] = pad_image pad_image = new_pad_image elif noise_pad: # Just build the noise image pad_image = self.buildNoisePadImage(pad_factor, noise_pad, rng) elif pad_image: # Just make sure pad_image is the right type if ( isinstance(image, galsim.BaseImageF) and not isinstance(pad_image, galsim.BaseImageF) ): pad_image = galsim.ImageF(pad_image) elif ( isinstance(image, galsim.BaseImageD) and not isinstance(pad_image, galsim.BaseImageD) ): pad_image = galsim.ImageD(pad_image) # Make the SBInterpolatedImage out of the image. sbinterpolatedimage = galsim.SBInterpolatedImage( image, xInterp=self.x_interpolant, kInterp=self.k_interpolant, dx=dx, pad_factor=pad_factor, pad_image=pad_image, gsparams=gsparams) # GalSim cannot automatically know what stepK and maxK are appropriate for the # input image. So it is usually worth it to do a manual calculation here. if calculate_stepk: sbinterpolatedimage.calculateStepK() if calculate_maxk: sbinterpolatedimage.calculateMaxK() # If the user specified a flux, then set to that flux value. if flux != None: if type(flux) != flux: flux = float(flux) sbinterpolatedimage.setFlux(flux) # If the user specified a flux normalization for the input Image, then since # SBInterpolatedImage works in terms of surface brightness, have to rescale the values to # get proper normalization. elif flux is None and normalization.lower() in ['flux','f'] and dx != 1.: sbinterpolatedimage.scaleFlux(1./(dx**2)) # If the input Image normalization is 'sb' then since that is the SBInterpolated default # assumption, no rescaling is needed. # Initialize the SBProfile GSObject.__init__(self, sbinterpolatedimage) # Apply the offset, and possibly fix the centering for even-sized images # Note reverse=True, since we want to fix the center in the opposite sense of what the # draw function does. prof = self._fix_center(image, dx, offset, use_true_center, reverse=True) GSObject.__init__(self, prof.SBProfile)
def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization='flux', scale=None, wcs=None, flux=None, pad_factor=4., noise_pad_size=0, noise_pad=0., rng=None, pad_image=None, calculate_stepk=True, calculate_maxk=True, use_cache=True, use_true_center=True, offset=None, gsparams=None, dx=None): # Check for obsolete dx parameter if dx is not None and scale is None: scale = dx import numpy # first try to read the image as a file. If it's not either a string or a valid # pyfits hdu or hdulist, then an exception will be raised, which we ignore and move on. try: image = galsim.fits.read(image) except: pass # make sure image is really an image and has a float type if not isinstance(image, galsim.Image): raise ValueError("Supplied image is not an Image instance") if image.dtype != numpy.float32 and image.dtype != numpy.float64: raise ValueError( "Supplied image is not an image of floats or doubles!") # it must have well-defined bounds, otherwise seg fault in SBInterpolatedImage constructor if not image.bounds.isDefined(): raise ValueError("Supplied image does not have bounds defined!") # check what normalization was specified for the image: is it an image of surface # brightness, or flux? if not normalization.lower() in ("flux", "f", "surface brightness", "sb"): raise ValueError(( "Invalid normalization requested: '%s'. Expecting one of 'flux', " + "'f', 'surface brightness', or 'sb'.") % normalization) # set up the interpolants if none was provided by user, or check that the user-provided ones # are of a valid type if x_interpolant is None: self.x_interpolant = galsim.InterpolantXY(galsim.Quintic(tol=1e-4)) else: self.x_interpolant = galsim.utilities.convert_interpolant_to_2d( x_interpolant) if k_interpolant is None: self.k_interpolant = galsim.InterpolantXY(galsim.Quintic(tol=1e-4)) else: self.k_interpolant = galsim.utilities.convert_interpolant_to_2d( k_interpolant) # Store the image as an attribute and make sure we don't change the original image # in anything we do here. (e.g. set scale, etc.) self.image = image.view() self.use_cache = use_cache # Set the wcs if necessary if scale is not None: if wcs is not None: raise TypeError( "Cannot provide both scale and wcs to InterpolatedImage") self.image.wcs = galsim.PixelScale(scale) elif wcs is not None: if not isinstance(wcs, galsim.BaseWCS): raise TypeError( "wcs parameter is not a galsim.BaseWCS instance") self.image.wcs = wcs elif self.image.wcs is None: raise ValueError( "No information given with Image or keywords about pixel scale!" ) # Set up the GaussianDeviate if not provided one, or check that the user-provided one is # of a valid type. if rng is None: if noise_pad: rng = galsim.BaseDeviate() elif not isinstance(rng, galsim.BaseDeviate): raise TypeError( "rng provided to InterpolatedImage constructor is not a BaseDeviate" ) # Check that given pad_image is valid: if pad_image: import numpy if isinstance(pad_image, str): pad_image = galsim.fits.read(pad_image) if not isinstance(pad_image, galsim.Image): raise ValueError("Supplied pad_image is not an Image!") if pad_image.dtype != numpy.float32 and pad_image.dtype != numpy.float64: raise ValueError( "Supplied pad_image is not one of the allowed types!") # Check that the given noise_pad is valid: try: noise_pad = float(noise_pad) except: pass if isinstance(noise_pad, float): if noise_pad < 0.: raise ValueError("Noise variance cannot be negative!") # There are other options for noise_pad, the validity of which will be checked in # the helper function self.buildNoisePadImage() # This will be passed to SBInterpolatedImage, so make sure it is the right type. pad_factor = float(pad_factor) if pad_factor <= 0.: raise ValueError("Invalid pad_factor <= 0 in InterpolatedImage") if use_true_center: im_cen = self.image.bounds.trueCenter() else: im_cen = self.image.bounds.center() local_wcs = self.image.wcs.local(image_pos=im_cen) # Make sure the image fits in the noise pad image: if noise_pad_size: import math # Convert from arcsec to pixels according to the local wcs. # Use the minimum scale, since we want to make sure noise_pad_size is # as large as we need in any direction. scale = local_wcs.minLinearScale() noise_pad_size = int(math.ceil(noise_pad_size / scale)) # Round up to a good size for doing FFTs noise_pad_size = galsim._galsim.goodFFTSize(noise_pad_size) if noise_pad_size <= min(self.image.array.shape): # Don't need any noise padding in this case. noise_pad_size = 0 elif noise_pad_size < max(self.image.array.shape): noise_pad_size = max(self.image.array.shape) # See if we need to pad out the image with either a pad_image or noise_pad if noise_pad_size: new_pad_image = self.buildNoisePadImage(noise_pad_size, noise_pad, rng) if pad_image: # if both noise_pad and pad_image are set, then we need to build up a larger # pad_image and place the given pad_image in the center. # We will change the bounds here, so make a new view to avoid modifying the # input pad_image. pad_image = pad_image.view() pad_image.setCenter(0, 0) new_pad_image.setCenter(0, 0) if new_pad_image.bounds.includes(pad_image.bounds): new_pad_image[pad_image.bounds] = pad_image else: new_pad_image = pad_image pad_image = new_pad_image elif pad_image: # Just make sure pad_image is the right type pad_image = galsim.Image(pad_image, dtype=image.dtype) # Now place the given image in the center of the padding image: if pad_image: pad_image.setCenter(0, 0) self.image.setCenter(0, 0) if pad_image.bounds.includes(self.image.bounds): pad_image[self.image.bounds] = self.image else: # If padding was smaller than original image, just use the original image. pad_image = self.image else: pad_image = self.image # Make the SBInterpolatedImage out of the image. sbinterpolatedimage = galsim._galsim.SBInterpolatedImage( pad_image.image, xInterp=self.x_interpolant, kInterp=self.k_interpolant, pad_factor=pad_factor, gsparams=gsparams) # GalSim cannot automatically know what stepK and maxK are appropriate for the # input image. So it is usually worth it to do a manual calculation here. if calculate_stepk: if calculate_stepk is True: sbinterpolatedimage.calculateStepK() else: # If not a bool, then value is max_stepk sbinterpolatedimage.calculateStepK(max_stepk=calculate_stepk) if calculate_maxk: if calculate_maxk is True: sbinterpolatedimage.calculateMaxK() else: # If not a bool, then value is max_maxk sbinterpolatedimage.calculateMaxK(max_maxk=calculate_maxk) # Initialize the SBProfile GSObject.__init__(self, sbinterpolatedimage) # Make sure offset is a PositionD offset = self._parse_offset(offset) # Apply the offset, and possibly fix the centering for even-sized images # Note reverse=True, since we want to fix the center in the opposite sense of what the # draw function does. prof = self._fix_center(self.image, offset, use_true_center, reverse=True) # Bring the profile from image coordinates into world coordinates prof = local_wcs.toWorld(prof) # If the user specified a flux, then set to that flux value. if flux is not None: prof = prof.withFlux(float(flux)) # If the user specified a surface brightness normalization for the input Image, then # need to rescale flux by the pixel area to get proper normalization. elif normalization.lower() in ['surface brightness', 'sb']: prof *= local_wcs.pixelArea() GSObject.__init__(self, prof)