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)