diff --git a/lcviz/helper.py b/lcviz/helper.py index 16743984..a82d8817 100644 --- a/lcviz/helper.py +++ b/lcviz/helper.py @@ -31,7 +31,7 @@ def _get_range_subset_bounds(self, subset_state, *args, **kwargs): # TODO: use display units once implemented in Glue for ScatterViewer # units = u.Unit(viewer.state.x_display_unit) units = u.Unit(viewer.time_unit) - else: + else: # pragma: no cover raise ValueError("Unable to find time axis units") region = reference_time + u.Quantity([subset_state.lo * units, subset_state.hi * units]) diff --git a/lcviz/parsers.py b/lcviz/parsers.py index e9401ab2..e9268c83 100644 --- a/lcviz/parsers.py +++ b/lcviz/parsers.py @@ -1,9 +1,7 @@ import os -from astropy.io import fits from glue.config import data_translator from jdaviz.core.registries import data_parser_registry -from lightkurve import LightCurve, KeplerLightCurve, TessLightCurve -from lightkurve.io.detect import detect_filetype +import lightkurve __all__ = ["light_curve_parser"] @@ -17,21 +15,11 @@ 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] - # detect the type light curve in a FITS file: - with fits.open(file_obj) as hdulist: - filetype = detect_filetype(hdulist) - - # get the constructor for this type of light curve: - filetype_to_cls = { - 'KeplerLightCurve': KeplerLightCurve, - 'TessLightCurve': TessLightCurve - } - cls = filetype_to_cls[filetype] # read the light curve: - light_curve = cls.read(file_obj) + light_curve = lightkurve.read(file_obj) # load a LightCurve object: - elif isinstance(file_obj, LightCurve): + elif isinstance(file_obj, lightkurve.LightCurve): light_curve = file_obj # make a data label: diff --git a/lcviz/plugins/ephemeris/ephemeris.py b/lcviz/plugins/ephemeris/ephemeris.py index 4eb0a87e..38aecee0 100644 --- a/lcviz/plugins/ephemeris/ephemeris.py +++ b/lcviz/plugins/ephemeris/ephemeris.py @@ -280,7 +280,7 @@ def _on_component_remove(self, lbl): _ = self._ephemerides.pop(lbl, {}) # remove the corresponding viewer, if it exists viewer_item = self.app._viewer_item_by_id(self._phase_viewer_id(lbl)) - if viewer_item is None: + if viewer_item is None: # pragma: no cover return cid = viewer_item.get('id', None) if cid is not None: @@ -346,7 +346,7 @@ def update_ephemeris(self, component=None, t0=None, period=None, dpdt=None): if component is None: component = self.component_selected - if component not in self.component.choices: + if component not in self.component.choices: # pragma: no cover raise ValueError(f"component must be one of {self.component.choices}") existing_ephem = self._ephemerides.get(component, {}) @@ -411,7 +411,7 @@ def _update_periodogram(self, *args): self.method_spinner = False self.method_err = str(err) return - else: + else: # pragma: no cover self.method_spinner = False raise NotImplementedError(f"periodogram not implemented for {self.method}") diff --git a/lcviz/plugins/flatten/flatten.py b/lcviz/plugins/flatten/flatten.py index 7d550f7a..d907e542 100644 --- a/lcviz/plugins/flatten/flatten.py +++ b/lcviz/plugins/flatten/flatten.py @@ -97,7 +97,7 @@ def marks(self): @observe('default_to_overwrite', 'dataset_selected') def _set_default_results_label(self, event={}): '''Generate a label and set the results field to that value''' - if not hasattr(self, 'dataset'): + if not hasattr(self, 'dataset'): # pragma: no cover return self.add_results.label_whitelist_overwrite = [self.dataset_selected] @@ -125,7 +125,7 @@ def flatten(self, add_data=True): The trend used to flatten the light curve. """ input_lc = self.dataset.selected_obj - if input_lc is None: + if input_lc is None: # pragma: no cover raise ValueError("no input dataset selected") output_lc, trend_lc = input_lc.flatten(return_trend=True, diff --git a/lcviz/state.py b/lcviz/state.py index eebd7ab5..ddf393e2 100644 --- a/lcviz/state.py +++ b/lcviz/state.py @@ -11,7 +11,7 @@ def _reset_att_limits(self, ax): # override glue's _reset_x/y_limits to account for all layers, # not just reference data att = f'{ax}_att' - if getattr(self, att) is None: + if getattr(self, att) is None: # pragma: no cover return ax_min, ax_max = np.inf, -np.inf @@ -21,14 +21,14 @@ def _reset_att_limits(self, ax): ax_min = min(ax_min, np.nanmin(ax_data)) ax_max = max(ax_max, np.nanmax(ax_data)) - if not np.all(np.isfinite([ax_min, ax_max])): + if not np.all(np.isfinite([ax_min, ax_max])): # pragma: no cover return with delay_callback(self, f'{ax}_min', f'{ax}_max'): setattr(self, f'{ax}_min', ax_min) setattr(self, f'{ax}_max', ax_max) - def _reset_x_limit(self, *event): + def _reset_x_limits(self, *event): self._reset_att_limits('x') def _reset_y_limits(self, *event): @@ -39,7 +39,7 @@ def reset_limits(self, *event): y_min, y_max = np.inf, -np.inf for layer in self.layers: - if not layer.visible: + if not layer.visible: # pragma: no cover continue x_data = layer.layer.data.get_data(self.x_att) diff --git a/lcviz/tests/test_parser.py b/lcviz/tests/test_parser.py index 5a210c73..c4585e4f 100644 --- a/lcviz/tests/test_parser.py +++ b/lcviz/tests/test_parser.py @@ -1,52 +1,53 @@ -# import pytest +import pytest import numpy as np from glue.core.roi import XRangeROI, YRangeROI from astropy.time import Time -# from astropy.utils.data import download_file +from astropy.utils.data import download_file from lightkurve import LightCurve -# from lightkurve.io import kepler +from lightkurve.io import kepler import astropy.units as u from lcviz.utils import TimeCoordinates -# @pytest.mark.remote_data -# def test_kepler_via_mast_local_file(helper): -# url = ( -# 'https://archive.stsci.edu/pub/kepler/' -# 'lightcurves/0014/001429092/kplr001429092-2009166043257_llc.fits' -# ) # 188 KB -# -# path = download_file(url, cache=True, timeout=100) -# helper.load_data(path) -# -# data = helper.app.data_collection[0] -# flux_arr = data['flux'] -# flux_unit = u.Unit(data.get_component('flux').units) -# flux = flux_arr * flux_unit -# -# assert isinstance(data.coords, TimeCoordinates) -# assert isinstance(flux, u.Quantity) -# assert flux.unit.is_equivalent(u.electron / u.s) -# -# -# @pytest.mark.remote_data -# def test_kepler_via_mast_preparsed(helper): -# url = ( -# 'https://archive.stsci.edu/pub/kepler/' -# 'lightcurves/0014/001429092/kplr001429092-2009166043257_llc.fits' -# ) # 188 KB -# -# light_curve = kepler.read_kepler_lightcurve(url) -# helper.load_data(light_curve) -# -# data = helper.app.data_collection[0] -# flux_arr = data['flux'] -# flux_unit = u.Unit(data.get_component('flux').units) -# flux = flux_arr * flux_unit -# -# assert isinstance(data.coords, TimeCoordinates) -# assert isinstance(flux, u.Quantity) -# assert flux.unit.is_equivalent(u.electron / u.s) + +@pytest.mark.remote_data +def test_kepler_via_mast_local_file(helper): + url = ( + 'https://archive.stsci.edu/pub/kepler/' + 'lightcurves/0014/001429092/kplr001429092-2009166043257_llc.fits' + ) # 188 KB + + path = download_file(url, cache=True, timeout=100) + helper.load_data(path) + + data = helper.app.data_collection[0] + flux_arr = data['flux'] + flux_unit = u.Unit(data.get_component('flux').units) + flux = flux_arr * flux_unit + + assert isinstance(data.coords, TimeCoordinates) + assert isinstance(flux, u.Quantity) + assert flux.unit.is_equivalent(u.electron / u.s) + + +@pytest.mark.remote_data +def test_kepler_via_mast_preparsed(helper): + url = ( + 'https://archive.stsci.edu/pub/kepler/' + 'lightcurves/0014/001429092/kplr001429092-2009166043257_llc.fits' + ) # 188 KB + + light_curve = kepler.read_kepler_lightcurve(url) + helper.load_data(light_curve) + + data = helper.app.data_collection[0] + flux_arr = data['flux'] + flux_unit = u.Unit(data.get_component('flux').units) + flux = flux_arr * flux_unit + + assert isinstance(data.coords, TimeCoordinates) + assert isinstance(flux, u.Quantity) + assert flux.unit.is_equivalent(u.electron / u.s) def test_synthetic_lc(helper): diff --git a/lcviz/tests/test_plugin_ephemeris.py b/lcviz/tests/test_plugin_ephemeris.py index 587e170e..a837b6d3 100644 --- a/lcviz/tests/test_plugin_ephemeris.py +++ b/lcviz/tests/test_plugin_ephemeris.py @@ -31,6 +31,22 @@ def test_plugin_ephemeris(helper, light_curve_like_kepler_quarter): assert 'renamed custom component' in ephem.ephemerides assert len(helper.app.get_viewer_ids()) == 3 + assert ephem.component == 'renamed custom component' + assert ephem.period == 3.14 + assert ephem.ephemeris['period'] == 3.14 + # modify the ephemeris of the NON-selected ephemeris component + ephem.update_ephemeris(component='default', period=2) + assert ephem.period == 3.14 + assert ephem.ephemerides['default']['period'] == 2 + ephem.remove_component('renamed custom component') assert len(ephem.ephemerides) == 1 assert len(helper.app.get_viewer_ids()) == 2 + assert ephem.component == 'default' + assert ephem.period == 2 + + assert ephem.method.selected == 'Lomb-Scargle' + ephem.method = 'Box Least Squares' + assert ephem._obj.method_err == '' + ephem._obj.vue_adopt_period_at_max_power() + assert ephem.period != 2 diff --git a/lcviz/tests/test_plugin_flatten.py b/lcviz/tests/test_plugin_flatten.py index 12110fe7..d14a0089 100644 --- a/lcviz/tests/test_plugin_flatten.py +++ b/lcviz/tests/test_plugin_flatten.py @@ -4,27 +4,56 @@ from lcviz.marks import LivePreviewTrend, LivePreviewFlattened -def _get_marks_from_viewer(viewer, cls=(LivePreviewTrend, LivePreviewFlattened)): - return [m for m in viewer.figure.marks if isinstance(m, cls)] +def _get_marks_from_viewer(viewer, cls=(LivePreviewTrend, LivePreviewFlattened), + include_not_visible=False): + return [m for m in viewer.figure.marks if isinstance(m, cls) + if include_not_visible or m.visible] def test_plugin_flatten(helper, light_curve_like_kepler_quarter): helper.load_data(light_curve_like_kepler_quarter) tv = helper.app.get_viewer(helper._default_time_viewer_reference_name) - f = helper.plugins['Flatten'] - f.plugin_opened = True ephem = helper.plugins['Ephemeris'] pv = ephem.create_phase_viewer() + f = helper.plugins['Flatten'] - assert len(_get_marks_from_viewer(tv)) == 2 - assert len(_get_marks_from_viewer(pv)) == 1 - - orig_label = f.dataset.selected - assert f.dataset.selected_obj is not None - assert f._obj.add_results.label_overwrite is True - assert f._obj.add_results.label == orig_label - f.flatten(add_data=True) + # no marks until plugin opened/active + assert len(_get_marks_from_viewer(tv)) == 0 + assert len(_get_marks_from_viewer(pv)) == 0 + + with f.as_active(): + assert len(_get_marks_from_viewer(tv)) == 2 + assert len(_get_marks_from_viewer(pv)) == 1 + + # update period which should update phasing in phase-viewer + ephem.period = 1.2 + + before_polyorder = f.polyorder + before_update = _get_marks_from_viewer(tv)[0].y + + # test error handling in live-preview + f.polyorder = -1 + assert f._obj.flatten_err != '' + assert len(_get_marks_from_viewer(tv)) == 0 + assert len(_get_marks_from_viewer(pv)) == 0 + + # update polyorder (live-preview should re-appear and have changed from before) + f.polyorder = before_polyorder + 1 + assert f._obj.flatten_err == '' + after_update = _get_marks_from_viewer(tv)[0].y + assert not np.allclose(before_update, after_update) + + orig_label = f.dataset.selected + assert f.dataset.selected_obj is not None + assert f._obj.add_results.label_overwrite is True + assert f._obj.add_results.label == orig_label + f._obj.vue_apply(add_data=True) + assert f._obj.flatten_err == '' + + # marks are hidden + assert len(_get_marks_from_viewer(tv)) == 0 + assert len(_get_marks_from_viewer(pv)) == 0 def test_no_overwrite(helper, light_curve_like_kepler_quarter): diff --git a/lcviz/tests/test_viewers.py b/lcviz/tests/test_viewers.py new file mode 100644 index 00000000..66f15da6 --- /dev/null +++ b/lcviz/tests/test_viewers.py @@ -0,0 +1,19 @@ + +def test_reset_limits(helper, light_curve_like_kepler_quarter): + helper.load_data(light_curve_like_kepler_quarter) + tv = helper.app.get_viewer(helper._default_time_viewer_reference_name) + + orig_xlims = (tv.state.x_min, tv.state.x_max) + orig_ylims = (tv.state.y_min, tv.state.y_max) + # set xmin and ymin to midpoints + new_xmin = (tv.state.x_min + tv.state.x_max) / 2 + new_ymin = (tv.state.y_min + tv.state.y_max) / 2 + tv.state.x_min = new_xmin + tv.state.y_min = new_ymin + + tv.state._reset_x_limits() + assert tv.state.x_min == orig_xlims[0] + assert tv.state.y_min == new_ymin + + tv.state._reset_y_limits() + assert tv.state.y_min == orig_ylims[0] diff --git a/lcviz/utils.py b/lcviz/utils.py index 1bb0e28e..3dbff498 100644 --- a/lcviz/utils.py +++ b/lcviz/utils.py @@ -25,7 +25,7 @@ class TimeCoordinates(Coordinates): """ def __init__(self, times, reference_time=None, unit=u.d): - if not isinstance(times, Time): + if not isinstance(times, Time): # pragma: no cover raise TypeError('values should be a Time instance') self._index = np.arange(len(times)) self._times = times @@ -44,7 +44,7 @@ def time_axis(self): return self._times def world_to_pixel_values(self, *world): - if len(world) > 1: + if len(world) > 1: # pragma: no cover raise ValueError('TimeCoordinates is a 1-d coordinate class ' 'and only accepts a single scalar or array to convert') return interp1d( @@ -52,7 +52,7 @@ def world_to_pixel_values(self, *world): )(world[0]) def pixel_to_world_values(self, *pixel): - if len(pixel) > 1: + if len(pixel) > 1: # pragma: no cover raise ValueError('SpectralCoordinates is a 1-d coordinate class ' 'and only accepts a single scalar or array to convert') return interp1d(