def _add_sky_background(self, image, uniform_deviate):
     sky_level_counts = self.sky_level_nmgy * self.image_parameters.counts_per_nmgy
     if self.include_noise:
         poisson_deviate = galsim.PoissonDeviate(uniform_deviate,
                                                 mean=sky_level_counts)
         image.addNoise(galsim.DeviateNoise(poisson_deviate))
     else:
         image.array[:] = image.array + sky_level_counts
Esempio n. 2
0
    def addNoise(self, config, base, im, rng, current_var, draw_method,
                 logger):

        # Get how much extra sky to assume from the image.noise attribute.
        sky = GetSky(base['image'], base)
        extra_sky = GetSky(config, base)
        total_sky = sky + extra_sky  # for the return value
        if isinstance(total_sky, galsim.Image):
            var = np.mean(total_sky.array)
        else:
            var = total_sky
        # (This could be zero, in which case we only add poisson noise for the object photons)

        # If we already have some variance in the image (from whitening), then we subtract this
        # much off of the sky level.  It's not precisely accurate, since the existing variance is
        # Gaussian, rather than Poisson, but it's the best we can do.
        if current_var:
            logger.debug(
                'image %d, obj %d: Target variance is %f, current variance is %f',
                base.get('image_num', 0), base.get('obj_num', 0), var,
                current_var)
            if isinstance(total_sky, galsim.Image):
                test = np.any(total_sky.image.array < current_var)
            else:
                test = (total_sky < current_var)
            if test:
                raise RuntimeError(
                    "Whitening already added more noise than the requested Poisson noise."
                )
            total_sky -= current_var
            extra_sky -= current_var

        # At this point, there is a slight difference between fft and phot. For photon shooting,
        # the galaxy already has Poisson noise, so we want to make sure not to add that again!
        if draw_method == 'phot':
            # Only add in the noise from the sky.
            if isinstance(total_sky, galsim.Image):
                noise_im = total_sky.copy()
                noise_im.addNoise(galsim.PoissonNoise(rng))
                noise_im -= total_sky
                # total_sky should now have zero mean, but with the noise of the total sky level.
                im += noise_im
            else:
                im.addNoise(
                    galsim.DeviateNoise(
                        galsim.PoissonDeviate(rng, mean=total_sky)))
                # This deviate adds a noisy version of the sky, so need to subtract the mean
                # back off.
                im -= total_sky
        else:
            im += extra_sky
            # Do the normal PoissonNoise calculation.
            im.addNoise(galsim.PoissonNoise(rng))
            im -= extra_sky

        logger.debug('image %d, obj %d: Added Poisson noise',
                     base.get('image_num', 0), base.get('obj_num', 0))
        return var
Esempio n. 3
0
    def _make_amp_image(self, amp_name):
        """
        Create the segment image for the amplier geometry specified in amp.

        Parameters
        ----------
        amp_name : str
            The amplifier name, e.g., "R22_S11_C00".
        """
        amp_info = self.camera_info.get_amp_info(amp_name)
        bbox = self.camera_info.mosaic_section(amp_info)
        full_segment = afwImage.ImageF(amp_info.getRawBBox())

        # Get the imaging segment (i.e., excluding prescan and
        # overscan regions), and fill with data from the eimage.
        imaging_segment \
            = full_segment.Factory(full_segment, amp_info.getRawDataBBox())
        data = self.eimage_data[bbox.getMinY():bbox.getMaxY() + 1,
                                bbox.getMinX():bbox.getMaxX() + 1].copy()

        # Apply flips in x and y relative to assembled eimage in order
        # to have the pixels in readout order.
        if amp_info.getRawFlipX():
            data = data[:, ::-1]
        if amp_info.getRawFlipY():
            data = data[::-1, :]
        imaging_segment.getArray()[:] = data

        # Add dark current.
        if (self.exptime > 0 and not self.config['electronics_readout']
            ['disable_readnoise_bias_darkcurrent']):
            dark_current = self.config['electronics_readout']['dark_current']
            imaging_arr = imaging_segment.getArray()
            rng = galsim.PoissonDeviate(seed=self.rng,
                                        mean=dark_current * self.exptime)
            dc_data = np.zeros(np.prod(imaging_arr.shape))
            rng.generate(dc_data)
            imaging_arr += dc_data.reshape(imaging_arr.shape)

        # Add defects.

        # Apply CTE.
        full_arr = full_segment.getArray()
        pcti = self.config['electronics_readout']['pcti']
        pcte_matrix = cte_matrix(full_arr.shape[0], pcti)
        for col in range(0, full_arr.shape[1]):
            full_arr[:, col] = np.dot(pcte_matrix, full_arr[:, col])

        scti = self.config['electronics_readout']['scti']
        scte_matrix = cte_matrix(full_arr.shape[1], scti)
        for row in range(0, full_arr.shape[0]):
            full_arr[row, :] = np.dot(scte_matrix, full_arr[row, :])

        # Convert to ADU.
        full_arr /= amp_info.getGain()

        self.amp_images[amp_name] = full_segment
Esempio n. 4
0
def allDetectorEffects(img, rng=None, exptime=None):
    """
    This utility applies all sources of noise and detector effects for WFIRST that are implemented
    in GalSim.  In terms of noise, this includes the Poisson noise due to the signal (sky +
    background), dark current, and read noise.  The detector effects that are included are
    reciprocity failure, quantization, nonlinearity, and interpixel capacitance.  It also includes
    the necessary factors of gain.  In short, the user should be able to pass in an Image with all
    sources of signal (background plus astronomical objects), and the Image will be modified to
    include all subsequent steps in the image generation process for WFIRST that are implemented in
    GalSim.

    @param img       The Image to be modified.
    @param rng       An optional galsim.BaseDeviate to use for the addition of noise.  If None, a
                     new one will be initialized.  [default: None]
    @param exptime   The exposure time, in seconds.  If None, then the WFIRST default exposure time
                     will be used.  [default: None]
    """
    # Deal appropriately with passed-in RNG, exposure time.
    if rng is None:
        rng = galsim.BaseDeviate()
    elif not isinstance(rng, galsim.BaseDeviate):
        raise TypeError(
            "The rng provided to RealGalaxy constructor is not a BaseDeviate")
    if exptime is None:
        exptime = galsim.wfirst.exptime

    # Add Poisson noise.
    poisson_noise = galsim.PoissonNoise(rng)
    img.addNoise(poisson_noise)

    # Reciprocity failure (use WFIRST routine, with the supplied exposure time).
    addReciprocityFailure(img, exptime=exptime)

    # Quantize.
    img.quantize()

    # Dark current (use exposure time).
    dark_current = galsim.wfirst.dark_current * exptime
    dark_noise = galsim.DeviateNoise(galsim.PoissonDeviate(rng, dark_current))
    img.addNoise(dark_noise)

    # Nonlinearity (use WFIRST routine).
    applyNonlinearity(img)

    # IPC (use WFIRST routine).
    applyIPC(img)

    # Read noise.
    read_noise = galsim.GaussianNoise(rng, sigma=galsim.wfirst.read_noise)
    img.addNoise(read_noise)

    # Gain.
    img /= galsim.wfirst.gain

    # Quantize.
    img.quantize()
Esempio n. 5
0
 def _add_sky_background(self, image, band_index, uniform_deviate):
     sky_level_nelec = (
         self.band_sky_level_nmgy[band_index] *
         self.image_parameters.band_nelec_per_nmgy[band_index])
     if self.include_noise:
         poisson_deviate = galsim.PoissonDeviate(uniform_deviate,
                                                 mean=sky_level_nelec)
         image.addNoise(galsim.DeviateNoise(poisson_deviate))
     else:
         image.array[:] = image.array + sky_level_nelec
Esempio n. 6
0
def _GenerateFromRandomPoisson(config, base, value_type):
    """@brief Return a random value drawn from a Poisson distribution
    """
    rng = galsim.config.GetRNG(config, base)

    req = { 'mean' : float }
    kwargs, safe = galsim.config.GetAllParams(config, base, req=req)

    mean = kwargs['mean']

    dev = galsim.PoissonDeviate(rng,mean=mean)
    val = dev()

    #print(base['obj_num'],'RandomPoisson: ',val)
    return val, False
Esempio n. 7
0
def get_bundled_photon_array(image, nphotons, nbundles_per_pix, rng):
    # A convenient way to do that is to have the fluxes of the
    # bundles be generated from a Poisson distribution.

    # Make a PhotonArray to hold the sky photons
    npix = np.prod(image.array.shape)
    nbundles = npix * nbundles_per_pix
    flux_per_bundle = np.float(nphotons) / nbundles
    #print('npix = ',npix)
    #print('nbundles = ',nbundles)
    #print('flux_per_bundle = ',flux_per_bundle)
    photon_array = galsim.PhotonArray(int(nbundles))

    # Generate the x,y values.
    xx, yy = np.meshgrid(np.arange(image.xmin, image.xmax + 1),
                         np.arange(image.ymin, image.ymax + 1))
    xx = xx.ravel()
    yy = yy.ravel()
    assert len(xx) == npix
    assert len(yy) == npix
    xx = np.repeat(xx, nbundles_per_pix)
    yy = np.repeat(yy, nbundles_per_pix)
    # If the photon_array is smaller than xx and yy,
    # randomly select the corresponding number of xy values.
    if photon_array.size() < len(xx):
        index = (np.random.permutation(np.arange(
            len(xx)))[:photon_array.size()], )
        xx = xx[index]
        yy = yy[index]
    assert len(xx) == photon_array.size()
    assert len(yy) == photon_array.size()
    galsim.random.permute(rng, xx, yy)  # Randomly reshuffle in place

    # The above values are pixel centers.  Add a random offset within each pixel.
    rng.generate(photon_array.x)  # Random values from 0..1
    photon_array.x -= 0.5
    rng.generate(photon_array.y)
    photon_array.y -= 0.5
    photon_array.x += xx
    photon_array.y += yy

    # Set the flux of the photons
    flux_pd = galsim.PoissonDeviate(rng, mean=flux_per_bundle)
    flux_pd.generate(photon_array.flux)

    return photon_array
Esempio n. 8
0
def _GenerateFromRandomPoisson(param, param_name, base, value_type):
    """@brief Return a random value drawn from a Poisson distribution
    """
    if 'rng' not in base:
        raise ValueError("No base['rng'] available for %s.type = RandomPoisson"%param_name)
    rng = base['rng']

    req = { 'mean' : float }
    kwargs, safe = GetAllParams(param, param_name, base, req=req)

    mean = kwargs['mean']

    dev = galsim.PoissonDeviate(rng,mean=mean)
    val = dev()

    #print base['obj_num'],'RandomPoisson: ',val
    return val, False
Esempio n. 9
0
def test_float_value():
    """Test various ways to generate a float value
    """
    import time
    t1 = time.time()

    config = {
        'input': {
            'catalog': [{
                'dir': 'config_input',
                'file_name': 'catalog.txt'
            }, {
                'dir': 'config_input',
                'file_name': 'catalog.fits'
            }],
            'dict': [{
                'dir': 'config_input',
                'file_name': 'dict.p'
            }, {
                'dir': 'config_input',
                'file_name': 'dict.json'
            }, {
                'dir': 'config_input',
                'file_name': 'dict.yaml'
            }]
        },
        'val1': 9.9,
        'val2': int(400),
        'str1': '8.73',
        'str2': '2.33e-9',
        'str3': '6.e-9',
        'cat1': {
            'type': 'Catalog',
            'col': 0
        },
        'cat2': {
            'type': 'Catalog',
            'col': 1
        },
        'cat3': {
            'type': 'Catalog',
            'num': 1,
            'col': 'float1'
        },
        'cat4': {
            'type': 'Catalog',
            'num': 1,
            'col': 'float2'
        },
        'ran1': {
            'type': 'Random',
            'min': 0.5,
            'max': 3
        },
        'ran2': {
            'type': 'Random',
            'min': -5,
            'max': 0
        },
        'gauss1': {
            'type': 'RandomGaussian',
            'sigma': 1
        },
        'gauss2': {
            'type': 'RandomGaussian',
            'sigma': 3,
            'mean': 4
        },
        'gauss3': {
            'type': 'RandomGaussian',
            'sigma': 1.5,
            'min': -2,
            'max': 2
        },
        'gauss4': {
            'type': 'RandomGaussian',
            'sigma': 0.5,
            'min': 0,
            'max': 0.8
        },
        'gauss5': {
            'type': 'RandomGaussian',
            'sigma': 0.3,
            'mean': 0.5,
            'min': 0,
            'max': 0.5
        },
        'dist1': {
            'type': 'RandomDistribution',
            'function': 'config_input/distribution.txt',
            'interpolant': 'linear'
        },
        'dist2': {
            'type': 'RandomDistribution',
            'function': 'config_input/distribution2.txt',
            'interpolant': 'linear'
        },
        'dist3': {
            'type': 'RandomDistribution',
            'function': 'x*x',
            'x_min': 0.,
            'x_max': 2.0
        },
        'dev1': {
            'type': 'RandomPoisson',
            'mean': 137
        },
        'dev2': {
            'type': 'RandomBinomial',
            'N': 17
        },
        'dev3': {
            'type': 'RandomBinomial',
            'N': 17,
            'p': 0.2
        },
        'dev4': {
            'type': 'RandomWeibull',
            'a': 1.7,
            'b': 4.3
        },
        'dev5': {
            'type': 'RandomGamma',
            'k': 1,
            'theta': 4
        },
        'dev6': {
            'type': 'RandomGamma',
            'k': 1.9,
            'theta': 4.1
        },
        'dev7': {
            'type': 'RandomChi2',
            'n': 17
        },
        'seq1': {
            'type': 'Sequence'
        },
        'seq2': {
            'type': 'Sequence',
            'step': 0.1
        },
        'seq3': {
            'type': 'Sequence',
            'first': 1.5,
            'step': 0.5
        },
        'seq4': {
            'type': 'Sequence',
            'first': 10,
            'step': -2
        },
        'seq5': {
            'type': 'Sequence',
            'first': 1,
            'last': 2.1,
            'repeat': 2
        },
        'list1': {
            'type': 'List',
            'items': [73, 8.9, 3.14]
        },
        'list2': {
            'type': 'List',
            'items':
            [0.6, 1.8, 2.1, 3.7, 4.3, 5.5, 6.1, 7.0, 8.6, 9.3, 10.8, 11.2],
            'index': {
                'type': 'Sequence',
                'first': 10,
                'step': -3
            }
        },
        'dict1': {
            'type': 'Dict',
            'key': 'f'
        },
        'dict2': {
            'type': 'Dict',
            'num': 1,
            'key': 'f'
        },
        'dict3': {
            'type': 'Dict',
            'num': 2,
            'key': 'f'
        },
        'dict4': {
            'type': 'Dict',
            'num': 2,
            'key': 'noise.models.1.gain'
        },
        'sum1': {
            'type': 'Sum',
            'items': [72, '2.33', {
                'type': 'Dict',
                'key': 'f'
            }]
        }
    }

    test_yaml = True
    try:
        galsim.config.ProcessInput(config)
    except:
        # We don't require PyYAML as a dependency, so if this fails, just remove the YAML dict.
        del config['input']['dict'][2]
        galsim.config.ProcessInput(config)
        test_yaml = False

    # Test direct values
    val1 = galsim.config.ParseValue(config, 'val1', config, float)[0]
    np.testing.assert_almost_equal(val1, 9.9)

    val2 = galsim.config.ParseValue(config, 'val2', config, float)[0]
    np.testing.assert_almost_equal(val2, 400)

    # Test conversions from strings
    str1 = galsim.config.ParseValue(config, 'str1', config, float)[0]
    np.testing.assert_almost_equal(str1, 8.73)

    str2 = galsim.config.ParseValue(config, 'str2', config, float)[0]
    np.testing.assert_almost_equal(str2, 2.33e-9)

    str3 = galsim.config.ParseValue(config, 'str3', config, float)[0]
    np.testing.assert_almost_equal(str3, 6.0e-9)

    # Test values read from a Catalog
    cat1 = []
    cat2 = []
    cat3 = []
    cat4 = []
    config['index_key'] = 'file_num'
    for k in range(5):
        config['file_num'] = k
        cat1.append(galsim.config.ParseValue(config, 'cat1', config, float)[0])
        cat2.append(galsim.config.ParseValue(config, 'cat2', config, float)[0])
        cat3.append(galsim.config.ParseValue(config, 'cat3', config, float)[0])
        cat4.append(galsim.config.ParseValue(config, 'cat4', config, float)[0])

    np.testing.assert_array_almost_equal(cat1,
                                         [1.234, 2.345, 3.456, 1.234, 2.345])
    np.testing.assert_array_almost_equal(cat2,
                                         [4.131, -900, 8000, 4.131, -900])
    np.testing.assert_array_almost_equal(cat3,
                                         [1.234, 2.345, 3.456, 1.234, 2.345])
    np.testing.assert_array_almost_equal(cat4,
                                         [4.131, -900, 8000, 4.131, -900])

    # Test values generated from a uniform deviate
    rng = galsim.UniformDeviate(1234)
    config['rng'] = galsim.UniformDeviate(
        1234)  # A second copy starting with the same seed.
    for k in range(6):
        config[
            'obj_num'] = k  # The Random type doesn't use obj_num, but this keeps it
        # from thinking current_val is still current.
        ran1 = galsim.config.ParseValue(config, 'ran1', config, float)[0]
        np.testing.assert_almost_equal(ran1, rng() * 2.5 + 0.5)

        ran2 = galsim.config.ParseValue(config, 'ran2', config, float)[0]
        np.testing.assert_almost_equal(ran2, rng() * 5 - 5)

    # Test values generated from a Gaussian deviate
    for k in range(6):
        config['obj_num'] = k
        gauss1 = galsim.config.ParseValue(config, 'gauss1', config, float)[0]
        gd = galsim.GaussianDeviate(rng, mean=0, sigma=1)
        np.testing.assert_almost_equal(gauss1, gd())

        gauss2 = galsim.config.ParseValue(config, 'gauss2', config, float)[0]
        gd = galsim.GaussianDeviate(rng, mean=4, sigma=3)
        np.testing.assert_almost_equal(gauss2, gd())

        gauss3 = galsim.config.ParseValue(config, 'gauss3', config, float)[0]
        gd = galsim.GaussianDeviate(rng, mean=0, sigma=1.5)
        gd_val = gd()
        while math.fabs(gd_val) > 2:
            gd_val = gd()
        np.testing.assert_almost_equal(gauss3, gd_val)

        gauss4 = galsim.config.ParseValue(config, 'gauss4', config, float)[0]
        gd = galsim.GaussianDeviate(rng, mean=0, sigma=0.5)
        gd_val = math.fabs(gd())
        while gd_val > 0.8:
            gd_val = math.fabs(gd())
        np.testing.assert_almost_equal(gauss4, gd_val)

        gauss5 = galsim.config.ParseValue(config, 'gauss5', config, float)[0]
        gd = galsim.GaussianDeviate(rng, mean=0.5, sigma=0.3)
        gd_val = gd()
        if gd_val > 0.5:
            gd_val = 1 - gd_val
        while gd_val < 0:
            gd_val = gd()
            if gd_val > 0.5:
                gd_val = 1 - gd_val
        np.testing.assert_almost_equal(gauss5, gd_val)

    # Test values generated from a distribution in a file
    dd = galsim.DistDeviate(rng,
                            function='config_input/distribution.txt',
                            interpolant='linear')
    for k in range(6):
        config['obj_num'] = k
        dist1 = galsim.config.ParseValue(config, 'dist1', config, float)[0]
        np.testing.assert_almost_equal(dist1, dd())
    dd = galsim.DistDeviate(rng,
                            function='config_input/distribution2.txt',
                            interpolant='linear')
    for k in range(6):
        config['obj_num'] = k
        dist2 = galsim.config.ParseValue(config, 'dist2', config, float)[0]
        np.testing.assert_almost_equal(dist2, dd())
    dd = galsim.DistDeviate(rng, function=lambda x: x * x, x_min=0., x_max=2.)
    for k in range(6):
        config['obj_num'] = k
        dist3 = galsim.config.ParseValue(config, 'dist3', config, float)[0]
        np.testing.assert_almost_equal(dist3, dd())

    # Test values generated from various other deviates
    for k in range(6):
        config['obj_num'] = k
        dev = galsim.PoissonDeviate(rng, mean=137)
        dev1 = galsim.config.ParseValue(config, 'dev1', config, float)[0]
        np.testing.assert_almost_equal(dev1, dev())

        dev = galsim.BinomialDeviate(rng, N=17)
        dev2 = galsim.config.ParseValue(config, 'dev2', config, float)[0]
        np.testing.assert_almost_equal(dev2, dev())

        dev = galsim.BinomialDeviate(rng, N=17, p=0.2)
        dev3 = galsim.config.ParseValue(config, 'dev3', config, float)[0]
        np.testing.assert_almost_equal(dev3, dev())

        dev = galsim.WeibullDeviate(rng, a=1.7, b=4.3)
        dev4 = galsim.config.ParseValue(config, 'dev4', config, float)[0]
        np.testing.assert_almost_equal(dev4, dev())

        dev = galsim.GammaDeviate(rng, k=1, theta=4)
        dev5 = galsim.config.ParseValue(config, 'dev5', config, float)[0]
        np.testing.assert_almost_equal(dev5, dev())

        dev = galsim.GammaDeviate(rng, k=1.9, theta=4.1)
        dev6 = galsim.config.ParseValue(config, 'dev6', config, float)[0]
        np.testing.assert_almost_equal(dev6, dev())

        dev = galsim.Chi2Deviate(rng, n=17)
        dev7 = galsim.config.ParseValue(config, 'dev7', config, float)[0]
        np.testing.assert_almost_equal(dev7, dev())

    # Test values generated from a Sequence
    seq1 = []
    seq2 = []
    seq3 = []
    seq4 = []
    seq5 = []
    config['index_key'] = 'file_num'
    for k in range(6):
        config['file_num'] = k
        seq1.append(galsim.config.ParseValue(config, 'seq1', config, float)[0])
    config['index_key'] = 'image_num'
    for k in range(6):
        config['image_num'] = k
        seq2.append(galsim.config.ParseValue(config, 'seq2', config, float)[0])
    config['index_key'] = 'obj_num'
    for k in range(6):
        config['obj_num'] = k
        seq3.append(galsim.config.ParseValue(config, 'seq3', config, float)[0])
    config['index_key'] = 'obj_num_in_file'
    config['start_obj_num'] = 10
    for k in range(6):
        config['obj_num'] = k + 10
        seq4.append(galsim.config.ParseValue(config, 'seq4', config, float)[0])
        seq5.append(galsim.config.ParseValue(config, 'seq5', config, float)[0])

    np.testing.assert_array_almost_equal(seq1, [0, 1, 2, 3, 4, 5])
    np.testing.assert_array_almost_equal(seq2, [0, 0.1, 0.2, 0.3, 0.4, 0.5])
    np.testing.assert_array_almost_equal(seq3, [1.5, 2, 2.5, 3, 3.5, 4])
    np.testing.assert_array_almost_equal(seq4, [10, 8, 6, 4, 2, 0])
    np.testing.assert_array_almost_equal(seq5, [1, 1, 2, 2, 1, 1])

    # Test values taken from a List
    list1 = []
    list2 = []
    config['index_key'] = 'obj_num'
    for k in range(5):
        config['obj_num'] = k
        list1.append(
            galsim.config.ParseValue(config, 'list1', config, float)[0])
        list2.append(
            galsim.config.ParseValue(config, 'list2', config, float)[0])

    np.testing.assert_array_almost_equal(list1, [73, 8.9, 3.14, 73, 8.9])
    np.testing.assert_array_almost_equal(list2, [10.8, 7.0, 4.3, 1.8, 10.8])

    # Test values read from a Dict
    dict = []
    dict.append(galsim.config.ParseValue(config, 'dict1', config, float)[0])
    dict.append(galsim.config.ParseValue(config, 'dict2', config, float)[0])
    if test_yaml:
        dict.append(
            galsim.config.ParseValue(config, 'dict3', config, float)[0])
        dict.append(
            galsim.config.ParseValue(config, 'dict4', config, float)[0])
    else:
        dict.append(0.1)
        dict.append(1.9)
    np.testing.assert_array_almost_equal(dict, [23.17, -17.23, 0.1, 1.9])

    sum1 = galsim.config.ParseValue(config, 'sum1', config, float)[0]
    np.testing.assert_almost_equal(sum1, 72 + 2.33 + 23.17)

    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
Esempio n. 10
0
    def addNoise(self, config, base, im, rng, current_var, draw_method,
                 logger):

        # This process goes a lot like the Poisson routine.  There are just two differences.
        # First, the Poisson noise is in electrons, not ADU, and now we allow for a gain = e-/ADU,
        # so we need to account for that properly.  Second, we also allow for an additional Gaussian
        # read noise.
        gain, read_noise, read_noise_var = self.getCCDNoiseParams(config, base)

        # Get how much extra sky to assume from the image.noise attribute.
        sky = GetSky(base['image'], base, logger)
        extra_sky = GetSky(config, base, logger)
        total_sky = sky + extra_sky  # for the return value
        if isinstance(total_sky, galsim.Image):
            var = np.mean(total_sky.array) + read_noise_var
        else:
            var = total_sky + read_noise_var

        # If we already have some variance in the image (from whitening), then we try to subtract
        # it from the read noise if possible.  If not, we subtract the rest off of the sky level.
        # It's not precisely accurate, since the existing variance is Gaussian, rather than
        # Poisson, but it's the best we can do.
        if current_var:
            logger.debug(
                'image %d, obj %d: Target variance is %f, current variance is %f',
                base.get('image_num', 0), base.get('obj_num', 0), var,
                current_var)
            read_noise_var_adu = read_noise_var / gain**2
            if isinstance(total_sky, galsim.Image):
                test = np.any(
                    total_sky.array / gain + read_noise_var_adu < current_var)
            else:
                target_var = total_sky / gain + read_noise_var_adu
                logger.debug(
                    'image %d, obj %d: Target variance is %f, current variance is %f',
                    base.get('image_num', 0), base.get('obj_num', 0),
                    target_var, current_var)
                test = target_var < current_var
            if test:
                raise galsim.GalSimConfigError(
                    "Whitening already added more noise than the requested CCD noise."
                )
            if read_noise_var_adu >= current_var:
                # First try to take away from the read_noise, since this one is actually Gaussian.
                import math
                read_noise_var -= current_var * gain**2
                read_noise = math.sqrt(read_noise_var)
            else:
                # Take read_noise down to zero, since already have at least that much already.
                current_var -= read_noise_var_adu
                read_noise = 0
                read_noise_var = 0
                # Take the rest away from the sky level
                total_sky -= current_var * gain
                extra_sky -= current_var * gain

        # At this point, there is a slight difference between fft and phot. For photon shooting,
        # the galaxy already has Poisson noise, so we want to make sure not to add that again!
        if draw_method == 'phot':
            # Add in the noise from the sky.
            if isinstance(total_sky, galsim.Image):
                noise_im = total_sky.copy()
                if gain != 1.0: noise_im *= gain
                noise_im.addNoise(galsim.PoissonNoise(rng))
                if gain != 1.0: noise_im /= gain
                noise_im -= total_sky
                # total_sky should now have zero mean, but with the noise of the total sky level.
                im += noise_im
            else:
                if gain != 1.0: im *= gain
                pd = galsim.PoissonDeviate(rng, mean=total_sky * gain)
                im.addNoise(galsim.DeviateNoise(pd))
                if gain != 1.0: im /= gain
                im -= total_sky
            # And add the read noise
            if read_noise != 0.:
                im.addNoise(galsim.GaussianNoise(rng, sigma=read_noise / gain))
        else:
            # Do the normal CCDNoise calculation.
            if isinstance(total_sky, galsim.Image):
                im += extra_sky
                im.addNoise(
                    galsim.CCDNoise(rng, gain=gain, read_noise=read_noise))
                im -= extra_sky
            else:
                im.addNoise(
                    galsim.CCDNoise(rng,
                                    gain=gain,
                                    read_noise=read_noise,
                                    sky_level=extra_sky))

        logger.debug(
            'image %d, obj %d: Added CCD noise with gain = %f, read_noise = %f',
            base.get('image_num', 0), base.get('obj_num', 0), gain, read_noise)

        return var
Esempio n. 11
0
def test_poisson():
    """Test the Poisson noise builder
    """
    scale = 0.3
    sky = 200

    config = {
        'image' : {
            'type' : 'Single',
            'random_seed' : 1234,
            'pixel_scale' : scale,
            'size' : 32,

            'noise' : {
                'type' : 'Poisson',
                'sky_level' : sky,
            }
        },
        'gal' : {
            'type' : 'Gaussian',
            'sigma' : 1.1,
            'flux' : 100,
        },
    }

    # First build by hand
    rng = galsim.BaseDeviate(1234 + 1)
    gal = galsim.Gaussian(sigma=1.1, flux=100)
    im1a = gal.drawImage(nx=32, ny=32, scale=scale)
    sky_pixel = sky * scale**2
    im1a.addNoise(galsim.PoissonNoise(rng, sky_level=sky_pixel))

    # Compare to what config builds
    im1b = galsim.config.BuildImage(config)
    np.testing.assert_equal(im1b.array, im1a.array)

    # Check noise variance
    var1 = galsim.config.CalculateNoiseVariance(config)
    np.testing.assert_equal(var1, sky_pixel)
    var2 = galsim.Image(3,3)
    galsim.config.AddNoiseVariance(config, var2)
    np.testing.assert_almost_equal(var2.array, sky_pixel)

    # Check include_obj_var=True
    var3 = galsim.Image(32,32)
    galsim.config.AddNoiseVariance(config, var3, include_obj_var=True)
    np.testing.assert_almost_equal(var3.array, sky_pixel + im1a.array)

    # Repeat using photon shooting, which needs to do something slightly different, since the
    # signal photons already have shot noise.
    rng.seed(1234 + 1)
    im2a = gal.drawImage(nx=32, ny=32, scale=scale, method='phot', rng=rng)
    # Need to add Poisson noise for the sky, but not the signal (which already has shot noise)
    im2a.addNoise(galsim.DeviateNoise(galsim.PoissonDeviate(rng, mean=sky_pixel)))
    im2a -= sky_pixel

    # Compare to what config builds
    galsim.config.RemoveCurrent(config)
    config['image']['draw_method'] = 'phot'  # Make sure it gets copied over to stamp properly.
    del config['stamp']['draw_method']
    del config['_copied_image_keys_to_stamp']
    im2b = galsim.config.BuildImage(config)
    np.testing.assert_equal(im2b.array, im2a.array)

    # Check non-trivial sky image
    galsim.config.RemoveCurrent(config)
    config['image']['sky_level'] = sky
    config['image']['wcs'] =  {
        'type' : 'UVFunction',
        'ufunc' : '0.05*x + 0.001*x**2',
        'vfunc' : '0.05*y + 0.001*y**2',
    }
    del config['image']['pixel_scale']
    del config['wcs']
    rng.seed(1234+1)
    wcs = galsim.UVFunction(ufunc='0.05*x + 0.001*x**2', vfunc='0.05*y + 0.001*y**2')
    im3a = gal.drawImage(nx=32, ny=32, wcs=wcs, method='phot', rng=rng)
    sky_im = galsim.Image(im3a.bounds, wcs=wcs)
    wcs.makeSkyImage(sky_im, sky)
    im3a += sky_im  # Add 1 copy of the raw sky image for image[sky]
    noise_im = sky_im.copy()
    noise_im *= 2.  # Now 2x because the noise includes both in image[sky] and noise[sky]
    noise_im.addNoise(galsim.PoissonNoise(rng))
    noise_im -= 2.*sky_im
    im3a += noise_im
    im3b = galsim.config.BuildImage(config)
    np.testing.assert_almost_equal(im3b.array, im3a.array, decimal=6)
Esempio n. 12
0
def AddNoisePhot(im,
                 weight_im,
                 noise,
                 base,
                 rng,
                 sky_level_pixel,
                 logger=None):
    """
    Add noise to an image according to the noise specifications in the noise dict
    appropriate for an image that has been drawn using the photon-shooting method.
    """
    if not isinstance(noise, dict):
        raise AttributeError("image.noise is not a dict.")

    if 'type' not in noise:
        noise['type'] = 'Poisson'  # Default is Poisson
    type = noise['type']

    # First add the sky noise, if provided
    if sky_level_pixel:
        im += sky_level_pixel

    if type == 'Gaussian':
        single = [{'sigma': float, 'variance': float}]
        params = galsim.config.GetAllParams(noise,
                                            'noise',
                                            base,
                                            single=single)[0]

        if 'sigma' in params:
            sigma = params['sigma']
        else:
            import math
            sigma = math.sqrt(params['variance'])
        im.addNoise(galsim.GaussianNoise(rng, sigma=sigma))

        if weight_im:
            weight_im += sigma * sigma
        if logger:
            logger.debug('   Added Gaussian noise with sigma = %f', sigma)

    elif type == 'Poisson':
        opt = {}
        if sky_level_pixel:
            opt['sky_level'] = float
            opt['sky_level_pixel'] = float
        else:
            single = [{'sky_level': float, 'sky_level_pixel': float}]
            sky_level_pixel = 0.  # Switch from None to 0.
        params = galsim.config.GetAllParams(noise,
                                            'noise',
                                            base,
                                            opt=opt,
                                            single=single)[0]
        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)
        if 'sky_level' in params:
            pixel_scale = im.scale
            sky_level_pixel += params['sky_level'] * pixel_scale**2
        if 'sky_level_pixel' in params:
            sky_level_pixel += params['sky_level_pixel']

        # We don't have an exact value for the variance in each pixel, but the drawn image
        # before adding the Poisson noise is our best guess for the variance from the
        # object's flux, so just use that for starters.
        if weight_im and include_obj_var:
            weight_im.copyFrom(im)

        # For photon shooting, galaxy already has Poisson noise, so we want
        # to make sure not to add that again!
        if sky_level_pixel != 0.:
            im.addNoise(
                galsim.DeviateNoise(
                    galsim.PoissonDeviate(rng, mean=sky_level_pixel)))
            im -= sky_level_pixel
            if weight_im:
                weight_im += sky_level_pixel

        if logger:
            logger.debug('   Added Poisson noise with sky_level_pixel = %f',
                         sky_level_pixel)

    elif type == 'CCD':
        opt = {'gain': float, 'read_noise': float}
        if sky_level_pixel:
            opt['sky_level'] = float
            opt['sky_level_pixel'] = float
        else:
            single = [{'sky_level': float, 'sky_level_pixel': float}]
            sky_level_pixel = 0.  # Switch from None to 0.
        params = galsim.config.GetAllParams(noise,
                                            'noise',
                                            base,
                                            opt=opt,
                                            single=single)[0]
        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)
        if 'sky_level' in params:
            pixel_scale = im.scale
            sky_level_pixel += params['sky_level'] * pixel_scale**2
        if 'sky_level_pixel' in params:
            sky_level_pixel += params['sky_level_pixel']
        gain = params.get('gain', 1.0)
        read_noise = params.get('read_noise', 0.0)

        # We don't have an exact value for the variance in each pixel, but the drawn image
        # before adding the Poisson noise is our best guess for the variance from the
        # object's flux, so just use that for starters.
        if weight_im and include_obj_var:
            weight_im.copyFrom(im)

        # For photon shooting, galaxy already has Poisson noise, so we want
        # to make sure not to add that again!
        if sky_level_pixel != 0.:
            if gain != 1.0: im *= gain
            im.addNoise(
                galsim.DeviateNoise(
                    galsim.PoissonDeviate(rng, mean=sky_level_pixel * gain)))
            if gain != 1.0: im /= gain
            im -= sky_level_pixel * gain
        if read_noise != 0.:
            im.addNoise(galsim.GaussianNoise(rng, sigma=read_noise))

        # Add in these effects to the weight image:
        if weight_im:
            import math
            if sky_level_pixel != 0.0 or read_noise != 0.0:
                weight_im += sky_level_pixel / gain + read_noise * read_noise
        if logger:
            logger.debug(
                '   Added CCD noise with sky_level_pixel = %f, ' +
                'gain = %f, read_noise = %f', sky_level_pixel, gain,
                read_noise)

    elif type == 'COSMOS':
        req = {'file_name': str}
        opt = {'dx_cosmos': float, 'variance': float}

        kwargs = galsim.config.GetAllParams(noise,
                                            'noise',
                                            base,
                                            req=req,
                                            opt=opt)[0]

        # Build and add the correlated noise
        cn = galsim.correlatednoise.getCOSMOSNoise(rng, **kwargs)
        im.addNoise(cn)

        # Then add the variance to the weight image, using the zero-lag correlation function value
        if weight_im:
            weight_im += cn.getVariance()
        if logger:
            logger.debug('   Added COSMOS correlated noise with variance = %f',
                         cn.getVariance())

    else:
        raise AttributeError("Invalid type %s for noise", type)
Esempio n. 13
0
def main(argv):
    # Where to find and output data.
    path, filename = os.path.split(__file__)
    outpath = os.path.abspath(os.path.join(path, "output/"))

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

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

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

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

    # In non-script code, use getLogger(__name__) at module scope instead.
    logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout)
    logger = logging.getLogger("demo13")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    logger.info('You can display the output in ds9 with a command line that looks something like:')
    logger.info('ds9 -zoom 0.5 -scale limits -500 1000 -rgb '+
                '-red output/demo13_H158.fits '+
                '-green output/demo13_J129.fits '+
                '-blue output/demo13_Y106.fits')
Esempio n. 14
0
def allDetectorEffects(img, rng=None, exptime=None, prev_exposures=[]):
    """
    This utility applies all sources of noise and detector effects for WFIRST that are implemented
    in GalSim.  In terms of noise, this includes the Poisson noise due to the signal (sky +
    background), dark current, and read noise.  The detector effects that are included are
    reciprocity failure, quantization, persistence, nonlinearity, and interpixel capacitance. It
    also includes the necessary factors of gain.  In short, the user should be able to pass in an
    Image with all sources of signal (background plus astronomical objects), and the Image will be
    modified to include all subsequent steps in the image generation process for WFIRST that are
    implemented in GalSim. However, to include the effect of persistence, the user needs to provide
    a list of up to {ncoeff} recent exposures (without the readout effects such nonlinearity and
    interpixel capacitance included) and the routine returns an updated list of up to {ncoeff}
    recent exposures.

    @param img               The Image to be modified.
    @param rng               An optional galsim.BaseDeviate to use for the addition of noise.  If
                             None, a new one will be initialized.  [default: None]
    @param exptime           The exposure time, in seconds.  If None, then the WFIRST default
                             exposure time will be used.  [default: None]
    @param prev_exposures    List of up to {ncoeff} Image instances in the order of exposures, with
                             the recent exposure being the first element. [default: []]

    @returns prev_exposures  Updated list of previous exposures containing up to {ncoeff} Image
                             instances.
    """.format(ncoeff=galsim.wfirst.persistence_coefficients)
    # Deal appropriately with passed-in RNG, exposure time.
    if rng is None:
        rng = galsim.BaseDeviate()
    elif not isinstance(rng, galsim.BaseDeviate):
        raise TypeError(
            "The rng provided to RealGalaxy constructor is not a BaseDeviate")
    if exptime is None:
        exptime = galsim.wfirst.exptime

    # Add Poisson noise.
    poisson_noise = galsim.PoissonNoise(rng)
    img.addNoise(poisson_noise)

    # Reciprocity failure (use WFIRST routine, with the supplied exposure time).
    addReciprocityFailure(img, exptime=exptime)

    # Quantize.
    img.quantize()

    # Dark current (use exposure time).
    dark_current = galsim.wfirst.dark_current * exptime
    dark_noise = galsim.DeviateNoise(galsim.PoissonDeviate(rng, dark_current))
    img.addNoise(dark_noise)

    # Persistence (use WFIRST coefficients)
    applyPersistence(img, prev_exposures)

    # Update the 'prev_exposures' queue
    if len(prev_exposures) >= len(galsim.wfirst.persistence_coefficients):
        prev_exposures.pop()
    prev_exposures.insert(0, img.copy())

    # Nonlinearity (use WFIRST routine).
    applyNonlinearity(img)

    # IPC (use WFIRST routine).
    applyIPC(img)

    # Read noise.
    read_noise = galsim.GaussianNoise(rng, sigma=galsim.wfirst.read_noise)
    img.addNoise(read_noise)

    # Gain.
    img /= galsim.wfirst.gain

    # Quantize.
    img.quantize()

    return prev_exposures
Esempio n. 15
0
    fft_image = image[galsim.BoundsI(1, nx, 1, ny)]
    phot_image = image[galsim.BoundsI(nx + 3, 2 * nx + 2, 1, ny)]
    final.drawImage(fft_image, method='fft')

    sky_level_pixel = sky_level * pixel_scale**2
    fft_image.addNoise(galsim.PoissonNoise(
        rng, sky_level=sky_level_pixel))  #add noise
    rng = galsim.UniformDeviate(random_seed + k + 1)
    rng()
    rng()
    rng()
    rng()
    final.drawImage(phot_image,method='phot',max_extra_noise=sky_level_pixel/100,\

                    rng=rng)
    pd = galsim.PoissonDeviate(rng, mean=sky_level_pixel)
    phot_image.addNoise(galsim.DeviateNoise(pd))
    phot_image -= sky_level_pixel

    imagea = image.array
    galarray1.append(imagea[0:64, 0:64])
for i in range(n):
    k += 1
    rng = galsim.UniformDeviate(random_seed + k + 1)
    flux = rng() * (gal_flux_max - gal_flux_min) + gal_flux_min
    this_gal = gal2.withFlux(flux)
    hlr = rng() * (gal_hlr_max - gal_hlr_min) + gal_hlr_min
    this_gal = this_gal.dilate(hlr)
    this_gal = this_gal.shear(e1=e10[i], e2=e20[i])  #.shear(g1=gm1,g2=gm2)
    final = galsim.Convolve([this_gal, psf])
Esempio n. 16
0
def test_poisson_noise():
    """Test Poisson random number generator
    """
    pMean = 17
    p = galsim.PoissonDeviate(testseed, mean=pMean)
    pResult = np.empty((10, 10))
    p.generate(pResult)
    noise = galsim.DeviateNoise(p)

    # Test filling an image
    noise.rng.seed(testseed)
    testimage = galsim.ImageI(10, 10)
    testimage.addNoise(galsim.DeviateNoise(p))
    np.testing.assert_array_equal(
        testimage.array,
        pResult,
        err_msg=
        'Wrong poisson random number sequence generated when applied to image.'
    )

    # The PoissonNoise version also subtracts off the mean value
    pn = galsim.PoissonNoise(galsim.BaseDeviate(testseed), sky_level=pMean)
    testimage.fill(0)
    testimage.addNoise(pn)
    np.testing.assert_array_equal(
        testimage.array,
        pResult - pMean,
        err_msg=
        'Wrong poisson random number sequence generated using PoissonNoise')

    # Test filling a single-precision image
    pn.rng.seed(testseed)
    testimage = galsim.ImageF(10, 10)
    testimage.addNoise(pn)
    np.testing.assert_array_almost_equal(
        testimage.array,
        pResult - pMean,
        precisionF,
        err_msg=
        'Wrong Poisson random number sequence generated when applied to ImageF.'
    )

    # Test filling an image with Fortran ordering
    pn.rng.seed(testseed)
    testimage = galsim.ImageD(10, 10)
    testimage.addNoise(pn)
    np.testing.assert_array_almost_equal(
        testimage.array,
        pResult - pMean,
        err_msg="Wrong Poisson noise generated for Fortran-ordered Image")

    # Check PoissonNoise variance:
    np.testing.assert_almost_equal(
        pn.getVariance(),
        pMean,
        precision,
        err_msg="PoissonNoise getVariance returns wrong variance")
    np.testing.assert_almost_equal(
        pn.sky_level,
        pMean,
        precision,
        err_msg="PoissonNoise sky_level returns wrong value")

    # Check that the noise model really does produce this variance.
    big_im = galsim.Image(2048, 2048, dtype=float)
    big_im.addNoise(pn)
    var = np.var(big_im.array)
    print('variance = ', var)
    print('getVar = ', pn.getVariance())
    np.testing.assert_almost_equal(
        var,
        pn.getVariance(),
        1,
        err_msg='Realized variance for PoissonNoise did not match getVariance()'
    )

    # Check that PoissonNoise adds to the image, not overwrites the image.
    gal = galsim.Exponential(half_light_radius=2.3, flux=0.3)
    # Note: in this case, flux/size^2 needs to be << sky_level or it will mess up the statistics.
    gal.drawImage(image=big_im)
    big_im.addNoise(pn)
    gal.withFlux(-0.3).drawImage(image=big_im, add_to_image=True)
    var = np.var(big_im.array)
    np.testing.assert_almost_equal(
        var,
        pn.getVariance(),
        1,
        err_msg='PoissonNoise wrong when already an object drawn on the image')

    # Check withVariance
    pn = pn.withVariance(9.)
    np.testing.assert_almost_equal(
        pn.getVariance(),
        9.,
        precision,
        err_msg="PoissonNoise withVariance results in wrong variance")
    np.testing.assert_almost_equal(
        pn.sky_level,
        9.,
        precision,
        err_msg="PoissonNoise withVariance results in wrong sky_level")

    # Check withScaledVariance
    pn = pn.withScaledVariance(4.)
    np.testing.assert_almost_equal(
        pn.getVariance(),
        36,
        precision,
        err_msg="PoissonNoise withScaledVariance results in wrong variance")
    np.testing.assert_almost_equal(
        pn.sky_level,
        36.,
        precision,
        err_msg="PoissonNoise withScaledVariance results in wrong sky_level")

    # Check arithmetic
    pn = pn.withVariance(0.5)
    pn2 = pn * 3
    np.testing.assert_almost_equal(
        pn2.getVariance(),
        1.5,
        precision,
        err_msg="PoissonNoise pn*3 results in wrong variance")
    np.testing.assert_almost_equal(
        pn.getVariance(),
        0.5,
        precision,
        err_msg="PoissonNoise pn*3 results in wrong variance for original pn")
    pn2 = 5 * pn
    np.testing.assert_almost_equal(
        pn2.getVariance(),
        2.5,
        precision,
        err_msg="PoissonNoise 5*pn results in wrong variance")
    np.testing.assert_almost_equal(
        pn.getVariance(),
        0.5,
        precision,
        err_msg="PoissonNoise 5*pn results in wrong variance for original pn")
    pn2 = pn / 2
    np.testing.assert_almost_equal(
        pn2.getVariance(),
        0.25,
        precision,
        err_msg="PoissonNoise pn/2 results in wrong variance")
    np.testing.assert_almost_equal(
        pn.getVariance(),
        0.5,
        precision,
        err_msg="PoissonNoise 5*pn results in wrong variance for original pn")
    pn *= 3
    np.testing.assert_almost_equal(
        pn.getVariance(),
        1.5,
        precision,
        err_msg="PoissonNoise pn*=3 results in wrong variance")
    pn /= 2
    np.testing.assert_almost_equal(
        pn.getVariance(),
        0.75,
        precision,
        err_msg="PoissonNoise pn/=2 results in wrong variance")

    # Check starting with PoissonNoise()
    pn = galsim.PoissonNoise()
    pn = pn.withVariance(9.)
    np.testing.assert_almost_equal(
        pn.getVariance(),
        9.,
        precision,
        err_msg="PoissonNoise().withVariance results in wrong variance")
    np.testing.assert_almost_equal(
        pn.sky_level,
        9.,
        precision,
        err_msg="PoissonNoise().withVariance results in wrong sky_level")
    pn = pn.withScaledVariance(4.)
    np.testing.assert_almost_equal(
        pn.getVariance(),
        36,
        precision,
        err_msg="PoissonNoise().withScaledVariance results in wrong variance")
    np.testing.assert_almost_equal(
        pn.sky_level,
        36.,
        precision,
        err_msg="PoissonNoise().withScaledVariance results in wrong sky_level")

    # Check picklability
    do_pickle(pn, lambda x: (x.rng.serialize(), x.sky_level))
    do_pickle(pn, drawNoise)
    do_pickle(pn)

    # Check copy, eq and ne
    pn = pn.withVariance(pMean)
    pn2 = galsim.PoissonNoise(pn.rng.duplicate(), pMean)
    pn3 = pn.copy()
    pn4 = pn.copy(rng=galsim.BaseDeviate(11))
    pn5 = galsim.PoissonNoise(pn.rng, 2 * pMean)
    assert pn == pn2
    assert pn == pn3
    assert pn != pn4
    assert pn != pn5
    assert pn.rng.raw() == pn2.rng.raw()
    assert pn == pn2
    assert pn == pn3
    pn.rng.raw()
    assert pn != pn2
    assert pn == pn3
Esempio n. 17
0
# This bit is also basically copied from imSim, although I've changed the logic about how
# the chunks work when bundles_per_pix > 0.  Still couldn't get it to work right though
# unless bundles_per_pix == skyCounts, which means it's not any faster to use bundling.
# I think because the photons slipping to neighbor pixels due to tree rings messes up
# the count statistics.
if bundles_per_pix == 0:
    print('using chunk_size = ', chunk_size)
    chunks = [chunk_size] * (num_photons // chunk_size)
    if num_photons % chunk_size > 0:
        chunks.append(num_photons % chunk_size)
else:
    print('bundles_per_pix = ', bundles_per_pix)
    # Each "chunk" is 1 bundle of photons.
    chunks = np.empty(bundles_per_pix)
    flux_per_chunk = float(num_photons) / bundles_per_pix
    pd = galsim.PoissonDeviate(rng, mean=flux_per_chunk)
    pd.generate(chunks)

for ichunk, nphot in enumerate(chunks):
    t0 = time.time()
    sys.stdout.write('{}  {}  {} {}  \n'.format(ichunk, nphot,
                                                image.array.min(),
                                                image.array.max()))
    sys.stdout.flush()

    if bundles_per_pix == 0:
        photon_array = get_photon_array(image, nphot, rng)
    else:
        photon_array = get_bundled_photon_array(image, nphot, 1., rng)
    waves.applyTo(photon_array)
    angles.applyTo(photon_array)
Esempio n. 18
0
def func(file_name, random_seed, pixel_scale, nx, ny, sky_level, gal_flux_min, gal_flux_max, gal_hlr_min, gal_hlr_max, gal_e_min, gal_e_max, psf_fwhm, gsparams, psf1, psf2, psf3_inner, psf3_outer, psf3, atmos, aberrations, psf4, optics,  psf5, psfs, psf_names, psf_times, psf_fft_times, psf_phot_times, gal1, gal2, gal3, gal4, bulge, disk, gal5, gals, gal_names, gal_times, gal_fft_times, gal_phot_times, setup_times, fft_times, phot_times, noise_times, k, all_fluxes_i, psf, psf_name, gal, gal_name):

    #Functioning
    #---> start here
    
    logger = logging.getLogger("demo7")

    ### this willl return the time in seconds
    t1 = time.time()

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

    ### use the random numbers for the flux
    # Generate random variates:
    flux = all_fluxes_i
    y_i = flux

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

    ### use the random numbers for the hlr
    hlr = rng() * (gal_hlr_max-gal_hlr_min) + gal_hlr_min

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

    ### use the random numbers for the ellipticity
    beta_ellip = rng() * 2*math.pi * galsim.radians
    ellip = rng() * (gal_e_max-gal_e_min) + gal_e_min
    gal_shape = galsim.Shear(e=ellip, beta=beta_ellip)

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

    ### build the final object by combinging the galaxy and psf
    final = galsim.Convolve([this_gal, psf])

    ### create the images and save them in an empty array
    image = galsim.ImageF(2*nx+2, ny, scale=pixel_scale)

    ### make a views subset of the larger image
    fft_image = image[galsim.BoundsI(1, nx, 1, ny)]
    phot_image = image[galsim.BoundsI(nx+3, 2*nx+2, 1, ny)]
    
    logger.debug('      Read in training sample galaxy and PSF from file')

    ### record time

    t2 = time.time()

    ### Draw the profile explicity through fft for the convolution
    final.drawImage(fft_image, method='fft')

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

    ### Add Poisson noise to the fft image convolution
    sky_level_pixel = sky_level * pixel_scale**2
    fft_image.addNoise(galsim.PoissonNoise(rng, sky_level=sky_level_pixel))
    ### record time
    t4 = time.time()

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

    ### repeat for photon-shooting
    final.drawImage(phot_image, method='phot', max_extra_noise=sky_level_pixel/100,
    rng=rng)

    ### record time
    t5 = time.time()

    ### For photon shooting, galaxy already has Poisson noise, so we want to make sure not to add that noise again.
    ### Thus, we just add sky noise, which is Poisson with the mean = sky_level_pixel
    pd = galsim.PoissonDeviate(rng, mean=sky_level_pixel)

    # DeviateNoise just adds the action of the given deviate to every pixel.
    phot_image.addNoise(galsim.DeviateNoise(pd))

    # For PoissonDeviate, the mean is not zero, so for a background-subtracted
    # image, we need to subtract the mean back off when we are done.
    phot_image -= sky_level_pixel

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

    ### record time
    t6 = time.time()
    #<----end here (return stuff)

    
    return image, t1, t2, t3, t4, t5, t6, k, flux, hlr, gal_shape, y_i, psfs, gals, file_name
Esempio n. 19
0
def test_resume():
    """Test that the resume option for accumulate works properly.
    """
    # Note: This test is based on a script devel/lsst/treering_skybg_check.py

    rng = galsim.UniformDeviate(314159)

    if __name__ == "__main__":
        flux_per_pixel = 40
        nx = 200
        ny = 200
        block_size = int(1.2e5)
        nrecalc = 1.e6
    else:
        flux_per_pixel = 40
        nx = 20
        ny = 20
        block_size = int(1.3e3)
        nrecalc = 1.e4

    expected_num_photons = nx * ny * flux_per_pixel
    pd = galsim.PoissonDeviate(rng, mean=expected_num_photons)
    num_photons = int(pd())  # Poisson realization of the given expected number of photons.
    #nrecalc = num_photons / 2  # Only recalc once.
    flux_per_photon = 1
    print('num_photons = ',num_photons,' .. expected = ',expected_num_photons)

    # Use treerings to make sure that aspect of the setup is preserved properly on resume
    treering_func = galsim.SiliconSensor.simple_treerings(0.5, 250.)
    treering_center = galsim.PositionD(-1000,0)
    sensor1 = galsim.SiliconSensor(rng=rng.duplicate(), nrecalc=nrecalc,
                                   treering_func=treering_func, treering_center=treering_center)
    sensor2 = galsim.SiliconSensor(rng=rng.duplicate(), nrecalc=nrecalc,
                                   treering_func=treering_func, treering_center=treering_center)
    sensor3 = galsim.SiliconSensor(rng=rng.duplicate(), nrecalc=nrecalc,
                                   treering_func=treering_func, treering_center=treering_center)

    waves = galsim.WavelengthSampler(sed = galsim.SED('1', 'nm', 'fphotons'),
                                     bandpass = galsim.Bandpass('LSST_r.dat', 'nm'),
                                     rng=rng)
    angles = galsim.FRatioAngles(1.2, 0.4, rng)

    im1 = galsim.ImageF(nx,ny)  # Will not use resume
    im2 = galsim.ImageF(nx,ny)  # Will use resume
    im3 = galsim.ImageF(nx,ny)  # Will run all photons in one pass

    t_resume = 0
    t_no_resume = 0

    all_photons = galsim.PhotonArray(num_photons)
    n_added = 0

    first = True
    while num_photons > 0:
        print(num_photons,'photons left. image min/max =',im1.array.min(),im1.array.max())
        nphot = min(block_size, num_photons)
        num_photons -= nphot

        t0 = time.time()
        photons = galsim.PhotonArray(int(nphot))
        rng.generate(photons.x) # 0..1 so far
        photons.x *= nx
        photons.x += 0.5  # Now from xmin-0.5 .. xmax+0.5
        rng.generate(photons.y)
        photons.y *= ny
        photons.y += 0.5
        photons.flux = flux_per_photon
        waves.applyTo(photons)
        angles.applyTo(photons)

        all_photons.x[n_added:n_added+nphot] = photons.x
        all_photons.y[n_added:n_added+nphot] = photons.y
        all_photons.flux[n_added:n_added+nphot] = photons.flux
        all_photons.dxdz[n_added:n_added+nphot] = photons.dxdz
        all_photons.dydz[n_added:n_added+nphot] = photons.dydz
        all_photons.wavelength[n_added:n_added+nphot] = photons.wavelength
        n_added += nphot

        t1 = time.time()
        sensor1.accumulate(photons, im1)

        t2 = time.time()
        sensor2.accumulate(photons, im2, resume = not first)
        first = False
        t3 = time.time()
        print('Times = ',t1-t0,t2-t1,t3-t2)
        t_resume += t3-t2
        t_no_resume += t2-t1

    print('max diff = ',np.max(np.abs(im1.array - im2.array)))
    print('max rel diff = ',np.max(np.abs(im1.array - im2.array)/np.abs(im2.array)))
    np.testing.assert_almost_equal(im2.array/expected_num_photons, im1.array/expected_num_photons,
                                   decimal=5)
    print('Time with resume = ',t_resume)
    print('Time without resume = ',t_no_resume)
    assert t_resume < t_no_resume

    # The resume path should be exactly the same as doing all the photons at once.
    sensor3.accumulate(all_photons, im3)
    np.testing.assert_array_equal(im2.array, im3.array)

    # If resume is used either with the wrong image or on the first call to accumulate, then
    # this should raise an exception.
    assert_raises(RuntimeError, sensor3.accumulate, all_photons, im1, resume=True)
    sensor4 = galsim.SiliconSensor(rng=rng.duplicate(), nrecalc=nrecalc,
                                   treering_func=treering_func, treering_center=treering_center)
    assert_raises(RuntimeError, sensor4.accumulate, all_photons, im1, resume=True)
Esempio n. 20
0
def test_poisson():
    """Test the Poisson noise builder
    """
    scale = 0.3
    sky = 200

    config = {
        'image' : {
            'type' : 'Single',
            'random_seed' : 1234,
            'pixel_scale' : scale,
            'size' : 32,

            'noise' : {
                'type' : 'Poisson',
                'sky_level' : sky,
            }
        },
        'gal' : {
            'type' : 'Gaussian',
            'sigma' : 1.1,
            'flux' : 100,
        },
    }

    # First build by hand
    rng = galsim.BaseDeviate(1234 + 1)
    gal = galsim.Gaussian(sigma=1.1, flux=100)
    im1a = gal.drawImage(nx=32, ny=32, scale=scale)
    sky_pixel = sky * scale**2
    im1a.addNoise(galsim.PoissonNoise(rng, sky_level=sky_pixel))

    # Compare to what config builds
    im1b = galsim.config.BuildImage(config)
    np.testing.assert_equal(im1b.array, im1a.array)

    # Check noise variance
    var1 = galsim.config.CalculateNoiseVariance(config)
    np.testing.assert_equal(var1, sky_pixel)
    var2 = galsim.Image(3,3)
    galsim.config.AddNoiseVariance(config, var2)
    np.testing.assert_almost_equal(var2.array, sky_pixel)

    # Check include_obj_var=True
    var3 = galsim.Image(32,32)
    galsim.config.AddNoiseVariance(config, var3, include_obj_var=True)
    np.testing.assert_almost_equal(var3.array, sky_pixel + im1a.array)

    # Repeat using photon shooting, which needs to do something slightly different, since the
    # signal photons already have shot noise.
    rng.seed(1234 + 1)
    im2a = gal.drawImage(nx=32, ny=32, scale=scale, method='phot', rng=rng)
    # Need to add Poisson noise for the sky, but not the signal (which already has shot noise)
    im2a.addNoise(galsim.DeviateNoise(galsim.PoissonDeviate(rng, mean=sky_pixel)))
    im2a -= sky_pixel

    # Compare to what config builds
    galsim.config.RemoveCurrent(config)
    config['image']['draw_method'] = 'phot'  # Make sure it gets copied over to stamp properly.
    del config['stamp']['draw_method']
    del config['stamp']['_done']
    im2b = galsim.config.BuildImage(config)
    np.testing.assert_equal(im2b.array, im2a.array)

    # Check non-trivial sky image
    galsim.config.RemoveCurrent(config)
    config['image']['sky_level'] = sky
    config['image']['wcs'] =  {
        'type' : 'UVFunction',
        'ufunc' : '0.05*x + 0.001*x**2',
        'vfunc' : '0.05*y + 0.001*y**2',
    }
    del config['image']['pixel_scale']
    del config['wcs']
    rng.seed(1234+1)
    wcs = galsim.UVFunction(ufunc='0.05*x + 0.001*x**2', vfunc='0.05*y + 0.001*y**2')
    im3a = gal.drawImage(nx=32, ny=32, wcs=wcs, method='phot', rng=rng)
    sky_im = galsim.Image(im3a.bounds, wcs=wcs)
    wcs.makeSkyImage(sky_im, sky)
    im3a += sky_im  # Add 1 copy of the raw sky image for image[sky]
    noise_im = sky_im.copy()
    noise_im *= 2.  # Now 2x because the noise includes both in image[sky] and noise[sky]
    noise_im.addNoise(galsim.PoissonNoise(rng))
    noise_im -= 2.*sky_im
    im3a += noise_im
    im3b = galsim.config.BuildImage(config)
    np.testing.assert_almost_equal(im3b.array, im3a.array, decimal=6)

    # With tree rings, the sky includes them as well.
    config['image']['sensor'] = {
        'type' : 'Silicon',
        'treering_func' : {
            'type' : 'File',
            'file_name' : 'tree_ring_lookup.dat',
            'amplitude' : 0.5
        },
        'treering_center' : {
            'type' : 'XY',
            'x' : 0,
            'y' : -500
        }
    }
    galsim.config.RemoveCurrent(config)
    config = galsim.config.CleanConfig(config)
    rng.seed(1234+1)
    trfunc = galsim.LookupTable.from_file('tree_ring_lookup.dat', amplitude=0.5)
    sensor = galsim.SiliconSensor(treering_func=trfunc, treering_center=galsim.PositionD(0,-500),
                                  rng=rng)
    im4a = gal.drawImage(nx=32, ny=32, wcs=wcs, method='phot', rng=rng, sensor=sensor)
    sky_im = galsim.Image(im3a.bounds, wcs=wcs)
    wcs.makeSkyImage(sky_im, sky)
    areas = sensor.calculate_pixel_areas(sky_im, use_flux=False)
    sky_im *= areas
    im4a += sky_im
    noise_im = sky_im.copy()
    noise_im *= 2.
    noise_im.addNoise(galsim.PoissonNoise(rng))
    noise_im -= 2.*sky_im
    im4a += noise_im
    im4b = galsim.config.BuildImage(config)
    np.testing.assert_almost_equal(im4b.array, im4a.array, decimal=6)

    # Can't have both sky_level and sky_level_pixel
    config['image']['noise']['sky_level_pixel'] = 2000.
    with assert_raises(galsim.GalSimConfigError):
        galsim.config.BuildImage(config)

    # Must have a valid noise type
    del config['image']['noise']['sky_level_pixel']
    config['image']['noise']['type'] = 'Invalid'
    with assert_raises(galsim.GalSimConfigError):
        galsim.config.BuildImage(config)

    # noise must be a dict
    config['image']['noise'] = 'Invalid'
    with assert_raises(galsim.GalSimConfigError):
        galsim.config.BuildImage(config)

    # Can't have signal_to_noise and  flux
    config['image']['noise'] = { 'type' : 'Poisson', 'sky_level' : sky }
    config['gal']['signal_to_noise'] = 100
    with assert_raises(galsim.GalSimConfigError):
        galsim.config.BuildImage(config)

    # This should work
    del config['gal']['flux']
    galsim.config.BuildImage(config)

    # These now hit the errors in CalculateNoiseVariance rather than AddNoise
    config['image']['noise']['type'] = 'Invalid'
    with assert_raises(galsim.GalSimConfigError):
        galsim.config.BuildImage(config)
    config['image']['noise'] = 'Invalid'
    with assert_raises(galsim.GalSimConfigError):
        galsim.config.BuildImage(config)
    del config['image']['noise']
    with assert_raises(galsim.GalSimConfigError):
        galsim.config.BuildImage(config)

    # If rather than signal_to_noise, we have an extra_weight output, then it hits
    # a different error.
    config['gal']['flux'] = 100
    del config['gal']['signal_to_noise']
    config['output'] = { 'weight' : {} }
    config['image']['noise'] = { 'type' : 'Poisson', 'sky_level' : sky }
    galsim.config.SetupExtraOutput(config)
    galsim.config.SetupConfigFileNum(config, 0, 0, 0)
    # This should work again.
    galsim.config.BuildImage(config)
    config['image']['noise']['type'] = 'Invalid'
    with assert_raises(galsim.GalSimConfigError):
        galsim.config.BuildImage(config)
    config['image']['noise'] = 'Invalid'
    with assert_raises(galsim.GalSimConfigError):
        galsim.config.BuildImage(config)
Esempio n. 21
0
def test_int_value():
    """Test various ways to generate an int value
    """
    import time
    t1 = time.time()

    config = {
        'input': {
            'catalog': [{
                'dir': 'config_input',
                'file_name': 'catalog.txt'
            }, {
                'dir': 'config_input',
                'file_name': 'catalog.fits'
            }],
            'dict': [{
                'dir': 'config_input',
                'file_name': 'dict.p'
            }, {
                'dir': 'config_input',
                'file_name': 'dict.json'
            }, {
                'dir': 'config_input',
                'file_name': 'dict.yaml'
            }]
        },
        'val1': 9,
        'val2': float(8.7),  # Reading as int will drop the fraction.
        'val3': -400.8,  # Not floor - negatives will round up.
        'str1': '8',
        'str2': '-2',
        'cat1': {
            'type': 'Catalog',
            'col': 2
        },
        'cat2': {
            'type': 'Catalog',
            'col': 3
        },
        'cat3': {
            'type': 'Catalog',
            'num': 1,
            'col': 'int1'
        },
        'cat4': {
            'type': 'Catalog',
            'num': 1,
            'col': 'int2'
        },
        'ran1': {
            'type': 'Random',
            'min': 0,
            'max': 3
        },
        'ran2': {
            'type': 'Random',
            'min': -5,
            'max': 10
        },
        'dev1': {
            'type': 'RandomPoisson',
            'mean': 137
        },
        'dev2': {
            'type': 'RandomBinomial',
            'N': 17
        },
        'dev3': {
            'type': 'RandomBinomial',
            'N': 17,
            'p': 0.2
        },
        'seq1': {
            'type': 'Sequence'
        },
        'seq2': {
            'type': 'Sequence',
            'step': 3
        },
        'seq3': {
            'type': 'Sequence',
            'first': 1,
            'step': 5
        },
        'seq4': {
            'type': 'Sequence',
            'first': 10,
            'step': -2
        },
        'seq5': {
            'type': 'Sequence',
            'first': 1,
            'last': 2,
            'repeat': 2
        },
        'seq_file': {
            'type': 'Sequence',
            'index_key': 'file_num'
        },
        'seq_image': {
            'type': 'Sequence',
            'index_key': 'image_num'
        },
        'seq_obj': {
            'type': 'Sequence',
            'index_key': 'obj_num'
        },
        'seq_obj2': {
            'type': 'Sequence',
            'index_key': 'obj_num_in_file'
        },
        'list1': {
            'type': 'List',
            'items': [73, 8, 3]
        },
        'list2': {
            'type': 'List',
            'items': [6, 8, 1, 7, 3, 5, 1, 0, 6, 3, 8, 2],
            'index': {
                'type': 'Sequence',
                'first': 10,
                'step': -3
            }
        },
        'dict1': {
            'type': 'Dict',
            'key': 'i'
        },
        'dict2': {
            'type': 'Dict',
            'num': 1,
            'key': 'i'
        },
        'dict3': {
            'type': 'Dict',
            'num': 2,
            'key': 'i'
        },
        'sum1': {
            'type': 'Sum',
            'items': [72.3, '2', {
                'type': 'Dict',
                'key': 'i'
            }]
        }
    }

    test_yaml = True
    try:
        galsim.config.ProcessInput(config)
    except:
        # We don't require PyYAML as a dependency, so if this fails, just remove the YAML dict.
        del config['input']['dict'][2]
        galsim.config.ProcessInput(config)
        test_yaml = False

    # Test direct values
    val1 = galsim.config.ParseValue(config, 'val1', config, int)[0]
    np.testing.assert_equal(val1, 9)

    val2 = galsim.config.ParseValue(config, 'val2', config, int)[0]
    np.testing.assert_equal(val2, 8)

    val3 = galsim.config.ParseValue(config, 'val3', config, int)[0]
    np.testing.assert_equal(val3, -400)

    # Test conversions from strings
    str1 = galsim.config.ParseValue(config, 'str1', config, int)[0]
    np.testing.assert_equal(str1, 8)

    str2 = galsim.config.ParseValue(config, 'str2', config, int)[0]
    np.testing.assert_equal(str2, -2)

    # Test values read from a Catalog
    cat1 = []
    cat2 = []
    cat3 = []
    cat4 = []
    config['index_key'] = 'image_num'
    for k in range(5):
        config['image_num'] = k
        cat1.append(galsim.config.ParseValue(config, 'cat1', config, int)[0])
        cat2.append(galsim.config.ParseValue(config, 'cat2', config, int)[0])
        cat3.append(galsim.config.ParseValue(config, 'cat3', config, int)[0])
        cat4.append(galsim.config.ParseValue(config, 'cat4', config, int)[0])

    np.testing.assert_array_equal(cat1, [9, 0, -4, 9, 0])
    np.testing.assert_array_equal(cat2, [-3, 8, 17, -3, 8])
    np.testing.assert_array_equal(cat3, [9, 0, -4, 9, 0])
    np.testing.assert_array_equal(cat4, [-3, 8, 17, -3, 8])

    # Test values generated from a uniform deviate
    rng = galsim.UniformDeviate(1234)
    config['rng'] = galsim.UniformDeviate(
        1234)  # A second copy starting with the same seed.
    for k in range(6):
        config['obj_num'] = k
        ran1 = galsim.config.ParseValue(config, 'ran1', config, int)[0]
        np.testing.assert_equal(ran1, int(math.floor(rng() * 4)))

        ran2 = galsim.config.ParseValue(config, 'ran2', config, int)[0]
        np.testing.assert_equal(ran2, int(math.floor(rng() * 16)) - 5)

    # Test values generated from various other deviates
    for k in range(6):
        config['obj_num'] = k
        dev = galsim.PoissonDeviate(rng, mean=137)
        dev1 = galsim.config.ParseValue(config, 'dev1', config, int)[0]
        np.testing.assert_almost_equal(dev1, dev())

        dev = galsim.BinomialDeviate(rng, N=17)
        dev2 = galsim.config.ParseValue(config, 'dev2', config, int)[0]
        np.testing.assert_almost_equal(dev2, dev())

        dev = galsim.BinomialDeviate(rng, N=17, p=0.2)
        dev3 = galsim.config.ParseValue(config, 'dev3', config, int)[0]
        np.testing.assert_almost_equal(dev3, dev())

    # Test values generated from a Sequence
    seq1 = []
    seq2 = []
    seq3 = []
    seq4 = []
    seq5 = []
    config['index_key'] = 'obj_num'
    for k in range(6):
        config['obj_num'] = k
        seq1.append(galsim.config.ParseValue(config, 'seq1', config, int)[0])
        seq2.append(galsim.config.ParseValue(config, 'seq2', config, int)[0])
        seq3.append(galsim.config.ParseValue(config, 'seq3', config, int)[0])
        seq4.append(galsim.config.ParseValue(config, 'seq4', config, int)[0])
        seq5.append(galsim.config.ParseValue(config, 'seq5', config, int)[0])

    np.testing.assert_array_equal(seq1, [0, 1, 2, 3, 4, 5])
    np.testing.assert_array_equal(seq2, [0, 3, 6, 9, 12, 15])
    np.testing.assert_array_equal(seq3, [1, 6, 11, 16, 21, 26])
    np.testing.assert_array_equal(seq4, [10, 8, 6, 4, 2, 0])
    np.testing.assert_array_equal(seq5, [1, 1, 2, 2, 1, 1])

    # This is more like how the indexing actually happens in a regular config run:
    seq_file = []
    seq_image = []
    seq_obj = []
    seq_obj2 = []
    config['file_num'] = 0
    config['image_num'] = 0
    config['obj_num'] = 0
    for file_num in range(3):
        config['start_obj_num'] = config['obj_num']
        for image_num in range(2):
            for obj_num in range(5):
                seq_file.append(
                    galsim.config.ParseValue(config, 'seq_file', config,
                                             int)[0])
                seq_image.append(
                    galsim.config.ParseValue(config, 'seq_image', config,
                                             int)[0])
                seq_obj.append(
                    galsim.config.ParseValue(config, 'seq_obj', config,
                                             int)[0])
                seq_obj2.append(
                    galsim.config.ParseValue(config, 'seq_obj2', config,
                                             int)[0])
                config['obj_num'] += 1
            config['image_num'] += 1
        config['file_num'] += 1

    np.testing.assert_array_equal(seq_file, [
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
        2, 2, 2, 2, 2, 2
    ])
    np.testing.assert_array_equal(seq_image, [
        0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4,
        4, 5, 5, 5, 5, 5
    ])
    np.testing.assert_array_equal(seq_obj, [
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 27, 28, 29
    ])
    np.testing.assert_array_equal(seq_obj2, [
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3,
        4, 5, 6, 7, 8, 9
    ])

    # Test values taken from a List
    list1 = []
    list2 = []
    config['index_key'] = 'obj_num'
    for k in range(5):
        config['obj_num'] = k
        list1.append(galsim.config.ParseValue(config, 'list1', config, int)[0])
        list2.append(galsim.config.ParseValue(config, 'list2', config, int)[0])

    np.testing.assert_array_equal(list1, [73, 8, 3, 73, 8])
    np.testing.assert_array_equal(list2, [8, 0, 3, 8, 8])

    # Test values read from a Dict
    dict = []
    dict.append(galsim.config.ParseValue(config, 'dict1', config, int)[0])
    dict.append(galsim.config.ParseValue(config, 'dict2', config, int)[0])
    if test_yaml:
        dict.append(galsim.config.ParseValue(config, 'dict3', config, int)[0])
    else:
        dict.append(1)
    np.testing.assert_array_equal(dict, [17, -23, 1])

    sum1 = galsim.config.ParseValue(config, 'sum1', config, int)[0]
    np.testing.assert_almost_equal(sum1, 72 + 2 + 17)

    t2 = time.time()
    print 'time for %s = %.2f' % (funcname(), t2 - t1)
    def drawObject(self, gsObject):
        """
        Draw an astronomical object on all of the relevant FITS files.

        @param [in] gsObject is an instantiation of the GalSimCelestialObject
        class carrying all of the information for the object whose image
        is to be drawn

        @param [out] outputString is a string denoting which detectors the astronomical
        object illumines, suitable for output in the GalSim InstanceCatalog
        """

        # find the detectors which the astronomical object illumines
        outputString, \
        detectorList, \
        centeredObj = self.findAllDetectors(gsObject)

        # Make sure this object is marked as "drawn" since we only
        # care that this method has been called for this object.
        self.drawn_objects.add(gsObject.uniqueId)

        # Compute the realized object fluxes (as drawn from the
        # corresponding Poisson distribution) for each band and return
        # right away if all values are zero in order to save compute.
        fluxes = [gsObject.flux(bandpassName) for bandpassName in self.bandpassDict]
        realized_fluxes = [galsim.PoissonDeviate(self._rng, mean=f)() for f in fluxes]
        if all([f == 0 for f in realized_fluxes]):
            return outputString

        if len(detectorList) == 0:
            # there is nothing to draw
            return outputString

        self._addNoiseAndBackground(detectorList)

        # Create a surface operation to sample incident angles and a
        # galsim.SED object for sampling the wavelengths of the
        # incident photons.
        fratio = 1.234  # From https://www.lsst.org/scientists/keynumbers
        obscuration = 0.606  # (8.4**2 - 6.68**2)**0.5 / 8.4
        angles = galsim.FRatioAngles(fratio, obscuration, self._rng)

        sed_lut = galsim.LookupTable(x=gsObject.sed.wavelen,
                                     f=gsObject.sed.flambda)
        gs_sed = galsim.SED(sed_lut, wave_type='nm', flux_type='flambda',
                            redshift=0.)

        local_hour_angle \
            = self.getHourAngle(self.obs_metadata.mjd.TAI,
                                self.obs_metadata.pointingRA)*galsim.degrees
        obs_latitude = self.observatory.getLatitude().asDegrees()*galsim.degrees
        ra_obs, dec_obs = observedFromPupilCoords(gsObject.xPupilRadians,
                                                  gsObject.yPupilRadians,
                                                  obs_metadata=self.obs_metadata)
        obj_coord = galsim.CelestialCoord(ra_obs*galsim.degrees,
                                          dec_obs*galsim.degrees)
        for bandpassName, realized_flux in zip(self.bandpassDict, realized_fluxes):
            gs_bandpass = self.gs_bandpass_dict[bandpassName]
            waves = galsim.WavelengthSampler(sed=gs_sed, bandpass=gs_bandpass,
                                             rng=self._rng)
            dcr = galsim.PhotonDCR(base_wavelength=gs_bandpass.effective_wavelength,
                                   HA=local_hour_angle,
                                   latitude=obs_latitude,
                                   obj_coord=obj_coord)

            # Set the object flux to the value realized from the
            # Poisson distribution.
            obj = centeredObj.withFlux(realized_flux)

            for detector in detectorList:

                name = self._getFileName(detector=detector,
                                         bandpassName=bandpassName)

                xPix, yPix = detector.camera_wrapper\
                   .pixelCoordsFromPupilCoords(gsObject.xPupilRadians,
                                               gsObject.yPupilRadians,
                                               chipName=detector.name,
                                               obs_metadata=self.obs_metadata)

                # Ensure the rng used by the sensor object is set to the desired state.
                self.sensor[detector.name].rng.reset(self._rng)
                surface_ops = [waves, dcr, angles]

                # Desired position to draw the object.
                image_pos = galsim.PositionD(xPix, yPix)

                # Find a postage stamp region to draw onto.  Use (sky
                # noise)/3. as the nominal minimum surface brightness
                # for rendering an extended object.
                keep_sb_level = np.sqrt(self.sky_bg_per_pixel)/3.
                bounds = self.getStampBounds(gsObject, realized_flux, image_pos,
                                             keep_sb_level, 3*keep_sb_level)

                # Ensure the bounds of the postage stamp lie within the image.
                bounds = bounds & self.detectorImages[name].bounds

                if bounds.isDefined():
                    # offset is relative to the "true" center of the postage stamp.
                    offset = image_pos - bounds.true_center

                    obj.drawImage(method='phot',
                                  offset=offset,
                                  rng=self._rng,
                                  maxN=int(1e6),
                                  image=self.detectorImages[name][bounds],
                                  sensor=self.sensor[detector.name],
                                  surface_ops=surface_ops,
                                  add_to_image=True,
                                  poisson_flux=False,
                                  gain=detector.photParams.gain)

                    # If we are writing centroid files,store the entry.
                    if self.centroid_base_name is not None:
                        centroid_tuple = (detector.fileName, bandpassName, gsObject.uniqueId,
                                          gsObject.flux(bandpassName), xPix, yPix)
                        self.centroid_list.append(centroid_tuple)

        self.write_checkpoint()
        return outputString
Esempio n. 23
0
def main(argv):
    """
    Make a fits image cube where each frame has two images of the same galaxy drawn 
    with regular FFT convolution and with photon shooting.

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

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

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

    # Define some parameters we'll use below.

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

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

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

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

    psf_fwhm = 0.65  # arcsec

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

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

    logger.info('Starting demo script 7')

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                t4 = time.time()

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

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

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

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

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

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

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

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

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

    # Get how much extra sky to assume from the image.noise attribute.
    if sky:
        opt = {'sky_level': float, 'sky_level_pixel': float}
        single = []
    else:
        opt = {}
        single = [{'sky_level': float, 'sky_level_pixel': float}]
    params = galsim.config.GetAllParams(noise,
                                        'noise',
                                        config,
                                        opt=opt,
                                        single=single,
                                        ignore=noise_ignore)[0]
    if 'sky_level' in params:
        if 'sky_level_pixel' in params:
            raise AttributeError(
                "Only one of sky_level and sky_level_pixel is allowed for "
                "noise.type = Poisson")
        sky_level = params['sky_level']
        if im.wcs.isUniform():
            extra_sky = sky_level * im.wcs.pixelArea()
        elif 'image_pos' in config:
            extra_sky = sky_level * im.wcs.pixelArea(config['image_pos'])
        else:
            extra_sky = galsim.Image(im.bounds, wcs=im.wcs)
            im.wcs.makeSkyImage(extra_sky, sky_level)
    elif 'sky_level_pixel' in params:
        extra_sky = params['sky_level_pixel']
    else:
        extra_sky = 0.

    # If we are saving the noise level in a weight image, do that now.
    if weight_im:
        # Check if a weight image should include the object variance.
        # Note: For the phot case, we don't actually have an exact value for the variance in each
        # pixel, but the drawn image before adding the Poisson noise is our best guess for the
        # variance from the object's flux, so if we want the object variance included, this is
        # still the best we can do.
        include_obj_var = False
        if ('output' in config and 'weight' in config['output']
                and 'include_obj_var' in config['output']['weight']):
            include_obj_var = galsim.config.ParseValue(
                config['output']['weight'], 'include_obj_var', config, bool)[0]
        if include_obj_var:
            # The image right now has the object variance in each pixel.  So before going on with
            # the noise, copy these over to the weight image.  (We invert this later...)
            weight_im.copyFrom(im)
        else:
            # Otherwise, just add in the current sky noise:
            if sky: weight_im += sky
        # And add in the extra sky noise:
        if extra_sky: weight_im += extra_sky

    # If we already have some variance in the image (from whitening), then we subtract this much
    # off of the sky level.  It's not precisely accurate, since the existing variance is Gaussian,
    # rather than Poisson, but it's the best we can do.
    if current_var:
        if isinstance(sky, galsim.Image) or isinstance(extra_sky,
                                                       galsim.Image):
            test = ((sky + extra_sky).image.array < current_var).any()
        else:
            test = (sky + extra_sky < current_var)
        if test:
            raise RuntimeError(
                "Whitening already added more noise than requested Poisson noise."
            )
        extra_sky -= current_var

    # At this point, there is a slight difference between fft and phot. For photon shooting, the
    # galaxy already has Poisson noise, so we want to make sure not to add that again!
    if draw_method == 'phot':
        # Only add in the noise from the sky.
        if isinstance(sky, galsim.Image) or isinstance(extra_sky,
                                                       galsim.Image):
            noise_im = sky + extra_sky
            noise_im.addNoise(galsim.PoissonNoise(rng))
            if sky:
                noise_im -= sky
            if extra_sky:
                noise_im -= extra_sky
            # noise_im should now have zero mean, but with the noise of the total sky level.
            im += noise_im
        else:
            total_sky = sky + extra_sky
            if total_sky > 0.:
                im.addNoise(
                    galsim.DeviateNoise(
                        galsim.PoissonDeviate(rng, mean=total_sky)))
                # This deviate adds a noisy version of the sky, so need to subtract the mean back
                # off.
                im -= total_sky
    else:
        im += extra_sky
        # Do the normal PoissonNoise calculation.
        im.addNoise(galsim.PoissonNoise(rng))
        im -= extra_sky

    if logger:
        logger.debug('image %d, obj %d: Added Poisson noise with sky = %f',
                     config['image_num'], config['obj_num'], sky)
Esempio n. 25
0
def main(argv):
    """
    Make a fits image cube where each frame has two images of the same galaxy drawn 
    with regular FFT convolution and with photon shooting.

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

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

    # Define some parameters we'll use below.

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

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

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

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

    psf_fwhm = 0.65  # arcsec

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

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

    logger.info('Starting demo script 7')

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                # Draw the profile
                final.draw(fft_image)

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

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

                t4 = time.time()

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

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

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

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

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

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

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

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

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

    # This process goes a lot like the Poisson routine.  There are just two differences.
    # The Poisson noise is in the electron, not ADU, and now we allow for a gain = e-/ADU,
    # so we need to account for that properly.  And we also allow for an additional Gaussian
    # read noise.

    # Get how much extra sky to assume from the image.noise attribute.
    opt = {'gain': float, 'read_noise': float}
    # The noise sky_level is only required here if the image doesn't have any.
    if sky:
        opt['sky_level'] = float
        opt['sky_level_pixel'] = float
        single = []
    else:
        single = [{'sky_level': float, 'sky_level_pixel': float}]
    params = galsim.config.GetAllParams(noise,
                                        'noise',
                                        config,
                                        opt=opt,
                                        single=single,
                                        ignore=noise_ignore)[0]
    gain = params.get('gain', 1.0)
    read_noise = params.get('read_noise', 0.0)
    read_noise_var = read_noise**2
    if 'sky_level' in params:
        if 'sky_level_pixel' in params:
            raise AttributeError(
                "Only one of sky_level and sky_level_pixel is allowed for "
                "noise.type = CCD")
        sky_level = params['sky_level']
        if im.wcs.isUniform():
            extra_sky = sky_level * im.wcs.pixelArea()
        elif 'image_pos' in config:
            extra_sky = sky_level * im.wcs.pixelArea(config['image_pos'])
        else:
            extra_sky = galsim.Image(im.bounds, wcs=im.wcs)
            im.wcs.makeSkyImage(extra_sky, sky_level)
    elif 'sky_level_pixel' in params:
        extra_sky = params['sky_level_pixel']
    else:
        extra_sky = 0.

    # If we are saving the noise level in a weight image, do that now.
    if weight_im:
        # Check if a weight image should include the object variance.
        # Note: For the phot case, we don't actually have an exact value for the variance in each
        # pixel, but the drawn image before adding the Poisson noise is our best guess for the
        # variance from the object's flux, so if we want the object variance included, this is
        # still the best we can do.
        include_obj_var = False
        if ('output' in config and 'weight' in config['output']
                and 'include_obj_var' in config['output']['weight']):
            include_obj_var = galsim.config.ParseValue(
                config['output']['weight'], 'include_obj_var', config, bool)[0]
        if include_obj_var:
            # The image right now has the object variance in each pixel.  So before going on with
            # the noise, copy these over to the weight image.  (We invert this later...)
            weight_im.copyFrom(im)

            # Account for the gain and read noise
            if gain != 1.0:
                import math
                weight_im /= math.sqrt(gain)
            if read_noise != 0.0:
                weight_im += read_noise_var
        else:
            # Otherwise, just add in the current sky noise:
            if sky or read_noise != 0.0:
                weight_im += sky / gain + read_noise_var

        # And add in the extra sky noise:
        if extra_sky: weight_im += extra_sky

    # If we already have some variance in the image (from whitening), then we try to subtract it
    # from the read noise if possible.  If now, we subtract the rest off of the sky level.  It's
    # not precisely accurate, since the existing variance is Gaussian, rather than Poisson, but
    # it's the best we can do.
    if current_var:
        if isinstance(sky, galsim.Image) or isinstance(extra_sky,
                                                       galsim.Image):
            test = ((sky + extra_sky).image.array / gain + read_noise_var <
                    current_var).any()
        else:
            test = (sky + extra_sky) / gain + read_noise_var < current_var
        if test:
            raise RuntimeError(
                "Whitening already added more noise than requested CCD noise.")
        if read_noise_var >= current_var:
            # First try to take away from the read_noise, since this one is actually Gaussian.
            import math
            read_noise_var -= current_var
            read_noise = math.sqrt(read_noise_var)
        else:
            # Take read_noise down to zero, since already have at least that much already.
            current_var -= read_noise_var
            read_noise = 0
            read_noise_var = 0
            # Take the rest away from the sky level
            extra_sky -= current_var * gain

    # At this point, there is a slight difference between fft and phot. For photon shooting, the
    # galaxy already has Poisson noise, so we want to make sure not to add that again!
    if draw_method == 'phot':
        # Add in the noise from the sky.
        if isinstance(sky, galsim.Image) or isinstance(extra_sky,
                                                       galsim.Image):
            noise_im = sky + extra_sky
            if gain != 1.0: noise_im *= gain
            noise_im.addNoise(galsim.PoissonNoise(rng))
            if gain != 1.0: noise_im /= gain
            if sky:
                noise_im -= sky
            if extra_sky:
                noise_im -= extra_sky
            # noise_im should now have zero mean, but with the noise of the total sky level.
            im += noise_im
        else:
            total_sky = sky + extra_sky
            if total_sky > 0.:
                if gain != 1.0: im *= gain
                im.addNoise(
                    galsim.DeviateNoise(
                        galsim.PoissonDeviate(rng, mean=total_sky * gain)))
                if gain != 1.0: im /= gain
                im -= total_sky
        # And add the read noise
        if read_noise != 0.:
            im.addNoise(galsim.GaussianNoise(rng, sigma=read_noise))
    else:
        # Do the normal CCDNoise calculation.
        im += extra_sky
        im.addNoise(galsim.CCDNoise(rng, gain=gain, read_noise=read_noise))
        im -= extra_sky

    if logger:
        logger.debug(
            'image %d, obj %d: Added CCD noise with sky = %f, ' +
            'gain = %f, read_noise = %f', config['image_num'],
            config['obj_num'], sky, gain, read_noise)
    def drawObject(self, gsObject):
        """
        Draw an astronomical object on all of the relevant FITS files.

        @param [in] gsObject is an instantiation of the GalSimCelestialObject
        class carrying all of the information for the object whose image
        is to be drawn

        @param [out] outputString is a string denoting which detectors the astronomical
        object illumines, suitable for output in the GalSim InstanceCatalog
        """

        # find the detectors which the astronomical object illumines
        outputString, \
        detectorList, \
        centeredObj = self.findAllDetectors(gsObject)

        # Make sure this object is marked as "drawn" since we only
        # care that this method has been called for this object.
        self.drawn_objects.add(gsObject.uniqueId)

        # Compute the realized object fluxes for each band and return
        # if all values are zero in order to save compute.
        fluxes = [gsObject.flux(bandpassName) for bandpassName in self.bandpassDict]
        realized_fluxes = [galsim.PoissonDeviate(self._rng, mean=f)() for f in fluxes]
        if all([f == 0 for f in realized_fluxes]):
            return outputString

        if len(detectorList) == 0:
            # there is nothing to draw
            return outputString

        self._addNoiseAndBackground(detectorList)

        for bandpassName, realized_flux in zip(self.bandpassDict, realized_fluxes):
            for detector in detectorList:

                name = self._getFileName(detector=detector, bandpassName=bandpassName)

                xPix, yPix = detector.camera_wrapper.pixelCoordsFromPupilCoords(gsObject.xPupilRadians,
                                                                                gsObject.yPupilRadians,
                                                                                detector.name,
                                                                                self.obs_metadata)

                # Set the object flux to the value realized from the
                # Poisson distribution.
                obj = centeredObj.withFlux(realized_flux)

                obj.drawImage(method='phot',
                              gain=detector.photParams.gain,
                              offset=galsim.PositionD(xPix-detector.xCenterPix,
                                                      yPix-detector.yCenterPix),
                              rng=self._rng,
                              maxN=int(1e6),
                              image=self.detectorImages[name],
                              poisson_flux=False,
                              add_to_image=True)

                # If we are writing centroid files, store the entry.
                if self.centroid_base_name is not None:
                    centroid_tuple = (detector.fileName, bandpassName, gsObject.uniqueId,
                                      gsObject.flux(bandpassName), xPix, yPix)
                    self.centroid_list.append(centroid_tuple)

        self.write_checkpoint()
        return outputString
Esempio n. 28
0
def test_ccdnoise_phot():
    """CCDNoise has some special code for photon shooting, so check that it works correctly.
    """
    scale = 0.3
    sky = 200
    gain = 1.8
    rn = 2.3

    config = {
        'image' : {
            'type' : 'Single',
            'random_seed' : 1234,
            'pixel_scale' : scale,
            'size' : 32,
            'draw_method' : 'phot',

            'noise' : {
                'type' : 'CCD',
                'gain' : gain,
                'read_noise' : rn,
                'sky_level' : sky,
            }
        },
        'gal' : {
            'type' : 'Gaussian',
            'sigma' : 1.1,
            'flux' : 100,
        },
    }

    # First build by hand
    rng = galsim.BaseDeviate(1234 + 1)
    gal = galsim.Gaussian(sigma=1.1, flux=100)
    im1a = gal.drawImage(nx=32, ny=32, scale=scale, method='phot', rng=rng)
    sky_pixel = sky * scale**2
    # Need to add Poisson noise for the sky, but not the signal (which already has shot noise)
    im1a *= gain
    im1a.addNoise(galsim.DeviateNoise(galsim.PoissonDeviate(rng, mean=sky_pixel * gain)))
    im1a /= gain
    im1a -= sky_pixel
    im1a.addNoise(galsim.GaussianNoise(rng, sigma=rn/gain))

    # Compare to what config builds
    im1b = galsim.config.BuildImage(config)
    np.testing.assert_equal(im1b.array, im1a.array)

    # Check noise variance
    var = sky_pixel / gain + rn**2 / gain**2
    var1 = galsim.config.CalculateNoiseVariance(config)
    np.testing.assert_equal(var1, var)
    var2 = galsim.Image(3,3)
    galsim.config.AddNoiseVariance(config, var2)
    np.testing.assert_almost_equal(var2.array, var)

    # Check include_obj_var=True
    var3 = galsim.Image(32,32)
    galsim.config.AddNoiseVariance(config, var3, include_obj_var=True)
    np.testing.assert_almost_equal(var3.array, var + im1a.array/gain)

    # Some slightly different code paths if rn = 0 or gain = 1:
    del config['image']['noise']['gain']
    del config['image']['noise']['read_noise']
    del config['image']['noise']['_get']
    rng.seed(1234 + 1)
    im2a = gal.drawImage(nx=32, ny=32, scale=scale, method='phot', rng=rng)
    im2a.addNoise(galsim.DeviateNoise(galsim.PoissonDeviate(rng, mean=sky_pixel)))
    im2a -= sky_pixel
    im2b = galsim.config.BuildImage(config)
    np.testing.assert_equal(im2b.array, im2a.array)
    var5 = galsim.config.CalculateNoiseVariance(config)
    np.testing.assert_equal(var5, sky_pixel)
    var6 = galsim.Image(3,3)
    galsim.config.AddNoiseVariance(config, var6)
    np.testing.assert_almost_equal(var6.array, sky_pixel)
    var7 = galsim.Image(32,32)
    galsim.config.AddNoiseVariance(config, var7, include_obj_var=True)
    np.testing.assert_almost_equal(var7.array, sky_pixel + im2a.array)

    # Check non-trivial sky image
    galsim.config.RemoveCurrent(config)
    config['image']['sky_level'] = sky
    config['image']['wcs'] =  {
        'type' : 'UVFunction',
        'ufunc' : '0.05*x + 0.001*x**2',
        'vfunc' : '0.05*y + 0.001*y**2',
    }
    del config['image']['pixel_scale']
    del config['wcs']
    rng.seed(1234+1)
    wcs = galsim.UVFunction(ufunc='0.05*x + 0.001*x**2', vfunc='0.05*y + 0.001*y**2')
    im3a = gal.drawImage(nx=32, ny=32, wcs=wcs, method='phot', rng=rng)
    sky_im = galsim.Image(im3a.bounds, wcs=wcs)
    wcs.makeSkyImage(sky_im, sky)
    im3a += sky_im  # Add 1 copy of the raw sky image for image[sky]
    noise_im = sky_im.copy()
    noise_im *= 2.  # Now 2x because the noise includes both in image[sky] and noise[sky]
    noise_im.addNoise(galsim.PoissonNoise(rng))
    noise_im -= 2.*sky_im
    im3a += noise_im
    im3b = galsim.config.BuildImage(config)
    np.testing.assert_almost_equal(im3b.array, im3a.array, decimal=6)

    # And again with the rn and gain put back in.
    galsim.config.RemoveCurrent(config)
    config['image']['noise']['gain'] = gain
    config['image']['noise']['read_noise'] = rn
    del config['image']['noise']['_get']
    rng.seed(1234+1)
    im4a = gal.drawImage(nx=32, ny=32, wcs=wcs, method='phot', rng=rng)
    wcs.makeSkyImage(sky_im, sky)
    im4a += sky_im
    noise_im = sky_im.copy()
    noise_im *= 2. * gain
    noise_im.addNoise(galsim.PoissonNoise(rng))
    noise_im /= gain
    noise_im -= 2. * sky_im
    im4a += noise_im
    im4a.addNoise(galsim.GaussianNoise(rng, sigma=rn/gain))
    im4b = galsim.config.BuildImage(config)
    np.testing.assert_almost_equal(im4b.array, im4a.array, decimal=6)
Esempio n. 29
0
def test_dep_random():
    """Test the deprecated methods in galsim/deprecated/random.py
    """
    rng = galsim.BaseDeviate(123)

    gd = galsim.GaussianDeviate(rng, mean=0.5, sigma=1.7)
    np.testing.assert_almost_equal(gd.getMean(), 0.5)
    np.testing.assert_almost_equal(gd.getSigma(), 1.7)

    check_dep(gd.setMean, 0.9)
    np.testing.assert_almost_equal(gd.getMean(), 0.9)
    np.testing.assert_almost_equal(gd.getSigma(), 1.7)

    check_dep(gd.setSigma, 2.3)
    np.testing.assert_almost_equal(gd.getMean(), 0.9)
    np.testing.assert_almost_equal(gd.getSigma(), 2.3)

    bd = galsim.BinomialDeviate(rng, N=7, p=0.7)
    np.testing.assert_almost_equal(bd.getN(), 7)
    np.testing.assert_almost_equal(bd.getP(), 0.7)

    check_dep(bd.setN, 9)
    np.testing.assert_almost_equal(bd.getN(), 9)
    np.testing.assert_almost_equal(bd.getP(), 0.7)

    check_dep(bd.setP, 0.3)
    np.testing.assert_almost_equal(bd.getN(), 9)
    np.testing.assert_almost_equal(bd.getP(), 0.3)

    pd = galsim.PoissonDeviate(rng, mean=0.5)
    np.testing.assert_almost_equal(pd.getMean(), 0.5)

    check_dep(pd.setMean, 0.9)
    np.testing.assert_almost_equal(pd.getMean(), 0.9)

    wd = galsim.WeibullDeviate(rng, a=0.5, b=1.7)
    np.testing.assert_almost_equal(wd.getA(), 0.5)
    np.testing.assert_almost_equal(wd.getB(), 1.7)

    check_dep(wd.setA, 0.9)
    np.testing.assert_almost_equal(wd.getA(), 0.9)
    np.testing.assert_almost_equal(wd.getB(), 1.7)

    check_dep(wd.setB, 2.3)
    np.testing.assert_almost_equal(wd.getA(), 0.9)
    np.testing.assert_almost_equal(wd.getB(), 2.3)

    gd = galsim.GammaDeviate(rng, k=0.5, theta=1.7)
    np.testing.assert_almost_equal(gd.getK(), 0.5)
    np.testing.assert_almost_equal(gd.getTheta(), 1.7)

    check_dep(gd.setK, 0.9)
    np.testing.assert_almost_equal(gd.getK(), 0.9)
    np.testing.assert_almost_equal(gd.getTheta(), 1.7)

    check_dep(gd.setTheta, 2.3)
    np.testing.assert_almost_equal(gd.getK(), 0.9)
    np.testing.assert_almost_equal(gd.getTheta(), 2.3)

    cd = galsim.Chi2Deviate(rng, n=5)
    np.testing.assert_almost_equal(cd.getN(), 5)

    check_dep(cd.setN, 9)
    np.testing.assert_almost_equal(cd.getN(), 9)
Esempio n. 30
0
    def setup(self, config, base, xsize, ysize, ignore, logger):
        """
        Do the initialization and setup for building a postage stamp.

        In the base class, we check for and parse the appropriate size and position values in
        config (aka base['stamp'] or base['image'].

        Values given in base['stamp'] take precedence if these are given in both places (which
        would be confusing, so probably shouldn't do that, but there might be a use case where it
        would make sense).

        Parameters:
            config:     The configuration dict for the stamp field.
            base:       The base configuration dict.
            xsize:      The xsize of the image to build (if known).
            ysize:      The ysize of the image to build (if known).
            ignore:     A list of parameters that are allowed to be in config that we can
                        ignore here. i.e. it won't be an error if these parameters are present.
            logger:     A logger object to log progress.

        Returns:
            xsize, ysize, image_pos, world_pos
        """
        gal = galsim.config.BuildGSObject(base, 'gal', logger=logger)[0]
        if gal is None:
            raise galsim.config.SkipThisObject('gal is None (invalid parameters)')
        self.gal = gal

        # Check if the realized flux is 0.
        self.rng = galsim.config.GetRNG(config, base, logger, "LSST_Silicon")
        self.realized_flux = galsim.PoissonDeviate(self.rng, mean=gal.flux)()
        if self.realized_flux == 0:
            # If so, we'll skip everything after this.
            # The mechanism within GalSim to do this is to raise a special SkipThisObject class.
            raise galsim.config.SkipThisObject('realized flux=0')

        # Otherwise figure out the stamp size
        if self.realized_flux < 10:
            # For really faint things, don't try too hard.  Just use 32x32.
            image_size = 32

        elif isinstance(gal, galsim.DeltaFunction):
            # For bright stars, set the folding threshold for the
            # stamp size calculation.  Use a
            # Kolmogorov_and_Gaussian_PSF since it is faster to
            # evaluate than an AtmosphericPSF.
            base['current_noise_image'] = base['current_image']
            noise_var = galsim.config.CalculateNoiseVariance(base)
            folding_threshold = noise_var/self.realized_flux
            if folding_threshold >= self._ft_default:
                gsparams = None
            else:
                # Every different folding threshold requires a new initialization of Kolmogorov,
                # which takes about a second.  So round down to the nearest e folding to
                # minimize how many of these we need to do.
                folding_threshold = np.exp(np.floor(np.log(folding_threshold)))
                logger.debug('Using folding_threshold %s',folding_threshold)
                logger.debug('From: noise_var = %s, flux = %s',noise_var,self.realized_flux)
                gsparams = galsim.GSParams(folding_threshold=folding_threshold)

            psf = self.Kolmogorov_and_Gaussian_PSF(gsparams=gsparams)
            image_size = psf.getGoodImageSize(self._pixel_scale)
            # No point in this being larger than a CCD.  Cut back to Nmax if larger than this.
            image_size = min(image_size, self._Nmax)

        else:
            # For extended objects, recreate the object to draw, but
            # convolved with the faster DoubleGaussian PSF.
            psf = self.DoubleGaussian()
            obj = galsim.Convolve(gal, psf).withFlux(self.realized_flux)

            # Start with GalSim's estimate of a good box size.
            image_size = obj.getGoodImageSize(self._pixel_scale)

            # Find a postage stamp region to draw onto.  Use (sky noise)/3. as the nominal
            # minimum surface brightness for rendering an extended object.
            base['current_noise_image'] = base['current_image']
            noise_var = galsim.config.CalculateNoiseVariance(base)
            keep_sb_level = np.sqrt(noise_var)/3.

            # For bright things, defined as having an average of at least 10 photons per
            # pixel on average, try to be careful about not truncating the surface brightness
            # at the edge of the box.
            if self.realized_flux > 10 * image_size**2:
                image_size = self._getGoodPhotImageSize([gal, psf], keep_sb_level,
                                                        pixel_scale=self._pixel_scale)

            # If the above size comes out really huge, scale back to what you get for
            # a somewhat brighter surface brightness limit.
            if image_size > self._Nmax:
                image_size = self._getGoodPhotImageSize([gal, psf], self._large_object_sb_level,
                                                        pixel_scale=self._pixel_scale)
                image_size = min(image_size, self._Nmax)

        logger.info('Object %d will use stamp size = %s',base.get('obj_num',0),image_size)

        # Also the position
        world_pos = galsim.config.ParseWorldPos(config, 'world_pos', base, logger)
        image_pos = None  # GalSim will figure this out from the wcs.

        return image_size, image_size, image_pos, world_pos