Read and plot maps with MadcubaMap#

Introduction to using madcubapy to read and plot FITS maps.

Usually, we would open and FITS files in python using astropy and plot them using matplotlib. This tutorial shows how this can be done with madcubapy, which uses the previous packages to do all the work, but in a more concise manner, simplifying this process for the user.

We start by importing the necessary libraries for this tutorial.

from madcubapy.io import MadcubaMap
from madcubapy.visualization import add_wcs_axes
from madcubapy.visualization import add_manual_wcs_axes
from madcubapy.visualization import insert_colorbar
import matplotlib.pyplot as plt

Reading the FITS map#

When MADCUBA exports a FITS map it also creates a history file with the same name of the FITS file ended with _hist.csv. With the MadcubaMap class we can open these FITS files alongside their history tables.

We can read the FITS file with the MadcubaMap.read() method, and the corresponding history file will be loaded as well if present.

madcuba_map = MadcubaMap.read(
    "../examples/data/IRAS16293_SO_2-1_moment0_madcuba.fits")

Note

Due to how MADCUBA saves some FITS header cards, several astropy warnings can pop up when reading FITS files. Usually these warnings are unexpected card names using non-standard conventions.

The MadcubaMap class resembles the CCDData class from Astropy with data, header, wcs, and unit attributes, with the addition of a hist attribute, the history table containing all the information from the history file:

madcuba_map.hist
Table length=9
IndexFITSMacroTypeFROM_ROWTO_ROWRoi_RangeUserDate
int64str1str500str5int64int64int64str50str23
1C//ORIGIN=/home/madcubapy/data/MAD_CUB_member.uid___A001_X133d_X320d.IRAS_16293-2422_sci.spw29.cube.I.pbcor.fitsI------00
2C//INFO: Import CASA FITS FILE, ESTIMATED_SIGMA=0.005020903444186465P------madcubapy2022-10-14T14:39:05.876
3Crun("Import CASA FITS FILE","select='/home/madcubapy/data/member.uid___A001_X133d_X320d.IRAS_16293-2422_sci.spw29.cube.I.pbcor.fits' changehead='Vrad$2.5$km/s'");P------madcubapy2022-10-14T14:39:05.876
4Crun("Open Virtual Cube","select='/home/madcubapy/data/MAD_CUB_member.uid___A001_X133d_X320d.IRAS_16293-2422_sci.spw29.cube.I.pbcor.fits' ");P------madcubapy2023-05-02T13:12:32.467
5C//ORIGIN=CUBE MAD_CUB_member.uid___A001_X133d_X320d.IRAS_16293-2422_sci.spw29.cube.I.pbcor.fitsI------00
6C//selectWindow("CUBE MAD_CUB_member.uid___A001_X133d_X320d.IRAS_16293-2422_sci.spw29.cube.I.pbcor.fits");I------00
7Crun("INTEGRATED INTENSITY PLUGIN","ranges=500.0$5500.0$# axisunit=m s-1 interpolate=0");P------madcubapy2023-05-02T13:12:34.442
8Crun("Save Cube","select='/home/madcubapy/data/IRAS16293_SO_2-1_moment0_madcuba.fits'");P------madcubapy2023-05-02T13:12:47.239
9C//PYTHON: Open cube: '../examples/data/IRAS16293_SO_2-1_moment0_madcuba.fits'Py------docs2026-03-29T12:56:06.723


As a failsafe, a CCDData object is also present inside the MadcubaMap object in the ccddata attribute. This way, we can do anything a CCDData object can with madcubapy through this attribute.

Plotting the FITS map#

To plot FITS maps we can use the buiilt-in visualization functions of madcubapy, or the classical option: plotting the data array with matplotlib (and astropy for coordinates).

The add_wcs_axes() method of madcubapy lets the user plot a MadcubaMap or CCDData quickly using only two statements.

A Figure must be set before calling add_wcs_axes(), and it must be the first input parameter of the function. The second mandatory parameter is fitsmap, the map object to plot, which can be a MadcubaMap or CCDData. Addionally, the user can set a series of kwargs parameters, which are passed to matplotlib.pyplot.imshow(), like vmin or vmax, for example.

# Create empty figure
fig = plt.figure(figsize=(6, 5))
# Use add_wcs_axes() to plot the map.
ax, img = add_wcs_axes(fig=fig, fitsmap=madcuba_map, vmin=0, vmax=300)
plt.show()
Image with pcolor

We can very quickly add a colorbar using add_colorbar() or insert_colorbar(). Check the Colorbar page to know more about how these two functions work.

# Create empty figure
fig = plt.figure(figsize=(6, 5))
# Use add_wcs_axes() to plot the map.
ax, img = add_wcs_axes(fig=fig, fitsmap=madcuba_map, vmin=0, vmax=300)
# Add colorbar
cbar = insert_colorbar(ax)
plt.show()
Image with pcolor

The visualization functions of madcubapy automatically parse information from the FITS header like the units for the data.

The same figure is recreated in Method #2 using the functions of matplotlib and astropy that madcubapy uses under the hood.

Note

For this tutorial we are calling this function using the parameter names explicitly (i.e add_wcs_axes(fig=fig_object, fitsmap=map_object)), and not by using positional arguments. If one opts to use positional arguments, to quickly plot one map the call to the function should be add_wcs_axes(fig_object, 1, 1, 1, map_object).

This is due to the functionality of add_wcs_axes() and its manual version add_manual_wcs_axes() to plot several maps in one figure. This is explained thoroughly in Plotting image with coordinates.

The classical option: plotting the data array of the FITS files using using matplotlib and astropy.

We can first plot the data array without bothering with any coordinate system with the matplotlib function imshow(), and add the title, axis labels and a colorbar using other matplotlib functions.

Keep in mind that usually we need to do some slicing to get rid of the spectral and polarization axes if present.

# Create a figure and plot the image
fig, ax = plt.subplots(1, 1, figsize=(6, 6))
map_data = madcuba_map.data[0, 0, :, :]  # apply slicing
img = ax.imshow(map_data, cmap='viridis', origin='lower', vmin=0, vmax=300)

# Add a colorbar
fig.colorbar(img)

# Set axis labels and title
ax.set_title('Madcuba Map')
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')

plt.show()
Image with imshow

This figure uses pixel coordinates for the X and Y axes, and the colorbar does not have the same height as the image, resulting in an ugly astronomical map.

We can correct the placement of the colorbar by manually creating a new axes with the size of the image, and use it for the colorbar.

# New imports needed to create the colorbar
import matplotlib.axes as maxes
from mpl_toolkits.axes_grid1 import make_axes_locatable

# Create a figure and plot the image
fig, ax = plt.subplots(1, 1, figsize=(6, 5))
map_data = madcuba_map.data[0, 0, :, :]  # apply slicing
img = ax.imshow(map_data, cmap='viridis', origin='lower', vmin=0, vmax=300)

# Add a colorbar with the same height as the image
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.08, axes_class=maxes.Axes)
fig.colorbar(img, cax=cax)

# Set axis labels and title
ax.set_title('Madcuba Map')
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')

plt.show()
Image with imshow

The colorbar is correctly placed, but we still have pixel coordinates.

We can use Astropy to create the plot using the WCS coordinates from the FITS file.

# Create a figure and add a subplot with WCS projection
fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(1, 1, 1, projection=madcuba_map.wcs.celestial)
map_data = madcuba_map.data[0, 0, :, :]  # apply slicing
img = ax.imshow(map_data, cmap='viridis', origin='lower', vmin=0, vmax=300)

# Add a colorbar with the same height as the image
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05, axes_class=maxes.Axes)
cbar = fig.colorbar(img, cax=cax, orientation='vertical')

# Set title
ax.set_title('Madcuba Map')

plt.show()
Image with imshow

As we can see, astropy created a WCSAxes object and set the X and Y labels to the correct values read from the FITS file, but their representation looks kind of ugly, and we still do not have a label for the colorbar. We must set the labels manually, and we have to look into the FITS header to get the units of the data, located in the BUNIT card.

For this FITS file, the units of the data are \({\rm Jy \ beam^{-1} \ m \ s^{-1}}\). We can set the labels with:

# Set correct labels
ax.coords[0].set_axislabel("RA (ICRS)")
ax.coords[1].set_axislabel("DEC (ICRS)")
cbar.set_label(r'$I \ {\rm (Jy \ beam^{-1} \ m \ s^{-1})}$')
Image with imshow

Fix units#

Some programs export the BUNIT fits card incorrectly with more than one slash and astropy has problems parsing the units from those strings. The MadcubaMap.fix_units() method tries to fix this problem and correctly parse the units.

For example, CARTA exports units like this. When we read a CARTA map, madcubapy warns us that no history file has been found, which we can ignore when working with FITS files not created by MADCUBA.

carta_map = MadcubaMap.read("../examples/data/IRAS16293_SO2c_moment0_carta.fits")
WARNING: Default history file not found.

If we check the units of the map we can see that they are incorrect, because the BUNIT card has a string with multiple slashes: ‘Jy/beam.km/s’. When astropy tries to parse this, it incorrectly places the \({\rm km}\) on the denominator:

print(f"Astropy parsed unit: {carta_map.unit}")
print(f"FITS header BUNIT: {carta_map.header["BUNIT"]}")
Astropy parsed unit: Jy / (beam km s)
FITS header BUNIT: Jy/beam.km/s

We can run the MadcubaMap.fix_units() method of the MadcubaMap object to fix this. It is important to note that the CCDData object inside MadcubaMap also gets the correct units when they are fixed.

carta_map.fix_units()

print(f"MadcubaMap unit: {carta_map.unit}")
print(f"CCDData unit: {carta_map.ccddata.unit}")
MadcubaMap unit: Jy km / (beam s)
CCDData unit: Jy km / (beam s)

Even though a CARTA exported map does not have a _hist.csv file, it is encouraged to read it as a MadcubaMap, even when the user wants to work with a CCDData object if some functionality isn’t working with a MadcubaMap object.

This is the reason why we have the failsafe CCDData object in the MadcubaMap.ccddata attribute. We can work with it just as if it was an object directly read with the CCDData.read() method from astropy (because it actually is read this way).

Total running time of the script: (0 minutes 1.409 seconds)

Gallery generated by Sphinx-Gallery