Exemplo n.º 1
0
def test_single_seg_psf(segmentid=1):
    """Test calculation of a single segment PSF, including options to remove piston/tip/tilt as used by MIRAGE

    """

    nrc = webbpsf.NIRCam()
    nrc.filter = 'F212N'
    nrc, ote = webbpsf.enable_adjustable_ote(nrc)
    ote.zero(zero_original=True)

    segname = webbpsf.constants.SEGNAMES_WSS_ORDER[segmentid-1][0:2]

    ote.move_seg_local(segname, xtilt=1, piston=-1)

    pupil = webbpsf.webbpsf_core.one_segment_pupil(segmentid)
    ote.amplitude = pupil[0].data


    psf = nrc.calc_psf(nlambda=1)

    ote.remove_piston = True
    ote.update_opd()
    psf_rm_piston = nrc.calc_psf(nlambda=1)
    assert np.allclose(psf[0].data, psf_rm_piston[0].data), "Piston removal should not affect the overall PSF"

    assert np.allclose( webbpsf.measure_centroid(psf), webbpsf.measure_centroid(psf_rm_piston)), "centroid should not shift"

    ote.remove_piston_tip_tilt = True
    ote.update_opd()
    psf_rm_ptt = nrc.calc_psf(nlambda=1)
    assert not np.allclose(psf[0].data, psf_rm_ptt[0].data), "Piston/Tip/Tip removal should shift the overall PSF"
    assert np.abs(webbpsf.measure_centroid(psf)[0] - webbpsf.measure_centroid(psf_rm_ptt)[0]) > 40, "centroid should shift susbtantially with/without tip/tilt removal"
Exemplo n.º 2
0
def nircam_nocoro(filter, Aber_WSS):
    """
    Create PSF
    :param filter:
    :param Aber_WSS:
    :return:
    """
    # Create NIRCam object
    nc = webbpsf.NIRCam()
    # Set filter
    nc.filter = filter

    # Adjust OTE with aberrations
    nc, ote = webbpsf.enable_adjustable_ote(nc)
    nc.include_si_wfe = False  # set SI internal WFE to zero
    ote.reset()
    ote.zero()
    for i in range(nb_seg):
        seg = wss_segs[i].split('-')[0]
        ote._apply_hexikes_to_seg(seg, Aber_WSS[i, :])

    # Calculate PSF
    psf_nc = nc.calc_psf(oversample=1, fov_pixels=int(im_size_e2e), nlambda=1)
    psf_webbpsf = psf_nc[1].data

    return psf_webbpsf
Exemplo n.º 3
0
def test_move_sur(plot=False):
    """ Test we can move mirrors using Segment Update Requests
    """
    import webbpsf
    import os
    import glob
    surdir = os.path.join(webbpsf.__path__[0], 'tests', 'surs')
    surs = glob.glob(surdir+'/*sur.xml')

    nrc = webbpsf.NIRCam()
    nrc.filter='F212N'
    nrc, ote = webbpsf.enable_adjustable_ote(nrc)
    ote.zero(zero_original=True)

    for s in surs:
        print("Testing "+s)
        ote.reset()
        ote.move_sur(s)
        # the coarse phasing SUR is a no-op after 3 groups; all others have some effect
        if 'coarse_phasing' not in s:
            assert not np.allclose(ote.segment_state, 0), "Expected some segments to be moved"

        ote.move_sur(s, reverse=True)
        assert np.allclose(ote.segment_state, 0), "Reversing moves didn't bring us back to zero"

        
    # Test every DOF on A1-1 and SM and check the OTE state updated accordingly
    s = glob.glob(surdir+'/example_alldof_A1-SM_sur.xml')[0]
    print("Testing "+s)
    ote.reset()
    ote.move_sur(s)
    assert np.allclose(ote.segment_state[0],  [1, 2, 3, 4, 5, 6])
    assert np.allclose(ote.segment_state[-1], [1, 2, 3, 4, 5, 0])
    
    
    # Test moving one at a time. This test relies on specifics of what's in the image stacking SUR.
    s = glob.glob(surdir+'/example_image_stacking*sur.xml')[0]
    print("Testing moving one group at a time with "+s)
    ote.reset()
    sur = webbpsf.surs.SUR(s)

    ngroups = len(sur.groups)
    oldstate = ote.segment_state.copy()

    for igrp in range(1, ngroups+1):
        print("Group {} should move segment {}".format(igrp, 2*igrp+6))
        ote.move_sur(s, group=igrp)

        movedsegs = np.abs((ote.segment_state - oldstate).sum(axis=1))
        assert (movedsegs!=0).sum()==1, "Only expected one segment to move"
        whichmoved = np.argmax(movedsegs)+1
        print ("Moved segment", whichmoved)
        assert whichmoved == 2*igrp+6, "An unexpected segment moved"
        oldstate = ote.segment_state.copy()
        if plot:
            psf = nrc.calc_psf(fov_pixels=256, add_distortion=False)
            plt.figure()
            ote.display_opd(title="After Group {}".format(igrp))
            plt.figure()
            webbpsf.display_psf(psf, ext=1, title="After Group {}".format(igrp))
Exemplo n.º 4
0
def nircam_coro(filter, fpm, ppm, Aber_WSS):
    """
    -- Deprecated function still used in analytical PASTIS and some notebooks. --

    Create NIRCam image with specified filter and coronagraph, and aberration input.
    :param filter: str, filter name
    :param fpm: focal plane mask
    :param ppm: pupil plane mask - Lyot stop
    :param Aber_WSS: list or array holding Zernike coefficients ordered in WSS convention and in METERS
    :return:
    """

    # Set up NIRCam and coronagraph
    nc = webbpsf.NIRCam()
    nc.filter = filter
    nc.image_mask = fpm
    nc.pupil_mask = ppm

    # Adjust OTE with aberrations
    nc, ote = webbpsf.enable_adjustable_ote(nc)
    nc.include_si_wfe = False  # set SI internal WFE to zero
    ote.reset()
    ote.zero()
    for i in range(NB_SEG):
        seg = WSS_SEGS[i].split('-')[0]
        ote._apply_hexikes_to_seg(seg, Aber_WSS[i,:])

    # Calculate PSF
    psf_nc = nc.calc_psf(oversample=1, fov_pixels=int(IM_SIZE_E2E), nlambda=1)
    psf_webbpsf = psf_nc[1].data

    return psf_webbpsf
Exemplo n.º 5
0
def nircam_nocoro(filter, Aber_WSS):
    """
    -- Deprecated function still used in analytical PASTIS and some notebooks. --
    :param filter:
    :param Aber_WSS:
    :return:
    """
    # Create NIRCam object
    nc = webbpsf.NIRCam()
    # Set filter
    nc.filter = filter

    # Adjust OTE with aberrations
    nc, ote = webbpsf.enable_adjustable_ote(nc)
    nc.include_si_wfe = False  # set SI internal WFE to zero
    ote.reset()
    ote.zero()
    for i in range(NB_SEG):
        seg = WSS_SEGS[i].split('-')[0]
        ote._apply_hexikes_to_seg(seg, Aber_WSS[i,:])

    # Calculate PSF
    psf_nc = nc.calc_psf(oversample=1, fov_pixels=int(IM_SIZE_E2E), nlambda=1)
    psf_webbpsf = psf_nc[1].data

    return psf_webbpsf
Exemplo n.º 6
0
def test_apply_field_dependence_model():
    ''' Test to make sure the field dependence model is giving sensible output

    Checks cases comparing master chief ray, center of NIRCam, and center of NIRISS.

    '''
    rms = lambda array, mask: np.sqrt((array[mask]**2).mean())

    # Get the OPD without any sort of field dependence
    ote = webbpsf.opds.OTE_Linear_Model_WSS(v2v3=None)
    opd_no_field_model = ote.opd.copy()

    mask = ote.get_transmission(0) != 0

    # Get the OPD at the zero field point of v2 = 0, v3 = -468 arcsec
    # Center of NIRCAM fields, but not physically on a detector.
    ote.v2v3 = (0, -468) * u.arcsec
    ote._apply_field_dependence_model(assume_si_focus=False)  # Do this test directly on the OTE OPD, without any implicit focus adjustments
    opd_zero_field = ote.opd.copy() * ote.get_transmission(0)
    rms1 =  rms(opd_no_field_model - opd_zero_field, mask)

    assert(rms1 < 7e-9), "OPDs expected to match didn't, at center field (zero field dependence)"

    # Get the OPD at some arbitrary nonzero field point (Center of NRC A)
    ote.v2v3 = (1.4, -8.2) * u.arcmin
    ote._apply_field_dependence_model(assume_si_focus=False)
    opd_arb_field = ote.opd.copy() * ote.get_transmission(0)
    rms2 = rms(opd_no_field_model - opd_arb_field, mask)

    assert(rms2 > 7e-9), "OPDs expected to differ didn't"
    assert np.isclose(rms2, 26.1e-9, atol=1e-9), "field dep OPD at center of NIRCam A was not the expected value"

    # Now we invoke this via an SI class, to show that works too:
    # Get the OPD at the center of NIRISS
    nis = webbpsf.NIRISS()
    nis.pupilopd = None  # disable any global WFE, so we just look at the field dependent part
    nis.detector_position = (1024, 1024)
    nis, ote_nis = webbpsf.enable_adjustable_ote(nis)

    # Test if we directly invoke the OTE model, in this case also disabling SI focus implicit optimization
    ote_nis._apply_field_dependence_model(assume_si_focus=False)
    opd_nis_cen = ote_nis.opd.copy()
    rms3 = rms(opd_nis_cen, mask)
    # The value used in the following test is derived from this model itself, so it's a bit circular;
    # but at least this test should suffice to detect any unintended significant change in the
    # outputs of this model
    assert np.isclose(rms3, 36.0e-9, atol=1e-9), "Field-dependent OTE WFE at selected field point (NIRISS center) didn't match expected value (test case: explicit call, assume_si_focus=False)"

    # Now test as usd in a webbpsf calculation, implicitly, and with the defocus backout ON
    # The WFE here is slightly less, due to the focus optimization
    nis = webbpsf.NIRISS()
    nis.pupilopd = None  # disable any global WFE, so we just look at the field dependent part
    nis.detector_position = (1024, 1024)
    osys = nis.get_optical_system()
    opd_nis_cen_v2 = osys.planes[0].opd.copy()
    rms4 = rms(opd_nis_cen_v2, mask)
    assert np.isclose(rms4, 28.0e-9, atol=1e-9), "Field-dependent OTE WFE at selected field point (NIRISS center) didn't match expected value(test case: implicit call, assume_si_focus=True."
Exemplo n.º 7
0
def generate_random_ote_deployment(out_dir, reduction_factor=0.2, save=True):
    """Create a WebbPSF adjustable OTE object representing a perturbed OTE
    mirror state by randomly generating mirror deployment errors.

    Parameters
    ----------
    out_dir : str
        Path to directory in which to save OPD FITS files and the deployment
        error dictionary
    reduction_factor : optional
        Factor by which to reduce the input deployment errors. Default is 0.2.
    save : bool, optional
        Denotes whether to save out the OPD (with and without tip/tilt)
        as FITs files and the deployment error dictionary as a yaml

    Returns
    -------
    ote : webbpsf.opds.OTE_Linear_Model_WSS object
        WebbPSF OTE object describing perturbed OTE state with tip and tilt removed
    segment_tilts : numpy.ndarray
        List of X and Y tilts for each mirror segment, in microradians
    ote_opd_with_tilts : numpy.ndarray
        Array representing perturbed OTE OPD pupil before tip/tilt is removed
    """
    # Make an adjustable OTE object with WebbPSF
    nc = webbpsf.NIRCam()
    nc, ote = webbpsf.enable_adjustable_ote(nc)

    # Generate OPD and vector list with reduced deployment errors
    deployment_errors = generate_deployment_errors(out_dir=out_dir, save=save)
    deployment_errors = reduce_deployment_errors(
        deployment_errors,
        reduction_factor=reduction_factor,
        out_dir=out_dir,
        save=save)

    # Create an OTE object with those deployment errors
    ote, segment_tilts = apply_deployment_errors(ote,
                                                 deployment_errors,
                                                 out_dir=out_dir,
                                                 save=save)
    ote_opd_with_tilts = ote.opd  # Save array to plot below

    # Remove tip and tilt
    ote = remove_piston_tip_tilt(ote, out_dir=out_dir, save=save)

    return ote, segment_tilts, ote_opd_with_tilts
Exemplo n.º 8
0
def set_up_nircam():
    """
    Return a configured instance of the NIRCam simulator on JWST.

    Sets up the Lyot stop and filter from the configfile, turns of science instrument (SI) internal WFE and zeros
    the OTE.
    :return: Tuple of NIRCam instance, and its OTE
    """

    nircam = webbpsf.NIRCam()
    nircam.include_si_wfe = False
    nircam.filter = CONFIG_PASTIS.get('JWST', 'filter_name')
    nircam.pupil_mask = CONFIG_PASTIS.get('JWST', 'pupil_plane_stop')

    nircam, ote = webbpsf.enable_adjustable_ote(nircam)
    ote.zero(zero_original=True)    # https://github.com/spacetelescope/webbpsf/blob/96537c459996f682ac6e9af808809ca13fb85e87/webbpsf/opds.py#L1125

    return nircam, ote
Exemplo n.º 9
0
def load_ote_from_deployment_yaml(deployments_file, out_dir, save=True):
    """Create a WebbPSF adjustable OTE object representing a perturbed OTE
    mirror state using mirror deployments defined in a YAML file.

    Parameters
    ----------
    deployments_file : str
        Path to YAML file containing deployments data. Expects format as
        produced by ``mirage.psf.deployments.generate_deployment_errors``
    out_dir : str
        Path to directory in which to save OPD FITS files
    save : bool, optional
        Denotes whether to save out the OPD (with and without tip/tilt)
        as FITs files

    Returns
    -------
    ote : webbpsf.opds.OTE_Linear_Model_WSS object
        WebbPSF OTE object describing perturbed OTE state with tip and tilt removed
    segment_tilts : numpy.ndarray
        List of X and Y tilts for each mirror segment, in microradians
    ote_opd_with_tilts : numpy.ndarray
        Array representing perturbed OTE OPD pupil before tip/tilt is removed
    """
    # Make an adjustable OTE object with WebbPSF
    nc = webbpsf.NIRCam()
    nc, ote = webbpsf.enable_adjustable_ote(nc)

    # Open existing file with previous deployments
    with open(deployments_file) as f:
        deployment_errors = yaml.unsafe_load(f)

    # Create OTE object and list of segment tilts
    ote, segment_tilts = apply_deployment_errors(ote,
                                                 deployment_errors,
                                                 out_dir=out_dir,
                                                 save=save)
    ote_opd_with_tilts = ote.opd  # Save array to plot below

    # Remove tip and tilt
    ote = remove_piston_tip_tilt(ote, out_dir=out_dir, save=save)

    return ote, segment_tilts, ote_opd_with_tilts
Exemplo n.º 10
0
def test_enable_adjustable_ote():
    """ Some basic tests of the OTE LOM"""
    nc = webbpsf.NIRCam()
    nc, ote = webbpsf.enable_adjustable_ote(nc)

    # did this produce an OTE object?
    assert isinstance(ote, webbpsf.opds.OTE_Linear_Model_WSS), "Didn't get an OTE object back"

    # can we compute the rms?
    rms = ote.rms()

    # and can we move a mirror?

    ote.move_seg_local('B1', piston=10, clocking=200)

    assert ote.segment_state[6, 2] == 10, "Couldn't piston"
    assert ote.segment_state[6, 3] == 200, "Couldn't clock"

    # did that misalignment make things much worse?

    assert ote.rms() > rms*10, "Huge piston offset didn't make the WFE much worse"
Exemplo n.º 11
0
def test_enable_adjustable_ote():
    """ Some basic tests of the OTE LOM"""
    nc = webbpsf.NIRCam()
    nc, ote = webbpsf.enable_adjustable_ote(nc)

    # did this produce an OTE object?
    assert isinstance(ote, webbpsf.opds.OTE_Linear_Model_WSS), "Didn't get an OTE object back"

    # can we compute the rms?
    rms = ote.rms()

    # and can we move a mirror?

    ote.move_seg_local('B1', piston=10, clocking=200)

    assert ote.segment_state[6, 2] == 10, "Couldn't piston"
    assert ote.segment_state[6, 3] == 200, "Couldn't clock"

    # did that misalignment make things much worse?

    assert ote.rms() > rms*10, "Huge piston offset didn't make the WFE much worse"
Exemplo n.º 12
0
def generate_wavefront_errors(nb_of_maps, errors, nb_zernikes, path):

    import poppy, webbpsf
    import random
    import matplotlib.pyplot as plt

    # intial wavefront map
    nc = webbpsf.NIRCam()
    nc, ote = webbpsf.enable_adjustable_ote(nc)
    osys = nc._get_aberrations()

    # perturbed wavefront map
    nc_perturb = webbpsf.NIRCam()
    nc_perturb, ote_perturb = webbpsf.enable_adjustable_ote(nc_perturb)
    osys_perturb = nc_perturb._get_aberrations()

    # final wavefront map
    nc_final = webbpsf.NIRCam()
    nc_final, ote_final = webbpsf.enable_adjustable_ote(nc_final)
    osys_final = nc_final._get_aberrations()

    tab_opd_final = []
    for n, error in zip(range(nb_of_maps), errors):
        print(n, error)

        # change aberrations in wavefront map: example with random zernikes
        # this map will be our perturbation map and we will add it to the initial map with a certain weight

        # creating the perturbation map
        #weight = 0.2
        weight = error / 100
        for i in range(nb_zernikes):
            #tmp = random.randint(-10,10)
            tmp = random.randint(-1, 1)
            osys_perturb.zernike_coeffs[
                i] = weight * tmp * osys.zernike_coeffs[i]
            osys_final.zernike_coeffs[i] = osys.zernike_coeffs[
                i] + weight * tmp * osys.zernike_coeffs[i]

        # implementing and displaying the wavefront maps
        #display_ote_and_psf(nc, ote, title="Initial OPD and PSF")

        ote_perturb.reset()
        ote_perturb.move_global_zernikes(osys_perturb.zernike_coeffs[0:10])
        #display_ote_and_psf(nc_perturb, ote_perturb, title="Perturbed OPD and PSF")

        ote_final.reset()
        ote_final.move_global_zernikes(osys_final.zernike_coeffs[0:10])
        #display_ote_and_psf(nc_final, ote_final, title="Final OPD and PSF")

        rms = ote.rms()
        rms_perturb = ote_perturb.rms()
        rms_final = ote_final.rms()
        print(rms, rms_perturb, rms_final)
        print('')

        #print(osys.zernike_coeffs)
        #print('')
        #print(osys_perturb.zernike_coeffs)
        #print('')
        #print(osys_final.zernike_coeffs)
        #print('')

        opd = poppy.zernike.opd_from_zernikes(
            osys.zernike_coeffs[0:10],
            npix=1024,
            basis=poppy.zernike.zernike_basis_faster)

        opd_perturb = poppy.zernike.opd_from_zernikes(
            osys_perturb.zernike_coeffs[0:10],
            npix=1024,
            basis=poppy.zernike.zernike_basis_faster)

        opd_final = poppy.zernike.opd_from_zernikes(
            osys_final.zernike_coeffs[0:10],
            npix=1024,
            basis=poppy.zernike.zernike_basis_faster)

        #tab_opd_final.append(opd_final)

        write_fits(path + '_opd' + str(n) + '.fits', opd)
        write_fits(path + '_opd_perturb' + str(n) + '.fits', opd_perturb)
        write_fits(path + '_opd_final' + str(n) + '.fits', opd_final)

        #plt.figure(figsize=(12,4))
        #ax1 = plt.subplot(131)
        #ax1.imshow(opd)
        #ax1.set_title('initial wavefront map')
        #ax2 = plt.subplot(132)
        #ax2.imshow(opd_perturb)
        #ax2.set_title('perturbed wavefront map')
        #ax3 = plt.subplot(133)
        #ax3.imshow(opd_final)
        #ax3.set_title('sum of maps')
        #plt.show()

        wavefront_error = mse(np.nan_to_num(opd), np.nan_to_num(opd_final))
        print('mse', error, wavefront_error)
        print("MSE: %.2f" % (wavefront_error * 100))

    return tab_opd_final
Exemplo n.º 13
0
def do_test_nircam_blc(clobber=False,
                       kind='circular',
                       angle=0,
                       save=False,
                       display=False,
                       outputdir=None):
    """ Test NIRCam BLC coronagraphs

    Compute BLC PSFs on axis and offset and check the values against the expectation.
    Note that the 'correct' values are just prior calculations with WebbPSF; the purpose of
    this routine is just to check for basic functionaltiy of the code and consistency with
    prior results. See the validate_* tests instead for validation against independent
    models of JWST coronagraphy performance - that is NOT what we're trying to do here.

    """

    nc = webbpsf_core.NIRCam()
    nc.pupilopd = None

    nc, ote = webbpsf.enable_adjustable_ote(nc)
    ote._include_nominal_field_dep = False  # disable OTE field dependence model for this test
    # for consistency with expected values prepared before that model existed

    nc.filter = 'F210M'
    offsets = [0, 0.25, 0.50]
    if kind == 'circular':
        nc.image_mask = 'MASK210R'
        nc.pupil_mask = 'CIRCLYOT'
        fn = 'm210r'
        expected_total_fluxes = [1.84e-5, 0.0240, 0.1376
                                 ]  # Based on a prior calculation with WebbPSF
        # values updated slightly for Rev W aperture results
        # Updated 2019-05-02 for coron WFE - changes from [1.35e-5, 0.0240, 0.1376] to [1.84e-5, 0.0240, 0.1376]
    else:
        nc.image_mask = 'MASKSWB'
        nc.pupil_mask = 'WEDGELYOT'
        nc.options[
            'bar_offset'] = 0  # For consistency with how this test was developed
        # FIXME update the expected fluxes for the offset positions
        # which are now the default.
        fn = 'mswb'
        if angle == 0:
            expected_total_fluxes = [
                3.71e-6, .0628, 0.1449
            ]  # Based on a prior calculation with WebbPSF
            # Updated 2019-05-02 for coron WFE - changes from [2.09e-6, .0415, 0.1442] to [3.71e-6, .0628, 0.1449]
        elif angle == 45 or angle == -45:
            expected_total_fluxes = [3.71e-6, 0.0221,
                                     0.1192]  # Based on a prior calculation
            # Updated 2016-09-29 for Rev W results - slight change from 0.1171 to 0.1176
            # Updated 2018-02-20 for recoded MASKSWB - changes from 0.0219 to 0.0220; 0.1176 to 0.1192 ??
            # Updated 2019-05-02 for coron WFE - changes from [2.09e-6, 0.0220, 0.1192] to [3.71e-6, 0.0221, 0.1192]
        else:
            raise ValueError(
                "Don't know how to check fluxes for angle={0}".format(angle))

    # If you change either of the following, the expected flux values will need to be updated:
    nlam = 1
    oversample = 4

    if outputdir is None:
        import tempfile
        outputdir = tempfile.gettempdir()

    if display:
        nc.display()
        plt.figure()

    #for offset in [0]:
    for offset, exp_flux in zip(
            offsets, expected_total_fluxes):  #np.linspace(0.0, 0.5, nsteps):
        nc.options['source_offset_theta'] = angle
        nc.options['source_offset_r'] = offset

        fnout = os.path.join(
            outputdir, 'test_nircam_%s_t%d_r%.2f.fits' % (fn, angle, offset))

        # We can save the outputs; this is not recommended or useful for general testing but is
        # helpful when/if debugging this test routine itself.
        if not os.path.exists(fnout) or clobber:
            psf = nc.calc_psf(oversample=oversample,
                              nlambda=nlam,
                              save_intermediates=False,
                              display=display)  #, monochromatic=10.65e-6)
            if save:
                plt.savefig(fnout + ".pdf")
                psf.writeto(fnout, clobber=clobber)
        else:
            psf = fits.open(fnout)
        totflux = psf[0].data.sum()

        #print("Offset: {}    Expected Flux: {}  Calc Flux: {}".format(offset,exp_flux,totflux))

        # FIXME tolerance temporarily increased to 1% in final flux, to allow for using
        # regular propagation rather than semi-analytic. See poppy issue #169
        assert abs(
            totflux - exp_flux
        ) < 1e-4, f"Total flux {totflux} is out of tolerance relative to expectations {exp_flux}, for offset={offset}, angle={angle}"
        #assert( abs(totflux - exp_flux) < 1e-2 )
        _log.info(
            "File {0} has the expected total flux based on prior reference calculation: {1}"
            .format(fnout, totflux))
Exemplo n.º 14
0
    # Create NIRCam objects, one for perfect PSF and one with coronagraph
    print('Setting up the E2E simulation.')
    nc = webbpsf.NIRCam()
    # Set filter
    nc.filter = filter

    # Same for coronagraphic case
    nc_coro = webbpsf.NIRCam()
    nc_coro.filter = filter

    # Add coronagraphic elements to nc_coro
    nc_coro.image_mask = fpm
    nc_coro.pupil_mask = lyot_stop

    # Null the OTE OPDs for the PSFs, maybe we will add internal WFE later.
    nc, ote = webbpsf.enable_adjustable_ote(nc)  # create OTE for default PSF
    nc_coro, ote_coro = webbpsf.enable_adjustable_ote(
        nc_coro)  # create OTE for coronagraph
    ote.zero()  # set OTE for default PSF to zero
    ote_coro.zero()  # set OTE for coronagraph to zero
    nc.include_si_wfe = False  # set SI internal WFE to zero
    nc_coro.include_si_wfe = False  # set SI internal WFE to zero

    # Generate the E2E PSFs with and without coronagraph
    print('Calculating perfect PSF without coronograph...')
    psf_start_time = time.time()
    psf_default_hdu = nc.calc_psf(fov_pixels=int(im_size_e2e),
                                  oversample=1,
                                  nlambda=1)
    psf_end_time = time.time()
    print('Calculating this PSF with WebbPSF took',
Exemplo n.º 15
0
def num_matrix_jwst():
    """
    Generate a numerical PASTIS matrix for a JWST coronagraph.

    All inputs are read from the (local) configfile and saved to the specified output directory.
    """

    import webbpsf
    from e2e_simulators import webbpsf_imaging as webbim

    # Keep track of time
    start_time = time.time()  # runtime is currently around 21 minutes
    print('Building numerical matrix for JWST\n')

    # Parameters
    resDir = os.path.join(CONFIG_INI.get('local', 'local_data_path'), 'active',
                          'matrix_numerical')
    which_tel = CONFIG_INI.get('telescope', 'name')
    nb_seg = CONFIG_INI.getint(which_tel, 'nb_subapertures')
    im_size_e2e = CONFIG_INI.getint('numerical', 'im_size_px_webbpsf')
    inner_wa = CONFIG_INI.getint(which_tel, 'IWA')
    outer_wa = CONFIG_INI.getint(which_tel, 'OWA')
    sampling = CONFIG_INI.getfloat('numerical', 'sampling')
    fpm = CONFIG_INI.get(which_tel, 'focal_plane_mask')  # focal plane mask
    lyot_stop = CONFIG_INI.get(which_tel, 'pupil_plane_stop')  # Lyot stop
    filter = CONFIG_INI.get(which_tel, 'filter_name')
    nm_aber = CONFIG_INI.getfloat('calibration', 'single_aberration') * u.nm
    wss_segs = webbpsf.constants.SEGNAMES_WSS_ORDER
    zern_max = CONFIG_INI.getint('zernikes', 'max_zern')
    zern_number = CONFIG_INI.getint('calibration', 'zernike')
    zern_mode = util.ZernikeMode(
        zern_number)  # Create Zernike mode object for easier handling
    wss_zern_nb = util.noll_to_wss(
        zern_number)  # Convert from Noll to WSS framework

    # If subfolder "matrix_numerical" doesn't exist yet, create it.
    if not os.path.isdir(resDir):
        os.mkdir(resDir)

    # If subfolder "OTE_images" doesn't exist yet, create it.
    if not os.path.isdir(os.path.join(resDir, 'OTE_images')):
        os.mkdir(os.path.join(resDir, 'OTE_images'))

    # If subfolder "psfs" doesn't exist yet, create it.
    if not os.path.isdir(os.path.join(resDir, 'psfs')):
        os.mkdir(os.path.join(resDir, 'psfs'))

    # If subfolder "darkholes" doesn't exist yet, create it.
    if not os.path.isdir(os.path.join(resDir, 'darkholes')):
        os.mkdir(os.path.join(resDir, 'darkholes'))

    # Create the dark hole mask.
    pup_im = np.zeros([im_size_e2e, im_size_e2e
                       ])  # this is just used for DH mask generation
    dh_area = util.create_dark_hole(pup_im, inner_wa, outer_wa, sampling)

    # Create a direct WebbPSF image for normalization factor
    fake_aber = np.zeros([nb_seg, zern_max])
    psf_perfect = webbim.nircam_nocoro(filter, fake_aber)
    normp = np.max(psf_perfect)
    psf_perfect = psf_perfect / normp

    # Set up NIRCam coro object from WebbPSF
    nc_coro = webbpsf.NIRCam()
    nc_coro.filter = filter
    nc_coro.image_mask = fpm
    nc_coro.pupil_mask = lyot_stop

    # Null the OTE OPDs for the PSFs, maybe we will add internal WFE later.
    nc_coro, ote_coro = webbpsf.enable_adjustable_ote(
        nc_coro)  # create OTE for coronagraph
    nc_coro.include_si_wfe = False  # set SI internal WFE to zero

    #-# Generating the PASTIS matrix and a list for all contrasts
    matrix_direct = np.zeros([nb_seg, nb_seg])  # Generate empty matrix
    all_psfs = []
    all_dhs = []
    all_contrasts = []

    print('nm_aber: {}'.format(nm_aber))

    for i in range(nb_seg):
        for j in range(nb_seg):

            print('\nSTEP: {}-{} / {}-{}'.format(i + 1, j + 1, nb_seg, nb_seg))

            # Get names of segments, they're being addressed by their names in the ote functions.
            seg_i = wss_segs[i].split('-')[0]
            seg_j = wss_segs[j].split('-')[0]

            # Put the aberration on the correct segments
            Aber_WSS = np.zeros([
                nb_seg, zern_max
            ])  # The Zernikes here will be filled in the WSS order!!!
            # Because it goes into _apply_hexikes_to_seg().
            Aber_WSS[i, wss_zern_nb - 1] = nm_aber.to(
                u.m
            ).value  # Aberration on the segment we're currently working on;
            # convert to meters; -1 on the Zernike because Python starts
            # numbering at 0.
            Aber_WSS[j, wss_zern_nb - 1] = nm_aber.to(
                u.m).value  # same for other segment

            # Putting aberrations on segments i and j
            ote_coro.reset(
            )  # Making sure there are no previous movements on the segments.
            ote_coro.zero()  # set OTE for coronagraph to zero

            # Apply both aberrations to OTE. If i=j, apply only once!
            ote_coro._apply_hexikes_to_seg(seg_i, Aber_WSS[
                i, :])  # set segment i  (segment numbering starts at 1)
            if i != j:
                ote_coro._apply_hexikes_to_seg(seg_j,
                                               Aber_WSS[j, :])  # set segment j

            # If you want to display it:
            # ote_coro.display_opd()
            # plt.show()

            # Save OPD images for testing
            opd_name = 'opd_' + zern_mode.name + '_' + zern_mode.convention + str(
                zern_mode.index) + '_segs_' + str(i + 1) + '-' + str(j + 1)
            plt.clf()
            ote_coro.display_opd()
            plt.savefig(os.path.join(resDir, 'OTE_images', opd_name + '.pdf'))

            print('Calculating WebbPSF image')
            image = nc_coro.calc_psf(fov_pixels=int(im_size_e2e),
                                     oversample=1,
                                     nlambda=1)
            psf = image[0].data / normp

            # Save WebbPSF image to disk
            filename_psf = 'psf_' + zern_mode.name + '_' + zern_mode.convention + str(
                zern_mode.index) + '_segs_' + str(i + 1) + '-' + str(j + 1)
            util.write_fits(psf,
                            os.path.join(resDir, 'psfs',
                                         filename_psf + '.fits'),
                            header=None,
                            metadata=None)
            all_psfs.append(psf)

            print('Calculating mean contrast in dark hole')
            dh_intensity = psf * dh_area
            contrast = np.mean(dh_intensity[np.where(dh_intensity != 0)])
            print('contrast:', contrast)

            # Save DH image to disk and put current contrast in list
            filename_dh = 'dh_' + zern_mode.name + '_' + zern_mode.convention + str(
                zern_mode.index) + '_segs_' + str(i + 1) + '-' + str(j + 1)
            util.write_fits(dh_intensity,
                            os.path.join(resDir, 'darkholes',
                                         filename_dh + '.fits'),
                            header=None,
                            metadata=None)
            all_dhs.append(dh_intensity)
            all_contrasts.append(contrast)

            # Fill according entry in the matrix
            matrix_direct[i, j] = contrast

    # Transform saved lists to arrays
    all_psfs = np.array(all_psfs)
    all_dhs = np.array(all_dhs)
    all_contrasts = np.array(all_contrasts)

    # Filling the off-axis elements
    matrix_two_N = np.copy(
        matrix_direct
    )  # This is just an intermediary copy so that I don't mix things up.
    matrix_pastis = np.copy(
        matrix_direct)  # This will be the final PASTIS matrix.

    for i in range(nb_seg):
        for j in range(nb_seg):
            if i != j:
                matrix_off_val = (matrix_two_N[i, j] - matrix_two_N[i, i] -
                                  matrix_two_N[j, j]) / 2.
                matrix_pastis[i, j] = matrix_off_val
                print('Off-axis for i{}-j{}: {}'.format(
                    i + 1, j + 1, matrix_off_val))

    # Normalize matrix for the input aberration
    matrix_pastis /= np.square(nm_aber.value)

    # Save matrix to file
    filename_matrix = 'PASTISmatrix_num_' + zern_mode.name + '_' + zern_mode.convention + str(
        zern_mode.index)
    util.write_fits(matrix_pastis,
                    os.path.join(resDir, filename_matrix + '.fits'),
                    header=None,
                    metadata=None)
    print('Matrix saved to:', os.path.join(resDir, filename_matrix + '.fits'))

    # Save the PSF and DH image *cubes* as well (as opposed to each one individually)
    util.write_fits(all_psfs,
                    os.path.join(resDir, 'psfs', 'psf_cube' + '.fits'),
                    header=None,
                    metadata=None)
    util.write_fits(all_dhs,
                    os.path.join(resDir, 'darkholes', 'dh_cube' + '.fits'),
                    header=None,
                    metadata=None)
    np.savetxt(os.path.join(resDir, 'contrasts.txt'), all_contrasts, fmt='%e')

    # Tell us how long it took to finish.
    end_time = time.time()
    print('Runtime for matrix_building.py:', end_time - start_time, 'sec =',
          (end_time - start_time) / 60, 'min')
    print('Data saved to {}'.format(resDir))
Exemplo n.º 16
0
    :param filter: str, filter name
    :param fpm: focal plane mask
    :param ppm: pupil plane mask - Lyot stop
    :return:
    """
    nc = webbpsf.NIRCam()
    nc.filter = filter
    nc.image_mask = fpm
    nc.pupil_mask = ppm

    return nc


if __name__ == '__main__':

    nc_coro = setup_coro('F335M', 'MASK335R', 'CIRCLYOT')
    nc_coro, ote_coro = webbpsf.enable_adjustable_ote(nc_coro)

    ote_coro.zero()
    #ote_coro._apply_hexikes_to_seg('A1', [1e-6])
    #ote_coro._apply_hexikes_to_seg('A3', [1e-6])
    #ote_coro.move_seg_local('A6', xtilt=0.5)
    psf = nc_coro.calc_psf(oversample=1)
    psf = psf[1].data

    plt.subplot(1, 2, 1)
    ote_coro.display_opd()
    plt.subplot(1, 2, 2)
    plt.imshow(psf, norm=LogNorm())
    plt.show()
Exemplo n.º 17
0
def generate_wavefront_errors_correction(nb_of_maps, errors, nb_zernikes):
    import poppy, webbpsf
    import matplotlib.pyplot as plt

    # intial wavefront map
    nc = webbpsf.NIRCam()
    nc, ote = webbpsf.enable_adjustable_ote(nc)
    osys = nc._get_aberrations()

    # final wavefront map
    nc_final = webbpsf.NIRCam()
    nc_final, ote_final = webbpsf.enable_adjustable_ote(nc_final)
    osys_final = nc_final._get_aberrations()

    tab_wavefront_error = np.zeros(nb_of_maps)
    tab_error = np.zeros(nb_of_maps)
    for n, error in zip(range(nb_of_maps), errors):
        print(n, error)
        #print(zip(range(nb_of_maps)))
        #print(errors)
        # change aberrations in wavefront map: example with random zernikes
        # this map will be our perturbation map and we will add it to the initial map with a certain weight

        # creating the perturbation map
        #weight = 0.2
        #weight = error/100
        osys_corrected = osys.zernike_coeffs.copy()

        for i in range(nb_zernikes):
            if i < error + 1:
                osys_corrected[i] = 0

        print(osys.zernike_coeffs)
        print(osys_corrected)

        opd = poppy.zernike.opd_from_zernikes(
            osys.zernike_coeffs,
            npix=1024,
            basis=poppy.zernike.zernike_basis_faster)

        opd_corrected = poppy.zernike.opd_from_zernikes(
            osys_corrected,
            npix=1024,
            basis=poppy.zernike.zernike_basis_faster)

        wavefront_error = mse(np.nan_to_num(opd), np.nan_to_num(opd_corrected))
        print('mse', error, wavefront_error)

        #tab_opd_final.append(opd_final)

        #write_fits('_opd'+str(n)+'.fits',opd)
        #write_fits('_opd_corrected'+str(n)+'.fits',opd_corrected)

        plt.figure(figsize=(12, 4))
        ax1 = plt.subplot(131)
        #ax1.imshow(opd,vmin=np.min(opd),vmax=np.max(opd))
        ax1.imshow(opd)
        ax1.set_title('initial wavefront map')
        ax2 = plt.subplot(132)
        #ax2.imshow(opd_corrected,vmin=np.min(opd),vmax=np.max(opd))
        ax2.imshow(opd_corrected)
        ax2.set_title('corrected wavefront map')
        ax3 = plt.subplot(133)
        ax3.imshow(opd - opd_corrected)
        ax3.set_title('sum of maps')
        plt.show()

        tab_wavefront_error[n] = wavefront_error
        tab_error[n] = error

    return tab_wavefront_error, tab_error