def do_primarybeam_correction(pbname, imagename):
    print(' Preparing to apply the primary beam correction...')
    image = fits.open(imagename)[0]
    pb = fits.open(pbname)[0]
    wcs = WCS(pb.header)
    # cutout pb field of view to match image field of view
    x_size = image.header['NAXIS1']
    x_pixel_deg = image.header[
        'CDELT2']  # CDELT1 is negative, so take positive one
    size = (
        x_size * x_pixel_deg * u.degree, x_size * x_pixel_deg * u.degree
    )  # angular size of cutout, using astropy coord. approx 32768*0.6 arcseconds.
    position = SkyCoord(pb.header['CRVAL1'] * u.degree, pb.header['CRVAL2'] *
                        u.degree)  # RA and DEC of beam PB pointing
    print(' Cutting out image FOV from primary beam image...')
    cutout = Cutout2D(pb.data[0, 0, :, :],
                      position=position,
                      size=size,
                      mode='trim',
                      wcs=wcs.celestial,
                      copy=True)
    pb.data = cutout.data  # Put the cutout image in the FITS HDU
    pb.header.update(
        cutout.wcs.to_header())  # Update the FITS header with the cutout WCS
    pb.writeto(pbname[:-5] + '_PBCOR.fits',
               overwrite=True)  # Write the cutout to a new FITS file

    # regrid PB image cutout to match pixel scale of the image FOV
    print(' Regridding image...')
    # get header of image to match PB to
    montage.mGetHdr(imagename, 'hdu_tmp.hdr')
    # regrid pb image (270 pixels) to size of ref image (32k pixels)
    montage.reproject(in_images=pbname[:-5] + '_PBCOR.fits',
                      out_images=pbname[:-5] + '_PBCOR_regrid.fits',
                      header='hdu_tmp.hdr',
                      exact_size=True)
    os.remove('hdu_tmp.hdr')  # get rid of header text file saved to disk

    # do pb correction
    pb = fits.open(pbname[:-5] + '_PBCOR_regrid.fits')[0]
    # fix nans introduced in primary beam by montage at edges
    print(
        ' A small buffer of NaNs is introduced around the image by Montage when regridding to match the size, \n these have been set to the value of their nearest neighbours to maintain the same image dimensions'
    )
    mask = np.isnan(pb.data)
    pb.data[mask] = np.interp(np.flatnonzero(mask), np.flatnonzero(~mask),
                              pb.data[~mask])
    image.data = image.data / pb.data
    image.writeto(imagename[:-5] + '_PBCOR.fits', overwrite=True)
Example #2
0
def feather(lowres, highres, exportpsf, sdfactor, regrid):
    # Import high-res
    original = fits.open(lowres)
    hdu_highres = fits.open(highres)
    if regrid is False:
        nx1_highres = hdu_highres[0].header["NAXIS1"]
        nx2_highres = hdu_highres[0].header["NAXIS2"]
        nx1_original = original[0].header["NAXIS1"]
        nx2_original = original[0].header["NAXIS2"]
        if nx1_highres != nx1_original or nx2_highres != nx2_original:
            print("Images are not the same size, and --regrid was not selected")
        else:
            # Single -> Double, remove redundant axes
            hr = np.squeeze(np.squeeze(hdu_highres[0].data.astype(float)))
            # Replace NaNs with zeros
            replace = np.isnan(hr)
            hr[replace]=0.0

            try:
                bmaj_highres = hdu_highres[0].header["BMAJ"]
            except KeyError:
                print("No valid beam in header of {0}".format(highres))
                sys.exit(1)
            bmin_highres = hdu_highres[0].header["BMIN"]
            bpa_highres = hdu_highres[0].header["BPA"]

        # Montage copies ALL the fits keys across, including the beam values! So we need to replace those with the original values
        # Test whether the file has a beam
            try:
                bmaj_lowres = original[0].header["BMAJ"]
            except KeyError:
               print("No valid beam in header of {0}".format(lowres))
               sys.exit(1)

            # Regrid low-res
            if regrid:
                lowres_rg = lowres.replace(".fits", "_montaged.fits")
                if not os.path.exists(lowres_rg):
                    montage.mGetHdr(highres,"temp.txt")
                    montage.reproject(lowres,lowres_rg,header="temp.txt",exact_size=True)
                else:
                    print("Will not overwrite existing regridded image {0}".format(lowres_rg))
            else:
                print("Not regridding; expecting image co-ordinates to match exactly.")
                lowres_rg = lowres
        # TODO: add a test for image co-ordinate match rather than letting it get to the FT then fail

            hdu_lowres = fits.open(lowres_rg)
            newhdr = hdu_lowres[0].header
            for fitskey in ["BMAJ", "BMIN", "BPA"]:
                newhdr[fitskey] = original[0].header[fitskey]
        #        print fitskey, original[0].header[fitskey]
            try:
                naxis4 = newhdr["NAXIS4"]
            except:
                naxis4 = None
            if naxis4:
                newhdr["NAXIS"] = 4

            hdu_lowres.writeto(lowres_rg, overwrite = True)

            # Import regridded low-res
            hdu_lowres = fits.open(lowres_rg)
            # Single -> Double, remove redundant axes
            lr = np.squeeze(np.squeeze(hdu_lowres[0].data.astype(float)))
            # Replace NaNs with zeros
            replace = np.isnan(lr)
            lr[replace]=0.0

            bmaj_lowres = hdu_lowres[0].header["BMAJ"]
            bmin_lowres = hdu_lowres[0].header["BMIN"]
            bpa_lowres = hdu_lowres[0].header["BPA"]


        # TODO: enable diagnostic plots
        # if plots
        #    py.figure(1)
        #    py.clf()
        #    py.imshow(np.log10(highres))
        #    py.savefig("highres.png")

            hr_fft = fft(hr)
            lr_fft = fft(lr)

            # According to https://casa.nrao.edu/docs/taskref/feather-task.html
            # Scale the low-resolution image by the ratio of the volumes of the two clean beams
            # (high-res / low-res)

            ratio = (sdfactor*bmaj_highres*bmin_highres) / (bmaj_lowres*bmin_lowres)

            #Add to this, the uv-grid of the high-resolution image, scaled by  
            #                   (1-wt) where "wt" is the Fourier transform of the "clean beam"
            #                   defined in the low-resolution image.  

            # Make a model image of low-resolution psf
            xmax = hdu_lowres[0].header["NAXIS1"]
            ymax = hdu_lowres[0].header["NAXIS2"]
            try:
                pix2deg = hdu_lowres[0].header["CDELT2"]
            except KeyError:
                pix2deg = hdu_lowres[0].header["CD2_2"]

            x, y = np.meshgrid(np.linspace(0,xmax,xmax), np.linspace(0,ymax,ymax))

            sigmax = bmaj_lowres / (pix2deg * sig2fwhm)
            sigmay = bmin_lowres / (pix2deg * sig2fwhm)
            mux = xmax/2 + 0.5
            muy = ymax/2 + 0.5

            g = gaussian2d(x, y, mux, muy, sigmax, sigmay, np.deg2rad(bpa_lowres))
            g_fft = fft(g)

            if exportpsf:
                exportfits(g, hdu_highres[0].header, lowres.replace(".fits","_psf.fits"))
                exportfits(np.real(g_fft), hdu_highres[0].header, lowres.replace(".fits","_psf_fft_real.fits"))
                try:
                    exportfits(np.imag(g_fft), hdu_highres[0].header, lowres.replace(".fits","_psf_fft_imag.fits"))
                except:
        # I get some weird errors when I try to export an incredibly tiny imaginary part, but this works:
                    exportfits(np.zeros(g_fft.shape), hdu_highres[0].header, lowres.replace(".fits","_psf_fft_imag.fits"))

            # Add together
            comb_fft = ratio * lr_fft + (1 - (g_fft/np.nanmax(g_fft))) * hr_fft

            # Inverse FFT
            comb = ifft(comb_fft)
            exportfits(comb, hdu_highres[0].header, highres.replace(".fits","+")+lowres)
Example #3
0
    # output files
    out_image_name = datadir + 'images/%s_cutout.fits' % (iauname)
    out_ivar_name = datadir + 'ivar/%s_ivar.fits' % (iauname)
    # tempfiles
    out_image_hdr =  datadir + 'hdr/%s_cutout.hdr' % (iauname)
    out_nsa_diff_name = datadir + 'diff/%s.fits' % (iauname)
    out_nsa_diff_re_name = datadir + 'diffre/%s.fits' % (iauname)
    out_nsa_pimage_re_name = datadir + 'pimagere/%s_nsa_pimage_re.fits' % (iauname)

    log.info("Process %s", iauname)
    if not os.path.isfile(ivar_name): save_ivar(filter, run, camcol, field)

    log.info("Cutout field image: %s", out_image_name)
    montage.mSubimage(field_name, out_image_name, ra, dec, size)
    log.info("Making header file: %s", out_image_hdr)
    hdr = montage.mGetHdr(out_image_name, out_image_hdr)

    # reproject everything to cutout field image
    log.info("Cutout ivar image: %s", out_ivar_name)
    montage.reproject(ivar_name, out_ivar_name, header=out_image_hdr, exact_size=True,
                        silent_cleanup=montage_silent)
    
    log.info("Save parent - child: %s", out_nsa_diff_name)
    hdu_child = fits.open(nsa_image_name)
    hdu_parent = fits.open(nsa_parent_name)
    fits.writeto(out_nsa_diff_name, data=hdu_parent[child_ext*2].data - hdu_child[child_ext].data,
                    header=hdu_child[2].header, clobber=True)        
    log.info("Reprojecting diff: %s", out_nsa_diff_re_name)
    montage.reproject(out_nsa_diff_name, out_nsa_diff_re_name, header=out_image_hdr, exact_size=True,
                        silent_cleanup=montage_silent)
Example #4
0
def reproject_snrs(snrs, clobber):
    padding = 4.0
    #    colors = ["072-080MHz", "080-088MHz", "088-095MHz", "095-103MHz", "103-111MHz", "111-118MHz", "118-126MHz", "126-134MHz", "139-147MHz", "147-154MHz", "154-162MHz", "162-170MHz", "170-177MHz", "177-185MHz", "185-193MHz", "193-200MHz", "200-208MHz", "208-216MHz", "216-223MHz", "223-231MHz"]
    colors = [
        "white", "red", "green", "blue", "072-080MHz", "080-088MHz",
        "088-095MHz", "095-103MHz", "103-111MHz", "111-118MHz", "118-126MHz",
        "126-134MHz", "139-147MHz", "147-154MHz", "154-162MHz", "162-170MHz",
        "170-177MHz", "177-185MHz", "185-193MHz", "193-200MHz", "200-208MHz",
        "208-216MHz", "216-223MHz", "223-231MHz"
    ]
    fitsdir = "/home/tash/data/MWA/GLEAM/GP/"

    for color in colors:
        print "Reprojecting " + color + " image"
        if not os.path.exists(color):
            os.makedirs(color)
        if color != "white":
            if not os.path.exists(color + "/rpj"):
                os.makedirs(color + "/rpj")

#        fitsfile = fitsdir+color+"_MOL.fits"
#
#        hdu = fits.open(fitsfile)
#        w = wcs.WCS(hdu[0].header)
#        try:
#            pix2deg = hdu[0].header["CDELT2"]
#        except KeyError:
#            pix2deg = hdu[0].header["CD2_2"]

# Using the astropy cut-out method

        for snr in snrs:
            print "Reprojecting " + snr.name
            # No point doing the ones not in my search region
            l = snr.loc.galactic.l.value
            if (((l > 180) and (l < 240)) or (l > 300) or (l < 60)):
                name = snr.name + ".fits"
                if clobber or not os.path.exists(color + "/" +
                                                 name) or not os.path.exists(
                                                     color + "/rpj/" + name):
                    # Week4 for GC SNR; Week2 for anticentre SNR
                    if ((l > 180) and (l < 240)):
                        psf = fits.open(fitsdir + "Week2/Week2_" + color +
                                        "_lownoise_comp_psf.fits")
                        hdu = fits.open(fitsdir + "Week2/Week2_" + color +
                                        "_lownoise_ddmod_rescaled.fits")
# HACK
#                        orig_hdu = fits.open(fitsdir+"Week2_"+color+"_lownoise_ddmod_rescaled.fits")
#                        hdu = fits.open("/home/tash/data/MWA/GLEAM/allmosaics/SNR_G189.6+3.3/"+color+"/"+snr.name+".fits")
                    else:
                        psf = fits.open(fitsdir + "Week4/Week4_" + color +
                                        "_lownoise_comp_psf.fits")
                        hdu = fits.open(fitsdir + "Week4/Week4_" + color +
                                        "_lownoise_ddmod_rescaled.fits")
                    try:
                        pix2deg = hdu[0].header["CDELT2"]
                    except KeyError:
                        pix2deg = hdu[0].header["CD2_2"]
                    w = wcs.WCS(hdu[0].header)
                    # Set a minimum cutout size; otherwise I can't properly measure the remnant against the background
                    if snr.min * 60 > 20:
                        framesize = u.Quantity(2 * padding * snr.maj, u.deg)
                    else:
                        framesize = u.Quantity(1, u.deg)
# Set a maximum cutout size to avoid getting weird results for some snr
                    if framesize.value > 4.0:
                        framesize = u.Quantity(4, u.deg)

                    print framesize
                    cutout = Cutout2D(hdu[0].data,
                                      snr.loc.fk5,
                                      framesize,
                                      wcs=w)
                    # Read these from the correct PSF image and then put them in the cutout
                    wpsf = wcs.WCS(psf[0].header)
                    cp = np.squeeze(
                        wpsf.wcs_world2pix(
                            [[snr.loc.fk5.ra.value, snr.loc.fk5.dec.value, 1]],
                            0))
                    xp, yp = int(cp[0]), int(cp[1])
                    bmaj = psf[0].data[0, yp, xp]
                    bmin = psf[0].data[1, yp, xp]
                    bpa = psf[0].data[2, yp, xp]
                    #    beamvolume = (1.1331 * bmaj * bmin) # gaussian beam conversion
                    header_new = cutout.wcs.to_header()
                    # Edit the header so that the CD values are copied from the PC values -- makes it DS9-readable
                    header_new["CD1_1"] = header_new["PC1_1"]
                    header_new["CD2_2"] = header_new["PC2_2"]
                    header_new["BMAJ"] = bmaj
                    header_new["BMIN"] = bmin
                    header_new["BPA"] = bpa
                    #HACK
                    header_new["FREQ"] = hdu[0].header["FREQ"]
                    #                    header_new["FREQ"] = orig_hdu[0].header["FREQ"]
                    new = fits.PrimaryHDU(cutout.data,
                                          header=header_new)  #create new hdu
                    newlist = fits.HDUList([new])  #create new hdulist
                    try:
                        newlist.writeto(color + "/" + name, overwrite=True)
# For some reason I get:
# NameError: global name 'VerifyError' is not defined
                    except:
                        print "Invalid fits keys for {0} at {1}".format(
                            snr.name, color)

                    print snr.name, "here"
                    # Reproject the other colours
                    if color != "white":
                        montage.mGetHdr("white/" + name, "temp.txt")
                        oldfile = color + "/" + name
                        newfile = color + "/rpj/" + name
                        try:
                            montage.reproject(oldfile,
                                              newfile,
                                              header="temp.txt",
                                              exact_size=True)
                            oldhdr = fits.open(oldfile)[0].header
                            newhdu = fits.open(newfile)
                            newhdr = newhdu[0].header
                            # This copies ALL the fits keys across, including the beam values! So we need to replace those with the original values
                            for fitskey in ["BMAJ", "BMIN", "BPA", "FREQ"]:
                                newhdr[fitskey] = oldhdr[fitskey]
                            newhdu.writeto(newfile, overwrite=True)


# For some reason I get:
# NameError: global name 'MontageError' is not defined
#                        except MontageError:
                        except:
                            print "Montage reprojection failed for {0} at {1}".format(
                                snr.name, color)
def convolve_regrid(imagename, ref_imagename):
    # first need to convolve to match low freq resolution
    print(' Convolving image...')
    hdu1400 = fits.open(imagename)[0]
    hdu560 = fits.open(ref_imagename)[0]
    # degrees per pixel
    cdelt2_1400 = hdu1400.header['CDELT2']
    cdelt2_560 = hdu560.header['CDELT2']
    # how many pixels across the fwhm of 1400 MHz beam in 1400 MHz image:
    fwhm1400_pix_in1400 = hdu1400.header['BMAJ'] / cdelt2_1400  # = 2.48
    # how many pixels across the fwhm of 560 MHz beam in 1400 MHz image:
    fwhm560_pix_in1400 = hdu560.header['BMAJ'] / cdelt2_1400  # = 6.21
    # convert fwhm to sigma
    sigma1400_orig = fwhm1400_pix_in1400 / np.sqrt(8 * np.log(2))
    sigma1400_target560 = fwhm560_pix_in1400 / np.sqrt(8 * np.log(2))
    # calculate gaussian kernels (only need the 560 MHz one to convolve with)
    # By default, the Gaussian kernel will go to 8 sigma in each direction. Go much larger to get better sampling (odd number required).
    psf1400_orig = Gaussian2DKernel(sigma1400_orig,
                                    x_size=29,
                                    y_size=29,
                                    mode='oversample',
                                    factor=10)
    psf1400_target560 = Gaussian2DKernel(sigma1400_target560,
                                         x_size=29,
                                         y_size=29,
                                         mode='oversample',
                                         factor=10)

    # work out convolution kernel required to achieve 560 MHz psf in final image
    # 1400 MHz psf convolved with Y = 560 MHz psf
    # convolution in multiplication in frequency space, so:
    ft1400 = fft.fft2(psf1400_orig)
    ft560 = fft.fft2(psf1400_target560)
    ft_kernel = (ft560 / ft1400)
    kernel = fft.ifft2(ft_kernel)
    kernel = fft.fftshift(kernel)  # centre the kernel

    # convolve input beam with kernel to check output beam is correct, and make plot
    outbeam = convolve(
        psf1400_orig, kernel.real,
        boundary='extend')  # normalising kernel is on by default.
    f, (ax1, ax2, ax3, ax4) = plt.subplots(1, 4, figsize=(20, 3))
    #im = plt.imshow()
    im1 = ax1.imshow(psf1400_orig)
    im2 = ax2.imshow(psf1400_target560)
    im3 = ax3.imshow(kernel.real)
    im4 = ax4.imshow(outbeam)
    plt.colorbar(im1, ax=ax1)
    plt.colorbar(im2, ax=ax2)
    plt.colorbar(im3, ax=ax3)
    plt.colorbar(im4, ax=ax4)
    plt.subplots_adjust(wspace=0.3)
    ax1.set_title('1400 MHz PSF')
    ax2.set_title('560 MHz PSF')
    ax3.set_title('Kernel')
    ax4.set_title('1400 convolved with kernel')
    plt.savefig('convolution-kernel-' + imagename[:-5] + '.png',
                bbox_inches='tight')

    # Convolve 1400 MHz image with new kernal to get 560 MHz resolution
    hdu1400_data_convolved = convolve(
        hdu1400.data, kernel.real,
        boundary='extend')  # normalising kernel is on by default.
    # update data and correct for Jy/beam scale change (proportional to change in beam area)
    hdu1400.data = hdu1400_data_convolved * ((hdu560.header['BMAJ']**2) /
                                             (hdu1400.header['BMAJ']**2))

    # save convolved image
    hdu1400.writeto(imagename[:-5] + '_convolved.fits', overwrite=True)
    # use montage to regrid image so they both have same pixel dimensions in prep for making cube
    print(' Regredding image...')
    # get header of 560 MHz image to match to
    montage.mGetHdr(ref_imagename, 'hdu560_tmp.hdr')
    # regrid 1400 MHz cropped image to 560 MHz image ref
    montage.reproject(in_images=imagename[:-5] + '_convolved.fits',
                      out_images=imagename[:-5] + '_convolved_regrid.fits',
                      header='hdu560_tmp.hdr',
                      exact_size=True)
    os.remove('hdu560_tmp.hdr')  # get rid of header text file saved to disk
def freproj3D_EQ_GAL(filedir_in,filedir_out,header_file):

    '''
    Reprojects a three-dimensional FITS image from equatorial to Galactic coordinates.

    Input
    filedir_in   : input file in equatorial coordinates
    filedir_out  : output file in Galactic coordinates
    header_file  : contains the reference header used for reprojection

    Output
    saves the reprojected FITS image to the input path filedir_out
    '''

    # extract data and headers
    data_EQ_3D,header_EQ_3D                                     = fits.getdata(filedir_in,header=True)
    header_EQ_3D_NAXIS1,header_EQ_3D_NAXIS2,header_EQ_3D_NAXIS3 = header_EQ_3D["NAXIS1"],header_EQ_3D["NAXIS2"],header_EQ_3D["NAXIS3"]
    header_EQ_3D_CTYPE1,header_EQ_3D_CTYPE2,header_EQ_3D_CTYPE3 = header_EQ_3D["CTYPE1"],header_EQ_3D["CTYPE2"],header_EQ_3D["CTYPE3"]
    header_EQ_3D_CRPIX1,header_EQ_3D_CRPIX2,header_EQ_3D_CRPIX3 = header_EQ_3D["CRPIX1"],header_EQ_3D["CRPIX2"],header_EQ_3D["CRPIX3"]
    header_EQ_3D_CRVAL1,header_EQ_3D_CRVAL2,header_EQ_3D_CRVAL3 = header_EQ_3D["CRVAL1"],header_EQ_3D["CRVAL2"],header_EQ_3D["CRVAL3"]
    header_EQ_3D_CDELT1,header_EQ_3D_CDELT2,header_EQ_3D_CDELT3 = header_EQ_3D["CDELT1"],header_EQ_3D["CDELT2"],header_EQ_3D["CDELT3"]

    # change WCS from equatorial to Galactic
    header_GAL_3D_CTYPE1,header_GAL_3D_CTYPE2 = ("GLON-TAN","GLAT-TAN")
    header_GAL_3D_CUNIT1,header_GAL_3D_CUNIT2 = ("deg","deg")
    header_GAL_3D_CROTA1,header_GAL_3D_CROTA2 = (0,0)

    ############################## make Galactic footprint larger ###################################
    #header_GAL_3D_NAXIS1,header_GAL_3D_NAXIS2 = (6000,6000)                                      # N1
    #header_GAL_2D_NAXIS1,header_GAL_2D_NAXIS2 = (3000,6500)                                      # N2
    #header_GAL_2D_NAXIS1,header_GAL_2D_NAXIS2 = (4000,7500)                                      # N3
    #################################################################################################

    header_GAL_3D_CRPIX1,header_GAL_3D_CRPIX2 = header_GAL_3D_NAXIS1*0.5,header_GAL_3D_NAXIS2*0.5
    crpix1_GAL_3D,crpix2_GAL_3D               = (int(header_GAL_3D_NAXIS1*0.5),int(header_GAL_3D_NAXIS2*0.5))

    w_EQ_3D                                   = wcs.WCS(fits.open(filedir_in)[0].header)

    crpix1_EQ_2D,crpix2_EQ_2D  = header_EQ_3D_NAXIS1*0.5,header_EQ_3D_NAXIS2*0.5
    crpix1_crpix2_radec_2D     = w_EQ_3D.all_pix2world(crpix1_EQ_2D,crpix2_EQ_2D,0,0)
    crpix1_ra_2D,crpix2_dec_2D = np.float(crpix1_crpix2_radec_2D[0]),np.float(crpix1_crpix2_radec_2D[1])

    # transform center pixel values from (ra,dec) to (l,b)
    coords                                    = SkyCoord(ra=crpix1_ra_2D*u.degree, dec=crpix2_dec_2D*u.degree, frame='fk5')
    header_GAL_3D_CRVAL1,header_GAL_3D_CRVAL2 = (coords.galactic.l.deg,coords.galactic.b.deg)

    # change CDELTs from equatorial to Galactic values
    header_GAL_3D_CDELT1,header_GAL_3D_CDELT2 = (header_EQ_3D_CDELT1,header_EQ_3D_CDELT2)

    # create 3D GAL header
    data_GAL_3D                                                             = np.zeros(shape=data_EQ_3D.shape)
    header_GAL_3D                                                           = fits.PrimaryHDU(data=data_GAL_3D).header
    header_GAL_3D["CTYPE1"],header_GAL_3D["CTYPE2"],header_GAL_3D["CTYPE3"] = header_GAL_3D_CTYPE1,header_GAL_3D_CTYPE2,header_EQ_3D_CTYPE3
    header_GAL_3D["NAXIS1"],header_GAL_3D["NAXIS2"],header_GAL_3D["NAXIS3"] = header_GAL_3D_NAXIS1,header_GAL_3D_NAXIS2,header_EQ_3D_NAXIS3
    header_GAL_3D["CRPIX1"],header_GAL_3D["CRPIX2"],header_GAL_3D["CRPIX3"] = header_GAL_3D_CRPIX1,header_GAL_3D_CRPIX2,header_EQ_3D_CRPIX3
    header_GAL_3D["CRVAL1"],header_GAL_3D["CRVAL2"],header_GAL_3D["CRVAL3"] = header_GAL_3D_CRVAL1,header_GAL_3D_CRVAL2,header_EQ_3D_CRVAL3
    header_GAL_3D["CDELT1"],header_GAL_3D["CDELT2"],header_GAL_3D["CDELT3"] = header_GAL_3D_CDELT1,header_GAL_3D_CDELT2,header_EQ_3D_CDELT3
    header_GAL_3D["CUNIT1"],header_GAL_3D["CUNIT2"]                         = header_GAL_3D_CUNIT1,header_GAL_3D_CUNIT2
    header_GAL_3D["CROTA1"],header_GAL_3D["CROTA2"],header_GAL_3D["CROTA3"] = 0.0,0.0,0.0

    header_file  = "/Users/campbell/Documents/PhD/data/GALFA-HI/N1/v_-10_+10_kms/GAL/header_GAL.fits"
    mheader_file = "/Users/campbell/Documents/PhD/data/GALFA-HI/N1/v_-10_+10_kms/GAL/mheader_GAL.txt"
    fits.writeto(header_file,data_GAL_3D,header_GAL_3D,overwrite=True)
    montage.mGetHdr(header_file,mheader_file)
    os.remove(header_file)
    montage.reproject_cube(filedir_in,filedir_out,header=mheader_file,clobber=True)
def freproj2D_EQ_GAL(filedir_in,filedir_out):

    '''
    Reprojects a two-dimensional FITS image from equatorial to Galactic coordinates using Montage.

    Input
    filedir_in   : input file in equatorial coordinates
    filedir_out  : output file in Galactic coordinates

    Output
    saves the reprojected FITS image to the input path filedir_out
    '''

    # extract data and headers
    data_EQ,header_EQ                   = fits.getdata(filedir_in,header=True)
    w_EQ                                = wcs.WCS(fits.open(filedir_in)[0].header)
    header_EQ_NAXIS1,header_EQ_NAXIS2   = header_EQ["NAXIS1"],header_EQ["NAXIS2"]

    # change WCS from equatorial to Galactic
    header_GAL_CTYPE1,header_GAL_CTYPE2 = ("GLON-TAN","GLAT-TAN")
    header_GAL_CUNIT1,header_GAL_CUNIT2 = ("deg","deg")
    header_GAL_CROTA1,header_GAL_CROTA2 = (0,0)

    ############################## make Galactic footprint larger ##############################
    #header_GAL_NAXIS1,header_GAL_NAXIS2 = (6000,6000)                                       # N1
    #header_GAL_NAXIS1,header_GAL_NAXIS2 = (3000,6500)                                       # N2
    #header_GAL_NAXIS1,header_GAL_NAXIS2 = (4000,7500)                                       # N3
    #header_GAL_NAXIS1,header_GAL_NAXIS2 = (6000,5500)                                       # N4
    #header_GAL_NAXIS1,header_GAL_NAXIS2 = (6000,6500)                                       # S1
    #header_GAL_NAXIS1,header_GAL_NAXIS2 = (8000,4000)                                       # S2
    #header_GAL_NAXIS1,header_GAL_NAXIS2 = (6000,7000)                                       # S3
    #header_GAL_NAXIS1,header_GAL_NAXIS2 = (8000,4000)                                       # S4
    ############################################################################################

    header_GAL_CRPIX1,header_GAL_CRPIX2 = header_GAL_NAXIS1/2.,header_GAL_NAXIS2/2.
    crpix1_GAL,crpix2_GAL               = (header_GAL_NAXIS1*0.5,header_GAL_NAXIS2*0.5)

    crpix1_EQ,crpix2_EQ                 = header_EQ_NAXIS1/2.,header_EQ_NAXIS2/2.
    crpix1_crpix2_radec                 = w_EQ.all_pix2world(crpix1_EQ,crpix2_EQ,0)
    crpix1_ra,crpix2_dec                = np.float(crpix1_crpix2_radec[0]),np.float(crpix1_crpix2_radec[1])

    # transform center pixel values from (ra,dec) to (l,b)
    coords_EQ                           = SkyCoord(ra=crpix1_ra*u.degree, dec=crpix2_dec*u.degree, frame="fk5")
    header_GAL_CRVAL1,header_GAL_CRVAL2 = (coords_EQ.galactic.l.deg,coords_EQ.galactic.b.deg)

    header_GAL_CDELT1     = header_EQ["CDELT1"]
    header_GAL_CDELT2     = header_EQ["CDELT2"]

    # write GAL header
    data_GAL              = np.zeros(shape=(header_GAL_NAXIS2,header_GAL_NAXIS1))
    header_GAL            = fits.PrimaryHDU(data=data_GAL).header
    header_GAL["NAXIS"]   = 2
    header_GAL["NAXIS1"]  = header_GAL_NAXIS1
    header_GAL["NAXIS2"]  = header_GAL_NAXIS2
    # NAXIS1
    header_GAL["CTYPE1"]  = header_GAL_CTYPE1
    header_GAL["CRPIX1"]  = header_GAL_CRPIX1
    header_GAL["CRVAL1"]  = header_GAL_CRVAL1
    header_GAL["CDELT1"]  = header_GAL_CDELT1
    header_GAL["CROTA1"]  = header_GAL_CROTA1
    # NAXIS2
    header_GAL["CTYPE2"]  = header_GAL_CTYPE2
    header_GAL["CRPIX2"]  = header_GAL_CRPIX2
    header_GAL["CRVAL2"]  = header_GAL_CRVAL2
    header_GAL["CDELT2"]  = header_GAL_CDELT2
    header_GAL["CROTA2"]  = header_GAL_CROTA2
    # other
    header_GAL["EQUINOX"] = 2000.
    header_GAL["CUNIT1"]  = header_GAL_CUNIT1
    header_GAL["CUNIT2"]  = header_GAL_CUNIT2

    # perform reprojection with Montage
    header_file  = "/Users/campbell/Documents/PhD/data/GALFACTS/N1/GAL/header_GAL.fits"
    mheader_file = "/Users/campbell/Documents/PhD/data/GALFACTS/N1/GAL/mheader_GAL.txt"
    fits.writeto(header_file,data_GAL,header_GAL,overwrite=True)
    montage.mGetHdr(header_file,mheader_file)
    os.remove(header_file)
    montage.reproject(filedir_in,filedir_out,header=mheader_file)
facetstart = cfg.getint('facet', 'start')
facetend = cfg.getint('facet', 'end')
facetstep = cfg.getint('facet', 'step')
facetrundir = cfg.get('facet', 'rundir')

for i in range(facetstart, facetend + 1, facetstep):
    print 'Doing', i, '..', i + facetstep - 1
    bs = '%02i%02i' % (i, i + facetstep - 1)
    cdir = facetrundir + bs
    mosfile = cdir + '/mosaic.fits'
    if not (os.path.isfile(mosfile)):
        die('Can\'t find ' + mosfile)
    print 'Mosaic file is', mosfile
    hdrfile = mosfile.replace('.fits', '.hdr')
    montage.mGetHdr(mosfile, hdrfile)

    # now loop over the appropriate beam images
    for band in range(i, i + facetstep):
        b1s = '%02i' % band
        beamimage = beampath + '/' + troot + '_B' + b1s + '_' + image_suffix + '_img0.avgpb'
        beamfits = beamimage + '.fits'
        if not (os.path.isfile(beamfits)):
            if not (os.path.isdir(beamimage)):
                die('Can\'t find ' + beamimage)
            run('tofits.py ' + beamimage)
        else:
            print 'FITS version of beam exists, not converting'

        projout = cdir + '/beam-' + b1s + '.fits'
        if not (os.path.isfile(projout)):