Beispiel #1
0
 def processStamp(self, obj_num, config, base, logger):
     # Note: This is just a placeholder for now.  Once we implement defects, saturation, etc.,
     # these features should be marked in the badpix mask.  For now though, all pixels = 0.
     if base['do_noise_in_stamps']:
         badpix_im = galsim.ImageS(base['current_stamp'].bounds,
                                   wcs=base['wcs'],
                                   init_value=0)
         self.scratch[obj_num] = badpix_im
Beispiel #2
0
 def processImage(self, index, obj_nums, config, base, logger):
     image = galsim.ImageS(base['image_bounds'], wcs=base['wcs'], init_value=0)
     if len(self.scratch) > 0.:
         # If we have been accumulating the variance on the stamps, build the total from them.
         # Make sure to only use the stamps for objects in this image.
         for obj_num in obj_nums:
             stamp = self.scratch[obj_num]
             b = stamp.bounds & image.bounds
             if b.isDefined():  # pragma: no branch
                 # This next line is equivalent to:
                 #    image[b] |= stamp[b]
                 # except that this doesn't work through the proxy.  We can only call methods
                 # that don't start with _.  Hence using the more verbose form here.
                 image.setSubImage(b, image.subImage(b) | stamp[b])
     else:
         # Otherwise, build the bad pixel mask here.
         # Again, nothing here yet.
         pass
     self.data[index] = image
Beispiel #3
0
    new_hdu_list = pyfits.HDUList()

    # First is the image hdu.  We use the new image that we built.
    assert se_hdu==1
    if do_print: print 'im = ',im
    if do_print: print im.array
    im.write(hdu_list=new_hdu_list)
    if do_print: print 'after write im: ',new_hdu_list
    if do_print: print new_hdu_list[0].header
    if do_print: print new_hdu_list[0].data

    # Leave the badpix image the same.
    # TODO: It might be nice to add in artifacts in the image based on the bad pixel map.
    assert se_badpix_hdu==2
    badpix_im = galsim.fits.read(hdu_list=hdu_list[se_badpix_hdu], compression='rice')
    badpix_im = galsim.ImageS(badpix_im)
    if do_print: print 'badpix_im = ',badpix_im
    if do_print: print badpix_im.array
    if do_print: print badpix_im.array.astype(numpy.int32)
    if do_print: print 'max = ',badpix_im.array.max()
    badpix_im.write(hdu_list=new_hdu_list)
    if do_print: print 'after write badpix_im: ',new_hdu_list
    if do_print: print new_hdu_list[1].header
    if do_print: print new_hdu_list[1].data

    # Rescale the weight image to have the correct mean noise level.  We still let the
    # weight map be variable, but the nosie we add is constant.  (We can change this is 
    # we want using galsim.VariableGaussianNoise.)  However, it was requested that the 
    # mean level be accurate.  So we rescale the map to have the right mean.
    assert se_wt_hdu==3
    wt_im = galsim.fits.read(hdu_list=hdu_list[se_wt_hdu], compression='rice')
Beispiel #4
0
def BuildScatteredImage(config, logger=None, image_num=0, obj_num=0,
                        make_psf_image=False, make_weight_image=False, make_badpix_image=False):
    """
    Build an image containing multiple objects placed at arbitrary locations.

    @param config              A configuration dict.
    @param logger              If given, a logger object to log progress.
    @param image_num           If given, the current image_num (default = 0)
    @param obj_num             If given, the current obj_num (default = 0)
    @param make_psf_image      Whether to make psf_image.
    @param make_weight_image   Whether to make weight_image.
    @param make_badpix_image   Whether to make badpix_image.

    @return (image, psf_image, weight_image, badpix_image)  

    Note: All 4 images are always returned in the return tuple,
          but the latter 3 might be None depending on the parameters make_*_image.    
    """
    config['seq_index'] = image_num

    ignore = [ 'random_seed', 'draw_method', 'noise', 'wcs', 'nproc' ,
               'image_pos', 'sky_pos', 'n_photons', 'wmult', 'offset',
               'stamp_size', 'stamp_xsize', 'stamp_ysize', 'gsparams' ]
    req = { 'nobjects' : int }
    opt = { 'size' : int , 'xsize' : int , 'ysize' : int , 
            'pixel_scale' : float , 'nproc' : int , 'index_convention' : str,
            'sky_level' : float , 'sky_level_pixel' : float }
    params = galsim.config.GetAllParams(
        config['image'], 'image', config, req=req, opt=opt, ignore=ignore)[0]

    nobjects = params['nobjects']

    # Special check for the size.  Either size or both xsize and ysize is required.
    if 'size' not in params:
        if 'xsize' not in params or 'ysize' not in params:
            raise AttributeError(
                "Either attribute size or both xsize and ysize required for image.type=Scattered")
        full_xsize = params['xsize']
        full_ysize = params['ysize']
    else:
        if 'xsize' in params:
            raise AttributeError(
                "Attributes xsize is invalid if size is set for image.type=Scattered")
        if 'ysize' in params:
            raise AttributeError(
                "Attributes ysize is invalid if size is set for image.type=Scattered")
        full_xsize = params['size']
        full_ysize = params['size']

    pixel_scale = params.get('pixel_scale',1.0)
    config['pixel_scale'] = pixel_scale

    convention = params.get('index_convention','1')
    _set_image_origin(config,convention)

    if 'sky_level' in params and 'sky_level_pixel' in params:
        raise AttributeError("Only one of sky_level and sky_level_pixel is allowed for "
            "noise.type = %s"%type)
    sky_level_pixel = params.get('sky_level_pixel',None)
    if 'sky_level' in params:
        sky_level_pixel = params['sky_level'] * pixel_scale**2

    # If image_xsize and image_ysize were set in config, make sure it matches.
    if ( 'image_xsize' in config and 'image_ysize' in config and
         (full_xsize != config['image_xsize'] or full_ysize != config['image_ysize']) ):
        raise ValueError(
            "Unable to reconcile saved image_xsize and image_ysize with provided "+
            "xsize=%d, ysize=%d, "%(full_xsize,full_ysize))

    if 'pix' not in config:
        config['pix'] = { 'type' : 'Pixel' , 'xw' : pixel_scale }

    # Set the rng to use for image stuff.
    if 'random_seed' in config['image']:
        #print 'random_seed = ',config['image']['random_seed']
        config['seq_index'] = obj_num+nobjects
        #print 'seq_index = ',config['seq_index']
        # Technically obj_num+nobjects will be the index of the random seed used for the next 
        # image's first object (if there is a next image).  But I don't think that will have 
        # any adverse effects.
        seed = galsim.config.ParseValue(config['image'], 'random_seed', config, int)[0]
        #print 'seed = ',seed
        rng = galsim.BaseDeviate(seed)
    else:
        rng = galsim.BaseDeviate()

    # If we have a power spectrum in config, we need to get a new realization at the start
    # of each image.
    if 'power_spectrum' in config:
        input_ps = config['input']['power_spectrum']
        if not isinstance(input_ps, list):
            input_ps = [ input_ps ]

        for i in range(len(config['power_spectrum'])):
            # PowerSpectrum can only do a square FFT, so make it the larger of the two sizes.
            if 'grid_spacing' not in input_ps[i]:
                raise AttributeError(
                    "power_spectrum.grid_spacing required for image.type=Scattered")
            grid_dx = galsim.config.ParseValue(input_ps[i], 'grid_spacing', config, float)[0]
            full_size = max(full_xsize, full_ysize)
            grid_nx = full_size * pixel_scale / grid_dx + 1
            if 'interpolant' in input_ps[i]:
                interpolant = galsim.config.ParseValue(input_ps[i], 'interpolant', config, str)[0]
            else:
                interpolant = None

            config['power_spectrum'][i].buildGrid(grid_spacing=grid_dx, ngrid=grid_nx, rng=rng,
                                                  interpolant=interpolant)
        # We don't care about the output here.  This just builds the grid, which we'll
        # access for each object using its position.

    if 'image_pos' in config['image'] and 'sky_pos' in config['image']:
        raise AttributeError("Both image_pos and sky_pos specified for Scattered image.")

    if 'image_pos' not in config['image'] and 'sky_pos' not in config['image']:
        xmin = config['image_origin'].x
        xmax = xmin + full_xsize-1
        ymin = config['image_origin'].y
        ymax = ymin + full_ysize-1
        config['image']['image_pos'] = { 
            'type' : 'XY' ,
            'x' : { 'type' : 'Random' , 'min' : xmin , 'max' : xmax },
            'y' : { 'type' : 'Random' , 'min' : ymin , 'max' : ymax }
        }

    nproc = params.get('nproc',1)

    full_image = galsim.ImageF(full_xsize, full_ysize, scale=pixel_scale)
    full_image.setOrigin(config['image_origin'])
    full_image.setZero()

    # Also define the overall image center, since we need that to calculate the position 
    # of each stamp relative to the center.
    config['image_cen'] = full_image.bounds.trueCenter()
    #print 'image_cen = ',full_image.bounds.trueCenter()

    if make_psf_image:
        full_psf_image = galsim.ImageF(full_xsize, full_ysize, scale=pixel_scale)
        full_psf_image.setOrigin(config['image_origin'])
        full_psf_image.setZero()
    else:
        full_psf_image = None

    if make_weight_image:
        full_weight_image = galsim.ImageF(full_xsize, full_ysize, scale=pixel_scale)
        full_weight_image.setOrigin(config['image_origin'])
        full_weight_image.setZero()
    else:
        full_weight_image = None

    if make_badpix_image:
        full_badpix_image = galsim.ImageS(full_xsize, full_ysize, scale=pixel_scale)
        full_badpix_image.setOrigin(config['image_origin'])
        full_badpix_image.setZero()
    else:
        full_badpix_image = None

    stamp_images = galsim.config.BuildStamps(
            nobjects=nobjects, config=config, 
            nproc=nproc, logger=logger,obj_num=obj_num,
            sky_level_pixel=sky_level_pixel, do_noise=False,
            make_psf_image=make_psf_image,
            make_weight_image=make_weight_image,
            make_badpix_image=make_badpix_image)

    images = stamp_images[0]
    psf_images = stamp_images[1]
    weight_images = stamp_images[2]
    badpix_images = stamp_images[3]

    for k in range(nobjects):
        bounds = images[k].bounds & full_image.bounds
        #print 'stamp bounds = ',images[k].bounds
        #print 'full bounds = ',full_image.bounds
        #print 'Overlap = ',bounds
        if bounds.isDefined():
            full_image[bounds] += images[k][bounds]
            if make_psf_image:
                full_psf_image[bounds] += psf_images[k][bounds]
            if make_weight_image:
                full_weight_image[bounds] += weight_images[k][bounds]
            if make_badpix_image:
                full_badpix_image[bounds] |= badpix_images[k][bounds]
        else:
            if logger:
                logger.warn(
                    "Object centered at (%d,%d) is entirely off the main image,\n"%(
                        images[k].bounds.center().x, images[k].bounds.center().y) +
                    "whose bounds are (%d,%d,%d,%d)."%(
                        full_image.bounds.xmin, full_image.bounds.xmax,
                        full_image.bounds.ymin, full_image.bounds.ymax))

    if 'noise' in config['image']:
        # Apply the noise to the full image
        draw_method = galsim.config.GetCurrentValue(config['image'],'draw_method')
        if draw_method == 'fft':
            galsim.config.AddNoiseFFT(
                full_image,full_weight_image,config['image']['noise'],config,rng,sky_level_pixel)
        elif draw_method == 'phot':
            galsim.config.AddNoisePhot(
                full_image,full_weight_image,config['image']['noise'],config,rng,sky_level_pixel)
        else:
            raise AttributeError("Unknown draw_method %s."%draw_method)

    elif sky_level_pixel:
        # If we aren't doing noise, we still need to add a non-zero sky_level
        full_image += sky_level_pixel

    return full_image, full_psf_image, full_weight_image, full_badpix_image
Beispiel #5
0
def BuildTiledImage(config, logger=None, image_num=0, obj_num=0,
                    make_psf_image=False, make_weight_image=False, make_badpix_image=False):
    """
    Build an image consisting of a tiled array of postage stamps

    @param config              A configuration dict.
    @param logger              If given, a logger object to log progress.
    @param image_num           If given, the current image_num (default = 0)
    @param obj_num             If given, the current obj_num (default = 0)
    @param make_psf_image      Whether to make psf_image.
    @param make_weight_image   Whether to make weight_image.
    @param make_badpix_image   Whether to make badpix_image.

    @return (image, psf_image, weight_image, badpix_image)  

    Note: All 4 images are always returned in the return tuple,
          but the latter 3 might be None depending on the parameters make_*_image.    
    """
    config['seq_index'] = image_num

    ignore = [ 'random_seed', 'draw_method', 'noise', 'wcs', 'nproc' ,
               'image_pos', 'n_photons', 'wmult', 'offset', 'gsparams' ]
    req = { 'nx_tiles' : int , 'ny_tiles' : int }
    opt = { 'stamp_size' : int , 'stamp_xsize' : int , 'stamp_ysize' : int ,
            'border' : int , 'xborder' : int , 'yborder' : int ,
            'pixel_scale' : float , 'nproc' : int , 'index_convention' : str,
            'sky_level' : float , 'sky_level_pixel' : float , 'order' : str }
    params = galsim.config.GetAllParams(
        config['image'], 'image', config, req=req, opt=opt, ignore=ignore)[0]

    nx_tiles = params['nx_tiles']
    ny_tiles = params['ny_tiles']
    nobjects = nx_tiles * ny_tiles

    stamp_size = params.get('stamp_size',0)
    stamp_xsize = params.get('stamp_xsize',stamp_size)
    stamp_ysize = params.get('stamp_ysize',stamp_size)

    convention = params.get('index_convention','1')
    _set_image_origin(config,convention)

    if (stamp_xsize == 0) or (stamp_ysize == 0):
        raise AttributeError(
            "Both image.stamp_xsize and image.stamp_ysize need to be defined and != 0.")

    border = params.get("border",0)
    xborder = params.get("xborder",border)
    yborder = params.get("yborder",border)

    pixel_scale = params.get('pixel_scale',1.0)
    config['pixel_scale'] = pixel_scale

    if 'sky_level' in params and 'sky_level_pixel' in params:
        raise AttributeError("Only one of sky_level and sky_level_pixel is allowed for "
            "noise.type = %s"%type)
    sky_level_pixel = params.get('sky_level_pixel',None)
    if 'sky_level' in params:
        sky_level_pixel = params['sky_level'] * pixel_scale**2

    do_noise = xborder >= 0 and yborder >= 0
    # TODO: Note: if one of these is < 0 and the other is > 0, then
    #       this will add noise to the border region.  Not exactly the 
    #       design, but I didn't bother to do the bookkeeping right to 
    #       make the borders pure 0 in that case.
    
    full_xsize = (stamp_xsize + xborder) * nx_tiles - xborder
    full_ysize = (stamp_ysize + yborder) * ny_tiles - yborder

    # If image_xsize and image_ysize were set in config, make sure it matches.
    if ( 'image_xsize' in config and 'image_ysize' in config and
         (full_xsize != config['image_xsize'] or full_ysize != config['image_ysize']) ):
        raise ValueError(
            "Unable to reconcile saved image_xsize and image_ysize with provided "+
            "nx_tiles=%d, ny_tiles=%d, "%(nx_tiles,ny_tiles) +
            "xborder=%d, yborder=%d\n"%(xborder,yborder) +
            "Calculated full_size = (%d,%d) "%(full_xsize,full_ysize)+
            "!= required (%d,%d)."%(config['image_xsize'],config['image_ysize']))

    if 'pix' not in config:
        config['pix'] = { 'type' : 'Pixel' , 'xw' : pixel_scale }

    # Set the rng to use for image stuff.
    if 'random_seed' in config['image']:
        config['seq_index'] = obj_num+nobjects
        # Technically obj_num+nobjects will be the index of the random seed used for the next 
        # image's first object (if there is a next image).  But I don't think that will have 
        # any adverse effects.
        seed = galsim.config.ParseValue(config['image'], 'random_seed', config, int)[0]
        #print 'seed = ',seed
        rng = galsim.BaseDeviate(seed)
    else:
        rng = galsim.BaseDeviate()

    # If we have a power spectrum in config, we need to get a new realization at the start
    # of each image.
    if 'power_spectrum' in config:
        input_ps = config['input']['power_spectrum']
        if not isinstance(input_ps, list):
            input_ps = [ input_ps ]

        for i in range(len(config['power_spectrum'])):
            # PowerSpectrum can only do a square FFT, so make it the larger of the two n's.
            n_tiles = max(nx_tiles, ny_tiles)
            stamp_size = max(stamp_xsize, stamp_ysize)
            if 'grid_spacing' in input_ps[i]:
                grid_dx = galsim.config.ParseValue(input_ps[i], 'grid_spacing', config, float)[0]
            else:
                grid_dx = stamp_size * pixel_scale
            if 'interpolant' in input_ps[i]:
                interpolant = galsim.config.ParseValue(input_ps[i], 'interpolant', config, str)[0]
            else:
                interpolant = None

            config['power_spectrum'][i].buildGrid(grid_spacing=grid_dx, ngrid=n_tiles, rng=rng,
                                                  interpolant=interpolant)
        # We don't care about the output here.  This just builds the grid, which we'll
        # access for each object using its position.

    # Make a list of ix,iy values according to the specified order:
    order = params.get('order','row').lower()
    if order.startswith('row'):
        ix_list = [ ix for iy in range(ny_tiles) for ix in range(nx_tiles) ]
        iy_list = [ iy for iy in range(ny_tiles) for ix in range(nx_tiles) ]
    elif order.startswith('col'):
        ix_list = [ ix for ix in range(nx_tiles) for iy in range(ny_tiles) ]
        iy_list = [ iy for ix in range(nx_tiles) for iy in range(ny_tiles) ]
    elif order.startswith('rand'):
        ix_list = [ ix for ix in range(nx_tiles) for iy in range(ny_tiles) ]
        iy_list = [ iy for ix in range(nx_tiles) for iy in range(ny_tiles) ]
        galsim.random.permute(rng, ix_list, iy_list)
        
    # Define a 'image_pos' field so the stamps can set their position appropriately in case
    # we need it for PowerSpectum or NFWHalo.
    x0 = (stamp_xsize-1)/2. + config['image_origin'].x
    y0 = (stamp_ysize-1)/2. + config['image_origin'].y
    dx = stamp_xsize + xborder
    dy = stamp_ysize + yborder
    config['image']['image_pos'] = { 
        'type' : 'XY' ,
        'x' : { 'type' : 'List',
                'items' : [ x0 + ix*dx for ix in ix_list ]
              },
        'y' : { 'type' : 'List',
                'items' : [ y0 + iy*dy for iy in iy_list ]
              }
    }

    nproc = params.get('nproc',1)

    full_image = galsim.ImageF(full_xsize, full_ysize, scale=pixel_scale)
    full_image.setOrigin(config['image_origin'])
    full_image.setZero()

    # Also define the overall image center, since we need that to calculate the position 
    # of each stamp relative to the center.
    config['image_cen'] = full_image.bounds.trueCenter()
    #print 'image_cen = ',full_image.bounds.trueCenter()

    if make_psf_image:
        full_psf_image = galsim.ImageF(full_xsize, full_ysize, scale=pixel_scale)
        full_psf_image.setOrigin(config['image_origin'])
        full_psf_image.setZero()
    else:
        full_psf_image = None

    if make_weight_image:
        full_weight_image = galsim.ImageF(full_xsize, full_ysize, scale=pixel_scale)
        full_weight_image.setOrigin(config['image_origin'])
        full_weight_image.setZero()
    else:
        full_weight_image = None

    if make_badpix_image:
        full_badpix_image = galsim.ImageS(full_xsize, full_ysize, scale=pixel_scale)
        full_badpix_image.setOrigin(config['image_origin'])
        full_badpix_image.setZero()
    else:
        full_badpix_image = None

    stamp_images = galsim.config.BuildStamps(
            nobjects=nobjects, config=config,
            nproc=nproc, logger=logger, obj_num=obj_num,
            xsize=stamp_xsize, ysize=stamp_ysize,
            sky_level_pixel=sky_level_pixel, do_noise=do_noise,
            make_psf_image=make_psf_image,
            make_weight_image=make_weight_image,
            make_badpix_image=make_badpix_image)

    images = stamp_images[0]
    psf_images = stamp_images[1]
    weight_images = stamp_images[2]
    badpix_images = stamp_images[3]

    for k in range(nobjects):
        #print 'full bounds = ',full_image.bounds
        #print 'stamp bounds = ',images[k].bounds
        assert full_image.bounds.includes(images[k].bounds)
        b = images[k].bounds
        full_image[b] += images[k]
        if make_psf_image:
            full_psf_image[b] += psf_images[k]
        if make_weight_image:
            full_weight_image[b] += weight_images[k]
        if make_badpix_image:
            full_badpix_image[b] |= badpix_images[k]

    if not do_noise:
        if 'noise' in config['image']:
            # If we didn't apply noise in each stamp, then we need to apply it now.
            draw_method = galsim.config.GetCurrentValue(config['image'],'draw_method')
            if draw_method == 'fft':
                galsim.config.AddNoiseFFT(
                    full_image,full_weight_image,config['image']['noise'],config,rng,
                    sky_level_pixel)
            elif draw_method == 'phot':
                galsim.config.AddNoisePhot(
                    full_image,full_weight_image,config['image']['noise'],config,rng,
                    sky_level_pixel)
            else:
                raise AttributeError("Unknown draw_method %s."%draw_method)
        elif sky_level_pixel:
            # If we aren't doing noise, we still need to add a non-zero sky_level
            full_image += sky_level_pixel

    return full_image, full_psf_image, full_weight_image, full_badpix_image
Beispiel #6
0
    def build_file(seed, file_name, mass):
        """A function that does all the work to build a single file.
           Returns the total time taken.
        """
        t1 = time.time()

        full_image = galsim.ImageF(image_size, image_size)
        full_image.setScale(pixel_scale)

        # The weight image will hold the inverse variance for each pixel.
        weight_image = galsim.ImageF(image_size, image_size)
        weight_image.setScale(pixel_scale)

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

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

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

        for k in range(nobj):

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

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

            # We also need the position in pixels to determine where to place the postage
            # stamp on the full image.
            image_pos = pos / pixel_scale + im_center

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

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

            # The remainder will be accounted for in a shift
            dx = x_nominal - ix_nominal
            dy = y_nominal - iy_nominal

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

            # Make the PSF profile:
            psf = galsim.Kolmogorov(fwhm = psf_fwhm)

            # Determine the random values for the galaxy:
            flux = rng() * (gal_flux_max-gal_flux_min) + gal_flux_min
            hlr = rng() * (gal_hlr_max-gal_hlr_min) + gal_hlr_min
            gd = galsim.GaussianDeviate(rng, sigma = gal_eta_rms)
            eta1 = gd()  # Unlike g or e, large values of eta are valid, so no need to cutoff.
            eta2 = gd()

            # Make the galaxy profile with these values:
            gal = galsim.Exponential(half_light_radius=hlr, flux=flux)
            gal.applyShear(eta1=eta1, eta2=eta2)

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

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

            gal.applyMagnification(mu)
            gal.applyShear(shear)

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

            # Account for the non-integral portion of the position
            final.applyShift(dx*pixel_scale,dy*pixel_scale)

            # Draw the stamp image
            stamp = final.draw(dx=pixel_scale)

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

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


        # Add Poisson noise to the full image
        sky_level_pixel = sky_level * pixel_scale**2

        # Going to the next seed isn't really required, but it matches the behavior of the 
        # config parser, so doing this will result in identical output files.
        # If you didn't care about that, you could instead construct this as a continuation
        # of the last RNG from the above loop
        rng = galsim.BaseDeviate(seed+nobj)
        full_image.addNoise(galsim.PoissonNoise(rng,sky_level=sky_level_pixel))

        # For the weight image, we only want the noise from the sky.  (If we were including
        # read_noise, we'd want that as well.)  Including the Poisson noise from the objects
        # as well tends to bias fits that use this as a weight, since the model becomes
        # magnitude-dependent.
        # The variance is just sky_level_pixel.  And we want the inverse of this.
        weight_image.fill(1./sky_level_pixel)

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

        t2 = time.time()
        return t2-t1
Beispiel #7
0
def measure_shapes(xlist, ylist, file_name, wcs, noweight):
    """Given x,y positions, an image file, and the wcs, measure shapes and sizes.

    We use the HSM module from GalSim to do this.

    Returns e1, e2, size, flag.
    """

    #im = galsim.fits.read(file_name)
    #bp_im = galsim.fits.read(file_name, hdu=2)
    #wt_im = galsim.fits.read(file_name, hdu=3)
    print 'file_name = ', file_name
    with pyfits.open(file_name) as f:
        #print 'f = ',f
        #print 'len(f) = ',len(f)
        # Some DES images have bad cards.  Fix them with verify('fix') before sending to GalSim.
        for i in range(1, len(f)):
            f[i].verify('fix')
            #print 'f[i] = ',f[i]
        #print 'after verify, f = ',f
        if file_name.endswith('fz'):
            im = galsim.fits.read(hdu_list=f, hdu=1, compression='rice')
            if noweight:
                bp_im = wt_im = None
            else:
                bp_im = galsim.fits.read(hdu_list=f, hdu=2, compression='rice')
                wt_im = galsim.fits.read(hdu_list=f, hdu=3, compression='rice')
        else:
            im = galsim.fits.read(hdu_list=f, hdu=0)
            if noweight:
                bp_im = wt_im = None
            else:
                bp_im = galsim.fits.read(hdu_list=f, hdu=1)
                wt_im = galsim.fits.read(hdu_list=f, hdu=2)
        #print 'im = ',im

    if not noweight:
        # The badpix image is offset by 32768 from the true value.  Subtract it off.
        if numpy.any(bp_im.array > 32767):
            bp_im -= 32768
        # Also, convert to int16, since it isn't by default weirdly.  I think this is
        # an error in astropy's RICE algorith, since fitsio converts it correctly to uint16.
        bp_im = galsim.ImageS(bp_im)

        # Also, it seems that the weight image has negative values where it should be 0.
        # Make them 0.
        wt_im.array[wt_im.array < 0] = 0.

    # Read the background image as well.
    base_file = file_name
    if os.path.splitext(base_file)[1] == '.fz':
        base_file = os.path.splitext(base_file)[0]
    if os.path.splitext(base_file)[1] == '.fits':
        base_file = os.path.splitext(base_file)[0]
    bkg_file_name = base_file + '_bkg.fits.fz'
    print 'bkg_file_name = ', bkg_file_name
    if os.path.exists(bkg_file_name):
        #bkg_im = galsim.fits.read(bkg_file_name)
        with pyfits.open(bkg_file_name) as f:
            f[1].verify('fix')
            bkg_im = galsim.fits.read(hdu_list=f, hdu=1, compression='rice')
        im -= bkg_im  # Subtract off the sky background.
    else:
        cat_file_name = base_file + '_psfcat.fits'
        print cat_file_name
        if os.path.exists(cat_file_name):
            print 'use BACKGROUND from ', cat_file_name
            with pyfits.open(cat_file_name) as f:
                bkg = f[2].data['BACKGROUND']
            print 'bkg = ', bkg
            bkg = numpy.median(bkg)
            print 'median = ', bkg
            im -= bkg
        else:
            print 'No easy way to estimate background.  Assuming image is zero subtracted...'

    stamp_size = 48

    n_psf = len(xlist)
    e1_list = [999.] * n_psf
    e2_list = [999.] * n_psf
    s_list = [999.] * n_psf
    flag_list = [0] * n_psf
    print 'len(xlist) = ', len(xlist)

    for i in range(n_psf):
        x = xlist[i]
        y = ylist[i]
        print 'Measure shape for star at ', x, y
        b = galsim.BoundsI(
            int(x) - stamp_size / 2,
            int(x) + stamp_size / 2,
            int(y) - stamp_size / 2,
            int(y) + stamp_size / 2)
        b = b & im.bounds

        try:
            subim = im[b]
            if noweight:
                subbp = subwt = None
            else:
                subbp = bp_im[b]
                subwt = wt_im[b]
            #print 'subim = ',subim.array
            #print 'subwt = ',subwt.array
            #print 'subbp = ',subbp.array
            #shape_data = subim.FindAdaptiveMom(weight=subwt, badpix=subbp, strict=False)
            shape_data = subim.FindAdaptiveMom(weight=subwt, strict=False)
        except Exception as e:
            print 'Caught ', e
            print ' *** Bad measurement (caught exception).  Mask this one.'
            flag_list[i] = MEAS_BAD_MEASUREMENT
            continue

        #print 'shape_data = ',shape_data
        #print 'image_bounds = ',shape_data.image_bounds
        #print 'shape = ',shape_data.observed_shape
        #print 'sigma = ',shape_data.moments_sigma
        #print 'amp = ',shape_data.moments_amp
        #print 'centroid = ',shape_data.moments_centroid
        #print 'rho4 = ',shape_data.moments_rho4
        #print 'niter = ',shape_data.moments_n_iter

        if shape_data.moments_status != 0:
            print 'status = ', shape_data.moments_status
            print ' *** Bad measurement.  Mask this one.'
            flag_list[i] = MEAS_BAD_MEASUREMENT
            continue

        dx = shape_data.moments_centroid.x - x
        dy = shape_data.moments_centroid.y - y
        #print 'dcentroid = ',dx,dy
        if dx**2 + dy**2 > MAX_CENTROID_SHIFT**2:
            print ' *** Centroid shifted by ', dx, dy, '.  Mask this one.'
            flag_list[i] = MEAS_CENTROID_SHIFT
            continue

        e1 = shape_data.observed_shape.e1
        e2 = shape_data.observed_shape.e2
        s = shape_data.moments_sigma
        # Note: this is (det M)^1/4, not ((Ixx+Iyy)/2)^1/2.
        # For reference, the latter is size * (1-e^2)^-1/4
        # So, not all that different, especially for stars with e ~= 0.

        # Account for the WCS:
        #print 'wcs = ',wcs
        jac = wcs.jacobian(galsim.PositionD(x, y))
        #print 'jac = ',jac
        # ( Iuu  Iuv ) = ( dudx  dudy ) ( Ixx  Ixy ) ( dudx  dvdx )
        # ( Iuv  Ivv )   ( dvdx  dvdy ) ( Ixy  Iyy ) ( dudy  dvdy )
        M = numpy.matrix([[1 + e1, e2], [e2, 1 - e1]])
        #print 'M = ',M
        #print 'det(M) = ',numpy.linalg.det(M)
        M2 = jac.getMatrix() * M * jac.getMatrix().T
        #print 'M2 = ',M2
        #print 'det(M2) = ',numpy.linalg.det(M2)
        e1 = (M2[0, 0] - M2[1, 1]) / (M2[0, 0] + M2[1, 1])
        e2 = (2. * M2[0, 1]) / (M2[0, 0] + M2[1, 1])
        #print 's = ',s
        s *= abs(numpy.linalg.det(jac.getMatrix()))**0.5
        #print 's -> ',s
        # Now convert back to a more normal shear definition, rather than distortion.
        shear = galsim.Shear(e1=e1, e2=e2)
        e1_list[i] = shear.g1
        e2_list[i] = shear.g2
        s_list[i] = s

    return e1_list, e2_list, s_list, flag_list
Beispiel #8
0
    def readImages(self, logger=None):
        """Read in the images from the input files and return them.

        :param logger:      A logger object for logging debug info. [default: None]

        :returns: a list of galsim.Image instances
        """
        import galsim

        # Read in the images from the files
        self.images = []
        for fname in self.image_files:
            if logger:
                logger.warning("Reading image file %s",fname)
            self.images.append(galsim.fits.read(fname, hdu=self.image_hdu))

        # Either read in the weight image, or build a dummy one
        if len(self.images) == 1:
            plural = ''
        else:
            plural = 's'
        if self.weight_hdu is not None:
            if logger:
                logger.info("Reading weight image%s from hdu %d.", plural, self.weight_hdu)
            self.weight = [ galsim.fits.read(fname, hdu=self.weight_hdu)
                            for fname in self.image_files ]
            for wt in self.weight:
                if np.all(wt.array == 0):
                    logger.error("According to the weight mask in %s, all pixels have zero weight!",
                                 fname)
        elif self.noise is not None:
            if logger:
                logger.debug("Making uniform weight image%s based on noise variance = %f", plural,
                             self.noise)
            self.weight = [ galsim.ImageF(im.bounds, init_value=1./self.noise) for im in self.images ]
        else:
            if logger:
                logger.debug("Making trivial (wt==1) weight image%s", plural)
            self.weight = [ galsim.ImageF(im.bounds, init_value=1) for im in self.images ]

        # If requested, set wt=0 for any bad pixels
        if self.badpix_hdu is not None:
            if logger:
                logger.info("Reading badpix image%s from hdu %d.", plural, self.badpix_hdu)
            for fname, wt in zip(self.image_files, self.weight):
                badpix = galsim.fits.read(fname, hdu=self.badpix_hdu)
                # The badpix image may be offset by 32768 from the true value.
                # If so, subtract it off.
                if np.any(badpix.array > 32767):
                    if logger:
                        logger.debug('min(badpix) = %s',np.min(badpix.array))
                        logger.debug('max(badpix) = %s',np.max(badpix.array))
                        logger.debug("subtracting 32768 from all values in badpix image")
                    badpix -= 32768
                if np.any(badpix.array < -32767):
                    if logger:
                        logger.debug('min(badpix) = %s',np.min(badpix.array))
                        logger.debug('max(badpix) = %s',np.max(badpix.array))
                        logger.debug("adding 32768 to all values in badpix image")
                    badpix += 32768
                # Also, convert to int16, in case it isn't by default.
                badpix = galsim.ImageS(badpix)
                if np.all(badpix.array != 0):
                    logger.error("According to the bad pixel array in %s, all pixels are masked!",
                                 fname)
                wt.array[badpix.array != 0] = 0
Beispiel #9
0
def BuildSingleStamp(config,
                     xsize=0,
                     ysize=0,
                     obj_num=0,
                     do_noise=True,
                     logger=None,
                     make_psf_image=False,
                     make_weight_image=False,
                     make_badpix_image=False):
    """
    Build a single image using the given config file

    @param config           A configuration dict.
    @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 obj_num          If given, the current obj_num [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]
    @param make_psf_image   Whether to make psf_image. [default: False]
    @param make_weight_image  Whether to make weight_image. [default: False]
    @param make_badpix_image  Whether to make badpix_image. [default: False]

    @returns the tuple (image, psf_image, weight_image, badpix_image, current_var, time)
    """
    import time
    t1 = time.time()

    # For everything except random_seed, the default key is obj_num_in_file
    config['index_key'] = 'obj_num_in_file'
    config['obj_num'] = obj_num

    # Initialize the random number generator we will be using.
    if 'random_seed' in config['image']:
        config['index_key'] = 'obj_num'
        seed = galsim.config.ParseValue(config['image'], 'random_seed', config,
                                        int)[0]
        config['index_key'] = 'obj_num_in_file'
        if logger:
            logger.debug('obj %d: seed = %d', obj_num, seed)
        rng = galsim.BaseDeviate(seed)
    else:
        rng = galsim.BaseDeviate()

    # Store the rng in the config for use by BuildGSObject function.
    config['rng'] = rng
    if 'gd' in config:
        del config['gd']  # In case it was set.

    if 'image' in config and 'retry_failures' in config['image']:
        ntries = galsim.config.ParseValue(config['image'], '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
    else:
        ntries = 1

    for itry in range(ntries):

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

            # Determine the size of this stamp
            if not xsize:
                if 'stamp_xsize' in config['image']:
                    xsize = galsim.config.ParseValue(config['image'],
                                                     'stamp_xsize', config,
                                                     int)[0]
                elif 'stamp_size' in config['image']:
                    xsize = galsim.config.ParseValue(config['image'],
                                                     'stamp_size', config,
                                                     int)[0]
            if not ysize:
                if 'stamp_ysize' in config['image']:
                    ysize = galsim.config.ParseValue(config['image'],
                                                     'stamp_ysize', config,
                                                     int)[0]
                elif 'stamp_size' in config['image']:
                    ysize = galsim.config.ParseValue(config['image'],
                                                     'stamp_size', config,
                                                     int)[0]
            if False:
                logger.debug('obj %d: xsize,ysize = %d,%d', obj_num, xsize,
                             ysize)
            if xsize: config['stamp_xsize'] = xsize
            if ysize: config['stamp_ysize'] = ysize

            # Determine where this object is going to go:
            if 'image_pos' in config['image'] and 'world_pos' in config[
                    'image']:
                image_pos = galsim.config.ParseValue(config['image'],
                                                     'image_pos', config,
                                                     galsim.PositionD)[0]
                world_pos = galsim.config.ParseValue(config['image'],
                                                     'world_pos', config,
                                                     galsim.PositionD)[0]

            elif 'image_pos' in config['image']:
                image_pos = galsim.config.ParseValue(config['image'],
                                                     'image_pos', config,
                                                     galsim.PositionD)[0]
                # 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' in config['image']:
                world_pos = galsim.config.ParseValue(config['image'],
                                                     'world_pos', config,
                                                     galsim.PositionD)[0]
                # Calculate and save the position relative to the image center
                image_pos = config['wcs'].toImage(world_pos)

            else:
                image_pos = None
                world_pos = None

            # Save these values for possible use in Evals or other modules
            if image_pos is not None:
                config['image_pos'] = image_pos
                if logger:
                    logger.debug('obj %d: image_pos = %s', obj_num,
                                 str(config['image_pos']))
            if world_pos is not None:
                config['world_pos'] = world_pos
                if logger:
                    logger.debug('obj %d: world_pos = %s', obj_num,
                                 str(config['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
                if False:
                    logger.debug('obj %d: nominal pos = %f,%f', obj_num,
                                 nominal_x, nominal_y)

                icenter = galsim.PositionI(int(math.floor(nominal_x + 0.5)),
                                           int(math.floor(nominal_y + 0.5)))
                if False:
                    logger.debug('obj %d: nominal icenter = %s', obj_num,
                                 str(icenter))
                offset = galsim.PositionD(nominal_x - icenter.x,
                                          nominal_y - icenter.y)
                if False:
                    logger.debug('obj %d: offset = %s', obj_num, str(offset))

            else:
                icenter = None
                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.)
                if False:
                    logger.debug('obj %d: no offset', obj_num)

            gsparams = {}
            if 'gsparams' in config['image']:
                gsparams = galsim.config.UpdateGSParams(
                    gsparams, config['image']['gsparams'], 'gsparams', config)

            skip = False
            try:
                t4 = t3 = t2 = t1  # in case we throw.

                psf = BuildPSF(config, logger, gsparams)
                t2 = time.time()

                gal = BuildGal(config, logger, gsparams)
                t4 = time.time()

                # Check that we have at least gal or psf.
                if not (gal or psf):
                    raise AttributeError(
                        "At least one of gal or psf must be specified in config."
                    )

            except galsim.config.gsobject.SkipThisObject, e:
                if logger:
                    logger.debug('obj %d: Caught SkipThisObject: e = %s',
                                 obj_num, e.msg)
                    if e.msg:
                        # If there is a message, upgrade to info level
                        logger.info('Skipping object %d: %s', obj_num, e.msg)
                skip = True

            if not skip and 'offset' in config['image']:
                offset1 = galsim.config.ParseValue(config['image'], 'offset',
                                                   config, galsim.PositionD)[0]
                offset += offset1

            if 'image' in config and 'draw_method' in config['image']:
                method = galsim.config.ParseValue(config['image'],
                                                  '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)

            if skip:
                if xsize and ysize:
                    # If the size is set, we need to do something reasonable to return this size.
                    im = galsim.ImageF(xsize, ysize)
                    im.setOrigin(config['image_origin'])
                    im.setZero()
                    if do_noise:
                        galsim.config.AddNoise(config, 'skip', im, weight_im,
                                               current_var, logger)
                else:
                    # Otherwise, we don't set the bounds, so it will be noticed as invalid upstream.
                    im = galsim.ImageF()

                if make_weight_image:
                    weight_im = galsim.ImageF(im.bounds, wcs=im.wcs)
                    weight_im.setZero()
                else:
                    weight_im = None
                current_var = 0

            else:
                im, current_var = DrawStamp(psf, gal, config, xsize, ysize,
                                            offset, method)
                if icenter:
                    im.setCenter(icenter.x, icenter.y)
                if make_weight_image:
                    weight_im = galsim.ImageF(im.bounds, wcs=im.wcs)
                    weight_im.setZero()
                else:
                    weight_im = None
                if do_noise:
                    galsim.config.AddNoise(config, method, im, weight_im,
                                           current_var, logger)

            if make_badpix_image:
                badpix_im = galsim.ImageS(im.bounds, wcs=im.wcs)
                badpix_im.setZero()
            else:
                badpix_im = None

            t5 = time.time()

            if make_psf_image:
                psf_im = DrawPSFStamp(psf, config, im.bounds, offset, method)
                if ('output' in config and 'psf' in config['output']
                        and 'signal_to_noise' in config['output']['psf']
                        and 'noise' in config['image']):
                    galsim.config.AddNoise(config,
                                           'fft',
                                           psf_im,
                                           None,
                                           0,
                                           logger,
                                           add_sky=False)
            else:
                psf_im = None

            t6 = time.time()

            if logger:
                logger.debug('obj %d: Times: %f, %f, %f, %f, %f', obj_num,
                             t2 - t1, t3 - t2, t4 - t3, t5 - t4, t6 - t5)

        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)
                # Need to remove the "current_val"s from the config dict.  Otherwise,
                # the value generators will do a quick return with the cached value.
                galsim.config.process.RemoveCurrent(config, keep_safe=True)
                continue
Beispiel #10
0
            weight_im.setScale(im.scale)
            weight_im.setZero()
        else:
            weight_im = None
        if do_noise:
            if 'noise' in config['image']:
                AddNoisePhot(im,weight_im,config['image']['noise'],config,rng,sky_level_pixel,
                             logger)
            elif sky_level_pixel:
                im += sky_level_pixel

    else:
        raise AttributeError("Unknown draw_method %s."%draw_method)

    if make_badpix_image:
        badpix_im = galsim.ImageS(im.bounds)
        badpix_im.setScale(im.scale)
        badpix_im.setZero()
    else:
        badpix_im = None

    t5 = time.time()

    if make_psf_image:
        psf_im = DrawPSFStamp(psf,pix,config,im.bounds,final_shift)
    else:
        psf_im = None

    t6 = time.time()

    if logger:
Beispiel #11
0
def test_celestial():
    """Test using a (realistic) CelestialWCS for the main image.
    """

    # Make a CelestialWCS.  The simplest kind to make from scratch is a TanWCS.
    affine = galsim.AffineTransform(0.26, -0.02, 0.03, 0.28,
                                    world_origin=galsim.PositionD(912.4, -833.1))
    ra = 13.2343 * galsim.hours
    dec = -39.8484 * galsim.degrees
    pointing = galsim.CelestialCoord(ra,dec)
    wcs = galsim.TanWCS(affine, world_origin=pointing)
    print('wcs = ',wcs)

    # Start with a larger image from which we will cut out the postage stamp
    full_image = galsim.Image(2048,2048, wcs=wcs)
    full_weight = galsim.ImageS(2048,2048, wcs=wcs, init_value=1)

    # Make a postage stamp cutout
    # This next bit is the same as we did for the EuclideanWCS
    size = 64
    image_pos = galsim.PositionD(1083.9, 617.3)
    sky_pos = wcs.toWorld(image_pos)
    if galsim.__version__ >= '2.0':
        u,v = pointing.project(sky_pos)
        field_pos = galsim.PositionD(u/galsim.arcsec, v/galsim.arcsec)
    else:
        field_pos = pointing.project(sky_pos)
    icen = int(image_pos.x)
    jcen = int(image_pos.y)

    bounds = galsim.BoundsI(icen-size//2+1, icen+size//2, jcen-size//2+1, jcen+size//2)
    image = full_image[bounds]
    weight = full_weight[bounds]

    galsim.Gaussian(sigma=5).drawImage(image)
    weight += image

    # With a CelestialWCS, we need to supply a pointing
    stardata = piff.StarData(image, image_pos, weight=weight, pointing=pointing)

    # Test properties
    print('props = ',stardata.properties)
    np.testing.assert_equal(stardata['x'], image_pos.x)
    np.testing.assert_equal(stardata['y'], image_pos.y)
    np.testing.assert_equal(stardata['u'], field_pos.x)
    np.testing.assert_equal(stardata['v'], field_pos.y)
    np.testing.assert_equal(stardata['ra'], sky_pos.ra/galsim.hours)
    np.testing.assert_equal(stardata['dec'], sky_pos.dec/galsim.degrees)

    # Test access via getImage method:
    im, wt, pos = stardata.getImage()
    np.testing.assert_array_equal(im.array, image.array)
    np.testing.assert_array_equal(wt.array, weight.array)
    np.testing.assert_equal(pos, image_pos)

    # Test access via getDataVector method:
    for data, wt, u, v in np.array(stardata.getDataVector()).T:
        # u,v values should correspond to image coordinates via wcs
        uv = galsim.PositionD(u,v) + field_pos
        if galsim.__version__ >= '2.0':
            radec = pointing.deproject(uv.x * galsim.arcsec, uv.y * galsim.arcsec)
        else:
            radec = pointing.deproject(uv)
        xy = wcs.toImage(radec)
        # These should now be integers, but round in case of numerical inaccuracy.
        ix = int(round(xy.x))
        jy = int(round(xy.y))
        np.testing.assert_equal(data, image(ix,jy))
        np.testing.assert_equal(wt, weight(ix,jy))

    print("Passed tests of StarData with CelestialWCS")
Beispiel #12
0
def test_euclidean():
    """Test a slightly more complicated WCS and an object not centered at the center of the image.
    """

    # Make a non-trivial WCS
    wcs = galsim.AffineTransform(0.26, -0.02, 0.03, 0.28,
                                 world_origin=galsim.PositionD(912.4, -833.1))
    print('wcs = ',wcs)

    # Start with a larger image from which we will cut out the postage stamp
    full_image = galsim.Image(2048,2048, wcs=wcs)
    full_weight = galsim.ImageS(2048,2048, wcs=wcs, init_value=1)
    print('origin of full image is at u,v = ',full_image.wcs.toWorld(full_image.origin))
    print('center of full image is at u,v = ',full_image.wcs.toWorld(full_image.center))

    # Make a postage stamp cutout
    size = 64   # This time, use an even size.
    image_pos = galsim.PositionD(1083.9, 617.3)
    field_pos = wcs.toWorld(image_pos)
    icen = int(image_pos.x)
    jcen = int(image_pos.y)

    bounds = galsim.BoundsI(icen-size//2+1, icen+size//2, jcen-size//2+1, jcen+size//2)
    image = full_image[bounds]
    weight = full_weight[bounds]

    print('image_pos = ',image_pos)
    print('field pos (u,v) = ',field_pos)
    print('origin of ps image is at u,v = ',image.wcs.toWorld(image.origin))
    print('center of ps image is at u,v = ',image.wcs.toWorld(image.center))

    # Just draw something so it has non-trivial pixel values.
    galsim.Gaussian(sigma=5).drawImage(image)
    weight += image

    stardata = piff.StarData(image, image_pos, weight=weight)

    # Test properties
    print('props = ',stardata.properties)
    np.testing.assert_equal(stardata['x'], image_pos.x)
    np.testing.assert_equal(stardata['y'], image_pos.y)
    np.testing.assert_equal(stardata['u'], field_pos.x)
    np.testing.assert_equal(stardata['v'], field_pos.y)
    # Shouldn't matter whether we use the original wcs or the one in the postage stamp.
    np.testing.assert_equal(stardata['u'], image.wcs.toWorld(image_pos).x)
    np.testing.assert_equal(stardata['v'], image.wcs.toWorld(image_pos).y)

    # Test access via getImage method:
    im, wt, pos = stardata.getImage()
    np.testing.assert_array_equal(im.array, image.array)
    np.testing.assert_array_equal(wt.array, weight.array)
    np.testing.assert_equal(pos, image_pos)

    # Test access via getDataVector method:
    for data, wt, u, v in np.array(stardata.getDataVector()).T:
        # u,v values should correspond to image coordinates via wcs
        uv = galsim.PositionD(u,v) + field_pos
        xy = wcs.toImage(uv)
        # These should now be integers, but round in case of numerical inaccuracy.
        ix = int(round(xy.x))
        jy = int(round(xy.y))
        np.testing.assert_equal(data, image(ix,jy))
        np.testing.assert_equal(wt, weight(ix,jy))

    print("Passed tests of StarData with EuclideanWCS")
Beispiel #13
0
    def load_images(stars,
                    file_name,
                    pointing=None,
                    image_hdu=None,
                    weight_hdu=None,
                    badpix_hdu=None,
                    sky=None,
                    logger=None):
        """Load the image data into a list of Stars.

        We don't store the image data for Stars when we write them to a file, since that
        would take up a lot of space and is usually not desired.  However, we do store the
        bounds in the original image where the star was cutout, so if you want to load back in
        the original data from the image file, you can do so with this function.

        :param stars:           A list of Star instances.
        :param file_name:       The file with the image data for these stars.
        :param pointing:        The pointing direction to use. [default: None]
        :param image_hdu:       The hdu to use for the main image. [default: None, which means
                                either 0 or 1 as appropriate according to the compression.]
        :param weight_hdu:      The hdu to use for the weight image. [default: None]
        :param badpix_hdu:      The hdu to use for the bad pixel mask. [default: None]
        :param sky:             Optional sky image or float value to subtract from the main
                                image. [default: None]
        :param logger:          A logger object for logging debug info. [default: None]

        :returns: a new list of Stars with the images information loaded.
        """
        import galsim
        # TODO: This is largely copied from InputHandler.readImages.
        #       This should probably be refactored a bit to avoid the duplicated code.
        logger = galsim.config.LoggerWrapper(logger)
        logger.info("Loading image information from file %s", file_name)
        image = galsim.fits.read(file_name, hdu=image_hdu)

        if sky is not None:
            image = image - sky

        # Either read in the weight image, or build a dummy one
        if weight_hdu is None:
            logger.debug("Making trivial (wt==1) weight image")
            weight = galsim.ImageI(image.bounds, init_value=1)
        else:
            logger.info("Reading weight image from hdu %d.", weight_hdu)
            weight = galsim.fits.read(file_name, hdu=weight_hdu)
            if np.all(weight.array == 0):
                logger.error(
                    "According to the weight mask in %s, all pixels have zero weight!",
                    file_name)

        # If requested, set wt=0 for any bad pixels
        if badpix_hdu is not None:
            logger.info("Reading badpix image from hdu %d.", badpix_hdu)
            badpix = galsim.fits.read(file_name, hdu=badpix_hdu)
            # The badpix image may be offset by 32768 from the true value.
            # If so, subtract it off.
            if np.any(badpix.array > 32767):
                logger.debug('min(badpix) = %s', np.min(badpix.array))
                logger.debug('max(badpix) = %s', np.max(badpix.array))
                logger.debug(
                    "subtracting 32768 from all values in badpix image")
                badpix -= 32768
            if np.any(badpix.array < -32767):
                logger.debug('min(badpix) = %s', np.min(badpix.array))
                logger.debug('max(badpix) = %s', np.max(badpix.array))
                logger.debug("adding 32768 to all values in badpix image")
                badpix += 32768
            # Also, convert to int16, in case it isn't by default.
            badpix = galsim.ImageS(badpix)
            if np.all(badpix.array != 0):
                logger.error(
                    "According to the bad pixel array in %s, all pixels are masked!",
                    file_name)
            weight.array[badpix.array != 0] = 0

        stars = [
            Star(data=StarData(image=image[star.data.image.bounds],
                               image_pos=star.data.image_pos,
                               weight=weight[star.data.image.bounds],
                               pointing=(pointing if pointing is not None else
                                         star.data.pointing),
                               properties=star.data.properties,
                               _xyuv_set=True),
                 fit=star.fit) for star in stars
        ]
        return stars
Beispiel #14
0
def BuildScatteredImage(config, logger=None, image_num=0, obj_num=0,
                        make_psf_image=False, make_weight_image=False, make_badpix_image=False):
    """
    Build an Image containing multiple objects placed at arbitrary locations.

    @param config              A configuration dict.
    @param logger              If given, a logger object to log progress. [default None]
    @param image_num           If given, the current `image_num` [default: 0]
    @param obj_num             If given, the current `obj_num` [default: 0]
    @param make_psf_image      Whether to make `psf_image`. [default: False]
    @param make_weight_image   Whether to make `weight_image`. [default: False]
    @param make_badpix_image   Whether to make `badpix_image`. [default: False]

    @returns the tuple `(image, psf_image, weight_image, badpix_image)`.

    Note: All 4 Images are always returned in the return tuple,
          but the latter 3 might be None depending on the parameters make_*_image.    
    """
    config['index_key'] = 'image_num'
    config['image_num'] = image_num
    config['obj_num'] = obj_num

    if logger:
        logger.debug('image %d: BuildScatteredImage: image, obj = %d,%d',
                     image_num,image_num,obj_num)

    if 'random_seed' in config['image'] and not isinstance(config['image']['random_seed'],dict):
        first = galsim.config.ParseValue(config['image'], 'random_seed', config, int)[0]
        config['image']['random_seed'] = { 'type' : 'Sequence', 'first' : first }

    nobjects = GetNObjForScatteredImage(config,image_num)
    if logger:
        logger.debug('image %d: nobj = %d',image_num,nobjects)

    ignore = [ 'random_seed', 'draw_method', 'noise', 'pixel_scale', 'wcs', 'nproc',
               'sky_level', 'sky_level_pixel',
               'retry_failures', 'image_pos', 'world_pos', 'n_photons', 'wmult', 'offset', 
               'stamp_size', 'stamp_xsize', 'stamp_ysize', 'gsparams', 'nobjects' ]
    opt = { 'size' : int , 'xsize' : int , 'ysize' : int , 
            'nproc' : int , 'index_convention' : str }
    params = galsim.config.GetAllParams(
        config['image'], 'image', config, opt=opt, ignore=ignore)[0]

    # Special check for the size.  Either size or both xsize and ysize is required.
    if 'size' not in params:
        if 'xsize' not in params or 'ysize' not in params:
            raise AttributeError(
                "Either attribute size or both xsize and ysize required for image.type=Scattered")
        full_xsize = params['xsize']
        full_ysize = params['ysize']
    else:
        if 'xsize' in params:
            raise AttributeError(
                "Attributes xsize is invalid if size is set for image.type=Scattered")
        if 'ysize' in params:
            raise AttributeError(
                "Attributes ysize is invalid if size is set for image.type=Scattered")
        full_xsize = params['size']
        full_ysize = params['size']


    # If image_force_xsize and image_force_ysize were set in config, make sure it matches.
    if ( ('image_force_xsize' in config and full_xsize != config['image_force_xsize']) or
         ('image_force_ysize' in config and full_ysize != config['image_force_ysize']) ):
        raise ValueError(
            "Unable to reconcile required image xsize and ysize with provided "+
            "xsize=%d, ysize=%d, "%(full_xsize,full_ysize))
    config['image_xsize'] = full_xsize
    config['image_ysize'] = full_ysize

    convention = params.get('index_convention','1')
    _set_image_origin(config,convention)
    if logger:
        logger.debug('image %d: image_origin = %s',image_num,str(config['image_origin']))
        logger.debug('image %d: image_center = %s',image_num,str(config['image_center']))

    wcs = galsim.config.BuildWCS(config, logger)

    # Set the rng to use for image stuff.
    if 'random_seed' in config['image']:
        # Technically obj_num+nobjects will be the index of the random seed used for the next 
        # image's first object (if there is a next image).  But I don't think that will have 
        # any adverse effects.
        config['obj_num'] = obj_num + nobjects
        config['index_key'] = 'obj_num'
        seed = galsim.config.ParseValue(config['image'], 'random_seed', config, int)[0]
        config['index_key'] = 'image_num'
        if logger:
            logger.debug('image %d: seed = %d',image_num,seed)
        rng = galsim.BaseDeviate(seed)
    else:
        rng = galsim.BaseDeviate()
    config['rng'] = rng

    if 'image_pos' in config['image'] and 'world_pos' in config['image']:
        raise AttributeError("Both image_pos and world_pos specified for Scattered image.")

    if 'image_pos' not in config['image'] and 'world_pos' not in config['image']:
        xmin = config['image_origin'].x
        xmax = xmin + full_xsize-1
        ymin = config['image_origin'].y
        ymax = ymin + full_ysize-1
        config['image']['image_pos'] = { 
            'type' : 'XY' ,
            'x' : { 'type' : 'Random' , 'min' : xmin , 'max' : xmax },
            'y' : { 'type' : 'Random' , 'min' : ymin , 'max' : ymax }
        }

    nproc = params.get('nproc',1)

    full_image = galsim.ImageF(full_xsize, full_ysize)
    full_image.setOrigin(config['image_origin'])
    full_image.wcs = wcs
    full_image.setZero()

    if make_psf_image:
        full_psf_image = galsim.ImageF(full_image.bounds, wcs=wcs)
        full_psf_image.setZero()
    else:
        full_psf_image = None

    if make_weight_image:
        full_weight_image = galsim.ImageF(full_image.bounds, wcs=wcs)
        full_weight_image.setZero()
    else:
        full_weight_image = None

    if make_badpix_image:
        full_badpix_image = galsim.ImageS(full_image.bounds, wcs=wcs)
        full_badpix_image.setZero()
    else:
        full_badpix_image = None

    # Sometimes an input field needs to do something special at the start of an image.
    if 'input' in config:
        for key in [ k for k in galsim.config.valid_input_types.keys() if k in config['input'] ]:
            if galsim.config.valid_input_types[key][4]:
                assert key in config
                fields = config['input'][key]
                if not isinstance(fields, list):
                    fields = [ fields ]
                input_objs = config[key]

                for i in range(len(fields)):
                    field = fields[i]
                    input_obj = input_objs[i]
                    func = eval(galsim.config.valid_input_types[key][4])
                    func(input_obj, field, config)

    stamp_images = galsim.config.BuildStamps(
            nobjects=nobjects, config=config,
            nproc=nproc, logger=logger,obj_num=obj_num, do_noise=False,
            make_psf_image=make_psf_image,
            make_weight_image=make_weight_image,
            make_badpix_image=make_badpix_image)

    images = stamp_images[0]
    psf_images = stamp_images[1]
    weight_images = stamp_images[2]
    badpix_images = stamp_images[3]
    current_vars = stamp_images[4]

    max_current_var = 0.
    for k in range(nobjects):
        # This is our signal that the object was skipped.
        if not images[k].bounds.isDefined(): continue
        bounds = images[k].bounds & full_image.bounds
        if False:
            logger.debug('image %d: full bounds = %s',image_num,str(full_image.bounds))
            logger.debug('image %d: stamp %d bounds = %s',image_num,k,str(images[k].bounds))
            logger.debug('image %d: Overlap = %s',image_num,str(bounds))
        if bounds.isDefined():
            full_image[bounds] += images[k][bounds]
            if make_psf_image:
                full_psf_image[bounds] += psf_images[k][bounds]
            if make_weight_image:
                full_weight_image[bounds] += weight_images[k][bounds]
            if make_badpix_image:
                full_badpix_image[bounds] |= badpix_images[k][bounds]
        else:
            if logger:
                logger.warn(
                    "Object centered at (%d,%d) is entirely off the main image,\n"%(
                        images[k].bounds.center().x, images[k].bounds.center().y) +
                    "whose bounds are (%d,%d,%d,%d)."%(
                        full_image.bounds.xmin, full_image.bounds.xmax,
                        full_image.bounds.ymin, full_image.bounds.ymax))
        if current_vars[k] > max_current_var: max_current_var = current_vars[k]

    # Mark that we are no longer doing a single galaxy by deleting image_pos from config top 
    # level, so it cannot be used for things like wcs.pixelArea(image_pos).  
    if 'image_pos' in config: del config['image_pos']

    if 'noise' in config['image']:
        # Apply the noise to the full image
        draw_method = galsim.config.GetCurrentValue(config['image'],'draw_method')
        if max_current_var > 0:
            import numpy
            # Then there was whitening applied in the individual stamps.
            # But there could be a different variance in each postage stamp, so the first
            # thing we need to do is bring everything up to a common level.
            noise_image = galsim.ImageF(full_image.bounds)
            for k in range(nobjects): 
                b = images[k].bounds & full_image.bounds
                if b.isDefined(): noise_image[b] += current_vars[k]
            # Update this, since overlapping postage stamps may have led to a larger 
            # value in some pixels.
            max_current_var = numpy.max(noise_image.array)
            # Figure out how much noise we need to add to each pixel.
            noise_image = max_current_var - noise_image
            # Add it.
            full_image.addNoise(galsim.VariableGaussianNoise(rng,noise_image))
        # Now max_current_var is how much noise is in each pixel.

        config['rng'] = rng
        galsim.config.AddNoise(
            config,draw_method,full_image,full_weight_image,max_current_var,logger)

    else:
        # If we aren't doing noise, we still may need to add a non-zero sky_level.
        # The same noise function does this with the 'skip' draw method.
        galsim.config.AddNoise(
            config,'skip',full_image,full_weight_image,max_current_var,logger)

    return full_image, full_psf_image, full_weight_image, full_badpix_image
Beispiel #15
0
def BuildTiledImage(config, logger=None, image_num=0, obj_num=0,
                    make_psf_image=False, make_weight_image=False, make_badpix_image=False):
    """
    Build an Image consisting of a tiled array of postage stamps.

    @param config              A configuration dict.
    @param logger              If given, a logger object to log progress. [default: None]
    @param image_num           If given, the current `image_num`. [default: 0]
    @param obj_num             If given, the current `obj_num`. [default: 0]
    @param make_psf_image      Whether to make `psf_image`. [default: False]
    @param make_weight_image   Whether to make `weight_image`. [default: False]
    @param make_badpix_image   Whether to make `badpix_image`. [default: False]

    @returns the tuple `(image, psf_image, weight_image, badpix_image)`.

    Note: All 4 Images are always returned in the return tuple,
          but the latter 3 might be None depending on the parameters make_*_image.    
    """
    config['index_key'] = 'image_num'
    config['image_num'] = image_num
    config['obj_num'] = obj_num

    if logger:
        logger.debug('image %d: BuildTiledImage: image, obj = %d,%d',image_num,image_num,obj_num)

    if 'random_seed' in config['image'] and not isinstance(config['image']['random_seed'],dict):
        first = galsim.config.ParseValue(config['image'], 'random_seed', config, int)[0]
        config['image']['random_seed'] = { 'type' : 'Sequence', 'first' : first }

    ignore = [ 'random_seed', 'draw_method', 'noise', 'pixel_scale', 'wcs', 'nproc',
               'sky_level', 'sky_level_pixel',
               'retry_failures', 'image_pos', 'n_photons', 'wmult', 'offset', 'gsparams' ]
    req = { 'nx_tiles' : int , 'ny_tiles' : int }
    opt = { 'stamp_size' : int , 'stamp_xsize' : int , 'stamp_ysize' : int ,
            'border' : int , 'xborder' : int , 'yborder' : int ,
            'nproc' : int , 'index_convention' : str, 'order' : str }
    params = galsim.config.GetAllParams(
        config['image'], 'image', config, req=req, opt=opt, ignore=ignore)[0]

    nx_tiles = params['nx_tiles']
    ny_tiles = params['ny_tiles']
    nobjects = nx_tiles * ny_tiles
    config['nx_tiles'] = nx_tiles
    config['ny_tiles'] = ny_tiles
    if logger:
        logger.debug('image %d: n_tiles = %d, %d',image_num,nx_tiles,ny_tiles)

    stamp_size = params.get('stamp_size',0)
    stamp_xsize = params.get('stamp_xsize',stamp_size)
    stamp_ysize = params.get('stamp_ysize',stamp_size)
    config['tile_xsize'] = stamp_xsize
    config['tile_ysize'] = stamp_ysize

    if (stamp_xsize == 0) or (stamp_ysize == 0):
        raise AttributeError(
            "Both image.stamp_xsize and image.stamp_ysize need to be defined and != 0.")

    border = params.get("border",0)
    xborder = params.get("xborder",border)
    yborder = params.get("yborder",border)

    do_noise = xborder >= 0 and yborder >= 0
    # TODO: Note: if one of these is < 0 and the other is > 0, then
    #       this will add noise to the border region.  Not exactly the 
    #       design, but I didn't bother to do the bookkeeping right to 
    #       make the borders pure 0 in that case.
    
    full_xsize = (stamp_xsize + xborder) * nx_tiles - xborder
    full_ysize = (stamp_ysize + yborder) * ny_tiles - yborder

    # If image_force_xsize and image_force_ysize were set in config, make sure it matches.
    if ( ('image_force_xsize' in config and full_xsize != config['image_force_xsize']) or
         ('image_force_ysize' in config and full_ysize != config['image_force_ysize']) ):
        raise ValueError(
            "Unable to reconcile required image xsize and ysize with provided "+
            "nx_tiles=%d, ny_tiles=%d, "%(nx_tiles,ny_tiles) +
            "xborder=%d, yborder=%d\n"%(xborder,yborder) +
            "Calculated full_size = (%d,%d) "%(full_xsize,full_ysize)+
            "!= required (%d,%d)."%(config['image_force_xsize'],config['image_force_ysize']))
    config['image_xsize'] = full_xsize
    config['image_ysize'] = full_ysize
    if logger:
        logger.debug('image %d: image_size = %d, %d',image_num,full_xsize,full_ysize)

    convention = params.get('index_convention','1')
    _set_image_origin(config,convention)
    if logger:
        logger.debug('image %d: image_origin = %s',image_num,str(config['image_origin']))
        logger.debug('image %d: image_center = %s',image_num,str(config['image_center']))

    wcs = galsim.config.BuildWCS(config, logger)

    # Set the rng to use for image stuff.
    if 'random_seed' in config['image']:
        # Technically obj_num+nobjects will be the index of the random seed used for the next 
        # image's first object (if there is a next image).  But I don't think that will have 
        # any adverse effects.
        config['obj_num'] = obj_num + nobjects
        config['index_key'] = 'obj_num'
        seed = galsim.config.ParseValue(config['image'], 'random_seed', config, int)[0]
        config['index_key'] = 'image_num'
        if logger:
            logger.debug('image %d: seed = %d',image_num,seed)
        rng = galsim.BaseDeviate(seed)
    else:
        rng = galsim.BaseDeviate()
    config['rng'] = rng

    # Make a list of ix,iy values according to the specified order:
    order = params.get('order','row').lower()
    if order.startswith('row'):
        ix_list = [ ix for iy in range(ny_tiles) for ix in range(nx_tiles) ]
        iy_list = [ iy for iy in range(ny_tiles) for ix in range(nx_tiles) ]
    elif order.startswith('col'):
        ix_list = [ ix for ix in range(nx_tiles) for iy in range(ny_tiles) ]
        iy_list = [ iy for ix in range(nx_tiles) for iy in range(ny_tiles) ]
    elif order.startswith('rand'):
        ix_list = [ ix for ix in range(nx_tiles) for iy in range(ny_tiles) ]
        iy_list = [ iy for ix in range(nx_tiles) for iy in range(ny_tiles) ]
        galsim.random.permute(rng, ix_list, iy_list)
        
    # Define a 'image_pos' field so the stamps can set their position appropriately in case
    # we need it for PowerSpectum or NFWHalo.
    x0 = (stamp_xsize-1)/2. + config['image_origin'].x
    y0 = (stamp_ysize-1)/2. + config['image_origin'].y
    dx = stamp_xsize + xborder
    dy = stamp_ysize + yborder
    config['image']['image_pos'] = { 
        'type' : 'XY' ,
        'x' : { 'type' : 'List',
                'items' : [ x0 + ix*dx for ix in ix_list ]
              },
        'y' : { 'type' : 'List',
                'items' : [ y0 + iy*dy for iy in iy_list ]
              }
    }

    nproc = params.get('nproc',1)

    full_image = galsim.ImageF(full_xsize, full_ysize)
    full_image.setOrigin(config['image_origin'])
    full_image.wcs = wcs
    full_image.setZero()

    if make_psf_image:
        full_psf_image = galsim.ImageF(full_image.bounds, wcs=wcs)
        full_psf_image.setZero()
    else:
        full_psf_image = None

    if make_weight_image:
        full_weight_image = galsim.ImageF(full_image.bounds, wcs=wcs)
        full_weight_image.setZero()
    else:
        full_weight_image = None

    if make_badpix_image:
        full_badpix_image = galsim.ImageS(full_image.bounds, wcs=wcs)
        full_badpix_image.setZero()
    else:
        full_badpix_image = None

    # Sometimes an input field needs to do something special at the start of an image.
    if 'input' in config:
        for key in [ k for k in galsim.config.valid_input_types.keys() if k in config['input'] ]:
            if galsim.config.valid_input_types[key][4]:
                assert key in config
                fields = config['input'][key]
                if not isinstance(fields, list):
                    fields = [ fields ]
                input_objs = config[key]

                for i in range(len(fields)):
                    field = fields[i]
                    input_obj = input_objs[i]
                    func = eval(galsim.config.valid_input_types[key][4])
                    func(input_obj, field, config)

    stamp_images = galsim.config.BuildStamps(
            nobjects=nobjects, config=config,
            nproc=nproc, logger=logger, obj_num=obj_num,
            xsize=stamp_xsize, ysize=stamp_ysize, do_noise=do_noise,
            make_psf_image=make_psf_image,
            make_weight_image=make_weight_image,
            make_badpix_image=make_badpix_image)

    images = stamp_images[0]
    psf_images = stamp_images[1]
    weight_images = stamp_images[2]
    badpix_images = stamp_images[3]
    current_vars = stamp_images[4]

    max_current_var = 0
    for k in range(nobjects):
        # This is our signal that the object was skipped.
        if not images[k].bounds.isDefined(): continue
        if False:
            logger.debug('image %d: full bounds = %s',image_num,str(full_image.bounds))
            logger.debug('image %d: stamp %d bounds = %s',image_num,k,str(images[k].bounds))
        assert full_image.bounds.includes(images[k].bounds)
        b = images[k].bounds
        full_image[b] += images[k]
        if make_psf_image:
            full_psf_image[b] += psf_images[k]
        if make_weight_image:
            full_weight_image[b] += weight_images[k]
        if make_badpix_image:
            full_badpix_image[b] |= badpix_images[k]
        if current_vars[k] > max_current_var: max_current_var = current_vars[k]

    # Mark that we are no longer doing a single galaxy by deleting image_pos from config top 
    # level, so it cannot be used for things like wcs.pixelArea(image_pos).  
    if 'image_pos' in config: del config['image_pos']

    # If didn't do noise above in the stamps, then need to do it here.
    if not do_noise:
        if 'noise' in config['image']:
            # If we didn't apply noise in each stamp, then we need to apply it now.
            draw_method = galsim.config.GetCurrentValue(config['image'],'draw_method')

            if max_current_var > 0:
                import numpy
                # Then there was whitening applied in the individual stamps.
                # But there could be a different variance in each postage stamp, so the first
                # thing we need to do is bring everything up to a common level.
                noise_image = galsim.ImageF(full_image.bounds)
                for k in range(nobjects): noise_image[images[k].bounds] += current_vars[k]
                # Update this, since overlapping postage stamps may have led to a larger 
                # value in some pixels.
                max_current_var = numpy.max(noise_image.array)
                # Figure out how much noise we need to add to each pixel.
                noise_image = max_current_var - noise_image
                # Add it.
                full_image.addNoise(galsim.VariableGaussianNoise(rng,noise_image))
            # Now max_current_var is how much noise is in each pixel.

            config['rng'] = rng
            galsim.config.AddNoise(
                config,draw_method,full_image,full_weight_image,max_current_var,logger)

        else:
            # If we aren't doing noise, we still may need to add a non-zero sky_level.
            # The same noise function does this with the 'skip' draw method.
            galsim.config.AddNoise(
                config,'skip',full_image,full_weight_image,max_current_var,logger)

    return full_image, full_psf_image, full_weight_image, full_badpix_image
Beispiel #16
0
    def build_file(seed, file_name, mass, nobj, rng, truth_file_name, halo_id,
                   first_obj_id):
        """A function that does all the work to build a single file.
           Returns the total time taken.
        """
        t1 = time.time()

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

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

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

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

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

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

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

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

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

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

        # Assign this wcs to full_image
        full_image.wcs = wcs

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

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

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

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

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

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

        for k in range(nobj):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # Subtract the sky back off.
        full_image -= weight_image

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

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

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

        t2 = time.time()
        return t2 - t1
Beispiel #17
0
            weight_im = galsim.ImageF(im.bounds, scale=im.scale)
            weight_im.setZero()
        else:
            weight_im = None
        if do_noise:
            if 'noise' in config['image']:
                AddNoisePhot(im, weight_im, config['image']['noise'], config,
                             rng, sky_level_pixel, logger)
            elif sky_level_pixel:
                im += sky_level_pixel

    else:
        raise AttributeError("Unknown draw_method %s." % draw_method)

    if make_badpix_image:
        badpix_im = galsim.ImageS(im.bounds, scale=im.scale)
        badpix_im.setZero()
    else:
        badpix_im = None

    t5 = time.time()

    if make_psf_image:
        psf_im = DrawPSFStamp(psf, pix, config, im.bounds, final_shift)
    else:
        psf_im = None

    t6 = time.time()

    if logger:
        logger.debug('   Times: %f, %f, %f, %f, %f', t2 - t1, t3 - t2, t4 - t3,
Beispiel #18
0
def test_star():
    # Star class is a pretty thin wrapper of StarData and StarFit classes.

    # Same setup as for test_euclidean
    wcs = galsim.AffineTransform(0.26,
                                 -0.02,
                                 0.03,
                                 0.28,
                                 world_origin=galsim.PositionD(912.4, -833.1))
    size = 64
    image_pos = galsim.PositionD(1083.9, 617.3)
    field_pos = wcs.toWorld(image_pos)
    icen = int(image_pos.x + 0.5)
    jcen = int(image_pos.y + 0.5)
    bounds = galsim.BoundsI(icen - size // 2 - 1, icen + size // 2,
                            jcen - size // 2 - 1, jcen + size // 2)
    image = galsim.Image(bounds, wcs=wcs)
    weight = galsim.ImageS(bounds, wcs=wcs, init_value=1)
    galsim.Gaussian(sigma=5).drawImage(image)
    weight += image

    properties = {
        'ra': 34.1234,
        'dec': -15.567,
        'color_ri': 0.5,
        'color_iz': -0.2,
        'chipnum': 3
    }

    stardata = piff.StarData(image,
                             image_pos,
                             weight=weight,
                             properties=properties)

    # Check access via star class
    star = piff.Star(stardata,
                     None)  # fit is optional, but must be explicitly None
    for key, value in stardata.properties.items():
        np.testing.assert_equal(star[key], value)
    np.testing.assert_equal(star.image.array, image.array)
    np.testing.assert_equal(star.weight.array, weight.array)
    assert star.image_pos == image_pos
    print('field_pos = ', field_pos)
    print('star.field_pos = ', star.field_pos)
    assert star.field_pos == field_pos
    assert star.x == image_pos.x
    assert star.y == image_pos.y
    assert star.u == field_pos.x
    assert star.v == field_pos.y
    assert star.chipnum == 3
    assert star.flux == 1.
    assert star.center == (0, 0)
    assert star.is_reserve == False

    star = star.withFlux(7)
    assert star.flux == 7.
    assert star.center == (0, 0)

    star = star.withFlux(17, center=(102, 22))
    assert star.flux == 17.
    assert star.center == (102, 22)

    star = star.withFlux(center=(12, 20))
    assert star.flux == 17.
    assert star.center == (12, 20)

    star = star.withFlux(flux=2)
    assert star.flux == 2.
    assert star.center == (12, 20)

    # Test using makeTarget
    star = piff.Star.makeTarget(properties=stardata.properties,
                                image=image,
                                weight=weight)
    np.testing.assert_equal(star.data['x'], image_pos.x)
    np.testing.assert_equal(star.data['y'], image_pos.y)
    np.testing.assert_equal(star.data['u'], field_pos.x)
    np.testing.assert_equal(star.data['v'], field_pos.y)
    # Shouldn't matter whether we use the original wcs or the one in the postage stamp.
    print('image.wcs = ', image.wcs)
    print('image_pos = ', image_pos)
    print('field_pos = ', field_pos)
    print('image.wcs.toWorld(image_pos) = ', image.wcs.toWorld(image_pos))
    print('in star.data: ', star.data['u'])
    print('in star.data: ', star.data['v'])
    np.testing.assert_equal(star.data['u'], image.wcs.toWorld(image_pos).x)
    np.testing.assert_equal(star.data['v'], image.wcs.toWorld(image_pos).y)
    im, wt, pos = star.data.getImage()
    np.testing.assert_array_equal(im.array, image.array)
    np.testing.assert_array_equal(wt.array, weight.array)
    np.testing.assert_equal(pos, image_pos)

    # Some invalid parameter checks
    np.testing.assert_raises(TypeError,
                             piff.Star.makeTarget,
                             x=1,
                             properties=stardata.properties,
                             image=image,
                             weight=weight)
    np.testing.assert_raises(TypeError,
                             piff.Star.makeTarget,
                             y=1,
                             properties=stardata.properties,
                             image=image,
                             weight=weight)
    np.testing.assert_raises(TypeError,
                             piff.Star.makeTarget,
                             u=1,
                             properties=stardata.properties,
                             image=image,
                             weight=weight)
    np.testing.assert_raises(TypeError,
                             piff.Star.makeTarget,
                             v=1,
                             properties=stardata.properties,
                             image=image,
                             weight=weight)
    np.testing.assert_raises(TypeError,
                             piff.Star.makeTarget,
                             x=1,
                             image=image,
                             weight=weight)
    np.testing.assert_raises(TypeError,
                             piff.Star.makeTarget,
                             y=1,
                             image=image,
                             weight=weight)
    np.testing.assert_raises(TypeError,
                             piff.Star.makeTarget,
                             u=1,
                             image=image,
                             weight=weight)
    np.testing.assert_raises(TypeError,
                             piff.Star.makeTarget,
                             image=image,
                             weight=weight)
    np.testing.assert_raises(TypeError,
                             piff.Star.makeTarget,
                             x=1,
                             y=2,
                             scale=4,
                             wcs=image.wcs,
                             image=image,
                             weight=weight)