1
1
import numpy as np
2
+ from astropy .coordinates import SkyCoord
2
3
from astropy .time import Time
4
+ import astropy .units as u
5
+ from astroquery .ipac .nexsci .nasa_exoplanet_archive import NasaExoplanetArchive
6
+
3
7
from traitlets import Bool , Float , List , Unicode , observe
4
8
5
9
from glue .core .link_helpers import LinkSame
8
12
from jdaviz .core .events import (NewViewerMessage , ViewerAddedMessage , ViewerRemovedMessage )
9
13
from jdaviz .core .registries import tray_registry
10
14
from jdaviz .core .template_mixin import (PluginTemplateMixin , DatasetSelectMixin ,
11
- SelectPluginComponent , EditableSelectPluginComponent )
15
+ SelectPluginComponent , EditableSelectPluginComponent ,
16
+ with_spinner )
12
17
from jdaviz .core .user_api import PluginUserApi
18
+ from jdaviz .core .events import SnackbarMessage
13
19
14
20
from lightkurve import periodogram , FoldedLightCurve
15
21
24
30
_default_dpdt = 0.0
25
31
_default_wrap_at = 1.0
26
32
33
+ _default_query_radius = 2 # [arcsec]
34
+
27
35
28
36
@tray_registry ('ephemeris' , label = "Ephemeris" )
29
37
class Ephemeris (PluginTemplateMixin , DatasetSelectMixin ):
@@ -59,6 +67,19 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin):
59
67
Dataset to use for determining the period.
60
68
* ``method`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
61
69
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``.
62
83
"""
63
84
template_file = __file__ , "ephemeris.vue"
64
85
@@ -87,13 +108,26 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin):
87
108
88
109
period_at_max_power = Float ().tag (sync = True )
89
110
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
+
90
123
def __init__ (self , * args , ** kwargs ):
91
124
super ().__init__ (* args , ** kwargs )
92
125
93
126
self ._default_initialized = False
94
127
self ._ignore_ephem_change = False
95
128
self ._ephemerides = {}
96
129
self ._prev_wrap_at = _default_wrap_at
130
+ self ._nasa_exoplanet_archive = None
97
131
98
132
self .dataset .add_filter (is_not_tpf )
99
133
@@ -117,6 +151,10 @@ def __init__(self, *args, **kwargs):
117
151
selected = 'method_selected' ,
118
152
manual_options = ['Lomb-Scargle' , 'Box Least Squares' ])
119
153
154
+ self .query_result = SelectPluginComponent (self ,
155
+ items = 'query_result_items' ,
156
+ selected = 'query_result_selected' )
157
+
120
158
# TODO: could optimize by only updating for the new data entry only
121
159
# (would require some refactoring and probably wouldn't have significant gains)
122
160
self .hub .subscribe (self , DataCollectionAddMessage , handler = self ._update_all_phase_arrays )
@@ -125,12 +163,16 @@ def __init__(self, *args, **kwargs):
125
163
126
164
@property
127
165
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
+ ]
134
176
return PluginUserApi (self , expose = expose )
135
177
136
178
def _phase_comp_lbl (self , component = None ):
@@ -578,3 +620,111 @@ def get_data(self, dataset, ephem_component=None):
578
620
phlc .sort ("time" )
579
621
580
622
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