Example #1
0
    def finalize(self, config, base, logger):
        # Make the OutputCatalog
        cols = config['columns']
        types = self.scratch.pop('types')
        self.cat = galsim.OutputCatalog(names=cols.keys(), types=types)

        # Add all the rows in order to the OutputCatalog
        # Note: types was popped above, so only the obj_num keys are left.
        obj_nums = sorted(self.scratch.keys())
        for obj_num in obj_nums:
            row = self.scratch[obj_num]
            self.cat.addRow(row)
Example #2
0
    def finalize(self, config, base, main_data, logger):
        # Make the OutputCatalog
        cols = config['columns']
        # Note: Provide a default here, because if all items were skipped it would otherwise
        # lead to a KeyError.
        types = self.scratch.pop('types', [float] * len(cols))
        self.cat = galsim.OutputCatalog(names=cols.keys(), types=types)

        # Add all the rows in order to the OutputCatalog
        # Note: types was popped above, so only the obj_num keys are left.
        obj_nums = sorted(self.scratch.keys())
        for obj_num in obj_nums:
            row = self.scratch[obj_num]
            self.cat.addRow(row)
        return self.cat
Example #3
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 #4
0
def main(argv):
    """
    Make images using variable PSF and shear:
      - The main image is 10 x 10 postage stamps.
      - Each postage stamp is 48 x 48 pixels.
      - The second HDU has the corresponding PSF image.
      - Applied shear is from a power spectrum P(k) ~ k^1.8.
      - Galaxies are real galaxies oriented in a ring test of 20 each.
      - The PSF is Gaussian with FWHM, ellipticity and position angle functions of (x,y)
      - Noise is Poisson using a nominal sky value of 1.e6.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo10")

    # Define some parameters we'll use below.
    # Normally these would be read in from some parameter file.

    n_tiles = 10  # number of tiles in each direction.
    stamp_size = 48  # pixels

    pixel_scale = 0.44  # arcsec / pixel
    sky_level = 1.e6  # ADU / arcsec^2

    # The random seed is used for both the power spectrum realization and the random properties
    # of the galaxies.
    random_seed = 3339201

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

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

    # These will be created for each object below.  The values we'll use will be functions
    # of (x,y) relative to the center of the image.  (r = sqrt(x^2+y^2))
    # psf_fwhm = 0.9 + 0.5 * (r/100)^2  -- arcsec
    # psf_e = 0.4 * (r/100)^1.5         -- large value at the edge, so visible by eye.
    # psf_beta = atan2(y/x) + pi/2      -- tangential pattern

    gal_dilation = 3  # Make the galaxies a bit larger than their original size.
    gal_signal_to_noise = 100  # Pretty high.
    psf_signal_to_noise = 1000  # Even higher.

    logger.info('Starting demo script 10')

    # Read in galaxy catalog
    cat_file_name = 'real_galaxy_catalog_23.5_example.fits'
    dir = 'data'
    real_galaxy_catalog = galsim.RealGalaxyCatalog(cat_file_name, dir=dir)
    logger.info('Read in %d real galaxies from catalog',
                real_galaxy_catalog.nobjects)

    # List of IDs to use.  We select 5 particularly irregular galaxies for this demo.
    # Then we'll choose randomly from this list.
    id_list = [106416, 106731, 108402, 116045, 116448]

    # Make the 5 galaxies we're going to use here rather than remake them each time.
    # This means the Fourier transforms of the real galaxy images don't need to be recalculated
    # each time, so it's a bit more efficient.
    gal_list = [
        galsim.RealGalaxy(real_galaxy_catalog, id=id) for id in id_list
    ]
    # Grab the index numbers before we transform them and lose the index attribute.
    cosmos_index = [gal.index for gal in gal_list]

    # Make the galaxies a bit larger than their original observed size.
    gal_list = [gal.dilate(gal_dilation) for gal in gal_list]

    # Setup the PowerSpectrum object we'll be using:
    ps = galsim.PowerSpectrum(lambda k: k**1.8)
    # The argument here is "e_power_function" which defines the E-mode power to use.

    # There is also a b_power_function if you want to include any B-mode power:
    #     ps = galsim.PowerSpectrum(e_power_function, b_power_function)

    # You may even omit the e_power_function argument and have a pure B-mode power spectrum.
    #     ps = galsim.PowerSpectrum(b_power_function = b_power_function)

    # All the random number generator classes derive from BaseDeviate.
    # When we construct another kind of deviate class from any other
    # kind of deviate class, the two share the same underlying random number
    # generator.  Sometimes it can be clearer to just construct a BaseDeviate
    # explicitly and then construct anything else you need from that.
    # Note: A BaseDeviate cannot be used to generate any values.  It can
    # only be used in the constructor for other kinds of deviates.
    # The seeds for the objects are random_seed+1..random_seed+nobj.
    # The seeds for things at the image or file level use random_seed itself.
    nobj = n_tiles * n_tiles
    rng = galsim.BaseDeviate(random_seed)

    # Have the PowerSpectrum object build a grid of shear values for us to use.
    grid_g1, grid_g2 = ps.buildGrid(grid_spacing=stamp_size * pixel_scale,
                                    ngrid=n_tiles,
                                    rng=rng)

    # Setup the images:
    gal_image = galsim.ImageF(stamp_size * n_tiles, stamp_size * n_tiles)
    psf_image = galsim.ImageF(stamp_size * n_tiles, stamp_size * n_tiles)

    # Update the image WCS to use the image center as the origin of the WCS.
    # The class that acts like a PixelScale except for this offset is called OffsetWCS.
    im_center = gal_image.true_center
    wcs = galsim.OffsetWCS(scale=pixel_scale, origin=im_center)
    gal_image.wcs = wcs
    psf_image.wcs = wcs

    # We will place the tiles in a random order.  To do this, we make two lists for the
    # ix and iy values.  Then we apply a random permutation to the lists (in tandem).
    ix_list = []
    iy_list = []
    for ix in range(n_tiles):
        for iy in range(n_tiles):
            ix_list.append(ix)
            iy_list.append(iy)
    # This next function will use the given random number generator, rng, and use it to
    # randomly permute any number of lists.  All lists will have the same random permutation
    # applied.
    galsim.random.permute(rng, ix_list, iy_list)

    # Initialize the OutputCatalog for the truth values
    names = [
        'gal_num', 'x_image', 'y_image', 'psf_e1', 'psf_e2', 'psf_fwhm',
        'cosmos_id', 'cosmos_index', 'theta', 'g1', 'g2', 'shift_x', 'shift_y'
    ]
    types = [
        int, float, float, float, float, float, str, int, float, float, float,
        float, float
    ]
    truth_catalog = galsim.OutputCatalog(names, types)

    # Build each postage stamp:
    for k in range(nobj):
        # The usual random number generator using a different seed for each galaxy.
        rng = galsim.BaseDeviate(random_seed + k + 1)

        # Determine the bounds for this stamp and its center position.
        ix = ix_list[k]
        iy = iy_list[k]
        b = galsim.BoundsI(ix * stamp_size + 1, (ix + 1) * stamp_size,
                           iy * stamp_size + 1, (iy + 1) * stamp_size)
        sub_gal_image = gal_image[b]
        sub_psf_image = psf_image[b]

        pos = wcs.toWorld(b.true_center)
        # The image comes out as about 211 arcsec across, so we define our variable
        # parameters in terms of (r/100 arcsec), so roughly the scale size of the image.
        rsq = (pos.x**2 + pos.y**2)
        r = math.sqrt(rsq)

        psf_fwhm = 0.9 + 0.5 * rsq / 100**2  # arcsec
        psf_e = 0.4 * (r / 100.)**1.5
        psf_beta = (math.atan2(pos.y, pos.x) + math.pi / 2) * galsim.radians

        # Define the PSF profile
        psf = galsim.Gaussian(fwhm=psf_fwhm)
        psf_shape = galsim.Shear(e=psf_e, beta=psf_beta)
        psf = psf.shear(psf_shape)

        # Define the galaxy profile:

        # For this demo, we are doing a ring test where the same galaxy profile is drawn at many
        # orientations stepped uniformly in angle, making a ring in e1-e2 space.
        # We're drawing each profile at 20 different orientations and then skipping to the
        # next galaxy in the list.  So theta steps by 1/20 * 360 degrees:
        theta_deg = (k % 20) * 360. / 20
        theta = theta_deg * galsim.degrees

        # The index needs to increment every 20 objects so we use k/20 using integer math.
        index = k // 20
        gal = gal_list[index]

        # This makes a new copy so we're not changing the object in the gal_list.
        gal = gal.rotate(theta)

        # Apply the shear from the power spectrum.  We should either turn the gridded shears
        # grid_g1[iy, ix] and grid_g2[iy, ix] into gridded reduced shears using a utility called
        # galsim.lensing.theoryToObserved, or use ps.getShear() which by default gets the reduced
        # shear.  ps.getShear() is also more flexible because it can get the shear at positions that
        # are not on the original grid, as long as they are contained within the bounds of the full
        # grid. So in this example we'll use ps.getShear().
        alt_g1, alt_g2 = ps.getShear(pos)
        gal = gal.shear(g1=alt_g1, g2=alt_g2)

        # Apply half-pixel shift in a random direction.
        shift_r = pixel_scale * 0.5
        ud = galsim.UniformDeviate(rng)
        t = ud() * 2. * math.pi
        dx = shift_r * math.cos(t)
        dy = shift_r * math.sin(t)
        gal = gal.shift(dx, dy)

        # Make the final image, convolving with the psf
        final = galsim.Convolve([psf, gal])

        # Draw the image
        final.drawImage(sub_gal_image)

        # For the PSF image, we don't match the galaxy shift.  Rather, we use the offset
        # parameter to drawImage to apply a random offset of up to 0.5 pixels in each direction.
        # Note the difference in units between shift and offset.  The shift is applied to the
        # surface brightness profile, so it is in sky coordinates (as all dimension are for
        # GSObjects), which are arcsec here.  The offset though is applied to the image itself,
        # so it is in pixels.  Hence, we don't multiply by pixel_scale.
        psf_dx = ud() - 0.5
        psf_dy = ud() - 0.5
        psf_offset = galsim.PositionD(psf_dx, psf_dy)

        # Draw the PSF image:
        # We use real space integration over the pixels to avoid some of the
        # artifacts that can show up with Fourier convolution.
        # The level of the artifacts is quite low, but when drawing with
        # so little noise, they are apparent with ds9's zscale viewing.
        psf.drawImage(sub_psf_image, method='real_space', offset=psf_offset)

        # Build the noise model: Poisson noise with a given sky level.
        sky_level_pixel = sky_level * pixel_scale**2
        noise = galsim.PoissonNoise(rng, sky_level=sky_level_pixel)

        # Add noise to the PSF image, using the normal noise model, but scaling the
        # PSF flux high enough to reach the desired signal-to-noise.
        # See demo5.py for more info about how this works.
        sub_psf_image.addNoiseSNR(noise, psf_signal_to_noise)

        # And also to the galaxy image using its signal-to-noise.
        sub_gal_image.addNoiseSNR(noise, gal_signal_to_noise)

        # Add the truth values to the truth catalog
        row = [
            k, b.true_center.x, b.true_center.y, psf_shape.e1,
            psf_shape.e2, psf_fwhm, id_list[index], cosmos_index[index],
            (theta_deg % 360.), alt_g1, alt_g2, dx, dy
        ]
        truth_catalog.addRow(row)

        logger.info('Galaxy (%d,%d): position relative to center = %s', ix, iy,
                    str(pos))

    logger.info('Done making images of postage stamps')

    # In this case, we'll attach the truth catalog as an additional HDU in the same file as
    # the image data.
    truth_hdu = truth_catalog.writeFitsHdu()

    # Now write the images to disk.
    images = [gal_image, psf_image, truth_hdu]
    # Any items in the "images" list that is already an hdu is just used directly.
    # The actual images are converted to FITS hdus that contain the image data.
    galsim.fits.writeMulti(images, file_name)
    logger.info('Wrote image to %r', file_name)
Example #5
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
Example #6
0
def test_output_catalog():
    """Test basic operations on Catalog."""
    import time
    t1 = time.time()

    names = [
        'float1', 'float2', 'int1', 'int2', 'bool1', 'bool2', 'str1', 'str2',
        'str3', 'str4', 'angle', 'posi', 'posd', 'shear'
    ]
    types = [
        float, float, int, int, bool, bool, str, str, str, str, galsim.Angle,
        galsim.PositionI, galsim.PositionD, galsim.Shear
    ]
    out_cat = galsim.OutputCatalog(names, types)

    out_cat.addRow([
        1.234, 4.131, 9, -3, 1, True, "He's", '"ceased', 'to', 'be"',
        1.2 * galsim.degrees,
        galsim.PositionI(5, 6),
        galsim.PositionD(0.3, -0.4),
        galsim.Shear(g1=0.2, g2=0.1)
    ])
    out_cat.addRow((2.345, -900, 0.0, 8, False, 0, "bleedin'", '"bereft', 'of',
                    'life"', 11 * galsim.arcsec, galsim.PositionI(-35, 106),
                    galsim.PositionD(23.5, 55.1), galsim.Shear(e1=-0.1,
                                                               e2=0.15)))
    out_cat.addRow([
        3.4560001, 8.e3, -4, 17.0, 1, 0, 'demised!', '"kicked', 'the',
        'bucket"', 0.4 * galsim.radians,
        galsim.PositionI(88, 99),
        galsim.PositionD(-0.99, -0.88),
        galsim.Shear()
    ])

    # First the ASCII version
    out_cat.write(dir='output', file_name='catalog.dat')
    cat = galsim.Catalog(dir='output', file_name='catalog.dat')
    np.testing.assert_equal(cat.ncols, 17)
    np.testing.assert_equal(cat.nobjects, 3)
    np.testing.assert_equal(cat.isFits(), False)
    np.testing.assert_almost_equal(cat.getFloat(1, 0), 2.345)
    np.testing.assert_almost_equal(cat.getFloat(2, 1), 8000.)
    np.testing.assert_equal(cat.getInt(0, 2), 9)
    np.testing.assert_equal(cat.getInt(2, 3), 17)
    np.testing.assert_equal(cat.getInt(2, 4), 1)
    np.testing.assert_equal(cat.getInt(0, 5), 1)
    np.testing.assert_equal(cat.get(2, 6), 'demised!')
    np.testing.assert_equal(cat.get(1, 7), '"bereft')
    np.testing.assert_equal(cat.get(0, 8), 'to')
    np.testing.assert_equal(cat.get(2, 9), 'bucket"')
    np.testing.assert_almost_equal(cat.getFloat(0, 10),
                                   1.2 * galsim.degrees / galsim.radians)
    np.testing.assert_almost_equal(cat.getInt(1, 11), -35)
    np.testing.assert_almost_equal(cat.getInt(1, 12), 106)
    np.testing.assert_almost_equal(cat.getFloat(2, 13), -0.99)
    np.testing.assert_almost_equal(cat.getFloat(2, 14), -0.88)
    np.testing.assert_almost_equal(cat.getFloat(0, 15), 0.2)
    np.testing.assert_almost_equal(cat.getFloat(0, 16), 0.1)

    # Next the FITS version
    out_cat.write(dir='output', file_name='catalog.fits')
    cat = galsim.Catalog(dir='output', file_name='catalog.fits')
    np.testing.assert_equal(cat.ncols, 17)
    np.testing.assert_equal(cat.nobjects, 3)
    np.testing.assert_equal(cat.isFits(), True)
    np.testing.assert_almost_equal(cat.getFloat(1, 'float1'), 2.345)
    np.testing.assert_almost_equal(cat.getFloat(2, 'float2'), 8000.)
    np.testing.assert_equal(cat.getInt(0, 'int1'), 9)
    np.testing.assert_equal(cat.getInt(2, 'int2'), 17)
    np.testing.assert_equal(cat.getInt(2, 'bool1'), 1)
    np.testing.assert_equal(cat.getInt(0, 'bool2'), 1)
    np.testing.assert_equal(cat.get(2, 'str1'), 'demised!')
    np.testing.assert_equal(cat.get(1, 'str2'), '"bereft')
    np.testing.assert_equal(cat.get(0, 'str3'), 'to')
    np.testing.assert_equal(cat.get(2, 'str4'), 'bucket"')
    np.testing.assert_almost_equal(cat.getFloat(0, 'angle.rad'),
                                   1.2 * galsim.degrees / galsim.radians)
    np.testing.assert_equal(cat.getInt(1, 'posi.x'), -35)
    np.testing.assert_equal(cat.getInt(1, 'posi.y'), 106)
    np.testing.assert_almost_equal(cat.getFloat(2, 'posd.x'), -0.99)
    np.testing.assert_almost_equal(cat.getFloat(2, 'posd.y'), -0.88)
    np.testing.assert_almost_equal(cat.getFloat(0, 'shear.g1'), 0.2)
    np.testing.assert_almost_equal(cat.getFloat(0, 'shear.g2'), 0.1)

    # Check pickling
    do_pickle(out_cat)
    out_cat2 = galsim.OutputCatalog(names, types)  # No data.
    do_pickle(out_cat2)

    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Example #7
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(' ')
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.  
      - 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)
    global logger
    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 nobj
    nobj = 30                        # number of galaxies in entire field; this number matches empirical
    global nstars
    nstars = 1000                         # 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 psf_fwhm
    psf_fwhm = 0.30
    global lam
    lam = 625                            # Central wavelength for an airy disk
    global exp_time
    exp_time = 300
    global noise_variance
    global sky_level
   
    psf_path = '/Users/jemcclea/Research/SuperBIT/superbit-ngmix/scripts/outputs/psfex_output'
    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.17     # redshift of the halo --> correct!
    nfw_z_source = 0.6     # redshift of the lensed sources; COSMOS galaxies don't have any
    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_23.5.fits'
    dir = 'data/COSMOS_23.5_training_sample'
    #cat_file_name = 'real_galaxy_catalog_23.5_example.fits'
    #dir = 'data'
    """
    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)
    
    # 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.
    # Will also multiply by the gain and relative pixel scales...
    hst_eff_area = 2.4**2 * (1.-0.33**2)
    sbit_eff_area = tel_diam**2 * (1.-0.3840**2) 
    #sbit_eff_area = tel_diam**2 * (1.-0.1**2) 
  
    ###
    ### 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")
    #all_psfs=glob.glob(psf_path+"/*300*.psf")

    random_seed = 35609377914
    
    i=0
    for psf_filen in range(1):
        
        logger.info('Beginning PSF %s...'% psf_filen)
        rng = galsim.BaseDeviate(random_seed)

        timescale=str(exp_time)
       
        outname=''.join(['debug_0.3FWHM_gaussStar_',timescale,'_',str(i),'.fits'])
        truth_file_name=''.join(['./output-debug/truth_0.3FWHM_gaussStar_',timescale,'_',str(i),'.dat'])
        file_name = os.path.join('output-debug',outname)

        # Set up the image:
        if timescale=='150':
            print("Automatically detecting a 150s exposure image, setting flux scale and noise accordingly")
            #noise_variance=570               # ADU^2  (Just use simple Gaussian noise here.)
            noise_variance=570               # ADU^2  (Just use simple Gaussian noise here.) 
            sky_level = 51                   # ADU 
            exp_time=150.
           
        else:
            print("Automatically detecting a 300s exposure image, setting flux scale and noise accordingly")
            #noise_variance=400              # ADU^2  (Just use simple Gaussian noise here.) 
            noise_variance=400              # ADU^2  (Just use simple Gaussian noise here.) 
            sky_level = 106                 # ADU  
            exp_time=300.
            
        flux_scaling = (sbit_eff_area/hst_eff_area) * exp_time * 3.33 * (.206/.05)**2
                
        # Setting up a truth catalog
        names = [ 'gal_num', 'x_image', 'y_image',
                      'ra', 'dec', 'g1_nopsf', 'g2_nopsf','g1_meas', 'g2_meas', 'fwhm','final_sigmaSize',
                      'nopsf_sigmaSize','nfw_g1', 'nfw_g2', 'nfw_mu', 'redshift','flux', 'stamp_sum', 'noisevar']
        types = [ int, float, float, float, 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(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

        
        # 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
                logger.debug("about to make stamp %d...",k)
                stamp,truth = make_a_galaxy(ud=ud,wcs=wcs,affine=affine)
                logger.debug("stamp %d is made",k)
                # 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]
                logger.debug("stamp %d added to full image",k)
                time2 = time.time()
                tot_time = time2-time1
                logger.info('Galaxy %d positioned relative to center t=%f s',
                                k, tot_time)
                try:
                    g1_real=stamp.FindAdaptiveMom().observed_shape.g1 
                    g2_real=stamp.FindAdaptiveMom().observed_shape.g2
                except:
                    g1_real=-9999.
                    g2_real=-9999.
                logger.debug("Galaxy %d made it past g1/g2_real stage",k)
                sum_flux=numpy.sum(stamp.array)
                row = [ k,truth.x, truth.y, truth.ra, truth.dec, truth.g1_nopsf, truth.g2_nopsf, g1_real, g2_real, truth.fwhm, truth.final_sigmaSize, truth.nopsf_sigmaSize,truth.g1,truth.g2, truth.mu, truth.z, truth.flux, sum_flux, truth.variance]
                truth_catalog.addRow(row)
                logger.debug("row for galaxy %d added to truth catalog\n\n",k)
                
            except:
                logger.info('Galaxy %d has failed, skipping...',k)
                #pdb.set_trace()
                pass
        
        ###### Inject cluster galaxy objects:
        
        random_seed=892465352
        center_coords = galsim.CelestialCoord(center_ra,center_dec)
        centerpix = wcs.toImage(center_coords)

        for k in range(40):
            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,affine=affine,centerpix=centerpix)                
                # 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)
            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)
            try:

                star_stamp,truth=make_a_star(ud=ud,wcs=wcs,affine=affine)
                bounds = star_stamp.bounds & full_image.bounds
                logger.debug("star stamp & truth catalog made for star %d" %k)
                # Add the stamp to the full image.
                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)

                try:
                    g1_real=star_stamp.FindAdaptiveMom().observed_shape.g1
                    g2_real=star_stamp.FindAdaptiveMom().observed_shape.g2
                except:
                    g1_real = -9999.
                    g2_real = -9999.
                this_var = -9999.
                sum_flux=numpy.sum(star_stamp.array)
                row = [ k,truth.x, truth.y, truth.ra, truth.dec, 
                           truth.g1_nopsf, truth.g2_nopsf, g1_real, g2_real,
                           truth.fwhm, truth.final_sigmaSize, truth.nopsf_sigmaSize, truth.g1,
                            truth.g2, truth.mu, truth.z, truth.flux, sum_flux, truth.variance]
                truth_catalog.addRow(row)
                            
            except:
                logger.info('Star %d has failed, skipping...',k)
                pdb.set_trace()
                
                    
            
        # 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 output image')

        
        # Now write the image to disk.  
        full_image.write(file_name)

        
        # Add a FLUXSCL keyword for later stacking
        this_hdu=astropy.io.fits.open(file_name)
        this_hdu[0].header['FLXSCALE'] = 300.0/exp_time
        this_hdu.writeto(file_name,overwrite='True')
        logger.info('Wrote image to %r',file_name)

        
        # Write truth catalog to file. 
        truth_catalog.write(truth_file_name)
        
        i=i+1
        logger.info('completed run %d for psf %s',i,psf_filen)
        
    logger.info('completed all images')
Example #9
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 #10
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(' ')
Example #11
0
def test_output_catalog():
    """Test basic operations on Catalog."""
    names = [
        'float1', 'float2', 'int1', 'int2', 'bool1', 'bool2', 'str1', 'str2',
        'str3', 'str4', 'angle', 'posi', 'posd', 'shear'
    ]
    types = [
        float, 'f8', int, 'i4', bool, 'bool', str, 'str', 'S', 'S0',
        galsim.Angle, galsim.PositionI, galsim.PositionD, galsim.Shear
    ]
    out_cat = galsim.OutputCatalog(names, types)

    row1 = (1.234, 4.131, 9, -3, 1, True, "He's", '"ceased', 'to', 'be"',
            1.2 * galsim.degrees, galsim.PositionI(5, 6),
            galsim.PositionD(0.3, -0.4), galsim.Shear(g1=0.2, g2=0.1))
    row2 = (2.345, -900, 0.0, 8, False, 0, "bleedin'", '"bereft', 'of',
            'life"', 11 * galsim.arcsec, galsim.PositionI(-35, 106),
            galsim.PositionD(23.5, 55.1), galsim.Shear(e1=-0.1, e2=0.15))
    row3 = (3.4560001, 8.e3, -4, 17.0, 1, 0, 'demised!', '"kicked', 'the',
            'bucket"', 0.4 * galsim.radians, galsim.PositionI(88, 99),
            galsim.PositionD(-0.99, -0.88), galsim.Shear())

    out_cat.addRow(row1)
    out_cat.addRow(row2)
    out_cat.addRow(row3)

    assert out_cat.names == out_cat.getNames() == names
    assert out_cat.types == out_cat.getTypes() == types
    assert len(out_cat) == out_cat.getNObjects() == out_cat.nobjects == 3
    assert out_cat.getNCols() == out_cat.ncols == len(names)

    # Can also set the types after the fact.
    # MJ: I think this used to be used by the "truth" catalog extra output.
    #     But it doesn't seem to be used there anymore.  Probably not by anything then.
    #     I'm not sure how useful it is, I guess it doesn't hurt to leave it in.
    out_cat2 = galsim.OutputCatalog(names)
    assert out_cat2.types == [float] * len(names)
    out_cat2.setTypes(types)
    assert out_cat2.types == out_cat2.getTypes() == types

    # Another feature that doesn't seem to be used anymore is you can add the rows out of order
    # and just give a key to use for sorting at the end.
    out_cat2.addRow(row3, 3)
    out_cat2.addRow(row1, 1)
    out_cat2.addRow(row2, 2)

    # Check ASCII round trip
    out_cat.write(dir='output', file_name='catalog.dat')
    cat = galsim.Catalog(dir='output', file_name='catalog.dat')
    np.testing.assert_equal(cat.ncols, 17)
    np.testing.assert_equal(cat.nobjects, 3)
    np.testing.assert_equal(cat.isFits(), False)
    np.testing.assert_almost_equal(cat.getFloat(1, 0), 2.345)
    np.testing.assert_almost_equal(cat.getFloat(2, 1), 8000.)
    np.testing.assert_equal(cat.getInt(0, 2), 9)
    np.testing.assert_equal(cat.getInt(2, 3), 17)
    np.testing.assert_equal(cat.getInt(2, 4), 1)
    np.testing.assert_equal(cat.getInt(0, 5), 1)
    np.testing.assert_equal(cat.get(2, 6), 'demised!')
    np.testing.assert_equal(cat.get(1, 7), '"bereft')
    np.testing.assert_equal(cat.get(0, 8), 'to')
    np.testing.assert_equal(cat.get(2, 9), 'bucket"')
    np.testing.assert_almost_equal(cat.getFloat(0, 10),
                                   1.2 * galsim.degrees / galsim.radians)
    np.testing.assert_almost_equal(cat.getInt(1, 11), -35)
    np.testing.assert_almost_equal(cat.getInt(1, 12), 106)
    np.testing.assert_almost_equal(cat.getFloat(2, 13), -0.99)
    np.testing.assert_almost_equal(cat.getFloat(2, 14), -0.88)
    np.testing.assert_almost_equal(cat.getFloat(0, 15), 0.2)
    np.testing.assert_almost_equal(cat.getFloat(0, 16), 0.1)

    # Check FITS round trip
    out_cat.write(dir='output', file_name='catalog.fits')
    cat = galsim.Catalog(dir='output', file_name='catalog.fits')
    np.testing.assert_equal(cat.ncols, 17)
    np.testing.assert_equal(cat.nobjects, 3)
    np.testing.assert_equal(cat.isFits(), True)
    np.testing.assert_almost_equal(cat.getFloat(1, 'float1'), 2.345)
    np.testing.assert_almost_equal(cat.getFloat(2, 'float2'), 8000.)
    np.testing.assert_equal(cat.getInt(0, 'int1'), 9)
    np.testing.assert_equal(cat.getInt(2, 'int2'), 17)
    np.testing.assert_equal(cat.getInt(2, 'bool1'), 1)
    np.testing.assert_equal(cat.getInt(0, 'bool2'), 1)
    np.testing.assert_equal(cat.get(2, 'str1'), 'demised!')
    np.testing.assert_equal(cat.get(1, 'str2'), '"bereft')
    np.testing.assert_equal(cat.get(0, 'str3'), 'to')
    np.testing.assert_equal(cat.get(2, 'str4'), 'bucket"')
    np.testing.assert_almost_equal(cat.getFloat(0, 'angle.rad'),
                                   1.2 * galsim.degrees / galsim.radians)
    np.testing.assert_equal(cat.getInt(1, 'posi.x'), -35)
    np.testing.assert_equal(cat.getInt(1, 'posi.y'), 106)
    np.testing.assert_almost_equal(cat.getFloat(2, 'posd.x'), -0.99)
    np.testing.assert_almost_equal(cat.getFloat(2, 'posd.y'), -0.88)
    np.testing.assert_almost_equal(cat.getFloat(0, 'shear.g1'), 0.2)
    np.testing.assert_almost_equal(cat.getFloat(0, 'shear.g2'), 0.1)

    # The one that was made out of order should write the same file.
    out_cat2.write(dir='output', file_name='catalog2.fits')
    cat2 = galsim.Catalog(dir='output', file_name='catalog2.fits')
    np.testing.assert_array_equal(cat2.data, cat.data)
    assert cat2 != cat  # Because file_name is different.

    # Check that it properly overwrites an existing output file.
    out_cat.addRow([
        1.234, 4.131, 9, -3, 1, True, "He's", '"ceased', 'to', 'be"',
        1.2 * galsim.degrees,
        galsim.PositionI(5, 6),
        galsim.PositionD(0.3, -0.4),
        galsim.Shear(g1=0.2, g2=0.1)
    ])
    assert out_cat.rows[3] == out_cat.rows[0]
    out_cat.write(dir='output',
                  file_name='catalog.fits')  # Same name as above.
    cat2 = galsim.Catalog(dir='output', file_name='catalog.fits')
    np.testing.assert_equal(cat2.ncols, 17)
    np.testing.assert_equal(cat2.nobjects, 4)
    for key in names[:10]:
        assert cat2.data[key][3] == cat2.data[key][0]

    # Check pickling
    do_pickle(out_cat)
    out_cat2 = galsim.OutputCatalog(names, types)  # No data.
    do_pickle(out_cat2)

    # Check errors
    with assert_raises(galsim.GalSimValueError):
        out_cat.addRow((1, 2, 3))  # Wrong length
    with assert_raises(galsim.GalSimValueError):
        out_cat.write(dir='output',
                      file_name='catalog.txt',
                      file_type='invalid')
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 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
    global exp_time
    global noise_variance
    global sky_level

    psf_path = '/Users/jemcclea/Research/SuperBIT_2019/superbit-ngmix/scripts/outputs/psfex_output'
    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.

    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.3840**2)
    #flux_scaling = (sbit_eff_area/hst_eff_area) * exp_time

    ###
    ### 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 + "/*150*.psf")
    logger.info('Beginning loop over jitter/optical psfs')
    # random_seed = 24783923
    random_seed = 247
    i = 0
    for psf_filen in all_psfs:
        logger.info('Beginning PSF %s...' % psf_filen)

        rng = galsim.BaseDeviate(random_seed)

        # This is specific to empirical PSFs
        try:
            timescale = psf_filen.split('target_')[1].split('_WCS')[0]
        except:
            timescale = psf_filen.split('sci_')[1].split('_WCS')[0]

        outname = ''.join(
            ['mockSuperbit_empiricalPSF_', timescale, '_',
             str(i), '.fits'])
        truth_file_name = ''.join(
            ['./output/truth_empiricalPSF_', timescale, '_',
             str(i), '.dat'])
        file_name = os.path.join('output', outname)

        # Set up the image:
        if timescale == '150':
            print(
                "Automatically detecting a 150s exposure image, setting flux scale and noise accordingly"
            )
            noise_variance = 1.8e3  # ADU^2  (Just use simple Gaussian noise here.) -->150s
            sky_level = 51  # ADU / arcsec^2 -->150s
            exp_time = 150.

        else:
            print(
                "Automatically detecting a 300s exposure image, setting flux scale and noise accordingly"
            )
            noise_variance = 2.55e3  # ADU^2  (Just use simple Gaussian noise here.) -->300s
            sky_level = 106  # ADU / arcsec^2 -->300s
            exp_time = 300.

        # 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', 'var'
        ]
        types = [
            int, 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(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)
        """

        this_im_wcs = get_wcs_info(psf_filen)
        affine = this_im_wcs.affine(full_image.true_center)
        full_image.wcs = this_im_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 = this_im_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,
                                             this_im_wcs=this_im_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.
                row = [
                    k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real,
                    truth.g1, truth.g2, truth.mu, truth.z, truth.flux,
                    truth.variance
                ]
                truth_catalog.addRow(row)
            except:
                logger.info('Galaxy %d has failed, skipping...', k)
                #pdb.set_trace()
                pass

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

        random_seed_stars = 3221987

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

            star_stamp, truth = make_a_star(ud=ud,
                                            this_im_wcs=this_im_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
                g2_real = star_stamp.FindAdaptiveMom().observed_shape.g2
                #g1_real = -9999.
                #g2_real = -9999.
                this_var = -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, this_var
                ]
                truth_catalog.addRow(row)

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

        # 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.

        try:
            noise = galsim.GaussianNoise(rng, sigma=math.sqrt(noise_variance))
        except:
            noise = galsim.GaussianNoise(rng, sigma=math.sqrt(1800))

        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.

        i = i + 1
        logger.info(' ')
        logger.info('completed run %d for psf %s', i, psf_filen)
    logger.info('completed all images')
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.  
      - Using parametric galaxies so that filter responses/system throughput can be convolved
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    global logger
    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)

    gain = 3.33
    dark_current = 0.33
    read_noise = 5

    global nobj
    nobj = 10500  # number of galaxies in entire field
    global nstars
    nstars = 320  # 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 = 625  # Central wavelength for an airy disk
    global exp_time
    global noise_variance
    global sky_level

    psf_path = '/Users/jemcclea/Research/SuperBIT/superbit-ngmix/scripts/output-real/psfex_output'
    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.17  # 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.

    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, as well as catalog containing
    # information from COSMOS fits like redshifts, hlr, etc.

    cat_file_name = 'real_galaxy_catalog_23.5.fits'
    fdir = 'data/COSMOS_23.5_training_sample'
    fit_file_name = 'real_galaxy_catalog_23.5_fits.fits'

    cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=fdir)
    fitcat = Table.read(os.path.join(fdir, fit_file_name))
    logger.info('Read in %d galaxies from catalog and associated fit info',
                cosmos_cat.nobjects)

    cluster_cat = galsim.COSMOSCatalog(
        'data/real_galaxy_catalog_23.5_example.fits')
    logger.info('Read in %d cluster 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.
    # Will also multiply by the gain and relative pixel scales...

    hst_eff_area = 2.4**2
    sbit_eff_area = tel_diam**2

    ###
    ### LOOP OVER PSFs TO MAKE GROUPS OF IMAGES
    ###

    all_psfs = glob.glob(
        '/Users/jemcclea/Research/SuperBIT/superbit-ngmix/scripts/output-real/psfex_output/dwb_image_ifc*WCS_cat.psf'
    )
    logger.info('Beginning loop over jitter/optical psfs')
    random_seed = 7839234
    i = 0
    for psf_filen in all_psfs:
        logger.info('Beginning PSF %s...' % psf_filen)
        rng = galsim.BaseDeviate(random_seed)

        # This is specific to empirical PSFs

        try:
            timescale = psf_filen.split('target_')[1].split('_WCS')[0]
        except:
            timescale = psf_filen.split('sci_')[1].split('_WCS')[0]

        outname = ''.join(
            ['mockSuperbit_nodilate_', timescale, '_',
             str(i), '.fits'])
        truth_file_name = ''.join(
            ['./output/truth_nodilate_', timescale, '_',
             str(i), '.dat'])
        file_name = os.path.join('output', outname)

        # Set up the image:
        if timescale == '150':
            print(
                "Automatically detecting a 150s exposure image, setting flux scale and sky accordingly"
            )
            sky_level = 51  # ADU
            exp_time = 150.

        else:
            print(
                "Automatically detecting a 300s exposure image, setting flux scale and sky accordingly"
            )
            sky_level = 102  # ADU
            exp_time = 300.

        flux_scaling = (sbit_eff_area / hst_eff_area
                        ) * exp_time * gain  #* (pixel_scale/0.05)#**2

        # Setting up a truth catalog

        names = [
            'gal_num', 'x_image', 'y_image', 'ra', 'dec', 'g1_meas', 'g2_meas',
            'fwhm', 'mom_size', 'nfw_g1', 'nfw_g2', 'nfw_mu', 'redshift',
            'flux', 'stamp_sum'
        ]
        types = [
            int, 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(image_xsize, image_ysize)
        full_image.setOrigin(0, 0)
        full_image.fill(sky_level)

        wcs = get_wcs_info(psf_filen)
        affine = wcs.affine(full_image.true_center)
        full_image.wcs = wcs

        # Now let's read in the PSFEx PSF model.
        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
                logger.debug("about to make stamp...")
                stamp, truth = make_a_galaxy(ud=ud,
                                             wcs=wcs,
                                             psf=psf,
                                             affine=affine,
                                             fitcat=fitcat)
                logger.debug("stamp is made")
                # Find the overlapping bounds:
                bounds = stamp.bounds & full_image.bounds

                # Finally, add the stamp to the full image.
                full_image[bounds] += stamp[bounds]
                logger.debug("stamp added to full image")
                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
                sum_flux = numpy.sum(stamp.array)
                row = [
                    k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real,
                    truth.fwhm, truth.mom_size, truth.g1, truth.g2, truth.mu,
                    truth.z, truth.flux, sum_flux
                ]
                truth_catalog.addRow(row)
                logger.debug("row added to truth catalog")

            except:
                logger.info('Galaxy %d has failed, skipping...', k)
                pdb.set_trace()

        #####
        ### Inject cluster galaxy objects:
        ### - Note that this "cluster" is just for aesthetics
        ### - So, 'n_cluster_gals' is arbitrary
        ### - You could concievably create a method to base the number of galaxies injected
        ###   using some scaling relation between (NFW) mass and richness to set n_cluster_gals
        ###   to something based in reality (though these are poorly constrained at low mass!).
        #####

        n_cluster_gals = 40

        for k in range(n_cluster_gals):
            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,
                    affine=affine,
                    psf=psf,
                    cluster_cat=cluster_cat,
                    fitcat=fitcat)

                # Find the overlapping bounds:
                bounds = cluster_stamp.bounds & full_image.bounds

                # 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)
                sum_flux = numpy.sum(cluster_stamp.array)
                g1_real = cluster_stamp.FindAdaptiveMom().observed_shape.g1
                g2_real = cluster_stamp.FindAdaptiveMom().observed_shape.g2

                row = [
                    k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real,
                    truth.fwhm, truth.mom_size, truth.g1, truth.g2, truth.mu,
                    truth.z, truth.flux, sum_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 = 3221987

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

                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.
                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
                g2_real = star_stamp.FindAdaptiveMom().observed_shape.g2
                #g1_real = -9999.
                #g2_real = -9999.
                sum_flux = numpy.sum(star_stamp.array)
                row = [
                    k, truth.x, truth.y, truth.ra, truth.dec, g1_real, g2_real,
                    truth.fwhm, truth.mom_size, truth.g1, truth.g2, truth.mu,
                    truth.z, truth.flux, sum_flux
                ]
                truth_catalog.addRow(row)

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

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

        logger.info('Added noise to final output image')

        # Now write the image to disk.
        full_image.write(file_name)

        # Add a FLUXSCL keyword for later stacking
        this_hdu = astropy.io.fits.open(file_name)
        this_hdu[0].header['FLXSCALE'] = 300.0 / exp_time
        this_hdu.writeto(file_name, overwrite='True')
        logger.info('Wrote image to %r', file_name)

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

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

    logger.info('completed all images')
Example #14
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.  
      - Using parametric galaxies so that filter responses/system throughput can be convolved
    """
    logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout)
    global logger
    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 nobj
    nobj = 2200                          # number of galaxies in entire field 
    global nstars
    nstars = 350                         # 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 = 625                            # Central wavelength for an airy disk
    global exp_time
    global noise_variance
    global sky_level
   
    psf_path = '/Users/jemcclea/Research/SuperBIT/superbit-ngmix/scripts/output-real/psfex_output'
    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.17       # 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.
    
    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_23.5.fits'
    dir = 'data/COSMOS_23.5_training_sample'   
    cosmos_cat = galsim.COSMOSCatalog(cat_file_name, dir=dir)
    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.
    # Will also multiply by the gain and relative pixel scales...
    hst_eff_area = 2.4**2 * (1.-0.33**2)
    sbit_eff_area = tel_diam**2 * (1.-0.3840**2) 
    
  
    ###
    ### 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+"/*300*.psf")
    logger.info('Beginning loop over jitter/optical psfs')
    random_seed = 4783923
    
    i=0
    for psf_filen in all_psfs:
        logger.info('Beginning PSF %s...'% psf_filen)
        rng = galsim.BaseDeviate(random_seed)

        # This is specific to empirical PSFs
        
        try:
            timescale=psf_filen.split('target_')[1].split('_WCS')[0]
        except:
            timescale=psf_filen.split('sci_')[1].split('_WCS')[0]
            
        outname=''.join(['mockSuperbit_bp_empiricalPSF_',timescale,'_',str(i),'.fits'])
        truth_file_name=''.join(['./output-bandpass/truth_bp_empiricalPSF_',timescale,'_',str(i),'.dat'])
        file_name = os.path.join('output-bandpass',outname)

        # Set up the image:
        if timescale=='150':
            print("Automatically detecting a 150s exposure image, setting flux scale and noise accordingly")
            noise_variance=570               # ADU^2  (Just use simple Gaussian noise here.) 
            sky_level = 51                   # ADU 
            exp_time=150.
            
        else:
            print("Automatically detecting a 300s exposure image, setting flux scale and noise accordingly")
            noise_variance=800              # ADU^2  (Just use simple Gaussian noise here.) 
            sky_level = 106                 # ADU  
            exp_time=300.
            
        flux_scaling = (sbit_eff_area/hst_eff_area) * exp_time *(.206/.05)**2
                
        # Setting up a truth catalog
        
        names = [ 'gal_num', 'x_image', 'y_image',
                      'ra', 'dec', 'g1_meas', 'g2_meas', 'fwhm','mom_size',
                      'nfw_g1', 'nfw_g2', 'nfw_mu', 'redshift','flux', 'stamp_sum']
        types = [ int, 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(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
        # This is only relevant if "RealGalaxy" type is used
        
        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.0 * 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.
        
        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
                logger.debug("about to make stamp...")
                stamp,truth = make_a_galaxy(ud=ud,wcs=wcs,psf=psf,affine=affine)
                logger.debug("stamp is made")
                # 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.
                # Uncomment if using "real" COSMOS galaxy type 
                #noise_image[bounds] += truth.variance
            
                # Finally, add the stamp to the full image.
                full_image[bounds] += stamp[bounds]
                logger.debug("stamp added to full image")
                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
                sum_flux=numpy.sum(stamp.array)
                row = [ k,truth.x, truth.y, truth.ra, truth.dec, 
                            g1_real, g2_real, truth.fwhm, truth.mom_size, truth.g1,
                            truth.g2, truth.mu, truth.z, truth.flux, sum_flux]
                truth_catalog.addRow(row)
                logger.debug("row added to truth catalog")
                
            except:
                logger.info('Galaxy %d has failed, skipping...',k)
                pdb.set_trace()
                
        
            #####
            ### Inject cluster galaxy objects:
            ### - Note that this "cluster" is just for aesthetics
            ### - So, 'n_cluster_gals' is arbitrary
            ### - You could concievably create a method to base the number of galaxies injected
            ###   using some scaling relation between (NFW) mass and richness to set n_cluster_gals
            ###   to something based in reality. 
            #####


            random_seed=892375351

            center_coords = galsim.CelestialCoord(center_ra,center_dec)
            centerpix = wcs.toImage(center_coords)
            n_cluster_gals = 30
            
            for k in range(n_cluster_gals):
                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,affine=affine,psf=psf,
                                                                  centerpix=centerpix,cluster_cat=cluster_cat)                
                    # 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. This is more relevant to
                    # "real" galaxy images, not parametric like we have
            
                    #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=3221987
        
        for k in range(nstars):
            time1 = time.time()
            ud = galsim.UniformDeviate(random_seed_stars+k+1)
            try:

                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.
                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
                g2_real=star_stamp.FindAdaptiveMom().observed_shape.g2
                #g1_real = -9999.
                #g2_real = -9999.
                sum_flux=numpy.sum(star_stamp.array)
                row = [ k,truth.x, truth.y, truth.ra, truth.dec, 
                            g1_real, g2_real, truth.fwhm, truth.mom_size, truth.g1,
                            truth.g2, truth.mu, truth.z, truth.flux, sum_flux]
                truth_catalog.addRow(row)
                            
            except:
                logger.info('Star %d has failed, skipping...',k)
                pdb.set_trace()
                    
            
        # 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 output image')

        
        # Now write the image to disk.  
        full_image.write(file_name)

        
        # Add a FLUXSCL keyword for later stacking
        this_hdu=astropy.io.fits.open(file_name)
        this_hdu[0].header['FLXSCALE'] = 300.0/exp_time
        this_hdu.writeto(file_name,overwrite='True')
        logger.info('Wrote image to %r',file_name)

        
        # Write truth catalog to file. 
        truth_catalog.write(truth_file_name)
        
        i=i+1
        logger.info('completed run %d for psf %s',i,psf_filen)
        
    logger.info('completed all images')