Working with the Transient Class#

This notebook will go through working with the methods of the transient class and how they will be useful. First we will setup the otter connection, if this doesn’t look familiar to you, you may want to return to the basic_usage.ipynb notebook!

Setup#

[1]:
# imports
import os
import io
import otter

import json
import requests # you may need to pip install requests

from astropy.coordinates import SkyCoord
from astropy import units as u
import pandas as pd

import matplotlib.pyplot as plt
[2]:
# connect to the dataset
db = otter.Otter()
Attempting to login to https://otter.idies.jhu.edu/api with the following credentials:
username: user-guest
password: test

The Transient Class#

For the detailed documentation for this class see https://astro-otter.readthedocs.io.

The particularly useful methods here are the getters which return what we have deemed to be the “default” value of a property.

First though, we must grab a transient from the OTTER dataset. Let’s use ASASSN-14li since it has a pretty solid dataset.

[3]:
t = db.query(names='ASASSN-14li')[0] # if you don't know why I use [0] go back to the basic_usage tutorial

t
[3]:
Transient(
        Name: ASASSN-14li,
        Keys: dict_keys(['_key', '_id', '_rev', 'reference_alias', 'distance', 'classification', 'date_reference', 'filter_alias', 'name', 'coordinate', 'photometry', 'host', 'schema_version'])
)

And now that next few cells will demonstrate some of the more helpful getter methods and instance variables

Name#

[4]:
t.default_name
[4]:
'ASASSN-14li'

Coordinate#

Note that this returns an astropy SkyCoord

[5]:
t.get_skycoord()
[5]:
<SkyCoord (ICRS): (ra, dec) in deg
    (192.06343875, 17.77402083)>

Classification#

This returns a tuple of (classification, our confidence, reference list)

[6]:
classification, conf, refs = t.get_classification()
print(f'{t.default_name} is classified as {classification} with a confidence of {conf}')
print(f'This has been confirmed by the following bibcodes:')
for b in refs:
    print(f'\t-{b}')
ASASSN-14li is classified as TDE with a confidence of 3.3
This has been confirmed by the following bibcodes:
        -2012PASP..124..668Y
        -2014ATel.6777....1J
        -2015Natur.526..542M
        -2016ApJ...819L..25A
        -2016ApJ...832L..10R
        -2016Sci...351...62V
        -2018MNRAS.475.4011B
        -2023PASP..135c4101G
        -2024ApJ...966..160G
        -2024MNRAS.527.2452M
        -ASAS-SN Supernovae

Redshift#

[7]:
t.get_redshift()
[7]:
'0.0206'

Discovery Date#

[8]:
t.get_discovery_date()
[8]:
<Time object: scale='utc' format='mjd' value=56983.0>

Photometry#

Since the Transient object only has the unconverted photometry we recommend you use the clean_photometry method to convert everything appropriately.

[9]:
t.clean_photometry()
ASASSN-14li has at least one photometry point where it is unclear if a host subtraction was performed. This can be especially detrimental for UV data. Please consider filtering out UV/Optical/IR or radio rows where the corr_host column is null/None/NaN.
[9]:
index reference raw raw_units date date_format filter_key computed obs_type upperlimit ... converted_flux converted_flux_err converted_flux_unit converted_date converted_date_unit _ref_str _norm_tele_name _mean_dates min_dates max_dates
0 29 2016Sci...351...62V 2.55 mJy 51505.66 mjd 1.4GHz NaN radio False ... 15.383650 0.050567 mag(AB) 51505.66 MJD 2016Sci...351...62V first NaN NaN NaN
1 30 2024ApJ...974..241A 1.30 mJy 59598.9 mjd 1655.5MHz NaN radio False ... 16.115142 0.127528 mag(AB) 59598.90 MJD 2024ApJ...974..241A racs NaN NaN NaN
2 31 2024ApJ...974..241A 2.88 mJy 58597.5 mjd 887.5MHz NaN radio False ... 15.251519 0.089545 mag(AB) 58597.50 MJD 2024ApJ...974..241A racs NaN NaN NaN
3 32 2024ApJ...974..241A 2.39 mJy 59664.7 mjd 887.5MHz NaN radio False ... 15.454005 0.077074 mag(AB) 59664.70 MJD 2024ApJ...974..241A racs NaN NaN NaN
4 33 2024ApJ...974..241A 1.54 mJy 59213.0 mjd 1367.5MHz NaN radio False ... 15.931198 0.113634 mag(AB) 59213.00 MJD 2024ApJ...974..241A racs NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
925 24 2016Sci...351...62V 0.49 mJy 57138.94 mjd 15.7GHz NaN radio False ... 17.174510 0.093983 mag(AB) 57138.94 MJD 2016Sci...351...62V ami 57138.94 57137.94 57139.94
926 25 2016Sci...351...62V 0.50 mJy 57142.95 mjd 15.7GHz NaN radio False ... 17.152575 0.092103 mag(AB) 57142.95 MJD 2016Sci...351...62V ami 57142.95 57141.95 57143.95
927 26 2016Sci...351...62V 0.45 mJy 57145.92 mjd 15.7GHz NaN radio False ... 17.266969 0.122805 mag(AB) 57145.92 MJD 2016Sci...351...62V ami 57145.92 57144.92 57146.92
928 27 2016Sci...351...62V 0.55 mJy 57150.75 mjd 15.7GHz NaN radio False ... 17.049093 0.167461 mag(AB) 57150.75 MJD 2016Sci...351...62V ami 57150.75 57149.75 57151.75
929 28 2016Sci...351...62V 0.38 mJy 57153.73 mjd 15.7GHz NaN radio False ... 17.450541 0.218140 mag(AB) 57153.73 MJD 2016Sci...351...62V ami 57153.73 57152.73 57154.73

930 rows × 48 columns

Host Information#

This will return a list of otter Host objects. See the tutorial host_objects.ipynb for more details on the functionality we provide here.

The Host objects only store metadata on the host like redshift, ra, dec, and name. If a Host is not in OTTER, we attempt to find the best matching hosts using astro-ghost.

[10]:
hlist = t.get_host()
h0 = hlist[0]

h0, type(hlist), type(h0)
[10]:
(SDSS J124815.23+174626.4 @ (RA, Dec)=(192.06249999999997 deg,17.77401111111111 deg),
 list,
 otter.io.host.Host)

Advanced Usage of the Transient Class#

Say you want other information that is not easily accessible by the getters shown above. Or, you think we are wrong about the default value for that property. You can then just treat the Transient object like a python dictionary to access the other values for those properties yourself.

First, it has a keys method, so let’s start there.

[11]:
t.keys()
[11]:
dict_keys(['_key', '_id', '_rev', 'reference_alias', 'distance', 'classification', 'date_reference', 'filter_alias', 'name', 'coordinate', 'photometry', 'host', 'schema_version'])

Let’s say you want to see what other distance measurements exist for ASASSN-14li besides the redshift. Let’s check that property

[12]:
t['distance']
[12]:
[{'value': '0.019999999552965164',
  'distance_type': 'redshift',
  'reference': ['2024ApJ...966..160G'],
  'default': False},
 {'value': '0.0205778',
  'distance_type': 'redshift',
  'reference': ['2015Natur.526..542M', '2024MNRAS.527.2452M'],
  'default': False},
 {'value': '0.0206',
  'distance_type': 'redshift',
  'reference': ['2012PASP..124..668Y',
   '2014ATel.6777....1J',
   '2015arXiv150701598H',
   '2016ApJ...819L..25A',
   '2016ApJ...832L..10R',
   '2016Sci...351...62V',
   '2017ApJ...838..149A',
   '2018MNRAS.475.4011B',
   '2023PASP..135c4101G',
   'ASAS-SN Supernovae'],
  'default': True},
 {'value': '0.021',
  'distance_type': 'redshift',
  'reference': ['2024ApJ...974..241A'],
  'default': False},
 {'value': '81.0',
  'distance_type': 'dispersion_measure',
  'unit': 'km/s',
  'reference': ['2017MNRAS.471.1694W', '2024MNRAS.527.2452M'],
  'default': True},
 {'value': '90.7',
  'distance_type': 'comoving',
  'unit': 'Mpc',
  'reference': ['2012PASP..124..668Y',
   '2014ATel.6777....1J',
   '2015arXiv150701598H',
   '2016A&A...594A..13P',
   '2017ApJ...835...64G',
   '2017ApJ...838..149A',
   'ASAS-SN Supernovae'],
  'default': True},
 {'value': '92.6',
  'distance_type': 'luminosity',
  'unit': 'Mpc',
  'reference': ['2012PASP..124..668Y',
   '2014ATel.6777....1J',
   '2015arXiv150701598H',
   '2016A&A...594A..13P',
   '2017ApJ...835...64G',
   '2017ApJ...838..149A',
   'ASAS-SN Supernovae'],
  'default': True}]

Just like most of the properties here, it is a list of distances with some keywords that tell you about it.

To work with this, we can simply put it in a pandas dataframe and filter it accordingly. Say you only want luminosity distances, we can then filter that pandas dataframe by the distance_type column.

[13]:
dists = pd.DataFrame(t['distance'])
lum_dists = dists[dists.distance_type == 'luminosity']
lum_dists
[13]:
value distance_type reference default unit
6 92.6 luminosity [2012PASP..124..668Y, 2014ATel.6777....1J, 201... True Mpc

Which then gives you a luminosity distance and the references you need to cite when you put it in your paper!

A similar process can be used for the rest of the properties so I won’t go through them in detail here but just remember, the Transient objects are basically just fancy python dictionaries!

Querying TNS for spectra of this Transient#

As an example, let’s consider the TDE 2019azh. Before proceeding, you need to go and make a TNS Bot: https://www.wis-tns.org/bots.

Once you have the TNS Bot, you can fill in the following variables (or save them as environment variables, as shown as the default)

[14]:
YOUR_BOT_ID = "your-bot-id"
YOUR_BOT_NAME = "your-bot-name"
YOUR_API_KEY = "your-api-key"

TNS_BOT_ID = os.environ.get("TNS_BOT_ID", YOUR_BOT_ID)
TNS_BOT_NAME = os.environ.get("TNS_BOT_NAME", YOUR_BOT_NAME)
TNS_API_KEY = os.environ.get("TNS_API_KEY", YOUR_API_KEY)
[15]:
TNS_GET_URL = "https://www.wis-tns.org/api/get/object"
t = db.get_meta(names="2019azh")[0]

# create a json header to pass to the request.post
headers = {
    "user-agent": json.dumps({
        "tns_id": TNS_BOT_ID,
        "type": "bot",
        "name": TNS_BOT_NAME
    })
}

# create a json of the data that you are requesting
data = {
    "api_key": TNS_API_KEY,
    "data": json.dumps({
        "objname": t.default_name, # this should be the TNS name (if we store one)!
        "spectra": "1" # this means "get the spectra"
    })
}

# post the request to TNS
response = requests.post(TNS_GET_URL, headers=headers, data=data)

# and then let's look at the test and status code
print(response.status_code)
print(response.text)
200
{"id_code":200,"id_message":"OK","data":{"objname":"2019azh","name_prefix":"TDE","objid":34607,"object_type":{"name":"TDE","id":120},"redshift":0.022,"ra":"08:13:16.945","dec":"+22:38:54.03","radeg":123.320605325,"decdeg":22.648343,"radeg_err":0,"decdeg_err":0,"hostname":"KUG 0810+227","host_redshift":0.022346,"internal_names":"ASASSN-19dj,ZTF17aaazdba,Gaia19bvo,ZTF18achzddr","discoverer_internal_name":"ASASSN-19dj","discoverydate":"2019-02-22 00:28:48.000","discoverer":"K. Z. Stanek, for the ASAS-SN team","reporter":"Prof. Krzysztof Stanek","reporterid":82,"source":"user","discoverymag":16.2,"discovery_ads_bibcode":"2019TNSTR.262....1S","class_ads_bibcodes":"2019TNSCR.287....1B, 2020TNSCR2126....1D","discmagfilter":{"id":"21","name":"g","family":"Sloan"},"reporting_group":{"groupid":3,"group_name":"ASAS-SN"},"discovery_data_source":{"groupid":3,"group_name":"ASAS-SN"},"public":1,"end_prop_period":null,"spectra":[{"obsdate":"2019-03-15 09:22:38","jd":2458557.8907175926,"public":1,"end_prop_period":null,"exptime":1200,"source_groupid":48,"source_group_name":"ZTF","observer":"SEDmRobot","reducer":"auto","asciifile":"https:\/\/www.wis-tns.org\/system\/files\/uploaded\/ZTF\/tns_2019azh_2019-03-15_09-22-38_P60_SEDM_ZTF.ascii","fitsfile":null,"remarks":"","source_group":{"id":48,"name":"ZTF"},"instrument":{"id":149,"name":"SEDM"},"telescope":{"id":1,"name":"Object"}},{"obsdate":"2019-02-25 03:48:04","jd":2458539.6583796297,"public":1,"end_prop_period":null,"exptime":300,"source_groupid":37,"source_group_name":"ePESSTO","observer":"Cristina Barbarino, Ana Sagues Carracedo","reducer":"Leonardo Tartaglia","asciifile":"https:\/\/www.wis-tns.org\/system\/files\/uploaded\/ePESSTO\/tns_2019azh_2019-02-25_03-48-04_ESO-NTT_EFOSC2-NTT_ePESSTO.asci","fitsfile":"https:\/\/www.wis-tns.org\/system\/files\/uploaded\/ePESSTO\/tns_2019azh_2019-02-25_03-48-04_ESO-NTT_EFOSC2-NTT_ePESSTO.fits","remarks":"","source_group":{"id":37,"name":"ePESSTO"},"instrument":{"id":31,"name":"EFOSC2-NTT"},"telescope":{"id":1,"name":"Object"}}]}}

It looks like the status is good (code 200) and the response.text is a JSON formatted string. The actual spectra files are just urls, so we will need to

  1. parse the JSON formatted string

  2. download the spectra from those URLs

[16]:
spectra = json.loads(response.text)["data"]["spectra"]
spectra
[16]:
[{'obsdate': '2019-03-15 09:22:38',
  'jd': 2458557.8907175926,
  'public': 1,
  'end_prop_period': None,
  'exptime': 1200,
  'source_groupid': 48,
  'source_group_name': 'ZTF',
  'observer': 'SEDmRobot',
  'reducer': 'auto',
  'asciifile': 'https://www.wis-tns.org/system/files/uploaded/ZTF/tns_2019azh_2019-03-15_09-22-38_P60_SEDM_ZTF.ascii',
  'fitsfile': None,
  'remarks': '',
  'source_group': {'id': 48, 'name': 'ZTF'},
  'instrument': {'id': 149, 'name': 'SEDM'},
  'telescope': {'id': 1, 'name': 'Object'}},
 {'obsdate': '2019-02-25 03:48:04',
  'jd': 2458539.6583796297,
  'public': 1,
  'end_prop_period': None,
  'exptime': 300,
  'source_groupid': 37,
  'source_group_name': 'ePESSTO',
  'observer': 'Cristina Barbarino, Ana Sagues Carracedo',
  'reducer': 'Leonardo Tartaglia',
  'asciifile': 'https://www.wis-tns.org/system/files/uploaded/ePESSTO/tns_2019azh_2019-02-25_03-48-04_ESO-NTT_EFOSC2-NTT_ePESSTO.asci',
  'fitsfile': 'https://www.wis-tns.org/system/files/uploaded/ePESSTO/tns_2019azh_2019-02-25_03-48-04_ESO-NTT_EFOSC2-NTT_ePESSTO.fits',
  'remarks': '',
  'source_group': {'id': 37, 'name': 'ePESSTO'},
  'instrument': {'id': 31, 'name': 'EFOSC2-NTT'},
  'telescope': {'id': 1, 'name': 'Object'}}]

We can now download the ascii files using another requests.post

[17]:
specfiles = []
for spec in spectra:
    file_url = spec["asciifile"]
    filename = file_url.split("/")[-1]
    outfile = os.path.join(os.getcwd(), filename)

    response = requests.post(file_url, headers=headers, data=data)
    df = pd.read_csv(io.StringIO(response.text), comment="#", sep="\s+", names=["wave", "flux", "flux_err"])
    specfiles.append(df)
<>:8: SyntaxWarning: invalid escape sequence '\s'
<>:8: SyntaxWarning: invalid escape sequence '\s'
/tmp/ipykernel_642086/840544031.py:8: SyntaxWarning: invalid escape sequence '\s'
  df = pd.read_csv(io.StringIO(response.text), comment="#", sep="\s+", names=["wave", "flux", "flux_err"])

Finally, we can plot these spectra!

[18]:
fig, ax = plt.subplots()
for spec in specfiles:
    ax.plot(
        spec.wave,
        spec.flux/spec.flux.mean()
    )

ax.set_ylabel("Normalized Flux")
ax.set_xlabel(r"Wavelength ($\AA$)")
[18]:
Text(0.5, 0, 'Wavelength ($\\AA$)')
../_images/examples_transient_objects_35_1.png
[ ]: