Hubble Source Catalog API Notebook

August 2019, Rick White

A new MAST interface supports queries to the current and previous versions of the Hubble Source Catalog. It allows searches of the summary table (with multi-filter mean photometry) and the detailed table (with all the multi-epoch measurements). It also has an associated API, which is used in this notebook.

This is based on HSC Use Case #3.

  • It searches the HSC for variable objects in the vicinity of dwarf galaxy IC 1613,
  • shows the positions of those objects in a color-magnitude diagram,
  • extracts light curves for an example object, and
  • displays cutout images from the Hubble observations that were used for the light curve measurements.

The whole process takes only 30 seconds to complete.

This notebook is available for download. Another simple notebook generates a color-magnitude diagram for the Small Magellanic Cloud in only a couple of minutes. A more complex notebook that shows how to access the proper motion tables using the HSC API is also available.

Initialization

Install Python modules

This notebook requires the use of Python 3.

This needs the requests and pillow modules in addition to the common requirements of astropy, numpy and scipy. For anaconda versions of Python the installation commands are:

conda install requests
conda install pillow
In [1]:
%matplotlib inline
import astropy, pylab, time, sys, os, requests, json
import numpy as np
from pprint import pprint

from astropy.table import Table
from astropy.io import ascii

from PIL import Image
from io import BytesIO

# Set page width to fill browser for longer output lines
from IPython.core.display import display, HTML
display(HTML("<style>.container { width:100% !important; }</style>"))
# set width for pprint
astropy.conf.max_width = 150

Useful functions

Execute HSC searches and resolve names using MAST query.

In [2]:
hscapiurl = "https://catalogs.mast.stsci.edu/api/v0.1/hsc"

def hsccone(ra,dec,radius,table="summary",release="v3",format="csv",magtype="magaper2",
            columns=None, baseurl=hscapiurl, verbose=False,
            **kw):
    """Do a cone search of the HSC catalog
    
    Parameters
    ----------
    ra (float): (degrees) J2000 Right Ascension
    dec (float): (degrees) J2000 Declination
    radius (float): (degrees) Search radius (<= 0.5 degrees)
    table (string): summary, detailed, propermotions, or sourcepositions
    release (string): v3 or v2
    magtype (string): magaper2 or magauto (only applies to summary table)
    format: csv, votable, json
    columns: list of column names to include (None means use defaults)
    baseurl: base URL for the request
    verbose: print info about request
    **kw: other parameters (e.g., 'numimages.gte':2)
    """
    
    data = kw.copy()
    data['ra'] = ra
    data['dec'] = dec
    data['radius'] = radius
    return hscsearch(table=table,release=release,format=format,magtype=magtype,
                     columns=columns,baseurl=baseurl,verbose=verbose,**data)


def hscsearch(table="summary",release="v3",magtype="magaper2",format="csv",
              columns=None, baseurl=hscapiurl, verbose=False,
           **kw):
    """Do a general search of the HSC catalog (possibly without ra/dec/radius)
    
    Parameters
    ----------
    table (string): summary, detailed, propermotions, or sourcepositions
    release (string): v3 or v2
    magtype (string): magaper2 or magauto (only applies to summary table)
    format: csv, votable, json
    columns: list of column names to include (None means use defaults)
    baseurl: base URL for the request
    verbose: print info about request
    **kw: other parameters (e.g., 'numimages.gte':2).  Note this is required!
    """
    
    data = kw.copy()
    if not data:
        raise ValueError("You must specify some parameters for search")
    if format not in ("csv","votable","json"):
        raise ValueError("Bad value for format")
    url = "{}.{}".format(cat2url(table,release,magtype,baseurl=baseurl),format)
    if columns:
        # check that column values are legal
        # create a dictionary to speed this up
        dcols = {}
        for col in hscmetadata(table,release,magtype)['name']:
            dcols[col.lower()] = 1
        badcols = []
        for col in columns:
            if col.lower().strip() not in dcols:
                badcols.append(col)
        if badcols:
            raise ValueError('Some columns not found in table: {}'.format(', '.join(badcols)))
        # two different ways to specify a list of column values in the API
        # data['columns'] = columns
        data['columns'] = '[{}]'.format(','.join(columns))

    # either get or post works
    # r = requests.post(url, data=data)
    r = requests.get(url, params=data)

    if verbose:
        print(r.url)
    r.raise_for_status()
    if format == "json":
        return r.json()
    else:
        return r.text


def hscmetadata(table="summary",release="v3",magtype="magaper2",baseurl=hscapiurl):
    """Return metadata for the specified catalog and table
    
    Parameters
    ----------
    table (string): summary, detailed, propermotions, or sourcepositions
    release (string): v3 or v2
    magtype (string): magaper2 or magauto (only applies to summary table)
    baseurl: base URL for the request
    
    Returns an astropy table with columns name, type, description
    """
    url = "{}/metadata".format(cat2url(table,release,magtype,baseurl=baseurl))
    r = requests.get(url)
    r.raise_for_status()
    v = r.json()
    # convert to astropy table
    tab = Table(rows=[(x['name'],x['type'],x['description']) for x in v],
               names=('name','type','description'))
    return tab


def cat2url(table="summary",release="v3",magtype="magaper2",baseurl=hscapiurl):
    """Return URL for the specified catalog and table
    
    Parameters
    ----------
    table (string): summary, detailed, propermotions, or sourcepositions
    release (string): v3 or v2
    magtype (string): magaper2 or magauto (only applies to summary table)
    baseurl: base URL for the request
    
    Returns a string with the base URL for this request
    """
    checklegal(table,release,magtype)
    if table == "summary":
        url = "{baseurl}/{release}/{table}/{magtype}".format(**locals())
    else:
        url = "{baseurl}/{release}/{table}".format(**locals())
    return url


def checklegal(table,release,magtype):
    """Checks if this combination of table, release and magtype is acceptable
    
    Raises a ValueError exception if there is problem
    """
    
    releaselist = ("v2", "v3")
    if release not in releaselist:
        raise ValueError("Bad value for release (must be one of {})".format(
            ', '.join(releaselist)))
    if release=="v2":
        tablelist = ("summary", "detailed")
    else:
        tablelist = ("summary", "detailed", "propermotions", "sourcepositions")
    if table not in tablelist:
        raise ValueError("Bad value for table (for {} must be one of {})".format(
            release, ", ".join(tablelist)))
    if table == "summary":
        magtypelist = ("magaper2", "magauto")
        if magtype not in magtypelist:
            raise ValueError("Bad value for magtype (must be one of {})".format(
                ", ".join(magtypelist)))


def mastQuery(request, url='https://mast.stsci.edu/api/v0/invoke'):
    """Perform a MAST query.

    Parameters
    ----------
    request (dictionary): The MAST request json object
    url (string): The service URL

    Returns the returned data content
    """
    
    # Encoding the request as a json string
    requestString = json.dumps(request)
    r = requests.post(url, data={'request': requestString})
    r.raise_for_status()
    return r.text


def resolve(name):
    """Get the RA and Dec for an object using the MAST name resolver
    
    Parameters
    ----------
    name (str): Name of object

    Returns RA, Dec tuple with position
    """

    resolverRequest = {'service':'Mast.Name.Lookup',
                       'params':{'input':name,
                                 'format':'json'
                                },
                      }
    resolvedObjectString = mastQuery(resolverRequest)
    resolvedObject = json.loads(resolvedObjectString)
    # The resolver returns a variety of information about the resolved object, 
    # however for our purposes all we need are the RA and Dec
    try:
        objRa = resolvedObject['resolvedCoordinate'][0]['ra']
        objDec = resolvedObject['resolvedCoordinate'][0]['decl']
    except IndexError as e:
        raise ValueError("Unknown object '{}'".format(name))
    return (objRa, objDec)

Get metadata on available columns

The metadata query returns information on the columns in the table. It works for any of the tables in the API (summary, detailed, propermotions, sourcepositions).

Note that the summary table has a huge number of columns! Each of the 133 filter/detector combinations has 3 columns with the magnitude, median absolute deviation (MAD, a robust measure of the scatter among the measurements), and the number of independent measurements in the filter. The filter name includes a prefix for the detector (A=ACS/WFC, W3=WFC3/UVIS or WFC3/IR, W2=WFPC2) followed by the standard name of the filter. So for instance all three instruments have an F814W filter, so there are columns for A_F814W, W3_F814W, and W2_F814W.

In [3]:
meta = hscmetadata("summary")
print(len(meta),"columns in summary")
filterlist = meta['name'][19::3].tolist()
print(len(filterlist),"filters")
pprint(filterlist, compact=True)
meta[:19]
418 columns in summary
133 filters
['W3_BLANK', 'W2_F122M', 'W2_F160BN15', 'W2_F160BW', 'W2_F170W', 'W2_F185W',
 'W3_F200LP', 'W3_F218W', 'W2_F218W', 'W3_F225W', 'W3_FQ232N', 'W3_FQ243N',
 'W2_F255W', 'W3_F275W', 'W3_F280N', 'W3_G280', 'W2_F300W', 'W3_F300X',
 'W3_F336W', 'W2_F336W', 'W3_F343N', 'W2_F343N', 'W3_F350LP', 'W3_F373N',
 'W2_F375N', 'W3_FQ378N', 'W2_F380W', 'W3_FQ387N', 'W3_F390M', 'W2_F390N',
 'W3_F390W', 'W3_F395N', 'W3_F410M', 'W2_F410M', 'W3_FQ422M', 'A_F435W',
 'W3_FQ436N', 'W3_FQ437N', 'W2_F437N', 'W3_F438W', 'W2_F439W', 'W2_F450W',
 'W3_F467M', 'W2_F467M', 'W3_F469N', 'W2_F469N', 'A_F475W', 'W3_F475W',
 'W3_F475X', 'W3_F487N', 'W2_F487N', 'W3_FQ492N', 'A_F502N', 'W3_F502N',
 'W2_F502N', 'W3_FQ508N', 'W3_F547M', 'W2_F547M', 'A_F550M', 'A_F555W',
 'W3_F555W', 'W2_F555W', 'W2_F569W', 'W3_FQ575N', 'W2_F588N', 'W3_F600LP',
 'A_F606W', 'W3_F606W', 'W2_F606W', 'W3_FQ619N', 'W3_F621M', 'W2_F622W',
 'A_F625W', 'W3_F625W', 'W3_F631N', 'W2_F631N', 'W3_FQ634N', 'W3_F645N',
 'W3_F656N', 'W2_F656N', 'W3_F657N', 'A_F658N', 'W3_F658N', 'W2_F658N',
 'A_F660N', 'W3_F665N', 'W3_F665N_F6', 'W3_FQ672N', 'W3_F673N', 'W2_F673N',
 'W3_FQ674N', 'W2_F675W', 'W3_F680N', 'W3_F689M', 'W2_F702W', 'W3_FQ727N',
 'W3_FQ750N', 'W3_F763M', 'A_F775W', 'W3_F775W', 'W2_F785LP', 'W2_F791W',
 'A_F814W', 'W3_F814W', 'W2_F814W', 'W3_F845M', 'A_F850LP', 'W3_F850LP',
 'W2_F850LP', 'W3_FQ889N', 'W3_FQ906N', 'W3_FQ924N', 'W3_FQ937N', 'W3_F953N',
 'W2_F953N', 'W3_F098M', 'W3_G102', 'W2_F1042M', 'W3_F105W', 'W3_F110W',
 'W3_F125W', 'W3_F126N', 'W3_F127M', 'W3_F128N', 'W3_F130N', 'W3_F132N',
 'W3_F139M', 'W3_F140W', 'W3_G141', 'W3_F153M', 'W3_F160W', 'W3_F164N',
 'W3_F167N']
Out[3]:
Table length=19
nametypedescription
str16str5str163
MatchIDlongidentifier for the match
MatchRAfloatright ascension coordinate of the match position
MatchDecfloatdeclination coordinate of the match position
DSigmafloatstandard deviation of source positions in match
AbsCorrcharindicator of whether the match contains sources that are aligned to a standard catalog
NumFiltersintnumber of filters in match with sources detected in the aper2 aperture
NumVisitsintnumber of visits in match with sources detected in the aper2 aperture
NumImagesintnumber of Hubble Legacy Archive single filter, visit-combined (level 2) images in match with sources detected in the aper2 aperture
StartTimecharearliest start time of exposures in match with sources detected in the aper2 aperture
StopTimecharlatest stop time of exposures in match with sources detected in the aper2 aperture
StartMJDfloatmodified Julian date (MJD) for earliest start time of exposures in match with sources detected in the aper2 aperture
StopMJDfloatmodified Julian date (MJD) for latest stop time of exposures in match with sources detected in the aper2 aperture
TargetNamecharname of a target for an exposure in match
CIfloataverage normalized concentration index for sources detected in the aper2 aperture within the match
CI_Sigmafloatstandard deviation of normalized concentration index values for sources detected in the aper2 aperture within the match
KronRadiusfloataverage Kron radius for sources detected in the aper2 aperture within the match
KronRadius_Sigmafloatstandard deviation of Kron radius values for sources detected in the aper2 aperture within the match
Extinctionfloatextinction, obtained from the NASA/IPAC Extragalactic Database (NED), along the line of sight to the match position
SpectrumFlagcharY/N indicator of whether there is a spectrum in the Hubble Legacy Archive for this match. If the value is Y, then there is an entry in table SpecCat for the match.

Find variable objects in the dwarf irregular galaxy IC 1613

This is based on HSC Use Case #3, which shows an example of selecting objects from the HSC in portal. This is simple to do using the HSC API.

Use MAST name resolver to get position of IC 1613

In [4]:
target = 'IC 1613'
ra, dec = resolve(target)
print(target,ra,dec)
IC 1613 16.19913 2.11778

Select objects with enough measurements to determine variability

This searches the summary table for objects within 0.5 degrees of the galaxy center that have at least 10 measurements in both ACS F475W and F814W.

In [5]:
# save typing a quoted list of columns
columns = """MatchID,MatchRA,MatchDec,NumFilters,NumVisits,NumImages,StartMJD,StopMJD,
    A_F475W, A_F475W_N, A_F475W_MAD,
    A_F814W, A_F814W_N, A_F814W_MAD""".split(",")
columns = [x.strip() for x in columns]
columns = [x for x in columns if x and not x.startswith('#')]

constraints = {'A_F475W_N.gte': 10, 'A_F814W_N.gte': 10}

t0 = time.time()
tab = ascii.read(hsccone(ra,dec,0.5,table="summary",release='v3',columns=columns,verbose=True,**constraints))
print("{:.1f} s: retrieved data and converted to {}-row astropy table".format(time.time()-t0, len(tab)))

# clean up the output format
tab['A_F475W'].format = "{:.3f}"
tab['A_F475W_MAD'].format = "{:.3f}"
tab['A_F814W'].format = "{:.3f}"
tab['A_F814W_MAD'].format = "{:.3f}"
tab['MatchRA'].format = "{:.6f}"
tab['MatchDec'].format = "{:.6f}"
tab['StartMJD'].format = "{:.5f}"
tab['StopMJD'].format = "{:.5f}"
tab
https://catalogs.mast.stsci.edu/api/v0.1/hsc/v3/summary/magaper2.csv?A_F475W_N.gte=10&A_F814W_N.gte=10&ra=16.19913&dec=2.11778&radius=0.5&columns=%5BMatchID%2CMatchRA%2CMatchDec%2CNumFilters%2CNumVisits%2CNumImages%2CStartMJD%2CStopMJD%2CA_F475W%2CA_F475W_N%2CA_F475W_MAD%2CA_F814W%2CA_F814W_N%2CA_F814W_MAD%5D
6.2 s: retrieved data and converted to 18666-row astropy table
Out[5]:
Table length=18666
MatchIDMatchRAMatchDecNumFiltersNumVisitsNumImagesStartMJDStopMJDA_F475WA_F475W_NA_F475W_MADA_F814WA_F814W_NA_F814W_MAD
int64float64float64int64int64int64float64float64float64int64float64float64int64float64
1840963216.0911732.1769292122453965.2915353967.7194123.498120.01122.080120.035
1841866616.1239352.1502163142653965.2915357009.8626223.913120.02022.675120.004
1838441316.1185092.1677323142653965.2915357009.8626225.563120.02424.838120.015
1839745816.1290542.1638662122453965.2915353967.7194125.998120.05326.004120.145
1839911216.1291132.1622022122453965.2915353967.7194125.857120.01626.314120.046
1852925216.1043892.1592662122453965.2915353967.7194126.231120.02126.333120.058
1837756016.1470882.1536092122453965.2915353967.7194125.410120.03225.614120.033
1837778916.1046792.1671272122453965.2915353967.7194124.888120.01625.446120.042
1838076416.0949622.1673262122453965.2915353967.7194125.211120.01225.728120.039
1838121516.1508782.1449082122253965.2915353967.7194126.226120.03026.569100.034
..........................................
9141168016.1210022.1492402122353965.2915353967.7194126.796110.06526.552120.028
9142534516.1334572.1542123142653965.2915357009.8626225.187120.00624.261120.009
9142670016.1254752.1697333132453965.4247357009.8626226.153110.03025.386110.023
9125738616.1308662.1875882122453965.2915353967.7194124.477120.01423.786120.032
9125794616.1041602.1752393142653965.2915357009.8626225.949120.02524.301120.023
9126038716.1039872.1252802122453965.2915353967.7194125.526120.01924.906120.034
9140022416.0943292.1399212122453965.2915353967.7194126.707120.02826.045120.055
9239981016.1421612.1624612122453965.2915353967.7194125.100120.01524.312120.015
9123735516.0963272.1528982122253965.2915353967.7194126.252120.05826.717100.058
9123933216.1165022.1391292122453965.2915353967.7194123.124120.00823.957120.008

Plot object positions on the sky

We mark the galaxy center as well. Note that this field is in the outskirts of IC 1613. The 0.5 search radius (which is the maximum allowed in the API) allows finding these objects.

In [6]:
pylab.rcParams.update({'font.size': 16})
pylab.figure(1,(10,10))
pylab.plot(tab['MatchRA'], tab['MatchDec'], 'bo', markersize=1,
          label='{} HSC measurements'.format(len(tab)))
pylab.plot(ra,dec,'rx',label=target,markersize=10)
pylab.gca().invert_xaxis()
pylab.gca().set_aspect('equal')
pylab.xlabel('RA [deg]')
pylab.ylabel('Dec [deg]')
pylab.legend(loc='best')
Out[6]:
<matplotlib.legend.Legend at 0x1197f9a58>

Plot MAD variability index versus magnitude in F475W

The median absolute deviation is measured among the ~12 magnitude measurements in the catalog. Some scatter is expected from noise (which increases for fainter objects). Objects with MAD values that are high are likely to be variable.

Select variable objects that are not too faint.

In [7]:
wvar = np.where((tab['A_F475W_MAD']>0.1) & (tab['A_F475W']<24) & (tab['A_F475W']>21))[0]
pylab.rcParams.update({'font.size': 16})
pylab.figure(1,(10,10))
pylab.plot(tab['A_F475W'], tab['A_F475W_MAD'], 'bo', markersize=2,
          label='{} HSC measurements near {}'.format(len(tab),target))
pylab.plot(tab['A_F475W'][wvar], tab['A_F475W_MAD'][wvar], 'ro', markersize=5,
          label='{} variable candidates'.format(len(wvar)))
pylab.xlabel('A_F475W [mag]')
pylab.ylabel('A_F475W_MAD [mag]')
pylab.legend(loc='best')
Out[7]:
<matplotlib.legend.Legend at 0x11caa2978>

Check positions of variable objects in a color-magnitude diagram

Note that these objects are generally located in the Cepheid instability strip.

In [8]:
pylab.rcParams.update({'font.size': 16})
pylab.figure(1,(10,10))
b_minus_i = tab['A_F475W'] - tab['A_F814W']
pylab.plot(b_minus_i, tab['A_F475W'], 'bo', markersize=2,
          label='{} HSC measurements near {}'.format(len(tab),target))
pylab.plot(b_minus_i[wvar], tab['A_F475W'][wvar], 'ro', markersize=5,
          label='{} variable candidates'.format(len(wvar)))
pylab.ylabel('A_F475W [mag]')
pylab.xlabel('A_F475W - A_F814W [mag]')
pylab.gca().invert_yaxis()
pylab.legend(loc='best')
Out[8]:
<matplotlib.legend.Legend at 0x11e04bf60>

Query the API for the light curve for one of the objects

Select the most variable object as an example.

In [9]:
wvar = wvar[np.argsort(-tab['A_F475W_MAD'][wvar])]
iselect = wvar[0]
print("MatchID {} B = {:.3f} B-I = {:.3f}".format(
    tab['MatchID'][iselect], tab['A_F475W'][iselect], b_minus_i[iselect]))
tab[wvar]
MatchID 80189155 B = 22.451 B-I = 0.450
Out[9]:
Table length=29
MatchIDMatchRAMatchDecNumFiltersNumVisitsNumImagesStartMJDStopMJDA_F475WA_F475W_NA_F475W_MADA_F814WA_F814W_NA_F814W_MAD
int64float64float64int64int64int64float64float64float64int64float64float64int64float64
8018915516.1407402.1552883142653965.2915357009.8626222.451120.29422.001120.198
820048816.1447722.1523383142653965.2915357009.8626222.202120.26021.768120.188
6601267816.1351342.1547403142653965.2915357009.8626222.559120.25322.101120.192
6307852616.1055132.1555803142653965.2915357009.8626223.317120.22722.897120.138
8990986216.1171802.1688103142653965.2915357009.8626222.733120.22422.545120.099
9211935816.1365632.1828172122453965.2915353967.7194123.347120.21323.234120.131
6698056416.1256112.1352873142653965.2915357009.8626222.732120.19422.365120.138
9572235616.1330302.1756383142653965.2915357009.8626223.359120.18022.921120.129
6835608316.0957092.1474602122453965.2915353967.7194123.378120.17622.981120.109
4578661016.1213752.1338533142653965.2915357009.8626222.901120.17422.557120.131
..........................................
6979010916.1412842.1528813142653965.2915357009.8626222.635120.13322.138120.071
10043722816.1113102.1609083142653965.2915357009.8626223.382120.12322.914120.070
3085511116.1059252.1576033142653965.2915357009.8626222.781120.11722.698120.056
7021746816.1071422.1781613142653965.2915357009.8626223.804120.11623.428120.116
951069816.1379632.1410273142653965.2915357009.8626223.343120.11423.101120.080
1331738016.1183222.1528173142653965.2915357009.8626223.599120.10923.169120.091
1724089916.1396672.1535973142653965.2915357009.8626222.761120.10522.195120.073
10554598616.1443362.1562623142653965.2915357009.8626223.160120.10422.941120.138
6671616616.1322602.1635123142653965.2915357009.8626223.075120.10422.864120.075
6375565016.0953562.1373382122453965.2915353967.7194122.729120.10322.442120.062

Get column metadata for detailed observation table (which has time-dependent magnitudes).

In [10]:
meta = hscmetadata("detailed")
print(len(meta),"columns in detailed")
pprint(meta['name'].tolist(), compact=True)
39 columns in detailed
['CatID', 'MatchID', 'MemID', 'SourceID', 'ImageID', 'Det', 'MatchRA',
 'MatchDec', 'SourceRA', 'SourceDec', 'D', 'DSigma', 'AbsCorr', 'XImage',
 'YImage', 'ImageName', 'Instrument', 'Mode', 'Detector', 'Aperture',
 'ExposureTime', 'StartTime', 'StopTime', 'StartMJD', 'StopMJD', 'WaveLength',
 'Filter', 'TargetName', 'FluxAper2', 'MagAper2', 'MagAuto', 'PropID', 'CI',
 'KronRadius', 'Flags', 'HTMID', 'X', 'Y', 'Z']

Get separate light curves for F475W and F814W from the detailed table

In [11]:
columns = """MatchID,SourceID,StartMJD,Detector,Filter,MagAper2,Flags,ImageName""".split(",")
columns = [x.strip() for x in columns]
columns = [x for x in columns if x and not x.startswith('#')]

constraints = {'MatchID': tab['MatchID'][iselect], 'Detector': 'ACS/WFC'}
t0 = time.time()
f475 = ascii.read(hscsearch(table="detailed",release='v3',columns=columns,Filter='F475W',**constraints))
f814 = ascii.read(hscsearch(table="detailed",release='v3',columns=columns,Filter='F814W',**constraints))
print("{:.1f} s: retrieved data and converted to {} (F475W) and {} (F814W) row astropy tables".format(time.time()-t0, len(f475), len(f814)))

f475.sort('StartMJD')
f814.sort('StartMJD')
f475['MagAper2'].format = "{:.3f}"
f475['StartMJD'].format = "{:.5f}"
f814['MagAper2'].format = "{:.3f}"
f814['StartMJD'].format = "{:.5f}"

f475
0.3 s: retrieved data and converted to 12 (F475W) and 12 (F814W) row astropy tables
Out[11]:
Table length=12
MatchIDSourceIDStartMJDDetectorFilterMagAper2FlagsImageName
int64int64float64str7str5float64int64str26
80189155400099906230553965.29153ACS/WFCF475W22.1920hst_10505_07_acs_wfc_f475w
80189155400099288800153965.42473ACS/WFCF475W22.2460hst_10505_08_acs_wfc_f475w
80189155400109511696253965.55794ACS/WFCF475W22.3760hst_10505_09_acs_wfc_f475w
80189155400092268073553965.69115ACS/WFCF475W22.5520hst_10505_10_acs_wfc_f475w
80189155400104227614553966.29057ACS/WFCF475W22.7050hst_10505_11_acs_wfc_f475w
80189155400122522139053966.42377ACS/WFCF475W22.8020hst_10505_12_acs_wfc_f475w
80189155400089576071953966.55698ACS/WFCF475W22.7790hst_10505_13_acs_wfc_f475w
80189155400081431786353966.69019ACS/WFCF475W22.8010hst_10505_14_acs_wfc_f475w
80189155400083092964353967.22301ACS/WFCF475W22.5260hst_10505_15_acs_wfc_f475w
80189155400085917111553967.35622ACS/WFCF475W21.5220hst_10505_16_acs_wfc_f475w
80189155400116287008253967.48942ACS/WFCF475W21.4490hst_10505_17_acs_wfc_f475w
80189155400092659285653967.62263ACS/WFCF475W21.7690hst_10505_18_acs_wfc_f475w

Plot the light curves

The light curves appear well-behaved and are closely correlated in the two filters.

In [12]:
pylab.rcParams.update({'font.size': 16})
pylab.figure(1,(10,10))
pylab.subplot(211)
pylab.plot(f475['StartMJD'], f475['MagAper2'], 'bo', label='ACS/WFC F475W')
pylab.gca().invert_yaxis()
pylab.ylabel('F475W [mag]')
pylab.legend(loc='best')
xlim = pylab.xlim()

pylab.subplot(212)
pylab.plot(f814['StartMJD'], f814['MagAper2'], 'ro', label='ACS/WFC F814W')
pylab.gca().invert_yaxis()
pylab.ylabel('F814W [mag]')
pylab.xlabel('MJD [days]')
pylab.xlim(xlim)
pylab.legend(loc='best')
Out[12]:
<matplotlib.legend.Legend at 0x12005b898>

Extract HLA cutout images for the F475W images

Get HLA F475W cutout images for the example variable. The get_hla_cutout function reads a single cutout image (as a JPEG grayscale image) and returns a PIL image object. See the documentation on the fitscut image cutout service for more information on the web service being used.

Examination of the images can be useful to identified cosmic-ray contamination and other possible image artifacts. In this case, no issues are seen, so the light curve is likely to be reliable.

In [13]:
def get_hla_cutout(imagename,ra,dec,size=33,autoscale=99.5,asinh=1,zoom=1):
    
    """Get JPEG cutout for an image"""
    
    url = "https://hla.stsci.edu/cgi-bin/fitscut.cgi"
    r = requests.get(url, params=dict(ra=ra, dec=dec, size=size, 
            format="jpeg", red=imagename, autoscale=autoscale, asinh=asinh, zoom=zoom))
    im = Image.open(BytesIO(r.content))
    return im

# sort images by magnitude from faintest to brightest
isort = np.argsort(-f475['MagAper2'])

imagename = f475['ImageName'][isort]
mag = f475['MagAper2'][isort]
mjd = f475['StartMJD'][isort]

nim = len(imagename)
ncols = 4 # images per row
nrows = (nim+ncols-1)//ncols

imsize = 15
mra = tab['MatchRA'][iselect]
mdec = tab['MatchDec'][iselect]

pylab.rcParams.update({"font.size":11})
pylab.figure(1,(15, (15/ncols)*nrows))
t0 = time.time()
for k in range(nim):
    im1 = get_hla_cutout(imagename[k],mra,mdec,size=imsize)
    pylab.subplot(nrows,ncols,k+1)
    pylab.imshow(im1,origin="upper",cmap="gray")
    pylab.title('{:.5f} f475w={:.3f}'.format(mjd[k],mag[k]))
    if ((k+1) % 10)==0:
        print("{:.1f} s: finished {} of {}".format(time.time()-t0,k+1,nim))
pylab.tight_layout()
print("{:.1f} s: finished {}".format(time.time()-t0,nim))
9.0 s: finished 10 of 12
11.1 s: finished 12
In [ ]: