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 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 test_poly_mean(): # Zero'th order polynomial fitting should be pretty trivial, just # the same as the mean fitting. So much of this code is just taken from # the mean testing in test_simple N = 0 nparam = 5 interp = piff.Polynomial(N) nstars = 100 # Choose some random values of star parameters np_rng = np.random.RandomState(1234) vectors = [np_rng.random_sample(size=nparam) for i in range(nstars)] # take the mean of them. Our curve fit should be able to reproduce this. mean = np.mean(vectors, axis=0) # Choose some random positions in the field. data = [ piff.Star.makeTarget(u=np_rng.random_sample() * 10, v=np_rng.random_sample() * 10).data for i in range(nstars) ] fit = [piff.StarFit(v) for v in vectors] stars = [piff.Star(d, f) for d, f in zip(data, fit)] # Run our solver. interp.solve(stars) # we expect one set of coefficients per object assert len(interp.coeffs) == 5 # We should have very close values (not necessarily identical) since # we calculate these in numerically different ways. for mu, val in zip(mean, interp.coeffs): assert np.isclose(mu, val[0, 0]) # We also expect that if we interpolate to any point we just # get the mean as well for i in range(30): target = piff.Star.makeTarget(u=np_rng.random_sample() * 10, v=np_rng.random_sample() * 10) target = interp.interpolate(target) np.testing.assert_almost_equal(target.fit.params, mean) # Now test running it via the config parser config = { 'interp': { 'type': 'Polynomial', 'order': 0, } } logger = piff.config.setup_logger() interp = piff.Interp.process(config['interp'], logger) interp.solve(stars) # Same tests assert len(interp.coeffs) == 5 for mu, val in zip(mean, interp.coeffs): assert np.isclose(mu, val[0, 0]) np.testing.assert_almost_equal(target.fit.params, mean)
def make_single_star(u, v, size, g1, g2, size_err, g1_err, g2_err): """Make a Star instance filled with a Kolmogorov profile :param u, v: Star coordinate. :param size: Star size. :param g1, g2: Shear applied to profile. :param size_err: Size error. :param g1_err, g2_err: Shear error. """ star = piff.Star.makeTarget(x=None, y=None, u=u, v=v, properties={}, wcs=None, scale=0.26, stamp_size=24, image=None, pointing=None, flux=1.) kolmogorov.drawImage(star.image, method='auto') fit = piff.StarFit(np.array([size, g1, g2]), params_var=np.array([size_err**2, g1_err**2, g2_err**2]), flux=1.) final_star = piff.Star(star.data, fit) return final_star
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 sub_poly_linear(type1): # Now lets do something more interesting - test a linear model. # with no noise this should fit really well, though again not # numerically perfectly. np_rng = np.random.RandomState(1234) nparam = 3 N = 1 nstars = 50 orders = [N for i in range(nparam)] interp = piff.Polynomial(orders=orders, poly_type=type1) X = 10.0 # size of the field Y = 10.0 pos = [(np_rng.random_sample() * X, np_rng.random_sample() * Y) for i in range(nstars)] # Let's make a function that is linear just as a function of one parameter # These are the linear fit parameters for each parameter in turn m1 = np_rng.uniform(size=nparam) m2 = np_rng.uniform(size=nparam) c = np_rng.uniform(size=nparam) def linear_func(pos): u = pos[0] v = pos[1] r = m1 * u + m2 * v + c return r # Simulate the vectors under this model vectors = [linear_func(p) for p in pos] # Fit them. Linear fitting is quite easy so this should be okay data = [piff.Star.makeTarget(u=p[0], v=p[1]).data for p in pos] fit = [piff.StarFit(v) for v in vectors] stars = [piff.Star(d, f) for d, f in zip(data, fit)] interp.solve(stars) # Check that the interpolation recovers the desired function for i in range(30): p = (np_rng.random_sample() * X, np_rng.random_sample() * Y) target = piff.Star.makeTarget(u=p[0], v=p[1]) target = interp.interpolate(target) np.testing.assert_almost_equal(linear_func(p), target.fit.params) # Now test running it via the config parser config = { 'interp': { 'type': 'Polynomial', 'order': 1, } } logger = piff.config.setup_logger() interp = piff.Interp.process(config['interp'], logger) interp.solve(stars) np.testing.assert_almost_equal(linear_func(p), target.fit.params)
def sub_poly_quadratic(type1): # This is basically the same as linear but with # quadratic variation np_rng = np.random.RandomState(1234) nparam = 3 N = 2 nstars = 50 orders = [N for i in range(nparam)] interp = piff.Polynomial(N, poly_type=type1) X = 10.0 # size of the field Y = 10.0 pos = [(np_rng.random_sample() * X, np_rng.random_sample() * Y) for i in range(nstars)] # Let's make a function that is linear just as a function of one parameter # These are the linear fit parameters for each parameter in turn m1 = np_rng.uniform(size=nparam) m2 = np_rng.uniform(size=nparam) q1 = np_rng.uniform(size=nparam) c = np_rng.uniform(size=nparam) def quadratic_func(pos): u = pos[0] v = pos[1] r = q1 * u * v + m1 * u + m2 * v + c return r # Simulate the vectors under this model vectors = [quadratic_func(p) for p in pos] # Fit them. data = [piff.Star.makeTarget(u=p[0], v=p[1]).data for p in pos] fit = [piff.StarFit(v) for v in vectors] stars = [piff.Star(d, f) for d, f in zip(data, fit)] interp.solve(stars) # Check that the interpolation recovers the desired function for i in range(30): p = (np_rng.random_sample() * X, np_rng.random_sample() * Y) target = piff.Star.makeTarget(u=p[0], v=p[1]) target = interp.interpolate(target) np.testing.assert_almost_equal(quadratic_func(p), target.fit.params) # Now test running it via the config parser config = { 'interp': { 'type': 'Polynomial', 'order': 2, } } logger = piff.config.setup_logger() interp = piff.Interp.process(config['interp'], logger) interp.solve(stars) np.testing.assert_almost_equal(quadratic_func(p), target.fit.params)
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 make_empty_star(icen=500, jcen=700, ccdnum=28, params=None, properties={}, fit_kwargs={}): properties['ccdnum'] = ccdnum # setting scale is crucial star = piff.Star.makeTarget(x=icen, y=jcen, properties=properties, scale=0.263) if params is None: starfit = None else: starfit = piff.StarFit(params, **fit_kwargs) star = piff.Star(star.data, starfit) return star
def make_star(icen=500, jcen=700, ccdnum=28, sigma=1, g1=0, g2=0, pixel_to_focal=False, properties={}, fit_kwargs={}): properties['ccdnum'] = ccdnum # setting scale is crucial stardata = piff.Star.makeTarget(x=icen, y=jcen, properties=properties, scale=0.263) # apply Gaussian sigma, g1, g2 params = np.array([sigma, g1, g2]) starfit = piff.StarFit(params, **fit_kwargs) star = piff.Star(stardata.data, starfit) return star
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_Mean(): """For the interpolation, the simplest possible model is just a mean value, which barely even qualifies as doing any kind of interpolating. But it tests the basic glue software. """ # Make a list of parameter vectors to "interpolate" np_rng = np.random.RandomState(1234) nstars = 100 vectors = [ np_rng.random_sample(10) for i in range(nstars) ] mean = np.mean(vectors, axis=0) print('mean = ',mean) # Make some dummy StarData objects to use. The only thing we really need is the properties, # although for the Mean interpolator, even this is ignored. target_data = [ piff.Star.makeTarget(x=np_rng.random_sample()*2048, y=np_rng.random_sample()*2048).data for i in range(nstars) ] fit = [ piff.StarFit(v) for v in vectors ] stars = [ piff.Star(d, f) for d,f in zip(target_data,fit) ] # Use the piff.Mean interpolator interp = piff.Mean() interp.solve(stars) print('True mean = ',mean) print('Interp mean = ',interp.mean) # This should be exactly equal, since we did the same calculation. But use almost_equal # anyway, just in case we decide to do something slightly different, but equivalent. np.testing.assert_almost_equal(mean, interp.mean) # Now test running it via the config parser config = { 'interp' : { 'type' : 'Mean' } } logger = piff.config.setup_logger() interp = piff.Interp.process(config['interp'], logger) interp.solve(stars) np.testing.assert_almost_equal(mean, interp.mean)
def test_poly_raise(): # Test that we can serialize and deserialize a polynomial # interpolator correctly. Copying all this stuff from above: np_rng = np.random.RandomState(1234) nparam = 3 nstars = 50 # Use three different sizes to test everything orders = [1, 2, 3] interp = piff.Polynomial(orders=orders) pos = [(np_rng.random_sample() * 10, np_rng.random_sample() * 10) for i in range(nstars)] #use the wrong number of parameters here so that we raise an error vectors = [np_rng.random_sample(size=nparam + 1) for i in range(nstars)] data = [piff.Star.makeTarget(u=p[0], v=p[1]).data for p in pos] fit = [piff.StarFit(v) for v in vectors] stars = [piff.Star(d, f) for d, f in zip(data, fit)] try: np.testing.assert_raises(ValueError, interp.solve, stars) except ImportError: pass
def test_poly_raise(): # Test that we can serialize and deserialize a polynomial # interpolator correctly. Copying all this stuff from above: np_rng = np.random.RandomState(1234) nparam = 3 nstars = 50 # Use three different sizes to test everything orders = [1, 2, 3] interp = piff.Polynomial(orders=orders) pos = [(np_rng.random_sample() * 10, np_rng.random_sample() * 10) for i in range(nstars)] #use the wrong number of parameters here so that we raise an error vectors = [np_rng.random_sample(size=nparam + 1) for i in range(nstars)] data = [piff.Star.makeTarget(u=p[0], v=p[1]).data for p in pos] fit = [piff.StarFit(v) for v in vectors] stars = [piff.Star(d, f) for d, f in zip(data, fit)] np.testing.assert_raises(ValueError, interp.solve, stars) # Invalid construction np.testing.assert_raises(TypeError, piff.Polynomial) np.testing.assert_raises(TypeError, piff.Polynomial, order=3, orders=[1, 2, 3]) np.testing.assert_raises(ValueError, piff.Polynomial, order=3, poly_type='invalid') # Cannot write before running fit. filename = 'output/test_invalid.fits' with fitsio.FITS(filename, 'rw', clobber=True) as f: with np.testing.assert_raises(RuntimeError): interp.write(f, extname='junk')
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_single_image(): """Test the simple case of one image and one catalog. """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file='output/test_single_image.log') # Make the image image = galsim.Image(2048, 2048, scale=0.26) # Where to put the stars. Include some flagged and not used locations. x_list = [ 123.12, 345.98, 567.25, 1094.94, 924.15, 1532.74, 1743.11, 888.39, 1033.29, 1409.31 ] y_list = [ 345.43, 567.45, 1094.32, 924.29, 1532.92, 1743.83, 888.83, 1033.19, 1409.20, 123.11 ] flag_list = [1, 1, 13, 1, 1, 4, 1, 1, 0, 1] # Draw a Gaussian PSF at each location on the image. sigma = 1.3 g1 = 0.23 g2 = -0.17 psf = galsim.Gaussian(sigma=sigma).shear(g1=g1, g2=g2) for x, y, flag in zip(x_list, y_list, flag_list): bounds = galsim.BoundsI(int(x - 31), int(x + 32), int(y - 31), int(y + 32)) offset = galsim.PositionD(x - int(x) - 0.5, y - int(y) - 0.5) psf.drawImage(image=image[bounds], method='no_pixel', offset=offset) # corrupt the ones that are marked as flagged if flag & 4: print('corrupting star at ', x, y) ar = image[bounds].array im_max = np.max(ar) * 0.2 ar[ar > im_max] = im_max image.addNoise( galsim.GaussianNoise(rng=galsim.BaseDeviate(1234), sigma=1e-6)) # Write out the image to a file image_file = os.path.join('output', 'simple_image.fits') image.write(image_file) # Write out the catalog to a file dtype = [('x', 'f8'), ('y', 'f8'), ('flag', 'i2')] data = np.empty(len(x_list), dtype=dtype) data['x'] = x_list data['y'] = y_list data['flag'] = flag_list cat_file = os.path.join('output', 'simple_cat.fits') fitsio.write(cat_file, data, clobber=True) # Use InputFiles to read these back in config = {'image_file_name': image_file, 'cat_file_name': cat_file} input = piff.InputFiles(config, logger=logger) assert input.image_file_name == [image_file] assert input.cat_file_name == [cat_file] # Check image assert input.nimages == 1 image1, _, image_pos, _, _, _ = input.getRawImageData(0) np.testing.assert_equal(image1.array, image.array) # Check catalog np.testing.assert_equal([pos.x for pos in image_pos], x_list) np.testing.assert_equal([pos.y for pos in image_pos], y_list) # Repeat, using flag columns this time. config = { 'image_file_name': image_file, 'cat_file_name': cat_file, 'flag_col': 'flag', 'use_flag': '1', 'skip_flag': '4', 'stamp_size': 48 } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, _, image_pos, _, _, _ = input.getRawImageData(0) assert len(image_pos) == 7 # Make star data orig_stars = input.makeStars() assert len(orig_stars) == 7 assert orig_stars[0].image.array.shape == (48, 48) # Process the star data # can only compare to truth if include_pixel=False model = piff.Gaussian(fastfit=True, include_pixel=False) interp = piff.Mean() fitted_stars = [model.fit(model.initialize(star)) for star in orig_stars] interp.solve(fitted_stars) print('mean = ', interp.mean) # Check that the interpolation is what it should be # Any position would work here. chipnum = 0 x = 1024 y = 123 orig_wcs = input.getWCS()[chipnum] orig_pointing = input.getPointing() image_pos = galsim.PositionD(x, y) world_pos = piff.StarData.calculateFieldPos(image_pos, orig_wcs, orig_pointing) u, v = world_pos.x, world_pos.y stamp_size = config['stamp_size'] target = piff.Star.makeTarget(x=x, y=y, u=u, v=v, wcs=orig_wcs, stamp_size=stamp_size, pointing=orig_pointing) true_params = [sigma, g1, g2] test_star = interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Check default values of options psf = piff.SimplePSF(model, interp) assert psf.chisq_thresh == 0.1 assert psf.max_iter == 30 assert psf.outliers == None assert psf.extra_interp_properties == [] # Now test running it via the config parser psf_file = os.path.join('output', 'simple_psf.fits') config = { 'input': { 'image_file_name': image_file, 'cat_file_name': cat_file, 'flag_col': 'flag', 'use_flag': 1, 'skip_flag': 4, 'stamp_size': stamp_size }, 'psf': { 'model': { 'type': 'Gaussian', 'fastfit': True, 'include_pixel': False }, 'interp': { 'type': 'Mean' }, 'max_iter': 10, 'chisq_thresh': 0.2, }, 'output': { 'file_name': psf_file }, } orig_stars, wcs, pointing = piff.Input.process(config['input'], logger) # Use a SimplePSF to process the stars data this time. interp = piff.Mean() psf = piff.SimplePSF(model, interp, max_iter=10, chisq_thresh=0.2) assert psf.chisq_thresh == 0.2 assert psf.max_iter == 10 psf.fit(orig_stars, wcs, pointing, logger=logger) test_star = psf.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # test that drawStar and drawStarList work test_star = psf.drawStar(target) test_star_list = psf.drawStarList([target])[0] np.testing.assert_equal(test_star.fit.params, test_star_list.fit.params) np.testing.assert_equal(test_star.image.array, test_star_list.image.array) # test copy_image property of drawStar and draw for draw in [psf.drawStar, psf.model.draw]: target_star_copy = psf.interp.interpolate( piff.Star(target.data.copy(), target.fit.copy())) # interp is so that when we do psf.model.draw we have fit.params to work with test_star_copy = draw(target_star_copy, copy_image=True) test_star_nocopy = draw(target_star_copy, copy_image=False) # if we modify target_star_copy, then test_star_nocopy should be modified, # but not test_star_copy target_star_copy.image.array[0, 0] = 23456 assert test_star_nocopy.image.array[ 0, 0] == target_star_copy.image.array[0, 0] assert test_star_copy.image.array[0, 0] != target_star_copy.image.array[0, 0] # however the other pixels SHOULD still be all the same value assert test_star_nocopy.image.array[ 1, 1] == target_star_copy.image.array[1, 1] assert test_star_copy.image.array[1, 1] == target_star_copy.image.array[1, 1] # test that draw works test_image = psf.draw(x=target['x'], y=target['y'], stamp_size=config['input']['stamp_size'], flux=target.fit.flux, offset=target.fit.center) # this image should be the same values as test_star assert test_image == test_star.image # test that draw does not copy the image image_ref = psf.draw(x=target['x'], y=target['y'], stamp_size=config['input']['stamp_size'], flux=target.fit.flux, offset=target.fit.center, image=test_image) image_ref.array[0, 0] = 123456789 assert test_image.array[0, 0] == image_ref.array[0, 0] assert test_star.image.array[0, 0] != test_image.array[0, 0] assert test_star.image.array[1, 1] == test_image.array[1, 1] # Round trip to a file psf.write(psf_file, logger) psf2 = piff.read(psf_file, logger) assert type(psf2.model) is piff.Gaussian assert type(psf2.interp) is piff.Mean assert psf2.chisq == psf.chisq assert psf2.last_delta_chisq == psf.last_delta_chisq assert psf2.chisq_thresh == psf.chisq_thresh assert psf2.max_iter == psf.max_iter assert psf2.dof == psf.dof assert psf2.nremoved == psf.nremoved test_star = psf2.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Do the whole thing with the config parser os.remove(psf_file) piff.piffify(config, logger) psf3 = piff.read(psf_file) assert type(psf3.model) is piff.Gaussian assert type(psf3.interp) is piff.Mean assert psf3.chisq == psf.chisq assert psf3.last_delta_chisq == psf.last_delta_chisq assert psf3.chisq_thresh == psf.chisq_thresh assert psf3.max_iter == psf.max_iter assert psf3.dof == psf.dof assert psf3.nremoved == psf.nremoved test_star = psf3.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Test using the piffify executable os.remove(psf_file) # This would be simpler as a direct assignment, but this once, test the way you would set # this from the command line, which would call parse_variables. piff.config.parse_variables(config, ['verbose=0'], logger=logger) #config['verbose'] = 0 with open('simple.yaml', 'w') as f: f.write(yaml.dump(config, default_flow_style=False)) config2 = piff.config.read_config('simple.yaml') assert config == config2 piffify_exe = get_script_name('piffify') p = subprocess.Popen([piffify_exe, 'simple.yaml']) p.communicate() psf4 = piff.read(psf_file) assert type(psf4.model) is piff.Gaussian assert type(psf4.interp) is piff.Mean assert psf4.chisq == psf.chisq assert psf4.last_delta_chisq == psf.last_delta_chisq assert psf4.chisq_thresh == psf.chisq_thresh assert psf4.max_iter == psf.max_iter assert psf4.dof == psf.dof assert psf4.nremoved == psf.nremoved test_star = psf4.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # With very low max_iter, we hit the warning about non-convergence config['psf']['max_iter'] = 1 with CaptureLog(level=1) as cl: piff.piffify(config, cl.logger) assert 'PSF fit did not converge' in cl.output
def test_yaml(): if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger(log_file='output/test_gp.log') # Take DES test image, and test doing a psf run with GP interpolator # Use config parser: psf_file = os.path.join('output', 'gp_psf.fits') config = { 'input': { # These can be regular strings 'image_file_name': 'input/DECam_00241238_01.fits.fz', # Or any GalSim str value type. e.g. FormattedStr 'cat_file_name': { 'type': 'FormattedStr', 'format': '%s/DECam_%08d_%02d_psfcat_tb_maxmag_17.0_magcut_3.0_findstars.fits', 'items': [ 'input', # dir 241238, # expnum 1 # chipnum ] }, # What hdu is everything in? 'image_hdu': 1, 'badpix_hdu': 2, 'weight_hdu': 3, 'cat_hdu': 2, # What columns in the catalog have things we need? 'x_col': 'XWIN_IMAGE', 'y_col': 'YWIN_IMAGE', 'ra': 'TELRA', 'dec': 'TELDEC', 'gain': 'GAINA', 'sky_col': 'BACKGROUND', # How large should the postage stamp cutouts of the stars be? 'stamp_size': 21, }, 'psf': { 'model': { 'type': 'GSObjectModel', 'fastfit': True, 'gsobj': 'galsim.Gaussian(sigma=1.0)' }, 'interp': { 'type': 'GPInterp', 'keys': ['u', 'v'], 'optimizer': 'none', 'kernel': 'RBF(200.0)' } }, 'output': { 'file_name': psf_file }, } piff.piffify(config, logger) psf = piff.read(psf_file) assert type(psf.model) is piff.GSObjectModel assert type(psf.interp) is piff.GPInterp print('nstars = ', len(psf.stars)) target = psf.stars[17] test_star = psf.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, target.fit.params, decimal=3) # This should also work if the target doesn't have a fit yet. print('interpolate ', piff.Star(target.data, None)) test_star = psf.interp.interpolate(piff.Star(target.data, None)) np.testing.assert_almost_equal(test_star.fit.params, target.fit.params, decimal=3)
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 fit_with_radial(self, star, interpfunc, vary_shape=True, vary_optics=True): params = star.fit.params flux = star.fit.flux if flux == 1.: # a pretty reasonable first guess is to just take the sum of the pixels flux = star.image.array.sum() du, dv = star.fit.center lmparams = lmfit.Parameters() # Order of params is important! lmparams.add('flux', value=flux, vary=True, min=0.0) lmparams.add('du', value=du, vary=True, min=-1, max=1) lmparams.add('dv', value=dv, vary=True, min=-1, max=1) # we must also cut the min and max based on opt_params to avoid things # like large ellipticities or small sizes min_size = self.optatmo_psf_kwargs['min_size'] max_size = self.optatmo_psf_kwargs['max_size'] max_g = self.optatmo_psf_kwargs['max_g1'] # getParams puts in atmosphere terms fit_size = params[0] fit_g1 = params[1] fit_g2 = params[2] opt_size = params[3] opt_g1 = params[4] opt_g2 = params[5] lmparams.add('atmo_size', value=fit_size, vary=vary_shape, min=min_size - opt_size, max=max_size - opt_size) lmparams.add('atmo_g1', value=fit_g1, vary=vary_shape, min=-max_g - opt_g1, max=max_g - opt_g1) lmparams.add('atmo_g2', value=fit_g2, vary=vary_shape, min=-max_g - opt_g2, max=max_g - opt_g2) # add other params to the params model # we do NOT vary the optics size, g1, g2 lmparams.add('optics_size', value=opt_size, vary=False) lmparams.add('optics_g1', value=opt_g1, vary=False) lmparams.add('optics_g2', value=opt_g2, vary=False) for i, pi in enumerate(params[6:]): # we do allow zernikes to vary lmparams.add('optics_zernike_{0}'.format(i + 4), value=pi, vary=vary_optics, min=-5, max=5) results = lmfit.minimize(_fit_model_residual_with_radial, lmparams, args=(star, self, interpfunc), epsfcn=1e-5, method='leastsq') # subtract 3 for the flux, du, dv fit_params = np.zeros(len(results.params) - 3) params_var = np.zeros(len(fit_params)) for i, key in enumerate(results.params): indx = i - 3 if key in ['flux', 'du', 'dv']: continue param = results.params[key] fit_params[indx] = param.value if hasattr(param, 'stderr'): params_var[indx] = param.stderr ** 2 flux = results.params['flux'].value du = results.params['du'].value dv = results.params['dv'].value center = (du, dv) chisq = results.chisqr dof = results.nfree fit = piff.StarFit(fit_params, params_var=params_var, flux=flux, center=center, chisq=chisq, dof=dof) star_fit = piff.Star(star.data.copy(), fit) return star_fit, results
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_single(): """Same as test_focal, but using the SingleCCD PSF type, which does a separate fit on each CCD. """ wcs1 = galsim.TanWCS( galsim.AffineTransform(0.26, 0.05, -0.08, -0.24, galsim.PositionD(1024, 1024)), galsim.CelestialCoord(-5 * galsim.arcmin, -25 * galsim.degrees)) wcs2 = galsim.TanWCS( galsim.AffineTransform(0.25, -0.02, 0.01, 0.24, galsim.PositionD(1024, 1024)), galsim.CelestialCoord(5 * galsim.arcmin, -25 * galsim.degrees)) field_center = galsim.CelestialCoord(0 * galsim.degrees, -25 * galsim.degrees) if __name__ == '__main__': nstars = 20 # per ccd logger = piff.config.setup_logger(verbose=2) else: nstars = 6 # per ccd logger = piff.config.setup_logger(log_file='output/test_single.log') rng = np.random.RandomState(1234) x = rng.random_sample(nstars) * 2000 + 24 y = rng.random_sample(nstars) * 2000 + 24 ra1, dec1 = wcs1.toWorld(x, y, units='rad') u, v = field_center.project_rad(ra1, dec1, projection='gnomonic') e1 = 0.02 + 2.e-5 * u - 3.e-9 * u**2 + 2.e-9 * v**2 e2 = -0.04 - 3.e-5 * v + 1.e-9 * u * v + 3.e-9 * v**2 s = 0.3 + 8.e-9 * (u**2 + v**2) - 1.e-9 * u * v data1 = np.array(list(zip(x, y, e1, e2, s)), dtype=[('x', float), ('y', float), ('e1', float), ('e2', float), ('s', float)]) im1 = drawImage(2048, 2048, wcs1, x, y, e1, e2, s) im1.write('output/test_single_im1.fits') fitsio.write('output/test_single_cat1.fits', data1, clobber=True) x = rng.random_sample(nstars) * 2000 + 24 y = rng.random_sample(nstars) * 2000 + 24 ra2, dec2 = wcs2.toWorld(x, y, units='rad') u, v = field_center.project_rad(ra1, dec1, projection='gnomonic') # Same functions of u,v, but using the positions on chip 2 e1 = 0.02 + 2.e-5 * u - 3.e-9 * u**2 + 2.e-9 * v**2 e2 = -0.04 - 3.e-5 * v + 1.e-9 * u * v + 3.e-9 * v**2 s = 0.3 + 8.e-9 * (u**2 + v**2) - 1.e-9 * u * v data2 = np.array(list(zip(x, y, e1, e2, s)), dtype=[('x', float), ('y', float), ('e1', float), ('e2', float), ('s', float)]) im2 = drawImage(2048, 2048, wcs2, x, y, e1, e2, s) im2.write('output/test_single_im2.fits') fitsio.write('output/test_single_cat2.fits', data2, clobber=True) ra12 = np.concatenate([ra1, ra2]) dec12 = np.concatenate([dec1, dec2]) data12 = np.array(list(zip(ra12, dec12)), dtype=[('ra', float), ('dec', float)]) fitsio.write('output/test_single_cat12.fits', data12, clobber=True) # Try to fit with the right model (Moffat) and interpolant (2nd order polyomial) # Should work very well, since no noise. config = { 'input': { # A third way to input these same file names. Use GalSim config values and # explicitly specify the number of images to read 'nimages': 2, 'image_file_name': { 'type': 'FormattedStr', 'format': '%s/test_single_im%d.fits', 'items': ['output', '$image_num+1'] }, 'cat_file_name': { 'type': 'FormattedStr', 'format': '%s/test_single_cat%d.fits', 'items': ['output', '$image_num+1'] }, # Use chipnum = 1,2 rather than the default 0,1. 'chipnum': '$image_num+1', 'x_col': 'x', 'y_col': 'y', 'ra': 0., 'dec': -25., }, 'psf': { 'type': 'SingleChip', 'model': { 'type': 'Moffat', 'beta': 2.5 }, 'interp': { 'type': 'Polynomial', 'order': 2 } }, } if __name__ != '__main__': config['verbose'] = 0 with CaptureLog(level=2) as cl: psf = piff.process(config, cl.logger) #print('without nproc, log = ',cl.output) assert "Building solution for chip 1" in cl.output assert "Building solution for chip 2" in cl.output for chipnum, data, wcs in [(1, data1, wcs1), (2, data2, wcs2)]: for k in range(nstars): x = data['x'][k] y = data['y'][k] e1 = data['e1'][k] e2 = data['e2'][k] s = data['s'][k] #print('k,x,y = ',k,x,y) #print(' true s,e1,e2 = ',s,e1,e2) image_pos = galsim.PositionD(x, y) star = piff.Star.makeTarget(x=x, y=y, wcs=wcs, stamp_size=48, pointing=field_center, chipnum=chipnum) star = psf.drawStar(star) #print(' fitted s,e1,e2 = ',star.fit.params) np.testing.assert_almost_equal(star.fit.params, [s, e1, e2], decimal=6) # Chipnum is required as a property to use SingleCCDPSF star1 = piff.Star.makeTarget(x=x, y=y, wcs=wcs, stamp_size=48, pointing=field_center) with np.testing.assert_raises(ValueError): psf.drawStar(star1) star2 = piff.Star(star1.data, star.fit) # If has a fit, it hits a different error with np.testing.assert_raises(ValueError): psf.drawStar(star2)
def poly_load_save_sub(type1, type2, fname): # Test that we can serialize and deserialize a polynomial # interpolator correctly. Copying all this stuff from above: np_rng = np.random.RandomState(1234) nparam = 3 nstars = 50 # Use three different sizes to test everything orders = [1, 2, 3] interp = piff.Polynomial(orders=orders, poly_type=type1) X = 10.0 # size of the field Y = 10.0 pos = [(np_rng.random_sample() * X, np_rng.random_sample() * Y) for i in range(nstars)] # Let's make a function that is linear just as a function of one parameter # These are the linear fit parameters for each parameter in turn m1 = np_rng.uniform(size=nparam) m2 = np_rng.uniform(size=nparam) q1 = np_rng.uniform(size=nparam) c = np_rng.uniform(size=nparam) def quadratic_func(pos): u = pos[0] v = pos[1] r = q1 * u * v + m1 * u + m2 * v + c return r # Simulate the vectors under this model vectors = [quadratic_func(p) for p in pos] # Fit them! data = [piff.Star.makeTarget(u=p[0], v=p[1]).data for p in pos] fit = [piff.StarFit(v) for v in vectors] stars = [piff.Star(d, f) for d, f in zip(data, fit)] interp.solve(stars) import tempfile import os import fitsio extname = "interp" dirname = 'output' filename = os.path.join(dirname, fname) with fitsio.FITS(filename, 'rw', clobber=True) as f: interp.write(f, extname=extname) with fitsio.FITS(filename, "r") as f2: interp2 = piff.Polynomial.read(f2, extname=extname) # The type and other parameters should now have been overwritten and updated assert interp2.poly_type == interp.poly_type assert interp2.order == interp.order np.testing.assert_array_equal(interp2.orders, interp.orders) assert interp2.nvariables == interp.nvariables np.testing.assert_array_equal(interp2.indices, interp.indices) # Check that the old and new interpolators generate the same # value for i in range(30): p = (np_rng.random_sample() * X, np_rng.random_sample() * Y) target = piff.Star.makeTarget(u=p[0], v=p[1]) target1 = interp.interpolate(target) target2 = interp.interpolate(target) np.testing.assert_almost_equal(target1.fit.params, target2.fit.params)
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 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 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 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)