diff --git a/CHANGES.rst b/CHANGES.rst index 049821e..4240889 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,8 @@ 1.1.0 (unreleased) ------------------ +* Add support for loading TESS DVT files. [#164] + 1.0.0 (12-02-2024) ------------------ diff --git a/lcviz/components/components.py b/lcviz/components/components.py index e36c5e0..e96f1a5 100644 --- a/lcviz/components/components.py +++ b/lcviz/components/components.py @@ -222,7 +222,15 @@ def _on_change_selected(self, *args): # manipulate the arrays in the data-collection directly, and modify FLUX_ORIGIN so that # exporting back to a lightkurve object works as expected self.app._jdaviz_helper._set_data_component(dc_item, 'flux', dc_item[self.selected]) - self.app._jdaviz_helper._set_data_component(dc_item, 'flux_err', dc_item[self.selected+"_err"]) # noqa + if self.selected+"_err" in dc_item.component_ids(): + if "flux_err" in dc_item.component_ids(): + self.app._jdaviz_helper._set_data_component(dc_item, 'flux_err', + dc_item[self.selected + "_err"]) + else: + dc_item.add_component(dc_item[self.selected + "_err"], 'flux_err') + else: + dc_item.remove_component(dc_item.find_component_id('flux_err')) + dc_item.meta['FLUX_ORIGIN'] = self.selected self.hub.broadcast(FluxColumnChangedMessage(dataset=self.dataset.selected, diff --git a/lcviz/helper.py b/lcviz/helper.py index 9e41ca9..c89bf26 100644 --- a/lcviz/helper.py +++ b/lcviz/helper.py @@ -1,3 +1,4 @@ +from astropy.io.fits import getheader import astropy.units as u import ipyvue import os @@ -144,7 +145,7 @@ def __init__(self, *args, **kwargs): # already been initialized plugin._obj.vdocs = self.app.vdocs - def load_data(self, data, data_label=None): + def load_data(self, data, data_label=None, extname=None): """ Load data into LCviz. @@ -161,7 +162,21 @@ def load_data(self, data, data_label=None): automatically determined from filename or randomly generated. The final label shown in LCviz may have additional information appended for clarity. + extname : str or `None` + Used for DVT parsing if only a single TCE from a multi-TCE file should be + loaded. Formatted as 'TCE_1', 'TCE_2', etc. """ + # Determine if we're loading a DVT file, which has a separate parser + if isinstance(data, str): + header = getheader(data) + if (header['TELESCOP'] == 'TESS' and 'CREATOR' in header and + 'DvTimeSeriesExporter' in header['CREATOR']): + super().load_data(data=data, + parser_reference='tess_dvt_parser', + data_label=data_label, + extname=extname) + return + super().load_data( data=data, parser_reference='light_curve_parser', diff --git a/lcviz/parsers.py b/lcviz/parsers.py index 3a1cc3f..4df53fe 100644 --- a/lcviz/parsers.py +++ b/lcviz/parsers.py @@ -1,7 +1,11 @@ import os + +from astropy.io import fits +from astropy.table import Table from glue.config import data_translator from jdaviz.core.registries import data_parser_registry import lightkurve +import numpy as np from lcviz.viewers import PhaseScatterView, TimeScatterView from lcviz.plugins.plot_options import PlotOptions @@ -12,9 +16,55 @@ 'kepler': {'prefix': 'Q', 'card': 'QUARTER'}, 'k2': {'prefix': 'C', 'card': 'CAMPAIGN'}, 'tess': {'prefix': 'S', 'card': 'SECTOR'}, + 'tess dvt': {'prefix': '', 'card': 'EXTNAME'} } +@data_parser_registry("tess_dvt_parser") +def tess_dvt_parser(app, file_obj, data_label=None, show_in_viewer=True, **kwargs): + ''' + Read a TESS DVT file and create a lightkurve object + ''' + hdulist = fits.open(file_obj) + ephem_plugin = app._jdaviz_helper.plugins.get('Ephemeris', None) + extname = kwargs.pop('extname') + + # Loop through the TCEs in the file. If we only want one (specified by + # `extname` keyword) then only load that one into the viewers and ephemeris. + for hdu in hdulist[1:]: + data = Table(hdu.data) + # don't load some columns with names that may + # conflict with components generated later by lcviz + data.remove_column('PHASE') + data.remove_column('CADENCENO') + # Remove rows that have NaN data + data = data[~np.isnan(data['LC_INIT'])] + header = hdu.header + time_offset = int(header['TUNIT1'] .split('- ')[1].split(',')[0]) + data['TIME'] += time_offset + lc = lightkurve.LightCurve(data=data, + time=data['TIME'], + flux=data['LC_INIT'], + flux_err=data['LC_INIT_ERR']) + lc.meta = hdulist[0].header + lc.meta['MISSION'] = 'TESS DVT' + lc.meta['FLUX_ORIGIN'] = "LC_INIT" + lc.meta['EXTNAME'] = header['EXTNAME'] + + if extname is not None and header['EXTNAME'] != extname: + show_ext_in_viewer = False + else: + show_ext_in_viewer = show_in_viewer + + light_curve_parser(app, lc, data_label=data_label, + show_in_viewer=show_ext_in_viewer, **kwargs) + + # add ephemeris information from the DVT extension + if ephem_plugin is not None and show_ext_in_viewer: + ephem_plugin.period = header['TPERIOD'] + ephem_plugin.t0 = header['TEPOCH'] + time_offset - app.data_collection[0].coords.reference_time.jd # noqa + + @data_parser_registry("light_curve_parser") def light_curve_parser(app, file_obj, data_label=None, show_in_viewer=True, **kwargs): # load a LightCurve or TargetPixelFile object: @@ -29,7 +79,6 @@ def light_curve_parser(app, file_obj, data_label=None, show_in_viewer=True, **kw if data_label is None: data_label = os.path.splitext(os.path.basename(file_obj))[0] - # read the light curve: light_curve = lightkurve.read(file_obj) elif isinstance(file_obj, cls_with_translator):