FITS Images

The previous section briefly introduced the WWT LayerManager, which allows you to add various graphical elements to the WWT view. One important use of this system, which we’ll cover in this section, is rendering FITS imagery.

Add the following JavaScript code to your index.html file. (If you’re not following this tutorial linearly, your baseline consists of the the HTML in A Basic WWT Window and the JavaScript in Loading an Image. Changes from this baseline are subtly highlighted below.)

// ... earlier code omitted ...

function on_wtml_loaded() {
    add_fits();
}

// ... intervening code omitted ...

function add_fits() {
    script_interface.addImageSetLayer(
        "https://github.com/WorldWideTelescope/pywwt-notebooks/raw/master/data/w5.fits",
        "fits", // the format of the data at the specified URL
        "w5.fits", // a name for the layer
        true, // automatically slew to the image center
        on_fits_loaded // callback invoked when the data are loaded
    );
}

function on_fits_loaded(layer) {
    layer.setImageScalePhysical(wwtlib.ScaleTypes.sqrt, 400, 1000);
    layer.set_colorMapperName("plasma");
    layer.set_opacity(0.8);
}

// ... following code omitted ...
</script>

If you relaunch your web app, you should see WWT slew to a view of the W5 star forming region:

WWT displaying a FITS image of W5

Here, ScriptInterface.addImageSetLayer() is a convenience function that helps orchestrate the various tasks associated with waiting for the data to be fetched, creating the layer, and adding it to the view. The on_fits_loaded callback is passed an ImageSetLayer instance, which includes methods for controlling how the FITS data will be mapped to RGB colors, as shown above. Whenever possible, WWT uses WebGL to RGB-ify and render the FITS data, so you can adjust display parameters like the image stretch or opacity and expect performance good enough for interactive use.

As usual, there are higher-level APIs to this functionality: WWTInstance.addImageSetLayer() and engineStore().addImageSetLayer() , which return promises that resolve to the ImageSetLayer instance.

WWT’s rendering engine cannot handle arbitrary FITS images containing, say, esoteric WCS definitions. In fact, it is fairly restrictive about what kinds of images it can display. We argue that this is desirable: the guts of the WWT engine are not the place to maintain code that tries to know how to read every kind of FITS file that you can throw at it. If you want to visualize arbitrary FITS images, you should include a code layer that homogenizes the input data to fit with WWT’s expectations. The Python function pywwt.utils.sanitize_image() does just that.

Large Data Sets🔗

On your first run of this code, there was probably a noticeable pause before WWT started slewing to the W5 region. This is because the engine had no choice but to download the entire w5.fits file, which weighs in at about 3.8 MiB, before displaying it. The image-display method shown above can work acceptably for small FITS data, but won’t scale for large images.

Fortunately, WWT can display large FITS images — in fact, it can display FITS images of arbitrary size. The tradeoff is that such images need to be preprocessed into a data format that’s friendly to progressive/streamed downloading, which FITS most certainly is not. Recognizing this, WWT provides a suite of tools that aim to make this data processing as easy as possible.

Let’s demonstrate this. Modify your JavaScript code to read as follows:

// ... earlier code omitted ...

function on_ready() {
    console.log("WWT is ready!");
    wwt = wwtlib.WWTControl.singleton;
    script_interface.add_collectionLoaded(on_wtml_loaded);
    script_interface.loadImageCollection("http://data1.wwtassets.org/packages/2021/09_phat_fits/index.wtml");
}

function on_wtml_loaded() {
    add_fits();
}

// ... intervening code omitted ...

function add_fits() {
    script_interface.addImageSetLayer(
        "http://data1.wwtassets.org/packages/2021/09_phat_fits/f475w/{1}/{3}/{3}_{2}.fits",
        "preloaded", // different mode: identify imagery using WTML metadata
        "PHAT F475W",
        true, // automatically slew to the image center
        on_fits_loaded
    );
}

function on_fits_loaded(layer) {
    // For technical reasons, with tiled FITS data we need to wait a frame
    // before applying the scale parameters -- otherwise they'll be overridden
    // by defaults specified in the WTML.
    window.requestAnimationFrame(function () {
        layer.setImageScalePhysical(wwtlib.ScaleTypes.linear, -0.1, 0.4);
    });
    layer.set_colorMapperName("viridis");
}

Now we are loading a WTML file that describes a “tiled FITS” dataset, where the underlying data have been broken into pieces and progressively downsampled into a “tile pyramid”. For instance, the dataset in this particular example, from the PHAT project, represents about 25 GiB of FITS data. And it probably loaded up faster than the W5 example above! If you zoom in, you’ll see the FITS data tile in at higher resolutions on-the-fly.

WWT displaying the PHAT M31 mosaic, dude!

As mentioned in a previous section, the toasty Python library is WWT’s official tool for generating tiled FITS data from whatever source files you have. It is thoroughly-tested and can use several forms of parallelism to accelerate its processing. The pywwt package includes support to automatically invoke Toasty as needed to process data, as well as to make those data available through a web server so that the WWT frontend can retrieve them. This support even works in cloud-based Jupyter environments, if the WWT kernel data relay Jupyter server extension has been installed.