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