Using MARTINI’s HDF5 output

MARTINI offers an option to output mock observation data cubes in HDF5 format. Below is an example illustrating reading the output and making a plot of the first three moments of a data cube.

Note

This example script requires MARTINI v2.1.3 or newer.

import h5py
import numpy as np
import matplotlib.pyplot as plt
from astropy import units as U
from martini import demo

hdf5file = "testcube.hdf5"
demo(hdf5file=hdf5file)
with h5py.File(hdf5file, "r") as f:
    flux_cube = f["FluxCube"][()] * U.Unit(f["FluxCube"].attrs["FluxCubeUnit"])
    ra_vertices = f["RA_vertices"][()] * U.Unit(f["RA_vertices"].attrs["Unit"])
    dec_vertices = f["Dec_vertices"][()] * U.Unit(f["RA_vertices"].attrs["Unit"])
    spec_vertices = f["channel_vertices"][()] * U.Unit(
        f["channel_vertices"].attrs["Unit"]
    )
    vch = (
        f["velocity_channel_mids"][()]
        * U.Unit(f["velocity_channel_mids"].attrs["Unit"])
        - 210 * U.km / U.s  # subtract systemic velocity here
    )

# if the RA range straddles RA=0 it's easier to plot on a -180<RA<180 range:
ra_vertices = np.where(
    ra_vertices > 180 * U.deg, ra_vertices - 360 * U.deg, ra_vertices
)

# choose units for plotting, not necessarily the units data are stored in:
ra_unit = U.deg
dec_unit = U.deg
mom0_unit = U.Jy / U.beam
mom1_unit = U.km / U.s
mom2_unit = U.km / U.s

fig = plt.figure(figsize=(16, 5))
sp1 = fig.add_subplot(1, 3, 1, aspect="equal")
sp2 = fig.add_subplot(1, 3, 2, aspect="equal")
sp3 = fig.add_subplot(1, 3, 3, aspect="equal")

# estimate RMS noise in the cube, in this case from a corner patch with little signal:
rms = np.std(flux_cube[:16, :16])

# simple 3D source mask by clipping above the noise:
clip = np.where(flux_cube > 5 * rms, 1, 0)

mom0 = np.sum(flux_cube, axis=-1)

# mask for higher moments to focus on high-density regions:
mask = np.where(mom0 > 0.05 * U.Jy / U.beam, 1, np.nan)

mom1 = np.sum(flux_cube * clip * vch, axis=-1) / mom0
mom2 = np.sqrt(
    np.sum(flux_cube * clip * np.power(vch - mom1[..., np.newaxis], 2), axis=-1)
    / mom0
)
im1 = sp1.pcolormesh(
    ra_vertices[..., 0].to_value(
        ra_unit
    ),  # pick one channel in ra_vertices, coordinates are the same in all of them
    dec_vertices[..., 0].to_value(
        dec_unit
    ),  # pick one channel in dec_vertices, coordinates are the same in all of them
    mom0.to_value(mom0_unit),
    cmap="Greys",
)
plt.colorbar(im1, ax=sp1, label=f"mom0 [{mom0_unit}]")
im2 = sp2.pcolormesh(
    ra_vertices[..., 0].to_value(
        ra_unit
    ),  # pick one channel in ra_vertices, coordinates are the same in all of them
    dec_vertices[..., 0].to_value(
        dec_unit
    ),  # pick one channel in dec_vertices, coordinates are the same in all of them
    (mom1 * mask).to_value(mom1_unit),
    cmap="RdBu_r",
    vmin=-np.nanmax(np.abs(mom1 * mask)).to_value(mom1_unit),
    vmax=np.nanmax(np.abs(mom1 * mask)).to_value(mom1_unit),
)
plt.colorbar(im2, ax=sp2, label=f"mom1 [{mom1_unit}]")
im3 = sp3.pcolormesh(
    ra_vertices[..., 0].to_value(
        ra_unit
    ),  # pick one channel in ra_vertices, coordinates are the same in all of them
    dec_vertices[..., 0].to_value(
        dec_unit
    ),  # pick one channel in dec_vertices, coordinates are the same in all of them
    (mom2 * mask).to_value(mom2_unit),
    cmap="magma",
    vmin=0,
)
plt.colorbar(im3, ax=sp3, label=f"mom2 [{mom2_unit}]")
for sp in sp1, sp2, sp3:
    sp.set_xlabel(f"RA [{ra_unit}]")
    sp.set_ylabel(f"Dec [{dec_unit}]")
    sp.set_xlim(sp.get_xlim()[::-1])

plt.subplots_adjust(wspace=0.3)
plt.show()

This produces the figure:

0th moment (surface density map), 1st moment (velocity map) and 2nd moment (velocity dispersion map) of the demo data cube.