Esempio n. 1
0
def test_run_through_Telescope_methods():
    duet = Telescope()
    duet_offax = Telescope(config='reduced_baseline')
    duet.info()
    duet.update_bandpass()
    duet.calc_radial_profile()
    duet.calc_psf_fwhm()
    duet.update_effarea()
    duet.fluence_to_rate(1 * u.ph / u.s / u.cm**2)
    duet.psf_model()
    duet.compute_psf_norms()
Esempio n. 2
0
def construct_image(frame,
                    exposure,
                    duet=None,
                    band=None,
                    gal_type=None,
                    gal_params=None,
                    source=None,
                    source_loc=None,
                    sky_rate=None,
                    n_exp=1,
                    duet_no=None):
    """Construct a simualted image with an optional background galaxy and source.

    1. Generate the empty image
    2. Add galaxy (see sim_galaxy)
    3. Add source (Poisson draw based on source*expossure)
    4. Convolve with PSF
    5. Rebin to the DUET pixel size.
    6. Add in expected background rates per pixel and dark current.
    7. Draw Poisson values and add read noise.

    Parameters
    ----------
    frame : ``numpy.array``
        Number of pixel along x and y axis.
        i.e., frame = np.array([30, 30])

    exposure : ``astropy.units.Quantity``
        Exposure time used for the light curve

    Other parameters
    ----------------

    duet : ``astroduet.config.Telescope``
        If None, a default one is created
        
    band : DUET bandpass (deprecated in favor of duet_no; defaults to DUET1)

    gal_type : string
        Default galaxy string ("spiral"/"elliptical") or "custom" w/ Sersic parameters
        in gal_params

    gal_params : dict
        Dictionary of parameters for Sersic model (see sim_galaxy)

    source : ``astropy.units.Quantity``
        Source photon rate in ph / s; can be array for multiple sources

    source_loc : ``numpy.array``
        Coordinates of source(s) relative to frame (values between 0 and 1). If source is an array, source_loc must be the same length.
        format: np.array([[X1,X2,X3,...,Xn],[Y1,Y2,Y3,...,Yn]])

    sky_rate : ``astropy.units.Quantity``
        Background photon rate in ph / s / pixel

    n_exp : int
        Number of simualted frames to co-add.
        NB: I don't like this here!
    
    duet_no : int (1 or 2)
        DUET band number (defaults to DUET1)

    Returns
    -------

    image : array with astropy.units
        NxM image array with integer number of counts observed per pixel.

    """
    from astroduet.utils import duet_no_from_band
    assert type(
        frame
    ) is np.ndarray, 'construct_image: Please enter frame as a numpy array'

    # Load telescope parameters:
    if duet is None:
        duet = Telescope()
    if band is None:
        band = duet.bandpass1
    if duet_no is None:
        duet_no = duet_no_from_band(band)

    read_noise = duet.read_noise

    oversample = 6
    pixel_size_init = duet.pixel / oversample

    # Load the PSF kernel. Note that this does NOT HAVE POINTING JITTER!
    psf_kernel = duet.psf_model(pixel_size=pixel_size_init)

    # 1. Generate the empty image
    # Initialise an image, oversampled by the oversample parameter to begin with

    im_array = np.zeros(frame * oversample) * u.ph / u.s

    # 2. Add a galaxy?
    if gal_type is not None:
        # Get a patch with a simulated galaxy on it
        gal = sim_galaxy(frame * oversample,
                         pixel_size_init,
                         gal_type=gal_type,
                         gal_params=gal_params,
                         duet=duet,
                         duet_no=duet_no)
        im_array += gal

    # 3. Add a source?
    if source is not None:
        # Place source as a delta function at the center of the frame
        if source_loc is None:
            im_array[im_array.shape[0] // 2 + 1,
                     im_array.shape[1] // 2 + 1] += source
        # Otherwise place sources at given source locations in frame
        else:
            source_inv = np.array([
                source_loc[1], source_loc[0]
            ])  # Invert axes because python is weird that way
            source_pix = (source_inv.transpose() *
                          np.array(im_array.shape)).transpose().astype(int)
            im_array[tuple(source_pix)] += source

    # Result should now be (floats) expected number of photons per pixel per second
    # in the oversampled imae

    # 4. Convolve with the PSF
    im_psf = convolve_fft(im_array.value, psf_kernel) * im_array.unit

    # Convolve again, now with the pointing jitter (need to re-apply units here as it's lost in convolution)
    #im_psf = convolve(im_psf_temp, Gaussian2DKernel((duet.jitter_rms/pixel_size_init).value)) * im_array.unit

    # 5. Bin up the image by oversample parameter to the correct pixel size
    shape = (frame[0], oversample, frame[1], oversample)
    im_binned = im_psf.reshape(shape).sum(-1).sum(1)

    # 6. Add sky background (these are both given in ph / pix / s)
    if sky_rate is not None:
        # Add sky rate per pixel across the whole image
        im_binned += sky_rate

    # 6b: Add dark current:
    im_binned += duet.dark_current

    # Convert to expected counts -- TEMPORARY: .value transform photons in a number
    im_counts = (im_binned * exposure)

    # Co-add a number of separate exposures
    im_final = np.zeros(frame)
    for i in range(n_exp):
        # Apply Poisson noise and instrument read noise. Note that read noise here
        # is
        im_noise = np.random.poisson(im_counts.value) + \
            np.random.normal(loc=0, scale=read_noise,size=im_counts.shape)
        im_noise = np.floor(im_noise)
        im_noise[im_noise < 0] = 0

        # Add to the co-add
        im_final += im_noise

    # Return image
    return im_final * im_counts.unit