Esempio n. 1
0
def test_crg_roundtrip():
    """Test that drawing a ChromaticRealGalaxy using the HST collecting area and filter gives back
    the original image.
    """
    f606w_cat = galsim.RealGalaxyCatalog('AEGIS_F606w_catalog.fits',
                                         dir=image_dir)
    f814w_cat = galsim.RealGalaxyCatalog('AEGIS_F814w_catalog.fits',
                                         dir=image_dir)

    indices = [0] if __name__ != "__main__" else list(range(len(f606w_cat)))

    for index in indices:
        orig_f606w = f606w_cat.getGalImage(index)
        orig_f814w = f814w_cat.getGalImage(index)

        crg = galsim.ChromaticRealGalaxy([f606w_cat, f814w_cat], index=index)

        # Note that getPSF() return value already includes convolution by the pixel
        f606w_obj = galsim.Convolve(crg, f606w_cat.getPSF(index))
        f814w_obj = galsim.Convolve(crg, f814w_cat.getPSF(index))
        f606w = f606w_cat.getBandpass()
        f814w = f814w_cat.getBandpass()

        im_f606w = f606w_obj.drawImage(f606w,
                                       image=orig_f606w.copy(),
                                       method='no_pixel')
        im_f814w = f814w_obj.drawImage(f814w,
                                       image=orig_f814w.copy(),
                                       method='no_pixel')

        printval(im_f606w, orig_f606w)
        printval(im_f814w, orig_f814w)

        orig_f606w_mom = galsim.hsm.FindAdaptiveMom(orig_f606w)
        orig_f814w_mom = galsim.hsm.FindAdaptiveMom(orig_f814w)

        im_f606w_mom = galsim.hsm.FindAdaptiveMom(im_f606w)
        im_f814w_mom = galsim.hsm.FindAdaptiveMom(im_f814w)

        # Images are only pixel-by-pixel consistent to 5% or so.  However, if you actually plot the
        # residuals (which you can do by flipping False->True in printval in galsim_test_helpers),
        # they appear as ringing over the whole image.  Probably it's unrealistic to expect this
        # test to work perfectly since we're effectively deconvolving and then reconvolving by the
        # same PSF, not a fatter PSF.
        np.testing.assert_allclose(orig_f606w.array,
                                   im_f606w.array,
                                   rtol=0.,
                                   atol=5e-2 * orig_f606w.array.max())
        np.testing.assert_allclose(orig_f814w.array,
                                   im_f814w.array,
                                   rtol=0.,
                                   atol=5e-2 * orig_f814w.array.max())

        # Happily, the pixel-by-pixel residuals don't appear to affect the moments much:
        np.testing.assert_allclose(orig_f606w_mom.moments_amp,
                                   im_f606w_mom.moments_amp,
                                   rtol=1e-3,
                                   atol=0)
        np.testing.assert_allclose(orig_f606w_mom.moments_centroid.x,
                                   im_f606w_mom.moments_centroid.x,
                                   rtol=0.,
                                   atol=1e-2)
        np.testing.assert_allclose(orig_f606w_mom.moments_centroid.y,
                                   im_f606w_mom.moments_centroid.y,
                                   rtol=0.,
                                   atol=1e-2)
        np.testing.assert_allclose(orig_f606w_mom.moments_sigma,
                                   im_f606w_mom.moments_sigma,
                                   rtol=1e-3,
                                   atol=0)
        np.testing.assert_allclose(orig_f606w_mom.observed_shape.g1,
                                   im_f606w_mom.observed_shape.g1,
                                   rtol=0,
                                   atol=2e-4)
        np.testing.assert_allclose(orig_f606w_mom.observed_shape.g2,
                                   im_f606w_mom.observed_shape.g2,
                                   rtol=0,
                                   atol=2e-4)

        np.testing.assert_allclose(orig_f814w_mom.moments_amp,
                                   im_f814w_mom.moments_amp,
                                   rtol=1e-3,
                                   atol=0)
        np.testing.assert_allclose(orig_f814w_mom.moments_centroid.x,
                                   im_f814w_mom.moments_centroid.x,
                                   rtol=0.,
                                   atol=1e-2)
        np.testing.assert_allclose(orig_f814w_mom.moments_centroid.y,
                                   im_f814w_mom.moments_centroid.y,
                                   rtol=0.,
                                   atol=1e-2)
        np.testing.assert_allclose(orig_f814w_mom.moments_sigma,
                                   im_f814w_mom.moments_sigma,
                                   rtol=1e-3,
                                   atol=0)
        np.testing.assert_allclose(orig_f814w_mom.observed_shape.g1,
                                   im_f814w_mom.observed_shape.g1,
                                   rtol=0,
                                   atol=1e-4)
        np.testing.assert_allclose(orig_f814w_mom.observed_shape.g2,
                                   im_f814w_mom.observed_shape.g2,
                                   rtol=0,
                                   atol=1e-4)
Esempio n. 2
0
    def __init__(self,
                 real_galaxy_catalog,
                 index=None,
                 id=None,
                 random=False,
                 rng=None,
                 x_interpolant=None,
                 k_interpolant=None,
                 flux=None,
                 flux_rescale=None,
                 pad_factor=4,
                 noise_pad_size=0,
                 gsparams=None,
                 logger=None):

        import numpy as np

        if rng is None:
            self.rng = galsim.BaseDeviate()
        elif not isinstance(rng, galsim.BaseDeviate):
            raise TypeError(
                "The rng provided to RealGalaxy constructor is not a BaseDeviate"
            )
        else:
            self.rng = rng
        self._rng = self.rng.duplicate(
        )  # This is only needed if we want to make sure eval(repr)
        # results in the same object.

        if flux is not None and flux_rescale is not None:
            raise TypeError(
                "Cannot supply a flux and a flux rescaling factor!")

        if isinstance(real_galaxy_catalog, tuple):
            # Special (undocumented) way to build a RealGalaxy without needing the rgc directly
            # by providing the things we need from it.  Used by COSMOSGalaxy.
            self.gal_image, self.psf_image, noise_image, pixel_scale, var = real_galaxy_catalog
            use_index = 0  # For the logger statements below.
            if logger:
                logger.debug('RealGalaxy %d: Start RealGalaxy constructor.',
                             use_index)
            self.catalog_file = None
        else:
            # Get the index to use in the catalog
            if index is not None:
                if id is not None or random is True:
                    raise AttributeError(
                        'Too many methods for selecting a galaxy!')
                use_index = index
            elif id is not None:
                if random is True:
                    raise AttributeError(
                        'Too many methods for selecting a galaxy!')
                use_index = real_galaxy_catalog.getIndexForID(id)
            elif random is True:
                uniform_deviate = galsim.UniformDeviate(self.rng)
                use_index = int(real_galaxy_catalog.nobjects *
                                uniform_deviate())
            else:
                raise AttributeError(
                    'No method specified for selecting a galaxy!')
            if logger:
                logger.debug('RealGalaxy %d: Start RealGalaxy constructor.',
                             use_index)

            # Read in the galaxy, PSF images; for now, rely on pyfits to make I/O errors.
            self.gal_image = real_galaxy_catalog.getGal(use_index)
            if logger:
                logger.debug('RealGalaxy %d: Got gal_image', use_index)

            self.psf_image = real_galaxy_catalog.getPSF(use_index)
            if logger:
                logger.debug('RealGalaxy %d: Got psf_image', use_index)

            #self.noise = real_galaxy_catalog.getNoise(use_index, self.rng, gsparams)
            # We need to duplication some of the RealGalaxyCatalog.getNoise() function, since we
            # want it to be possible to have the RealGalaxyCatalog in another process, and the
            # BaseCorrelatedNoise object is not picklable.  So we just build it here instead.
            noise_image, pixel_scale, var = real_galaxy_catalog.getNoiseProperties(
                use_index)
            if logger:
                logger.debug('RealGalaxy %d: Got noise_image', use_index)
            self.catalog_file = real_galaxy_catalog.getFileName()

        if noise_image is None:
            self.noise = galsim.UncorrelatedNoise(var,
                                                  rng=self.rng,
                                                  scale=pixel_scale,
                                                  gsparams=gsparams)
        else:
            ii = galsim.InterpolatedImage(noise_image,
                                          normalization="sb",
                                          calculate_stepk=False,
                                          calculate_maxk=False,
                                          x_interpolant='linear',
                                          gsparams=gsparams)
            self.noise = galsim.correlatednoise._BaseCorrelatedNoise(
                self.rng, ii, noise_image.wcs)
            self.noise = self.noise.withVariance(var)
        if logger:
            logger.debug('RealGalaxy %d: Finished building noise', use_index)

        # Save any other relevant information as instance attributes
        self.catalog = real_galaxy_catalog
        self.index = use_index
        self.pixel_scale = float(pixel_scale)
        self._x_interpolant = x_interpolant
        self._k_interpolant = k_interpolant
        self._pad_factor = pad_factor
        self._noise_pad_size = noise_pad_size
        self._flux = flux
        self._gsparams = gsparams

        # Convert noise_pad to the right noise to pass to InterpolatedImage
        if noise_pad_size:
            noise_pad = self.noise
        else:
            noise_pad = 0.

        # Build the InterpolatedImage of the PSF.
        self.original_psf = galsim.InterpolatedImage(
            self.psf_image,
            x_interpolant=x_interpolant,
            k_interpolant=k_interpolant,
            flux=1.0,
            gsparams=gsparams)
        if logger:
            logger.debug('RealGalaxy %d: Made original_psf', use_index)

        # Build the InterpolatedImage of the galaxy.
        # Use the stepK() value of the PSF as a maximum value for stepK of the galaxy.
        # (Otherwise, low surface brightness galaxies can get a spuriously high stepk, which
        # leads to problems.)
        self.original_gal = galsim.InterpolatedImage(
            self.gal_image,
            x_interpolant=x_interpolant,
            k_interpolant=k_interpolant,
            pad_factor=pad_factor,
            noise_pad_size=noise_pad_size,
            calculate_stepk=self.original_psf.stepK(),
            calculate_maxk=self.original_psf.maxK(),
            noise_pad=noise_pad,
            rng=self.rng,
            gsparams=gsparams)
        if logger:
            logger.debug('RealGalaxy %d: Made original_gal', use_index)

        # If flux is None, leave flux as given by original image
        if flux is not None:
            flux_rescale = flux / self.original_gal.getFlux()
        if flux_rescale is not None:
            self.original_gal *= flux_rescale
            self.noise *= flux_rescale**2

        # Calculate the PSF "deconvolution" kernel
        psf_inv = galsim.Deconvolve(self.original_psf, gsparams=gsparams)

        # Initialize the SBProfile attribute
        GSObject.__init__(
            self,
            galsim.Convolve([self.original_gal, psf_inv], gsparams=gsparams))
        if logger:
            logger.debug('RealGalaxy %d: Made gsobject', use_index)

        # Save the noise in the image as an accessible attribute
        self.noise = self.noise.convolvedWith(psf_inv, gsparams)
        if logger:
            logger.debug('RealGalaxy %d: Finished building RealGalaxy',
                         use_index)
Esempio n. 3
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.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.true_center

        # 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 21 arcsec,
            # which is large enough to cover all the way to the corners, although we'll need
            # to watch out for galaxies that are fully off the edge of the image.
            radius = 21
            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 (available as stamp.center)
            # cannot be at the true center (available as stamp.true_center) 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([gal, psf])

            # 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
            # If there is no overlap, then the intersection comes out as not defined, which we
            # can check with bounds.isDefined().
            if not bounds.isDefined():
                logger.info("object %d is fully off the edge of the image.  Skipping this one.", k)
                continue
            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
            bulge_frac = frac
            bulge = galsim.DeVaucouleurs(half_light_radius=dev_radius * scale)
            bulge = bulge.shear(e1=dev_e2, e2=dev_e2)

            flux = flux_pl.sample()
            gal = frac * bulge + (1 - frac) * disk

            redshift = np.random.rand() * max_redshift
            sed = np.random.choice(template_seds, 1)[0].atRedshift(redshift)

            #normalize flux according to specified filter
            sed = sed.withFlux(flux, filters[args.filter_norm])
            mgal = galsim.Chromatic(gal, sed)

            cgal = galsim.Convolve([mgal, psf])

            gtrue.rad = frac * dev_radius + (1 - frac) * exp_radius
            gtrue.sed = sed
            gtrue.redshift = redshift

            true_x.append(gtrue.x)
            true_y.append(gtrue.y)

            for filter_name, filter_ in filters.items():
                tmp = cgal.drawImage(filter_,
                                     image=images[filter_name],
                                     add_to_image=True,
                                     offset=(offsetx, offsety))
                gtrue.fluxes[filter_name] = tmp.added_flux
Esempio n. 5
0
def main(argv):
    """
    Make a fits image cube where each frame has two images of the same galaxy drawn
    with regular FFT convolution and with photon shooting.

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

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

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

    # Define some parameters we'll use below.

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

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

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

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

    psf_fwhm = 0.65  # arcsec

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

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

    logger.info('Starting demo script 7')

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                t4 = time.time()

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

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

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

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

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

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

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

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

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

# this script was used to generate the idealized Gaussian test cases used in ../test_real.py
import galsim

fake_gal_fwhm = 0.7 # arcsec
fake_gal_shear1 = 0.29 # shear representing intrinsic shape component 1
fake_gal_shear2 = -0.21 # shear representing intrinsic shape component 2; note non-round, to detect
              # possible issues with x<->y or others that might not show up using circular galaxy
fake_gal_flux = 1000.0
fake_gal_orig_PSF_fwhm = 0.1 # arcsec
fake_gal_orig_PSF_shear1 = 0.0
fake_gal_orig_PSF_shear2 = -0.07

pixel_scale = 0.03 # high-resolution data

orig_gal = galsim.Gaussian(flux = fake_gal_flux, fwhm = fake_gal_fwhm)
orig_gal.applyShear(fake_gal_shear1, fake_gal_shear2)
orig_PSF = galsim.Gaussian(flux = 1.0, fwhm = fake_gal_orig_PSF_fwhm)
orig_PSF.applyShear(fake_gal_orig_PSF_shear1, fake_gal_orig_PSF_shear2)
orig_observed = galsim.Convolve(orig_gal, orig_PSF)

obs_image = galsim.ImageF(200, 200)
orig_observed.draw(obs_image, dx = pixel_scale)
obs_PSF_image = galsim.ImageF(30, 30)
orig_PSF.draw(obs_PSF_image, dx = pixel_scale)

obs_image.write('tmp_obs_image.fits', clobber=True)
obs_PSF_image.write('tmp_obs_PSF_image.fits', clobber = True)

Esempio n. 7
0
def _run_metacal(*, n_sims, rng, swap_g1g2, dudx, dudy, dvdx, dvdy):
    """Run metacal on an image composed of stamps w/ constant noise.

    Parameters
    ----------
    n_sims : int
        The number of objects to run.
    rng : np.random.RandomState
        An RNG to use.
    swap_g1g2 : bool
        If True, set the true shear on the 2-axis to 0.02 and 1-axis to 0.0.
        Otherwise, the true shear on the 1-axis is 0.02 and on the 2-axis is
        0.0.
    dudx : float
        The du/dx Jacobian component.
    dudy : float
        The du/dy Jacobian component.
    dydx : float
        The dv/dx Jacobian component.
    dvdy : float
        The dv/dy Jacobian component.

    Returns
    -------
    result : dict
        A dictionary with each of the metacal catalogs.
    """

    method = 'auto'

    stamp_size = 33
    psf_stamp_size = 33

    cen = (stamp_size - 1) / 2
    psf_cen = (psf_stamp_size - 1) / 2

    s2n = 1e16
    flux = 1e6

    galsim_jac = galsim.JacobianWCS(dudx=dudx, dudy=dudy, dvdx=dvdx, dvdy=dvdy)

    if swap_g1g2:
        g1 = 0.0
        g2 = 0.02
    else:
        g1 = 0.02
        g2 = 0.0

    gal = galsim.Exponential(half_light_radius=0.5).withFlux(flux).shear(g1=g1,
                                                                         g2=g2)

    psf = galsim.Gaussian(fwhm=0.9).withFlux(1)
    obj = galsim.Convolve(gal, psf)
    obj_im = obj.drawImage(nx=111, ny=111).array
    noise = np.sqrt(np.sum(obj_im**2)) / s2n

    data = []
    for ind in tqdm.trange(n_sims):
        ################################
        # make the obs

        # psf
        psf_im = psf.drawImage(nx=psf_stamp_size,
                               ny=psf_stamp_size,
                               wcs=galsim_jac,
                               method=method).array
        psf_noise = np.sqrt(np.sum(psf_im**2)) / 10000
        wgt = np.ones_like(psf_im) / psf_noise**2
        psf_im += (rng.normal(size=psf_im.shape) * psf_noise)
        psf_jac = ngmix.Jacobian(x=psf_cen,
                                 y=psf_cen,
                                 dudx=dudx,
                                 dudy=dudy,
                                 dvdx=dvdx,
                                 dvdy=dvdy)
        psf_obs = ngmix.Observation(image=psf_im, weight=wgt, jacobian=psf_jac)

        # now render object
        scale = psf_jac.scale
        shift = rng.uniform(low=-scale / 2, high=scale / 2, size=2)
        _obj = obj.shift(dx=shift[0], dy=shift[1])
        xy = galsim_jac.toImage(galsim.PositionD(shift))
        im = _obj.drawImage(nx=stamp_size,
                            ny=stamp_size,
                            wcs=galsim_jac,
                            method=method).array
        jac = ngmix.Jacobian(x=cen + xy.x,
                             y=cen + xy.y,
                             dudx=dudx,
                             dudy=dudy,
                             dvdx=dvdx,
                             dvdy=dvdy)
        wgt = np.ones_like(im) / noise**2
        nse = rng.normal(size=im.shape) * noise
        im += (rng.normal(size=im.shape) * noise)
        obs = ngmix.Observation(image=im,
                                weight=wgt,
                                noise=nse,
                                bmask=np.zeros_like(im, dtype=np.int32),
                                ormask=np.zeros_like(im, dtype=np.int32),
                                jacobian=jac,
                                psf=psf_obs)

        # build the mbobs
        mbobs = ngmix.MultiBandObsList()
        obslist = ngmix.ObsList()
        obslist.append(obs)
        mbobs.append(obslist)

        mbobs.meta['id'] = ind + 1
        # these settings do not matter that much I think
        mbobs[0].meta['Tsky'] = 1
        mbobs[0].meta['magzp_ref'] = 26.5
        mbobs[0][0].meta['orig_col'] = ind + 1
        mbobs[0][0].meta['orig_row'] = ind + 1

        ################################
        # run the fitters
        try:
            res = _run_metacal_fitter(mbobs, rng)
        except Exception as e:
            print(e)
            res = None

        if res is not None:
            data.append(res)

    if len(data) > 0:
        res = data
    else:
        res = None

    return res
Esempio n. 8
0
def main(argv):
    """
    Make images using constant PSF and variable shear:
      - The main image is 2048 x 2048 pixels.
      - Pixel scale is 0.2 arcsec/pixel, hence the image is about 0.11 degrees on a side.
      - Applied shear is from a cosmological power spectrum read in from file.
      - The PSF is a real one from SDSS, and corresponds to a convolution of atmospheric PSF,
        optical PSF, and pixel response, which has been sampled at pixel centers.  We used a PSF
        from SDSS in order to have a PSF profile that could correspond to what you see with a real
        telescope. However, in order that the galaxy resolution not be too poor, we tell GalSim that
        the pixel scale for that PSF image is 0.2" rather than 0.396".  We are simultaneously lying
        about the intrinsic size of the PSF and about the pixel scale when we do this.
      - The galaxies come from COSMOSCatalog, which can produce either RealGalaxy profiles
        (like in demo10) and parametric fits to those profiles.  We choose 30% 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("demo11")

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

    pixel_scale = 0.2  # arcsec/pixel
    image_size = 2048  # size of image in pixels
    image_size_arcsec = image_size * pixel_scale  # size of big image in each dimension (arcsec)
    noise_variance = 5.e4  # ADU^2  (Just use simple Gaussian noise here.)
    nobj = 288  # number of galaxies in entire field
    # (This corresponds to 8 galaxies / arcmin^2)
    grid_spacing = 90.0  # The spacing between the samples for the power spectrum
    # realization (arcsec)
    tel_diam = 4  # Let's figure out the flux for a 4 m class telescope
    exp_time = 300  # exposing for 300 seconds.
    center_ra = 19.3 * galsim.hours  # The RA, Dec of the center of the image on the sky
    center_dec = -33.1 * galsim.degrees

    # 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.  Note that what is important is
    # the *effective* area after taking into account obscuration.  For HST, the telescope diameter
    # is 2.4 but there is obscuration (a linear factor of 0.33).  Here, we assume that the telescope
    # we're simulating effectively has no obscuration factor.  We're also ignoring the pi/4 factor
    # since it appears in the numerator and denominator, so we use area = diam^2.
    hst_eff_area = 2.4**2 * (1. - 0.33**2)
    flux_scaling = (tel_diam**2 / hst_eff_area) * exp_time

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

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

    logger.info('Starting demo script 11')

    # Read in galaxy catalog
    # The COSMOSCatalog uses the same input file as we have been using for RealGalaxyCatalogs
    # along with a second file called real_galaxy_catalog_23.5_examples_fits.fits, which stores
    # the information about the parameteric fits.  There is no need to specify the second file
    # name, since the name is derivable from the name of the main 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)

    # Setup the PowerSpectrum object we'll be using:
    # To do this, we first have to read in the tabulated shear power spectrum, often denoted
    # C_ell(ell), where ell has units of inverse angle and C_ell has units of angle^2.  However,
    # GalSim works in the flat-sky approximation, so we use this notation interchangeably with
    # P(k).  GalSim does not calculate shear power spectra for users, who must be able to provide
    # their own (or use the examples in the repository).
    #
    # Here we use a tabulated power spectrum from iCosmo (http://icosmo.org), with the following
    # cosmological parameters and survey design:
    # H_0 = 70 km/s/Mpc
    # Omega_m = 0.25
    # Omega_Lambda = 0.75
    # w_0 = -1.0
    # w_a = 0.0
    # n_s = 0.96
    # sigma_8 = 0.8
    # Smith et al. prescription for the non-linear power spectrum.
    # Eisenstein & Hu transfer function with wiggles.
    # Default dN/dz with z_med = 1.0
    # The file has, as required, just two columns which are k and P(k).  However, iCosmo works in
    # terms of ell and C_ell; ell is inverse radians and C_ell in radians^2.  Since GalSim tends to
    # work in terms of arcsec, we have to tell it that the inputs are radians^-1 so it can convert
    # to store in terms of arcsec^-1.
    pk_file = os.path.join('data', 'cosmo-fid.zmed1.00.out')
    ps = galsim.PowerSpectrum(pk_file, units=galsim.radians)
    # The argument here is "e_power_function" which defines the E-mode power to use.
    logger.info('Set up power spectrum from tabulated P(k)')

    # Now let's read in the PSF.  It's a real SDSS PSF, which means pixel scale of 0.396".  However,
    # the typical seeing is 1.2" and we want to simulate better seeing, so we will just tell GalSim
    # that the pixel scale is 0.2".  We have to be careful with SDSS PSF images, as they have an
    # added 'soft bias' of 1000 which has been removed before creation of this file, so that the sky
    # level is properly zero.  Also, the file is bzipped, to demonstrate the ability of GalSim
    # handle this kind of compressed file (among others).  We read the image directly into an
    # InterpolatedImage GSObject, so we can manipulate it as needed (here, the only manipulation
    # needed is convolution).  The flux is 1 as needed for a PSF.
    psf_file = os.path.join('data', 'example_sdss_psf_sky0.fits.bz2')
    psf = galsim.InterpolatedImage(psf_file, scale=pixel_scale, flux=1.)
    logger.info('Read in PSF image from bzipped FITS file')

    # Setup the image:
    full_image = galsim.ImageF(image_size, image_size)

    # The default convention for indexing an image is to follow the FITS standard where the
    # lower-left pixel is called (1,1).  However, this can be counter-intuitive to people more
    # used to C or python indexing, where indices start at 0.  It is possible to change the
    # coordinates of the lower-left pixel with the methods `setOrigin`.  For this demo, we
    # switch to 0-based indexing, so the lower-left pixel will be called (0,0).
    full_image.setOrigin(0, 0)

    # As for demo10, we use random_seed for the random numbers required for the
    # whole image.  In this case, both the power spectrum realization and the noise on the
    # full image we apply later.
    rng = galsim.BaseDeviate(random_seed)

    # We want to make random positions within our image.  However, currently for shears from a power
    # spectrum we first have to get shears on a grid of positions, and then we can choose random
    # positions within that.  So, let's make the grid.  We're going to make it as large as the
    # image, with grid points spaced by 90 arcsec (hence interpolation only happens below 90"
    # scales, below the interesting scales on which we want the shear power spectrum to be
    # represented exactly).  The lensing engine wants positions in arcsec, so calculate that:
    ps.buildGrid(grid_spacing=grid_spacing,
                 ngrid=int(math.ceil(image_size_arcsec / grid_spacing)),
                 rng=rng)
    logger.info('Made gridded shears')

    # We keep track of how much noise is already in the image from the RealGalaxies.
    # The default initial value is all pixels = 0.
    noise_image = galsim.ImageF(image_size, image_size)
    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  dudy ) = ( cos(theta)  -sin(theta) ) * pixel_scale
    # ( dvdx  dvdy )   ( sin(theta)   cos(theta) )
    # Note: if you use numpy trig rather than math trig functions, you can call them directly
    # with a galsim.Angle instance (e.g. theta here) and it will work correctly.
    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)

    # We can also put it on the celestial sphere to give it a bit more realism.
    # The TAN projection takes a (u,v) coordinate system on a tangent plane and projects
    # that plane onto the sky using a given point as the tangent point.  The tangent
    # point should be given as a CelestialCoord.
    sky_center = galsim.CelestialCoord(ra=center_ra, dec=center_dec)

    # The third parameter, units, defaults to arcsec, but we make it explicit here.
    # It sets the angular units of the (u,v) intermediate coordinate system.
    wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
    full_image.wcs = wcs

    # Now we need to loop over our 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)

        # Choose a random RA, Dec around the sky_center.
        # Note that for this to come out close to a square shape, we need to account for the
        # cos(dec) part of the metric: ds^2 = dr^2 + r^2 d(dec)^2 + r^2 cos^2(dec) d(ra)^2
        # So need to calculate dec first.
        dec = center_dec + (ud() - 0.5) * image_size_arcsec * galsim.arcsec
        ra = center_ra + (
            ud() - 0.5) * image_size_arcsec / numpy.cos(dec) * galsim.arcsec
        world_pos = galsim.CelestialCoord(ra, dec)

        # We will need the image position as well, so use the wcs to get that
        image_pos = wcs.toImage(world_pos)

        # We also need this in the tangent plane, which we call "world coordinates" here,
        # since the PowerSpectrum class is really defined on that plane, not in (ra,dec).
        uv_pos = affine.toWorld(image_pos)

        # Get the reduced shears and magnification at this point
        g1, g2, mu = ps.getLensing(pos=uv_pos)

        # Now we will have the COSMOSCatalog make a galaxy profile for us.  It can make either
        # a RealGalaxy using the original HST image and PSF, or a parametric model based on
        # parametric fits to the light distribution of the HST observation.  The parametric
        # models are either a Sersic fit to the data or a bulge + disk fit according to which
        # one gave the better chisq value.  We will select a galaxy at random from the catalog.
        # One could easily do this by choosing an index = int(ud() * cosmos_cat.nobjects), but
        # we will instead allow the catalog to choose a random galaxy for us.  It will remove any
        # selection effects involved in postage stamp creation using weights that are stored in
        # the catalog.  (If for some reason you prefer not to do that, you can always choose a
        # purely random index yourself using int(ud() * cosmos_cat.nobjects).)  We employ this
        # random selection by simply failing to specify an index or identifier for a galaxy, in
        # which case it chooses a random one.

        # First determine whether we will make a real galaxy (`gal_type = 'real'`) or a parametric
        # galaxy (`gal_type = 'parametric'`).  The real galaxies take longer to render, so for this
        # script, we just use them 30% of the time and use parametric galaxies the other 70%.

        # We could just use `ud()<0.3` for this, but instead we introduce another Deviate type
        # available in GalSim that we haven't used yet: BinomialDeviate.
        # It takes an N and p value and returns integers according to a binomial distribution.
        # i.e. How many heads you get after N flips if each flip has a chance, p, of being heads.
        binom = galsim.BinomialDeviate(ud, N=1, p=0.3)
        real = binom()

        if real:
            # For real galaxies, we will want to whiten the noise in the image (below).
            # When whitening the image, we need to make sure the original correlated noise is
            # present throughout the whole image, otherwise the whitening will do the wrong thing
            # to the parts of the image that don't include the original image.  The RealGalaxy
            # stores the correct noise profile to use as the gal.noise attribute.  This noise
            # profile is automatically updated as we shear, dilate, convolve, etc.  But we need to
            # tell it how large to pad with this noise by hand.  This is a bit complicated for the
            # code to figure out on its own, so we have to supply the size for noise padding
            # with the noise_pad_size parameter.

            # The large galaxies will render fine without any noise padding, but the postage stamp
            # for the smaller galaxies will be sized appropriately for the PSF, which may make the
            # stamp larger than the original galaxy image.  The psf image is 40 x 40, although
            # the bright part is much more concentrated than that.  If we pad out the galaxy image
            # to at least 40 x sqrt(2), we should be safe even if the galaxy image is rotated
            # with respect to the psf image.
            #     noise_pad_size = 40 * sqrt(2) * 0.2 arcsec/pixel = 11.3 arcsec
            gal = cosmos_cat.makeGalaxy(gal_type='real',
                                        rng=ud,
                                        noise_pad_size=11.3)
        else:
            gal = cosmos_cat.makeGalaxy(gal_type='parametric', rng=ud)

        # Apply a random rotation
        theta = ud() * 2.0 * numpy.pi * galsim.radians
        gal = gal.rotate(theta)

        # Rescale the flux to match our telescope configuration.
        # This automatically scales up the noise variance by flux_scaling**2.
        gal *= flux_scaling

        # Apply the cosmological (reduced) shear and magnification at this position using a single
        # GSObject method.
        gal = gal.lens(g1, g2, mu)

        # Convolve with the PSF.
        final = galsim.Convolve(psf, gal)

        # Account for the fractional part of the position
        # cf. demo9.py for an explanation of this nominal position stuff.
        x_nominal = image_pos.x + 0.5
        y_nominal = image_pos.y + 0.5
        ix_nominal = int(math.floor(x_nominal + 0.5))
        iy_nominal = int(math.floor(y_nominal + 0.5))
        dx = x_nominal - ix_nominal
        dy = y_nominal - iy_nominal
        offset = galsim.PositionD(dx, dy)

        # We use method='no_pixel' here because the SDSS PSF image that we are using includes the
        # pixel response already.
        stamp = final.drawImage(wcs=wcs.local(image_pos),
                                offset=offset,
                                method='no_pixel')

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

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

        # Now, if we are using a real galaxy, we want to ether whiten or at least symmetrize the
        # noise on the postage stamp to avoid having to deal with correlated noise in any kind of
        # image processing you would want to do on the final image.  (Like measure galaxy shapes.)

        # Galsim automatically propagates the noise correctly from the initial RealGalaxy object
        # through the applied shear, distortion, rotation, and convolution into the final object's
        # noise attribute.  To make the noise fully white, use the image.whitenNoise() method.
        # The returned value is the variance of the Gaussian noise that is present after the
        # whitening process.

        # However, this is often overkill for many applications.  If it is acceptable to merely end
        # up with noise with some degree of symmetry (say 4-fold or 8-fold symmetry), then you can
        # instead have GalSim just add enough noise to make the resulting noise have this kind of
        # symmetry.  Usually this requires adding significantly less additional noise, which means
        # you can have the resulting total variance be somewhat smaller.  The returned variance
        # corresponds to the zero-lag value of the noise correlation function, which will still have
        # off-diagonal elements.  We can do this step using the image.symmetrizeNoise() method.
        if real:
            if True:
                # We use the symmetrizing option here.
                new_variance = stamp.symmetrizeNoise(final.noise, 8)
            else:
                # Here is how you would do it if you wanted to fully whiten the image.
                new_variance = stamp.whitenNoise(final.noise)

            # 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] += new_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: position relative to center = %s, t=%f s', k,
                    str(uv_pos), tot_time)

    # 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.  We have a special noise class
    # that can do this.  VariableGaussianNoise takes an image of variance values and applies
    # Gaussian noise with the corresponding variance to each pixel.
    # So all we need to do is build an image with how much noise to add to each pixel to get us
    # up to the maximum value that we already have in the 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.  We have to do this step
    # at the end, rather than adding to individual postage stamps, in order to get the noise
    # level right in the overlap regions between postage stamps.
    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)

    # 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_size - 1), (image_size - 1, 0),
                   (image_size - 1, image_size - 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.'
    )
Esempio n. 9
0
def main(argv):
    """
    A little bit more sophisticated, but still pretty basic:
      - Use a sheared, exponential profile for the galaxy.
      - Convolve it by a circular Moffat PSF.
      - Add Poisson noise to the image.
    """
    # In non-script code, use getLogger(__name__) at module scope instead.
    logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout)
    logger = logging.getLogger("demo2")

    gal_flux = 1.e5    # counts
    gal_r0 = 2.7       # arcsec
    g1 = 0.1           #
    g2 = 0.2           #
    psf_beta = 5       #
    psf_re = 1.0       # arcsec
    pixel_scale = 0.2  # arcsec / pixel
    sky_level = 2.5e3  # counts / arcsec^2

    # This time use a particular seed, so the image is deterministic.
    # This is the same seed that is used in demo2.yaml, which means the images produced
    # by the two methods will be precisely identical.
    random_seed = 1534225


    logger.info('Starting demo script 2 using:')
    logger.info('    - sheared (%.2f,%.2f) exponential galaxy (flux = %.1e, scale radius = %.2f),',
                g1, g2, gal_flux, gal_r0)
    logger.info('    - circular Moffat PSF (beta = %.1f, re = %.2f),', psf_beta, psf_re)
    logger.info('    - pixel scale = %.2f,', pixel_scale)
    logger.info('    - Poisson noise (sky level = %.1e).', sky_level)

    # Initialize the (pseudo-)random number generator that we will be using below.
    # For a technical reason that will be explained later (demo9.py), we add 1 to the
    # given random seed here.
    rng = galsim.BaseDeviate(random_seed+1)

    # Define the galaxy profile.
    gal = galsim.Exponential(flux=gal_flux, scale_radius=gal_r0)

    # Shear the galaxy by some value.
    # There are quite a few ways you can use to specify a shape.
    # q, beta      Axis ratio and position angle: q = b/a, 0 < q < 1
    # e, beta      Ellipticity and position angle: |e| = (1-q^2)/(1+q^2)
    # g, beta      ("Reduced") Shear and position angle: |g| = (1-q)/(1+q)
    # eta, beta    Conformal shear and position angle: eta = ln(1/q)
    # e1,e2        Ellipticity components: e1 = e cos(2 beta), e2 = e sin(2 beta)
    # g1,g2        ("Reduced") shear components: g1 = g cos(2 beta), g2 = g sin(2 beta)
    # eta1,eta2    Conformal shear components: eta1 = eta cos(2 beta), eta2 = eta sin(2 beta)
    gal = gal.shear(g1=g1, g2=g2)
    logger.debug('Made galaxy profile')

    # Define the PSF profile.
    psf = galsim.Moffat(beta=psf_beta, flux=1., half_light_radius=psf_re)
    logger.debug('Made PSF profile')

    # Final profile is the convolution of these.
    final = galsim.Convolve([gal, psf])
    logger.debug('Convolved components into final profile')

    # Draw the image with a particular pixel scale.
    image = final.drawImage(scale=pixel_scale)
    # The "effective PSF" is the PSF as drawn on an image, which includes the convolution
    # by the pixel response.  We label it epsf here.
    image_epsf = psf.drawImage(scale=pixel_scale)
    logger.debug('Made image of the profile')

    # To get Poisson noise on the image, we will use a class called PoissonNoise.
    # However, we want the noise to correspond to what you would get with a significant
    # flux from tke sky.  This is done by telling PoissonNoise to add noise from a
    # sky level in addition to the counts currently in the image.
    #
    # One wrinkle here is that the PoissonNoise class needs the sky level in each pixel,
    # while we have a sky_level in counts per arcsec^2.  So we need to convert:
    sky_level_pixel = sky_level * pixel_scale**2
    noise = galsim.PoissonNoise(rng, sky_level=sky_level_pixel)
    image.addNoise(noise)
    logger.debug('Added Poisson noise')

    # Write the image to a file.
    if not os.path.isdir('output'):
        os.mkdir('output')
    file_name = os.path.join('output', 'demo2.fits')
    file_name_epsf = os.path.join('output','demo2_epsf.fits')
    image.write(file_name)
    image_epsf.write(file_name_epsf)
    logger.info('Wrote image to %r',file_name)
    logger.info('Wrote effective PSF image to %r',file_name_epsf)

    results = galsim.hsm.EstimateShear(image, image_epsf)

    logger.info('HSM reports that the image has observed shape and size:')
    logger.info('    e1 = %.3f, e2 = %.3f, sigma = %.3f (pixels)', results.observed_shape.e1,
                results.observed_shape.e2, results.moments_sigma)
    logger.info('When carrying out Regaussianization PSF correction, HSM reports distortions')
    logger.info('    e1, e2 = %.3f, %.3f',
                results.corrected_e1, results.corrected_e2)
    logger.info('Expected values in the limit that noise and non-Gaussianity are negligible:')
    exp_shear = galsim.Shear(g1=g1, g2=g2)
    logger.info('    g1, g2 = %.3f, %.3f', exp_shear.e1,exp_shear.e2)
Esempio n. 10
0
def check_crg_noise(n_sed, n_im, n_trial, tol):
    print("Checking CRG noise for")
    print("n_sed = {}".format(n_sed))
    print("n_im = {}".format(n_im))
    print("n_trial = {}".format(n_trial))
    print("Constructing chromatic PSFs")
    in_PSF = galsim.ChromaticAiry(lam=700., diam=2.4)
    out_PSF = galsim.ChromaticAiry(lam=700., diam=0.6)

    print("Constructing filters and SEDs")
    waves = np.arange(550.0, 900.1, 10.0)
    visband = galsim.Bandpass(galsim.LookupTable(waves,
                                                 np.ones_like(waves),
                                                 interpolant='linear'),
                              wave_type='nm')
    split_points = np.linspace(550.0, 900.0, n_im + 1, endpoint=True)
    bands = [
        visband.truncate(blue_limit=blim, red_limit=rlim)
        for blim, rlim in zip(split_points[:-1], split_points[1:])
    ]

    maxk = max([
        out_PSF.evaluateAtWavelength(waves[0]).maxK(),
        out_PSF.evaluateAtWavelength(waves[-1]).maxK()
    ])

    SEDs = [
        galsim.SED(galsim.LookupTable(waves, waves**i, interpolant='linear'),
                   flux_type='fphotons',
                   wave_type='nm').withFlux(1.0, visband) for i in range(n_sed)
    ]

    print("Constructing input noise correlation functions")
    rng = galsim.BaseDeviate(57721)
    in_xis = [
        galsim.getCOSMOSNoise(cosmos_scale=0.03,
                              rng=rng).dilate(1 + i * 0.05).rotate(
                                  5 * i * galsim.degrees) for i in range(n_im)
    ]

    print("Creating noise images")
    img_sets = []
    for i in range(n_trial):
        imgs = []
        for xi in in_xis:
            img = galsim.Image(128, 128, scale=0.03)
            img.addNoise(xi)
            imgs.append(img)
        img_sets.append(imgs)

    print("Constructing `ChromaticRealGalaxy`s")
    crgs = []
    for imgs in img_sets:
        crgs.append(
            galsim.ChromaticRealGalaxy.makeFromImages(imgs,
                                                      bands,
                                                      in_PSF,
                                                      in_xis,
                                                      SEDs=SEDs,
                                                      maxk=maxk))

    print("Convolving by output PSF")
    objs = [galsim.Convolve(crg, out_PSF) for crg in crgs]

    print("Drawing through output filter")
    out_imgs = [
        obj.drawImage(visband, nx=30, ny=30, scale=0.1) for obj in objs
    ]

    noise = objs[0].noise

    print("Measuring images' correlation functions")
    xi_obs = galsim.correlatednoise.CorrelatedNoise(out_imgs[0])
    for img in out_imgs[1:]:
        xi_obs += galsim.correlatednoise.CorrelatedNoise(img)
    xi_obs /= n_trial
    xi_obs_img = galsim.Image(30, 30, scale=0.1)
    xi_obs.drawImage(xi_obs_img)
    noise_img = galsim.Image(30, 30, scale=0.1)
    noise.drawImage(noise_img)

    print("Predicted/Observed variance:",
          noise.getVariance() / xi_obs.getVariance())
    print("Predicted/Observed xlag-1 covariance:",
          noise_img.array[14, 15] / xi_obs_img.array[14, 15])
    print("Predicted/Observed ylag-1 covariance:",
          noise_img.array[15, 14] / xi_obs_img.array[15, 14])
    # Just test that the covariances for nearest neighbor pixels are accurate.
    np.testing.assert_allclose(noise_img.array[14:17, 14:17],
                               xi_obs_img.array[14:17, 14:17],
                               rtol=0,
                               atol=noise.getVariance() * tol)
Esempio n. 11
0
def test_real_galaxy_ideal():
    """Test accuracy of various calculations with fake Gaussian RealGalaxy vs. ideal expectations"""
    # read in faked Gaussian RealGalaxy from file
    rgc = galsim.RealGalaxyCatalog(catalog_file, dir=image_dir)
    assert len(rgc) == rgc.getNObjects() == rgc.nobjects == len(rgc.cat)
    rg = galsim.RealGalaxy(rgc, index=ind_fake)
    # as a side note, make sure it behaves okay given a legit RNG and a bad RNG
    # or when trying to specify the galaxy too many ways
    rg_1 = galsim.RealGalaxy(rgc, index=ind_fake, rng=galsim.BaseDeviate(1234))
    rg_2 = galsim.RealGalaxy(rgc, random=True)
    try:
        np.testing.assert_raises(TypeError,
                                 galsim.RealGalaxy,
                                 rgc,
                                 index=ind_fake,
                                 rng='foo')
        np.testing.assert_raises(AttributeError,
                                 galsim.RealGalaxy,
                                 rgc,
                                 index=ind_fake,
                                 id=0)
        np.testing.assert_raises(AttributeError,
                                 galsim.RealGalaxy,
                                 rgc,
                                 index=ind_fake,
                                 random=True)
        np.testing.assert_raises(AttributeError,
                                 galsim.RealGalaxy,
                                 rgc,
                                 id=0,
                                 random=True)
        np.testing.assert_raises(AttributeError, galsim.RealGalaxy, rgc)
    except ImportError:
        print('The assert_raises tests require nose')
    # Different RNGs give different random galaxies.
    rg_3 = galsim.RealGalaxy(rgc, random=True, rng=galsim.BaseDeviate(12345))
    rg_4 = galsim.RealGalaxy(rgc, random=True, rng=galsim.BaseDeviate(67890))
    assert rg_3.index != rg_4.index, 'Different seeds did not give different random objects!'

    check_basic(rg, "RealGalaxy", approx_maxsb=True)
    check_basic(rg_1, "RealGalaxy", approx_maxsb=True)
    check_basic(rg_2, "RealGalaxy", approx_maxsb=True)

    do_pickle(
        rgc, lambda x: [
            x.getGalImage(ind_fake),
            x.getPSFImage(ind_fake),
            x.getNoiseProperties(ind_fake)
        ])
    do_pickle(
        rgc,
        lambda x: drawNoise(x.getNoise(ind_fake, rng=galsim.BaseDeviate(123))))
    do_pickle(rgc)
    do_pickle(
        rg, lambda x: [
            x.gal_image, x.psf_image,
            repr(x.noise), x.original_psf.flux, x.original_gal.flux, x.flux
        ])
    do_pickle(rg, lambda x: x.drawImage(nx=20, ny=20, scale=0.7))
    do_pickle(rg_1, lambda x: x.drawImage(nx=20, ny=20, scale=0.7))
    do_pickle(rg)
    do_pickle(rg_1)

    ## for the generation of the ideal right answer, we need to add the intrinsic shape of the
    ## galaxy and the lensing shear using the rule for addition of distortions which is ugly, but oh
    ## well:
    (d1, d2) = galsim.utilities.g1g2_to_e1e2(fake_gal_shear1, fake_gal_shear2)
    (d1app, d2app) = galsim.utilities.g1g2_to_e1e2(targ_applied_shear1,
                                                   targ_applied_shear2)
    denom = 1.0 + d1 * d1app + d2 * d2app
    dapp_sq = d1app**2 + d2app**2
    d1tot = (d1 + d1app + d2app / dapp_sq * (1.0 - np.sqrt(1.0 - dapp_sq)) *
             (d2 * d1app - d1 * d2app)) / denom
    d2tot = (d2 + d2app + d1app / dapp_sq * (1.0 - np.sqrt(1.0 - dapp_sq)) *
             (d1 * d2app - d2 * d1app)) / denom

    # convolve with a range of Gaussians, with and without shear (note, for this test all the
    # original and target ePSFs are Gaussian - there's no separate pixel response so that everything
    # can be calculated analytically)
    for tps in targ_pixel_scale:
        for tpf in targ_PSF_fwhm:
            for tps1 in targ_PSF_shear1:
                for tps2 in targ_PSF_shear2:
                    print('tps,tpf,tps1,tps2 = ', tps, tpf, tps1, tps2)
                    # make target PSF
                    targ_PSF = galsim.Gaussian(fwhm=tpf).shear(g1=tps1,
                                                               g2=tps2)
                    # simulate image
                    tmp_gal = rg.withFlux(fake_gal_flux).shear(
                        g1=targ_applied_shear1, g2=targ_applied_shear2)
                    final_tmp_gal = galsim.Convolve(targ_PSF, tmp_gal)
                    sim_image = final_tmp_gal.drawImage(scale=tps,
                                                        method='no_pixel')
                    # galaxy sigma, in units of pixels on the final image
                    sigma_ideal = (fake_gal_fwhm / tps) * fwhm_to_sigma
                    # compute analytically the expected galaxy moments:
                    mxx_gal, myy_gal, mxy_gal = ellip_to_moments(
                        d1tot, d2tot, sigma_ideal)
                    # compute analytically the expected PSF moments:
                    targ_PSF_e1, targ_PSF_e2 = galsim.utilities.g1g2_to_e1e2(
                        tps1, tps2)
                    targ_PSF_sigma = (tpf / tps) * fwhm_to_sigma
                    mxx_PSF, myy_PSF, mxy_PSF = ellip_to_moments(
                        targ_PSF_e1, targ_PSF_e2, targ_PSF_sigma)
                    # get expected e1, e2, sigma for the PSF-convolved image
                    tot_e1, tot_e2, tot_sigma = moments_to_ellip(
                        mxx_gal + mxx_PSF, myy_gal + myy_PSF,
                        mxy_gal + mxy_PSF)

                    # compare with images that are expected
                    expected_gaussian = galsim.Gaussian(flux=fake_gal_flux,
                                                        sigma=tps * tot_sigma)
                    expected_gaussian = expected_gaussian.shear(e1=tot_e1,
                                                                e2=tot_e2)
                    expected_image = galsim.ImageD(sim_image.array.shape[0],
                                                   sim_image.array.shape[1])
                    expected_gaussian.drawImage(expected_image,
                                                scale=tps,
                                                method='no_pixel')
                    printval(expected_image, sim_image)
                    np.testing.assert_array_almost_equal(
                        sim_image.array,
                        expected_image.array,
                        decimal=3,
                        err_msg=
                        "Error in comparison of ideal Gaussian RealGalaxy calculations"
                    )
Esempio n. 12
0
def test_crg_noise_draw_transform_commutativity():
    """Test commutativity of ChromaticRealGalaxy correlated noise under operations of drawImage and
    applying transformations.
    """
    LSST_i = galsim.Bandpass(os.path.join(bppath, "LSST_r.dat"), 'nm')
    f606w_cat = galsim.RealGalaxyCatalog('AEGIS_F606w_catalog.fits',
                                         dir=image_dir)
    f814w_cat = galsim.RealGalaxyCatalog('AEGIS_F814w_catalog.fits',
                                         dir=image_dir)

    psf = galsim.Gaussian(fwhm=0.6)
    crg = galsim.ChromaticRealGalaxy([f606w_cat, f814w_cat],
                                     id=14886,
                                     maxk=psf.maxK())

    factor = 1.5
    g1 = g2 = 0.1
    mu = 1.2
    theta = 45 * galsim.degrees
    jac = [1.1, 0.1, -0.1, 1.2]

    orig = galsim.Convolve(crg, psf)
    orig.drawImage(LSST_i)

    draw_transform_img = galsim.ImageD(16, 16, scale=0.2)
    transform_draw_img = draw_transform_img.copy()

    multiplied = orig * factor
    multiplied.drawImage(LSST_i)  # needed to populate noise property
    (orig.noise * factor**2).drawImage(image=draw_transform_img)
    multiplied.noise.drawImage(image=transform_draw_img)
    np.testing.assert_array_almost_equal(draw_transform_img.array,
                                         transform_draw_img.array)

    divided = orig / factor
    divided.drawImage(LSST_i)
    (orig.noise / factor**2).drawImage(image=draw_transform_img)
    divided.noise.drawImage(image=transform_draw_img)
    np.testing.assert_array_almost_equal(draw_transform_img.array,
                                         transform_draw_img.array)

    expanded = orig.expand(factor)
    expanded.drawImage(LSST_i)
    orig.noise.expand(factor).drawImage(image=draw_transform_img)
    expanded.noise.drawImage(image=transform_draw_img)
    np.testing.assert_array_almost_equal(draw_transform_img.array,
                                         transform_draw_img.array)

    dilated = orig.dilate(factor)
    dilated.drawImage(LSST_i)
    orig.noise.dilate(factor).drawImage(image=draw_transform_img)
    dilated.noise.drawImage(image=transform_draw_img)
    np.testing.assert_array_almost_equal(draw_transform_img.array,
                                         transform_draw_img.array)

    magnified = orig.magnify(mu)
    magnified.drawImage(LSST_i)
    orig.noise.magnify(mu).drawImage(image=draw_transform_img)
    magnified.noise.drawImage(image=transform_draw_img)
    np.testing.assert_array_almost_equal(draw_transform_img.array,
                                         transform_draw_img.array)

    lensed = orig.lens(g1, g2, mu)
    lensed.drawImage(LSST_i)
    orig.noise.lens(g1, g2, mu).drawImage(image=draw_transform_img)
    lensed.noise.drawImage(image=transform_draw_img)
    np.testing.assert_array_almost_equal(draw_transform_img.array,
                                         transform_draw_img.array)

    rotated = orig.rotate(theta)
    rotated.drawImage(LSST_i)
    orig.noise.rotate(theta).drawImage(image=draw_transform_img)
    rotated.noise.drawImage(image=transform_draw_img)
    np.testing.assert_array_almost_equal(draw_transform_img.array,
                                         transform_draw_img.array)

    sheared = orig.shear(g1=g1, g2=g2)
    sheared.drawImage(LSST_i)
    orig.noise.shear(g1=g1, g2=g2).drawImage(image=draw_transform_img)
    sheared.noise.drawImage(image=transform_draw_img)
    np.testing.assert_array_almost_equal(draw_transform_img.array,
                                         transform_draw_img.array)

    transformed = orig.transform(*jac)
    transformed.drawImage(LSST_i)
    orig.noise.transform(*jac).drawImage(image=draw_transform_img)
    transformed.noise.drawImage(image=transform_draw_img)
    np.testing.assert_array_almost_equal(draw_transform_img.array,
                                         transform_draw_img.array)
Esempio n. 13
0
def test_area_norm():
    """Check that area_norm works as expected"""
    f606w_cat = galsim.RealGalaxyCatalog('AEGIS_F606w_catalog.fits',
                                         dir=image_dir)
    f814w_cat = galsim.RealGalaxyCatalog('AEGIS_F814w_catalog.fits',
                                         dir=image_dir)

    psf = galsim.Gaussian(fwhm=0.6)

    rng = galsim.BaseDeviate(5772)
    crg1 = galsim.ChromaticRealGalaxy([f606w_cat, f814w_cat],
                                      random=True,
                                      rng=rng.duplicate())
    crg2 = galsim.ChromaticRealGalaxy([f606w_cat, f814w_cat],
                                      random=True,
                                      rng=rng.duplicate(),
                                      area_norm=galsim.real.HST_area)
    assert crg1 != crg2
    LSST_i = galsim.Bandpass(os.path.join(bppath, "LSST_r.dat"), 'nm')
    obj1 = galsim.Convolve(crg1, psf)
    obj2 = galsim.Convolve(crg2, psf)
    im1 = obj1.drawImage(LSST_i, exptime=1, area=1)
    im2 = obj2.drawImage(LSST_i, exptime=1, area=galsim.real.HST_area)
    printval(im1, im2)
    np.testing.assert_array_almost_equal(im1.array, im2.array)
    np.testing.assert_almost_equal(
        obj1.noise.getVariance(),
        obj2.noise.getVariance() * galsim.real.HST_area**2)

    # area_norm is equivalant to an overall scaling
    crg3 = galsim.ChromaticRealGalaxy([f606w_cat, f814w_cat],
                                      random=True,
                                      rng=rng.duplicate())
    crg3 /= galsim.real.HST_area
    obj3 = galsim.Convolve(crg3, psf)
    im3 = obj3.drawImage(LSST_i, exptime=1, area=galsim.real.HST_area)
    np.testing.assert_array_almost_equal(im3.array, im2.array)
    np.testing.assert_almost_equal(obj3.noise.getVariance(),
                                   obj2.noise.getVariance())

    rg1 = galsim.RealGalaxy(f606w_cat, index=1)
    rg2 = galsim.RealGalaxy(f606w_cat, index=1, area_norm=galsim.real.HST_area)
    assert rg1 != rg2
    obj1 = galsim.Convolve(rg1, psf)
    obj2 = galsim.Convolve(rg2, psf)
    im1 = obj1.drawImage()
    im2 = obj2.drawImage(exptime=1, area=galsim.real.HST_area)
    printval(im1, im2)
    np.testing.assert_array_almost_equal(im1.array, im2.array)
    np.testing.assert_almost_equal(
        obj1.noise.getVariance(),
        obj2.noise.getVariance() * galsim.real.HST_area**2)

    # area_norm is equivalant to an overall scaling
    rg3 = galsim.RealGalaxy(f606w_cat, index=1)
    rg3 /= galsim.real.HST_area
    obj3 = galsim.Convolve(rg3, psf)
    im3 = obj3.drawImage(exptime=1, area=galsim.real.HST_area)
    np.testing.assert_array_almost_equal(im3.array, im2.array)
    np.testing.assert_almost_equal(obj3.noise.getVariance(),
                                   obj2.noise.getVariance())
Esempio n. 14
0
def test_crg_roundtrip_larger_target_psf():
    """Test that drawing a chromatic galaxy with a color gradient directly using an LSST-size PSF
    is equivalent to first drawing the galaxy to HST-like images, and then using ChromaticRealGalaxy
    to produce an LSST-like image.
    """
    # load some spectra
    bulge_SED = (galsim.SED(
        os.path.join(sedpath, 'CWW_E_ext.sed'),
        wave_type='ang',
        flux_type='flambda').thin(rel_err=1e-3).withFluxDensity(
            target_flux_density=0.3, wavelength=500.0))

    disk_SED = (galsim.SED(
        os.path.join(sedpath, 'CWW_Sbc_ext.sed'),
        wave_type='ang',
        flux_type='flambda').thin(rel_err=1e-3).withFluxDensity(
            target_flux_density=0.3, wavelength=500.0))

    bulge = galsim.Sersic(n=4, half_light_radius=0.6) * bulge_SED
    disk = galsim.Sersic(n=1, half_light_radius=0.4) * disk_SED
    # Decenter components a bit to make the test more complicated
    disk = disk.shift(0.05, 0.1)
    gal = (bulge + disk).shear(g1=0.3, g2=0.1)

    # Much faster to just use some achromatic HST-like PSFs.  We'll make them slightly different in
    # each band though.
    f606w_PSF = galsim.ChromaticObject(galsim.Gaussian(half_light_radius=0.05))
    f814w_PSF = galsim.ChromaticObject(galsim.Gaussian(half_light_radius=0.07))
    LSSTPSF = galsim.ChromaticAtmosphere(galsim.Kolmogorov(fwhm=0.7),
                                         600.0,
                                         zenith_angle=0.0 * galsim.degrees)

    f606w = galsim.Bandpass(os.path.join(bppath, "ACS_wfc_F606W.dat"),
                            'nm').truncate()
    f814w = galsim.Bandpass(os.path.join(bppath, "ACS_wfc_F814W.dat"), 'nm')
    LSST_i = galsim.Bandpass(os.path.join(bppath, "LSST_r.dat"), 'nm')

    truth_image = galsim.Convolve(LSSTPSF, gal).drawImage(LSST_i,
                                                          nx=24,
                                                          ny=24,
                                                          scale=0.2)
    f606w_image = galsim.Convolve(f606w_PSF, gal).drawImage(f606w,
                                                            nx=192,
                                                            ny=192,
                                                            scale=0.03)
    f814w_image = galsim.Convolve(f814w_PSF, gal).drawImage(f814w,
                                                            nx=192,
                                                            ny=192,
                                                            scale=0.03)

    crg = galsim.ChromaticRealGalaxy.makeFromImages(
        images=[f606w_image, f814w_image],
        bands=[f606w, f814w],
        PSFs=[f606w_PSF, f814w_PSF],
        xis=[galsim.UncorrelatedNoise(1e-16)] * 2,
        SEDs=[bulge_SED, disk_SED])

    test_image = galsim.Convolve(crg, LSSTPSF).drawImage(LSST_i,
                                                         nx=24,
                                                         ny=24,
                                                         scale=0.2)

    truth_mom = galsim.hsm.FindAdaptiveMom(truth_image)
    test_mom = galsim.hsm.FindAdaptiveMom(test_image)

    np.testing.assert_allclose(test_mom.moments_amp,
                               truth_mom.moments_amp,
                               rtol=1e-3,
                               atol=0)
    np.testing.assert_allclose(test_mom.moments_centroid.x,
                               truth_mom.moments_centroid.x,
                               rtol=0.,
                               atol=1e-2)
    np.testing.assert_allclose(test_mom.moments_centroid.y,
                               truth_mom.moments_centroid.y,
                               rtol=0.,
                               atol=1e-2)
    np.testing.assert_allclose(test_mom.moments_sigma,
                               truth_mom.moments_sigma,
                               rtol=1e-3,
                               atol=0)
    np.testing.assert_allclose(test_mom.observed_shape.g1,
                               truth_mom.observed_shape.g1,
                               rtol=0,
                               atol=1e-4)
    np.testing.assert_allclose(test_mom.observed_shape.g2,
                               truth_mom.observed_shape.g2,
                               rtol=0,
                               atol=1e-4)
Esempio n. 15
0
def test_dcr():
    """Test the dcr surface op
    """
    # This tests that implementing DCR with the surface op is equivalent to using
    # ChromaticAtmosphere.
    # We use fairly extreme choices for the parameters to make the comparison easier, so
    # we can still get good discrimination of any errors with only 10^6 photons.
    zenith_angle = 45 * galsim.degrees  # Larger angle has larger DCR.
    parallactic_angle = 129 * galsim.degrees  # Something random, not near 0 or 180
    pixel_scale = 0.03  # Small pixel scale means shifts are many pixels, rather than a fraction.
    alpha = -1.2  # The normal alpha is -0.2, so this is exaggerates the effect.

    bandpass = galsim.Bandpass('LSST_r.dat', 'nm')
    base_wavelength = bandpass.effective_wavelength
    base_wavelength += 500  # This exaggerates the effects fairly substantially.

    sed = galsim.SED('CWW_E_ext.sed', wave_type='ang', flux_type='flambda')

    flux = 1.e6
    base_PSF = galsim.Kolmogorov(fwhm=0.3)

    # Use ChromaticAtmosphere
    im1 = galsim.ImageD(50, 50, scale=pixel_scale)
    star = galsim.DeltaFunction() * sed
    star = star.withFlux(flux, bandpass=bandpass)
    chrom_PSF = galsim.ChromaticAtmosphere(base_PSF,
                                           base_wavelength=base_wavelength,
                                           zenith_angle=zenith_angle,
                                           parallactic_angle=parallactic_angle,
                                           alpha=alpha)
    chrom = galsim.Convolve(star, chrom_PSF)
    chrom.drawImage(bandpass, image=im1)

    # Use PhotonDCR
    im2 = galsim.ImageD(50, 50, scale=pixel_scale)
    dcr = galsim.PhotonDCR(base_wavelength=base_wavelength,
                           zenith_angle=zenith_angle,
                           parallactic_angle=parallactic_angle,
                           alpha=alpha)
    achrom = base_PSF.withFlux(flux)
    rng = galsim.BaseDeviate(31415)
    wave_sampler = galsim.WavelengthSampler(sed, bandpass, rng)
    photon_ops = [wave_sampler, dcr]
    achrom.drawImage(image=im2, method='phot', rng=rng, photon_ops=photon_ops)

    do_pickle(dcr)

    im1 /= flux  # Divide by flux, so comparison is on a relative basis.
    im2 /= flux
    printval(im2, im1, show=False)
    np.testing.assert_almost_equal(im2.array, im1.array, decimal=4,
                                   err_msg="PhotonDCR didn't match ChromaticAtmosphere")

    # Repeat with thinned bandpass and SED to check that thin still works well.
    im3 = galsim.ImageD(50, 50, scale=pixel_scale)
    thin = 0.1  # Even higher also works.  But this is probably enough.
    thin_bandpass = bandpass.thin(thin)
    thin_sed = sed.thin(thin)
    print('len bp = %d => %d'%(len(bandpass.wave_list), len(thin_bandpass.wave_list)))
    print('len sed = %d => %d'%(len(sed.wave_list), len(thin_sed.wave_list)))
    wave_sampler = galsim.WavelengthSampler(thin_sed, thin_bandpass, rng)
    achrom.drawImage(image=im3, method='phot', rng=rng, photon_ops=photon_ops)

    im3 /= flux
    printval(im3, im1, show=False)
    np.testing.assert_almost_equal(im3.array, im1.array, decimal=4,
                                   err_msg="thinning factor %f led to 1.e-4 level inaccuracy"%thin)

    # Check scale_unit
    im4 = galsim.ImageD(50, 50, scale=pixel_scale/60)
    dcr = galsim.PhotonDCR(base_wavelength=base_wavelength,
                           zenith_angle=zenith_angle,
                           parallactic_angle=parallactic_angle,
                           scale_unit='arcmin',
                           alpha=alpha)
    photon_ops = [wave_sampler, dcr]
    achrom.dilate(1./60).drawImage(image=im4, method='phot', rng=rng, photon_ops=photon_ops)
    im4 /= flux
    printval(im4, im1, show=False)
    np.testing.assert_almost_equal(im4.array, im1.array, decimal=4,
                                   err_msg="PhotonDCR with scale_unit=arcmin, didn't match")

    # Check some other valid options
    # alpha = 0 means don't do any size scaling.
    # obj_coord, HA and latitude are another option for setting the angles
    # pressure, temp, and water pressure are settable.
    # Also use a non-trivial WCS.
    wcs = galsim.FitsWCS('des_data/DECam_00154912_12_header.fits')
    image = galsim.Image(50, 50, wcs=wcs)
    bandpass = galsim.Bandpass('LSST_r.dat', wave_type='nm').thin(0.1)
    base_wavelength = bandpass.effective_wavelength
    lsst_lat = galsim.Angle.from_dms('-30:14:23.76')
    lsst_long = galsim.Angle.from_dms('-70:44:34.67')
    local_sidereal_time = 3.14 * galsim.hours  # Not pi. This is the time for this observation.

    im5 = galsim.ImageD(50, 50, wcs=wcs)
    obj_coord = wcs.toWorld(im5.true_center)
    base_PSF = galsim.Kolmogorov(fwhm=0.9)
    achrom = base_PSF.withFlux(flux)
    dcr = galsim.PhotonDCR(base_wavelength=bandpass.effective_wavelength,
                           obj_coord=obj_coord,
                           HA=local_sidereal_time-obj_coord.ra,
                           latitude=lsst_lat,
                           pressure=72,         # default is 69.328
                           temperature=290,     # default is 293.15
                           H2O_pressure=0.9)    # default is 1.067
                           #alpha=0)            # default is 0, so don't need to set it.
    photon_ops = [wave_sampler, dcr]
    achrom.drawImage(image=im5, method='phot', rng=rng, photon_ops=photon_ops)

    do_pickle(dcr)

    im6 = galsim.ImageD(50, 50, wcs=wcs)
    star = galsim.DeltaFunction() * sed
    star = star.withFlux(flux, bandpass=bandpass)
    chrom_PSF = galsim.ChromaticAtmosphere(base_PSF,
                                           base_wavelength=bandpass.effective_wavelength,
                                           obj_coord=obj_coord,
                                           HA=local_sidereal_time-obj_coord.ra,
                                           latitude=lsst_lat,
                                           pressure=72,
                                           temperature=290,
                                           H2O_pressure=0.9,
                                           alpha=0)
    chrom = galsim.Convolve(star, chrom_PSF)
    chrom.drawImage(bandpass, image=im6)

    im5 /= flux  # Divide by flux, so comparison is on a relative basis.
    im6 /= flux
    printval(im5, im6, show=False)
    np.testing.assert_almost_equal(im5.array, im6.array, decimal=3,
                                   err_msg="PhotonDCR with alpha=0 didn't match")

    # Also check invalid parameters
    zenith_coord = galsim.CelestialCoord(13.54 * galsim.hours, lsst_lat)
    assert_raises(TypeError, galsim.PhotonDCR,
                  zenith_angle=zenith_angle,
                  parallactic_angle=parallactic_angle)  # base_wavelength is required
    assert_raises(TypeError, galsim.PhotonDCR,
                  base_wavelength=500,
                  parallactic_angle=parallactic_angle)  # zenith_angle (somehow) is required
    assert_raises(TypeError, galsim.PhotonDCR, 500,
                  zenith_angle=34.4,
                  parallactic_angle=parallactic_angle)  # zenith_angle must be Angle
    assert_raises(TypeError, galsim.PhotonDCR, 500,
                  zenith_angle=zenith_angle,
                  parallactic_angle=34.5)               # parallactic_angle must be Angle
    assert_raises(TypeError, galsim.PhotonDCR, 500,
                  obj_coord=obj_coord,
                  latitude=lsst_lat)                    # Missing HA
    assert_raises(TypeError, galsim.PhotonDCR, 500,
                  obj_coord=obj_coord,
                  HA=local_sidereal_time-obj_coord.ra)  # Missing latitude
    assert_raises(TypeError, galsim.PhotonDCR, 500,
                  obj_coord=obj_coord)                  # Need either zenith_coord, or (HA,lat)
    assert_raises(TypeError, galsim.PhotonDCR, 500,
                  obj_coord=obj_coord,
                  zenith_coord=zenith_coord,
                  HA=local_sidereal_time-obj_coord.ra)  # Can't have both HA and zenith_coord
    assert_raises(TypeError, galsim.PhotonDCR, 500,
                  obj_coord=obj_coord,
                  zenith_coord=zenith_coord,
                  latitude=lsst_lat)                    # Can't have both lat and zenith_coord
    assert_raises(TypeError, galsim.PhotonDCR, 500,
                  zenith_angle=zenith_angle,
                  parallactic_angle=parallactic_angle,
                  H20_pressure=1.)                      # invalid (misspelled)
    assert_raises(ValueError, galsim.PhotonDCR, 500,
                  zenith_angle=zenith_angle,
                  parallactic_angle=parallactic_angle,
                  scale_unit='inches')                  # invalid scale_unit

    # Invalid to use dcr without some way of setting wavelengths.
    assert_raises(galsim.GalSimError, achrom.drawImage, im2, method='phot', photon_ops=[dcr])
Esempio n. 16
0
def test_CRG_noise(args):
    """Test noise propagation in ChromaticRealGalaxy
    """
    t0 = time.time()

    print("Constructing chromatic PSFs")
    in_PSF = galsim.ChromaticAiry(lam=700., diam=2.4)
    out_PSF = galsim.ChromaticAiry(lam=700., diam=1.2)

    print("Constructing filters and SEDs")
    waves = np.arange(550.0, 900.1, 10.0)
    visband = galsim.Bandpass(galsim.LookupTable(waves,
                                                 np.ones_like(waves),
                                                 interpolant='linear'),
                              wave_type='nm')
    split_points = np.linspace(550.0, 900.0, args.Nim + 1, endpoint=True)
    bands = [
        visband.truncate(blue_limit=blim, red_limit=rlim)
        for blim, rlim in zip(split_points[:-1], split_points[1:])
    ]

    maxk = max([
        out_PSF.evaluateAtWavelength(waves[0]).maxK(),
        out_PSF.evaluateAtWavelength(waves[-1]).maxK()
    ])

    SEDs = [
        galsim.SED(galsim.LookupTable(waves, waves**i, interpolant='linear'),
                   flux_type='fphotons',
                   wave_type='nm').withFlux(1.0, visband)
        for i in range(args.NSED)
    ]

    print("Constructing input noise correlation functions")
    rng = galsim.BaseDeviate(args.seed)
    in_xis = [
        galsim.getCOSMOSNoise(cosmos_scale=args.in_scale,
                              rng=rng).dilate(1 + i * 0.05).rotate(
                                  5 * i * galsim.degrees)
        for i in range(args.Nim)
    ]

    print("Creating noise images")
    img_sets = []
    for i in range(args.Ntrial):
        imgs = []
        for j, xi in enumerate(in_xis):
            img = galsim.Image(args.in_Nx, args.in_Nx, scale=args.in_scale)
            img.addNoise(xi)
            imgs.append(img)
        img_sets.append(imgs)

    print("Constructing `ChromaticRealGalaxy`s")
    crgs = []
    with ProgressBar(len(img_sets)) as bar:
        for imgs in img_sets:
            crgs.append(
                galsim.ChromaticRealGalaxy.makeFromImages(imgs,
                                                          bands,
                                                          in_PSF,
                                                          in_xis,
                                                          SEDs=SEDs,
                                                          maxk=maxk))
            bar.update()

    print("Convolving by output PSF")
    objs = [galsim.Convolve(crg, out_PSF) for crg in crgs]

    print("Drawing through output filter")
    out_imgs = [
        obj.drawImage(visband,
                      nx=args.out_Nx,
                      ny=args.out_Nx,
                      scale=args.out_scale,
                      iimult=args.iimult) for obj in objs
    ]

    noise = objs[0].noise

    print("Measuring images' correlation functions")
    xi_obs = galsim.correlatednoise.CorrelatedNoise(out_imgs[0])
    for img in out_imgs[1:]:
        xi_obs += galsim.correlatednoise.CorrelatedNoise(img)
    xi_obs /= args.Ntrial
    xi_obs_img = galsim.Image(args.out_Nx, args.out_Nx, scale=args.out_scale)
    xi_obs.drawImage(xi_obs_img)

    print("Observed image variance: ", xi_obs.getVariance())
    print("Predicted image variance: ", noise.getVariance())
    print("Predicted/Observed variance:",
          noise.getVariance() / xi_obs.getVariance())

    print("Took {} seconds".format(time.time() - t0))

    if args.plot:
        import matplotlib.pyplot as plt
        out_array = (np.arange(args.out_Nx) - args.out_Nx / 2) * args.out_scale
        out_extent = [
            -args.out_Nx * args.out_scale / 2,
            args.out_Nx * args.out_scale / 2,
            -args.out_Nx * args.out_scale / 2, args.out_Nx * args.out_scale / 2
        ]

        fig = plt.figure(figsize=(5, 5))

        # Sample image
        ax = fig.add_subplot(111)
        ax.imshow(out_imgs[0].array, extent=out_extent)
        ax.set_title("sample output image")
        ax.set_xlabel("x")
        ax.set_ylabel("y")
        # ax.colorbar()
        fig.show()

        # 2D correlation functions
        fig = plt.figure(figsize=(10, 10))
        ax1 = fig.add_subplot(221)
        noise_img = galsim.Image(args.out_Nx,
                                 args.out_Nx,
                                 scale=args.out_scale)
        noise.drawImage(noise_img)
        ax1.imshow(np.log10(np.abs(noise_img.array)), extent=out_extent)
        ax1.set_title("predicted covariance function")
        ax1.set_xlabel(r"$\Delta x$")
        ax1.set_ylabel(r"$\Delta y$")
        ax2 = fig.add_subplot(222)
        ax2.imshow(np.log10(np.abs(xi_obs_img.array)), extent=out_extent)
        ax2.set_title("observed covariance function")
        ax2.set_xlabel(r"$\Delta x$")
        ax2.set_ylabel(r"$\Delta y$")

        # 1D slide through correlation functions
        ax3 = fig.add_subplot(223)
        ax3.plot(out_array,
                 noise_img.array[args.out_Nx / 2, :],
                 label="prediction",
                 color='red')
        ax3.plot(out_array,
                 xi_obs_img.array[args.out_Nx / 2, :],
                 label="observation",
                 color='blue')
        ax3.legend(loc='best')
        ax3.set_xlabel(r"$\Delta x$")
        ax3.set_ylabel(r"$\xi$")

        ax4 = fig.add_subplot(224)
        ax4.plot(out_array,
                 noise_img.array[args.out_Nx / 2, :],
                 label="prediction",
                 color='red')
        ax4.plot(out_array,
                 xi_obs_img.array[args.out_Nx / 2, :],
                 label="observation",
                 color='blue')
        ax4.plot(out_array,
                 -noise_img.array[args.out_Nx / 2, :],
                 ls=':',
                 color='red')
        ax4.plot(out_array,
                 -xi_obs_img.array[args.out_Nx / 2, :],
                 ls=':',
                 color='blue')
        ax4.legend(loc='best')
        ax4.set_yscale('log')
        ax4.set_xlabel(r"$\Delta x$")
        ax4.set_ylabel(r"$\xi$")

        plt.tight_layout()
        plt.show()
Esempio n. 17
0
def main():

    # Using very low accuracy GSParams here for speed
    gsparams = galsim.GSParams(
        minimum_fft_size=256,
        folding_threshold=0.1,
        kvalue_accuracy=1e-3,
        stepk_minimum_hlr=2.5,
    )

    # Note - we actually use an interpolated image instead; just putting this in
    # so you can run the code without needing that file
    psf_prof = galsim.OpticalPSF(
        lam=725,  # nm
        diam=1.2,  # m
        defocus=0,
        obscuration=0.33,
        nstruts=3,
        gsparams=gsparams)

    pixel_scale = 0.02
    convolved_image = galsim.Image(256, 256, scale=pixel_scale)
    convolved_image.setCenter(0, 0)

    # Do this once here to get the right kimage size/shape and wrap size.
    gal_prof = galsim.Sersic(n=4, half_light_radius=0.3, gsparams=gsparams)
    convolved_prof = galsim.Convolve(gal_prof, psf_prof, gsparams=gsparams)

    psf_kimage, wrap_size = convolved_prof.drawFFT_makeKImage(convolved_image)

    # Draw the PSF onto the kimage.
    psf_prof._drawKImage(psf_kimage)

    # Use the same size/shape for the galaxy part.
    gal_kimage = psf_kimage.copy()
    convolved_image2 = convolved_image.copy()

    for _i in range(1000):

        gal_prof = galsim.Sersic(n=4, half_light_radius=0.3, gsparams=gsparams)

        # Account for the fact that this is an even sized image.  The drawFFT function will
        # draw the profile centered on the nominal (integer) center pixel, which (since this is
        # an even-sized image) actuall +0.5,+0.5 from the true center.
        gal_prof_cen = gal_prof._shift(
            galsim.PositionD(-0.5 * pixel_scale, -0.5 * pixel_scale))

        # Draw just the galaxy profile in k-space
        gal_prof_cen._drawKImage(gal_kimage)

        # Multiply by the (constant) PSF kimage
        gal_kimage.array[:, :] *= psf_kimage.array

        # Finish the draw process
        gal_prof.drawFFT_finish(convolved_image,
                                gal_kimage,
                                wrap_size,
                                add_to_image=False)

        if False:
            # Check that we get the same thing as the normal draw procedure
            convolved_prof = galsim.Convolve(gal_prof,
                                             psf_prof,
                                             gsparams=gsparams)
            # Using no pixel method here since we plan to use a PSF profile
            # which already includes the pixel response
            convolved_prof.drawImage(convolved_image2, method='no_pixel')
            max_diff = np.max(
                np.abs(convolved_image.array - convolved_image2.array))
            print('max diff = ', max_diff)
            assert (max_diff < 1.e-8)
Esempio n. 18
0
def test_spergel():
    """Test the generation of a specific Spergel profile against a known result.
    """
    test_spergel_nu = [-0.85, -0.5, 0.0, 0.85, 4.0]
    mathica_enclosed_fluxes = [
        3.06256e-2, 9.99995e-6, 6.06443e-10, 2.94117e-11, 6.25011e-12
    ]
    mathica_enclosing_radii = [
        2.3973e-17, 1.00001e-5, 1.69047e-3, 5.83138e-3, 1.26492e-2
    ]

    for nu, enclosed_flux, enclosing_radius in zip(test_spergel_nu,
                                                   mathica_enclosed_fluxes,
                                                   mathica_enclosing_radii):
        filename = "spergel_nu{0:.2f}.fits".format(nu)
        savedImg = galsim.fits.read(os.path.join(imgdir, filename))
        savedImg.setCenter(0, 0)
        dx = 0.2
        myImg = galsim.ImageF(savedImg.bounds, scale=dx)
        myImg.setCenter(0, 0)

        spergel = galsim.Spergel(nu=nu, half_light_radius=1.0)
        # Reference images were made with old centering,
        # which is equivalent to use_true_center=False.
        myImg = spergel.drawImage(myImg,
                                  scale=dx,
                                  method="sb",
                                  use_true_center=False)

        np.testing.assert_array_almost_equal(
            myImg.array,
            savedImg.array,
            5,
            err_msg="Using GSObject Spergel disagrees with expected result")

        gsp = galsim.GSParams(xvalue_accuracy=1.e-8, kvalue_accuracy=1.e-8)
        spergel2 = galsim.Spergel(nu=nu, half_light_radius=1.0, gsparams=gsp)
        assert spergel2 != spergel
        assert spergel2 == spergel.withGSParams(gsp)

        # nu < 0 has inf for xValue(0,0), so the x tests fail for them.
        check_basic(spergel, "Spergel with nu=%f" % nu, do_x=(nu > 0))

        # Only nu >= -0.3 give reasonably sized FFTs,
        # and small nu method='phot' is super slow.
        if nu >= -0.3:
            test_im = galsim.Image(16, 16, scale=dx)
            do_kvalue(spergel, test_im, "Spergel(nu={0:1}) ".format(nu))

            # Test photon shooting.
            # Convolve with a small gaussian to smooth out the central peak.
            spergel2 = galsim.Convolve(spergel, galsim.Gaussian(sigma=0.3))
            do_shoot(spergel2, myImg, "Spergel")

        # Test integrated flux routines against Mathematica
        spergel = galsim.Spergel(nu=nu, scale_radius=1.0)
        np.testing.assert_almost_equal(
            spergel.calculateFluxRadius(1.e-5) / enclosing_radius,
            1.0,
            4,
            err_msg="Calculated incorrect Spergel(nu={0}) flux-enclosing-radius."
            .format(nu))
        np.testing.assert_almost_equal(
            spergel.calculateIntegratedFlux(1.e-5) / enclosed_flux,
            1.0,
            4,
            err_msg="Calculated incorrect Spergel(nu={0}) enclosed flux.".
            format(nu))

    # Use non-unity values.
    spergel = galsim.Spergel(nu=0.37, flux=1.7, half_light_radius=2.3)

    check_basic(spergel, "Spergel")

    # Check picklability
    do_pickle(spergel, lambda x: x.drawImage(method='no_pixel'))
    do_pickle(spergel)
    do_pickle(galsim.Spergel(0, 1))

    # Should raise an exception if both scale_radius and half_light_radius are provided.
    assert_raises(TypeError,
                  galsim.Spergel,
                  nu=0,
                  scale_radius=3,
                  half_light_radius=1)
    assert_raises(TypeError, galsim.Spergel, nu=0)
    assert_raises(TypeError, galsim.Spergel, scale_radius=3)

    # Allowed range = [-0.85, 4.0]
    assert_raises(ValueError, galsim.Spergel, nu=-0.9)
    assert_raises(ValueError, galsim.Spergel, nu=4.1)
Esempio n. 19
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 + 1,
                                    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)
Esempio n. 20
0
def main(argv):
    """
    Make a fits image cube using real COSMOS galaxies from a catalog describing the training
    sample.

      - The number of images in the cube matches the number of rows in the catalog.
      - Each image size is computed automatically by GalSim based on the Nyquist size.
      - Both galaxies and stars.
      - PSF is a double Gaussian, the same for each galaxy.
      - Galaxies are randomly rotated to remove the imprint of any lensing shears in the COSMOS
        data.
      - The same shear is applied to each galaxy.
      - Noise is Poisson using a nominal sky value of 1.e6 ADU/arcsec^2,
        the noise in the original COSMOS data.
    """
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo6")

    # Define some parameters we'll use below.

    cat_file_name = 'real_galaxy_catalog_example.fits'
    dir = 'data'
    # Make output directory if not already present.
    if not os.path.isdir('output'):
        os.mkdir('output')
    cube_file_name = os.path.join('output', 'cube_real.fits')
    psf_file_name = os.path.join('output', 'psf_real.fits')

    random_seed = 1512413
    sky_level = 1.e6  # ADU / arcsec^2
    pixel_scale = 0.16  # arcsec
    gal_flux = 1.e5  # arbitrary choice, makes nice (not too) noisy images
    gal_g1 = -0.027  #
    gal_g2 = 0.031  #
    gal_mu = 1.082  # mu = ( (1-kappa)^2 - g1^2 - g2^2 )^-1
    psf_inner_fwhm = 0.6  # arcsec
    psf_outer_fwhm = 2.3  # arcsec
    psf_inner_fraction = 0.8  # fraction of total PSF flux in the inner Gaussian
    psf_outer_fraction = 0.2  # fraction of total PSF flux in the inner Gaussian
    ngal = 100

    logger.info('Starting demo script 6 using:')
    logger.info('    - real galaxies from catalog %r', cat_file_name)
    logger.info('    - double Gaussian PSF')
    logger.info('    - pixel scale = %.2f', pixel_scale)
    logger.info('    - Applied gravitational shear = (%.3f,%.3f)', gal_g1,
                gal_g2)
    logger.info('    - Poisson noise (sky level = %.1e).', sky_level)

    # Read in galaxy catalog
    # Note: dir is the directory both for the catalog itself and also the directory prefix
    # for the image files listed in the catalog.
    # If the images are in a different directory, you may also specify image_dir, which gives
    # the relative path from dir to wherever the images are located.
    real_galaxy_catalog = galsim.RealGalaxyCatalog(cat_file_name, dir=dir)
    logger.info('Read in %d real galaxies from catalog',
                real_galaxy_catalog.nobjects)

    # Make the double Gaussian PSF
    psf1 = galsim.Gaussian(fwhm=psf_inner_fwhm, flux=psf_inner_fraction)
    psf2 = galsim.Gaussian(fwhm=psf_outer_fwhm, flux=psf_outer_fraction)
    psf = psf1 + psf2
    # Draw the PSF with no noise.
    psf_image = psf.drawImage(scale=pixel_scale)
    # write to file
    psf_image.write(psf_file_name)
    logger.info('Created PSF and wrote to file %r', psf_file_name)

    # Build the images
    all_images = []
    for k in range(ngal):
        logger.debug('Start work on image %d', k)
        t1 = time.time()

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

        gal = galsim.RealGalaxy(real_galaxy_catalog, index=k)
        logger.debug('   Read in training sample galaxy and PSF from file')
        t2 = time.time()

        # Set the flux
        gal = gal.withFlux(gal_flux)

        # Rotate by a random angle
        theta = 2. * math.pi * rng() * galsim.radians
        gal = gal.rotate(theta)

        # Apply the desired shear
        gal = gal.shear(g1=gal_g1, g2=gal_g2)

        # Also apply a magnification mu = ( (1-kappa)^2 - |gamma|^2 )^-1
        # This conserves surface brightness, so it scales both the area and flux.
        gal = gal.magnify(gal_mu)

        # Make the combined profile
        final = galsim.Convolve([psf, gal])

        # Offset by up to 1/2 pixel in each direction
        # We had previously (in demo4 and demo5) used shift(dx,dy) as a way to shift the center of
        # the image.  Since that is applied to the galaxy, the units are arcsec (since the galaxy
        # profile itself doesn't know about the pixel scale).  Here, the offset applies to the
        # drawn image, which does know about the pixel scale, so the units of offset are pixels,
        # not arcsec.  Here, we apply an offset of up to half a pixel in each direction.
        dx = rng() - 0.5
        dy = rng() - 0.5

        # Draw the profile
        if k == 0:
            # Note that the offset argument may be a galsim.PositionD object or a tuple (dx,dy).
            im = final.drawImage(scale=pixel_scale, offset=(dx, dy))
            xsize, ysize = im.array.shape
        else:
            im = galsim.ImageF(xsize, ysize)
            final.drawImage(im, scale=pixel_scale, offset=(dx, dy))

        logger.debug('   Drew image')
        t3 = time.time()

        # Add a constant background level
        background = sky_level * pixel_scale**2
        im += background

        # Add Poisson noise.  This time, we don't give a sky_level, since we have already
        # added it to the image, so we don't want any more added.  The sky_level parameter
        # really defines how much _extra_ sky should be added above what is already in the image.
        im.addNoise(galsim.PoissonNoise(rng))

        logger.debug('   Added Poisson noise')
        t4 = time.time()

        # Store that into the list of all images
        all_images += [im]
        t5 = time.time()

        logger.debug('   Times: %f, %f, %f, %f', t2 - t1, t3 - t2, t4 - t3,
                     t5 - t4)
        logger.info('Image %d: size = %d x %d, total time = %f sec', k, xsize,
                    ysize, t5 - t1)

    logger.info('Done making images of galaxies')

    # Now write the image to disk.
    # We write the images to a fits data cube.
    galsim.fits.writeCube(all_images, cube_file_name)
    logger.info('Wrote image to fits data cube %r', cube_file_name)
Esempio n. 21
0
def generate_sample(args):
    """Generate one valid sample and write it to the destination arrays.

    Args (packed as a tuple):
        i: index of the current sample
        sersic_index: an optional value of the Sersic Index (default: None, i.e. random)
        psf_re: an optional PSF (default: None, i.e. random)
        noise: an optional Gaussian noise level (default: None, i.e. random)
    """
    # Unpack the arguments
    i, sersic_index, psf_re, noise = args

    # Loop until a sample satisfies all criteria
    while True:

        with counter.get_lock():
            # Increment the global iteration counter
            counter.value += 1
            # Initialize the random number generators
            random.seed(random_seed + counter.value)
            rng = galsim.BaseDeviate(random_seed + counter.value + 1)

        # PSF Moffat full-width-half-max of the profile: fixed vs random
        if psf_re is None:
            psf_re = random.uniform(0.5, 1)
            random_psf = True
        else:
            random_psf = False

        # Gaussian noise level: fixed vs random
        noise = random.randint(200, 400) if noise is None else noise

        # Sersic index: discrete vs continuous
        bulge_n = random.uniform(0.5,
                                 6) if sersic_index is None else sersic_index

        # Sersic radius, unit arcsec
        bulge_re = random.uniform(0.1, 0.6)

        # q is ellipticity and beta is orientation.
        # You could directly predict q and beta but there would be a discontiniuty issue
        # for beta. A jump from 180 degree to 1 degree.
        # radial sampling for g1 and g2 -reduced shear -> ellipticiy and orientation
        A = random.uniform(
            0, 0.67)  # gal_q =b/a will ranges in (0.2,1) & A=1-q / 1+q
        gal_q = (1 - A) / (1 + A)
        gal_beta = random.uniform(0, 3.14)  # radians
        g_1 = A * np.cos(2 * gal_beta)
        g_2 = A * np.sin(2 * gal_beta)

        gal_flux = 1e5 * random.uniform(0.3, 4)

        gal = galsim.Sersic(bulge_n, half_light_radius=bulge_re)
        gal = gal.withFlux(gal_flux)
        gal = gal.shear(g1=g_1, g2=g_2)
        psf = galsim.Moffat(beta=psf_beta, flux=1.0, fwhm=psf_re)
        final = galsim.Convolve([psf, gal])
        image = galsim.ImageF(image_size, image_size, scale=pixel_scale)
        final.drawImage(image=image)

        # signal to noise ratio
        snr = np.sqrt((image.array**2).sum()) / noise

        # After generating the data, preserve only that with SNR [10, 100]
        if 10 <= snr <= 100:
            break

    image_nonoise = copy.deepcopy(image.array)
    image.addNoise(galsim.PoissonNoise(rng, sky_level=0.0))
    # noise map for bkgr gaussian noise
    image.addNoise(galsim.GaussianNoise(rng, sigma=noise))

    # Optionally: generate a PSF image
    if i == 0 or random_psf:
        psf_image = galsim.ImageF(image_size, image_size, scale=pixel_scale)
        psf.drawImage(image=psf_image)
        data["psf_img"][i] = psf_image.array

    data["img"][i] = image.array  # final noised image
    data["img_nonoise"][i] = image_nonoise  # noiseless image
    data["gal_flux"][i] = gal_flux
    data["bulge_re"][i] = bulge_re
    data["bulge_n"][i] = bulge_n
    data["gal_q"][i] = gal_q
    data["gal_beta"][i] = gal_beta
    data["psf_r"][i] = psf_re
    data["snr"][i] = snr
    data["sigma"][i] = noise
    data["g_1"][i] = g_1
    data["g_2"][i] = g_2
Esempio n. 22
0
def DrawStamp(psf, gal, config, xsize, ysize, offset, method):
    """
    Draw an image using the given psf and gal profiles (which may be None)
    using the FFT method for doing the convolution.

    @returns the resulting image.
    """

    # Setup the object to draw:
    prof_list = [ prof for prof in (gal,psf) if prof is not None ]
    assert len(prof_list) > 0  # Should have already been checked.
    if len(prof_list) > 1:
        final = galsim.Convolve(prof_list)
    else:
        final = prof_list[0]

    # Setup the kwargs to pass to drawImage
    kwargs = {}
    if xsize:
        kwargs['image'] = galsim.ImageF(xsize, ysize)
    kwargs['offset'] = offset
    kwargs['method'] = method
    if 'image' in config and 'wmult' in config['image']:
        kwargs['wmult'] = galsim.config.ParseValue(config['image'], 'wmult', config, float)[0]
    kwargs['wcs'] = config['wcs'].local(image_pos = config['image_pos'])
    if method == 'phot':
        kwargs['rng'] = config['rng']

    # Check validity of extra phot options:
    max_extra_noise = None
    if 'image' in config and 'n_photons' in config['image']:
        if method != 'phot':
            raise AttributeError('n_photons is invalid with method != phot')
        if 'max_extra_noise' in config['image']:
            import warnings
            warnings.warn(
                "Both 'max_extra_noise' and 'n_photons' are set in config['image'], "+
                "ignoring 'max_extra_noise'.")
        kwargs['n_photons'] = galsim.config.ParseValue(config['image'], 'n_photons', config, int)[0]
    elif 'image' in config and 'max_extra_noise' in config['image']:
        if method != 'phot':
            raise AttributeError('max_extra_noise is invalid with method != phot')
        max_extra_noise = galsim.config.ParseValue(
            config['image'], 'max_extra_noise', config, float)[0]
    elif method == 'phot':
        max_extra_noise = 0.01

    if 'image' in config and 'poisson_flux' in config['image']:
        if method != 'phot':
            raise AttributeError('poisson_flux is invalid with method != phot')
        kwargs['poisson_flux'] = galsim.config.ParseValue(
                config['image'], 'poisson_flux', config, bool)[0]

    if max_extra_noise is not None:
        if max_extra_noise < 0.:
            raise ValueError("image.max_extra_noise cannot be negative")
        if max_extra_noise > 0.:
            if 'image' in config and 'noise' in config['image']:
                noise_var = galsim.config.CalculateNoiseVar(config)
            else:
                raise AttributeError(
                    "Need to specify noise level when using draw_method = phot")
            if noise_var < 0.:
                raise ValueError("noise_var calculated to be < 0.")
            max_extra_noise *= noise_var
            kwargs['max_extra_noise'] = max_extra_noise

    im = final.drawImage(**kwargs)
    im.setOrigin(config['image_origin'])

    # If the object has a noise attribute, then check if we need to do anything with it.
    current_var = 0.  # Default if not overwritten
    if hasattr(final,'noise'):
        if 'noise' in config['image']:
            noise = config['image']['noise']
            if 'whiten' in noise:
                if 'symmetrize' in noise:
                    raise AttributeError('Only one of whiten or symmetrize is allowed')
                whiten, safe = galsim.config.ParseValue(noise, 'whiten', config, bool)
                current_var = final.noise.whitenImage(im)

            elif 'symmetrize' in noise:
                symmetrize, safe = galsim.config.ParseValue(noise, 'symmetrize', config, int)
                current_var = final.noise.symmetrizeImage(im, symmetrize)

    if (('gal' in config and 'signal_to_noise' in config['gal']) or
        ('gal' not in config and 'psf' in config and 'signal_to_noise' in config['psf'])):
        if method == 'phot':
            raise NotImplementedError(
                "signal_to_noise option not implemented for draw_method = phot")
        import math
        import numpy
        if 'gal' in config: root_key = 'gal'
        else: root_key = 'psf'

        if 'flux' in config[root_key]:
            raise AttributeError(
                'Only one of signal_to_noise or flux may be specified for %s'%root_key)

        if 'image' in config and 'noise' in config['image']:
            noise_var = galsim.config.CalculateNoiseVar(config)
        else:
            raise AttributeError(
                "Need to specify noise level when using %s.signal_to_noise"%root_key)
        sn_target = galsim.config.ParseValue(config[root_key], 'signal_to_noise', config, float)[0]
            
        # Now determine what flux we need to get our desired S/N
        # There are lots of definitions of S/N, but here is the one used by Great08
        # We use a weighted integral of the flux:
        # S = sum W(x,y) I(x,y) / sum W(x,y)
        # N^2 = Var(S) = sum W(x,y)^2 Var(I(x,y)) / (sum W(x,y))^2
        # Now we assume that Var(I(x,y)) is dominated by the sky noise, so
        # Var(I(x,y)) = var
        # We also assume that we are using a matched filter for W, so W(x,y) = I(x,y).
        # Then a few things cancel and we find that
        # S/N = sqrt( sum I(x,y)^2 / var )

        sn_meas = math.sqrt( numpy.sum(im.array**2) / noise_var )
        # Now we rescale the flux to get our desired S/N
        flux = sn_target / sn_meas
        im *= flux
        if hasattr(final,'noise'):
            current_var *= flux**2

    return im, current_var
Esempio n. 23
0
def test_IPC_basic():
    # Make an image with non-trivially interesting scale.
    g = galsim.Gaussian(sigma=3.7)
    im = g.drawImage(scale=0.25)
    im_save = im.copy()

    # Check for no IPC
    ipc_kernel = galsim.Image(3, 3)
    ipc_kernel.setValue(2, 2, 1.0)
    im_new = im.copy()

    im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='extend')
    np.testing.assert_array_equal(
        im_new.array,
        im.array,
        err_msg="Image is altered for no IPC with edge_treatment = 'extend'")

    im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='wrap')
    np.testing.assert_array_equal(
        im_new.array,
        im.array,
        err_msg="Image is altered for no IPC with edge_treatment = 'wrap'")

    im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='crop')
    np.testing.assert_array_equal(
        im_new.array,
        im.array,
        err_msg="Image is altered for no IPC with edge_treatment = 'crop'")

    assert_raises(ValueError, im_new.applyIPC, galsim.Image(2, 2,
                                                            init_value=1))
    assert_raises(ValueError, im_new.applyIPC, galsim.Image(3,
                                                            3,
                                                            init_value=-1))
    assert_raises(ValueError, im_new.applyIPC, ipc_kernel * -1)
    assert_raises(ValueError,
                  im_new.applyIPC,
                  ipc_kernel,
                  edge_treatment='invalid')

    # Test with a scalar fill_value
    fill_value = np.pi  # a non-trivial one
    im_new.applyIPC(IPC_kernel=ipc_kernel,
                    edge_treatment='crop',
                    fill_value=fill_value)

    #Input arrays and output arrays will differ at the edges for this option.
    np.testing.assert_array_equal(
        im_new.array[1:-1, 1:-1],
        im.array[1:-1, 1:-1],
        err_msg=
        "Image is altered for no IPC with edge_treatment = 'crop' and with a fill_value"
    )
    # Check if the edges are filled with fill_value
    np.testing.assert_array_equal(
        im_new.array[0, :],
        fill_value,
        err_msg="Top edge is not filled with the correct value by applyIPC")
    np.testing.assert_array_equal(
        im_new.array[-1, :],
        fill_value,
        err_msg="Bottom edge is not filled with the correct value by applyIPC")
    np.testing.assert_array_equal(
        im_new.array[:, 0],
        fill_value,
        err_msg="Left edge is not filled with the correct value by applyIPC")
    np.testing.assert_array_equal(
        im_new.array[:, -1],
        fill_value,
        err_msg="Left edge is not filled with the correct value by applyIPC")

    # Testing for flux conservation
    np.random.seed(1234)
    ipc_kernel = galsim.Image(abs(np.random.randn(3, 3)))  # a random kernel
    im_new = im.copy()
    # Set edges to zero since flux is not conserved at the edges otherwise
    im_new.array[0, :] = 0.0
    im_new.array[-1, :] = 0.0
    im_new.array[:, 0] = 0.0
    im_new.array[:, -1] = 0.0
    with assert_warns(galsim.GalSimWarning):  # warn about the sum not being 1
        im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='extend')
    np.testing.assert_almost_equal(
        im_new.array.sum(),
        im.array[1:-1, 1:-1].sum(),
        4,
        err_msg=
        "Normalized IPC kernel does not conserve the total flux for 'extend' option."
    )

    # With kernel_normalization = False, it won't warn, but it also won't conserve flux.
    im_new = im.copy()
    im_new.applyIPC(IPC_kernel=ipc_kernel,
                    edge_treatment='extend',
                    kernel_normalization=False)
    assert np.abs(im_new.array.sum() - im.array[1:-1, 1:-1].sum()) > 1.e-8

    im_new = im.copy()
    ipc_kernel /= ipc_kernel.array.sum(
    )  # Explicitly normalizing also avoids warning.
    im_new.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='wrap')
    np.testing.assert_almost_equal(
        im_new.array.sum(),
        im.array.sum(),
        4,
        err_msg=
        "Normalized IPC kernel does not conserve the total flux for 'wrap' option."
    )

    # Checking directionality
    ipc_kernel = galsim.Image(3, 3)
    ipc_kernel.setValue(2, 2, 0.875)
    ipc_kernel.setValue(2, 3, 0.125)
    # This kernel should correspond to each pixel getting contribution from the pixel above it.
    im1 = im.copy()
    im1.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='crop')
    np.testing.assert_array_almost_equal(
        0.875 * im.array[1:-1, 1:-1] + 0.125 * im.array[2:, 1:-1],
        im1.array[1:-1, 1:-1],
        7,
        err_msg="Difference in directionality for up kernel in applyIPC")
    # Checking for one pixel in the central bulk
    np.testing.assert_almost_equal(
        im1(2, 2),
        0.875 * im(2, 2) + 0.125 * im(2, 3),
        7,
        err_msg="Direction is not as intended for up kernel in applyIPC")

    ipc_kernel = galsim.Image(3, 3)
    ipc_kernel.setValue(2, 2, 0.875)
    ipc_kernel.setValue(1, 2, 0.125)
    # This kernel should correspond to each pixel getting contribution from the pixel to its left.
    im1 = im.copy()
    im1.applyIPC(IPC_kernel=ipc_kernel, edge_treatment='crop')
    np.testing.assert_array_almost_equal(
        im1.array[1:-1, 1:-1],
        im1.array[1:-1, 1:-1],
        7,
        err_msg="Difference in directionality for left kernel in applyIPC")
    # Checking for one pixel in the central bulk
    np.testing.assert_almost_equal(
        im1(2, 3),
        0.875 * im(2, 3) + 0.125 * im(2, 2),
        7,
        err_msg="Direction is not as intended for left kernel in applyIPC")

    # Check using GalSim's native Convolve routine for GSObjects for a realisitic kernel
    ipc_kernel = galsim.Image(
        np.array([[0.01, 0.1, 0.01], [0.1, 1.0, 0.1], [0.01, 0.1, 0.01]]))
    ipc_kernel /= ipc_kernel.array.sum()
    ipc_kernel_int = galsim.InterpolatedImage(ipc_kernel,
                                              x_interpolant='nearest',
                                              scale=im.scale)
    im1 = im.copy()
    im1.applyIPC(IPC_kernel=ipc_kernel,
                 edge_treatment='crop',
                 kernel_normalization=False)
    im2 = im.copy()
    im2_int = galsim.InterpolatedImage(im2, x_interpolant='nearest')
    ipc_kernel_int = galsim.InterpolatedImage(ipc_kernel,
                                              x_interpolant='nearest',
                                              scale=im.scale)
    im_int = galsim.Convolve(ipc_kernel_int, im2_int, real_space=False)
    im_int.drawImage(im2, method='no_pixel', scale=im.scale)
    np.testing.assert_array_almost_equal(
        im1.array,
        im2.array,
        6,
        err_msg="Output of applyIPC does not match the output from Convolve")

    try:
        with warnings.catch_warnings():
            warnings.filterwarnings("ignore", category=RuntimeWarning)
            from scipy import signal
        print(
            "SciPy found installed. Checking IPC kernel convolution against SciPy's `convolve2d`"
        )
        # SciPy is going to emit a warning that we don't want to worry about, so let's deliberately
        # ignore it by going into a `catch_warnings` context.
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")

            # Generate an arbitrary kernel
            np.random.seed(2345)
            ipc_kernel = galsim.Image(abs(np.random.randn(3, 3)))
            ipc_kernel /= ipc_kernel.array.sum()
            # Convolution requires the kernel to be flipped up-down and left-right.
            im_new = im.copy()
            im_new.applyIPC(IPC_kernel=ipc_kernel,
                            edge_treatment='extend',
                            kernel_normalization=False)
            np.testing.assert_array_almost_equal(
                im_new.array,
                signal.convolve2d(im.array,
                                  np.flipud(np.fliplr(ipc_kernel.array)),
                                  mode='same',
                                  boundary='fill'),
                7,
                err_msg=
                "Image differs from SciPy's result using `mode='same'` and "
                "`boundary='fill`.")

            im_new = im.copy()
            im_new.applyIPC(IPC_kernel=ipc_kernel,
                            edge_treatment='crop',
                            kernel_normalization=False)
            np.testing.assert_array_almost_equal(
                im_new.array[1:-1, 1:-1],
                signal.convolve2d(im.array,
                                  np.fliplr(np.flipud(ipc_kernel.array)),
                                  mode='valid',
                                  boundary='fill'),
                7,
                err_msg=
                "Image differs from SciPy's result using `mode=valid'` and "
                "`boundary='fill'`.")

            im_new = im.copy()
            im_new.applyIPC(IPC_kernel=ipc_kernel,
                            edge_treatment='wrap',
                            kernel_normalization=False)
            np.testing.assert_array_almost_equal(
                im_new.array,
                signal.convolve2d(im.array,
                                  np.fliplr(np.flipud(ipc_kernel.array)),
                                  mode='same',
                                  boundary='wrap'),
                7,
                err_msg=
                "Image differs from SciPy's result using `mode=same'` and "
                "boundary='wrap'`.")

    except ImportError:
        # Skip without any warning if SciPy is not installed
        pass
Esempio n. 24
0
def main(argv):
    """
    About as simple as it gets:
      - Use a circular Gaussian profile for the galaxy.
      - Convolve it by a circular Gaussian PSF.
      - Add Gaussian noise to the image.
    """
    # In non-script code, use getLogger(__name__) at module scope instead.
    logging.basicConfig(format="%(message)s",
                        level=logging.INFO,
                        stream=sys.stdout)
    logger = logging.getLogger("demo1")

    gal_flux = 1.e5  # total counts on the image
    gal_sigma = 2.  # arcsec
    psf_sigma = 1.  # arcsec
    pixel_scale = 0.2  # arcsec / pixel
    noise = 30.  # standard deviation of the counts in each pixel

    logger.info('Starting demo script 1 using:')
    logger.info('    - circular Gaussian galaxy (flux = %.1e, sigma = %.1f),',
                gal_flux, gal_sigma)
    logger.info('    - circular Gaussian PSF (sigma = %.1f),', psf_sigma)
    logger.info('    - pixel scale = %.2f,', pixel_scale)
    logger.info('    - Gaussian noise (sigma = %.2f).', noise)

    # Define the galaxy profile
    gal = galsim.Gaussian(flux=gal_flux, sigma=gal_sigma)
    logger.debug('Made galaxy profile')

    # Define the PSF profile
    psf = galsim.Gaussian(flux=1.,
                          sigma=psf_sigma)  # PSF flux should always = 1
    logger.debug('Made PSF profile')

    # Final profile is the convolution of these
    # Can include any number of things in the list, all of which are convolved
    # together to make the final flux profile.
    final = galsim.Convolve([gal, psf])
    logger.debug('Convolved components into final profile')

    # Draw the image with a particular pixel scale, given in arcsec/pixel.
    # The returned image has a member, added_flux, which is gives the total flux actually added to
    # the image.  One could use this value to check if the image is large enough for some desired
    # accuracy level.  Here, we just ignore it.
    image = final.drawImage(scale=pixel_scale)
    logger.debug('Made image of the profile: flux = %f, added_flux = %f',
                 gal_flux, image.added_flux)

    # Add Gaussian noise to the image with specified sigma
    image.addNoise(galsim.GaussianNoise(sigma=noise))
    logger.debug('Added Gaussian noise')

    # Write the image to a file
    if not os.path.isdir('output'):
        os.mkdir('output')
    file_name = os.path.join('output', 'demo1.fits')
    # Note: if the file already exists, this will overwrite it.
    image.write(file_name)
    logger.info('Wrote image to %r' %
                file_name)  # using %r adds quotes around filename for us

    results = image.FindAdaptiveMom()

    logger.info('HSM reports that the image has observed shape and size:')
    logger.info('    e1 = %.3f, e2 = %.3f, sigma = %.3f (pixels)',
                results.observed_shape.e1, results.observed_shape.e2,
                results.moments_sigma)
    logger.info(
        'Expected values in the limit that pixel response and noise are negligible:'
    )
    logger.info('    e1 = %.3f, e2 = %.3f, sigma = %.3f', 0.0, 0.0,
                math.sqrt(gal_sigma**2 + psf_sigma**2) / pixel_scale)
Esempio n. 25
0
import ngmix
import matplotlib.pyplot as plt
import ipdb

mcal_shear = 0.01
#true_shear = -0.02
true_shear = 0.05


# Set up realistic observation parameters
random_seed = 6222019; ud = galsim.UniformDeviate(random_seed+1)
`

gal_ideal = galsim.InclinedExponential(80*galsim.degrees,half_light_radius=1.,flux=7000).rotate(20*galsim.degrees)
psf = galsim.Gaussian(fwhm=.5)
gal_ideal_observed = galsim.Convolve([gal_ideal,psf])
gal_ideal_image = gal_ideal_observed.drawImage(scale=0.206)

sky_image = galsim.ImageF(gal_ideal_image)
sky_level = 106
sky_image.fill(sky_level)
gal_ideal_image+= sky_image # add a flat sky noise to image

psf_image = psf.drawImage(scale=0.206)
psf_weight_image = np.ones_like(psf_image.array)*1e9
weight_image = np.ones_like(gal_ideal_image.array)*1e9

# Set up for metacalibration.
jj_im = ngmix.jacobian.DiagonalJacobian(scale=0.206,x=gal_ideal_image.center.x,y=gal_ideal_image.center.y)
jj_psf = ngmix.jacobian.DiagonalJacobian(scale=0.206,x=psf_image.center.x,y=psf_image.center.y)
psf_obs = ngmix.Observation(psf_image.array,weight=psf_weight_image,jacobian=jj_psf)
Esempio n. 26
0
def estimate(n, p, g1, g2, el, s, show_last_gal, plot_progression, update_freq,
             typ, pxl):

    shear = []
    shear.append(g1)  # lensing shear to apply
    shear.append(g2)
    stamp_xsize = p
    stamp_ysize = p
    random_seed = 4218409
    pixel_scale = 1.0
    sky_level = 1.e6
    gal_sigma = s
    gal_flux = 1.0
    ###########

    print "\nPixel width and height is", p
    print "Applied shear is", shear
    print "Galaxy sigma in pixels", s
    print "Ellipticity is", el
    print "Number of galaxies is", n, '\n'
    if pxl:
        print "Will convolve with pixel"
    else:
        print "Will not convolve with pixel"
    if typ == 'g':
        print "Galaxy type is gaussian"
    elif typ == 'e':
        print "Galaxy type is exponential"
    else:
        print "Galaxy type unrecognized. Sersic not implemented yet."

    #gi=(sqrt(1+el)-sqrt(1-el))/(sqrt(1+el)+sqrt(1-el)) #shear due to intrinsic ellipticity

    px = []
    py = []
    py2 = []
    py3 = []
    py4 = []

    img = galsim.ImageD(stamp_xsize, stamp_ysize, scale=pixel_scale)

    first_in_pair = True

    storeXY(stamp_xsize, stamp_ysize)
    #storeXY(3,3)
    #print XX
    #print YY
    #print XY

    #start = timeit.default_timer()
    for i in range(0, n):

        ud = galsim.UniformDeviate(random_seed + i)
        if first_in_pair:
            theta = ud() * 2. * pi * galsim.radians
            first_in_pair = False
        else:
            theta += 90 * galsim.degrees
            first_in_pair = True

        if typ == 'g':
            profile = galsim.Gaussian(sigma=gal_sigma, flux=gal_flux)
        elif typ == 'e':
            profile = galsim.Exponential(scale_radius=gal_sigma / 2.,
                                         flux=gal_flux)
        else:
            print "Unrecognized galaxy type"
            sys.exit(2)

        gal = profile.createSheared(e=el, beta=theta)
        #gal=profile

        g = sqrt(shear[0]**2. + shear[1]**2.)
        gal.applyLensing(g1=shear[0], g2=shear[1], mu=1. / (1. - 2. * (g**2.)))

        if pxl:
            pix = galsim.Pixel(pixel_scale)
            final = galsim.Convolve([gal, pix])
        else:
            final = gal

        #psf = galsim.Gaussian(sigma=1.0)
        final.draw(img)

        #myimg=drawEllipse(shear[0],shear[1],stamp_xsize,stamp_ysize,s)

        infarr = img.array
        q = getQuad(infarr, pxl, *np.shape(infarr))
        E = polE(q)

        if ((i % update_freq) is 0) and (i > 0):
            print "Done " + str(i) + " galaxies..."

        py.append(0.5 * E[0])
        py2.append(0.5 * E[1])
        if plot_progression:
            px.append(i + 1)
            py3.append(np.array(py).mean())
            py4.append(np.array(py2).mean())

    #stop = timeit.default_timer()
    #print "\nAverage time spent on one galaxy: ", (stop-start)/(i+1)

    if plot_progression:
        plt.clf()
        ax = plt.subplot(1, 1, 1)

        ax.set_xlabel("Number of galaxies")
        ax.set_ylabel("Inferred shear")
        ax.plot(px, py3, 'ro')
        ax.plot(px, py4, 'bx')
        ax.axhline(y=shear[0])
        ax.axhline(y=shear[1])

    print "\nApplied shear is " + str(shear)

    for i, pp in enumerate([py, py2]):
        ## we are doing this clever rotation
        ## so we need to reduce by 2
        pp = [(pp[2 * c] + pp[2 * c + 1]) / 2.0 for c in range(len(pp) / 2)]
        p = np.array(pp)
        inf = p.mean()
        sh = shear[i]
        err = p.std() / sqrt(len(p))
        print "Inferred shear" + str(
            i + 1) + " is", inf, "+/-", err, "off by ", (
                inf - sh) * 100 / sh, " %", (inf - sh) / err, " sigma"

    if show_last_gal:
        plt.subplot(2, 2, 1)
        arr = img.array / img.array.sum()
        plt.imshow(arr, interpolation='nearest')
        plt.colorbar()
        #plt.subplot (2,2,2)
        #plt.imshow(myimg, interpolation='nearest')
        #plt.colorbar()
        #plt.subplot (2,2,3)
        #plt.imshow((arr-myimg)/(myimg+arr+1e-5), interpolation='nearest')
        #plt.imshow((arr-myimg)/(myimg+1e-5), interpolation='nearest')
        #plt.imshow(img.array-img2.array, interpolation='nearest')
        #plt.colorbar()

    if show_last_gal or plot_progression:
        plt.show()
Esempio n. 27
0
def simReal(real_galaxy,
            target_PSF,
            target_pixel_scale,
            g1=0.0,
            g2=0.0,
            rotation_angle=None,
            rand_rotate=True,
            rng=None,
            target_flux=1000.0,
            image=None):
    """Function to simulate images (no added noise) from real galaxy training data.

    This function takes a RealGalaxy from some training set, and manipulates it as needed to
    simulate a (no-noise-added) image from some lower-resolution telescope.  It thus requires a
    target PSF (which could be an image, or one of our base classes) that represents all PSF
    components including the pixel response, and a target pixel scale.

    The default rotation option is to impose a random rotation to make irrelevant any real shears
    in the galaxy training data (optionally, the RNG can be supplied).  This default can be turned
    off by setting `rand_rotate = False` or by requesting a specific rotation angle using the
    `rotation_angle` keyword, in which case `rand_rotate` is ignored.

    Optionally, the user can specify a shear (default 0).  Finally, they can specify a flux
    normalization for the final image, default 1000.

    @param real_galaxy      The RealGalaxy object to use, not modified in generating the
                            simulated image.
    @param target_PSF       The target PSF, either one of our base classes or an Image.
    @param target_pixel_scale  The pixel scale for the final image, in arcsec.
    @param g1               First component of shear to impose (components defined with respect
                            to pixel coordinates), [default: 0]
    @param g2               Second component of shear to impose, [default: 0]
    @param rotation_angle   Angle by which to rotate the galaxy (must be an Angle
                            instance). [default: None]
    @param rand_rotate      Should the galaxy be rotated by some random angle?  [default: True;
                            unless `rotation_angle` is set, then False]
    @param rng              A BaseDeviate instance to use for the random selection or rotation
                            angle. [default: None]
    @param target_flux      The target flux in the output galaxy image, [default: 1000.]
    @param image            As with the GSObject.drawImage() function, if an image is provided,
                            then it will be used and returned.  [default: None, which means an
                            appropriately-sized image will be created.]

    @return a simulated galaxy image.
    """
    # do some checking of arguments
    if not isinstance(real_galaxy, galsim.RealGalaxy):
        raise RuntimeError("Error: simReal requires a RealGalaxy!")
    if isinstance(target_PSF, galsim.Image):
        target_PSF = galsim.InterpolatedImage(target_PSF,
                                              scale=target_pixel_scale)
    if not isinstance(target_PSF, galsim.GSObject):
        raise RuntimeError("Error: target PSF is not an Image or GSObject!")
    if rotation_angle is not None and not isinstance(rotation_angle,
                                                     galsim.Angle):
        raise RuntimeError(
            "Error: specified rotation angle is not an Angle instance!")
    if (target_pixel_scale < real_galaxy.pixel_scale):
        import warnings
        message = "Warning: requested pixel scale is higher resolution than original!"
        warnings.warn(message)
    import math  # needed for pi, sqrt below
    g = math.sqrt(g1**2 + g2**2)
    if g > 1:
        raise RuntimeError("Error: requested shear is >1!")

    # make sure target PSF is normalized
    target_PSF = target_PSF.withFlux(1.0)

    # rotate
    if rotation_angle is not None:
        real_galaxy = real_galaxy.rotate(rotation_angle)
    elif rotation_angle is None and rand_rotate == True:
        if rng is None:
            uniform_deviate = galsim.UniformDeviate()
        elif isinstance(rng, galsim.BaseDeviate):
            uniform_deviate = galsim.UniformDeviate(rng)
        else:
            raise TypeError("The rng provided is not a BaseDeviate")
        rand_angle = galsim.Angle(math.pi * uniform_deviate(), galsim.radians)
        real_galaxy = real_galaxy.rotate(rand_angle)

    # set fluxes
    real_galaxy = real_galaxy.withFlux(target_flux)

    # shear
    if (g1 != 0.0 or g2 != 0.0):
        real_galaxy = real_galaxy.shear(g1=g1, g2=g2)

    # convolve, resample
    out_gal = galsim.Convolve([real_galaxy, target_PSF])
    image = out_gal.drawImage(image=image,
                              scale=target_pixel_scale,
                              method='no_pixel')

    # return simulated image
    return image
Esempio n. 28
0
def main():
    start = timer()

    p = get_params()

    #can't output anything but noise_var
    sys.stderr.write("Parameters chosen for postage stamp sims: \n")
    sys.stderr.write("{}\n".format(p))

    rng = galsim.UniformDeviate(p.random_seed)

    #where to save images
    target_file_name = "stamps_{0}_.fits".format(p.id)
    template_file_name = "template_stamps_{0}_.fits".format(p.id)

    if p.noise_suppression == False:
        gal_name = os.path.splitext(target_file_name)
    else:
        gal_name = os.path.splitext(template_file_name)

    #initial noise
    noise = galsim.GaussianNoise(rng)

    #create test image to determine noise level
    if p.noise_suppression == False:
        test_psf = galsim.Moffat(3.5,
                                 half_light_radius=p.psf_hlr * p.pixel_scale)
        test_psf = test_psf.shear(e2=p.psf_e2)

        test_bulge = galsim.DeVaucouleurs(half_light_radius=(
            (p.gal_hlr_max - p.gal_hlr_min) / 2. + p.gal_hlr_min) *
                                          p.pixel_scale)
        test_disk = galsim.Exponential(half_light_radius=(
            (p.gal_hlr_max - p.gal_hlr_min) / 2. + p.gal_hlr_min) *
                                       p.pixel_scale)
        test_gal = 0.5 * test_bulge + 0.5 * test_disk
        test_gal = test_gal.withFlux(p.gal_flux_min)

        test_image = galsim.ImageF(p.stamp_size,
                                   p.stamp_size,
                                   scale=p.pixel_scale)

        test_final = galsim.Convolve([test_psf, test_gal])
        test_final.drawImage(test_image)

        noise_var = test_image.addNoiseSNR(noise,
                                           p.gal_snr_min,
                                           preserve_flux=True)
        print noise_var

        #final noise
        noise = galsim.GaussianNoise(rng, sigma=np.sqrt(noise_var))

    #make galaxy image
    gal_image = galsim.ImageF(p.nx_tiles * p.stamp_size,
                              p.ny_tiles * p.stamp_size,
                              scale=p.pixel_scale)

    #make ellipticity distribution from sigma
    ellip_dist = BobsEDist(p.gal_ellip_sigma, rng)

    gal_images, psf_images, gal_files, psf_files = [], [], [], []

    for ifile in range(p.n_files):
        final_gals, images = [], []

        #all files have same psf
        psf = galsim.Moffat(3.5, half_light_radius=p.psf_hlr * p.pixel_scale)
        psf = psf.shear(e2=p.psf_e2)
        psf_image = galsim.ImageF(p.stamp_size,
                                  p.stamp_size,
                                  scale=p.pixel_scale)
        psf.drawImage(psf_image)

        for iy in range(p.ny_tiles):
            for ix in range(p.nx_tiles):

                b = galsim.BoundsI(ix * p.stamp_size + 1,
                                   (ix + 1) * p.stamp_size,
                                   iy * p.stamp_size + 1,
                                   (iy + 1) * p.stamp_size)
                sub_gal_image = gal_image[b]

                hlr = (rng() * (p.gal_hlr_max - p.gal_hlr_min) +
                       p.gal_hlr_min) * p.pixel_scale
                flux = rng() * (p.gal_flux_max -
                                p.gal_flux_min) + p.gal_flux_min
                bulge_frac = rng()

                e1, e2 = ellip_dist.sample()

                #galaxy = ExponentialDisk + DeVaucouleurs
                this_bulge = galsim.DeVaucouleurs(half_light_radius=hlr)
                this_disk = galsim.Exponential(half_light_radius=hlr)

                #same ellipticity
                this_bulge = this_bulge.shear(e1=e1, e2=e2)
                this_disk = this_disk.shear(e1=e1, e2=e2)

                #Apply a shift in disk center within one pixel
                shift_r = p.pixel_scale / 2.
                dx = shift_r * (rng() * 2. - 1.)
                dy = shift_r * (rng() * 2. - 1.)
                this_disk = this_disk.shift(dx, dy)

                #Apply a shift in bulge center within circle of radius hlr
                theta = rng() * 2. * np.pi
                r = rng() * hlr
                dx = r * np.cos(theta)
                dy = r * np.sin(theta)
                this_bulge = this_bulge.shift(dx, dy)

                this_gal = bulge_frac * this_bulge + (1 -
                                                      bulge_frac) * this_disk
                this_gal = this_gal.withFlux(flux)

                #All galaxies have same applied shear
                this_gal = this_gal.shear(g1=p.g1, g2=p.g2)

                final = galsim.Convolve([psf, this_gal])

                final.drawImage(sub_gal_image)

                if p.noise_suppression == False:
                    sub_gal_image.addNoise(noise)

        gal_file = os.path.join(
            p.file_dir,
            ''.join([gal_name[0],
                     str(ifile + 1).zfill(2), gal_name[1]]))
        gal_files.append(gal_file)

        psf_file = os.path.join(
            p.file_dir, ''.join([gal_name[0],
                                 str(ifile + 1).zfill(2), '.psf']))
        psf_files.append(psf_file)

        gal_images.append(gal_image)
        psf_images.append(psf_image)

    filepool = mp.Pool(12)
    filepool.map(writeToFile, zip(gal_images, gal_files))

    psfpool = mp.Pool(12)
    psfpool.map(writeToFile, zip(psf_images, psf_files))

    end = timer()
    sys.stderr.write("Images created in {} sec\n".format(end - start))
Esempio n. 29
0
def main(argv):
    """
    Make images using constant PSF and variable shear:
      - The main image is 0.2 x 0.2 degrees.
      - Pixel scale is 0.2 arcsec, hence the image is 3600 x 3600 pixels.
      - Applied shear is from a cosmological power spectrum read in from file.
      - The PSF is a real one from SDSS, and corresponds to a convolution of atmospheric PSF,
        optical PSF, and pixel response, which has been sampled at pixel centers.  We used a PSF
        from SDSS in order to have a PSF profile that could correspond to what you see with a real
        telescope. However, in order that the galaxy resolution not be too poor, we tell GalSim that
        the pixel scale for that PSF image is 0.2" rather than 0.396".  We are simultaneously lying
        about the intrinsic size of the PSF and about the pixel scale when we do this.
      - The 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("demo11")

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

    base_stamp_size = 32  # number of pixels in each dimension of galaxy images
    # This will be scaled up according to the dilation.
    # Hence the "base_" prefix.

    pixel_scale = 0.2  # arcsec/pixel
    image_size = 0.2 * galsim.degrees  # size of big image in each dimension
    image_size = int(
        (image_size / galsim.arcsec) / pixel_scale)  # convert to pixels
    image_size_arcsec = image_size * pixel_scale  # size of big image in each dimension (arcsec)
    noise_variance = 1.e4  # ADU^2
    nobj = 288  # number of galaxies in entire field
    # (This corresponds to 2 galaxies / arcmin^2)
    grid_spacing = 90.0  # The spacing between the samples for the power spectrum
    # realization (arcsec)
    gal_signal_to_noise = 100  # S/N of each galaxy

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

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

    logger.info('Starting demo script 11')

    # Read in galaxy catalog
    cat_file_name = 'real_galaxy_catalog_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]

    # We will cache the galaxies that we make in order to save some of the calculations that
    # happen on construction.  In particular, we don't want to recalculate the Fourier transforms
    # of the real galaxy images, so it's more efficient so make a store of RealGalaxy instances.
    # We start with them all = None, and fill them in as we make them.
    gal_list = [None] * len(id_list)

    # Setup the PowerSpectrum object we'll be using:
    # To do this, we first have to read in the tabulated shear power spectrum, often denoted
    # C_ell(ell), where ell has units of inverse angle and C_ell has units of angle^2.  However,
    # GalSim works in the flat-sky approximation, so we use this notation interchangeably with
    # P(k).  GalSim does not calculate shear power spectra for users, who must be able to provide
    # their own (or use the examples in the repository).
    #
    # Here we use a tabulated power spectrum from iCosmo (http://icosmo.org), with the following
    # cosmological parameters and survey design:
    # H_0 = 70 km/s/Mpc
    # Omega_m = 0.25
    # Omega_Lambda = 0.75
    # w_0 = -1.0
    # w_a = 0.0
    # n_s = 0.96
    # sigma_8 = 0.8
    # Smith et al. prescription for the non-linear power spectrum.
    # Eisenstein & Hu transfer function with wiggles.
    # Default dN/dz with z_med = 1.0
    # The file has, as required, just two columns which are k and P(k).  However, iCosmo works in
    # terms of ell and C_ell; ell is inverse radians and C_ell in radians^2.  Since GalSim tends to
    # work in terms of arcsec, we have to tell it that the inputs are radians^-1 so it can convert
    # to store in terms of arcsec^-1.
    pk_file = os.path.join('data', 'cosmo-fid.zmed1.00.out')
    ps = galsim.PowerSpectrum(pk_file, units=galsim.radians)
    # The argument here is "e_power_function" which defines the E-mode power to use.
    logger.info('Set up power spectrum from tabulated P(k)')

    # Now let's read in the PSF.  It's a real SDSS PSF, which means pixel scale of 0.396".  However,
    # the typical seeing is 1.2" and we want to simulate better seeing, so we will just tell GalSim
    # that the pixel scale is 0.2".  We have to be careful with SDSS PSF images, as they have an
    # added 'soft bias' of 1000 which has been removed before creation of this file, so that the sky
    # level is properly zero.  Also, the file is bzipped, to demonstrate the new capability of
    # reading in a file that has been compressed in various ways (which GalSim can infer from the
    # filename).  We want to read the image directly into an InterpolatedImage GSObject, so we can
    # manipulate it as needed (here, the only manipulation needed is convolution).  We want a PSF
    # with flux 1, and we can set the pixel scale using a keyword.
    psf_file = os.path.join('data', 'example_sdss_psf_sky0.fits.bz2')
    psf = galsim.InterpolatedImage(psf_file, scale=pixel_scale, flux=1.)
    logger.info('Read in PSF image from bzipped FITS file')

    # Setup the image:
    full_image = galsim.ImageF(image_size, image_size)

    # The default convention for indexing an image is to follow the FITS standard where the
    # lower-left pixel is called (1,1).  However, this can be counter-intuitive to people more
    # used to C or python indexing, where indices start at 0.  It is possible to change the
    # coordinates of the lower-left pixel with the methods `setOrigin`.  For this demo, we
    # switch to 0-based indexing, so the lower-left pixel will be called (0,0).
    full_image.setOrigin(0, 0)

    # As for demo10, we use random_seed+nobj for the random numbers required for the
    # whole image.  In this case, both the power spectrum realization and the noise on the
    # full image we apply later.
    rng = galsim.BaseDeviate(random_seed + nobj)

    # We want to make random positions within our image.  However, currently for shears from a power
    # spectrum we first have to get shears on a grid of positions, and then we can choose random
    # positions within that.  So, let's make the grid.  We're going to make it as large as the
    # image, with grid points spaced by 90 arcsec (hence interpolation only happens below 90"
    # scales, below the interesting scales on which we want the shear power spectrum to be
    # represented exactly).  The lensing engine wants positions in arcsec, so calculate that:
    ps.buildGrid(grid_spacing=grid_spacing,
                 ngrid=int(math.ceil(image_size_arcsec / grid_spacing)),
                 rng=rng.duplicate())
    logger.info('Made gridded shears')

    # We keep track of how much noise is already in the image from the RealGalaxies.
    # The default initial value is all pixels = 0.
    noise_image = galsim.ImageF(image_size, image_size)
    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  dudy ) = ( cos(theta)  -sin(theta) ) * pixel_scale
    # ( dvdx  dvdy )   ( sin(theta)   cos(theta) )
    dudx = math.cos(theta.rad()) * pixel_scale
    dudy = -math.sin(theta.rad()) * pixel_scale
    dvdx = math.sin(theta.rad()) * pixel_scale
    dvdy = math.cos(theta.rad()) * pixel_scale
    image_center = full_image.trueCenter()
    affine = galsim.AffineTransform(dudx,
                                    dudy,
                                    dvdx,
                                    dvdy,
                                    origin=full_image.trueCenter())

    # We can also put it on the celestial sphere to give it a bit more realism.
    # The TAN projection takes a (u,v) coordinate system on a tangent plane and projects
    # that plane onto the sky using a given point as the tangent point.  The tangent
    # point should be given as a CelestialCoord.
    sky_center = galsim.CelestialCoord(ra=19.3 * galsim.hours,
                                       dec=-33.1 * galsim.degrees)

    # The third parameter, units, defaults to arcsec, but we make it explicit here.
    # It sets the angular units of the (u,v) intermediate coordinate system.
    wcs = galsim.TanWCS(affine, sky_center, units=galsim.arcsec)
    full_image.wcs = wcs

    # Now we need to loop over our 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)

        # Draw the size from a plausible size distribution: N(r) ~ r^-2.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.
        # N.B. This calculation logically belongs later in the script, but given how the config
        #      structure works and the fact that we also use this value for the stamp size
        #      calculation, in order to get the output file to match the YAML output file, it
        #      turns out this is where we need to put this use of the random number generator.
        distdev = galsim.DistDeviate(ud,
                                     function=lambda x: x**-2.5,
                                     x_min=1,
                                     x_max=5)
        dilat = distdev()

        # Choose a random position in the image
        x = ud() * (image_size - 1)
        y = ud() * (image_size - 1)
        image_pos = galsim.PositionD(x, y)

        # Turn this into a position in world coordinates
        # We leave this in the (u,v) plane, since the PowerSpectrum class is really defined
        # on the tangent plane, not in (ra,dec).
        world_pos = affine.toWorld(image_pos)

        # Get the reduced shears and magnification at this point
        g1, g2, mu = ps.getLensing(pos=world_pos)

        # Construct the galaxy:
        # Select randomly from among our list of galaxies.
        index = int(ud() * len(gal_list))
        gal = gal_list[index]

        # If we haven't made this galaxy yet, we need to do so.
        if gal is None:
            # When whitening the image, we need to make sure the original correlated noise is
            # present throughout the whole image, otherwise the whitening will do the wrong thing
            # to the parts of the image that don't include the original image.  The RealGalaxy
            # stores the correct noise profile to use as the gal.noise attribute.  This noise
            # profile is automatically updated as we shear, dilate, convolve, etc.  But we need to
            # tell it how large to pad with this noise by hand.  This is a bit complicated for the
            # code to figure out on its own, so we have to supply the size for noise padding
            # with the noise_pad_size parameter.

            # In this case, the postage stamp will be 32 pixels for the undilated galaxies.
            # We expand the postage stamp as we dilate the galaxies, so that factor doesn't
            # come into play here.  The shear and magnification are not significant, but the
            # image can be rotated, which adds an extra factor of sqrt(2). So the net required
            # padded size is
            #     noise_pad_size = 32 * sqrt(2) * 0.2 arcsec/pixel = 9.1 arcsec
            # We round this up to 10 to be safe.
            gal = galsim.RealGalaxy(real_galaxy_catalog,
                                    rng=ud,
                                    id=id_list[index],
                                    noise_pad_size=10)
            # Save it for next time we use this galaxy.
            gal_list[index] = gal

        # Apply the dilation we calculated above.
        gal = gal.dilate(dilat)

        # Apply a random rotation
        theta = ud() * 2.0 * numpy.pi * galsim.radians
        gal = gal.rotate(theta)

        # Apply the cosmological (reduced) shear and magnification at this position using a single
        # GSObject method.
        gal = gal.lens(g1, g2, mu)

        # Convolve with the PSF.
        final = galsim.Convolve(psf, gal)

        # Account for the fractional part of the position:
        ix = int(math.floor(x + 0.5))
        iy = int(math.floor(y + 0.5))
        offset = galsim.PositionD(x - ix, y - iy)

        # Draw it with our desired stamp size (scaled up by the dilation factor):
        # Note: We make the stamp size odd to make the above calculation of the offset easier.
        this_stamp_size = 2 * int(math.ceil(base_stamp_size * dilat / 2)) + 1
        stamp = galsim.ImageF(this_stamp_size, this_stamp_size)

        # We use method='no_pixel' here because the SDSS PSF image that we are using includes the
        # pixel response already.
        final.drawImage(image=stamp,
                        wcs=wcs.local(image_pos),
                        offset=offset,
                        method='no_pixel')

        # Now we can whiten or symmetrize the noise on the postage stamp.  Galsim automatically
        # propagates the noise correctly from the initial RealGalaxy object through the applied
        # shear, distortion, rotation, and convolution into the final object's noise attribute.  To
        # make the noise fully white, use the image.whitenNoise() method. The returned value is the
        # variance of the Gaussian noise that is present after the whitening process.
        #
        # However, this is often overkill for many applications.  If it is acceptable to merely end
        # up with noise with some degree of symmetry (say 4-fold or 8-fold symmetry), then you can
        # instead have GalSim just add enough noise to make the resulting noise have this kind of
        # symmetry.  Usually this requires adding significantly less additional noise, which means
        # you can have the resulting total variance be somewhat smaller.  The returned variance
        # corresponds to the zero-lag value of the noise correlation function, which will still have
        # off-diagonal elements.  We can do this step using the image.symmetrizeNoise() method.
        #new_variance = stamp.whitenNoise(final.noise)
        new_variance = stamp.symmetrizeNoise(final.noise, 8)

        # Rescale flux to get the S/N we want.  We have to do that before we add it to the big
        # image, which might have another galaxy near that point (so our S/N calculation would
        # erroneously include the flux from the other object).
        # See demo5.py for the math behind this calculation.
        sn_meas = math.sqrt(numpy.sum(stamp.array**2) / noise_variance)
        flux_scaling = gal_signal_to_noise / sn_meas
        stamp *= flux_scaling
        # This also scales up the current variance by flux_scaling**2.
        new_variance *= flux_scaling**2

        # Recenter the stamp at the desired position:
        stamp.setCenter(ix, iy)

        # Find the overlapping bounds:
        bounds = stamp.bounds & full_image.bounds
        full_image[bounds] += stamp[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] += new_variance

        time2 = time.time()
        tot_time = time2 - time1
        logger.info('Galaxy %d: position relative to center = %s, t=%f s', k,
                    str(world_pos), tot_time)

    # 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.  We have a special noise class
    # that can do this.  VariableGaussianNoise takes an image of variance values and applies
    # Gaussian noise with the corresponding variance to each pixel.
    # So all we need to do is build an image with how much noise to add to each pixel to get us
    # up to the maximum value that we already have in the 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.  We have to do this step
    # at the end, rather than adding to individual postage stamps, in order to get the noise
    # level right in the overlap regions between postage stamps.
    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)

    # 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 = sky_center.ra.hms()
    dec_str = sky_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_size - 1), (image_size - 1, 0),
                   (image_size - 1, image_size - 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,3600), etc. with the same RA, Dec.'
    )
Esempio n. 30
0
def chromatic_galaxy(theta):
  gal_type,half_radius,pixel_scale,skyvar,redshift,e1,e2,indx =theta 
  # where to find and output data
  path,filename = os.path.split(__file__)
  datapath      = os.path.abspath(os.path.join(path,"data/"))
  outpath       = os.path.abspath(os.path.join(path,"output/"))
  
  # In non-script code, use getLogger(__name__) at module scope instead
  logging.basicConfig(format="%(message)s",level=logging.INFO,stream=sys.stdout)
  logger        = logging.getLogger("colored_images")

  # Initialize (pseudo-) random number generator
  random_seed   = 1234567
  rng           = galsim.BaseDeviate(random_seed) 
  
  # read in SEDs
  SED_names     = ['CWW_E_ext','CWW_Sbc_ext','CWW_Scd_ext','CWW_Im_ext']
  SEDs          = {}
  for SED_name in SED_names:
      SED_filename   = os.path.join(datapath,'{1}.sed'.format(SED_name)) 
      SED            = galsim.SED(SED_filename,wave_type='Ang')
      SEDs[SED_name] = SED.withFluxDensity(target_flux_density=1.0,wavelength=500)
  logger.debug('Successfully read in SEDs') 

  filter_names = 'ugrizy'
  filters      = {}

  for filter_name in filter_names:
     filter_filename      = os.path.join(datapath,'LSST_{0}.dat'.format(filter_name))
     filters[filter_name] = galsim.Bandpass(filter_filename)
     filters[filter_name] = filters[filter_name].thin(rel_err=1e-4)

  logger.debug('Read in filters')
  PSF       = galsim.Moffat(fwhm=0.6,beta=2.5)

  #-------------------------------------------------------------
  # Part A: Chromatic de Vaucouleurs galaxy
  if gal_type == 'deVoucauleurs':
    logger.info('')
    logger.info('Starting part A: chromatic de Vaucouleurs galaxy')
    mono_gal  = galsim.DeVaucouleurs(half_light_radius=half_radius)
    plt.imshow(mono_gal.drawImage(64,64).array)
    plt.show()
    SED       = SEDs['CWW_E_ext'].atRedshift(redshift)
    gal       = galsim.Chromatic(mono_gal,SED)
  
    gal       = gal.shear(g1=0.5,g2=0.3).dilate(1.05).shift((1.0,2.1))
    logger.debug('Created Chromatic')

    final     = galsim.Convolve([gal,PSF])
    logger.debug('Created final profile')

    gaussian_noise = galsim.GaussianNoise(rng,sigma=skyvar)
    for filter_name,filter_ in filters.iteritems():
      img          = galsim.ImageF(64,64,scale=pixel_scale)
      final.drawImage(filter_,image=img)
      #plt.imshow(final.drawImage(64,64).array)
      #plt.show()
      #img.addNoise(gaussian_noise)
      logger.debug('Created {0}-band image'.format(filter_name))
      out_filename = os.path.join(outpath,'demo12a_{0}_{1}.fits'.format(filter_name,str(indx)))
      galsim.fits.write(img,out_filename)
      logger.debug('Wrote {0}-band image to disk'.format(filter_name))
      logger.info('Added flux for {0}-band image:{1}'.format(filter_name,img.added_flux))
  #-----------------------------------------------------------------------
  # PART B: chromatic bulge_disk galaxy
  if gal_type == 'diskbulge':
    logger.info('')
    logger.info('Starting part B: chromatic bulge_disk galaxy')

    mono_bulge = galsim.DeVaucouleurs(half_light_radius=0.05)
    bulge_SED  = SEDs['CWW_E_ext'].atRedshift(redshift)
    bulge      = mono_bulge * bulge_SED
    bulge      = bulge.shear(g1=0.05,g2=0.07)
    logger.debug('Created bulge component')
    mono_disk  = galsim.Exponential(half_light_radius=1.0)
    disk_SED   = SEDs['CWW_Im_ext'].atRedshift(redshift)
    disk       = mono_disk*disk_SED
    disk       = disk.shear(g1=e1,g2=e2)
    logger.debug('Created disk component')

    bdgal      = 1.1*(0.8*bulge+4.0*disk)
    bdfinal    = galsim.Convolve([bdgal,PSF])
    logger.debug('Created bulge+disk galaxy final profile')
    gaussian_noise = galsim.GaussianNoise(rng,sigma=skyvar)
    for filter_name,filter_ in filters.iteritems():
      img          = galsim.ImageF(64,64,scale=pixel_scale)
      bdfinal.drawImage(filter_,image=img)
      #img.addNoise(gaussian_noise)
      logger.debug('Created {0}-band image'.format(filter_name))
      out_filename = os.path.join(outpath,'demo12b_{0}_{1}.fits'.format(filter_name,str(indx)))
      galsim.fits.write(img,out_filename)
      logger.debug('Wrote {0}-band image to disk'.format(filter_name))
      logger.info('Added flux for {0}-band image:{1}'.format(filter_name,img.added_flux))
  # PART C: chromatic real galaxy
  if gal_type == 'real':
    logger.info('')
    logger.info('Starting part B: chromatic bulge_disk galaxy')
    cubeimg    = pf.getdata('cube_real.fits')
    idx        = np.random.randint(low=0,high=99)
    imarr      = cubeimg[idx,:,:]
    nx1,nx2    = np.shape(imarr) 
    img        = galsim.ImageF(nx1,nx2,scale=pixel_scale)
    bulge_SED  = SEDs['CWW_E_ext'].atRedshift(redshift)
    bulge      = mono_bulge * bulge_SED
    bulge      = bulge.shear(g1=0.05,g2=0.07)
    logger.debug('Created bulge component')
    mono_disk  = galsim.Exponential(half_light_radius=1.0)
    disk_SED   = SEDs['CWW_Im_ext'].atRedshift(redshift)
    disk       = mono_disk*disk_SED
    disk       = disk.shear(g1=e1,g2=e2)
    logger.debug('Created disk component')

    bdgal      = 1.1*(0.8*bulge+4.0*disk)
    bdfinal    = galsim.Convolve([bdgal,PSF])
    logger.debug('Created bulge+disk galaxy final profile')
    gaussian_noise = galsim.GaussianNoise(rng,sigma=skyvar)
    for filter_name,filter_ in filters.iteritems():
      img          = galsim.ImageF(64,64,scale=pixel_scale)
      bdfinal.drawImage(filter_,image=img)
      img.addNoise(gaussian_noise)
      logger.debug('Created {0}-band image'.format(filter_name))
      out_filename = os.path.join(outpath,'demo12b_{0}_{1}.fits'.format(filter_name,str(indx)))
      galsim.fits.write(img,out_filename)
      logger.debug('Wrote {0}-band image to disk'.format(filter_name))
      logger.info('Added flux for {0}-band image:{1}'.format(filter_name,img.added_flux))