Beispiel #1
0
def add_errors(wf,
               onaxis=True,
               phase_screen=None,
               amp_screen=None,
               tiptilt=None,
               misalign=[0, 0, 0, 0, 0, 0],
               ngrid=1024,
               verbose=False,
               **conf):

    # apply phase screen (scao residuals, ncpa, petal piston)
    if phase_screen is not None:
        assert phase_screen.ndim == 2, "phase_screen dim must be 2."
        proper.prop_add_phase(wf, pad_img(phase_screen, ngrid))

    # apply amplitude screen (Talbot effect)
    if amp_screen is not None:
        assert amp_screen.ndim == 2, "amp_screen dim must be 2."
        proper.prop_multiply(wf, pad_img(amp_screen, ngrid))

    # apply tip-tilt (Zernike 2,3)
    if tiptilt is not None:
        proper.prop_zernikes(wf, [2, 3], np.array(tiptilt, ndmin=1))

    # pupil-plane apodization (already preloaded if no RA misalign)
    if onaxis == False or ('RAVC' in conf['mode'] and misalign is not None):
        conf.update(ravc_misalign=misalign)
        wf = apodizer(wf, onaxis=onaxis, verbose=verbose, **conf)

    return wf
Beispiel #2
0
def lyot_stop(wf, mode='RAVC', ravc_r=0.6, ls_dRext=0.03, ls_dRint=0.05, 
        ls_dRspi=0.04, spi_width=0.5, spi_angles=[0,60,120], diam_ext=37, 
        diam_int=11, ls_misalign=None, file_app_phase='', file_app_amp='', 
        ngrid=1024, npupil=285, margin=50, get_amp=False, 
        get_phase=False, verbose=False, **conf):

    """Add a Lyot stop, or an APP."""
    
    # case 1: Lyot stop
    if mode in ['CVC', 'RAVC']:
        # LS parameters
        r_obstr = ravc_r if mode in ['RAVC'] else diam_int/diam_ext
        ls_int = r_obstr + ls_dRint
        ls_ext = 1 - ls_dRext
        ls_spi = spi_width/diam_ext + ls_dRspi
        # LS misalignments
        ls_misalign = [0,0,0,0,0,0] if ls_misalign is None else list(ls_misalign)
        dx_amp, dy_amp, dz_amp = ls_misalign[0:3]
        dx_phase, dy_phase, dz_phase = ls_misalign[3:6]
        # create Lyot stop
        proper.prop_circular_aperture(wf, ls_ext, dx_amp, dy_amp, NORM=True)
        if diam_int > 0:
            proper.prop_circular_obscuration(wf, ls_int, dx_amp, dy_amp, NORM=True)
        if spi_width > 0:
            for angle in spi_angles:
                proper.prop_rectangular_obscuration(wf, ls_spi, 2, \
                        dx_amp, dy_amp, ROTATION=angle, NORM=True)
        if verbose is True:
            print('Create Lyot stop')
            print('   ls_int=%3.4f, ls_ext=%3.4f, ls_spi=%3.4f'\
                %(ls_int, ls_ext, ls_spi))
            print('')

    # case 2: APP
    elif mode in ['APP']:
        if verbose is True:
            print('Load APP from files\n')
        # get amplitude and phase data
        APP_amp = fits.getdata(file_app_amp) if os.path.isfile(file_app_amp) \
                else np.ones((npupil, npupil))
        APP_phase = fits.getdata(file_app_phase) if os.path.isfile(file_app_phase) \
                else np.zeros((npupil, npupil))
        # resize to npupil
        APP_amp = impro.resize_img(APP_amp, npupil)
        APP_phase = impro.resize_img(APP_phase, npupil)
        # pad with zeros to match PROPER ngrid
        APP_amp = impro.pad_img(APP_amp, ngrid, 1)
        APP_phase = impro.pad_img(APP_phase, ngrid, 0)
        # multiply the loaded APP
        proper.prop_multiply(wf, APP_amp*np.exp(1j*APP_phase))
    
    # get the LS amplitude and phase for output
    LS_amp = impro.crop_img(proper.prop_get_amplitude(wf), npupil, margin)\
            if get_amp is True else None
    LS_phase = impro.crop_img(proper.prop_get_phase(wf), npupil, margin)\
            if get_phase is True else None
    
    return wf, LS_amp, LS_phase
Beispiel #3
0
def lyot_stop(wf, mode='RAVC', ravc_r=0.6, ls_dRext=0.03, ls_dRint=0.05, 
        ls_dRspi=0.04, spi_width=0.5, spi_angles=[0,60,120], diam_ext=37, 
        diam_int=11, diam_nominal=37, ls_misalign=None, ngrid=1024, npupil=285, 
        file_lyot_stop='', verbose=False, **conf):

    """ Add a Lyot stop for a focal plane mask """
    
    if mode in ['CVC', 'RAVC', 'CLC']:

        # load lyot stop from file if provided
        if os.path.isfile(file_lyot_stop):
            if verbose is True:
                print("   apply lyot stop from '%s'"%os.path.basename(file_lyot_stop))
            # get amplitude and phase data
            ls_mask = fits.getdata(file_lyot_stop)
            # resize to npupil
            ls_mask = resize_img(ls_mask, npupil)
            # pad with zeros and add to wavefront
            proper.prop_multiply(wf, pad_img(ls_mask, ngrid))

        # if no lyot stop, create one
        else:
            # scale nominal values to pupil external diameter
            scaling = diam_nominal/diam_ext
            # LS parameters
            r_obstr = ravc_r if mode in ['RAVC'] else diam_int/diam_ext
            ls_int = r_obstr + ls_dRint*scaling
            ls_ext = 1 - ls_dRext*scaling
            ls_spi = spi_width/diam_ext + ls_dRspi*scaling
            # LS misalignments
            ls_misalign = [0,0,0,0,0,0] if ls_misalign is None else list(ls_misalign)
            dx_amp, dy_amp, dz_amp = ls_misalign[0:3]
            dx_phase, dy_phase, dz_phase = ls_misalign[3:6]
            # create Lyot stop
            proper.prop_circular_aperture(wf, ls_ext, dx_amp, dy_amp, NORM=True)
            if diam_int > 0:
                proper.prop_circular_obscuration(wf, ls_int, dx_amp, dy_amp, NORM=True)
            if spi_width > 0:
                for angle in spi_angles:
                    proper.prop_rectangular_obscuration(wf, 2*ls_spi, 2, \
                            dx_amp, dy_amp, ROTATION=angle, NORM=True)
            if verbose is True:
                print('   apply Lyot stop: ls_int=%s, ls_ext=%s, ls_spi=%s'\
                    %(round(ls_int, 4), round(ls_ext, 4), round(ls_spi, 4)))
    
    return wf
Beispiel #4
0
def propagate_one(wf, phase_screen=None, tiptilt=None, misalign=None, 
        ngrid=1024, npupil=285, tag=None, onaxis=True, savefits=False, verbose=False, 
        **conf):
            
    """ 
    Propagate one single wavefront.
    An off-axis PSF can be obtained by switching onaxis to False,
    thereby decentering the focal plane mask (if any).
    """

    # update conf
    conf.update(ngrid=ngrid, npupil=npupil)
    
    # keep a copy of the input wavefront
    wf1 = deepcopy(wf)

    # apply phase screen (scao residuals, ncpa, petal piston)
    if phase_screen is not None:
        phase_screen = np.nan_to_num(phase_screen)
        phase_screen = pad_img(resize_img(phase_screen, npupil), ngrid)
        proper.prop_add_phase(wf1, phase_screen)
    # apply tip-tilt (Zernike 2,3)
    if tiptilt is not None:
        # translate the tip/tilt from lambda/D into RMS phase errors
        # RMS = x/2 = ((lam/D)*(D/2))/2 = lam/4
        tiptilt = np.array(tiptilt, ndmin=1)*conf['lam']/4
        proper.prop_zernikes(wf1, [2,3], tiptilt)
    # update RAVC misalignment
    conf.update(ravc_misalign=misalign)
    # pupil-plane apodization
    wf1, apo_amp, apo_phase = apodizer(wf1, get_amp=True, verbose=verbose, **conf)
    # focal-plane mask, only in 'on-axis' configuration
    if onaxis == True:
        wf1 = fp_mask(wf1, verbose=verbose, **conf)
    # Lyot-stop or APP
    wf1, ls_amp, ls_phase = lyot_stop(wf1, get_amp=True, verbose=verbose, **conf)
    # detector
    psf = detector(wf1, verbose=verbose, **conf)

    # save psf as fits file
    if savefits == True:
        tag = '' if tag is None else '%s_'%tag
        name = '%s%s_PSF'%(tag, {True: 'onaxis', False: 'offaxis'}[onaxis])
        save2fits(psf, name, **conf)

    return psf
def propagate_cube(wf,
                   phase_screens,
                   amp_screens,
                   tiptilts,
                   apo_misaligns,
                   ls_misaligns,
                   nframes=10,
                   nstep=1,
                   mode='RAVC',
                   ngrid=1024,
                   cpu_count=1,
                   vc_chrom_leak=2e-3,
                   add_cl_det=False,
                   add_cl_vort=False,
                   tag=None,
                   onaxis=True,
                   send_to=None,
                   savefits=False,
                   verbose=False,
                   **conf):

    # update conf
    conf.update(mode=mode,
                ngrid=ngrid,
                cpu_count=cpu_count,
                vc_chrom_leak=vc_chrom_leak,
                add_cl_det=add_cl_det,
                add_cl_vort=add_cl_vort,
                tag=tag,
                onaxis=onaxis)

    # keep a copy of the input wavefront
    wf1 = deepcopy(wf)

    if verbose == True:
        print('Create %s-axis PSF cube' % {True: 'on', False: 'off'}[onaxis])
        if add_cl_det is True:
            print('   adding chromatic leakage at detector plane: %s' %
                  vc_chrom_leak)
        if add_cl_vort is True:
            print('   adding chromatic leakage at vortex plane: %s' %
                  vc_chrom_leak)

    # preload amp screen if only one frame
    if len(amp_screens) == 1 and np.any(amp_screens) != None:
        proper.prop_multiply(wf1, pad_img(amp_screens, ngrid))
        # then create a cube of None values
        amp_screens = [None] * int(nframes / nstep + 0.5)
        if verbose == True:
            print('   preloading amplitude screen')

    # preload apodizer when no drift
    if ('APP' in mode) or ('RAVC' in mode and np.all(apo_misaligns) == None):
        wf1 = apodizer(wf1, verbose=False, **conf)
        conf['apo_loaded'] = True
        if verbose == True:
            print('   preloading %s apodizer' % mode)
    else:
        conf['apo_loaded'] = False

    # preload Lyot stop when no drift
    if ('VC' in mode or 'LC' in mode) and np.all(ls_misaligns) == None:
        conf['ls_mask'] = lyot_stop(wf1, apply_ls=False, verbose=False, **conf)
        if verbose == True:
            print('   preloading Lyot stop')

    # run simulation
    posvars = [
        phase_screens, amp_screens, tiptilts, apo_misaligns, ls_misaligns
    ]
    kwargs = dict(verbose=False, **conf)
    psfs = multiCPU(propagate_one,
                    posargs=[wf1],
                    posvars=posvars,
                    kwargs=kwargs,
                    case='e2e simulation',
                    cpu_count=cpu_count)

    # if only one wavefront, make dim = 2
    if len(psfs) == 1:
        psfs = psfs[0]

    # save cube of PSFs to fits file, and notify by email
    if savefits == True:
        tag = '' if tag is None else '%s_' % tag
        name = '%s%s_PSF' % (tag, {True: 'onaxis', False: 'offaxis'}[onaxis])
        filename = save2fits(psfs, name, **conf)
        notify('saved to %s' % filename, send_to)

    return psfs
Beispiel #6
0
def apodizer(wf,
             mode='RAVC',
             ravc_t=0.8,
             ravc_r=0.6,
             ngrid=1024,
             npupil=285,
             f_app_amp='',
             f_app_phase='',
             f_ravc_amp='',
             f_ravc_phase='',
             apo_misalign=None,
             onaxis=True,
             verbose=False,
             **conf):
    ''' Create a wavefront object at the entrance pupil plane.
    The pupil is either loaded from a fits file, or created using
    pupil parameters.
    Can also select only one petal and mask the others.

    wf: WaveFront
        PROPER wavefront object
    mode: str
        HCI mode
    ravc_t: float
        RA transmittance
    ravc_r: float
        RA radius
    ngrid: int
        number of pixels of the wavefront array
    npupil: int
        number of pixels of the pupil
    f_app_amp: str
    f_app_phase: str 
        apodizing phase plate files
    f_ravc_amp: str
    f_ravc_phase: str 
        ring apodizer files (optional)
    apo_misalign: list of float
        apodizer misalignment

    '''

    # case 1: Ring Apodizer
    if 'RAVC' in mode and ravc_r > 0:

        # load apodizer from files if provided
        if os.path.isfile(f_ravc_amp) and os.path.isfile(f_ravc_phase):
            if verbose is True:
                print('   apply ring apodizer from files')
            # get amplitude and phase data
            RAVC_amp = fits.getdata(f_ravc_amp)
            RAVC_phase = fits.getdata(f_ravc_phase)
            # resize to npupil
            RAVC_amp = impro.resize_img(RAVC_amp, npupil)
            RAVC_phase = impro.resize_img(RAVC_phase, npupil)
            # pad with zeros to match PROPER gridsize
            RAVC_amp = impro.pad_img(RAVC_amp, ngrid)
            RAVC_phase = impro.pad_img(RAVC_phase, ngrid)
            # build complex apodizer
            ring = RAVC_amp * np.exp(1j * RAVC_phase)

        # else, define the apodizer as a ring (with % misalignments)
        else:
            # RAVC misalignments
            dx, dy = [0, 0
                      ] if apo_misalign is None else list(apo_misalign)[0:2]
            # create apodizer
            ring = circular_apodization(wf,
                                        ravc_r,
                                        1,
                                        ravc_t,
                                        xc=dx,
                                        yc=dy,
                                        NORM=True)
            if verbose is True:
                print('   apply ring apodizer: ravc_t=%s, ravc_r=%s' %
                      (round(ravc_t, 4), round(ravc_r, 4)))

        # multiply the loaded apodizer
        proper.prop_multiply(wf, ring)

    # case 2: Apodizing Phase Plate
    elif 'APP' in mode:
        # get amplitude and phase data
        if os.path.isfile(f_app_amp):
            if verbose is True:
                print('   apply APP stop (amplitude)')
            APP_amp = fits.getdata(f_app_amp)
        else:
            APP_amp = np.ones((npupil, npupil))
        if os.path.isfile(f_app_phase) and onaxis == True:
            if verbose is True:
                print('   apply APP phase')
            APP_phase = fits.getdata(f_app_phase)
        else:
            APP_phase = np.zeros((npupil, npupil))
        # resize to npupil
        APP_amp = impro.resize_img(APP_amp, npupil)
        APP_phase = impro.resize_img(APP_phase, npupil)
        # rotate for negative PSF
        if 'neg' in mode:
            APP_amp = np.rot90(APP_amp, 2)
            APP_phase = np.rot90(APP_phase, 2)
        # pad with zeros to match PROPER ngrid
        APP_amp = impro.pad_img(APP_amp, ngrid, 0)
        APP_phase = impro.pad_img(APP_phase, ngrid, 0)

        # multiply the loaded APP
        proper.prop_multiply(wf, APP_amp * np.exp(1j * APP_phase))

    return wf
Beispiel #7
0
def fp_mask(wf, mode='RAVC', focal=660, verbose=False, **conf):

    # case 1: vortex coronagraphs
    if mode in ['CVC', 'RAVC']:
        if verbose is True:
            print('Apply Vortex phase mask')                        
        # update conf
        conf.update(focal=focal)
        # load vortex calibration files: 
        #   conf['psf_num'], conf['vvc'], conf['perf_num']
        conf = vortex_init(verbose=verbose, **conf)
        # propagate to vortex
        lens(wf, focal)
        # apply vortex
        scale_psf = wf._wfarr[0,0]/conf['psf_num'][0,0]
        wf_corr = (conf['psf_num']*conf['vvc'] - conf['perf_num'])*scale_psf
        wf._wfarr = wf._wfarr*conf['vvc'] - wf_corr
        # propagate to lyot stop
        lens(wf, focal)

    # TODO: cleanup CLC
    
    # case 2: classical Lyot
    elif mode in ['CLC']:
        if verbose is True:
            print('Apply Classical Lyot mask\n')        
        f_lens = conf['focal']
        beam_ratio = conf['beam_ratio']
        CLC_diam = conf['CLC_diam'] # classical lyot diam in lam/D (default to 4)
        gridsize = conf['gridsize']
        tmp_dir = conf['temp_dir']
        
        # propagate to lyot mask
        lens(wf, conf['focal'])
        
        # create or load the classical Lyot mask
        calib = str(CLC_diam)+str('_')+str(int(beam_ratio*100))+str('_')+str(gridsize)
        my_file = os.path.join(tmp_dir, 'clc_'+calib+'.fits')
        if not os.path.isfile(my_file):
            # calculate exact size of Lyot mask diameter, in pixels
            Dmask = CLC_diam/beam_ratio
            # oversample the Lyot mask (round up)
            samp = 100
            ndisk = int(samp*np.ceil(Dmask))
            ndisk = ndisk + 1 if not ndisk % 2 else ndisk # must be odd
            # find center
            cdisk = int((ndisk - 1)/2)
            # calculate the distances to center
            xy = range(-cdisk, cdisk + 1)
            x,y = np.meshgrid(xy, xy)
            dist = np.sqrt(x**2 + y**2)
            # create the Lyot mask
            mask = np.zeros((ndisk, ndisk))
            mask[np.where(dist > samp*Dmask/2)] = 1
            # resize to Lyot mask real size, and pad with ones
            mask = resize_img(mask, int(ndisk/samp))
            mask = pad_img(mask, gridsize, 1)
            # write mask
            fits.writeto(my_file, mask)
        else:
            mask = fits.getdata(my_file)
        
        # apply lyot mask
        mask = proper.prop_shift_center(mask)
        wf._wfarr.real *= mask
        
        # propagate to lyot stop
        lens(wf, conf['focal'])

    return wf
Beispiel #8
0
def pupil(pup=None,
          f_pupil='',
          lam=3.8e-6,
          ngrid=1024,
          npupil=285,
          pupil_img_size=40,
          diam_ext=37,
          diam_int=11,
          spi_width=0.5,
          spi_angles=[0, 60, 120],
          npetals=6,
          seg_width=0,
          seg_gap=0,
          seg_rms=0,
          seg_ny=[
              10, 13, 16, 19, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 30, 31,
              30, 31, 30, 31, 30, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 19,
              16, 13, 10
          ],
          seg_missing=[],
          select_petal=None,
          norm_I=True,
          savefits=False,
          verbose=False,
          **conf):
    ''' Create a wavefront object at the entrance pupil plane. 
    The pupil is either loaded from a fits file, or created using 
    pupil parameters.
    Can also select only one petal and mask the others.

    Args:
        dir_output (str):
            path to saved pupil file
        band (str):
            spectral band (e.g. 'L', 'M', 'N1', 'N2')
        mode (str):
            HCI mode: RAVC, CVC, APP, CLC
        f_pupil: str
            path to a pupil fits file
        lam: float
            wavelength in m
        ngrid: int
            number of pixels of the wavefront array
        npupil: int
            number of pixels of the pupil
        pupil_img_size: float
            pupil image (for PROPER) in m
        diam_ext: float
            outer circular aperture in m
        diam_int: float
            central obscuration in m
        spi_width: float
            spider width in m
        spi_angles: list of float
            spider angles in deg
        seg_width: float
            segment width in m
        seg_gap: float
            gap between segments in m
        seg_rms: float
            rms of the reflectivity of all segments
        seg_ny: list of int
            number of hexagonal segments per column (from left to right)
        seg_missing: list of tupples
            coordinates of missing segments
        npetals: int
            number of petals
        select_petal: int
            selected petal in range npetals, default to None
    
    '''

    # initialize wavefront using PROPER
    beam_ratio = npupil / ngrid * (diam_ext / pupil_img_size)
    wf = proper.prop_begin(diam_ext, lam, ngrid, beam_ratio)

    # case 1: load pupil from data
    if pup is not None:
        if verbose is True:
            print("Load pupil data from 'pup'")
        pup = resize_img(pup, npupil)

    # case 2: load pupil from file
    elif os.path.isfile(f_pupil):
        if verbose is True:
            print("Load pupil from '%s'" % os.path.basename(f_pupil))
        pup = resize_img(fits.getdata(f_pupil), npupil)

    # case 3: create a pupil
    else:
        if verbose is True:
            print("Create pupil: spi_width=%s m, seg_width=%s m, seg_gap=%s m, seg_rms=%s"\
                %(spi_width, seg_width, seg_gap, seg_rms))
        conf.update(npupil=npupil,
                    pupil_img_size=pupil_img_size,
                    diam_ext=diam_ext,
                    diam_int=diam_int,
                    spi_width=spi_width,
                    spi_angles=spi_angles,
                    seg_width=seg_width,
                    seg_gap=seg_gap,
                    seg_ny=seg_ny,
                    seg_missing=seg_missing,
                    seg_rms=seg_rms)
        pup = create_pupil(**conf)

    # select one petal (optional)
    if select_petal in range(npetals) and npetals > 1:
        if verbose is True:
            print("   select_petal=%s" % select_petal)
        petal = create_petal(select_petal, npetals, npupil)
        pup *= petal

    # normalize the entrance pupil intensity (total flux = 1)
    if norm_I is True:
        I_pup = pup**2
        pup = np.sqrt(I_pup / np.sum(I_pup))
    # save pupil as fits file
    if savefits == True:
        save2fits(pup, 'pupil', **conf)

    # pad with zeros and add to wavefront
    proper.prop_multiply(wf, pad_img(pup, ngrid))

    return wf
Beispiel #9
0
def pupil(file_pupil='',
          lam=3.8e-6,
          ngrid=1024,
          npupil=285,
          pupil_img_size=40,
          diam_ext=37,
          diam_int=11,
          spi_width=0.5,
          spi_angles=[0, 60, 120],
          npetals=6,
          seg_width=0,
          seg_gap=0,
          seg_rms=0,
          seg_ny=[
              10, 13, 16, 19, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 30, 31,
              30, 31, 30, 31, 30, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 19,
              16, 13, 10
          ],
          seg_missing=[],
          select_petal=None,
          savefits=False,
          verbose=False,
          **conf):
    ''' Create a wavefront object at the entrance pupil plane. 
    The pupil is either loaded from a fits file, or created using 
    pupil parameters.
    Can also select only one petal and mask the others.

    Args:
        dir_output (str):
            path to saved pupil file
        band (str):
            spectral band (e.g. 'L', 'M', 'N1', 'N2')
        mode (str):
            HCI mode: RAVC, CVC, APP, CLC
        file_pupil: str
            path to a pupil fits file
        lam: float
            wavelength in m
        ngrid: int
            number of pixels of the wavefront array
        npupil: int
            number of pixels of the pupil
        pupil_img_size: float
            pupil image (for PROPER) in m
        diam_ext: float
            outer circular aperture in m
        diam_int: float
            central obscuration in m
        spi_width: float
            spider width in m
        spi_angles: list of float
            spider angles in deg
        seg_width: float
            segment width in m
        seg_gap: float
            gap between segments in m
        seg_rms: float
            rms of the reflectivity of all segments
        seg_ny: list of int
            number of hexagonal segments per column (from left to right)
        seg_missing: list of tupples
            coordinates of missing segments
        npetals: int
            number of petals
        select_petal: int
            selected petal in range npetals, default to None
    
    '''

    # initialize wavefront using PROPER
    beam_ratio = npupil / ngrid * (diam_ext / pupil_img_size)
    wf = proper.prop_begin(pupil_img_size, lam, ngrid, beam_ratio)

    # load pupil file
    if os.path.isfile(file_pupil):
        if verbose is True:
            print("Load pupil from '%s'" % os.path.basename(file_pupil))
        #conf.update()
        pup = fits.getdata(file_pupil).astype(np.float32)
        # resize to npupil
        pup = resize_img(pup, npupil)
    # if no pupil file, create a pupil
    else:
        if verbose is True:
            print("Create pupil: spi_width=%s m, seg_width=%s m, seg_gap=%s m, seg_rms=%s"\
                %(spi_width, seg_width, seg_gap, seg_rms))
        conf.update(npupil=npupil,
                    pupil_img_size=pupil_img_size,
                    diam_ext=diam_ext,
                    diam_int=diam_int,
                    spi_width=spi_width,
                    spi_angles=spi_angles,
                    seg_width=seg_width,
                    seg_gap=seg_gap,
                    seg_ny=seg_ny,
                    seg_missing=seg_missing,
                    seg_rms=seg_rms)
        pup = create_pupil(**conf)

    # normalize the entrance pupil intensity (total flux = 1)
    I_pup = pup**2
    pup = np.sqrt(I_pup / np.sum(I_pup))
    # pad with zeros and add to wavefront
    proper.prop_multiply(wf, pad_img(pup, ngrid))

    # select one petal (optional)
    if select_petal in range(npetals) and npetals > 1:
        if verbose is True:
            print("   select_petal=%s" % select_petal)
        # petal start and end angles
        pet_angle = 2 * np.pi / npetals
        pet_start = pet_angle / 2 + (select_petal - 1) * pet_angle
        pet_end = pet_start + pet_angle
        # petal angles must be 0-2pi
        ti %= (2 * np.pi)
        pet_start %= (2 * np.pi)
        pet_end %= (2 * np.pi)
        # check if petal crosses 0 angle
        if pet_end - pet_start < 0:
            pet_start = (pet_start + np.pi) % (2 * np.pi) - np.pi
            pet_end = (pet_end + np.pi) % (2 * np.pi) - np.pi
            ti = (ti + np.pi) % (2 * np.pi) - np.pi
        # create petal and add to grid
        petal = ((ti >= pet_start) * (ti <= pet_end)).astype(int)
        petal = resize_img(petal, ngrid)
        proper.prop_multiply(wf, petal)

    # save pupil as fits file
    if savefits == True:
        save2fits(pup, 'pupil', **conf)

    if verbose is True:
        print('   diam=%s m, resize to %s pix, zero-pad to %s pix\n'\
            %(round(diam_ext, 2), npupil, ngrid))

    return wf
Beispiel #10
0
def lyotmask_init(lyotmask_calib='',
                  dir_temp='',
                  clc_diam=80,
                  pscale=5.47,
                  magnification=100,
                  ngrid=1024,
                  beam_ratio=0.26,
                  verbose=False,
                  **conf):
    '''
    
    Creates/writes classical lyot masks, or loads them if files 
    already exist.
    The following parameters will be added to conf: 
        lyotmask_calib, lyotmask
    
    Returns: conf (updated and sorted)

    '''

    # update conf with local variables (remove unnecessary)
    conf.update(locals())
    [conf.pop(key) for key in ['conf', 'verbose'] if key in conf]

    # check if lyot mask already loaded for this calib
    calib = 'lyotmask_%s_%s_%3.4f' % (clc_diam, ngrid, beam_ratio)
    if lyotmask_calib == calib:
        return conf

    else:
        # check for existing file
        filename = os.path.join(dir_temp, '%s.fits' % calib)
        if os.path.isfile(filename):
            if verbose is True:
                print('   loading lyot mask')
            lyotmask = fits.getdata(os.path.join(dir_temp, filename))

        # create file
        else:
            if verbose is True:
                print("   writing lyot mask")
            # calculate the size (in pixels) of the lyot mask,
            # rounded up to an odd value
            nmask = int(np.ceil(clc_diam / pscale))
            nmask += 1 - nmask % 2
            # create a magnified lyot mask
            rmask = clc_diam / pscale / nmask
            r, t = polar_coord(nmask * magnification)
            lyotmask = np.zeros(np.shape(r))
            lyotmask[r > rmask] = 1
            # resize and pad with ones to amtch ngrid
            lyotmask = pad_img(resize_img(lyotmask, nmask), ngrid, 1)
            # save as fits file
            fits.writeto(os.path.join(dir_temp, filename),
                         np.float32(lyotmask),
                         overwrite=True)

        # shift the lyotmask amplitude
        lyotmask = proper.prop_shift_center(lyotmask)
        # add lyotmask amplitude at the end of conf
        conf = {k: v for k, v in sorted(conf.items())}
        conf.update(lyotmask_calib=calib, lyotmask=lyotmask)

        if verbose is True:
            print('   clc_diam=%s, ngrid=%s, beam_ratio=%3.4f'%\
                (clc_diam, ngrid, beam_ratio))

        return conf
Beispiel #11
0
def propagate_cube(wf,
                   phase_screens,
                   amp_screens,
                   tiptilts,
                   misaligns,
                   cpu_count=1,
                   vc_chrom_leak=2e-3,
                   add_cl_det=False,
                   add_cl_vort=False,
                   tag=None,
                   onaxis=True,
                   send_to=None,
                   savefits=False,
                   verbose=False,
                   **conf):

    # update conf
    conf.update(cpu_count=cpu_count, vc_chrom_leak=vc_chrom_leak, \
            add_cl_det=add_cl_det, add_cl_vort=add_cl_vort, tag=tag, onaxis=onaxis)

    # preload amp screen if only one frame
    if len(amp_screens) == 1 and np.any(amp_screens) != None:
        import proper
        from heeps.util.img_processing import pad_img, resize_img
        amp_screens = np.nan_to_num(amp_screens[0])
        amp_screens = pad_img(resize_img(amp_screens, conf['npupil']),
                              conf['ngrid'])
        proper.prop_multiply(wf, amp_screens)
        # then create a cube of None values
        amp_screens = [None] * int((conf['nframes'] / conf['nstep']) + 0.5)

    # preload apodizer when no drift
    if np.all(misaligns) == None or 'APP' in conf['mode']:
        wf = apodizer(wf, verbose=False, **conf)

    if verbose == True:
        print('Create %s-axis PSF cube' % {True: 'on', False: 'off'}[onaxis])
        if add_cl_det is True:
            print('   adding chromatic leakage at detector plane: %s' %
                  vc_chrom_leak)
        if add_cl_vort is True:
            print('   adding chromatic leakage at vortex plane: %s' %
                  vc_chrom_leak)

    # run simulation
    posvars = [phase_screens, amp_screens, tiptilts, misaligns]
    kwargs = dict(verbose=False, **conf)
    psfs = multiCPU(propagate_one, posargs=[wf], posvars=posvars, kwargs=kwargs, \
        case='e2e simulation', cpu_count=cpu_count)

    # if only one wavefront, make dim = 2
    if len(psfs) == 1:
        psfs = psfs[0]

    # save cube of PSFs to fits file, and notify by email
    if savefits == True:
        tag = '' if tag is None else '%s_' % tag
        name = '%s%s_PSF' % (tag, {True: 'onaxis', False: 'offaxis'}[onaxis])
        filename = save2fits(psfs, name, **conf)
        notify('saved to %s' % filename, send_to)

    return psfs
Beispiel #12
0
def propagate_cube(wf,
                   phase_screens,
                   amp_screens,
                   tiptilts,
                   misaligns,
                   cpu_count=1,
                   send_to=None,
                   tag=None,
                   onaxis=True,
                   savefits=False,
                   verbose=False,
                   **conf):

    # preload amp screen if only one frame
    if len(amp_screens) == 1 and np.any(amp_screens) != None:
        import proper
        from heeps.util.img_processing import pad_img, resize_img
        amp_screens = np.nan_to_num(amp_screens[0])
        amp_screens = pad_img(resize_img(amp_screens, conf['npupil']),
                              conf['ngrid'])
        proper.prop_multiply(wf, amp_screens)
        # then create a cube of None values
        amp_screens = [None] * int((conf['nframes'] / conf['nstep']) + 0.5)

    # preload apodizer when no drift
    if np.all(misaligns) == None or 'APP' in conf['mode']:
        wf = apodizer(wf, verbose=False, **conf)

    if verbose == True:
        print('Create %s-axis PSF cube' % {True: 'on', False: 'off'}[onaxis])

    # run simulation
    t0 = time.time()
    if cpu_count != 1 and platform in ['linux', 'linux2', 'darwin']:
        if cpu_count == None:
            cpu_count = mpro.cpu_count() - 1
        if verbose is True:
            print('   %s: e2e simulation starts, using %s cores'\
                %(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), cpu_count))
        p = mpro.Pool(cpu_count)
        func = partial(propagate_one, wf, onaxis=onaxis, verbose=False, **conf)
        psfs = np.array(
            p.starmap(func, zip(phase_screens, amp_screens, tiptilts,
                                misaligns)))
        p.close()
        p.join()
    else:
        if verbose is True:
            print('   %s: e2e simulation starts, using 1 core'\
                %(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
        for i, (phase_screen, amp_screen, tiptilt, misalign) \
                in enumerate(zip(phase_screens, amp_screens, tiptilts, misaligns)):
            psf = propagate_one(wf, phase_screen, amp_screen, tiptilt, misalign, \
                onaxis=onaxis, verbose=True, **conf)
            psfs = psf if i == 0 else np.dstack((psfs.T, psf.T)).T
    if verbose is True:
        print('   %s: finished, elapsed %.3f seconds'\
            %(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), time.time() - t0))

    # if only one wavefront, make dim = 2
    if len(psfs) == 1:
        psfs = psfs[0]

    # save cube of PSFs to fits file, and notify by email
    if savefits == True:
        tag = '' if tag is None else '%s_' % tag
        name = '%s%s_PSF' % (tag, {True: 'onaxis', False: 'offaxis'}[onaxis])
        filename = save2fits(psfs, name, **conf)
        notify('saved to %s' % filename, send_to)

    return psfs
Beispiel #13
0
def apodizer(wf,
             mode='RAVC',
             ravc_t=0.8,
             ravc_r=0.6,
             ravc_misalign=None,
             ngrid=1024,
             npupil=285,
             file_ravc_amp='',
             file_ravc_phase='',
             margin=50,
             get_amp=False,
             get_phase=False,
             verbose=False,
             **conf):
    ''' Create a wavefront object at the entrance pupil plane. 
    The pupil is either loaded from a fits file, or created using 
    pupil parameters.
    Can also select only one petal and mask the others.

    wf: WaveFront
        PROPER wavefront object
    mode: str
        HCI mode
    ravc_t: float
        RA transmittance
    ravc_r: float
        RA radius
    ravc_misalign: list of float
        RA misalignment
    ngrid: int
        number of pixels of the wavefront array
    npupil: int
        number of pixels of the pupil
    file_ravc_amp: str
    file_ravc_phase: str 
        ring apodizer files (optional)
    
    '''

    if mode in ['RAVC']:

        # load apodizer from files if provided
        if os.path.isfile(file_ravc_amp) and os.path.isfile(file_ravc_phase):
            if verbose is True:
                print('Load ring apodizer from files\n')
            # get amplitude and phase data
            RAVC_amp = fits.getdata(file_ravc_amp)
            RAVC_phase = fits.getdata(file_ravc_phase)
            # resize to npupil
            RAVC_amp = impro.resize_img(RAVC_amp, npupil)
            RAVC_phase = impro.resize_img(RAVC_phase, npupil)
            # pad with zeros to match PROPER gridsize
            RAVC_amp = impro.pad_img(RAVC_amp, ngrid)
            RAVC_phase = impro.pad_img(RAVC_phase, ngrid)
            # build complex apodizer
            apo = RAVC_amp * np.exp(1j * RAVC_phase)

        # or else, define the apodizer as a ring (with % misalignments)
        else:
            # RAVC misalignments
            ravc_misalign = [
                0, 0, 0, 0, 0, 0
            ] if ravc_misalign is None else list(ravc_misalign)
            dx_amp, dy_amp, dz_amp = ravc_misalign[0:3]
            dx_phase, dy_phase, dz_phase = ravc_misalign[3:6]
            # create apodizer
            apo = circular_apodization(wf, ravc_r, 1., ravc_t, xc=dx_amp, \
                yc=dy_amp, NORM=True)
            apo = proper.prop_shift_center(apo)
            if verbose is True:
                print('Create ring apodizer')
                print('   ravc_t=%3.4f, ravc_r=%3.4f'\
                    %(ravc_t, ravc_r))
                print('   ravc_misalign=%s' % ravc_misalign)
                print('')

        # multiply the loaded apodizer
        proper.prop_multiply(wf, apo)

        # get the apodizer amplitude and phase for output
        apo_amp = impro.crop_img(proper.prop_get_amplitude(wf), npupil,\
                margin) if get_amp is True else None
        apo_phase = impro.crop_img(proper.prop_get_phase(wf), npupil,\
                margin) if get_phase is True else None

        return wf, apo_amp, apo_phase

    else:  # no ring apodizer
        return wf, None, None
Beispiel #14
0
npts = 592
new_size = 285
pad_resize = 39.9988/37
new_name = 'cube_COMPASS_20181008_3600s_300ms_0piston_meters_scao_only_%s.fits'%new_size

def is_odd(n):
    return bool(n % 2)

def oversamp(precision, start, end, stop=1e6):
    scale = end/start
    size = np.array([(x, x*scale) for x in np.arange(start, stop) \
            if x*scale % 1 < precision and is_odd(x) is is_odd(round(x*scale))])
    return size[0] if np.any(size) else 0

t0 = time.time()
cube[:,mask==0] = np.nan
cube = (cube.T - np.nanmean(cube,(1,2))).T # removing piston
cube *= 1e-6 # in meters


n1,n2 = oversamp(1e-2, npts, npts*39.9988/37)
print(npts, n1, n2, int(n2)/int(n1)*37)
cube = resize_cube(cube, int(n1), cpu_count=None, verbose=True)
cube = np.float32([pad_img(x, int(n2), np.nan) for x in cube])
cube = resize_cube(cube, new_size, cpu_count=None, verbose=True)

cube.shape

fits.writeto(os.path.join(folder, new_name), np.float32(cube), overwrite=True)
print(time.time() - t0)