def fourier_combine_cubes(cube1, cube2, highresextnum=0,
                          highresscalefactor=1.0,
                          lowresscalefactor=1.0, lowresfwhm=1*u.arcmin,
                          return_regridded_cube2=False,
                          return_hdu=False,
                         ):
    """
    Fourier combine two data cubes

    Parameters
    ----------
    cube1 : SpectralCube
    highresfitsfile : str
        The high-resolution FITS file
    cube2 : SpectralCube
    lowresfitsfile : str
        The low-resolution (single-dish) FITS file
    highresextnum : int
        The extension number to use from the high-res FITS file
    highresscalefactor : float
    lowresscalefactor : float
        A factor to multiply the high- or low-resolution data by to match the
        low- or high-resolution data
    lowresfwhm : `astropy.units.Quantity`
        The full-width-half-max of the single-dish (low-resolution) beam;
        or the scale at which you want to try to match the low/high resolution
        data
    return_hdu : bool
        Return an HDU instead of just a cube.  It will contain two image
        planes, one for the real and one for the imaginary data.
    return_regridded_cube2 : bool
        Return the 2nd cube regridded into the pixel space of the first?
    """
    if isinstance(cube1, str):
        cube1 = SpectralCube.read(cube1)
    if isinstance(cube2, str):
        cube2 = SpectralCube.read(cube2)
    #cube1 = spectral_cube.io.fits.load_fits_cube(highresfitsfile,
    #                                             hdu=highresextnum)
    im1 = cube1._data # want the raw data for this
    hd1 = cube1.header
    assert hd1['NAXIS'] == im1.ndim == 3
    w1 = cube1.wcs
    pixscale = np.abs(w1.wcs.get_cdelt()[0]) # REPLACE EVENTUALLY...

    cube2 = cube2.to(cube1.unit)

    assert cube1.unit == cube2.unit, 'Cubes must have same or equivalent unit'
    assert cube1.unit.is_equivalent(u.Jy/u.beam) or cube1.unit.is_equivalent(u.K), "Cubes must have brightness units."

    #f2 = regrid_fits_cube(lowresfitsfile, hd1)
    f2 = regrid_cube_hdu(cube2.hdu, hd1)
    w2 = wcs.WCS(f2.header)

    nax1,nax2,nax3 = (hd1['NAXIS1'],
                      hd1['NAXIS2'],
                      hd1['NAXIS3'])

    dcube1 = im1 * highresscalefactor
    dcube2 = f2.data * lowresscalefactor
    outcube = np.empty_like(dcube1)

    xgrid,ygrid = (np.indices([nax2,nax1])-np.array([(nax2-1.)/2,(nax1-1.)/2.])[:,None,None])
    fwhm = np.sqrt(8*np.log(2))
    # sigma in pixels
    sigma = ((lowresfwhm/fwhm/(pixscale*u.deg)).decompose().value)
    #sigma_fftspace = (1/(4*np.pi**2*sigma**2))**0.5
    sigma_fftspace = (2*np.pi*sigma)**-1
    log.debug('sigma = {0}, sigma_fftspace={1}'.format(sigma, sigma_fftspace))

    kernel = np.fft.fftshift(np.exp(-(xgrid**2+ygrid**2)/(2*sigma**2)))
    # convert the kernel, which is just a gaussian in image space,
    # to its corresponding kernel in fourier space
    kfft = np.abs(np.fft.fft2(kernel)) # should be mostly real
    # normalize the kernel
    kfft/=kfft.max()
    ikfft = 1-kfft

    pb = ProgressBar(dcube1.shape[0])

    for ii,(im1,im2) in enumerate(zip(dcube1, dcube2)):

        fft1 = np.fft.fft2(np.nan_to_num(im1))
        fft2 = np.fft.fft2(np.nan_to_num(im2))

        fftsum = kfft*fft2 + ikfft*fft1

        combo = np.fft.ifft2(fftsum)
        outcube[ii,:,:] = combo.real

        pb.update(ii+1)

    if return_regridded_cube2:
        return outcube, f2
    elif return_hdu:
        return fits.PrimaryHDU(data=outcube, header=w1.to_header())
    else:
        return outcube
def fourier_combine_cubes(
    cube1,
    cube2,
    highresextnum=0,
    highresscalefactor=1.0,
    lowresscalefactor=1.0,
    lowresfwhm=1 * u.arcmin,
    return_regridded_cube2=False,
    return_hdu=False,
):
    """
    Fourier combine two data cubes

    Parameters
    ----------
    highresfitsfile : str
        The high-resolution FITS file
    lowresfitsfile : str
        The low-resolution (single-dish) FITS file
    highresextnum : int
        The extension number to use from the high-res FITS file
    highresscalefactor : float
    lowresscalefactor : float
        A factor to multiply the high- or low-resolution data by to match the
        low- or high-resolution data
    lowresfwhm : `astropy.units.Quantity`
        The full-width-half-max of the single-dish (low-resolution) beam;
        or the scale at which you want to try to match the low/high resolution
        data
    return_hdu : bool
        Return an HDU instead of just a cube.  It will contain two image
        planes, one for the real and one for the imaginary data.
    return_regridded_cube2 : bool
        Return the 2nd cube regridded into the pixel space of the first?
    """
    # cube1 = spectral_cube.io.fits.load_fits_cube(highresfitsfile,
    #                                             hdu=highresextnum)
    im1 = cube1._data  # want the raw data for this
    hd1 = cube1.header
    assert hd1["NAXIS"] == im1.ndim == 3
    w1 = cube1.wcs
    pixscale = np.abs(w1.wcs.get_cdelt()[0])  # REPLACE EVENTUALLY...

    # f2 = regrid_fits_cube(lowresfitsfile, hd1)
    f2 = regrid_cube_hdu(cube2.hdu, hd1)
    w2 = wcs.WCS(f2.header)

    nax1, nax2, nax3 = (hd1["NAXIS1"], hd1["NAXIS2"], hd1["NAXIS3"])

    dcube1 = im1 * highresscalefactor
    dcube2 = f2.data * lowresscalefactor
    outcube = np.empty_like(dcube1)

    xgrid, ygrid = np.indices([nax2, nax1]) - np.array([(nax2 - 1.0) / 2, (nax1 - 1.0) / 2.0])[:, None, None]
    fwhm = np.sqrt(8 * np.log(2))
    # sigma in pixels
    sigma = (lowresfwhm / fwhm / (pixscale * u.deg)).decompose().value

    kernel = np.fft.fftshift(np.exp(-(xgrid ** 2 + ygrid ** 2) / (2 * sigma ** 2)))
    kernel /= kernel.max()
    ikernel = 1 - kernel

    pb = ProgressBar(dcube1.shape[0])

    for ii, (im1, im2) in enumerate(izip(dcube1, dcube2)):

        fft1 = np.fft.fft2(np.nan_to_num(im1))
        fft2 = np.fft.fft2(np.nan_to_num(im2))

        fftsum = kernel * fft2 + ikernel * fft1

        combo = np.fft.ifft2(fftsum)
        outcube[ii, :, :] = combo.real

        pb.update(ii + 1)

    if return_regridded_cube2:
        return outcube, f2
    elif return_hdu:
        return fits.PrimaryHDU(data=outcube, header=w1.to_header())
    else:
        return outcube
def extract_subcube(cubefilename, outfilename, linefreq=218.22219*u.GHz,
                    debug=False, smooth=False, vsmooth=False):
    ffile = fits.open(cubefilename)
    # I don't know why this is necessary, but there were weird bugs showing up
    # if I did not add this (some parts of the cube defaulted to 3e-319)
    ffile[0].data = ffile[0].data.astype('float32')
    hdr = ffile[0].header

    cdk = 'CD3_3' if 'CD3_3' in hdr else 'CDELT3'

    if debug:
        xarr = (np.arange(hdr['NAXIS3'])+1-hdr['CRPIX3']) * hdr[cdk] + hdr['CRVAL3']
        print xarr.min(),xarr.max()

    hdr['CTYPE3'] = 'VELO'
    cdfrq = hdr[cdk]
    crvfreq = hdr['CRVAL3']
    crpixf = hdr['CRPIX3']
    hdr[cdk] = -((cdfrq/crvfreq)*constants.c).to(u.km/u.s).value
    hdr['CRVAL3'] = 0.0
    hdr['CRPIX3'] = (linefreq.to(u.Hz).value-crvfreq)/cdfrq + crpixf
    hdr['CUNIT3'] = 'km/s'

    if debug:
        xarr = (np.arange(hdr['NAXIS3'])+1-hdr['CRPIX3']) * hdr[cdk] + hdr['CRVAL3']
        print xarr.min(),xarr.max()

    for k in ['CTYPE3','CRVAL3',cdk,'CRPIX3','CUNIT3']:
        assert ffile[0].header[k] == hdr[k]

    outhdr = hdr.copy()
    outhdr[cdk] = 1.0
    outhdr['RESTFREQ'] = linefreq.to(u.Hz).value
    outhdr['RESTFRQ'] = linefreq.to(u.Hz).value
    outhdr['CRVAL3'] = 50
    outhdr['CRPIX3'] = 199.5
    outhdr['NAXIS3'] = 400

    if smooth:
        #cubesm = gsmooth_cube(ffile[0].data, [3,2,2], use_fft=True,
        #                      psf_pad=False, fft_pad=False)
        # smoothed with 2 pixels -> sigma=10", fwhm=23"
        # this is an "optimal smooth", boosting s/n and smoothing to 36"
        # resolution.
        kw = 2 if not vsmooth else 4
        cubesm = cube_regrid.spatial_smooth_cube(ffile[0].data, kw,
                                                 use_fft=False,
                                                 numcores=4)
        cubesm = cube_regrid.spectral_smooth_cube(cubesm, 3/2.35,
                                                  use_fft=False,
                                                  numcores=4)
        ffile[0].data = cubesm

        outhdr[cdk] = 3.0
        outhdr['CRVAL3'] = 50
        outhdr['CRPIX3'] = 70
        outhdr['NAXIS3'] = 140
    
    if debug:
        xarr = (np.arange(outhdr['NAXIS3'])+1-outhdr['CRPIX3']) * outhdr[cdk] + outhdr['CRVAL3']
        print xarr.min(),xarr.max()
        xarr = (-xarr/3e5) * linefreq + linefreq
        print xarr.min(),xarr.max()
        return hdr,outhdr

    newhdu = cube_regrid.regrid_cube_hdu(ffile[0], outhdr, order=1,
                                         prefilter=False)

    newhdu.writeto(outfilename, clobber=True)
    
    return newhdu
def fourier_combine_cubes(
    cube1,
    cube2,
    highresextnum=0,
    highresscalefactor=1.0,
    lowresscalefactor=1.0,
    lowresfwhm=1 * u.arcmin,
    return_regridded_cube2=False,
    return_hdu=False,
):
    """
    Fourier combine two data cubes

    Parameters
    ----------
    cube1 : SpectralCube
    highresfitsfile : str
        The high-resolution FITS file
    cube2 : SpectralCube
    lowresfitsfile : str
        The low-resolution (single-dish) FITS file
    highresextnum : int
        The extension number to use from the high-res FITS file
    highresscalefactor : float
    lowresscalefactor : float
        A factor to multiply the high- or low-resolution data by to match the
        low- or high-resolution data
    lowresfwhm : `astropy.units.Quantity`
        The full-width-half-max of the single-dish (low-resolution) beam;
        or the scale at which you want to try to match the low/high resolution
        data
    return_hdu : bool
        Return an HDU instead of just a cube.  It will contain two image
        planes, one for the real and one for the imaginary data.
    return_regridded_cube2 : bool
        Return the 2nd cube regridded into the pixel space of the first?
    """
    if isinstance(cube1, str):
        cube1 = SpectralCube.read(cube1)
    if isinstance(cube2, str):
        cube2 = SpectralCube.read(cube2)
    #cube1 = spectral_cube.io.fits.load_fits_cube(highresfitsfile,
    #                                             hdu=highresextnum)
    im1 = cube1._data  # want the raw data for this
    hd1 = cube1.header
    assert hd1['NAXIS'] == im1.ndim == 3
    w1 = cube1.wcs
    pixscale = np.abs(w1.wcs.get_cdelt()[0])  # REPLACE EVENTUALLY...

    cube2 = cube2.to(cube1.unit)

    assert cube1.unit == cube2.unit, 'Cubes must have same or equivalent unit'
    assert cube1.unit.is_equivalent(u.Jy / u.beam) or cube1.unit.is_equivalent(
        u.K), "Cubes must have brightness units."

    #f2 = regrid_fits_cube(lowresfitsfile, hd1)
    f2 = regrid_cube_hdu(cube2.hdu, hd1)
    w2 = wcs.WCS(f2.header)

    nax1, nax2, nax3 = (hd1['NAXIS1'], hd1['NAXIS2'], hd1['NAXIS3'])

    dcube1 = im1 * highresscalefactor
    dcube2 = f2.data * lowresscalefactor
    outcube = np.empty_like(dcube1)

    xgrid, ygrid = (np.indices([nax2, nax1]) -
                    np.array([(nax2 - 1.) / 2,
                              (nax1 - 1.) / 2.])[:, None, None])
    fwhm = np.sqrt(8 * np.log(2))
    # sigma in pixels
    sigma = ((lowresfwhm / fwhm / (pixscale * u.deg)).decompose().value)
    #sigma_fftspace = (1/(4*np.pi**2*sigma**2))**0.5
    sigma_fftspace = (2 * np.pi * sigma)**-1
    log.debug('sigma = {0}, sigma_fftspace={1}'.format(sigma, sigma_fftspace))

    kernel = np.fft.fftshift(np.exp(-(xgrid**2 + ygrid**2) / (2 * sigma**2)))
    # convert the kernel, which is just a gaussian in image space,
    # to its corresponding kernel in fourier space
    kfft = np.abs(np.fft.fft2(kernel))  # should be mostly real
    # normalize the kernel
    kfft /= kfft.max()
    ikfft = 1 - kfft

    pb = ProgressBar(dcube1.shape[0])

    for ii, (im1, im2) in enumerate(zip(dcube1, dcube2)):

        fft1 = np.fft.fft2(np.nan_to_num(im1))
        fft2 = np.fft.fft2(np.nan_to_num(im2))

        fftsum = kfft * fft2 + ikfft * fft1

        combo = np.fft.ifft2(fftsum)
        outcube[ii, :, :] = combo.real

        pb.update(ii + 1)

    if return_regridded_cube2:
        return outcube, f2
    elif return_hdu:
        return fits.PrimaryHDU(data=outcube, header=w1.to_header())
    else:
        return outcube