From 6f914350b81bfdcfa2534f9a26af090934a573bd Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 1 Sep 2023 13:34:20 -0400 Subject: [PATCH 01/11] implement "wrap_at" functionality --- lcviz/plugins/ephemeris/ephemeris.py | 33 +++++++++++++++++---------- lcviz/plugins/ephemeris/ephemeris.vue | 14 ++++++++++++ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/lcviz/plugins/ephemeris/ephemeris.py b/lcviz/plugins/ephemeris/ephemeris.py index 38aecee0..501299cc 100644 --- a/lcviz/plugins/ephemeris/ephemeris.py +++ b/lcviz/plugins/ephemeris/ephemeris.py @@ -18,9 +18,10 @@ __all__ = ['Ephemeris'] -_default_t0 = 0 -_default_period = 1 -_default_dpdt = 0 +_default_t0 = 0.0 +_default_period = 1.0 +_default_dpdt = 0.0 +_default_wrap_at = 1.0 @tray_registry('ephemeris', label="Ephemeris") @@ -39,6 +40,8 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin): Period of the ephemeris, defined at ``t0``. * :attr:`dpdt`: First derivative of the period of the ephemeris. + * :attr:`wrap_at`: + Phase at which to wrap (maximum phase) * :meth:`ephemeris` * :meth:`ephemerides` * :meth:`update_ephemeris` @@ -68,6 +71,7 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin): period_step = Float(0.1).tag(sync=True) dpdt = FloatHandleEmpty(_default_dpdt).tag(sync=True) dpdt_step = Float(0.1).tag(sync=True) + wrap_at = Float(_default_wrap_at).tag(sync=True) # PERIOD FINDING method_items = List().tag(sync=True) @@ -108,7 +112,7 @@ def __init__(self, *args, **kwargs): @property def user_api(self): - expose = ['component', 'period', 'dpdt', 't0', + expose = ['component', 'period', 'dpdt', 't0', 'wrap_at', 'ephemeris', 'ephemerides', 'update_ephemeris', 'create_phase_viewer', 'add_component', 'remove_component', 'rename_component', @@ -150,17 +154,19 @@ def _times_to_phases_callable(self, component): t0 = self.t0 period = self.period dpdt = self.dpdt + wrap_at = self.wrap_at else: ephem = self.ephemerides.get(component, {}) t0 = ephem.get('t0', _default_t0) period = ephem.get('period', _default_period) dpdt = ephem.get('dpdt', _default_dpdt) + wrap_at = ephem.get('wrap_at', _default_wrap_at) def _callable(times): if dpdt != 0: - return np.mod(1./dpdt * np.log(1 + dpdt/period*(times-t0)), 1.0) # noqa + return np.mod(1./dpdt * np.log(1 + dpdt/period*(times-t0)) + (1-wrap_at), 1.0) - (1-wrap_at) # noqa else: - return np.mod((times-t0)/period, 1.0) + return np.mod((times-t0)/period + (1-wrap_at), 1.0) - (1-wrap_at) return _callable @@ -314,9 +320,10 @@ def _change_component(self, *args): self.t0 = ephem.get('t0', self.t0) self.period = ephem.get('period', self.period) self.dpdt = ephem.get('dpdt', self.dpdt) + self.wrap_at = ephem.get('wrap_at', self.wrap_at) # if this is a new component, update those default values back to the dictionary - self.update_ephemeris(t0=self.t0, period=self.period, dpdt=self.dpdt) + self.update_ephemeris(t0=self.t0, period=self.period, dpdt=self.dpdt, wrap_at=self.wrap_at) self._ignore_ephem_change = False if ephem: # if there were any changes applied by accessing the dictionary, @@ -324,7 +331,7 @@ def _change_component(self, *args): # otherwise, this is a new component and there is no need. self._ephem_traitlet_changed() - def update_ephemeris(self, component=None, t0=None, period=None, dpdt=None): + def update_ephemeris(self, component=None, t0=None, period=None, dpdt=None, wrap_at=None): """ Update the ephemeris for a given component. @@ -337,7 +344,9 @@ def update_ephemeris(self, component=None, t0=None, period=None, dpdt=None): period : float, optional value of period to replace dpdt : float, optional - value of period to replace + value of dpdt to replace + wrap_at : float, optional + value of wrap_at to replace Returns ------- @@ -350,7 +359,7 @@ def update_ephemeris(self, component=None, t0=None, period=None, dpdt=None): raise ValueError(f"component must be one of {self.component.choices}") existing_ephem = self._ephemerides.get(component, {}) - for name, value in {'t0': t0, 'period': period, 'dpdt': dpdt}.items(): + for name, value in {'t0': t0, 'period': period, 'dpdt': dpdt, 'wrap_at': wrap_at}.items(): if value is not None: existing_ephem[name] = value if component == self.component_selected: @@ -360,11 +369,11 @@ def update_ephemeris(self, component=None, t0=None, period=None, dpdt=None): self._update_all_phase_arrays(component=component) return existing_ephem - @observe('period', 'dpdt', 't0') + @observe('period', 'dpdt', 't0', 'wrap_at') def _ephem_traitlet_changed(self, event={}): if self._ignore_ephem_change: return - for value in (self.period, self.dpdt, self.t0): + for value in (self.period, self.dpdt, self.t0, self.wrap_at): if not isinstance(value, (int, float)): return if self.period <= 0: diff --git a/lcviz/plugins/ephemeris/ephemeris.vue b/lcviz/plugins/ephemeris/ephemeris.vue index 852e6d89..3fc3d7ec 100644 --- a/lcviz/plugins/ephemeris/ephemeris.vue +++ b/lcviz/plugins/ephemeris/ephemeris.vue @@ -74,6 +74,20 @@ > + + + + Period Finding/Refining Date: Fri, 1 Sep 2023 14:02:10 -0400 Subject: [PATCH 02/11] in-plugin dialog to reset x-limits * whenever the ephemeris (including wrap_at) or the viewer limits are changed, the limits are checked against the full range of data and displays a message and button to reset the x-limits if not all data are shown in the phase-viewer --- lcviz/plugins/ephemeris/ephemeris.py | 39 ++++++++++++++++++++++++++- lcviz/plugins/ephemeris/ephemeris.vue | 13 ++++++++- lcviz/state.py | 17 +++++++++--- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/lcviz/plugins/ephemeris/ephemeris.py b/lcviz/plugins/ephemeris/ephemeris.py index 501299cc..eb75b8d8 100644 --- a/lcviz/plugins/ephemeris/ephemeris.py +++ b/lcviz/plugins/ephemeris/ephemeris.py @@ -73,6 +73,8 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin): dpdt_step = Float(0.1).tag(sync=True) wrap_at = Float(_default_wrap_at).tag(sync=True) + xlimits_contain_all_data = Bool(True).tag(sync=True) + # PERIOD FINDING method_items = List().tag(sync=True) method_selected = Unicode().tag(sync=True) @@ -140,6 +142,12 @@ def phase_viewer_ids(self): def phase_viewer_id(self): return self._phase_viewer_id(self.component_selected) + @property + def phase_viewer(self): + if not self.phase_viewer_exists: + return None + return self.app.get_viewer(self.phase_viewer_id) + @property def ephemerides(self): return self._ephemerides @@ -259,7 +267,14 @@ def create_phase_viewer(self): visible = time_viewer_item['selected_data_items'].get(data_id, 'hidden') self.app.set_data_visibility(phase_viewer_id, data.label, visible == 'visible') - pv = self.app.get_viewer(phase_viewer_id) + # hook-up zoom limits calls + pv = self.app.get_viewer(phase_viewer_id) + pv.state.add_callback("x_min", lambda x_min: self._check_phase_viewer_limits(viewer_id=phase_viewer_id)) # noqa + pv.state.add_callback("x_max", lambda x_max: self._check_phase_viewer_limits(viewer_id=phase_viewer_id)) # noqa + + else: + pv = self.app.get_viewer(phase_viewer_id) + pv.state.x_att = self.phase_cids[self.component_selected] return pv @@ -272,9 +287,28 @@ def vue_period_halve(self, *args): def vue_period_double(self, *args): self.period *= 2 + def vue_reset_viewer_limits(self, *args): + pv = self.phase_viewer + if pv is None: + return + pv.state._reset_x_limits() + self.xlimits_contain_all_data = True + def _check_if_phase_viewer_exists(self, *args): self.phase_viewer_exists = self.phase_viewer_id in self.app.get_viewer_ids() + def _check_phase_viewer_limits(self, viewer_id=None): + if viewer_id is not None and viewer_id != self.phase_viewer_id: + # coming from the state callback, so might not be this same viewer + return + + pv = self.phase_viewer + if pv is None: + self.xlimits_contain_all_data = True + return + + self.xlimits_contain_all_data = pv.state.xlimits_contain_all_data + def _on_component_rename(self, old_lbl, new_lbl): # this is triggered when the plugin component detects a change to the component name self._ephemerides[new_lbl] = self._ephemerides.pop(old_lbl, {}) @@ -330,6 +364,7 @@ def _change_component(self, *args): # then we need to update phasing, etc (since we set _ignore_ephem_change) # otherwise, this is a new component and there is no need. self._ephem_traitlet_changed() + self._check_phase_viewer_limits() def update_ephemeris(self, component=None, t0=None, period=None, dpdt=None, wrap_at=None): """ @@ -398,6 +433,8 @@ def round_to_1(x): 1./1000000) self.t0_step = round_to_1(self.period/1000) + self._check_phase_viewer_limits() + @observe('dataset_selected', 'method_selected') def _update_periodogram(self, *args): if not (hasattr(self, 'method') and hasattr(self, 'dataset')): diff --git a/lcviz/plugins/ephemeris/ephemeris.vue b/lcviz/plugins/ephemeris/ephemeris.vue index 3fc3d7ec..c270fe8f 100644 --- a/lcviz/plugins/ephemeris/ephemeris.vue +++ b/lcviz/plugins/ephemeris/ephemeris.vue @@ -80,7 +80,7 @@ type="number" label="Wrapping phase" v-model.number="wrap_at" - :step="0.2" + :step="0.1" type="number" hint="The phase at which to wrap (maximum phase, may need to reset zoom limits)." persistent-hint @@ -88,6 +88,17 @@ > +
+ + Current viewer x-limits do not encompass all phased data. + + + reset x-limits + + + +
+ Period Finding/Refining 0: ax_min = min(ax_min, np.nanmin(ax_data)) ax_max = max(ax_max, np.nanmax(ax_data)) + return ax_min, ax_max + + def _reset_att_limits(self, ax): + # override glue's _reset_x/y_limits to account for all layers, + # not just reference data + ax_min, ax_max = self._get_att_limits(ax) if not np.all(np.isfinite([ax_min, ax_max])): # pragma: no cover return @@ -34,6 +38,11 @@ def _reset_x_limits(self, *event): def _reset_y_limits(self, *event): self._reset_att_limits('y') + @property + def xlimits_contain_all_data(self): + data_min, data_max = self._get_att_limits('x') + return bool(self.x_min <= data_min and self.x_max >= data_max) + def reset_limits(self, *event): x_min, x_max = np.inf, -np.inf y_min, y_max = np.inf, -np.inf From 28d1794fe72e14d4cfeb5ca034254daf2f462d0d Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 1 Sep 2023 14:14:47 -0400 Subject: [PATCH 03/11] set initial phase-viewer limits based on wrap_at --- lcviz/plugins/ephemeris/ephemeris.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lcviz/plugins/ephemeris/ephemeris.py b/lcviz/plugins/ephemeris/ephemeris.py index eb75b8d8..1d7f8623 100644 --- a/lcviz/plugins/ephemeris/ephemeris.py +++ b/lcviz/plugins/ephemeris/ephemeris.py @@ -271,6 +271,7 @@ def create_phase_viewer(self): pv = self.app.get_viewer(phase_viewer_id) pv.state.add_callback("x_min", lambda x_min: self._check_phase_viewer_limits(viewer_id=phase_viewer_id)) # noqa pv.state.add_callback("x_max", lambda x_max: self._check_phase_viewer_limits(viewer_id=phase_viewer_id)) # noqa + pv.state.x_min, pv.state.x_max = (1-self.wrap_at, self.wrap_at) else: pv = self.app.get_viewer(phase_viewer_id) From 14273c140a8768ed1944838bba06e17781c52d09 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 1 Sep 2023 14:14:53 -0400 Subject: [PATCH 04/11] test coverage --- lcviz/tests/test_plugin_ephemeris.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lcviz/tests/test_plugin_ephemeris.py b/lcviz/tests/test_plugin_ephemeris.py index f6f3b800..866cfc91 100644 --- a/lcviz/tests/test_plugin_ephemeris.py +++ b/lcviz/tests/test_plugin_ephemeris.py @@ -1,3 +1,5 @@ +from numpy.testing import assert_allclose + def test_docs_snippets(helper, light_curve_like_kepler_quarter): lcviz, lc = helper, light_curve_like_kepler_quarter @@ -29,6 +31,18 @@ def test_plugin_ephemeris(helper, light_curve_like_kepler_quarter): ephem._obj.vue_period_halve() assert ephem.period == 3.14 + pv = ephem._obj.phase_viewer + assert ephem._obj.xlimits_contain_all_data is True + # original limits are set to 0->1 (technically 1-phase_wrap -> phase_wrap) + assert (pv.state.x_min, pv.state.x_max) == (0.0, 1.0) + ephem.wrap_at = 0.5 + assert ephem._obj.xlimits_contain_all_data is False + ephem._obj.vue_reset_viewer_limits() + assert ephem._obj.xlimits_contain_all_data is True + # this won't be exactly 0 and 0.5, since it computes the outermost data points, + # actual values with the current test data are -0.49999989500376074, 0.4997347643670089 + assert_allclose((pv.state.x_min, pv.state.x_max), (-0.5, 0.5), atol=0.01) + ephem.add_component('custom component') assert not ephem._obj.phase_viewer_exists ephem.create_phase_viewer() From 34df6c506c1bd7d5b83a504c13166005c1da8104 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 1 Sep 2023 14:20:34 -0400 Subject: [PATCH 05/11] improve hint text --- lcviz/plugins/ephemeris/ephemeris.py | 2 +- lcviz/plugins/ephemeris/ephemeris.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lcviz/plugins/ephemeris/ephemeris.py b/lcviz/plugins/ephemeris/ephemeris.py index 1d7f8623..5c8033cb 100644 --- a/lcviz/plugins/ephemeris/ephemeris.py +++ b/lcviz/plugins/ephemeris/ephemeris.py @@ -41,7 +41,7 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin): * :attr:`dpdt`: First derivative of the period of the ephemeris. * :attr:`wrap_at`: - Phase at which to wrap (maximum phase) + Phase at which to wrap (phased data will encompass the range 1-wrap_at to wrap_at). * :meth:`ephemeris` * :meth:`ephemerides` * :meth:`update_ephemeris` diff --git a/lcviz/plugins/ephemeris/ephemeris.vue b/lcviz/plugins/ephemeris/ephemeris.vue index c270fe8f..7e7af56a 100644 --- a/lcviz/plugins/ephemeris/ephemeris.vue +++ b/lcviz/plugins/ephemeris/ephemeris.vue @@ -82,7 +82,7 @@ v-model.number="wrap_at" :step="0.1" type="number" - hint="The phase at which to wrap (maximum phase, may need to reset zoom limits)." + :hint="'Phased data will encompass the range (1-'+wrap_at+', '+wrap_at+').'" persistent-hint :rules="[() => wrap_at!=='' || 'This field is required']" > From 56d674ba7f7493c1148f26471e38f9c563fbf394 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 1 Sep 2023 14:31:29 -0400 Subject: [PATCH 06/11] add test-coverage for non-zero dpdt --- lcviz/tests/test_plugin_ephemeris.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lcviz/tests/test_plugin_ephemeris.py b/lcviz/tests/test_plugin_ephemeris.py index 866cfc91..476ddda1 100644 --- a/lcviz/tests/test_plugin_ephemeris.py +++ b/lcviz/tests/test_plugin_ephemeris.py @@ -74,3 +74,6 @@ def test_plugin_ephemeris(helper, light_curve_like_kepler_quarter): assert ephem._obj.method_err == '' ephem._obj.vue_adopt_period_at_max_power() assert ephem.period != 2 + + # test coverage for non-zero dpdt + ephem.dpdt = 0.00001 From b520d72d462d9d3338a73e83c863dd00e0d0a763 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Wed, 6 Sep 2023 08:45:47 -0400 Subject: [PATCH 07/11] improved formatting of phase-range in hint --- lcviz/plugins/ephemeris/ephemeris.vue | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lcviz/plugins/ephemeris/ephemeris.vue b/lcviz/plugins/ephemeris/ephemeris.vue index 7e7af56a..8009688d 100644 --- a/lcviz/plugins/ephemeris/ephemeris.vue +++ b/lcviz/plugins/ephemeris/ephemeris.vue @@ -82,7 +82,7 @@ v-model.number="wrap_at" :step="0.1" type="number" - :hint="'Phased data will encompass the range (1-'+wrap_at+', '+wrap_at+').'" + :hint="'Phased data will encompass the range '+wrap_at_range+'.'" persistent-hint :rules="[() => wrap_at!=='' || 'This field is required']" > @@ -160,3 +160,14 @@ + + From 0a4c6261111496f9a9aac5b5fc27bd9659a8c905 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Wed, 6 Sep 2023 09:31:19 -0400 Subject: [PATCH 08/11] replace in-plugin dialog with automatic limit shifting --- lcviz/plugins/ephemeris/ephemeris.py | 43 +++++++-------------------- lcviz/plugins/ephemeris/ephemeris.vue | 11 ------- lcviz/state.py | 17 +++-------- lcviz/tests/test_plugin_ephemeris.py | 9 +----- lcviz/viewers.py | 4 +++ 5 files changed, 19 insertions(+), 65 deletions(-) diff --git a/lcviz/plugins/ephemeris/ephemeris.py b/lcviz/plugins/ephemeris/ephemeris.py index 5c8033cb..22c41f33 100644 --- a/lcviz/plugins/ephemeris/ephemeris.py +++ b/lcviz/plugins/ephemeris/ephemeris.py @@ -73,8 +73,6 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin): dpdt_step = Float(0.1).tag(sync=True) wrap_at = Float(_default_wrap_at).tag(sync=True) - xlimits_contain_all_data = Bool(True).tag(sync=True) - # PERIOD FINDING method_items = List().tag(sync=True) method_selected = Unicode().tag(sync=True) @@ -256,7 +254,8 @@ def create_phase_viewer(self): if self.phase_comp_lbl not in [comp.label for comp in dc[0].components]: self.update_ephemeris() # calls _update_all_phase_arrays - if not self.phase_viewer_exists: + create_phase_viewer = not self.phase_viewer_exists + if create_phase_viewer: # TODO: stack horizontally by default? self.app._on_new_viewer(NewViewerMessage(PhaseScatterView, data=None, sender=self.app), vid=phase_viewer_id, name=phase_viewer_id) @@ -267,15 +266,9 @@ def create_phase_viewer(self): visible = time_viewer_item['selected_data_items'].get(data_id, 'hidden') self.app.set_data_visibility(phase_viewer_id, data.label, visible == 'visible') - # hook-up zoom limits calls - pv = self.app.get_viewer(phase_viewer_id) - pv.state.add_callback("x_min", lambda x_min: self._check_phase_viewer_limits(viewer_id=phase_viewer_id)) # noqa - pv.state.add_callback("x_max", lambda x_max: self._check_phase_viewer_limits(viewer_id=phase_viewer_id)) # noqa + pv = self.app.get_viewer(phase_viewer_id) + if create_phase_viewer: pv.state.x_min, pv.state.x_max = (1-self.wrap_at, self.wrap_at) - - else: - pv = self.app.get_viewer(phase_viewer_id) - pv.state.x_att = self.phase_cids[self.component_selected] return pv @@ -288,28 +281,9 @@ def vue_period_halve(self, *args): def vue_period_double(self, *args): self.period *= 2 - def vue_reset_viewer_limits(self, *args): - pv = self.phase_viewer - if pv is None: - return - pv.state._reset_x_limits() - self.xlimits_contain_all_data = True - def _check_if_phase_viewer_exists(self, *args): self.phase_viewer_exists = self.phase_viewer_id in self.app.get_viewer_ids() - def _check_phase_viewer_limits(self, viewer_id=None): - if viewer_id is not None and viewer_id != self.phase_viewer_id: - # coming from the state callback, so might not be this same viewer - return - - pv = self.phase_viewer - if pv is None: - self.xlimits_contain_all_data = True - return - - self.xlimits_contain_all_data = pv.state.xlimits_contain_all_data - def _on_component_rename(self, old_lbl, new_lbl): # this is triggered when the plugin component detects a change to the component name self._ephemerides[new_lbl] = self._ephemerides.pop(old_lbl, {}) @@ -365,7 +339,6 @@ def _change_component(self, *args): # then we need to update phasing, etc (since we set _ignore_ephem_change) # otherwise, this is a new component and there is no need. self._ephem_traitlet_changed() - self._check_phase_viewer_limits() def update_ephemeris(self, component=None, t0=None, period=None, dpdt=None, wrap_at=None): """ @@ -428,14 +401,18 @@ def round_to_1(x): else: self._update_all_phase_arrays(component=self.component_selected) + # update zoom-limits if wrap_at was changed + if event.get('name') == 'wrap_at': + pvs = self.phase_viewer.state + delta_phase = event.get('new') - event.get('old') + pvs.x_min, pvs.x_max = pvs.x_min + delta_phase, pvs.x_max + delta_phase + # update step-sizes self.period_step = round_to_1(self.period/5000) self.dpdt_step = max(round_to_1(abs(self.period * self.dpdt)/1000) if self.dpdt != 0 else 0, 1./1000000) self.t0_step = round_to_1(self.period/1000) - self._check_phase_viewer_limits() - @observe('dataset_selected', 'method_selected') def _update_periodogram(self, *args): if not (hasattr(self, 'method') and hasattr(self, 'dataset')): diff --git a/lcviz/plugins/ephemeris/ephemeris.vue b/lcviz/plugins/ephemeris/ephemeris.vue index 8009688d..6499a2e3 100644 --- a/lcviz/plugins/ephemeris/ephemeris.vue +++ b/lcviz/plugins/ephemeris/ephemeris.vue @@ -88,17 +88,6 @@ > -
- - Current viewer x-limits do not encompass all phased data. - - - reset x-limits - - - -
- Period Finding/Refining 0: ax_min = min(ax_min, np.nanmin(ax_data)) ax_max = max(ax_max, np.nanmax(ax_data)) - return ax_min, ax_max - - def _reset_att_limits(self, ax): - # override glue's _reset_x/y_limits to account for all layers, - # not just reference data - ax_min, ax_max = self._get_att_limits(ax) if not np.all(np.isfinite([ax_min, ax_max])): # pragma: no cover return @@ -38,11 +34,6 @@ def _reset_x_limits(self, *event): def _reset_y_limits(self, *event): self._reset_att_limits('y') - @property - def xlimits_contain_all_data(self): - data_min, data_max = self._get_att_limits('x') - return bool(self.x_min <= data_min and self.x_max >= data_max) - def reset_limits(self, *event): x_min, x_max = np.inf, -np.inf y_min, y_max = np.inf, -np.inf diff --git a/lcviz/tests/test_plugin_ephemeris.py b/lcviz/tests/test_plugin_ephemeris.py index 476ddda1..13c982ef 100644 --- a/lcviz/tests/test_plugin_ephemeris.py +++ b/lcviz/tests/test_plugin_ephemeris.py @@ -1,4 +1,3 @@ -from numpy.testing import assert_allclose def test_docs_snippets(helper, light_curve_like_kepler_quarter): lcviz, lc = helper, light_curve_like_kepler_quarter @@ -32,16 +31,10 @@ def test_plugin_ephemeris(helper, light_curve_like_kepler_quarter): assert ephem.period == 3.14 pv = ephem._obj.phase_viewer - assert ephem._obj.xlimits_contain_all_data is True # original limits are set to 0->1 (technically 1-phase_wrap -> phase_wrap) assert (pv.state.x_min, pv.state.x_max) == (0.0, 1.0) ephem.wrap_at = 0.5 - assert ephem._obj.xlimits_contain_all_data is False - ephem._obj.vue_reset_viewer_limits() - assert ephem._obj.xlimits_contain_all_data is True - # this won't be exactly 0 and 0.5, since it computes the outermost data points, - # actual values with the current test data are -0.49999989500376074, 0.4997347643670089 - assert_allclose((pv.state.x_min, pv.state.x_max), (-0.5, 0.5), atol=0.01) + assert (pv.state.x_min, pv.state.x_max) == (-0.5, 0.5) ephem.add_component('custom component') assert not ephem._obj.phase_viewer_exists diff --git a/lcviz/viewers.py b/lcviz/viewers.py index dd897c1a..2e878067 100644 --- a/lcviz/viewers.py +++ b/lcviz/viewers.py @@ -113,6 +113,7 @@ def _set_plot_x_axes(self, dc, component_labels, light_curve): xlabel = f'{str(x_unit.physical_type).title()} ({x_unit})' self.figure.axes[0].label = xlabel + self.figure.axes[0].num_ticks = 5 def _set_plot_y_axes(self, dc, component_labels, light_curve): self.state.y_att = dc[0].components[component_labels.index('flux')] @@ -142,6 +143,8 @@ def _set_plot_y_axes(self, dc, component_labels, light_curve): self.figure.axes[0].tick_format = 'g' self.figure.axes[1].tick_format = 'g' + self.figure.axes[1].num_ticks = 5 + def _expected_subset_layer_default(self, layer_state): super()._expected_subset_layer_default(layer_state) @@ -212,6 +215,7 @@ def _set_plot_x_axes(self, dc, component_labels, light_curve): # setting of y_att will be handled by ephemeris plugin self.state.x_att = dc[0].components[component_labels.index(f'phase:{self.ephemeris_component}')] # noqa self.figure.axes[0].label = 'phase' + self.figure.axes[0].num_ticks = 5 def times_to_phases(self, times): ephem = self.jdaviz_helper.plugins.get('Ephemeris', None) From 2d4a74e3dd1e351784a9f65b222b2b8ca203da10 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 8 Sep 2023 12:26:55 -0400 Subject: [PATCH 09/11] fix lower-limit in hint text --- lcviz/plugins/ephemeris/ephemeris.py | 2 +- lcviz/plugins/ephemeris/ephemeris.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lcviz/plugins/ephemeris/ephemeris.py b/lcviz/plugins/ephemeris/ephemeris.py index 22c41f33..202271d2 100644 --- a/lcviz/plugins/ephemeris/ephemeris.py +++ b/lcviz/plugins/ephemeris/ephemeris.py @@ -268,7 +268,7 @@ def create_phase_viewer(self): pv = self.app.get_viewer(phase_viewer_id) if create_phase_viewer: - pv.state.x_min, pv.state.x_max = (1-self.wrap_at, self.wrap_at) + pv.state.x_min, pv.state.x_max = (self.wrap_at-1, self.wrap_at) pv.state.x_att = self.phase_cids[self.component_selected] return pv diff --git a/lcviz/plugins/ephemeris/ephemeris.vue b/lcviz/plugins/ephemeris/ephemeris.vue index 6499a2e3..093ecd74 100644 --- a/lcviz/plugins/ephemeris/ephemeris.vue +++ b/lcviz/plugins/ephemeris/ephemeris.vue @@ -154,7 +154,7 @@ module.exports = { computed: { wrap_at_range() { - const lower = 1-this.wrap_at + const lower = this.wrap_at - 1 return '('+lower.toFixed(2)+', '+this.wrap_at.toFixed(2)+')' }, } From b4d0c2f0d7501a0d82012f9c3eaad538f80f74f3 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Wed, 13 Sep 2023 08:55:02 -0400 Subject: [PATCH 10/11] update example notebook to use wrap_at instead of t0 --- notebooks/LCvizExample.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notebooks/LCvizExample.ipynb b/notebooks/LCvizExample.ipynb index df10849c..cc9082de 100644 --- a/notebooks/LCvizExample.ipynb +++ b/notebooks/LCvizExample.ipynb @@ -119,8 +119,8 @@ " (morris2017_epoch - reference_time).to_value(time_coordinates.unit) % eph.period\n", ")\n", "\n", - "# offset the phase axis so mid-transit occurs at phase=0.5:\n", - "eph.t0 += 0.5 * eph.period" + "# offset the wrapping phase so the transit (at phase 0) displays at center\n", + "eph.wrap_at = 0.5" ] }, { From 0bb47ae616c51c1efd00b329f023c01fb293b556 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Wed, 13 Sep 2023 13:51:12 -0400 Subject: [PATCH 11/11] fix to use FloatHandleEmpty --- lcviz/plugins/ephemeris/ephemeris.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lcviz/plugins/ephemeris/ephemeris.py b/lcviz/plugins/ephemeris/ephemeris.py index 202271d2..ea7a277d 100644 --- a/lcviz/plugins/ephemeris/ephemeris.py +++ b/lcviz/plugins/ephemeris/ephemeris.py @@ -71,7 +71,7 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin): period_step = Float(0.1).tag(sync=True) dpdt = FloatHandleEmpty(_default_dpdt).tag(sync=True) dpdt_step = Float(0.1).tag(sync=True) - wrap_at = Float(_default_wrap_at).tag(sync=True) + wrap_at = FloatHandleEmpty(_default_wrap_at).tag(sync=True) # PERIOD FINDING method_items = List().tag(sync=True) @@ -87,6 +87,7 @@ def __init__(self, *args, **kwargs): self._ignore_ephem_change = False self._ephemerides = {} + self._prev_wrap_at = _default_wrap_at self.component = EditableSelectPluginComponent(self, mode='component_mode', @@ -403,9 +404,14 @@ def round_to_1(x): # update zoom-limits if wrap_at was changed if event.get('name') == 'wrap_at': - pvs = self.phase_viewer.state - delta_phase = event.get('new') - event.get('old') - pvs.x_min, pvs.x_max = pvs.x_min + delta_phase, pvs.x_max + delta_phase + old = event.get('old') if event.get('old') != '' else self._prev_wrap_at + if event.get('new') != '': + pvs = self.phase_viewer.state + delta_phase = event.get('new') - old + pvs.x_min, pvs.x_max = pvs.x_min + delta_phase, pvs.x_max + delta_phase + # we need to cache the old value since it could become a string + # if the widget is cleared + self._prev_wrap_at = event.get('new') # update step-sizes self.period_step = round_to_1(self.period/5000)