Example #1
0
def spectrum_aperture_technical():
    """
    Compute residual corrected spectrum. Plot spectra extracted from various maps for comparison.
    Also plot clean-to-dirty beam ratio epsilon.
    """
    filename = "./data/Pisco.cube.50kms.image.fits"
    ra, dec = (205.533741, 9.477317341)  # [degrees] we know where the source is
    radius = 1.3  # [arcsec] we know the size of the aperture we want
    scale = 1e3  # map units are Jy/beam, will use to scale fluxes to mJy

    # load the cube and perform residual scaling spectrum extraction
    mcub = MultiCube(filename)  # because the cubes follow a naming convention, will open several present cubes
    spectrum, err, tab = mcub.spectrum_corrected(ra=ra, dec=dec, radius=radius, calc_error=True)
    freqs = mcub.freqs  # this will be the x-axis

    # tab.write("spectrum.txt", format="ascii.fixed_width", overwrite=True)  # save results in a human readable format

    # plot the spectrum, fill around the fitted continuum value
    fig, axes = plt.subplots(figsize=(4.8, 4.8), nrows=2, ncols=1, sharex=True, gridspec_kw={'height_ratios': [3, 1]})
    ax = axes[0]
    ax.set_title("Spectrum with and without correction")

    # the table returned from spectrum_corrected contains fluxes measured in different map
    # as well as clean-to-dirty beam ratios

    spectrum_dirty = tab["flux_dirty"]
    ax.plot(freqs, spectrum_dirty * scale, color="black", drawstyle='steps-mid', lw=0.75)
    ax.fill_between(freqs, spectrum_dirty * scale, 0, color="firebrick", step='mid', lw=0, alpha=1, label="Dirty")

    spectrum_uncorrected = tab["flux_image"]
    ax.plot(freqs, spectrum_uncorrected * scale, color="black", drawstyle='steps-mid', lw=0.75)
    ax.fill_between(freqs, spectrum_uncorrected * scale, 0, color="forestgreen", step='mid', lw=0, alpha=1,
                    label="Uncorrected")

    ax.plot(freqs, spectrum * scale, color="black", drawstyle='steps-mid', lw=0.75)
    ax.fill_between(freqs, spectrum * scale, 0, color="skyblue", step='mid', lw=0, alpha=1, label="Corrected")

    ax.set_xlim(freqs[0], freqs[-1])
    ax.tick_params(direction='in', which="both")
    ax.set_ylabel("Aperture flux density (mJy)")
    ax.legend(frameon=False)  # loc="upper right"

    ax2 = axes[1]
    # epsilon_fix was estimated from higher S/N channels and applied on all channels
    ax2.axhline(tab["epsilon_fix"][0], color="skyblue", lw=1)
    ax2.plot(freqs, tab["epsilon"], lw=0, marker="o", ms=1, color="black")

    ax2.tick_params(direction='in', which="both")
    ax2.set_xlabel("Frequency (GHz)")
    ax2.set_ylabel("Clean-to-dirty\nbeam ratio: " + r"$\epsilon$")
    ax2.set_ylim(-1.5, 1.5)

    plt.savefig("./plots/spectrum_aperture_technical.pdf", bbox_inches="tight")  # save plot
    plt.savefig("./thumbnails/spectrum_aperture_technical.png", bbox_inches="tight", dpi=72)  # web raster version

    plt.show()
Example #2
0
def growing_aperture_paper():
    filename = "./data/Pisco.cii.455kms.image.fits"
    ra, dec = (205.533741, 9.477317341)  # we know where the source is
    redshift = (1900.538 / 222.547) - 1  # redshift from the observed line peak, z= freq_rest_CII / freq_obs - 1
    aper_rad = 1.3  # final manually chosen aperture radius

    mcub = MultiCube(filename)  # load maps

    # This map is a single channel collapse over a [CII] emission line, total width of 455 km/s
    # If line fluxes in units of Jy.km/s are preferred, use the lower scaling
    scale = mcub["image"].deltavel()  # channel width in kms

    radius, flux, err, tab = mcub.growing_aperture_corrected(ra=ra, dec=dec, maxradius=3, calc_error=True)

    # tab.write("growth.txt", format="ascii.fixed_width", overwrite=True)  # save results in a human readable format

    fig, ax = plt.subplots(figsize=(4.8, 3))
    ax.plot(radius, flux * scale, color="firebrick", lw=2, label="Corrected")
    ax.fill_between(radius, (flux - err) * scale, (flux + err) * scale, color="firebrick", lw=0, alpha=0.2)
    ax.plot(radius, tab["flux_image"] * scale, label="Uncorrected", ls="--", color="gray")

    ax.axvline(aper_rad, color="gray", lw=0.75, ls=":", label="Chosen aperture size")

    # Could obtain just the single flux value at given aper_rad with
    # flux, err, tab = mcub.spectrum_corrected(ra=ra, dec=dec, radius=aper_rad, calc_error=True)

    # print(flux*scale,err*scale)
    ax.tick_params(direction='in', which="both")
    ax.set_xlabel("Aperture radius (arcsec)")
    ax.set_ylabel("Line flux density (Jy km/s)")
    ax.set_xlim(0, 3)
    ax.legend(loc="lower right", frameon=False)

    # add physical distances scale
    kpc_per_arcsec = iftools.arcsec2kpc(redshift)

    ax2 = ax.twiny()
    ax2.set_xlim(ax.get_xlim()[0] * kpc_per_arcsec, ax.get_xlim()[1] * kpc_per_arcsec)
    ax2.set_xlabel("Radius (kpc)")
    ax2.tick_params(direction='in', which="both")

    plt.savefig("./plots/growing_aperture_paper.pdf", bbox_inches="tight")  # save plot
    plt.savefig("./thumbnails/growing_aperture_paper.png", bbox_inches="tight", dpi=72)  # web raster version

    plt.show()
Example #3
0
def growing_aperture_technical():
    """
    Compute curve of growths in multiple maps up to some maximum radius.
    Derive corrected flux using residual scaling.
    """
    filename = "./data/Pisco.cii.455kms.image.fits"
    ra, dec = (205.533741, 9.477317341)  # we know where the source is
    scale = 1e3  # map units are Jy/beam, will use to scale fluxes to mJy/beam

    mcub = MultiCube(filename)  # load maps
    radius, flux, err, tab = mcub.growing_aperture_corrected(ra=ra, dec=dec, maxradius=3, calc_error=True)

    # tab.write("growth.txt", format="ascii.fixed_width", overwrite=True)  # save results in a human readable format

    fig, ax = plt.subplots(figsize=(4.8, 3))
    ax.set_title("Curves of growth")
    ax.plot(radius, flux * scale, color="firebrick", lw=2, label="Corrected")
    ax.fill_between(radius, (flux - err) * scale, (flux + err) * scale, color="firebrick", lw=0, alpha=0.2)

    ax.plot(radius, tab["flux_dirty"] * scale, label="Dirty", color="black", ls=":")
    ax.plot(radius, tab["flux_clean"] * scale, label="Cleaned components only", ls="-.", color="navy")
    ax.plot(radius, tab["flux_residual"] * scale, label="Residual", ls="--", color="orange")
    ax.plot(radius, tab["flux_image"] * scale, label="Uncorrected: clean + residual", dashes=[10, 3],
            color="forestgreen")
    ax.plot(radius, tab["epsilon"], color="gray", label="Clean-to-dirty beam ratio")
    ax.axhline(0, color="gray", lw=0.5, ls=":")

    ax.tick_params(direction='in', which="both")
    ax.set_xlabel("Radius (arcsec)")
    ax.set_ylabel("Cumulative flux density (mJy)")
    ax.tick_params(direction='in', which="both")
    ax.set_xlim(0, 3)

    ax.legend(bbox_to_anchor=(1, 0.8))

    plt.savefig("./plots/growing_aperture_technical.pdf", bbox_inches="tight")  # save plot
    plt.savefig("./thumbnails/growing_aperture_technical.png", bbox_inches="tight", dpi=72)  # web raster version

    plt.show()
Example #4
0
def spectrum_aperture_paper():
    """
    Compute residual corrected spectrum. Fit a Gaussian plus a continuum.
    Generate paper quality plot.
    """
    filename = "./data/Pisco.cube.50kms.image.fits"
    ra, dec = (205.533741, 9.477317341)  # [degrees] we know where the source is
    radius = 1.3  # [arcsec] we know the size of the aperture we want
    scale = 1e3  # map units are Jy/beam, will use to scale fluxes to mJy

    # load the cube and perform residual scaling spectrum extraction
    mcub = MultiCube(filename)  # because the cubes follow a naming convention, will open several present cubes
    spectrum, err, tab = mcub.spectrum_corrected(ra=ra, dec=dec, radius=radius, calc_error=True)
    freqs = mcub.freqs  # this will be the x-axis

    # fit the spectrum with a Gaussian on top of a constant continuum, initial fit parameters (p0) must be set manually
    popt, pcov = curve_fit(iftools.gausscont, freqs, spectrum, p0=(1, 5, 222.5, 0.2), sigma=err, absolute_sigma=True)
    cont, amp, nu, sigma = popt
    cont_err, amp_err, nu_err, sigma_err = np.sqrt(np.diagonal(pcov))
    # compute some further numbers from the fit
    sigma_kms = iftools.ghz2kms(sigma, nu)
    fwhm_kms = iftools.sig2fwhm(sigma_kms)
    fwhm_err_kms = iftools.sig2fwhm(iftools.ghz2kms(sigma_err, nu))
    integral_fit = amp * sigma_kms * np.sqrt(2 * np.pi)
    integral_err = integral_fit * np.sqrt((sigma_err / sigma) ** 2 + (nu_err / nu) ** 2 + (amp_err / amp) ** 2)

    txt = "[CII] Flux = " + str(iftools.sigfig(integral_fit, 2)) \
          + r" $\pm$ " + str(iftools.sigfig(integral_err, 1)) + " Jy km/s\n" \
          + "[CII] FWHM = " + str(iftools.sigfig(int(fwhm_kms), 2)) \
          + r" $\pm$ " + str(iftools.sigfig(int(fwhm_err_kms), 1)) + " km/s\n" \
          + "Freq = " + str(iftools.sigfig(nu, 6)) \
          + r" $\pm$ " + str(iftools.sigfig(nu_err, 1)) + " GHz\n" \
          + "Continuum = " + str(iftools.sigfig(cont * scale, 2)) \
          + r" $\pm$ " + str(iftools.sigfig(cont_err * scale, 1)) + " mJy\n"

    # print("Gaussian fit:")
    # print("Flux = " + str(iftools.sigfig(integral_fit, 2)) + " +- " + str(iftools.sigfig(integral_err, 1)) + " Jy.km/s")
    # print("FWHM = " + str(iftools.sigfig(fwhm_kms, 2)) + " +- " + str(iftools.sigfig(fwhm_err_kms, 1)) + " km/s")
    # print("Freq = " + str(iftools.sigfig(nu, 7)) + " +- " + str(iftools.sigfig(nu_err, 1)) + " GHz")

    # plot the spectrum, fill around the fitted continuum value
    fig, ax = plt.subplots(figsize=(4.8, 3))
    ax.plot(freqs, spectrum * scale, color="black", drawstyle='steps-mid', lw=0.75)
    ax.fill_between(freqs, spectrum * scale, cont * scale, color="skyblue", step='mid', lw=0, alpha=0.3)

    ax.text(0.98, 0.95, txt, va='top', ha='right', transform=ax.transAxes)

    # Plot the uncorrected specturum as well
    # ax.plot(freqs, tab["flux_image"] * scale, color="black", drawstyle='steps-mid', lw=0.5, ls="--")

    # plot Gaussian fit
    x_gauss = np.linspace(freqs[0], freqs[-1], 1000)
    y_gauss = iftools.gausscont(x_gauss, *popt)
    ax.plot(x_gauss, y_gauss * scale, color="firebrick")

    # add velocity axis based around the fitted peak
    vels = mcub.cubes["image"].vels(nu)
    ax2 = ax.twiny()

    # match ranges of the two axes
    ax.set_xlim(freqs[0], freqs[-1])
    ax2.set_xlim(vels[0], vels[-1])

    # add axis labels
    ax.tick_params(direction='in', which="both")
    ax.set_xlabel("Frequency (GHz)")
    ax.set_ylabel("Aperture flux density (mJy)")
    ax2.tick_params(direction='in', which="both")
    ax2.set_xlabel(r"Velocity (km s$^{-1}$)")

    # add the zero line
    ax.axhline(0, color="gray", lw=0.5, ls=":")

    plt.savefig("./plots/spectrum_aperture_paper.pdf", bbox_inches="tight")  # save plot
    plt.savefig("./thumbnails/spectrum_aperture_paper.png", bbox_inches="tight", dpi=72)  # web raster version

    plt.show()
Example #5
0
def map_technical():
    """
    Plot several maps generated in the cleaning process (CASA tclean outputs).
    """
    filename = "./data/Pisco.cii.455kms.image.fits"
    ra, dec, freq = (205.533741, 9.477317341, 222.547)  # we know where the source is
    cutout = 2.5  # arcsec, check that it is smaller than the image!
    # scale = 1e3  # Jy/beam to mJy/beam

    ch = 0  # channel to plot (for simple 2D maps, the first channel is the only channel)

    mcub = MultiCube(filename)
    mcub.make_clean_comp()  # generate clean component map

    fig, axes = plt.subplots(figsize=(6, 4), nrows=2, ncols=3, sharex=True, sharey=True)

    # fig = plt.figure(figsize=figsize)
    # grid = ImageGrid(fig, 111, nrows_ncols=(nrows, ncols), axes_pad=0.05, share_all=True,
    # 				 cbar_location="right", cbar_mode="single", cbar_size="3%", cbar_pad=0.05)

    # save a reference to the main image for easier use
    cub = mcub["image"]
    # get the extent of the cutouts
    px, py = cub.radec2pix()  # do not give coordinates, take the central pixel
    r = int(np.round(cutout * 1.05 / cub.pixsize))  # slightly larger cutout than required for edge bleeding
    edgera, edgedec = cub.pix2radec([px - r, px + r], [py - r, py + r])  # coordinates of the two opposite corners
    ra, dec = cub.pix2radec(px, py)
    extent = [(edgera - ra) * 3600, (edgedec - dec) * 3600]
    extent = extent[0].tolist() + extent[1].tolist()  # concat two lists

    # Image map
    ax = axes[0, 0]
    subim = mcub["image"].im[px - r:px + r + 1, py - r:py + r + 1, ch]  # scale units
    vmax = np.nanmax(subim)
    vmin = -0.1 * vmax
    ax.imshow(subim.T, origin='lower', cmap="RdBu_r", vmin=vmin, vmax=vmax, extent=extent)
    ax.set_title("Cleaned")

    # set limits to exact cutout size
    ax.set_xlim(cutout, -cutout)
    ax.set_ylim(-cutout, cutout)

    # calc rms and plot contours
    # rms = mcub["image"].rms[ch]
    # ax.contour(subim.T, extent=extent, colors="gray", levels=np.array([-8, -4, -2]) * rms, zorder=1,
    # 		   linewidths=0.5, linestyles="--")
    # ax.contour(subim.T, extent=extent, colors="black", levels=np.array([2, 4, 8, 16, 32]) * rms, zorder=1,
    # 		   linewidths=0.5, linestyles="-")

    # add beam, angle is between north celestial pole and major axis, angle increases toward increasing RA
    ellipse = Ellipse(xy=(cutout * 0.8, -cutout * 0.8),
                      width=cub.beam["bmin"], height=cub.beam["bmaj"], angle=-cub.beam["bpa"],
                      edgecolor='black', fc='w', lw=0.75)
    ax.add_patch(ellipse)

    # Dirty map
    ax = axes[0, 1]
    subim = mcub["dirty"].im[px - r:px + r + 1, py - r:py + r + 1, ch]
    ax.imshow(subim.T, origin='lower', cmap="RdBu_r", vmin=vmin, vmax=vmax, extent=extent)
    ax.set_title("Dirty")

    # Residual map
    ax = axes[0, 2]
    subim = mcub["residual"].im[px - r:px + r + 1, py - r:py + r + 1, ch]
    ax.imshow(subim.T, origin='lower', cmap="RdBu_r", vmin=vmin, vmax=vmax, extent=extent)
    ax.set_title("Residual")

    # Clean components map
    ax = axes[1, 0]
    subim = mcub["clean.comp"].im[px - r:px + r + 1, py - r:py + r + 1, ch]
    # Used generated map "clean.comp", alternatively, plot the difference directly
    # subim = (mcub["image"].im - mcub["residual"].im)[px - r:px + r + 1, py - r:py + r + 1, ch]
    ax.imshow(subim.T, origin='lower', cmap="RdBu_r", vmin=vmin, vmax=vmax, extent=extent)
    ax.set_title("Clean component")

    # model
    ax = axes[1, 1]
    subim = mcub["model"].im[px - r:px + r + 1, py - r:py + r + 1, ch]
    # model has units of Jy/pixel so generate different maximums here
    vmax = np.nanmax(subim)
    vmin = -0.1 * vmax
    ax.imshow(subim.T, origin='lower', cmap="RdBu_r", vmin=vmin, vmax=vmax, extent=extent)
    ax.set_title("Model")

    # PSF
    ax = axes[1, 2]
    subim = mcub["psf"].im[px - r:px + r + 1, py - r:py + r + 1, ch]
    ax.imshow(subim.T, origin='lower', cmap="RdBu_r", vmin=-0.05, vmax=0.5, extent=extent)
    ax.set_title("PSF")

    # PB
    # Not needed for targeted obs where the source is in the phase center (PB = 1)
    # ax = axes[1, 2]
    # subim = mcub["pb"].im[px - r:px + r + 1, py - r:py + r + 1, ch]
    # ax.imshow(subim.T, origin='lower', cmap="RdBu_r", vmin=0.95, vmax=1, extent=extent)
    # ax.set_title("PB")

    plt.savefig("./plots/map_technical.pdf", bbox_inches="tight", dpi=600)  # need higher dpi for crisp data pixels
    plt.savefig("./thumbnails/map_technical.png", bbox_inches="tight", dpi=72)  # web raster version

    plt.show()