In [ ]:
from __future__ import print_function, division, absolute_import

Visualizing astronomical images

David Shupe, Caltech/IPAC
Scientist, ZTF and LSST Science User Interface

Topics for this session

Visualizing numpy arrays as bitmaps and contours
Adding axis information to matplotlib renderings
Overlaying catalogs
Linking and brushing for data exploration

Tools covered

Astropy visualization + matplotlib
Ginga
Firefly
Glue

Astronomical image?

For today, FITS format
Includes World Coordinate System information
Overlay tabular data with coordinates

Scaling and Stretching

The astropy.visualization module provides a framework for transforming values in images (and more generally any arrays), typically for the purpose of visualization. Two main types of transformations are provided:

Normalization to the [0:1] range using lower and upper limits where $x$ represents the values in the original image:

$y = {{x - v_{min}} \over {v_{max} - v_{min}}}$

Stretching of values in the [0:1] range to the [0:1] range using a linear or non-linear function:

$z=f(y)$

displaying a bitmap -- imshow (matplotlib)


In [ ]:
import astropy.io.fits as fits
import matplotlib.pylab as plt
from astropy.visualization import (MinMaxInterval, LogStretch,
                                   ImageNormalize)

Here we use only the numpy array of the data.


In [ ]:
image = fits.getdata('data/w5.fits')

Astropy visualization functions for scaling and stretching


In [ ]:
# Scale to image minimum and maximum, stretch with log function
norm = ImageNormalize(image, interval=MinMaxInterval(),
                      stretch=LogStretch())

Note that astronomical images in FITS use origin="lower" which is not the matplotlib default


In [ ]:
%matplotlib inline
fig = plt.figure(figsize=(8,8))
plt.imshow(image, norm=norm, origin="lower", cmap='Greys_r');

Contours with matplotlib


In [ ]:
%matplotlib inline
from astropy.visualization import (ManualInterval, LogStretch,
                                   ImageNormalize)
norm = ImageNormalize(image, interval=ManualInterval(vmin=370.0, vmax=1000.0), 
                      stretch=LogStretch())

In [ ]:
%matplotlib inline
fig = plt.figure(figsize=(8,8))
#plt.imshow(image, norm=norm, origin="lower", cmap='Greys_r')
plt.contour(image, [550, 750, 950], origin='lower', 
            colors=['grey', 'green', 'blue']);

See this contour demo from the matplotlib documentation for more to do with contours

Adding coordinates (wcsaxes)

Prior to this point, we have visualized numpy arrays


In [ ]:
from astropy.wcs import WCS

hdu = fits.open('data/w5.fits')[0]
wcs = WCS(hdu.header)

In the case the World Coordinate System is a simple tangent projection


In [ ]:
wcs

In [ ]:
norm = ImageNormalize(hdu.data, interval=MinMaxInterval(),
                     stretch=LogStretch())

Make sure to pick a good sequential colormap and avoid the default 'jet'!


In [ ]:
%matplotlib inline
fig = plt.figure(figsize=(8,8))
ax = plt.subplot(projection=wcs)
plt.imshow(hdu.data, norm=norm, cmap='viridis', origin="lower")
plt.grid(color='white', ls='solid')
plt.xlabel('Right Ascension')
plt.ylabel('Declination')

Adding a secondary axis


In [ ]:
fig = plt.figure(figsize=(8,8))
ax = plt.subplot(projection=wcs)

overlay = ax.get_coords_overlay('galactic')

plt.imshow(hdu.data, norm=norm, origin="lower", cmap='Greys_r')


ax.coords['ra'].set_ticks(color='white')
ax.coords['dec'].set_ticks(color='white')

ax.coords['ra'].set_axislabel('Right Ascension')
ax.coords['dec'].set_axislabel('Declination')

ax.coords.grid(color='yellow', linestyle='solid', alpha=0.5)

overlay['l'].set_ticks(color='white')
overlay['b'].set_ticks(color='white')

overlay['l'].set_axislabel('Galactic Longitude')
overlay['b'].set_axislabel('Galactic Latitude')

overlay.grid(color='white', linestyle='solid', alpha=0.5)

Overlaying markers


In [ ]:
from astropy.table import Table
w5tbl = Table.read('data/w5_wise.tbl', format='ascii.ipac')
w5tbl = w5tbl[w5tbl['w4snr'] > 30.0]

In [ ]:
fit = plt.figure(figsize=(8,8))
hdu = fits.open('data/w5.fits')[0]
wcs = WCS(hdu.header)
ax = plt.subplot(projection=wcs)
plt.imshow(hdu.data, norm=norm, origin="lower", cmap='Greys_r')
ax.scatter(w5tbl['ra'], w5tbl['dec'], transform=ax.get_transform('world'))
plt.xlabel('Right Ascension')
plt.ylabel('Declination')

Ginga for image display

Toolkit for building viewers for scientific (esp. astronomical) data in Python
Written and maintained by s/w engineers at the Subaru Telescope, NAOJ
Image display object
Plugin-based; highly customizable

Installing Ginga will put ginga in your command path (same location as python).

This is the "reference viewer" which we can run from the command line.


In [ ]:
## uncomment to run
#!ginga ./data/w5.fits

Ginga can be run from the notebook.

To show some of Ginga's interactive capabilities, we will follow an excerpt of this online notebook.

  • Start up a viewer
  • Retrieve coordinates from it
  • Draw a rectangle
  • Find objects in the rectangle and overplot

In [ ]:
# setup
from ginga.web.pgw import ipg
# Set this to True if you have a non-buggy python OpenCv bindings--it greatly speeds up some operations
use_opencv = False

server = ipg.make_server(host='localhost', port=9914, use_opencv=use_opencv)

In [ ]:
# Start viewer server
# IMPORTANT: if running in an IPython/Jupyter notebook, use the no_ioloop=True option
server.start(no_ioloop=True)

In [ ]:
# Get a viewer
# This will get a handle to the viewer v1 = server.get_viewer('v1')
v1 = server.get_viewer('v1')

In [ ]:
# where is my viewer
v1.url

In [ ]:
# open the viewer in a new tab
v1.open()

NOTE: if you don't have the webbrowser module, open the link that was printed in the cell above in a new window to get the viewer.

You can open as many of these viewers as you want--just keep a handle to it and use a different name for each unique one.

Keyboard/mouse bindings in the viewer window: http://ginga.readthedocs.io/en/latest/quickref.html


In [ ]:
# Load an image into the viewer
# (change the path to where you downloaded the sample images, or use your own)
v1.load('./data/w5.fits')

In [ ]:
# Example of embedding a viewer
v1.embed(height=650)

In [ ]:
# capture the current state of the screen
v1.show()

Now set a pan position by shift-clicking somewhere in the viewer window.


In [ ]:
# Let's get the pan position we just set
dx, dy = v1.get_pan()
dx, dy

In [ ]:
# Getting values from the FITS header is also easy
img = v1.get_image()
hdr =img.get_header()
hdr['SURVEY']

In [ ]:
# What are the coordinates of the pan position?
# This uses astropy.wcs under the hood if you have it installed
img.pixtoradec(dx, dy)

In [ ]:
# Let's overlay some objects
# First, let's add a drawing canvas
canvas = v1.add_canvas()

In [ ]:
# Use our table from before
# Draw circles around all objects
Circle = canvas.get_draw_class('circle')
for (ra, dec) in zip(w5tbl['ra'],w5tbl['dec']):
    x, y = img.radectopix(ra,dec)
    canvas.add(Circle(x, y, radius=10, color='yellow'))

In [ ]:
v1.show()

Firefly

Web-based visualizer
Basis for LSST Science User Interface
Remote server sits close to the data
Many operations performed by your browser
Developed for Spitzer Heritage Archive
Employed as IRSA Viewer

Can access user interface from IRSA

Example: Herschel High-Level Images http://irsa.ipac.caltech.edu/data/Herschel/HHLI/index.html
Search on W5
Overlay a WISE catalog

Easy way to start a Firefly server

Download Firefly Standalone from release page (https://github.com/Caltech-IPAC/firefly/releases

Only requirement is java 1.8

Start with java -jar firefly-exec.war

By default, will be available on localhost port 80: http://localhost:8080/firefly/

Python client for Firefly

Install with pip install firefly_client
This part requires a local Firefly server running


In [ ]:
from firefly_client import FireflyClient

In [ ]:
fc = FireflyClient('localhost:8080', channel='mine')

In [ ]:
fc.launch_browser()

In [ ]:
fval = fc.upload_file('./data/w5.fits')

In [ ]:
fc.show_fits(fval, plot_id='w5_wise')

In [ ]:
tval = fc.upload_file('./data/w5_wise.tbl')

In [ ]:
fc.show_table(tval, title='Bright WISE W4')

The LSST stack can display images to Firefly.

Experimental Jupyter widgets have been developed.

Glue for linking data

Based on brushing-and-linking paradigm
Used for JWST
Images, scatter plots, histograms

To start from the notebook, execute the next line in its own cell


In [ ]:
%gui qt

In [ ]:
from glue import qglue

In [ ]:
qglue()

After quitting glue, try linking the data


In [ ]:
from glue.core.data_factories import load_data
from glue.core import DataCollection
from glue.core.link_helpers import LinkSame
from glue.app.qt.application import GlueApplication
import warnings
from astropy.utils.exceptions import AstropyWarning

#load 2 datasets from files
image = load_data('./data/w5.fits')
with warnings.catch_warnings():
    warnings.simplefilter('ignore', AstropyWarning)
    catalog = load_data('./data/w5_psc.vot')
dc = DataCollection([image, catalog])

In [ ]:
# link positional information
dc.add_link(LinkSame(image.id['Right Ascension'], catalog.id['RAJ2000']))
dc.add_link(LinkSame(image.id['Declination'], catalog.id['DEJ2000']))

In [ ]:
#start Glue
app = GlueApplication(dc)
app.start()

In [ ]:
image.component_ids()

In [ ]:
dc.links

In [ ]:
dc.subset_groups

In [ ]:
g1 = dc.subset_groups[0]

In [ ]:
g1.label

In [ ]: