Exemple #1
0
def getPSF(SCAs=None,
           approximate_struts=False,
           n_waves=None,
           extra_aberrations=None,
           wavelength_limits=None,
           logger=None,
           wavelength=None,
           high_accuracy=False,
           gsparams=None):
    """
    Get the PSF for WFIRST observations.

    By default, this routine returns a dict of ChromaticOpticalPSF objects, with the dict indexed by
    the SCA (Sensor Chip Array, the equivalent of a chip in an optical CCD).  The PSF for a given
    SCA corresponds to that for the center of the SCA.  Currently we do not use information about
    PSF variation within each SCA, which is relatively small.

    This routine also takes an optional keyword `SCAs`, which can be a single number or an iterable;
    if this is specified then results are not included for the other SCAs.

    The default is to do the calculations using the full specification of the WFIRST pupil plane,
    which is a costly calculation in terms of memory.  For this, we use the provided pupil plane for
    red bands from

    http://wfirst.gsfc.nasa.gov/science/sdt_public/wps/references/instrument/   (Cycle 5)

    and we neglect for now the fact that the pupil plane configuration is slightly different for
    imaging in Z087, Y106, J129.  To avoid using the full pupil plane configuration, use the
    optional keyword `approximate_struts`.  In this case, the pupil plane will have the correct
    obscuration and number of struts, but the struts will be purely radial and evenly spaced instead
    of the true configuration.  The simplicity of this arrangement leads to a much faster
    calculation, and somewhat simplifies the configuration of the diffraction spikes.  Also note
    that currently the orientation of the struts is fixed, rather than rotating depending on the
    orientation of the focal plane.  Rotation of the PSF can easily be affected by the user via

       psf = galsim.wfirst.getPSF(...).rotate(angle)

    which will rotate the entire PSF (including the diffraction spikes and any other features).

    The calculation takes advantage of the fact that the diffraction limit and aberrations have a
    simple, understood wavelength-dependence.  (The WFIRST project webpage for Cycle 5 does in fact
    provide aberrations as a function of wavelength, but the deviation from the expected chromatic
    dependence is very small and we neglect it here.)  For reference, the script use to parse the
    Zernikes given on the webpage and create the files in the GalSim repository can be found in
    `devel/external/parse_wfirst_zernikes_0715.py`.  The resulting chromatic object can be used to
    draw into any of the WFIRST bandpasses.

    For applications that require very high accuracy in the modeling of the PSF, with very limited
    aliasing, the `high_accuracy` option can be set to True.  When using this option, the MTF has a
    value below 1e-4 for all wavenumbers above the band limit when using `approximate_struts=True`,
    or below 3e-4 when using `approximate_struts=False`.  In contrast, when `high_accuracy=False`
    (the default), there are some bumps in the MTF above the band limit that reach an amplitude of
    ~1e-2.

    By default, no additional aberrations are included above the basic design.  However, users can
    provide an optional keyword `extra_aberrations` that will be included on top of those that are
    part of the design.  This should be in the same format as for the ChromaticOpticalPSF class,
    with units of waves at the fiducial wavelength, 1293 nm. Currently, only aberrations up to order
    11 (Noll convention) can be simulated.  For WFIRST, the current tolerance for additional
    aberrations is a total of 90 nanometers RMS:
    http://wfirst.gsfc.nasa.gov/science/sdt_public/wps/references/instrument/README_AFTA_C5_WFC_Zernike_and_Field_Data.pdf
    distributed largely among coma, astigmatism, trefoil, and spherical aberrations (NOT defocus).
    This information might serve as a guide for reasonable `extra_aberrations` inputs.

    Jitter and charge diffusion are, by default, not included.  Users who wish to include these can
    find some guidelines for typical length scales of the Gaussians that can represent these
    effects, and convolve the ChromaticOpticalPSF with appropriate achromatic Gaussians.

    The PSFs are always defined assuming the user will specify length scales in arcsec.

    @param    SCAs                 Specific SCAs for which the PSF should be loaded.  This can be
                                   either a single number or an iterable.  If None, then the PSF
                                   will be loaded for all SCAs (1...18).  Note that the object that
                                   is returned is a dict indexed by the requested SCA indices.
                                   [default: None]
    @param    approximate_struts   Should the routine use an approximate representation of the pupil
                                   plane, with 6 equally-spaced radial struts, instead of the exact
                                   representation of the pupil plane?  Setting this parameter to
                                   True will lead to faster calculations, with a slightly less
                                   realistic PSFs.  [default: False]
    @param    n_waves              Number of wavelengths to use for setting up interpolation of the
                                   chromatic PSF objects, which can lead to much faster image
                                   rendering.  If None, then no interpolation is used. Note that
                                   users who want to interpolate can always set up the interpolation
                                   later on even if they do not do so when calling getPSF().
                                   [default: None]
    @param    extra_aberrations    Array of extra aberrations to include in the PSF model, on top of
                                   those that are part of the WFIRST design.  These should be
                                   provided in units of waves at the fiducial wavelength of 1293 nm,
                                   as an array of length 12 with entries 4 through 11 corresponding
                                   to defocus through spherical aberrations.  [default: None]
    @param    wavelength_limits    A tuple or list of the blue and red wavelength limits to use for
                                   interpolating the chromatic object, if `n_waves` is not None.  If
                                   None, then it uses the blue and red limits of all imaging
                                   passbands to determine the most inclusive wavelength range
                                   possible.  But this keyword can be used to reduce the range of
                                   wavelengths if only one passband (or a subset of passbands) is to
                                   be used for making the images.
                                   [default: None]
    @param    logger               A logger object for output of progress statements if the user
                                   wants them.  [default: None]
    @param    wavelength           An option to get an achromatic PSF for a single wavelength, for
                                   users who do not care about chromaticity of the PSF.  If None,
                                   then the fully chromatic PSF is returned.  Alternatively the user
                                   should supply either (a) a wavelength in nanometers, and they
                                   will get achromatic OpticalPSF objects for that wavelength, or
                                   (b) a bandpass object, in which case they will get achromatic
                                   OpticalPSF objects defined at the effective wavelength of that
                                   bandpass.
                                   [default: False]
    @param    high_accuracy        If True, make higher-fidelity representations of the PSF in
                                   Fourier space, to minimize aliasing (see plots on
                                   https://github.com/GalSim-developers/GalSim/issues/661 for more
                                   details).  This setting is more expensive in terms of time and
                                   RAM, and may not be necessary for many applications.
                                   [default: False]
    @param gsparams                An optional GSParams argument.  See the docstring for GSParams
                                   for details. [default: None]
    @returns  A dict of ChromaticOpticalPSF or OpticalPSF objects for each SCA.
    """
    # Check which SCAs are to be done using a helper routine in this module.
    SCAs = galsim.wfirst._parse_SCAs(SCAs)

    # Deal with some accuracy settings.
    if high_accuracy:
        if approximate_struts:
            oversampling = 3.5
        else:
            oversampling = 2.0

            # In this case, we need to pad the edges of the pupil plane image, so we cannot just use
            # the stored file.
            tmp_pupil_plane_im = galsim.fits.read(
                galsim.wfirst.pupil_plane_file)
            old_bounds = tmp_pupil_plane_im.bounds
            new_bounds = old_bounds.withBorder(
                (old_bounds.xmax + 1 - old_bounds.xmin) / 2)
            pupil_plane_im = galsim.Image(bounds=new_bounds)
            pupil_plane_im[old_bounds] = tmp_pupil_plane_im
            pupil_plane_scale = galsim.wfirst.pupil_plane_scale
    else:
        if approximate_struts:
            oversampling = 1.5
        else:
            oversampling = 1.2
            pupil_plane_im = galsim.wfirst.pupil_plane_file
            pupil_plane_scale = galsim.wfirst.pupil_plane_scale

    if wavelength is None:
        if n_waves is not None:
            if wavelength_limits is None:
                # To decide the range of wavelengths to use (if none were passed in by the user),
                # first check out all the bandpasses.
                bandpass_dict = galsim.wfirst.getBandpasses()
                # Then find the blue and red limit to be used for the imaging bandpasses overall.
                blue_limit, red_limit = _find_limits(default_bandpass_list,
                                                     bandpass_dict)
            else:
                if not isinstance(wavelength_limits, tuple):
                    raise ValueError(
                        "Wavelength limits must be entered as a tuple!")
                blue_limit, red_limit = wavelength_limits
                if red_limit <= blue_limit:
                    raise ValueError(
                        "Wavelength limits must have red_limit > blue_limit."
                        "Input: blue limit=%f, red limit=%f nanometers" %
                        (blue_limit, red_limit))
    else:
        if isinstance(wavelength, galsim.Bandpass):
            wavelength_nm = wavelength.effective_wavelength
        elif isinstance(wavelength, float):
            wavelength_nm = wavelength
        else:
            raise TypeError(
                "Keyword 'wavelength' should either be a Bandpass, float,"
                " or None.")

    # Start reading in the aberrations for the relevant SCAs.
    aberration_dict = {}
    PSF_dict = {}
    if logger: logger.debug('Beginning to loop over SCAs and get the PSF:')
    for SCA in SCAs:
        aberration_dict[SCA] = _read_aberrations(SCA)

        use_aberrations = aberration_dict[SCA]
        if extra_aberrations is not None:
            use_aberrations += extra_aberrations
        # We don't want to use piston, tip, or tilt aberrations.  The former doesn't affect the
        # appearance of the PSF, and the latter cause centroid shifts.  So, we set the first 4
        # numbers (corresponding to a place-holder, piston, tip, and tilt) to zero.
        use_aberrations[0:4] = 0.

        # Now set up the PSF for this SCA, including the option to simplify the pupil plane.
        if logger: logger.debug('   ... SCA %d' % SCA)
        if wavelength is None:
            if approximate_struts:
                PSF = galsim.ChromaticOpticalPSF(
                    lam=zemax_wavelength,
                    diam=galsim.wfirst.diameter,
                    aberrations=use_aberrations,
                    obscuration=galsim.wfirst.obscuration,
                    nstruts=6,
                    oversampling=oversampling,
                    gsparams=gsparams)
            else:
                PSF = galsim.ChromaticOpticalPSF(
                    lam=zemax_wavelength,
                    diam=galsim.wfirst.diameter,
                    aberrations=use_aberrations,
                    obscuration=galsim.wfirst.obscuration,
                    pupil_plane_im=pupil_plane_im,
                    pupil_plane_scale=pupil_plane_scale,
                    oversampling=oversampling,
                    pad_factor=2.,
                    gsparams=gsparams)
            if n_waves is not None:
                PSF = PSF.interpolate(waves=np.linspace(
                    blue_limit, red_limit, n_waves),
                                      oversample_fac=1.5)
        else:
            tmp_aberrations = use_aberrations * zemax_wavelength / wavelength_nm
            if approximate_struts:
                PSF = galsim.OpticalPSF(lam=wavelength_nm,
                                        diam=galsim.wfirst.diameter,
                                        aberrations=tmp_aberrations,
                                        obscuration=galsim.wfirst.obscuration,
                                        nstruts=6,
                                        oversampling=oversampling,
                                        gsparams=gsparams)
            else:
                PSF = galsim.OpticalPSF(lam=wavelength_nm,
                                        diam=galsim.wfirst.diameter,
                                        aberrations=tmp_aberrations,
                                        obscuration=galsim.wfirst.obscuration,
                                        pupil_plane_im=pupil_plane_im,
                                        pupil_plane_scale=pupil_plane_scale,
                                        oversampling=oversampling,
                                        pad_factor=2.,
                                        gsparams=gsparams)

        PSF_dict[SCA] = PSF

    return PSF_dict
Exemple #2
0
def _get_single_PSF(SCA, bandpass, SCA_pos, approximate_struts, n_waves,
                    extra_aberrations, logger, wavelength, high_accuracy,
                    pupil_plane_type, gsparams):
    """Routine for making a single PSF.  This gets called by getPSF() after it parses all the
       options that were passed in.  Users will not directly interact with this routine.
    """
    # Deal with some accuracy settings.
    if high_accuracy:
        if approximate_struts:
            oversampling = 3.5
        else:
            oversampling = 2.0

            # In this case, we need to pad the edges of the pupil plane image, so we cannot just use
            # the stored file.
            if pupil_plane_type == 'long':
                tmp_pupil_plane_im = galsim.fits.read(
                    galsim.wfirst.pupil_plane_file_longwave)
            else:
                tmp_pupil_plane_im = galsim.fits.read(
                    galsim.wfirst.pupil_plane_file_shortwave)
            old_bounds = tmp_pupil_plane_im.bounds
            new_bounds = old_bounds.withBorder(
                (old_bounds.xmax + 1 - old_bounds.xmin) / 2)
            pupil_plane_im = galsim.Image(bounds=new_bounds)
            pupil_plane_im[old_bounds] = tmp_pupil_plane_im
            pupil_plane_scale = galsim.wfirst.pupil_plane_scale
    else:
        if approximate_struts:
            oversampling = 1.5
        else:
            oversampling = 1.2
            if pupil_plane_type == 'long':
                pupil_plane_im = galsim.wfirst.pupil_plane_file_longwave
            else:
                pupil_plane_im = galsim.wfirst.pupil_plane_file_shortwave
            pupil_plane_scale = galsim.wfirst.pupil_plane_scale

    # Start reading in the aberrations for that SCA
    if logger:
        logger.debug('Beginning to get the PSF aberrations for SCA %d.' % SCA)
    aberrations, x_pos, y_pos = _read_aberrations(SCA)
    # Do bilinear interpolation, unless we're exactly at the center (default).
    use_aberrations = _interp_aberrations_bilinear(aberrations, x_pos, y_pos,
                                                   SCA_pos)

    if extra_aberrations is not None:
        use_aberrations += extra_aberrations
    # We don't want to use piston, tip, or tilt aberrations.  The former doesn't affect the
    # appearance of the PSF, and the latter cause centroid shifts.  So, we set the first 4
    # numbers (corresponding to a place-holder, piston, tip, and tilt) to zero.
    use_aberrations[0:4] = 0.

    # Now set up the PSF, including the option to simplify the pupil plane.
    if wavelength is None:
        if approximate_struts:
            PSF = galsim.ChromaticOpticalPSF(
                lam=zemax_wavelength,
                diam=galsim.wfirst.diameter,
                aberrations=use_aberrations,
                obscuration=galsim.wfirst.obscuration,
                nstruts=6,
                oversampling=oversampling,
                gsparams=gsparams)
        else:
            PSF = galsim.ChromaticOpticalPSF(
                lam=zemax_wavelength,
                diam=galsim.wfirst.diameter,
                aberrations=use_aberrations,
                obscuration=galsim.wfirst.obscuration,
                pupil_plane_im=pupil_plane_im,
                pupil_plane_scale=pupil_plane_scale,
                oversampling=oversampling,
                pad_factor=2.,
                gsparams=gsparams)
        if n_waves is not None:
            # To decide the range of wavelengths to use, check the bandpass.
            bp_dict = galsim.wfirst.getBandpasses()
            bp = bp_dict[bandpass]
            PSF = PSF.interpolate(waves=np.linspace(bp.blue_limit,
                                                    bp.red_limit, n_waves),
                                  oversample_fac=1.5)
    else:
        if not isinstance(wavelength, float):
            raise TypeError(
                "wavelength should either be a Bandpass, float, or None.")
        tmp_aberrations = use_aberrations * zemax_wavelength / wavelength
        if approximate_struts:
            PSF = galsim.OpticalPSF(lam=wavelength,
                                    diam=galsim.wfirst.diameter,
                                    aberrations=tmp_aberrations,
                                    obscuration=galsim.wfirst.obscuration,
                                    nstruts=6,
                                    oversampling=oversampling,
                                    gsparams=gsparams)
        else:
            PSF = galsim.OpticalPSF(lam=wavelength,
                                    diam=galsim.wfirst.diameter,
                                    aberrations=tmp_aberrations,
                                    obscuration=galsim.wfirst.obscuration,
                                    pupil_plane_im=pupil_plane_im,
                                    pupil_plane_scale=pupil_plane_scale,
                                    oversampling=oversampling,
                                    pad_factor=2.,
                                    gsparams=gsparams)

    return PSF