def test_lsst_y_focus():
    # Check that applying reasonable focus depth (from O'Connor++06) indeed leads to smaller spot
    # size for LSST y-band.
    rng = galsim.BaseDeviate(9876543210)
    bandpass = galsim.Bandpass("LSST_y.dat", wave_type='nm')
    sed = galsim.SED("1", wave_type='nm', flux_type='flambda')
    obj = galsim.Gaussian(fwhm=1e-5)
    oversampling = 32
    photon_ops0 = [
        galsim.WavelengthSampler(sed, bandpass, rng=rng),
        galsim.FRatioAngles(1.234, 0.606, rng=rng),
        galsim.FocusDepth(0.0),
        galsim.Refraction(3.9)
    ]
    img0 = obj.drawImage(
        sensor=galsim.SiliconSensor(),
        method='phot',
        n_photons=100000,
        photon_ops=photon_ops0,
        scale=0.2/oversampling,
        nx=32*oversampling,
        ny=32*oversampling,
        rng=rng
    )
    T0 = img0.calculateMomentRadius()
    T0 *= 10*oversampling/0.2  # arcsec => microns

    # O'Connor finds minimum spot size when the focus depth is ~ -12 microns.  Our sensor isn't
    # necessarily the same as the one there though; our minimum seems to be around -6 microns.
    # That could be due to differences in the design of the sensor though.  We just use -6 microns
    # here, which is still useful to test the sign of the `depth` parameter and the interaction of
    # the 4 different surface operators required to produce this effect, and is roughly consistent
    # with O'Connor.

    depth1 = -6.  # microns, negative means surface is intrafocal
    depth1 /= 10  # microns => pixels
    photon_ops1 = [
        galsim.WavelengthSampler(sed, bandpass, rng=rng),
        galsim.FRatioAngles(1.234, 0.606, rng=rng),
        galsim.FocusDepth(depth1),
        galsim.Refraction(3.9)
    ]
    img1 = obj.drawImage(
        sensor=galsim.SiliconSensor(),
        method='phot',
        n_photons=100000,
        photon_ops=photon_ops1,
        scale=0.2/oversampling,
        nx=32*oversampling,
        ny=32*oversampling,
        rng=rng
    )
    T1 = img1.calculateMomentRadius()
    T1 *= 10*oversampling/0.2  # arcsec => microns
    np.testing.assert_array_less(T1, T0)
def time_silicon_accumulate():
    nx = 1000
    ny = 1000
    nobj = 1000
    photons_per_obj = 10000
    flux_per_photon = 1

    rng = galsim.UniformDeviate(314159)

    sensor = galsim.SiliconSensor(rng=rng.duplicate(), diffusion_factor=0.0)

    im = galsim.ImageF(nx, ny)

    num_photons = nobj * photons_per_obj
    photons = galsim.PhotonArray(num_photons)

    rng.generate(photons.x)
    photons.x *= nx
    photons.x += 0.5
    rng.generate(photons.y)
    photons.y *= ny
    photons.y += 0.5
    photons.flux = flux_per_photon

    t1 = time.time()
    sensor.accumulate(photons, im)
    t2 = time.time()
    print('Time = ', t2 - t1)
    def __init__(self, obs_metadata=None, detectors=None, bandpassDict=None,
                 noiseWrapper=None, epoch=None, seed=None, bf_strength=1):
        super(GalSimSiliconInterpeter, self)\
            .__init__(obs_metadata=obs_metadata, detectors=detectors,
                      bandpassDict=bandpassDict, noiseWrapper=noiseWrapper,
                      epoch=epoch, seed=seed)

        self.gs_bandpass_dict = {}
        for bandpassName in bandpassDict:
            bandpass = bandpassDict[bandpassName]
            index = np.where(bandpass.sb != 0)
            bp_lut = galsim.LookupTable(x=bandpass.wavelen[index],
                                        f=bandpass.sb[index])
            self.gs_bandpass_dict[bandpassName] \
                = galsim.Bandpass(bp_lut, wave_type='nm')

        self.sky_bg_per_pixel = None

        # Create a PSF that's fast to evaluate for the postage stamp
        # size calculation for extended objects in .getStampBounds.
        FWHMgeom = obs_metadata.OpsimMetaData['FWHMgeom']
        self._double_gaussian_psf = SNRdocumentPSF(FWHMgeom)

        # Save the parameters needed to create a Kolmogorov PSF for a
        # custom value of gsparams.folding_threshold.  That PSF will
        # to be used in the .getStampBounds function for bright stars.
        altRad = np.radians(obs_metadata.OpsimMetaData['altitude'])
        self._airmass = 1.0/np.sqrt(1.0-0.96*(np.sin(0.5*np.pi-altRad))**2)
        self._rawSeeing = obs_metadata.OpsimMetaData['rawSeeing']
        self._band = obs_metadata.bandpass

        # Save the default folding threshold for determining when to recompute
        # the PSF for bright point sources.
        self._ft_default = galsim.GSParams().folding_threshold

        # Create SiliconSensor objects for each detector.
        self.sensor = dict()
        for det in detectors:
            self.sensor[det.name] \
                = galsim.SiliconSensor(strength=bf_strength,
                                       treering_center=det.tree_rings.center,
                                       treering_func=det.tree_rings.func,
                                       transpose=True)
Exemple #4
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)
Exemple #5
0
def test_treerings():
    """Test the addition of tree rings.
    compare image positions with the simple sensor to
    a SiliconSensor with no tree rings and six
    different additions of tree rings.
    """
    # Set up the different sensors.
    treering_amplitude = 0.5
    rng1 = galsim.BaseDeviate(5678)
    rng2 = galsim.BaseDeviate(5678)
    rng3 = galsim.BaseDeviate(5678)
    rng4 = galsim.BaseDeviate(5678)
    rng5 = galsim.BaseDeviate(5678)
    rng6 = galsim.BaseDeviate(5678)
    rng7 = galsim.BaseDeviate(5678)
    sensor0 = galsim.Sensor()
    sensor1 = galsim.SiliconSensor(rng=rng1)
    tr2 = galsim.SiliconSensor.simple_treerings(treering_amplitude, 250.)
    sensor2 = galsim.SiliconSensor(rng=rng2, treering_func=tr2,
                                   treering_center=galsim.PositionD(-1000.0,0.0))
    sensor3 = galsim.SiliconSensor(rng=rng3, treering_func=tr2,
                                   treering_center=galsim.PositionD(1000.0,0.0))
    sensor4 = galsim.SiliconSensor(rng=rng4, treering_func=tr2,
                                   treering_center=galsim.PositionD(0.0,-1000.0))
    tr5 = galsim.SiliconSensor.simple_treerings(treering_amplitude, 250., r_max=2000, dr=1.)
    sensor5 = galsim.SiliconSensor(rng=rng5, treering_func=tr5,
                                   treering_center=galsim.PositionD(0.0,1000.0))

    # Now test the ability to read in a user-specified function
    tr6 = galsim.LookupTable.from_func(treering_function, x_min=0.0, x_max=2000)
    sensor6 = galsim.SiliconSensor(rng=rng6, treering_func=tr6,
                                   treering_center=galsim.PositionD(-1000.0,0.0))

    # Now test the ability to read in a lookup table
    tr7 = galsim.LookupTable.from_file('tree_ring_lookup.dat', amplitude=treering_amplitude)
    sensor7 = galsim.SiliconSensor(rng=rng7, treering_func=tr7,
                                   treering_center=galsim.PositionD(-1000.0,0.0))

    sensors = [sensor0, sensor1, sensor2, sensor3, sensor4, sensor5, sensor6, sensor7]
    names = ['Sensor()',
             'SiliconSensor, no TreeRings',
             'SiliconSensor, TreeRingCenter= (-1000,0)',
             'SiliconSensor, TreeRingCenter= (1000,0)',
             'SiliconSensor, TreeRingCenter= (0,-1000)',
             'SiliconSensor, TreeRingCenter= (0,1000)',
             'SiliconSensor with specified function, TreeRingCenter= (-1000,0)',
             'SiliconSensor with lookup table, TreeRingCenter= (-1000,0)']
    centers = [None, None,
               (-1000,0),
               (1000,0),
               (0,-1000),
               (0,1000),
               (-1000,0),
               (-1000,0)]

    init_flux = 200000
    obj = galsim.Gaussian(flux=init_flux, sigma=0.3)

    im = galsim.ImageD(10,10, scale=0.3)
    obj.drawImage(im)
    ref_mom = galsim.utilities.unweighted_moments(im)
    print('Reference Moments Mx, My = (%f, %f):'%(ref_mom['Mx'], ref_mom['My']))

    for sensor, name, center in zip(sensors, names, centers):
        im = galsim.ImageD(10,10, scale=0.3)
        obj.drawImage(im, method='phot', sensor=sensor)
        mom = galsim.utilities.unweighted_moments(im)
        print('%s, Moments Mx, My = (%f, %f):'%(name, mom['Mx'], mom['My']))
        if center is None:
            np.testing.assert_almost_equal(mom['Mx'], ref_mom['Mx'], decimal = 1)
            np.testing.assert_almost_equal(mom['My'], ref_mom['My'], decimal = 1)
        else:
            np.testing.assert_almost_equal(ref_mom['Mx'] + treering_amplitude * center[0] / 1000,
                                           mom['Mx'], decimal=1)
            np.testing.assert_almost_equal(ref_mom['My'] + treering_amplitude * center[1] / 1000,
                                           mom['My'], decimal=1)

    assert_raises(TypeError, galsim.SiliconSensor, treering_func=lambda x:np.cos(x))
    assert_raises(TypeError, galsim.SiliconSensor, treering_func=tr7, treering_center=(3,4))
Exemple #6
0
def test_bf_slopes():
    """Test the brighter-fatter slopes
    with both the B-F effect and diffusion turned on and off.
    """
    from scipy import stats
    simple = galsim.Sensor()

    init_flux = 200000
    obj = galsim.Gaussian(flux=init_flux, sigma=0.3)

    num_fluxes = 5
    x_moments = np.zeros([num_fluxes, 3])
    y_moments = np.zeros([num_fluxes, 3])
    fluxes = np.zeros([num_fluxes])

    for fluxmult in range(num_fluxes):
        rng1 = galsim.BaseDeviate(5678)
        rng2 = galsim.BaseDeviate(5678)
        rng3 = galsim.BaseDeviate(5678)
        # silicon1 has diffusion turned off, silicon2 has it turned on.
        silicon1 = galsim.SiliconSensor(rng=rng1, diffusion_factor=0.0)
        silicon2 = galsim.SiliconSensor(rng=rng2)

        # We'll draw the same object using SiliconSensor, Sensor, and the default (sensor=None)
        im1 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=silicon1 (diffusion off)
        im2 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=silicon2 (diffusion on)
        im3 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=simple

        obj1 = obj * (fluxmult + 1)

        obj1.drawImage(im1, method='phot', poisson_flux=False, sensor=silicon1, rng=rng1)
        obj1.drawImage(im2, method='phot', poisson_flux=False, sensor=silicon2, rng=rng2)
        obj1.drawImage(im3, method='phot', poisson_flux=False, sensor=simple, rng=rng3)

        print('Moments Mx, My, Mxx, Myy, Mxy for im1, flux = %f:'%obj1.flux)
        mom = galsim.utilities.unweighted_moments(im1)
        x_moments[fluxmult,0] = mom['Mxx']
        y_moments[fluxmult,0] = mom['Myy']
        print('Moments Mx, My, Mxx, Myy, Mxy for im2, flux = %f:'%obj1.flux)
        mom = galsim.utilities.unweighted_moments(im2)
        x_moments[fluxmult,1] = mom['Mxx']
        y_moments[fluxmult,1] = mom['Myy']
        print('Moments Mx, My, Mxx, Myy, Mxy for im3, flux = %f:'%obj1.flux)
        mom = galsim.utilities.unweighted_moments(im3)
        x_moments[fluxmult,2] = mom['Mxx']
        y_moments[fluxmult,2] = mom['Myy']
        fluxes[fluxmult] = im1.array.max()
    print('fluxes = ',fluxes)
    print('x_moments = ',x_moments[:,0])
    print('y_moments = ',y_moments[:,0])
    x_slope, intercept, r_value, p_value, std_err = stats.linregress(fluxes,x_moments[:,0])
    y_slope, intercept, r_value, p_value, std_err = stats.linregress(fluxes,y_moments[:,0])
    x_slope *= 50000.0 * 100.0
    y_slope *= 50000.0 * 100.0
    print('With BF turned on, diffusion off, x_slope = %.3f, y_slope = %.3f %% per 50K e-'%(
            x_slope, y_slope ))
    assert x_slope > 0.5
    assert y_slope > 0.5
    x_slope, intercept, r_value, p_value, std_err = stats.linregress(fluxes,x_moments[:,1])
    y_slope, intercept, r_value, p_value, std_err = stats.linregress(fluxes,y_moments[:,1])
    x_slope *= 50000.0 * 100.0
    y_slope *= 50000.0 * 100.0
    print('With BF turned on, diffusion on, x_slope = %.3f, y_slope = %.3f %% per 50K e-'%(
            x_slope, y_slope ))
    assert x_slope > 0.5
    assert y_slope > 0.5
    x_slope, intercept, r_value, p_value, std_err = stats.linregress(fluxes,x_moments[:,2])
    y_slope, intercept, r_value, p_value, std_err = stats.linregress(fluxes,y_moments[:,2])
    x_slope *= 50000.0 * 100.0
    y_slope *= 50000.0 * 100.0
    print('With BF turned off, x_slope = %.3f, y_slope = %.3f %% per 50K e-'%(x_slope, y_slope ))
    assert abs(x_slope) < 0.5
    assert abs(y_slope) < 0.5
Exemple #7
0
def test_silicon_fft():
    """Test that drawing with method='fft' also works for SiliconSensor.
    """
    # Lower this somewhat so we get more accurate fluxes from the FFT.
    # (Still only accurate to 3 d.p. though.)
    gsparams = galsim.GSParams(maxk_threshold=1.e-5)
    obj = galsim.Gaussian(flux=3539, sigma=0.3, gsparams=gsparams)

    # We'll draw the same object using SiliconSensor, Sensor, and the default (sensor=None)
    im1 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=silicon
    im2 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=simple
    im3 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=None

    rng = galsim.BaseDeviate(5678)
    silicon = galsim.SiliconSensor(rng=rng, diffusion_factor=0.0)
    simple = galsim.Sensor()

    obj.drawImage(im1, method='fft', sensor=silicon, rng=rng)
    obj.drawImage(im2, method='fft', sensor=simple, rng=rng)
    obj.drawImage(im3, method='fft')

    printval(im1,im2)

    r1 = im1.calculateMomentRadius(flux=obj.flux)
    r2 = im2.calculateMomentRadius(flux=obj.flux)
    r3 = im3.calculateMomentRadius(flux=obj.flux)
    print('Flux = %.0f:  sum        peak         radius'%obj.flux)
    print('im1:         %.1f     %.2f       %f'%(im1.array.sum(),im1.array.max(), r1))
    print('im2:         %.1f     %.2f       %f'%(im2.array.sum(),im2.array.max(), r2))
    print('im3:         %.1f     %.2f       %f'%(im3.array.sum(),im3.array.max(), r3))

    # First, im2 and im3 should be almost exactly equal.  Not precisely, since im2 was made with
    # subsampling and then binning, so the FFT ringing is different (im3 is worse in this regard,
    # since it used convolution with a larger pixel).  So 3 digits is all we manage to get here.
    np.testing.assert_almost_equal(im2.array, im3.array, decimal=3)

    # im1 should be similar, but not equal
    np.testing.assert_almost_equal(im1.array/obj.flux, im2.array/obj.flux, decimal=2)

    # Fluxes should all equal obj.flux
    np.testing.assert_almost_equal(im1.array.sum(), obj.flux, decimal=3)
    np.testing.assert_almost_equal(im2.array.sum(), obj.flux, decimal=3)
    np.testing.assert_almost_equal(im3.array.sum(), obj.flux, decimal=3)
    np.testing.assert_almost_equal(im1.added_flux, obj.flux, decimal=3)
    np.testing.assert_almost_equal(im2.added_flux, obj.flux, decimal=3)
    np.testing.assert_almost_equal(im3.added_flux, obj.flux, decimal=3)

    # Sizes are all about equal since flux is not large enough for B/F to be significant
    sigma_r = 1./np.sqrt(obj.flux) * im1.scale
    np.testing.assert_allclose(r1, r2, atol=2.*sigma_r)
    np.testing.assert_allclose(r2, r3, atol=2.*sigma_r)

    # Repeat with 20X more photons where the brighter-fatter effect should kick in more.
    obj *= 200
    obj.drawImage(im1, method='fft', sensor=silicon, rng=rng)
    obj.drawImage(im2, method='fft', sensor=simple, rng=rng)
    obj.drawImage(im3, method='fft')

    r1 = im1.calculateMomentRadius(flux=obj.flux)
    r2 = im2.calculateMomentRadius(flux=obj.flux)
    r3 = im3.calculateMomentRadius(flux=obj.flux)
    print('Flux = %.0f:  sum        peak          radius'%obj.flux)
    print('im1:         %.1f     %.2f       %f'%(im1.array.sum(),im1.array.max(), r1))
    print('im2:         %.1f     %.2f       %f'%(im2.array.sum(),im2.array.max(), r2))
    print('im3:         %.1f     %.2f       %f'%(im3.array.sum(),im3.array.max(), r3))

    # Fluxes should still be fine.
    np.testing.assert_almost_equal(im1.array.sum(), obj.flux, decimal=1)
    np.testing.assert_almost_equal(im2.array.sum(), obj.flux, decimal=1)
    np.testing.assert_almost_equal(im3.array.sum(), obj.flux, decimal=1)
    np.testing.assert_almost_equal(im1.added_flux, obj.flux, decimal=1)
    np.testing.assert_almost_equal(im2.added_flux, obj.flux, decimal=1)
    np.testing.assert_almost_equal(im3.added_flux, obj.flux, decimal=1)

    # Sizes for 2,3 should be about equal, but 1 should be larger.
    sigma_r = 1./np.sqrt(obj.flux) * im1.scale
    print('check |r2-r3| = %f <? %f'%(np.abs(r2-r3), 2.*sigma_r))
    np.testing.assert_allclose(r2, r3, atol=2.*sigma_r)
    print('check |r1-r3| = %f >? %f'%(np.abs(r1-r3), 2.*sigma_r))
    assert r1-r3 > 2.*sigma_r
Exemple #8
0
fig, ax = plt.subplots()
oversampling = 16
for filter in ['g', 'z', 'y']:
    bandpass = galsim.Bandpass("LSST_{}.dat".format(filter), wave_type='nm')
    Ts = []
    for depth in depths:
        depth_pix = depth / 10
        surface_ops = [
            galsim.WavelengthSampler(sed, bandpass, rng=bd),
            galsim.FRatioAngles(1.234, 0.606, rng=bd),
            galsim.FocusDepth(depth_pix),
            galsim.Refraction(3.9)  # approx number for Silicon
        ]
        img = obj.drawImage(
            sensor=galsim.SiliconSensor(),
            method='phot',
            n_photons=1_000_000,
            surface_ops=surface_ops,
            scale=0.2 /
            oversampling,  # oversample pixels to better resolve PSF size
            nx=32 * oversampling,  # 6.4 arcsec stamp
            ny=32 * oversampling,
        )
        Ts.append(img.calculateMomentRadius())
    Ts = np.array(Ts) / 0.2 * 10 * oversampling  # convert arcsec -> micron
    ax.scatter(depths, Ts, label=filter)
ax.set_ylim(2, 7)
ax.axvline(0, c='k')
ax.legend()
ax.set_xlabel("<- intrafocal   focus(micron)   extrafocal ->")
Exemple #9
0
def main(argv):
    """
    Make images to be used for characterizing the brighter-fatter effect
      - Each fits file is 10 x 10 postage stamps.
      - Each postage stamp is 40 x 40 pixels.
      - There are 2 fits files, one with tree rings and one without
      - Each image is in output/tr_nfile.fits, where nfile ranges from 1-2.
    """
    logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout)
    logger = logging.getLogger("tr_plots")

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

    nx_tiles = 10                   #
    ny_tiles = 10                   #
    stamp_xsize = 40                #
    stamp_ysize = 40                #

    random_seed = 6424512           #

    pixel_scale = 0.2               # arcsec / pixel
    sky_level = 0.01                # ADU / arcsec^2

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

    gal_sigma = 1.0     # pixels
    psf_sigma = 0.01     # pixels
    pixel_scale = 1.0
    noise = 0.01        # standard deviation of the counts in each pixel

    logger.info('Starting tr_plots using:')
    logger.info('    - image with %d x %d postage stamps',nx_tiles,ny_tiles)
    logger.info('    - postage stamps of size %d x %d pixels',stamp_xsize,stamp_ysize)

    rng = galsim.BaseDeviate(5678)    
    sensor1 = galsim.SiliconSensor(rng=rng, diffusion_factor=0.0)
    # The following makes a sensor with hugely magnified tree rings so you can see the effect
    sensor2 = galsim.SiliconSensor(rng=rng, diffusion_factor = 0.0, treeringamplitude = 1.00)

    for nfile in range(1,3):
        starttime = time.time()
        exec("sensor = sensor%d"%nfile)

        gal_file_name = os.path.join('output','tr_%d.fits'%nfile)
        sex_file_name = os.path.join('output','tr_%d_SEX.fits.cat.reg'%nfile)
        sexfile = open(sex_file_name, 'w')
        gal_flux = 2.0e5    # total counts on the image
        # Define the galaxy profile
        gal = galsim.Gaussian(flux=gal_flux, sigma=gal_sigma)
        logger.debug('Made galaxy profile')

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

        # This profile is placed with different orientations and noise realizations
        # at each postage stamp in the gal image.
        gal_image = galsim.ImageF(stamp_xsize * nx_tiles-1 , stamp_ysize * ny_tiles-1,
                                  scale=pixel_scale)
        psf_image = galsim.ImageF(stamp_xsize * nx_tiles-1 , stamp_ysize * ny_tiles-1,
                                  scale=pixel_scale)

        k = 0
        for iy in range(ny_tiles):
            for ix in range(nx_tiles):
                # The normal procedure for setting random numbers in GalSim is to start a new
                # random number generator for each object using sequential seed values.
                # This sounds weird at first (especially if you were indoctrinated by Numerical 
                # Recipes), but for the boost random number generator we use, the "random" 
                # number sequences produced from sequential initial seeds are highly uncorrelated.
                # 
                # The reason for this procedure is that when we use multiple processes to build
                # our images, we want to make sure that the results are deterministic regardless
                # of the way the objects get parcelled out to the different processes. 
                #
                # Of course, this script isn't using multiple processes, so it isn't required here.
                # However, we do it nonetheless in order to get the same results as the config
                # version of this demo script (demo5.yaml).
                ud = galsim.UniformDeviate(random_seed+k)

                # Any kind of random number generator can take another RNG as its first 
                # argument rather than a seed value.  This makes both objects use the same
                # underlying generator for their pseudo-random values.
                #gd = galsim.GaussianDeviate(ud, sigma=gal_ellip_rms)

                # The -1's in the next line are to provide a border of
                # 1 pixel between postage stamps
                b = galsim.BoundsI(ix*stamp_xsize+1 , (ix+1)*stamp_xsize-1, 
                                   iy*stamp_ysize+1 , (iy+1)*stamp_ysize-1)

                sub_gal_image = gal_image[b]
                sub_psf_image = psf_image[b]
                #print "ix = %d, iy = %d, cenx = %d, ceny = %d"%(ix,iy,sub_gal_image.center().x,sub_gal_image.center().y)
                # Make the final image, convolving with the (unshifted) psf
                final_gal = galsim.Convolve([psf,gal])

                # Draw the image
                final_gal.drawImage(sub_gal_image, method = 'phot', sensor=sensor, rng = rng)

                x = b.center().x
                y = b.center().y
                k = k+1
                sexline = 'circle %f %f %f\n'%(x,y,gal_sigma/pixel_scale)
                sexfile.write(sexline)

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

        # Now write the images to disk.
        #psf_image.write(psf_file_name)
        #logger.info('Wrote PSF file %s',psf_file_name)

        gal_image.write(gal_file_name)
        logger.info('Wrote image to %r',gal_file_name)  # using %r adds quotes around filename for us

        finishtime = time.time()
        print("Time to complete file %d = %.2f seconds\n"%(nfile, finishtime-starttime))
Exemple #10
0
    def process_photons(self, image, skyCounts, detector, chunk_size=int(5e6)):
        tree_rings = detector.tree_rings

        # Add photons by amplifier since a full 4k x 4k sensor uses too much
        # memory to represent all of the pixel vertices.

        # imSim images use the Camera Coordinate System where the
        # parallel transfer direction is along the x-axis:
        nx, ny = 2, 8
        nrow, ncol = image.array.shape
        dx = ncol // nx  # number of pixels in x for an amp
        dy = nrow // ny  # number of pixels in y

        config = get_config()
        # Disable the updating of the pixel boundaries by
        # setting nrecalc to 1e300
        nrecalc = 1e300
        sensor = galsim.SiliconSensor(rng=self.randomNumbers,
                                      nrecalc=nrecalc,
                                      strength=config['ccd']['bf_strength'],
                                      treering_center=tree_rings.center,
                                      treering_func=tree_rings.func,
                                      transpose=True)
        for i in range(nx):
            # galsim boundaries start at 1 and include pixels at both ends.
            xmin = i * dx + 1
            xmax = (i + 1) * dx
            for j in range(ny):
                self.logger.info("ESOSiliconSkyModel: processing amp %d" %
                                 (i * ny + j + 1))
                ymin = j * dy + 1
                ymax = (j + 1) * dy
                # Actual bounds of the amplifier region.
                amp_bounds = galsim.BoundsI(xmin, xmax, ymin, ymax)

                # Create a temporary image to contain the single amp data
                # with a 1-pixel buffer around the perimeter.
                temp_image = galsim.ImageF(ncol, nrow)
                bounds = (
                    galsim.BoundsI(xmin - 1, xmax + 1, ymin - 1, ymax + 1)
                    & temp_image.bounds)
                temp_amp = temp_image[bounds]
                nphotons = self.get_nphotons(temp_amp, skyCounts)
                chunks = [chunk_size] * (nphotons // chunk_size)
                if nphotons % chunk_size > 0:
                    chunks.append(nphotons % chunk_size)

                for ichunk, nphot in enumerate(chunks):
                    photon_array = self.get_photon_array(temp_amp, nphot)
                    self.logger.info("chunk %d of %d", ichunk + 1, len(chunks))

                    self.waves.applyTo(photon_array)
                    self.angles.applyTo(photon_array)

                    # Accumulate the photons on the temporary amp image.
                    sensor.accumulate(photon_array,
                                      temp_amp,
                                      resume=(ichunk > 0))
                # Add the temp_amp image to the final image, excluding
                # the 1-pixel buffer.
                image[amp_bounds] += temp_image[amp_bounds]
        return image
Exemple #11
0
# @param N            The number of photons to store in this PhotonArray.  This value cannot be
#                     changed.
# @param x            Optionally, the initial x values. [default: None]
# @param y            Optionally, the initial y values. [default: None]
# @param flux         Optionally, the initial flux values. [default: None]
# @param dxdz         Optionally, the initial dxdz values. [default: None]
# @param dydz         Optionally, the initial dydz values. [default: None]
# @param wavelength   Optionally, the initial wavelength values. [default: None]
# """
photons.x = 0.5
photons.y = 0.5
photons.flux = 1.

# Accumulate these photons
sensor = galsim.SiliconSensor(strength=1,
                              diffusion_factor=1,
                              qdist=4,
                              name='lsst_itl_32')

# """--------------------------------------------------

#     A model of a silicon-based CCD sensor that converts photons to electrons at a wavelength-
#     dependent depth (probabilistically) and drifts them down to the wells, properly taking
#     into account the repulsion of previously accumulated electrons (known as the brighter-fatter
#     effect).
#     There are currently three sensors shipped with GalSim, which you can specify as the `name`
#     parameter mentioned below.
#         lsst_itl_8      The ITL sensor being used for LSST, using 8 points along each side of the
#                         pixel boundaries.
#         lsst_itl_32     The ITL sensor being used for LSST, using 32 points along each side of the
#                         pixel boundaries.  (This is more accurate than the lsst_itl_8, but slower.)
#         lsst_etv_32     The ETV sensor being used for LSST, using 32 points along each side of the
Exemple #12
0
    def addNoiseAndBackground(self,
                              image,
                              bandpass=None,
                              m5=None,
                              FWHMeff=None,
                              photParams=None,
                              detector=None):
        """
        Add the sky level counts to the image, rescale by the distorted
        pixel areas to account for tree rings, etc., then add Poisson noise.
        This implementation is based on GalSim/devel/lsst/treering_skybg2.py.
        """
        if detector is None:
            raise RuntimeError("A GalSimDetector object must be provided.")

        # Create a SiliconSensor object to handle the calculations
        # of the quantities related to the pixel boundary distortions
        # from electrostatic effects such as tree rings, etc..
        # Use the transpose=True option since "eimages" of LSST sensors
        # follow the Camera Coordinate System convention where the
        # parallel transfer direction is along the x-axis.
        config = get_config()
        nrecalc = 1e300  # disable pixel boundary updating.
        sensor = galsim.SiliconSensor(
            rng=self.randomNumbers,
            nrecalc=nrecalc,
            strength=config['ccd']['bf_strength'],
            treering_func=detector.tree_rings.func,
            treering_center=detector.tree_rings.center,
            transpose=True)

        # Loop over 1/2 amplifiers to save memory when storing the 36
        # pixel vertices per pixel. The 36 vertices arise from the 8
        # vertices per side + 4 corners (8*(4 sides) + 4 = 36).  This
        # corresponds to 72 floats per pixel (x and y coordinates) for
        # representing the pixel distortions in memory.
        nrow, ncol = image.array.shape
        nx, ny = 4, 8
        dx = ncol // nx
        dy = nrow // ny

        for i in range(nx):
            xmin = i * dx + 1
            xmax = (i + 1) * dx
            for j in range(ny):
                self.logger.debug(
                    "FastSiliconSkyModel: processing amp region %d" %
                    (i * ny + j + 1))
                ymin = j * dy + 1
                ymax = (j + 1) * dy

                # Create a temporary image with the detector wcs to
                # contain the single amp data.
                temp_image = galsim.ImageF(ncol, nrow, wcs=detector.wcs)

                # Include a 2-pixel buffer around the perimeter of the
                # amp region to account for charge redistribution
                # across pixel boundaries into and out of neighboring
                # segments.
                buf = 2
                bounds = (galsim.BoundsI(xmin - buf, xmax + buf, ymin - buf,
                                         ymax + buf)
                          & temp_image.bounds)
                temp_amp = temp_image[bounds]

                # Compute the pixel areas as distorted by tree rings, etc..
                sensor_area = sensor.calculate_pixel_areas(temp_amp)

                # Apply distortion from wcs.
                temp_amp.wcs.makeSkyImage(temp_amp, sky_level=1.)

                # Since sky_level was 1, the temp_amp array at this
                # point contains the pixel areas.
                mean_pixel_area = temp_amp.array.mean()

                # Scale by the sky counts.
                temp_amp *= self.sky_counts(detector.name) / mean_pixel_area

                # Include pixel area scaling from electrostatic distortions.
                temp_amp *= sensor_area

                # Add Poisson noise.
                noise = galsim.PoissonNoise(self.randomNumbers)
                temp_amp.addNoise(noise)

                # Actual bounds of the amplifier segment to be filled.
                amp_bounds = galsim.BoundsI(xmin, xmax, ymin, ymax)

                # Add the single amp image to the final full CCD image.
                image[amp_bounds] += temp_image[amp_bounds]
        return image
Exemple #13
0
def make_flat(gs_det,
              counts_per_iter,
              niter,
              rng,
              buf=2,
              logger=None,
              wcs=None):
    """
    Create a flat by successively adding lower level flat sky images.
    This is based on
    https://github.com/GalSim-developers/GalSim/blob/releases/2.0/devel/lsst/treering_flat.py

    Full LSST CCDs are assembled one amp at a time to limit the memory
    used by the galsim.SiliconSensor model.

    Parameters
    ----------
    gs_det: GalSimDetector
        The detector in the LSST focalplane to use.  This object
        contains the CCD pixel geometry, WCS, and treering info.
    counts_per_iter: int
        The mean number of electrons/pixel to add at each iteration.
    niter: int
        Number of iterations. Final flat will have niter*counts_per_iter
        electrons/pixel.
    rng: galsim.BaseDeviate
        Random number generator.
    buf: int [2]
        Pixel buffer around each to account for charge redistribution
        across pixel boundaries.
    logger: logging.Logger [None]
        Logging object. If None, then a default logger will be used.
    wcs: galsim.WCS [None]
        Alternative WCS object. If None, then gs_det.wcs will be used.

    Returns
    -------
    galsim.ImageF
    """
    config = desc.imsim.get_config()

    if logger is None:
        logger = desc.imsim.get_logger('DEBUG', name='make_flat')
    if wcs is None:
        wcs = gs_det.wcs
    ncol = gs_det.xMaxPix - gs_det.xMinPix + 1
    nrow = gs_det.yMaxPix - gs_det.yMinPix + 1
    flat = galsim.ImageF(ncol, nrow, wcs=wcs)
    sensor = galsim.SiliconSensor(rng=rng,
                                  strength=config['ccd']['bf_strength'],
                                  treering_center=gs_det.tree_rings.center,
                                  treering_func=gs_det.tree_rings.func,
                                  transpose=True)

    # Create a noise-free base image to add at each iteration.
    base_image = galsim.ImageF(ncol, nrow, wcs=wcs)
    base_image.wcs.makeSkyImage(base_image, sky_level=1.)
    mean_pixel_area = base_image.array.mean()
    sky_level_per_iter = counts_per_iter / mean_pixel_area
    base_image *= sky_level_per_iter

    noise = galsim.PoissonNoise(rng)

    # Build up the full CCD by amplifier segment to limit the memory
    # used by the silicon model.
    nx, ny = 2, 8
    dx = ncol // nx
    dy = nrow // ny
    for i in range(nx):
        xmin = i * dx + 1
        xmax = (i + 1) * dx
        for j in range(ny):
            logger.info("amp: %d, %d", i, j)
            ymin = j * dy + 1
            ymax = (j + 1) * dy
            temp_image = galsim.ImageF(ncol, nrow, wcs=wcs)
            bounds = (
                galsim.BoundsI(xmin - buf, xmax + buf, ymin - buf, ymax + buf)
                & temp_image.bounds)
            temp_amp = temp_image[bounds]
            for it in range(niter):
                logger.debug("iter %d", it)
                temp = sensor.calculate_pixel_areas(temp_amp)
                temp /= temp.array.mean()
                temp *= base_image[bounds]
                temp.addNoise(noise)
                temp_amp += temp
            amp_bounds = galsim.BoundsI(xmin, xmax, ymin, ymax)
            flat[amp_bounds] += temp_image[amp_bounds]
    return flat
Exemple #14
0
def main(argv):
    """
    About as simple as it gets:
      - Use a circular Gaussian profile for the galaxy.
      - Convolve it by a circular Gaussian PSF.
      - Add Gaussian noise to the image.
    """
    # In non-script code, use getLogger(__name__) at module scope instead.
    logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout)
    logger = logging.getLogger("demo1")

    # MOD crank up the flux
    data_index = 1
    flux_factor = 1
    gal_flux = 2.e6 * flux_factor   # total counts on the image
    gal_sigma = 2.     # arcsec
    psf_sigma = 1.     # arcsec
    # MOD decrease the resolution
    pixel_scale = 2.0  # arcsec / pixel
    noise = 30.        # standard deviation of the counts in each pixel

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

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

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

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

    # Draw the image with a particular pixel scale, given in arcsec/pixel.
    # The returned image has a member, added_flux, which is gives the total flux actually added to
    # the image.  One could use this value to check if the image is large enough for some desired
    # accuracy level.  Here, we just ignore it.
    # MOD use the 'phot' method with an ideal sensor
    image = final.drawImage(scale=pixel_scale,
                            method='phot',
                            sensor=galsim.Sensor())
    logger.debug('Made image of the profile: flux = %f, added_flux = %f', gal_flux, image.added_flux)
    file_name = os.path.join('output', 'spot_nobf_' + str(data_index) + '.fits')

    # Write the image to a file
    if not os.path.isdir('output'):
        os.mkdir('output')

    # Note: if the file already exists, this will overwrite it.
    image.write(file_name)
    logger.info('Wrote Ideal image to %r' % file_name)  # using %r adds quotes around filename for us

    results = image.FindAdaptiveMom()

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

    sensor_name = 'lsst_e2v_50_32'
    # sensor_name = 'lsst_itl_50_32'
    # MOD use the 'phot' method, with a e2v sensor
    rng = galsim.BaseDeviate(5678)
    image = final.drawImage(scale=pixel_scale,
                            method='phot',
                            sensor=galsim.SiliconSensor(name=sensor_name,
                                                        transpose=False,
                                                        rng=rng,
                                                        diffusion_factor=0.0))
    logger.debug('Made image of the profile: flux = %f, added_flux = %f', gal_flux, image.added_flux)
    file_name = os.path.join('output', 'spot_' + sensor_name + '_bf_' + str(data_index) + '.fits')

    # Add Gaussian noise to the image with specified sigma
    # MOD actually, don't add any noise
    # image.addNoise(galsim.GaussianNoise(sigma=noise))
    # logger.debug('Added Gaussian noise')
    logger.debug('No noise added')

    # Write the image to a file
    if not os.path.isdir('output'):
        os.mkdir('output')

    # Note: if the file already exists, this will overwrite it.
    image.write(file_name)
    logger.info('Wrote bfed image to %r' % file_name)  # using %r adds quotes around filename for us

    results = image.FindAdaptiveMom()

    logger.info('HSM reports that the image has observed shape and size:')
    logger.info('    e1 = %.3f, e2 = %.3f, sigma = %.3f (pixels)', results.observed_shape.e1,
                results.observed_shape.e2, results.moments_sigma)
    logger.info('Expected values in the limit that pixel response and noise are negligible:')
    logger.info('    e1 = %.3f, e2 = %.3f, sigma = %.3f', 0.0, 0.0,
                math.sqrt(gal_sigma**2 + psf_sigma**2)/pixel_scale)
Exemple #15
0
def test_flat():
    """Test building a flat field image using the Silicon class.
    """
    # Note: This test is based on a script devel/lsst/treering_flat.py

    if __name__ == '__main__':
        nx = 200
        ny = 200
        nflats = 20
        niter = 50
        toler = 0.01
    else:
        nx = 50
        ny = 50
        nflats = 3
        niter = 20  # Seem to really need 20 or more iterations to get covariances close.
        toler = 0.05

    counts_total = 80.e3
    counts_per_iter = counts_total / niter

    # Silicon sensor with tree rings
    seed = 31415
    rng = galsim.UniformDeviate(seed)
    treering_func = galsim.SiliconSensor.simple_treerings(0.26, 47)
    treering_center = galsim.PositionD(0,0)
    sensor = galsim.SiliconSensor(rng=rng,
                                   treering_func=treering_func, treering_center=treering_center)

    # Use a non-trivial WCS to make sure that works properly.
    wcs = galsim.FitsWCS('fits_files/tnx.fits')
    # We add on a border of 2 pixels, since the outer row/col get a little messed up by photons
    # falling off the edge, but not coming on from the other direction.
    # We do 2 rows/cols rather than just 1 to be safe, since I think diffusion can probably go
    # 2 pixels, even though the deficit is only really evident on the outer pixel.
    nborder = 2
    base_image = galsim.ImageF(nx+2*nborder, ny+2*nborder, wcs=wcs)
    base_image.wcs.makeSkyImage(base_image, sky_level=1.)

    # Rescale so that the mean sky level per pixel is skyCounts
    mean_pixel_area = base_image.array.mean()
    sky_level_per_iter = counts_per_iter / mean_pixel_area  # in ADU/arcsec^2 now.
    base_image *= sky_level_per_iter

    # The base_image now has the right level to account for the WCS distortion, but not any sensor
    # effects.
    # This is the noise-free level that we want to add each iteration modulated by the sensor.

    noise = galsim.PoissonNoise(rng)
    flats = []

    for n in range(nflats):
        print('n = ',n)
        # image is the image that we will build up in steps.
        image = galsim.ImageF(nx+2*nborder, ny+2*nborder, wcs=wcs)

        for i in range(niter):
            # temp is the additional flux we will add to the image in this iteration.
            # Start with the right area due to the sensor effects.
            temp = sensor.calculate_pixel_areas(image)
            temp /= temp.array.mean()

            # Multiply by the base image to get the right mean level and wcs effects
            temp *= base_image

            # Finally, add noise.  What we have here so far is the expectation value in each pixel.
            # We need to realize this according to Poisson statistics with these means.
            temp.addNoise(noise)

            # Add this to the image we are building up.
            image += temp

        # Cut off the outer border where things don't work quite right.
        image = image.subImage(galsim.BoundsI(1+nborder,nx+nborder,1+nborder,ny+nborder))
        flats.append(image.array)

    # These are somewhat noisy, so compute for all pairs and average them.
    mean = var = cov01 = cov10 = cov11a = cov11b = cov02 = cov20 = 0
    n = len(flats)
    npairs = 0

    for i in range(n):
        flati = flats[i]
        print('mean ',i,' = ',flati.mean())
        mean += flati.mean()
        for j in range(i+1,n):
            flatj = flats[j]
            diff = flati - flatj
            var += diff.var()/2
            cov01 += np.mean(diff[1:,:] * diff[:-1,:])
            cov10 += np.mean(diff[:,1:] * diff[:,:-1])
            cov11a += np.mean(diff[1:,1:] * diff[:-1,:-1])
            cov11b += np.mean(diff[1:,:-1] * diff[:-1,1:])
            cov02 += np.mean(diff[2:,:] * diff[:-2,:])
            cov20 += np.mean(diff[:,2:] * diff[:,:-2])
            npairs += 1
    mean /= n
    var /= npairs
    cov01 /= npairs
    cov10 /= npairs
    cov11a /= npairs
    cov11b /= npairs
    cov02 /= npairs
    cov20 /= npairs

    print('var(diff)/2 = ',var, 0.93*counts_total)
    print('cov01 = ',cov01, 0.03*counts_total)   # Note: I don't actually know if these are
    print('cov10 = ',cov10, 0.015*counts_total)  # the right covariances...
    print('cov11a = ',cov11a, cov11a/counts_total)
    print('cov11b = ',cov11b, cov11b/counts_total)
    print('cov02 = ',cov02, cov02/counts_total)
    print('cov20 = ',cov20, cov20/counts_total)

    # Mean should be close to target counts
    np.testing.assert_allclose(mean, counts_total, rtol=toler)
    # Variance is a bit less than the mean due to B/F.
    np.testing.assert_allclose(var, 0.93 * counts_total, rtol=toler)
    # 01 and 10 covariances are significant.
    np.testing.assert_allclose(cov01, 0.03 * counts_total, rtol=30*toler)
    np.testing.assert_allclose(cov10, 0.015 * counts_total, rtol=60*toler)
    # The rest are small
    np.testing.assert_allclose(cov11a / counts_total, 0., atol=2*toler)
    np.testing.assert_allclose(cov11b / counts_total, 0., atol=2*toler)
    np.testing.assert_allclose(cov20 / counts_total, 0., atol=2*toler)
    np.testing.assert_allclose(cov02 / counts_total, 0., atol=2*toler)
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)
desc.imsim.read_config()
instcat_file = os.path.join(os.environ['IMSIM_DIR'], 'tests',
                            'tiny_instcat.txt')
instcat = desc.imsim.parsePhoSimInstanceFile(instcat_file)

pixel_scale = 0.2

skymodel = desc.imsim.ESOSiliconSkyModel(instcat.obs_metadata,
                                         instcat.phot_params,
                                         seed=args.seed,
                                         addNoise=False,
                                         addBackground=True,
                                         bundles_per_pix=args.bundles_per_pix)
waves = skymodel.waves
angles = skymodel.angles

for nxy in np.logspace(np.log10(args.nxy_min), np.log10(args.nxy_max),
                       args.npts):
    image = galsim.Image(int(nxy), int(nxy), scale=pixel_scale)
    nrecalc = int(args.nrecalc_factor * np.prod(image.array.shape))
    sensor = galsim.SiliconSensor(rng=skymodel.randomNumbers, nrecalc=nrecalc)
    photon_array = skymodel.get_photon_array(image, args.nphot)
    waves.applyTo(photon_array)
    angles.applyTo(photon_array)

    t0 = time.clock()
    sensor.accumulate(photon_array, image)
    print(int(nxy)**2, time.clock() - t0)
    sys.stdout.flush()
Exemple #18
0
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions, and the disclaimer given in the accompanying LICENSE
#    file.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions, and the disclaimer given in the documentation
#    and/or other materials provided with the distribution.
#

from __future__ import print_function
import galsim

obj = galsim.Gaussian(flux=3.539e6, sigma=0.1)

rng = galsim.BaseDeviate(5678)
silicon = galsim.SiliconSensor(rng=rng)

im = obj.drawImage(nx=17, ny=17, scale=0.3, dtype=float, method='phot', sensor=silicon)
im.setCenter(0,0)
im.write('test.fits')

print('im min = ',im.array.min())
print('im max = ',im.array.max())
print('im(0,0) = ',im(0,0))
print('+- 1 along column: ',im(0,1),im(0,-1))
print('+- 1 along row:    ',im(1,0),im(-1,0))

area_image = silicon.calculate_pixel_areas(im)
area_image.write('area.fits')
print('area min = ',area_image.array.min())
print('area max = ',area_image.array.max())
    niter = int(counts_total / counts_per_iter + 0.5)
    counts_per_iter = counts_total / niter  # Recalculate in case not even multiple.
    print('Total counts = {} = {} * {}'.format(counts_total, niter,
                                               counts_per_iter))

    # Not an LSST wcs, but just make sure this works properly with a non-trivial wcs.
    wcs = galsim.FitsWCS('../../tests/fits_files/tnx.fits')

    base_image = galsim.ImageF(nx + 2 * nborder, ny + 2 * nborder, wcs=wcs)
    print('image bounds = ', base_image.bounds)

    # nrecalc is actually irrelevant here, since a recalculation will be forced on each iteration.
    # Which is really the point.  We need to set coundsPerIter appropriately so that the B/F effect
    # doesn't change *too* much between iterations.
    sensor = galsim.SiliconSensor(rng=rng,
                                  treering_func=treering_func,
                                  treering_center=treering_center)

    # We also need to account for the distortion of the wcs across the image.
    # This expects sky_level in ADU/arcsec^2, not ADU/pixel.
    base_image.wcs.makeSkyImage(base_image, sky_level=1.)
    base_image.write('wcs_area.fits')

    # Rescale so that the mean sky level per pixel is skyCounts
    mean_pixel_area = base_image.array.mean()

    sky_level_per_iter = counts_per_iter / mean_pixel_area  # in ADU/arcsec^2 now.
    base_image *= sky_level_per_iter

    # The base_image has the right level to account for the WCS distortion, but not any sensor effects.
    # This is the noise-free level that we want to add each iteration modulated by the sensor.
spectrum_image.write(nobf_filename)
if display:
    galsim_sensor_image = spectrum_image.array.copy()

# now do it again, but with the BF effect
# spectrum_image = galsim.Image(smeared_spectrum2d, scale=.25)  # scale is angstroms/pixel
# interpolate the image so GalSim can manipulate it
# spectrum_interpolated = galsim.InterpolatedImage(spectrum_image)

spectrum_interpolated.drawImage(
    image=spectrum_image,
    method='phot',
    # center=(15,57),
    offset=(0, 0),  # this needs 4 digits
    sensor=galsim.SiliconSensor(name='lsst_e2v_50_32',
                                transpose=False,
                                rng=rng,
                                diffusion_factor=0.0))

print('image center:', spectrum_image.center)
print('image true center:', spectrum_image.true_center)
spectrum_image.write(yesbf_filename)
if display:
    galsim_bf_image = spectrum_image.array.copy()
''' block comment out the center correction
 apparently the correction is flux dependant, and therefore part of the data
"""Measure the offset between the two data sets:"""
with fits.open(nobf_filename) as hdul:
    galsim_sensor_image = hdul[0].data

    with fits.open(yesbf_filename) as hdul:
        galsim_bf_image = hdul[0].data
Exemple #21
0
def test_silicon():
    """Test the basic construction and use of the SiliconSensor class.
    """

    # Note: Use something quite small in terms of npixels so the B/F effect kicks in without
    # requiring a ridiculous number of photons
    obj = galsim.Gaussian(flux=10000, sigma=0.3)

    # We'll draw the same object using SiliconSensor, Sensor, and the default (sensor=None)
    im1 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=silicon
    im2 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=simple
    im3 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=None

    rng1 = galsim.BaseDeviate(5678)
    rng2 = galsim.BaseDeviate(5678)
    rng3 = galsim.BaseDeviate(5678)

    silicon = galsim.SiliconSensor(rng=rng1, diffusion_factor=0.0)
    simple = galsim.Sensor()

    # Start with photon shooting, since that's more straightforward.
    obj.drawImage(im1, method='phot', poisson_flux=False, sensor=silicon, rng=rng1)
    obj.drawImage(im2, method='phot', poisson_flux=False, sensor=simple, rng=rng2)
    obj.drawImage(im3, method='phot', poisson_flux=False, rng=rng3)

    # First, im2 and im3 should be exactly equal.
    np.testing.assert_array_equal(im2.array, im3.array)

    # im1 should be similar, but not equal
    np.testing.assert_almost_equal(im1.array/obj.flux, im2.array/obj.flux, decimal=2)

    # Now use a different seed for 3 to see how much of the variation is just from randomness.
    rng3.seed(234241)
    obj.drawImage(im3, method='phot', poisson_flux=False, rng=rng3)

    r1 = im1.calculateMomentRadius(flux=obj.flux)
    r2 = im2.calculateMomentRadius(flux=obj.flux)
    r3 = im3.calculateMomentRadius(flux=obj.flux)
    print('Flux = %.0f:  sum        peak          radius'%obj.flux)
    print('im1:         %.1f     %.2f       %f'%(im1.array.sum(),im1.array.max(), r1))
    print('im2:         %.1f     %.2f       %f'%(im2.array.sum(),im2.array.max(), r2))
    print('im3:         %.1f     %.2f       %f'%(im3.array.sum(),im3.array.max(), r3))

    # Fluxes should all equal obj.flux
    np.testing.assert_almost_equal(im1.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im2.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im3.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im1.added_flux, obj.flux, decimal=6)
    np.testing.assert_almost_equal(im2.added_flux, obj.flux, decimal=6)
    np.testing.assert_almost_equal(im3.added_flux, obj.flux, decimal=6)

    # Sizes are all about equal since flux is not large enough for B/F to be significant
    # Variance of Irr for Gaussian with Poisson noise is
    # Var(Irr) = Sum(I r^4) = 4Irr [using Gaussian kurtosis = 8sigma^2, Irr = 2sigma^2]
    # r = sqrt(Irr/flux), so sigma(r) = 1/2 r sqrt(Var(Irr))/Irr = 1/sqrt(flux)
    # Use 2sigma for below checks.
    sigma_r = 1. / np.sqrt(obj.flux) * im1.scale
    np.testing.assert_allclose(r1, r2, atol=2.*sigma_r)
    np.testing.assert_allclose(r2, r3, atol=2.*sigma_r)

    # Repeat with 100X more photons where the brighter-fatter effect should kick in more.
    obj *= 100
    rng1 = galsim.BaseDeviate(5678)
    rng2 = galsim.BaseDeviate(5678)
    rng3 = galsim.BaseDeviate(5678)

    obj.drawImage(im1, method='phot', poisson_flux=False, sensor=silicon, rng=rng1)
    obj.drawImage(im2, method='phot', poisson_flux=False, sensor=simple, rng=rng2)
    obj.drawImage(im3, method='phot', poisson_flux=False, rng=rng3)

    r1 = im1.calculateMomentRadius(flux=obj.flux)
    r2 = im2.calculateMomentRadius(flux=obj.flux)
    r3 = im3.calculateMomentRadius(flux=obj.flux)
    print('Flux = %.0f:  sum        peak          radius'%obj.flux)
    print('im1:         %.1f     %.2f       %f'%(im1.array.sum(),im1.array.max(), r1))
    print('im2:         %.1f     %.2f       %f'%(im2.array.sum(),im2.array.max(), r2))
    print('im3:         %.1f     %.2f       %f'%(im3.array.sum(),im3.array.max(), r3))

    # Fluxes should still be fine.
    np.testing.assert_almost_equal(im1.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im2.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im3.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im1.added_flux, obj.flux, decimal=6)
    np.testing.assert_almost_equal(im2.added_flux, obj.flux, decimal=6)
    np.testing.assert_almost_equal(im3.added_flux, obj.flux, decimal=6)

    # Sizes for 2,3 should be about equal, but 1 should be larger.
    sigma_r = 1. / np.sqrt(obj.flux) * im1.scale
    print('check |r2-r3| = %f <? %f'%(np.abs(r2-r3), 2.*sigma_r))
    np.testing.assert_allclose(r2, r3, atol=2.*sigma_r)
    print('check r1 - r3 = %f > %f due to brighter-fatter'%(r1-r2,sigma_r))
    assert r1 - r3 > 2*sigma_r

    # Check that it is really responding to flux, not number of photons.
    # Using fewer shot photons will mean each one encapsulates several electrons at once.
    obj.drawImage(im1, method='phot', n_photons=1000, poisson_flux=False, sensor=silicon,
                  rng=rng1)
    obj.drawImage(im2, method='phot', n_photons=1000, poisson_flux=False, sensor=simple,
                  rng=rng2)
    obj.drawImage(im3, method='phot', n_photons=1000, poisson_flux=False, rng=rng3)

    r1 = im1.calculateMomentRadius(flux=obj.flux)
    r2 = im2.calculateMomentRadius(flux=obj.flux)
    r3 = im3.calculateMomentRadius(flux=obj.flux)
    print('Flux = %.0f:  sum        peak          radius'%obj.flux)
    print('im1:         %.1f     %.2f       %f'%(im1.array.sum(),im1.array.max(), r1))
    print('im2:         %.1f     %.2f       %f'%(im2.array.sum(),im2.array.max(), r2))
    print('im3:         %.1f     %.2f       %f'%(im3.array.sum(),im3.array.max(), r3))

    np.testing.assert_almost_equal(im1.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im2.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im3.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im1.added_flux, obj.flux, decimal=6)
    np.testing.assert_almost_equal(im2.added_flux, obj.flux, decimal=6)
    np.testing.assert_almost_equal(im3.added_flux, obj.flux, decimal=6)

    print('check r1 - r3 = %f > %f due to brighter-fatter'%(r1-r2,sigma_r))
    assert r1 - r3 > 2*sigma_r

    # Can also get the stronger BF effect with the strength parameter.
    obj /= 100  # Back to what it originally was.
    rng1 = galsim.BaseDeviate(5678)
    rng2 = galsim.BaseDeviate(5678)
    rng3 = galsim.BaseDeviate(5678)

    silicon = galsim.SiliconSensor(dir='lsst_itl', strength=100., rng=rng1, diffusion_factor=0.0)
    obj.drawImage(im1, method='phot', poisson_flux=False, sensor=silicon, rng=rng1)
    obj.drawImage(im2, method='phot', poisson_flux=False, sensor=simple, rng=rng2)
    obj.drawImage(im3, method='phot', poisson_flux=False, rng=rng3)

    r1 = im1.calculateMomentRadius(flux=obj.flux)
    r2 = im2.calculateMomentRadius(flux=obj.flux)
    r3 = im3.calculateMomentRadius(flux=obj.flux)
    print('Flux = %.0f:  sum        peak          radius'%obj.flux)
    print('im1:         %.1f     %.2f       %f'%(im1.array.sum(),im1.array.max(), r1))
    print('im2:         %.1f     %.2f       %f'%(im2.array.sum(),im2.array.max(), r2))
    print('im3:         %.1f     %.2f       %f'%(im3.array.sum(),im3.array.max(), r3))

    # Fluxes should still be fine.
    np.testing.assert_almost_equal(im1.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im2.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im3.array.sum(), obj.flux, decimal=6)
    np.testing.assert_almost_equal(im1.added_flux, obj.flux, decimal=6)
    np.testing.assert_almost_equal(im2.added_flux, obj.flux, decimal=6)
    np.testing.assert_almost_equal(im3.added_flux, obj.flux, decimal=6)

    # Sizes for 2,3 should be about equal, but 1 should be larger.
    sigma_r = 1. / np.sqrt(obj.flux) * im1.scale
    print('check |r2-r3| = %f <? %f'%(np.abs(r2-r3), 2.*sigma_r))
    np.testing.assert_allclose(r2, r3, atol=2.*sigma_r)
    print('check r1 - r3 = %f > %f due to brighter-fatter'%(r1-r2,sigma_r))
    assert r1 - r3 > 2*sigma_r / 100

    # Check the construction with an explicit directory.
    s0 = galsim.SiliconSensor(rng=rng1)
    dir = os.path.join(galsim.meta_data.share_dir, 'sensors', 'lsst_itl')
    s1 = galsim.SiliconSensor(dir=dir, strength=1.0, rng=rng1, diffusion_factor=1.0, qdist=3,
                              nrecalc=10000)
    assert s0 == s1
    s1 = galsim.SiliconSensor(dir, 1.0, rng1, 1.0, 3, 10000)
    assert s0 == s1
    s2 = galsim.SiliconSensor(rng=rng1, dir='lsst_itl')
    assert s0 == s2
    s3 = galsim.SiliconSensor(rng=rng1, strength=10.)
    s4 = galsim.SiliconSensor(rng=rng1, diffusion_factor=2.0)
    s5 = galsim.SiliconSensor(rng=rng1, qdist=4)
    s6 = galsim.SiliconSensor(rng=rng1, nrecalc=12345)
    s7 = galsim.SiliconSensor(dir=dir, strength=1.5, rng=rng1, diffusion_factor=1.3, qdist=4,
                              nrecalc=12345)
    for s in [ s3, s4, s5, s6, s7 ]:
        assert silicon != s
        assert s != s0

    do_pickle(s0)
    do_pickle(s1)
    do_pickle(s7)

    try:
        np.testing.assert_raises(IOError, galsim.SiliconSensor, dir='junk')
        np.testing.assert_raises(IOError, galsim.SiliconSensor, dir='output')
        np.testing.assert_raises(RuntimeError, galsim.SiliconSensor, rng=3.4)
        np.testing.assert_raises(TypeError, galsim.SiliconSensor, 'lsst_itl', 'hello')
    except ImportError:
        print('The assert_raises tests require nose')
Exemple #22
0
def main():
    pr = cProfile.Profile()
    pr.enable()

    rng = galsim.UniformDeviate(8675309)

    wcs = galsim.FitsWCS('../tests/des_data/DECam_00154912_12_header.fits')
    image = galsim.Image(xsize, ysize, wcs=wcs)
    bandpass = galsim.Bandpass('LSST_r.dat', wave_type='nm').thin(0.1)
    base_wavelength = bandpass.effective_wavelength

    angles = galsim.FRatioAngles(fratio, obscuration, rng)
    sensor = galsim.SiliconSensor(rng=rng, nrecalc=nrecalc)

    # Figure out the local_sidereal time from the observation location and time.
    lsst_lat = '-30:14:23.76'
    lsst_long = '-70:44:34.67'
    obs_time = '2012-11-24 03:37:25.023964'  # From the header of the wcs file

    obs = astropy.time.Time(obs_time, scale='utc', location=(lsst_long + 'd', lsst_lat + 'd'))
    local_sidereal_time = obs.sidereal_time('apparent').value

    # Convert the ones we need below to galsim Angles.
    local_sidereal_time *= galsim.hours
    lsst_lat = galsim.Angle.from_dms(lsst_lat)

    times = []
    mem = []
    phot = []

    t0 = time.clock()
    for iobj in range(nobjects):
        sys.stderr.write('.')
        psf = make_psf(rng)
        gal = make_gal(rng)
        obj = galsim.Convolve(psf, gal)

        sed = get_sed(rng)
        waves = galsim.WavelengthSampler(sed=sed, bandpass=bandpass, rng=rng)

        image_pos = get_pos(rng)
        sky_coord = wcs.toWorld(image_pos)
        bounds, offset = calculate_bounds(obj, image_pos, image)

        ha = local_sidereal_time - sky_coord.ra
        dcr = galsim.PhotonDCR(base_wavelength=base_wavelength,
                               obj_coord=sky_coord, HA=ha, latitude=lsst_lat)

        surface_ops = (waves, angles, dcr)

        obj.drawImage(method='phot', image=image[bounds], offset=offset,
                      rng=rng, sensor=sensor,
                      surface_ops=surface_ops)

        times.append(time.clock() - t0)
        mem.append(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)
        phot.append(obj.flux)

    image.write('phot.fits')
    phot = np.cumsum(phot)
    make_plots(times, mem, phot)

    pr.disable()
    ps = pstats.Stats(pr).sort_stats('time')
    ps.print_stats(20)
Exemple #23
0
def test_sensor_wavelengths_and_angles():

    print('Starting test_wavelengths_and_angles')
    sys.stdout.flush()

    bppath = os.path.join(galsim.meta_data.share_dir, "bandpasses")
    sedpath = os.path.join(galsim.meta_data.share_dir, "SEDs")
    sed = galsim.SED(os.path.join(sedpath, 'CWW_E_ext.sed'), 'nm', 'flambda').thin()

    # Add the directions (not currently working?? seems to work - CL)
    fratio = 1.2
    obscuration = 0.2
    seed = 12345
    assigner = galsim.FRatioAngles(fratio, obscuration, seed)
    obj = galsim.Gaussian(flux=3539, sigma=0.3)

    if __name__ == "__main__":
        bands = ['r', 'i', 'z', 'y']
    else:
        bands = ['i'] # Only test the i band for nosetests

    for band in bands:
        bandpass = galsim.Bandpass(os.path.join(bppath, 'LSST_%s.dat'%band), 'nm').thin()
        rng3 = galsim.BaseDeviate(1234)
        sampler = galsim.WavelengthSampler(sed, bandpass, rng3)
        rng4 = galsim.BaseDeviate(5678)
        silicon = galsim.SiliconSensor(rng=rng4, diffusion_factor=0.0)

        # We'll draw the same object using SiliconSensor
        im1 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=silicon, no wavelengths
        im2 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=silicon, with wavelengths
        im3 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=silicon, with angles
        im4 = galsim.ImageD(64, 64, scale=0.3)  # Will use sensor=silicon, with wavelengths, angles

        rng1 = galsim.BaseDeviate(5678)
        rng2 = galsim.BaseDeviate(5678)
        rng3 = galsim.BaseDeviate(5678)
        rng4 = galsim.BaseDeviate(5678)

        # Use photon shooting first
        obj.drawImage(im1, method='phot', sensor=silicon, rng=rng1)
        obj.drawImage(im2, method='phot', sensor=silicon, surface_ops=[sampler], rng=rng2)
        obj.drawImage(im3, method='phot', sensor=silicon, surface_ops=[assigner], rng=rng3)
        obj.drawImage(im4, method='phot', sensor=silicon, surface_ops=[sampler, assigner], rng=rng4)

        r1 = im1.calculateMomentRadius(flux=obj.flux)
        r2 = im2.calculateMomentRadius(flux=obj.flux)
        r3 = im3.calculateMomentRadius(flux=obj.flux)
        r4 = im4.calculateMomentRadius(flux=obj.flux)
        print('Testing Wavelength and Angle sampling - %s band'%band)
        print('Flux = %.0f:                sum        peak          radius'%obj.flux)
        print('No lamb, no angles:         %.1f     %.2f       %f'%(
                im1.array.sum(),im1.array.max(), r1))
        print('W/ lamb, no angles:         %.1f     %.2f       %f'%(
                im2.array.sum(),im2.array.max(), r2))
        print('No lamb, w/ angles:         %.1f     %.2f       %f'%(
                im3.array.sum(),im3.array.max(), r3))
        print('W/ lamb, w/ angles:         %.1f     %.2f       %f'%(
                im4.array.sum(),im4.array.max(), r4))

        # r4 should be greater than r1 with wavelengths and angles turned on.
        sigma_r = 1. / np.sqrt(obj.flux) * im1.scale
        print('check r4 > r1 due to added wavelengths and angles')
        print('r1 = %f, r4 = %f, 2*sigma_r = %f'%(r1,r4,2.*sigma_r))
        assert r4 > r1

        # Now check fft
        obj.drawImage(im1, method='fft', sensor=silicon, rng=rng1)
        obj.drawImage(im2, method='fft', sensor=silicon, surface_ops=[sampler], rng=rng2)
        obj.drawImage(im3, method='fft', sensor=silicon, surface_ops=[assigner], rng=rng3)
        obj.drawImage(im4, method='fft', sensor=silicon, surface_ops=[sampler, assigner], rng=rng4)

        r1 = im1.calculateMomentRadius(flux=obj.flux)
        r2 = im2.calculateMomentRadius(flux=obj.flux)
        r3 = im3.calculateMomentRadius(flux=obj.flux)
        r4 = im4.calculateMomentRadius(flux=obj.flux)
        print('Testing Wavelength and Angle sampling - %s band'%band)
        print('Flux = %.0f:                sum        peak          radius'%obj.flux)
        print('No lamb, no angles:         %.1f     %.2f       %f'%(
                im1.array.sum(),im1.array.max(), r1))
        print('W/ lamb, no angles:         %.1f     %.2f       %f'%(
                im2.array.sum(),im2.array.max(), r2))
        print('No lamb, w/ angles:         %.1f     %.2f       %f'%(
                im3.array.sum(),im3.array.max(), r3))
        print('W/ lamb, w/ angles:         %.1f     %.2f       %f'%(
                im4.array.sum(),im4.array.max(), r4))

        # r4 should be greater than r1 with wavelengths and angles turned on.
        sigma_r = 1. / np.sqrt(obj.flux) * im1.scale
        print('check r4 > r1 due to added wavelengths and angles')
        print('r1 = %f, r4 = %f, 2*sigma_r = %f'%(r1,r4,2.*sigma_r))
        assert r4 > r1
Exemple #24
0
def test_silicon_area():
    """Test the Silicon class calculate_pixel_areas() function.
    """
    # Adding this test to compare to Poisson simulation
    # Craig Lage - 27Apr18
    # Draw a very small spot with 80K electrons
    # This puts all of the charge in the central pixel to
    # match how the Poisson simulations are done
    obj = galsim.Gaussian(flux=8.0E4, sigma=0.02)
    im = obj.drawImage(nx=9, ny=9, scale=0.3, dtype=float)
    im.setCenter(0,0)

    rng = galsim.BaseDeviate(5678)
    silicon = galsim.SiliconSensor(rng=rng)
    area_image = silicon.calculate_pixel_areas(im)
    # Get the area data from the Poisson simulation
    area_filename = silicon.vertex_file.split('/')[-1].strip('.dat')+'_areas.dat'
    area_dir = os.path.join(os.getcwd(),'sensor_validation/')
    area_data = np.loadtxt(area_dir+area_filename, skiprows = 1)
    # Now test that they are almost equal
    for line in area_data:
        nx = int(line[0]-4)
        ny = int(line[1]-4)
        poisson_area = line[2]
        galsim_area = area_image[nx,ny]
        #print(nx,ny,poisson_area,galsim_area)
        np.testing.assert_almost_equal(poisson_area/100.0, galsim_area, decimal=3)

    # Draw a smallish but very bright Gaussian image
    obj = galsim.Gaussian(flux=5.e5, sigma=0.2)
    im = obj.drawImage(nx=17, ny=17, scale=0.3, dtype=float)
    im.setCenter(0,0)

    print('im min = ',im.array.min())
    print('im max = ',im.array.max())
    print('im(0,0) = ',im(0,0))
    assert im(0,0) == im.array.max()
    np.testing.assert_almost_equal(im(0,0), 149462.06966413918)

    rng = galsim.BaseDeviate(5678)
    silicon = galsim.SiliconSensor(rng=rng, diffusion_factor=0.0)
    area_image = silicon.calculate_pixel_areas(im)
    print('area min = ',area_image.array.min())
    print('area max = ',area_image.array.max())
    print('area(0,0) = ',area_image(0,0))
    assert area_image(0,0) == area_image.array.min()
    # Regression test based on values current for GalSim version 1.6.
    np.testing.assert_almost_equal(area_image(0,0), 0.8962292034379046)
    np.testing.assert_almost_equal(area_image.array.max(), 1.0112006254350212)

    # The Silicon code is asymmetric.  Charge flows more easily along columns than rows.
    # It's not completely intuitive, since there are competing effects in play, but the net
    # result on the areas for this image is that the pixels above and below the central pixel
    # are slightly larger than the ones to the left and right.
    print('+- 1 along column: ',area_image(0,1),area_image(0,-1))
    print('+- 1 along row:    ',area_image(1,0),area_image(-1,0))
    np.testing.assert_almost_equal((area_image(0,1) + area_image(0,-1))/2., 0.9789158236482416)
    np.testing.assert_almost_equal((area_image(1,0) + area_image(-1,0))/2., 0.9686605382489861)

    # Just to confirm that the bigger effect really is along the column directions, draw the
    # object with the silicon sensor in play.
    im2 = obj.drawImage(nx=17, ny=17, scale=0.3, method='phot', sensor=silicon, rng=rng)
    im2.setCenter(0,0)
    print('im min = ',im2.array.min())
    print('im max = ',im2.array.max())
    print('im(0,0) = ',im2(0,0))
    print('+- 1 along column: ',im2(0,1),im2(0,-1))
    print('+- 1 along row:    ',im2(1,0),im2(-1,0))
    assert im2(0,0) == im2.array.max()
    assert im2(0,1) + im2(0,-1) > im2(1,0) + im2(-1,0)
    np.testing.assert_almost_equal(im2(0,0), 143340)
    np.testing.assert_almost_equal((im2(0,1) + im2(0,-1))/2., 59307.0)
    np.testing.assert_almost_equal((im2(1,0) + im2(-1,0))/2., 59031.0)

    # Repeat with transpose=True to check that things are transposed properly.
    siliconT = galsim.SiliconSensor(rng=rng, transpose=True, diffusion_factor=0.0)
    area_imageT = siliconT.calculate_pixel_areas(im)
    print('with transpose=True:')
    print('area min = ',area_imageT.array.min())
    print('area max = ',area_imageT.array.max())
    print('area(0,0) = ',area_imageT(0,0))
    print('+- 1 along column: ',area_imageT(0,1),area_imageT(0,-1))
    print('+- 1 along row:    ',area_imageT(1,0),area_imageT(-1,0))
    np.testing.assert_almost_equal(area_imageT(0,0), 0.8962292034379046)
    np.testing.assert_almost_equal((area_imageT(0,1) + area_imageT(0,-1))/2., 0.9686605382489861)
    np.testing.assert_almost_equal((area_imageT(1,0) + area_imageT(-1,0))/2., 0.9789158236482416)
    np.testing.assert_almost_equal(area_imageT.array, area_image.array.T, decimal=14)

    im2T = obj.drawImage(nx=17, ny=17, scale=0.3, method='phot', sensor=siliconT, rng=rng)
    im2T.setCenter(0,0)
    print('im min = ',im2T.array.min())
    print('im max = ',im2T.array.max())
    print('im(0,0) = ',im2T(0,0))
    print('+- 1 along column: ',im2T(0,1),im2T(0,-1))
    print('+- 1 along row:    ',im2T(1,0),im2T(-1,0))
    assert im2T(0,0) == im2T.array.max()
    assert im2T(0,1) + im2T(0,-1) < im2T(1,0) + im2T(-1,0)
    # Actual values are different, since rng is in different state. But qualitatively transposed.
    np.testing.assert_almost_equal(im2T(0,0), 142604)
    np.testing.assert_almost_equal((im2T(0,1) + im2T(0,-1))/2., 59185.5)
    np.testing.assert_almost_equal((im2T(1,0) + im2T(-1,0))/2., 59375.0)

    do_pickle(siliconT)
def main(argv):
    """
    Make images to be used for characterizing the brighter-fatter effect
      - Each fits file is 5 x 5 postage stamps.
      - Each postage stamp is 40 x 40 pixels.
      - There are 3 sets of 5 images each.  The 5 images are at 5 different flux levels
      - The three sets are (bf_1) B-F off, (bf_2) B-F on, diffusion off, (bf_3) B-F and diffusion on
      - Each image is in output/bf_set/bf_nfile.fits, where set ranges from 1-3 and nfile ranges from 1-5.
    """
    logging.basicConfig(format="%(message)s", level=logging.INFO, stream=sys.stdout)
    logger = logging.getLogger("bf_plots")

    # Add the wavelength info
    bppath = "../../share/bandpasses/"
    sedpath = "../../share/"
    sed = galsim.SED(os.path.join(sedpath, 'CWW_E_ext.sed'), 'nm', 'flambda').thin()

    # Add the directions (seems to work - CL)
    fratio = 1.2
    obscuration = 0.2
    seed = 12345
    assigner = galsim.FRatioAngles(fratio, obscuration, seed)
    bandpass = galsim.Bandpass(os.path.join(bppath, 'LSST_r.dat'), 'nm').thin()
    rng3 = galsim.BaseDeviate(1234)
    sampler = galsim.WavelengthSampler(sed, bandpass, rng3)


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

    nx_tiles = 10                   #
    ny_tiles = 10                   #
    stamp_xsize = 40                #
    stamp_ysize = 40                #

    random_seed = 6424512           #

    pixel_scale = 0.2               # arcsec / pixel
    sky_level = 0.01                # ADU / arcsec^2

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

    gal_sigma = 0.2     # arcsec
    psf_sigma = 0.01     # arcsec
    pixel_scale = 0.2  # arcsec / pixel
    noise = 0.01        # standard deviation of the counts in each pixel

    shift_radius = 0.2              # arcsec (=pixels)

    logger.info('Starting bf_plots using:')
    logger.info('    - image with %d x %d postage stamps',nx_tiles,ny_tiles)
    logger.info('    - postage stamps of size %d x %d pixels',stamp_xsize,stamp_ysize)
    logger.info('    - Centroid shifts up to = %.2f pixels',shift_radius)

    rng = galsim.BaseDeviate(5678)    
    sensor1 = galsim.Sensor()
    sensor2 = galsim.SiliconSensor(rng=rng, diffusion_factor=0.0)
    sensor3 = galsim.SiliconSensor(rng=rng)

    for set in range(1,4):
        starttime = time.time()
        exec("sensor = sensor%d"%set)
        for nfile in range(1,6):
            # Make bf_x directory if not already present.
            if not os.path.isdir('output/bf_%d'%set):
                os.mkdir('output/bf_%d'%set)

            gal_file_name = os.path.join('output','bf_%d/bf_%d.fits'%(set,nfile))
            sex_file_name = os.path.join('output','bf_%d/bf_%d_SEX.fits.cat.reg'%(set,nfile))
            sexfile = open(sex_file_name, 'w')
            gal_flux = 2.0e5 * nfile    # total counts on the image
            # Define the galaxy profile
            gal = galsim.Gaussian(flux=gal_flux, sigma=gal_sigma)
            logger.debug('Made galaxy profile')

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

            # This profile is placed with different orientations and noise realizations
            # at each postage stamp in the gal image.
            gal_image = galsim.ImageF(stamp_xsize * nx_tiles-1 , stamp_ysize * ny_tiles-1,
                                      scale=pixel_scale)
            psf_image = galsim.ImageF(stamp_xsize * nx_tiles-1 , stamp_ysize * ny_tiles-1,
                                      scale=pixel_scale)

            shift_radius_sq = shift_radius**2

            first_in_pair = True  # Make pairs that are rotated by 90 degrees

            k = 0
            for iy in range(ny_tiles):
                for ix in range(nx_tiles):
                    # The normal procedure for setting random numbers in GalSim is to start a new
                    # random number generator for each object using sequential seed values.
                    # This sounds weird at first (especially if you were indoctrinated by Numerical 
                    # Recipes), but for the boost random number generator we use, the "random" 
                    # number sequences produced from sequential initial seeds are highly uncorrelated.
                    # 
                    # The reason for this procedure is that when we use multiple processes to build
                    # our images, we want to make sure that the results are deterministic regardless
                    # of the way the objects get parcelled out to the different processes. 
                    #
                    # Of course, this script isn't using multiple processes, so it isn't required here.
                    # However, we do it nonetheless in order to get the same results as the config
                    # version of this demo script (demo5.yaml).
                    ud = galsim.UniformDeviate(random_seed+k)

                    # Any kind of random number generator can take another RNG as its first 
                    # argument rather than a seed value.  This makes both objects use the same
                    # underlying generator for their pseudo-random values.
                    #gd = galsim.GaussianDeviate(ud, sigma=gal_ellip_rms)

                    # The -1's in the next line are to provide a border of
                    # 1 pixel between postage stamps
                    b = galsim.BoundsI(ix*stamp_xsize+1 , (ix+1)*stamp_xsize-1, 
                                       iy*stamp_ysize+1 , (iy+1)*stamp_ysize-1)
                    sub_gal_image = gal_image[b]
                    sub_psf_image = psf_image[b]

                    # Great08 randomized the locations of the two galaxies in each pair,
                    # but for simplicity, we just do them in sequential postage stamps.

                    if first_in_pair:
                        # Use a random orientation:
                        beta = ud() * 2. * math.pi * galsim.radians

                        # Determine the ellipticity to use for this galaxy.
                        ellip = 0.0
                        first_in_pair = False
                    else:
                        # Use the previous ellip and beta + 90 degrees
                        beta += 90 * galsim.degrees
                        first_in_pair = True

                    # Make a new copy of the galaxy with an applied e1/e2-type distortion 
                    # by specifying the ellipticity and a real-space position angle
                    this_gal = gal#gal.shear(e=ellip, beta=beta)

                    # Apply a random shift_radius:
                    rsq = 2 * shift_radius_sq
                    while (rsq > shift_radius_sq):
                        dx = (2*ud()-1) * shift_radius
                        dy = (2*ud()-1) * shift_radius
                        rsq = dx**2 + dy**2

                    this_gal = this_gal.shift(dx,dy)
                    # Note that the shifted psf that we create here is purely for the purpose of being able
                    # to draw a separate, shifted psf image.  We do not use it when convolving the galaxy
                    # with the psf.
                    this_psf = psf.shift(dx,dy)

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

                    # Draw the image

                    if ix == 0 and iy == 0:
                        final_gal.drawImage(sub_gal_image, method = 'phot', sensor=sensor, surface_ops=[sampler, assigner], rng = rng, save_photons = True)
                        photon_file = os.path.join('output','bf_%d/bf_%d_nx_%d_ny_%d_photon_file.fits'%(set,nfile,ix,iy))
                        sub_gal_image.photons.write(photon_file)
                    else:
                        final_gal.drawImage(sub_gal_image, method = 'phot', sensor=sensor, surface_ops=[sampler, assigner], rng = rng)
                    
                    # Now add an appropriate amount of noise to get our desired S/N
                    # There are lots of definitions of S/N, but here is the one used by Great08
                    # We use a weighted integral of the flux:
                    #   S = sum W(x,y) I(x,y) / sum W(x,y)
                    #   N^2 = Var(S) = sum W(x,y)^2 Var(I(x,y)) / (sum W(x,y))^2
                    # Now we assume that Var(I(x,y)) is constant so
                    #   Var(I(x,y)) = noise_var
                    # We also assume that we are using a matched filter for W, so W(x,y) = I(x,y).
                    # Then a few things cancel and we find that
                    # S/N = sqrt( sum I(x,y)^2 / noise_var )
                    #
                    # The above procedure is encapsulated in the function image.addNoiseSNR which
                    # sets the flux appropriately given the variance of the noise model.
                    # In our case, noise_var = sky_level_pixel
                    sky_level_pixel = sky_level * pixel_scale**2
                    noise = galsim.PoissonNoise(ud, sky_level=sky_level_pixel)
                    #sub_gal_image.addNoiseSNR(noise, gal_signal_to_noise)

                    # Draw the PSF image
                    # No noise on PSF images.  Just draw it as is.
                    this_psf.drawImage(sub_psf_image)

                    # For first instance, measure moments
                    """
                    if ix==0 and iy==0:
                        psf_shape = sub_psf_image.FindAdaptiveMom()
                        temp_e = psf_shape.observed_shape.e
                        if temp_e > 0.0:
                            g_to_e = psf_shape.observed_shape.g / temp_e
                        else:
                            g_to_e = 0.0
                        logger.info('Measured best-fit elliptical Gaussian for first PSF image: ')
                        logger.info('  g1, g2, sigma = %7.4f, %7.4f, %7.4f (pixels)',
                                    g_to_e*psf_shape.observed_shape.e1,
                                    g_to_e*psf_shape.observed_shape.e2, psf_shape.moments_sigma)
                    """
                    x = b.center().x
                    y = b.center().y
                    logger.info('Galaxy (%d,%d): center = (%.0f,%0.f)  (e,beta) = (%.4f,%.3f)',
                                ix,iy,x,y,ellip,beta/galsim.radians)
                    k = k+1
                    sexline = 'circle %f %f %f\n'%(x+dx/pixel_scale,y+dy/pixel_scale,gal_sigma/pixel_scale)
                    sexfile.write(sexline)

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

            # Now write the images to disk.
            #psf_image.write(psf_file_name)
            #logger.info('Wrote PSF file %s',psf_file_name)

            gal_image.write(gal_file_name)
            logger.info('Wrote image to %r',gal_file_name)  # using %r adds quotes around filename for us

        finishtime = time.time()
        print("Time to complete set %d = %.2f seconds\n"%(set, finishtime-starttime))
Exemple #26
0
 def get_sensor(self, nrecalc):
     return galsim.SiliconSensor(rng=self._rng, nrecalc=nrecalc)