Skip to content

Commit 7757a7c

Browse files
bmorris3kecnry
andauthored
Add methods for querying NASA Exoplanet Archive for ephemeris (#127)
* add methods for querying NASA Exoplanet Archive for ephemeris * create new ephem viewers from query results * adding coverage * increasing coverage * only allow new components for ephemeris queries * disable component creation for existing components * remove unused method, rename its successor * Update lcviz/plugins/ephemeris/ephemeris.py * adding user API docs for new ephemeris features --------- Co-authored-by: Kyle Conroy <[email protected]>
1 parent 4c05c94 commit 7757a7c

7 files changed

+329
-17
lines changed

CHANGES.rst

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
* Prevent duplicate sub-intervals (quarter/sector/campaign) in data labels. [#120]
55

6+
* Add feature to query the NASA Exoplanet Archive for exoplanet ephemerides. [#127]
7+
68
0.4.3 (unreleased)
79
------------------
810

lcviz/conftest.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,15 @@ def light_curve_like_kepler_quarter(seed=42):
3737
)
3838
lc['flux_alt'] = flux + 1
3939
lc['flux_alt_err'] = flux_err
40-
lc.meta['MISSION'] = 'KEPLER'
41-
lc.meta['QUARTER'] = 10
40+
lc.meta.update(
41+
{
42+
'MISSION': 'KEPLER',
43+
'QUARTER': 10,
44+
'OBJECT': 'HAT-P-11',
45+
'RA': 297.7101763,
46+
'DEC': 48.0818635,
47+
}
48+
)
4249

4350
return lc
4451

lcviz/plugins/ephemeris/ephemeris.py

+157-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import numpy as np
2+
from astropy.coordinates import SkyCoord
23
from astropy.time import Time
4+
import astropy.units as u
5+
from astroquery.ipac.nexsci.nasa_exoplanet_archive import NasaExoplanetArchive
6+
37
from traitlets import Bool, Float, List, Unicode, observe
48

59
from glue.core.link_helpers import LinkSame
@@ -8,8 +12,10 @@
812
from jdaviz.core.events import (NewViewerMessage, ViewerAddedMessage, ViewerRemovedMessage)
913
from jdaviz.core.registries import tray_registry
1014
from jdaviz.core.template_mixin import (PluginTemplateMixin, DatasetSelectMixin,
11-
SelectPluginComponent, EditableSelectPluginComponent)
15+
SelectPluginComponent, EditableSelectPluginComponent,
16+
with_spinner)
1217
from jdaviz.core.user_api import PluginUserApi
18+
from jdaviz.core.events import SnackbarMessage
1319

1420
from lightkurve import periodogram, FoldedLightCurve
1521

@@ -24,6 +30,8 @@
2430
_default_dpdt = 0.0
2531
_default_wrap_at = 1.0
2632

33+
_default_query_radius = 2 # [arcsec]
34+
2735

2836
@tray_registry('ephemeris', label="Ephemeris")
2937
class Ephemeris(PluginTemplateMixin, DatasetSelectMixin):
@@ -59,6 +67,19 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin):
5967
Dataset to use for determining the period.
6068
* ``method`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
6169
Method/algorithm to determine the period.
70+
* :meth:`query_for_ephemeris`
71+
Query the `NASA Exoplanet Archive <https://exoplanetarchive.ipac.caltech.edu/>`_'s
72+
`Planetary System Composite Parameters
73+
<https://exoplanetarchive.ipac.caltech.edu/docs/pscp_about.html>`_
74+
table for the planet-hosting star identified
75+
by the observation's header key "OBJECT", or if that fails,
76+
by the observation's header keys for RA and Dec.
77+
* ``query_result`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
78+
The name of a planet from a NASA Exoplanet Archive query, used for
79+
adopting literature values for the orbital period and mid-transit time.
80+
* :meth:`create_ephemeris_from_query`
81+
Create an ephemeris component with the period and epoch from
82+
the planet selected from the NASA Exoplanet Archive query in ``query_result``.
6283
"""
6384
template_file = __file__, "ephemeris.vue"
6485

@@ -87,13 +108,26 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin):
87108

88109
period_at_max_power = Float().tag(sync=True)
89110

111+
# QUERIES
112+
query_name = Unicode().tag(sync=True)
113+
query_ra = FloatHandleEmpty().tag(sync=True)
114+
query_dec = FloatHandleEmpty().tag(sync=True)
115+
query_radius = FloatHandleEmpty(_default_query_radius).tag(sync=True)
116+
query_result_items = List().tag(sync=True)
117+
query_result_selected = Unicode().tag(sync=True)
118+
ra_dec_step = Float(0.01).tag(sync=True)
119+
period_from_catalog = FloatHandleEmpty().tag(sync=True)
120+
t0_from_catalog = FloatHandleEmpty().tag(sync=True)
121+
query_spinner = Bool().tag(sync=True)
122+
90123
def __init__(self, *args, **kwargs):
91124
super().__init__(*args, **kwargs)
92125

93126
self._default_initialized = False
94127
self._ignore_ephem_change = False
95128
self._ephemerides = {}
96129
self._prev_wrap_at = _default_wrap_at
130+
self._nasa_exoplanet_archive = None
97131

98132
self.dataset.add_filter(is_not_tpf)
99133

@@ -117,6 +151,10 @@ def __init__(self, *args, **kwargs):
117151
selected='method_selected',
118152
manual_options=['Lomb-Scargle', 'Box Least Squares'])
119153

154+
self.query_result = SelectPluginComponent(self,
155+
items='query_result_items',
156+
selected='query_result_selected')
157+
120158
# TODO: could optimize by only updating for the new data entry only
121159
# (would require some refactoring and probably wouldn't have significant gains)
122160
self.hub.subscribe(self, DataCollectionAddMessage, handler=self._update_all_phase_arrays)
@@ -125,12 +163,16 @@ def __init__(self, *args, **kwargs):
125163

126164
@property
127165
def user_api(self):
128-
expose = ['component', 'period', 'dpdt', 't0', 'wrap_at',
129-
'ephemeris', 'ephemerides',
130-
'update_ephemeris', 'create_phase_viewer',
131-
'add_component', 'remove_component', 'rename_component',
132-
'times_to_phases', 'phases_to_times', 'get_data',
133-
'dataset', 'method', 'period_at_max_power', 'adopt_period_at_max_power']
166+
expose = [
167+
'component', 'period', 'dpdt', 't0', 'wrap_at',
168+
'ephemeris', 'ephemerides',
169+
'update_ephemeris', 'create_phase_viewer',
170+
'add_component', 'remove_component', 'rename_component',
171+
'times_to_phases', 'phases_to_times', 'get_data',
172+
'dataset', 'method', 'period_at_max_power',
173+
'adopt_period_at_max_power', 'query_for_ephemeris',
174+
'query_result', 'create_ephemeris_from_query'
175+
]
134176
return PluginUserApi(self, expose=expose)
135177

136178
def _phase_comp_lbl(self, component=None):
@@ -578,3 +620,111 @@ def get_data(self, dataset, ephem_component=None):
578620
phlc.sort("time")
579621

580622
return phlc
623+
624+
@property
625+
def nasa_exoplanet_archive(self):
626+
if self._nasa_exoplanet_archive is None:
627+
self._nasa_exoplanet_archive = NasaExoplanetArchive()
628+
629+
return self._nasa_exoplanet_archive
630+
631+
@observe('dataset_selected')
632+
def _query_params_from_metadata(self, *args):
633+
self.query_name = self.dataset.selected_obj.meta.get('OBJECT', '')
634+
self.query_ra = self.dataset.selected_obj.meta.get('RA')
635+
self.query_dec = self.dataset.selected_obj.meta.get('DEC')
636+
637+
def query_for_ephemeris(self):
638+
query_result = None
639+
640+
if self.query_name:
641+
# first query by object name:
642+
query_result = self.nasa_exoplanet_archive.query_object(
643+
object_name=self.query_name,
644+
table='pscomppars'
645+
)
646+
647+
if (
648+
(query_result is None or len(query_result) == 0) and
649+
(None not in (self.query_ra, self.query_dec))
650+
):
651+
# next query by coordinates:
652+
coord = SkyCoord(ra=self.query_ra, dec=self.query_dec, unit=u.deg)
653+
query_result = self.nasa_exoplanet_archive.query_region(
654+
table='pscomppars',
655+
coordinates=coord,
656+
radius=self.query_radius * u.arcsec,
657+
)
658+
659+
if query_result is None or len(query_result) == 0:
660+
# no metadata found for RA, Dec, or object name
661+
return None
662+
663+
else:
664+
query_result.sort('pl_name')
665+
self.astroquery_result = query_result
666+
self.astroquery_result.add_index('pl_name')
667+
self.query_result_items = [
668+
{
669+
'label': name, # required key for SelectPluginComponent
670+
'period': period,
671+
'epoch': epoch if not np.isnan(epoch) else 0
672+
}
673+
for name, period, epoch in zip(
674+
list(self.astroquery_result['pl_name']),
675+
np.array(self.astroquery_result['pl_orbper'].to_value(u.day)),
676+
np.array(self.astroquery_result['pl_tranmid'].to_value(u.day))
677+
)
678+
]
679+
680+
@observe('query_result_selected')
681+
def _select_query_result(self, *args):
682+
selected_query_result = self.astroquery_result.loc[self.query_result_selected]
683+
self.period_from_catalog = selected_query_result['pl_orbper'].base.to_value(u.day)
684+
ref_time = self.app.data_collection[0].coords.reference_time.jd
685+
if np.isnan(selected_query_result['pl_tranmid'].base.to_value(u.day)):
686+
self.t0_from_catalog = 0
687+
else:
688+
self.t0_from_catalog = (
689+
selected_query_result['pl_tranmid'].base.to_value(u.day) - ref_time
690+
) % self.period_from_catalog
691+
692+
@with_spinner('query_spinner')
693+
def vue_query_for_ephemeris(self, *args):
694+
self.query_for_ephemeris()
695+
696+
def create_ephemeris_from_query(self, *args):
697+
new_component_label = self.query_result_selected.replace(' ', '')
698+
if new_component_label in self.component.choices:
699+
# warn the user that an ephemeris component already exists with this label,
700+
# a second won't be added:
701+
self.hub.broadcast(
702+
SnackbarMessage(
703+
f"Ephemeris component {new_component_label} already exists, "
704+
f"this ephemeris component will not be added.",
705+
sender=self, color="warning"
706+
)
707+
)
708+
elif not np.any(np.isnan([self.period_from_catalog, self.t0_from_catalog])):
709+
self.add_component(new_component_label)
710+
self.create_phase_viewer()
711+
712+
self.period = self.period_from_catalog
713+
self.t0 = self.t0_from_catalog
714+
715+
# reset the phase axis wrap to feature the primary transit:
716+
self.wrap_at = 0.5
717+
viewer = self._get_phase_viewers()[0]
718+
viewer.reset_limits()
719+
else:
720+
self.hub.broadcast(
721+
SnackbarMessage(
722+
f"Catalog period ({self.period_from_catalog}) or "
723+
f"epoch ({self.t0_from_catalog}) is NaN, this ephemeris "
724+
f"component will not be added.",
725+
sender=self, color="warning"
726+
)
727+
)
728+
729+
def vue_create_ephemeris_from_query(self, *args):
730+
self.create_ephemeris_from_query()

0 commit comments

Comments
 (0)