def spectrum_aperture(): """ Extract aperture spectrum at the specified position. """ filename = "./data/Pisco.cube.50kms.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 cub = Cube(filename) aper = 1.3 flux, err = cub.aperture_value(ra=ra, dec=dec, radius=aper, calc_error=True) # flux, err _ = cub.spectrum(ra=ra, dec=dec, radius=1.3, calc_error=True) # alternatively fig, ax = plt.subplots(figsize=(4.8, 3)) ax.set_title("Integrated aperture spectrum") ax.plot(cub.freqs, flux * scale, color="black", drawstyle='steps-mid', lw=0.75, label="Spectrum within r=" + str(aper) + '"') ax.fill_between(cub.freqs, flux * scale, 0, color="skyblue", step='mid', lw=0, alpha=0.3) ax.plot(cub.freqs, err * scale, color="gray", ls=":", label=r"1$\sigma$ error") # 1sigma error ax.tick_params(direction='in', which="both") ax.set_xlabel("Frequency (GHz)") ax.set_ylabel("Aperture flux density (mJy)") ax.legend(frameon=False) plt.savefig("./plots/spectrum_aperture.pdf", bbox_inches="tight") # save plot plt.savefig("./thumbnails/spectrum_aperture.png", bbox_inches="tight", dpi=72) # web raster version plt.show()
def spectrum_single_pixel(): """ Extract the spectrum from the central pixel. """ filename = "./data/Pisco.cube.50kms.image.fits" scale = 1e3 # map units are Jy/beam, will use to scale fluxes to mJy cub = Cube(filename) flux = cub.single_pixel_value() # without coords gv=iven, use central pixel in the map # flux, _, _ = cub.spectrum() # alternatively fig, ax = plt.subplots(figsize=(4.8, 3)) ax.set_title("Single pixel spectrum") ax.plot(cub.freqs, flux * scale, color="black", drawstyle='steps-mid', lw=0.75, label="Spectrum") ax.fill_between(cub.freqs, flux * scale, 0, color="skyblue", step='mid', lw=0, alpha=0.3) ax.plot(cub.freqs, cub.rms * scale, color="gray", ls=":", label="Noise rms") ax.tick_params(direction='in', which="both") ax.set_xlabel("Frequency (GHz)") ax.set_ylabel("Flux density (mJy / beam)") ax.legend(frameon=False) plt.savefig("./plots/spectrum_single_pixel.pdf", bbox_inches="tight") # save plot plt.savefig("./thumbnails/spectrum_single_pixel.png", bbox_inches="tight", dpi=72) # web raster version plt.show()
def growing_aperture(): """ Compute azimuthally averaged profile and the curve of growth (cumulative flux in aperture) as a function of radius. """ filename = "./data/Pisco.cii.455kms.image.fits" ra, dec = (205.533741, 9.477317341) # we know where the source is cub = Cube(filename) # load the map scale = 1e3 # map units are Jy/beam, will use to scale fluxes to mJy/beam # 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 = cub.deltavel() # channel width in kms, will scale to Jy/beam.km/s (profile) and Jy.km/s (cumulative) radius, profile, err = cub.profile_r(ra=ra, dec=dec, maxradius=3, calc_error=True) # or alternatively, with same results: # radius, flux, err, _ = cub.growing_aperture(ra=ra, dec=dec, maxradius=3, calc_error=True, profile=True) print("rms mjy", cub.rms * scale) print("err[0]", err[0] * scale) fig, ax = plt.subplots(figsize=(4.8, 3)) col = "navy" ax.plot(radius, profile * scale, color=col) ax.fill_between(radius, (profile - err) * scale, (profile + err) * scale, color=col, lw=0, alpha=0.2) ax.tick_params(direction='in', which="both") ax.set_xlabel("Radius (arcsec)") ax.set_ylabel("Azimuthal average (mJy/beam)", color=col) ax.tick_params(axis='y', colors=col) ax.set_xlim(0, 3) radius, flux, err = cub.aperture_r(ra=ra, dec=dec, maxradius=3, calc_error=True) # radius, flux, err, _ = cub.growing_aperture(ra=ra, dec=dec, maxradius=3, calc_error=True) # alternatively col = "firebrick" ax2 = ax.twinx() ax2.plot(radius, flux * scale, color=col) ax2.fill_between(radius, (flux - err) * scale, (flux + err) * scale, color=col, lw=0, alpha=0.2) ax2.tick_params(direction='in', which="both") ax2.set_ylabel("Cumulative flux density (mJy)", color=col) ax2.tick_params(axis='y', colors=col) plt.savefig("./plots/growing_aperture.pdf", bbox_inches="tight") # save plot plt.savefig("./thumbnails/growing_aperture.png", bbox_inches="tight", dpi=72) # web raster version plt.show()
def growing_aperture_psf(): """ Compute azimuthally averaged profile and the curve of growth (cumulative flux in aperture) as a function of radius. Applied to point spread function (the dirty beam). """ filename = "./data/Pisco.cii.455kms.psf.fits" scale = 1 # no need to scale units, PSF is 1 at its maximum by definition cub = Cube(filename) radius, profile = cub.profile_r( maxradius=5) # profile, no coords are given, assume central pixel # Compute the beam FWHM. It is elliptical so use geometric mean to obtain a single value for the size. mean_beam_fwhm = np.sqrt( cub.beam["bmaj"][0] * cub.beam["bmin"][0]) # [0] is the first (and only) channel print("Beam FWHM [arcsec]", mean_beam_fwhm) fig, ax = plt.subplots(figsize=(4.8, 3)) ax.set_title("Point spread function") col = "navy" ax.plot(radius, profile * scale, color=col) ax.tick_params(direction='in', which="both") ax.set_xlabel("Radius (arcsec)") ax.set_ylabel("Azimuthal average", color=col) ax.tick_params(axis='y', colors=col) ax.set_xlim(0, 5) radius, flux = cub.aperture_r(maxradius=5) # cumulative col = "firebrick" ax2 = ax.twinx() ax2.plot(radius, flux * scale, color=col) ax2.tick_params(direction='in', which="both") ax2.set_ylabel("Cumulative", color=col) ax2.tick_params(axis='y', colors=col) plt.savefig("./plots/growing_aperture_psf.pdf", bbox_inches="tight") # save plot plt.savefig("./thumbnails/growing_aperture_psf.png", bbox_inches="tight", dpi=72) # web raster version plt.show()
def map_channels_wcsaxes(): """ Plot a channel maps from the cube using wcsaxes and lin-log scaling. Disclaimer: ccolorbar size is optimized for 3x3 grid. """ filename = "./data/Pisco.cube.50kms.image.fits" cutout = 2 # arcsec scale = 1e3 # Jy/beam to mJy/beam ra, dec, freq = (205.533741, 9.477317341, 222.547) # set up the channel map grid (change the figure size if necessary for font scaling) nrows = 3 ncols = 3 figsize = (8, 8) idx_center = int(0.5 * nrows * ncols) # index of the central panel # if needed, set offset_v to make channel maps blue- or red-wards of the line, but keeping the line as the # reference to write in the title of each channel (e.g. \pm Delta_v = XX km/s) offset_v = 0 cub = Cube(filename) # load map cutout_pix = cutout / cub.pixsize ch_peak = cub.freq2pix( freq * (1 + offset_v / 3e5)) # referent channel in the cube - one with the peak line emission # velocity offset of each channel from the referent frequency # velocities = cub.vels(freq) # use the frequency from the spectral fit velocities = cub.vels(cub.freqs[ch_peak]) # it's nicer if the ch_peak velocity is set to exactly 0 km/s. fig = plt.figure(figsize=figsize) # get the extent of the cutouts px, py = cub.radec2pix(ra, dec) r = int(np.round(cutout * 1.05 / cub.pixsize)) # slightly larger cutout than required, for edge bleeding # use the reference channel to set the colorbar scale # vmax = np.nanmax(cub.im[px - r:px + r + 1, py - r:py + r + 1, ch_peak]) * scale # or use a range of channels to find the max value for colorbar scaling: vmax = np.nanmax(cub.im[px - r:px + r + 1, py - r:py + r + 1, ch_peak - idx_center:ch_peak - idx_center + nrows * ncols]) * scale linthres = 3 * np.std(cub.im[px - r:px + r + 1, py - r:py + r + 1, ch_peak - idx_center:ch_peak - idx_center + nrows * ncols]) * scale ax_list = [] for i in range(nrows * ncols): ax = fig.add_subplot(str(nrows) + str(ncols) + str(i + 1), projection=cub.wcs, label='overlays', slices=('x', 'y', 50)) ax_list.append(ax) ra = ax.coords[0] dec = ax.coords[1] ch = ch_peak - idx_center + i # this will put the peak channel on i = idx_center position subim = cub.im[:, :, ch] * scale # scale units # show image axim = ax.imshow(subim.T, origin='lower', cmap="PuOr_r", vmin=-vmax, vmax=vmax, zorder=-1, norm=colors.SymLogNorm(linthresh=linthres, linscale=0.5, vmin=-vmax, vmax=vmax)) # set limits to exact cutout size ax.set_xlim(cub.im.shape[0] / 2 - cutout_pix, cub.im.shape[0] / 2 + cutout_pix) ax.set_ylim(cub.im.shape[1] / 2 - cutout_pix, cub.im.shape[1] / 2 + cutout_pix) # calc rms and plot contours rms = cub.rms[ch] * scale ax.contour(subim.T, colors="gray", levels=np.array([-8, -4, -2]) * rms, zorder=1, linewidths=0.5, linestyles="--") ax.contour(subim.T, 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=(cub.im.shape[0] / 2 - cutout_pix * 0.75, cub.im.shape[1] / 2 - cutout_pix * 0.75), width=cub.beam["bmin"][ch] / cub.pixsize, height=cub.beam["bmaj"][ch] / cub.pixsize, angle=cub.beam["bpa"][ch], edgecolor='black', fc='w', lw=0.75) ax.add_patch(ellipse) # add circular aperture to the central panel if i == idx_center: aper_rad = 1.3 ellipse = Ellipse(xy=(cub.im.shape[0] / 2, cub.im.shape[1] / 2), width=2 * aper_rad / cub.pixsize, height=2 * aper_rad / cub.pixsize, angle=0, edgecolor='firebrick', fc="none", lw=1, ls=":") ax.add_patch(ellipse) # add text on top of the map paneltext = str(int(velocities[ch] + offset_v)) + " km/s" ax.text(0.5, 0.95, paneltext, path_effects=[pe.Stroke(linewidth=3, foreground='k'), pe.Normal()], va='top', ha='center', color="white", transform=ax.transAxes) ax.tick_params(direction='in', which="both") dec.set_axislabel(' ') ra.set_axislabel(' ') ra.set_ticklabel_visible(False) dec.set_ticklabel_visible(False) # writing RA and DEC and ticklabels only on outer edges of the grid if (i) % nrows == 0: dec.set_ticklabel_visible(True) dec.set_ticks(number=5) if i >= (nrows - 1) * ncols: ra.set_ticklabel_visible(True) ra.set_ticks(number=2) if i == nrows * ncols - 2: ra.set_axislabel("RA", fontsize=14) if i == int(nrows / 2) * ncols: dec.set_axislabel("DEC", fontsize=14) fig.subplots_adjust(right=0.92) cbar_ax = fig.add_axes([0.94, 0.11, 0.03, 0.77]) fig.colorbar(axim, cax=cbar_ax) cbar_ax.yaxis.set_label_text(r"$S_\nu$ (mJy beam$^{-1}$)", fontsize=14) cbar_ax.yaxis.set_label_position('right') fig.subplots_adjust(hspace=0.1, wspace=0.1) plt.savefig("./plots/map_channels_wcsaxes.pdf", bbox_inches="tight", dpi=600) plt.savefig("./thumbnails/map_channels_wcsaxes.png", bbox_inches="tight", dpi=72) plt.show() return None
def map_wcsaxes(): """ Plot a map with wcsaxes and semi-logarithmic scaling. Scaling can be controlled to ensure consistency across several plots. Prints fluxes of the central source. """ # Two parameters that control the scaling to ensure consistency across plots. If none, uses maximum of map and # 5sigma rms noise aas threshold to go from linear to logarithmmic vmax = None linthres = None filename = "./data/Pisco.cii.455kms.image.fits" cub = Cube(filename) # we know where the source is cutout = 4 # radius of the cutout in arcsec (full panel is 2xcutout) cutout_pix = cutout / cub.pixsize # DEC radius of cutout in arcsec aper_rad = 2 scale = 1e3 # Jy/beam to mJy/beam fig = plt.figure(figsize=(3, 3)) # Use the ImageGrid to display a map and a colorbar to the right ax = plt.subplot(projection=cub.wcs, label='overlays', slices=('x', 'y', 200)) lon = ax.coords[0] lat = ax.coords[1] # for color scaling if vmax == None: vmax = np.nanmax(cub.im[:, :, 0] * scale) linthres = 5 * np.std(cub.im[:, :, 0].T * scale) # show image axim = ax.imshow(cub.im[:, :, 0].T * scale, origin='lower', cmap="PuOr_r", vmin=-vmax, vmax=vmax, zorder=-1, norm=colors.SymLogNorm(linthresh=linthres, linscale=0.5, vmin=-vmax, vmax=vmax)) # calc rms and plot contours rms = cub.rms[0] * scale ax.contour(cub.im[:, :, 0].T * scale, colors="gray", levels=np.array([-8, -4, -2]) * rms, zorder=1, linewidths=0.5, linestyles="--") ax.contour(cub.im[:, :, 0].T * scale, 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=(cub.im.shape[0] / 2 - cutout_pix * 0.75, cub.im.shape[0] / 2 - cutout_pix * 0.75), width=cub.beam["bmin"] / cub.pixsize, height=cub.beam["bmaj"] / cub.pixsize, angle=cub.beam["bpa"], edgecolor='black', fc='w', lw=0.75) ax.add_patch(ellipse) # set limits to exact cutout size ax.set_xlim(cub.im.shape[0] / 2 - cutout_pix, cub.im.shape[0] / 2 + cutout_pix) ax.set_ylim(cub.im.shape[1] / 2 - cutout_pix, cub.im.shape[1] / 2 + cutout_pix) # add circular aperture ellipse = Ellipse(xy=(cub.im.shape[1] / 2, cub.im.shape[1] / 2), width=2 * aper_rad / cub.pixsize, height=2 * aper_rad / cub.pixsize, angle=0, edgecolor='maroon', fc="none", lw=1, ls=":") ax.add_patch(ellipse) # add text on top of the map ax.text(0.5, 0.95, r'[CII] 158 $\mu$m', path_effects=[pe.Stroke(linewidth=2, foreground='k'), pe.Normal()], va='top', ha='center', color="white", transform=ax.transAxes) divider = make_axes_locatable(ax) cax = divider.append_axes("top", size="5%", axes_class=maxes.Axes) cbar = fig.colorbar(axim, cax=cax, orientation='horizontal', ticks=[-int(vmax), -2 * linthres, -linthres / 2, linthres / 2, 2 * linthres, int(vmax)], format='%0.1f') cax.xaxis.set_ticks_position('top') cax.xaxis.set_label_text(r"$S_\nu$ (mJy beam$^{-1}$)") cax.xaxis.set_label_position('top') lon.set_ticks(number=4) lat.set_minor_frequency(1) ax.tick_params(direction='in', which="both") ax.set_xlabel(r"RA ") ax.set_ylabel(r"Dec") plt.savefig("./plots/map_wcsaxes.pdf", bbox_inches="tight", dpi=600) # need higher dpi for crisp data pixels plt.savefig("./thumbnails/map_wcsaxes.png", bbox_inches="tight", dpi=72) # web raster version plt.show() return vmax, linthres
def map_channels_paper(): """ Plot a channel maps from the cube. """ filename = "./data/Pisco.cube.50kms.image.fits" ra, dec, freq = (205.533741, 9.477317341, 222.547) # we know where the source is cutout = 1.5 # arcsec scale = 1e3 # Jy/beam to mJy/beam # set up the channel map grid (change the figure size if necessary for font scaling) nrows = 3 ncols = 3 figsize = (6, 6) idx_center = int(0.5 * nrows * ncols) # index of the central panel cub = Cube(filename) # load map ch_peak = cub.freq2pix(freq) # referent channel in the cube - one with the peak line emission # velocity offset of each channel from the referent frequency velocities = cub.vels(freq) # use the frequency from the spectral fit # velocities = cub.vels(cub.freqs[ch_peak]) # it's nicer if the ch_peak velocity is set to exactly 0 km/s. 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) # get the extent of the cutouts px, py = cub.radec2pix(ra, dec) 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 extent = [(edgera - ra) * 3600, (edgedec - dec) * 3600] extent = extent[0].tolist() + extent[1].tolist() # concat two lists # use the reference channel to set the colorbar scale # vmax = np.nanmax(cub.im[px - r:px + r + 1, py - r:py + r + 1, ch_peak]) * scale # or use a range of channels to find the max value for colorbar scaling: vmax = np.nanmax(cub.im[px - r:px + r + 1, py - r:py + r + 1, ch_peak - idx_center:ch_peak - idx_center + nrows * ncols]) * scale vmin = -0.1 * vmax for i in range(nrows * ncols): ax = grid[i] ch = ch_peak - idx_center + i # this will put the peak channel on i = idx_center position subim = cub.im[px - r:px + r + 1, py - r:py + r + 1, ch] * scale # scale units # show image axim = ax.imshow(subim.T, origin='lower', cmap="RdBu_r", vmin=vmin, vmax=vmax, extent=extent) # set limits to exact cutout size ax.set_xlim(cutout, -cutout) ax.set_ylim(-cutout, cutout) # calc rms and plot contours rms = cub.rms[ch] * scale 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"][ch], height=cub.beam["bmaj"][ch], angle=-cub.beam["bpa"][ch], edgecolor='black', fc='w', lw=0.75) ax.add_patch(ellipse) # add circular aperture to the central panel if i == idx_center: aper_rad = 1.3 ellipse = Ellipse(xy=(0, 0), width=2 * aper_rad, height=2 * aper_rad, angle=0, edgecolor='white', fc="none", lw=1, ls=":") ax.add_patch(ellipse) # add text on top of the map # paneltext = str(cub.freqs[ch]) paneltext = str(int(velocities[ch])) + " km/s" ax.text(0.5, 0.95, paneltext, path_effects=[pe.Stroke(linewidth=3, foreground='k'), pe.Normal()], va='top', ha='center', color="white", transform=ax.transAxes) ax.tick_params(direction='in', which="both") # Could put global labels to the figure, but in this case just put labels to the middle edge panels if i == (ncols * int(nrows / 2)): ax.set_ylabel(r"$\Delta$ Dec (arcsec)") if i == (nrows * ncols - int(ncols / 2) - 1): ax.set_xlabel(r"$\Delta$ RA (arcsec)") # add colorbar cb = ax.cax.colorbar(axim) cb.set_label_text(r"$S_\nu$ (mJy beam$^{-1}$)") plt.savefig("./plots/map_channels_paper.pdf", bbox_inches="tight", dpi=600) # need higher dpi for crisp data pixels plt.savefig("./thumbnails/map_channels_paper.png", bbox_inches="tight", dpi=72) # web raster version plt.show() return None
def map_single_paper(): """ Plot a single 2D map with contours, synthesised beam, colorbar, and text overlay. """ filename = "./data/Pisco.cii.455kms.image.fits" ra, dec = (205.533741, 9.477317341) # we know where the source is cutout = 3 # radius of the cutout in arcsec (full panel is 2xcutout) aper_rad = 1.3 titletext = r"[CII] 158 $\mu$m" cub = Cube(filename) # load map scale = 1e3 # Jy/beam to mJy/beam # scale = cub.deltavel() # use this to scale units to Jy/beam km/s fig = plt.figure(figsize=(3, 3)) # nrows_ncols=(2,4) # Use the ImageGrid to display a map and a colorbar to the right grid = ImageGrid(fig, 111, nrows_ncols=(1, 1), axes_pad=0.05, share_all=True, cbar_location="right", cbar_mode="single", cbar_size="3%", cbar_pad=0.05) ax = grid[0] # create a smaller cutout for plotting, a subimage # Note: there should be a better way to plot cutouts, but this works. px, py = cub.radec2pix(ra, dec) 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 extent = [(edgera - ra) * 3600, (edgedec - dec) * 3600] extent = extent[0].tolist() + extent[1].tolist() # concat two lists # get the cutout; warning: no index out of bounds checking is done here subim = cub.im[px - r:px + r + 1, py - r:py + r + 1, 0] * scale # scale units # for color scaling vmax = np.nanmax(subim) vmin = -0.1 * vmax # show image axim = ax.imshow(subim.T, origin='lower', cmap="RdBu_r", vmin=vmin, vmax=vmax, extent=extent) # calc rms and plot contours rms = cub.rms[0] * scale 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) # set limits to exact cutout size ax.set_xlim(cutout, -cutout) ax.set_ylim(-cutout, cutout) # add circular aperture ellipse = Ellipse(xy=(0, 0), width=2 * aper_rad, height=2 * aper_rad, angle=0, edgecolor='white', fc="none", lw=1, ls=":") ax.add_patch(ellipse) # add text on top of the map ax.text(0.5, 0.95, titletext, path_effects=[pe.Stroke(linewidth=2, foreground='k'), pe.Normal()], va='top', ha='center', color="white", transform=ax.transAxes) # add colorbar cb = ax.cax.colorbar(axim) cb.set_label_text(r"$S_\nu$ (mJy beam$^{-1}$)") # ax.tick_params(direction='in', which="both") ax.set_xlabel(r"$\Delta$ RA (arcsec)") ax.set_ylabel(r"$\Delta$ Dec (arcsec)") plt.savefig("./plots/map_single_paper.pdf", bbox_inches="tight", dpi=600) # need higher dpi for crisp data pixels plt.savefig("./thumbnails/map_single_paper.png", bbox_inches="tight", dpi=72) # web raster version plt.show()