def _get_band_objects(self): """Get a list of effective PSF-convolved galsim images w/ their offsets in the image. Returns ------- all_band_objs : list of lists A list of lists of objects in each band. positions : list of galsim.PositionD A list of galsim positions for each object. """ all_band_obj = [] positions = [] nobj = self._get_nobj() if self.gal_grid is not None: self._gal_grid_ind = 0 gal_kws = self.gal_kws or {} if 'min_dist' in gal_kws: LOGGER.debug('using min dist: %f', gal_kws['min_dist']) others = [] for i in range(nobj): # unsheared offset from center of image if 'min_dist' in gal_kws: if i == 0: dx, dy = self._get_dxdy() else: dx, dy = self._get_dxdy(others=np.array(others), min_dist=gal_kws['min_dist']) others.append([dx, dy]) else: dx, dy = self._get_dxdy() # get the galaxy if self.gal_type == 'exp': gals = self._get_gal_exp() elif self.gal_type == 'ground_galsim_parametric': gals = self._get_gal_ground_galsim_parametric() elif self.gal_type == 'wldeblend': gals = self._get_gal_wldeblend() else: raise ValueError('gal_type "%s" not valid!' % self.gal_type) # compute the final image position if self.shear_scene: sdx, sdy = np.dot(self.shear_mat, np.array([dx, dy])) else: sdx = dx sdy = dy pos = galsim.PositionD(x=sdx / self.scale + self.im_cen, y=sdy / self.scale + self.im_cen) # get the PSF info _, _psf_wcs, _, _psfs, _ = self._render_psf_image(x=pos.x, y=pos.y) # shear, shift, and then convolve the galaxy _obj = [] for gal, _psf in zip(gals, _psfs): gal = gal.shear(g1=self.g1, g2=self.g2) gal = galsim.Convolve(gal, _psf) _obj.append(gal) all_band_obj.append(_obj) positions.append(pos) return all_band_obj, positions
def setupImage(self, input_obj, config, base, logger=None): """Set up the PowerSpectrum input object's gridded values based on the size of the image and the grid spacing. @param input_obj The PowerSpectrum object to use @param config The configuration dict for 'power_spectrum' @param base The base configuration dict. @param logger If given, a logger object to log progress. """ logger = galsim.config.LoggerWrapper(logger) # Attach the logger to the input_obj so we can use it when evaluating values. input_obj.logger = logger if 'grid_spacing' in config: grid_spacing = galsim.config.ParseValue(config, 'grid_spacing', base, float)[0] elif 'grid_xsize' in base and 'grid_ysize' in base: # Then we have a tiled image. Can use the tile spacing as the grid spacing. grid_size = min(base['grid_xsize'], base['grid_ysize']) # This size is in pixels, so we need to convert to arcsec using the pixel scale. # Note: we use the (max) pixel scale at the image center. This isn't # necessarily optimal, but it seems like the best choice for a non-trivial WCS. scale = base['wcs'].maxLinearScale(base['image_center']) grid_spacing = grid_size * scale else: raise galsim.GalSimConfigError( "power_spectrum.grid_spacing required for non-tiled images") if 'ngrid' in config: ngrid = galsim.config.ParseValue(config, 'ngrid', base, float)[0] elif 'grid_xsize' in base and base['grid_xsize'] == base['grid_ysize']: # PowerSpectrum can only do a square FFT, so make it the larger of the two n's. nx_grid = int(math.ceil(base['image_xsize'] / base['grid_xsize'])) ny_grid = int(math.ceil(base['image_ysize'] / base['grid_ysize'])) ngrid = max(nx_grid, ny_grid) + 1 # Normally that's good, but if tiles aren't square, need to drop through to the # second option. else: image_size = max(base['image_xsize'], base['image_ysize']) scale = base['wcs'].maxLinearScale(base['image_center']) ngrid = int(math.ceil(image_size * scale / grid_spacing)) + 1 if 'interpolant' in config: interpolant = galsim.config.ParseValue(config, 'interpolant', base, str)[0] else: interpolant = None if 'variance' in config: variance = galsim.config.ParseValue(config, 'variance', base, float)[0] else: variance = None if 'center' in config: center = galsim.config.stamp.ParseWorldPos(config, 'center', base, logger) elif base['wcs'].isCelestial(): center = galsim.PositionD(0, 0) else: center = base['wcs'].toWorld(base['image_center']) if 'index' in config: index = galsim.config.ParseValue(config, 'index', base, int)[0] current_index = config.get('current_setup_index', None) if index == current_index: logger.info('image %d: power spectrum grid is already current', base.get('image_num', 0)) return config['current_setup_index'] = index rng = galsim.config.GetRNG(config, base, logger, 'PowerSpectrum') # We don't care about the output here. This just builds the grid, which we'll # access for each object using its position. logger.debug( 'image %d: PowerSpectrum buildGrid(grid_spacing=%s, ngrid=%s, center=%s, ' 'interpolant=%s, variance=%s)', base.get('image_num', 0), grid_spacing, ngrid, center, interpolant, variance) input_obj.buildGrid(grid_spacing=grid_spacing, ngrid=ngrid, center=center, rng=rng, interpolant=interpolant, variance=variance) # Make sure this process gives consistent results regardless of the number of processes # being used. if not isinstance(input_obj, galsim.PowerSpectrum) and rng is not None: # Then input_obj is really a proxy, which means the rng was pickled, so we need to # discard the same number of random calls from the one in the config dict. rng.discard(input_obj.nRandCallsForBuildGrid())
def as_galsim_position(self): return galsim.PositionD( self.ra, self.dec, )
def test_psf(): """Test the two kinds of PSF files we have in DES. """ data_dir = 'des_data' psfex_file = "DECam_00154912_12_psfcat.psf" fitpsf_file = "DECam_00154912_12_fitpsf.fits" wcs_file = "DECam_00154912_12_header.fits" wcs = galsim.FitsWCS(wcs_file, dir=data_dir) # We don't require that the files in example_data_dir have been downloaded. If they # haven't, then we just directly set the comparison values that we want here. example_data_dir = '../examples/des/des_data' cat_file = "DECam_00154912_12_cat.fits" image_file = "DECam_00154912_12.fits.fz" try: cat = galsim.Catalog(cat_file, hdu=2, dir=example_data_dir) size = numpy.array( [cat.getFloat(i, 'FLUX_RADIUS') for i in range(cat.nobjects)]) mag = numpy.array( [cat.getFloat(i, 'MAG_AUTO') for i in range(cat.nobjects)]) flags = numpy.array( [cat.getInt(i, 'FLAGS') for i in range(cat.nobjects)]) index = numpy.array(range(cat.nobjects)) xvals = numpy.array( [cat.getFloat(i, 'X_IMAGE') for i in range(cat.nobjects)]) yvals = numpy.array( [cat.getFloat(i, 'Y_IMAGE') for i in range(cat.nobjects)]) # Pick bright small objects as probable stars mask = (flags == 0) & (mag < 14) & (mag > 13) & (size > 2) & (size < 2.5) idx = numpy.argsort(size[mask]) # This choice of a star is fairly isolated from neighbors, isn't too near an edge or a tape # bump, and doesn't have any noticeable image artifacts in its vicinity. x = xvals[mask][idx][27] y = yvals[mask][idx][27] print('Using x,y = ', x, y) image_pos = galsim.PositionD(x, y) print('size, mag = ', size[mask][idx][27], mag[mask][idx][27]) data = galsim.fits.read(image_file, dir=example_data_dir) b = galsim.BoundsI(int(x) - 15, int(x) + 16, int(y) - 15, int(y) + 16) data_stamp = data[b] header = galsim.fits.FitsHeader(image_file, dir=example_data_dir) sky_level = header['SKYBRITE'] data_stamp -= sky_level raw_meas = data_stamp.FindAdaptiveMom() print('raw_meas = ', raw_meas) ref_size = raw_meas.moments_sigma ref_shape = raw_meas.observed_shape print('ref size: ', ref_size) print('ref shape: ', ref_shape) except IOError: x, y = 1195.64074707, 1276.63427734 image_pos = galsim.PositionD(x, y) b = galsim.BoundsI(int(x) - 15, int(x) + 16, int(y) - 15, int(y) + 16) ref_size = 1.80668628216 ref_shape = galsim.Shear(g1=0.022104322221, g2=-0.130925191715) # First the PSFEx model using the wcs_file to get the model is sky coordinates. psfex = galsim.des.DES_PSFEx(psfex_file, wcs_file, dir=data_dir) psf = psfex.getPSF(image_pos) # The getLocalWCS function should return a local WCS assert psfex.getLocalWCS(image_pos).isLocal() # Draw the postage stamp image # Note: the PSF already includes the pixel response, so draw with method 'no_pixel'. stamp = psf.drawImage(wcs=wcs.local(image_pos), bounds=b, method='no_pixel') print('wcs = ', wcs.local(image_pos)) meas = stamp.FindAdaptiveMom() print('meas = ', meas) print('pixel scale = ', stamp.wcs.minLinearScale(image_pos=image_pos)) print('cf sizes: ', ref_size, meas.moments_sigma) print('cf shapes: ', ref_shape, meas.observed_shape) # The agreement for a single star is not great of course, not even 2 decimals. # Divide by 2 to get agreement at 2 dp. numpy.testing.assert_almost_equal(meas.moments_sigma / 2, ref_size / 2, decimal=2, err_msg="PSFEx size doesn't match") numpy.testing.assert_almost_equal(meas.observed_shape.g1 / 2, ref_shape.g1 / 2, decimal=2, err_msg="PSFEx shape.g1 doesn't match") numpy.testing.assert_almost_equal(meas.observed_shape.g2 / 2, ref_shape.g2 / 2, decimal=2, err_msg="PSFEx shape.g2 doesn't match") # Repeat without the wcs_file argument, so the model is in chip coordinates. # Also check the functionality where the file is already open. with pyfits.open(os.path.join(data_dir, psfex_file)) as hdu_list: psfex = galsim.des.DES_PSFEx(hdu_list[1]) psf = psfex.getPSF(image_pos) # In this case, the getLocalWCS function won't return anything useful. assert psfex.getLocalWCS(image_pos) is None # Draw the postage stamp image. This time in image coords, so pixel_scale = 1.0. stamp = psf.drawImage(bounds=b, scale=1.0, method='no_pixel') meas = stamp.FindAdaptiveMom() numpy.testing.assert_almost_equal( meas.moments_sigma / 2, ref_size / 2, decimal=2, err_msg="no-wcs PSFEx size doesn't match") numpy.testing.assert_almost_equal( meas.observed_shape.g1 / 2, ref_shape.g1 / 2, decimal=2, err_msg="no-wcs PSFEx shape.g1 doesn't match") numpy.testing.assert_almost_equal( meas.observed_shape.g2 / 2, ref_shape.g2 / 2, decimal=2, err_msg="no-wcs PSFEx shape.g2 doesn't match") with assert_raises(TypeError): # file_name must be a string. galsim.des.DES_PSFEx(psf, wcs=wcs_file, dir=data_dir) with assert_raises(galsim.GalSimError): # Cannot provide both image_file_name and wcs galsim.des.DES_PSFEx(psfex_file, image_file_name=wcs_file, wcs=wcs_file, dir=data_dir) with assert_raises((IOError, OSError)): # This one doesn't exist. galsim.des.DES_PSFEx('nonexistant.psf', wcs=wcs_file, dir=data_dir) with assert_raises(OSError): # This one exists, but has invalid header parameters. galsim.des.DES_PSFEx('invalid_psfcat.psf', wcs=wcs_file, dir=data_dir) # Now the shapelet PSF model. This model is already in sky coordinates, so no wcs_file needed. fitpsf = galsim.des.DES_Shapelet(os.path.join(data_dir, fitpsf_file)) psf = fitpsf.getPSF(image_pos) # Draw the postage stamp image # Again, the PSF already includes the pixel response. stamp = psf.drawImage(wcs=wcs.local(image_pos), bounds=b, method='no_pixel') meas = stamp.FindAdaptiveMom() numpy.testing.assert_almost_equal( meas.moments_sigma / 2, ref_size / 2, decimal=2, err_msg="Shapelet PSF size doesn't match") numpy.testing.assert_almost_equal( meas.observed_shape.g1 / 2, ref_shape.g1 / 2, decimal=2, err_msg="Shapelet PSF shape.g1 doesn't match") numpy.testing.assert_almost_equal( meas.observed_shape.g2 / 2, ref_shape.g2 / 2, decimal=2, err_msg="Shapelet PSF shape.g2 doesn't match") with assert_raises(galsim.GalSimBoundsError): fitpsf.getPSF(image_pos=galsim.PositionD(4000, 5000))
def main(): # Using very low accuracy GSParams here for speed gsparams = galsim.GSParams( minimum_fft_size=256, folding_threshold=0.1, kvalue_accuracy=1e-3, stepk_minimum_hlr=2.5, ) # Note - we actually use an interpolated image instead; just putting this in # so you can run the code without needing that file psf_prof = galsim.OpticalPSF( lam=725, # nm diam=1.2, # m defocus=0, obscuration=0.33, nstruts=3, gsparams=gsparams) pixel_scale = 0.02 convolved_image = galsim.Image(256, 256, scale=pixel_scale) convolved_image.setCenter(0, 0) # Do this once here to get the right kimage size/shape and wrap size. gal_prof = galsim.Sersic(n=4, half_light_radius=0.3, gsparams=gsparams) convolved_prof = galsim.Convolve(gal_prof, psf_prof, gsparams=gsparams) psf_kimage, wrap_size = convolved_prof.drawFFT_makeKImage(convolved_image) # Draw the PSF onto the kimage. psf_prof._drawKImage(psf_kimage) # Use the same size/shape for the galaxy part. gal_kimage = psf_kimage.copy() convolved_image2 = convolved_image.copy() for _i in range(1000): gal_prof = galsim.Sersic(n=4, half_light_radius=0.3, gsparams=gsparams) # Account for the fact that this is an even sized image. The drawFFT function will # draw the profile centered on the nominal (integer) center pixel, which (since this is # an even-sized image) actuall +0.5,+0.5 from the true center. gal_prof_cen = gal_prof._shift( galsim.PositionD(-0.5 * pixel_scale, -0.5 * pixel_scale)) # Draw just the galaxy profile in k-space gal_prof_cen._drawKImage(gal_kimage) # Multiply by the (constant) PSF kimage gal_kimage.array[:, :] *= psf_kimage.array # Finish the draw process gal_prof.drawFFT_finish(convolved_image, gal_kimage, wrap_size, add_to_image=False) if False: # Check that we get the same thing as the normal draw procedure convolved_prof = galsim.Convolve(gal_prof, psf_prof, gsparams=gsparams) # Using no pixel method here since we plan to use a PSF profile # which already includes the pixel response convolved_prof.drawImage(convolved_image2, method='no_pixel') max_diff = np.max( np.abs(convolved_image.array - convolved_image2.array)) print('max diff = ', max_diff) assert (max_diff < 1.e-8)
def test_wfirst_psfs(): """Test the WFIRST PSF routines for reasonable behavior. """ # The WFIRST PSF routines can take a long time under some circumstances. For example, storing # images for interpolation can be expensive, particularly when using the full pupil plane # functionality. To speed up our calculations, we will limit the unit tests to certain # situations: # - fully chromatic PSFs without interpolation and without loading the pupil plane image. But # then we just want to play with the objects in a fast way (e.g., evaluating at one # wavelength, not integrating over a bandpass). # - fully chromatic PSFs with interpolation, but only interpolating between two wavelengths. # - achromatic PSFs without loading the pupil plane image. # Providing a wavelength returns achromatic PSFs psf_5 = galsim.wfirst.getPSF(SCA=5, bandpass='******', wavelength=1950.) assert isinstance(psf_5, galsim.GSObject) # Make sure we do the case where we add aberrations psf_5_ab = galsim.wfirst.getPSF(SCA=5, bandpass='******', wavelength=1950., extra_aberrations=np.zeros(23)+0.001) # Check that we get the same answer if we specify the center of the focal plane. psf_5_tmp = galsim.wfirst.getPSF(SCA=5, bandpass='******', wavelength=1950., SCA_pos=galsim.PositionD(galsim.wfirst.n_pix/2, galsim.wfirst.n_pix/2)) assert psf_5==psf_5_tmp # Check that if we specify a particular wavelength, the PSF that is drawn is the same as if we # had gotten chromatic PSFs and then used evaluateAtWavelength. Note that this nominally seems # like a test of the chromatic functionality, but there are ways that getPSF() could mess up # inputs such that there is a disagreement. That's why this unit test belongs here. use_sca = 5 all_bp = galsim.wfirst.getBandpasses() zbp = all_bp['Z087'] use_lam = zbp.effective_wavelength psf_chrom = galsim.wfirst.getPSF(use_sca, None, approximate_struts=True) psf_achrom = galsim.wfirst.getPSF(use_sca, None, approximate_struts=True, wavelength=use_lam) psf_achrom2 = galsim.wfirst.getPSF(use_sca, 'Z087', approximate_struts=True, wavelength=use_lam) # First, we can draw the achromatic PSF. im_achrom = psf_achrom.drawImage(scale=galsim.wfirst.pixel_scale) im_achrom2 = im_achrom.copy() im_achrom2 = psf_achrom2.drawImage(image=im_achrom2, scale=galsim.wfirst.pixel_scale) im_chrom = im_achrom.copy() obj_chrom = psf_chrom.evaluateAtWavelength(use_lam) im_chrom = obj_chrom.drawImage(image=im_chrom, scale=galsim.wfirst.pixel_scale) # Normalization should probably not be right. im_chrom *= im_achrom.array.sum()/im_chrom.array.sum() # But otherwise these images should agree *extremely* well. np.testing.assert_array_almost_equal( im_chrom.array, im_achrom.array, decimal=8, err_msg='PSF at a given wavelength and chromatic one evaluated at that wavelength disagree.') np.testing.assert_array_almost_equal( im_achrom.array, im_achrom2.array, decimal=8, err_msg='Two PSFs at a given wavelength specified in different ways disagree.') # Make a very limited check that interpolation works: just 2 wavelengths, 1 SCA. # use the blue and red limits for Z087: blue_limit = all_bp['Z087'].blue_limit red_limit = all_bp['Z087'].red_limit n_waves = 3 psf_int = galsim.wfirst.getPSF(SCA=use_sca, bandpass='******', approximate_struts=True, n_waves=n_waves) # Check that evaluation at a single wavelength is consistent with previous results. im_int = im_achrom.copy() obj_int = psf_int.evaluateAtWavelength(use_lam) im_int = obj_int.drawImage(image=im_int, scale=galsim.wfirst.pixel_scale) # These images should agree well, but not perfectly. One of them comes from drawing an image # from an object directly, whereas the other comes from drawing an image of that object, making # it into an InterpolatedImage, then re-drawing it. Different accuracies are used for those # intermediate steps than would be used when drawing directly, so that can give rise to some # disagreement. Check for agreement at the level of 2e-3 (requiring 1e-3 gives rise to failure # in 2 pixels!). diff_im = 0.5*(im_int.array-im_achrom.array) np.testing.assert_array_almost_equal( diff_im, np.zeros_like(diff_im), decimal=3, err_msg='PSF at a given wavelength and interpolated chromatic one evaluated at that ' 'wavelength disagree.') # Check some invalid inputs. # Note, this is a total cheat for getting test coverage of the high_accuracy branches # in getPSF. The actual test of this functionality comes below, but it is only run for # __name__==__main__ runs (i.e. run_all_tests). with assert_raises(TypeError): galsim.wfirst.getPSF(SCA=use_sca, bandpass='******', n_waves=2, approximate_struts=True, high_accuracy=True, wavelength='Z087') with assert_raises(TypeError): galsim.wfirst.getPSF(SCA=use_sca, bandpass=None, n_waves=2, approximate_struts=True, high_accuracy=True, wavelength_limits=red_limit) with assert_raises(TypeError): galsim.wfirst.getPSF(SCA=use_sca, bandpass='******', approximate_struts=False, high_accuracy=True, wavelength='Z087') with assert_raises(ValueError): galsim.wfirst.getPSF(SCA=use_sca, bandpass='******', n_waves=2, approximate_struts=True, high_accuracy=False, wavelength='Z099') with assert_raises(ValueError): galsim.wfirst.getPSF(SCA=use_sca, bandpass='******', n_waves=2, approximate_struts=False, high_accuracy=False, wavelength='Z099') with assert_raises(TypeError): galsim.wfirst.getPSF(SCA=use_sca, bandpass='******', n_waves=2, approximate_struts=False, high_accuracy=False, wavelength='Z087') with assert_raises(TypeError): galsim.wfirst.getPSF(SCA=use_sca, bandpass='******', n_waves=2, approximate_struts=False, high_accuracy=True, wavelength='F184') with assert_raises(galsim.GalSimValueError): galsim.wfirst.getPSF(SCA=use_sca, bandpass=3, approximate_struts=True) # Make sure we can instantiate a PSF with bandpass='******'/'long' and get an equivalent object # when we're not using interpolation. use_sca = 3 bp_type = 'long' bp = galsim.wfirst.longwave_bands[0] psf1 = galsim.wfirst.getPSF(use_sca, bp) psf2 = galsim.wfirst.getPSF(use_sca, 'long') assert psf1==psf2 # Test the construction of PSFs with high_accuracy and/or not approximate_struts # But only if we're running from the command line. if __name__ == '__main__': for kwargs in [ { 'approximate_struts':True, 'high_accuracy':False }, # This is a repeat of the above { 'approximate_struts':True, 'high_accuracy':True }, # These three are all new. { 'approximate_struts':False, 'high_accuracy':False }, # This last test works, but it takes ~10 min to run. So even in the slow tests, # this is a bit too extreme. #{ 'approximate_struts':False, 'high_accuracy':True, } ]: psf = galsim.wfirst.getPSF(use_sca, 'Y106', **kwargs) psf_achrom = galsim.wfirst.getPSF(use_sca, 'Y106', wavelength=use_lam, **kwargs) psf_chrom = psf.evaluateAtWavelength(use_lam) im_achrom = psf_achrom.drawImage(scale=galsim.wfirst.pixel_scale) im_chrom = psf_chrom.drawImage(image=im_achrom.copy()) #im_achrom.write('im_achrom.fits') #im_chrom.write('im_chrom.fits') print("chrom, achrom fluxes = ", im_chrom.array.sum(), im_achrom.array.sum()) im_chrom *= im_achrom.array.sum()/im_chrom.array.sum() print("max diff = ",np.max(np.abs(im_chrom.array - im_achrom.array))) np.testing.assert_array_almost_equal( im_chrom.array, im_achrom.array, decimal=8, err_msg='getPSF with %s has discrepency for chrom/achrom'%kwargs) # Check for exceptions if we: # (1) Include optional aberrations in an unacceptable form. # (2) Invalid SCA numbers. # (3) Invalid kwarg combination. assert_raises(ValueError, galsim.wfirst.getPSF, 3, None, extra_aberrations=[0.03, -0.06]) assert_raises(ValueError, galsim.wfirst.getPSF, 30, None) assert_raises(ValueError, galsim.wfirst.getPSF, 0, None) assert_raises(ValueError, galsim.wfirst.getPSF, 3, 'short', n_waves=10)
def test_meds_config(): """ Create a meds file from a config and compare with a manual creation. """ # Some parameters: if __name__ == '__main__': nobj = 5 n_per_obj = 8 else: nobj = 2 n_per_obj = 3 file_name = 'output/test_meds.fits' stamp_size = 64 pixel_scale = 0.26 seed = 5757231 g1 = -0.17 g2 = 0.23 # generate offsets that depend on the object num so can be easily reproduced # for testing below offset_x = '$ np.sin(999.*(@obj_num+1))' offset_y = '$ np.sin(998.*(@obj_num+1))' def get_offset(obj_num): return galsim.PositionD(np.sin(999. * (obj_num + 1)), np.sin(998. * (obj_num + 1))) # The config dict to write some images to a MEDS file config = { 'gal': { 'type': 'Sersic', 'n': 1.3, 'half_light_radius': { 'type': 'Sequence', 'first': 0.7, 'step': 0.1, 'repeat': n_per_obj }, 'shear': { 'type': 'G1G2', 'g1': g1, 'g2': g2 }, }, 'psf': { 'type': 'Moffat', 'beta': 2.9, 'fwhm': 0.7 }, 'image': { 'pixel_scale': pixel_scale, 'size': stamp_size, 'random_seed': seed }, 'output': { 'type': 'MEDS', 'nobjects': nobj, 'nstamps_per_object': n_per_obj, 'file_name': file_name } } import logging logging.basicConfig(format="%(message)s", level=logging.WARN, stream=sys.stdout) logger = logging.getLogger('test_meds_config') galsim.config.BuildFile(galsim.config.CopyConfig(config), logger=logger) # Add in badpix and offset so we run both with and without options. config = galsim.config.CleanConfig(config) config['image']['offset'] = {'type': 'XY', 'x': offset_x, 'y': offset_y} config['output']['badpix'] = {} # These three are just added for coverage really. config['output']['weight'] = {} config['output']['psf'] = {} config['output']['meds_get_offset'] = {} galsim.config.BuildFile(galsim.config.CopyConfig(config), logger=logger) # Scattered image is invalid with MEDS output config = galsim.config.CleanConfig(config) config['image'] = { 'type': 'Scattered', 'nobjects': 20, 'pixel_scale': pixel_scale, 'size': stamp_size, } with assert_raises(galsim.GalSimConfigError): galsim.config.BuildFile(galsim.config.CopyConfig(config), logger=logger) # Now repeat, making a separate file for each config = galsim.config.CleanConfig(config) config['gal']['half_light_radius'] = { 'type': 'Sequence', 'first': 0.7, 'step': 0.1, 'index_key': 'file_num' } config['output'] = { 'type': 'Fits', 'nfiles': nobj, 'weight': { 'hdu': 1 }, 'badpix': { 'hdu': 2 }, 'psf': { 'hdu': 3 }, 'dir': 'output', 'file_name': { 'type': 'NumberedFile', 'root': 'test_meds' } } config['image'] = { 'type': 'Tiled', 'nx_tiles': 1, 'ny_tiles': n_per_obj, 'pixel_scale': pixel_scale, 'offset': { 'type': 'XY', 'x': offset_x, 'y': offset_y }, 'stamp_size': stamp_size, 'random_seed': seed } galsim.config.Process(galsim.config.CopyConfig(config), logger=logger) try: import meds import fitsio except ImportError: print( 'Failed to import either meds or fitsio. Unable to do tests of meds file.' ) return try: m = meds.MEDS(file_name) except AttributeError: print( 'Seems to be the wrong meds package. Unable to do tests of meds file.' ) return assert m.size == nobj # Test that the images made as meds mosaics match the ones written to the separate fits files. cat = m.get_cat() for iobj in range(nobj): ref_file = os.path.join('output', 'test_meds%d.fits' % iobj) ref_im = galsim.fits.read(ref_file) meds_im_array = m.get_mosaic(iobj) # Just for reference. If you get an error, you can open this file with ds9. alt_meds_file = os.path.join('output', 'test_alt_meds%d.fits' % iobj) alt_meds_im = galsim.Image(meds_im_array) alt_meds_im.write(alt_meds_file) numpy.testing.assert_array_equal( ref_im.array, meds_im_array, err_msg="config MEDS has wrong im for object %d" % iobj) meds_wt_array = m.get_mosaic(iobj, type='weight') ref_wt_im = galsim.fits.read(ref_file, hdu=1) numpy.testing.assert_array_equal( ref_wt_im.array, meds_wt_array, err_msg="config MEDS has wrong wt for object %d" % iobj) meds_seg_array = m.get_mosaic(iobj, type='seg') ref_seg_im = galsim.fits.read(ref_file, hdu=2) ref_seg_im = 1 - ref_seg_im # The seg mag is 1 where badpix == 0 numpy.testing.assert_array_equal( ref_seg_im.array, meds_seg_array, err_msg="config MEDS has wrong seg for object %d" % iobj) meds_psf_array = numpy.concatenate( [m.get_psf(iobj, icut) for icut in range(n_per_obj)], axis=0) ref_psf_im = galsim.fits.read(ref_file, hdu=3) numpy.testing.assert_array_equal( ref_psf_im.array, meds_psf_array, err_msg="config MEDS has wrong psf for object %d" % iobj) # Check that the various positions and sizes are set correctly. info = m.get_image_info() for iobj in range(nobj): n_cut = cat['ncutout'][iobj] for icut in range(n_cut): # This should be stamp_size box_size = cat['box_size'][iobj] numpy.testing.assert_almost_equal(box_size, stamp_size) # cutout_row and cutout_col are the "zero-offset" # position of the object in the stamp. In this convention, the center # of the first pixel is at (0,0), call this meds_center. # This means cutout_row/col should be the same as meds_center + offset offset = get_offset(iobj * n_cut + icut) meds_center = galsim.PositionD((box_size - 1.) / 2., (box_size - 1.) / 2.) cutout_row = cat['cutout_row'][iobj][icut] cutout_col = cat['cutout_col'][iobj][icut] print('cutout_row, cutout_col = ', cutout_col, cutout_row) numpy.testing.assert_almost_equal(cutout_col, (meds_center + offset).x) numpy.testing.assert_almost_equal(cutout_row, (meds_center + offset).y) # The col0 and row0 here should be the same. wcs_meds = m.get_jacobian(iobj, icut) numpy.testing.assert_almost_equal(wcs_meds['col0'], (meds_center + offset).x) numpy.testing.assert_almost_equal(wcs_meds['row0'], (meds_center + offset).y) # The centroid should be (roughly) at the nominal center + offset img = m.get_cutout(iobj, icut, type='image') x, y = numpy.meshgrid(range(img.shape[1]), range(img.shape[0])) itot = numpy.sum(img) ix = numpy.sum(x * img) iy = numpy.sum(y * img) print('centroid = ', ix / itot, iy / itot) print('center + offset = ', meds_center + offset) numpy.testing.assert_almost_equal(ix / itot, (meds_center + offset).x, decimal=2) numpy.testing.assert_almost_equal(iy / itot, (meds_center + offset).y, decimal=2) # The orig positions are irrelevant and should be 0. orig_row = cat['orig_row'][iobj][icut] orig_col = cat['orig_col'][iobj][icut] orig_start_row = cat['orig_start_row'][iobj][icut] orig_start_col = cat['orig_start_col'][iobj][icut] numpy.testing.assert_almost_equal(orig_col, 0.) numpy.testing.assert_almost_equal(orig_row, 0.) numpy.testing.assert_almost_equal(orig_start_col, 0.) numpy.testing.assert_almost_equal(orig_start_row, 0.) # This should be also be 0. numpy.testing.assert_almost_equal(info['position_offset'], 0.)
def test_newdes(): # This is a DES Y6 PSF file made by Robert Gruendl using python 2, so # check that this also works correctly. try: import pixmappy except ImportError: print('pixmappy not installed. Skipping test_newdes()') return # Also make sure pixmappy is recent enough to work. if 'exposure_file' not in pixmappy.GalSimWCS._opt_params: print('pixmappy not recent enough version. Skipping test_newdes()') return import copy if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger(log_file='output/test_newdes.log') fname = os.path.join('input', 'D00232418_i_c19_r5006p01_piff-model.fits') with warnings.catch_warnings(): # This file was written with GalSim 2.1, and now raises a deprecation warning for 2.2. warnings.simplefilter("ignore", galsim.GalSimDeprecationWarning) psf = piff.PSF.read(fname, logger=logger) print('psf.wcs = ', psf.wcs[0]) print('(0,0) -> ', psf.wcs[0].toWorld(galsim.PositionD(0, 0))) print(psf.wcs[0].toWorld(galsim.PositionD(0, 0)).ra / galsim.degrees, psf.wcs[0].toWorld(galsim.PositionD(0, 0)).dec / galsim.degrees) assert np.isclose( psf.wcs[0].toWorld(galsim.PositionD(0, 0)).ra / galsim.degrees, 15.4729872672) assert np.isclose( psf.wcs[0].toWorld(galsim.PositionD(0, 0)).dec / galsim.degrees, 1.95221895945) print('local at 0,0 = ', psf.wcs[0].local(galsim.PositionD(0, 0))) print('area at 0,0 = ', psf.wcs[0].pixelArea(galsim.PositionD(0, 0)), ' = %f**2' % (psf.wcs[0].pixelArea(galsim.PositionD(0, 0))**0.5)) assert np.isclose(psf.wcs[0].pixelArea(galsim.PositionD(0, 0)), 0.263021**2, rtol=1.e-3) image = psf.draw(x=103.3, y=592.0, logger=logger) print('image shape = ', image.array.shape) print('image near center = ', image.array[23:26, 23:26]) print('image sum = ', image.array.sum()) assert np.isclose(image.array.sum(), 1.0, rtol=1.e-2) # The center values should be at least close to the following: regression_array = np.array([[0.03305565, 0.04500969, 0.0395154], [0.03765249, 0.05419811, 0.04867231], [0.02734579, 0.0418797, 0.03928504]]) # Note: the centering mechanics have changed since this regression was set up to make the # nominal PSF center closer to the image center. So the second slice changed from # 23:26 -> 22:25. np.testing.assert_allclose(image.array[23:26, 22:25], regression_array, rtol=1.e-5) # Also check that it is picklable. psf2 = copy.deepcopy(psf) image2 = psf2.draw(x=103.3, y=592.0) np.testing.assert_equal(image2.array, image.array)
def test_table2d_GSInterp(): def f(x_, y_): return 2 * y_ * y_ + 3 * x_ * x_ + 4 * x_ * y_ - np.cos(x_) x = np.linspace(0.1, 3.3, 25) y = np.linspace(0.2, 10.4, 75) yy, xx = np.meshgrid(y, x) # Note the ordering of both input and output here! interpolants = ['lanczos3', 'lanczos3F', 'lanczos7', 'sinc', 'quintic'] for interpolant in interpolants: z = f(xx, yy) tab2d = galsim.LookupTable2D(x, y, z, interpolant=interpolant) do_pickle(tab2d) # Make sure precomputed-hash gets covered hash(tab2d) # Use InterpolatedImage to validate wcs = galsim.JacobianWCS( (max(x) - min(x)) / (len(x) - 1), 0, 0, (max(y) - min(y)) / (len(y) - 1), ) img = galsim.Image(z.T, wcs=wcs) ii = galsim.InterpolatedImage( img, use_true_center=True, offset=(galsim.PositionD(img.xmin, img.ymin) - img.true_center), x_interpolant=interpolant, normalization='sb', calculate_maxk=False, calculate_stepk=False).shift(min(x), min(y)) # Check single value functionality. x1, y1 = 2.3, 3.2 np.testing.assert_allclose(tab2d(x1, y1), ii.xValue(x1, y1), atol=1e-10, rtol=0) # Check vectorized output newx = np.linspace(0.2, 3.1, 15) newy = np.linspace(0.3, 10.1, 25) newyy, newxx = np.meshgrid(newy, newx) np.testing.assert_allclose( tab2d(newxx, newyy).ravel(), np.array([ ii.xValue(x_, y_) for x_, y_ in zip(newxx.ravel(), newyy.ravel()) ]).ravel(), atol=1e-10, rtol=0) np.testing.assert_array_almost_equal(tab2d(newxx, newyy), tab2d(newx, newy, grid=True)) # Check that edge_mode='wrap' works tab2d = galsim.LookupTable2D(x, y, z, edge_mode='wrap') ref_dfdx, ref_dfdy = tab2d.gradient(newxx, newyy) test_dfdx, test_dfdy = tab2d.gradient(newxx + 3 * tab2d.xperiod, newyy) np.testing.assert_array_almost_equal(ref_dfdx, test_dfdx) np.testing.assert_array_almost_equal(ref_dfdy, test_dfdy) test_dfdx, test_dfdy = tab2d.gradient(newxx, newyy + 13 * tab2d.yperiod) np.testing.assert_array_almost_equal(ref_dfdx, test_dfdx) np.testing.assert_array_almost_equal(ref_dfdy, test_dfdy) test_dfdx, test_dfdy = tab2d.gradient(newx, newy + 13 * tab2d.yperiod, grid=True) np.testing.assert_array_almost_equal(ref_dfdx, test_dfdx) np.testing.assert_array_almost_equal(ref_dfdy, test_dfdy) # Test mix of inside and outside original boundary test_dfdx, test_dfdy = tab2d.gradient( np.dstack([newxx, newxx + 3 * tab2d.xperiod]), np.dstack([newyy, newyy])) np.testing.assert_array_almost_equal(ref_dfdx, test_dfdx[:, :, 0]) np.testing.assert_array_almost_equal(ref_dfdy, test_dfdy[:, :, 0]) np.testing.assert_array_almost_equal(ref_dfdx, test_dfdx[:, :, 1]) np.testing.assert_array_almost_equal(ref_dfdy, test_dfdy[:, :, 1])
def test_parallel(): # Run the same test as test_single, but using nproc 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 else: nstars = 6 # per ccd 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_parallel_im1.fits') 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_parallel_im2.fits') 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_parallel.fits', data12, clobber=True) # im3 is blank. Will give errors trying to measure PSF from it. im3 = galsim.Image(2048, 2048, wcs=wcs2) im3.write('output/test_parallel_im3.fits') psf_file = os.path.join('output', 'test_single.fits') 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_parallel_im%d.fits', 'items': ['output', '$image_num+1'], }, 'cat_file_name': 'output/test_parallel.fits', 'chipnum': '$image_num+1', 'ra_col': 'ra', 'dec_col': 'dec', 'ra_units': 'rad', 'dec_units': 'rad', 'nproc': -1, }, 'psf': { 'type': 'SingleChip', 'model': { 'type': 'Moffat', 'beta': 2.5, }, 'interp': { 'type': 'Polynomial', 'order': 2, }, 'nproc': 2, }, 'output': { 'file_name': psf_file, }, } with CaptureLog(level=2) as cl: piff.piffify(config, logger=cl.logger) psf = piff.read(psf_file) 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] 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) np.testing.assert_almost_equal(star.fit.params, [s, e1, e2], decimal=6) # Finally, check that the logger properly captures the subprocess logs with CaptureLog(level=2) as cl: psf = piff.process(config, cl.logger) #print('with nproc=2, log = ',cl.output) assert "Processing catalog 1" in cl.output assert "Processing catalog 2" in cl.output assert "Building solution for chip 1" in cl.output assert "Building solution for chip 2" in cl.output # Check that errors in the solution get properly reported. config['input']['nimages'] = 3 with CaptureLog(level=2) as cl: psf = piff.process(config, cl.logger) assert "Removed 6 stars in initialize" in cl.output assert "No stars. Cannot find PSF model." in cl.output assert "Solutions failed for chipnums: [3]" in cl.output # Check that errors in the multiprocessing input get properly reported. config['input']['ra_col'] = 'invalid' with CaptureLog(level=2) as cl: with np.testing.assert_raises(ValueError): psf = piff.process(config, cl.logger) assert "ra_col = invalid is not a column" in cl.output # With nproc=1, the error is raised directly. config['input']['nproc'] = 1 config['verbose'] = 0 with np.testing.assert_raises(ValueError): psf = piff.process(config) # But just the input error. Not the one in fitting. config['psf']['nproc'] = 1 config['input']['ra_col'] = 'ra' config['verbose'] = 1 with CaptureLog(level=1) as cl: psf = piff.process(config, logger=cl.logger) assert "No stars. Cannot find PSF model." in cl.output assert "Ignoring this failure and continuing on." in cl.output
def test_focal(): """This test uses 2 input files and two catalogs, but does the interpolation over the whole field of view. """ # Give them different wcs's. # The centers should be separated by ~0.25 arcsec/pixel * 2048 pixels / cos(dec) = 565 arcsec # The actual separation of 10 arcmin gives a bit of a gap between the chips. 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 else: nstars = 3 # per ccd rng = np.random.RandomState(1234) x = rng.random_sample(nstars) * 2000 + 24 y = rng.random_sample(nstars) * 2000 + 24 u, v = field_center.project_rad(*wcs1._radec(x.copy(), y.copy()), 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)]) np.testing.assert_array_equal(data1['x'], x) np.testing.assert_array_equal(data1['y'], y) np.testing.assert_array_equal(data1['e1'], e1) np.testing.assert_array_equal(data1['e2'], e2) np.testing.assert_array_equal(data1['s'], s) im1 = drawImage(2048, 2048, wcs1, x, y, e1, e2, s) im1.write('output/test_focal_im1.fits') fitsio.write('output/test_focal_cat1.fits', data1, clobber=True) x = rng.random_sample(nstars) * 2000 + 24 y = rng.random_sample(nstars) * 2000 + 24 u, v = field_center.project_rad(*wcs2._radec(x.copy(), y.copy()), 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_focal_im2.fits') fitsio.write('output/test_focal_cat2.fits', data2, clobber=True) # Try to fit with the right model (Moffat) and interpolant (2nd order polyomial) # Should work very well, since no noise. config = { 'input': { 'image_file_name': 'output/test_focal_im?.fits', 'cat_file_name': 'output/test_focal_cat?.fits', 'x_col': 'x', 'y_col': 'y', 'ra': 0., 'dec': -25., }, 'psf': { 'type': 'Simple', 'model': { 'type': 'Moffat', 'beta': 2.5 }, 'interp': { 'type': 'Polynomial', 'order': 2 } } } if __name__ != '__main__': config['verbose'] = 0 psf = piff.process(config) for data, wcs in [(data1, wcs1), (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) 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)
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 test_wrongwcs(): """Same as test_focal, but the images are written out with the wrong wcs. """ 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)) wrong_wcs = galsim.TanWCS( galsim.AffineTransform(0.25, 0, 0, 0.25, galsim.PositionD(1024, 1024)), galsim.CelestialCoord(0 * galsim.arcmin, -25 * galsim.degrees)) field_center = galsim.CelestialCoord(0 * galsim.degrees, -25 * galsim.degrees) if __name__ == '__main__': nstars = 20 # per ccd else: nstars = 3 # per ccd rng = np.random.RandomState(1234) x = rng.random_sample(nstars) * 2000 + 24 y = rng.random_sample(nstars) * 2000 + 24 u, v = field_center.project_rad(*wcs1._radec(x.copy(), y.copy()), 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) x = rng.random_sample(nstars) * 2000 + 24 y = rng.random_sample(nstars) * 2000 + 24 u, v = field_center.project_rad(*wcs2._radec(x.copy(), y.copy()), 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) # Put in the wrong wcs before writing them to files. im1.wcs = im2.wcs = wrong_wcs im1.write('output/test_wrongwcs_im1.fits') im2.write('output/test_wrongwcs_im2.fits') fitsio.write('output/test_wrongwcs_cat1.fits', data1, clobber=True) fitsio.write('output/test_wrongwcs_cat2.fits', data2, clobber=True) config = { 'modules': ['custom_wcs'], 'input': { 'dir': 'output', # Normally more convenient to use a glob string, but an explicit list is also allowed. 'image_file_name': ['test_wrongwcs_im1.fits', 'test_wrongwcs_im2.fits'], 'cat_file_name': ['test_wrongwcs_cat1.fits', 'test_wrongwcs_cat2.fits'], 'x_col': 'x', 'y_col': 'y', 'ra': 0., 'dec': -25., # But here tell Piff the correct WCS to use. This uses a custom WCS builder, # mostly so we can test the 'modules' option. In practice, you might use a # galsim_extra Pixmappy WCS class. Or maybe an LSST DM WCS. 'wcs': { 'type': 'Custom' } }, 'psf': { 'type': 'Simple', 'model': { 'type': 'Moffat', 'beta': 2.5 }, 'interp': { 'type': 'Polynomial', 'order': 2 } }, } if __name__ != '__main__': config['verbose'] = 0 psf = piff.process(config) for data, wcs in [(data1, wcs1), (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) 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)
def Transform(obj, jac=(1., 0., 0., 1.), offset=galsim.PositionD(0., 0.), flux_ratio=1., gsparams=None): """A function for transforming either a GSObject or ChromaticObject. This function will inspect its input argument to decide if a Transformation object or a ChromaticTransformation object is required to represent the resulting transformed object. Note: the name of the flux_ratio parameter is technically wrong here if the jacobian has a non-unit determinant, since that would also scale the flux. The flux_ratio parameter actually only refers to an overall amplitude ratio for the surface brightness profile. The total flux scaling is actually |det(jac)| * flux_ratio. @param obj The object to be transformed. @param jac A list or tuple ( dudx, dudy, dvdx, dvdy ) describing the Jacobian of the transformation. [default: (1,0,0,1)] @param offset A galsim.PositionD giving the offset by which to shift the profile. @param flux_ratio A factor by which to multiply the surface brightness of the object. (Technically, not necessarily the flux. See above.) [default: 1] @param gsparams An optional GSParams argument. See the docstring for GSParams for details. [default: None] @returns a Transformation or ChromaticTransformation instance as appropriate. """ if not (isinstance(obj, galsim.GSObject) or isinstance(obj, galsim.ChromaticObject)): raise TypeError( "Argument to Transform must be either a GSObject or a ChromaticObject." ) elif (hasattr(jac, '__call__') or hasattr(offset, '__call__') or hasattr(flux_ratio, '__call__') or isinstance(obj, galsim.ChromaticObject)): # Sometimes for Chromatic compound types, it is more efficient to apply the # transformation to the components rather than the whole. In particular, this can # help preserve separability in many cases. # Don't transform ChromaticSum object, better to just transform the arguments. if isinstance(obj, galsim.ChromaticSum) or isinstance(obj, galsim.Sum): return galsim.ChromaticSum([ Transform(o, jac, offset, flux_ratio, gsparams) for o in obj.objlist ]) # If we are just flux scaling, then a Convolution can do that to the first element. # NB. Even better, if the flux scaling is chromatic, would be to find a component # that is already non-separable. But we don't bother trying to do that currently. elif (isinstance( obj, galsim.ChromaticConvolution or isinstance(obj, galsim.Convolution)) and np.array_equal(np.asarray(jac).ravel(), (1, 0, 0, 1)) and offset == galsim.PositionD(0., 0.)): first = Transform(obj.objlist[0], flux_ratio=flux_ratio, gsparams=gsparams) return galsim.ChromaticConvolution([first] + [o for o in obj.objlist[1:]]) else: return galsim.ChromaticTransformation(obj, jac, offset, flux_ratio, gsparams) else: return Transformation(obj, jac, offset, flux_ratio, gsparams)
def test_wfirst_wcs(): """Test the WFIRST WCS routines against the one produced by code from Chris Hirata. """ # The standard against which we will compare is the output of some software provided by Chris # Hirata. The files used here were generated by Rachel on her Macbook using the script in # wfirst_files/, with sky positions randomly selected and then stored as part of the # comparison. We read in a list of FPA center positions and other RA/dec positions, the # position angle for the observation, and the SCA those other positions land on (if any). Then # we compare that with the GalSim routines for finding SCAs. import datetime date = datetime.datetime(2025, 1, 12) test_data_file = os.path.join('wfirst_files','chris_comparison.txt') test_data = np.loadtxt(test_data_file).transpose() ra_cen = test_data[0,:] dec_cen = test_data[1,:] ra = test_data[2,:] dec = test_data[3,:] pa = test_data[4,:] chris_sca = test_data[5,:] if __name__ != "__main__": i_start = 4 n_test = 3 # None of these 3 fail, so the nfail test is ok. else: i_start = 0 n_test = len(ra_cen) n_fail = 0 for i_test in range(i_start, i_start+n_test): print('i_test = ',i_test) # Make the WCS for this test. world_pos = galsim.CelestialCoord(ra_cen[i_test]*galsim.degrees, dec_cen[i_test]*galsim.degrees) gs_wcs_dict = galsim.wfirst.getWCS(PA=pa[i_test]*galsim.radians, world_pos=world_pos, PA_is_FPA=True, date=date) np.testing.assert_equal( len(gs_wcs_dict), galsim.wfirst.n_sca, err_msg='WCS dict has wrong length: %d vs. %d'%(len(gs_wcs_dict), galsim.wfirst.n_sca)) found_sca = galsim.wfirst.findSCA( gs_wcs_dict, galsim.CelestialCoord(ra[i_test]*galsim.degrees, dec[i_test]*galsim.degrees)) if found_sca is None: found_sca=0 if found_sca != chris_sca[i_test]: n_fail += 1 print('Failed to find SCA: ',found_sca, chris_sca[i_test]) # Just cycle through the SCAs for the next bits. sca_test = i_test % 18 + 1 gs_wcs = gs_wcs_dict[sca_test] # Check center position: im_cent_pos = galsim.PositionD(galsim.wfirst.n_pix/2., galsim.wfirst.n_pix/2) gs_cent_pos = gs_wcs.toWorld(im_cent_pos) # Check pixel area pix_area = gs_wcs.pixelArea(image_pos=im_cent_pos) print('pix_area = ',pix_area) np.testing.assert_allclose(pix_area, 0.012, atol=0.001) if i_test == 0: # For just one of our tests cases, we'll do some additional tests. These will target # the findSCA() functionality. First, check that the center is found in that SCA. found_sca = galsim.wfirst.findSCA(gs_wcs_dict, gs_cent_pos) np.testing.assert_equal(found_sca, sca_test, err_msg='Did not find SCA center position to be on that SCA!') # Then, we go to a place that should be off the side by a tiny bit, and check that it is # NOT on an SCA if we exclude borders, but IS on the SCA if we include borders. im_off_edge_pos = galsim.PositionD(-2., galsim.wfirst.n_pix/2.) world_off_edge_pos = gs_wcs.toWorld(im_off_edge_pos) found_sca = galsim.wfirst.findSCA(gs_wcs_dict, world_off_edge_pos) assert found_sca is None found_sca = galsim.wfirst.findSCA(gs_wcs_dict, world_off_edge_pos, include_border=True) np.testing.assert_equal(found_sca, sca_test, err_msg='Did not find slightly off-edge position on the SCA' ' when including borders!') if i_test < 5: # Also make sure that for a given SCA, we find positions on it that should be on it, # without/with inclusion of borders. Just do this test a limited number of times. for sca_ind in range(1,19): sca_edge_test = sca_ind tmp_wcs = gs_wcs_dict[sca_edge_test] im_test_pos = galsim.PositionD(10.0, galsim.wfirst.n_pix/2) tmp_pos = tmp_wcs.toWorld(im_test_pos) found_sca = galsim.wfirst.findSCA(gs_wcs_dict, tmp_pos, include_border=False) assert found_sca==sca_edge_test found_sca = galsim.wfirst.findSCA(gs_wcs_dict, tmp_pos, include_border=True) assert found_sca==sca_edge_test im_test_pos = galsim.PositionD(galsim.wfirst.n_pix/2, galsim.wfirst.n_pix+3) tmp_pos = tmp_wcs.toWorld(im_test_pos) found_sca = galsim.wfirst.findSCA(gs_wcs_dict, tmp_pos, include_border=False) assert found_sca==None found_sca = galsim.wfirst.findSCA(gs_wcs_dict, tmp_pos, include_border=True) assert found_sca==sca_edge_test # And check that we can go from the center of that SCA and reverse-engineer the # position of the center of the FPA. im_test_pos = galsim.PositionD(galsim.wfirst.n_pix/2, galsim.wfirst.n_pix/2) test_sca_pos = tmp_wcs.toWorld(im_test_pos) test_fpa_pos = galsim.wfirst.convertCenter(test_sca_pos, int(sca_edge_test), PA=pa[i_test]*galsim.radians, date=date, PA_is_FPA=True) # Also test that convertCenter checks inputs appropriately. with assert_raises(TypeError): galsim.wfirst.convertCenter(test_sca_pos, 3.5) with assert_raises(TypeError): galsim.wfirst.convertCenter( test_sca_pos, int(sca_edge_test), PA=pa[i_test]*galsim.radians, date=date, PA_is_FPA=True, tol=1.0) delta_arcsec = test_fpa_pos.distanceTo(world_pos) / galsim.arcsec assert delta_arcsec<0.5, "could not round-trip from FPA to SCA to FPA center" # There were few-arcsec offsets in our WCS, so allow some fraction of failures. print('n_fail = ',n_fail) assert n_fail < 0.2*n_test, 'Failed in SCA-matching against reference: %d %d'%(n_fail,n_test) # Check whether we're allowed to look at certain positions on certain dates. # Let's choose RA=90 degrees, dec=10 degrees. # We know that it's best to look about 90 degrees from the Sun. So on the vernal and autumnal # equinox, this should be a great place to look, but not midway in between. We'll use # approximate dates for these. pos = galsim.CelestialCoord(90.*galsim.degrees, 10.*galsim.degrees) import datetime assert galsim.wfirst.allowedPos(pos, datetime.date(2025,3,20)) assert galsim.wfirst.allowedPos(pos, datetime.date(2025,9,20)) assert not galsim.wfirst.allowedPos(pos, datetime.date(2025,6,20)) assert galsim.wfirst.bestPA(pos, datetime.date(2025,6,20)) is None # Finally make sure it does something reasonable for the observatory position angle. # When the sun is at (0,0), and we look at (90,0), then +Z points towards the Sun and +Y points # North, giving a PA of 0 degrees. pos = galsim.CelestialCoord(90.*galsim.degrees, 0.*galsim.degrees) test_date = datetime.datetime(2025,3,20,9,2) pa = galsim.wfirst.bestPA(pos, test_date) np.testing.assert_almost_equal(pa.rad, 0., decimal=3) # Now make it look at the same RA as the sun but quite different declination. It wants +Z # pointing North toward Sun, so we'll get a -90 degree angle for the PA. pos = galsim.CelestialCoord(0.*galsim.degrees, -70.*galsim.degrees) pa = galsim.wfirst.bestPA(pos, test_date) np.testing.assert_almost_equal(pa.rad, -np.pi/2, decimal=3) sun_pos= galsim.CelestialCoord(0*galsim.degrees, 0*galsim.degrees) sun_pa = galsim.wfirst.bestPA(sun_pos, test_date) assert sun_pa is None with assert_raises(TypeError): galsim.wfirst.getWCS(world_pos=galsim.PositionD(300,400)) with assert_raises(galsim.GalSimError): galsim.wfirst.getWCS(world_pos=sun_pos, date=test_date) with assert_raises(TypeError): galsim.wfirst.getWCS(world_pos=pos, PA=33.) with assert_raises(galsim.GalSimRangeError): galsim.wfirst.getWCS(world_pos=pos, SCAs=[-1,1]) with assert_raises(galsim.GalSimRangeError): galsim.wfirst.getWCS(world_pos=pos, SCAs=[1,23]) # Check the rather bizarre convention that LONPOLE is always 180 EXCEPT (!!) when # observing directly at the south pole. Apparently, this convention comes from the WFIRST # project office's use of the LONPOLE keyword. So we keep it, even though it's stupid. # cf. https://github.com/GalSim-developers/GalSim/pull/651#discussion-diff-26277673 assert gs_wcs_dict[1].header['LONPOLE'] == 180. south_pole = galsim.CelestialCoord(0*galsim.degrees, -90*galsim.degrees) wcs = galsim.wfirst.getWCS(world_pos=south_pole, SCAs=1) assert wcs[1].header['LONPOLE'] == 0 with assert_raises(TypeError): galsim.wfirst.findSCA(wcs_dict=None, world_pos=pos) with assert_raises(TypeError): galsim.wfirst.findSCA(wcs_dict=wcs, world_pos=galsim.PositionD(300,400))
def test_wfirst_wcs(): """Test the WFIRST WCS routines against those from software provided by WFIRST project office. """ # The standard against which we will compare is the output of some software provided by Jeff # Kruk. The files used here were generated by Rachel on her Macbook using the script in # wfirst_files/make_standards.sh, and none of the parameters below can be changed without # modifying and rerunning that script. We use 4 sky positions and rotation angles (2 defined # using the focal plane array, 2 using the observatory coordinates), and in each case, use a # different SCA for our tests. We will simply read in the stored WCS and generate new ones, and # check that they have the right value of SCA center and pixel scale at the center, and that if # we offset by 500 pixels in some direction that gives the same sky position in each case. ra_test = [127., 307.4, -61.52, 0.0] dec_test = [-70., 50., 22.7, 0.0] pa_test = [160., 79., 23.4, -3.1] sca_test = [2, 13, 7, 18] import datetime ve = datetime.datetime(2025, 3, 20, 9, 2, 0) date_test = [ve, ve, ve, datetime.date(2025, 6, 20)] pa_is_fpa_test = [True, False, True, False] dist_arcsec = [] dist_2_arcsec = [] pix_area_ratio = [] for i_test in range(len(ra_test)): # Make the WCS for this test. world_pos = galsim.CelestialCoord(ra_test[i_test] * galsim.degrees, dec_test[i_test] * galsim.degrees) if i_test == 0: # Just for this case, we want to get the WCS for all SCAs. This will enable some # additional tests that we don't do for the other test case. gs_wcs_dict = galsim.wfirst.getWCS( PA=pa_test[i_test] * galsim.degrees, world_pos=world_pos, PA_is_FPA=pa_is_fpa_test[i_test], date=date_test[i_test]) np.testing.assert_equal( len(gs_wcs_dict), galsim.wfirst.n_sca, err_msg='WCS dict has wrong length: %d vs. %d' % (len(gs_wcs_dict), galsim.wfirst.n_sca)) else: # Use the SCAs keyword to just get the WCS for the SCA that we want. gs_wcs_dict = galsim.wfirst.getWCS( PA=pa_test[i_test] * galsim.degrees, world_pos=world_pos, PA_is_FPA=pa_is_fpa_test[i_test], SCAs=sca_test[i_test], date=date_test[i_test]) np.testing.assert_equal( len(gs_wcs_dict), 1, err_msg='WCS dict has wrong length: %d vs. %d' % (len(gs_wcs_dict), 1)) # Read in reference. test_file = 'test%d_sca_%02d.fits' % (i_test + 1, sca_test[i_test]) ref_wcs = galsim.FitsWCS(os.path.join('wfirst_files', test_file)) gs_wcs = gs_wcs_dict[sca_test[i_test]] # Check center position: im_cent_pos = galsim.PositionD(galsim.wfirst.n_pix / 2., galsim.wfirst.n_pix / 2) ref_cent_pos = ref_wcs.toWorld(im_cent_pos) gs_cent_pos = gs_wcs.toWorld(im_cent_pos) dist_arcsec.append( ref_cent_pos.distanceTo(gs_cent_pos) / galsim.arcsec) # Check pixel area rat = ref_wcs.pixelArea(image_pos=im_cent_pos) / gs_wcs.pixelArea( image_pos=im_cent_pos) pix_area_ratio.append(rat - 1.) # Check another position, just in case rotations are messed up. im_other_pos = galsim.PositionD(im_cent_pos.x + 500., im_cent_pos.y - 200.) ref_other_pos = ref_wcs.toWorld(im_other_pos) gs_other_pos = gs_wcs.toWorld(im_other_pos) dist_2_arcsec.append( ref_other_pos.distanceTo(gs_other_pos) / galsim.arcsec) if i_test == 0: # For just one of our tests cases, we'll do some additional tests. These will target # the findSCA() functionality. First, we'll choose an SCA and check that its center is # found to be in that SCA. found_sca = galsim.wfirst.findSCA(gs_wcs_dict, gs_cent_pos) np.testing.assert_equal( found_sca, sca_test[i_test], err_msg='Did not find SCA center position to be on that SCA!') # Then, we go to a place that should be off the side by a tiny bit, and check that it is # NOT on an SCA if we exclude borders, but IS on the SCA if we include borders. im_off_edge_pos = galsim.PositionD(-2., galsim.wfirst.n_pix / 2.) world_off_edge_pos = gs_wcs.toWorld(im_off_edge_pos) found_sca = galsim.wfirst.findSCA(gs_wcs_dict, world_off_edge_pos) assert found_sca is None found_sca = galsim.wfirst.findSCA(gs_wcs_dict, world_off_edge_pos, include_border=True) np.testing.assert_equal( found_sca, sca_test[i_test], err_msg='Did not find slightly off-edge position on the SCA' ' when including borders!') np.testing.assert_array_less( np.array(dist_arcsec), np.ones(len(ra_test)) * galsim.wfirst.pixel_scale / 100, err_msg= 'For at least one WCS, center offset from reference was > 0.01(pixel scale).' ) np.testing.assert_array_less( np.array(dist_2_arcsec), np.ones(len(ra_test)) * galsim.wfirst.pixel_scale / 100, err_msg= 'For at least one WCS, other offset from reference was > 0.01(pixel scale).' ) np.testing.assert_array_less( np.array(pix_area_ratio), np.ones(len(ra_test)) * 0.0001, err_msg= 'For at least one WCS, pixel areas differ from reference by >0.01%.') # Check whether we're allowed to look at certain positions on certain dates. # Let's choose RA=90 degrees, dec=10 degrees. # We know that it's best to look about 90 degrees from the Sun. So on the vernal and autumnal # equinox, this should be a great place to look, but not midway in between. We'll use # approximate dates for these. pos = galsim.CelestialCoord(90. * galsim.degrees, 10. * galsim.degrees) import datetime assert galsim.wfirst.allowedPos(pos, datetime.date(2025, 3, 20)) assert galsim.wfirst.allowedPos(pos, datetime.date(2025, 9, 20)) assert not galsim.wfirst.allowedPos(pos, datetime.date(2025, 6, 20)) # Finally make sure it does something reasonable for the observatory position angle. # When the sun is at (0,0), and we look at (90,0), then +Z points towards the Sun and +Y points # North, giving a PA of 0 degrees. pos = galsim.CelestialCoord(90. * galsim.degrees, 0. * galsim.degrees) test_date = datetime.datetime(2025, 3, 20, 9, 2) pa = galsim.wfirst.bestPA(pos, test_date) np.testing.assert_almost_equal(pa.rad(), 0., decimal=3) # Now make it look at the same RA as the sun but quite different declination. It wants +Z # pointing North toward Sun, so we'll get a -90 degree angle for the PA. pos = galsim.CelestialCoord(0. * galsim.degrees, -70. * galsim.degrees) pa = galsim.wfirst.bestPA(pos, test_date) np.testing.assert_almost_equal(pa.rad(), -np.pi / 2, decimal=3)
def skip_wfirst_wcs(): """Test the WFIRST WCS routines against ones provided by the WFIRST project office. """ # This test is out of date and is not run, but since it was a useful test, the code is kept here # as a reminder to reinstate it if/when we get an updated version of the WCS software from the # WFIRST project office for cycle 7+. Everything below this comment is the original code from # GalSim v1.4. ######################################################################################3 # The standard against which we will compare is the output of some software provided by Jeff # Kruk. The files used here were generated by Rachel on her Macbook using the script in # wfirst_files/make_standards.sh, and none of the parameters below can be changed without # modifying and rerunning that script. We use 4 sky positions and rotation angles (2 defined # using the focal plane array, 2 using the observatory coordinates), and in each case, use a # different SCA for our tests. We will simply read in the stored WCS and generate new ones, and # check that they have the right value of SCA center and pixel scale at the center, and that if # we offset by 500 pixels in some direction that gives the same sky position in each case. ra_test = [127., 307.4, -61.52, 0.0] dec_test = [-70., 50., 22.7, 0.0] pa_test = [160., 79., 23.4, -3.1] sca_test = [2, 13, 7, 18] import datetime ve = datetime.datetime(2025,3,20,9,2,0) date_test = [ve, ve, ve, datetime.date(2025,6,20)] pa_is_fpa_test = [True, False, True, False] dist_arcsec = [] dist_2_arcsec = [] pix_area_ratio = [] for i_test in range(len(ra_test)): # Make the WCS for this test. world_pos = galsim.CelestialCoord(ra_test[i_test]*galsim.degrees, dec_test[i_test]*galsim.degrees) if i_test == 0: # Just for this case, we want to get the WCS for all SCAs. This will enable some # additional tests that we don't do for the other test case. gs_wcs_dict = galsim.wfirst.getWCS(PA=pa_test[i_test]*galsim.degrees, world_pos=world_pos, PA_is_FPA=pa_is_fpa_test[i_test], date=date_test[i_test]) np.testing.assert_equal( len(gs_wcs_dict), galsim.wfirst.n_sca, err_msg='WCS dict has wrong length: %d vs. %d'%(len(gs_wcs_dict), galsim.wfirst.n_sca)) else: # Use the SCAs keyword to just get the WCS for the SCA that we want. gs_wcs_dict = galsim.wfirst.getWCS(PA=pa_test[i_test]*galsim.degrees, world_pos=world_pos, PA_is_FPA=pa_is_fpa_test[i_test], SCAs=sca_test[i_test], date=date_test[i_test]) np.testing.assert_equal( len(gs_wcs_dict), 1, err_msg='WCS dict has wrong length: %d vs. %d'%(len(gs_wcs_dict), 1)) # Read in reference. test_file = 'test%d_sca_%02d.fits'%(i_test+1, sca_test[i_test]) ref_wcs = galsim.FitsWCS(os.path.join('wfirst_files',test_file)) gs_wcs = gs_wcs_dict[sca_test[i_test]] # Check center position: im_cent_pos = galsim.PositionD(galsim.wfirst.n_pix/2., galsim.wfirst.n_pix/2) ref_cent_pos = ref_wcs.toWorld(im_cent_pos) gs_cent_pos = gs_wcs.toWorld(im_cent_pos) dist_arcsec.append(ref_cent_pos.distanceTo(gs_cent_pos) / galsim.arcsec) # Check pixel area rat = ref_wcs.pixelArea(image_pos=im_cent_pos)/gs_wcs.pixelArea(image_pos=im_cent_pos) pix_area_ratio.append(rat-1.) # Check another position, just in case rotations are messed up. im_other_pos = galsim.PositionD(im_cent_pos.x+500., im_cent_pos.y-200.) ref_other_pos = ref_wcs.toWorld(im_other_pos) gs_other_pos = gs_wcs.toWorld(im_other_pos) dist_2_arcsec.append(ref_other_pos.distanceTo(gs_other_pos) / galsim.arcsec) if i_test == 0: # For just one of our tests cases, we'll do some additional tests. These will target # the findSCA() functionality. First, we'll choose an SCA and check that its center is # found to be in that SCA. found_sca = galsim.wfirst.findSCA(gs_wcs_dict, gs_cent_pos) np.testing.assert_equal(found_sca, sca_test[i_test], err_msg='Did not find SCA center position to be on that SCA!') # Then, we go to a place that should be off the side by a tiny bit, and check that it is # NOT on an SCA if we exclude borders, but IS on the SCA if we include borders. im_off_edge_pos = galsim.PositionD(-2., galsim.wfirst.n_pix/2.) world_off_edge_pos = gs_wcs.toWorld(im_off_edge_pos) found_sca = galsim.wfirst.findSCA(gs_wcs_dict, world_off_edge_pos) assert found_sca is None found_sca = galsim.wfirst.findSCA(gs_wcs_dict, world_off_edge_pos, include_border=True) np.testing.assert_equal(found_sca, sca_test[i_test], err_msg='Did not find slightly off-edge position on the SCA' ' when including borders!') np.testing.assert_array_less( np.array(dist_arcsec), np.ones(len(ra_test))*galsim.wfirst.pixel_scale/100, err_msg='For at least one WCS, center offset from reference was > 0.01(pixel scale).') np.testing.assert_array_less( np.array(dist_2_arcsec), np.ones(len(ra_test))*galsim.wfirst.pixel_scale/100, err_msg='For at least one WCS, other offset from reference was > 0.01(pixel scale).') np.testing.assert_array_less( np.array(pix_area_ratio), np.ones(len(ra_test))*0.0001, err_msg='For at least one WCS, pixel areas differ from reference by >0.01%.')
def convertCenter(world_pos, SCA, PA=None, date=None, PA_is_FPA=False, tol=0.5*galsim.arcsec): """ This is a simple helper routine that takes an input position `world_pos` that is meant to correspond to the position of the center of an SCA, and tells where the center of the focal plane array should be. The goal is to provide a position that can be used as an input to getWCS(), which wants the center of the focal plane array. The results of the calculation are deterministic if given a fixed position angle (PA). If it's not given one, it will try to determine the best one for this location and date, like getWCS() does. Because of distortions varying across the focal plane, this routine has to iteratively correct its initial result based on empirical tests. The `tol` kwarg can be used to adjust how careful it will be, but it always does at least one iteration. To fully understand all possible inputs and outputs to this routine, users may wish to consult the diagram on the GalSim wiki, https://github.com/GalSim-developers/GalSim/wiki/GalSim-WFIRST-module-diagrams @param world_pos A galsim.CelestialCoord indicating the position to observe at the center of the given SCA. Note that if the given position is not observable on the given date, then the routine will raise an exception. @param SCA A single number giving the SCA for which the center should be located at `world_pos`. @param PA galsim.Angle representing the position angle of the observatory +Y axis, unless `PA_is_FPA=True`, in which case it's the position angle of the FPA. For users to do not care about this, then leaving this as None will result in the routine using the supplied `date` and `world_pos` to select the optimal orientation for the observatory. Note that if a user supplies a `PA` value, the routine does not check whether this orientation is actually allowed. [default: None] @param date The date of the observation, as a python datetime object. If None, then the vernal equinox in 2025 will be used. [default: None] @param PA_is_FPA If True, then the position angle that was provided was the PA of the focal plane array, not the observatory. [default: False] @param tol Tolerance for errors due to distortions, as a galsim.Angle. [default: 0.5*galsim.arcsec] @returns a CelestialCoord object indicating the center of the focal plane array. """ if not isinstance(SCA, int): raise TypeError("Must pass in an int corresponding to the SCA") if not isinstance(tol, galsim.Angle): raise TypeError("tol must be a galsim.Angle") use_SCA = SCA # Parse inputs appropriately. _, _, pa_fpa, _ = _parse_WCS_inputs(world_pos, PA, date, PA_is_FPA, [SCA]) # Now pretend world_pos was the FPA center and we want to find the location of this SCA: _, u, v = _get_sca_center_pos(use_SCA, world_pos, pa_fpa) # The (u, v) values give an offset, and we can invert this. fpa_cent = world_pos.deproject(-u, -v, projection='gnomonic') # This is only approximately correct, especially for detectors that are far from the center of # the FPA, because of distortions etc. We can do an iterative correction. # For the default value of 'tol', typically just 1-2 iterations are needed. shift_val = 1000.0 # arcsec while shift_val > tol/galsim.arcsec: test_wcs = getWCS(fpa_cent, PA, date, use_SCA, PA_is_FPA)[use_SCA] im_cent_pos = galsim.PositionD(galsim.wfirst.n_pix/2, galsim.wfirst.n_pix/2) test_sca_pos = test_wcs.toWorld(im_cent_pos) delta_ra = np.cos(world_pos.dec)*(world_pos.ra-test_sca_pos.ra) delta_dec = world_pos.dec-test_sca_pos.dec shift_val = np.abs(world_pos.distanceTo(test_sca_pos)/galsim.arcsec) fpa_cent = galsim.CelestialCoord(fpa_cent.ra + delta_ra, fpa_cent.dec + delta_dec) return fpa_cent
def test_meds(): """ Create two objects, each with three exposures. Save them to a MEDS file. Load the MEDS file. Compare the created objects with the one read by MEDS. """ # initialise empty MultiExposureObject list objlist = [] # we will be using 2 objects for testing, each with 3 cutouts n_obj_test = 2 n_cut_test = 3 # set the image size box_size = 32 # first obj img11 = galsim.Image(box_size, box_size, init_value=111) img12 = galsim.Image(box_size, box_size, init_value=112) img13 = galsim.Image(box_size, box_size, init_value=113) seg11 = galsim.Image(box_size, box_size, init_value=121) seg12 = galsim.Image(box_size, box_size, init_value=122) seg13 = galsim.Image(box_size, box_size, init_value=123) wth11 = galsim.Image(box_size, box_size, init_value=131) wth12 = galsim.Image(box_size, box_size, init_value=132) wth13 = galsim.Image(box_size, box_size, init_value=133) psf11 = galsim.Image(box_size, box_size, init_value=141) psf12 = galsim.Image(box_size, box_size, init_value=142) psf13 = galsim.Image(box_size, box_size, init_value=143) dudx = 11.1 dudy = 11.2 dvdx = 11.3 dvdy = 11.4 x0 = 11.5 y0 = 11.6 wcs11 = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, galsim.PositionD(x0, y0)) dudx = 12.1 dudy = 12.2 dvdx = 12.3 dvdy = 12.4 wcs12 = galsim.JacobianWCS(dudx, dudy, dvdx, dvdy) wcs13 = galsim.PixelScale(13) # create lists images = [img11, img12, img13] weight = [wth11, wth12, wth13] seg = [seg11, seg12, seg13] psf = [psf11, psf12, psf13] wcs = [wcs11, wcs12, wcs13] # create object obj1 = galsim.des.MultiExposureObject(images=images, weight=weight, seg=seg, psf=psf, wcs=wcs, id=1) # second obj img21 = galsim.Image(box_size, box_size, init_value=211) img22 = galsim.Image(box_size, box_size, init_value=212) img23 = galsim.Image(box_size, box_size, init_value=213) seg21 = galsim.Image(box_size, box_size, init_value=221) seg22 = galsim.Image(box_size, box_size, init_value=222) seg23 = galsim.Image(box_size, box_size, init_value=223) wth21 = galsim.Image(box_size, box_size, init_value=231) wth22 = galsim.Image(box_size, box_size, init_value=332) wth23 = galsim.Image(box_size, box_size, init_value=333) psf21 = galsim.Image(box_size, box_size, init_value=241) psf22 = galsim.Image(box_size, box_size, init_value=342) psf23 = galsim.Image(box_size, box_size, init_value=343) dudx = 21.1 dudy = 21.2 dvdx = 21.3 dvdy = 21.4 x0 = 21.5 y0 = 21.6 wcs21 = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, galsim.PositionD(x0, y0)) dudx = 22.1 dudy = 22.2 dvdx = 22.3 dvdy = 22.4 wcs22 = galsim.JacobianWCS(dudx, dudy, dvdx, dvdy) wcs23 = galsim.PixelScale(23) # create lists images = [img21, img22, img23] weight = [wth21, wth22, wth23] seg = [seg21, seg22, seg23] psf = [psf21, psf22, psf23] wcs = [wcs21, wcs22, wcs23] # create object # This time put the wcs in the image and get it there. img21.wcs = wcs21 img22.wcs = wcs22 img23.wcs = wcs23 obj2 = galsim.des.MultiExposureObject(images=images, weight=weight, seg=seg, psf=psf, id=2) obj3 = galsim.des.MultiExposureObject(images=images, id=3) # create an object list objlist = [obj1, obj2] # save objects to MEDS file filename_meds = 'output/test_meds.fits' galsim.des.WriteMEDS(objlist, filename_meds, clobber=True) bad1 = galsim.Image(32, 48, init_value=0) bad2 = galsim.Image(35, 35, init_value=0) bad3 = galsim.Image(48, 48, init_value=0) with assert_raises(TypeError): galsim.des.MultiExposureObject(images=img11) with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=[]) with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=[bad1]) with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=[bad2]) with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=[img11, bad3]) with assert_raises(TypeError): galsim.des.MultiExposureObject(images=images, weight=wth11) with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=images, weight=[]) with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=[img11], weight=[bad3]) with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=[img11], psf=[bad1]) with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=[img11], psf=[bad2]) with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=[img11, img12], psf=[bad2, psf12]) with assert_raises(TypeError): galsim.des.MultiExposureObject(images=images, wcs=wcs11) celestial_wcs = galsim.FitsWCS("DECam_00154912_12_header.fits", dir='des_data') with assert_raises(galsim.GalSimValueError): galsim.des.MultiExposureObject(images=[img11], wcs=[celestial_wcs]) # Check the one with no psf, weight, etc. filename_meds2 = 'output/test_meds_image_only.fits' galsim.des.WriteMEDS([obj3], filename_meds2, clobber=True) # Note that while there are no tests prior to this, the above still checks for # syntax errors in the meds creation software, so it's still worth running as part # of the normal unit tests. # But for the rest of the tests, we'll use the meds module to make sure our code # stays in sync with any changes there. try: import meds # Meds will import this, so check for this too. import fitsio except ImportError: print( 'Failed to import either meds or fitsio. Unable to do tests of meds file.' ) return # Run meds module's validate function try: meds.util.validate_meds(filename_meds) meds.util.validate_meds(filename_meds2) except AttributeError: print( 'Seems to be the wrong meds package. Unable to do tests of meds file.' ) return m = meds.MEDS(filename_meds) # Check the image_info extension: ref_info = meds.util.get_image_info_dtype(1) info = m.get_image_info() print('info = ', info) for name, dt in ref_info: dt = numpy.dtype(dt) print(name, dt, info.dtype[name], dt.char, info.dtype[name].char) assert name in info.dtype.names, "column %s not present in image_info extension" % name # I think S and U for this purpose are equivalent. # But I'm finding S in the reference, and U in info. c = info.dtype[name].char c = 'S' if c == 'U' else c assert dt.char == c, "column %s is the wrong type" % name # Check the basic structure of the object_data extension cat = m.get_cat() ref_data = meds.util.get_meds_output_dtype(1) for tup in ref_data: # Some of these tuples have 3 items, not 2. The last two are the full dtype tuple. name = tup[0] if len(tup) == 2: dt = tup[1] else: dt = tup[1:] dt = numpy.dtype(dt) print(name, dt, cat.dtype[name], dt.char, cat.dtype[name].char) assert name in cat.dtype.names, "column %s not present in object_data extension" % name assert dt.char == cat.dtype[ name].char, "column %s is the wrong type" % name # Check that we have the right number of objects. n_obj = len(cat) print('number of objects is %d' % n_obj) numpy.testing.assert_equal(n_obj, n_obj_test, err_msg="MEDS file has wrong number of objects") # loop over objects and exposures - test get_cutout for iobj in range(n_obj): # check ID is correct numpy.testing.assert_equal( cat['id'][iobj], iobj + 1, err_msg="MEDS file has wrong id for object %d" % iobj) # get number of cutouts and check if it's right n_cut = cat['ncutout'][iobj] numpy.testing.assert_equal( n_cut, n_cut_test, err_msg="MEDS file has wrong ncutout for object %d" % iobj) # loop over cutouts for icut in range(n_cut): # get the images etc to compare with originals img = m.get_cutout(iobj, icut, type='image') wth = m.get_cutout(iobj, icut, type='weight') seg = m.get_cutout(iobj, icut, type='seg') psf = m.get_psf(iobj, icut) wcs_meds = m.get_jacobian(iobj, icut) # Note: col == x, row == y. wcs_array_meds = numpy.array([ wcs_meds['dudcol'], wcs_meds['dudrow'], wcs_meds['dvdcol'], wcs_meds['dvdrow'], wcs_meds['col0'], wcs_meds['row0'] ]) # compare numpy.testing.assert_array_equal( img, objlist[iobj].images[icut].array, err_msg="MEDS cutout has wrong img for object %d" % iobj) numpy.testing.assert_array_equal( wth, objlist[iobj].weight[icut].array, err_msg="MEDS cutout has wrong wth for object %d" % iobj) numpy.testing.assert_array_equal( seg, objlist[iobj].seg[icut].array, err_msg="MEDS cutout has wrong seg for object %d" % iobj) numpy.testing.assert_array_equal( psf, objlist[iobj].psf[icut].array, err_msg="MEDS cutout has wrong psf for object %d" % iobj) wcs_orig = objlist[iobj].wcs[icut] wcs_array_orig = numpy.array([ wcs_orig.dudx, wcs_orig.dudy, wcs_orig.dvdx, wcs_orig.dvdy, wcs_orig.origin.x, wcs_orig.origin.y ]) numpy.testing.assert_array_equal( wcs_array_meds, wcs_array_orig, err_msg="MEDS cutout has wrong wcs for object %d" % iobj) # get the mosaic to compare with originals img = m.get_mosaic(iobj, type='image') wth = m.get_mosaic(iobj, type='weight') seg = m.get_mosaic(iobj, type='seg') # There is currently no get_mosaic option for the psfs. #psf = m.get_mosaic( iobj, type='psf') psf = numpy.concatenate( [m.get_psf(iobj, icut) for icut in range(n_cut)], axis=0) # get the concatenated images - create the true mosaic true_mosaic_img = numpy.concatenate( [x.array for x in objlist[iobj].images], axis=0) true_mosaic_wth = numpy.concatenate( [x.array for x in objlist[iobj].weight], axis=0) true_mosaic_seg = numpy.concatenate( [x.array for x in objlist[iobj].seg], axis=0) true_mosaic_psf = numpy.concatenate( [x.array for x in objlist[iobj].psf], axis=0) # compare numpy.testing.assert_array_equal( true_mosaic_img, img, err_msg="MEDS mosaic has wrong img for object %d" % iobj) numpy.testing.assert_array_equal( true_mosaic_wth, wth, err_msg="MEDS mosaic has wrong wth for object %d" % iobj) numpy.testing.assert_array_equal( true_mosaic_seg, seg, err_msg="MEDS mosaic has wrong seg for object %d" % iobj) numpy.testing.assert_array_equal( true_mosaic_psf, psf, err_msg="MEDS mosaic has wrong psf for object %d" % iobj)
def main(argv): """ Make images using variable PSF and shear: - The main image is 10 x 10 postage stamps. - Each postage stamp is 48 x 48 pixels. - The second HDU has the corresponding PSF image. - Applied shear is from a power spectrum P(k) ~ k^1.8. - Galaxies are real galaxies oriented in a ring test of 20 each. - The PSF is Gaussian with FWHM, ellipticity and position angle functions of (x,y) - Noise is Poisson using a nominal sky value of 1.e6. """ logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout) logger = logging.getLogger("demo10") # Define some parameters we'll use below. # Normally these would be read in from some parameter file. n_tiles = 10 # number of tiles in each direction. stamp_size = 48 # pixels pixel_scale = 0.44 # arcsec / pixel sky_level = 1.e6 # ADU / arcsec^2 # The random seed is used for both the power spectrum realization and the random properties # of the galaxies. random_seed = 3339201 # Make output directory if not already present. if not os.path.isdir('output'): os.mkdir('output') file_name = os.path.join('output','power_spectrum.fits') # These will be created for each object below. The values we'll use will be functions # of (x,y) relative to the center of the image. (r = sqrt(x^2+y^2)) # psf_fwhm = 0.9 + 0.5 * (r/100)^2 -- arcsec # psf_e = 0.4 * (r/100)^1.5 -- large value at the edge, so visible by eye. # psf_beta = atan2(y/x) + pi/2 -- tangential pattern gal_signal_to_noise = 100 # Great08 "LowNoise" run gal_dilation = 3 # Make the galaxies a bit larger than their original size. logger.info('Starting demo script 10') # Read in galaxy catalog cat_file_name = 'real_galaxy_catalog_example.fits' # This script is designed to be run from the examples directory so dir is a relative path. # But the '../examples/' part lets bin/demo10 also be run from the bin directory. dir = '../examples/data' real_galaxy_catalog = galsim.RealGalaxyCatalog(cat_file_name, dir=dir) real_galaxy_catalog.preload() logger.info('Read in %d real galaxies from catalog', real_galaxy_catalog.nobjects) # List of IDs to use. We select 5 particularly irregular galaxies for this demo. # Then we'll choose randomly from this list. id_list = [ 106416, 106731, 108402, 116045, 116448 ] # Make the 5 galaxies we're going to use here rather than remake them each time. # This means the Fourier transforms of the real galaxy images don't need to be recalculated # each time, so it's a bit more efficient. gal_list = [ galsim.RealGalaxy(real_galaxy_catalog, id=id) for id in id_list ] # Make the galaxies a bit larger than their original observed size. for gal in gal_list: gal.applyDilation(gal_dilation) # Setup the PowerSpectrum object we'll be using: ps = galsim.PowerSpectrum(lambda k : k**1.8) # The argument here is "e_power_function" which defines the E-mode power to use. # There is also a b_power_function if you want to include any B-mode power: # ps = galsim.PowerSpectrum(e_power_function, b_power_function) # You may even omit the e_power_function argument and have a pure B-mode power spectrum. # ps = galsim.PowerSpectrum(b_power_function = b_power_function) # All the random number generator classes derive from BaseDeviate. # When we construct another kind of deviate class from any other # kind of deviate class, the two share the same underlying random number # generator. Sometimes it can be clearer to just construct a BaseDeviate # explicitly and then construct anything else you need from that. # Note: A BaseDeviate cannot be used to generate any values. It can # only be used in the constructor for other kinds of deviates. # The seeds for the objects are random_seed..random_seed+nobj-1 (which comes later), # so use the next one. nobj = n_tiles * n_tiles rng = galsim.BaseDeviate(random_seed+nobj) # Now have the PowerSpectrum object build a grid of shear values for us to use. grid_g1, grid_g2 = ps.buildGrid(grid_spacing=stamp_size*pixel_scale, ngrid=n_tiles, rng=rng) # Setup the images: gal_image = galsim.ImageF(stamp_size * n_tiles , stamp_size * n_tiles) psf_image = galsim.ImageF(stamp_size * n_tiles , stamp_size * n_tiles) gal_image.setScale(pixel_scale) psf_image.setScale(pixel_scale) im_center = gal_image.bounds.trueCenter() # We will place the tiles in a random order. To do this, we make two lists for the # ix and iy values. Then we apply a random permutation to the lists (in tandem). ix_list = [] iy_list = [] for ix in range(n_tiles): for iy in range(n_tiles): ix_list.append(ix) iy_list.append(iy) # This next function will use the given random number generator, rng, and use it to # randomly permute any number of lists. All lists will have the same random permutation # applied. galsim.random.permute(rng, ix_list, iy_list) # Build each postage stamp: for k in range(nobj): # The usual random number generator using a different seed for each galaxy. rng = galsim.BaseDeviate(random_seed+k) # Determine the bounds for this stamp and its center position. ix = ix_list[k] iy = iy_list[k] b = galsim.BoundsI(ix*stamp_size+1 , (ix+1)*stamp_size, iy*stamp_size+1 , (iy+1)*stamp_size) sub_gal_image = gal_image[b] sub_psf_image = psf_image[b] pos = b.trueCenter() - im_center pos = galsim.PositionD(pos.x * pixel_scale , pos.y * pixel_scale) # The image comes out as about 211 arcsec across, so we define our variable # parameters in terms of (r/100 arcsec), so roughly the scale size of the image. r = math.sqrt(pos.x**2 + pos.y**2) / 100 psf_fwhm = 0.9 + 0.5 * r**2 # arcsec psf_e = 0.4 * r**1.5 psf_beta = (math.atan2(pos.y,pos.x) + math.pi/2) * galsim.radians # Define the PSF profile psf = galsim.Gaussian(fwhm=psf_fwhm) psf.applyShear(e=psf_e, beta=psf_beta) # Define the pixel pix = galsim.Pixel(pixel_scale) # Define the galaxy profile: # For this demo, we are doing a ring test where the same galaxy profile is drawn at many # orientations stepped uniformly in angle, making a ring in e1-e2 space. # We're drawing each profile at 20 different orientations and then skipping to the # next galaxy in the list. So theta steps by 1/20 * 360 degrees: theta = k/20. * 360. * galsim.degrees # The index needs to increment every 20 objects so we use k/20 using integer math. index = k / 20 gal = gal_list[index] # This makes a new copy so we're not changing the object in the gal_list. gal = gal.createRotated(theta) # Apply the shear from the power spectrum. We should either turn the gridded shears # grid_g1[iy, ix] and grid_g2[iy, ix] into gridded reduced shears using a utility called # galsim.lensing.theoryToObserved, or use ps.getShear() which by default gets the reduced # shear. ps.getShear() is also more flexible because it can get the shear at positions that # are not on the original grid, as long as they are contained within the bounds of the full # grid. So in this example we'll use ps.getShear(). alt_g1,alt_g2 = ps.getShear(pos) gal.applyShear(g1=alt_g1, g2=alt_g2) # Apply half-pixel shift in a random direction. shift_r = pixel_scale * 0.5 ud = galsim.UniformDeviate(rng) theta = ud() * 2. * math.pi dx = shift_r * math.cos(theta) dy = shift_r * math.sin(theta) gal.applyShift(dx,dy) # Make the final image, convolving with psf and pix final = galsim.Convolve([psf,pix,gal]) # Draw the image final.draw(sub_gal_image) # Now add noise to get our desired S/N # See demo5.py for more info about how this works. sky_level_pixel = sky_level * pixel_scale**2 noise = galsim.PoissonNoise(rng, sky_level=sky_level_pixel) sub_gal_image.addNoiseSNR(noise, gal_signal_to_noise) # Draw the PSF image: # We use real space convolution to avoid some of the # artifacts that can show up with Fourier convolution. # The level of the artifacts is quite low, but when drawing with # no noise, they are apparent with ds9's zscale viewing. final_psf = galsim.Convolve([psf,pix], real_space=True) # For the PSF image, we also shift the PSF by the same amount. final_psf.applyShift(dx,dy) # No noise on PSF images. Just draw it as is. final_psf.draw(sub_psf_image) logger.info('Galaxy (%d,%d): position relative to center = %s', ix,iy,str(pos)) logger.info('Done making images of postage stamps') # Now write the images to disk. images = [ gal_image , psf_image ] galsim.fits.writeMulti(images, file_name) logger.info('Wrote image to %r',file_name)
def get_offset(obj_num): return galsim.PositionD(np.sin(999. * (obj_num + 1)), np.sin(998. * (obj_num + 1)))
def test_interleaveImages(): import time t1 = time.time() # 1a) With galsim Gaussian g = galsim.Gaussian(sigma=3.7, flux=1000.) gal = galsim.Convolve([g, galsim.Pixel(1.0)]) im_list = [] offset_list = [] n = 2 for j in xrange(n): for i in xrange(n): im = galsim.Image(16 * n, 16 * n) offset = galsim.PositionD(-(i + 0.5) / n + 0.5, -(j + 0.5) / n + 0.5) offset_list.append(offset) gal.drawImage(image=im, method='no_pixel', offset=offset, scale=0.5) im_list.append(im) scale = im.scale # Input to N as an int img = galsim.utilities.interleaveImages(im_list, n, offsets=offset_list) im = galsim.Image(16 * n * n, 16 * n * n) g = galsim.Gaussian(sigma=3.7, flux=1000. * n * n) gal = galsim.Convolve([g, galsim.Pixel(1.0)]) gal.drawImage(image=im, method='no_pixel', offset=galsim.PositionD(0.0, 0.0), scale=1. * scale / n) np.testing.assert_array_equal(img.array,im.array,\ err_msg="Interleaved Gaussian images do not match") assert im.wcs == img.wcs # 1b) With im_list and offsets permuted offset_list = [] # An elegant way of generating the default offsets DX = np.arange(0.0, -1.0, -1.0 / n) DX -= DX.mean() DY = DX for dy in DY: for dx in DX: offset = galsim.PositionD(dx, dy) offset_list.append(offset) np.random.seed(42) # for generating the same random permutation everytime rand_idx = np.random.permutation(len(offset_list)) im_list_randperm = [im_list[idx] for idx in rand_idx] offset_list_randperm = [offset_list[idx] for idx in rand_idx] # Input to N as a tuple img_randperm = galsim.utilities.interleaveImages( im_list_randperm, (n, n), offsets=offset_list_randperm) np.testing.assert_array_equal(img_randperm.array,img.array,\ err_msg="Interleaved images do not match when 'offsets' is supplied") assert img_randperm.scale == img.scale # 1c) Catching errors in offsets offset_list = [] im_list = [] n = 5 # Generate approximate offsets DX = np.array([-0.67, -0.33, 0., 0.33, 0.67]) DY = DX for dy in DY: for dx in DX: offset = galsim.PositionD(dx, dy) offset_list.append(offset) im = galsim.Image(16, 16) gal.drawImage(image=im, offset=offset, method='no_pixel') im_list.append(im) try: N = (n, n) np.testing.assert_raises(ValueError, galsim.utilities.interleaveImages, im_list, N, offset_list) except ImportError: print "The assert_raises tests require nose" offset_list = [] im_list = [] n = 5 DX = np.arange(0., 1., 1. / n) DY = DX for dy in DY: for dx in DX: offset = galsim.PositionD(dx, dy) offset_list.append(offset) im = galsim.Image(16, 16) gal.drawImage(image=im, offset=offset, method='no_pixel') im_list.append(im) try: N = (n, n) np.testing.assert_raises(ValueError, galsim.utilities.interleaveImages, im_list, N, offset_list) except ImportError: print "The assert_raises tests require nose" # 2a) Increase resolution along one direction - square to rectangular images n = 2 g = galsim.Gaussian(sigma=3.7, flux=100.) gal1 = g.shear(g=1. * (n**2 - 1) / (n**2 + 1), beta=0.0 * galsim.radians) im_list = [] offset_list = [] # Generating offsets in a natural way DY = np.arange(0.0, 1.0, 1.0 / (n * n)) DY -= DY.mean() for dy in DY: im = galsim.Image(16, 16) offset = galsim.PositionD(0.0, dy) offset_list.append(offset) gal1.drawImage(im, offset=offset, method='no_pixel', scale=2.0) im_list.append(im) img = galsim.utilities.interleaveImages(im_list, N=[1, n**2], offsets=offset_list, add_flux=False, suppress_warnings=True) im = galsim.Image(16, 16 * n * n) # The interleaved image has the total flux averaged out since `add_flux = False' gal = galsim.Gaussian(sigma=3.7 * n, flux=100.) gal.drawImage(image=im, method='no_pixel', scale=2.0) np.testing.assert_array_equal( im.array, img.array, err_msg="Sheared gaussian not interleaved correctly") assert img.wcs == galsim.JacobianWCS(2.0, 0.0, 0.0, 2. / (n**2)) # 2b) Increase resolution along one direction - rectangular to square images n = 2 g = galsim.Gaussian(sigma=3.7, flux=100.) gal2 = g.shear(g=1. * (n**2 - 1) / (n**2 + 1), beta=90. * galsim.degrees) im_list = [] offset_list = [] # Generating offsets in a natural way DX = np.arange(0.0, 1.0, 1.0 / n**2) DX -= DX.mean() for dx in DX: offset = galsim.PositionD(dx, 0.0) offset_list.append(offset) im = galsim.Image(16, 16 * n * n) gal2.drawImage(im, offset=offset, method='no_pixel', scale=3.0) im_list.append(im) img = galsim.utilities.interleaveImages(im_list, N=np.array([n**2, 1]), offsets=offset_list, suppress_warnings=True) im = galsim.Image(16 * n * n, 16 * n * n) gal = galsim.Gaussian(sigma=3.7, flux=100. * n * n) scale = im_list[0].scale gal.drawImage(image=im, scale=1. * scale / n, method='no_pixel') np.testing.assert_array_equal( im.array, img.array, err_msg="Sheared gaussian not interleaved correctly") assert img.wcs == galsim.JacobianWCS(1. * scale / n**2, 0.0, 0.0, scale) # 3) Check compatability with deInterleaveImage n = 3 g = galsim.Gaussian(sigma=3.7, flux=100.) gal = g.shear( g=0.2, beta=0. * galsim.degrees ) # break symmetry to detect possible bugs in deInterleaveImage im_list = [] offset_list = [] # Generating offsets in the order they would be returned by deInterleaveImage, for convenience for i in xrange(n): for j in xrange(n): im = galsim.Image(16 * n, 16 * n) offset = galsim.PositionD(-(i + 0.5) / n + 0.5, -(j + 0.5) / n + 0.5) offset_list.append(offset) gal.drawImage(image=im, method='no_pixel', offset=offset, scale=0.5) im.setOrigin(3, 3) # for non-trivial bounds im_list.append(im) img = galsim.utilities.interleaveImages(im_list, N=n, offsets=offset_list) im_list_1, offset_list_1 = galsim.utilities.deInterleaveImage(img, N=n) for k in xrange(n**2): assert offset_list_1[k] == offset_list[k] np.testing.assert_array_equal(im_list_1[k].array, im_list[k].array) assert im_list_1[k].wcs == im_list[k].wcs assert im_list[k].origin() == img.origin() assert im_list[k].bounds == im_list_1[k].bounds # Checking for non-default flux option img = galsim.utilities.interleaveImages(im_list, N=n, offsets=offset_list, add_flux=False) im_list_2, offset_list_2 = galsim.utilities.deInterleaveImage( img, N=n, conserve_flux=True) for k in xrange(n**2): assert offset_list_2[k] == offset_list[k] np.testing.assert_array_equal(im_list_2[k].array, im_list[k].array) assert im_list_2[k].wcs == im_list[k].wcs t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def test_psf_config(): """Test building the two PSF types using the config layer. """ data_dir = 'des_data' psfex_file = "DECam_00154912_12_psfcat.psf" fitpsf_file = "DECam_00154912_12_fitpsf.fits" wcs_file = "DECam_00154912_12_header.fits" image_pos = galsim.PositionD(123.45, 543.21) config = { 'input': { 'des_shapelet': { 'dir': data_dir, 'file_name': fitpsf_file }, 'des_psfex': [ { 'dir': data_dir, 'file_name': psfex_file }, { 'dir': data_dir, 'file_name': psfex_file, 'image_file_name': wcs_file }, ] }, 'psf1': { 'type': 'DES_Shapelet' }, 'psf2': { 'type': 'DES_PSFEx', 'num': 0 }, 'psf3': { 'type': 'DES_PSFEx', 'num': 1 }, 'psf4': { 'type': 'DES_Shapelet', 'image_pos': galsim.PositionD(567, 789), 'flux': 179, 'gsparams': { 'folding_threshold': 1.e-4 } }, 'psf5': { 'type': 'DES_PSFEx', 'image_pos': galsim.PositionD(789, 567), 'flux': 388, 'gsparams': { 'folding_threshold': 1.e-4 } }, 'bad1': { 'type': 'DES_Shapelet', 'image_pos': galsim.PositionD(5670, 789) }, # This would normally be set by the config processing. Set it manually here. 'image_pos': image_pos, } galsim.config.ProcessInput(config) psf1a = galsim.config.BuildGSObject(config, 'psf1')[0] fitpsf = galsim.des.DES_Shapelet(fitpsf_file, dir=data_dir) psf1b = fitpsf.getPSF(image_pos) gsobject_compare(psf1a, psf1b) psf2a = galsim.config.BuildGSObject(config, 'psf2')[0] psfex0 = galsim.des.DES_PSFEx(psfex_file, dir=data_dir) psf2b = psfex0.getPSF(image_pos) gsobject_compare(psf2a, psf2b) psf3a = galsim.config.BuildGSObject(config, 'psf3')[0] psfex1 = galsim.des.DES_PSFEx(psfex_file, wcs_file, dir=data_dir) psf3b = psfex1.getPSF(image_pos) gsobject_compare(psf3a, psf3b) gsparams = galsim.GSParams(folding_threshold=1.e-4) psf4a = galsim.config.BuildGSObject(config, 'psf4')[0] psf4b = fitpsf.getPSF(galsim.PositionD(567, 789), gsparams=gsparams).withFlux(179) gsobject_compare(psf4a, psf4b) # Insert a wcs for thes last one. config['wcs'] = galsim.FitsWCS(os.path.join(data_dir, wcs_file)) config = galsim.config.CleanConfig(config) galsim.config.ProcessInput(config) psfex2 = galsim.des.DES_PSFEx(psfex_file, dir=data_dir, wcs=config['wcs']) psf5a = galsim.config.BuildGSObject(config, 'psf5')[0] psf5b = psfex2.getPSF(galsim.PositionD(789, 567), gsparams=gsparams).withFlux(388) gsobject_compare(psf5a, psf5b) del config['image_pos'] galsim.config.RemoveCurrent(config) with assert_raises(galsim.GalSimConfigError): galsim.config.BuildGSObject(config, 'psf1')[0] with assert_raises(galsim.GalSimConfigError): galsim.config.BuildGSObject(config, 'psf2')[0] with assert_raises(galsim.config.gsobject.SkipThisObject): galsim.config.BuildGSObject(config, 'bad1')[0]
cpus = comm.Get_size() # cosmology omega_m0 = 0.31 omega_lam0 = 1 - omega_m0 h = 0.6735 H_0 = 100 * h cosmos = FlatLambdaCDM(H_0, Om0=omega_m0) # Halo parameters Mass = 3*10 ** 13 # M_sun/h conc = 6 # concentration len_z = 0.2 # redshift halo_position = galsim.PositionD(0, 0) # arcsec com_dist_len = cosmos.comoving_distance(len_z).value * h # Mpc/h print("Lens plane at z = %.2f, %.5f Mpc/h" % (len_z, com_dist_len)) # lens profile CF = hk_gglensing_tool.Cosmos_flat(omega_m0, 100*h) CF.NFW_profile_galsim((0,0), Mass, conc, len_z) param_path = "/home/hklee/work/Galaxy_Galaxy_lensing_test/cata/background/continue_source_z_9/params" separation_bin_num = 1 Rmin, Rmax = 0.05, 0.07 # Mpc/h separation_bin = hk_tool_box.set_bin_log(Rmin, Rmax, separation_bin_num + 1) result = numpy.zeros((6, separation_bin_num))
def test_shapelet_fit(): """Test fitting a Shapelet decomposition of an image """ for method, norm in [('no_pixel', 'f'), ('sb', 'sb')]: # We fit a shapelet approximation of a distorted Moffat profile: flux = 20 psf = galsim.Moffat(beta=3.4, half_light_radius=1.2, flux=flux) psf = psf.shear(g1=0.11, g2=0.07).shift(0.03, 0.04) scale = 0.2 pixel = galsim.Pixel(scale) conv = galsim.Convolve([psf, pixel]) im1 = conv.drawImage(scale=scale, method=method) sigma = 1.2 # Match half-light-radius as a decent first approximation. shapelet = galsim.FitShapelet(sigma, 10, im1, normalization=norm) #print('fitted shapelet coefficients = ',shapelet.bvec) # Check flux print('flux = ', shapelet.getFlux(), ' cf. ', flux) np.testing.assert_almost_equal( shapelet.getFlux() / flux, 1., 1, err_msg="Fitted shapelet has the wrong flux") # Test centroid print('centroid = ', shapelet.centroid(), ' cf. ', conv.centroid()) np.testing.assert_almost_equal( shapelet.centroid().x, conv.centroid().x, 2, err_msg="Fitted shapelet has the wrong centroid (x)") np.testing.assert_almost_equal( shapelet.centroid().y, conv.centroid().y, 2, err_msg="Fitted shapelet has the wrong centroid (y)") # Test drawing image from shapelet im2 = im1.copy() shapelet.drawImage(im2, method=method) # Check that images are close to the same: print('norm(diff) = ', np.sum((im1.array - im2.array)**2)) print('norm(im) = ', np.sum(im1.array**2)) print('max(diff) = ', np.max(np.abs(im1.array - im2.array))) print('max(im) = ', np.max(np.abs(im1.array))) peak_scale = np.max(np.abs( im1.array)) * 3 # Check agreement to within 3% of peak value. np.testing.assert_almost_equal( im2.array / peak_scale, im1.array / peak_scale, decimal=2, err_msg="Shapelet version not a good match to original") # Remeasure -- should now be very close to the same. shapelet2 = galsim.FitShapelet(sigma, 10, im2, normalization=norm) np.testing.assert_equal( shapelet.sigma, shapelet2.sigma, err_msg="Second fitted shapelet has the wrong sigma") np.testing.assert_equal( shapelet.order, shapelet2.order, err_msg="Second fitted shapelet has the wrong order") np.testing.assert_almost_equal( shapelet.bvec, shapelet2.bvec, 6, err_msg="Second fitted shapelet coefficients do not match original" ) # Test drawing off center im2 = im1.copy() offset = galsim.PositionD(0.3, 1.4) shapelet.drawImage(im2, method=method, offset=offset) shapelet2 = galsim.FitShapelet(sigma, 10, im2, normalization=norm, center=im2.trueCenter() + offset) np.testing.assert_equal( shapelet.sigma, shapelet2.sigma, err_msg="Second fitted shapelet has the wrong sigma") np.testing.assert_equal( shapelet.order, shapelet2.order, err_msg="Second fitted shapelet has the wrong order") np.testing.assert_almost_equal( shapelet.bvec, shapelet2.bvec, 6, err_msg="Second fitted shapelet coefficients do not match original" )
def measurement_function_NL(profile, e1_inter_vec=[], e2_inter_vec=[], size_inter_vec=[], noise=None, beta=3.566e-7, string='', type='nl'): print " " print "n: ", n logger.info(string) print "beta: ", beta #### Calculate moments without effect image = profile.drawImage(image=galsim.Image(base_size, base_size), scale=pixel_scale / n, method='no_pixel') #print np.amax(image.array), np.sum(image.array) if not noise == None: read_noise = galsim.GaussianNoise(sigma=noise / (n**2)) image.addNoise(read_noise) results = image.FindAdaptiveMom(hsmparams=new_params, precision=1e-7) ref_e1 = results.observed_shape.e1 ref_e2 = results.observed_shape.e2 ref_s = results.moments_sigma print "ref_e1, ref_e2, ref_s", ref_e1, ref_e2, ref_s ## Interleave the profiles im_list = [] offsets_list = [] #create list of images to interleave-no effect for j in xrange(n): for i in xrange(n): im = galsim.Image(base_size, base_size) offset = galsim.PositionD(-(i + 0.5) / n + 0.5, -(j + 0.5) / n + 0.5) offsets_list.append(offset) print "Offset: ", offset profile.drawImage(image=im, scale=pixel_scale, offset=offset, method='no_pixel') if type == 'bf': #cd = PowerLawCD(5, 5.e-7, 5.e-7, 1.5e-7, 1.5e-7, 2.5e-7, 2.5e-7, 1.3) cd = BaseCDModel(aL, aR, aB, aT) im = cd.applyForward(im) elif type == 'nl': im.applyNonlinearity(f, beta) else: print "wrong type: 'bf' or 'nl' " sys.exit(1) im_list.append(im) image = galsim.utilities.interleaveImages(im_list=im_list, N=(n, n), offsets=offsets_list) print "Image shape, after interleave: ", image.array.shape if not noise == None: read_noise = galsim.GaussianNoise(sigma=noise) image.addNoise(read_noise) results = image.FindAdaptiveMom(hsmparams=new_params) e1_inter_vec.append(results.observed_shape.e1 - ref_e1) e2_inter_vec.append(results.observed_shape.e2 - ref_e2) size_inter_vec.append((results.moments_sigma - ref_s) / ref_s)
# and/or other materials provided with the distribution. # # Just some temporary testing stuff if __name__ == "__main__": import numpy as np import matplotlib.pyplot as plt import galsim IMSIZE = 40 # First make a noise field with an even number of elements enoise = galsim.ImageViewD(np.random.randn(IMSIZE, IMSIZE)) encf = galsim.correlatednoise.CorrFunc(enoise) print encf.SBProfile.xValue(galsim.PositionD(0, 0)) # Then make a noise field with an odd number of elements onoise = galsim.ImageViewD(np.random.randn(IMSIZE + 1, IMSIZE + 1)) oncf = galsim.correlatednoise.CorrFunc(onoise) print oncf.SBProfile.xValue(galsim.PositionD(0, 0)) testim = galsim.ImageD(10, 10) cv = encf.SBProfile.getCovarianceMatrix(testim.view(), dx=1.) #plt.pcolor(cv.array); plt.colorbar() # Construct an image with noise correlated in the y direction plt.figure() ynoise = galsim.ImageViewD(enoise.array[:, :] + np.roll(enoise.array, 1, axis=0)) plt.pcolor(ynoise.array)
def test_scattered(): """Test aspects of building an Scattered image """ import copy import time t1 = time.time() # Name some variables to make it easier to be sure they are the same value in the config dict # as when we build the image manually. size = 48 stamp_size = 20 scale = 0.45 flux = 17 sigma = 0.7 x1 = 23.1 y1 = 27.3 x2 = 13.4 y2 = 31.9 x3 = 39.8 y3 = 19.7 # This part of the config will be the same for all tests base_config = {'gal': {'type': 'Gaussian', 'sigma': sigma, 'flux': flux}} # Check that the stamps are centered at the correct location for both odd and even stamp size. base_config['image'] = { 'type': 'Scattered', 'size': size, 'pixel_scale': scale, 'stamp_size': stamp_size, 'image_pos': { 'type': 'XY', 'x': x1, 'y': y1 }, 'nobjects': 1 } for convention in [0, 1]: for test_stamp_size in [stamp_size, stamp_size + 1]: # Deep copy to make sure we don't have any "current_val" caches present. config = copy.deepcopy(base_config) config['image']['stamp_size'] = test_stamp_size config['image']['index_convention'] = convention image, _, _, _ = galsim.config.BuildImage(config) np.testing.assert_equal(image.getXMin(), convention) np.testing.assert_equal(image.getYMin(), convention) xgrid, ygrid = np.meshgrid( np.arange(size) + image.getXMin(), np.arange(size) + image.getYMin()) obs_flux = np.sum(image.array) cenx = np.sum(xgrid * image.array) / flux ceny = np.sum(ygrid * image.array) / flux ixx = np.sum((xgrid - cenx)**2 * image.array) / flux ixy = np.sum((xgrid - cenx) * (ygrid - ceny) * image.array) / flux iyy = np.sum((ygrid - ceny)**2 * image.array) / flux np.testing.assert_almost_equal(obs_flux / flux, 1, decimal=3) np.testing.assert_almost_equal(cenx, x1, decimal=3) np.testing.assert_almost_equal(ceny, y1, decimal=3) np.testing.assert_almost_equal(ixx / (sigma / scale)**2, 1, decimal=1) np.testing.assert_almost_equal(ixy, 0., decimal=3) np.testing.assert_almost_equal(iyy / (sigma / scale)**2, 1, decimal=1) # Check that stamp_xsize, stamp_ysize, image_pos use the object count, rather than the # image count. config = copy.deepcopy(base_config) config['image'] = { 'type': 'Scattered', 'size': size, 'pixel_scale': scale, 'stamp_xsize': { 'type': 'Sequence', 'first': stamp_size }, 'stamp_ysize': { 'type': 'Sequence', 'first': stamp_size }, 'image_pos': { 'type': 'List', 'items': [ galsim.PositionD(x1, y1), galsim.PositionD(x2, y2), galsim.PositionD(x3, y3) ] }, 'nobjects': 3 } image, _, _, _ = galsim.config.BuildImage(config) image2 = galsim.ImageF(size, size, scale=scale) image2.setZero() gal = galsim.Gaussian(sigma=sigma, flux=flux) pix = galsim.Pixel(scale) conv = galsim.Convolve([pix, gal]) for (i, x, y) in [(0, x1, y1), (1, x2, y2), (2, x3, y3)]: stamp = galsim.ImageF(stamp_size + i, stamp_size + i, scale=scale) if (stamp_size + i) % 2 == 0: x += 0.5 y += 0.5 ix = int(np.floor(x + 0.5)) iy = int(np.floor(y + 0.5)) stamp.setCenter(ix, iy) dx = x - ix dy = y - iy conv2 = conv.createShifted(dx * scale, dy * scale) conv2.draw(stamp) b = image2.bounds & stamp.bounds image2[b] += stamp[b] np.testing.assert_almost_equal(image.array, image2.array) t2 = time.time() print 'time for %s = %.2f' % (funcname(), t2 - t1)
def objects_to_galsim(img, objects, psf_model=None, extended='i_extendedness', psf_mag='psf_mag', gal_mag='cmodel_mag', exp_mag='cmodel_exp_mag', dev_mag='cmodel_dev_mag'): """ Convert the HSC objects into GalSim model image. """ try: import galsim except ImportError: raise Exception("# Please install GalSim first!") # Shape of the image img_shape = img.shape # Empty image img_empty = np.zeros(img_shape) # This is the "canvas" of the model img_model = galsim.ImageF(img_shape[1], img_shape[0]) # The "true" center of the image is allowed to be halfway between two pixels, as is the # case for even-sized images. full_image.center is an integer position, # which would be 1/2 pixel up and to the right of the true center in this case. img_center = img_model.true_center # Pass the WCS from the header of the image to GalSim # img_wcs = galsim.AstropyWCS(wcs=cutout_wcs) # Pass the PSF image to GalSim if psf_model is not None: psf_obj = galsim.InterpolatedImage(galsim.image.Image(psf_model), scale=1.0) else: psf_obj = None # Go through the list of objects, there appears to be no other way than a for loop for ii, obj in enumerate(objects): # Position of the image img_position = galsim.PositionD(obj['x'], obj['y']) # Get the offset of the object on the canvas obj_offset = galsim.PositionD(img_position.x - img_center.x + 1., img_position.y - img_center.y + 1.) # Seperate the type of the object if obj[extended] < 0.5: if np.isfinite(obj[psf_mag]) & (obj[psf_mag] > 0): # Generate a star psf_flux = abmag_to_image(obj[psf_mag]) psf = psf_obj.withFlux(psf_flux) # Add it to the empty image img_empty += psf.drawImage(img_model, offset=obj_offset).array else: print("# Cannot generate star {} with magnitude {}".format( ii, obj[psf_mag])) else: if (np.isfinite(obj[exp_mag]) & (obj[exp_mag] > 0) & np.isfinite(obj[dev_mag]) & (obj[dev_mag] > 0) & np.isfinite(obj[gal_mag]) & (obj[gal_mag] > 0)): try: # The exponential component flux_exp = abmag_to_image(obj[exp_mag]) shape_exp = galsim.Shear( q=obj['cmodel_exp_ellipse_ba'], beta=obj['cmodel_exp_ellipse_theta'] * galsim.degrees) comp_exp = galsim.Exponential( half_light_radius=obj['cmodel_exp_ellipse_r'], flux=flux_exp) comp_exp = comp_exp.shear(shape_exp) # The De Vacouleurs component flux_dev = abmag_to_image(obj[dev_mag]) shape_dev = galsim.Shear( q=obj['cmodel_dev_ellipse_ba'], beta=obj['cmodel_dev_ellipse_theta'] * galsim.degrees) comp_dev = galsim.DeVaucouleurs( half_light_radius=obj['cmodel_dev_ellipse_r'], flux=flux_dev, trunc=(6.0 * obj['cmodel_dev_ellipse_r'])) comp_dev = comp_dev.shear(shape_dev) # Combine the two component cmodel = galsim.Add([comp_exp, comp_dev]) if psf_obj is not None: # Convolution with PSF cmodel = galsim.Convolve([cmodel, psf_obj]) # Add it to the empty image img_empty += cmodel.drawImage(img_model, method='no_pixel', offset=obj_offset).array except Exception: print( "# Cannot generate galaxy {} with magnitude {}".format( ii, obj[gal_mag])) else: print("# Problematic galaxy {} with magnitude {}".format( ii, obj[gal_mag])) return img_empty
def _get_local_jacobian(self, *, x, y): return self.wcs.jacobian(image_pos=galsim.PositionD(x=x + 1, y=y + 1))