def test_fail(): # Some vv noisy images that result in errors in the fit to check the error reporting. scale = 1.3 g1 = 0.33 g2 = -0.27 flux = 15 noise = 2. seed = 1234 psf = galsim.Moffat(half_light_radius=1.0, beta=2.5, trunc=3.0) psf = psf.dilate(scale).shear(g1=g1, g2=g2).withFlux(flux) image = psf.drawImage(nx=64, ny=64, scale=0.3) weight = image.copy() weight.fill(1 / noise**2) noisy_image = image.copy() rng = galsim.BaseDeviate(seed) noisy_image.addNoise(galsim.GaussianNoise(sigma=noise, rng=rng)) star1 = piff.Star(piff.StarData(image, image.true_center, weight), None) star2 = piff.Star(piff.StarData(noisy_image, image.true_center, weight), None) model1 = piff.Moffat(fastfit=True, beta=2.5) with np.testing.assert_raises(RuntimeError): model1.initialize(star2) with np.testing.assert_raises(RuntimeError): model1.fit(star2) star3 = model1.initialize(star1) star3 = model1.fit(star3) star3 = piff.Star(star2.data, star3.fit) with np.testing.assert_raises(RuntimeError): model1.fit(star3) # This is contrived to hit the fit failure for the reference. # I'm not sure what realistic use case would actually hit it, but at least it's # theoretically possible to fail there. model2 = piff.GSObjectModel(galsim.InterpolatedImage(noisy_image), fastfit=True) with np.testing.assert_raises(RuntimeError): model2.initialize(star1) model3 = piff.Moffat(fastfit=False, beta=2.5, scipy_kwargs={'max_nfev': 10}) with np.testing.assert_raises(RuntimeError): model3.initialize(star2) with np.testing.assert_raises(RuntimeError): model3.fit(star2).fit star3 = model3.initialize(star1) star3 = model3.fit(star3) star3 = piff.Star(star2.data, star3.fit) with np.testing.assert_raises(RuntimeError): model3.fit(star3)
def test_psf(): """Test PSF base class """ # Need a dummy star for the below calls image = galsim.Image(1, 1, scale=1) stardata = piff.StarData(image, image.true_center) star = piff.Star(stardata, None) # Can't do much with a base PSF class psf = piff.PSF() np.testing.assert_raises(NotImplementedError, psf.parseKwargs, None) np.testing.assert_raises(NotImplementedError, psf.interpolateStar, star) np.testing.assert_raises(NotImplementedError, psf.interpolateStarList, [star]) np.testing.assert_raises(NotImplementedError, psf.drawStar, star) np.testing.assert_raises(NotImplementedError, psf.drawStarList, [star]) np.testing.assert_raises(NotImplementedError, psf._drawStar, star) # Invalid to read a type that isn't a piff.PSF type. # Mock this by pretending that SingleChip is the only subclass of PSF. if sys.version_info < (3, ): return # mock only available on python 3 from unittest import mock filename = os.path.join('input', 'D00240560_r_c01_r2362p01_piff.fits') with mock.patch('piff.util.get_all_subclasses', return_value=[piff.SingleChipPSF]): np.testing.assert_raises(ValueError, piff.PSF.read, filename)
def test_Gaussian(): """This is about the simplest possible model I could think of. It just uses the HSM adaptive moments routine to measure the moments, and then it models the PSF as a Gaussian. """ # Here is the true PSF sigma = 1.3 g1 = 0.23 g2 = -0.17 psf = galsim.Gaussian(sigma=sigma).shear(g1=g1, g2=g2) # Draw the PSF onto an image. Let's go ahead and give it a non-trivial WCS. wcs = galsim.JacobianWCS(0.26, 0.05, -0.08, -0.29) image = galsim.Image(64,64, wcs=wcs) # This is only going to come out right if we (unphysically) don't convolve by the pixel. psf.drawImage(image, method='no_pixel') # Make a StarData instance for this image stardata = piff.StarData(image, image.true_center) star = piff.Star(stardata, None) # Fit the model from the image model = piff.Gaussian(include_pixel=False) star = model.initialize(star) fit = model.fit(star).fit print('True sigma = ',sigma,', model sigma = ',fit.params[0]) print('True g1 = ',g1,', model g1 = ',fit.params[1]) print('True g2 = ',g2,', model g2 = ',fit.params[2]) # This test is pretty accurate, since we didn't add any noise and didn't convolve by # the pixel, so the image is very accurately a sheared Gaussian. true_params = [ sigma, g1, g2 ] np.testing.assert_almost_equal(fit.params[0], sigma, decimal=7) np.testing.assert_almost_equal(fit.params[1], g1, decimal=7) np.testing.assert_almost_equal(fit.params[2], g2, decimal=7) np.testing.assert_almost_equal(fit.params, true_params, decimal=7) # Now test running it via the config parser config = { 'model' : { 'type' : 'Gaussian', 'include_pixel': False } } if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger(log_file='output/test_Gaussian.log') model = piff.Model.process(config['model'], logger) fit = model.fit(star).fit # Same tests. np.testing.assert_almost_equal(fit.params[0], sigma, decimal=7) np.testing.assert_almost_equal(fit.params[1], g1, decimal=7) np.testing.assert_almost_equal(fit.params[2], g2, decimal=7) np.testing.assert_almost_equal(fit.params, true_params, decimal=7)
def test_decam_wavefront(): file_name = 'input/Science-20121120s1-v20i2.fits' extname = 'Science-20121120s1-v20i2' if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger(log_file='output/test_decam.log') knn = piff.des.DECamWavefront(file_name, extname, logger=logger) n_samples = 2000 np_rng = np.random.RandomState(1234) ccdnums = np_rng.randint(1, 63, n_samples) star_list = [] for ccdnum in ccdnums: # make some basic images, pass Xi as properties # Draw the PSF onto an image. Let's go ahead and give it a non-trivial WCS. wcs = galsim.JacobianWCS(0.26, 0.05, -0.08, -0.29) image = galsim.Image(64,64, wcs=wcs) # set icen and jcen icen = np_rng.randint(100, 2048) jcen = np_rng.randint(100, 4096) image.setCenter(icen, jcen) image_pos = image.center stardata = piff.StarData(image, image_pos, properties={'chipnum': ccdnum}) star = piff.Star(stardata, None) star_list.append(star) # get the focal positions star_list = piff.des.DECamInfo().pixel_to_focalList(star_list) star_list_predicted = knn.interpolateList(star_list) # test misalignment misalignment = {'z04d': 10, 'z10x': 10, 'z09y': -10} knn.misalign_wavefront(misalignment) star_list_misaligned = knn.interpolateList(star_list) # test the prediction algorithm y_predicted = np.array([s.fit.params for s in star_list_predicted]) y_misaligned = np.array([s.fit.params for s in star_list_misaligned]) X = np.array([knn.getProperties(s) for s in star_list]) # check the misalignments work np.testing.assert_array_almost_equal(y_predicted[:,0], y_misaligned[:,0] - misalignment['z04d']) np.testing.assert_array_almost_equal(y_predicted[:,5], y_misaligned[:,5] - misalignment['z09y'] * X[:,0]) np.testing.assert_array_almost_equal(y_predicted[:,6], y_misaligned[:,6] - misalignment['z10x'] * X[:,1]) # Check shape of misalignment if array np.testing.assert_raises(ValueError, knn.misalign_wavefront, knn.misalignment[:,:2]) np.testing.assert_raises(ValueError, knn.misalign_wavefront, knn.misalignment[:-1,:]) # empty dict is equivalent to no misalignment knn.misalign_wavefront({}) np.testing.assert_equal(knn.misalignment, 0.)
def test_extra_interp(): # Test that specifying extra_interp_properties works properly # TODO: This is a very bare bones test of the interface. There is basically no test of # this functionality at all yet. TBD! sigma = 1.3 g1 = 0.23 g2 = -0.17 psf = galsim.Gaussian(sigma=sigma).shear(g1=g1, g2=g2) wcs = galsim.JacobianWCS(0.26, 0.05, -0.08, -0.29) image = galsim.Image(64, 64, wcs=wcs) psf.drawImage(image, method='no_pixel') # use g-i color as an extra property for interpolation. props = dict(gi_color=0.3) print('props = ', props) star = piff.Star(piff.StarData(image, image.true_center, properties=props), None) model = piff.Gaussian(fastfit=True, include_pixel=False) interp = piff.Mean() psf = piff.SimplePSF(model, interp, extra_interp_properties=['gi_color']) assert psf.extra_interp_properties == ['gi_color'] # Note: Mean doesn't actually do anything useful with the extra properties, so this # isn't really testing anything other than that the code doesn't completely break. pointing = galsim.CelestialCoord(-5 * galsim.arcmin, -25 * galsim.degrees) psf.fit([star], wcs={0: wcs}, pointing=pointing) # Not much of a check here. Just check that it actually draws something with flux ~= 1 im = psf.draw(x=5, y=7, gi_color=0.3) np.testing.assert_allclose(im.array.sum(), 1.0, rtol=1.e-3) # Check missing or extra properties with np.testing.assert_raises(TypeError): psf.draw(x=5, y=7) with np.testing.assert_raises(TypeError): psf.draw(x=5, y=7, gi_color=0.3, ri_color=3) # No stars raises an error. (This can happen in practice if all stars are excluded on input.) with np.testing.assert_raises(RuntimeError): psf.fit([], wcs={0: wcs}, pointing=pointing) # Also for SingleChipPSf psf2 = piff.SingleChipPSF(psf, extra_interp_properties=['gi_color']) assert psf2.extra_interp_properties == ['gi_color'] with np.testing.assert_raises(TypeError): psf2.draw(x=5, y=7, chipnum=0)
def drawProfile(self, star, prof, params, use_fit=True, copy_image=True, interpfunc=None): """Generate PSF image for a given star and profile :param star: Star instance holding information needed for interpolation as well as an image/WCS into which PSF will be rendered. :param profile: A galsim profile :param params: Params associated with profile to put in the star. :param use_fit: Bool [default: True] shift the profile by a star's fitted center and multiply by its fitted flux :param self: PSF instance :param interpfunc: The interpolation function :returns: Star instance with its image filled with rendered PSF """ # use flux and center properties if use_fit: prof = prof.shift(star.fit.center) * star.fit.flux image, weight, image_pos = star.data.getImage() if copy_image: image_model = image.copy() else: image_model = image prof.drawImage(image_model, method='auto', offset=(star.image_pos-image_model.true_center)) properties = star.data.properties.copy() for key in ['x', 'y', 'u', 'v']: # Get rid of keys that constructor doesn't want to see: properties.pop(key, None) data = piff.StarData(image=image_model, image_pos=star.data.image_pos, weight=star.data.weight, pointing=star.data.pointing, field_pos=star.data.field_pos, values_are_sb=star.data.values_are_sb, orig_weight=star.data.orig_weight, properties=properties) fit = piff.StarFit(params, flux=star.fit.flux, center=star.fit.center) new_s = piff.Star(data, fit) # apply radial correction if interpfunc is not None: new_im = apply_correction(new_s, interpfunc) new_s = piff.Star(new_s.data.setData(new_im.flatten(), include_zero_weight=True), new_s.fit) return new_s
def generate_data(n_samples=100): # generate as Norm(0, 1) for all parameters np_rng = np.random.RandomState(1234) X = np_rng.normal(0, 1, size=(n_samples, len(keys))) y = np_rng.normal(0, 1, size=(n_samples, ntarget)) star_list = [] for Xi, yi in zip(X, y): wcs = galsim.JacobianWCS(0.26, 0.05, -0.08, -0.29) image = galsim.Image(64,64, wcs=wcs) properties = {k:v for k,v in zip(keys, Xi)} stardata = piff.StarData(image, image.true_center, properties=properties) # params = np.array([yi[ith] for ith in attr_target]) params = yi starfit = piff.StarFit(params) star = piff.Star(stardata, starfit) star_list.append(star) return star_list
def test_add_poisson(): """Test the addPoisson() method """ size = 23 icen = 598 jcen = 109 image = galsim.Image(size, size, scale=0.25) image.setCenter(icen, jcen) image_pos = image.center # Make a signal image galsim.Gaussian(sigma=1.1, flux=100).drawImage(image) stardata1 = piff.StarData(image, image_pos, weight=4) # Make another stardata with a blank image image2 = galsim.ImageF(image.bounds, init_value=0) weight = galsim.ImageF(image.bounds, init_value=0.025) stardata2 = piff.StarData(image, image_pos, weight=weight) # Add poisson noise with gain=1 test = stardata2.addPoisson(stardata1, gain=1) np.testing.assert_allclose(1. / test.weight.array, 1. / weight.array + image.array) # Note repeated application always adds to the original weight map, not the current one. test = test.addPoisson(stardata1, gain=3) np.testing.assert_allclose(1. / test.weight.array, 1. / weight.array + image.array / 3) test = test.addPoisson(stardata1, gain=2) np.testing.assert_allclose(1. / test.weight.array, 1. / weight.array + image.array / 2) # gain=None means don't change anything test = stardata2.addPoisson(stardata1) np.testing.assert_allclose(test.weight.array, stardata2.weight.array) # signal=None means use self for signal test = stardata1.addPoisson(gain=2) np.testing.assert_allclose(1. / test.weight.array, 1. / stardata1.weight.array + image.array / 2) # If gain is in properties, use that stardata3 = piff.StarData(image, image_pos, weight=weight, properties=dict(gain=4)) test = stardata3.addPoisson(stardata1) np.testing.assert_allclose(1. / test.weight.array, 1. / weight.array + image.array / 4) # But explicit gain takes precedence test = stardata3.addPoisson(stardata1, gain=0.3) np.testing.assert_allclose(1. / test.weight.array, 1. / weight.array + image.array / 0.3) # After one application with gain, the star remembers that value. test = stardata2.addPoisson(stardata2, gain=2) test = test.addPoisson(stardata1) np.testing.assert_allclose(1. / test.weight.array, 1. / weight.array + image.array / 2) # Error if signal is different shape small_image = galsim.Image(15, 15, scale=0.25) stardata4 = piff.StarData(small_image, image_pos) with np.testing.assert_raises(ValueError): stardata2.addPoisson(stardata4, gain=1) # Equivalent to access function from Star class star = piff.Star(stardata2, None) test = star.addPoisson(stardata1, gain=3) np.testing.assert_allclose(1. / test.weight.array, 1. / weight.array + image.array / 3)
def makeStars(self, logger=None): """Process the input images and star data, cutting out stamps for each star along with other relevant information. The base class implementation expects the derived class to have appropriately set the following attributes: :stamp_size: The size of the postage stamp to use for the cutouts :x_col: The name of the column in the catalogs to use for the x position. :y_col: The name of the column in the catalogs to use for the y position. :param logger: A logger object for logging debug info. [default: None] :returns: a list of Star instances """ import galsim import piff stars = [] if logger: if len(self.cats) == 1: logger.debug("Making star list from catalog %s", self.cat_files[0]) else: logger.debug("Making star list from %d catalogs", len(self.cats)) for i in range(len(self.images)): image = self.images[i] wt = self.weight[i] cat = self.cats[i] chipnum = self.chipnums[i] fname = self.cat_files[i] if logger: logger.info("Processing catalog %s with %d stars",fname,len(cat)) nstars_in_image = 0 for k in range(len(cat)): x = cat[self.x_col][k] y = cat[self.y_col][k] icen = int(x+0.5) jcen = int(y+0.5) half_size = self.stamp_size // 2 bounds = galsim.BoundsI(icen+half_size-self.stamp_size+1, icen+half_size, jcen+half_size-self.stamp_size+1, jcen+half_size) if not image.bounds.includes(bounds): bounds = bounds & image.bounds if not bounds.isDefined(): if logger: logger.warning("Star at position %f,%f is off the edge of the image."%(x,y)) logger.warning("Skipping this star.") continue if logger: logger.info("Star at position %f,%f is near the edge of the image."%(x,y)) logger.info("Using smaller than the full stamp size: %s"%bounds) stamp = image[bounds] props = { 'chipnum' : chipnum } sky = None if self.sky_col is not None: sky = cat[self.sky_col][k] elif self.sky is not None: if type(self.sky) in [float, int]: sky = float(self.sky) elif str(self.sky) != self.sky: raise ValueError("Unable to parse input sky: %s"%self.sky) else: file_name = self.image_files[0] fits = fitsio.FITS(file_name) hdu = 1 if file_name.endswith('.fz') else 0 header = fits[hdu].read_header() sky = float(header[self.sky]) if sky is not None: if logger: logger.debug("Subtracting off sky = %f", sky) stamp = stamp - sky # Don't change the original! props['sky'] = sky wt_stamp = wt[bounds] # if a star is totally masked, then don't add it! if np.all(wt_stamp.array == 0): if logger: logger.warning("Star at position %f,%f is completely masked."%(x,y)) logger.warning("Skipping this star.") continue pos = galsim.PositionD(x,y) data = piff.StarData(stamp, pos, weight=wt_stamp, pointing=self.pointing, properties=props) stars.append(piff.Star(data, None)) nstars_in_image += 1 if self.nstars is not None and nstars_in_image >= self.nstars: if logger: logger.info("Reached limit of %d stars in image %d",self.nstars,i) break if logger: logger.warning("Read a total of %d stars from %d image%s",len(stars),len(self.images), "s" if len(self.images) > 1 else "") return stars
def makeStars(self, logger=None): """Process the input images and star data, cutting out stamps for each star along with other relevant information. The base class implementation expects the derived class to have appropriately set the following attributes: :stamp_size: The size of the postage stamp to use for the cutouts :x_col: The name of the column in the catalogs to use for the x position. :y_col: The name of the column in the catalogs to use for the y position. :param logger: A logger object for logging debug info. [default: None] :returns: a list of Star instances """ import galsim import piff logger = galsim.config.LoggerWrapper(logger) stars = [] if len(self.chipnums) == 1: logger.debug("Making star list") else: logger.debug("Making star list from %d catalogs", len(self.chipnums)) for image_num in range(len(self.chipnums)): image = self.images[image_num] wt = self.weight[image_num] image_pos = self.image_pos[image_num] sky = self.sky[image_num] gain = self.gain[image_num] chipnum = self.chipnums[image_num] logger.info("Processing catalog %s with %d stars", chipnum, len(image_pos)) nstars_in_image = 0 for k in range(len(image_pos)): x = image_pos[k].x y = image_pos[k].y icen = int(x + 0.5) jcen = int(y + 0.5) half_size = self.stamp_size // 2 bounds = galsim.BoundsI(icen + half_size - self.stamp_size + 1, icen + half_size, jcen + half_size - self.stamp_size + 1, jcen + half_size) if not image.bounds.includes(bounds): bounds = bounds & image.bounds if not bounds.isDefined(): logger.warning( "Star at position %f,%f is off the edge of the image. " "Skipping this star.", x, y) continue if self.use_partial: logger.info( "Star at position %f,%f overlaps the edge of the image. " "Using smaller than the full stamp size: %s", x, y, bounds) else: logger.warning( "Star at position %f,%f overlaps the edge of the image. " "Skipping this star.", x, y) continue stamp = image[bounds] props = {'chipnum': chipnum, 'gain': gain[k]} if sky is not None: logger.debug("Subtracting off sky = %f", sky[k]) logger.debug("Median pixel value = %f", np.median(stamp.array)) stamp = stamp - sky[k] # Don't change the original! props['sky'] = sky[k] wt_stamp = wt[bounds] # if a star is totally masked, then don't add it! if np.all(wt_stamp.array == 0): logger.warning( "Star at position %f,%f is completely masked." % (x, y)) logger.warning("Skipping this star.") continue # Check the snr and limit it if appropriate snr = self.calculateSNR(stamp, wt_stamp) logger.debug("SNR = %f", snr) if self.min_snr is not None and snr < self.min_snr: logger.info( "Skipping star at position %f,%f with snr=%f." % (x, y, snr)) continue if self.max_snr > 0 and snr > self.max_snr: factor = (self.max_snr / snr)**2 logger.debug( "Scaling noise by factor of %f to achieve snr=%f", factor, self.max_snr) wt_stamp = wt_stamp * factor snr = self.max_snr props['snr'] = snr pos = galsim.PositionD(x, y) data = piff.StarData(stamp, pos, weight=wt_stamp, pointing=self.pointing, properties=props) star = piff.Star(data, None) g = gain[k] if g is not None: logger.debug( "Adding Poisson noise to weight map according to gain=%f", g) star = star.addPoisson(gain=g) stars.append(star) nstars_in_image += 1 logger.warning("Read a total of %d stars from %d image%s", len(stars), len(self.images), "s" if len(self.images) > 1 else "") return stars
def test_simple(): """Initial simple test of Gaussian, Kolmogorov, and Moffat PSFs. """ # Here is the true PSF scale = 1.3 g1 = 0.23 g2 = -0.17 du = 0.1 dv = 0.4 for fiducial in [fiducial_gaussian, fiducial_kolmogorov, fiducial_moffat]: print() print("fiducial = ", fiducial) print() psf = fiducial.dilate(scale).shear(g1=g1, g2=g2).shift(du, dv) # Draw the PSF onto an image. Let's go ahead and give it a non-trivial WCS. wcs = galsim.JacobianWCS(0.26, 0.05, -0.08, -0.29) image = galsim.Image(64, 64, wcs=wcs) # This is only going to come out right if we (unphysically) don't convolve by the pixel. psf.drawImage(image, method='no_pixel') # Make a StarData instance for this image stardata = piff.StarData(image, image.true_center) fiducial_star = piff.Star(stardata, None) # First try fastfit. print('Fast fit') model = piff.GSObjectModel(fiducial, fastfit=True, include_pixel=False) fit = model.fit(model.initialize(fiducial_star)).fit print('True scale = ', scale, ', model scale = ', fit.params[0]) print('True g1 = ', g1, ', model g1 = ', fit.params[1]) print('True g2 = ', g2, ', model g2 = ', fit.params[2]) print('True du = ', du, ', model du = ', fit.center[0]) print('True dv = ', dv, ', model dv = ', fit.center[1]) # This test is fairly accurate, since we didn't add any noise and didn't convolve by # the pixel, so the image is very accurately a sheared GSObject. np.testing.assert_allclose(fit.params[0], scale, rtol=1e-4) np.testing.assert_allclose(fit.params[1], g1, rtol=0, atol=1e-7) np.testing.assert_allclose(fit.params[2], g2, rtol=0, atol=1e-7) np.testing.assert_allclose(fit.center[0], du, rtol=0, atol=1e-7) np.testing.assert_allclose(fit.center[1], dv, rtol=0, atol=1e-7) # Now try fastfit=False. print('Slow fit') model = piff.GSObjectModel(fiducial, fastfit=False, include_pixel=False) fit = model.fit(model.initialize(fiducial_star)).fit print('True scale = ', scale, ', model scale = ', fit.params[0]) print('True g1 = ', g1, ', model g1 = ', fit.params[1]) print('True g2 = ', g2, ', model g2 = ', fit.params[2]) print('True du = ', du, ', model du = ', fit.center[0]) print('True dv = ', dv, ', model dv = ', fit.center[1]) np.testing.assert_allclose(fit.params[0], scale, rtol=1e-6) np.testing.assert_allclose(fit.params[1], g1, rtol=0, atol=1e-6) np.testing.assert_allclose(fit.params[2], g2, rtol=0, atol=1e-6) np.testing.assert_allclose(fit.center[0], du, rtol=0, atol=1e-6) np.testing.assert_allclose(fit.center[1], dv, rtol=0, atol=1e-6) # Now test running it via the config parser config = { 'model': { 'type': 'GSObjectModel', 'gsobj': repr(fiducial), 'include_pixel': False } } if __name__ == '__main__': logger = piff.config.setup_logger(verbose=3) else: logger = piff.config.setup_logger(verbose=1) model = piff.Model.process(config['model'], logger) fit = model.fit(model.initialize(fiducial_star)).fit # Same tests. np.testing.assert_allclose(fit.params[0], scale, rtol=1e-6) np.testing.assert_allclose(fit.params[1], g1, rtol=0, atol=1e-6) np.testing.assert_allclose(fit.params[2], g2, rtol=0, atol=1e-6) np.testing.assert_allclose(fit.center[0], du, rtol=0, atol=1e-6) np.testing.assert_allclose(fit.center[1], dv, rtol=0, atol=1e-6) # Also need to test ability to serialize outfile = os.path.join('output', 'gsobject_test.fits') with fitsio.FITS(outfile, 'rw', clobber=True) as f: model.write(f, 'psf_model') with fitsio.FITS(outfile, 'r') as f: roundtrip_model = piff.GSObjectModel.read(f, 'psf_model') assert model.__dict__ == roundtrip_model.__dict__ # Finally, we should also test with pixel convolution included. This really only makes # sense for fastfit=False, since HSM FindAdaptiveMom doesn't account for the pixel shape # in its measurements. # Draw the PSF onto an image. Let's go ahead and give it a non-trivial WCS. wcs = galsim.JacobianWCS(0.26, 0.05, -0.08, -0.29) image = galsim.Image(64, 64, wcs=wcs) psf.drawImage(image, method='auto') # Make a StarData instance for this image stardata = piff.StarData(image, image.true_center) fiducial_star = piff.Star(stardata, None) print('Slow fit, pixel convolution included.') model = piff.GSObjectModel(fiducial, fastfit=False, include_pixel=True) star = model.initialize(fiducial_star) star = model.fit( star, fastfit=True) # Get better results with one round of fastfit. # Use a no op convert_func, just to touch that branch in the code. convert_func = lambda prof: prof fit = model.fit(star, convert_func=convert_func).fit print('True scale = ', scale, ', model scale = ', fit.params[0]) print('True g1 = ', g1, ', model g1 = ', fit.params[1]) print('True g2 = ', g2, ', model g2 = ', fit.params[2]) print('True du = ', du, ', model du = ', fit.center[0]) print('True dv = ', dv, ', model dv = ', fit.center[1]) # Accuracy goals are a bit looser here since it's harder to fit with the pixel involved. np.testing.assert_allclose(fit.params[0], scale, rtol=1e-6) np.testing.assert_allclose(fit.params[1], g1, rtol=0, atol=1e-6) np.testing.assert_allclose(fit.params[2], g2, rtol=0, atol=1e-6) np.testing.assert_allclose(fit.center[0], du, rtol=0, atol=1e-5) np.testing.assert_allclose(fit.center[1], dv, rtol=0, atol=1e-5)
def test_var(): """Check that the variance estimate in params_var is sane. """ # Here is the true PSF scale = 1.3 g1 = 0.23 g2 = -0.17 du = 0.1 dv = 0.4 flux = 500 wcs = galsim.JacobianWCS(0.26, 0.05, -0.08, -0.29) noise = 0.2 gsobjs = [ galsim.Gaussian(sigma=1.0), galsim.Kolmogorov(half_light_radius=1.0), galsim.Moffat(half_light_radius=1.0, beta=3.0), galsim.Moffat(half_light_radius=1.0, beta=2.5, trunc=3.0) ] # Mix of centered = True/False, # fastfit = True/False, # include_pixel = True/False models = [ piff.Gaussian(fastfit=False, include_pixel=False, centered=False), piff.Kolmogorov(fastfit=True, include_pixel=True, centered=False), piff.Moffat(fastfit=False, beta=4.8, include_pixel=True, centered=True), piff.Moffat(fastfit=True, beta=2.5, trunc=3.0, include_pixel=False, centered=True) ] names = ['Gaussian', 'Kolmogorov', 'Moffat3', 'Moffat2.5'] for gsobj, model, name in zip(gsobjs, models, names): print() print("gsobj = ", gsobj) print() psf = gsobj.dilate(scale).shear(g1=g1, g2=g2).shift(du, dv).withFlux(flux) image = psf.drawImage(nx=64, ny=64, wcs=wcs, method='no_pixel') weight = image.copy() weight.fill(1 / noise**2) # Save this one without noise. image1 = image.copy() image1.addNoise(galsim.GaussianNoise(sigma=noise)) # Make a StarData instance for this image stardata = piff.StarData(image, image.true_center, weight) star = piff.Star(stardata, None) star = model.initialize(star) fit = model.fit(star).fit file_name = 'input/test_%s_var.npz' % name print(file_name) if not os.path.isfile(file_name): num_runs = 1000 all_params = [] for i in range(num_runs): image1 = image.copy() image1.addNoise(galsim.GaussianNoise(sigma=noise)) sd = piff.StarData(image1, image1.true_center, weight) s = piff.Star(sd, None) try: s = model.initialize(s) s = model.fit(s) except RuntimeError as e: # Occasionally hsm fails. print('Caught ', e) continue print(s.fit.params) all_params.append(s.fit.params) var = np.var(all_params, axis=0) np.savez(file_name, var=var) var = np.load(file_name)['var'] print('params = ', fit.params) print('empirical var = ', var) print('piff estimate = ', fit.params_var) print('ratio = ', fit.params_var / var) print('max ratio = ', np.max(fit.params_var / var)) print('min ratio = ', np.min(fit.params_var / var)) print('mean ratio = ', np.mean(fit.params_var / var)) # Note: The fastfit=False estimates are better -- typically better than 10% # The fastfit=True estimates are much rougher. Especially size. Need rtol=0.3. np.testing.assert_allclose(fit.params_var, var, rtol=0.3)
def test_direct(): """ Simple test for directly instantiated Gaussian, Kolmogorov, and Moffat without going through GSObjectModel explicitly. """ # Here is the true PSF scale = 1.3 g1 = 0.23 g2 = -0.17 du = 0.1 dv = 0.4 gsobjs = [ galsim.Gaussian(sigma=1.0), galsim.Kolmogorov(half_light_radius=1.0), galsim.Moffat(half_light_radius=1.0, beta=3.0), galsim.Moffat(half_light_radius=1.0, beta=2.5, trunc=3.0) ] models = [ piff.Gaussian(fastfit=True, include_pixel=False), piff.Kolmogorov(fastfit=True, include_pixel=False), piff.Moffat(fastfit=True, beta=3.0, include_pixel=False), piff.Moffat(fastfit=True, beta=2.5, trunc=3.0, include_pixel=False) ] for gsobj, model in zip(gsobjs, models): print() print("gsobj = ", gsobj) print() psf = gsobj.dilate(scale).shear(g1=g1, g2=g2).shift(du, dv) # Draw the PSF onto an image. Let's go ahead and give it a non-trivial WCS. wcs = galsim.JacobianWCS(0.26, 0.05, -0.08, -0.29) image = galsim.Image(64, 64, wcs=wcs) # This is only going to come out right if we (unphysically) don't convolve by the pixel. psf.drawImage(image, method='no_pixel') # Make a StarData instance for this image stardata = piff.StarData(image, image.true_center) star = piff.Star(stardata, None) star = model.initialize(star) # First try fastfit. print('Fast fit') fit = model.fit(star).fit print('True scale = ', scale, ', model scale = ', fit.params[0]) print('True g1 = ', g1, ', model g1 = ', fit.params[1]) print('True g2 = ', g2, ', model g2 = ', fit.params[2]) print('True du = ', du, ', model du = ', fit.center[0]) print('True dv = ', dv, ', model dv = ', fit.center[1]) # This test is fairly accurate, since we didn't add any noise and didn't convolve by # the pixel, so the image is very accurately a sheared GSObject. # These tests are more strict above. The truncated Moffat included here but not there # doesn't work quite as well. np.testing.assert_allclose(fit.params[0], scale, rtol=1e-4) np.testing.assert_allclose(fit.params[1], g1, rtol=0, atol=1e-5) np.testing.assert_allclose(fit.params[2], g2, rtol=0, atol=1e-5) np.testing.assert_allclose(fit.center[0], du, rtol=0, atol=1e-5) np.testing.assert_allclose(fit.center[1], dv, rtol=0, atol=1e-5) # Also need to test ability to serialize outfile = os.path.join('output', 'gsobject_direct_test.fits') with fitsio.FITS(outfile, 'rw', clobber=True) as f: model.write(f, 'psf_model') with fitsio.FITS(outfile, 'r') as f: roundtrip_model = piff.GSObjectModel.read(f, 'psf_model') assert model.__dict__ == roundtrip_model.__dict__ # repeat with fastfit=False models = [ piff.Gaussian(fastfit=False, include_pixel=False), piff.Kolmogorov(fastfit=False, include_pixel=False), piff.Moffat(fastfit=False, beta=3.0, include_pixel=False), piff.Moffat(fastfit=False, beta=2.5, trunc=3.0, include_pixel=False) ] for gsobj, model in zip(gsobjs, models): print() print("gsobj = ", gsobj) print() psf = gsobj.dilate(scale).shear(g1=g1, g2=g2).shift(du, dv) # Draw the PSF onto an image. Let's go ahead and give it a non-trivial WCS. wcs = galsim.JacobianWCS(0.26, 0.05, -0.08, -0.29) image = galsim.Image(64, 64, wcs=wcs) # This is only going to come out right if we (unphysically) don't convolve by the pixel. psf.drawImage(image, method='no_pixel') # Make a StarData instance for this image stardata = piff.StarData(image, image.true_center) star = piff.Star(stardata, None) star = model.initialize(star) print('Slow fit') fit = model.fit(star).fit print('True scale = ', scale, ', model scale = ', fit.params[0]) print('True g1 = ', g1, ', model g1 = ', fit.params[1]) print('True g2 = ', g2, ', model g2 = ', fit.params[2]) print('True du = ', du, ', model du = ', fit.center[0]) print('True dv = ', dv, ', model dv = ', fit.center[1]) # This test is fairly accurate, since we didn't add any noise and didn't convolve by # the pixel, so the image is very accurately a sheared GSObject. np.testing.assert_allclose(fit.params[0], scale, rtol=1e-5) np.testing.assert_allclose(fit.params[1], g1, rtol=0, atol=1e-5) np.testing.assert_allclose(fit.params[2], g2, rtol=0, atol=1e-5) np.testing.assert_allclose(fit.center[0], du, rtol=0, atol=1e-5) np.testing.assert_allclose(fit.center[1], dv, rtol=0, atol=1e-5) # Also need to test ability to serialize outfile = os.path.join('output', 'gsobject_direct_test.fits') with fitsio.FITS(outfile, 'rw', clobber=True) as f: model.write(f, 'psf_model') with fitsio.FITS(outfile, 'r') as f: roundtrip_model = piff.GSObjectModel.read(f, 'psf_model') assert model.__dict__ == roundtrip_model.__dict__
def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None): """Determine a Piff PSF model for an exposure given a list of PSF candidates. Parameters ---------- exposure : `lsst.afw.image.Exposure` Exposure containing the PSF candidates. psfCandidateList : `list` of `lsst.meas.algorithms.PsfCandidate` A sequence of PSF candidates typically obtained by detecting sources and then running them through a star selector. metadata : `lsst.daf.base import PropertyList` or `None`, optional A home for interesting tidbits of information. flagKey : `str` or `None`, optional Schema key used to mark sources actually used in PSF determination. Returns ------- psf : `lsst.meas.extensions.piff.PiffPsf` The measured PSF model. psfCellSet : `None` Unused by this PsfDeterminer. """ stars = [] for candidate in psfCandidateList: cmi = candidate.getMaskedImage() weight = computeWeight(cmi, self.config.maxSNR) bbox = cmi.getBBox() bds = galsim.BoundsI(galsim.PositionI(*bbox.getMin()), galsim.PositionI(*bbox.getMax())) gsImage = galsim.Image(bds, scale=1.0, dtype=float) gsImage.array[:] = cmi.image.array gsWeight = galsim.Image(bds, scale=1.0, dtype=float) gsWeight.array[:] = weight source = candidate.getSource() image_pos = galsim.PositionD(source.getX(), source.getY()) data = piff.StarData(gsImage, image_pos, weight=gsWeight) stars.append(piff.Star(data, None)) kernelSize = int( np.clip(self.config.kernelSize, self.config.kernelSizeMin, self.config.kernelSizeMax)) piffConfig = { 'type': "Simple", 'model': { 'type': 'PixelGrid', 'scale': self.config.samplingSize, 'size': kernelSize }, 'interp': { 'type': 'BasisPolynomial', 'order': self.config.spatialOrder }, 'outliers': { 'type': 'Chisq', 'nsigma': self.config.outlierNSigma, 'max_remove': self.config.outlierMaxRemove } } piffResult = piff.PSF.process(piffConfig) # Run on a single CCD, and in image coords rather than sky coords. wcs = {0: galsim.PixelScale(1.0)} pointing = None logger = logging.getLogger(self.log.getName() + ".Piff") logger.addHandler(lsst.log.LogHandler()) piffResult.fit(stars, wcs, pointing, logger=logger) psf = PiffPsf(kernelSize, kernelSize, piffResult) used_image_pos = [s.image_pos for s in piffResult.stars] if flagKey: for candidate in psfCandidateList: source = candidate.getSource() posd = galsim.PositionD(source.getX(), source.getY()) if posd in used_image_pos: source.set(flagKey, True) if metadata is not None: metadata.set("spatialFitChi2", piffResult.chisq) metadata.set("numAvailStars", len(stars)) metadata.set("numGoodStars", len(piffResult.stars)) metadata.set("avgX", np.mean([p.x for p in piffResult.stars])) metadata.set("avgY", np.mean([p.y for p in piffResult.stars])) return psf, None
def test_star(): # Star class is a pretty thin wrapper of StarData and StarFit classes. # Same setup as for test_euclidean wcs = galsim.AffineTransform(0.26, -0.02, 0.03, 0.28, world_origin=galsim.PositionD(912.4, -833.1)) size = 64 image_pos = galsim.PositionD(1083.9, 617.3) field_pos = wcs.toWorld(image_pos) icen = int(image_pos.x + 0.5) jcen = int(image_pos.y + 0.5) bounds = galsim.BoundsI(icen - size // 2 - 1, icen + size // 2, jcen - size // 2 - 1, jcen + size // 2) image = galsim.Image(bounds, wcs=wcs) weight = galsim.ImageS(bounds, wcs=wcs, init_value=1) galsim.Gaussian(sigma=5).drawImage(image) weight += image properties = { 'ra': 34.1234, 'dec': -15.567, 'color_ri': 0.5, 'color_iz': -0.2, 'chipnum': 3 } stardata = piff.StarData(image, image_pos, weight=weight, properties=properties) # Check access via star class star = piff.Star(stardata, None) # fit is optional, but must be explicitly None for key, value in stardata.properties.items(): np.testing.assert_equal(star[key], value) np.testing.assert_equal(star.image.array, image.array) np.testing.assert_equal(star.weight.array, weight.array) assert star.image_pos == image_pos print('field_pos = ', field_pos) print('star.field_pos = ', star.field_pos) assert star.field_pos == field_pos assert star.x == image_pos.x assert star.y == image_pos.y assert star.u == field_pos.x assert star.v == field_pos.y assert star.chipnum == 3 assert star.flux == 1. assert star.center == (0, 0) assert star.is_reserve == False star = star.withFlux(7) assert star.flux == 7. assert star.center == (0, 0) star = star.withFlux(17, center=(102, 22)) assert star.flux == 17. assert star.center == (102, 22) star = star.withFlux(center=(12, 20)) assert star.flux == 17. assert star.center == (12, 20) star = star.withFlux(flux=2) assert star.flux == 2. assert star.center == (12, 20) # Test using makeTarget star = piff.Star.makeTarget(properties=stardata.properties, image=image, weight=weight) np.testing.assert_equal(star.data['x'], image_pos.x) np.testing.assert_equal(star.data['y'], image_pos.y) np.testing.assert_equal(star.data['u'], field_pos.x) np.testing.assert_equal(star.data['v'], field_pos.y) # Shouldn't matter whether we use the original wcs or the one in the postage stamp. print('image.wcs = ', image.wcs) print('image_pos = ', image_pos) print('field_pos = ', field_pos) print('image.wcs.toWorld(image_pos) = ', image.wcs.toWorld(image_pos)) print('in star.data: ', star.data['u']) print('in star.data: ', star.data['v']) np.testing.assert_equal(star.data['u'], image.wcs.toWorld(image_pos).x) np.testing.assert_equal(star.data['v'], image.wcs.toWorld(image_pos).y) im, wt, pos = star.data.getImage() np.testing.assert_array_equal(im.array, image.array) np.testing.assert_array_equal(wt.array, weight.array) np.testing.assert_equal(pos, image_pos) # Some invalid parameter checks np.testing.assert_raises(TypeError, piff.Star.makeTarget, x=1, properties=stardata.properties, image=image, weight=weight) np.testing.assert_raises(TypeError, piff.Star.makeTarget, y=1, properties=stardata.properties, image=image, weight=weight) np.testing.assert_raises(TypeError, piff.Star.makeTarget, u=1, properties=stardata.properties, image=image, weight=weight) np.testing.assert_raises(TypeError, piff.Star.makeTarget, v=1, properties=stardata.properties, image=image, weight=weight) np.testing.assert_raises(TypeError, piff.Star.makeTarget, x=1, image=image, weight=weight) np.testing.assert_raises(TypeError, piff.Star.makeTarget, y=1, image=image, weight=weight) np.testing.assert_raises(TypeError, piff.Star.makeTarget, u=1, image=image, weight=weight) np.testing.assert_raises(TypeError, piff.Star.makeTarget, image=image, weight=weight) np.testing.assert_raises(TypeError, piff.Star.makeTarget, x=1, y=2, scale=4, wcs=image.wcs, image=image, weight=weight)
def test_init(): """Test the basic initialization of a StarData object. """ # Use an odd-sized image, so image.true_center and image.center are the same thing. # Otherwise, u,v below will be half-integer values. size = 63 # Center the image at a non-trivial location to simulate this being a cutout from a larger # image. icen = 598 jcen = 109 # Use pixel scale = 1, so image_pos and focal pos are the same thing. image = galsim.Image(size,size, scale=1) field_pos = image.center # Update the bounds so the image is centered at icen, jcen. # Note: this also updates the wcs, so u,v at the center is still field_pos image.setCenter(icen, jcen) # Just draw something so it has non-trivial pixel values. galsim.Gaussian(sigma=5).drawImage(image) weight = galsim.ImageI(image.bounds, init_value=1) # all weights = 1 # To make below tests of weight pixel values useful, add the image to weight, so pixel # values are not all identical. image_pos = image.center properties = { 'ra' : 34.1234, 'dec' : -15.567, 'color_ri' : 0.5, 'color_iz' : -0.2, 'ccdnum' : 3 } stardata = piff.StarData(image, image_pos, weight=weight, properties=properties) # Test attributes np.testing.assert_array_equal(stardata.image.array, image.array) np.testing.assert_array_equal(stardata.weight.array, weight.array) np.testing.assert_equal(stardata.image_pos, image_pos) # Test properties access viw properties attribute or directly with [] for key, value in properties.items(): np.testing.assert_equal(stardata.properties[key], value) np.testing.assert_equal(stardata[key], value) # Test the automatically generated properties print('image_pos = ',image_pos) print('image.wcs = ',image.wcs) print('props = ',stardata.properties) for key, value in [ ('x',image_pos.x), ('y',image_pos.y), ('u',field_pos.x), ('v',field_pos.y) ]: np.testing.assert_equal(stardata.properties[key], value) np.testing.assert_equal(stardata[key], value) # Test access via getImage method: im, wt, pos = stardata.getImage() np.testing.assert_array_equal(im.array, image.array) np.testing.assert_array_equal(wt.array, weight.array) np.testing.assert_equal(pos, image_pos) # Test access via getDataVector method: # Note: This array() and then .T is like zip for Python lists. for data, wt, u, v in np.array(stardata.getDataVector()).T: # In this case, these should be integers, but round in case of numerical inaccuracy. iu = int(round(u)) jv = int(round(v)) # GalSim images access pixels as (x,y) np.testing.assert_equal(data, image(iu+icen,jv+jcen)) np.testing.assert_equal(wt, weight(iu+icen,jv+jcen)) # Numpy arrays access elements as [y,x] np.testing.assert_equal(data, image.array[jv+size//2, iu+size//2]) np.testing.assert_equal(wt, weight.array[jv+size//2, iu+size//2]) print("Passed basic initialization of StarData")
def test_celestial(): """Test using a (realistic) CelestialWCS for the main image. """ # Make a CelestialWCS. The simplest kind to make from scratch is a TanWCS. affine = galsim.AffineTransform(0.26, -0.02, 0.03, 0.28, world_origin=galsim.PositionD(912.4, -833.1)) ra = 13.2343 * galsim.hours dec = -39.8484 * galsim.degrees pointing = galsim.CelestialCoord(ra,dec) wcs = galsim.TanWCS(affine, world_origin=pointing) print('wcs = ',wcs) # Start with a larger image from which we will cut out the postage stamp full_image = galsim.Image(2048,2048, wcs=wcs) full_weight = galsim.ImageS(2048,2048, wcs=wcs, init_value=1) # Make a postage stamp cutout # This next bit is the same as we did for the EuclideanWCS size = 64 image_pos = galsim.PositionD(1083.9, 617.3) sky_pos = wcs.toWorld(image_pos) if galsim.__version__ >= '2.0': u,v = pointing.project(sky_pos) field_pos = galsim.PositionD(u/galsim.arcsec, v/galsim.arcsec) else: field_pos = pointing.project(sky_pos) icen = int(image_pos.x) jcen = int(image_pos.y) bounds = galsim.BoundsI(icen-size//2+1, icen+size//2, jcen-size//2+1, jcen+size//2) image = full_image[bounds] weight = full_weight[bounds] galsim.Gaussian(sigma=5).drawImage(image) weight += image # With a CelestialWCS, we need to supply a pointing stardata = piff.StarData(image, image_pos, weight=weight, pointing=pointing) # Test properties print('props = ',stardata.properties) np.testing.assert_equal(stardata['x'], image_pos.x) np.testing.assert_equal(stardata['y'], image_pos.y) np.testing.assert_equal(stardata['u'], field_pos.x) np.testing.assert_equal(stardata['v'], field_pos.y) np.testing.assert_equal(stardata['ra'], sky_pos.ra/galsim.hours) np.testing.assert_equal(stardata['dec'], sky_pos.dec/galsim.degrees) # Test access via getImage method: im, wt, pos = stardata.getImage() np.testing.assert_array_equal(im.array, image.array) np.testing.assert_array_equal(wt.array, weight.array) np.testing.assert_equal(pos, image_pos) # Test access via getDataVector method: for data, wt, u, v in np.array(stardata.getDataVector()).T: # u,v values should correspond to image coordinates via wcs uv = galsim.PositionD(u,v) + field_pos if galsim.__version__ >= '2.0': radec = pointing.deproject(uv.x * galsim.arcsec, uv.y * galsim.arcsec) else: radec = pointing.deproject(uv) xy = wcs.toImage(radec) # These should now be integers, but round in case of numerical inaccuracy. ix = int(round(xy.x)) jy = int(round(xy.y)) np.testing.assert_equal(data, image(ix,jy)) np.testing.assert_equal(wt, weight(ix,jy)) print("Passed tests of StarData with CelestialWCS")
def test_euclidean(): """Test a slightly more complicated WCS and an object not centered at the center of the image. """ # Make a non-trivial WCS wcs = galsim.AffineTransform(0.26, -0.02, 0.03, 0.28, world_origin=galsim.PositionD(912.4, -833.1)) print('wcs = ',wcs) # Start with a larger image from which we will cut out the postage stamp full_image = galsim.Image(2048,2048, wcs=wcs) full_weight = galsim.ImageS(2048,2048, wcs=wcs, init_value=1) print('origin of full image is at u,v = ',full_image.wcs.toWorld(full_image.origin)) print('center of full image is at u,v = ',full_image.wcs.toWorld(full_image.center)) # Make a postage stamp cutout size = 64 # This time, use an even size. image_pos = galsim.PositionD(1083.9, 617.3) field_pos = wcs.toWorld(image_pos) icen = int(image_pos.x) jcen = int(image_pos.y) bounds = galsim.BoundsI(icen-size//2+1, icen+size//2, jcen-size//2+1, jcen+size//2) image = full_image[bounds] weight = full_weight[bounds] print('image_pos = ',image_pos) print('field pos (u,v) = ',field_pos) print('origin of ps image is at u,v = ',image.wcs.toWorld(image.origin)) print('center of ps image is at u,v = ',image.wcs.toWorld(image.center)) # Just draw something so it has non-trivial pixel values. galsim.Gaussian(sigma=5).drawImage(image) weight += image stardata = piff.StarData(image, image_pos, weight=weight) # Test properties print('props = ',stardata.properties) np.testing.assert_equal(stardata['x'], image_pos.x) np.testing.assert_equal(stardata['y'], image_pos.y) np.testing.assert_equal(stardata['u'], field_pos.x) np.testing.assert_equal(stardata['v'], field_pos.y) # Shouldn't matter whether we use the original wcs or the one in the postage stamp. np.testing.assert_equal(stardata['u'], image.wcs.toWorld(image_pos).x) np.testing.assert_equal(stardata['v'], image.wcs.toWorld(image_pos).y) # Test access via getImage method: im, wt, pos = stardata.getImage() np.testing.assert_array_equal(im.array, image.array) np.testing.assert_array_equal(wt.array, weight.array) np.testing.assert_equal(pos, image_pos) # Test access via getDataVector method: for data, wt, u, v in np.array(stardata.getDataVector()).T: # u,v values should correspond to image coordinates via wcs uv = galsim.PositionD(u,v) + field_pos xy = wcs.toImage(uv) # These should now be integers, but round in case of numerical inaccuracy. ix = int(round(xy.x)) jy = int(round(xy.y)) np.testing.assert_equal(data, image(ix,jy)) np.testing.assert_equal(wt, weight(ix,jy)) print("Passed tests of StarData with EuclideanWCS")