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
parse the JSON formatted string
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$)')
[ ]: