Example #1
0
def duet_fluence_to_abmag(fluence, duet_no, duet=None, bandpass=None):
    """
    Convert AB magnitude for a source into the number of source counts.


    Parameters
    ----------
    fluence: float
        fluence in the bandpass that you're using in units (ph / cm2 / sec)

    duet_no: int, 1 or 2
        DUET channel

    Other parameters
    ----------------
    duet : `astroduet.config.Telescope` object
        if None, allocate a new Telescope object

    bandpass: array
        DUET bandpass you're using

    Returns
    -------
    AB magnitude in the band (ABmag)


    Example
    -------
    >>> funit = u.ph / u.cm**2/u.s
    >>> abmag = duet_fluence_to_abmag(0.01*funit, 1)
    >>> np.isclose(abmag.value,  18.54584301)
    True

    """
    from astroduet.config import Telescope
    if duet is None:
        duet = Telescope()

    band = getattr(duet, f'band{duet_no}')
    spec = [1] * u.ph / (u.s * u.cm**2 * u.AA)
    wave = [band['eff_wave'].to(u.AA).value] * u.AA
    if bandpass is None:
        bandpass = band['eff_width'].to(u.AA)
    scale = (duet.apply_filters(wave, spec, duet_no)).value[0]

    fluence_corr = fluence / scale

    ABmag = (fluence_corr / bandpass).to(u.ABmag,
                                         equivalencies=u.spectral_density(
                                             band['eff_wave'].to(u.AA)))

    return ABmag
Example #2
0
def duet_abmag_to_fluence(ABmag, duet_no, duet=None, bandpass=None):
    """
    Convert AB magnitude for a source into the number of source counts.


    Parameters
    ----------
    ABmag: float
        AB magnitude in the bandpass that you're using

    duet_no: int, 1 or 2
        DUET channel

    Other parameters
    ----------------
    duet : `astroduet.config.Telescope` object
        if None, allocate a new Telescope object

    bandpass: array
        DUET bandpass you're using

    Returns
    -------
    Fluence in the band (ph / cm2 / sec)


    Example
    -------
    >>> fluence = duet_abmag_to_fluence(20*u.ABmag, 1)
    >>> np.isclose(fluence.value, 0.00262022)
    True

    """
    import numpy as np
    from astroduet.config import Telescope
    if duet is None:
        duet = Telescope()

    band = getattr(duet, f'band{duet_no}')
    spec = [1] * u.ph / (u.s * u.cm**2 * u.AA)
    wave = [band['eff_wave'].to(u.AA).value] * u.AA
    if bandpass is None:
        bandpass = band['eff_width'].to(u.AA)
    scale = (duet.apply_filters(wave, spec, duet_no)).value[0]

    funit = u.ph / u.cm**2 / u.s / u.AA  # Spectral radiances per Hz or per angstrom

    fluence = bandpass * ABmag.to(
        funit, equivalencies=u.spectral_density(band['eff_wave'].to(u.AA)))

    return fluence * scale
Example #3
0
def panstarrs_to_duet(panmags, duet=None):
    """
    Converts GALEX FUV and NUV ABmags into DUET 1 and DUET 2 ABmags, assuming flat Fnu


    Parameters
    ----------
    panmags: array
        PanSTARRS AB magnitudes and errors, input as [[g, g_err, r, r_err, i, i_err, z, z_err, y, y_err],[]] without units

    duet: Telescope instance

    Returns
    -------
    duetfluences: Array with same shape as galmags, with panstarrs g, panstarrs r, DUET 1 and DUET 2 fluences.
    badstars: number of stars that were not fitted because they had only two or less good magnitudes
    badfits: number of stars where the fit failed due to a runtime error or value error after trying all initial guesses
    
    Example
    -------
    >>> from astroduet.config import Telescope
    >>> duet = Telescope()
    >>> star = np.array([[11.,0.1,10.3,0.1,10,0.1,10,0.1,10,0.1]])
    >>> duetfluences, badstars, badfits = panstarrs_to_duet(star,duet=duet)
    >>> np.isclose(duetfluences['d1_fluence'][0],0.041547)
    True

    """

    from astropy.modeling.blackbody import FLAM
    from scipy.optimize import curve_fit
    from astroduet.bbmag import bbfunc, bb_abmag_fluence
    from astropy.table import Table

    if duet is None:
        from astroduet.config import Telescope
        duet = Telescope()

    fluxunit = u.erg / u.s / u.cm**2
    # Central wavelengths of PanSTARRS bands:
    pswav = np.array([486.6, 621.5, 754.5, 867.9, 963.3]) * u.nm

    duetfluences = Table(np.zeros(4),
                         names=('ps_g', 'ps_r', 'd1_fluence', 'd2_fluence'))
    badstars = 0
    badfits = 0

    # Loop over stars:
    for i, star in enumerate(panmags):
        # Replace bad values with nan:
        star[star == -999.] = np.nan
        # Order and convert to magnitudes
        mags = star[::2] * u.ABmag
        magerrs = star[1::2]
        # Find valid values:
        valid = ~(np.isnan(mags))

        # Convert to flux densities
        fden = mags[valid].to(FLAM,
                              equivalencies=u.spectral_density(pswav[valid]))
        snrs = 1. / (10.**(magerrs[valid] / 2.5) - 1.)
        # Set snr to 10 for nan errors:
        snrs[np.isnan(snrs)] = 10
        fden_err = fden / snrs

        # Filter for stars with only one good data point:
        if len(fden) > 2:

            # Fit blackbody:
            try:
                # Starting value for blackbody fit:
                p0 = [5000, 1.E-8]
                coeff, var_matrix = curve_fit(bbfunc,
                                              pswav[valid].value,
                                              fden.value,
                                              p0=p0,
                                              sigma=fden_err.value,
                                              absolute_sigma=True)
            except RuntimeError:
                badfits += 1
                continue
            except ValueError:
                try:
                    p0 = [10000, 1.E-8]
                    coeff, var_matrix = curve_fit(bbfunc,
                                                  pswav[valid].value,
                                                  fden.value,
                                                  p0=p0,
                                                  sigma=fden_err.value,
                                                  absolute_sigma=True)
                except RuntimeError:
                    badfits += 1
                    continue
                except ValueError:
                    try:
                        p0 = [5000, 1.E-9]
                        coeff, var_matrix = curve_fit(bbfunc,
                                                      pswav[valid].value,
                                                      fden.value,
                                                      p0=p0,
                                                      sigma=fden_err.value,
                                                      absolute_sigma=True)
                    except RuntimeError:
                        badfits += 1
                        continue
                    except ValueError:
                        try:
                            p0 = [10000, 1.E-9]
                            coeff, var_matrix = curve_fit(bbfunc,
                                                          pswav[valid].value,
                                                          fden.value,
                                                          p0=p0,
                                                          sigma=fden_err.value,
                                                          absolute_sigma=True)
                        except RuntimeError:
                            badfits += 1
                            continue
                        except ValueError:
                            try:
                                p0 = [5000, 1.E-10]
                                coeff, var_matrix = curve_fit(
                                    bbfunc,
                                    pswav[valid].value,
                                    fden.value,
                                    p0=p0,
                                    sigma=fden_err.value,
                                    absolute_sigma=True)
                            except RuntimeError:
                                badfits += 1
                                continue
                            except ValueError:
                                try:
                                    p0 = [10000, 1.E-10]
                                    coeff, var_matrix = curve_fit(
                                        bbfunc,
                                        pswav[valid].value,
                                        fden.value,
                                        p0=p0,
                                        sigma=fden_err.value,
                                        absolute_sigma=True)
                                except RuntimeError:
                                    badfits += 1
                                    continue
                                except ValueError:
                                    badfits += 1

            # Get DUET fluences:
            duetfluence = bb_abmag_fluence(duet=duet,
                                           bbtemp=coeff[0] * u.K,
                                           bolflux=coeff[1] * fluxunit)
            duetfluences.add_row(
                [mags[0], mags[1], duetfluence[0], duetfluence[1]])

        else:
            badstars += 1

    duetfluences.remove_row(0)
    duetfluences['d1_fluence'].unit = u.ph / u.s / u.cm**2
    duetfluences['d2_fluence'].unit = u.ph / u.s / u.cm**2

    return duetfluences, badstars, badfits
Example #4
0
def test_run_through_Telescope_methods():
    duet = Telescope()
    duet_offax = Telescope(config='reduced_baseline')
    duet.info()
    duet.update_bandpass()
    duet.update_psf()
    # duet.update_psf_vals()
    duet.calc_radial_profile()
    duet.calc_psf_fwhm()
    duet.update_effarea()
    duet.fluence_to_rate(1 * u.ph / u.s / u.cm**2)
    duet.psf_model()
    duet.compute_psf_norms()
Example #5
0
def galex_to_duet(galmags, duet=None):
    """
    Converts GALEX FUV and NUV ABmags into DUET 1 and DUET 2 ABmags, assuming flat Fnu


    Parameters
    ----------
    galmags: array
        GALEX AB magnitudes, either as [[FUV1, ..., FUVN],[NUV1, ..., NUVN]] or as [[FUV1, NUV1],...,[FUVN, NUVN]]
        Code assumes the first format if len(galmags) = 2

    duet: Telescope instance

    Returns
    -------
    duetmags: Array with same shape as galmags, with DUET 1 and DUET 2 ABmags.

    Example
    -------
    >>> from astroduet.config import Telescope
    >>> duet = Telescope()
    >>> galmags = [20,20]
    >>> duetmags = galex_to_duet(galmags, duet)
    >>> np.allclose(duetmags, [20,20])
    True

    """

    from astropy.modeling.blackbody import FNU

    if duet is None:
        from astroduet.config import Telescope
        duet = Telescope()

    galex_fuv_lef = 151.6 * u.nm
    galex_nuv_lef = 226.7 * u.nm

    duet_1_lef = duet.band1['eff_wave']
    duet_2_lef = duet.band2['eff_wave']

    galex_fuv_nef = galex_fuv_lef.to(u.Hz, u.spectral())
    galex_nuv_nef = galex_nuv_lef.to(u.Hz, u.spectral())

    duet_1_nef = duet_1_lef.to(u.Hz, u.spectral())
    duet_2_nef = duet_2_lef.to(u.Hz, u.spectral())

    # Sort input array into FUV and NUV magnitudes
    if len(galmags) == 2:
        fuv_mag = galmags[0] * u.ABmag
        nuv_mag = galmags[1] * u.ABmag
    else:
        fuv_mag = galmags[:, 0] * u.ABmag
        nuv_mag = galmags[:, 1] * u.ABmag

    # Convert GALEX magnitudes to flux densities
    fuv_fnu = fuv_mag.to(FNU, u.spectral_density(galex_fuv_nef))
    nuv_fnu = nuv_mag.to(FNU, u.spectral_density(galex_nuv_nef))

    # Extrapolate to DUET bands assuming linear Fnu/nu
    delta_fnu = (nuv_fnu - fuv_fnu) / (galex_nuv_nef - galex_fuv_nef)

    d1_fnu = fuv_fnu + delta_fnu * (duet_1_nef - galex_fuv_nef)
    d2_fnu = fuv_fnu + delta_fnu * (duet_2_nef - galex_fuv_nef)

    # Convert back to magnitudes
    d1_mag = d1_fnu.to(u.ABmag, u.spectral_density(duet_1_nef))
    d2_mag = d2_fnu.to(u.ABmag, u.spectral_density(duet_2_nef))

    # Construct output array
    if len(galmags) == 2:
        duetmags = np.array([d1_mag.value, d2_mag.value])
    else:
        duetmags = np.array([d1_mag.value, d2_mag.value]).transpose()

    return duetmags
Example #6
0
def convert_model(filename, name='NoName', duet=None):
    '''
    Reads in the EMGW shock breakout models, converts them to DUET fluences, and
    writes out the resulting models to FITS files.

    Parameters
    ----------

    filename : string
        Path to GRB shock file.

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

    name : string
        name to use for the model. Default is 'NoName'

    '''

    if duet is None:
        duet = Telescope()

    bandone = duet.bandpass1
    bandtwo = duet.bandpass2
    dist0 = 10*u.pc

    shock_data = np.loadtxt(filename)

    time = (shock_data[:,0]*u.d).to(u.s)
    temps = shock_data[:,2]
    bolflux = 10**shock_data[:,1]

    # Set up outputs
    shock_lc = Table([time,
            np.zeros(len(time))*u.ABmag,
            np.zeros(len(time))*u.ABmag,
            np.zeros(len(time))*u.ph/(u.s*u.cm**2),
            np.zeros(len(time))*u.ph/(u.s*u.cm**2)],
               names=('time', 'mag_D1', 'mag_D2', 'fluence_D1', 'fluence_D2'),
               meta={'name': name + ' at 10 pc',
                      'dist0_pc' : '{}'.format(dist0.to(u.pc).value)})
    N = len(temps)
    for k, t, bf in tqdm(list(zip(np.arange(N), temps, bolflux))):
        t *= u.K
        bf *= (u.erg/u.s) /(4 * np.pi * dist0**2)

        band1_mag, band2_mag = bb_abmag(bbtemp=t, bolflux = bf,
                        bandone=bandone, bandtwo=bandtwo, val=True)

        band1_fluence, band2_fluence = bb_abmag_fluence(bbtemp=t,
            bolflux=bf)

        shock_lc[k]['mag_D1'] = band1_mag
        shock_lc[k]['mag_D2'] = band2_mag
        shock_lc[k]['fluence_D1'] = band1_fluence.value
        shock_lc[k]['fluence_D2'] = band2_fluence.value

    shock_lc['mag_D1'].unit = None
    shock_lc['mag_D2'].unit = None

    return shock_lc
Example #7
0
def bb_abmag_fluence(val=False, duet=None, **kwargs):
    """
    Take a blackbody with a certain temperature and convert to photon rate in a band.

    Other Parameters
    ----------------

    val : boolean
        Retrurns AB mags without units (False, default) or with Astropy units

    duet: ``astroduet.conifg.Telescope() object``
        If you've already instanced a duet telecope object, feed it in here.
        Currently allows the use of the default bandpasses.

    umag : float
        Must have astropy AB units
        Apparent U-band AB mag. Only used if other values not provided?

    siwftmag : float
        Must have astropy AB units. Apparent Swift magnitude (default is 22*u.ABmag)

    ref : string
        Band to use for reference magnitude; options are 'u', 'swift' ('swift')

    bbtemp : float
        Blackbody temperature to use (20000*ur.K)

    dist : float
        Distance of the source. swiftmags are assumed to be given at a reference
        distance of 10 pc (I think?)

    bolflux : float
        Bolometric flux; if not 1, refmag and distance are ignored. Should have
        units like (1*ur.erg/ur.cm**2/ur.s)

    diag : boolean
        SHow diagnostic inforamtion

    Returns
    -------
    ABmag1, ABmag2





    """

    import astropy.units as ur
    import astropy.constants as cr
    from astropy.modeling import models
    from astropy.modeling.blackbody import FLAM
    import numpy as np
    from astroduet.config import Telescope

    if duet is None:
        duet = Telescope()

    bbtemp = kwargs.pop('bbtemp', 20000. * ur.K)
    umag = kwargs.pop('umag', 22 * ur.ABmag)
    swiftmag = kwargs.pop('swiftmag', 22 * ur.ABmag)
    dist = kwargs.pop('dist', 10 * ur.pc)
    ref = kwargs.pop('ref', 'swift')
    diag = kwargs.pop('diag', False)
    dist0 = 10 * ur.pc
    bolflux = kwargs.pop('bolflux', 1. * ur.erg / (ur.cm**2 * ur.s))

    bandu = [340, 380] * ur.nm  # For comparison purposes
    bandsw = [
        172.53, 233.57
    ] * ur.nm  # Swift UVW2 effective band (lambda_eff +/- 0.5 width_eff)

    wav = np.arange(1000, 9000) * ur.AA  # Wavelength scale in 1 Angstrom steps
    bb = models.BlackBody1D(
        temperature=bbtemp,
        bolometric_flux=bolflux)  # Load the blackbody model
    flux = bb(wav).to(FLAM, ur.spectral_density(wav))

    # Get Swift reference AB mag
    fluxden_sw = np.mean(flux[(wav >= bandsw[0].to(ur.AA))
                              & (wav <= bandsw[1].to(ur.AA))])
    magsw = fluxden_sw.to(ur.ABmag,
                          equivalencies=ur.spectral_density(np.mean(bandsw)))

    # Conver to flux AB mags across the band.
    flux_ab = flux.to(ur.ABmag, equivalencies=ur.spectral_density(wav))

    # Distance modulus
    distmod = (5 * np.log10(dist / dist0)).value * ur.mag

    # Set up input:
    magoff = swiftmag - magsw

    # Apply the distance modulus and the Swift reference offset
    if (bolflux == 1. * ur.erg / (ur.cm**2 * ur.s)):
        flux_mag = flux_ab + magoff + distmod
    else:
        flux_mag = flux_ab

    # Convert back to flux
    flux_conv = flux_mag.to(FLAM, equivalencies=ur.spectral_density(wav))
    dw = 1 * ur.AA
    ph_energy = (cr.h.cgs * cr.c.cgs / wav.cgs) / ur.ph

    # Convert to photon flux.
    ph_flux = flux_conv * dw / ph_energy

    # Apply filters, QE, etc.
    band1_fluence = duet.apply_filters(wav, ph_flux, diag=diag, **kwargs).sum()
    band2_fluence = duet.apply_filters(wav,
                                       ph_flux,
                                       band=2,
                                       diag=diag,
                                       **kwargs).sum()

    if diag:
        print()
        print('Compute ABmags in TD bands for blackbody')
        print('Blackbody temperature: {}'.format(bbtemp))
        print('Reference UVW2-band magnitude: {}'.format(swiftmag))
        print('Distance: {} (Reference distance is 10 pc)'.format(dist))
        print()
        print('Flux density Swift: {}'.format(fluxden_sw))
        print('Distance modulus: {}'.format(distmod))
        print('Raw ABmag Swift: {}'.format(magsw))
        print('Offset from Swift band: {}'.format(magoff))
        print('Fluence band one: {}'.format(band1_fluence))
        print('Fluence band two: {}'.format(band2_fluence))
        print('')

    if val:
        return band1_fluence.value, band2_fluence.value
    else:
        return band1_fluence, band2_fluence
Example #8
0
def sim_galaxy(patch_size,
               pixel_size,
               gal_type=None,
               gal_params=None,
               duet=None,
               band=None,
               duet_no=None):
    '''
        Return 2D array of a Sersic profile to simulate a galaxy

        Required inputs:
        patch_size = Axis sizes of returned galaxy patch in pixels (15,15)
        pixel_size = Angular size of pixel (6 * ur.arcsec)

        Optional inputs:
        gal_type = String that loads a pre-built 'average' galaxy or allows custom definition
        gal_params = Dictionary of parameters for Sersic model: ...
        duet = Telescope instance
        band = duet.bandpass (defaults to DUET1; deprecated)
        duet_no = integer (1 or 2) for DUET bandpass
    '''
    from astropy.modeling.models import Sersic2D
    from astroduet.utils import duet_abmag_to_fluence, duet_no_from_band

    if duet is None:
        duet = Telescope()
    if band is None:
        band = duet.bandpass1
    if duet_no is None:
        duet_no = duet_no_from_band(band)

    x = np.linspace(-(patch_size[0] // 2), patch_size[0] // 2, patch_size[0])
    y = np.linspace(-(patch_size[1] // 2), patch_size[1] // 2, patch_size[1])
    x, y = np.meshgrid(x, y)

    # Takes either a keyword or Sersic profile parameters
    # Typical galaxy parameters based on Bai et al. (2013)
    # Hardcoded for now, to-do: take distance as an input
    if gal_type == 'spiral':
        # A typical spiral galaxy at 100 Mpc
        surface_mag = 26.2 * u.ABmag  # surface brightness (per arcsec**2)
        surface_rate = duet.fluence_to_rate(
            duet_abmag_to_fluence(surface_mag, duet_no,
                                  duet=duet))  # surface count rate at r_eff
        amplitude = surface_rate * pixel_size.value**2  # surface brightness (per pixel)
        r_eff = 16.5 / pixel_size.value
        n = 1
        theta = 0
        ellip = 0.5
        x_0, y_0 = r_eff, 0
    elif gal_type == 'elliptical':
        # A typical elliptical galaxy at 100 Mpc
        surface_mag = 25.0 * u.ABmag
        surface_rate = duet.fluence_to_rate(
            duet_abmag_to_fluence(surface_mag, duet_no,
                                  duet=duet))  # surface count rate at r_eff
        amplitude = surface_rate * pixel_size.value**2  # surface brightness (per pixel)
        r_eff = 12.5 / pixel_size.value
        n = 4
        theta = 0
        ellip = 0.5
        x_0, y_0 = r_eff, 0
    elif gal_type == 'dwarf':
        # A typical dwarf galaxy at 10 Mpc
        surface_mag = 25.8 * u.ABmag
        surface_rate = duet.fluence_to_rate(
            duet_abmag_to_fluence(surface_mag, duet_no,
                                  duet=duet))  # surface count rate at r_eff
        amplitude = surface_rate * pixel_size.value**2  # surface brightness (per pixel)
        r_eff = 70 / pixel_size
        r_eff = r_eff.value
        n = 4
        theta = 0
        ellip = 0.5
        x_0, y_0 = r_eff, 0
    elif (gal_type == 'custom') | (gal_type == None):
        # Get args from gal_params, default to spiral values
        surface_mag = gal_params.get('magnitude', 26) * u.ABmag
        surface_rate = duet.fluence_to_rate(
            duet_abmag_to_fluence(surface_mag, duet_no,
                                  duet=duet))  # surface count rate at r_eff
        amplitude = surface_rate * pixel_size.value**2  # surface brightness (per pixel)
        r_eff = gal_params.get('r_eff', 16.5 / pixel_size.value)
        n = gal_params.get('n', 1)
        theta = gal_params.get('theta', 0)
        ellip = gal_params.get('ellip', 0.5)
        x_0 = gal_params.get('x_0', 16.5 / pixel_size.value)
        y_0 = gal_params.get('y_0', 0)

    mod = Sersic2D(amplitude=amplitude,
                   r_eff=r_eff,
                   n=n,
                   x_0=x_0,
                   y_0=y_0,
                   ellip=ellip,
                   theta=theta)
    gal = mod(x, y)

    return gal
Example #9
0
def convert_sn_model(label, name='NoName', duet=None):
    '''
    Reads in the SNe models, converts them to DUET fluences, and
    writes out the resulting models to FITS files.

    Parameters
    ----------

    filename : string
        Path to SN shock file.

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

    name : string
        name to use for the model. Default is 'NoName'

    '''
    if duet is None:
        duet = Telescope()

    bandone = duet.bandpass1
    bandtwo = duet.bandpass2
    dist0 = 10*u.pc

    temptable = \
        Table.read(f'{label}_teff.txt', format='ascii', names=['time', 'T'])
    radiustable = \
        Table.read(f'{label}_radius.txt', format='ascii', names=['time', 'R'])
    table = join(temptable, radiustable)
    N = len(table['time'])
    time = table['time'] * u.s

    shock_lc = Table([time,
            np.zeros(len(time))*u.ABmag,
            np.zeros(len(time))*u.ABmag,
            np.zeros(len(time))*u.ph/(u.s*u.cm**2),
            np.zeros(len(time))*u.ph/(u.s*u.cm**2)],
               names=('time', 'mag_D1', 'mag_D2', 'fluence_D1', 'fluence_D2'),
               meta={'name': name + ' at 10 pc',
                      'dist0_pc' : '{}'.format(dist0.to(u.pc).value)})

    bolflux = (table['T'] * u.K) ** 4 * const.sigma_sb.cgs * (
                (table['R'] * u.cm) / dist0.to(u.cm)) ** 2

    temps = table['T'] * u.K

    for k, t, bf in tqdm(list(zip(np.arange(N), temps, bolflux))):
        band1_mag, band2_mag = bb_abmag(bbtemp=t, bolflux = bf,
                        bandone=bandone, bandtwo=bandtwo, val=True)

        band1_fluence, band2_fluence = bb_abmag_fluence(bbtemp=t,
            bolflux=bf)

        shock_lc[k]['mag_D1'] = band1_mag
        shock_lc[k]['mag_D2'] = band2_mag
        shock_lc[k]['fluence_D1'] = band1_fluence.value
        shock_lc[k]['fluence_D2'] = band2_fluence.value

    shock_lc['mag_D1'].unit = None
    shock_lc['mag_D2'].unit = None
    return shock_lc
Example #10
0
def run_daophot(image,
                threshold,
                star_tbl,
                niters=1,
                snr_lim=5,
                duet=None,
                diag=False):
    '''
        Given an image and a PSF, go run DAOPhot PSF-fitting algorithm
    '''
    from photutils.psf import DAOPhotPSFPhotometry, IntegratedGaussianPRF

    if duet is None:
        duet = Telescope()

    fwhm = (duet.psf_fwhm / duet.pixel).to('').value

    # Fix star table columns
    star_tbl['x_0'] = star_tbl['x']
    star_tbl['y_0'] = star_tbl['y']

    # Define a fittable PSF model
    sigma = fwhm / (2. * np.sqrt(2 * np.log(2)))
    # Simple Gaussian model to fit
    #psf_model = IntegratedGaussianPRF(sigma=sigma)
    #flux_norm = 1

    # Use DUET-like PSF
    #oversample = 2 # Needs to be oversampled but only minimally
    #duet_psf_os = duet.psf_model(pixel_size=duet.pixel/oversample, x_size=12, y_size=12) # Even numbers work better
    #psf_model = EPSFModel(duet_psf_os.array,oversampling=oversample)
    #flux_norm = 1/oversample**2 # A quirk of constructing an oversampled ePSF using photutils
    psf_model = duet.epsf_model

    # Temporarily turn off Astropy warnings
    import warnings
    from astropy.utils.exceptions import AstropyWarning
    warnings.simplefilter('ignore', category=AstropyWarning)

    ## FROM HERE ON NO UNITS ###########
    # Initialise a Photometry object
    # This object loops find, fit and subtract
    threshold = threshold.to(image.unit)
    photometry = DAOPhotPSFPhotometry(fwhm,
                                      threshold.value,
                                      fwhm,
                                      psf_model, (5, 5),
                                      niters=niters,
                                      sigma_radius=5,
                                      aperture_radius=fwhm)

    # Problem with _recursive_lookup while fitting (needs latest version of astropy fix to modeling/utils.py)
    result = photometry(image=image.value, init_guesses=star_tbl)
    residual_image = photometry.get_residual_image()

    # Filter results to only keep those with S/N greater than snr_lim (default is 5)
    result_sig = result[np.abs(result['flux_fit'] /
                               result['flux_unc']) >= snr_lim]

    if diag:
        print("PSF-fitting complete")

    # Turn warnings back on again
    warnings.simplefilter('default')
    ## FROM HERE ON YES UNITS ###########
    result_sig['flux_fit'] = result_sig['flux_fit'] * image.unit
    result_sig['flux_unc'] = result_sig['flux_unc'] * image.unit

    return result_sig, residual_image * image.unit
Example #11
0
def construct_image(frame,
                    exposure,
                    duet=None,
                    band=None,
                    gal_type=None,
                    gal_params=None,
                    source=None,
                    source_loc=None,
                    sky_rate=None,
                    n_exp=1,
                    duet_no=None):
    """Construct a simualted image with an optional background galaxy and source.

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

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

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

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

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

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

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

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

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

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

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

    Returns
    -------

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

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

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

    read_noise = duet.read_noise

    oversample = 6
    pixel_size_init = duet.pixel / oversample

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

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

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

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

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

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

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

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

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

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

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

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

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

        # Add to the co-add
        im_final += im_noise

    # Return image
    return im_final * im_counts.unit
Example #12
0
def filter_parameters(duet=None, *args, **kwargs):
    """
    Construct the effective central wavelength and the effective bandpass
    for the filters.

    Parameters
    ----------


    Other parameters
    ----------------
    vega : conditional, default False
        Use the Vega spetrum (9.8e3 K blackbody) to compute values. Otherwise computed
        "flat" values if quoting AB mags.

    diag : conditional, default False
        Show the diagnostic info on the parameters.

    Examples
    --------
    >>> band1, band2 = filter_parameters()
    >>> allclose(band1['eff_wave'].value, 198.63858525)
    True

    """
    from astroduet.config import Telescope

    if duet is None:
        duet = Telescope()

    from astropy.modeling import models
    from astropy.modeling.blackbody import FNU, FLAM
    from astropy import units as u
    import numpy as np

    vega = kwargs.pop('vega', False)
    diag = kwargs.pop('diag', False)

    wave = np.arange(1000, 10000) * u.AA
    if vega:
        temp = 9.8e3 * u.K
        bb = models.BlackBody1D(temperature=temp)
        flux = bb(wave).to(FLAM, u.spectral_density(wave))
    else:
        flat_model = np.zeros_like(wave.value)
        flat_model += 1
        flat_model *= FNU
        flux = flat_model.to(FLAM, u.spectral_density(wave))

    band1 = duet.apply_filters(wave, flux, band=1, **kwargs)
    band2 = duet.apply_filters(wave, flux, band=2, **kwargs)

    λ_eff1 = ((band1 * wave).sum() / (band1.sum())).to(u.nm)
    λ_eff2 = ((band2 * wave).sum() / (band2.sum())).to(u.nm)

    dλ = wave[1] - wave[0]
    t1 = band1 / flux
    t2 = band2 / flux

    w1 = (dλ * t1.sum() / t1.max()).to(u.nm)
    w2 = (dλ * t2.sum() / t2.max()).to(u.nm)

    band1 = {'eff_wave': λ_eff1, 'eff_width': w1}
    band2 = {'eff_wave': λ_eff2, 'eff_width': w2}

    if diag:
        print('Band1: {0:0.2f} λ_eff, {1:0.2f} W_eff'.format(λ_eff1, w1))
        print('Band2: {0:0.2f} λ_eff, {1:0.2f} W_eff'.format(λ_eff2, w2))

    return band1, band2
Example #13
0
def imsim_srcdetect_combined(run='050719',
                             gal='spiral',
                             zodi='low',
                             nmags=71,
                             sfb=[20, 30],
                             stack=1):
    """
    Run background estimation, image differencing and source detection on stacked simulated images

    Assumes that the script is run in the directory that contains the run_... directory tree

    Parameters
    ----------
    run: string (date, as in '050719')
        To track runs

    zodi: 'low', 'med' or 'high', default is low

    gal: 'spiral', 'elliptical', 'dwarf', or 'none'

    sfb: [sfb_low, sfb_high], default is [20,30]
        List of lowest and highest surface brightness that have been simulated

    nmags: float, default is 71
        Number of source magnitudes used in image simulations

    stack: int, default is 1
        Number of stacked exposures

    Returns
    -------
    run_gal_zodi_band.fits: fits table with source detection results
    """
    # Get telescope configuration from teldef file:
    with open('run_' + run + '/teldef') as origin:
        for line in origin:
            if 'DUET Telescope State' in line:
                tel = line.split(':')[1].strip('\n').strip()

    # Initialize parameters
    duet = Telescope(config=tel)

    # Set up path
    path1 = 'run_' + run + '/gal_' + gal + '/zodi_' + zodi + '/duet1/'
    path2 = 'run_' + run + '/gal_' + gal + '/zodi_' + zodi + '/duet2/'

    # Make galaxy surface brightness array
    if gal != 'none':
        sfb_arr = np.arange(sfb[0], sfb[1] + 1.).astype(str)

    # Make source magnitude array
    src_arr = np.linspace(20.5 - 0.5 * (nmags - 1) * 0.1,
                          20.5 + 0.5 * (nmags - 1) * 0.1,
                          num=nmags,
                          endpoint=True)  # Currently in steps of 0.1 mag

    # Set up results table
    # columns: galaxy mag, source input mag, source input count rate, distance from galaxy center, reference depth, source detected True/False,
    # if True: retrieved count rate, count rate error; number of false positives
    tab = Table(np.zeros(9),
                names=('galmag', 'srcmag', 'src-ctrate', 'dist', 'ref_depth',
                       'detected', 'ctrate', 'ctrate_err', 'false-pos'),
                dtype=('f8', 'f8', 'f8', 'f8', 'i8', 'b', 'f8', 'f8', 'i8'),
                meta={'name': gal + ' - ' + zodi + 'zodi - combined'})
    print('Finding sources...')
    if gal == 'none':
        reffile1 = run + '_duet1_zodi-' + zodi + '_reference.fits'
        hdu_ref1 = fits.open(path1 + reffile1)
        reffile2 = run + '_duet2_zodi-' + zodi + '_reference.fits'
        hdu_ref2 = fits.open(path2 + reffile2)

        for srcmag in src_arr:
            imfile1 = run + '_duet1_zodi-' + zodi + '_stack-' + str(
                stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits'
            hdu_im1 = fits.open(path1 + imfile1)
            imfile2 = run + '_duet2_zodi-' + zodi + '_stack-' + str(
                stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits'
            hdu_im2 = fits.open(path2 + imfile2)

            # Get input countrate
            src_ctrate1 = duet.fluence_to_rate(
                duet_abmag_to_fluence(srcmag * u.ABmag, 1, duet=duet))
            src_ctrate2 = duet.fluence_to_rate(
                duet_abmag_to_fluence(srcmag * u.ABmag, 2, duet=duet))
            src_ctrate_comb = src_ctrate1 + src_ctrate2

            # Run source detection for this set of HDUs:
            tab = run_srcdetect_combined(hdu_ref1=hdu_ref1,
                                         hdu_ref2=hdu_ref2,
                                         hdu_im1=hdu_im1,
                                         hdu_im2=hdu_im2,
                                         tab=tab,
                                         duet=duet,
                                         sfb=np.nan,
                                         srcmag=srcmag,
                                         src_ctrate=src_ctrate_comb)
            hdu_im1.close()
            hdu_im2.close()
        hdu_ref1.close()
        hdu_ref2.close()

    else:
        for sfb in sfb_arr:
            print('SFB: ' + sfb)
            reffile1 = run + '_duet1_' + gal + '_' + sfb + '_zodi-' + zodi + '_reference.fits'
            hdu_ref1 = fits.open(path1 + reffile1)
            reffile2 = run + '_duet2_' + gal + '_' + sfb + '_zodi-' + zodi + '_reference.fits'
            hdu_ref2 = fits.open(path2 + reffile2)

            for srcmag in src_arr:
                imfile1 = run + '_duet1_' + gal + '_' + sfb + '_zodi-' + zodi + '_stack-' + str(
                    stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits'
                hdu_im1 = fits.open(path1 + imfile1)
                imfile2 = run + '_duet2_' + gal + '_' + sfb + '_zodi-' + zodi + '_stack-' + str(
                    stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits'
                hdu_im2 = fits.open(path2 + imfile2)
                # Get input countrate
                src_ctrate1 = duet.fluence_to_rate(
                    duet_abmag_to_fluence(srcmag * u.ABmag, 1, duet=duet))
                src_ctrate2 = duet.fluence_to_rate(
                    duet_abmag_to_fluence(srcmag * u.ABmag, 2, duet=duet))
                src_ctrate_comb = src_ctrate1 + src_ctrate2

                # Run source detection for this set of HDUs:
                tab = run_srcdetect_combined(hdu_ref1=hdu_ref1,
                                             hdu_ref2=hdu_ref2,
                                             hdu_im1=hdu_im1,
                                             hdu_im2=hdu_im2,
                                             tab=tab,
                                             duet=duet,
                                             sfb=float(sfb),
                                             srcmag=srcmag,
                                             src_ctrate=src_ctrate)
                hdu_im1.close()
                hdu_im2.close()
            hdu_ref1.close()
            hdu_ref2.close()
    # Save output table
    print('Writing file')
    tab.remove_row(0)
    tab.write('run' + run + '_gal-' + gal + '_zodi-' + zodi + '_stack-' +
              str(stack) + '-combined.fits',
              format='fits',
              overwrite=True)

    print('Done')
Example #14
0
def imsim(**kwargs):
    """
    Simulate images of sources in galaxies with a range of surface brightnesses
    Warning! Takes several hours to run (~6 hours for 10 surface brightness levels, nmag=71 and nsrc=100)

    Will make a directory tree run/galaxy/zodi-level/duet[1,2] in directory where it's run.

    Parameters
    ----------
    tel: 'config' default is 'baseline'
        Sets telescope parameters

    run: string (date, as in '050719', or other identifying string)
        To track runs

    gal: 'spiral', 'elliptical', 'dwarf' or 'none', default is spiral
        Sets Sersic index and size

    zodi: 'low', 'med' or 'high', default is low
        Use the medium zodiacal background rate. Overrides low_zodi.

    sfb: [sfb_low, sfb_high], default is [20,30]
        List of lowest and highest surface brightness to simulate

    nmags: int, default is 71
        Number of source magnitudes to simulate, in 0.1 mag steps around 20.5

    nsrc: int, default is 100
        Number of sources to simulate at each source mag

    stack: int, default is 1
        Depth of science image (number of stacked 300s exposures)

    nref: list, default is [1,3,7,11]
        List of reference image depths

    Returns
    -------
    telescope_band_gal_sfb_zodi_src-mag.fits: fits file with simulated images.
    """

    # Deal with kwargs:
    tel = kwargs.pop('tel', 'baseline')
    gal = kwargs.pop('gal', 'spiral')
    zodi = kwargs.pop('zodi', 'low')
    sfb_lim = kwargs.pop('sfb', [20, 30])
    nmags = kwargs.pop('nmags', 71)
    nsrc = kwargs.pop('nsrc', 100)
    run = kwargs.pop('run')
    stack = kwargs.pop('stack', 1)
    ref_arr = kwargs.pop('nref', [1, 3, 7, 11])

    # set some telescope, instrument parameters
    duet = Telescope(config=tel)

    # Set/make directories:
    path1 = sim_path(run=run, gal=gal, zodi=zodi, band='duet1')
    path2 = sim_path(run=run, gal=gal, zodi=zodi, band='duet2')

    # Write telescope definition file if it doesn't exist yet for this run:
    if not os.path.exists('run_' + run + '/teldef'):
        teldef = duet.info()
        teldef_file = open('run_' + run + '/teldef', 'w+')
        teldef_file.write(teldef)
        teldef_file.close()

    # Define image simulation parameters
    exposure = 300 * u.s
    frame = np.array(
        [30, 30]
    )  # Dimensions of the image I'm simulating in DUET pixels (30x30 ~ 3x3 arcmin)
    oversample = 6  # Hardcoded in construct_image

    # Get backgrounds
    if zodi == 'low':
        [bgd_band1, bgd_band2] = background_pixel_rate(duet,
                                                       low_zodi=True,
                                                       diag=False)
    elif zodi == 'med':
        [bgd_band1, bgd_band2] = background_pixel_rate(duet,
                                                       med_zodi=True,
                                                       diag=False)
    elif zodi == 'high':
        [bgd_band1, bgd_band2] = background_pixel_rate(duet,
                                                       high_zodi=True,
                                                       diag=False)

    # Define galaxy: magnitude is placeholder. Sizes are typical at 100 Mpc
    if gal == 'spiral':
        reff = 16.5 * u.arcsec
        gal_params = {
            'magnitude': 1,
            'r_eff': reff / (duet.pixel / oversample),
            'n': 1,
            'x_0': 0,
            'y_0': 0
        }
    elif gal == 'elliptical':
        reff = 12.5 * u.arcsec
        gal_params = {
            'magnitude': 1,
            'r_eff': reff / (duet.pixel / oversample),
            'n': 4,
            'x_0': 0,
            'y_0': 0
        }
    elif gal == 'dwarf':
        reff = 7 * u.arcsec
        gal_params = {
            'magnitude': 1,
            'r_eff': reff / (duet.pixel / oversample),
            'n': 1,
            'x_0': 0,
            'y_0': 0
        }
    elif gal == 'none':
        gal_params = None

    # Make galaxy surface brightness array (if necessary)
    if gal != 'none':
        sfb_arr = np.arange(sfb_lim[0],
                            sfb_lim[1] + 1.)  # Now in steps of 1 mag

    # Make srcmag array:
    srcmag_arr = np.linspace(20.5 - 0.5 * (nmags - 1) * 0.1,
                             20.5 + 0.5 * (nmags - 1) * 0.1,
                             num=nmags,
                             endpoint=True)  # Currently in steps of 0.1 mag

    # No background galaxy:
    if gal == 'none':
        # Make reference images:
        ref_hdu_1, ref_hdu_2 = run_sim_ref(duet=duet,
                                           bkg1=bgd_band1,
                                           bkg2=bgd_band2,
                                           ref_arr=ref_arr,
                                           gal=False,
                                           exposure=exposure,
                                           frame=frame)
        # Update headers:
        ref_hdu_1 = update_header(ref_hdu_1,
                                  im_type='reference',
                                  zodi=zodi,
                                  gal=gal,
                                  band='DUET1',
                                  nframes=len(ref_arr),
                                  exptime=exposure.value)
        ref_hdu_2 = update_header(ref_hdu_2,
                                  im_type='reference',
                                  zodi=zodi,
                                  gal=gal,
                                  band='DUET2',
                                  nframes=len(ref_arr),
                                  exptime=exposure.value)

        # Write files
        ref_filename1 = run + '_duet1_zodi-' + zodi + '_reference.fits'
        ref_hdu_1.writeto(path1 + '/' + ref_filename1, overwrite=True)

        ref_filename2 = run + '_duet2_zodi-' + zodi + '_reference.fits'
        ref_hdu_2.writeto(path2 + '/' + ref_filename2, overwrite=True)

        # Make source images:
        for srcmag in srcmag_arr:
            src_hdu_1, src_hdu_2 = run_sim(duet=duet,
                                           bkg1=bgd_band1,
                                           bkg2=bgd_band2,
                                           stack=stack,
                                           srcmag=srcmag,
                                           nsrc=nsrc,
                                           gal=False,
                                           exposure=exposure,
                                           frame=frame)
            # Update headers
            src_hdu_1 = update_header(src_hdu_1,
                                      im_type='source',
                                      zodi=zodi,
                                      gal=gal,
                                      band='DUET1',
                                      srcmag=srcmag,
                                      nframes=nsrc,
                                      exptime=exposure.value)
            src_hdu_2 = update_header(src_hdu_2,
                                      im_type='source',
                                      zodi=zodi,
                                      gal=gal,
                                      band='DUET2',
                                      srcmag=srcmag,
                                      nframes=nsrc,
                                      exptime=exposure.value)

            # Write file
            filename1 = run + '_duet1_zodi-' + zodi + '_stack-' + str(
                stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits'
            src_hdu_1.writeto(path1 + '/' + filename1, overwrite=True)

            filename2 = run + '_duet2_zodi-' + zodi + '_stack-' + str(
                stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits'
            src_hdu_2.writeto(path2 + '/' + filename2, overwrite=True)

    # Yes background galaxy:
    else:
        for i, sfb in enumerate(sfb_arr):
            print('Surface brightness level ' + str(i + 1) + ' of ' +
                  str(len(sfb_arr)) + '...')
            # Calculate count rate:
            gal_params['magnitude'] = sfb
            # Make reference images:
            ref_hdu_1, ref_hdu_2 = run_sim_ref(duet=duet,
                                               bkg1=bgd_band1,
                                               bkg2=bgd_band2,
                                               ref_arr=ref_arr,
                                               gal=True,
                                               gal_params=gal_params,
                                               exposure=exposure,
                                               frame=frame)
            # Update headers:
            ref_hdu_1 = update_header(ref_hdu_1,
                                      im_type='reference',
                                      zodi=zodi,
                                      gal=gal,
                                      band='DUET1',
                                      nframes=len(ref_arr),
                                      exptime=exposure.value)
            ref_hdu_2 = update_header(ref_hdu_2,
                                      im_type='reference',
                                      zodi=zodi,
                                      gal=gal,
                                      band='DUET2',
                                      nframes=len(ref_arr),
                                      exptime=exposure.value)
            # Write files:
            ref_filename1 = run + '_duet1_' + gal + '_' + str(
                sfb) + '_zodi-' + zodi + '_reference.fits'
            ref_hdu_1.writeto(path1 + '/' + ref_filename1, overwrite=True)

            ref_filename2 = run + '_duet2_' + gal + '_' + str(
                sfb) + '_zodi-' + zodi + '_reference.fits'
            ref_hdu_2.writeto(path2 + '/' + ref_filename2, overwrite=True)

            # Make source images:
            for srcmag in srcmag_arr:
                src_hdu_1, src_hdu_2 = run_sim(duet=duet,
                                               bkg1=bgd_band1,
                                               bkg2=bgd_band2,
                                               stack=stack,
                                               srcmag=srcmag,
                                               nsrc=nsrc,
                                               gal=True,
                                               gal_params=gal_params,
                                               exposure=exposure,
                                               frame=frame)
                # Update headers:
                src_hdu_1 = update_header(src_hdu_1,
                                          im_type='source',
                                          zodi=zodi,
                                          gal=gal,
                                          band='DUET1',
                                          srcmag=srcmag,
                                          nframes=nsrc,
                                          exptime=exposure.value)
                src_hdu_2 = update_header(src_hdu_2,
                                          im_type='source',
                                          zodi=zodi,
                                          gal=gal,
                                          band='DUET2',
                                          srcmag=srcmag,
                                          nframes=nsrc,
                                          exptime=exposure.value)

                # Write files:
                filename1 = run + '_duet1_' + gal + '_' + str(
                    sfb) + '_zodi-' + zodi + '_stack-' + str(
                        stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits'
                src_hdu_1.writeto(path1 + '/' + filename1, overwrite=True)

                filename2 = run + '_duet2_' + gal + '_' + str(
                    sfb) + '_zodi-' + zodi + '_stack-' + str(
                        stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits'
                src_hdu_2.writeto(path2 + '/' + filename2, overwrite=True)