Beispiel #1
0
    def draw(self, profiles, image, method, offset, config, base, logger):
        """
        Draw the profiles onto the stamp.
        """
        if 'stamp_xsize' not in base or 'stamp_ysize' not in base:
            raise RuntimeError(
                "stamp size must be given for stamp type=BlendSet")

        nx = base['stamp_xsize']
        ny = base['stamp_ysize']
        wcs = base['wcs']

        if profiles is not None:
            # Then we haven't drawn the full image yet.
            # We need to draw an image large enough to contain each of the cutouts
            bounds = galsim.BoundsI(galsim.PositionI(0, 0))
            for pos in self.neighbor_pos:
                image_pos = wcs.toImage(pos)
                # Convert to nearest integer position
                image_pos = galsim.PositionI(int(image_pos.x + 0.5),
                                             int(image_pos.y + 0.5))
                bounds += image_pos
            bounds = bounds.withBorder(max(nx, ny) // 2 + 1)

            self.full_images = []
            for prof in profiles:
                im = galsim.ImageF(bounds=bounds, wcs=wcs)
                galsim.config.DrawBasic(prof, im, method,
                                        offset - im.true_center, config, base,
                                        logger)
                self.full_images.append(im)

        # Figure out what bounds to use for the cutouts.
        k = base['obj_num'] - self.first
        if k == 0:
            center_pos = galsim.PositionI(0, 0)
        else:
            center_pos = self.neighbor_pos[k - 1]
        center_image_pos = wcs.toImage(center_pos)
        xmin = int(center_image_pos.x) - nx // 2 + 1
        ymin = int(center_image_pos.y) - ny // 2 + 1
        self.bounds = galsim.BoundsI(xmin, xmin + nx - 1, ymin, ymin + ny - 1)

        # Add up the cutouts from the profile images
        image.setZero()
        image.wcs = wcs
        for full_im in self.full_images:
            assert full_im.bounds.includes(self.bounds)
            image += full_im[self.bounds]

        # And also build the neighbor image for the deblend image
        self.neighbor_image = image.copy()
        self.neighbor_image -= self.full_images[k][self.bounds]

        # Save this in base for the deblend output
        base['blend_neighbor_image'] = self.neighbor_image

        return image
Beispiel #2
0
    def test_copy(self):
        """
        Test that copy() works
        """

        pointing = CelestialCoord(64.82*galsim.degrees, -16.73*galsim.degrees)
        rotation = 116.8*galsim.degrees
        chip_name = 'R:1,2 S:2,2'
        wcs0 = LsstWCS(pointing, rotation, chip_name)
        wcs0 = wcs0._newOrigin(galsim.PositionI(112, 4))
        wcs1 = wcs0.copy()
        self.assertEqual(wcs0, wcs1)

        wcs0 = wcs0._newOrigin(galsim.PositionI(66, 77))
        self.assertNotEqual(wcs0, wcs1)
Beispiel #3
0
def SetupConfigImageSize(config, xsize, ysize, logger=None):
    """Do some further setup of the config dict at the image processing level based on
    the provided image size.

    - Set config['image_xsize'], config['image_ysize'] to the size of the image
    - Set config['image_origin'] to the origin of the image
    - Set config['image_center'] to the center of the image
    - Set config['image_bounds'] to the bounds of the image
    - Build the WCS based on either config['image']['wcs'] or config['image']['pixel_scale']
    - Set config['wcs'] to be the built wcs
    - If wcs.isPixelScale(), also set config['pixel_scale'] for convenience.
    - Set config['world_center'] to either a given value or based on wcs and image_center

    Parameters:
        config:     The configuration dict.
        xsize:      The size of the image in the x-dimension.
        ysize:      The size of the image in the y-dimension.
        logger:     If given, a logger object to log progress. [default: None]
    """
    logger = galsim.config.LoggerWrapper(logger)
    config['image_xsize'] = xsize
    config['image_ysize'] = ysize
    image = config['image']

    origin = 1  # default
    if 'index_convention' in image:
        convention = galsim.config.ParseValue(image, 'index_convention',
                                              config, str)[0]
        if convention.lower() in ('0', 'c', 'python'):
            origin = 0
        elif convention.lower() in ('1', 'fortran', 'fits'):
            origin = 1
        else:
            raise galsim.GalSimConfigValueError(
                "Unknown index_convention", convention,
                ('0', 'c', 'python', '1', 'fortran', 'fits'))

    config['image_origin'] = galsim.PositionI(origin, origin)
    config['image_center'] = galsim.PositionD(origin + (xsize - 1.) / 2.,
                                              origin + (ysize - 1.) / 2.)
    config['image_bounds'] = galsim.BoundsI(origin, origin + xsize - 1, origin,
                                            origin + ysize - 1)

    # Build the wcs
    wcs = galsim.config.BuildWCS(image, 'wcs', config, logger)
    config['wcs'] = wcs

    # If the WCS is a PixelScale or OffsetWCS, then store the pixel_scale in base.  The
    # config apparatus does not use it -- we always use the wcs -- but we keep it in case
    # the user wants to use it for an Eval item.  It's one of the variables they are allowed
    # to assume will be present for them.
    if wcs.isPixelScale():
        config['pixel_scale'] = wcs.scale

    # Set world_center
    if 'world_center' in image:
        config['world_center'] = galsim.config.ParseValue(
            image, 'world_center', config, galsim.CelestialCoord)[0]
    else:
        config['world_center'] = wcs.toWorld(config['image_center'])
Beispiel #4
0
    def test_eq(self):
        """
        Test that __eq__ works for LsstWCS
        """

        start = time.clock()

        wcs1 = LsstWCS(self.pointing, self.rotation, self.chip_name)
        self.assertEqual(self.wcs, wcs1)

        new_origin = galsim.PositionI(9, 9)
        wcs1 = wcs1._newOrigin(new_origin)
        self.assertNotEqual(self.wcs, wcs1)

        other_pointing = CelestialCoord(1.9*galsim.degrees, -34.0*galsim.degrees)
        wcs2 = LsstWCS(other_pointing, self.rotation, self.chip_name)
        self.assertNotEqual(self.wcs, wcs2)

        wcs3 = LsstWCS(self.pointing, 112.0*galsim.degrees, self.chip_name)
        self.assertNotEqual(self.wcs, wcs3)

        wcs4 = LsstWCS(self.pointing, self.rotation, 'R:2,2 S:2,2')
        self.assertNotEqual(self.wcs, wcs4)

        print 'time to run %s = %e sec' % (funcname(), time.clock()-start)
Beispiel #5
0
 def construct_image(self, band_index, uniform_deviate):
     world_origin = self.image_parameters.world_origin.as_galsim_position()
     degrees_per_pixel = self.image_parameters.degrees_per_pixel()
     wcs = (
         # Here we implement the confusing mapping X <-> Dec, Y <-> RA
         galsim.JacobianWCS(0, degrees_per_pixel, degrees_per_pixel,
                            0).withOrigin(galsim.PositionI(0, 0),
                                          world_origin=world_origin))
     image = galsim.ImageF(
         self.image_parameters.width_px,
         self.image_parameters.height_px,
         wcs=wcs,
     )
     for index, light_source in enumerate(self._light_sources):
         sys.stdout.write('Band {} source {}\r'.format(
             band_index + 1, index + 1))
         sys.stdout.flush()
         galsim_light_source = light_source.get_galsim_light_source(
             band_index,
             self.psf_sigma_pixels *
             self.image_parameters.degrees_per_pixel(),
             self.image_parameters,
         )
         galsim_light_source.drawImage(
             image,
             add_to_image=True,
             method='phot',
             max_extra_noise=self.band_sky_level_nmgy[band_index] *
             self.image_parameters.band_nelec_per_nmgy[band_index] / 1000.0,
             rng=uniform_deviate,
         )
     self._add_sky_background(image, band_index, uniform_deviate)
     return image
Beispiel #6
0
def test_position_type_promotion():
    pd1 = galsim.PositionD(0.1, 0.2)
    pd2 = galsim.PositionD(-0.3, 0.4)
    pd3 = galsim.PositionD()  # Also test 0-argument initializer here

    pi1 = galsim.PositionI(3, 65)
    pi2 = galsim.PositionI(-4, 4)
    pi3 = galsim.PositionI()

    # First check combinations that should yield a PositionD
    for lhs, rhs in zip([pd1, pd1, pi1, pd1, pi2], [pd2, pi1, pd2, pi3, pd3]):
        assert lhs + rhs == galsim.PositionD(lhs.x + rhs.x, lhs.y + rhs.y)
        assert lhs - rhs == galsim.PositionD(lhs.x - rhs.x, lhs.y - rhs.y)

    # Also check PosI +/- PosI -> PosI
    assert pi1 + pi2 == galsim.PositionI(pi1.x + pi2.x, pi1.y + pi2.y)
    assert pi1 - pi2 == galsim.PositionI(pi1.x - pi2.x, pi1.y - pi2.y)
Beispiel #7
0
    def updateSkip(self, prof, image, method, offset, config, base, logger):
        """Before drawing the profile, see whether this object can be trivially skipped.

        The base method checks if the object is completely off the main image, so the
        intersection bounds will be undefined.  In this case, don't bother drawing the
        postage stamp for this object.

        Parameters:
            prof:       The profile to draw.
            image:      The image onto which to draw the profile (which may be None).
            method:     The method to use in drawImage.
            offset:     The offset to apply when drawing.
            config:     The configuration dict for the stamp field.
            base:       The base configuration dict.
            logger:     If given, a logger object to log progress.

        Returns:
            whether to skip drawing this object.
        """
        if isinstance(prof, galsim.GSObject) and base.get(
                'current_image', None) is not None:
            if image is None:
                prof = base['wcs'].toImage(prof, image_pos=base['image_pos'])
                N = prof.getGoodImageSize(1.)
                N += 2 + int(np.abs(offset.x) + np.abs(offset.y))
                bounds = galsim._BoundsI(1, N, 1, N)
            else:
                bounds = image.bounds

            # Set the origin appropriately
            stamp_center = base['stamp_center']
            if stamp_center:
                bounds = bounds.shift(stamp_center - bounds.center)
            else:
                bounds = bounds.shift(
                    base.get('image_origin', galsim.PositionI(1, 1)) -
                    galsim.PositionI(bounds.xmin, bounds.ymin))

            overlap = bounds & base['current_image'].bounds
            if not overlap.isDefined():
                logger.info(
                    'obj %d: skip drawing object because its image will be entirely off '
                    'the main image.', base['obj_num'])
                return True

        return False
Beispiel #8
0
def _set_image_origin(config, convention):
    """Set config['image_origin'] appropriately based on the provided convention.
    """
    if convention.lower() in [ '0', 'c', 'python' ]:
        origin = 0
    elif convention.lower() in [ '1', 'fortran', 'fits' ]:
        origin = 1
    else:
        raise AttributeError("Unknown index_convention: %s"%convention)
    config['image_origin'] = galsim.PositionI(origin,origin)
Beispiel #9
0
    def test_copy(self):
        """
        Test that copy() works
        """

        start = time.clock()

        pointing = CelestialCoord(64.82*galsim.degrees, -16.73*galsim.degrees)
        rotation = 116.8*galsim.degrees
        chip_name = 'R:1,2 S:2,2'
        wcs0 = LsstWCS(pointing, rotation, chip_name)
        wcs0 = wcs0._newOrigin(galsim.PositionI(112, 4))
        wcs1 = wcs0.copy()
        self.assertEqual(wcs0, wcs1)

        wcs0 = wcs0._newOrigin(galsim.PositionI(66, 77))
        self.assertNotEqual(wcs0, wcs1)

        print 'time to run %s = %e sec' % (funcname(), time.clock()-start)
Beispiel #10
0
    def __init__(self,
                 i_gal,
                 stamp_size,
                 gal_model,
                 st_model,
                 pointing,
                 sca_center,
                 real_wcs=False):
        self.i_gal = i_gal
        self.stamp_size = stamp_size
        self.gal_model = gal_model
        self.st_model = st_model
        self.pointing = pointing
        self.sca_center = sca_center
        self.real_wcs = real_wcs

        self.stamp_size_factor = old_div(
            int(self.gal_model.getGoodImageSize(wfirst.pixel_scale)),
            self.stamp_size)
        if self.stamp_size_factor == 0:
            self.stamp_size_factor = 1

        self.wcs, self.sky_level = self.pointing.get_wcs()
        self.xy = self.wcs.toImage(self.sca_center)  # galaxy position
        if self.real_wcs == True:
            self.xyI = galsim.PositionI(int(self.xy.x), int(self.xy.y))
            self.b = galsim.BoundsI(
                xmin=self.xyI.x -
                old_div(int(self.stamp_size_factor * self.stamp_size), 2) + 1,
                ymin=self.xyI.y -
                old_div(int(self.stamp_size_factor * self.stamp_size), 2) + 1,
                xmax=self.xyI.x +
                old_div(int(self.stamp_size_factor * self.stamp_size), 2),
                ymax=self.xyI.y +
                old_div(int(self.stamp_size_factor * self.stamp_size), 2))
        else:
            self.xyI = galsim.PositionI(
                int(self.stamp_size_factor * self.stamp_size),
                int(self.stamp_size_factor * self.stamp_size))
            self.b = galsim.BoundsI(xmin=1,
                                    xmax=self.xyI.x,
                                    ymin=1,
                                    ymax=self.xyI.y)
Beispiel #11
0
    def draw_image(self, gal_model, st_model):
        self.make_stamp()

        if self.real_wcs == True:
            offset = self.xy - self.gal_stamp.true_center  # original galaxy position - stamp center
        else:
            offset = galsim.PositionI(0, 0)
        gal_model.drawImage(image=self.gal_stamp, offset=offset)
        st_model.drawImage(image=self.psf_stamp, offset=offset)

        return self.gal_stamp, self.psf_stamp, offset
Beispiel #12
0
    def accumulate(self, photons, image, orig_center=galsim.PositionI(0, 0)):
        """Accumulate the photons incident at the surface of the sensor into the appropriate
        pixels in the image.

        @param photons      A PhotonArray instance describing the incident photons
        @param image        The image into which the photons should be accumuated.
        @param orig_center  The position of the image center in the original image coordinates.
                            [default: (0,0)]
        """
        return self._silicon.accumulate(photons, self.rng, image._image.view(),
                                        orig_center)
Beispiel #13
0
def test_galsim_bounds_error():
    """Test basic usage of GalSimBoundsError
    """
    pos = galsim.PositionI(0,0)
    bounds = galsim.BoundsI(1,10,1,10)
    err = galsim.GalSimBoundsError("Test", pos, bounds)
    print('str = ',str(err))
    print('repr = ',repr(err))
    assert str(err) == "Test galsim.PositionI(0,0) not in galsim.BoundsI(1,10,1,10)"
    assert err.pos == pos
    assert err.bounds == bounds
    assert isinstance(err, galsim.GalSimError)
    assert isinstance(err, ValueError)
    do_pickle(err)
Beispiel #14
0
def _set_image_origin(config, convention):
    """Set `config['image_origin']` appropriately based on the provided `convention`.
    """
    if convention.lower() in [ '0', 'c', 'python' ]:
        origin = 0
    elif convention.lower() in [ '1', 'fortran', 'fits' ]:
        origin = 1
    else:
        raise AttributeError("Unknown index_convention: %s"%convention)
    config['image_origin'] = galsim.PositionI(origin,origin)
    # Also define the overall image center while we're at it.
    xsize = config['image_xsize']
    ysize = config['image_ysize']
    config['image_center'] = galsim.PositionD( origin + (xsize-1.)/2., origin + (ysize-1.)/2. )
Beispiel #15
0
def SetupConfigImageSize(config, xsize, ysize):
    """Do some further setup of the config dict at the image processing level based on
    the provided image size.

    - Set config['image_xsize'], config['image_ysize'] to the size of the image
    - Set config['image_origin'] to the origin of the image
    - Set config['image_center'] to the center of the image
    - Set config['image_bounds'] to the bounds of the image
    - Build the WCS based on either config['image']['wcs'] or config['image']['pixel_scale']
    - Set config['wcs'] to be the built wcs
    - If wcs.isPixelScale(), also set config['pixel_scale'] for convenience.

    @param config       The configuration dict.
    @param xsize        The size of the image in the x-dimension.
    @param ysize        The size of the image in the y-dimension.
    """
    config['image_xsize'] = xsize
    config['image_ysize'] = ysize

    origin = 1  # default
    if 'index_convention' in config['image']:
        convention = galsim.config.ParseValue(config['image'],
                                              'index_convention', config,
                                              str)[0]
        if convention.lower() in ['0', 'c', 'python']:
            origin = 0
        elif convention.lower() in ['1', 'fortran', 'fits']:
            origin = 1
        else:
            raise AttributeError("Unknown index_convention: %s" % convention)

    config['image_origin'] = galsim.PositionI(origin, origin)
    config['image_center'] = galsim.PositionD(origin + (xsize - 1.) / 2.,
                                              origin + (ysize - 1.) / 2.)
    config['image_bounds'] = galsim.BoundsI(origin, origin + xsize - 1, origin,
                                            origin + ysize - 1)

    # Build the wcs
    wcs = galsim.config.BuildWCS(config)
    config['wcs'] = wcs

    # If the WCS is a PixelScale or OffsetWCS, then store the pixel_scale in base.  The
    # config apparatus does not use it -- we always use the wcs -- but we keep it in case
    # the user wants to use it for an Eval item.  It's one of the variables they are allowed
    # to assume will be present for them.
    if wcs.isPixelScale():
        config['pixel_scale'] = wcs.scale
Beispiel #16
0
    def test_round_trip(self):
        """
        Test writing out an image with an LsstWCS, reading it back in, and comparing
        the resulting pixel -> ra, dec mappings
        """

        start = time.clock()

        path, filename = os.path.split(__file__)

        im0 = galsim.Image(int(4000), int(4000), wcs=self.wcs)

        outputFile = os.path.join(path,'scratch_space','lsst_roundtrip_img.fits')
        if os.path.exists(outputFile):
            os.unlink(outputFile)
        im0.write(outputFile)

        im1 = galsim.fits.read(outputFile)

        xPix = []
        yPix = []
        pixPts = []
        for xx in range(0, 4000, 100):
            for yy in range(0, 4000, 100):
                xPix.append(xx)
                yPix.append(yy)
                pixPts.append(galsim.PositionI(xx, yy))

        xPix = np.array(xPix)
        yPix = np.array(yPix)

        ra_control, dec_control = self.wcs._radec(xPix, yPix)
        for rr, dd, pp in zip(ra_control, dec_control, pixPts):
            ra_dec_test = im1.wcs.toWorld(pp)
            self.assertAlmostEqual(rr, ra_dec_test.ra/galsim.radians, 12)
            self.assertAlmostEqual(dd, ra_dec_test.dec/galsim.radians, 12)

        if os.path.exists(outputFile):
            os.unlink(outputFile)

        print 'time to run %s = %e sec' % (funcname(), time.clock()-start)
Beispiel #17
0
    def get_wcs(self):
        #self.find_coordinates()
        WCS = wfirst.getWCS(world_pos  = galsim.CelestialCoord(ra=self.ra*galsim.radians, \
                                                               dec=self.dec*galsim.radians),
                                    PA          = self.position_angle*galsim.radians,
                                    date        = self.date,
                                    SCAs        = self.sca,
                                    PA_is_FPA   = True
                                    )[self.sca]

        sky_level = wfirst.getSkyLevel(self.bpass,
                                       world_pos=WCS.toWorld(
                                           galsim.PositionI(
                                               old_div(wfirst.n_pix, 2),
                                               old_div(wfirst.n_pix, 2))),
                                       date=self.date)
        sky_level *= (1.0 + wfirst.stray_light_fraction) * (
            wfirst.pixel_scale
        )**2  # adds stray light and converts to photons/cm^2
        sky_level *= self.stamp_size * self.stamp_size

        return WCS, sky_level
Beispiel #18
0
    def cutout_psfs(self, psf, wcs):
        """
        Grab square PSF cutout images

        :param psf: a DES_PSFEx instance 
        :param wcs: (astropy.WCS) the wcs for the tile

        :return: 3D Numpy array
        """
        object_x, object_y = self.get_object_xy(wcs)

        psf_cutouts = np.empty(
            (len(self.coadd_ids), self.psf_cutout_size, self.psf_cutout_size),
            dtype=np.double)

        for i, (x, y) in enumerate(zip(object_x, object_y)):
            pos = galsim.PositionI(x, y)
            psfimg = psf.getPSFArray(pos)
            center = (psfimg.shape[0] // 2, psfimg.shape[1] // 2)
            psfimg = self.single_cutout(psfimg, center, self.psf_cutout_size)
            psf_cutouts[i] = psfimg

        return psf_cutouts
Beispiel #19
0
    def addnoise(self, stamp, ivarstamp):
        """Add noise to the object postage stamp.  Remember that STAMP and IVARSTAMP
        are in units of nanomaggies and 1/nanomaggies**2, respectively.

        """
        varstamp = ivarstamp.copy()
        varstamp.invertSelf()
        if np.min(varstamp.array) < 0:
            print(np.min(varstamp.array))
            #sys.exit(1)

        # Add the variance of the object to the variance image (in electrons).
        stamp *= self.nano2e  # [electron]
        #stamp.array = np.abs(stamp.array)
        st = np.abs(stamp.array)
        stamp = galsim.Image(st)
        varstamp *= self.nano2e**2  # [electron^2]
        firstvarstamp = varstamp + stamp

        # Add Poisson noise
        stamp.addNoise(
            galsim.VariableGaussianNoise(galsim.BaseDeviate(), firstvarstamp))

        # ensure the Poisson variance from the object is >0 (see Galsim.demo13)
        objvar = galsim.Image(np.sqrt(stamp.array**2), scale=stamp.scale)
        objvar.setOrigin(galsim.PositionI(stamp.xmin, stamp.ymin))
        varstamp += objvar

        # Convert back to [nanomaggies]
        stamp /= self.nano2e
        varstamp /= self.nano2e**2

        ivarstamp = varstamp.copy()
        ivarstamp.invertSelf()

        return stamp, ivarstamp
Beispiel #20
0
def get_wcs(dither_i, sca, filter_, stamp_size, random_angle):
    dither_i = dither_i
    sca = sca
    filter_ = filter_

    bpass = wfirst.getBandpasses(AB_zeropoint=True)[filter_]

    d = fio.FITS('observing_sequence_hlsonly_5yr.fits')[-1][dither_i]
    ra = d['ra'] * np.pi / 180.  # RA of pointing
    dec = d['dec'] * np.pi / 180.  # Dec of pointing
    #pa     = d['pa']  * np.pi / 180.  # Position angle of pointing
    date = Time(d['date'], format='mjd').datetime

    #random_dir = galsim.UniformDeviate(314)
    #pa = math.pi * random_dir()
    pa = random_angle * np.pi / 180.

    WCS = wfirst.getWCS(world_pos  = galsim.CelestialCoord(ra=ra*galsim.radians, \
                                                           dec=dec*galsim.radians),
                                PA          = pa*galsim.radians,
                                date        = date,
                                SCAs        = sca,
                                PA_is_FPA   = True
                                )[sca]

    sky_level = wfirst.getSkyLevel(bpass,
                                   world_pos=WCS.toWorld(
                                       galsim.PositionI(
                                           old_div(wfirst.n_pix, 2),
                                           old_div(wfirst.n_pix, 2))),
                                   date=date)
    sky_level *= (1.0 + wfirst.stray_light_fraction) * (
        wfirst.pixel_scale)**2  # adds stray light and converts to photons/cm^2
    sky_level *= stamp_size * stamp_size

    return WCS, sky_level
Beispiel #21
0
def test_draw():
    """Test the various options of the PSF.draw command.
    """
    if __name__ == '__main__':
        logger = piff.config.setup_logger(verbose=2)
    else:
        logger = piff.config.setup_logger(log_file='output/test_draw.log')

    # Use an existing Piff solution to match as closely as possible how users would actually
    # use this function.
    psf = piff.read('input/test_single_py27.piff', logger=logger)

    # Data that was used to make that file.
    wcs = galsim.TanWCS(
        galsim.AffineTransform(0.26, 0.05, -0.08, -0.24,
                               galsim.PositionD(1024, 1024)),
        galsim.CelestialCoord(-5 * galsim.arcmin, -25 * galsim.degrees))
    data = fitsio.read('input/test_single_cat1.fits')
    field_center = galsim.CelestialCoord(0 * galsim.degrees,
                                         -25 * galsim.degrees)
    chipnum = 1

    for k in range(len(data)):
        x = data['x'][k]
        y = data['y'][k]
        e1 = data['e1'][k]
        e2 = data['e2'][k]
        s = data['s'][k]
        print('k,x,y = ', k, x, y)
        #print('  true s,e1,e2 = ',s,e1,e2)

        # First, the same test with this file that is in test_wcs.py:test_pickle()
        image_pos = galsim.PositionD(x, y)
        star = piff.Star.makeTarget(x=x,
                                    y=y,
                                    wcs=wcs,
                                    stamp_size=48,
                                    pointing=field_center,
                                    chipnum=chipnum)
        star = psf.drawStar(star)
        #print('  fitted s,e1,e2 = ',star.fit.params)
        np.testing.assert_almost_equal(star.fit.params, [s, e1, e2], decimal=6)

        # Now use the regular PSF.draw() command.  This version is equivalent to the above.
        # (It's not equal all the way to machine precision, but pretty close.)
        im1 = psf.draw(x, y, chipnum, stamp_size=48)
        np.testing.assert_allclose(im1.array,
                                   star.data.image.array,
                                   rtol=1.e-14,
                                   atol=1.e-14)

        # The wcs in the image is the wcs of the original image
        assert im1.wcs == psf.wcs[1]

        # The image is 48 x 48
        assert im1.array.shape == (48, 48)

        # The bounds are centered close to x,y.  Within 0.5 pixel.
        np.testing.assert_allclose(im1.bounds.true_center.x, x, atol=0.5)
        np.testing.assert_allclose(im1.bounds.true_center.y, y, atol=0.5)

        # This version draws the star centered at (x,y).  Check the hsm centroid.
        hsm = im1.FindAdaptiveMom()
        #print('hsm = ',hsm)
        np.testing.assert_allclose(hsm.moments_centroid.x, x, atol=0.01)
        np.testing.assert_allclose(hsm.moments_centroid.y, y, atol=0.01)

        # The total flux should be close to 1.
        np.testing.assert_allclose(im1.array.sum(), 1.0, rtol=1.e-3)

        # We can center the star at an arbitrary location on the image.
        # The default is equivalent to center=(x,y).  So check that this is equivalent.
        # Also, 48 is the default stamp size, so that can be omitted here.
        im2 = psf.draw(x, y, chipnum, center=(x, y))
        assert im2.bounds == im1.bounds
        np.testing.assert_allclose(im2.array,
                                   im1.array,
                                   rtol=1.e-14,
                                   atol=1.e-14)

        # Moving by an integer number of pixels should be very close to the same image
        # over a different slice of the array.
        im3 = psf.draw(x, y, chipnum, center=(x + 1, y + 3))
        assert im3.bounds == im1.bounds
        # (Remember -- numpy indexing is y,x!)
        # Also, the FFTs will be different in detail, so only match to 1.e-6.
        #print('im1 argmax = ',np.unravel_index(np.argmax(im1.array),im1.array.shape))
        #print('im3 argmax = ',np.unravel_index(np.argmax(im3.array),im3.array.shape))
        np.testing.assert_allclose(im3.array[3:, 1:],
                                   im1.array[:-3, :-1],
                                   rtol=1.e-6,
                                   atol=1.e-6)
        hsm = im3.FindAdaptiveMom()
        np.testing.assert_allclose(hsm.moments_centroid.x, x + 1, atol=0.01)
        np.testing.assert_allclose(hsm.moments_centroid.y, y + 3, atol=0.01)

        # Can center at other locations, and the hsm centroids should come out centered pretty
        # close to that location.
        # (Of course the array will be different here, so can't test that.)
        im4 = psf.draw(x, y, chipnum, center=(x + 1.3, y - 0.8))
        assert im4.bounds == im1.bounds
        hsm = im4.FindAdaptiveMom()
        np.testing.assert_allclose(hsm.moments_centroid.x, x + 1.3, atol=0.01)
        np.testing.assert_allclose(hsm.moments_centroid.y, y - 0.8, atol=0.01)

        # Also allowed is center=True to place in the center of the image.
        im5 = psf.draw(x, y, chipnum, center=True)
        assert im5.bounds == im1.bounds
        assert im5.array.shape == (48, 48)
        np.testing.assert_allclose(im5.bounds.true_center.x, x, atol=0.5)
        np.testing.assert_allclose(im5.bounds.true_center.y, y, atol=0.5)
        np.testing.assert_allclose(im5.array.sum(), 1., rtol=1.e-3)
        hsm = im5.FindAdaptiveMom()
        center = im5.true_center
        np.testing.assert_allclose(hsm.moments_centroid.x, center.x, atol=0.01)
        np.testing.assert_allclose(hsm.moments_centroid.y, center.y, atol=0.01)

        # Some invalid ways to try to do this. (Must be either True or a tuple.)
        np.testing.assert_raises(ValueError,
                                 psf.draw,
                                 x,
                                 y,
                                 chipnum,
                                 center='image')
        np.testing.assert_raises(ValueError,
                                 psf.draw,
                                 x,
                                 y,
                                 chipnum,
                                 center=im5.true_center)

        # If providing your own image with bounds far away from the star (say centered at 0),
        # then center=True works fine to draw in the center of that image.
        im6 = im5.copy()
        im6.setCenter(0, 0)
        psf.draw(x, y, chipnum, center=True, image=im6)
        assert im6.bounds.center == galsim.PositionI(0, 0)
        np.testing.assert_allclose(im6.array.sum(), 1., rtol=1.e-3)
        hsm = im6.FindAdaptiveMom()
        center = im6.true_center
        np.testing.assert_allclose(hsm.moments_centroid.x, center.x, atol=0.01)
        np.testing.assert_allclose(hsm.moments_centroid.y, center.y, atol=0.01)
        np.testing.assert_allclose(im6.array,
                                   im5.array,
                                   rtol=1.e-14,
                                   atol=1.e-14)

        # Check non-even stamp size.  Also, not unit flux while we're at it.
        im7 = psf.draw(x,
                       y,
                       chipnum,
                       center=(x + 1.3, y - 0.8),
                       stamp_size=43,
                       flux=23.7)
        assert im7.array.shape == (43, 43)
        np.testing.assert_allclose(im7.bounds.true_center.x, x, atol=0.5)
        np.testing.assert_allclose(im7.bounds.true_center.y, y, atol=0.5)
        np.testing.assert_allclose(im7.array.sum(), 23.7, rtol=1.e-3)
        hsm = im7.FindAdaptiveMom()
        np.testing.assert_allclose(hsm.moments_centroid.x, x + 1.3, atol=0.01)
        np.testing.assert_allclose(hsm.moments_centroid.y, y - 0.8, atol=0.01)

        # Can't do mixed even/odd shape with stamp_size, but it will respect a provided image.
        im8 = galsim.Image(43, 44)
        im8.setCenter(
            x, y
        )  # It will respect the given bounds, so put it near the right place.
        psf.draw(x,
                 y,
                 chipnum,
                 center=(x + 1.3, y - 0.8),
                 image=im8,
                 flux=23.7)
        assert im8.array.shape == (44, 43)
        np.testing.assert_allclose(im8.array.sum(), 23.7, rtol=1.e-3)
        hsm = im8.FindAdaptiveMom()
        np.testing.assert_allclose(hsm.moments_centroid.x, x + 1.3, atol=0.01)
        np.testing.assert_allclose(hsm.moments_centroid.y, y - 0.8, atol=0.01)

        # The offset parameter can add an additional to whatever center is used.
        # Here center=None, so this is equivalent to im4 above.
        im9 = psf.draw(x, y, chipnum, offset=(1.3, -0.8))
        assert im9.bounds == im1.bounds
        hsm = im9.FindAdaptiveMom()
        np.testing.assert_allclose(im9.array,
                                   im4.array,
                                   rtol=1.e-14,
                                   atol=1.e-14)

        # With both, they are effectively added together.  Not sure if there would be a likely
        # use for this, but it's allowed.  (The above with default center is used in unit
        # tests a number of times, so that version at least is useful if only for us.
        # I'm hard pressed to imaging end users wanting to specify things this way though.)
        im10 = psf.draw(x,
                        y,
                        chipnum,
                        center=(x + 0.8, y - 0.3),
                        offset=(0.5, -0.5))
        assert im10.bounds == im1.bounds
        np.testing.assert_allclose(im10.array,
                                   im4.array,
                                   rtol=1.e-14,
                                   atol=1.e-14)
Beispiel #22
0
def test_output_catalog():
    """Test basic operations on Catalog."""
    import time
    t1 = time.time()

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

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

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

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

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

    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Beispiel #23
0
def test_operations_simple():
    """Simple test of operations on InterpolatedImage: shear, magnification, rotation, shifting."""
    import time
    t1 = time.time()

    # Make some nontrivial image that can be described in terms of sums and convolutions of
    # GSObjects.  We want this to be somewhat hard to describe, but should be at least
    # critically-sampled, so put in an Airy PSF.
    gal_flux = 1000.
    pix_scale = 0.03 # arcsec
    bulge_frac = 0.3
    bulge_hlr = 0.3 # arcsec
    bulge_e = 0.15
    bulge_pos_angle = 30.*galsim.degrees
    disk_hlr = 0.6 # arcsec
    disk_e = 0.5
    disk_pos_angle = 60.*galsim.degrees
    lam = 800              # nm    NB: don't use lambda - that's a reserved word.
    tel_diam = 2.4         # meters
    lam_over_diam = lam * 1.e-9 / tel_diam # radians
    lam_over_diam *= 206265  # arcsec
    im_size = 512

    # define subregion for comparison
    comp_region=30 # compare the central region of this linear size
    comp_bounds = galsim.BoundsI(1,comp_region,1,comp_region)
    comp_bounds = comp_bounds.shift(galsim.PositionI((im_size-comp_region)/2,
                                                     (im_size-comp_region)/2))

    bulge = galsim.Sersic(4, half_light_radius=bulge_hlr)
    bulge.applyShear(e=bulge_e, beta=bulge_pos_angle)
    disk = galsim.Exponential(half_light_radius = disk_hlr)
    disk.applyShear(e=disk_e, beta=disk_pos_angle)
    gal = bulge_frac*bulge + (1.-bulge_frac)*disk
    gal.setFlux(gal_flux)
    psf = galsim.Airy(lam_over_diam)
    pix = galsim.Pixel(pix_scale)
    obj = galsim.Convolve(gal, psf, pix)
    im = obj.draw(scale=pix_scale)

    # Turn it into an InterpolatedImage with default param settings
    int_im = galsim.InterpolatedImage(im)

    # Shear it, and compare with expectations from GSObjects directly
    test_g1=-0.07
    test_g2=0.1
    test_decimal=2 # in % difference, i.e. 2 means 1% agreement
    test_int_im = int_im.createSheared(g1=test_g1, g2=test_g2)
    ref_obj = obj.createSheared(g1=test_g1, g2=test_g2)
    # make large images
    im = galsim.ImageD(im_size, im_size)
    ref_im = galsim.ImageD(im_size, im_size)
    test_int_im.draw(image=im, scale=pix_scale)
    ref_obj.draw(image=ref_im, scale=pix_scale)
    # define subregion for comparison
    im_sub = im.subImage(comp_bounds)
    ref_im_sub = ref_im.subImage(comp_bounds)
    diff_im=im_sub-ref_im_sub
    rel = diff_im/im_sub
    zeros_arr = np.zeros((comp_region, comp_region))
    # require relative difference to be smaller than some amount
    np.testing.assert_array_almost_equal(rel.array, zeros_arr,
        test_decimal,
        err_msg='Sheared InterpolatedImage disagrees with reference')

    # Magnify it, and compare with expectations from GSObjects directly
    test_mag = 1.08
    test_decimal=2 # in % difference, i.e. 2 means 1% agreement
    comp_region=30 # compare the central region of this linear size
    test_int_im = int_im.createMagnified(test_mag)
    ref_obj = obj.createMagnified(test_mag)
    # make large images
    im = galsim.ImageD(im_size, im_size)
    ref_im = galsim.ImageD(im_size, im_size)
    test_int_im.draw(image=im, scale=pix_scale)
    ref_obj.draw(image=ref_im, scale=pix_scale)
    # define subregion for comparison
    im_sub = im.subImage(comp_bounds)
    ref_im_sub = ref_im.subImage(comp_bounds)
    diff_im=im_sub-ref_im_sub
    rel = diff_im/im_sub
    zeros_arr = np.zeros((comp_region, comp_region))
    # require relative difference to be smaller than some amount
    np.testing.assert_array_almost_equal(rel.array, zeros_arr,
        test_decimal,
        err_msg='Magnified InterpolatedImage disagrees with reference')

    # Lens it (shear and magnify), and compare with expectations from GSObjects directly
    test_g1 = -0.03
    test_g2 = -0.04
    test_mag = 0.74
    test_decimal=2 # in % difference, i.e. 2 means 1% agreement
    comp_region=30 # compare the central region of this linear size
    test_int_im = int_im.createLensed(test_g1, test_g2, test_mag)
    ref_obj = obj.createLensed(test_g1, test_g2, test_mag)
    # make large images
    im = galsim.ImageD(im_size, im_size)
    ref_im = galsim.ImageD(im_size, im_size)
    test_int_im.draw(image=im, scale=pix_scale)
    ref_obj.draw(image=ref_im, scale=pix_scale)
    # define subregion for comparison
    im_sub = im.subImage(comp_bounds)
    ref_im_sub = ref_im.subImage(comp_bounds)
    diff_im=im_sub-ref_im_sub
    rel = diff_im/im_sub
    zeros_arr = np.zeros((comp_region, comp_region))
    # require relative difference to be smaller than some amount
    np.testing.assert_array_almost_equal(rel.array, zeros_arr,
        test_decimal,
        err_msg='Lensed InterpolatedImage disagrees with reference')

    # Rotate it, and compare with expectations from GSObjects directly
    test_rot_angle = 32.*galsim.degrees
    test_decimal=2 # in % difference, i.e. 2 means 1% agreement
    comp_region=30 # compare the central region of this linear size
    test_int_im = int_im.createRotated(test_rot_angle)
    ref_obj = obj.createRotated(test_rot_angle)
    # make large images
    im = galsim.ImageD(im_size, im_size)
    ref_im = galsim.ImageD(im_size, im_size)
    test_int_im.draw(image=im, scale=pix_scale)
    ref_obj.draw(image=ref_im, scale=pix_scale)
    # define subregion for comparison
    im_sub = im.subImage(comp_bounds)
    ref_im_sub = ref_im.subImage(comp_bounds)
    diff_im=im_sub-ref_im_sub
    rel = diff_im/im_sub
    zeros_arr = np.zeros((comp_region, comp_region))
    # require relative difference to be smaller than some amount
    np.testing.assert_array_almost_equal(rel.array, zeros_arr,
        test_decimal,
        err_msg='Rotated InterpolatedImage disagrees with reference')

    # Shift it, and compare with expectations from GSObjects directly
    x_shift = -0.31
    y_shift = 0.87
    test_decimal=2 # in % difference, i.e. 2 means 1% agreement
    comp_region=30 # compare the central region of this linear size
    test_int_im = int_im.createShifted(x_shift, y_shift)
    ref_obj = obj.createShifted(x_shift, y_shift)
    # make large images
    im = galsim.ImageD(im_size, im_size)
    ref_im = galsim.ImageD(im_size, im_size)
    test_int_im.draw(image=im, scale=pix_scale)
    ref_obj.draw(image=ref_im, scale=pix_scale)
    # define subregion for comparison
    im_sub = im.subImage(comp_bounds)
    ref_im_sub = ref_im.subImage(comp_bounds)
    diff_im=im_sub-ref_im_sub
    rel = diff_im/im_sub
    zeros_arr = np.zeros((comp_region, comp_region))
    # require relative difference to be smaller than some amount
    np.testing.assert_array_almost_equal(rel.array, zeros_arr,
        test_decimal,
        err_msg='Shifted InterpolatedImage disagrees with reference')

    t2 = time.time()
    print 'time for %s = %.2f'%(funcname(),t2-t1)
Beispiel #24
0
def get_blend_shape(mu,
                    c,
                    e1,
                    e2,
                    hlr,
                    flux,
                    hsm=HLRShearModel(),
                    wcs=None,
                    pixel_scale=None,
                    return_hlr=False,
                    return_moments=False,
                    out_unit='pixel'):
    """        
    Returns the combined shear of the blended system
    
    Not quite ready for multiple blended systems! TODO!

    [assuming N galaxies in the blended system]

    #A  : the array of total NORMALIZED fluxes
    mu : the array (N vectors) of galaxy centers (i.e. the peaks of the Gaussians)
    c  : the vector pointing to the luminosity center of the blended system
    e1 : the array of the first component of the shears for N galaxies 
    e2 : the array of the second component of the shears for N galaxies
    flux : the flux of galaxies in the blend (in whatever unit or zeropoint but consistent) 
    hlr in arcsec and mu,c in degrees.
    returns Q_blend in pixel^2, hlr_blend in arcsec
    """

    if wcs is None:
        cen_ra = 0.5 * (mu[0].max() + mu[0].min()) * galsim.degrees
        cen_dec = 0.5 * (mu[1].max() + mu[1].min()) * galsim.degrees
        cen_coord = galsim.CelestialCoord(cen_ra, cen_dec)  #, gsparams=gsp)
        affine_wcs = galsim.PixelScale(pixel_scale).affine().withOrigin(
            galsim.PositionI(0, 0))
        wcs = galsim.TanWCS(affine_wcs,
                            world_origin=cen_coord)  #, gsparams=gsp)

    mu = galsim_world2pix(wcs, mu[0], mu[1])
    c = galsim_world2pix(wcs, [c[0]], [c[1]])  # assumes scalar c[0], c[1]

    Sigma = get_shape_covmat_fast(
        hlr / pixel_scale, e1, e2, hsm=hsm
    )  # an array filled with second moments tensors of the blend members
    A = flux / (2 * np.pi * np.linalg.det(Sigma)**0.5)

    # compute the second moments of the blend "system" (collectively)
    Q_blend = get_blend_moments(A, mu, c, Sigma, unit='pixel')
    hlr_blend, e1_blend, e2_blend = hlr_from_moments_fast(Q_blend,
                                                          hsm=hsm,
                                                          return_shape=True)

    to_return = [e1_blend, e2_blend]

    if out_unit.startswith('deg'):
        convertor = pixel_scale * 3600
    elif out_unit.startswith('arcmin'):
        convertor = pixel_scale * 60
    elif out_unit.startswith('arcsec'):
        convertor = pixel_scale
    elif out_unit.startswith('pix'):
        convertor = 1.0
    else:
        raise RuntimeError('Invalid `out_unit`')

    if return_hlr:
        to_return += [hlr_blend * convertor]

    if return_moments:
        to_return += [Q_blend * convertor**2]

    return to_return
Beispiel #25
0
def BuildStamp(config,
               obj_num=0,
               xsize=0,
               ysize=0,
               do_noise=True,
               logger=None):
    """
    Build a single stamp image using the given config file

    @param config           A configuration dict.
    @param obj_num          If given, the current obj_num [default: 0]
    @param xsize            The xsize of the image to build (if known). [default: 0]
    @param ysize            The ysize of the image to build (if known). [default: 0]
    @param do_noise         Whether to add noise to the image (according to config['noise']).
                            [default: True]
    @param logger           If given, a logger object to log progress. [default: None]

    @returns the tuple (image, current_var)
    """
    SetupConfigObjNum(config, obj_num)

    stamp = config['stamp']
    stamp_type = stamp['type']
    if stamp_type not in valid_stamp_types:
        raise AttributeError("Invalid stamp.type=%s." % stamp_type)
    builder = valid_stamp_types[stamp_type]

    # Add 1 to the seed here so the first object has a different rng than the file or image.
    seed = galsim.config.SetupConfigRNG(config, seed_offset=1)
    if logger:
        logger.debug('obj %d: seed = %d', obj_num, seed)

    if 'retry_failures' in stamp:
        ntries = galsim.config.ParseValue(stamp, 'retry_failures', config,
                                          int)[0]
        # This is how many _re_-tries.  Do at least 1, so ntries is 1 more than this.
        ntries = ntries + 1
    elif ('reject' in stamp or 'min_flux_frac' in stamp or 'min_snr' in stamp
          or 'max_snr' in stamp):
        # Still impose a maximum number of tries to prevent infinite loops.
        ntries = 20
    else:
        ntries = 1

    for itry in range(ntries):

        # The rest of the stamp generation stage is wrapped in a try/except block.
        # If we catch an exception, we continue the for loop to try again.
        # On the last time through, we reraise any exception caught.
        # If no exception is thrown, we simply break the loop and return.
        try:

            # Do the necessary initial setup for this stamp type.
            xsize, ysize, image_pos, world_pos = builder.setup(
                stamp, config, xsize, ysize, stamp_ignore, logger)

            # Save these values for possible use in Evals or other modules
            SetupConfigStampSize(config, xsize, ysize, image_pos, world_pos)
            stamp_center = config['stamp_center']
            if logger:
                if xsize:
                    logger.debug('obj %d: xsize,ysize = %s,%s', obj_num, xsize,
                                 ysize)
                if image_pos:
                    logger.debug('obj %d: image_pos = %s', obj_num, image_pos)
                if world_pos:
                    logger.debug('obj %d: world_pos = %s', obj_num, world_pos)
                if stamp_center:
                    logger.debug('obj %d: stamp_center = %s', obj_num,
                                 stamp_center)

            # Get the global gsparams kwargs.  Individual objects can add to this.
            gsparams = {}
            if 'gsparams' in stamp:
                gsparams = galsim.config.UpdateGSParams(
                    gsparams, stamp['gsparams'], config)

            skip = False
            try:
                psf = galsim.config.BuildGSObject(config,
                                                  'psf',
                                                  gsparams=gsparams,
                                                  logger=logger)[0]
                prof = builder.buildProfile(stamp, config, psf, gsparams,
                                            logger)
            except galsim.config.gsobject.SkipThisObject as e:
                if logger:
                    logger.debug('obj %d: Caught SkipThisObject: e = %s',
                                 obj_num, e.msg)
                if logger:
                    if e.msg:
                        # If there is a message, upgrade to info level
                        logger.info('Skipping object %d: %s', obj_num, e.msg)
                skip = True
                # Note: Skip is different from Reject.
                #       Skip means we return None for this stamp image and continue on.
                #       Reject means we retry this object using the same obj_num.
                #       This has implications for the total number of objects as well as
                #       things like ring tests that rely on objects being made in pairs.
                #
                #       Skip is also different from prof = None.
                #       If prof is None, then the user indicated that no object should be
                #       drawn on this stamp, but that a noise image is still desired.

            im = builder.makeStamp(stamp, config, xsize, ysize, logger)

            if not skip:
                if 'draw_method' in stamp:
                    method = galsim.config.ParseValue(stamp, 'draw_method',
                                                      config, str)[0]
                else:
                    method = 'auto'
                if method not in [
                        'auto', 'fft', 'phot', 'real_space', 'no_pixel', 'sb'
                ]:
                    raise AttributeError("Invalid draw_method: %s" % method)

                offset = config['stamp_offset']
                if 'offset' in stamp:
                    offset += galsim.config.ParseValue(stamp, 'offset', config,
                                                       galsim.PositionD)[0]
                if logger:
                    logger.debug('obj %d: offset = %s', obj_num, offset)

                im = builder.draw(prof, im, method, offset, stamp, config,
                                  logger)

                scale_factor = builder.getSNRScale(im, stamp, config, logger)
                im, prof = builder.applySNRScale(im, prof, scale_factor,
                                                 method, logger)

            # Set the origin appropriately
            if im is None:
                # Note: im might be None here if the stamp size isn't given and skip==True.
                pass
            elif stamp_center:
                im.setCenter(stamp_center)
            else:
                im.setOrigin(config.get('image_origin', galsim.PositionI(1,
                                                                         1)))

            # Store the current stamp in the base-level config for reference
            config['current_stamp'] = im
            # This is also information that the weight image calculation needs
            config['do_noise_in_stamps'] = do_noise

            # Check if this object should be rejected.
            if not skip:
                reject = builder.reject(stamp, config, prof, psf, im, logger)
                if reject:
                    if itry + 1 < ntries:
                        if logger:
                            logger.warning(
                                'Object %d: Rejecting this object and rebuilding',
                                obj_num)
                        builder.reset(config, logger)
                        continue
                    else:  # pragma: no cover
                        if logger:
                            logger.error(
                                'Object %d: Too many rejections for this object. Aborting.',
                                obj_num)
                        raise RuntimeError(
                            "Rejected an object %d times. If this is expected, "
                            % ntries +
                            "you should specify a larger stamp.retry_failures."
                        )

            galsim.config.ProcessExtraOutputsForStamp(config, logger)

            # We always need to do the whiten step here in the stamp processing
            if not skip:
                current_var = builder.whiten(prof, im, stamp, config, logger)
                if current_var != 0.:
                    if logger:
                        logger.debug(
                            'obj %d: whitening noise brought current var to %f',
                            config['obj_num'], current_var)
            else:
                current_var = 0.

            # Sometimes, depending on the image type, we go on to do the rest of the noise as well.
            if do_noise:
                im, current_var = builder.addNoise(stamp, config, im, skip,
                                                   current_var, logger)

            return im, current_var

        except KeyboardInterrupt:
            raise
        except Exception as e:
            if itry == ntries - 1:
                # Then this was the last try.  Just re-raise the exception.
                raise
            else:
                if logger:
                    logger.info('Object %d: Caught exception %s', obj_num,
                                str(e))
                    logger.info('This is try %d/%d, so trying again.',
                                itry + 1, ntries)
                if logger:
                    import traceback
                    tr = traceback.format_exc()
                    logger.debug('obj %d: Traceback = %s', obj_num, tr)
                # Need to remove the "current_val"s from the config dict.  Otherwise,
                # the value generators will do a quick return with the cached value.
                builder.reset(config, logger)
                continue
Beispiel #26
0
def SetupConfigStampSize(config, xsize, ysize, image_pos, world_pos):
    """Do further setup of the config dict at the stamp (or object) processing level reflecting
    the stamp size and position in either image or world coordinates.

    Includes:
    - If given, set config['stamp_xsize'] = xsize
    - If given, set config['stamp_ysize'] = ysize
    - If only image_pos or world_pos is given, compute the other from config['wcs']
    - Set config['index_pos'] = image_pos
    - Set config['world_pos'] = world_pos
    - Calculate the appropriate value of the center of the stamp, to be used with the
      command: stamp_image.setCenter(stamp_center).  Save this as config['stamp_center']
    - Calculate the appropriate offset for the position of the object from the center of
      the stamp due to just the fractional part of the image position, not including
      any config['stamp']['offset'] item that may be present in the config dict.
      Save this as config['stamp_offset']

    @param config           A configuration dict.
    @param xsize            The size of the stamp in the x-dimension. [may be None]
    @param ysize            The size of the stamp in the y-dimension. [may be None]
    @param image_pos        The position of the stamp in image coordinates. [may be None]
    @param world_pos        The position of the stamp in world coordinates. [may be None]
    """

    # Make sure we have a valid wcs in case image-level processing was skipped.
    if 'wcs' not in config:
        config['wcs'] = galsim.config.BuildWCS(config)

    if xsize: config['stamp_xsize'] = xsize
    if ysize: config['stamp_ysize'] = ysize
    if image_pos is not None and world_pos is None:
        # Calculate and save the position relative to the image center
        world_pos = config['wcs'].toWorld(image_pos)

        # Wherever we use the world position, we expect a Euclidean position, not a
        # CelestialCoord.  So if it is the latter, project it onto a tangent plane at the
        # image center.
        if isinstance(world_pos, galsim.CelestialCoord):
            # Then project this position relative to the image center.
            world_center = config['wcs'].toWorld(config['image_center'])
            world_pos = world_center.project(world_pos, projection='gnomonic')

    elif world_pos is not None and image_pos is None:
        # Calculate and save the position relative to the image center
        image_pos = config['wcs'].toImage(world_pos)

    if image_pos is not None:
        import math
        # The image_pos refers to the location of the true center of the image, which is
        # not necessarily the nominal center we need for adding to the final image.  In
        # particular, even-sized images have their nominal center offset by 1/2 pixel up
        # and to the right.
        # N.B. This works even if xsize,ysize == 0, since the auto-sizing always produces
        # even sized images.
        nominal_x = image_pos.x  # Make sure we don't change image_pos, which is
        nominal_y = image_pos.y  # stored in config['image_pos'].
        if xsize % 2 == 0: nominal_x += 0.5
        if ysize % 2 == 0: nominal_y += 0.5

        stamp_center = galsim.PositionI(int(math.floor(nominal_x + 0.5)),
                                        int(math.floor(nominal_y + 0.5)))
        config['stamp_center'] = stamp_center
        config['stamp_offset'] = galsim.PositionD(nominal_x - stamp_center.x,
                                                  nominal_y - stamp_center.y)
        config['image_pos'] = image_pos
        config['world_pos'] = world_pos

    else:
        config['stamp_center'] = None
        config['stamp_offset'] = galsim.PositionD(0., 0.)
        # Set the image_pos to (0,0) in case the wcs needs it.  Probably, if
        # there is no image_pos or world_pos defined, then it is unlikely a
        # non-trivial wcs will have been set.  So anything would actually be fine.
        config['image_pos'] = galsim.PositionD(0., 0.)
        config['world_pos'] = world_pos
Beispiel #27
0
def inject_psf(image, mag, coord, psf=None, seed=None):
    """Realize the DES_PSFEx PSF model `psf` at location `coord` on
    ZTF science image `image` (image path) with magnitude `mag` in
    the AB system, fluctuated by Poisson noise.
    """

    # initialize the random number generator
    import galsim
    rng = galsim.BaseDeviate(seed)

    # handle both scalar and vector inputs
    mag = np.atleast_1d(mag)
    if coord.isscalar:
        coord = coord.reshape([1])

    with fits.open(image, mode='update') as hdul:

        # read in the WCS
        header = hdul[0].header
        wcs = WCS(header=header)

        # measure the PSF using PSFEx if not already specified
        if psf is None:
            psf = measure_psf(image)

        # load the image into galsim
        gimage = galsim.fits.read(hdu_list=hdul)

        # convert the world coordinates to pixel coordinates
        ipos = wcs.all_world2pix([[pos.ra.deg, pos.dec.deg] for pos in coord],
                                 1)

        for m, pos in zip(mag, ipos):

            # calculate the measured flux of the object
            flux = 10**(-0.4 * (m - header['MAGZP']))

            image_pos = galsim.PositionD(*pos)

            # store the center of the nearest integer pixel
            iimage_pos = galsim.PositionI(*tuple(map(round, pos)))

            ix, iy = iimage_pos.x, iimage_pos.y

            # calculate the offset between the stamp center
            # and the profile center
            offset = image_pos - iimage_pos

            # create an output stamp onto which to draw the fluctuated
            # psf

            bounds = galsim.BoundsI(ix - NPIX, ix + NPIX, iy - NPIX, iy + NPIX)

            # check that there is at least some overlap between the
            bounds = bounds & gimage.bounds
            if not bounds.isDefined():
                raise RuntimeError('No overlap between PSF stamp and image. '
                                   'Is the object coordinate contained by the '
                                   'image?')

            # get the noise
            noise = galsim.PoissonNoise(rng)

            # realize the psf at the coordinates
            realization = psf.getPSF(image_pos).withFlux(flux)

            # get the local wcs
            lwcs = psf.getLocalWCS(image_pos)

            # draw the image
            imout = realization.drawImage(wcs=lwcs,
                                          offset=offset,
                                          nx=NPIX * 2 + 1,
                                          ny=NPIX * 2 + 1)

            # add the noise
            imout.addNoise(noise)

            # shift the image to the right spot
            imout.setCenter(iimage_pos)

            # add the photons
            gimage[bounds] = gimage[bounds] + imout[bounds]

        # save it as a new hdu
        galsim.fits.write(gimage, hdu_list=hdul)

        # propagate original WCS to output extension
        wcskeys = wcs.to_header(relax=True)
        hdul[-1].header.update(wcskeys)

        # add a record of the fakes as a bintable
        record = {
            'fake_mag': mag,
            'fake_ra': coord.ra.deg,
            'fake_dec': coord.dec.deg,
            'fake_x': ipos[:, 0],
            'fake_y': ipos[:, 1]
        }

        # save the bintable as an extension
        table = Table(record)
        nhdu = fits.BinTableHDU(table.as_array())
        hdul.append(nhdu)
Beispiel #28
0
    def determinePsf(self,
                     exposure,
                     psfCandidateList,
                     metadata=None,
                     flagKey=None):
        """Determine a Piff PSF model for an exposure given a list of PSF
        candidates.

        Parameters
        ----------
        exposure : `lsst.afw.image.Exposure`
           Exposure containing the PSF candidates.
        psfCandidateList : `list` of `lsst.meas.algorithms.PsfCandidate`
           A sequence of PSF candidates typically obtained by detecting sources
           and then running them through a star selector.
        metadata : `lsst.daf.base import PropertyList` or `None`, optional
           A home for interesting tidbits of information.
        flagKey : `str` or `None`, optional
           Schema key used to mark sources actually used in PSF determination.

        Returns
        -------
        psf : `lsst.meas.extensions.piff.PiffPsf`
           The measured PSF model.
        psfCellSet : `None`
           Unused by this PsfDeterminer.
        """
        stars = []
        for candidate in psfCandidateList:
            cmi = candidate.getMaskedImage()
            weight = computeWeight(cmi, self.config.maxSNR)

            bbox = cmi.getBBox()
            bds = galsim.BoundsI(galsim.PositionI(*bbox.getMin()),
                                 galsim.PositionI(*bbox.getMax()))
            gsImage = galsim.Image(bds, scale=1.0, dtype=float)
            gsImage.array[:] = cmi.image.array
            gsWeight = galsim.Image(bds, scale=1.0, dtype=float)
            gsWeight.array[:] = weight

            source = candidate.getSource()
            image_pos = galsim.PositionD(source.getX(), source.getY())

            data = piff.StarData(gsImage, image_pos, weight=gsWeight)
            stars.append(piff.Star(data, None))

        kernelSize = int(
            np.clip(self.config.kernelSize, self.config.kernelSizeMin,
                    self.config.kernelSizeMax))

        piffConfig = {
            'type': "Simple",
            'model': {
                'type': 'PixelGrid',
                'scale': self.config.samplingSize,
                'size': kernelSize
            },
            'interp': {
                'type': 'BasisPolynomial',
                'order': self.config.spatialOrder
            },
            'outliers': {
                'type': 'Chisq',
                'nsigma': self.config.outlierNSigma,
                'max_remove': self.config.outlierMaxRemove
            }
        }

        piffResult = piff.PSF.process(piffConfig)
        # Run on a single CCD, and in image coords rather than sky coords.
        wcs = {0: galsim.PixelScale(1.0)}
        pointing = None

        logger = logging.getLogger(self.log.getName() + ".Piff")
        logger.addHandler(lsst.log.LogHandler())

        piffResult.fit(stars, wcs, pointing, logger=logger)
        psf = PiffPsf(kernelSize, kernelSize, piffResult)

        used_image_pos = [s.image_pos for s in piffResult.stars]
        if flagKey:
            for candidate in psfCandidateList:
                source = candidate.getSource()
                posd = galsim.PositionD(source.getX(), source.getY())
                if posd in used_image_pos:
                    source.set(flagKey, True)

        if metadata is not None:
            metadata.set("spatialFitChi2", piffResult.chisq)
            metadata.set("numAvailStars", len(stars))
            metadata.set("numGoodStars", len(piffResult.stars))
            metadata.set("avgX", np.mean([p.x for p in piffResult.stars]))
            metadata.set("avgY", np.mean([p.y for p in piffResult.stars]))

        return psf, None
Beispiel #29
0
def main(argv):
    # Where to find and output data.
    path, filename = os.path.split(__file__)
    outpath = os.path.abspath(os.path.join(path, "output/"))

    # Just use a few galaxies, to save time.  Note that we are going to put 4000 galaxy images into
    # our big image, so if we have n_use=10, each galaxy will appear 400 times.  Users who want a
    # more interesting image with greater variation in the galaxy population can change `n_use` to
    # something larger (but it should be <=100, the number of galaxies in this small example
    # catalog).  With 4000 galaxies in a 4k x 4k image with the WFIRST pixel scale, the effective
    # galaxy number density is 74/arcmin^2.  This is not the number density that is expected for a
    # sample that is so bright (I<23.5) but it makes the image more visually interesting.  One could
    # think of it as what you'd get if you added up several images at once, making the images for a
    # sample that is much deeper have the same S/N as that for an I<23.5 sample in a single image.
    n_use = 10
    n_tot = 4000

    # Default is to use all filters.  Specify e.g. 'YJH' to only do Y106, J129, and H158.
    use_filters = None

    # quick and dirty command line parsing.
    for var in argv:
        if var.startswith('data='): datapath = var[5:]
        if var.startswith('out='): outpath = var[4:]
        if var.startswith('nuse='): n_use = int(var[5:])
        if var.startswith('ntot='): n_tot = int(var[5:])
        if var.startswith('filters='): use_filters = var[8:].upper()

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

    # 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("demo13")

    # Initialize (pseudo-)random number generator.
    random_seed = 123456
    rng = galsim.BaseDeviate(random_seed)

    # Generate a Poisson noise model.
    poisson_noise = galsim.PoissonNoise(rng)
    logger.info('Poisson noise model created.')

    # Read in the WFIRST filters, setting an AB zeropoint appropriate for this telescope given its
    # diameter and (since we didn't use any keyword arguments to modify this) using the typical
    # exposure time for WFIRST images.  By default, this routine truncates the parts of the
    # bandpasses that are near 0 at the edges, and thins them by the default amount.
    filters = wfirst.getBandpasses(AB_zeropoint=True)
    logger.debug('Read in WFIRST imaging filters.')

    logger.info('Reading from a parametric COSMOS catalog.')
    # Read in a galaxy catalog - just a random subsample of 100 galaxies for F814W<23.5 from COSMOS.
    cat_file_name = 'real_galaxy_catalog_23.5_example_fits.fits'
    dir = 'data'
    # Use the routine that can take COSMOS real or parametric galaxy information, and tell it we
    # want parametric galaxies that represent an I<23.5 sample.
    cat = galsim.COSMOSCatalog(cat_file_name, dir=dir, use_real=False)
    logger.info('Read in %d galaxies from catalog'%cat.nobjects)

    # Here we carry out the initial steps that are necessary to get a fully chromatic PSF.  We use
    # the getPSF() routine in the WFIRST module, which knows all about the telescope parameters
    # (diameter, bandpasses, obscuration, etc.).  Note that we arbitrarily choose a single SCA
    # (Sensor Chip Assembly) rather than all of them, for faster calculations, and use a simple
    # representation of the struts for faster calculations.  To do a more exact calculation of the
    # chromaticity and pupil plane configuration, remove the `approximate_struts` and the `n_waves`
    # keyword from the call to getPSF():
    use_SCA = 7 # This could be any number from 1...18
    logger.info('Doing expensive pre-computation of PSF.')
    t1 = time.time()
    logger.setLevel(logging.DEBUG)
    # Need to make a separate PSF for each filter.  We are, however, ignoring the
    # position-dependence of the PSF within each SCA, just using the PSF at the center of the SCA
    # (default kwargs).
    PSFs = {}
    for filter_name, filter_ in filters.items():
        logger.info('PSF pre-computation for SCA %d, filter %s.'%(use_SCA, filter_name))
        PSFs[filter_name] = wfirst.getPSF(use_SCA, filter_name,
                                          approximate_struts=True, n_waves=10, logger=logger)
    logger.setLevel(logging.INFO)
    t2 = time.time()
    logger.info('Done PSF precomputation in %.1f seconds!'%(t2-t1))

    # Define the size of the postage stamp that we use for each individual galaxy within the larger
    # image, and for the PSF images.
    stamp_size = 256

    # We choose a particular (RA, dec) location on the sky for our observation.
    ra_targ = 90.*galsim.degrees
    dec_targ = -10.*galsim.degrees
    targ_pos = galsim.CelestialCoord(ra=ra_targ, dec=dec_targ)
    # Get the WCS for an observation at this position.  We are not supplying a date, so the routine
    # will assume it's the vernal equinox.  We are also not supplying a position angle for the
    # observatory, which means that it will just find the optimal one (the one that has the solar
    # panels pointed most directly towards the Sun given this targ_pos and date).  The output of
    # this routine is a dict of WCS objects, one for each SCA.  We then take the WCS for the SCA
    # that we are using.
    wcs_list = wfirst.getWCS(world_pos=targ_pos, SCAs=use_SCA)
    wcs = wcs_list[use_SCA]
    # We need to find the center position for this SCA.  We'll tell it to give us a CelestialCoord
    # corresponding to (X, Y) = (wfirst.n_pix/2, wfirst.n_pix/2).
    SCA_cent_pos = wcs.toWorld(galsim.PositionD(wfirst.n_pix/2, wfirst.n_pix/2))

    # We randomly distribute points in (X, Y) on the CCD.
    # If we had a real galaxy catalog with positions in terms of RA, dec we could use wcs.toImage()
    # to find where those objects should be in terms of (X, Y).
    pos_rng = galsim.UniformDeviate(random_seed)
    # Make a list of (X, Y, F814W magnitude, n_rot, flip) values.
    # (X, Y) give the position of the galaxy centroid (or the center of the postage stamp into which
    # we draw the galaxy) in the big image.
    # F814W magnitudes are randomly drawn from the catalog, and are used to create a more realistic
    # flux distribution for the galaxies instead of just having the 10 flux values for the galaxies
    # we have chosen to draw.
    # n_rot says how many 90 degree rotations to include for a given realization of each galaxy, so
    # it doesn't appear completely identical each time we put it in the image.
    # flip is a random number that will determine whether we include an x-y flip for this appearance
    # of the galaxy or not.
    x_stamp = []
    y_stamp = []
    mag_stamp = []
    n_rot_stamp = []
    flip_stamp = []
    for i_gal in range(n_tot):
        x_stamp.append(pos_rng()*wfirst.n_pix)
        y_stamp.append(pos_rng()*wfirst.n_pix)
        # Note that we could use wcs.toWorld() to get the (RA, dec) for these (x, y) positions.  Or,
        # if we had started with (RA, dec) positions, we could have used wcs.toImage() to get the
        # CCD coordinates for those positions.
        mag_stamp.append(cat.param_cat['mag_auto'][int(pos_rng()*cat.nobjects)])
        n_rot_stamp.append(int(4*pos_rng()))
        flip_stamp.append(pos_rng())

    # Make the 2-component parametric GSObjects for each object, including chromaticity (roughly
    # appropriate SEDs per galaxy component, at the appropriate galaxy redshift).  Note that since
    # the PSF is position-independent within the SCA, we can simply do the convolution with that PSF
    # now instead of using a different one for each position.  We also have to include the correct
    # flux scaling: The catalog returns objects that would be observed by 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.
    logger.info('Processing the objects in the catalog to get GSObject representations')
    # Choose a random set of unique indices in the catalog (will be the same each time script is
    # run, due to use of the same random seed):
    rand_indices = []
    while len(rand_indices)<n_use:
        tmp_ind = int(pos_rng()*cat.nobjects)
        if tmp_ind not in rand_indices:
            rand_indices.append(tmp_ind)
    obj_list = cat.makeGalaxy(rand_indices, chromatic=True, gal_type='parametric')
    hst_eff_area = 2.4**2 * (1.-0.33**2)
    wfirst_eff_area = galsim.wfirst.diameter**2 * (1.-galsim.wfirst.obscuration**2)
    flux_scaling = (wfirst_eff_area/hst_eff_area) * wfirst.exptime
    mag_list = []
    for ind in range(len(obj_list)):
        # First, let's check what magnitude this object has in F814W.  We want to do this because
        # (to inject some variety into our images) we are going to rescale the fluxes in all bands
        # for different instances of this galaxy in the final image in order to get a reasonable S/N
        # distribution.  So we need to save the original magnitude in F814W, to compare with a
        # randomly drawn one from the catalog.  This is not something that most users would need to
        # do.
        mag_list.append(cat.param_cat['mag_auto'][cat.orig_index[rand_indices[ind]]])

    # Calculate the sky level for each filter, and draw the PSF and the galaxies through the
    # filters.
    for filter_name, filter_ in filters.items():
        if use_filters is not None and filter_name[0] not in use_filters:
            logger.info('Skipping filter {0}.'.format(filter_name))
            continue

        logger.info('Beginning work for {0}.'.format(filter_name))

        # Drawing PSF.  Note that the PSF object intrinsically has a flat SED, so if we convolve it
        # with a galaxy, it will properly take on the SED of the galaxy.  For the sake of this demo,
        # we will simply convolve with a 'star' that has a flat SED and unit flux in this band, so
        # that the PSF image will be normalized to unit flux. This does mean that the PSF image
        # being drawn here is not quite the right PSF for the galaxy.  Indeed, the PSF for the
        # galaxy effectively varies within it, since it differs for the bulge and the disk.  To make
        # a real image, one would have to choose SEDs for stars and convolve with a star that has a
        # reasonable SED, but we just draw with a flat SED for this demo.
        out_filename = os.path.join(outpath, 'demo13_PSF_{0}.fits'.format(filter_name))
        # Generate a point source.
        point = galsim.DeltaFunction(flux=1.)
        # Use a flat SED here, but could use something else.  A stellar SED for instance.
        # Or a typical galaxy SED.  Depending on your purpose for drawing the PSF.
        star_sed = galsim.SED(lambda x:1, 'nm', 'flambda').withFlux(1.,filter_)  # Give it unit flux in this filter.
        star = galsim.Convolve(point*star_sed, PSFs[filter_name])
        img_psf = galsim.ImageF(64,64)
        star.drawImage(bandpass=filter_, image=img_psf, scale=wfirst.pixel_scale)
        img_psf.write(out_filename)
        logger.debug('Created PSF with flat SED for {0}-band'.format(filter_name))

        # Set up the full image that will contain all the individual galaxy images, with information
        # about WCS:
        final_image = galsim.ImageF(wfirst.n_pix,wfirst.n_pix, wcs=wcs)

        # Draw the galaxies into the image.
        for i_gal in range(n_use):
            logger.info('Drawing image for the object at row %d in the input catalog'%i_gal)

            # We want to only draw the galaxy once (for speed), not over and over with different
            # sub-pixel offsets.  For this reason we ignore the sub-pixel offset entirely.  Note
            # that we are setting the postage stamp to have the average WFIRST pixel scale.  This is
            # simply an approximation for the purpose of speed; really, one should draw directly
            # into final_image, which has the appropriate WCS for WFIRST.  In that case, the image
            # of the galaxy might look different in different parts of the detector due to the WCS
            # (including distortion), and we would have to re-draw each time.  To keep the demo
            # relatively quick, we are just using the approximate average pixel scale and drawing
            # once.
            stamp = galsim.Image(stamp_size, stamp_size, scale=wfirst.pixel_scale)

            # Convolve the chromatic galaxy and the chromatic PSF for this bandpass, and rescale flux.
            final = galsim.Convolve(flux_scaling*obj_list[ind], PSFs[filter_name])
            final.drawImage(filter_, image=stamp)

            # Have to find where to place it:
            for i_gal_use in range(i_gal*n_tot//n_use, (i_gal+1)*n_tot//n_use):
                # Account for the fractional part of the position:
                ix = int(math.floor(x_stamp[i_gal_use]+0.5))
                iy = int(math.floor(y_stamp[i_gal_use]+0.5))
                # We don't actually use this offset.
                offset = galsim.PositionD(x_stamp[i_gal]-ix, y_stamp[i_gal]-iy)

                # Create a nominal bound for the postage stamp given the integer part of the
                # position.
                stamp_bounds = galsim.BoundsI(ix-0.5*stamp_size, ix+0.5*stamp_size-1,
                                              iy-0.5*stamp_size, iy+0.5*stamp_size-1)

                # Find the overlapping bounds between the large image and the individual postage
                # stamp.
                bounds = stamp_bounds & final_image.bounds

                # Just to inject a bit of variety into the image, so it isn't *quite* as obvious
                # that we've repeated the same 10 objects over and over, we randomly rotate the
                # postage stamp by some factor of 90 degrees and possibly include a random flip.
                if flip_stamp[i_gal_use] > 0.5:
                    new_arr = numpy.ascontiguousarray(
                        numpy.rot90(stamp.array, n_rot_stamp[i_gal_use]))
                else:
                    new_arr = numpy.ascontiguousarray(
                        numpy.fliplr(numpy.rot90(stamp.array, n_rot_stamp[i_gal_use])))
                stamp_rot = galsim.Image(new_arr, scale=stamp.scale)
                stamp_rot.setOrigin(galsim.PositionI(stamp_bounds.xmin, stamp_bounds.ymin))

                # Rescale the flux to match that of a randomly chosen galaxy in the galaxy, but
                # keeping the same SED as for this particular galaxy.  This gives a bit more
                # variety in the flux values and SNR of the galaxies in the image without having
                # to render images of many more objects.
                flux_scaling = 10**(-0.4*(mag_stamp[i_gal_use]-mag_list[i_gal]))

                # Copy the image into the right place in the big image.
                final_image[bounds] += flux_scaling * stamp_rot[bounds]

        # Now we're done with the per-galaxy drawing for this image.  The rest will be done for the
        # entire image at once.
        logger.info('Postage stamps of all galaxies drawn on a single big image for this filter.')
        logger.info('Adding the sky level, noise and detector non-idealities.')

        # First we get the amount of zodaical light for a position corresponding to the center of
        # this SCA.  The results are provided in units of e-/arcsec^2, using the default WFIRST
        # exposure time since we did not explicitly specify one.  Then we multiply this by a factor
        # >1 to account for the amount of stray light that is expected.  If we do not provide a date
        # for the observation, then it will assume that it's the vernal equinox (sun at (0,0) in
        # ecliptic coordinates) in 2025.
        sky_level = wfirst.getSkyLevel(filters[filter_name], world_pos=SCA_cent_pos)
        sky_level *= (1.0 + wfirst.stray_light_fraction)
        # Make a image of the sky that takes into account the spatially variable pixel scale.  Note
        # that makeSkyImage() takes a bit of time.  If you do not care about the variable pixel
        # scale, you could simply compute an approximate sky level in e-/pix by multiplying
        # sky_level by wfirst.pixel_scale**2, and add that to final_image.
        sky_image = final_image.copy()
        wcs.makeSkyImage(sky_image, sky_level)
        # This image is in units of e-/pix.  Finally we add the expected thermal backgrounds in this
        # band.  These are provided in e-/pix/s, so we have to multiply by the exposure time.
        sky_image += wfirst.thermal_backgrounds[filter_name]*wfirst.exptime
        # Adding sky level to the image.
        final_image += sky_image

        # Now that all sources of signal (from astronomical objects and background) have been added
        # to the image, we can start adding noise and detector effects.  There is a utility,
        # galsim.wfirst.allDetectorEffects(), that can apply ALL implemented noise and detector
        # effects in the proper order.  Here we step through the process and explain these in a bit
        # more detail without using that utility.

        # First, we include the expected Poisson noise:
        final_image.addNoise(poisson_noise)

        # The subsequent steps account for the non-ideality of the detectors.

        # 1) Reciprocity failure:
        # Reciprocity, in the context of photography, is the inverse relationship between the
        # incident flux (I) of a source object and the exposure time (t) required to produce a given
        # response(p) in the detector, i.e., p = I*t. However, in NIR detectors, this relation does
        # not hold always. The pixel response to a high flux is larger than its response to a low
        # flux. This flux-dependent non-linearity is known as 'reciprocity failure', and the
        # approximate amount of reciprocity failure for the WFIRST detectors is known, so we can
        # include this detector effect in our images.

        if diff_mode:
            # Save the image before applying the transformation to see the difference
            save_image = final_image.copy()

        # If we had wanted to, we could have specified a different exposure time than the default
        # one for WFIRST, but otherwise the following routine does not take any arguments.
        wfirst.addReciprocityFailure(final_image)
        logger.debug('Included reciprocity failure in {0}-band image'.format(filter_name))

        if diff_mode:
            # Isolate the changes due to reciprocity failure.
            diff = final_image-save_image

            out_filename = os.path.join(outpath,'demo13_RecipFail_{0}.fits'.format(filter_name))
            final_image.write(out_filename)
            out_filename = os.path.join(outpath,
                                        'demo13_diff_RecipFail_{0}.fits'.format(filter_name))
            diff.write(out_filename)

        # At this point in the image generation process, an integer number of photons gets
        # detected, hence we have to round the pixel values to integers:
        final_image.quantize()

        # 2) Adding dark current to the image:
        # Even when the detector is unexposed to any radiation, the electron-hole pairs that
        # are generated within the depletion region due to finite temperature are swept by the
        # high electric field at the junction of the photodiode. This small reverse bias
        # leakage current is referred to as 'dark current'. It is specified by the average
        # number of electrons reaching the detectors per unit time and has an associated
        # Poisson noise since it is a random event.
        dark_current = wfirst.dark_current*wfirst.exptime
        dark_noise = galsim.DeviateNoise(galsim.PoissonDeviate(rng, dark_current))
        final_image.addNoise(dark_noise)

        # NOTE: Sky level and dark current might appear like a constant background that can be
        # simply subtracted. However, these contribute to the shot noise and matter for the
        # non-linear effects that follow. Hence, these must be included at this stage of the
        # image generation process. We subtract these backgrounds in the end.

        # 3) Applying a quadratic non-linearity:
        # In order to convert the units from electrons to ADU, we must use the gain factor. The gain
        # has a weak dependency on the charge present in each pixel. This dependency is accounted
        # for by changing the pixel values (in electrons) and applying a constant nominal gain
        # later, which is unity in our demo.

        # Save the image before applying the transformation to see the difference:
        if diff_mode:
            save_image = final_image.copy()

        # Apply the WFIRST nonlinearity routine, which knows all about the nonlinearity expected in
        # the WFIRST detectors.
        wfirst.applyNonlinearity(final_image)
        # Note that users who wish to apply some other nonlinearity function (perhaps for other NIR
        # detectors, or for CCDs) can use the more general nonlinearity routine, which uses the
        # following syntax:
        # final_image.applyNonlinearity(NLfunc=NLfunc)
        # with NLfunc being a callable function that specifies how the output image pixel values
        # should relate to the input ones.
        logger.debug('Applied nonlinearity to {0}-band image'.format(filter_name))
        if diff_mode:
            diff = final_image-save_image

            out_filename = os.path.join(outpath,'demo13_NL_{0}.fits'.format(filter_name))
            final_image.write(out_filename)
            out_filename = os.path.join(outpath,'demo13_diff_NL_{0}.fits'.format(filter_name))
            diff.write(out_filename)

            # Save this image to do the diff after applying IPC.
            save_image = final_image.copy()

        # 4) Including Interpixel capacitance:
        # The voltage read at a given pixel location is influenced by the charges present in the
        # neighboring pixel locations due to capacitive coupling of sense nodes. This interpixel
        # capacitance effect is modeled as a linear effect that is described as a convolution of a
        # 3x3 kernel with the image.  The WFIRST IPC routine knows about the kernel already, so the
        # user does not have to supply it.
        wfirst.applyIPC(final_image)
        logger.debug('Applied interpixel capacitance to {0}-band image'.format(filter_name))

        if diff_mode:
            # Isolate the changes due to the interpixel capacitance effect.
            diff = final_image-save_image

            out_filename = os.path.join(outpath,'demo13_IPC_{0}.fits'.format(filter_name))
            final_image.write(out_filename)
            out_filename = os.path.join(outpath,'demo13_diff_IPC_{0}.fits'.format(filter_name))
            diff.write(out_filename)

        # 5) Adding read noise:
        # Read noise is the noise due to the on-chip amplifier that converts the charge into an
        # analog voltage.  We already applied the Poisson noise due to the sky level, so read noise
        # should just be added as Gaussian noise:
        read_noise = galsim.GaussianNoise(rng, sigma=wfirst.read_noise)
        final_image.addNoise(read_noise)
        logger.debug('Added readnoise to {0}-band image'.format(filter_name))

        # We divide by the gain to convert from e- to ADU. Currently, the gain value in the WFIRST
        # module is just set to 1, since we don't know what the exact gain will be, although it is
        # expected to be approximately 1. Eventually, this may change when the camera is assembled,
        # and there may be a different value for each SCA. For now, there is just a single number,
        # which is equal to 1.
        final_image /= wfirst.gain

        # Finally, the analog-to-digital converter reads in an integer value.
        final_image.quantize()
        # Note that the image type after this step is still a float.  If we want to actually
        # get integer values, we can do new_img = galsim.Image(final_image, dtype=int)

        # Since many people are used to viewing background-subtracted images, we provide a
        # version with the background subtracted (also rounding that to an int).
        sky_image.quantize()
        tot_sky_image = (sky_image + round(dark_current))/wfirst.gain
        tot_sky_image.quantize()
        final_image -= tot_sky_image

        logger.debug('Subtracted background for {0}-band image'.format(filter_name))
        # Write the final image to a file.
        out_filename = os.path.join(outpath,'demo13_{0}.fits'.format(filter_name))
        final_image.write(out_filename)

        logger.info('Completed {0}-band image.'.format(filter_name))

    logger.info('You can display the output in ds9 with a command line that looks something like:')
    logger.info('ds9 -zoom 0.5 -scale limits -500 1000 -rgb '+
                '-red output/demo13_H158.fits '+
                '-green output/demo13_J129.fits '+
                '-blue output/demo13_Y106.fits')
Beispiel #30
0
def test_output_catalog():
    """Test basic operations on Catalog."""
    names = [
        'float1', 'float2', 'int1', 'int2', 'bool1', 'bool2', 'str1', 'str2',
        'str3', 'str4', 'angle', 'posi', 'posd', 'shear'
    ]
    types = [
        float, 'f8', int, 'i4', bool, 'bool', str, 'str', 'S', 'S0',
        galsim.Angle, galsim.PositionI, galsim.PositionD, galsim.Shear
    ]
    out_cat = galsim.OutputCatalog(names, types)

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

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

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

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

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

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

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

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

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

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

    # Check errors
    with assert_raises(galsim.GalSimValueError):
        out_cat.addRow((1, 2, 3))  # Wrong length
    with assert_raises(galsim.GalSimValueError):
        out_cat.write(dir='output',
                      file_name='catalog.txt',
                      file_type='invalid')