User documentation

SMOC (a.k.a MOC): Spatial coverages

Introduction to Space-MOCs

Space MOCs are an ensemble of HEALPix cells of mixed orders. They represent sky regions in an approximate way. They are designed for efficient calculations between sky regions.

The coordinates for Space-MOCs are always in IRCS at epoch J2000 by definition.

Space-MOCs can represent arbitrary shapes. Common examples are an approximated cone, an ensemble of approximated cones, or the coverage of a specific survey.

The following notebook is an introduction to manipulation of Space-MOCs with MOCPy:

As you saw, Space-MOCs can be visualized with either astropy+matplotlib or with the ipyaladin widget.

Space-MOC creation methods

Reading a MOC from a file or from a server

There are diverse ways to instantiate a MOC. A first approach is to get a MOC from a FITS file on our disk, or from a distant server that already has a pre-calculated MOC.

Calculating a MOC on the fly from a region of the sky

Here is a simple example of the creation of a MOC that fully covers a polygon defined by its vertices on the sky. The vertices are linked by great circles.

import astropy.units as u
import matplotlib.pyplot as plt
import numpy as np
from astropy.coordinates import Angle, SkyCoord
from mocpy import MOC, WCS

# The set of points delimiting the polygon in deg
vertices = np.array(
    [
        [18.36490956, 5.0],
        [15.7446692, 6.97214743],
        [16.80755056, 10.29852928],
        [13.36215502, 10.14616136],
        [12.05298691, 13.10706197],
        [9.54793022, 10.4556709],
        [8.7677627, 7.80921809],
        [9.71595962, 5.30855011],
        [7.32238541, 6.44905255],
        [0.807265, 6.53399616],
        [1.08855146, 3.51294225],
        [2.07615384, 0.7118289],
        [3.90690158, -1.61886929],
        [9.03727956, 2.80521847],
        [9.22274427, -4.38008174],
        [10.23563378, 4.06950324],
        [10.79931601, 3.77655576],
        [14.16533992, 1.7579858],
        [19.36243491, 1.78587203],
        [15.31732084, 5.0],
    ],
)
skycoord = SkyCoord(vertices, unit="deg", frame="icrs")
moc = MOC.from_polygon_skycoord(skycoord, complement=False, max_depth=9)

# Plot the MOC using matplotlib
fig = plt.figure(111, figsize=(10, 10))
# Define a astropy WCS easily
with WCS(
    fig,
    fov=20 * u.deg,
    center=SkyCoord(10, 5, unit="deg", frame="icrs"),
    coordsys="icrs",
    rotation=Angle(0, u.degree),
    # The gnomonic projection transforms great circles into straight lines.
    projection="TAN",
) as wcs:
    ax = fig.add_subplot(1, 1, 1, projection=wcs)
    # Call fill with a matplotlib axe and the `~astropy.wcs.WCS` wcs object.
    moc.fill(ax=ax, wcs=wcs, alpha=0.5, fill=True, color="red", linewidth=1)
    moc.border(ax=ax, wcs=wcs, alpha=1, color="red")

plt.xlabel("ra")
plt.ylabel("dec")
plt.title("MOC from a polygon")
plt.grid(color="black", linestyle="dotted")
plt.show()

(Source code, png, hires.png, pdf)

../_images/polygon_coverage.png

For a more extended description on how to create MOCs from diverse shapes, you can check the example notebooks Creating Space-MOCs from shapes and Creating MOCs out of astropy-regions..

Instantiating a MOC when we already know its HEALPix cells

This can be done with methods like from_string(), from_healpix_cells(), from_depth29_ranges(). A simple MOC that represents the full sky is the MOC of order 0 that contains all the 12 HEALPix cells of the order 0. Creating it from a string looks like:

from mocpy import MOC

all_sky = MOC.from_str("0/0-11")  # the 12 cells of HEALPix at order 0
all_sky.display_preview()  # the inside of the MOC is represented in red with display_preview

(Source code, png, hires.png, pdf)

../_images/all_sky.png

We could also take only odd numbered cells of order 1. Let’s use from_json() that takes a dictionary:

from mocpy import MOC

moc = MOC.from_json({"1": [i for i in range(12 * 4) if i % 2 == 1]})
moc.display_preview()

(Source code, png, hires.png, pdf)

../_images/odd_cells.png

Useful methods

Calculating a Space-MOC sky area

This example shows how to calculate the sky area of a Space-MOC.

import astropy.units as u
import numpy as np
from mocpy import MOC

ALL_SKY = 4 * np.pi * u.steradian
moc = MOC.from_string("0/0-11")  # any other MOC instance will work
area = moc.sky_fraction * ALL_SKY
area = area.to(u.deg**2).value

(Source code)

Other examples (operations, use-cases)

Here are some other code examples manipulating MOC objects.

Loading and plotting the MOC of SDSS

This example:

  • Load the coverage of SDSS from a FITS file.

  • Plot the MOC by:

  1. Defining a matplotlib figure.

  2. Defining an astropy WCS representing the field of view of the plot.

  3. Call the fill() and border() so that mocpy plot on a matplotlib axis.

  4. Set the axis labels, a title, enable the grid and plot the final figure.

import matplotlib.pyplot as plt
from astropy.visualization.wcsaxes.frame import EllipticalFrame
from astropy.wcs import WCS
from mocpy import MOC

# Load a MOC
filename = "./../../resources/P-SDSS9-r.fits"
moc = MOC.from_fits(filename)
# Plot the MOC using matplotlib
fig = plt.figure(111, figsize=(15, 10))
# Define a astropy WCS
wcs = WCS(
    {
        "naxis": 2,
        "naxis1": 1620,
        "naxis2": 810,
        "crpix1": 810.5,
        "crpix2": 405.5,
        "cdelt1": -0.2,
        "cdelt2": 0.2,
        "ctype1": "RA---AIT",
        "ctype2": "DEC--AIT",
    },
)
ax = fig.add_subplot(1, 1, 1, projection=wcs, frame_class=EllipticalFrame)
# Call fill with a matplotlib axe and the `~astropy.wcs.WCS` wcs object.
moc.fill(ax=ax, wcs=wcs, alpha=0.5, fill=True, color="green")
moc.border(ax=ax, wcs=wcs, alpha=0.5, color="black")
ax.set_aspect(1.0)
plt.xlabel("ra")
plt.ylabel("dec")
plt.title("Coverage of P-SDSS9-r")
plt.grid(color="black", linestyle="dotted")
plt.show()

(Source code, png, hires.png, pdf)

../_images/plot_SDSS_r.png

Intersection between GALEX and SDSS

This example:

  • Load the coverages of SDSS and GALEX from FITS files.

  • Compute their intersection

  • Compute their union

  • Plot the resulting intersection and union on a same matplotlib axis.

import matplotlib.pyplot as plt
from astropy.visualization.wcsaxes.frame import EllipticalFrame
from astropy.wcs import WCS
from mocpy import MOC

# Load Galex and SDSS
sdss = MOC.from_fits("./../../resources/P-SDSS9-r.fits")
galex = MOC.from_fits("./../../resources/P-GALEXGR6-AIS-FUV.fits")
# Compute their intersection
inter = sdss & galex
union = sdss + galex
# Plot the MOC using matplotlib
fig = plt.figure(111, figsize=(10, 8))
# Define a astropy WCS
wcs = WCS(
    {
        "naxis": 2,
        "naxis1": 3240,
        "naxis2": 1620,
        "crpix1": 1620.5,
        "crpix2": 810.5,
        "cdelt1": -0.1,
        "cdelt2": 0.1,
        "ctype1": "RA---AIT",
        "ctype2": "DEC--AIT",
    },
)
ax = fig.add_subplot(1, 1, 1, projection=wcs, frame_class=EllipticalFrame)
# Call fill with a matplotlib axe and the `~astropy.wcs.WCS` wcs object.
union.fill(
    ax=ax,
    wcs=wcs,
    alpha=0.5,
    fill=True,
    color="blue",
    linewidth=0,
    label="Union",
)
union.border(ax=ax, wcs=wcs, alpha=1, color="black")

inter.fill(
    ax=ax,
    wcs=wcs,
    alpha=0.5,
    fill=True,
    color="green",
    linewidth=0,
    label="Intersection",
)
inter.border(ax=ax, wcs=wcs, alpha=1, color="black")
ax.legend()
ax.set_aspect(1.0)
plt.xlabel("ra")
plt.ylabel("dec")
plt.title("Logical operations between SDSS and GALEX")
plt.grid(color="black", linestyle="dotted")
plt.show()

(Source code, png, hires.png, pdf)

../_images/logical_operations.png

Get the border(s) of a MOC

This example shows how to call get_boundaries. The borders are returned as a list of SkyCoord each defining one border. In this example:

  1. The sky coordinates defining the border of the MOC are projected to the pixel image system.

  2. Then, a matplotlib path is defined from the projected vertices.

  3. Finally the path is plot on top of the MOC.

import astropy.units as u
import matplotlib.pyplot as plt
import numpy as np
from astropy.coordinates import Angle, SkyCoord
from matplotlib.patches import PathPatch
from matplotlib.path import Path
from mocpy import MOC, WCS

moc = MOC.from_fits("polygon_moc.fits")
skycoords = moc.get_boundaries()

# Plot the MOC using matplotlib
fig = plt.figure(111, figsize=(10, 10))
# Define a astropy WCS easily
with WCS(
    fig,
    fov=20 * u.deg,
    center=SkyCoord(10, 5, unit="deg", frame="icrs"),
    coordsys="icrs",
    rotation=Angle(0, u.degree),
    # The gnomonic projection transforms great circles into straight lines.
    projection="TAN",
) as wcs:
    ax = fig.add_subplot(1, 1, 1, projection=wcs)
    # Call fill with a matplotlib axe and the `~astropy.wcs.WCS` wcs object.
    moc.fill(ax=ax, wcs=wcs, alpha=0.5, fill=True, color="red", linewidth=1)
    moc.border(ax=ax, wcs=wcs, alpha=1, color="red")

    # Plot the border
    from astropy.wcs.utils import skycoord_to_pixel

    x, y = skycoord_to_pixel(skycoords[0], wcs)
    p = Path(np.vstack((x, y)).T)
    patch = PathPatch(p, color="black", fill=False, alpha=0.75, lw=2)
    ax.add_patch(patch)

plt.xlabel("ra")
plt.ylabel("dec")
plt.title("Get the border(s) of a MOC")
plt.grid(color="black", linestyle="dotted")
plt.show()

(Source code)

Gravitational Waves MOCs

This example shows the probability confidence regions of gravitational waves. HEALPix cells are given under the uniq pixel notation. Each pixel is associated with a specific probability density value. We convert this into a probability by multiplying it with the area of each cell. Then, we can create a MOC from which a GW has x% of chance of being localized in it. By definition the MOC which has 100% of chance of containing a GW is the full sky MOC.

import astropy.units as u
import matplotlib.pyplot as plt
import numpy as np
from astropy.io import fits
from astropy.visualization.wcsaxes.frame import EllipticalFrame
from astropy.wcs import WCS
from mocpy import MOC

# this can be found in mocpy's repository
# https://github.com/cds-astro/mocpy/blob/master/resources/bayestar.multiorder.fits
fits_image_filename = "./../../resources/bayestar.multiorder.fits"

with fits.open(fits_image_filename) as hdul:
    hdul.info()
    data = hdul[1].data
    max_order = hdul[1].header["MOCORDER"]

uniq = data["UNIQ"]
probdensity = data["PROBDENSITY"]

# let's convert the probability density into a probability
orders = (np.log2(uniq // 4)) // 2
area = 4 * np.pi / np.array([MOC.n_cells(int(order)) for order in orders]) * u.sr
prob = probdensity * area

# now we create the mocs corresponding to different probability thresholds
cumul_to = np.linspace(0.5, 0.9, 5)[::-1]
colors = ["blue", "green", "yellow", "orange", "red"]
mocs = [
    MOC.from_valued_healpix_cells(uniq, prob, max_order, cumul_to=c) for c in cumul_to
]


# Plot the MOC using matplotlib
fig = plt.figure(111)
# Define a astropy WCS
wcs = WCS(
    {
        "naxis": 2,
        "naxis1": 3240,
        "naxis2": 3240,
        "crpix1": 1620.5,
        "crpix2": 1620.5,
        "cdelt1": -0.0353,
        "cdelt2": 0.0353,
        "ctype1": "RA---SIN",
        "ctype2": "DEC--SIN",
    },
)

ax = fig.add_subplot(1, 1, 1, projection=wcs, frame_class=EllipticalFrame)
# Call fill with a matplotlib axe and the `~astropy.wcs.WCS` wcs object.
for moc, c, col in zip(mocs, cumul_to, colors):
    moc.fill(
        ax=ax,
        wcs=wcs,
        alpha=0.5,
        linewidth=0,
        fill=True,
        color=col,
        label="confidence proba " + str(round(c * 100)) + "%",
    )
    moc.border(ax=ax, wcs=wcs, alpha=0.5, color=col)
ax.legend(loc="upper center", bbox_to_anchor=(0, 1))
ax.set_aspect(1.0)
plt.xlabel("ra")
plt.ylabel("dec")
plt.title("Bayestar")
plt.grid(color="black", linestyle="dotted")
plt.tight_layout()
plt.show()

(Source code, png, hires.png, pdf)

../_images/bayestar.png

Performing computation on the pixels of an FITS image lying in a MOC

This example shows how a MOC can filter pixels from a specific FITS image (i.e. associated with a WCS). These pixels can then be retrieved from the image for performing some computations on them: e.g. mean, variance analysis thanks to numpy/scikit-learn…

import astropy.units as u
import matplotlib.pyplot as plt
import numpy as np
from astropy.io import fits
from astropy.visualization import simple_norm
from astropy.wcs import WCS as WCS
from mocpy import MOC

# load 2MASS cutout covering the galactic plane
hdu = fits.open(
    "http://alasky.u-strasbg.fr/hips-image-services/hips2fits?hips=CDS%2FP%2F2MASS%2FK&width=1200&height=700&fov=30&projection=TAN&coordsys=galactic&rotation_angle=0.0&object=gal%20center&format=fits",
)

# load Spitzer MOC
moc = MOC.from_fits("http://skies.esac.esa.int/Spitzer/IRAC1_bright_ISM/Moc.fits")

# create WCS from 2MASS image header
twomass_wcs = WCS(header=hdu[0].header)

# compute skycoords for every pixel of the image
width = hdu[0].header["NAXIS1"]
height = hdu[0].header["NAXIS2"]

xv, yv = np.meshgrid(np.arange(0, width), np.arange(0, height))
skycoords = twomass_wcs.pixel_to_world(xv, yv)

ra, dec = skycoords.icrs.ra.deg, skycoords.icrs.dec.deg
# Get the skycoord lying in the MOC mask
mask_in_moc = moc.contains(ra * u.deg, dec * u.deg)

img = hdu[0].data
img_test = img.copy()

# Set to zero the pixels that do not belong to the MOC
img_test[~mask_in_moc] = 0

# Plot the filtered pixels image
fig = plt.figure(111, figsize=(15, 10))
ax = fig.add_subplot(1, 1, 1, projection=twomass_wcs)

im = ax.imshow(
    img_test,
    origin="lower",
    norm=simple_norm(hdu[0].data, "sqrt", vmin=-1, vmax=150),
)
plt.show()

(Source code, png, hires.png, pdf)

../_images/filter_image_pixels_00_00.png
# Sum of pixels lying in the MOC
np.sum(img[mask_in_moc])

# Sum of pixels of the whole image
np.sum(img)

TMOC: Temporal coverages

The TimeMOC class represents a temporal coverage.

STMOC: Space & Time coverages

Space-Time coverages are a new feature of mocpy since its version 0.7.0 and are bind spatial and temporal coverages together. The standard description is published by the IVOA here.

Space-Time coverages allow to:

  1. Retrieve the spatial coverage observed by a mission within a set of time frames (i.e. astropy.time.Time ranges).

  2. Retrieve the temporal coverage observed by a mission within a spatial coverage.

As we do for spatial or temporal coverages, one can also perform the union, intersection or difference between two Space-Time coverages.

FMOC: Frequency coverages