def file_in(filename, extnum=0): """ Take the input files. If input is already HDU, then return it. If input is a .fits filename, then read the .fits file. Return ---------- hdu : obj An object containing both the image and the header im : (float point?) array The image array header : header object The header of the input .fits file Parameters ---------- filename : str The input .fits filename or a HDU variable name extnum : int The extension number to use from the input .fits file """ if isinstance(filename, (fits.ImageHDU, fits.PrimaryHDU)): hdu = filename else: hdu = fits.open(filename)[extnum] im = hdu.data.squeeze() header = FITS_tools.strip_headers.flatten_header(hdu.header) return hdu, im, header
def feather_simple( hires, lores, highresextnum=0, lowresextnum=0, highresscalefactor=1.0, lowresscalefactor=1.0, lowresfwhm=1 * u.arcmin, return_hdu=False, return_regridded_lores=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 an image. 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(hires, (fits.ImageHDU, fits.PrimaryHDU)): hdu1 = hires else: hdu1 = fits.open(hires)[highresextnum] if isinstance(lores, (fits.ImageHDU, fits.PrimaryHDU)): hdu2 = lores else: hdu2 = fits.open(lores)[lowresextnum] im1 = hdu1.data.squeeze() hd1 = FITS_tools.strip_headers.flatten_header(hdu1.header) assert hd1["NAXIS"] == im1.ndim == 2 pixscale = FITS_tools.header_tools.header_to_platescale(hd1) log.debug("pixscale = {0}".format(pixscale)) im2raw = hdu2.data.squeeze() hd2 = FITS_tools.strip_headers.flatten_header(hdu2.header) assert hd2["NAXIS"] == im2raw.ndim == 2 hdu2 = fits.PrimaryHDU(data=im2raw, header=hd2) hdu2 = hcongrid_hdu(hdu2, hd1) im2 = hdu2.data.squeeze() nax1, nax2 = (hd1["NAXIS1"], hd1["NAXIS2"]) ygrid, xgrid = 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 log.debug("sigma = {0}".format(sigma)) kernel = np.fft.fftshift(np.exp(-(xgrid ** 2 + ygrid ** 2) / (2 * sigma ** 2))) kernel /= kernel.max() ikernel = 1 - kernel fft1 = np.fft.fft2(np.nan_to_num(im1 * highresscalefactor)) fft2 = np.fft.fft2(np.nan_to_num(im2 * lowresscalefactor)) fftsum = kernel * fft2 + ikernel * fft1 combo = np.fft.ifft2(fftsum) if return_regridded_lores: return combo, hdu2 elif return_hdu: combo_hdu = fits.PrimaryHDU(data=np.abs(combo), header=hdu1.header) else: return combo
def fourier_combine(highresfitsfile, lowresfitsfile, matching_scale=60 * u.arcsec, scale=False, return_hdu=False): """ Simple reimplementation of 'feather' """ f1 = fits.open(highresfitsfile) w1 = wcs.WCS(f1[0].header) f2 = fits.open(lowresfitsfile) w2 = wcs.WCS(f2[0].header) nax1, nax2 = f1[0].header["NAXIS1"], f1[0].header["NAXIS2"] # We take care of zooming later... # if not(nax1 == f2[0].header['NAXIS1'] and nax2 == f2[0].header['NAXIS2']): # raise ValueError("Images are not in the same pixel space; reproject " # "them to common pixel space first.") pixscale1 = w1.wcs.get_cdelt()[1] pixscale2 = w2.wcs.get_cdelt()[1] center = w1.sub([wcs.WCSSUB_CELESTIAL]).wcs_pix2world([nax1 / 2.0], [nax2 / 2.0], 1) frame = "icrs" if w1.celestial.wcs.ctype[0][:2] == "RA" else "galactic" if w2.celestial.wcs.ctype[0][:2] == "RA": center = coordinates.SkyCoord(*(center * u.deg), frame=frame).fk5 cxy = center.ra.deg, center.dec.deg elif w2.celestial.wcs.ctype[0][:4] == "GLON": center = coordinates.SkyCoord(*(center * u.deg), frame=frame).galactic cxy = center.l.deg, center.b.deg im1 = f1[0].data.squeeze() im1[np.isnan(im1)] = 0 shape = im1.shape im2raw = f2[0].data.squeeze() im2raw[np.isnan(im2raw)] = 0 if len(shape) != im2raw.ndim: raise ValueError("Different # of dimensions in the interferometer and " "single-dish images") if len(shape) == 3: if shape[0] != im2raw.shape[0]: raise ValueError("Spectral dimensions of cubes do not match.") center_pixel = w2.sub([wcs.WCSSUB_CELESTIAL]).wcs_world2pix(cxy[0], cxy[1], 0)[::-1] zoomed = zoom_on_pixel(np.nan_to_num(im2raw), center_pixel, usfac=np.abs(pixscale2 / pixscale1), outshape=shape) im2 = zoomed xax, psd1 = fft_psd_tools.PSD2(im1, oned=True) xax, psd2 = fft_psd_tools.PSD2(im2, oned=True) xax_as = (pixscale1 / xax * u.deg).to(u.arcsec) if scale: closest_point = np.argmin(np.abs(xax_as - matching_scale)) scale_2to1 = (psd1[closest_point] / psd2[closest_point]) ** 0.5 else: scale_2to1 = 1 fft1 = np.fft.fft2(im1) fft2 = np.fft.fft2(im2) * scale_2to1 xgrid, ygrid = np.indices(shape) - np.array([(shape[0] - 1.0) / 2, (shape[1] - 1.0) / 2.0])[:, None, None] sigma = np.abs(shape[0] / ((matching_scale / (pixscale1 * u.deg)).decompose().value)) / np.sqrt(8 * np.log(2)) kernel = np.fft.fftshift(np.exp(-(xgrid ** 2 + ygrid ** 2) / (2 * sigma ** 2))) kernel /= kernel.max() fftsum = kernel * fft2 + (1 - kernel) * fft1 combo = np.fft.ifft2(fftsum) if not return_hdu: return combo elif return_hdu: combo_hdu = fits.PrimaryHDU(data=np.abs(combo), header=w1.to_header()) return combo_hdu