def makeRadiusBins(galaxy, basemap, outDir, beam=15.0): ''' Create bins for the data basemap: map used to create bins (SpectralCube Projection) outDir: directory to write output bin image Date Programmer Description of Changes ---------------------------------------------------------------------- 10/29/2020 Yiqing Song Original Code 12/3/2020 A.A. Kepley Added more comments plus moved GCR map calculate up to other code. 4/15/2021 A.A. Kepley Changes bins to be beam width apart in radius. ''' minrad = 0.0 + beam / 2.0 maxrad = np.max( basemap).value + beam # want to add one bin beyond to capture max. binedge = np.arange(minrad, maxrad, beam) binedge = np.insert(binedge, 0, 0) binedge = binedge * basemap.unit bins = np.digitize(basemap.value, binedge.value) binlabels = ['{0:1.2f}'.format(i) + ' arcsec' for i in binedge] # make bins map binmap = Projection(bins, wcs=basemap.wcs, header=basemap.header) binmap.write(os.path.join(outDir, galaxy['NAME'].upper() + '_binsbyradius.fits'), overwrite=True) return binmap, binedge, binlabels
def mapLTIR(galaxy, mom0cut, regridDir, outDir): ''' make LTIR map galaxy: line from degas_base.fits table with galaxy information mom0cut: S/N cut on mom0 regridDir: input data directory with all regridded data outDir: output data directory Date Programmer Description of Changes ---------------------------------------------------------------------- 12/10/2020 A.A. Kepley Original code based on mapStellar ''' hdu = fits.open( os.path.join(regridDir, galaxy['NAME'] + '_LTIR_gauss15_regrid.fits'))[0] data = hdu.data data[np.isnan(mom0cut)] = np.nan #apply SN mask (SN >3) LTIRmap = Projection(data, header=hdu.header, wcs=WCS(hdu.header)) LTIRmap.quicklook() plt.savefig(os.path.join(outDir, galaxy['NAME'] + '_LTIR.png')) plt.clf() plt.close() return LTIRmap
def mapSFR(galaxy, mom0cut, regridDir, outDir): ''' import sfr map from W4+FUV Date Programmer Description of Changes ---------------------------------------------------------------------- 10/29/2020 Yiqing Song Original Code 12/3/2020 A.A. Kepley Added comments and clarified inputs ''' sfrhdu = fits.open( os.path.join(regridDir, galaxy['NAME'] + '_sfr_fuvw4_gauss15_regrid.fits'))[0] sfr = sfrhdu.data sfr[np.isnan(mom0cut)] = np.nan w = WCS(sfrhdu.header) ## TODO CHECK UNITS HERE sfrhdu.header[ 'BUNIT'] = 'MSUN/YR/KPC^2' #might change when getting new files from Sarah sfrmap = Projection(sfr, header=sfrhdu.header, wcs=w) sfrmap.quicklook() # save plot of map plt.savefig(os.path.join(outDir, galaxy['NAME'] + '_sfr.png')) plt.clf() plt.close() return sfrmap
def linear_combine(hires, lores, highresextnum=0, lowresextnum=0, highresscalefactor=1.0, lowresscalefactor=1.0, lowresfwhm=None, return_hdu=False, return_regridded_lores=False, match_units=True, convolve=convolve_fft, ): """ Implement a simple linear combination following Faridani et al 2017 """ if isinstance(hires, str): proj_hi = Projection.from_hdu(fits.open(hires)[highresextnum]) else: proj_hi = Projection.from_hdu(hires) if isinstance(hires, str): proj_lo = Projection.from_hdu(fits.open(lores)[lowresextnum]) else: proj_lo = Projection.from_hdu(lores) if lowresfwhm is None: beam_low = proj_lo.beam lowresfwhm = beam_low.major log.info("Low-res FWHM: {0}".format(lowresfwhm)) else: beam_low = Beam(major=lowresfwhm) if match_units: # After this step, the units of im_hi are some sort of surface brightness # unit equivalent to that specified in the high-resolution header's units # Note that this step does NOT preserve the values of im_lowraw and # header_lowraw from above # im_lowraw, header_low = match_flux_units(image=proj_lo.value, # image_header=proj_lo.header.copy(), # target_header=proj_hi.header) # proj_lo = Projection(im_lowraw, header=header_low) proj_lo = proj_lo.to(proj_hi.unit) proj_lo_regrid = proj_lo.reproject(proj_hi.header) missing_flux = proj_lo_regrid - proj_hi.convolve_to(beam_low) combo = missing_flux + proj_hi if return_hdu: combo_hdu = fits.PrimaryHDU(data=combo.real, header=proj_hi.header) combo = combo_hdu if return_regridded_lores: return combo, hdu_low else: return combo
def test_feather_simple(plaw_test_data): orig_hdu, lowres_hdu, highres_hdu = plaw_test_data # HDU input combo = feather_simple(highres_hdu, lowres_hdu) # Projection input lowres_proj = Projection.from_hdu(lowres_hdu) highres_proj = Projection.from_hdu(highres_hdu) combo = feather_simple(highres_proj, lowres_proj)
def mapGCR(galaxy, basemap): ''' Create map of galactic radii Date Programmer Description of Changes ---------------------------------------------------------------------- 10/29/2020 Yiqing Song Original Code 12/03/2020 A.A. Kepley Tweak to how input data is handled and added comments ''' # get the data we need for the galaxy ra = galaxy['RA_DEG'] dec = galaxy['DEC_DEG'] inc = np.radians(galaxy['INCL_DEG']) pa = np.radians(galaxy['POSANG_DEG']) r25 = galaxy['R25_DEG'] * 3600.0 # arcsec Dmpc = galaxy['DIST_MPC'] # get wcs w = basemap.wcs # get center x0, y0 = w.all_world2pix(ra, dec, 0, ra_dec_order=True) # get coordinates y = np.arange(0, np.shape(basemap)[0], 1) x = np.arange(0, np.shape(basemap)[1], 1) # create a 2d image of coordinates xx, yy = np.meshgrid(x, y) # calculate the radius in pixels xx_new = x0 + (xx - x0) * np.cos(pa) + (yy - y0) * np.sin(pa) yy_new = y0 - (xx - x0) * np.sin(pa) + (yy - y0) * np.cos(pa) R = np.sqrt((xx_new - x0)**2 / np.cos(inc)**2 + (yy_new - y0)**2) #pixels # now convert from pixels to actual units head = basemap.header pxscale = np.radians(np.abs(head['CDELT1'])) #radian/pixel R_arcsec = np.degrees( R * pxscale) * 3600.0 * u.arcsec # map of GCR in arcsec R_kpc = R * pxscale * Dmpc * 1000 * u.kpc # map of GCR in kpc R_r25 = R_arcsec / r25 # map of GCR in units of R25 Rarcsec_map = Projection(R_arcsec, header=head, wcs=w) Rkpc_map = Projection(R_kpc, header=head, wcs=w) Rr25_map = Projection(R_r25, header=head, wcs=w) # map of galactocentric radius in unit of kpc, arcmin, in r25 return Rarcsec_map, Rkpc_map, Rr25_map
def image_sz512as_pl1p5_fwhm2as_scale1as(tmp_path): pixel_scale = 1 * units.arcsec restfreq = 100 * units.GHz highres_major = 2 * units.arcsec # Generate input image input_hdu = generate_test_fits(imsize=512, powerlaw=1.5, beamfwhm=highres_major, pixel_scale=pixel_scale, restfreq=restfreq, brightness_unit=units.Jy / units.sr) input_fn = tmp_path / "input_image_sz512as_pl1.5_fwhm2as_scale1as.fits" input_hdu.writeto(input_fn, overwrite=True) input_proj = Projection.from_hdu(input_hdu).to(units.Jy / units.beam) # Make Interferometric image intf_data = interferometrically_observe_image(image=input_hdu.data, pixel_scale=pixel_scale, largest_angular_scale=40*units.arcsec, smallest_angular_scale=highres_major)[0].real intf_hdu = fits.PrimaryHDU(data=intf_data.value if hasattr(intf_data, "value") else intf_data, header=input_hdu.header) intf_proj = Projection.from_hdu(intf_hdu).to(units.Jy / units.beam) intf_fn = tmp_path / "input_image_sz512as_pl1.5_fwhm2as_scale1as_intf2to40as.fits" intf_proj.write(intf_fn, overwrite=True) # Make SD image sd_header = input_hdu.header.copy() major = 15*units.arcsec # Eff SD diam (to compare with CASA in troubleshooting) sd_beam = Beam(major=major) sd_header.update(sd_beam.to_header_keywords()) sd_fn = tmp_path / "input_image_sz512as_pl1.5_fwhm2as_scale1as_sd15as.fits" sd_data = singledish_observe_image(input_hdu.data, pixel_scale=pixel_scale, beam=sd_beam, boundary='wrap') sd_hdu = fits.PrimaryHDU(data=sd_data.value if hasattr(sd_data, "value") else sd_data, header=sd_header) sd_hdu.header.update(sd_beam.to_header_keywords()) sd_proj = Projection.from_hdu(sd_hdu).to(units.Jy / units.beam) sd_proj.write(sd_fn, overwrite=True) return tmp_path, input_fn, intf_fn, sd_fn
def makeBins(galaxy, basemap, bintype, outDir): ''' Create bins for the data basemap: map used to create bins (SpectralCube Projection) bintype: type of bins ('intensity','stellarmass', 'radius') outDir: directory to write output bin image Date Programmer Description of Changes ---------------------------------------------------------------------- 10/29/2020 Yiqing Song Original Code 12/3/2020 A.A. Kepley Added more comments plus moved GCR map calculate up to other code. ''' if bintype == 'intensity' or bintype == 'stellarmass': #Bin the basemap by brightness binnum = int( np.log(np.nanmax(basemap.value) / np.nanmin(basemap.value))) + 1 binedge = np.nanmin(basemap.value) * np.logspace( 0, binnum, num=binnum + 1, base=np.e) #create bins based on dynamic range of mom0 bins = np.digitize( basemap.value, binedge ) #this will automatically add an extra bin in the end for nan values binlabels = [""] + [ '{0:1.2f}'.format(i) + basemap.unit.to_string() for i in binedge ] #need to add units to stellarmass map!! elif bintype == 'radius': binnum = 5 binedge = np.zeros(binnum + 1) binedge[1:] = np.logspace(-1, np.log10(0.5), num=binnum, base=10) #create bins based on r25 bins = np.digitize( basemap, binedge ) #this will automatically add an extra bin in the end for nan values binlabels = [""] + ['{0:1.2f}'.format(i) + 'R25' for i in binedge] else: raise Exception( "bintype should either be 'radius' or 'intensity' or 'stellarmass' " ) # Blank NaN values bins[bins == len(binedge)] = 0 # make bins map binmap = Projection(bins, wcs=basemap.wcs, header=basemap.header) binmap.write(os.path.join( outDir, galaxy['NAME'].upper() + '_binsby' + bintype + '.fits'), overwrite=True) return binmap, binedge, binlabels
def mapStellar(galaxy,mom0cut,plotdir='./',stellardatadir='./'): stellarhdu=fits.open(stellardatadir+galaxy+'_w1_stellarmass_regrid.fits')[0] starmask=fits.getdata(stellardatadir+galaxy+'_w1_gauss15_stars_regrid.fits') stellar=stellarhdu.data stellar[starmask==1.0]=np.nan stellar[np.isnan(mom0cut)]=np.nan w=WCS(stellarhdu.header) stellarhdu.header['BUNIT']='Msun/pc^2' #not the correct unit!! stellarmap=Projection(stellar,header=stellarhdu.header,wcs=w) stellarmap.quicklook() plt.savefig(plotdir+galaxy+'_stellarmass.png') plt.clf() plt.close() return stellarmap
def convert_and_reproject(name, template=None, unit=None, order=1): """ Helper for moment1 hybrid routine. Reads in data, makes sure it is a projection, converts units, and reprojects as necessary. """ # Ensure inputs are Projections if name is not None: if type(name) is Projection: data = name elif type(name) is str: hdu_list = fits.open(name) data = Projection.from_hdu(hdu_list[0]) else: print("Input is not a string or a Projection") raise ValueError if unit is not None: data = data.to(unit) if template is not None: data = data.reproject(template.header, order=order) else: data = None return (data)
def mapSFR(galaxy,mom0cut,plotdir='./',sfrdatadir='./'): sfrhdu=fits.open(sfrdatadir+galaxy+'_w4fuv_sfr_regrid.fits')[0] starmask_fuv=fits.getdata(sfrdatadir+galaxy+'_fuv_gauss15_stars_regrid.fits') starmask_nuv=fits.getdata(sfrdatadir+galaxy+'_nuv_gauss15_stars_regrid.fits') sfr=sfrhdu.data sfr[starmask_fuv==1.0]=np.nan #mask out stars from fuv and nuv sfr[starmask_nuv==1.0]=np.nan sfr[np.isnan(mom0cut)]=np.nan w=WCS(sfrhdu.header) sfrhdu.header['BUNIT']='MSUN/YR/KPC^2' sfrmap=Projection(sfr,header=sfrhdu.header,wcs=w) sfrmap.quicklook() plt.savefig(plotdir+galaxy+'_sfr.png') plt.clf() plt.close() return sfrmap
def sfr_get(gal,hdr=None): if isinstance(gal,Galaxy): name = gal.name.lower() elif isinstance(gal,str): name = gal.lower() else: raise ValueError("'gal' must be a str or galaxy!") if name=='m33': filename = fits.open('notphangsdata/cube.fits')[13] else: filename = 'phangsdata/sfr/'+name+'_sfr_fuvw4.fits' if os.path.isfile(filename): sfr_map = Projection.from_hdu(fits.open(filename)) else: print('WARNING: No SFR map was found!') sfr_map = None return sfr_map if hdr!=None: sfr = sfr_map.reproject(hdr) # Msun/yr/kpc^2. See header. # https://www.aanda.org/articles/aa/pdf/2015/06/aa23518-14.pdf else: sfr = sfr_map return sfr
def mapStellar(galaxy, mom0cut, regridDir, outDir): ''' make stellarmass map galaxy: line from degas_base.fits table with galaxy information mom0cut: S/N cut on mom0 regridDir: input data directory with all regridded data outDir: output data directory Date Programmer Description of Changes ---------------------------------------------------------------------- 10/29/2020 Yiqing Song Original Code 12/3/2020 A.A. Kepley Added comments and clarified inputs ''' # open the stellar mass ## TODO : NEED TO UPDATE WITH NEW ANCILLAR DATA FROM SARAH stellarhdu = fits.open( os.path.join(regridDir, galaxy['NAME'] + '_mstar_gauss15_regrid.fits'))[0] stellar = stellarhdu.data #stellar[starmask==1.0]=np.nan #apply star mask ## AAK: I think I can skip this. stellar[np.isnan(mom0cut)] = np.nan #apply SN mask (SN >3) w = WCS(stellarhdu.header) # stellarhdu.header['BUNIT']='Msun/pc^2' #not the correct unit!! ## AAK: I think units are okay for the newdata stellarmap = Projection(stellar, header=stellarhdu.header, wcs=w) stellarmap.quicklook() plt.savefig(os.path.join(outDir, galaxy['NAME'] + '_stellarmass.png')) plt.clf() plt.close() return stellarmap
def makeBins(galaxy, basemap, bintype): if bintype=='intensity' or bintype=='stellarmass': #Bin the basemap by brightness binnum=int(np.log(np.nanmax(basemap.value)/np.nanmin(basemap.value)))+1 binedge = np.nanmin(basemap.value)*np.logspace(0, binnum, num=binnum+1, base=np.e) #create bins based on dynamic range of mom0 bins = np.digitize(basemap.value, binedge) #this will automatically add an extra bin in the end for nan values binlabels=[""]+['{0:1.2f}'.format(i)+basemap.unit.to_string() for i in binedge] #need to add units to stellarmass map!! elif bintype=='radius': R_arcmin, R_kpc, R_r25=mapGCR(galaxy,basemap) #can choose from 3 different Rmaps binnum=5 binedge = np.zeros(binnum+1) binedge[1:]=np.logspace(-1, np.log10(0.5), num=binnum,base=10)#create bins based on r25 bins = np.digitize(R_r25, binedge) #this will automatically add an extra bin in the end for nan values binlabels=[""]+['{0:1.2f}'.format(i)+'R25' for i in binedge] else: raise Exception ("bintype should either be 'radius' or 'intensity' or 'stellarmass' ") # Blank NaN values bins[bins==len(binedge)] = 0 # make bins map binmap=Projection(bins,wcs=basemap.wcs,header=basemap.header) binmap.write(datadir+galaxy.upper()+'_binsby'+bintype+'.fits', overwrite=True) return binmap, binedge, binlabels
def test_SC_inputs(): hdr['BUNIT'] = 'K' hdu = PrimaryHDU(img, header=hdr) proj = Projection.from_hdu(hdu) output = input_data(proj) npt.assert_equal(img, output["data"].value) assert output['data'].unit == u.K npt.assert_equal(proj.header, output["header"]) slic = Slice.from_hdu(hdu) output = input_data(slic) npt.assert_equal(img, output["data"].value) assert output['data'].unit == u.K npt.assert_equal(slic.header, output["header"])
def test_pspec(plotname="pspec_rnoise_beamsmooth_apodizetukey.pdf", size=256, powerlaw=3., run_kwargs={ 'verbose': False, 'apodize_kernel': 'tukey' }, plot_kwargs={'fit_color': 'black'}, beam_smooth=True, pixel_scale=2 * u.arcsec, bmin=8.09 * u.arcsec, bmaj=10.01 * u.arcsec, bpa=-12.9 * u.deg, restfreq=1.4 * u.GHz, bunit=u.K): from spectral_cube import Projection from radio_beam import Beam rnoise_img = make_extended(size, powerlaw) # Create a FITS HDU rnoise_hdu = create_fits_hdu(rnoise_img, 2 * u.arcsec, 2 * u.arcsec, rnoise_img.shape, 1.4 * u.GHz, u.K) pspec = PowerSpectrum(rnoise_hdu) if beam_smooth: pencil_beam = Beam(0 * u.deg) rnoise_proj = Projection.from_hdu(rnoise_hdu).with_beam(pencil_beam) new_beam = Beam(bmaj, bmin, bpa) rnoise_conv = rnoise_proj.convolve_to(new_beam) # hdr = fits.Header(header) # rnoise_hdu = fits.PrimaryHDU(rnoise_img, header=hdr) pspec = PowerSpectrum(rnoise_conv) pspec.run(**run_kwargs) pspec.plot_fit(save_name=plotname, **plot_kwargs) return pspec
beamfwhm = 3 * u.arcsec imshape = rnoise_img.shape restfreq = 1.4 * u.GHz bunit = u.K plaw_hdu = create_fits_hdu(rnoise_img, pixel_scale, beamfwhm, imshape, restfreq, bunit) pspec = PowerSpectrum(plaw_hdu) pspec.run(verbose=True, radial_pspec_kwargs={'binsize': 1.0}, fit_kwargs={'weighted_fit': False}, fit_2D=False, low_cut=1. / (60 * u.pix), save_name=osjoin(fig_path, "rednoise_pspec_slope3.png")) pencil_beam = Beam(0 * u.deg) plaw_proj = Projection.from_hdu(plaw_hdu) plaw_proj = plaw_proj.with_beam(pencil_beam) new_beam = Beam(3 * plaw_hdu.header['CDELT2'] * u.deg) plaw_conv = plaw_proj.convolve_to(new_beam) plaw_conv.quicklook() plt.savefig('images/rednoise_slope3_img_smoothed.png') plt.close() pspec2 = PowerSpectrum(plaw_conv) pspec2.run(verbose=True, xunit=u.pix**-1, fit_2D=False, low_cut=0.025 / u.pix, high_cut=0.1 / u.pix, radial_pspec_kwargs={'binsize': 1.0}, apodize_kernel='tukey') plt.axvline(np.log10(1 / 3.), color=col_pal[3], linewidth=8, alpha=0.8,
fig = show_fov_on_spitzer(**pfxs, fieldid=fieldid, spitzerpath='spitzer_datapath', contour_level={'B3':[100], 'B6': [100]}, mips=True) fig.savefig(f'mips_datapath/fov_plots/{fieldid}_field_of_view_plot_mips.png', bbox_inches='tight') fig = show_fov_on_spitzer(**pfxs, fieldid=fieldid, spitzerpath='spitzer_datapath', contour_level=contour_levels[fieldid], mips=True, zoom=2) fig.savefig(f'mips_datapath/fov_contour_plots/{fieldid}_field_of_view_contour_plot_mips.png', bbox_inches='tight', dpi=300) field = prefixes[fieldid]['finaliter_prefix_b3'].split("/")[0] for (line, band, spw, color) in (('n2hp', 'B3', 'spw0', 'cyan'), ('sio', 'B6', 'spw1', 'blue')): fn = f'{basepath}/moments/{field}/{field}_{band}_{spw}_12M_{spw}.JvM.image.pbcor.fits.{line}.m0.fits' #pbfn = f'{basepath}/{field}/{band}/linecubes_12m/{field}_{band}_{spw}_12M_{line}.pb' pbfn = f'{basepath}/{field}_{band}_{spw}_12M_{line}.pb' if os.path.exists(fn): print(line, band, spw, fn, pbfn) fh = fits.open(fn) pb = SpectralCube.read(pbfn, format='casa_image') pbv,_ = reproject.reproject_interp(pb[10].hdu, fh[0].header) # 10th channel is arbitrary but avoids edge channel data = fh[0].data * pbv m0 = Projection(data, wcs=wcs.WCS(fh[0].header)) std = stats.mad_std(data, ignore_nan=True) levels = np.array([3, 5, 10, 20, 30])*std fig = show_contours_on_spitzer(image=m0, fieldid=fieldid, mips=True, spitzerpath='spitzer_datapath', contour_levels=levels, zoom=2, line=line, color=color) fig.savefig(f'mips_datapath/m0_contour_plots/{field}_{line}_contour_plot_mips.png', bbox_inches='tight', dpi=300) fig = show_contours_on_spitzer(image=m0, fieldid=fieldid, mips=False, spitzerpath='spitzer_datapath', contour_levels=levels, zoom=2, line=line, color=color) fig.savefig(f'spitzer_datapath/m0_contour_plots/{field}_{line}_contour_plot.png', bbox_inches='tight', dpi=300) else: print(f"{line, band, spw}: {fn} does not exist")
from spectral_cube import SpectralCube, Projection import paths import files import regions import pylab as pl regs = regions.read_ds9(paths.rpath('sio_masers.reg')) v2maser = regs[2] bluefile = paths.Fpath('SgrB2_N_SiO_blue_20to50kms.fits') redfile = paths.Fpath('SgrB2_N_SiO_blue_77to100kms.fits') if os.path.exists(bluefile): blue = Projection.from_hdu(fits.open(bluefile)) red = Projection.from_hdu(fits.open(redfile)) else: siocube = (SpectralCube.read( '/Volumes/external/sgrb2/full_SgrB2N_spw0_lines_cutoutN_medsub.fits'). with_spectral_unit(u.km / u.s, velocity_convention='radio', rest_value=217.10498 * u.GHz).spectral_slab( -200 * u.km / u.s, 250 * u.km / u.s)) siocube.spectral_slab(0 * u.km / u.s, 120 * u.km / u.s).write( 'SgrB2_N_SiO_medsub_cutout.fits', overwrite=True) blue = siocube.spectral_slab(20 * u.km / u.s, 50 * u.km / u.s).moment0() blue.write(bluefile, overwrite=True)
mask_hdu.data.sum(0) > 10) del maskint_hdu, mask_hdu # Load in PB plane to account for varying uncertainty pb = fits.open(fourteenA_HI_file_dict['PB'], mode='denywrite') pb_plane = pb[0].data[0].copy() del pb # Need peak temp and centroid maps. # peak_name = fourteenA_wEBHIS_HI_file_dict['PeakTemp'] # peaktemp = Projection.from_hdu(fits.open(peak_name)) vcent_name = fourteenA_wEBHIS_HI_file_dict['Moment1'] vcent = Projection.from_hdu(fits.open(vcent_name)).to(u.km / u.s) noise_val = 0.72 * u.K # Set max number of gaussians to something ridiculous. # Just so we don't have a failure putting into the output array max_comp = 30 err_map = noise_val / pb_plane params_name = fourteenA_HI_data_wEBHIS_path( "individ_multigaussian_gausspy_fits.fits", no_check=True) if run_fit: agd_kwargs = {
def write_ew(cube, outfile=None, errorfile=None, rms=None, channel_correlation=None, overwrite=True, unit=None, return_products=True): """ Write out linewidth (equivalent-width-based) map for a SpectralCube Keywords: --------- cube : SpectralCube (Masked) spectral cube to write a EW-based velocity dispersion map outfile : str File name of output file errorfile : str File name of map for the uncertainty rms : SpectralCube Root-mean-square estimate of the error. This must have an estimate the noise level at all positions where there is signal, and only at those positions. channel_correlation : np.array One-dimensional array containing the channel-to-channel normalize correlation coefficients overwrite : bool Set to True (the default) to overwrite existing maps if present. unit : astropy.Unit Preferred unit for moment masks return_products : bool Return products calculated in the map """ maxmap = cube.max(axis=0) mom0 = cube.moment0() sigma_ew = mom0 / maxmap / np.sqrt(2 * np.pi) spaxis = cube.spectral_axis.value if errorfile is not None and rms is None: logger.error("Equivalent width error requested but no RMS provided") sigma_ewerr_projection = None if rms is not None: argmaxmap = cube.argmax(axis=0) rms_at_max = np.take_along_axis(rms.filled_data[:], argmaxmap[np.newaxis, :, :], 0).value sigma_ew_err = np.empty(sigma_ew.shape) sigma_ew_err.fill(np.nan) rms = rms.with_mask(cube._mask.include(), inherit_mask=False) dv = np.abs(spaxis[1] - spaxis[0]) if channel_correlation is None: term1 = (np.nansum((rms.filled_data[:].value)**2, axis=0) * dv**2 / (2 * np.pi * maxmap.value**2)) term2 = (sigma_ew.value**2 - sigma_ew.value * dv / np.sqrt(2 * np.pi)) * np.squeeze(rms_at_max)**2 sigma_ew_err = (term1 + term2)**0.5 else: yy, xx = np.where(np.isfinite(sigma_ew)) for y, x in zip(yy, xx): slc = (slice(None), slice(y, y + 1, None), slice(x, x + 1, None)) mask = np.squeeze(cube.mask.include(view=slc)) index = np.where(mask)[0] rms_spec = rms.flattened(slc).value spec = cube.flattened(slc).value covar = build_covariance( spectrum=spec, rms=rms_spec, channel_correlation=channel_correlation, index=index) sigma_ew_err[y, x] = ( np.sum(covar) * dv**2 / (2 * np.pi * maxmap[y, x].value**2) + (sigma_ew[y, x].value**2 - sigma_ew[y, x].value * dv / np.sqrt(2 * np.pi)) * rms_at_max[0, y, x]**2)**0.5 sigma_ew_err = u.Quantity(sigma_ew_err, cube.spectral_axis.unit, copy=False) if unit is not None: sigma_ew_err = sigma_ew_err.to(unit) sigma_ewerr_projection = Projection(sigma_ew_err, wcs=sigma_ew.wcs, header=sigma_ew.header, meta=sigma_ew.meta) if outfile is not None: sigma_ewerr_projection = update_metadata(sigma_ewerr_projection, cube, error=True) writer(sigma_ewerr_projection, errorfile, overwrite=overwrite) # sigma_ewerr_projection.write(errorfile, overwrite=overwrite) # Do the conversion here to not mess up errors in units. if unit is not None: sigma_ew = sigma_ew.to(unit) if outfile is not None: sigma_ew = update_metadata(sigma_ew, cube) writer(sigma_ew, outfile, overwrite=overwrite) # sigma_ew.write(outfile, overwrite=True) if return_products and sigma_ewerr_projection is not None: return (sigma_ew, sigma_ewerr_projection) elif return_products and sigma_ewerr_projection is None: return (sigma_ew)
def write_tmax(cubein, outfile=None, errorfile=None, rms=None, channel_correlation=None, overwrite=True, unit=None, window=None, return_products=True): """ Write out Tmax map for a SpectralCube Keywords: --------- cube : SpectralCube (Masked) spectral cube to write a Tmax map outfile : str File name of output file errorfile : str File name of map for the uncertainty rms : SpectralCube Root-mean-square estimate of the error. This must have an estimate the noise level at all positions where there is signal, and only at those positions. channel_correlation : np.array One-dimensional array containing the channel-to-channel normalize correlation coefficients overwrite : bool Set to True (the default) to overwrite existing maps if present. unit : astropy.Unit Preferred unit for moment masks window : astropy.Quantity Spectral window over which the data should be smoothed return_products : bool Return products calculated in the map """ # hack the mask to span the spectral range of the mask but lose spatial information. mask = cubein.get_mask_array() mask_spec = np.any(mask, axis=(1, 2)) lo = np.min(np.where(mask_spec)) hi = np.max(np.where(mask_spec)) mask = np.zeros_like(mask, dtype='bool') mask[lo:hi, :, :] = True mask *= np.isfinite(cubein.unmasked_data[:]) new_cube = cubein.with_mask(mask, inherit_mask=False) # spectral smoothing if desired if window is not None: window = u.Quantity(window) from astropy.convolution import Box1DKernel dv = channel_width(new_cube) nChan = (window / dv).to(u.dimensionless_unscaled).value if nChan > 1: cube = new_cube.spectral_smooth(Box1DKernel(nChan)) rmsfac = 1 / np.sqrt(nChan) else: cube = new_cube rmsfac = 1.0 else: cube = new_cube rmsfac = 1.0 maxmap = cube.max(axis=0) if errorfile is not None and rms is None: logger.error("Tmax error requested but no RMS provided") if rms is not None: argmaxmap = cube.argmax(axis=0) rms = rms.with_mask(cube._mask.include(), inherit_mask=False) rms_at_max = np.take_along_axis(rms.filled_data[:], argmaxmap[np.newaxis, :, :], 0).value # rmsfac accounts for smoothing leading to reduction in rms # assuming channels are (nearly) independent rms_at_max = np.squeeze(rms_at_max) * rmsfac rms_at_max = u.Quantity(rms_at_max, cube.unit, copy=False) if unit is not None: rms_at_max = rms_at_max.to(unit) tmaxerr_projection = Projection(rms_at_max, wcs=maxmap.wcs, header=maxmap.header, meta=maxmap.meta) if errorfile is not None: tmaxerr_projection = update_metadata(tmaxerr_projection, cube, error=True) writer(tmaxerr_projection, errorfile, overwrite=overwrite) # tmaxerr_projection.write(errorfile, overwrite=overwrite) if unit is not None: maxmap = maxmap.to(unit) if outfile is not None: maxmap = update_metadata(maxmap, cube) writer(maxmap, outfile, overwrite=overwrite) # maxmap.write(outfile, overwrite=True) if return_products and tmaxerr_projection is not None: return (maxmap, tmaxerr_projection) elif return_products and tmaxerr_projection is None: return (maxmap)
def write_moment2(cube, rms=None, channel_correlation=None, outfile=None, errorfile=None, overwrite=True, unit=None, return_products=True): """ Write out linewidth (moment2-based) map for a SpectralCube Keywords: --------- cube : SpectralCube (Masked) spectral cube to write a Moment 2 (velocity dispersion) map outfile : str File name of output file errorfile : str File name of map for the uncertainty rms : SpectralCube Root-mean-square estimate of the error. This must have an estimate the noise level at all positions where there is signal, and only at those positions. channel_correlation : np.array One-dimensional array containing the channel-to-channel normalize correlation coefficients overwrite : bool Set to True (the default) to overwrite existing maps if present. unit : astropy.Unit Preferred unit for moment masks return_products : bool Return products calculated in the map """ mom2 = cube.linewidth_sigma() spaxis = cube.spectral_axis.value if errorfile is not None and rms is None: logger.error("Moment 2 error requested but no RMS provided") if rms is not None: mom2err = np.empty(mom2.shape) mom2err.fill(np.nan) rms = rms.with_mask(cube._mask.include(), inherit_mask=False) valid = np.isfinite(mom2) if channel_correlation is None: sum_T = cube.sum(axis=0).value mom1 = cube.moment1().value vval = spaxis numer = np.nansum(rms.filled_data[:].value**2 * ( (vval[:, np.newaxis, np.newaxis] - mom1[np.newaxis, :, :])**2 - (mom2.value)[np.newaxis, :, :]**2)**2, axis=0) mom2err = (numer / sum_T**2)**0.25 else: yy, xx = np.where(valid) for y, x in zip(yy, xx): slc = (slice(None), slice(y, y + 1, None), slice(x, x + 1, None)) mask = np.squeeze(cube.mask.include(view=slc)) index = np.where(mask)[0] rms_spec = rms.flattened(slc).value spec = cube.flattened(slc).value covar = build_covariance( spectrum=spec, rms=rms_spec, channel_correlation=channel_correlation, index=index) vval = spaxis[index] sum_T = np.sum(spec) sum_vT = np.sum(spec * vval) vbar = sum_vT / sum_T vdisp = (vval - vbar)**2 wtvdisp = np.sum(spec * vdisp) # Dear future self: There is no crossterm (error term from # vbar) since dispersion is at a minimum around vbar jacobian = (vdisp / sum_T - wtvdisp / sum_T**2) mom2err[y, x] = np.dot(np.dot(jacobian[np.newaxis, :], covar), jacobian[:, np.newaxis])**0.25 mom2err = u.Quantity(mom2err, cube.spectral_axis.unit, copy=False) if unit is not None: mom2err = mom2err.to(unit) mom2err_proj = Projection(mom2err, wcs=mom2.wcs, header=mom2.header, meta=mom2.meta) if errorfile is not None: mom2err_proj = update_metadata(mom2err_proj, cube, error=True) writer(mom2err_proj, errorfile, overwrite=overwrite) # mom2err_proj.write(errorfile, overwrite=overwrite) if unit is not None: mom2 = mom2.to(unit) if outfile is not None: mom2 = update_metadata(mom2, cube) writer(mom2, outfile, overwrite=overwrite) # mom2.write(outfile, overwrite=True) if return_products and mom2err_proj is not None: return (mom2, mom2err_proj) elif return_products and mom2err_proj is None: return (mom2)
def write_moment1_hybrid(cube, rms=None, channel_correlation=None, outfile=None, errorfile=None, overwrite=True, unit=None, return_products=True, strict_mom1=None, strict_emom1=None, broad_mom1=None, broad_emom1=None, broad_mom0=None, broad_emom0=None, vfield_prior=None, vfield_reject_thresh='30km/s', mom0_thresh=2.0, context=None): """Write out moment1 map using combination of other moment maps. This is a secondary moment that needs to be calculated in the context of other moments. Keywords: --------- cube : SpectralCube Included to keep same call signature but not used rms : SpectralCube Included to keep the same call signature but not used. channel_correlation : np.array Included to keep the same call signature but not used. outfile : str File name of output file errorfile : str File name of map for the uncertainty overwrite : bool Set to True (the default) to overwrite existing maps if present. unit : astropy.Unit Preferred unit for moment masks return_products : bool Return products calculated in the map strict_mom1 : str Moment tag for velocity field to be used as a high confidence map broad_mom1 : str Moment tag for velocity field for low confidence map broad_mom0 : str Moment tag to be used as an estimate of the signal for a S/N cut on where the broad_mom1 is valid. Also finds a noise estimate of the same and uses this for the Noise component vfield_prior : str Moment tag for low-resolution prior map of velocity field vfield_reject_thresh : astropy.units.Quantity The maximum difference between the broad field and the prior field in units that can convert to that of the velocity field. mom0_thresh : float S/N threshold for using a broad_mom0 estimate in the map """ # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Read in and align the data and tuning parameters # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # The threshold for outlier rejection from the prior velocity field vfield_reject_thresh = u.Quantity(vfield_reject_thresh) # Read the high confidence velocity map and error mom1strict = convert_and_reproject(strict_mom1, unit=unit) mom1strict_error = convert_and_reproject(strict_emom1, unit=unit) # Read the intensity map to be used as a prior mom0broad = convert_and_reproject(broad_mom0, template=mom1strict) mom0broad_error = convert_and_reproject(broad_emom0, template=mom1strict) # This broad moment 1 map will be used as a candidate velocity field mom1broad = convert_and_reproject(broad_mom1, template=mom1strict, unit=unit) mom1broad_error = convert_and_reproject(broad_emom1, template=mom1strict, unit=unit) # This prior velocity field will be used to reject outliers mom1prior = convert_and_reproject(vfield_prior, template=mom1strict, unit=unit) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Hybridize the low and high confidence moment maps # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Start with the high quality strict mask mom1hybrid = mom1strict.value # Candidate entries are places with a broad value valid_broad_mom1 = np.isfinite(mom1broad.value) # ... but not any strict value valid_broad_mom1[np.isfinite(mom1strict)] = False # If thresholding on intensity, apply that cut: if (mom0broad is not None): if (mom0broad_error is not None): # ... either as signal-to-noise valid_broad_mom1 *= \ (mom0broad.value > (mom0_thresh* mom0broad_error.value)) else: # ... or a simple intensity cut valid_broad_mom1 *= \ (mom0broad.value > mom0_thresh) # Thresholding on offset from vfield prior if mom1prior is not None: valid_broad_mom1 = ( valid_broad_mom1 * (np.abs(mom1broad - mom1prior) < vfield_reject_thresh)) # Fill in the still-valid locations in the hybrid mom1hybrid[valid_broad_mom1] = (mom1broad.value)[valid_broad_mom1] mom1hybrid = u.Quantity(mom1hybrid, unit) if unit is not None: mom1hybrid = mom1hybrid.to(unit) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Attach to WCS and write to disk # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Attach to WCS mom1hybrid_proj = Projection(mom1hybrid, wcs=mom1strict.wcs, header=mom1strict.header, meta=mom1strict.meta) # Write to disk if outfile is not None: writer(mom1hybrid_proj, outfile, overwrite=overwrite) # mom1hybrid_proj.write(outfile, # overwrite=overwrite) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Propagate errors from the input map to an error map # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= mom1hybrid_error_proj = None print(type(mom1broad_error), type(mom1strict_error)) if (type(mom1broad_error) is Projection and type(mom1strict_error) is Projection): mom1hybrid_error = mom1broad_error mom1hybrid_error[~np.isfinite(mom1hybrid.value)] = np.nan strictvals = np.isfinite(mom1strict_error.value) mom1hybrid_error[strictvals] = mom1strict_error[strictvals] if unit is not None: mom1hybrid_error = mom1hybrid_error.to(unit) mom1hybrid_error_proj = Projection(mom1hybrid_error, wcs=mom1strict.wcs, header=mom1strict.header, meta=mom1strict.meta) if errorfile is not None: mom1hybrid_error_proj = update_metadata(mom1hybrid_error_proj, cube, error=True) writer(mom1hybrid_error_proj, errorfile, overwrite=overwrite) # mom1hybrid_error_proj.write(errorfile, # overwrite=overwrite) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Return if requested # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= if return_products: if mom1hybrid_error_proj is not None: return (mom1hybrid_proj, mom1hybrid_error_proj) elif mom1hybrid_error_proj is None: return (mom1hybrid_proj) return ()
def test_casafeather(image_sz512as_pl1p5_fwhm2as_scale1as, sdfactor, lowpassfilterSD): tmp_path, input_fn, intf_fn, sd_fn = image_sz512as_pl1p5_fwhm2as_scale1as intf_hdu = fits.open(intf_fn)[0] sd_hdu = fits.open(sd_fn)[0] # Grab the rest frequency set in the header restfreq = (intf_hdu.header['RESTFRQ'] * units.Hz).to(units.GHz) # Feathering with CASA intf_fn_image = intf_fn.parent / intf_fn.name.replace(".fits", ".image") sd_fn_image = sd_fn.parent / sd_fn.name.replace(".fits", ".image") # CASA needs a posix string to work importfits(fitsimage=intf_fn.as_posix(), imagename=intf_fn_image.as_posix(), defaultaxes=True, defaultaxesvalues=['', '', f'{restfreq.value}GHz', 'I']) importfits(fitsimage=sd_fn.as_posix(), imagename=sd_fn_image.as_posix(), defaultaxes=True, defaultaxesvalues=['', '', f'{restfreq.value}GHz', 'I']) output_name = tmp_path / 'casafeathered.image' feather( imagename=output_name.as_posix(), highres=intf_fn_image.as_posix(), lowres=sd_fn_image.as_posix(), sdfactor=sdfactor, lowpassfiltersd=lowpassfilterSD, ) # Right now read as spectralcube despite being a 2D image. casa_feather_proj = SpectralCube.read(output_name)[0] # Feathering with uvcombine feathered_hdu = feather_simple(hires=intf_hdu, lores=sd_hdu, lowresscalefactor=sdfactor, lowpassfilterSD=lowpassfilterSD, deconvSD=False, return_hdu=True) uvcomb_feather_proj = Projection.from_hdu(feathered_hdu) diff = (casa_feather_proj - uvcomb_feather_proj).value # By-hand checks. Keep so we remember. # print("Proof that we have exactly reimplemented CASA's feather: ") # print("((casa-uvcombine)**2 / casa**2).sum() = {0}" # .format(((diff**2)/(casa_feather_proj.value**2)).sum())) # print("Maximum of abs(diff): {0}".format(np.abs(diff).max())) # Check for agreement within 0.05% if lowpassfilterSD: # assert np.abs(diff / casa_feather_proj.value).max() < 2e-4 assert np.abs(np.median(diff / casa_feather_proj.value)) < 5e-4 else: # assert np.abs(diff / casa_feather_proj.value).max() < 1e-7 assert np.abs(np.median(diff / casa_feather_proj.value)) < 5e-4
for res_type in res_types: print("Resolution {}".format(res_type)) if res_type == 'orig': filename = "{0}_{1}_mjysr_cutout.fits".format(gal.lower(), name) else: filename = "{0}_{1}_{2}_mjysr_cutout.fits".format(gal.lower(), name, res_type) if not os.path.exists(osjoin(data_path, gal, filename)): print("Could not find {}. Skipping".format(filename)) continue hdu = fits.open(osjoin(data_path, gal, filename)) proj = Projection.from_hdu(fits.PrimaryHDU(hdu[0].data.squeeze(), hdu[0].header)) # Attach equiv Gaussian beam # if res_type == 'orig': # proj = proj.with_beam(names[name]) # With and without 30 Dor for slice_name in lmc_mips24_slice: slicer = lmc_mips24_slice[slice_name] if res_type == 'orig': save_name = "{0}_{1}_{2}_mjysr.pspec.pkl".format(gal.lower(), name, slice_name) else: save_name = "{0}_{1}_{2}_{3}_mjysr.pspec.pkl".format(gal.lower(), name,
def convolve_projection(proj, newbeam, res_tol=0.0, min_coverage=0.8, append_raw=False, verbose=False, suppress_error=False): """ Convolve a 2D image to a specified beam. Very similar to `convolve_cube()`, but this function deals with 2D images (i.e., projections) rather than 3D cubes. Parameters ---------- proj : ~spectral_cube.Projection object Input 2D image newbeam : radio_beam.Beam object Target beam to convolve to res_tol : float, optional Tolerance on the difference between input/output resolution By default, a convolution is performed on the input image whenever its native resolution is different from (sharper than) the target resolution. Use this keyword to specify a tolerance on resolution, within which no convolution will be performed. For example, res_tol=0.1 will allow a 10% tolerance. min_coverage : float or None, optional When the convolution meets NaN values or edges, the output is calculated based on beam-weighted average. This keyword specifies the minimum beam covering fraction of valid (np.finite) values. All pixels with less beam covering fraction will be assigned NaNs. Default is 80% beam covering fraction (min_coverage=0.8). If the user would rather use the interpolation strategy in `astropy.convolution.convolve_fft`, set this keyword to None. Note that the NaN pixels will be kept as NaN. append_raw : bool, optional Whether to append the raw convolved image and weight image Default is not to append. verbose : bool, optional Whether to print the detailed processing information in terminal Default is to not print. suppress_error : bool, optional Whether to suppress the error when convolution is unsuccessful Default is to not suppress. Returns ------- outproj : Projection object or tuple Convolved 2D image (when append_raw=False), or a tuple comprising 3 images (when append_raw=True) """ from functools import partial from astropy.convolution import convolve_fft if min_coverage is None: # Skip coverage check and preserve NaN values. # This uses the default interpolation strategy # implemented in 'astropy.convolution.convolve_fft' convolve_func = partial(convolve_fft, preserve_nan=True, allow_huge=True, quiet=~verbose) else: # Do coverage check to determine the mask on the output convolve_func = partial(convolve_fft, nan_treatment='fill', boundary='fill', fill_value=0., allow_huge=True, quiet=~verbose) if (res_tol > 0) and (newbeam.major != newbeam.minor): raise ValueError("You cannot specify a non-zero resolution " "torelance if the target beam is not round") tol = newbeam.major * np.array([1-res_tol, 1+res_tol]) if ((tol[0] < proj.beam.major < tol[1]) and (tol[0] < proj.beam.minor < tol[1])): if verbose: print("Native resolution within tolerance - " "Copying original image...") my_append_raw = False newproj = proj.copy() else: if verbose: print("Convolving image...") try: convproj = proj.convolve_to(newbeam, convolve=convolve_func) if min_coverage is not None: # divide the raw convolved image by the weight image my_append_raw = True wtproj = Projection( np.isfinite(proj.data).astype('float'), wcs=proj.wcs, beam=proj.beam) wtproj = wtproj.convolve_to(newbeam, convolve=convolve_func) newproj = convproj / wtproj.hdu.data # mask all pixels w/ weight smaller than min_coverage threshold = min_coverage * u.dimensionless_unscaled newproj[wtproj < threshold] = np.nan else: my_append_raw = False newproj = convproj except ValueError as err: if suppress_error: return else: raise ValueError( "Unsuccessful convolution: {}\nOld: {}\nNew: {}" "".format(err, proj.beam, newbeam)) if append_raw and my_append_raw: return newproj, convproj, wtproj else: return newproj
from astropy import wcs from astropy.io import fits from astropy import stats import paths import pylab as pl from scipy.ndimage import map_coordinates import scipy.signal import reproject from files import b3_hires_cont, b6_hires_cont, b7_hires_cont from constants import source, extraction_path, origin, central_freqs # vmap produced by stacked_line_search.py vmap_name = paths.dpath('disk_velocity_map.fits') hdu = fits.open(vmap_name)[0] vmap = Projection.from_hdu(hdu) b3beam = radio_beam.Beam.from_fits_header( fits.getheader(paths.dpath(b3_hires_cont))) print("per-band continuum measurements in the spectral extraction aperture: ") for ii, contfn in enumerate((b3_hires_cont, b6_hires_cont, b7_hires_cont)): band = contfn[14:16] conthdu = fits.open(paths.dpath(contfn))[0] ww = wcs.WCS(conthdu.header) #vmap_proj,_ = reproject.reproject_interp(vmap.hdu, # ww, # shape_out=conthdu.data.shape)
sc1 = SpectralCube(data=cube1, wcs=WCS(header)) mask = LazyMask(np.isfinite, sc1) sc1 = sc1.with_mask(mask) # Set the scale for the purposes of the tests props1 = Moments(sc1, scale=0.003031065017916262 * u.Unit("")) # props1.make_mask(mask=mask) props1.make_moments() props1.make_moment_errors() dataset1 = props1.to_dict() moment0_hdu1 = fits.PrimaryHDU(dataset1["moment0"][0], header=dataset1["moment0"][1]) moment0_proj = Projection.from_hdu(moment0_hdu1) ############################################################################## path2 = os.path.join(turb_path, "data/dataset2.npz") dataset2 = np.load(path2) cube2 = np.empty((500, 32, 32)) count = 0 for posn, kept in zip(*dataset2["channels"]): posn = int(posn) if kept: cube2[posn, :, :] = dataset2["cube"][count, :, :] count += 1
(r'Ratio Fit: $\sigma_{\rm CO} = 0.56\, \sigma_{\rm HI}$', r'$\sigma_{\rm CO} = \sigma_{\rm HI}$'), frameon=True, loc=(0.56, 0.6), ) # plt.tight_layout() plt.subplots_adjust(hspace=0.03, wspace=0.03) plt.savefig(osjoin(fig_path, "sigma_HI_vs_H2_w_fit_cornerplot.png")) plt.savefig(osjoin(fig_path, "sigma_HI_vs_H2_w_fit_cornerplot.pdf")) plt.close() # What does this relation look like for line widths from the second moment co_lwidth = Projection.from_hdu( fits.open( iram_co21_14B088_data_path("m33.co21_iram.14B-088_HI.lwidth.fits"))[0]) hi_lwidth = Projection.from_hdu( fits.open(fourteenB_wGBT_HI_file_dict['LWidth'])[0]) co_lwidth_vals = co_lwidth.value[tab['ypts'][good_pts], tab['xpts'][good_pts]] / 1000. hi_lwidth_vals = hi_lwidth.value[tab['ypts'][good_pts], tab['xpts'][good_pts]] / 1000. # How bad is the relation between the 2nd moment line widths hist2d(hi_lwidth_vals, co_lwidth_vals, bins=13, data_kwargs={"alpha": 0.5}) plt.plot([4, 16], [4. * slope_ratio, 16. * slope_ratio], '--', color=sb.color_palette()[1],
def write_vquad(cubein, outfile=None, errorfile=None, rms=None, channel_correlation=None, overwrite=True, unit=None, window=None, maxshift=0.5, return_products=True): """ Write out velocity map at max brightness temp for a SpectralCube using the quadratic peak interpolation Keywords: --------- cube : SpectralCube (Masked) spectral cube to write a Vmax map outfile : str File name of output file errorfile : str File name of map for the uncertainty rms : SpectralCube Root-mean-square estimate of the error. This must have an estimate the noise level at all positions where there is signal, and only at those positions. channel_correlation : np.array One-dimensional array containing the channel-to-channel normalize correlation coefficients overwrite : bool Set to True (the default) to overwrite existing maps if present. unit : astropy.Unit Preferred unit for moment masks window : astropy.Quantity Spectral window over which the data should be smoothed maxshift : np.float Maximum number of channels that the algorithm can shift the peak estimator (default = 0.5). Set to None to suppress clipping. return_products : bool Return products calculated in the map """ from scipy.interpolate import interp1d if type(window) is u.Quantity: from astropy.convolution import Box1DKernel dv = channel_width(cubein) nChan = (window / dv).to(u.dimensionless_unscaled).value if nChan > 1: cube = cubein.spectral_smooth(Box1DKernel(nChan)) else: cube = cubein else: cube = cubein spaxis = cube.spectral_axis.value pixinterp = interp1d(np.arange(spaxis.size), spaxis) maxmap = cube.max(axis=0) argmaxmap = cube.argmax(axis=0) argmaxmap = np.clip(argmaxmap, 1, cube.shape[0] - 2) Tup = np.take_along_axis(cube.filled_data[:], argmaxmap[np.newaxis, :, :] + 1, 0).value Tdown = np.take_along_axis(cube.filled_data[:], argmaxmap[np.newaxis, :, :] - 1, 0).value Tup = np.squeeze(np.nan_to_num(Tup)) Tup[Tup < 0] = 0 Tdown = np.squeeze(np.nan_to_num(Tdown)) Tdown[Tdown < 0] = 0 delta = -1 * ((Tup - Tdown) / (Tup + Tdown - 2 * maxmap.value)) if maxshift is not None: delta = np.clip(delta, -maxshift, maxshift) peakchan = argmaxmap + delta vmaxmap = np.empty(maxmap.shape) vmaxmap.fill(np.nan) good = np.isfinite(maxmap) vmaxmap[good] = u.Quantity(pixinterp(peakchan[good]), unit) vmaxmap[~np.isfinite(maxmap)] = np.nan if errorfile is not None and rms is None: logger.error("Vquad error requested but no RMS provided") if rms is not None: rms = rms.with_mask(cube._mask.include(), inherit_mask=False) dv = channel_width(cube) RMSup = np.take_along_axis(rms.filled_data[:], argmaxmap[np.newaxis, :, :] + 1, 0).value RMSdown = np.take_along_axis(rms.filled_data[:], argmaxmap[np.newaxis, :, :] - 1, 0).value RMSmax = np.take_along_axis(rms.filled_data[:], argmaxmap[np.newaxis, :, :], 0).value denom = (Tup + Tdown - 2 * maxmap.value) j1 = (1 / denom - (Tup - Tdown) / denom**2) j2 = (2 * (Tup - Tdown) / denom**2) j3 = (-1 / denom - (Tup - Tdown) / denom**2) jacobian = np.r_[j1[np.newaxis, :, :], j2[np.newaxis, :, :], j3[np.newaxis, :, :]] if ((channel_correlation is None) or len(channel_correlation) == 1): covar = np.r_[RMSup, RMSmax, RMSdown] covar = covar**2 error = np.einsum('i...,i...', jacobian**2, covar) else: if len(channel_correlation) == 2: ccor = np.r_[channel_correlation, np.array([0])] else: ccor = channel_correlation[0:3] corrmat = ccor[np.array([[0, 1, 2], [1, 0, 1], [2, 1, 0]])] rmsvec = np.r_[RMSup, RMSmax, RMSdown] # They never should have taught me how to do this. covar = np.einsum('ij,ilm,jlm->ijlm', corrmat, rmsvec, rmsvec) error = np.einsum('ilm,jlm,ijlm->lm', jacobian, jacobian, covar) if maxshift is not None: error = np.clip(error, -maxshift, maxshift) vquaderr = error * dv if unit is not None: vquaderr = vquaderr.to(unit) vquaderr_projection = Projection(vquaderr, wcs=maxmap.wcs, header=maxmap.header, meta=maxmap.meta) if errorfile is not None: vquaderr_projection = update_metadata(vquaderr_projection, cube, error=True) writer(vquaderr_projection, errorfile, overwrite=overwrite) # vquaderr_projection.write(errorfile, overwrite=overwrite) vmaxmap = u.Quantity(vmaxmap, cube.spectral_axis.unit) if unit is not None: vmaxmap = vmaxmap.to(unit) vmaxmap_projection = Projection(vmaxmap, wcs=maxmap.wcs, header=maxmap.header, meta=maxmap.meta) if outfile is not None: vmaxmap_projection = update_metadata(vmaxmap_projection, cube) writer(vmaxmap_projection, outfile, overwrite=overwrite) # vmaxmap_projection.write(outfile, overwrite=overwrite) if return_products and vquaderr_projection is not None: return (vmaxmap_projection, vquaderr_projection) elif return_products and vquaderr_projection is None: return (vmaxmap_projection)
for gal in gals: dist = gals[gal] # Load in the dust column density maps to set the allowed # spatial region filename_coldens = glob( osjoin(data_path, gal, "*dust.surface.density*.fits")) hdu_coldens = fits.open(filename_coldens[0]) pad_size = 0.5 * u.arcmin proj_coldens = Projection.from_hdu( fits.PrimaryHDU(hdu_coldens[0].data[0].squeeze(), hdu_coldens[0].header)) # Get minimal size proj_coldens = proj_coldens[nd.find_objects( np.isfinite(proj_coldens))[0]] # Get spatial extents. # NOTE: extrema for 2D objects broken in spectral-cube! Need to fix... lat, lon = proj_coldens.spatial_coordinate_map lat_min = lat.min() - pad_size lat_max = lat.max() + pad_size lon_min = lon.min() - pad_size lon_max = lon.max() + pad_size def spat_mask_maker(lat_map, lon_map):
def write_moment0(cube, rms=None, channel_correlation=None, outfile=None, errorfile=None, overwrite=True, unit=None, include_limits=True, line_width=10 * u.km / u.s, return_products=True): """Write out moment0 map for a SpectralCube Keywords: --------- cube : SpectralCube (Masked) spectral cube to write a moment0 map outfile : str File name of output file errorfile : str File name of map for the uncertainty rms : SpectralCube Root-mean-square estimate of the error. This must have an estimate the noise level at all positions where there is signal, and only at those positions. channel_correlation : np.array One-dimensional array containing the channel-to-channel normalize correlation coefficients overwrite : bool Set to True (the default) to overwrite existing maps if present. unit : astropy.Unit Preferred unit for moment masks include_limits : bool If true: For masked lines of sight inside the data, set the moment0 value to 0 and the error to a value of 1sigma over line_width as specified below. line_width : astropy.Quantity Assumed line width for moment0 upper limit. Default = 10 km/s return_products : bool Return products calculated in the map """ # Spectral cube collapse routine. Applies the masked, automatically mom0 = cube.moment0() valid = np.isfinite(mom0) if include_limits: observed = np.any(np.isfinite(cube._data), axis=0) mom0[np.logical_and(np.isnan(mom0), observed)] = 0.0 # Handle the error. mom0err_proj = None if errorfile is not None and rms is None: logger.error("Moment 0 error requested but no RMS provided") if rms is not None: # Initialize the error map mom0err = np.empty(mom0.shape) mom0err.fill(np.nan) # Note the channel width dv = channel_width(cube) if include_limits: rmsmed = np.nanmedian(rms.filled_data[:].value, axis=0) mom0err[observed] = (rmsmed[observed] * (np.abs( line_width / dv).to(u.dimensionless_unscaled).value)**0.5) # Make a masked version of the noise cube rms = rms.with_mask(cube._mask.include(), inherit_mask=False) if channel_correlation is None: sumofsq = (rms * rms).sum(axis=0) mom0err[valid] = np.sqrt(sumofsq[valid]) else: # Iterates over the cube one ray at a time yy, xx = np.where(valid) for y, x in zip(yy, xx): slc = (slice(None), slice(y, y + 1, None), slice(x, x + 1, None)) mask = np.squeeze(cube.mask.include(view=slc)) index = np.where(mask)[0] # One dimensional versions of the data and noise rms_spec = rms.flattened(slc).value spec = cube.flattened(slc).value # Build a covariance matrix given the channel correlation covar = build_covariance( spectrum=spec, rms=rms_spec, channel_correlation=channel_correlation, index=index) # Collapse the covariance matrix into an integrated moment map mom0err[y, x] = (np.sum(covar))**0.5 # Multiply by the channel width and assign correct units mom0err = u.Quantity(mom0err * dv.value, cube.unit * dv.unit, copy=False) # Convert units if request if unit is not None: mom0err = mom0err.to(unit) # Convert from an array into a spectral-cube projection that # shares metadata with the moment map mom0err_proj = Projection(mom0err, wcs=mom0.wcs, header=mom0.header, meta=mom0.meta) # Write to disk if requested if errorfile is not None: mom0err_proj = update_metadata(mom0err_proj, cube, error=True) writer(mom0err_proj, errorfile, overwrite=overwrite) # mom0err_proj.write(errorfile, overwrite=overwrite) else: mom0err_proj = None # Convert units if requested if unit is not None: mom0 = mom0.to(unit) # If requested, write to disk if outfile is not None: mom0 = update_metadata(mom0, cube) writer(mom0, outfile, overwrite=overwrite) # mom0.write(outfile, overwrite=overwrite) # If requested, return if return_products: return (mom0, mom0err_proj)