Example #1
0
def test_OpticalPSF_flux():
    """Compare an unaberrated OpticalPSF flux to unity.
    """
    import time
    t1 = time.time()
    lods = (
        1.e-8, 4., 9.e5
    )  # lambda/D values: don't choose unity in case symmetry hides something
    nlook = 512  # Need a bit bigger image than below to get enough flux
    image = galsim.ImageF(nlook, nlook)
    for lod in lods:
        optics_test = galsim.OpticalPSF(lam_over_diam=lod, pad_factor=1)
        optics_array = optics_test.draw(dx=.25 * lod, image=image).array
        np.testing.assert_almost_equal(
            optics_array.sum(),
            1.,
            2,
            err_msg="Unaberrated Optical flux not quite unity.")
    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Example #2
0
    def getProfile(self, params):
        """Get a version of the model as a GalSim GSObject

        :param params:      A np array with [z4, z5, z6...z11]

        :returns: a galsim.GSObject instance
        """
        import galsim
        prof = []
        # gaussian
        if self.sigma is not None:
            gaussian = galsim.Gaussian(sigma=self.sigma)
            prof.append(gaussian)
        # atmosphere
        if len(self.kolmogorov_kwargs) > 0:
            atm = galsim.Kolmogorov(**self.kolmogorov_kwargs)
            prof.append(atm)
        # optics
        if params is None or len(params) == 0:
            # no optics here
            pass
        else:
            aberrations = [0, 0, 0, 0] + list(params)
            optics = galsim.OpticalPSF(aberrations=aberrations,
                                       **self.optical_psf_kwargs)
            prof.append(optics)
            # convolve together

        if len(prof) == 0:
            raise RuntimeError('No profile returned by model!')

        if len(prof) == 1:
            prof = prof[0]
        else:
            prof = galsim.Convolve(prof)

        if self.g1 is not None or self.g2 is not None:
            prof = prof.shear(g1=self.g1, g2=self.g2)

        return prof
Example #3
0
def _BuildOpticalPSF(config, base, ignore, gsparams, logger):
    """@brief Build an OpticalPSF.
    """
    kwargs, safe = galsim.config.GetAllParams(config, base,
        req = galsim.OpticalPSF._req_params,
        opt = galsim.OpticalPSF._opt_params,
        single = galsim.OpticalPSF._single_params,
        ignore = [ 'aberrations' ] + ignore)
    if gsparams: kwargs['gsparams'] = galsim.GSParams(**gsparams)

    if 'aberrations' in config:
        aber_list = [0.0] * 4  # Initial 4 values are ignored.
        aberrations = config['aberrations']
        if not isinstance(aberrations,list):
            raise AttributeError("aberrations entry for config.OpticalPSF entry is not a list.")
        for i in range(len(aberrations)):
            value, safe1 = galsim.config.ParseValue(aberrations, i, base, float)
            aber_list.append(value)
            safe = safe and safe1
        kwargs['aberrations'] = aber_list

    return galsim.OpticalPSF(**kwargs), safe
Example #4
0
    def get_psf(self, x, y, additional_coefs=None):
        """
        - x: x position on FOV [deg]
        - y: y position on FOV [deg]
        - additional_coefs: ndarray (Noll ordering: defocus, astig1, astig2, coma1, coma2,
                            trefoil1, trefoil2, spher), additional Zernike coefficients.
                            If None, no additional errors are added [wavelength]

        Outputs
        - optics: galsim.optics.OpticalPSF
        """
        if x < self.xmin or x > self.xmax or y < self.ymin or y > self.ymax:
            import warnings
            warnings.warn("Warning: position (%f,%f) not within the bounds " %
                          (x, y) +
                          "of the gridded values.  Min, max x: (%f,%f) " %
                          (self.xmin, self.xmax) +
                          "and min, max y: (%f, %f) " % (self.ymin, self.ymax))

        coefs = self.get_zernike_coefficients(x, y)
        if additional_coefs is not None:
            coefs += additional_coefs
        optics = galsim.OpticalPSF(lam_over_diam=self.lam_over_diam,
                                   defocus=coefs[0],
                                   astig1=coefs[1],
                                   astig2=coefs[2],
                                   coma1=coefs[3],
                                   coma2=coefs[4],
                                   trefoil1=coefs[5],
                                   trefoil2=coefs[6],
                                   spher=coefs[7],
                                   obscuration=self.obscuration,
                                   nstruts=self.nstruts,
                                   strut_thick=self.strut_thick,
                                   strut_angle=self.strut_angle,
                                   pad_factor=self.pad_factor,
                                   suppress_warning=True)

        return optics
Example #5
0
def test_OpticalPSF_vs_Airy_with_obs():
    """Compare the array view on an unaberrated OpticalPSF with obscuration to that of an Airy.
    """
    import time
    t1 = time.time()
    lod = 7.5    # lambda/D value: don't choose unity in case symmetry hides something
    obses = (0.1, 0.3, 0.5) # central obscuration radius ratios
    nlook = 100          # size of array region at the centre of each image to compare
    image = galsim.ImageF(nlook,nlook)
    for obs in obses:
        airy_test = galsim.Airy(lam_over_diam=lod, obscuration=obs, flux=1.)
        optics_test = galsim.OpticalPSF(lam_over_diam=lod, pad_factor=1, obscuration=obs,
                                        suppress_warning=True)
        airy_array = airy_test.drawImage(scale=1.,image=image, method='no_pixel').array
        optics_array = optics_test.drawImage(scale=1.,image=image, method='no_pixel').array
        np.testing.assert_array_almost_equal(optics_array, airy_array, decimal_dft,
                err_msg="Unaberrated Optical with obscuration not quite equal to Airy")
    do_pickle(optics_test, lambda x: x.drawImage(nx=20, ny=20, scale=1.7, method='no_pixel'))
    do_pickle(optics_test)

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Example #6
0
def test_OpticalPSF_flux():
    """Compare an unaberrated OpticalPSF flux to unity.
    """
    lods = (
        1.e-8, 4., 9.e5
    )  # lambda/D values: don't choose unity in case symmetry hides something
    nlook = 512  # Need a bit bigger image than below to get enough flux
    image = galsim.ImageF(nlook, nlook)
    for lod in lods:
        optics_test = galsim.OpticalPSF(lam_over_diam=lod)
        optics_array = optics_test.drawImage(scale=.25 * lod,
                                             image=image,
                                             method='no_pixel').array
        np.testing.assert_almost_equal(
            optics_array.sum(),
            1.,
            2,
            err_msg="Unaberrated Optical flux not quite unity.")
    do_pickle(
        optics_test,
        lambda x: x.drawImage(nx=20, ny=20, scale=1.7, method='no_pixel'))
    do_pickle(optics_test)
Example #7
0
def test_OpticalPSF_vs_Airy():
    """Compare the array view on an unaberrated OpticalPSF to that of an Airy.
    """
    import time
    t1 = time.time()
    lods = (
        4.e-7, 9., 16.4
    )  # lambda/D values: don't choose unity in case symmetry hides something
    nlook = 100
    image = galsim.ImageF(nlook, nlook)
    for lod in lods:
        airy_test = galsim.Airy(lam_over_diam=lod, obscuration=0., flux=1.)
        optics_test = galsim.OpticalPSF(
            lam_over_diam=lod, pad_factor=1)  #pad same as an Airy, natch!
        airy_array = airy_test.draw(dx=.25 * lod, image=image).array
        optics_array = optics_test.draw(dx=.25 * lod, image=image).array
        np.testing.assert_array_almost_equal(
            optics_array,
            airy_array,
            decimal_dft,
            err_msg="Unaberrated Optical not quite equal to Airy")
    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
def _make_psf():
    jitter_fwhm = 0.3
    jitter = galsim.Gaussian(flux=1., fwhm=jitter_fwhm)

    lam_over_diam = 0.257831  # units of arcsec
    aberrations = np.zeros(38)  # Set the initial size.
    aberrations[0] = 0.  # First entry must be zero
    aberrations[1] = -0.00305127
    aberrations[4] = -0.02474205  # Noll index 4 = Defocus
    aberrations[11] = -0.01544329  # Noll index 11 = Spherical
    aberrations[22] = 0.00199235
    aberrations[26] = 0.00000017
    aberrations[37] = 0.00000004

    optics = galsim.OpticalPSF(lam=625,
                               diam=0.5,
                               obscuration=0.38,
                               nstruts=4,
                               strut_angle=(90 * galsim.degrees),
                               strut_thick=0.087,
                               aberrations=aberrations)

    psf = galsim.Convolve([jitter, optics])
    return psf
Example #9
0
def main(argv):
    """
    Make images using model PSFs and galaxy cluster shear:
      - The galaxies come from COSMOSCatalog, which can produce either RealGalaxy profiles
        (like in demo10) and parametric fits to those profiles. We chose parametric fits since
        these are required for chromatic galaxies (ones with filter response included)
      - The real galaxy images include some initial correlated noise from the original HST
        observation, which would need to be whitened. But we are using parametric galaxies, 
        so this isn't a concern.
    """

    global logger
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("mock_superbit_data")

    M = MPIHelper()

    # Define some parameters we'll use below.
    sbparams = SuperBITParameters(argv=argv)

    # Set up the NFWHalo:
    nfw = galsim.NFWHalo(mass=sbparams.mass,
                         conc=sbparams.nfw_conc,
                         redshift=sbparams.nfw_z_halo,
                         omega_m=sbparams.omega_m,
                         omega_lam=sbparams.omega_lam)

    logger.info('Set up NFW halo for lensing')

    # Read in galaxy catalog, as well as catalog containing
    # information from COSMOS fits like redshifts, hlr, etc.
    cosmos_cat = galsim.COSMOSCatalog(sbparams.cat_file_name,
                                      dir=sbparams.cosmosdir)
    fitcat = Table.read(
        os.path.join(os.path.join(sbparams.cosmosdir, sbparams.fit_file_name)))
    logger.info('Read in %d galaxies from catalog and associated fit info',
                cosmos_cat.nobjects)

    cluster_cat = galsim.COSMOSCatalog(sbparams.cluster_cat_name)
    print('Read in %d cluster galaxies from catalog' % cosmos_cat.nobjects)

    ### Now create PSF. First, define Zernicke polynomial component
    ### note: aberrations were definined for lam = 550, and close to the
    ### center of the camera. The PSF degrades at the edge of the FOV
    lam_over_diam = sbparams.lam * 1.e-9 / sbparams.tel_diam  # radians
    lam_over_diam *= 206265.

    aberrations = numpy.zeros(38)  # Set the initial size.
    aberrations[0] = 0.  # First entry must be zero
    aberrations[1] = -0.00305127
    aberrations[4] = -0.02474205  # Noll index 4 = Defocus
    aberrations[11] = -0.01544329  # Noll index 11 = Spherical
    aberrations[22] = 0.00199235
    aberrations[26] = 0.00000017
    aberrations[37] = 0.00000004
    logger.info('Calculated lambda over diam = %f arcsec', lam_over_diam)

    # will store the Zernicke component of the PSF
    optics = galsim.OpticalPSF(lam=sbparams.lam,
                               diam=sbparams.tel_diam,
                               obscuration=sbparams.obscuration,
                               nstruts=sbparams.nstruts,
                               strut_angle=sbparams.strut_angle,
                               strut_thick=sbparams.strut_thick,
                               aberrations=aberrations)

    logger.info('Made telescope PSF profile')

    # load SuperBIT bandpass
    bandpass = galsim.Bandpass(sbparams.bp_file,
                               wave_type='nm',
                               blue_limit=310,
                               red_limit=1100)

    ###
    ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES
    ### WITHIN EACH PSF, ITERATE n TIMES TO MAKE n SEPARATE IMAGES
    ###

    #all_psfs=glob.glob(sbparams.psf_path+"/*121*.psf")
    logger.info('Beginning loop over jitter/optical psfs')

    for im in np.arange(1):

        for i in numpy.arange(1, sbparams.nexp + 1):
            # get MPI processes in sync at start of each image
            M.barrier()
            logger.info('Beginning loop %d' % i)

            #rng = galsim.BaseDeviate(sbparams.noise_seed+i)

            try:
                timescale = str(sbparams.exp_time)
                outname = ''.join(
                    ['superbit_gaussPSF_',
                     str(i).zfill(3), '.fits'])
                truth_file_name = ''.join([
                    sbparams.outdir, '/truth_gaussPSF_',
                    str(i).zfill(3), '.dat'
                ])
                file_name = os.path.join(sbparams.outdir, outname)

            except galsim.errors.GalSimError:
                print("naming failed, check path")
                pdb.set_trace()

            # Setting up a truth catalog
            names = [
                'gal_num', 'x_image', 'y_image', 'ra', 'dec', 'g1_meas',
                'g2_meas', 'nfw_mu', 'redshift', 'flux', 'truth_fwhm',
                'truth_mom'
            ]
            types = [
                int, float, float, float, float, float, float, float, float,
                float, float, float
            ]
            truth_catalog = galsim.OutputCatalog(names, types)

            # Set up the image:
            full_image = galsim.ImageF(sbparams.image_xsize,
                                       sbparams.image_ysize)
            sky_level = sbparams.exp_time * sbparams.sky_bkg
            # fill with sky_level moved until after MPI results summed
            full_image.fill(sky_level)
            full_image.setOrigin(0, 0)

            # We keep track of how much noise is already in the image from the RealGalaxies.
            noise_image = galsim.ImageF(sbparams.image_xsize,
                                        sbparams.image_ysize)
            noise_image.setOrigin(0, 0)

            # If you wanted to make a non-trivial WCS system, could set theta to a non-zero number
            theta = 0.0 * galsim.degrees
            dudx = numpy.cos(theta) * sbparams.pixel_scale
            dudy = -numpy.sin(theta) * sbparams.pixel_scale
            dvdx = numpy.sin(theta) * sbparams.pixel_scale
            dvdy = numpy.cos(theta) * sbparams.pixel_scale
            image_center = full_image.true_center
            affine = galsim.AffineTransform(dudx,
                                            dudy,
                                            dvdx,
                                            dvdy,
                                            origin=full_image.true_center)
            sky_center = galsim.CelestialCoord(ra=sbparams.center_ra,
                                               dec=sbparams.center_dec)

            wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
            full_image.wcs = wcs

            # Now let's read in the PSFEx PSF model.  We read the image directly into an
            # InterpolatedImage GSObject, so we can manipulate it as needed
            psf_wcs = wcs
            #psf = galsim.des.DES_PSFEx(psf_filen,wcs=psf_wcs)
            logger.info('Constructed PSF object from PSFEx file')

            #####
            ## Loop over galaxy objects:
            #####

            # get local range to iterate over in this process
            local_start, local_end = M.mpi_local_range(sbparams.nobj)
            for k in range(local_start, local_end):
                time1 = time.time()

                # The usual random number generator using a different seed for each galaxy.
                ud = galsim.UniformDeviate(sbparams.galobj_seed + k + 1)

                try:
                    # make single galaxy object
                    stamp, truth = make_a_galaxy(ud=ud,
                                                 wcs=wcs,
                                                 affine=affine,
                                                 fitcat=fitcat,
                                                 cosmos_cat=cosmos_cat,
                                                 optics=optics,
                                                 nfw=nfw,
                                                 bandpass=bandpass,
                                                 sbparams=sbparams)
                    # Find the overlapping bounds:
                    bounds = stamp.bounds & full_image.bounds

                    # We need to keep track of how much variance we have currently in the image, so when
                    # we add more noise, we can omit what is already there.

                    # noise_image[bounds] += truth.variance

                    # Finally, add the stamp to the full image.

                    full_image[bounds] += stamp[bounds]
                    time2 = time.time()
                    tot_time = time2 - time1
                    logger.info(
                        'Galaxy %d positioned relative to center t=%f s', k,
                        tot_time)
                    this_flux = numpy.sum(stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, truth.g1,
                        truth.g2, truth.mu, truth.z, this_flux, truth.fwhm,
                        truth.mom_size
                    ]
                    truth_catalog.addRow(row)
                except galsim.errors.GalSimError:
                    logger.info('Galaxy %d has failed, skipping...', k)

            #####
            ### Inject cluster galaxy objects:
            #####

            center_coords = galsim.CelestialCoord(sbparams.center_ra,
                                                  sbparams.center_dec)
            centerpix = wcs.toImage(center_coords)

            # get local range to iterate over in this process
            local_start, local_end = M.mpi_local_range(sbparams.nclustergal)
            for k in range(local_start, local_end):

                time1 = time.time()

                # The usual random number generator using a different seed for each galaxy.
                ud = galsim.UniformDeviate(sbparams.cluster_seed + k + 1)

                try:
                    # make single galaxy object
                    cluster_stamp, truth = make_cluster_galaxy(
                        ud=ud,
                        wcs=wcs,
                        affine=affine,
                        centerpix=centerpix,
                        cluster_cat=cluster_cat,
                        optics=optics,
                        bandpass=bandpass,
                        sbparams=sbparams)
                    # Find the overlapping bounds:
                    bounds = cluster_stamp.bounds & full_image.bounds

                    # We need to keep track of how much variance we have currently in the image, so when
                    # we add more noise, we can omit what is already there.

                    #noise_image[bounds] += truth.variance

                    # Finally, add the stamp to the full image.

                    full_image[bounds] += cluster_stamp[bounds]
                    time2 = time.time()
                    tot_time = time2 - time1
                    logger.info(
                        'Cluster galaxy %d positioned relative to center t=%f s',
                        k, tot_time)
                    this_flux = numpy.sum(stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, truth.g1,
                        truth.g2, truth.mu, truth.z, this_flux, truth.fwhm,
                        truth.mom_size
                    ]
                    truth_catalog.addRow(row)
                except galsim.errors.GalSimError:
                    logger.info('Cluster galaxy %d has failed, skipping...', k)

            #####
            ### Now repeat process for stars!
            #####

            # get local range to iterate over in this process
            local_start, local_end = M.mpi_local_range(sbparams.nstars)
            for k in range(local_start, local_end):
                time1 = time.time()
                ud = galsim.UniformDeviate(sbparams.stars_seed + k + 1)

                star_stamp, truth = make_a_star(ud=ud,
                                                wcs=wcs,
                                                affine=affine,
                                                optics=optics,
                                                sbparams=sbparams)
                bounds = star_stamp.bounds & full_image.bounds

                # Add the stamp to the full image.
                try:
                    full_image[bounds] += star_stamp[bounds]

                    time2 = time.time()
                    tot_time = time2 - time1

                    logger.info(
                        'Star %d: positioned relative to center, t=%f s', k,
                        tot_time)
                    this_flux = numpy.sum(star_stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, truth.g1,
                        truth.g2, truth.mu, truth.z, this_flux, truth.fwhm,
                        truth.mom_size
                    ]
                    truth_catalog.addRow(row)

                except galsim.errors.GalSimError:
                    logger.info('Star %d has failed, skipping...', k)

            # Gather results from MPI processes, reduce to single result on root
            # Using same names on left and right sides is hiding lots of MPI magic
            full_image = M.gather(full_image)
            truth_catalog = M.gather(truth_catalog)
            #noise_image = M.gather(noise_image)
            if M.is_mpi_root():
                full_image = reduce(combine_images, full_image)
                truth_catalog = reduce(combine_catalogs, truth_catalog)
                #noise_image = reduce(combine_images, noise_image)
            else:
                # do the adding of noise and writing to disk entirely on root
                # root and the rest meet again at barrier at start of loop
                continue

            # The first thing to do is to make the Gaussian noise uniform across the whole image.
            # If real-type COSMOS galaxies are used, the noise across the image won't be uniform. Since this code is
            # using parametric-type galaxies, the following section is commented out.
            #         max_current_variance = numpy.max(noise_image.array)
            #         noise_image = max_current_variance - noise_image

            # The first thing to do is to make the Gaussian noise uniform across the whole image.

            # Add dark current

            logger.info('Adding Dark current')

            dark_noise = sbparams.dark_current * sbparams.exp_time
            # np.random.normal(
            #     sbparams.dark_current, sbparams.dark_current_std,
            #     size=(sbparams.image_ysize, sbparams.image_xsize)) * sbparams.exp_time
            # dark_noise = np.clip(dark_noise, a_min=0, a_max=2**16)

            full_image += dark_noise

            # Add ccd noise; removed rng in noise

            logger.info('Adding CCD noise')
            noise = galsim.CCDNoise(sky_level=0,
                                    gain=1 / sbparams.gain,
                                    read_noise=sbparams.read_noise)
            full_image.addNoise(noise)

            logger.debug('Added noise to final output image')
            if not os.path.exists(os.path.dirname(file_name)):
                os.makedirs(os.path.dirname(file_name))
            full_image.write(file_name)

            # Write truth catalog to file.
            truth_catalog.write(truth_file_name)
            logger.info('Wrote image to %r', file_name)

            logger.info(' ')
            logger.info('completed run %d', im)
            i = i + 1
            logger.info(' ')

        logger.info(' ')
        logger.info('completed all images')
        logger.info(' ')
 cn.setVariance(1000.)  # Again chosen to be non-unity
 # Define a PSF with which to convolve the noise field, one WITHOUT 2-fold rotational symmetry
 # (see test_autocorrelate in test_SBProfile.py for more info as to why this is relevant)
 # Make a relatively realistic mockup of a GREAT3 target image
 lam_over_diam_cosmos = (814.e-9 /
                         2.4) * (180. / np.pi) * 3600.  # ~lamda/D in arcsec
 lam_over_diam_ground = lam_over_diam_cosmos * 2.4 / 4.  # Generic 4m at same lambda
 psf_cosmos = galsim.Convolve([
     galsim.Airy(lam_over_diam=lam_over_diam_cosmos, obscuration=0.4),
     galsim.Pixel(0.05)
 ])
 psf_ground = galsim.Convolve([
     galsim.Kolmogorov(fwhm=0.8),
     galsim.Pixel(0.18),
     galsim.OpticalPSF(lam_over_diam=lam_over_diam_ground,
                       coma2=0.4,
                       defocus=-0.6)
 ])
 psf_shera = galsim.Convolve([
     psf_ground, (galsim.Deconvolve(psf_cosmos)).createSheared(g1=0.03,
                                                               g2=-0.01)
 ])
 # Then define the convolved cosmos correlated noise model
 conv_cn = cn.copy()
 conv_cn.convolveWith(psf_shera)
 # Then draw the correlation function for this correlated noise as the reference
 refim = galsim.ImageD(smallim_size, smallim_size)
 conv_cn.draw(refim, dx=0.18)
 # Now start the tests...
 #
 # First we generate a COSMOS noise field (cosimage), read it into an InterpolatedImage and
Example #11
0
    def build_file(seed, file_name, mass, nobj, rng, truth_file_name, halo_id,
                   first_obj_id):
        """A function that does all the work to build a single file.
           Returns the total time taken.
        """
        t1 = time.time()

        # Build the image onto which we will draw the galaxies.
        full_image = galsim.ImageF(image_size, image_size)

        # The "true" center of the image is allowed to be halfway between two pixels, as is the
        # case for even-sized images.  full_image.bounds.center() is an integer position,
        # which would be 1/2 pixel up and to the right of the true center in this case.
        im_center = full_image.bounds.trueCenter()

        # For the WCS, this time we use UVFunction, which lets you define arbitrary u(x,y)
        # and v(x,y) functions.  We use a simple cubic radial function to create a
        # pincushion distortion.  This is a typical kind of telescope distortion, although
        # we exaggerate the magnitude of the effect to make it more apparent.
        # The pixel size in the center of the image is 0.05, but near the corners (r=362),
        # the pixel size is approximately 0.075, which is much more distortion than is
        # normally present in typical telescopes.  But it makes the effect of the variable
        # pixel area obvious when you look at the weight image in the output files.
        ufunc1 = lambda x, y: 0.05 * x * (1. + 2.e-6 * (x**2 + y**2))
        vfunc1 = lambda x, y: 0.05 * y * (1. + 2.e-6 * (x**2 + y**2))

        # It's not required to provide the inverse functions.  However, if we don't, then
        # you will only be able to do toWorld operations, not the inverse toImage.
        # The inverse function does not have to be exact either.  For example, you could provide
        # a function that does some kind of iterative solution to whatever accuracy you care
        # about.  But in this case, we can do the exact inverse.
        #
        # Let w = sqrt(u**2 + v**2) and r = sqrt(x**2 + y**2).  Then the solutions are:
        # x = (u/w) r and y = (u/w) r, and we use Cardano's method to solve for r given w:
        # See http://en.wikipedia.org/wiki/Cubic_function#Cardano.27s_method
        #
        # w = 0.05 r + 2.e-6 * 0.05 * r**3
        # r = 100 * ( ( 5 sqrt(w**2 + 5.e3/27) + 5 w )**(1./3.) -
        #           - ( 5 sqrt(w**2 + 5.e3/27) - 5 w )**(1./3.) )

        def xfunc1(u, v):
            import math
            wsq = u * u + v * v
            if wsq == 0.:
                return 0.
            else:
                w = math.sqrt(wsq)
                temp = 5. * math.sqrt(wsq + 5.e3 / 27)
                r = 100. * ((temp + 5 * w)**(1. / 3.) -
                            (temp - 5 * w)**(1. / 3))
                return u * r / w

        def yfunc1(u, v):
            import math
            wsq = u * u + v * v
            if wsq == 0.:
                return 0.
            else:
                w = math.sqrt(wsq)
                temp = 5. * math.sqrt(wsq + 5.e3 / 27)
                r = 100. * ((temp + 5 * w)**(1. / 3.) -
                            (temp - 5 * w)**(1. / 3))
                return v * r / w

        # You could pass the above functions to UVFunction, and normally we would do that.
        # The only down side to doing so is that the specification of the WCS in the FITS
        # file is rather ugly.  GalSim is able to turn the python byte code into strings,
        # but they are basically a really ugly mess of random-looking characters.  GalSim
        # will be able to read it back in, but human readers will have no idea what WCS
        # function was used.  To see what they look like, uncomment this line and comment
        # out the later wcs line.
        #wcs = galsim.UVFunction(ufunc1, vfunc1, xfunc1, yfunc1, origin=im_center)

        # If you provide the functions as strings, then those strings will be preserved
        # in the FITS header in a form that is more legible to human readers.
        # It also has the extra benefit of matching the output from demo9.yaml, which we
        # always try to do.  The config file has no choice but to specify the functions
        # as strings.

        ufunc = '0.05 * x * (1. + 2.e-6 * (x**2 + y**2))'
        vfunc = '0.05 * y * (1. + 2.e-6 * (x**2 + y**2))'
        xfunc = (
            '( lambda w: ( 0 if w==0 else ' +
            '100.*u/w*(( 5*(w**2 + 5.e3/27.)**0.5 + 5*w )**(1./3.) - ' +
            '( 5*(w**2 + 5.e3/27.)**0.5 - 5*w )**(1./3.))))( (u**2+v**2)**0.5 )'
        )
        yfunc = (
            '( lambda w: ( 0 if w==0 else ' +
            '100.*v/w*(( 5*(w**2 + 5.e3/27.)**0.5 + 5*w )**(1./3.) - ' +
            '( 5*(w**2 + 5.e3/27.)**0.5 - 5*w )**(1./3.))))( (u**2+v**2)**0.5 )'
        )

        # The origin parameter defines where on the image should be considered (x,y) = (0,0)
        # in the WCS functions.
        wcs = galsim.UVFunction(ufunc, vfunc, xfunc, yfunc, origin=im_center)

        # Assign this wcs to full_image
        full_image.wcs = wcs

        # The weight image will hold the inverse variance for each pixel.
        # We can set the wcs directly on construction with the wcs parameter.
        weight_image = galsim.ImageF(image_size, image_size, wcs=wcs)

        # It is common for astrometric images to also have a bad pixel mask.  We don't have any
        # defect simulation currently, so our bad pixel masks are currently all zeros.
        # But someday, we plan to add defect functionality to GalSim, at which point, we'll
        # be able to mark those defects on a bad pixel mask.
        # Note: the S in ImageS means to use "short int" for the data type.
        # This is a typical choice for a bad pixel image.
        badpix_image = galsim.ImageS(image_size, image_size, wcs=wcs)

        # We also draw a PSF image at the location of every galaxy.  This isn't normally done,
        # and since some of the PSFs overlap, it's not necessarily so useful to have this kind
        # of image.  But in this case, it's fun to look at the psf image, especially with
        # something like log scaling in ds9 to see how crazy an aberrated OpticalPSF with
        # struts can look when there is no atmospheric component to blur it out.
        psf_image = galsim.ImageF(image_size, image_size, wcs=wcs)

        # We will also write some truth information to an output catalog.
        # In real simulations, it is often useful to have a catalog of the truth values
        # to compare to measurements either directly or as cuts on the galaxy sample to
        # find where systematic errors are largest.
        # For now, we just make an empty OutputCatalog object with the names and types of the
        # columns.
        names = [
            'object_id', 'halo_id', 'flux', 'radius', 'h_over_r',
            'inclination.rad', 'theta.rad', 'mu', 'redshift', 'shear.g1',
            'shear.g2', 'pos.x', 'pos.y', 'image_pos.x', 'image_pos.y',
            'halo_mass', 'halo_conc', 'halo_redshift'
        ]
        types = [
            int, int, float, float, float, float, float, float, float, float,
            float, float, float, float, float, float, float, float
        ]
        truth_cat = galsim.OutputCatalog(names, types)

        # Setup the NFWHalo stuff:
        nfw = galsim.NFWHalo(mass=mass,
                             conc=nfw_conc,
                             redshift=nfw_z_halo,
                             omega_m=omega_m,
                             omega_lam=omega_lam)
        # Note: the last two are optional.  If they are omitted, then (omega_m=0.3, omega_lam=0.7)
        # are actually the defaults.  If you only specify one of them, the other is set so that
        # the total is 1.  But you can define both values so that the total is not 1 if you want.
        # Radiation is assumed to be zero and dark energy equation of state w = -1.
        # If you want to include either radiation or more complicated dark energy models,
        # you can define your own cosmology class that defines the functions a(z), E(a), and
        # Da(z_source, z_lens).  Then you can pass this to NFWHalo as a `cosmo` parameter.

        # Make the PSF profile outside the loop to minimize the (significant) OpticalPSF
        # construction overhead.
        psf = galsim.OpticalPSF(lam=psf_lam,
                                diam=psf_D,
                                obscuration=psf_obsc,
                                nstruts=psf_nstruts,
                                strut_thick=psf_strut_thick,
                                strut_angle=psf_strut_angle,
                                defocus=psf_defocus,
                                astig1=psf_astig1,
                                astig2=psf_astig2,
                                coma1=psf_coma1,
                                coma2=psf_coma2,
                                trefoil1=psf_trefoil1,
                                trefoil2=psf_trefoil2)

        for k in range(nobj):

            # Initialize the random number generator we will be using for this object:
            ud = galsim.UniformDeviate(seed + k + 1)

            # Determine where this object is going to go.
            # We choose points randomly within a donut centered at the center of the main image
            # in order to avoid placing galaxies too close to the halo center where the lensing
            # is not weak.  We use an inner radius of 3 arcsec and an outer radius of 12 arcsec,
            # which takes us essentially to the edge of the image.
            radius = 12
            inner_radius = 3
            max_rsq = radius**2
            min_rsq = inner_radius**2
            while True:  # (This is essentially a do..while loop.)
                x = (2. * ud() - 1) * radius
                y = (2. * ud() - 1) * radius
                rsq = x**2 + y**2
                if rsq >= min_rsq and rsq <= max_rsq: break
            pos = galsim.PositionD(x, y)

            # We also need the position in pixels to determine where to place the postage
            # stamp on the full image.
            image_pos = wcs.toImage(pos)

            # For even-sized postage stamps, the nominal center (returned by stamp.bounds.center())
            # cannot be at the true center (returned by stamp.bounds.trueCenter()) of the postage
            # stamp, since the nominal center values have to be integers.  Thus, the nominal center
            # is 1/2 pixel up and to the right of the true center.
            # If we used odd-sized postage stamps, we wouldn't need to do this.
            x_nominal = image_pos.x + 0.5
            y_nominal = image_pos.y + 0.5

            # Get the integer values of these which will be the actual nominal center of the
            # postage stamp image.
            ix_nominal = int(math.floor(x_nominal + 0.5))
            iy_nominal = int(math.floor(y_nominal + 0.5))

            # The remainder will be accounted for in an offset when we draw.
            dx = x_nominal - ix_nominal
            dy = y_nominal - iy_nominal
            offset = galsim.PositionD(dx, dy)

            # Draw the flux from a power law distribution: N(f) ~ f^-1.5
            # For this, we use the class DistDeviate which can draw deviates from an arbitrary
            # probability distribution.  This distribution can be defined either as a functional
            # form as we do here, or as tabulated lists of x and p values, from which the
            # function is interpolated.
            flux_dist = galsim.DistDeviate(ud,
                                           function=lambda x: x**-1.5,
                                           x_min=gal_flux_min,
                                           x_max=gal_flux_max)
            flux = flux_dist()

            # We introduce here another surface brightness profile, called InclinedExponential.
            # It represents a typical 3D galaxy disk profile inclined at an arbitrary angle
            # relative to face on.
            #
            #     inclination =  0 degrees corresponds to a face-on disk, which is equivalent to
            #                             the regular Exponential profile.
            #     inclination = 90 degrees corresponds to an edge-on disk.
            #
            # A random orientation corresponds to the inclination angle taking the probability
            # distribution:
            #
            #     P(inc) = 0.5 sin(inc)
            #
            # so we again use a DistDeviate to generate these values.
            inc_dist = galsim.DistDeviate(ud,
                                          function=lambda x: 0.5 * math.sin(x),
                                          x_min=0,
                                          x_max=math.pi)
            inclination = inc_dist() * galsim.radians

            # The parameters scale_radius and scale_height give the scale distances in the
            # 3D distribution:
            #
            #     I(R,z) = I_0 / (2 scale_height) * sech^2(z/scale_height) * exp(-r/scale_radius)
            #
            # These values can be given separately if desired.  However, it is often easier to
            # give the ratio scale_h_over_r as an independent value, since the radius and height
            # values are correlated, while h/r is approximately independent of h or r.
            h_over_r = ud() * (gal_h_over_r_max -
                               gal_h_over_r_min) + gal_h_over_r_min

            radius = ud() * (gal_r_max - gal_r_min) + gal_r_min

            # The inclination is around the x-axis, so we want to rotate the galaxy by a
            # random angle.
            theta = ud() * math.pi * 2. * galsim.radians

            # Make the galaxy profile with these values:
            gal = galsim.InclinedExponential(scale_radius=radius,
                                             scale_h_over_r=h_over_r,
                                             inclination=inclination,
                                             flux=flux)
            gal = gal.rotate(theta)

            # Now apply the appropriate lensing effects for this position from
            # the NFW halo mass.
            try:
                g1, g2 = nfw.getShear(pos, nfw_z_source)
                nfw_shear = galsim.Shear(g1=g1, g2=g2)
            except:
                # This shouldn't happen, since we exclude the inner 10 arcsec, but it's a
                # good idea to use the try/except block here anyway.
                import warnings
                warnings.warn(
                    "Warning: NFWHalo shear is invalid -- probably strong lensing!  "
                    + "Using shear = 0.")
                nfw_shear = galsim.Shear(g1=0, g2=0)

            nfw_mu = nfw.getMagnification(pos, nfw_z_source)
            if nfw_mu < 0:
                import warnings
                warnings.warn(
                    "Warning: mu < 0 means strong lensing!  Using mu=25.")
                nfw_mu = 25
            elif nfw_mu > 25:
                import warnings
                warnings.warn(
                    "Warning: mu > 25 means strong lensing!  Using mu=25.")
                nfw_mu = 25

            # Calculate the total shear to apply
            # Since shear addition is not commutative, it is worth pointing out that
            # the order is in the sense that the second shear is applied first, and then
            # the first shear.  i.e. The field shear is taken to be behind the cluster.
            # Kind of a cosmic shear contribution between the source and the cluster.
            # However, this is not quite the same thing as doing:
            #     gal.shear(field_shear).shear(nfw_shear)
            # since the shear addition ignores the rotation that would occur when doing the
            # above lines.  This is normally ok, because the rotation is not observable, but
            # it is worth keeping in mind.
            total_shear = nfw_shear + field_shear

            # Apply the magnification and shear to the galaxy
            gal = gal.magnify(nfw_mu)
            gal = gal.shear(total_shear)

            # Build the final object
            final = galsim.Convolve([psf, gal])

            # Draw the stamp image
            # To draw the image at a position other than the center of the image, you can
            # use the offset parameter, which applies an offset in pixels relative to the
            # center of the image.
            # We also need to provide the local wcs at the current position.
            local_wcs = wcs.local(image_pos)
            stamp = final.drawImage(wcs=local_wcs, offset=offset)

            # Recenter the stamp at the desired position:
            stamp.setCenter(ix_nominal, iy_nominal)

            # Find overlapping bounds
            bounds = stamp.bounds & full_image.bounds
            full_image[bounds] += stamp[bounds]

            # Also draw the PSF
            psf_stamp = galsim.ImageF(
                stamp.bounds)  # Use same bounds as galaxy stamp
            psf.drawImage(psf_stamp, wcs=local_wcs, offset=offset)
            psf_image[bounds] += psf_stamp[bounds]

            # Add the truth information for this object to the truth catalog
            row = ((first_obj_id + k), halo_id, flux, radius, h_over_r,
                   inclination.rad(), theta.rad(), nfw_mu, nfw_z_source,
                   total_shear.g1, total_shear.g2, pos.x, pos.y, image_pos.x,
                   image_pos.y, mass, nfw_conc, nfw_z_halo)
            truth_cat.addRow(row)

        # Add Poisson noise to the full image
        # Note: The normal calculation of Poission noise isn't quite correct right now.
        # The pixel area is variable, which means the amount of sky flux that enters each
        # pixel is also variable.  The wcs classes have a function `makeSkyImage` which
        # will fill an image with the correct amount of sky flux given the sky level
        # in units of ADU/arcsec^2.  We use the weight image as our work space for this.
        wcs.makeSkyImage(weight_image, sky_level)

        # Add this to the current full_image (temporarily).
        full_image += weight_image

        # Add Poisson noise, given the current full_image.
        # The config parser uses a different random number generator for file-level and
        # image-level values than for the individual objects.  This makes it easier to
        # parallelize the calculation if desired.  In fact, this is why we've been adding 1
        # to each seed value all along.  The seeds for the objects take the values
        # random_seed+1 .. random_seed+nobj.  The seed for the image is just random_seed,
        # which we built already (below) when we calculated how many objects need to
        # be in each file.  Use the same rng again here, since this is also at image scope.
        full_image.addNoise(galsim.PoissonNoise(rng))

        # Subtract the sky back off.
        full_image -= weight_image

        # The weight image is nominally the inverse variance of the pixel noise.  However, it is
        # common to exclude the Poisson noise from the objects themselves and only include the
        # noise from the sky photons.  The variance of the noise is just the sky level, which is
        # what is currently in the weight_image.  (If we wanted to include the variance from the
        # objects too, then we could use the full_image before we added the PoissonNoise to it.)
        # So all we need to do now is to invert the values in weight_image.
        weight_image.invertSelf()

        # Write the file to disk:
        galsim.fits.writeMulti(
            [full_image, badpix_image, weight_image, psf_image], file_name)

        # And write the truth catalog file
        truth_cat.write(truth_file_name)

        t2 = time.time()
        return t2 - t1
                # Gaussian
                # no noise
                #logger.info("First loop: gaussian, no noise")

                gal_flux = flux_dict[lam] * 2.512**(m_zero - m_gal)
                #gal= galsim.Convolve (galsim.Gaussian(flux=gal_flux, sigma=gal_sigma).shear(galsim.Shear(e1=e[0],e2=e[1])) , galsim.Pixel(pixel_scale), gsparams=big_fft_params )
                #measurement_function_NL (gal,  e1_inter_vec=gauss_no_noise[lam][e][0], e2_inter_vec=gauss_no_noise[lam][e][1], size_inter_vec=gauss_no_noise[lam][e][2], noise=None, beta=beta, string='Gausian, no noise')
                ###### noise
                #measurement_function_NL (gal,  e1_inter_vec=gauss_noise[m_gal][0], e2_inter_vec=gauss_noise[m_gal][1], size_inter_vec=gauss_noise[m_gal][2], noise=noise, beta=beta, string='Gaussian, noise')

                #######################Optical

                logger.info("Third loop: Optical, no noise")
                gal = galsim.Convolve(galsim.OpticalPSF(
                    lam_over_diam,
                    obscuration=obscuration_optical,
                    flux=gal_flux).shear(galsim.Shear(e1=e[0], e2=e[1])),
                                      galsim.Pixel(pixel_scale),
                                      gsparams=big_fft_params)
                measurement_function_NL(
                    gal,
                    e1_inter_vec=optical_no_noise[lam][e][0],
                    e2_inter_vec=optical_no_noise[lam][e][1],
                    size_inter_vec=optical_no_noise[lam][e][2],
                    noise=None,
                    beta=beta,
                    string='Optical, no noise')

                ###### noise
                #measurement_function_NL (gal,  e1_inter_vec=optical_noise[m_gal][0], e2_inter_vec=optical_noise[m_gal][1], size_inter_vec=optical_noise[m_gal][2], noise=noise, beta=beta, string='Optical, noise')
Example #13
0
def main(argv):
    """
    Make images using model PSFs and galaxy cluster shear:
      - The galaxies come from COSMOSCatalog, which can produce either RealGalaxy profiles
        (like in demo10) and parametric fits to those profiles.  We choose 40% of the galaxies
        to use the images, and the other 60% to use the parametric fits
      - The real galaxy images include some initial correlated noise from the original HST
        observation.  However, we whiten the noise of the final image so the final image has
        stationary Gaussian noise, rather than correlated noise.
    """
    global logger
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("mock_superbit_data")

    # Define some parameters we'll use below.
    # Normally these would be read in from some parameter file.
    global pixel_scale
    pixel_scale = 0.206  # arcsec/pixel
    global image_xsize
    image_xsize = 6665  # size of image in pixels
    global image_ysize
    image_ysize = 4453  # size of image in pixels
    global image_xsize_arcsec
    image_xsize_arcsec = image_xsize * pixel_scale  # size of big image in each dimension (arcsec)
    global image_ysize_arcsec
    image_ysize_arcsec = image_ysize * pixel_scale  # size of big image in each dimension (arcsec)
    global center_ra
    center_ra = 19.3 * galsim.hours  # The RA, Dec of the center of the image on the sky
    global center_dec
    center_dec = -33.1 * galsim.degrees
    global exp_time
    exp_time = 300
    global sky_bkg  # mean sky background from AG's paper
    sky_bkg = 0.32  # ADU / s / pix
    global sky_sigma  # standard deviation of sky background
    sky_sigma = 0.16  # ADU / s / pix
    global nobj
    nobj = 22  # number of galaxies in entire field
    global nstars
    nstars = 300  # number of stars in the entire field
    global flux_scaling
    global tel_diam
    tel_diam = 0.5
    global lam
    lam = 625  # Central wavelength for Airy disk
    global optics
    psf_path = '/Users/jemcclea/Research/GalSim/examples/data/fpsc_flight_jitter_psf_oversampled_fixed_10x'
    global optics  # will store the Zernicke component of the PSF
    global nfw  # will store the NFWHalo information
    global cosmos_cat  # will store the COSMOS catalog from which we draw objects
    global example_cat  # also a COSMOS catalog which will contain cluster galaxies
    # Set up the NFWHalo:
    mass = 5E14  # Cluster mass (Msol/h)
    nfw_conc = 4  # Concentration parameter = virial radius / NFW scale radius
    nfw_z_halo = 0.17  # redshift of the halo
    omega_m = 0.3  # Omega matter for the background cosmology.
    omega_lam = 0.7  # Omega lambda for the background cosmology.

    nfw = galsim.NFWHalo(mass=mass,
                         conc=nfw_conc,
                         redshift=nfw_z_halo,
                         omega_m=omega_m,
                         omega_lam=omega_lam)
    logger.info('Set up NFW halo for lensing')

    # Read in galaxy catalog
    cat_file_name = 'real_galaxy_catalog_25.2.fits'
    dir = 'data/COSMOS_25.2_training_sample/'

    cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=dir)
    logger.info('Read in %d galaxies from catalog', cosmos_cat.nobjects)

    # Also read in example catalog
    example_cat_file_name = 'data/real_galaxy_catalog_23.5_example.fits'
    example_cat = galsim.COSMOSCatalog(example_cat_file_name)

    # The catalog returns objects that are appropriate for HST in 1 second exposures.  So for our
    # telescope we scale up by the relative area, exposure time and pixel scale
    hst_eff_area = 2.4**2 * (1. - 0.33**2)
    sbit_eff_area = tel_diam**2 * (1. - 0.380**2)
    flux_scaling = (sbit_eff_area / hst_eff_area) * exp_time * (pixel_scale /
                                                                .05)**2

    ### Now create PSF. First, define Zernicke polynomial component
    lam_over_diam = lam * 1.e-9 / tel_diam  # radians
    lam_over_diam *= 206265  # arcsec
    aberrations = [0.0] * 12  # Set the initial size.
    aberrations[4] = -0.00725859  # Noll index 4 = Defocus
    aberrations[5:7] = [0.0, -0.00]  # Noll index 5,6 = Astigmatism
    aberrations[7:9] = [0.07, 0.00]  # Noll index 7,8 = Coma
    aberrations[11] = 0.00133254  # Noll index 11 = Spherical

    logger.info('Calculated lambda over diam = %f arcsec', lam_over_diam)

    optics = galsim.OpticalPSF(lam_over_diam,
                               obscuration=0.380,
                               aberrations=aberrations)
    logger.info('Made telescope PSF profile')

    ###
    ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES
    ### WITHIN EACH PSF, ITERATE 5 TIMES TO MAKE 5 SEPARATE IMAGES
    ###
    all_psfs = glob.glob(psf_path + "/*247530*.psf")  # this is 121s
    logger.info('Beginning loop over jitter/optical psfs')

    for psf_filen in all_psfs:
        logger.info('Beginning PSF %s...' % psf_filen)

        for i in numpy.arange(1, 2):
            logger.info('Beginning loop %d' % i)

            random_seed = 23058923781
            rng = galsim.BaseDeviate(random_seed)

            # This is specific to Javier mock PSFs
            try:
                root = psf_filen.split('data/')[1].split('/')[0]
                timescale = psf_filen.split('_10x/')[1].split('.')[0]
                outname = ''.join([
                    'mock_superbit_', root, timescale,
                    str(i).zfill(3), '.fits'
                ])
                truth_file_name = ''.join([
                    './output/truth_', root, timescale,
                    str(i).zfill(3), '.dat'
                ])
                file_name = os.path.join('output', outname)
            except:
                pdb.set_trace()

            # Setting up a truth catalog
            names = [
                'gal_num', 'x_image', 'y_image', 'ra', 'dec', 'g1_meas',
                'g2_meas', 'nfw_mu', 'redshift', 'flux'
            ]
            types = [
                int, float, float, float, float, float, float, float, float,
                float
            ]
            truth_catalog = galsim.OutputCatalog(names, types)

            # Set up the image:
            full_image = galsim.ImageF(image_xsize, image_ysize)
            sky_level = exp_time * sky_bkg
            full_image.fill(sky_level)
            full_image.setOrigin(0, 0)

            # We keep track of how much noise is already in the image from the RealGalaxies.
            noise_image = galsim.ImageF(image_xsize, image_ysize)
            noise_image.setOrigin(0, 0)

            # Make a slightly non-trivial WCS.  We'll use a slightly rotated coordinate system
            # and center it at the image center.
            theta = 0.17 * galsim.degrees
            dudx = numpy.cos(theta) * pixel_scale
            dudy = -numpy.sin(theta) * pixel_scale
            dvdx = numpy.sin(theta) * pixel_scale
            dvdy = numpy.cos(theta) * pixel_scale
            image_center = full_image.true_center
            affine = galsim.AffineTransform(dudx,
                                            dudy,
                                            dvdx,
                                            dvdy,
                                            origin=full_image.true_center)
            sky_center = galsim.CelestialCoord(ra=center_ra, dec=center_dec)

            wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
            full_image.wcs = wcs

            # Now let's read in the PSFEx PSF model.  We read the image directly into an
            # InterpolatedImage GSObject, so we can manipulate it as needed
            psf_wcs = wcs
            psf_file = os.path.join(psf_path, psf_filen)
            psf = galsim.des.DES_PSFEx(psf_file, wcs=psf_wcs)
            logger.info('Constructed PSF object from PSFEx file')

            # Loop over galaxy objects:
            for k in range(nobj):
                time1 = time.time()

                # The usual random number generator using a different seed for each galaxy.
                ud = galsim.UniformDeviate(random_seed + k + 1)

                try:
                    # make single galaxy object
                    stamp, truth = make_a_galaxy(ud=ud,
                                                 wcs=wcs,
                                                 psf=psf,
                                                 affine=affine)
                    # Find the overlapping bounds:
                    bounds = stamp.bounds & full_image.bounds

                    # We need to keep track of how much variance we have currently in the image, so when
                    # we add more noise, we can omit what is already there.

                    noise_image[bounds] += truth.variance

                    # Finally, add the stamp to the full image.

                    full_image[bounds] += stamp[bounds]
                    time2 = time.time()
                    tot_time = time2 - time1
                    logger.info(
                        'Galaxy %d positioned relative to center t=%f s', k,
                        tot_time)
                    this_flux = numpy.sum(stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, truth.g1,
                        truth.g2, truth.mu, truth.z, this_flux
                    ]
                    truth_catalog.addRow(row)
                except:
                    logger.info('Galaxy %d has failed, skipping...', k)
                    pdb.set_trace()

            ###### Inject cluster galaxy objects:

            random_seed = 892465352
            for k in range(50):
                time1 = time.time()

                # The usual random number generator using a different seed for each galaxy.
                ud = galsim.UniformDeviate(random_seed + k + 1)

                try:
                    # make single galaxy object
                    cluster_stamp, truth = make_cluster_galaxy(ud=ud,
                                                               wcs=wcs,
                                                               psf=psf,
                                                               affine=affine)
                    # Find the overlapping bounds:
                    bounds = cluster_stamp.bounds & full_image.bounds

                    # We need to keep track of how much variance we have currently in the image, so when
                    # we add more noise, we can omit what is already there.

                    noise_image[bounds] += truth.variance

                    # Finally, add the stamp to the full image.

                    full_image[bounds] += cluster_stamp[bounds]
                    time2 = time.time()
                    tot_time = time2 - time1
                    logger.info(
                        'Cluster galaxy %d positioned relative to center t=%f s',
                        k, tot_time)
                    this_flux = numpy.sum(stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, truth.g1,
                        truth.g2, truth.mu, truth.z, this_flux
                    ]
                    truth_catalog.addRow(row)
                except:
                    logger.info('Cluster galaxy %d has failed, skipping...', k)
                    pdb.set_trace()

            ####
            ### Now repeat process for stars!
            ####

            random_seed_stars = 2308173501873

            for k in range(nstars):
                time1 = time.time()
                ud = galsim.UniformDeviate(random_seed_stars + k + 1)

                star_stamp, truth = make_a_star(ud=ud,
                                                wcs=wcs,
                                                psf=psf,
                                                affine=affine)
                bounds = star_stamp.bounds & full_image.bounds

                # Add the stamp to the full image.
                try:
                    full_image[bounds] += star_stamp[bounds]

                    time2 = time.time()
                    tot_time = time2 - time1

                    logger.info(
                        'Star %d: positioned relative to center, t=%f s', k,
                        tot_time)
                    this_flux = numpy.sum(star_stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, truth.g1,
                        truth.g2, truth.mu, truth.z, this_flux
                    ]
                    truth_catalog.addRow(row)

                except:
                    logger.info('Star %d has failed, skipping...', k)
                    pass

            # If real-type COSMOS galaxies are used, the noise across the image won't be uniform. Since this code is
            # using parametric-type galaxies, the following section is commented out.
            #
            # The first thing to do is to make the Gaussian noise uniform across the whole image.

            max_current_variance = numpy.max(noise_image.array)
            noise_image = max_current_variance - noise_image

            vn = galsim.VariableGaussianNoise(rng, noise_image)
            full_image.addNoise(vn)

            # Now max_current_variance is the noise level across the full image.  We don't want to add that
            # twice, so subtract off this much from the intended noise that we want to end up in the image.
            sky_sigma -= numpy.sqrt(max_current_variance)

            # Regardless of galaxy type, add Gaussian noise with this variance to the final image.
            this_noise_sigma = sky_sigma * exp_time
            noise = galsim.GaussianNoise(rng, sigma=this_noise_sigma)
            full_image.addNoise(noise)

            logger.debug('Added noise to final output image')
            full_image.write(file_name)

            # Write truth catalog to file.
            truth_catalog.write(truth_file_name)
            logger.info('Wrote image to %r', file_name)

            logger.info(' ')
            logger.info('completed run %d for psf %s', i, psf_filen)
            i = i + 1
            logger.info(' ')

        logger.info(' ')
        logger.info('completed all images')
        logger.info(' ')
Example #14
0
def main(argv):
    """
    Make a fits image cube where each frame has two images of the same galaxy drawn 
    with regular FFT convolution and with photon shooting.

    We do this for 5 different PSFs and 5 different galaxies, each with 4 different (random)
    fluxes, sizes, and shapes.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo7")

    # To turn off logging:
    #logger.propagate = False

    # To turn on the debugging messages:
    #logger.setLevel(logging.DEBUG)

    # Define some parameters we'll use below.

    # Make output directory if not already present.
    if not os.path.isdir('output'):
        os.mkdir('output')

    file_name = os.path.join('output', 'cube_phot.fits.gz')

    random_seed = 553728
    sky_level = 1.e4  # ADU / arcsec^2
    pixel_scale = 0.28  # arcsec
    nx = 64
    ny = 64

    gal_flux_min = 1.e4  # Range for galaxy flux
    gal_flux_max = 1.e5
    gal_hlr_min = 0.3  # arcsec
    gal_hlr_max = 1.3  # arcsec
    gal_e_min = 0.  # Range for ellipticity
    gal_e_max = 0.8

    psf_fwhm = 0.65  # arcsec

    # This script is set up as a comparison between using FFTs for doing the convolutions and
    # shooting photons.  The two methods have trade-offs in speed and accuracy which vary
    # with the kind of profile being drawn and the S/N of the object, among other factors.
    # In addition, for each method, there are a number of parameters GalSim uses that control
    # aspects of the calculation that further affect the speed and accuracy.
    #
    # We encapsulate these parameters with an object called GSParams.  The default values
    # are intended to be accurate enough for normal precision shear tests, without sacrificing
    # too much speed.
    #
    # Any PSF or galaxy object can be given a gsparams argument on construction that can
    # have different values to make the calculation more or less accurate (typically trading
    # off for speed or memory).
    #
    # In this script, we adjust some of the values slightly, just to show you how it works.
    # You could play around with these values and see what effect they have on the drawn images.
    # Usually, it requires a pretty drastic change in these parameters for you to be able to
    # notice the difference by eye.  But subtle effects that may impact the shapes of galaxies
    # can happen well before then.

    # Type help(galsim.GSParams) for the complete list of parameters and more detailed
    # documentation, including the default values for each parameter.
    gsparams = galsim.GSParams(
        folding_threshold=
        1.e-2,  # maximum fractional flux that may be folded around edge of FFT
        maxk_threshold=
        2.e-3,  # k-values less than this may be excluded off edge of FFT
        xvalue_accuracy=
        1.e-4,  # approximations in real space aim to be this accurate
        kvalue_accuracy=
        1.e-4,  # approximations in fourier space aim to be this accurate
        shoot_accuracy=
        1.e-4,  # approximations in photon shooting aim to be this accurate
        minimum_fft_size=64)  # minimum size of ffts

    logger.info('Starting demo script 7')

    # Make the PSF profiles:
    psf1 = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams)
    psf2 = galsim.Moffat(fwhm=psf_fwhm, beta=2.4, gsparams=gsparams)
    psf3_inner = galsim.Gaussian(fwhm=psf_fwhm, flux=0.8, gsparams=gsparams)
    psf3_outer = galsim.Gaussian(fwhm=2 * psf_fwhm,
                                 flux=0.2,
                                 gsparams=gsparams)
    psf3 = psf3_inner + psf3_outer
    atmos = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams)
    # The OpticalPSF and set of Zernike values chosen below correspond to a reasonably well aligned,
    # smallish ~0.3m / 12 inch diameter telescope with a central obscuration of ~0.12m or 5 inches
    # diameter, being used in optical wavebands.
    # In the Noll convention, the value of the Zernike coefficient also gives the RMS optical path
    # difference across a circular pupil.  An RMS difference of ~0.5 or larger indicates that parts
    # of the wavefront are in fully destructive interference, and so we might expect aberrations to
    # become strong when Zernike aberrations summed in quadrature approach 0.5 wave.
    # The aberrations chosen in this case correspond to operating close to a 0.25 wave RMS optical
    # path difference.  Unlike in demo3, we specify the aberrations by making a list that we pass
    # in using the 'aberrations' kwarg.  The order of aberrations starting from index 4 is defocus,
    # astig1, astig2, coma1, coma2, trefoil1, trefoil2, spher as in the Noll convention.
    # We ignore the first 4 values so that the index number corresponds to the Zernike index
    # in the Noll convention. This will be particularly convenient once we start allowing
    # coefficients beyond spherical (index 11).  c.f. The Wikipedia page about the Noll indices:
    #
    #     http://en.wikipedia.org/wiki/Zernike_polynomials#Zernike_polynomials

    aberrations = [0.0] * 12  # Set the initial size.
    aberrations[4] = 0.06  # Noll index 4 = Defocus
    aberrations[5:7] = [0.12, -0.08]  # Noll index 5,6 = Astigmatism
    aberrations[7:9] = [0.07, 0.04]  # Noll index 7,8 = Coma
    aberrations[11] = -0.13  # Noll index 11 = Spherical
    # You could also define these all at once if that is more convenient:
    #aberrations = [0.0, 0.0, 0.0, 0.0, 0.06, 0.12, -0.08, 0.07, 0.04, 0.0, 0.0, -0.13]

    optics = galsim.OpticalPSF(lam_over_diam=0.6 * psf_fwhm,
                               obscuration=0.4,
                               aberrations=aberrations,
                               gsparams=gsparams)
    psf4 = galsim.Convolve([atmos, optics
                            ])  # Convolve inherits the gsparams from the first
    # item in the list.  (Or you can supply a gsparams
    # argument explicitly if you want to override this.)
    atmos = galsim.Kolmogorov(fwhm=psf_fwhm, gsparams=gsparams)
    optics = galsim.Airy(lam_over_diam=0.3 * psf_fwhm, gsparams=gsparams)
    psf5 = galsim.Convolve([atmos, optics])
    psfs = [psf1, psf2, psf3, psf4, psf5]
    psf_names = [
        "Gaussian", "Moffat", "Double Gaussian", "OpticalPSF",
        "Kolmogorov * Airy"
    ]
    psf_times = [0, 0, 0, 0, 0]
    psf_fft_times = [0, 0, 0, 0, 0]
    psf_phot_times = [0, 0, 0, 0, 0]

    # Make the galaxy profiles:
    gal1 = galsim.Gaussian(half_light_radius=1, gsparams=gsparams)
    gal2 = galsim.Exponential(half_light_radius=1, gsparams=gsparams)
    gal3 = galsim.DeVaucouleurs(half_light_radius=1, gsparams=gsparams)
    gal4 = galsim.Sersic(half_light_radius=1, n=2.5, gsparams=gsparams)
    # A Sersic profile may be truncated if desired.
    # The units for this are expected to be arcsec (or specifically -- whatever units
    # you are using for all the size values as defined by the pixel_scale).
    bulge = galsim.Sersic(half_light_radius=0.7,
                          n=3.2,
                          trunc=8.5,
                          gsparams=gsparams)
    disk = galsim.Sersic(half_light_radius=1.2, n=1.5, gsparams=gsparams)
    gal5 = 0.4 * bulge + 0.6 * disk  # Net half-light radius is only approximate for this one.
    gals = [gal1, gal2, gal3, gal4, gal5]
    gal_names = [
        "Gaussian", "Exponential", "Devaucouleurs", "n=2.5 Sersic",
        "Bulge + Disk"
    ]
    gal_times = [0, 0, 0, 0, 0]
    gal_fft_times = [0, 0, 0, 0, 0]
    gal_phot_times = [0, 0, 0, 0, 0]

    # Other times to keep track of:
    setup_times = 0
    fft_times = 0
    phot_times = 0
    noise_times = 0

    # Loop over combinations of psf, gal, and make 4 random choices for flux, size, shape.
    all_images = []
    k = 0
    for ipsf in range(len(psfs)):
        psf = psfs[ipsf]
        psf_name = psf_names[ipsf]
        logger.info('psf %d: %s', ipsf + 1, psf)
        # Note that this implicitly calls str(psf).  We've made an effort to give all GalSim
        # objects an informative but relatively succinct str representation.  Some details may
        # be missing, but it should look essentially like how you would create the object.
        logger.debug('repr = %r', psf)
        # The repr() version are a bit more pedantic in form and should be completely informative,
        # to the point where two objects that are not identical should never have equal repr
        # strings. As such the repr strings may in some cases be somewhat unwieldy.  For instance,
        # since we set non-default gsparams in these, the repr includes that information, but
        # it is omitted from the str for brevity.
        for igal in range(len(gals)):
            gal = gals[igal]
            gal_name = gal_names[igal]
            logger.info('   galaxy %d: %s', igal + 1, gal)
            logger.debug('   repr = %r', gal)
            for i in range(4):
                logger.debug('      Start work on image %d', i)
                t1 = time.time()

                # Initialize the random number generator we will be using.
                rng = galsim.UniformDeviate(random_seed + k)

                # Generate random variates:
                flux = rng() * (gal_flux_max - gal_flux_min) + gal_flux_min

                # Use a new variable name, since we'll want to keep the original unmodified.
                this_gal = gal.withFlux(flux)

                hlr = rng() * (gal_hlr_max - gal_hlr_min) + gal_hlr_min
                this_gal = this_gal.dilate(hlr)

                beta_ellip = rng() * 2 * math.pi * galsim.radians
                ellip = rng() * (gal_e_max - gal_e_min) + gal_e_min
                gal_shape = galsim.Shear(e=ellip, beta=beta_ellip)
                this_gal = this_gal.shear(gal_shape)

                # Build the final object by convolving the galaxy and PSF.
                final = galsim.Convolve([this_gal, psf])

                # Create the large, double width output image
                # Rather than provide a scale= argument to the drawImage commands, we can also
                # set the pixel scale in the image constructor.
                # Note: You can also change it after the construction with im.scale=pixel_scale
                image = galsim.ImageF(2 * nx + 2, ny, scale=pixel_scale)

                # Assign the following two Image "views", fft_image and phot_image.
                # Using the syntax below, these are views into the larger image.
                # Changes/additions to the sub-images referenced by the views are automatically
                # reflected in the original image.
                fft_image = image[galsim.BoundsI(1, nx, 1, ny)]
                phot_image = image[galsim.BoundsI(nx + 3, 2 * nx + 2, 1, ny)]

                logger.debug(
                    '      Read in training sample galaxy and PSF from file')
                t2 = time.time()

                # Draw the profile
                # This default rendering method (method='auto') usually defaults to FFT, since
                # that is normally the most efficient method.  However, we can also set method
                # to 'fft' explicitly to force it to always use FFTs for the convolution
                # by the pixel response.  (In this case, it doesn't have any effect, since
                # the 'auto' method would have always chosen 'fft' anyway, so this is just
                # for illustrative purposes.)
                final.drawImage(fft_image, method='fft')

                logger.debug(
                    '      Drew fft image.  Total drawn flux = %f.  .flux = %f',
                    fft_image.array.sum(), final.getFlux())
                t3 = time.time()

                # Add Poisson noise
                sky_level_pixel = sky_level * pixel_scale**2
                fft_image.addNoise(
                    galsim.PoissonNoise(rng, sky_level=sky_level_pixel))

                t4 = time.time()

                # The next two lines are just to get the output from this demo script
                # to match the output from the parsing of demo7.yaml.
                rng = galsim.UniformDeviate(random_seed + k)
                rng()
                rng()
                rng()
                rng()

                # Repeat for photon shooting image.
                # The max_extra_noise parameter indicates how much extra noise per pixel we are
                # willing to tolerate.  The sky noise will be adding a variance of sky_level_pixel,
                # so we allow up to 1% of that extra.
                final.drawImage(phot_image,
                                method='phot',
                                max_extra_noise=sky_level_pixel / 100,
                                rng=rng)
                t5 = time.time()

                # For photon shooting, galaxy already has Poisson noise, so we want to make
                # sure not to add that noise again!  Thus, we just add sky noise, which
                # is Poisson with the mean = sky_level_pixel
                pd = galsim.PoissonDeviate(rng, mean=sky_level_pixel)
                # DeviateNoise just adds the action of the given deviate to every pixel.
                phot_image.addNoise(galsim.DeviateNoise(pd))
                # For PoissonDeviate, the mean is not zero, so for a background-subtracted
                # image, we need to subtract the mean back off when we are done.
                phot_image -= sky_level_pixel

                logger.debug(
                    '      Added Poisson noise.  Image fluxes are now %f and %f',
                    fft_image.array.sum(), phot_image.array.sum())
                t6 = time.time()

                # Store that into the list of all images
                all_images += [image]

                k = k + 1
                logger.info(
                    '      %d: flux = %.2e, hlr = %.2f, ellip = (%.2f,%.2f)',
                    k, flux, hlr, gal_shape.getE1(), gal_shape.getE2())
                logger.debug('      Times: %f, %f, %f, %f, %f', t2 - t1,
                             t3 - t2, t4 - t3, t5 - t4, t6 - t5)

                psf_times[ipsf] += t6 - t1
                psf_fft_times[ipsf] += t3 - t2
                psf_phot_times[ipsf] += t5 - t4
                gal_times[igal] += t6 - t1
                gal_fft_times[igal] += t3 - t2
                gal_phot_times[igal] += t5 - t4
                setup_times += t2 - t1
                fft_times += t3 - t2
                phot_times += t5 - t4
                noise_times += t4 - t3 + t6 - t5

    logger.info('Done making images of galaxies')
    logger.info('')
    logger.info('Some timing statistics:')
    logger.info('   Total time for setup steps = %f', setup_times)
    logger.info('   Total time for regular fft drawing = %f', fft_times)
    logger.info('   Total time for photon shooting = %f', phot_times)
    logger.info('   Total time for adding noise = %f', noise_times)
    logger.info('')
    logger.info('Breakdown by PSF type:')
    for ipsf in range(len(psfs)):
        logger.info('   %s: Total time = %f  (fft: %f, phot: %f)',
                    psf_names[ipsf], psf_times[ipsf], psf_fft_times[ipsf],
                    psf_phot_times[ipsf])
    logger.info('')
    logger.info('Breakdown by Galaxy type:')
    for igal in range(len(gals)):
        logger.info('   %s: Total time = %f  (fft: %f, phot: %f)',
                    gal_names[igal], gal_times[igal], gal_fft_times[igal],
                    gal_phot_times[igal])
    logger.info('')

    # Now write the image to disk.
    # With any write command, you can optionally compress the file using several compression
    # schemes:
    #   'gzip' uses gzip on the full output file.
    #   'bzip2' uses bzip2 on the full output file.
    #   'rice' uses rice compression on the image, leaving the fits headers readable.
    #   'gzip_tile' uses gzip in tiles on the output image, leaving the fits headers readable.
    #   'hcompress' uses hcompress on the image, but it is only valid for 2-d data, so it
    #               doesn't work for writeCube.
    #   'plio' uses plio on the image, but it is only valid for positive integer data.
    # Furthermore, the first three have standard filename extensions associated with them,
    # so if you don't specify a compression, but the filename ends with '.gz', '.bz2' or '.fz',
    # the corresponding compression will be selected automatically.
    # In other words, the `compression='gzip'` specification is actually optional here:
    galsim.fits.writeCube(all_images, file_name, compression='gzip')
    logger.info('Wrote fft image to fits data cube %r', file_name)
Example #15
0
def getPSF(SCAs=None,
           approximate_struts=False,
           n_waves=None,
           extra_aberrations=None,
           wavelength_limits=None,
           logger=None,
           wavelength=None,
           high_accuracy=False,
           gsparams=None):
    """
    Get the PSF for WFIRST observations.

    By default, this routine returns a dict of ChromaticOpticalPSF objects, with the dict indexed by
    the SCA (Sensor Chip Array, the equivalent of a chip in an optical CCD).  The PSF for a given
    SCA corresponds to that for the center of the SCA.  Currently we do not use information about
    PSF variation within each SCA, which is relatively small.

    This routine also takes an optional keyword `SCAs`, which can be a single number or an iterable;
    if this is specified then results are not included for the other SCAs.

    The default is to do the calculations using the full specification of the WFIRST pupil plane,
    which is a costly calculation in terms of memory.  For this, we use the provided pupil plane for
    red bands from

    http://wfirst.gsfc.nasa.gov/science/sdt_public/wps/references/instrument/   (Cycle 5)

    and we neglect for now the fact that the pupil plane configuration is slightly different for
    imaging in Z087, Y106, J129.  To avoid using the full pupil plane configuration, use the
    optional keyword `approximate_struts`.  In this case, the pupil plane will have the correct
    obscuration and number of struts, but the struts will be purely radial and evenly spaced instead
    of the true configuration.  The simplicity of this arrangement leads to a much faster
    calculation, and somewhat simplifies the configuration of the diffraction spikes.  Also note
    that currently the orientation of the struts is fixed, rather than rotating depending on the
    orientation of the focal plane.  Rotation of the PSF can easily be affected by the user via

       psf = galsim.wfirst.getPSF(...).rotate(angle)

    which will rotate the entire PSF (including the diffraction spikes and any other features).

    The calculation takes advantage of the fact that the diffraction limit and aberrations have a
    simple, understood wavelength-dependence.  (The WFIRST project webpage for Cycle 5 does in fact
    provide aberrations as a function of wavelength, but the deviation from the expected chromatic
    dependence is very small and we neglect it here.)  For reference, the script use to parse the
    Zernikes given on the webpage and create the files in the GalSim repository can be found in
    `devel/external/parse_wfirst_zernikes_0715.py`.  The resulting chromatic object can be used to
    draw into any of the WFIRST bandpasses.

    For applications that require very high accuracy in the modeling of the PSF, with very limited
    aliasing, the `high_accuracy` option can be set to True.  When using this option, the MTF has a
    value below 1e-4 for all wavenumbers above the band limit when using `approximate_struts=True`,
    or below 3e-4 when using `approximate_struts=False`.  In contrast, when `high_accuracy=False`
    (the default), there are some bumps in the MTF above the band limit that reach an amplitude of
    ~1e-2.

    By default, no additional aberrations are included above the basic design.  However, users can
    provide an optional keyword `extra_aberrations` that will be included on top of those that are
    part of the design.  This should be in the same format as for the ChromaticOpticalPSF class,
    with units of waves at the fiducial wavelength, 1293 nm. Currently, only aberrations up to order
    11 (Noll convention) can be simulated.  For WFIRST, the current tolerance for additional
    aberrations is a total of 90 nanometers RMS:
    http://wfirst.gsfc.nasa.gov/science/sdt_public/wps/references/instrument/README_AFTA_C5_WFC_Zernike_and_Field_Data.pdf
    distributed largely among coma, astigmatism, trefoil, and spherical aberrations (NOT defocus).
    This information might serve as a guide for reasonable `extra_aberrations` inputs.

    Jitter and charge diffusion are, by default, not included.  Users who wish to include these can
    find some guidelines for typical length scales of the Gaussians that can represent these
    effects, and convolve the ChromaticOpticalPSF with appropriate achromatic Gaussians.

    The PSFs are always defined assuming the user will specify length scales in arcsec.

    @param    SCAs                 Specific SCAs for which the PSF should be loaded.  This can be
                                   either a single number or an iterable.  If None, then the PSF
                                   will be loaded for all SCAs (1...18).  Note that the object that
                                   is returned is a dict indexed by the requested SCA indices.
                                   [default: None]
    @param    approximate_struts   Should the routine use an approximate representation of the pupil
                                   plane, with 6 equally-spaced radial struts, instead of the exact
                                   representation of the pupil plane?  Setting this parameter to
                                   True will lead to faster calculations, with a slightly less
                                   realistic PSFs.  [default: False]
    @param    n_waves              Number of wavelengths to use for setting up interpolation of the
                                   chromatic PSF objects, which can lead to much faster image
                                   rendering.  If None, then no interpolation is used. Note that
                                   users who want to interpolate can always set up the interpolation
                                   later on even if they do not do so when calling getPSF().
                                   [default: None]
    @param    extra_aberrations    Array of extra aberrations to include in the PSF model, on top of
                                   those that are part of the WFIRST design.  These should be
                                   provided in units of waves at the fiducial wavelength of 1293 nm,
                                   as an array of length 12 with entries 4 through 11 corresponding
                                   to defocus through spherical aberrations.  [default: None]
    @param    wavelength_limits    A tuple or list of the blue and red wavelength limits to use for
                                   interpolating the chromatic object, if `n_waves` is not None.  If
                                   None, then it uses the blue and red limits of all imaging
                                   passbands to determine the most inclusive wavelength range
                                   possible.  But this keyword can be used to reduce the range of
                                   wavelengths if only one passband (or a subset of passbands) is to
                                   be used for making the images.
                                   [default: None]
    @param    logger               A logger object for output of progress statements if the user
                                   wants them.  [default: None]
    @param    wavelength           An option to get an achromatic PSF for a single wavelength, for
                                   users who do not care about chromaticity of the PSF.  If None,
                                   then the fully chromatic PSF is returned.  Alternatively the user
                                   should supply either (a) a wavelength in nanometers, and they
                                   will get achromatic OpticalPSF objects for that wavelength, or
                                   (b) a bandpass object, in which case they will get achromatic
                                   OpticalPSF objects defined at the effective wavelength of that
                                   bandpass.
                                   [default: False]
    @param    high_accuracy        If True, make higher-fidelity representations of the PSF in
                                   Fourier space, to minimize aliasing (see plots on
                                   https://github.com/GalSim-developers/GalSim/issues/661 for more
                                   details).  This setting is more expensive in terms of time and
                                   RAM, and may not be necessary for many applications.
                                   [default: False]
    @param gsparams                An optional GSParams argument.  See the docstring for GSParams
                                   for details. [default: None]
    @returns  A dict of ChromaticOpticalPSF or OpticalPSF objects for each SCA.
    """
    # Check which SCAs are to be done using a helper routine in this module.
    SCAs = galsim.wfirst._parse_SCAs(SCAs)

    # Deal with some accuracy settings.
    if high_accuracy:
        if approximate_struts:
            oversampling = 3.5
        else:
            oversampling = 2.0

            # In this case, we need to pad the edges of the pupil plane image, so we cannot just use
            # the stored file.
            tmp_pupil_plane_im = galsim.fits.read(
                galsim.wfirst.pupil_plane_file)
            old_bounds = tmp_pupil_plane_im.bounds
            new_bounds = old_bounds.withBorder(
                (old_bounds.xmax + 1 - old_bounds.xmin) / 2)
            pupil_plane_im = galsim.Image(bounds=new_bounds)
            pupil_plane_im[old_bounds] = tmp_pupil_plane_im
            pupil_plane_scale = galsim.wfirst.pupil_plane_scale
    else:
        if approximate_struts:
            oversampling = 1.5
        else:
            oversampling = 1.2
            pupil_plane_im = galsim.wfirst.pupil_plane_file
            pupil_plane_scale = galsim.wfirst.pupil_plane_scale

    if wavelength is None:
        if n_waves is not None:
            if wavelength_limits is None:
                # To decide the range of wavelengths to use (if none were passed in by the user),
                # first check out all the bandpasses.
                bandpass_dict = galsim.wfirst.getBandpasses()
                # Then find the blue and red limit to be used for the imaging bandpasses overall.
                blue_limit, red_limit = _find_limits(default_bandpass_list,
                                                     bandpass_dict)
            else:
                if not isinstance(wavelength_limits, tuple):
                    raise ValueError(
                        "Wavelength limits must be entered as a tuple!")
                blue_limit, red_limit = wavelength_limits
                if red_limit <= blue_limit:
                    raise ValueError(
                        "Wavelength limits must have red_limit > blue_limit."
                        "Input: blue limit=%f, red limit=%f nanometers" %
                        (blue_limit, red_limit))
    else:
        if isinstance(wavelength, galsim.Bandpass):
            wavelength_nm = wavelength.effective_wavelength
        elif isinstance(wavelength, float):
            wavelength_nm = wavelength
        else:
            raise TypeError(
                "Keyword 'wavelength' should either be a Bandpass, float,"
                " or None.")

    # Start reading in the aberrations for the relevant SCAs.
    aberration_dict = {}
    PSF_dict = {}
    if logger: logger.debug('Beginning to loop over SCAs and get the PSF:')
    for SCA in SCAs:
        aberration_dict[SCA] = _read_aberrations(SCA)

        use_aberrations = aberration_dict[SCA]
        if extra_aberrations is not None:
            use_aberrations += extra_aberrations
        # We don't want to use piston, tip, or tilt aberrations.  The former doesn't affect the
        # appearance of the PSF, and the latter cause centroid shifts.  So, we set the first 4
        # numbers (corresponding to a place-holder, piston, tip, and tilt) to zero.
        use_aberrations[0:4] = 0.

        # Now set up the PSF for this SCA, including the option to simplify the pupil plane.
        if logger: logger.debug('   ... SCA %d' % SCA)
        if wavelength is None:
            if approximate_struts:
                PSF = galsim.ChromaticOpticalPSF(
                    lam=zemax_wavelength,
                    diam=galsim.wfirst.diameter,
                    aberrations=use_aberrations,
                    obscuration=galsim.wfirst.obscuration,
                    nstruts=6,
                    oversampling=oversampling,
                    gsparams=gsparams)
            else:
                PSF = galsim.ChromaticOpticalPSF(
                    lam=zemax_wavelength,
                    diam=galsim.wfirst.diameter,
                    aberrations=use_aberrations,
                    obscuration=galsim.wfirst.obscuration,
                    pupil_plane_im=pupil_plane_im,
                    pupil_plane_scale=pupil_plane_scale,
                    oversampling=oversampling,
                    pad_factor=2.,
                    gsparams=gsparams)
            if n_waves is not None:
                PSF = PSF.interpolate(waves=np.linspace(
                    blue_limit, red_limit, n_waves),
                                      oversample_fac=1.5)
        else:
            tmp_aberrations = use_aberrations * zemax_wavelength / wavelength_nm
            if approximate_struts:
                PSF = galsim.OpticalPSF(lam=wavelength_nm,
                                        diam=galsim.wfirst.diameter,
                                        aberrations=tmp_aberrations,
                                        obscuration=galsim.wfirst.obscuration,
                                        nstruts=6,
                                        oversampling=oversampling,
                                        gsparams=gsparams)
            else:
                PSF = galsim.OpticalPSF(lam=wavelength_nm,
                                        diam=galsim.wfirst.diameter,
                                        aberrations=tmp_aberrations,
                                        obscuration=galsim.wfirst.obscuration,
                                        pupil_plane_im=pupil_plane_im,
                                        pupil_plane_scale=pupil_plane_scale,
                                        oversampling=oversampling,
                                        pad_factor=2.,
                                        gsparams=gsparams)

        PSF_dict[SCA] = PSF

    return PSF_dict
Example #16
0
def main(argv):
    
    logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout)
    logger = logging.getLogger("demo7")
    
    ### Make output directory if not already present.
    if not os.path.isdir('output'):
        os.mkdir('output')
        
    file_name = os.path.join('output','cube_phot.fits.gz')


    scaleimg_y=[]
    scaleimg_x_DFT=[]
    scaleimg_x_Photon=[]
    ranges = np.linspace(10,50,5)
    for num in ranges:




### Define some parameters we'll use below.
        random_seed = 553728
        sky_level = 1.e4        # ADU / arcsec^2
        pixel_scale =.28      # arcsec*
        nx = num
        ny = num

        gal_flux_min = 1.e1     # Range for galaxy flux
        gal_flux_max = 1.e5
        gal_hlr_min = 0.3       # arcsec
        gal_hlr_max = 1.3       # arcsec
        gal_e_min = 0.          # Range for ellipticity
        gal_e_max = 0.8

        psf_fwhm = 0.65         # arcsec

    # We encapsulate these parameters with an object called GSParams.  The default values
    # are intended to be accurate enough for normal precision shear tests, without sacrificing
    # too much speed.Any PSF or galaxy object can be given a gsparams argument on construction that can 
    # have different values to make the calculation more or less accurate (typically trading
    # off for speed or memory).  

        gsparams = galsim.GSParams(
            folding_threshold=1.e-2, # maximum fractional flux that may be folded around edge of FFT
            maxk_threshold=2.e-3,    # k-values less than this may be excluded off edge of FFT
            xvalue_accuracy=1.e-4,   # approximations in real space aim to be this accurate
            kvalue_accuracy=1.e-4,   # approximations in fourier space aim to be this accurate
            shoot_accuracy=1.e-4,    # approximations in photon shooting aim to be this accurate
            minimum_fft_size=64)     # minimum size of ffts

        logger.info('Starting psf')

    # Make the PSF profiles:
### psf 1
        psf1 = galsim.Gaussian(fwhm = psf_fwhm, gsparams=gsparams)
    
### psf 2
        psf2 = galsim.Moffat(fwhm = psf_fwhm, beta = 2.4, gsparams=gsparams)
    
### psf 3
        psf3_inner = galsim.Gaussian(fwhm = psf_fwhm, flux = 0.8, gsparams=gsparams)
        psf3_outer = galsim.Gaussian(fwhm = 2*psf_fwhm, flux = 0.2, gsparams=gsparams)
        psf3 = psf3_inner + psf3_outer
        atmos = galsim.Gaussian(fwhm = psf_fwhm, gsparams=gsparams)

### defining the telescope

    # The OpticalPSF and set of Zernike values chosen below correspond to a reasonably well aligned,
    # smallish ~0.3m / 12 inch diameter telescope with a central obscuration of ~0.12m or 5 inches
    # diameter, being used in optical wavebands.  

        aberrations = [ 0.0 ] * 12          # Set the initial size.
        aberrations[4] = 0.06               # Noll index 4 = Defocus
        aberrations[5:7] = [ 0.12, -0.08 ]  # Noll index 5,6 = Astigmatism
        aberrations[7:9] = [ 0.07, 0.04 ]   # Noll index 7,8 = Coma
        aberrations[11] = -0.13             # Noll index 11 = Spherical

        optics = galsim.OpticalPSF(
            lam_over_diam = 0.6 * psf_fwhm, obscuration = 0.4, aberrations = aberrations,
            gsparams=gsparams)

### psf 4
        psf4 = galsim.Convolve([atmos, optics]) # Convolve inherits the gsparams from the first
                                            # item in the list.  (Or you can supply a gsparams
                                            # argument explicitly if you want to override this.)
        atmos = galsim.Kolmogorov(fwhm = psf_fwhm, gsparams=gsparams)
        optics = galsim.Airy(lam_over_diam = 0.3 * psf_fwhm, gsparams=gsparams)

### psf 5
        psf5 = galsim.Convolve([atmos,optics])

### define where to keep the psfs info
        psfs = [psf1, psf2, psf3, psf4, psf5]
        psf_names = ["Gaussian", "Moffat", "Double Gaussian", "OpticalPSF", "Kolmogorov * Airy"]
        psf_times = [0,0,0,0,0]
        psf_fft_times = [0,0,0,0,0]
        psf_phot_times = [0,0,0,0,0]

    # Make the galaxy profiles:
    
### gal 1
        gal1 = galsim.Gaussian(half_light_radius = 1, gsparams=gsparams)

### gal 2
        gal2 = galsim.Exponential(half_light_radius = 1, gsparams=gsparams)

### gal 3
        gal3 = galsim.DeVaucouleurs(half_light_radius = 1, gsparams=gsparams)

### gal 4
        gal4 = galsim.Sersic(half_light_radius = 1, n = 2.5, gsparams=gsparams)
        bulge = galsim.Sersic(half_light_radius = 0.7, n = 3.2, trunc = 8.5, gsparams=gsparams)
        disk = galsim.Sersic(half_light_radius = 1.2, n = 1.5, gsparams=gsparams)

### gal 5
        gal5 = 0.4*bulge + 0.6*disk  # Net half-light radius is only approximate for this one.

### define where to keep the galaxys info
        gals = [gal1, gal2, gal3, gal4, gal5]
        gal_names = ["Gaussian", "Exponential", "Devaucouleurs", "n=2.5 Sersic", "Bulge + Disk"]
        gal_times = [0,0,0,0,0]
        gal_fft_times = [0,0,0,0,0]
        gal_phot_times = [0,0,0,0,0]

### initial time conditions
    # Other times to keep track of:
        setup_times = 0
        fft_times = 0
        phot_times = 0
        noise_times = 0

    # Loop over combinations of psf, gal, and make 4 random choices for flux, size, shape.
        all_images = []
        k = 0
    
        n=[]
        x_DFT=[]
        x_Photon=[]
        y=[]
        all_fluxes = np.linspace(gal_flux_min,gal_flux_max,100)
    
### will loop through the numbers (amount) of psfs
        for ipsf in range(len(psfs)):
        
### calls on the 5 psfs and their names
            psf = psfs[ipsf]
            psf_name = psf_names[ipsf]

### outputs the psf number and the psf information needed to create an object
            logger.info('psf %d: %s',ipsf+1,psf)
        
            logger.debug('repr = %r',psf)

### will loop through the numbers (amount) of galaxies
            for igal in range(len(gals)):

### calls on the 5 galaxies and their names
                gal = gals[igal]
                gal_name = gal_names[igal]
            
### outputs the psf number and the psf information needed to create an object
                logger.info('   galaxy %d: %s',igal+1,gal)
                logger.debug('   repr = %r',gal)

### will loop though 0,1,2,3 flux, size, and shape to create 4 images for each
### combination of galaxy and psf
                for i in range(4):
                    logger.debug('      Start work on image %d',i)
                    all_fluxes_i = all_fluxes[i]
                    image, t1, t2, t3, t4, t5, t6, k, flux, hlr, gal_shape, y_i, psfs, gals, file_name = func(file_name, random_seed, pixel_scale, nx, ny, sky_level, gal_flux_min, gal_flux_max, gal_hlr_min, gal_hlr_max, gal_e_min, gal_e_max, psf_fwhm, gsparams, psf1, psf2, psf3_inner, psf3_outer, psf3, atmos, aberrations, psf4, optics,  psf5, psfs, psf_names, psf_times, psf_fft_times, psf_phot_times, gal1, gal2, gal3, gal4, bulge, disk, gal5, gals, gal_names, gal_times, gal_fft_times, gal_phot_times, setup_times, fft_times, phot_times, noise_times, k, all_fluxes_i, psf, psf_name, gal, gal_name)
                    y = np.append(y, y_i)

### Store that into the list of all images
                    all_images += [image]

### add an itteration though the loop for the psf and galaxy combination images
                    k = k+1

### express the flux,hlr, and ellip of each image combination,4 for every loop
                    logger.info('      %d: flux = %.2e, hlr = %.2f, ellip = (%.2f,%.2f)',
                                k, flux, hlr, gal_shape.getE1(), gal_shape.getE2())
                            
                            
                    logger.debug('      Times: %f, %f, %f, %f, %f',t2-t1, t3-t2, t4-t3, t5-t4, t6-t5)

                    psf_times[ipsf] += t6-t1
                    psf_fft_times[ipsf] += t3-t2
                    psf_phot_times[ipsf] += t5-t4
                    gal_times[igal] += t6-t1
                    gal_fft_times[igal] += t3-t2
                    gal_phot_times[igal] += t5-t4
                    setup_times += t2-t1
                    fft_times += t3-t2
                    phot_times += t5-t4
                    noise_times += t4-t3 + t6-t5
                    x_DFT = np.append(x_DFT,gal_fft_times[igal])
                    x_Photon = np.append(x_Photon,gal_phot_times[igal])

#### flux and time of each galaxy profile with each PSF
        scaleimg_y=np.append(scaleimg_y,y)
        scaleimg_x_DFT=np.append(scaleimg_x_DFT,x_DFT)
        scaleimg_x_Photon=np.append(scaleimg_x_Photon,x_Photon)

    print 'noise DFT =', n



    #### [FOR FIGURES 1-5] the DFT and Photon-Shooting time for the first psf and 5 galaxy profile all at flux 10

    x1_DFT_1=(scaleimg_x_DFT[0],scaleimg_x_DFT[100],scaleimg_x_DFT[200],scaleimg_x_DFT[300],scaleimg_x_DFT[400])
    x1_DFT_2=(scaleimg_x_DFT[4],scaleimg_x_DFT[104],scaleimg_x_DFT[204],scaleimg_x_DFT[304],scaleimg_x_DFT[404])
    x1_DFT_3=(scaleimg_x_DFT[8],scaleimg_x_DFT[108],scaleimg_x_DFT[208],scaleimg_x_DFT[308],scaleimg_x_DFT[408])
    x1_DFT_4=(scaleimg_x_DFT[12],scaleimg_x_DFT[112],scaleimg_x_DFT[212],scaleimg_x_DFT[312],scaleimg_x_DFT[412])
    x1_DFT_5=(scaleimg_x_DFT[16],scaleimg_x_DFT[116],scaleimg_x_DFT[216],scaleimg_x_DFT[316],scaleimg_x_DFT[416])

    x1_Photon_1=(scaleimg_x_Photon[0],scaleimg_x_Photon[100],scaleimg_x_Photon[200],scaleimg_x_Photon[300],scaleimg_x_Photon[400])
    x1_Photon_2=(scaleimg_x_Photon[4],scaleimg_x_Photon[104],scaleimg_x_Photon[204],scaleimg_x_Photon[304],scaleimg_x_Photon[404])
    x1_Photon_3=(scaleimg_x_Photon[8],scaleimg_x_Photon[108],scaleimg_x_Photon[208],scaleimg_x_Photon[308],scaleimg_x_Photon[408])
    x1_Photon_4=(scaleimg_x_Photon[12],scaleimg_x_Photon[112],scaleimg_x_Photon[212],scaleimg_x_Photon[312],scaleimg_x_Photon[412])
    x1_Photon_5=(scaleimg_x_Photon[16],scaleimg_x_Photon[116],scaleimg_x_Photon[216],scaleimg_x_Photon[316],scaleimg_x_Photon[416])
    
    #### [FOR FIGURES 6-10] for the first PSF and 5 galaxy profile for all flux at 1020
    x2_DFT_1=(scaleimg_x_DFT[1],scaleimg_x_DFT[101],scaleimg_x_DFT[201],scaleimg_x_DFT[301],scaleimg_x_DFT[401])
    x2_DFT_2=(scaleimg_x_DFT[2],scaleimg_x_DFT[102],scaleimg_x_DFT[202],scaleimg_x_DFT[302],scaleimg_x_DFT[402])
    x2_DFT_3=(scaleimg_x_DFT[3],scaleimg_x_DFT[103],scaleimg_x_DFT[203],scaleimg_x_DFT[303],scaleimg_x_DFT[403])
    x2_DFT_4=(scaleimg_x_DFT[4],scaleimg_x_DFT[104],scaleimg_x_DFT[204],scaleimg_x_DFT[304],scaleimg_x_DFT[404])
    x2_DFT_5=(scaleimg_x_DFT[5],scaleimg_x_DFT[105],scaleimg_x_DFT[205],scaleimg_x_DFT[305],scaleimg_x_DFT[405])

    x2_Photon_1=(scaleimg_x_Photon[1],scaleimg_x_Photon[101],scaleimg_x_Photon[201],scaleimg_x_Photon[301],scaleimg_x_Photon[401])
    x2_Photon_2=(scaleimg_x_Photon[2],scaleimg_x_Photon[102],scaleimg_x_Photon[202],scaleimg_x_Photon[302],scaleimg_x_Photon[402])
    x2_Photon_3=(scaleimg_x_Photon[3],scaleimg_x_Photon[103],scaleimg_x_Photon[203],scaleimg_x_Photon[303],scaleimg_x_Photon[403])
    x2_Photon_4=(scaleimg_x_Photon[4],scaleimg_x_Photon[104],scaleimg_x_Photon[204],scaleimg_x_Photon[304],scaleimg_x_Photon[404])
    x2_Photon_5=(scaleimg_x_Photon[5],scaleimg_x_Photon[105],scaleimg_x_Photon[205],scaleimg_x_Photon[305],scaleimg_x_Photon[405])
    
    logger.info('Done making images of galaxies')

##### [FOR FIGURES 1-5] the DFT and Photon-Shooting time for the first psf and 5 galaxy profile all at flux 10
###subtraction of points

    xnew_1=[]
    xnew_2=[]
    xnew_3=[]
    xnew_4=[]
    xnew_5=[]
    for a,b,c,d,e,f,g,h,i,j in zip (x1_DFT_1,x1_Photon_1,x1_DFT_2,x1_Photon_2,x1_DFT_3,x1_Photon_3,x1_DFT_4,x1_Photon_4,x1_DFT_5,x1_Photon_5):
        x1=a-b
        x2=c-d
        x3=e-f
        x4=g-h
        x5=i-j
        xnew_1= np.append(xnew_1,x1)
        xnew_2= np.append(xnew_2,x2)
        xnew_3= np.append(xnew_3,x3)
        xnew_4= np.append(xnew_4,x4)
        xnew_5= np.append(xnew_5,x5)

    #### [FOR FIGURES 6-10] for the first PSF and 5 galaxy profile for all flux at 1020
    xnew_6=[]
    xnew_7=[]
    xnew_8=[]
    xnew_9=[]
    xnew_10=[]
    for a,b,c,d,e,f,g,h,i,j in zip (x2_DFT_1,x2_Photon_1,x2_DFT_2,x2_Photon_2,x2_DFT_3,x2_Photon_3,x2_DFT_4,x2_Photon_4,x2_DFT_5,x2_Photon_5):
        x1=a-b
        x2=c-d
        x3=e-f
        x4=g-h
        x5=i-j
        xnew_6= np.append(xnew_6,x1)
        xnew_7= np.append(xnew_7,x2)
        xnew_8= np.append(xnew_8,x3)
        xnew_9= np.append(xnew_9,x4)
        xnew_10= np.append(xnew_10,x5)

    y = [0,0,0,0,0]
      
    #fig1
    f, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
    l1, = ax1.plot(ranges,x1_DFT_1, 'b-', label='l1')
    l2, = ax1.plot(ranges,x1_Photon_1, 'g-', label='l2')
    l3, = ax1.plot(ranges,y,'r--',label='l3')
    plt.legend([l1,l2,l3],['DFT','Photon','Y=0'], loc='upper right')
    ax1.set_title('Gaussian PSF with Gaussian Galaxy Profile at flux=10')
    l4, = ax2.plot(ranges,xnew_1,'y-', label='l4')
    l5, = ax2.plot(ranges,y,'r--', label='l5')
    plt.legend([l4,l5],['DFT - Photon','Y=0'], loc='upper right')
    plt.xlabel('pixel scale')
    plt.ylabel('time')
    f.subplots_adjust(hspace=0)
    plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False)
    plt.savefig('n_1.png')

    #fig2
    f, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
    l1, = ax1.plot(ranges,x1_DFT_2, 'b-', label='l1')
    l2, = ax1.plot(ranges,x1_Photon_2, 'g-', label='l2')
    l3, = ax1.plot(ranges,y,'r--', label='l3')
    plt.legend([l1,l2,l3],['DFT','Photon','Y=0'], loc='upper right')
    ax1.set_title('Gaussian PSF with Exponential Galaxy Profile at flux=10')
    l4, = ax2.plot(ranges,xnew_2,'y-', label='l4')
    l5, = ax2.plot(ranges,y,'r--', label='l5')
    plt.legend([l4,l5],['DFT - Photon','Y=0'], loc='upper right')
    plt.xlabel('pixel scale')
    plt.ylabel('time')
    plt.legend(loc='best')
    f.subplots_adjust(hspace=0)
    plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False)
    plt.savefig('n_2.png')

    #fig3
    f, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
    l1, = ax1.plot(ranges,x1_DFT_3, 'b-', label='l1')
    l2, = ax1.plot(ranges,x1_Photon_3, 'g-', label='l2')
    l3, = ax1.plot(ranges,y,'r--', label='l3')
    plt.legend([l1,l2,l3],['DFT','Photon','Y=0'], loc='upper right')
    ax1.set_title('Gaussian PSF with Devaucouleurs Galaxy Profile at flux=10')
    l4, = ax2.plot(ranges,xnew_3, 'y-', label='l4')
    l5, = ax2.plot(ranges,y,'r--', label='l5')
    plt.legend([l4,l5],['DFT - Photon','Y=0'], loc='upper right')
    plt.xlabel('pixel scale')
    plt.ylabel('time')
    plt.legend(loc='best')
    f.subplots_adjust(hspace=0)
    plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False)
    plt.savefig('n_3.png')

    #fig4
    f, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
    l1, = ax1.plot(ranges,x1_DFT_4, 'b-', label='l1')
    l2, = ax1.plot(ranges,x1_Photon_4, 'g-', label='l2')
    l3, = ax1.plot(ranges,y,'r--', label='l3')
    plt.legend([l1,l2,l3],['DFT','Photon','Y=0'], loc='upper right')
    ax1.set_title('Gaussian PSF with n=2.5 Sersic Galaxy Profile at flux=10')
    l4, = ax2.plot(ranges,xnew_4, 'y-', label='l4')
    l5, = ax2.plot(ranges,y,'r--', label='l5')
    plt.legend([l4,l5],['DFT - Photon','Y=0'], loc='upper right')
    plt.xlabel('pixel scale')
    plt.ylabel('time')
    plt.legend(loc='best')
    f.subplots_adjust(hspace=0)
    plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False)
    plt.savefig('n_4.png')

    #fig5
    f, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
    l1, = ax1.plot(ranges,x1_DFT_5, 'b-', label='l1')
    l2, = ax1.plot(ranges,x1_Photon_5, 'g-', label='l2')
    l3, = ax1.plot(ranges,y,'r--', label='l3')
    plt.legend([l1,l2,l3],['DFT','Photon','Y=0'], loc='upper right')
    ax1.set_title('Gaussian PSF with Bulge + Disk Galaxy Profile at flux=10')
    l4, = ax2.plot(ranges,xnew_5, 'y-', label='l4')
    l5, = ax2.plot(ranges,y,'r--', label='l5')
    plt.legend([l4,l5],['DFT - Photon','Y=0'], loc='upper right')
    plt.xlabel('pixel scale')
    plt.ylabel('time')
    plt.legend(loc='best')
    f.subplots_adjust(hspace=0)
    plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False)
    plt.savefig('n_5.png')

    #fig6
    f, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
    l1, = ax1.plot(ranges,x2_DFT_1, 'b-', label='l1')
    l2, = ax1.plot(ranges,x2_Photon_1, 'g-', label='l2')
    l3, = ax1.plot(ranges,y,'r--', label='l3')
    plt.legend([l1,l2,l3],['DFT','Photon','Y=0'], loc='upper right')
    ax1.set_title('Gaussian PSF with Bulge + Disk Galaxy Profile at flux=1020')
    l4, = ax2.plot(ranges,xnew_6, 'y-', label='l4')
    l5, = ax2.plot(ranges,y,'r--', label='l5')
    plt.legend([l4,l5],['DFT - Photon','Y=0'], loc='upper right')
    plt.xlabel('pixel scale')
    plt.ylabel('time')
    plt.legend(loc='best')
    f.subplots_adjust(hspace=0)
    plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False)
    plt.savefig('n_6.png')

    #fig7
    f, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
    l1, = ax1.plot(ranges,x2_DFT_2, 'b-', label='l1')
    l2, = ax1.plot(ranges,x2_Photon_2, 'g-', label='l2')
    l3, = ax1.plot(ranges,y,'r--', label='l3')
    plt.legend([l1,l2,l3],['DFT','Photon','Y=0'], loc='upper right')
    ax1.set_title('Gaussian PSF with Bulge + Disk Galaxy Profile at flux=1020')
    l4, = ax2.plot(ranges,xnew_7, 'y-', label='l4')
    l5, = ax2.plot(ranges,y,'r--', label='l5')
    plt.legend([l4,l5],['DFT - Photon','Y=0'], loc='upper right')
    plt.xlabel('pixel scale')
    plt.ylabel('time')
    plt.legend(loc='best')
    f.subplots_adjust(hspace=0)
    plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False)
    plt.savefig('n_7.png')

    #fig8
    f, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
    l1, = ax1.plot(ranges,x2_DFT_3, 'b-', label='l1')
    l2, = ax1.plot(ranges,x2_Photon_3, 'g-', label='l2')
    l3, = ax1.plot(ranges,y,'r--', label='l3')
    plt.legend([l1,l2,l3],['DFT','Photon','Y=0'], loc='upper right')
    ax1.set_title('Gaussian PSF with Bulge + Disk Galaxy Profile at flux=1020')
    l4, = ax2.plot(ranges,xnew_8, 'y-', label='l4')
    l5, = ax2.plot(ranges,y,'r--', label='l5')
    plt.legend([l4,l5],['DFT - Photon','Y=0'], loc='upper right')
    plt.xlabel('pixel scale')
    plt.ylabel('time')
    plt.legend(loc='best')
    f.subplots_adjust(hspace=0)
    plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False)
    plt.savefig('n_8.png')

    #fig9
    f, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
    l1, = ax1.plot(ranges,x2_DFT_4, 'b-', label='l1')
    l2, = ax1.plot(ranges,x2_Photon_4, 'g-', label='l2')
    l3, = ax1.plot(ranges,y,'r--', label='l3')
    plt.legend([l1,l2,l3],['DFT','Photon','Y=0'], loc='upper right')
    ax1.set_title('Gaussian PSF with Bulge + Disk Galaxy Profile at flux=1020')
    l4, = ax2.plot(ranges,xnew_9, 'y-', label='l4')
    l5, = ax2.plot(ranges,y,'r--', label='l5')
    plt.legend([l4,l5],['DFT - Photon','Y=0'], loc='upper right')
    plt.xlabel('pixel scale')
    plt.ylabel('time')
    plt.legend(loc='best')
    f.subplots_adjust(hspace=0)
    plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False)
    plt.savefig('n_9.png')

    #fig10
    f, (ax1, ax2) = plt.subplots(2, sharex=True, sharey=True)
    l1, = ax1.plot(ranges,x2_DFT_5, 'b-', label='l1')
    l2, = ax1.plot(ranges,x2_Photon_5, 'g-', label='l2')
    l3, = ax1.plot(ranges,y,'r--', label='l3')
    plt.legend([l1,l2,l3],['DFT','Photon','Y=0'], loc='upper right')
    ax1.set_title('Gaussian PSF with Bulge + Disk Galaxy Profile at flux=1020')
    l4, = ax2.plot(ranges,xnew_10, 'y-', label='l4')
    l5, = ax2.plot(ranges,y,'r--', label='l5')
    plt.legend([l4,l5],['DFT - Photon','Y=0'], loc='upper right')
    plt.xlabel('pixel scale')
    plt.ylabel('time')
    plt.legend(loc='best')
    f.subplots_adjust(hspace=0)
    plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False)
    plt.savefig('n_10.png')

    plt.show()


### breakdown of psf and galaxy types as well as overal timing statistics 
    logger.info('')
    logger.info('Some timing statistics:')
    logger.info('   Total time for setup steps = %f',setup_times)
    logger.info('   Total time for regular fft drawing = %f',fft_times)
    logger.info('   Total time for photon shooting = %f',phot_times)
    logger.info('   Total time for adding noise = %f',noise_times)
    logger.info('')
    logger.info('Breakdown by PSF type:')
    for ipsf in range(len(psfs)):
        logger.info('   %s: Total time = %f  (fft: %f, phot: %f)',
                    psf_names[ipsf],psf_times[ipsf],psf_fft_times[ipsf],psf_phot_times[ipsf])
    logger.info('')
    logger.info('Breakdown by Galaxy type:')
    for igal in range(len(gals)):
        logger.info('   %s: Total time = %f  (fft: %f, phot: %f)',
                    gal_names[igal],gal_times[igal],gal_fft_times[igal],gal_phot_times[igal])
    logger.info('')

### compress into a gzip file and save a a cube
    galsim.fits.writeCube(all_images, file_name, compression='gzip')
    logger.info('Wrote fft image to fits data cube %r',file_name)
Example #17
0
def _get_single_PSF(SCA, bandpass, SCA_pos, approximate_struts, n_waves,
                    extra_aberrations, logger, wavelength, high_accuracy,
                    pupil_plane_type, gsparams):
    """Routine for making a single PSF.  This gets called by getPSF() after it parses all the
       options that were passed in.  Users will not directly interact with this routine.
    """
    # Deal with some accuracy settings.
    if high_accuracy:
        if approximate_struts:
            oversampling = 3.5
        else:
            oversampling = 2.0

            # In this case, we need to pad the edges of the pupil plane image, so we cannot just use
            # the stored file.
            if pupil_plane_type == 'long':
                tmp_pupil_plane_im = galsim.fits.read(
                    galsim.wfirst.pupil_plane_file_longwave)
            else:
                tmp_pupil_plane_im = galsim.fits.read(
                    galsim.wfirst.pupil_plane_file_shortwave)
            old_bounds = tmp_pupil_plane_im.bounds
            new_bounds = old_bounds.withBorder(
                (old_bounds.xmax + 1 - old_bounds.xmin) / 2)
            pupil_plane_im = galsim.Image(bounds=new_bounds)
            pupil_plane_im[old_bounds] = tmp_pupil_plane_im
            pupil_plane_scale = galsim.wfirst.pupil_plane_scale
    else:
        if approximate_struts:
            oversampling = 1.5
        else:
            oversampling = 1.2
            if pupil_plane_type == 'long':
                pupil_plane_im = galsim.wfirst.pupil_plane_file_longwave
            else:
                pupil_plane_im = galsim.wfirst.pupil_plane_file_shortwave
            pupil_plane_scale = galsim.wfirst.pupil_plane_scale

    # Start reading in the aberrations for that SCA
    if logger:
        logger.debug('Beginning to get the PSF aberrations for SCA %d.' % SCA)
    aberrations, x_pos, y_pos = _read_aberrations(SCA)
    # Do bilinear interpolation, unless we're exactly at the center (default).
    use_aberrations = _interp_aberrations_bilinear(aberrations, x_pos, y_pos,
                                                   SCA_pos)

    if extra_aberrations is not None:
        use_aberrations += extra_aberrations
    # We don't want to use piston, tip, or tilt aberrations.  The former doesn't affect the
    # appearance of the PSF, and the latter cause centroid shifts.  So, we set the first 4
    # numbers (corresponding to a place-holder, piston, tip, and tilt) to zero.
    use_aberrations[0:4] = 0.

    # Now set up the PSF, including the option to simplify the pupil plane.
    if wavelength is None:
        if approximate_struts:
            PSF = galsim.ChromaticOpticalPSF(
                lam=zemax_wavelength,
                diam=galsim.wfirst.diameter,
                aberrations=use_aberrations,
                obscuration=galsim.wfirst.obscuration,
                nstruts=6,
                oversampling=oversampling,
                gsparams=gsparams)
        else:
            PSF = galsim.ChromaticOpticalPSF(
                lam=zemax_wavelength,
                diam=galsim.wfirst.diameter,
                aberrations=use_aberrations,
                obscuration=galsim.wfirst.obscuration,
                pupil_plane_im=pupil_plane_im,
                pupil_plane_scale=pupil_plane_scale,
                oversampling=oversampling,
                pad_factor=2.,
                gsparams=gsparams)
        if n_waves is not None:
            # To decide the range of wavelengths to use, check the bandpass.
            bp_dict = galsim.wfirst.getBandpasses()
            bp = bp_dict[bandpass]
            PSF = PSF.interpolate(waves=np.linspace(bp.blue_limit,
                                                    bp.red_limit, n_waves),
                                  oversample_fac=1.5)
    else:
        if not isinstance(wavelength, float):
            raise TypeError(
                "wavelength should either be a Bandpass, float, or None.")
        tmp_aberrations = use_aberrations * zemax_wavelength / wavelength
        if approximate_struts:
            PSF = galsim.OpticalPSF(lam=wavelength,
                                    diam=galsim.wfirst.diameter,
                                    aberrations=tmp_aberrations,
                                    obscuration=galsim.wfirst.obscuration,
                                    nstruts=6,
                                    oversampling=oversampling,
                                    gsparams=gsparams)
        else:
            PSF = galsim.OpticalPSF(lam=wavelength,
                                    diam=galsim.wfirst.diameter,
                                    aberrations=tmp_aberrations,
                                    obscuration=galsim.wfirst.obscuration,
                                    pupil_plane_im=pupil_plane_im,
                                    pupil_plane_scale=pupil_plane_scale,
                                    oversampling=oversampling,
                                    pad_factor=2.,
                                    gsparams=gsparams)

    return PSF
Example #18
0
def test_ne():
    import time
    t1 = time.time()
    # Use some very forgiving settings to speed up this test.  We're not actually going to draw
    # any images (other than internally the PSF), so should be okay.
    gsp1 = galsim.GSParams(maxk_threshold=5.e-2, folding_threshold=5e-2, kvalue_accuracy=1e-3,
                           xvalue_accuracy=1e-3)
    gsp2 = galsim.GSParams(maxk_threshold=5.1e-2, folding_threshold=5e-2, kvalue_accuracy=1e-3,
                           xvalue_accuracy=1e-3)
    pupil_plane_im = galsim.fits.read(os.path.join(imgdir, pp_file))

    # Params include: lam_over_diam, (lam/diam), aberrations by name, aberrations by list, nstruts,
    # strut_thick, strut_angle, obscuration, oversampling, pad_factor, flux, gsparams,
    # circular_pupil, interpolant, pupil_plane_im, pupil_angle, scale_unit
    objs = [galsim.OpticalPSF(lam_over_diam=1.0, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, gsparams=gsp2),
            galsim.OpticalPSF(lam=1.0, diam=1.0, gsparams=gsp1),
            galsim.OpticalPSF(lam=1.0, diam=1.0, scale_unit=galsim.arcmin, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, defocus=0.1, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, aberrations=[0, 0, 0, 0, 0.2], gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, nstruts=2, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, nstruts=2, strut_thick=0.1, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, nstruts=2, strut_angle=10.*galsim.degrees,
                              gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, obscuration=0.5, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, oversampling=2.0, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, pad_factor=2.0, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, flux=2.0, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, circular_pupil=False, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, interpolant='Linear', gsparams=gsp1)]
    if __name__ == "__main__":
        objs += [galsim.OpticalPSF(lam_over_diam=1.0, pupil_plane_im=pupil_plane_im, gsparams=gsp1,
                                   suppress_warning=True),
                 galsim.OpticalPSF(lam_over_diam=1.0, pupil_plane_im=pupil_plane_im,
                                   pupil_angle=10*galsim.degrees, suppress_warning=True)]
    all_obj_diff(objs)

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Example #19
0
def main(argv):
    """
    Make images using model PSFs and galaxy cluster shear:
      - The galaxies come from a processed COSMOS 2015 Catalog, scaled to match
        anticipated SuperBIT 2021 observations
      - The galaxy shape parameters are assigned in a probabilistic way through matching
        galaxy fluxes and redshifts to similar GalSim-COSMOS galaxies (see A. Gill+ 2021)
    """
    
    global logger
    logging.basicConfig(format="%(message)s", level=logging.DEBUG, stream=sys.stdout)
    logger = logging.getLogger("mock_superbit_data")

    M = MPIHelper()

    # Define some parameters we'll use below.
    sbparams = SuperBITParameters(argv=argv)
    
    # Set up the NFWHalo:
    nfw = galsim.NFWHalo(mass=sbparams.mass, conc=sbparams.nfw_conc, redshift=sbparams.nfw_z_halo,
                     omega_m=sbparams.omega_m, omega_lam=sbparams.omega_lam)

    logger.info('Set up NFW halo for lensing')

    # Read in galaxy catalog, as well as catalog containing
    # information from COSMOS fits like redshifts, hlr, etc.   
    # cosmos_cat = galsim.COSMOSCatalog(sbparams.cat_file_name, dir=sbparams.cosmosdir)
    # fitcat = Table.read(os.path.join(sbparams.cosmosdir, sbparams.fit_file_name))

    cosmos_cat = Table.read(os.path.join(sbparams.cosmosdir,sbparams.cat_file_name))
    logger.info('Read in %d galaxies from catalog and associated fit info', len(cosmos_cat))

    cluster_cat = galsim.COSMOSCatalog(sbparams.cluster_cat_name)
    logger.debug('Read in %d cluster galaxies from catalog' % cosmos_cat.nobjects)
    

    ### Now create PSF. First, define Zernicke polynomial component
    ### note: aberrations were definined for lam = 550, and close to the
    ### center of the camera. The PSF degrades at the edge of the FOV
    lam_over_diam = sbparams.lam * 1.e-9 / sbparams.tel_diam    # radians
    lam_over_diam *= 206265.

    aberrations = numpy.zeros(38)             # Set the initial size.
    aberrations[0] = 0.                       # First entry must be zero
    aberrations[1] = -0.00305127
    aberrations[4] = -0.02474205              # Noll index 4 = Defocus
    aberrations[11] = -0.01544329             # Noll index 11 = Spherical
    aberrations[22] = 0.00199235
    aberrations[26] = 0.00000017
    aberrations[37] = 0.00000004
    logger.info('Calculated lambda over diam = %f arcsec', lam_over_diam)

    # will store the Zernicke component of the PSF
    optics = galsim.OpticalPSF(lam=sbparams.lam,diam=sbparams.tel_diam, 
                        obscuration=sbparams.obscuration, nstruts=sbparams.nstruts, 
                        strut_angle=sbparams.strut_angle, strut_thick=sbparams.strut_thick,
                        aberrations=aberrations)

    logger.info('Made telescope PSF profile')
        
    ###
    ### MAKE SIMULATED OBSERVATIONS 
    ### ITERATE n TIMES TO MAKE n SEPARATE IMAGES
    ###

        
    for i in numpy.arange(1,sbparams.nexp+1):          
        # get MPI processes in sync at start of each image
        M.barrier()
        
        #rng = galsim.BaseDeviate(sbparams.noise_seed+i)

        try:
            timescale=str(sbparams.exp_time)
            outname=''.join(['superbit_gaussJitter_',str(i).zfill(3),'.fits'])
            truth_file_name=''.join([sbparams.outdir, '/truth_gaussJitter_', str(i).zfill(3), '.dat'])
            file_name = os.path.join(sbparams.outdir, outname)

        except galsim.errors.GalSimError:
            print("naming failed, check path")
            pdb.set_trace()

            
        # Setting up a truth catalog
        names = [ 'gal_num', 'x_image', 'y_image',
                    'ra', 'dec', 'g1_meas', 'g2_meas', 'nfw_mu', 'redshift','flux','truth_fwhm','truth_mom',
                      'n','hlr','inclination','scale_h_over_r']
        types = [ int, float, float, float,float,float,
                    float, float, float, float, float, float,
                      float, float, float, float]
        truth_catalog = galsim.OutputCatalog(names, types)

        
        # Set up the image:
        full_image = galsim.ImageF(sbparams.image_xsize, sbparams.image_ysize)
        sky_level = sbparams.exp_time * sbparams.sky_bkg
        full_image.fill(sky_level)
        full_image.setOrigin(0,0)
        
        
        # If you wanted to make a non-trivial WCS system, could set theta to a non-zero number
        theta = 0.0 * galsim.degrees
        dudx = numpy.cos(theta) * sbparams.pixel_scale
        dudy = -numpy.sin(theta) * sbparams.pixel_scale
        dvdx = numpy.sin(theta) * sbparams.pixel_scale
        dvdy = numpy.cos(theta) * sbparams.pixel_scale
        image_center = full_image.true_center
        affine = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin=full_image.true_center)
        sky_center = galsim.CelestialCoord(ra=sbparams.center_ra, dec=sbparams.center_dec)
        
        wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
        full_image.wcs = wcs

        
        ## Now let's read in the PSFEx PSF model, if using.
        ## We read the image directly into an InterpolatedImage GSObject,
        ## so we can manipulate it as needed 
        #psf_wcs=wcs
        #psf = galsim.des.DES_PSFEx(psf_filen,wcs=psf_wcs)
        #logger.info('Constructed PSF object from PSFEx file')

        #####
        ## Loop over galaxy objects:
        #####
        
        # get local range to iterate over in this process
        local_start, local_end = M.mpi_local_range(sbparams.nobj)
        for k in range(local_start, local_end):
            time1 = time.time()
            
            # The usual random number generator using a different seed for each galaxy.
            ud = galsim.UniformDeviate(sbparams.galobj_seed+k+1)

            try: 
                # make single galaxy object
                stamp,truth = make_a_galaxy(ud=ud,wcs=wcs,affine=affine,
                        cosmos_cat=cosmos_cat,optics=optics,nfw=nfw,
                        sbparams=sbparams)                
                # Find the overlapping bounds:
                bounds = stamp.bounds & full_image.bounds
                
                # We need to keep track of how much variance we have currently in the image, so when
                # we add more noise, we can omit what is already there.

                # noise_image[bounds] += truth.variance
        
                # Finally, add the stamp to the full image.
            
                full_image[bounds] += stamp[bounds]
                time2 = time.time()
                tot_time = time2-time1
                logger.info('Galaxy %d positioned relative to center t=%f s\n',
                            k, tot_time)
                this_flux=numpy.sum(stamp.array)
                row = [ k,truth.x, truth.y, truth.ra, truth.dec, truth.g1, truth.g2, truth.mu,truth.z,
                            this_flux,truth.fwhm, truth.mom_size,
                            truth.n, truth.hlr, truth.inclination, truth.scale_h_over_r]
                truth_catalog.addRow(row)
            except galsim.errors.GalSimError:
                logger.info('Galaxy %d has failed, skipping...',k)

        #####
        ### Inject cluster galaxy objects:
        #####
     
        center_coords = galsim.CelestialCoord(sbparams.center_ra,sbparams.center_dec)
        centerpix = wcs.toImage(center_coords)
        
        # get local range to iterate over in this process
        local_start, local_end = M.mpi_local_range(sbparams.nclustergal)
        for k in range(local_start, local_end):

            time1 = time.time()
        
            # The usual random number generator using a different seed for each galaxy.
            ud = galsim.UniformDeviate(sbparams.cluster_seed+k+1)
            
            try: 
                # make single galaxy object
                cluster_stamp,truth = make_cluster_galaxy(ud=ud,wcs=wcs,affine=affine,
                                                              centerpix=centerpix,
                                                              cluster_cat=cluster_cat,
                                                              optics=optics,
                                                              sbparams=sbparams)                
                # Find the overlapping bounds:
                bounds = cluster_stamp.bounds & full_image.bounds
                
                # We need to keep track of how much variance we have currently in the image, so when
                # we add more noise, we can omit what is already there.
        
                #noise_image[bounds] += truth.variance
        
                # Finally, add the stamp to the full image.
                
                full_image[bounds] += cluster_stamp[bounds]
                time2 = time.time()
                tot_time = time2-time1
                logger.info('Cluster galaxy %d positioned relative to center t=%f s\n',
                                k, tot_time)
                this_flux=numpy.sum(stamp.array)
                row = [ k,truth.x, truth.y, truth.ra, truth.dec, truth.g1, truth.g2, truth.mu,truth.z,
                            this_flux,truth.fwhm,truth.mom_size,
                            truth.n, truth.hlr, truth.inclination, truth.scale_h_over_r]
                truth_catalog.addRow(row)
            except galsim.errors.GalSimError:
                logger.info('Cluster galaxy %d has failed, skipping...',k)
                
        
            
        #####
        ### Now repeat process for stars!
        #####
        
        # get local range to iterate over in this process
        local_start, local_end = M.mpi_local_range(sbparams.nstars)
        for k in range(local_start, local_end):
            time1 = time.time()
            ud = galsim.UniformDeviate(sbparams.stars_seed+k+1)

            star_stamp,truth = make_a_star(ud=ud, wcs=wcs, affine=affine, 
                    optics=optics, sbparams=sbparams)
            bounds = star_stamp.bounds & full_image.bounds
           
            # Add the stamp to the full image.
            try: 
                full_image[bounds] += star_stamp[bounds]
        
                time2 = time.time()
                tot_time = time2-time1
                
                logger.info('Star %d: positioned relative to center, t=%f s',
                            k,  tot_time)
                this_flux=numpy.sum(star_stamp.array)
                row = [ k,truth.x, truth.y, truth.ra, truth.dec, truth.g1, truth.g2, truth.mu,
                            truth.z, this_flux,truth.fwhm,truth.mom_size,
                            truth.n, truth.hlr, truth.inclination, truth.scale_h_over_r]
                truth_catalog.addRow(row)
                
            except galsim.errors.GalSimError:
                logger.info('Star %d has failed, skipping...',k)

        # Gather results from MPI processes, reduce to single result on root
        # Using same names on left and right sides is hiding lots of MPI magic
        full_image = M.gather(full_image)
        truth_catalog = M.gather(truth_catalog)
        if M.is_mpi_root():
            full_image = reduce(combine_images, full_image)
            truth_catalog = reduce(combine_catalogs, truth_catalog)
        else:
            # do the adding of noise and writing to disk entirely on root
            # root and the rest meet again at barrier at start of loop
            continue
        

        # The first thing to do is to make the Gaussian noise uniform across the whole image.
        
        # Add dark current
        logger.info('Adding Dark current')
        dark_noise = sbparams.dark_current * sbparams.exp_time
        full_image += dark_noise
        
        # Add ccd noise
        logger.info('Adding CCD noise')
        noise = galsim.CCDNoise(
            sky_level=0, gain=1/sbparams.gain,
            read_noise=sbparams.read_noise)
        full_image.addNoise(noise)
        
        logger.debug('Added noise to final output image')
        if not os.path.exists(os.path.dirname(file_name)):
            os.makedirs(os.path.dirname(file_name))
        full_image.write(file_name)

     
        # Write truth catalog to file. 
        truth_catalog.write(truth_file_name)
        logger.info('Wrote image to %r',file_name)

            
    logger.info(' ')
    logger.info('completed all images')
    logger.info(' ')
Example #20
0
def test_OpticalPSF_aberrations_struts():
    """Test the generation of optical aberrations and struts against a known result.
    """
    lod = 0.04
    obscuration = 0.3
    imsize = 128 # Size of saved images as generated by generate_optics_comparison_images.py
    myImg = galsim.ImageD(imsize, imsize)

    # We don't bother running all of these for the regular unit tests, since it adds
    # ~5s to the test run time on a fast-ish laptop.  So only run these when individually
    # running python test_optics.py.
    if do_slow_tests:
        # test defocus
        savedImg = galsim.fits.read(os.path.join(imgdir, "optics_defocus.fits"))
        optics = galsim.OpticalPSF(lod, defocus=.5, obscuration=obscuration, oversampling=1)
        myImg = optics.drawImage(myImg, scale=0.2*lod, use_true_center=True, method='no_pixel')

        np.testing.assert_array_almost_equal(
            myImg.array, savedImg.array, 6,
            err_msg="Optical aberration (defocus) disagrees with expected result")

        # test astig1
        savedImg = galsim.fits.read(os.path.join(imgdir, "optics_astig1.fits"))
        optics = galsim.OpticalPSF(lod, defocus=.5, astig1=.5, obscuration=obscuration,
                                   oversampling=1)
        myImg = optics.drawImage(myImg, scale=0.2*lod, use_true_center=True, method='no_pixel')
        np.testing.assert_array_almost_equal(
            myImg.array, savedImg.array, 6,
            err_msg="Optical aberration (astig1) disagrees with expected result")

        # test astig2
        savedImg = galsim.fits.read(os.path.join(imgdir, "optics_astig2.fits"))
        optics = galsim.OpticalPSF(lod, defocus=.5, astig2=.5, obscuration=obscuration,
                                   oversampling=1)
        myImg = optics.drawImage(myImg, scale=0.2*lod, use_true_center=True, method='no_pixel')
        np.testing.assert_array_almost_equal(
            myImg.array, savedImg.array, 6,
            err_msg="Optical aberration (astig2) disagrees with expected result")

        # test coma1
        savedImg = galsim.fits.read(os.path.join(imgdir, "optics_coma1.fits"))
        optics = galsim.OpticalPSF(lod, coma1=.5, obscuration=obscuration, oversampling=1)
        myImg = optics.drawImage(myImg, scale=0.2*lod, use_true_center=True, method='no_pixel')
        np.testing.assert_array_almost_equal(
            myImg.array, savedImg.array, 6,
            err_msg="Optical aberration (coma1) disagrees with expected result")

        # test coma2
        savedImg = galsim.fits.read(os.path.join(imgdir, "optics_coma2.fits"))
        optics = galsim.OpticalPSF(lod, coma2=.5, obscuration=obscuration, oversampling=1)
        myImg = optics.drawImage(myImg, scale=0.2*lod, use_true_center=True, method='no_pixel')
        np.testing.assert_array_almost_equal(
            myImg.array, savedImg.array, 6,
            err_msg="Optical aberration (coma2) disagrees with expected result")

        # test trefoil1
        savedImg = galsim.fits.read(os.path.join(imgdir, "optics_trefoil1.fits"))
        optics = galsim.OpticalPSF(lod, trefoil1=.5, obscuration=obscuration, oversampling=1)
        myImg = optics.drawImage(myImg, scale=0.2*lod, use_true_center=True, method='no_pixel')
        np.testing.assert_array_almost_equal(
            myImg.array, savedImg.array, 6,
            err_msg="Optical aberration (trefoil1) disagrees with expected result")

        # test trefoil2
        savedImg = galsim.fits.read(os.path.join(imgdir, "optics_trefoil2.fits"))
        optics = galsim.OpticalPSF(lod, trefoil2=.5, obscuration=obscuration, oversampling=1)
        myImg = optics.drawImage(myImg, scale=0.2*lod, use_true_center=True, method='no_pixel')
        np.testing.assert_array_almost_equal(
            myImg.array, savedImg.array, 6,
            err_msg="Optical aberration (trefoil2) disagrees with expected result")

        # test spherical
        savedImg = galsim.fits.read(os.path.join(imgdir, "optics_spher.fits"))
        optics = galsim.OpticalPSF(lod, spher=.5, obscuration=obscuration, oversampling=1)
        myImg = optics.drawImage(myImg, scale=0.2*lod, use_true_center=True, method='no_pixel')
        np.testing.assert_array_almost_equal(
            myImg.array, savedImg.array, 6,
            err_msg="Optical aberration (spher) disagrees with expected result")

    # test all aberrations
    savedImg = galsim.fits.read(os.path.join(imgdir, "optics_all.fits"))
    optics = galsim.OpticalPSF(lod, defocus=.5, astig1=0.5, astig2=0.3, coma1=0.4, coma2=-0.3,
                               trefoil1=-0.2, trefoil2=0.1, spher=-0.8, obscuration=obscuration,
                               oversampling=1)
    myImg = optics.drawImage(myImg, scale=0.2*lod, use_true_center=True, method='no_pixel')
    np.testing.assert_array_almost_equal(
        myImg.array, savedImg.array, 6,
        err_msg="Optical aberration (all aberrations) disagrees with expected result")
    do_pickle(optics, lambda x: x.drawImage(nx=20, ny=20, scale=1.7, method='no_pixel'))
    do_pickle(optics)

    # test struts
    savedImg = galsim.fits.read(os.path.join(imgdir, "optics_struts.fits"))
    optics = galsim.OpticalPSF(
        lod, obscuration=obscuration, nstruts=5, strut_thick=0.04, strut_angle=8.*galsim.degrees,
        astig2=0.04, coma1=-0.07, defocus=0.09, oversampling=1)
    with assert_raises(TypeError):
        galsim.OpticalPSF(lod, nstruts=5, strut_thick=0.01, strut_angle=8.) # wrong units
    do_pickle(optics, lambda x: x.drawImage(nx=20, ny=20, scale=1.7, method='no_pixel'))
    do_pickle(optics)

    # Make sure it doesn't have some weird error if strut_angle=0 (should be the easiest case, but
    # check anyway...)
    optics_2 = galsim.OpticalPSF(
        lod, obscuration=obscuration, nstruts=4, strut_thick=0.05, strut_angle=0.*galsim.degrees,
        astig2=0.04, coma1=-0.07, defocus=0.09, oversampling=1)
    myImg = optics.drawImage(myImg, scale=0.2*lod, use_true_center=True, method='no_pixel')
    np.testing.assert_array_almost_equal(
        myImg.array, savedImg.array, 6,
        err_msg="Optical PSF (with struts) disagrees with expected result")
    # These are also the defaults for strut_thick and strut_angle
    optics_3 = galsim.OpticalPSF(
        lod, obscuration=obscuration, nstruts=4,
        astig2=0.04, coma1=-0.07, defocus=0.09, oversampling=1)
    assert optics_3 == optics_2
    do_pickle(optics_3)

    # make sure it doesn't completely explode when asked to return a PSF with non-circular pupil and
    # non-zero obscuration
    optics = galsim.OpticalPSF(
        lod, obscuration=obscuration, nstruts=5, strut_thick=0.04, strut_angle=8.*galsim.degrees,
        astig2=0.04, coma1=-0.07, defocus=0.09, circular_pupil=False, oversampling=1)
    do_pickle(optics, lambda x: x.drawImage(nx=20, ny=20, scale=1.7, method='no_pixel'))
    do_pickle(optics)
Example #21
0
def main(argv):
    """
    Getting reasonably close to including all the principle features of an image from a
    ground-based telescope:
      - Use a bulge plus disk model for the galaxy
      - Both galaxy components are Sersic profiles (n=3.5 and n=1.5 respectively)
      - Let the PSF have both atmospheric and optical components.
      - The atmospheric component is a Kolmogorov spectrum.
      - The optical component has some defocus, coma, and astigmatism.
      - Add both Poisson noise to the image and Gaussian read noise.
      - Let the pixels be slightly distorted relative to the sky.
    """
    # We do some fancier logging for demo3, just to demonstrate that we can:
    # - we log to both stdout and to a log file
    # - the log file has a lot more (mostly redundant) information
    logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout)
    if not os.path.isdir('output'):
        os.mkdir('output')
    logFile = logging.FileHandler(os.path.join("output", "script3.log"))
    logFile.setFormatter(logging.Formatter("%(name)s[%(levelname)s] %(asctime)s: %(message)s"))
    logging.getLogger("demo3").addHandler(logFile)
    logger = logging.getLogger("demo3")

    gal_flux = 1.e6        # ADU  ("Analog-to-digital units", the units of the numbers on a CCD)
    bulge_n = 3.5          #
    bulge_re = 2.3         # arcsec
    disk_n = 1.5           #
    disk_r0 = 0.85         # arcsec (corresponds to half_light_radius of ~3.7 arcsec)
    bulge_frac = 0.3       #
    gal_q = 0.73           # (axis ratio 0 < q < 1)
    gal_beta = 23          # degrees (position angle on the sky)
    atmos_fwhm=2.1         # arcsec
    atmos_e = 0.13         #
    atmos_beta = 0.81      # radians
    opt_defocus=0.53       # wavelengths
    opt_a1=-0.29           # wavelengths
    opt_a2=0.12            # wavelengths
    opt_c1=0.64            # wavelengths
    opt_c2=-0.33           # wavelengths
    opt_obscuration=0.3    # linear scale size of secondary mirror obscuration
    lam = 800              # nm    NB: don't use lambda - that's a reserved word.
    tel_diam = 4.          # meters
    pixel_scale = 0.23     # arcsec / pixel
    image_size = 64        # n x n pixels
    wcs_g1 = -0.02         #
    wcs_g2 = 0.01          #
    sky_level = 2.5e4      # ADU / arcsec^2
    gain = 1.7             # e- / ADU
                           # Note: here we assume 1 photon -> 1 e-, ignoring QE.  If you wanted,
                           # you could include the QE factor as part of the gain.
    read_noise = 0.3       # e- / pixel

    random_seed = 1314662

    logger.info('Starting demo script 3 using:')
    logger.info('    - Galaxy is bulge plus disk, flux = %.1e',gal_flux)
    logger.info('       - Bulge is Sersic (n = %.1f, re = %.2f), frac = %.1f',
                bulge_n,bulge_re,bulge_frac)
    logger.info('       - Disk is Sersic (n = %.1f, r0 = %.2f), frac = %.1f',
                disk_n,disk_r0,1-bulge_frac)
    logger.info('       - Shape is q,beta (%.2f,%.2f deg)', gal_q, gal_beta)
    logger.info('    - Atmospheric PSF is Kolmogorov with fwhm = %.2f',atmos_fwhm)
    logger.info('       - Shape is e,beta (%.2f,%.2f rad)', atmos_e, atmos_beta)
    logger.info('    - Optical PSF has defocus = %.2f, astigmatism = (%.2f,%.2f),',
                opt_defocus, opt_a1, opt_a2)
    logger.info('          coma = (%.2f,%.2f), lambda = %.0f nm, D = %.1f m',
                opt_c1, opt_c2, lam, tel_diam)
    logger.info('          obscuration linear size = %.1f',opt_obscuration)
    logger.info('    - pixel scale = %.2f,',pixel_scale)
    logger.info('    - WCS distortion = (%.2f,%.2f),',wcs_g1,wcs_g2)
    logger.info('    - Poisson noise (sky level = %.1e, gain = %.1f).',sky_level, gain)
    logger.info('    - Gaussian read noise (sigma = %.2f).',read_noise)

    # Initialize the (pseudo-)random number generator that we will be using below.
    rng = galsim.BaseDeviate(random_seed+1)

    # Define the galaxy profile.
    # Normally Sersic profiles are specified by half-light radius, the radius that
    # encloses half of the total flux.  However, for some purposes, it can be
    # preferable to instead specify the scale radius, where the surface brightness
    # drops to 1/e of the central peak value.
    bulge = galsim.Sersic(bulge_n, half_light_radius=bulge_re)
    disk = galsim.Sersic(disk_n, scale_radius=disk_r0)

    # Objects may be multiplied by a scalar (which means scaling the flux) and also
    # added to each other.
    gal = bulge_frac * bulge + (1-bulge_frac) * disk
    # Could also have written the following, which does the same thing:
    #   gal = galsim.Add([ bulge.withFlux(bulge_frac) , disk.withFlux(1-bulge_frac) ])
    # Both syntaxes work with more than two summands as well.

    # Set the overall flux of the combined object.
    gal = gal.withFlux(gal_flux)
    # Since the total flux of the components was 1, we could also have written:
    #   gal *= gal_flux
    # The withFlux method will always set the flux to the given value, while `gal *= flux`
    # will multiply whatever the current flux is by the given factor.

    # Set the shape of the galaxy according to axis ratio and position angle
    # Note: All angles in GalSim must have explicit units.  Options are:
    #       galsim.radians
    #       galsim.degrees
    #       galsim.arcmin
    #       galsim.arcsec
    #       galsim.hours
    gal_shape = galsim.Shear(q=gal_q, beta=gal_beta*galsim.degrees)
    gal = gal.shear(gal_shape)
    logger.debug('Made galaxy profile')

    # Define the atmospheric part of the PSF.
    # Note: the flux here is the default flux=1.
    atmos = galsim.Kolmogorov(fwhm=atmos_fwhm)
    # For the PSF shape here, we use ellipticity rather than axis ratio.
    # And the position angle can be either degrees or radians.  Here we chose radians.
    atmos = atmos.shear(e=atmos_e, beta=atmos_beta*galsim.radians)
    logger.debug('Made atmospheric PSF profile')

    # Define the optical part of the PSF:
    # The first argument of OpticalPSF below is lambda/diam (wavelength of light / telescope
    # diameter), which needs to be in the same units used to specify the image scale.  We are using
    # arcsec for that, so we have to self-consistently use arcsec here, using the following
    # calculation:
    lam_over_diam = lam * 1.e-9 / tel_diam # radians
    lam_over_diam *= 206265  # arcsec
    # Note that we could also have made GalSim do the conversion for us if we did not know the right
    # factor:
    # lam_over_diam = lam * 1.e-9 / tel_diam * galsim.radians
    # lam_over_diam = lam_over_diam / galsim.arcsec
    logger.debug('Calculated lambda over diam = %f arcsec', lam_over_diam)
    # The rest of the values should be given in units of the wavelength of the incident light.
    optics = galsim.OpticalPSF(lam_over_diam,
                               defocus = opt_defocus,
                               coma1 = opt_c1, coma2 = opt_c2,
                               astig1 = opt_a1, astig2 = opt_a2,
                               obscuration = opt_obscuration)
    logger.debug('Made optical PSF profile')

    # So far, our coordinate transformation between image and sky coordinates has been just a
    # scaling of the units between pixels and arcsec, which we have defined as the "pixel scale".
    # This is fine for many purposes, so we have made it easy to treat the coordinate systems
    # this way via the `scale` parameter to commands like drawImage.  However, in general, the
    # transformation between the two coordinate systems can be more complicated than that,
    # including distortions, rotations, variation in pixel size, and so forth.  GalSim can
    # model a number of different "World Coordinate System" (WCS) transformations.  See the
    # docstring for BaseWCS for more information.

    # In this case, we use a WCS that includes a distortion (specified as g1,g2 in this case),
    # which we call a ShearWCS.
    wcs = galsim.ShearWCS(scale=pixel_scale, shear=galsim.Shear(g1=wcs_g1, g2=wcs_g2))
    logger.debug('Made the WCS')

    # Next we will convolve the components in world coordinates.
    psf = galsim.Convolve([atmos, optics])
    final = galsim.Convolve([psf, gal])
    logger.debug('Convolved components into final profile')

    # This time we specify a particular size for the image rather than let GalSim
    # choose the size automatically.  GalSim has several kinds of images that it can use:
    #   ImageF uses 32-bit floats    (like a C float, aka numpy.float32)
    #   ImageD uses 64-bit floats    (like a C double, aka numpy.float64)
    #   ImageS uses 16-bit integers  (usually like a C short, aka numpy.int16)
    #   ImageI uses 32-bit integers  (usually like a C int, aka numpy.int32)
    # If you let the GalSim drawImage command create the image for you, it will create an ImageF.
    # However, you can make a different type if you prefer.  In this case, we still use
    # ImageF, since 32-bit floats are fine.  We just want to set the size explicitly.
    image = galsim.ImageF(image_size, image_size)
    # Draw the image with the given WCS.  Note that we use wcs rather than scale when the
    # WCS is more complicated than just a pixel scale.
    final.drawImage(image=image, wcs=wcs)

    # Also draw the effective PSF by itself and the optical PSF component alone.
    image_epsf = galsim.ImageF(image_size, image_size)
    psf.drawImage(image_epsf, wcs=wcs)

    # We also draw the optical part of the PSF at its own Nyquist-sampled pixel size
    # in order to better see the features of the (highly structured) profile.
    # In this case, we draw a "surface brightness image" using method='sb'.  Rather than
    # integrate the flux over the area of each pixel, this method just samples the surface
    # brightness value at the locations of the pixel centers.  We will encounter a few other
    # drawing methods as we go through this sequence of demos.  cf. demos 7, 8, 10, and 11.
    image_opticalpsf = optics.drawImage(method='sb')
    logger.debug('Made image of the profile')

    # Add a constant sky level to the image.
    image += sky_level * pixel_scale**2

    # This time, we use CCDNoise to model the real noise in a CCD image.  It takes a sky level,
    # gain, and read noise, so it can be a bit more realistic than the simpler GaussianNoise
    # or PoissonNoise that we used in demos 1 and 2.
    #
    # The gain is in units of e-/ADU.  Technically, one should also account for quantum efficiency
    # (QE) of the detector. An ideal CCD has one electron per incident photon, but real CCDs have
    # QE less than 1, so not every photon triggers an electron.  We are essentially folding
    # the quantum efficiency (and filter transmission and anything else like that) into the gain.
    # The read_noise value is given as e-/pixel.  This is modeled as a pure Gaussian noise
    # added to the image after applying the pure Poisson noise.
    image.addNoise(galsim.CCDNoise(rng, gain=gain, read_noise=read_noise))

    # Subtract off the sky.
    image -= sky_level * pixel_scale**2
    logger.debug('Added Gaussian and Poisson noise')

    # Write the images to files.
    file_name = os.path.join('output', 'demo3.fits')
    file_name_epsf = os.path.join('output','demo3_epsf.fits')
    file_name_opticalpsf = os.path.join('output','demo3_opticalpsf.fits')
    image.write(file_name)
    image_epsf.write(file_name_epsf)
    image_opticalpsf.write(file_name_opticalpsf)
    logger.info('Wrote image to %r', file_name)
    logger.info('Wrote effective PSF image to %r', file_name_epsf)
    logger.info('Wrote optics-only PSF image (Nyquist sampled) to %r', file_name_opticalpsf)

    # Check that the HSM package, which is bundled with GalSim, finds a good estimate
    # of the shear.
    results = galsim.hsm.EstimateShear(image, image_epsf)

    logger.info('HSM reports that the image has observed shape and size:')
    logger.info('    e1 = %.3f, e2 = %.3f, sigma = %.3f (pixels)', results.observed_shape.e1,
                results.observed_shape.e2, results.moments_sigma)
    logger.info('When carrying out Regaussianization PSF correction, HSM reports')
    logger.info('    e1, e2 = %.3f, %.3f',
                results.corrected_e1, results.corrected_e2)
    logger.info('Expected values in the limit that noise and non-Gaussianity are negligible:')
    # Convention for shear addition is to apply the second term initially followed by the first.
    # So this needs to be the WCS shear + the galaxy shape in that order.
    total_shape = galsim.Shear(g1=wcs_g1, g2=wcs_g2) + gal_shape
    logger.info('    e1, e2 = %.3f, %.3f', total_shape.e1, total_shape.e2)
Example #22
0
def test_OpticalPSF_aberrations_kwargs():
    """Test that OpticalPSF aberrations kwarg works just like specifying aberrations.
    """
    # Make an OpticalPSF with direct specification of aberrations.
    lod = 0.04
    obscuration = 0.3
    opt1 = galsim.OpticalPSF(lod, obscuration=obscuration, defocus=0.1, coma1=-0.1, coma2=0.3)

    # Now make it with an aberrations list.  (Note: should work with len < 12)
    aberrations = np.zeros(9)
    aberrations[4] = 0.1
    aberrations[7] = -0.1
    aberrations[8] = 0.3

    opt2 = galsim.OpticalPSF(lod, obscuration=obscuration, aberrations=aberrations)

    # Make sure they agree.
    np.testing.assert_array_equal(
        opt1.drawImage(scale=0.2*lod, method='no_pixel').array,
        opt2.drawImage(scale=0.2*lod, method='no_pixel').array,
        err_msg="Optical PSF depends on how aberrations are specified (4,8,11)")

    # Repeat with all aberrations up to index 11, using a regular list, not a numpy array
    opt1 = galsim.OpticalPSF(lod, defocus=.5, astig1=0.5, astig2=0.3, coma1=0.4, coma2=-0.3,
                             trefoil1=-0.2, trefoil2=0.1, spher=-0.8, obscuration=obscuration)
    aberrations = [ 0.0 ] * 4 + [ 0.5, 0.5, 0.3, 0.4, -0.3, -0.2, 0.1, -0.8 ]
    opt2 = galsim.OpticalPSF(lod, obscuration=obscuration, aberrations=aberrations)
    np.testing.assert_array_equal(
        opt1.drawImage(scale=0.2*lod, method='no_pixel').array,
        opt2.drawImage(scale=0.2*lod, method='no_pixel').array,
        err_msg="Optical PSF depends on how aberrations are specified (full list)")
    do_pickle(opt2, lambda x: x.drawImage(nx=20, ny=20, scale=0.01, method='no_pixel'))
    do_pickle(opt2)

    # Also, check for proper response to weird inputs.
    # aberrations must be a list or an array
    with assert_raises(TypeError):
        galsim.OpticalPSF(lod, aberrations=0.3)
    # It must have at least 2 elements
    with assert_raises(ValueError):
        galsim.OpticalPSF(lod, aberrations=[0.0])
    with assert_raises(ValueError):
        galsim.OpticalPSF(lod, aberrations=[])
    # 2 zeros is equivalent to None
    assert galsim.OpticalPSF(lod, aberrations=[0, 0]) == galsim.OpticalPSF(lod)
    # The first element must be 0. (Just a warning!)
    with assert_warns(galsim.GalSimWarning):
        galsim.OpticalPSF(lod, aberrations=[0.3]*8)
    # Cannot provide both aberrations and specific ones by name.
    with assert_raises(TypeError):
        galsim.OpticalPSF(lod, aberrations=np.zeros(8), defocus=-0.12)
Example #23
0
def main(argv):
    """
    Make a fits image cube where each frame has two images of the same galaxy drawn 
    with regular FFT convolution and with photon shooting.

    We do this for 5 different PSFs and 5 different galaxies, each with 4 different (random)
    fluxes, sizes, and shapes.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo7")

    # To turn off logging:
    #logger.propagate = False

    # Define some parameters we'll use below.

    # Make output directory if not already present.
    if not os.path.isdir('output'):
        os.mkdir('output')

    file_name = os.path.join('output', 'cube_phot.fits.gz')

    random_seed = 553728
    sky_level = 1.e4  # ADU / arcsec^2
    pixel_scale = 0.28  # arcsec
    nx = 64
    ny = 64

    gal_flux_min = 1.e4  # Range for galaxy flux
    gal_flux_max = 1.e5
    gal_hlr_min = 0.3  # arcsec
    gal_hlr_max = 1.3  # arcsec
    gal_e_min = 0.  # Range for ellipticity
    gal_e_max = 0.8

    psf_fwhm = 0.65  # arcsec

    # This script is set up as a comparison between using FFTs for doing the convolutions and
    # shooting photons.  The two methods have trade-offs in speed and accuracy which vary
    # with the kind of profile being drawn and the S/N of the object, among other factors.
    # In addition, for each method, there are a number of parameters GalSim uses that control
    # aspects of the calculation that further affect the speed and accuracy.
    #
    # We encapsulate these parameters with an object called GSParams.  The default values
    # are intended to be accurate enough for normal precision shear tests, without sacrificing
    # too much speed.
    #
    # Any PSF or galaxy object can be given a gsparams argument on construction that can
    # have different values to make the calculation more or less accurate (typically trading
    # off for speed or memory).
    #
    # In this script, we adjust some of the values slightly, just to show you how it works.
    # You could play around with these values and see what effect they have on the drawn images.
    # Usually, it requires a pretty drastic change in these parameters for you to be able to
    # notice the difference by eye.  But subtle effects that may impact the shapes of galaxies
    # can happen well before then.

    # Type help(galsim.GSParams) for the complete list of parameters and more detailed
    # documentation, including the default values for each parameter.
    gsparams = galsim.GSParams(
        alias_threshold=
        1.e-2,  # maximum fractional flux that may be aliased around edge of FFT
        maxk_threshold=
        2.e-3,  # k-values less than this may be excluded off edge of FFT
        xvalue_accuracy=
        1.e-4,  # approximations in real space aim to be this accurate
        kvalue_accuracy=
        1.e-4,  # approximations in fourier space aim to be this accurate
        shoot_accuracy=
        1.e-4,  # approximations in photon shooting aim to be this accurate
        minimum_fft_size=64)  # minimum size of ffts

    logger.info('Starting demo script 7')

    # Make the pixel:
    pix = galsim.Pixel(xw=pixel_scale)

    # Make the PSF profiles:
    psf1 = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams)
    psf2 = galsim.Moffat(fwhm=psf_fwhm, beta=2.4, gsparams=gsparams)
    psf3_inner = galsim.Gaussian(fwhm=psf_fwhm, flux=0.8, gsparams=gsparams)
    psf3_outer = galsim.Gaussian(fwhm=2 * psf_fwhm,
                                 flux=0.2,
                                 gsparams=gsparams)
    psf3 = psf3_inner + psf3_outer
    atmos = galsim.Gaussian(fwhm=psf_fwhm, gsparams=gsparams)
    optics = galsim.OpticalPSF(lam_over_diam=0.6 * psf_fwhm,
                               obscuration=0.4,
                               defocus=0.1,
                               astig1=0.3,
                               astig2=-0.2,
                               coma1=0.2,
                               coma2=0.1,
                               spher=-0.3,
                               gsparams=gsparams)
    psf4 = galsim.Convolve([atmos, optics
                            ])  # Convolve inherits the gsparams from the first
    # item in the list.  (Or you can supply a gsparams
    # argument explicitly if you want to override this.)
    atmos = galsim.Kolmogorov(fwhm=psf_fwhm, gsparams=gsparams)
    optics = galsim.Airy(lam_over_diam=0.3 * psf_fwhm, gsparams=gsparams)
    psf5 = galsim.Convolve([atmos, optics])
    psfs = [psf1, psf2, psf3, psf4, psf5]
    psf_names = [
        "Gaussian", "Moffat", "Double Gaussian", "OpticalPSF",
        "Kolmogorov * Airy"
    ]
    psf_times = [0, 0, 0, 0, 0]
    psf_fft_times = [0, 0, 0, 0, 0]
    psf_phot_times = [0, 0, 0, 0, 0]

    # Make the galaxy profiles:
    gal1 = galsim.Gaussian(half_light_radius=1, gsparams=gsparams)
    gal2 = galsim.Exponential(half_light_radius=1, gsparams=gsparams)
    gal3 = galsim.DeVaucouleurs(half_light_radius=1, gsparams=gsparams)
    gal4 = galsim.Sersic(half_light_radius=1, n=2.5, gsparams=gsparams)
    bulge = galsim.Sersic(half_light_radius=0.7, n=3.2, gsparams=gsparams)
    disk = galsim.Sersic(half_light_radius=1.2, n=1.5, gsparams=gsparams)
    gal5 = 0.4 * bulge + 0.6 * disk  # Net half-light radius is only approximate for this one.
    gals = [gal1, gal2, gal3, gal4, gal5]
    gal_names = [
        "Gaussian", "Exponential", "Devaucouleurs", "n=2.5 Sersic",
        "Bulge + Disk"
    ]
    gal_times = [0, 0, 0, 0, 0]
    gal_fft_times = [0, 0, 0, 0, 0]
    gal_phot_times = [0, 0, 0, 0, 0]

    # Other times to keep track of:
    setup_times = 0
    fft_times = 0
    phot_times = 0
    noise_times = 0

    # Loop over combinations of psf, gal, and make 4 random choices for flux, size, shape.
    all_images = []
    k = 0
    for ipsf in range(len(psfs)):
        psf = psfs[ipsf]
        psf_name = psf_names[ipsf]
        for igal in range(len(gals)):
            gal = gals[igal]
            gal_name = gal_names[igal]
            for i in range(4):
                logger.debug('Start work on image %d', i)
                t1 = time.time()

                # Initialize the random number generator we will be using.
                rng = galsim.UniformDeviate(random_seed + k)

                # Get a new copy, we'll want to keep the original unmodified.
                gal1 = gal.copy()

                # Generate random variates:
                flux = rng() * (gal_flux_max - gal_flux_min) + gal_flux_min
                gal1.setFlux(flux)

                hlr = rng() * (gal_hlr_max - gal_hlr_min) + gal_hlr_min
                gal1.applyDilation(hlr)

                beta_ellip = rng() * 2 * math.pi * galsim.radians
                ellip = rng() * (gal_e_max - gal_e_min) + gal_e_min
                gal_shape = galsim.Shear(e=ellip, beta=beta_ellip)
                gal1.applyShear(gal_shape)

                # Build the final object by convolving the galaxy, PSF and pixel response.
                final = galsim.Convolve([psf, pix, gal1])
                # For photon shooting, need a version without the pixel (see below).
                final_nopix = galsim.Convolve([psf, gal1])

                # Create the large, double width output image
                image = galsim.ImageF(2 * nx + 2, ny)

                # Rather than provide a dx= argument to the draw commands, we can also
                # set the pixel scale in the image itself with setScale.
                image.setScale(pixel_scale)

                # Assign the following two "ImageViews", fft_image and phot_image.
                # Using the syntax below, these are views into the larger image.
                # Changes/additions to the sub-images referenced by the views are automatically
                # reflected in the original image.
                fft_image = image[galsim.BoundsI(1, nx, 1, ny)]
                phot_image = image[galsim.BoundsI(nx + 3, 2 * nx + 2, 1, ny)]

                logger.debug(
                    '   Read in training sample galaxy and PSF from file')
                t2 = time.time()

                # Draw the profile
                final.draw(fft_image)

                logger.debug(
                    '   Drew fft image.  Total drawn flux = %f.  .flux = %f',
                    fft_image.array.sum(), final.getFlux())
                t3 = time.time()

                # Add Poisson noise
                sky_level_pixel = sky_level * pixel_scale**2
                fft_image.addNoise(
                    galsim.PoissonNoise(rng, sky_level=sky_level_pixel))

                t4 = time.time()

                # The next two lines are just to get the output from this demo script
                # to match the output from the parsing of demo7.yaml.
                rng = galsim.UniformDeviate(random_seed + k)
                rng()
                rng()
                rng()
                rng()

                # Repeat for photon shooting image.
                # Photon shooting automatically convolves by the pixel, so we've made sure not
                # to include it in the profile!
                final_nopix.drawShoot(phot_image,
                                      max_extra_noise=sky_level_pixel / 100,
                                      rng=rng)
                t5 = time.time()

                # For photon shooting, galaxy already has Poisson noise, so we want to make
                # sure not to add that noise again!  Thus, we just add sky noise, which
                # is Poisson with the mean = sky_level_pixel
                pd = galsim.PoissonDeviate(rng, mean=sky_level_pixel)
                # DeviateNoise just adds the action of the given deviate to every pixel.
                phot_image.addNoise(galsim.DeviateNoise(pd))
                # For PoissonDeviate, the mean is not zero, so for a background-subtracted
                # image, we need to subtract the mean back off when we are done.
                phot_image -= sky_level_pixel

                logger.debug(
                    '   Added Poisson noise.  Image fluxes are now %f and %f',
                    fft_image.array.sum(), phot_image.array.sum())
                t6 = time.time()

                # Store that into the list of all images
                all_images += [image]

                k = k + 1
                logger.info(
                    '%d: %s * %s, flux = %.2e, hlr = %.2f, ellip = (%.2f,%.2f)',
                    k, gal_name, psf_name, flux, hlr, gal_shape.getE1(),
                    gal_shape.getE2())
                logger.debug('   Times: %f, %f, %f, %f, %f', t2 - t1, t3 - t2,
                             t4 - t3, t5 - t4, t6 - t5)

                psf_times[ipsf] += t6 - t1
                psf_fft_times[ipsf] += t3 - t2
                psf_phot_times[ipsf] += t5 - t4
                gal_times[igal] += t6 - t1
                gal_fft_times[igal] += t3 - t2
                gal_phot_times[igal] += t5 - t4
                setup_times += t2 - t1
                fft_times += t3 - t2
                phot_times += t5 - t4
                noise_times += t4 - t3 + t6 - t5

    logger.info('Done making images of galaxies')
    logger.info('')
    logger.info('Some timing statistics:')
    logger.info('   Total time for setup steps = %f', setup_times)
    logger.info('   Total time for regular fft drawing = %f', fft_times)
    logger.info('   Total time for photon shooting = %f', phot_times)
    logger.info('   Total time for adding noise = %f', noise_times)
    logger.info('')
    logger.info('Breakdown by PSF type:')
    for ipsf in range(len(psfs)):
        logger.info('   %s: Total time = %f  (fft: %f, phot: %f)',
                    psf_names[ipsf], psf_times[ipsf], psf_fft_times[ipsf],
                    psf_phot_times[ipsf])
    logger.info('')
    logger.info('Breakdown by Galaxy type:')
    for igal in range(len(gals)):
        logger.info('   %s: Total time = %f  (fft: %f, phot: %f)',
                    gal_names[igal], gal_times[igal], gal_fft_times[igal],
                    gal_phot_times[igal])
    logger.info('')

    # Now write the image to disk.
    # With any write command, you can optionally compress the file using several compression
    # schemes:
    #   'gzip' uses gzip on the full output file.
    #   'bzip2' uses bzip2 on the full output file.
    #   'rice' uses rice compression on the image, leaving the fits headers readable.
    #   'gzip_tile' uses gzip in tiles on the output image, leaving the fits headers readable.
    #   'hcompress' uses hcompress on the image, but it is only valid for 2-d data, so it
    #               doesn't work for writeCube.
    #   'plio' uses plio on the image, but it is only valid for positive integer data.
    # Furthermore, the first three have standard filename extensions associated with them,
    # so if you don't specify a compression, but the filename ends with '.gz', '.bz2' or '.fz',
    # the corresponding compression will be selected automatically.
    # In other words, the `compression='gzip'` specification is actually optional here:
    galsim.fits.writeCube(all_images, file_name, compression='gzip')
    logger.info('Wrote fft image to fits data cube %r', file_name)
Example #24
0
def test_OpticalPSF_flux_scaling():
    """Test flux scaling for OpticalPSF.
    """
    # OpticalPSF test params (only a selection)
    test_flux = 1.8
    test_loD = 1.9
    test_obscuration = 0.32
    test_defocus = -0.7
    test_astig1 = 0.03
    test_astig2 = -0.04
    test_oversampling = 1.3
    test_pad_factor = 1.7

    # decimal point to go to for parameter value comparisons
    param_decimal = 12

    # init
    obj = galsim.OpticalPSF(
        lam_over_diam=test_loD, oversampling=test_oversampling,pad_factor=test_pad_factor,
        defocus=test_defocus, astig1=test_astig1, astig2=test_astig2, flux=test_flux)
    obj *= 2.
    np.testing.assert_almost_equal(
        obj.flux, test_flux * 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __imul__.")
    obj = galsim.OpticalPSF(
        lam_over_diam=test_loD, oversampling=test_oversampling, pad_factor=test_pad_factor,
        defocus=test_defocus, astig1=test_astig1, astig2=test_astig2, flux=test_flux)
    obj /= 2.
    np.testing.assert_almost_equal(
        obj.flux, test_flux / 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __idiv__.")
    obj = galsim.OpticalPSF(
        lam_over_diam=test_loD, oversampling=test_oversampling, pad_factor=test_pad_factor,
        defocus=test_defocus, astig1=test_astig1, astig2=test_astig2, flux=test_flux)
    obj2 = obj * 2.
    # First test that original obj is unharmed...
    np.testing.assert_almost_equal(
        obj.flux, test_flux, decimal=param_decimal,
        err_msg="Flux param inconsistent after __rmul__ (original).")
    # Then test new obj2 flux
    np.testing.assert_almost_equal(
        obj2.flux, test_flux * 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __rmul__ (result).")
    obj = galsim.OpticalPSF(
        lam_over_diam=test_loD, oversampling=test_oversampling, pad_factor=test_pad_factor,
        defocus=test_defocus, astig1=test_astig1, astig2=test_astig2, flux=test_flux)
    obj2 = 2. * obj
    # First test that original obj is unharmed...
    np.testing.assert_almost_equal(
        obj.flux, test_flux, decimal=param_decimal,
        err_msg="Flux param inconsistent after __mul__ (original).")
    # Then test new obj2 flux
    np.testing.assert_almost_equal(
        obj2.flux, test_flux * 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __mul__ (result).")
    obj = galsim.OpticalPSF(
        lam_over_diam=test_loD, oversampling=test_oversampling, pad_factor=test_pad_factor,
        defocus=test_defocus, astig1=test_astig1, astig2=test_astig2, flux=test_flux)
    obj2 = obj / 2.
    # First test that original obj is unharmed...
    np.testing.assert_almost_equal(
        obj.flux, test_flux, decimal=param_decimal,
        err_msg="Flux param inconsistent after __div__ (original).")
    # Then test new obj2 flux
    np.testing.assert_almost_equal(
        obj2.flux, test_flux / 2., decimal=param_decimal,
        err_msg="Flux param inconsistent after __div__ (result).")
Example #25
0
def test_flip():
    """Test several ways to flip a profile
    """
    # The Shapelet profile has the advantage of being fast and not circularly symmetric, so
    # it is a good test of the actual code for doing the flips (in SBTransform).
    # But since the bug Rachel reported in #645 was actually in SBInterpolatedImage
    # (one calculation implicitly assumed dx > 0), it seems worthwhile to run through all the
    # classes to make sure we hit everything with negative steps for dx and dy.
    prof_list = [
        galsim.Shapelet(sigma=0.17, order=2,
                        bvec=[1.7, 0.01,0.03, 0.29, 0.33, -0.18]),
    ]
    if __name__ == "__main__":
        image_dir = './real_comparison_images'
        catalog_file = 'test_catalog.fits'
        rgc = galsim.RealGalaxyCatalog(catalog_file, dir=image_dir)
        # Some of these are slow, so only do the Shapelet test as part of the normal unit tests.
        prof_list += [
            galsim.Airy(lam_over_diam=0.17, flux=1.7),
            galsim.Airy(lam_over_diam=0.17, obscuration=0.2, flux=1.7),
            # Box gets rendered with real-space convolution.  The default accuracy isn't quite
            # enough to get the flip to match at 6 decimal places.
            galsim.Box(0.17, 0.23, flux=1.7,
                       gsparams=galsim.GSParams(realspace_relerr=1.e-6)),
            # Without being convolved by anything with a reasonable k cutoff, this needs
            # a very large fft.
            galsim.DeVaucouleurs(half_light_radius=0.17, flux=1.7),
            # I don't really understand why this needs a lower maxk_threshold to work, but
            # without it, the k-space tests fail.
            galsim.Exponential(scale_radius=0.17, flux=1.7,
                               gsparams=galsim.GSParams(maxk_threshold=1.e-4)),
            galsim.Gaussian(sigma=0.17, flux=1.7),
            galsim.Kolmogorov(fwhm=0.17, flux=1.7),
            galsim.Moffat(beta=2.5, fwhm=0.17, flux=1.7),
            galsim.Moffat(beta=2.5, fwhm=0.17, flux=1.7, trunc=0.82),
            galsim.OpticalPSF(lam_over_diam=0.17, obscuration=0.2, nstruts=6,
                              coma1=0.2, coma2=0.5, defocus=-0.1, flux=1.7),
            # Like with Box, we need to increase the real-space convolution accuracy.
            # This time lowering both relerr and abserr.
            galsim.Pixel(0.23, flux=1.7,
                         gsparams=galsim.GSParams(realspace_relerr=1.e-6,
                                                  realspace_abserr=1.e-8)),
            # Note: RealGalaxy should not be rendered directly because of the deconvolution.
            # Here we convolve it by a Gaussian that is slightly larger than the original PSF.
            galsim.Convolve([ galsim.RealGalaxy(rgc, index=0, flux=1.7),  # "Real" RealGalaxy
                              galsim.Gaussian(sigma=0.08) ]),
            galsim.Convolve([ galsim.RealGalaxy(rgc, index=1, flux=1.7),  # "Fake" RealGalaxy
                              galsim.Gaussian(sigma=0.08) ]),             # (cf. test_real.py)
            galsim.Spergel(nu=-0.19, half_light_radius=0.17, flux=1.7),
            galsim.Spergel(nu=0., half_light_radius=0.17, flux=1.7),
            galsim.Spergel(nu=0.8, half_light_radius=0.17, flux=1.7),
            galsim.Sersic(n=2.3, half_light_radius=0.17, flux=1.7),
            galsim.Sersic(n=2.3, half_light_radius=0.17, flux=1.7, trunc=0.82),
            # The shifts here caught a bug in how SBTransform handled the recentering.
            # Two of the shifts (0.125 and 0.375) lead back to 0.0 happening on an integer
            # index, which now works correctly.
            galsim.Sum([ galsim.Gaussian(sigma=0.17, flux=1.7).shift(-0.2,0.125),
                         galsim.Exponential(scale_radius=0.23, flux=3.1).shift(0.375,0.23)]),
            galsim.TopHat(0.23, flux=1.7),
            # Box and Pixel use real-space convolution.  Convolve with a Gaussian to get fft.
            galsim.Convolve([ galsim.Box(0.17, 0.23, flux=1.7).shift(-0.2,0.1),
                              galsim.Gaussian(sigma=0.09) ]),
            galsim.Convolve([ galsim.TopHat(0.17, flux=1.7).shift(-0.275,0.125),
                              galsim.Gaussian(sigma=0.09) ]),
            # Test something really crazy with several layers worth of transformations
            galsim.Convolve([
                galsim.Sum([
                    galsim.Gaussian(sigma=0.17, flux=1.7).shear(g1=0.1,g2=0.2).shift(2,3),
                    galsim.Kolmogorov(fwhm=0.33, flux=3.9).transform(0.31,0.19,-0.23,0.33) * 88.,
                    galsim.Box(0.11, 0.44, flux=4).rotate(33 * galsim.degrees) / 1.9
                ]).shift(-0.3,1),
                galsim.AutoConvolve(galsim.TopHat(0.5).shear(g1=0.3,g2=0)).rotate(3*galsim.degrees),
                (galsim.AutoCorrelate(galsim.Box(0.2, 0.3)) * 11).shift(3,2).shift(2,-3) * 0.31
            ]).shift(0,0).transform(0,-1,-1,0).shift(-1,1)
        ]

    s = galsim.Shear(g1=0.11, g2=-0.21)
    s1 = galsim.Shear(g1=0.11, g2=0.21)  # Appropriate for the flips around x and y axes
    s2 = galsim.Shear(g1=-0.11, g2=-0.21)  # Appropriate for the flip around x=y

    # Also use shears with just a g1 to get dx != dy, but dxy, dyx = 0.
    q = galsim.Shear(g1=0.11, g2=0.)
    q1 = galsim.Shear(g1=0.11, g2=0.)  # Appropriate for the flips around x and y axes
    q2 = galsim.Shear(g1=-0.11, g2=0.)  # Appropriate for the flip around x=y

    decimal=6  # Oddly, these aren't as precise as I would have expected.
               # Even when we only go to this many digits of accuracy, the Exponential needed
               # a lower than default value for maxk_threshold.
    im = galsim.ImageD(16,16, scale=0.05)

    for prof in prof_list:
        print('prof = ',prof)

        # Not all profiles are expected to have a max_sb value close to the maximum pixel value,
        # so mark the ones where we don't want to require this to be true.
        close_maxsb = True
        name = str(prof)
        if ('DeVauc' in name or 'Sersic' in name or 'Spergel' in name or
            'Optical' in name or 'shift' in name):
            close_maxsb = False

        # Make sure we hit all 4 fill functions.
        # image_x uses fillXValue with izero, jzero
        # image_x1 uses fillXValue with izero, jzero, and unequal dx,dy
        # image_x2 uses fillXValue with dxy, dyx
        # image_k uses fillKValue with izero, jzero
        # image_k1 uses fillKValue with izero, jzero, and unequal dx,dy
        # image_k2 uses fillKValue with dxy, dyx
        image_x = prof.drawImage(image=im.copy(), method='no_pixel')
        image_x1 = prof.shear(q).drawImage(image=im.copy(), method='no_pixel')
        image_x2 = prof.shear(s).drawImage(image=im.copy(), method='no_pixel')
        image_k = prof.drawImage(image=im.copy())
        image_k1 = prof.shear(q).drawImage(image=im.copy())
        image_k2 = prof.shear(s).drawImage(image=im.copy())

        if close_maxsb:
            np.testing.assert_allclose(
                    image_x.array.max(), prof.max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image_x1.array.max(), prof.shear(q).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image_x2.array.max(), prof.shear(s).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")

        # Flip around y axis (i.e. x -> -x)
        flip1 = prof.transform(-1, 0, 0, 1)
        image2_x = flip1.drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x.array, image2_x.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed x test")
        image2_x1 = flip1.shear(q1).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x1.array, image2_x1.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed x1 test")
        image2_x2 = flip1.shear(s1).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x2.array, image2_x2.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed x2 test")
        image2_k = flip1.drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k.array, image2_k.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed k test")
        image2_k1 = flip1.shear(q1).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k1.array, image2_k1.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed k1 test")
        image2_k2 = flip1.shear(s1).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k2.array, image2_k2.array[:,::-1], decimal=decimal,
            err_msg="Flipping image around y-axis failed k2 test")

        if close_maxsb:
            np.testing.assert_allclose(
                    image2_x.array.max(), flip1.max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x1.array.max(), flip1.shear(q).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x2.array.max(), flip1.shear(s).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")

        # Flip around x axis (i.e. y -> -y)
        flip2 = prof.transform(1, 0, 0, -1)
        image2_x = flip2.drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x.array, image2_x.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed x test")
        image2_x1 = flip2.shear(q1).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x1.array, image2_x1.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed x1 test")
        image2_x2 = flip2.shear(s1).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x2.array, image2_x2.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed x2 test")
        image2_k = flip2.drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k.array, image2_k.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed k test")
        image2_k1 = flip2.shear(q1).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k1.array, image2_k1.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed k1 test")
        image2_k2 = flip2.shear(s1).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k2.array, image2_k2.array[::-1,:], decimal=decimal,
            err_msg="Flipping image around x-axis failed k2 test")

        if close_maxsb:
            np.testing.assert_allclose(
                    image2_x.array.max(), flip2.max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x1.array.max(), flip2.shear(q).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x2.array.max(), flip2.shear(s).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")

        # Flip around x=y (i.e. y -> x, x -> y)
        flip3 = prof.transform(0, 1, 1, 0)
        image2_x = flip3.drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x.array, np.transpose(image2_x.array), decimal=decimal,
            err_msg="Flipping image around x=y failed x test")
        image2_x1 = flip3.shear(q2).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x1.array, np.transpose(image2_x1.array), decimal=decimal,
            err_msg="Flipping image around x=y failed x1 test")
        image2_x2 = flip3.shear(s2).drawImage(image=im.copy(), method='no_pixel')
        np.testing.assert_array_almost_equal(
            image_x2.array, np.transpose(image2_x2.array), decimal=decimal,
            err_msg="Flipping image around x=y failed x2 test")
        image2_k = flip3.drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k.array, np.transpose(image2_k.array), decimal=decimal,
            err_msg="Flipping image around x=y failed k test")
        image2_k1 = flip3.shear(q2).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k1.array, np.transpose(image2_k1.array), decimal=decimal,
            err_msg="Flipping image around x=y failed k1 test")
        image2_k2 = flip3.shear(s2).drawImage(image=im.copy())
        np.testing.assert_array_almost_equal(
            image_k2.array, np.transpose(image2_k2.array), decimal=decimal,
            err_msg="Flipping image around x=y failed k2 test")

        if close_maxsb:
            np.testing.assert_allclose(
                    image2_x.array.max(), flip3.max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x1.array.max(), flip3.shear(q).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")
            np.testing.assert_allclose(
                    image2_x2.array.max(), flip3.shear(s).max_sb*im.scale**2, rtol=0.2,
                    err_msg="max_sb did not match maximum pixel value")

        do_pickle(prof, lambda x: x.drawImage(image=im.copy(), method='no_pixel'))
        do_pickle(flip1, lambda x: x.drawImage(image=im.copy(), method='no_pixel'))
        do_pickle(flip2, lambda x: x.drawImage(image=im.copy(), method='no_pixel'))
        do_pickle(flip3, lambda x: x.drawImage(image=im.copy(), method='no_pixel'))
        do_pickle(prof)
        do_pickle(flip1)
        do_pickle(flip2)
        do_pickle(flip3)
Example #26
0
def test_OpticalPSF_pupil_plane():
    """Test the ability to generate a PSF using an image of the pupil plane.
    """
    # Test case: lam/diam=0.12, obscuration=0.18, 4 struts of the default width and with rotation
    # from the vertical of -15 degrees.  There are two versions of these tests at different
    # oversampling levels.
    #
    # To (re-)generate the pupil plane images for this test, simply delete
    # tests/Optics_comparison_images/sample_pupil_rolled.fits and
    # tests/Optics_comparison_images/sample_pupil_rolled_oversample.fits.gz,
    # and then rerun this function.  Note that these images are also used in test_ne(), so there
    # may be some racing if this script is tested in parallel before the fits files are regenerated.

    # First test: should get excellent agreement between that particular OpticalPSF with specified
    # options and one from loading the pupil plane image.  Note that this won't work if you change
    # the optical PSF parameters, unless you also regenerate the test image.
    lam_over_diam = 0.12
    obscuration = 0.18
    nstruts = 4
    strut_angle = -15.*galsim.degrees
    scale = 0.055
    ref_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, nstruts=nstruts,
                                oversampling=pp_oversampling, strut_angle=strut_angle,
                                pad_factor=pp_pad_factor)
    if os.path.isfile(os.path.join(imgdir, pp_file)):
        im = galsim.fits.read(os.path.join(imgdir, pp_file))
    else:
        import warnings
        warnings.warn("Could not find file {0}, so generating it from scratch.  This should only "
                      "happen if you intentionally deleted the file in order to regenerate it!"
                      .format(pp_file))
        im = galsim.Image(ref_psf._psf.aper.illuminated.astype(float))
        im.scale = ref_psf._psf.aper.pupil_plane_scale
        print('pupil_plane image has scale = ',im.scale)
        im.write(os.path.join(imgdir, pp_file))
    pp_scale = im.scale
    print('pupil_plane image has scale = ',pp_scale)

    # For most of the tests, we remove this scale, since for achromatic tests, you don't really
    # need it, and it is invalid to give lam_over_diam (rather than lam, diam separately) when
    # there is a specific scale for the pupil plane image.  But see the last test below where
    # we do use lam, diam separately with the input image.
    im.wcs = None
    # This implies that the lam_over_diam value is valid.
    test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration,
                                 oversampling=pp_oversampling, pupil_plane_im=im,
                                 pad_factor=pp_pad_factor)
    im_ref_psf = ref_psf.drawImage(scale=scale)
    im_test_psf = galsim.ImageD(im_ref_psf.array.shape[0], im_ref_psf.array.shape[1])
    im_test_psf = test_psf.drawImage(image=im_test_psf, scale=scale)

    if pp_test_type == 'image':
        np.testing.assert_array_almost_equal(
            im_test_psf.array, im_ref_psf.array, decimal=pp_decimal,
            err_msg="Inconsistent OpticalPSF image for basic model after loading pupil plane.")
    else:
        test_moments = im_test_psf.FindAdaptiveMom()
        ref_moments = im_ref_psf.FindAdaptiveMom()
        np.testing.assert_almost_equal(
            test_moments.moments_sigma, ref_moments.moments_sigma, decimal=pp_decimal,
            err_msg="Inconsistent OpticalPSF image for basic model after loading pupil plane.")

    if do_slow_tests:
        do_pickle(test_psf, lambda x: x.drawImage(nx=20, ny=20, scale=0.07, method='no_pixel'))
        do_pickle(test_psf)

    # Make a smaller pupil plane image to test the pickling of this, even without slow tests.
    factor = 4 if not do_slow_tests else 16
    with assert_warns(galsim.GalSimWarning):
        alt_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration,
                                    oversampling=1., pupil_plane_im=im.bin(factor,factor),
                                    pad_factor=1.)
        do_pickle(alt_psf)

    assert_raises(ValueError, galsim.OpticalPSF, lam_over_diam, pupil_plane_im='pp_file')
    assert_raises(ValueError, galsim.OpticalPSF, lam_over_diam, pupil_plane_im=im,
                  pupil_plane_scale=pp_scale)
    assert_raises(ValueError, galsim.OpticalPSF, lam_over_diam,
                  pupil_plane_im=im.view(scale=pp_scale))
    # These aren't raised until the image is actually used
    with assert_raises(ValueError):
        # not square
        op = galsim.OpticalPSF(lam_over_diam, pupil_plane_im=galsim.Image(im.array[:-2,:]))
        op.drawImage()
    with assert_raises(ValueError):
        # not even sides
        op = galsim.OpticalPSF(lam_over_diam, pupil_plane_im=galsim.Image(im.array[:-1,:-1]))
        op.drawImage()

    # It is supposed to be able to figure this out even if we *don't* tell it the pad factor. So
    # make sure that it still works even if we don't tell it that value.
    test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=im,
                                 oversampling=pp_oversampling)
    im_test_psf = galsim.ImageD(im_ref_psf.array.shape[0], im_ref_psf.array.shape[1])
    im_test_psf = test_psf.drawImage(image=im_test_psf, scale=scale)

    if pp_test_type == 'image':
        np.testing.assert_array_almost_equal(
            im_test_psf.array, im_ref_psf.array, decimal=pp_decimal,
            err_msg="Inconsistent OpticalPSF image for basic model after loading pupil plane without "
            "specifying parameters.")
    else:
        test_moments = im_test_psf.FindAdaptiveMom()
        ref_moments = im_ref_psf.FindAdaptiveMom()
        np.testing.assert_almost_equal(
            test_moments.moments_sigma, ref_moments.moments_sigma, decimal=pp_decimal,
            err_msg="Inconsistent OpticalPSF image for basic model after loading pupil plane without "
            "specifying parameters.")

    # Next test (less trivial): Rotate the struts by +27 degrees, and check that agreement is
    # good. This is making sure that the linear interpolation that is done when rotating does not
    # result in significant loss of accuracy.
    rot_angle = 27.*galsim.degrees
    ref_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, nstruts=nstruts,
                                strut_angle=strut_angle+rot_angle, oversampling=pp_oversampling,
                                pad_factor=pp_pad_factor)
    test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=im,
                                 pupil_angle=rot_angle, oversampling=pp_oversampling,
                                 pad_factor=pp_pad_factor)
    im_ref_psf = ref_psf.drawImage(scale=scale)
    im_test_psf = galsim.ImageD(im_ref_psf.array.shape[0], im_ref_psf.array.shape[1])
    im_test_psf = test_psf.drawImage(image=im_test_psf, scale=scale)
    # We are slightly less stringent here since it should not be exact.
    if pp_test_type == 'image':
        np.testing.assert_array_almost_equal(
            im_test_psf.array, im_ref_psf.array, decimal=pp_decimal-1,
            err_msg="Inconsistent OpticalPSF image for rotated model after loading pupil plane.")
    else:
        test_moments = im_test_psf.FindAdaptiveMom()
        ref_moments = im_ref_psf.FindAdaptiveMom()
        np.testing.assert_almost_equal(
            test_moments.moments_sigma, ref_moments.moments_sigma, decimal=pp_decimal-1,
            err_msg="Inconsistent OpticalPSF image for rotated model after loading pupil plane.")

    # Now include aberrations.  Here we are testing the ability to figure out the pupil plane extent
    # and sampling appropriately.  Those get fed into the routine for making the aberrations.
    defocus = -0.03
    coma1 = 0.03
    spher = -0.02
    ref_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, nstruts=nstruts,
                                strut_angle=strut_angle, defocus=defocus, coma1=coma1, spher=spher,
                                oversampling=pp_oversampling, pad_factor=pp_pad_factor)
    test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=im,
                                 defocus=defocus, coma1=coma1, spher=spher,
                                 oversampling=pp_oversampling, pad_factor=pp_pad_factor)
    im_ref_psf = ref_psf.drawImage(scale=scale)
    im_test_psf = galsim.ImageD(im_ref_psf.array.shape[0], im_ref_psf.array.shape[1])
    im_test_psf = test_psf.drawImage(image=im_test_psf, scale=scale)
    if pp_test_type == 'image':
        np.testing.assert_array_almost_equal(
            im_test_psf.array, im_ref_psf.array, decimal=pp_decimal,
            err_msg="Inconsistent OpticalPSF image for aberrated model after loading pupil plane.")
    else:
        test_moments = im_test_psf.FindAdaptiveMom()
        ref_moments = im_ref_psf.FindAdaptiveMom()
        np.testing.assert_almost_equal(
            test_moments.moments_sigma, ref_moments.moments_sigma, decimal=pp_decimal,
            err_msg="Inconsistent OpticalPSF image for aberrated model after loading pupil plane.")

    # Test for preservation of symmetries: the result should be the same if the pupil plane is
    # rotated by integer multiples of 2pi/(nstruts).
    ref_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, nstruts=nstruts,
                                strut_angle=strut_angle, oversampling=pp_oversampling,
                                pad_factor=pp_pad_factor)
    im_ref_psf = ref_psf.drawImage(scale=scale)
    for ind in range(1,nstruts):
        rot_angle = ind*2.*np.pi/nstruts
        test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=im,
                                     pupil_angle=rot_angle*galsim.radians,
                                     oversampling=pp_oversampling, pad_factor=pp_pad_factor)
        im_test_psf = galsim.ImageD(im_ref_psf.array.shape[0], im_ref_psf.array.shape[1])
        im_test_psf = test_psf.drawImage(image=im_test_psf, scale=scale)
        if pp_test_type == 'image':
            np.testing.assert_array_almost_equal(
                im_test_psf.array, im_ref_psf.array, decimal=pp_decimal,
                err_msg="Inconsistent OpticalPSF image after rotating pupil plane by invariant "
                "angle.")
        else:
            test_moments = im_test_psf.FindAdaptiveMom()
            ref_moments = im_test_psf.FindAdaptiveMom()
            np.testing.assert_almost_equal(
                test_moments.moments_sigma, ref_moments.moments_sigma, decimal=pp_decimal,
                err_msg="Inconsistent OpticalPSF image after rotating pupil plane by invariant "
                "angle.")

    # Test that if we rotate pupil plane with no aberrations, that's equivalent to rotating the PSF
    # itself.  Use rotation angle of 90 degrees so numerical issues due to the interpolation should
    # be minimal.
    rot_angle = 90.*galsim.degrees
    psf_1 = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=im,
                              oversampling=pp_oversampling, pad_factor=pp_pad_factor)
    rot_psf_1 = psf_1.rotate(rot_angle)
    psf_2 = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=im,
                              pupil_angle=rot_angle, oversampling=pp_oversampling,
                              pad_factor=pp_pad_factor)
    im_1 = psf_1.drawImage(scale=scale)
    im_2 = galsim.ImageD(im_1.array.shape[0], im_1.array.shape[1])
    im_2 = psf_2.drawImage(image=im_2, scale=scale)
    if pp_test_type == 'image':
        np.testing.assert_array_almost_equal(
            im_1.array, im_2.array, decimal=pp_decimal,
            err_msg="Inconsistent OpticalPSF image after rotating pupil plane vs. rotating PSF.")
    else:
        test_moments = im_1.FindAdaptiveMom()
        ref_moments = im_2.FindAdaptiveMom()
        np.testing.assert_almost_equal(
            test_moments.moments_sigma, ref_moments.moments_sigma, decimal=pp_decimal,
            err_msg="Inconsistent OpticalPSF image after rotating pupil plane vs. rotating PSF.")

    # Supply the pupil plane at higher resolution, and make sure that the routine figures out the
    # sampling and gets the right image scale etc.
    rescale_fac = 0.77
    ref_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, nstruts=nstruts,
                                strut_angle=strut_angle, oversampling=pp_oversampling,
                                pad_factor=pp_pad_factor/rescale_fac)
    # Make higher resolution pupil plane image via interpolation
    int_im = galsim.InterpolatedImage(galsim.Image(im, scale=1.0, dtype=np.float32),
                                      calculate_maxk=False, calculate_stepk=False,
                                      x_interpolant='linear')
    new_im = int_im.drawImage(scale=rescale_fac, method='no_pixel')
    new_im.wcs = None  # Let OpticalPSF figure out the scale automatically.
    test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration,
                                 pupil_plane_im=new_im, oversampling=pp_oversampling)
    im_ref_psf = ref_psf.drawImage(scale=scale)
    im_test_psf = galsim.ImageD(im_ref_psf.array.shape[0], im_ref_psf.array.shape[1])
    im_test_psf = test_psf.drawImage(image=im_test_psf, scale=scale)
    test_moments = im_test_psf.FindAdaptiveMom()
    ref_moments = im_ref_psf.FindAdaptiveMom()
    if pp_test_type == 'image':
        np.testing.assert_almost_equal(
            test_moments.moments_sigma/ref_moments.moments_sigma-1., 0, decimal=2,
            err_msg="Inconsistent OpticalPSF image for basic model after loading high-res pupil plane.")
    else:
        np.testing.assert_almost_equal(
            test_moments.moments_sigma/ref_moments.moments_sigma-1., 0, decimal=1,
            err_msg="Inconsistent OpticalPSF image for basic model after loading high-res pupil plane.")

    # Now supply the pupil plane at the original resolution, but remove some of the padding.  We
    # want it to properly recognize that it needs more padding, and include it.
    remove_pad = -23
    sub_im = im[im.bounds.withBorder(remove_pad)]
    test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration,
                                 pupil_plane_im=sub_im, oversampling=pp_oversampling,
                                 pad_factor=pp_pad_factor)
    im_test_psf = galsim.ImageD(im_ref_psf.array.shape[0], im_ref_psf.array.shape[1])
    im_test_psf = test_psf.drawImage(image=im_test_psf, scale=scale)
    test_moments = im_test_psf.FindAdaptiveMom()
    ref_moments = im_ref_psf.FindAdaptiveMom()
    np.testing.assert_almost_equal(
        test_moments.moments_sigma/ref_moments.moments_sigma-1., 0, decimal=pp_decimal-3,
        err_msg="Inconsistent OpticalPSF image for basic model after loading less padded pupil plane.")

    # Now supply the pupil plane at the original resolution, with extra padding.
    new_pad = 76
    big_im = galsim.Image(im.bounds.withBorder(new_pad))
    big_im[im.bounds] = im
    test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration,
                                 pupil_plane_im=big_im, oversampling=pp_oversampling,
                                 pad_factor=pp_pad_factor)
    im_test_psf = galsim.ImageD(im_ref_psf.array.shape[0], im_ref_psf.array.shape[1])
    im_test_psf = test_psf.drawImage(image=im_test_psf, scale=scale)
    test_moments = im_test_psf.FindAdaptiveMom()
    ref_moments = im_ref_psf.FindAdaptiveMom()
    np.testing.assert_almost_equal(
        test_moments.moments_sigma, ref_moments.moments_sigma, decimal=pp_decimal-2,
        err_msg="Inconsistent OpticalPSF image size for basic model "
        "after loading more padded pupil plane.")

    # Check for same answer if we use image, array, or filename for reading in array.
    test_psf = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=im,
                                 oversampling=pp_oversampling, pad_factor=pp_pad_factor)
    im_test_psf = test_psf.drawImage(scale=scale)
    test_psf_2 = galsim.OpticalPSF(lam_over_diam, obscuration=obscuration, pupil_plane_im=im.array,
                                   oversampling=pp_oversampling, pad_factor=pp_pad_factor)
    im_test_psf_2 = test_psf_2.drawImage(scale=scale)
    np.testing.assert_almost_equal(
        im_test_psf.array, im_test_psf_2.array, decimal=pp_decimal,
        err_msg="Inconsistent OpticalPSF image from Image vs. array.")

    # The following had used lam_over_diam, but that is now invalid because the fits file
    # has a specific pixel scale.  So we need to provide lam and diam separately so that the
    # units are consistent.
    diam = 500.e-9 / lam_over_diam * galsim.radians / galsim.arcsec
    test_psf_3 = galsim.OpticalPSF(
        lam=500, diam=diam, obscuration=obscuration, oversampling=pp_oversampling,
        pupil_plane_im=os.path.join(imgdir, pp_file),
        pad_factor=pp_pad_factor)
    im_test_psf_3 = test_psf_3.drawImage(scale=scale)
    np.testing.assert_almost_equal(
        im_test_psf.array, im_test_psf_3.array, decimal=pp_decimal,
        err_msg="Inconsistent OpticalPSF image from Image vs. file read-in.")
Example #27
0
def main(argv):
    """
    Make images using model PSFs and galaxy cluster shear:
      - The galaxies come from COSMOSCatalog, which can produce either RealGalaxy profiles
        (like in demo10) and parametric fits to those profiles.  We choose 40% of the galaxies
        to use the images, and the other 60% to use the parametric fits
      - The real galaxy images include some initial correlated noise from the original HST
        observation.  However, we whiten the noise of the final image so the final image has
        stationary Gaussian noise, rather than correlated noise.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("mock_superbit_data")

    # Define some parameters we'll use below.
    # Normally these would be read in from some parameter file.
    global pixel_scale
    pixel_scale = 0.206  # arcsec/pixel
    global image_xsize
    image_xsize = 6665  # size of image in pixels
    global image_ysize
    image_ysize = 4453  # size of image in pixels
    global image_xsize_arcsec
    image_xsize_arcsec = image_xsize * pixel_scale  # size of big image in each dimension (arcsec)
    global image_ysize_arcsec
    image_ysize_arcsec = image_ysize * pixel_scale  # size of big image in each dimension (arcsec)
    global center_ra
    center_ra = 19.3 * galsim.hours  # The RA, Dec of the center of the image on the sky
    global center_dec
    center_dec = -33.1 * galsim.degrees
    global exp_time
    exp_time = 3000  # exposing for 1500 seconds to match real, observed galaxy/flux count.
    global noise_variance
    noise_variance = 1.8e3  # ADU^2  (Just use simple Gaussian noise here.) -->150s
    #noise_variance = 2.55e3           # ADU^2  (Just use simple Gaussian noise here.) -->300s
    global sky_level
    sky_level = 51  # ADU / arcsec^2 -->150s
    #sky_level = 106                   # ADU / arcsec^2 -->300s
    global nobj
    nobj = 1700  # number of galaxies in entire field -- an adjustment to ensure ~1100 detections
    global nstars
    nstars = 370  # number of stars in the entire field
    global flux_scaling  # Let's figure out the flux for a 0.5 m class telescope
    global tel_diam
    tel_diam = 0.5
    global lam
    lam = 587  # Central wavelength

    psf_path = '/Users/jemcclea/Research/GalSim/examples/data/fpsc_flight_jitter_psf_oversampled_fixed_10x'
    global optics  # will store the Zernicke component of the PSF
    global nfw  # will store the NFWHalo information
    global cosmos_cat  # will store the COSMOS catalog from which we draw objects

    # Set up the NFWHalo:
    mass = 5E14  # Cluster mass (Msol/h)
    nfw_conc = 4  # Concentration parameter = virial radius / NFW scale radius
    nfw_z_halo = 0.3  # redshift of the halo
    nfw_z_source = 0.6  # redshift of the lensed sources
    omega_m = 0.3  # Omega matter for the background cosmology.
    omega_lam = 0.7  # Omega lambda for the background cosmology.
    field_g1 = 0.03  # The field shear is some cosmic shear applied to the whole field,
    field_g2 = 0.01  # taken to be behind the foreground NFW halo (not needed for now)

    nfw = galsim.NFWHalo(mass=mass,
                         conc=nfw_conc,
                         redshift=nfw_z_halo,
                         omega_m=omega_m,
                         omega_lam=omega_lam)
    logger.info('Set up NFW halo for lensing')

    # Read in galaxy catalog
    if True:
        # The catalog we distribute with the GalSim code only has 100 galaxies.
        # The galaxies will typically be reused several times here.
        cat_file_name = 'real_galaxy_catalog_23.5_example.fits'
        dir = 'data'
        cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=dir)
    else:
        # If you've run galsim_download_cosmos, you can leave out the cat_file_name and dir
        # to use the full COSMOS catalog with 56,000 galaxies in it.
        cosmos_cat = galsim.COSMOSCatalog()
    logger.info('Read in %d galaxies from catalog', cosmos_cat.nobjects)

    # The catalog returns objects that are appropriate for HST in 1 second exposures.  So for our
    # telescope we scale up by the relative area and exposure time.
    hst_eff_area = 2.4**2 * (1. - 0.33**2)
    sbit_eff_area = tel_diam**2 * (
        1. - 0.10**2
    )  # For want of something better, operating with 10% obscuration
    flux_scaling = (sbit_eff_area / hst_eff_area) * exp_time

    ### Now create PSF. First, define Zernicke polynomial component
    lam_over_diam = lam * 1.e-9 / tel_diam  # radians
    lam_over_diam *= 206265  # arcsec
    aberrations = [0.0] * 12  # Set the initial size.
    aberrations[4] = -0.00725859  # Noll index 4 = Defocus
    aberrations[5:7] = [0.0, -0.00]  # Noll index 5,6 = Astigmatism
    aberrations[7:9] = [0.07, 0.00]  # Noll index 7,8 = Coma
    aberrations[11] = 0.00133254  # Noll index 11 = Spherical

    logger.info('Calculated lambda over diam = %f arcsec', lam_over_diam)
    optics = galsim.OpticalPSF(lam_over_diam,
                               obscuration=0.10,
                               aberrations=aberrations)
    logger.info('Made telescope PSF profile')

    ###
    ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES
    ### WITHIN EACH PSF, ITERATE 5 TIMES TO MAKE 5 SEPARATE IMAGES
    ###
    all_psfs = glob.glob(psf_path + "/*.psf")
    logger.info('Beginning loop over jitter/optical psfs')

    for psf_filen in all_psfs:
        logger.info('Beginning PSF %s...' % psf_filen)

        for i in numpy.arange(1, 6):
            logger.info('Beginning loop %d' % i)

            random_seed = scipy.random.randint(low=10000000, high=99999999)
            rng = galsim.BaseDeviate(random_seed)

            # This is specific to Javier mock PSFs
            try:
                root = psf_filen.split('data/')[1].split('/')[0]
                timescale = psf_filen.split('_10x/')[1].split('.')[0]
                outname = ''.join([
                    'mock_superbit_', root, timescale,
                    str(i).zfill(3), '.fits'
                ])
                truth_file_name = ''.join([
                    './output/truth_', root, timescale,
                    str(i).zfill(3), '.dat'
                ])
                file_name = os.path.join('output', outname)
            except:
                pdb.set_trace()

            # Setting up a truth catalog
            names = [
                'gal_num', 'x_image', 'y_image', 'ra', 'dec', 'g1_meas',
                'g2_meas', 'nfw_g1', 'nfw_g2', 'nfw_mu', 'redshift', 'flux'
            ]
            types = [
                int, float, float, float, float, float, float, float, float,
                float, float, float
            ]
            truth_catalog = galsim.OutputCatalog(names, types)

            # Set up the image:
            full_image = galsim.ImageF(image_xsize, image_ysize)
            full_image.fill(sky_level)
            full_image.setOrigin(0, 0)

            # We keep track of how much noise is already in the image from the RealGalaxies.
            noise_image = galsim.ImageF(image_xsize, image_ysize)
            noise_image.setOrigin(0, 0)

            # Make a slightly non-trivial WCS.  We'll use a slightly rotated coordinate system
            # and center it at the image center.
            theta = 0.17 * galsim.degrees
            dudx = numpy.cos(theta) * pixel_scale
            dudy = -numpy.sin(theta) * pixel_scale
            dvdx = numpy.sin(theta) * pixel_scale
            dvdy = numpy.cos(theta) * pixel_scale
            image_center = full_image.true_center
            affine = galsim.AffineTransform(dudx,
                                            dudy,
                                            dvdx,
                                            dvdy,
                                            origin=full_image.true_center)
            sky_center = galsim.CelestialCoord(ra=center_ra, dec=center_dec)

            wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
            full_image.wcs = wcs

            # Now let's read in the PSFEx PSF model.  We read the image directly into an
            # InterpolatedImage GSObject, so we can manipulate it as needed
            psf_wcs = wcs
            psf_file = os.path.join(psf_path, psf_filen)
            psf = galsim.des.DES_PSFEx(psf_file, wcs=psf_wcs)
            logger.info('Constructed PSF object from PSFEx file')

            # Loop over galaxy objects:
            for k in range(nobj):
                time1 = time.time()

                # The usual random number generator using a different seed for each galaxy.
                ud = galsim.UniformDeviate(random_seed + k + 1)

                try:
                    # make single galaxy object
                    stamp, truth = make_a_galaxy(ud=ud,
                                                 wcs=wcs,
                                                 psf=psf,
                                                 affine=affine)
                    # Find the overlapping bounds:
                    bounds = stamp.bounds & full_image.bounds

                    # We need to keep track of how much variance we have currently in the image, so when
                    # we add more noise, we can omit what is already there.

                    noise_image[bounds] += truth.variance

                    # Finally, add the stamp to the full image.

                    full_image[bounds] += stamp[bounds]
                    time2 = time.time()
                    tot_time = time2 - time1
                    logger.info(
                        'Galaxy %d positioned relative to center t=%f s', k,
                        tot_time)
                    #g1_real=stamp.FindAdaptiveMom().observed_shape.g1
                    #g2_real=stamp.FindAdaptiveMom().observed_shape.g2
                    g1_real = -9999.
                    g2_real = -9999.
                    this_flux = numpy.sum(stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, g1_real,
                        g2_real, truth.g1, truth.g2, truth.mu, truth.z,
                        this_flux
                    ]
                    truth_catalog.addRow(row)
                except:
                    logger.info('Galaxy %d has failed, skipping...', k)

            ####
            ### Now repeat process for stars!
            ####

            random_seed_stars = scipy.random.randint(low=10000000,
                                                     high=99999999)

            for k in range(nstars):
                time1 = time.time()
                ud = galsim.UniformDeviate(random_seed_stars + k + 1)

                star_stamp, truth = make_a_star(ud=ud,
                                                wcs=wcs,
                                                psf=psf,
                                                affine=affine)
                bounds = star_stamp.bounds & full_image.bounds

                # Add the stamp to the full image.
                try:
                    full_image[bounds] += star_stamp[bounds]

                    time2 = time.time()
                    tot_time = time2 - time1

                    logger.info(
                        'Star %d: positioned relative to center, t=%f s', k,
                        tot_time)

                    #g1_real=star_stamp.FindAdaptiveMom().observed_shape.g1 --> no longer positive definite :-?
                    #g2_real=star_stamp.FindAdaptiveMom().observed_shape.g2
                    g1_real = -9999.
                    g2_real = -9999.
                    this_flux = numpy.sum(star_stamp.array)
                    row = [
                        k, truth.x, truth.y, truth.ra, truth.dec, g1_real,
                        g2_real, truth.g1, truth.g2, truth.mu, truth.z,
                        this_flux
                    ]
                    truth_catalog.addRow(row)

                except:
                    logger.info('Star %d has failed, skipping...', k)

            # We already have some noise in the image, but it isn't uniform.  So the first thing to do is
            # to make the Gaussian noise uniform across the whole image.
            max_current_variance = numpy.max(noise_image.array)
            noise_image = max_current_variance - noise_image
            vn = galsim.VariableGaussianNoise(rng, noise_image)
            full_image.addNoise(vn)

            # Now max_current_variance is the noise level across the full image.  We don't want to add that
            # twice, so subtract off this much from the intended noise that we want to end up in the image.
            noise_variance -= max_current_variance

            # Now add Gaussian noise with this variance to the final image.

            noise = galsim.GaussianNoise(rng, sigma=math.sqrt(noise_variance))
            full_image.addNoise(noise)
            logger.info('Added noise to final large image')

            # Now write the image to disk.  It is automatically compressed with Rice compression,
            # since the filename we provide ends in .fz.
            full_image.write(file_name)
            logger.info('Wrote image to %r', file_name)

            # Write truth catalog to file.
            truth_catalog.write(truth_file_name)

            # Compute some sky positions of some of the pixels to compare with the values of RA, Dec
            # that ds9 reports.  ds9 always uses (1,1) for the lower left pixel, so the pixel coordinates
            # of these pixels are different by 1, but you can check that the RA and Dec values are
            # the same as what GalSim calculates.
            ra_str = center_ra.hms()
            dec_str = center_dec.dms()
            logger.info(
                'Center of image    is at RA %sh %sm %ss, DEC %sd %sm %ss',
                ra_str[0:3], ra_str[3:5], ra_str[5:], dec_str[0:3],
                dec_str[3:5], dec_str[5:])
            for (x, y) in [(0, 0), (0, image_xsize - 1), (image_ysize - 1, 0),
                           (image_xsize - 1, image_ysize - 1)]:
                world_pos = wcs.toWorld(galsim.PositionD(x, y))
                ra_str = world_pos.ra.hms()
                dec_str = world_pos.dec.dms()
                logger.info(
                    'Pixel (%4d, %4d) is at RA %sh %sm %ss, DEC %sd %sm %ss',
                    x, y, ra_str[0:3], ra_str[3:5], ra_str[5:], dec_str[0:3],
                    dec_str[3:5], dec_str[5:])
            logger.info(
                'ds9 reports these pixels as (1,1), (1,2048), etc. with the same RA, Dec.'
            )
            i = i + 1
            logger.info(' ')
            logger.info('completed run %d for psf %s', i, psf_filen)
        logger.info('completed all images')
Example #28
0
def test_ne():
    # Use some very forgiving settings to speed up this test.  We're not actually going to draw
    # any images (other than internally the PSF), so should be okay.
    gsp1 = galsim.GSParams(maxk_threshold=5.e-2, folding_threshold=5e-2, kvalue_accuracy=1e-3,
                           xvalue_accuracy=1e-3)
    gsp2 = galsim.GSParams(maxk_threshold=5.1e-2, folding_threshold=5e-2, kvalue_accuracy=1e-3,
                           xvalue_accuracy=1e-3)
    pupil_plane_im = galsim.fits.read(os.path.join(imgdir, pp_file))
    pupil_plane_im.wcs = None

    # Params include: lam_over_diam, (lam/diam), aberrations by name, aberrations by list, nstruts,
    # strut_thick, strut_angle, obscuration, oversampling, pad_factor, flux, gsparams,
    # circular_pupil, interpolant, pupil_plane_im, pupil_angle, scale_unit
    objs = [galsim.OpticalPSF(lam_over_diam=1.0, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, gsparams=gsp2),
            galsim.OpticalPSF(lam=1.0, diam=1.0, gsparams=gsp1),
            galsim.OpticalPSF(lam=1.0, diam=1.0, scale_unit=galsim.arcmin, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, defocus=0.1, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, aberrations=[0, 0, 0, 0, 0.2], gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, nstruts=2, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, nstruts=2, strut_thick=0.3, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, nstruts=2, strut_angle=10.*galsim.degrees,
                              gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, obscuration=0.5, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, obscuration=0.5, coma1=1.0, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, obscuration=0.5, coma1=1.0, annular_zernike=True,
                              gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, oversampling=2.0, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, pad_factor=2.0, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, flux=2.0, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, circular_pupil=False, gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, interpolant='Linear', gsparams=gsp1),
            galsim.OpticalPSF(lam_over_diam=1.0, gsparams=gsp1, ii_pad_factor=2.)]
    stepk = objs[0].stepk
    maxk = objs[0].maxk
    objs += [galsim.OpticalPSF(lam_over_diam=1.0, gsparams=gsp1, _force_stepk=stepk/1.5),
             galsim.OpticalPSF(lam_over_diam=1.0, gsparams=gsp1, _force_maxk=maxk*2)]

    if do_slow_tests:
        objs += [galsim.OpticalPSF(lam_over_diam=1.0, pupil_plane_im=pupil_plane_im, gsparams=gsp1,
                                   suppress_warning=True),
                 galsim.OpticalPSF(lam_over_diam=1.0, pupil_plane_im=pupil_plane_im, gsparams=gsp1,
                                   pupil_angle=10*galsim.degrees, suppress_warning=True)]
    all_obj_diff(objs)
Example #29
0
    import galsim
except ImportError:
    path, filename = os.path.split(__file__)
    sys.path.append(os.path.abspath(os.path.join(path, "..", "..")))
    import galsim

if __name__ == "__main__":
    lod = 0.04
    obscuration = 0.3
    imsize = 128

    # predefine image of fixed size for drawing into
    im = galsim.ImageD(imsize, imsize)

    # defocus
    optics = galsim.OpticalPSF(lod, defocus=.5, obscuration=obscuration)
    im = optics.draw(im, dx=0.2 * lod)
    im.write(
        os.path.join(os.path.abspath(os.path.dirname(__file__)),
                     "optics_defocus.fits"))

    # astig1
    optics = galsim.OpticalPSF(lod,
                               defocus=.5,
                               astig1=.5,
                               obscuration=obscuration)
    im = optics.draw(im, dx=0.2 * lod)
    im.write(
        os.path.join(os.path.abspath(os.path.dirname(__file__)),
                     "optics_astig1.fits"))
Example #30
0
def test_geometric_shoot():
    """Test that geometric photon shooting is reasonably consistent with Fourier optics."""
    jmax = 20
    bd = galsim.BaseDeviate(1111111)
    u = galsim.UniformDeviate(bd)

    lam = 500.0
    diam = 4.0

    for i in range(4):  # Do a few random tests.  Takes about 1 sec.
        aberrations = [0]+[u()*0.1 for i in range(jmax)]
        opt_psf = galsim.OpticalPSF(diam=diam, lam=lam, aberrations=aberrations,
                                    geometric_shooting=True)
        # Use really good seeing, so that the optics contribution actually matters.
        atm_psf = galsim.Kolmogorov(fwhm=0.4)

        psf = galsim.Convolve(opt_psf, atm_psf)
        u1 = u.duplicate()
        im_shoot = psf.drawImage(nx=256, ny=256, scale=0.2, method='phot', n_photons=100000, rng=u)
        im_fft = psf.drawImage(nx=256, ny=256, scale=0.2)

        printval(im_fft, im_shoot)
        shoot_moments = galsim.hsm.FindAdaptiveMom(im_shoot)
        fft_moments = galsim.hsm.FindAdaptiveMom(im_fft)

        # 40th of a pixel centroid tolerance.
        np.testing.assert_allclose(
            shoot_moments.moments_centroid.x, fft_moments.moments_centroid.x, rtol=0, atol=0.025,
            err_msg="")
        np.testing.assert_allclose(
            shoot_moments.moments_centroid.y, fft_moments.moments_centroid.y, rtol=0, atol=0.025,
            err_msg="")
        # 2% size tolerance
        np.testing.assert_allclose(
            shoot_moments.moments_sigma, fft_moments.moments_sigma, rtol=0.02, atol=0,
            err_msg="")
        # Not amazing ellipticity consistency at the moment.  0.01 tolerance.
        print(fft_moments.observed_shape)
        print(shoot_moments.observed_shape)
        np.testing.assert_allclose(
            shoot_moments.observed_shape.g1, fft_moments.observed_shape.g1, rtol=0, atol=0.01,
            err_msg="")
        np.testing.assert_allclose(
            shoot_moments.observed_shape.g2, fft_moments.observed_shape.g2, rtol=0, atol=0.01,
            err_msg="")

        # Check the flux
        # The Airy part sends a lot of flux off the edge, so this test is a little loose.
        added_flux = im_shoot.added_flux
        print('psf.flux = ',psf.flux)
        print('added_flux = ',added_flux)
        print('image flux = ',im_shoot.array.sum())
        assert np.isclose(added_flux, psf.flux, rtol=3.e-4)
        assert np.isclose(im_shoot.array.sum(), psf.flux, rtol=3.e-4)

        # Check doing this with photon_ops
        im_shoot2 = opt_psf.drawImage(nx=256, ny=256, scale=0.2, method='phot',
                                      n_photons=100000, rng=u1.duplicate(),
                                      photon_ops=[atm_psf])
        np.testing.assert_allclose(im_shoot2.array, im_shoot.array)
        im_shoot3 = galsim.DeltaFunction().drawImage(nx=256, ny=256, scale=0.2, method='phot',
                                                     n_photons=100000, rng=u1.duplicate(),
                                                     photon_ops=[opt_psf, atm_psf])
        np.testing.assert_allclose(im_shoot3.array, im_shoot.array)