def test_chipnum(): """Test the ability to renumber the chipnums """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file=os.path.join('output', 'test_input_chipnum.log')) # First, the default is to just use the index in the image list dir = 'input' image_files = 'test_input_image_*.fits' cat_files = 'test_input_cat_*.fits' config = { 'dir': dir, 'image_file_name': image_files, 'cat_file_name': cat_files } input = piff.InputFiles(config, logger=logger) assert input.chipnums == list(range(3)) # Now make the chipnums something else config = { 'dir': dir, 'image_file_name': image_files, 'cat_file_name': cat_files, 'chipnum': [5, 6, 7] } input = piff.InputFiles(config, logger=logger) assert input.chipnums == [i + 5 for i in range(3)] # Use the GalSim Eval capability to get the index + 1 config = { 'dir': dir, 'image_file_name': image_files, 'cat_file_name': cat_files, 'chipnum': '$image_num + 1' } input = piff.InputFiles(config, logger=logger) assert input.chipnums == [i + 1 for i in range(3)] # Or parse it from the image_file_name config = { 'dir': dir, 'image_file_name': image_files, 'cat_file_name': cat_files, 'chipnum': { 'type': 'Eval', 'str': "image_file_name.split('_')[-1].split('.')[0]", 'simage_file_name': '@input.image_file_name' } } input = piff.InputFiles(config, logger=logger) assert input.chipnums == list(range(3))
def test_boolarray(): """Test the ability to use a flag_col that is really a boolean array rather than ints. """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file=os.path.join('output', 'test_input_bool.log')) cat_file_name = os.path.join('input', 'test_input_cat_00.fits') data = fitsio.read(cat_file_name) print('flag = ', data['flag']) new_flag = np.empty((len(data), 50), dtype=bool) for bit in range(50): new_flag[:, bit] = data['flag'] & 2**bit != 0 print('new_flag = ', new_flag) # Write out the catalog to a file print('dtype = ', new_flag.dtype) dtype = [('x', 'f8'), ('y', 'f8'), ('flag', bool, 50)] new_data = np.empty(len(data), dtype=dtype) new_data['x'] = data['x'] new_data['y'] = data['y'] new_data['flag'] = new_flag new_cat_file_name = os.path.join('input', 'test_input_boolarray.fits') fitsio.write(new_cat_file_name, new_data, clobber=True) # Specifiable columns are: x, y, flag, use, sky, gain. (We'll do flag, use below.) config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_boolarray.fits', 'x_col': 'x', 'y_col': 'y', 'flag_col': 'flag', 'skip_flag': '$2**1 + 2**2 + 2**39' } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, _, image_pos, _, _, _ = input.getRawImageData(0) print('len = ', len(image_pos)) assert len(image_pos) == 80
def test_single_image(): """Test the simple case of one image and one catalog. """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file='output/test_single_image.log') # Make the image image = galsim.Image(2048, 2048, scale=0.26) # Where to put the stars. Include some flagged and not used locations. x_list = [ 123.12, 345.98, 567.25, 1094.94, 924.15, 1532.74, 1743.11, 888.39, 1033.29, 1409.31 ] y_list = [ 345.43, 567.45, 1094.32, 924.29, 1532.92, 1743.83, 888.83, 1033.19, 1409.20, 123.11 ] flag_list = [1, 1, 13, 1, 1, 4, 1, 1, 0, 1] # Draw a Gaussian PSF at each location on the image. sigma = 1.3 g1 = 0.23 g2 = -0.17 psf = galsim.Gaussian(sigma=sigma).shear(g1=g1, g2=g2) for x, y, flag in zip(x_list, y_list, flag_list): bounds = galsim.BoundsI(int(x - 31), int(x + 32), int(y - 31), int(y + 32)) offset = galsim.PositionD(x - int(x) - 0.5, y - int(y) - 0.5) psf.drawImage(image=image[bounds], method='no_pixel', offset=offset) # corrupt the ones that are marked as flagged if flag & 4: print('corrupting star at ', x, y) ar = image[bounds].array im_max = np.max(ar) * 0.2 ar[ar > im_max] = im_max image.addNoise( galsim.GaussianNoise(rng=galsim.BaseDeviate(1234), sigma=1e-6)) # Write out the image to a file image_file = os.path.join('output', 'simple_image.fits') image.write(image_file) # Write out the catalog to a file dtype = [('x', 'f8'), ('y', 'f8'), ('flag', 'i2')] data = np.empty(len(x_list), dtype=dtype) data['x'] = x_list data['y'] = y_list data['flag'] = flag_list cat_file = os.path.join('output', 'simple_cat.fits') fitsio.write(cat_file, data, clobber=True) # Use InputFiles to read these back in config = {'image_file_name': image_file, 'cat_file_name': cat_file} input = piff.InputFiles(config, logger=logger) assert input.image_file_name == [image_file] assert input.cat_file_name == [cat_file] # Check image assert input.nimages == 1 image1, _, image_pos, _, _, _ = input.getRawImageData(0) np.testing.assert_equal(image1.array, image.array) # Check catalog np.testing.assert_equal([pos.x for pos in image_pos], x_list) np.testing.assert_equal([pos.y for pos in image_pos], y_list) # Repeat, using flag columns this time. config = { 'image_file_name': image_file, 'cat_file_name': cat_file, 'flag_col': 'flag', 'use_flag': '1', 'skip_flag': '4', 'stamp_size': 48 } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, _, image_pos, _, _, _ = input.getRawImageData(0) assert len(image_pos) == 7 # Make star data orig_stars = input.makeStars() assert len(orig_stars) == 7 assert orig_stars[0].image.array.shape == (48, 48) # Process the star data # can only compare to truth if include_pixel=False model = piff.Gaussian(fastfit=True, include_pixel=False) interp = piff.Mean() fitted_stars = [model.fit(model.initialize(star)) for star in orig_stars] interp.solve(fitted_stars) print('mean = ', interp.mean) # Check that the interpolation is what it should be # Any position would work here. chipnum = 0 x = 1024 y = 123 orig_wcs = input.getWCS()[chipnum] orig_pointing = input.getPointing() image_pos = galsim.PositionD(x, y) world_pos = piff.StarData.calculateFieldPos(image_pos, orig_wcs, orig_pointing) u, v = world_pos.x, world_pos.y stamp_size = config['stamp_size'] target = piff.Star.makeTarget(x=x, y=y, u=u, v=v, wcs=orig_wcs, stamp_size=stamp_size, pointing=orig_pointing) true_params = [sigma, g1, g2] test_star = interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Check default values of options psf = piff.SimplePSF(model, interp) assert psf.chisq_thresh == 0.1 assert psf.max_iter == 30 assert psf.outliers == None assert psf.extra_interp_properties == [] # Now test running it via the config parser psf_file = os.path.join('output', 'simple_psf.fits') config = { 'input': { 'image_file_name': image_file, 'cat_file_name': cat_file, 'flag_col': 'flag', 'use_flag': 1, 'skip_flag': 4, 'stamp_size': stamp_size }, 'psf': { 'model': { 'type': 'Gaussian', 'fastfit': True, 'include_pixel': False }, 'interp': { 'type': 'Mean' }, 'max_iter': 10, 'chisq_thresh': 0.2, }, 'output': { 'file_name': psf_file }, } orig_stars, wcs, pointing = piff.Input.process(config['input'], logger) # Use a SimplePSF to process the stars data this time. interp = piff.Mean() psf = piff.SimplePSF(model, interp, max_iter=10, chisq_thresh=0.2) assert psf.chisq_thresh == 0.2 assert psf.max_iter == 10 psf.fit(orig_stars, wcs, pointing, logger=logger) test_star = psf.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # test that drawStar and drawStarList work test_star = psf.drawStar(target) test_star_list = psf.drawStarList([target])[0] np.testing.assert_equal(test_star.fit.params, test_star_list.fit.params) np.testing.assert_equal(test_star.image.array, test_star_list.image.array) # test copy_image property of drawStar and draw for draw in [psf.drawStar, psf.model.draw]: target_star_copy = psf.interp.interpolate( piff.Star(target.data.copy(), target.fit.copy())) # interp is so that when we do psf.model.draw we have fit.params to work with test_star_copy = draw(target_star_copy, copy_image=True) test_star_nocopy = draw(target_star_copy, copy_image=False) # if we modify target_star_copy, then test_star_nocopy should be modified, # but not test_star_copy target_star_copy.image.array[0, 0] = 23456 assert test_star_nocopy.image.array[ 0, 0] == target_star_copy.image.array[0, 0] assert test_star_copy.image.array[0, 0] != target_star_copy.image.array[0, 0] # however the other pixels SHOULD still be all the same value assert test_star_nocopy.image.array[ 1, 1] == target_star_copy.image.array[1, 1] assert test_star_copy.image.array[1, 1] == target_star_copy.image.array[1, 1] # test that draw works test_image = psf.draw(x=target['x'], y=target['y'], stamp_size=config['input']['stamp_size'], flux=target.fit.flux, offset=target.fit.center) # this image should be the same values as test_star assert test_image == test_star.image # test that draw does not copy the image image_ref = psf.draw(x=target['x'], y=target['y'], stamp_size=config['input']['stamp_size'], flux=target.fit.flux, offset=target.fit.center, image=test_image) image_ref.array[0, 0] = 123456789 assert test_image.array[0, 0] == image_ref.array[0, 0] assert test_star.image.array[0, 0] != test_image.array[0, 0] assert test_star.image.array[1, 1] == test_image.array[1, 1] # Round trip to a file psf.write(psf_file, logger) psf2 = piff.read(psf_file, logger) assert type(psf2.model) is piff.Gaussian assert type(psf2.interp) is piff.Mean assert psf2.chisq == psf.chisq assert psf2.last_delta_chisq == psf.last_delta_chisq assert psf2.chisq_thresh == psf.chisq_thresh assert psf2.max_iter == psf.max_iter assert psf2.dof == psf.dof assert psf2.nremoved == psf.nremoved test_star = psf2.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Do the whole thing with the config parser os.remove(psf_file) piff.piffify(config, logger) psf3 = piff.read(psf_file) assert type(psf3.model) is piff.Gaussian assert type(psf3.interp) is piff.Mean assert psf3.chisq == psf.chisq assert psf3.last_delta_chisq == psf.last_delta_chisq assert psf3.chisq_thresh == psf.chisq_thresh assert psf3.max_iter == psf.max_iter assert psf3.dof == psf.dof assert psf3.nremoved == psf.nremoved test_star = psf3.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Test using the piffify executable os.remove(psf_file) # This would be simpler as a direct assignment, but this once, test the way you would set # this from the command line, which would call parse_variables. piff.config.parse_variables(config, ['verbose=0'], logger=logger) #config['verbose'] = 0 with open('simple.yaml', 'w') as f: f.write(yaml.dump(config, default_flow_style=False)) config2 = piff.config.read_config('simple.yaml') assert config == config2 piffify_exe = get_script_name('piffify') p = subprocess.Popen([piffify_exe, 'simple.yaml']) p.communicate() psf4 = piff.read(psf_file) assert type(psf4.model) is piff.Gaussian assert type(psf4.interp) is piff.Mean assert psf4.chisq == psf.chisq assert psf4.last_delta_chisq == psf.last_delta_chisq assert psf4.chisq_thresh == psf.chisq_thresh assert psf4.max_iter == psf.max_iter assert psf4.dof == psf.dof assert psf4.nremoved == psf.nremoved test_star = psf4.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # With very low max_iter, we hit the warning about non-convergence config['psf']['max_iter'] = 1 with CaptureLog(level=1) as cl: piff.piffify(config, cl.logger) assert 'PSF fit did not converge' in cl.output
def test_basic(): """Test the (usual) basic kind of input field without too many bells and whistles. """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file=os.path.join('output', 'test_input_basic.log')) dir = 'input' image_file = 'test_input_image_00.fits' cat_file = 'test_input_cat_00.fits' # Simple with one image, cat config = { 'dir': dir, 'image_file_name': image_file, 'cat_file_name': cat_file } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, _, image_pos, _, _, _ = input.getRawImageData(0) assert len(image_pos) == 100 # Can omit the dir and just inlcude it in the file names config = { 'image_file_name': os.path.join(dir, image_file), 'cat_file_name': os.path.join(dir, cat_file) } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, _, image_pos, _, _, _ = input.getRawImageData(0) assert len(image_pos) == 100 # 3 images in a list image_files = ['test_input_image_%02d.fits' % k for k in range(3)] cat_files = ['test_input_cat_%02d.fits' % k for k in range(3)] config = { 'dir': dir, 'image_file_name': image_files, 'cat_file_name': cat_files } input = piff.InputFiles(config, logger=logger) assert input.nimages == 3 for i in range(3): _, _, image_pos, _, _, _ = input.getRawImageData(i) assert len(image_pos) == 100 # Again without dir. image_files = ['input/test_input_image_%02d.fits' % k for k in range(3)] cat_files = ['input/test_input_cat_%02d.fits' % k for k in range(3)] config = {'image_file_name': image_files, 'cat_file_name': cat_files} input = piff.InputFiles(config, logger=logger) assert input.nimages == 3 for i in range(3): _, _, image_pos, _, _, _ = input.getRawImageData(i) assert len(image_pos) == 100 # 3 images using glob image_files = 'test_input_image_*.fits' cat_files = 'test_input_cat_*.fits' config = { 'dir': dir, 'image_file_name': image_files, 'cat_file_name': cat_files } input = piff.InputFiles(config, logger=logger) assert input.nimages == 3 for i in range(3): _, _, image_pos, _, _, _ = input.getRawImageData(i) assert len(image_pos) == 100 # Can limit the number of stars config['nstars'] = 37 input = piff.InputFiles(config, logger=logger) assert input.nimages == 3 for i in range(3): _, _, image_pos, _, _, _ = input.getRawImageData(i) assert len(image_pos) == 37 # Can limit stars differently on each chip config[ 'nstars'] = '$0 if @image_num == 1 else 20 if @image_num == 2 else 40' input = piff.InputFiles(config, logger=logger) assert input.nimages == 3 for i in range(3): _, _, image_pos, _, _, _ = input.getRawImageData(i) if i == 0: assert len(image_pos) == 40 elif i == 1: assert len(image_pos) == 0 else: assert len(image_pos) == 20 # Semi-gratuitous use of reserve_frac when one image has no stars for coverage config['reserve_frac'] = 0.2 config['use_partial'] = True config['ra'] = 6.0 config['dec'] = -30.0 input = piff.InputFiles(config, logger=logger) assert input.nimages == 3 stars = input.makeStars(logger=logger) print('stars = ', stars) print('len stars = ', len(stars)) assert len(stars) == 60 assert len([s for s in stars if s.chipnum == 0]) == 40 assert len([s for s in stars if s.chipnum == 1]) == 0 assert len([s for s in stars if s.chipnum == 2]) == 20 reserve_stars = [s for s in stars if s.is_reserve] assert len(reserve_stars) == 12 assert len([s for s in reserve_stars if s['chipnum'] == 0]) == 8 assert len([s for s in reserve_stars if s['chipnum'] == 1]) == 0 assert len([s for s in reserve_stars if s['chipnum'] == 2]) == 4 # If no stars, raise error # (normally because all stars have errors, but easier to just limit to 0 to test this.) config['nstars'] = 0 with np.testing.assert_raises(RuntimeError): input = piff.Input.process(config, logger=logger)
def test_stars(): """Test the input.makeStars function """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file=os.path.join('output', 'test_input_stars.log')) dir = 'input' image_file = 'test_input_image_00.fits' cat_file = 'test_input_cat_00.fits' # Turn off two defaults for now (max_snr=100 and use_partial=False) config = { 'dir': dir, 'image_file_name': image_file, 'cat_file_name': cat_file, 'weight_hdu': 1, 'sky_col': 'sky', 'gain_col': 'gain', 'max_snr': 0, 'use_partial': True, } input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 100 chipnum_list = [star['chipnum'] for star in stars] gain_list = [star['gain'] for star in stars] snr_list = [star['snr'] for star in stars] snr_list2 = [ piff.util.calculateSNR(star.data.image, star.data.orig_weight) for star in stars ] print('snr = ', np.min(snr_list), np.max(snr_list)) np.testing.assert_array_equal(chipnum_list, 0) np.testing.assert_array_equal(gain_list, gain_list[0]) np.testing.assert_almost_equal(snr_list, snr_list2, decimal=5) print('min_snr = ', np.min(snr_list)) print('max_snr = ', np.max(snr_list)) assert np.min(snr_list) < 20. assert np.max(snr_list) > 600. # max_snr increases the noise to achieve a maximum snr config['max_snr'] = 120 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 100 snr_list = [star['snr'] for star in stars] print('snr = ', np.min(snr_list), np.max(snr_list)) assert np.min(snr_list) < 20. assert np.max(snr_list) == 120. snr_list2 = [ piff.util.calculateSNR(star.data.image, star.data.orig_weight) for star in stars ] snr_list = np.array(snr_list) snr_list2 = np.array(snr_list2) lo = np.where(snr_list < 120) hi = np.where(snr_list == 120) # Uncorrected stars still have the same snr np.testing.assert_almost_equal(snr_list[lo], snr_list2[lo], decimal=5) # Corrected ones come out a little lower than the target. assert np.all(snr_list2[hi] <= 120.) assert np.all(snr_list2[hi] > 110.) # The default is max_snr == 100 del config['max_snr'] input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 100 snr_list = np.array([star['snr'] for star in stars]) print('snr = ', np.min(snr_list), np.max(snr_list)) assert np.min(snr_list) < 20. assert np.max(snr_list) == 100. snr_list2 = [ piff.util.calculateSNR(star.data.image, star.data.orig_weight) for star in stars ] snr_list = np.array(snr_list) snr_list2 = np.array(snr_list2) lo = np.where(snr_list < 100) hi = np.where(snr_list == 100) np.testing.assert_almost_equal(snr_list[lo], snr_list2[lo], decimal=5) assert np.all(snr_list2[hi] <= 100.) # min_snr removes stars with a snr < min_snr config['min_snr'] = 50 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('len should be ', len(snr_list[snr_list >= 50])) print('actual len is ', len(stars)) assert len(stars) == len(snr_list[snr_list >= 50]) assert len(stars) == 93 # hard-coded for this case, just to make sure snr_list = np.array([star['snr'] for star in stars]) print('snr = ', np.min(snr_list), np.max(snr_list)) assert np.min(snr_list) >= 50. assert np.max(snr_list) == 100. snr_list2 = [ piff.util.calculateSNR(star.data.image, star.data.orig_weight) for star in stars ] snr_list = np.array(snr_list) snr_list2 = np.array(snr_list2) lo = np.where(snr_list < 100) hi = np.where(snr_list == 100) np.testing.assert_almost_equal(snr_list[lo], snr_list2[lo], decimal=5) assert np.all(snr_list2[hi] <= 100.) # use_partial=False will skip any stars that are partially off the edge of the image config['use_partial'] = False input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 91 # skipped 2 additional stars # use_partial=False is the default del config['use_partial'] input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 91 # Setting satur will skip any stars with a pixel above that value. # Here there is 1 star with a pixel > 2000. config['satur'] = 'SATURAT' input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 90 # 7 stars have pixels > 1900 config['satur'] = 1900 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 84 del config['satur'] # hsm_size_reject=True rejects a few of these. config['hsm_size_reject'] = True input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 88 # hsm_size_reject can also be a float. (True is equivalent to 10.) config['hsm_size_reject'] = 100. input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 90 config['hsm_size_reject'] = 3. input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 84 config['hsm_size_reject'] = 10. input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 88 del config['hsm_size_reject'] # alt_x and alt_y also include some object completely off the image, which are always skipped. # (Don't do the min_snr anymore, since most of these stamps don't actually have any signal.) config['x_col'] = 'alt_x' config['y_col'] = 'alt_y' del config['min_snr'] input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 37 # Also skip objects which are all weight=0 config['weight_hdu'] = 3 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 0 # But not ones that are only partially weight=0 config['weight_hdu'] = 8 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 37 # Check the masked pixel cut # This is a bit artificial, b/c 512 / 1024 of the pixels are masked in the test case del config['x_col'] del config['y_col'] config['weight_hdu'] = 8 config['max_mask_pixels'] = 513 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 98 config['max_mask_pixels'] = 500 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 0 # Check the edge fraction cut # with use_partial=True to make sure it catch edge case del config['max_mask_pixels'] config['max_edge_frac'] = 0.25 config['use_partial'] = True input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 94 # Check that negative snr flux yields 0, not an error (from sqrt(neg)) # Negative flux is actually ok, since it gets squared, but if an image has negative weights # (which would be weird of course), then it could get to negative flux = wI^2. star0 = stars[0] star0.data.orig_weight *= -1. snr0 = piff.util.calculateSNR(star0.data.image, star0.data.orig_weight) assert snr0 == 0.
def test_lsst_weight(): """Test the way LSSTDM stores the weight (as a variance including signal) """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file=os.path.join('output', 'test_input_lsst.log')) # First with a sky level. This isn't actually how LSST calexps are made (they are sky # subtracted), but it is how the input images are made. config = { 'image_file_name': 'input/test_input_image_00.fits', 'cat_file_name': 'input/test_input_cat_00.fits', 'weight_hdu': 10, 'sky': 'SKYLEVEL', 'gain': 'GAIN_A', 'invert_weight': True, 'remove_signal_from_weight': True, } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 image, weight, image_pos, sky, gain, _ = input.getRawImageData(0) assert len(image_pos) == 100 assert image.array.shape == (1024, 1024) assert weight.array.shape == (1024, 1024) gain = gain[0] sky = sky[0] read_noise = 10 expected_noise = sky / gain + read_noise**2 / gain**2 print('expected noise = ', expected_noise) print('var = ', weight.array**-1) np.testing.assert_allclose(weight.array, expected_noise**-1, rtol=1.e-6) # If the gain is not given, it can determine it automatically. config = { 'image_file_name': 'input/test_input_image_00.fits', 'cat_file_name': 'input/test_input_cat_00.fits', 'weight_hdu': 10, 'sky': 'SKYLEVEL', 'invert_weight': True, 'remove_signal_from_weight': True, } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 image, weight, image_pos, sky, gain1, _ = input.getRawImageData(0) np.testing.assert_allclose(gain1, gain, rtol=1.e-6) np.testing.assert_allclose(weight.array, expected_noise**-1, rtol=1.e-6) # Now pretend that the sky is part of the signal, so the input can match how we would # do this when running on calexps. config = { 'image_file_name': 'input/test_input_image_00.fits', 'cat_file_name': 'input/test_input_cat_00.fits', 'weight_hdu': 10, 'gain': 'GAIN_A', 'invert_weight': True, 'remove_signal_from_weight': True, } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 image, weight, image_pos, _, gain, _ = input.getRawImageData(0) assert len(image_pos) == 100 assert image.array.shape == (1024, 1024) assert weight.array.shape == (1024, 1024) gain = gain[0] read_noise = 10 expected_noise = read_noise**2 / gain**2 print('expected noise = ', expected_noise) print('var = ', weight.array**-1) np.testing.assert_allclose(weight.array, expected_noise**-1, rtol=1.e-5)
def test_weight(): """Test the weight map and bad pixel masks """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file=os.path.join('output', 'test_input_weight.log')) # If no weight or badpix is specified, the weights are all equal. config = { 'image_file_name': 'input/test_input_image_00.fits', 'cat_file_name': 'input/test_input_cat_00.fits', } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 image, weight, image_pos, _, _, _ = input.getRawImageData(0) assert len(image_pos) == 100 assert image.array.shape == (1024, 1024) assert weight.array.shape == (1024, 1024) np.testing.assert_array_equal(weight.array, 1.0) # The default weight and badpix masks that GalSim makes don't do any masking, so this # is the almost the same as above, but the weight value is 1/sky. config = { 'image_file_name': 'input/test_input_image_00.fits', 'cat_file_name': 'input/test_input_cat_00.fits', 'weight_hdu': 1, 'badpix_hdu': 2, 'sky_col': 'sky', # Used to determine what the value of weight should be 'gain_col': 'gain', } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 image, weight, image_pos, sky, gain, _ = input.getRawImageData(0) assert len(image_pos) == 100 assert image.array.shape == (1024, 1024) assert weight.array.shape == (1024, 1024) sky = sky[0] gain = gain[0] read_noise = 10 expected_noise = sky / gain + read_noise**2 / gain**2 np.testing.assert_almost_equal(weight.array, expected_noise**-1) # Can set the noise by hand config = { 'image_file_name': 'input/test_input_image_00.fits', 'cat_file_name': 'input/test_input_cat_00.fits', 'noise': 32, } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, weight, _, _, _, _ = input.getRawImageData(0) assert weight.array.shape == (1024, 1024) np.testing.assert_almost_equal(weight.array, 32.**-1) # Some old versions of fitsio had a bug where the badpix mask could be offset by 32768. # We move them back to 0 config = { 'image_file_name': 'input/test_input_image_00.fits', 'cat_file_name': 'input/test_input_cat_00.fits', 'weight_hdu': 1, 'badpix_hdu': 5, } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, weight, _, _, _, _ = input.getRawImageData(0) assert weight.array.shape == (1024, 1024) np.testing.assert_almost_equal(weight.array, expected_noise**-1) config['badpix_hdu'] = 6 input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, weight, _, _, _, _ = input.getRawImageData(0) assert weight.array.shape == (1024, 1024) np.testing.assert_almost_equal(weight.array, expected_noise**-1) # Various ways to get all weight values == 0 (which will emit a logger message, but isn't # an error). config['weight_hdu'] = 1 config['badpix_hdu'] = 7 # badpix > 0 input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, weight, _, _, _, _ = input.getRawImageData(0) assert weight.array.shape == (1024, 1024) np.testing.assert_almost_equal(weight.array, 0.) config['weight_hdu'] = 3 # wt = 0 config['badpix_hdu'] = 2 input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, weight, _, _, _, _ = input.getRawImageData(0) assert weight.array.shape == (1024, 1024) np.testing.assert_almost_equal(weight.array, 0.) config['weight_hdu'] = 8 # Even cols are = 0 config['badpix_hdu'] = 9 # Odd cols are > 0 input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, weight, _, _, _, _ = input.getRawImageData(0) assert weight.array.shape == (1024, 1024) np.testing.assert_almost_equal(weight.array, 0.) # Negative valued weights are invalid config['weight_hdu'] = 4 input = piff.InputFiles(config) with CaptureLog() as cl: _, weight, _, _, _, _ = input.getRawImageData(0, logger=cl.logger) assert 'Warning: weight map has invalid negative-valued pixels.' in cl.output
def test_cols(): """Test the various allowed column specifications """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file=os.path.join('output', 'test_input_cols.log')) # Specifiable columns are: x, y, flag, use, sky, gain. (We'll do flag, use below.) config = { 'dir': 'input', 'image_file_name': 'test_input_image_02.fits', 'cat_file_name': 'test_input_cat_02.fits', 'x_col': 'x', 'y_col': 'y', } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, _, image_pos, _, _, _ = input.getRawImageData(0) assert len(image_pos) == 100 # Can do ra, dec instead of x, y config = { 'dir': 'input', 'image_file_name': 'test_input_image_02.fits', 'cat_file_name': 'test_input_cat_02.fits', 'ra_col': 'ra', 'dec_col': 'dec', 'ra_units': 'hours', 'dec_units': 'degrees', } input2 = piff.InputFiles(config, logger=logger) assert input2.nimages == 1 _, _, image_pos2, _, _, _ = input2.getRawImageData(0) print('input.image_pos = ', image_pos) print('input2.image_pos = ', image_pos2) assert len(image_pos2) == 100 x1 = [pos.x for pos in image_pos] x2 = [pos.x for pos in image_pos2] y1 = [pos.y for pos in image_pos] y2 = [pos.y for pos in image_pos2] np.testing.assert_allclose(x2, x1) np.testing.assert_allclose(y2, y1) # Back to first file, where we also have header values for things. cat_file_name = os.path.join('input', 'test_input_cat_00.fits') data = fitsio.read(cat_file_name) sky = np.mean(data['sky']) gain = np.mean(data['gain']) config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_cat_00.fits', 'x_col': 'x', 'y_col': 'y', 'sky_col': 'sky', 'gain_col': 'gain', } input = piff.InputFiles(config, logger=logger) assert input.nimages == 1 _, _, image_pos, sky_list, gain_list, _ = input.getRawImageData(0) assert len(image_pos) == 100 assert len(sky_list) == 100 assert len(gain_list) == 100 # sky and gain are constant (although they don't have to be of course) np.testing.assert_array_equal(sky_list, sky) np.testing.assert_array_equal(gain_list, gain) # sky and gain can also be given as float values for the whole catalog config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_cat_00.fits', 'sky': sky, 'gain': gain, } input = piff.InputFiles(config, logger=logger) _, _, image_pos, sky_list, gain_list, _ = input.getRawImageData(0) assert len(image_pos) == 100 assert len(sky_list) == 100 assert len(gain_list) == 100 # These aren't precisely equal because we go through a str value, which truncates it. # We could hack this to keep it exact, but it's probably not worth it and it's easier to # enable both str and float by reading it as str and then trying the float conversion to see # it if works. Anyway, that's why this is only decimal=9. np.testing.assert_almost_equal(sky_list, sky, decimal=9) np.testing.assert_almost_equal(gain_list, gain, decimal=9) # sky and gain can also be given as str values, which mean look in the FITS header. config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_cat_00.fits', 'sky': 'SKYLEVEL', 'gain': 'GAIN_A', } input = piff.InputFiles(config, logger=logger) _, _, image_pos, sky_list, gain_list, _ = input.getRawImageData(0) assert len(image_pos) == 100 assert len(sky_list) == 100 assert len(gain_list) == 100 np.testing.assert_almost_equal(sky_list, sky) np.testing.assert_almost_equal(gain_list, gain) # including satur will skip stars that are over the given saturation value. # (It won't skip them here, just when building the stars list.) config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_cat_00.fits', 'sky': 'SKYLEVEL', 'gain': 'GAIN_A', 'satur': 2000, } input = piff.InputFiles(config, logger=logger) _, _, image_pos, _, _, satur = input.getRawImageData(0) assert satur == 2000 assert len(image_pos) == 100 config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_cat_00.fits', 'sky': 'SKYLEVEL', 'gain': 'GAIN_A', 'satur': 'SATURAT', } input = piff.InputFiles(config, logger=logger) _, _, image_pos, _, _, satur = input.getRawImageData(0) assert satur == 2000 assert len(image_pos) == 100 # Using flag will skip flagged columns. Here every 5th item is flagged. config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_cat_00.fits', 'flag_col': 'flag', 'skip_flag': 4 } input = piff.InputFiles(config, logger=logger) _, _, image_pos, _, _, _ = input.getRawImageData(0) assert input.nimages == 1 print('len = ', len(image_pos)) assert len(image_pos) == 80 # Similarly the use columns will skip anything with use == 0 (every 7th item here) config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_cat_00.fits', 'flag_col': 'flag', 'use_flag': 1 } input = piff.InputFiles(config, logger=logger) _, _, image_pos, _, _, _ = input.getRawImageData(0) print('len = ', len(image_pos)) assert len(image_pos) == 85 # Can do both config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_cat_00.fits', 'flag_col': 'flag', 'skip_flag': '$2**2', 'use_flag': '$2**0', } input = piff.InputFiles(config, logger=logger) _, _, image_pos, _, _, _ = input.getRawImageData(0) print('len = ', len(image_pos)) assert len(image_pos) == 68 # If no skip_flag is specified, it skips all != 0. config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_cat_00.fits', 'flag_col': 'flag', } input = piff.InputFiles(config, logger=logger) _, _, image_pos, _, _, _ = input.getRawImageData(0) print('len = ', len(image_pos)) assert len(image_pos) == 12 # Check invalid column names base_config = { 'dir': 'input', 'image_file_name': 'test_input_image_00.fits', 'cat_file_name': 'test_input_cat_00.fits', } input = piff.InputFiles(dict(x_col='xx', **base_config)) np.testing.assert_raises(ValueError, input.getRawImageData, 0) input = piff.InputFiles(dict(x_col='xx', y_col='y', **base_config)) np.testing.assert_raises(ValueError, input.getRawImageData, 0) input = piff.InputFiles(dict(x_col='x', y_col='xx', **base_config)) np.testing.assert_raises(ValueError, input.getRawImageData, 0) input = piff.InputFiles(dict(ra_col='xx', **base_config)) np.testing.assert_raises(ValueError, input.getRawImageData, 0) input = piff.InputFiles(dict(ra_col='xx', dec_col='y', **base_config)) np.testing.assert_raises(ValueError, input.getRawImageData, 0) input = piff.InputFiles(dict(ra_col='x', dec_col='xx', **base_config)) np.testing.assert_raises(ValueError, input.getRawImageData, 0) input = piff.InputFiles(dict(sky_col='xx', **base_config)) np.testing.assert_raises(ValueError, input.getRawImageData, 0) input = piff.InputFiles(dict(gain_col='xx', **base_config)) np.testing.assert_raises(ValueError, input.getRawImageData, 0) input = piff.InputFiles(dict(flag_col='xx', **base_config)) np.testing.assert_raises(ValueError, input.getRawImageData, 0) # skip_flag, use_flag need to be integers np.testing.assert_raises( ValueError, piff.InputFiles, dict(flag_col='flag', skip_flag='xx', **base_config)) np.testing.assert_raises( ValueError, piff.InputFiles, dict(flag_col='flag', use_flag='xx', **base_config)) # Can't give duplicate sky, gain np.testing.assert_raises(ValueError, piff.InputFiles, dict(sky_col='sky', sky=3, **base_config)) np.testing.assert_raises(ValueError, piff.InputFiles, dict(gain_col='gain', gain=3, **base_config)) # Invalid header keys input = piff.InputFiles(dict(sky='sky', **base_config)) np.testing.assert_raises(KeyError, input.getRawImageData, 0) input = piff.InputFiles(dict(gain='gain', **base_config)) np.testing.assert_raises(KeyError, input.getRawImageData, 0) input = piff.InputFiles(dict(satur='satur', **base_config)) np.testing.assert_raises(KeyError, input.getRawImageData, 0)
def test_single_image(): """Test the whole process with a single image. Note: This test is based heavily on test_single_image in test_simple.py. """ import os import fitsio np_rng = np.random.RandomState(1234) # Make the image image = galsim.Image(2048, 2048, scale=0.2) # The (x,y) values will be on a grid 5 x 5 stars with a random sub-pixel offset. xvals = np.linspace(50., 1950., 5) yvals = np.linspace(50., 1950., 5) x_list, y_list = np.meshgrid(xvals, yvals) x_list = x_list.flatten() y_list = y_list.flatten() x_list = x_list + (np_rng.rand(len(x_list)) - 0.5) y_list = y_list + (np_rng.rand(len(x_list)) - 0.5) print('x_list = ', x_list) print('y_list = ', y_list) # Range of fluxes from 100 to 15000 flux_list = 100. * np.exp(5. * np_rng.rand(len(x_list))) print('fluxes range from ', np.min(flux_list), np.max(flux_list)) # Draw a Moffat PSF at each location on the image. # Have the truth values vary quadratically across the image. beta_fn = lambda x, y: 3.5 - 0.1 * (x / 1000) + 0.08 * (y / 1000)**2 fwhm_fn = lambda x, y: 0.9 + 0.05 * (x / 1000) - 0.03 * ( y / 1000) + 0.02 * (x / 1000) * (y / 1000) e1_fn = lambda x, y: 0.02 - 0.01 * (x / 1000) e2_fn = lambda x, y: -0.03 + 0.02 * (x / 1000)**2 - 0.01 * (y / 1000) * 2 for x, y, flux in zip(x_list, y_list, flux_list): beta = beta_fn(x, y) fwhm = fwhm_fn(x, y) e1 = e1_fn(x, y) e2 = e2_fn(x, y) print(x, y, beta, fwhm, e1, e2) moffat = galsim.Moffat(fwhm=fwhm, beta=beta, flux=flux).shear(e1=e1, e2=e2) bounds = galsim.BoundsI(int(x - 31), int(x + 32), int(y - 31), int(y + 32)) offset = galsim.PositionD(x - int(x) - 0.5, y - int(y) - 0.5) moffat.drawImage(image=image[bounds], offset=offset, method='no_pixel') print('drew image') # Write out the image to a file image_file = os.path.join('data', 'pixel_moffat_image.fits') image.write(image_file) print('wrote image') # Write out the catalog to a file dtype = [('x', 'f8'), ('y', 'f8')] data = np.empty(len(x_list), dtype=dtype) data['x'] = x_list data['y'] = y_list cat_file = os.path.join('data', 'pixel_moffat_cat.fits') fitsio.write(cat_file, data, clobber=True) print('wrote catalog') # Use InputFiles to read these back in input = piff.InputFiles(image_file, cat_file, stamp_size=32) assert input.image_files == [image_file] assert input.cat_files == [cat_file] assert input.x_col == 'x' assert input.y_col == 'y' # Check image input.readImages() assert len(input.images) == 1 np.testing.assert_equal(input.images[0].array, image.array) # Check catalog input.readStarCatalogs() assert len(input.cats) == 1 np.testing.assert_equal(input.cats[0]['x'], x_list) np.testing.assert_equal(input.cats[0]['y'], y_list) # Make stars orig_stars = input.makeStars() assert len(orig_stars) == len(x_list) assert orig_stars[0].image.array.shape == (32, 32) # Make a test star, not at the location of any of the model stars to use for each of the # below tests. x0 = 1024 # Some random position, not where a star was originally. y0 = 133 beta = beta_fn(x0, y0) fwhm = fwhm_fn(x0, y0) e1 = e1_fn(x0, y0) e2 = e2_fn(x0, y0) moffat = galsim.Moffat(fwhm=fwhm, beta=beta).shear(e1=e1, e2=e2) target_star = piff.Star.makeTarget(x=x0, y=y0, scale=image.scale) test_im = galsim.ImageD(bounds=target_star.image.bounds, scale=image.scale) moffat.drawImage(image=test_im, method='no_pixel', use_true_center=False) print('made test star') # These tests are slow, and it's really just doing the same thing three times, so # only do the first one when running via nosetests. if True: # Process the star data model = piff.PixelGrid(0.2, 16, start_sigma=0.9 / 2.355) interp = piff.BasisPolynomial(order=2) if __name__ == '__main__': logger = piff.config.setup_logger(2) else: logger = None pointing = None # wcs is not Celestial here, so pointing needs to be None. psf = piff.SimplePSF(model, interp) psf.fit(orig_stars, {0: input.images[0].wcs}, pointing, logger=logger) # Check that the interpolation is what it should be print('target.flux = ', target_star.fit.flux) test_star = psf.drawStar(target_star) #print('test_im center = ',test_im[b].array) #print('flux = ',test_im.array.sum()) #print('interp_im center = ',test_star.image[b].array) #print('flux = ',test_star.image.array.sum()) #print('max diff = ',np.max(np.abs(test_star.image.array-test_im.array))) np.testing.assert_almost_equal(test_star.image.array, test_im.array, decimal=3) # Check the convenience function that an end user would typically use image = psf.draw(x=x0, y=y0) np.testing.assert_almost_equal(image.array, test_im.array, decimal=3) # Round trip through a file psf_file = os.path.join('output', 'pixel_psf.fits') psf.write(psf_file, logger) psf = piff.read(psf_file, logger) assert type(psf.model) is piff.PixelGrid assert type(psf.interp) is piff.BasisPolynomial test_star = psf.drawStar(target_star) np.testing.assert_almost_equal(test_star.image.array, test_im.array, decimal=3) # Check the convenience function that an end user would typically use image = psf.draw(x=x0, y=y0) np.testing.assert_almost_equal(image.array, test_im.array, decimal=3) # Do the whole thing with the config parser config = { 'input': { 'images': image_file, 'cats': cat_file, 'x_col': 'x', 'y_col': 'y', 'stamp_size': 48 # Bigger than we drew, but should still work. }, 'output': { 'file_name': psf_file }, 'psf': { 'model': { 'type': 'PixelGrid', 'scale': 0.2, 'size': 16, # Much smaller than the input stamps, but this is plenty here. 'start_sigma': 0.9 / 2.355 }, 'interp': { 'type': 'BasisPolynomial', 'order': 2 }, }, } if __name__ == '__main__': print("Running piffify function") piff.piffify(config) psf = piff.read(psf_file) test_star = psf.drawStar(target_star) np.testing.assert_almost_equal(test_star.image.array, test_im.array, decimal=3) # Test using the piffify executable config['verbose'] = 0 with open('pixel_moffat.yaml', 'w') as f: f.write(yaml.dump(config, default_flow_style=False)) if __name__ == '__main__': print("Running piffify executable") if os.path.exists(psf_file): os.remove(psf_file) piffify_exe = get_script_name('piffify') p = subprocess.Popen([piffify_exe, 'pixel_moffat.yaml']) p.communicate() psf = piff.read(psf_file) test_star = psf.drawStar(target_star) np.testing.assert_almost_equal(test_star.image.array, test_im.array, decimal=3)
def test_pointing(): """Test the input.setPointing function """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file=os.path.join('output', 'test_input_pointing.log')) dir = 'input' image_file = 'test_input_image_00.fits' cat_file = 'test_input_cat_00.fits' # First, with no ra, dec, pointing is None config = { 'dir': dir, 'image_file_name': image_file, 'cat_file_name': cat_file, } input = piff.InputFiles(config, logger=logger) assert input.pointing is None # Explicit ra, dec as floats config['ra'] = 6.0 config['dec'] = -30.0 input = piff.InputFiles(config, logger=logger) np.testing.assert_almost_equal(input.pointing.ra.rad, np.pi / 2.) np.testing.assert_almost_equal(input.pointing.dec.rad, -np.pi / 6.) # Also ok as ints in this case config['ra'] = 6 config['dec'] = -30 input = piff.InputFiles(config, logger=logger) np.testing.assert_almost_equal(input.pointing.ra.rad, np.pi / 2.) np.testing.assert_almost_equal(input.pointing.dec.rad, -np.pi / 6.) # Strings as hh:mm:ss or dd:mm:ss config['ra'] = '06:00:00' config['dec'] = '-30:00:00' input = piff.InputFiles(config, logger=logger) np.testing.assert_almost_equal(input.pointing.ra.rad, np.pi / 2.) np.testing.assert_almost_equal(input.pointing.dec.rad, -np.pi / 6.) # Strings as keys into FITS header config['ra'] = 'RA' config['dec'] = 'DEC' input = piff.InputFiles(config, logger=logger) np.testing.assert_almost_equal(input.pointing.ra.rad, np.pi / 2.) np.testing.assert_almost_equal(input.pointing.dec.rad, -np.pi / 6.) # If multiple files, use the first one. config['image_file_name'] = 'test_input_image_*.fits' config['cat_file_name'] = 'test_input_cat_*.fits' input = piff.InputFiles(config, logger=logger) np.testing.assert_almost_equal(input.pointing.ra.rad, np.pi / 2.) np.testing.assert_almost_equal(input.pointing.dec.rad, -np.pi / 6.) # Check invalid ra,dec values base_config = { 'dir': dir, 'image_file_name': image_file, 'cat_file_name': cat_file } np.testing.assert_raises(ValueError, piff.InputFiles, dict(ra=0, dec='00:00:00', **base_config)) np.testing.assert_raises(ValueError, piff.InputFiles, dict(ra='00:00:00', dec=0, **base_config)) np.testing.assert_raises( ValueError, piff.InputFiles, dict(ra=0 * galsim.degrees, dec=0 * galsim.radians, **base_config)) np.testing.assert_raises(KeyError, piff.InputFiles, dict(ra='bad_ra', dec='DEC', **base_config)) np.testing.assert_raises(KeyError, piff.InputFiles, dict(ra='RA', dec='bad_dec', **base_config)) np.testing.assert_raises(ValueError, piff.InputFiles, dict(ra=0, **base_config)) np.testing.assert_raises(ValueError, piff.InputFiles, dict(dec=0, **base_config)) # If image has celestial wcs, and no ra, dec specified then it will compute it for you config = { 'dir': dir, 'image_file_name': 'DECam_00241238_01.fits.fz', 'cat_file_name': 'DECam_00241238_01_cat.fits', 'cat_hdu': 2, 'x_col': 'XWIN_IMAGE', 'y_col': 'YWIN_IMAGE', } input = piff.InputFiles(config, logger=logger) np.testing.assert_almost_equal(input.pointing.ra / galsim.hours, 4.063, decimal=3) np.testing.assert_almost_equal(input.pointing.dec / galsim.degrees, -51.471, decimal=3) # Similar, but not quite equal to teh TELRA, TELDEC, which is at center of exposure. config['ra'] = 'TELRA' config['dec'] = 'TELDEC' input = piff.InputFiles(config, logger=logger) np.testing.assert_almost_equal(input.pointing.ra / galsim.hours, 4.097, decimal=3) np.testing.assert_almost_equal(input.pointing.dec / galsim.degrees, -52.375, decimal=3) # We only have the one celestial wcs image in the repo, but with multiple ones, it will # average over all images. config = { 'dir': dir, 'image_file_name': ['DECam_00241238_01.fits.fz', 'DECam_00241238_01.fits.fz'], 'cat_file_name': ['DECam_00241238_01_cat.fits', 'DECam_00241238_01_cat.fits'], 'cat_hdu': 2, 'x_col': 'XWIN_IMAGE', 'y_col': 'YWIN_IMAGE', } input = piff.InputFiles(config, logger=logger) np.testing.assert_almost_equal(input.pointing.ra / galsim.hours, 4.063, decimal=3) np.testing.assert_almost_equal(input.pointing.dec / galsim.degrees, -51.471, decimal=3)
def test_single_image(): """Test the simple case of one image and one catalog. """ # Make the image image = galsim.Image(2048, 2048, scale=0.26) # Where to put the stars. Include some flagged and not used locations. x_list = [ 123.12, 345.98, 567.25, 1094.94, 924.15, 1532.74, 1743.11, 888.39, 1033.29, 1409.31 ] y_list = [ 345.43, 567.45, 1094.32, 924.29, 1532.92, 1743.83, 888.83, 1033.19, 1409.20, 123.11 ] flag_list = [0, 0, 12, 0, 0, 1, 0, 0, 0, 0] use_list = [1, 1, 1, 1, 1, 0, 1, 1, 0, 1] # Draw a Gaussian PSF at each location on the image. sigma = 1.3 g1 = 0.23 g2 = -0.17 psf = galsim.Gaussian(sigma=sigma).shear(g1=g1, g2=g2) for x, y, flag, use in zip(x_list, y_list, flag_list, use_list): bounds = galsim.BoundsI(int(x - 31), int(x + 32), int(y - 31), int(y + 32)) offset = galsim.PositionD(x - int(x) - 0.5, y - int(y) - 0.5) psf.drawImage(image=image[bounds], method='no_pixel', offset=offset) # corrupt the ones that are marked as flagged if flag: print('corrupting star at ', x, y) ar = image[bounds].array im_max = np.max(ar) * 0.2 ar[ar > im_max] = im_max image.addNoise( galsim.GaussianNoise(rng=galsim.BaseDeviate(1234), sigma=1e-6)) # Write out the image to a file image_file = os.path.join('data', 'simple_image.fits') image.write(image_file) # Write out the catalog to a file dtype = [('x', 'f8'), ('y', 'f8'), ('flag', 'i2'), ('use', 'i2')] data = np.empty(len(x_list), dtype=dtype) data['x'] = x_list data['y'] = y_list data['flag'] = flag_list data['use'] = use_list cat_file = os.path.join('data', 'simple_cat.fits') fitsio.write(cat_file, data, clobber=True) # Use InputFiles to read these back in input = piff.InputFiles(image_file, cat_file) assert input.image_files == [image_file] assert input.cat_files == [cat_file] assert input.x_col == 'x' assert input.y_col == 'y' # Check image input.readImages() assert len(input.images) == 1 np.testing.assert_equal(input.images[0].array, image.array) # Check catalog input.readStarCatalogs() assert len(input.cats) == 1 np.testing.assert_equal(input.cats[0]['x'], x_list) np.testing.assert_equal(input.cats[0]['y'], y_list) # Repeat, using flag and use columns this time. input = piff.InputFiles(image_file, cat_file, flag_col='flag', use_col='use', stamp_size=48) assert input.flag_col == 'flag' assert input.use_col == 'use' input.readImages() input.readStarCatalogs() assert len(input.cats[0]) == 7 # Make star data orig_stars = input.makeStars() assert len(orig_stars) == 7 assert orig_stars[0].image.array.shape == (48, 48) # Process the star data # can only compare to truth if include_pixel=False model = piff.Gaussian(fastfit=True, include_pixel=False) interp = piff.Mean() fitted_stars = [model.fit(model.initialize(star)) for star in orig_stars] interp.solve(fitted_stars) print('mean = ', interp.mean) # Check that the interpolation is what it should be target = piff.Star.makeTarget(x=1024, y=123) # Any position would work here. true_params = [sigma, g1, g2] test_star = interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Now test running it via the config parser psf_file = os.path.join('output', 'simple_psf.fits') config = { 'input': { 'images': image_file, 'cats': cat_file, 'flag_col': 'flag', 'use_col': 'use', 'stamp_size': 48 }, 'psf': { 'model': { 'type': 'Gaussian', 'fastfit': True, 'include_pixel': False }, 'interp': { 'type': 'Mean' }, }, 'output': { 'file_name': psf_file }, } if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger(verbose=0) orig_stars, wcs, pointing = piff.Input.process(config['input'], logger) # Use a SimplePSF to process the stars data this time. psf = piff.SimplePSF(model, interp) psf.fit(orig_stars, wcs, pointing, logger=logger) test_star = psf.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Round trip to a file psf.write(psf_file, logger) psf = piff.read(psf_file, logger) assert type(psf.model) is piff.Gaussian assert type(psf.interp) is piff.Mean test_star = psf.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Do the whole thing with the config parser os.remove(psf_file) piff.piffify(config, logger) psf = piff.read(psf_file) test_star = psf.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Test using the piffify executable os.remove(psf_file) config['verbose'] = 0 with open('simple.yaml', 'w') as f: f.write(yaml.dump(config, default_flow_style=False)) piffify_exe = get_script_name('piffify') p = subprocess.Popen([piffify_exe, 'simple.yaml']) p.communicate() psf = piff.read(psf_file) test_star = psf.interp.interpolate(target) np.testing.assert_almost_equal(test_star.fit.params, true_params, decimal=4) # Test that we can make rho statistics min_sep = 1 max_sep = 100 bin_size = 0.1 stats = piff.RhoStats(min_sep=min_sep, max_sep=max_sep, bin_size=bin_size) stats.compute(psf, orig_stars) rhos = [stats.rho1, stats.rho2, stats.rho3, stats.rho4, stats.rho5] for rho in rhos: # Test the range of separations radius = np.exp(rho.logr) # last bin can be one bigger than max_sep np.testing.assert_array_less(radius, np.exp(np.log(max_sep) + bin_size)) np.testing.assert_array_less(min_sep, radius) np.testing.assert_array_almost_equal(np.diff(rho.logr), bin_size, decimal=5) # Test that the max absolute value of each rho isn't crazy np.testing.assert_array_less(np.abs(rho.xip), 1) # # Check that each rho isn't precisely zero. This means the sum of abs > 0 np.testing.assert_array_less(0, np.sum(np.abs(rho.xip))) # Test the plotting and writing rho_psf_file = os.path.join('output', 'simple_psf_rhostats.pdf') stats.write(rho_psf_file) # Test that we can make summary shape statistics, using HSM shapeStats = piff.ShapeHistogramsStats() shapeStats.compute(psf, orig_stars) # test their characteristics np.testing.assert_array_almost_equal(sigma, shapeStats.T, decimal=4) np.testing.assert_array_almost_equal(sigma, shapeStats.T_model, decimal=3) np.testing.assert_array_almost_equal(g1, shapeStats.g1, decimal=4) np.testing.assert_array_almost_equal(g1, shapeStats.g1_model, decimal=3) np.testing.assert_array_almost_equal(g2, shapeStats.g2, decimal=4) np.testing.assert_array_almost_equal(g2, shapeStats.g2_model, decimal=3) shape_psf_file = os.path.join('output', 'simple_psf_shapestats.pdf') shapeStats.write(shape_psf_file) # Test that we can use the config parser for both RhoStats and ShapeHistogramsStats config['output']['stats'] = [ { 'type': 'ShapeHistograms', 'file_name': shape_psf_file }, { 'type': 'Rho', 'file_name': rho_psf_file }, { 'type': 'TwoDHist', 'file_name': os.path.join('output', 'simple_psf_twodhiststats.pdf'), 'number_bins_u': 3, 'number_bins_v': 3, }, { 'type': 'TwoDHist', 'file_name': os.path.join('output', 'simple_psf_twodhiststats_std.pdf'), 'reducing_function': 'np.std', 'number_bins_u': 3, 'number_bins_v': 3, }, ] os.remove(psf_file) os.remove(rho_psf_file) os.remove(shape_psf_file) piff.piffify(config, logger) # Test using the piffify executable os.remove(psf_file) os.remove(rho_psf_file) os.remove(shape_psf_file) config['verbose'] = 0 with open('simple.yaml', 'w') as f: f.write(yaml.dump(config, default_flow_style=False)) p = subprocess.Popen([piffify_exe, 'simple.yaml']) p.communicate()
def test_chisq(): """Test the Chisq outlier class """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file='output/test_single_image.log') # Make the image image = galsim.Image(512, 512, scale=0.26) nstars = 1000 # enough that there could be some overlaps. Also, some over the edge. rng = np.random.RandomState(1234) x = rng.random_sample(nstars) * 512 y = rng.random_sample(nstars) * 512 sigma = np.ones_like(x) * 0.4 # Most have this sigma g1 = np.ones_like(x) * 0.023 # Most are pretty round. g2 = np.ones_like(x) * 0.012 flux = np.exp(rng.normal(size=nstars)) print('flux range = ', np.min(flux), np.max(flux), np.median(flux), np.mean(flux)) # Make a few intentionally wrong. g1[35] = 0.29 g1[188] = -0.15 sigma[239] = 0.2 g2[347] = -0.15 sigma[551] = 1.3 g2[809] = 0.05 g1[922] = -0.03 # Draw a Gaussian PSF at each location on the image. for i in range(nstars): psf = galsim.Gaussian(sigma=sigma[i]).shear(g1=g1[i], g2=g2[i]) stamp = psf.drawImage(scale=0.26, center=galsim.PositionD(x[i], y[i])) b = stamp.bounds & image.bounds image[b] += stamp[b] noise = 0.02 image.addNoise( galsim.GaussianNoise(rng=galsim.BaseDeviate(1234), sigma=noise)) image_file = os.path.join('output', 'test_chisq_im.fits') image.write(image_file) # Write out the catalog to a file dtype = [('x', 'f8'), ('y', 'f8')] data = np.empty(len(x), dtype=dtype) data['x'] = x data['y'] = y cat_file = os.path.join('output', 'test_chisq_cat.fits') fitsio.write(cat_file, data, clobber=True) # Read the catalog in as stars. config = { 'image_file_name': image_file, 'cat_file_name': cat_file, 'noise': noise**2, # Variance here is sigma^2 'stamp_size': 15, 'use_partial': True, } input = piff.InputFiles(config, logger=logger) stars = input.makeStars() # Skip the solve step. Just give it the right answer and see what it finds for outliers model = piff.Gaussian() interp = piff.Mean() interp.mean = np.array([0.4, 0.023, 0.012]) psf = piff.SimplePSF(model, interp) stars = psf.interpolateStarList(stars) stars = [psf.model.reflux(s, logger=logger) for s in stars] outliers1 = piff.ChisqOutliers(nsigma=5) stars1, nremoved1 = outliers1.removeOutliers(stars, logger=logger) print('nremoved1 = ', nremoved1) assert len(stars1) == len(stars) - nremoved1 # This is what nsigma=5 means in terms of probability outliers2 = piff.ChisqOutliers(prob=5.733e-7) stars2, nremoved2 = outliers2.removeOutliers(stars, logger=logger) print('nremoved2 = ', nremoved2) assert len(stars2) == len(stars) - nremoved2 assert nremoved1 == nremoved2 # The following is nearly equivalent for this particular data set. # For dof=222 (what most of these have, this probability converts to # thresh = 455.40143379 # or ndof = 2.0513578 # But note that when using the above prop or nsigma, the code uses a tailored threshold # different for each star's particular dof, which varies (since some are off the edge). outliers3 = piff.ChisqOutliers(thresh=455.401) stars3, nremoved3 = outliers3.removeOutliers(stars, logger=logger) print('nremoved3 = ', nremoved3) assert len(stars3) == len(stars) - nremoved3 outliers4 = piff.ChisqOutliers(ndof=2.05136) stars4, nremoved4 = outliers4.removeOutliers(stars, logger=logger) print('nremoved4 = ', nremoved4) assert len(stars4) == len(stars) - nremoved4 assert nremoved3 == nremoved4 # Regression tests. If these change, make sure we understand why. assert nremoved1 == nremoved2 == 58 assert nremoved3 == nremoved4 == 16 # Much less, since edge objects aren't being removed # nearly as often as when they have a custom thresh. # Can't provide multiple thresh specifications np.testing.assert_raises(TypeError, piff.ChisqOutliers, nsigma=5, prob=1.e-3) np.testing.assert_raises(TypeError, piff.ChisqOutliers, nsigma=5, thresh=100) np.testing.assert_raises(TypeError, piff.ChisqOutliers, nsigma=5, ndof=3) np.testing.assert_raises(TypeError, piff.ChisqOutliers, prob=1.e-3, thresh=100) np.testing.assert_raises(TypeError, piff.ChisqOutliers, prob=1.e-3, ndof=3) np.testing.assert_raises(TypeError, piff.ChisqOutliers, thresh=100, ndof=3) # Need to specifiy it somehow. np.testing.assert_raises(TypeError, piff.ChisqOutliers)
def test_load_images(): """Test the load_images function """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger(log_file='output/test_load_image2.log') # Same setup as test_single_image, but without flags image = galsim.Image(2048, 2048, scale=0.26) x_list = [ 123.12, 345.98, 567.25, 1094.94, 924.15, 1532.74, 1743.11, 888.39, 1033.29, 1409.31 ] y_list = [ 345.43, 567.45, 1094.32, 924.29, 1532.92, 1743.83, 888.83, 1033.19, 1409.20, 123.11 ] sigma = 1.3 g1 = 0.23 g2 = -0.17 psf = galsim.Gaussian(sigma=sigma).shear(g1=g1, g2=g2) for x,y in zip(x_list, y_list): bounds = galsim.BoundsI(int(x-31), int(x+32), int(y-31), int(y+32)) psf.drawImage(image[bounds], center=galsim.PositionD(x,y), method='no_pixel') image.addNoise(galsim.GaussianNoise(rng=galsim.BaseDeviate(1234), sigma=1e-6)) sky = 10. image += sky image_file = os.path.join('output','test_load_images_im.fits') image.write(image_file) dtype = [ ('x','f8'), ('y','f8') ] data = np.empty(len(x_list), dtype=dtype) data['x'] = x_list data['y'] = y_list cat_file = os.path.join('output','test_load_images_cat.fits') fitsio.write(cat_file, data, clobber=True) # Make star data config = { 'image_file_name' : image_file, 'cat_file_name': cat_file, 'sky': 10 } orig_stars, wcs, pointing = piff.Input.process(config, logger) # Fit these with a simple Mean, Gaussian model = piff.Gaussian() interp = piff.Mean() psf = piff.SimplePSF(model, interp) psf.fit(orig_stars, wcs, pointing, logger=logger) psf_file = os.path.join('output','test_load_images_psf.fits') psf.write(psf_file, logger) # Read this file back in. It has the star data, but the images are blank. psf2 = piff.read(psf_file, logger) assert len(psf2.stars) == 10 for star in psf2.stars: np.testing.assert_array_equal(star.image.array, 0.) # First the old API: with CaptureLog() as cl: loaded_stars = piff.Star.load_images(psf2.stars, image_file, logger=cl.logger) assert "WARNING: The Star.load_images function is deprecated." in cl.output for star, orig in zip(loaded_stars, psf.stars): np.testing.assert_array_equal(star.image.array, orig.image.array) # Can optionally supply a different sky to subtract # (This is not allowed by the new API, but it probably doesn't make much sense.) loaded_stars = piff.Star.load_images(psf2.stars, image_file, sky=2*sky) for star, orig in zip(loaded_stars, psf.stars): np.testing.assert_array_equal(star.image.array, orig.image.array - sky) # Now the new API: psf2 = piff.read(psf_file, logger) # Reset stars to not have images in them. loaded_stars = piff.InputFiles(config).load_images(psf2.stars) for star, orig in zip(loaded_stars, psf.stars): np.testing.assert_array_equal(star.image.array, orig.image.array)
def test_basic(): """Test the (usual) basic kind of input field without too many bells and whistles. """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file=os.path.join('output', 'test_input_basic.log')) dir = 'input' image_file = 'test_input_image_00.fits' cat_file = 'test_input_cat_00.fits' # Simple with one image, cat config = { 'dir': dir, 'image_file_name': image_file, 'cat_file_name': cat_file } input = piff.InputFiles(config, logger=logger) assert len(input.image_pos) == 1 assert len(input.image_pos[0]) == 100 # Can omit the dir and just inlcude it in the file names config = { 'image_file_name': os.path.join(dir, image_file), 'cat_file_name': os.path.join(dir, cat_file) } input = piff.InputFiles(config, logger=logger) assert len(input.image_pos) == 1 assert len(input.image_pos[0]) == 100 # 3 images in a list image_files = ['test_input_image_%02d.fits' % k for k in range(3)] cat_files = ['test_input_cat_%02d.fits' % k for k in range(3)] config = { 'dir': dir, 'image_file_name': image_files, 'cat_file_name': cat_files } input = piff.InputFiles(config, logger=logger) assert len(input.image_pos) == 3 np.testing.assert_array_equal([len(p) for p in input.image_pos], 100) # Again without dir. image_files = ['input/test_input_image_%02d.fits' % k for k in range(3)] cat_files = ['input/test_input_cat_%02d.fits' % k for k in range(3)] config = {'image_file_name': image_files, 'cat_file_name': cat_files} input = piff.InputFiles(config, logger=logger) assert len(input.image_pos) == 3 np.testing.assert_array_equal([len(p) for p in input.image_pos], 100) # 3 images using glob image_files = 'test_input_image_*.fits' cat_files = 'test_input_cat_*.fits' config = { 'dir': dir, 'image_file_name': image_files, 'cat_file_name': cat_files } input = piff.InputFiles(config, logger=logger) assert len(input.image_pos) == 3 np.testing.assert_array_equal([len(p) for p in input.image_pos], 100) # Can limit the number of stars config['nstars'] = 37 input = piff.InputFiles(config, logger=logger) assert len(input.image_pos) == 3 np.testing.assert_array_equal([len(p) for p in input.image_pos], 37)
def test_stars(): """Test the input.makeStars function """ if __name__ == '__main__': logger = piff.config.setup_logger(verbose=2) else: logger = piff.config.setup_logger( log_file=os.path.join('output', 'test_input_stars.log')) dir = 'input' image_file = 'test_input_image_00.fits' cat_file = 'test_input_cat_00.fits' # Turn off two defaults for now (max_snr=100 and use_partial=False) config = { 'dir': dir, 'image_file_name': image_file, 'cat_file_name': cat_file, 'weight_hdu': 1, 'sky_col': 'sky', 'gain_col': 'gain', 'max_snr': 0, 'use_partial': True, } input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 100 chipnum_list = [star['chipnum'] for star in stars] gain_list = [star['gain'] for star in stars] snr_list = [star['snr'] for star in stars] snr_list2 = [ input.calculateSNR(star.data.image, star.data.orig_weight) for star in stars ] print('snr = ', np.min(snr_list), np.max(snr_list)) np.testing.assert_array_equal(chipnum_list, 0) np.testing.assert_array_equal(gain_list, gain_list[0]) np.testing.assert_almost_equal(snr_list, snr_list2, decimal=5) print('min_snr = ', np.min(snr_list)) print('max_snr = ', np.max(snr_list)) assert np.min(snr_list) < 40. assert np.max(snr_list) > 600. # max_snr increases the noise to achieve a maximum snr config['max_snr'] = 120 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 100 snr_list = [star['snr'] for star in stars] snr_list2 = [ input.calculateSNR(star.data.image, star.data.orig_weight) for star in stars ] print('snr = ', np.min(snr_list), np.max(snr_list)) np.testing.assert_almost_equal(snr_list, snr_list2, decimal=5) assert np.min(snr_list) < 40. assert np.max(snr_list) == 120. # The default is max_snr == 100 del config['max_snr'] input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 100 snr_list = np.array([star['snr'] for star in stars]) snr_list2 = [ input.calculateSNR(star.data.image, star.data.orig_weight) for star in stars ] print('snr = ', np.min(snr_list), np.max(snr_list)) np.testing.assert_almost_equal(snr_list, snr_list2, decimal=5) assert np.min(snr_list) < 40. assert np.max(snr_list) == 100. # min_snr removes stars with a snr < min_snr config['min_snr'] = 50 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('len should be ', len(snr_list[snr_list >= 50])) print('actual len is ', len(stars)) assert len(stars) == len(snr_list[snr_list >= 50]) assert len(stars) == 96 # hard-coded for this case, just to make sure snr_list = np.array([star['snr'] for star in stars]) snr_list2 = [ input.calculateSNR(star.data.image, star.data.orig_weight) for star in stars ] print('snr = ', np.min(snr_list), np.max(snr_list)) np.testing.assert_almost_equal(snr_list, snr_list2, decimal=5) assert np.min(snr_list) >= 50. assert np.max(snr_list) == 100. # use_partial=False will skip any stars that are partially off the edge of the image config['use_partial'] = False input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 94 # skipped 2 additional stars # use_partial=False is the default del config['use_partial'] input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) assert len(stars) == 94 # skipped 2 additional stars # alt_x and alt_y also include some object completely off the image, which are always skipped. # (Don't do the min_snr anymore, since most of these stamps don't actually have any signal.) config['x_col'] = 'alt_x' config['y_col'] = 'alt_y' del config['min_snr'] input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 37 # Also skip objects which are all weight=0 config['weight_hdu'] = 3 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 0 # But not ones that are only partially weight=0 config['weight_hdu'] = 8 input = piff.InputFiles(config, logger=logger) stars = input.makeStars(logger=logger) print('new len is ', len(stars)) assert len(stars) == 37 # Check that negative snr flux yields 0, not an error (from sqrt(neg)) # Negative flux is actually ok, since it gets squared, but if an image has negative weights # (which would be weird of course), then it could get to negative flux = wI^2. star0 = stars[0] star0.data.orig_weight *= -1. snr0 = input.calculateSNR(star0.data.image, star0.data.orig_weight) assert snr0 == 0.