From c68f06e62cc12e572dbe41cb0d70aff0ed473732 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Wed, 14 Feb 2024 12:58:04 -0500 Subject: [PATCH 1/9] implement basic (not yet functional) slice plugin --- docs/plugins.rst | 33 +++++++++++++++++++ lcviz/helper.py | 3 +- lcviz/plugins/__init__.py | 1 + lcviz/plugins/slice/__init__.py | 1 + lcviz/plugins/slice/slice.py | 58 +++++++++++++++++++++++++++++++++ lcviz/viewers.py | 14 ++++++-- 6 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 lcviz/plugins/slice/__init__.py create mode 100644 lcviz/plugins/slice/slice.py diff --git a/docs/plugins.rst b/docs/plugins.rst index 60f5f24c..c52661c9 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -179,6 +179,39 @@ visible when the plugin is opened. Jdaviz documentation on the Markers plugin. +.. _slice: + +Slice +===== + +The slice plugin allows defining the time at which all image cubes are displayed. + + +.. admonition:: User API Example + :class: dropdown + + See the :class:`~lcviz.plugins.slice.slice.Slice` user API documentation for more details. + + .. code-block:: python + + from lcviz import LCviz + lc = search_lightcurve("HAT-P-11", mission="Kepler", + cadence="long", quarter=10).download().flatten() + lcviz = LCviz() + lcviz.load_data(lc) + lcviz.show() + + sl = lcviz.plugins['Slice'] + sl.open_in_tray() + + +.. seealso:: + + :ref:`Jdaviz Markers ` + Jdaviz documentation on the Markers plugin. + + + .. _flatten: Flatten diff --git a/lcviz/helper.py b/lcviz/helper.py index 9f234d37..596922cf 100644 --- a/lcviz/helper.py +++ b/lcviz/helper.py @@ -74,7 +74,8 @@ class LCviz(ConfigHelper): 'toolbar': ['g-data-tools', 'g-subset-tools', 'lcviz-coords-info'], 'tray': ['lcviz-metadata-viewer', 'flux-column', 'lcviz-plot-options', 'lcviz-subset-plugin', - 'lcviz-markers', 'flatten', 'frequency-analysis', 'ephemeris', + 'lcviz-markers', 'lcviz-slice', + 'flatten', 'frequency-analysis', 'ephemeris', 'binning', 'lcviz-export-plot'], 'viewer_area': [{'container': 'col', 'children': [{'container': 'row', diff --git a/lcviz/plugins/__init__.py b/lcviz/plugins/__init__.py index 2bf109b5..332b2641 100644 --- a/lcviz/plugins/__init__.py +++ b/lcviz/plugins/__init__.py @@ -6,6 +6,7 @@ from .flux_column.flux_column import * # noqa from .frequency_analysis.frequency_analysis import * # noqa from .markers.markers import * # noqa +from .slice.slice import * # noqa from .metadata_viewer.metadata_viewer import * # noqa from .plot_options.plot_options import * # noqa from .subset_plugin.subset_plugin import * # noqa diff --git a/lcviz/plugins/slice/__init__.py b/lcviz/plugins/slice/__init__.py new file mode 100644 index 00000000..9bd7645f --- /dev/null +++ b/lcviz/plugins/slice/__init__.py @@ -0,0 +1 @@ +from .slice import * # noqa diff --git a/lcviz/plugins/slice/slice.py b/lcviz/plugins/slice/slice.py new file mode 100644 index 00000000..fedc6d04 --- /dev/null +++ b/lcviz/plugins/slice/slice.py @@ -0,0 +1,58 @@ +from glue_jupyter.bqplot.scatter import BqplotScatterView + +from jdaviz.configs.cubeviz.plugins import Slice +from jdaviz.core.registries import tray_registry + +__all__ = ['Slice'] + + +@tray_registry('lcviz-slice', label="Slice") +class Slice(Slice): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.docs_link = f"https://lcviz.readthedocs.io/en/{self.vdocs}/plugins.html#slice" + self.docs_description = "Select time to show in the image viewer. The slice can also be changed interactively in any time viewer by activating the slice tool." # noqa + self.value_label = 'Time' + self.value_unit = 'd' + + for id, viewer in self.app._viewer_store.items(): + if isinstance(viewer, BqplotScatterView) or len(viewer.data()): + self._watch_viewer(viewer, True) + + @property + def user_api(self): + api = super().user_api + # can be removed after deprecated upstream attributes for wavelength/wavelength_value + # are removed in the lowest supported version of jdaviz + api._expose = [e for e in api._expose if e not in ('wavelength', 'wavelength_value', 'show_wavelength')] # noqa + return api + + def _watch_viewer(self, viewer, watch=True): + super()._watch_viewer(viewer, watch=watch) + # image viewer watching handled upstream + if isinstance(viewer, BqplotScatterView): + if self._x_all is None and len(viewer.data()): + # cache values (wavelengths/freqs) so that value <> slice conversion can efficient + self._update_data(viewer.data()[0].time) + + if viewer not in self._indicator_viewers: + self._indicator_viewers.append(viewer) + # if the units (or data) change, we need to update internally +# viewer.state.add_callback("reference_data", +# self._update_reference_data) + + def _update_reference_data(self, reference_data): + if reference_data is None: + return # pragma: no cover + self._update_data(reference_data.get_object().time) + + @property + def slice_axis(self): + return 'time' + + def _viewer_slices_changed(self, value): + if len(value) == 3: + self.slice = float(value[0]) + + def _set_viewer_to_slice(self, viewer, value): + viewer.state.slices = (value, 0, 0) diff --git a/lcviz/viewers.py b/lcviz/viewers.py index f1670aef..2f5cfb8a 100644 --- a/lcviz/viewers.py +++ b/lcviz/viewers.py @@ -12,7 +12,7 @@ from jdaviz.core.events import NewViewerMessage from jdaviz.core.registries import viewer_registry -from jdaviz.configs.cubeviz.plugins.viewers import CubevizImageView +from jdaviz.configs.cubeviz.plugins.viewers import CubevizImageView, WithSliceIndicator from jdaviz.configs.default.plugins.viewers import JdavizViewerMixin from jdaviz.configs.specviz.plugins.viewers import SpecvizProfileView @@ -60,13 +60,14 @@ def clone_viewer(self): @viewer_registry("lcviz-time-viewer", label="flux-vs-time") -class TimeScatterView(JdavizViewerMixin, CloneViewerMixin, BqplotScatterView): +class TimeScatterView(JdavizViewerMixin, CloneViewerMixin, WithSliceIndicator, BqplotScatterView): # categories: zoom resets, zoom, pan, subset, select tools, shortcuts tools_nested = [ ['jdaviz:homezoom', 'jdaviz:prevzoom'], ['jdaviz:boxzoom', 'jdaviz:xrangezoom', 'jdaviz:yrangezoom'], ['jdaviz:panzoom', 'jdaviz:panzoom_x', 'jdaviz:panzoom_y'], ['bqplot:xrange', 'bqplot:yrange', 'bqplot:rectangle'], + ['jdaviz:selectslice'], ['lcviz:viewer_clone', 'jdaviz:sidebar_plot', 'jdaviz:sidebar_export'] ] default_class = LightCurve @@ -248,6 +249,15 @@ def apply_roi(self, roi, use_current=False): @viewer_registry("lcviz-phase-viewer", label="phase-vs-time") class PhaseScatterView(TimeScatterView): + # categories: zoom resets, zoom, pan, subset, select tools, shortcuts + tools_nested = [ + ['jdaviz:homezoom', 'jdaviz:prevzoom'], + ['jdaviz:boxzoom', 'jdaviz:xrangezoom', 'jdaviz:yrangezoom'], + ['jdaviz:panzoom', 'jdaviz:panzoom_x', 'jdaviz:panzoom_y'], + ['bqplot:xrange', 'bqplot:yrange', 'bqplot:rectangle'], + ['lcviz:viewer_clone', 'jdaviz:sidebar_plot', 'jdaviz:sidebar_export'] + ] + @property def ephemeris_component(self): return self.reference.split('[')[0].split(':')[-1] From 7bb1b61dbb381d76820212ba670b8275908ec700 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Thu, 15 Feb 2024 14:42:32 -0500 Subject: [PATCH 2/9] override upstream functionality for working slice indicator * still requires a notebook hack to send the actual data from astropy import units as u times = lcviz.viewers['image']._obj.data()[0].get_component('dt').data[:, 0, 0] sl._obj._update_data(times * u.d) --- lcviz/marks.py | 12 +++++++++++- lcviz/plugins/slice/slice.py | 32 +++++++++++++++++++++++--------- lcviz/tools.py | 13 +++++++++++++ lcviz/viewers.py | 2 +- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/lcviz/marks.py b/lcviz/marks.py index b5c89da5..28d3b736 100644 --- a/lcviz/marks.py +++ b/lcviz/marks.py @@ -1,11 +1,21 @@ +from astropy import units as u import numpy as np -from jdaviz.core.marks import PluginLine, PluginScatter +from jdaviz.core.marks import PluginLine, PluginScatter, SliceIndicatorMarks from lcviz.viewers import PhaseScatterView __all__ = ['LivePreviewTrend', 'LivePreviewFlattened', 'LivePreviewBinning'] +def _slice_indicator_get_slice_axis(self, data): + if hasattr(data, 'time'): + return data.time.value * u.d + return [] * u.dimensionless_unscaled + + +SliceIndicatorMarks._get_slice_axis = _slice_indicator_get_slice_axis + + class WithoutPhaseSupport: def update_ty(self, times, y): self.times = np.asarray(times) diff --git a/lcviz/plugins/slice/slice.py b/lcviz/plugins/slice/slice.py index fedc6d04..b102ae7e 100644 --- a/lcviz/plugins/slice/slice.py +++ b/lcviz/plugins/slice/slice.py @@ -19,6 +19,23 @@ def __init__(self, *args, **kwargs): if isinstance(viewer, BqplotScatterView) or len(viewer.data()): self._watch_viewer(viewer, True) + @property + def slice_component_label(self): + # label of the component in the cubes corresponding to the slice axis + # calling data_collection_item.get_component(slice_component_label) on any + # input cube-data must work + return 'dt' + + @property + def slice_index(self): + # index in viewer.slices corresponding to the slice axis + return 0 + + @property + def slice_axis(self): + # global display unit "axis" corresponding to the slice axis + return 'time' + @property def user_api(self): api = super().user_api @@ -38,6 +55,7 @@ def _watch_viewer(self, viewer, watch=True): if viewer not in self._indicator_viewers: self._indicator_viewers.append(viewer) # if the units (or data) change, we need to update internally +# need to subscribe to add_data instead of reference_data.... # viewer.state.add_callback("reference_data", # self._update_reference_data) @@ -46,13 +64,9 @@ def _update_reference_data(self, reference_data): return # pragma: no cover self._update_data(reference_data.get_object().time) - @property - def slice_axis(self): - return 'time' - - def _viewer_slices_changed(self, value): - if len(value) == 3: - self.slice = float(value[0]) +# def _viewer_slices_changed(self, value): +# if len(value) == 3: +# self.slice = float(value[0]) - def _set_viewer_to_slice(self, viewer, value): - viewer.state.slices = (value, 0, 0) +# def _set_viewer_to_slice(self, viewer, value): +# viewer.state.slices = (value, 0, 0) diff --git a/lcviz/tools.py b/lcviz/tools.py index badb5aa2..30b905f0 100644 --- a/lcviz/tools.py +++ b/lcviz/tools.py @@ -4,6 +4,9 @@ from glue.viewers.common.tool import Tool from jdaviz.core.tools import SidebarShortcutPlotOptions, SidebarShortcutExportPlot +from jdaviz.configs.cubeviz.plugins.tools import SelectSlice + +from lcviz.viewers import CubeView ICON_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), 'data', 'icons')) @@ -15,6 +18,16 @@ __all__ = ['ViewerClone'] +def _slice_is_visible(self): + if getattr(self.viewer, 'jdaviz_helper', None) is None: + return False + return len([viewer for viewer in self.viewer.jdaviz_helper.viewers.values() + if isinstance(viewer._obj, CubeView)]) > 0 + + +SelectSlice.is_visible = _slice_is_visible + + @viewer_tool class ViewerClone(Tool): icon = os.path.join(ICON_DIR, 'viewer_clone') diff --git a/lcviz/viewers.py b/lcviz/viewers.py index 2f5cfb8a..c2fdb50a 100644 --- a/lcviz/viewers.py +++ b/lcviz/viewers.py @@ -80,7 +80,7 @@ def __init__(self, *args, **kwargs): self.display_mask = False self.time_unit = kwargs.get('time_unit', u.d) - self.initialize_toolbar() + self.initialize_toolbar(default_tool_priority=['jdaviz:selectslice']) self._subscribe_to_layers_update() # hack to inherit a small subset of methods from SpecvizProfileView # TODO: refactor jdaviz so these can be included in some mixin From 763509369f449e8797c1534895a991e69960aaa5 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 16 Feb 2024 10:22:38 -0500 Subject: [PATCH 3/9] context-aware plugin * slice plugin is hidden if no cube-like data * requires upstream support, subject to change --- lcviz/plugins/slice/slice.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lcviz/plugins/slice/slice.py b/lcviz/plugins/slice/slice.py index b102ae7e..7181795c 100644 --- a/lcviz/plugins/slice/slice.py +++ b/lcviz/plugins/slice/slice.py @@ -1,8 +1,13 @@ from glue_jupyter.bqplot.scatter import BqplotScatterView +from glue.core.message import (DataCollectionAddMessage, + DataCollectionDeleteMessage) + from jdaviz.configs.cubeviz.plugins import Slice from jdaviz.core.registries import tray_registry +#from lcviz.viewers import CubeView + __all__ = ['Slice'] @@ -19,6 +24,10 @@ def __init__(self, *args, **kwargs): if isinstance(viewer, BqplotScatterView) or len(viewer.data()): self._watch_viewer(viewer, True) + for msg in (DataCollectionAddMessage, DataCollectionDeleteMessage): + self.session.hub.subscribe(self, msg, handler=self._update_relevant) + self._update_relevant() + @property def slice_component_label(self): # label of the component in the cubes corresponding to the slice axis @@ -36,6 +45,16 @@ def slice_axis(self): # global display unit "axis" corresponding to the slice axis return 'time' + def _update_relevant(self, *args): + # whether the plugin is currently relevant and should appear in the tray + # TODO: if adding a button in the slice plugin to create an image viewer, then this + # should be changed to check for cube data in the data-collection instead + for data in self.app.data_collection: + if data.ndim == 3: + self.irrelevant_msg = '' + return + self.irrelevant_msg = 'Slice plugin is only relevant when cube-like data (e.g., TPF data) is loaded in the app' # noqa + @property def user_api(self): api = super().user_api From 682ce00a2f0e7bcdaaba58a7917b81be182cb48e Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 16 Feb 2024 12:51:49 -0500 Subject: [PATCH 4/9] lcviz support for "show cube viewer" button --- lcviz/plugins/slice/slice.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lcviz/plugins/slice/slice.py b/lcviz/plugins/slice/slice.py index 7181795c..f8c5e377 100644 --- a/lcviz/plugins/slice/slice.py +++ b/lcviz/plugins/slice/slice.py @@ -6,13 +6,16 @@ from jdaviz.configs.cubeviz.plugins import Slice from jdaviz.core.registries import tray_registry -#from lcviz.viewers import CubeView +from lcviz.viewers import CubeView __all__ = ['Slice'] @tray_registry('lcviz-slice', label="Slice") class Slice(Slice): + _cube_viewer_cls = CubeView + _cube_viewer_default_label = 'image' + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.docs_link = f"https://lcviz.readthedocs.io/en/{self.vdocs}/plugins.html#slice" @@ -82,10 +85,3 @@ def _update_reference_data(self, reference_data): if reference_data is None: return # pragma: no cover self._update_data(reference_data.get_object().time) - -# def _viewer_slices_changed(self, value): -# if len(value) == 3: -# self.slice = float(value[0]) - -# def _set_viewer_to_slice(self, viewer, value): -# viewer.state.slices = (value, 0, 0) From 1ddcbcef2685d738ba3080f56e79d512d6b53286 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Mon, 19 Feb 2024 09:00:36 -0500 Subject: [PATCH 5/9] support upstream refactor * removes need for context-aware as the plugin is now always relevant with synced time indicators * renames "Slice" plugin to "Time Selector" --- docs/plugins.rst | 19 +++-- lcviz/helper.py | 2 +- lcviz/plugins/__init__.py | 2 +- lcviz/plugins/slice/__init__.py | 1 - lcviz/plugins/slice/slice.py | 87 -------------------- lcviz/plugins/time_selector/__init__.py | 1 + lcviz/plugins/time_selector/time_selector.py | 57 +++++++++++++ lcviz/tools.py | 13 --- lcviz/viewers.py | 27 +++++- 9 files changed, 95 insertions(+), 114 deletions(-) delete mode 100644 lcviz/plugins/slice/__init__.py delete mode 100644 lcviz/plugins/slice/slice.py create mode 100644 lcviz/plugins/time_selector/__init__.py create mode 100644 lcviz/plugins/time_selector/time_selector.py diff --git a/docs/plugins.rst b/docs/plugins.rst index c52661c9..2d848c47 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -179,18 +179,19 @@ visible when the plugin is opened. Jdaviz documentation on the Markers plugin. -.. _slice: +.. _time-indicator: -Slice -===== +Time Selector +============== -The slice plugin allows defining the time at which all image cubes are displayed. +The time selector plugin allows defining the time indicated in all light curve viewers +(time and phase viewers) as well as the time at which all image cubes are displayed. .. admonition:: User API Example :class: dropdown - See the :class:`~lcviz.plugins.slice.slice.Slice` user API documentation for more details. + See the :class:`~lcviz.plugins.time_selector.time_selector.TimeSelector` user API documentation for more details. .. code-block:: python @@ -201,14 +202,14 @@ The slice plugin allows defining the time at which all image cubes are displayed lcviz.load_data(lc) lcviz.show() - sl = lcviz.plugins['Slice'] - sl.open_in_tray() + ts = lcviz.plugins['Time Selector'] + ts.open_in_tray() .. seealso:: - :ref:`Jdaviz Markers ` - Jdaviz documentation on the Markers plugin. + :ref:`Jdaviz Slice Plugin ` + Jdaviz documentation on the Slice plugin. diff --git a/lcviz/helper.py b/lcviz/helper.py index 596922cf..bbd6ecab 100644 --- a/lcviz/helper.py +++ b/lcviz/helper.py @@ -74,7 +74,7 @@ class LCviz(ConfigHelper): 'toolbar': ['g-data-tools', 'g-subset-tools', 'lcviz-coords-info'], 'tray': ['lcviz-metadata-viewer', 'flux-column', 'lcviz-plot-options', 'lcviz-subset-plugin', - 'lcviz-markers', 'lcviz-slice', + 'lcviz-markers', 'time-selector', 'flatten', 'frequency-analysis', 'ephemeris', 'binning', 'lcviz-export-plot'], 'viewer_area': [{'container': 'col', diff --git a/lcviz/plugins/__init__.py b/lcviz/plugins/__init__.py index 332b2641..fe4c9879 100644 --- a/lcviz/plugins/__init__.py +++ b/lcviz/plugins/__init__.py @@ -6,7 +6,7 @@ from .flux_column.flux_column import * # noqa from .frequency_analysis.frequency_analysis import * # noqa from .markers.markers import * # noqa -from .slice.slice import * # noqa +from .time_selector.time_selector import * # noqa from .metadata_viewer.metadata_viewer import * # noqa from .plot_options.plot_options import * # noqa from .subset_plugin.subset_plugin import * # noqa diff --git a/lcviz/plugins/slice/__init__.py b/lcviz/plugins/slice/__init__.py deleted file mode 100644 index 9bd7645f..00000000 --- a/lcviz/plugins/slice/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .slice import * # noqa diff --git a/lcviz/plugins/slice/slice.py b/lcviz/plugins/slice/slice.py deleted file mode 100644 index f8c5e377..00000000 --- a/lcviz/plugins/slice/slice.py +++ /dev/null @@ -1,87 +0,0 @@ -from glue_jupyter.bqplot.scatter import BqplotScatterView -from glue.core.message import (DataCollectionAddMessage, - DataCollectionDeleteMessage) - - -from jdaviz.configs.cubeviz.plugins import Slice -from jdaviz.core.registries import tray_registry - -from lcviz.viewers import CubeView - -__all__ = ['Slice'] - - -@tray_registry('lcviz-slice', label="Slice") -class Slice(Slice): - _cube_viewer_cls = CubeView - _cube_viewer_default_label = 'image' - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.docs_link = f"https://lcviz.readthedocs.io/en/{self.vdocs}/plugins.html#slice" - self.docs_description = "Select time to show in the image viewer. The slice can also be changed interactively in any time viewer by activating the slice tool." # noqa - self.value_label = 'Time' - self.value_unit = 'd' - - for id, viewer in self.app._viewer_store.items(): - if isinstance(viewer, BqplotScatterView) or len(viewer.data()): - self._watch_viewer(viewer, True) - - for msg in (DataCollectionAddMessage, DataCollectionDeleteMessage): - self.session.hub.subscribe(self, msg, handler=self._update_relevant) - self._update_relevant() - - @property - def slice_component_label(self): - # label of the component in the cubes corresponding to the slice axis - # calling data_collection_item.get_component(slice_component_label) on any - # input cube-data must work - return 'dt' - - @property - def slice_index(self): - # index in viewer.slices corresponding to the slice axis - return 0 - - @property - def slice_axis(self): - # global display unit "axis" corresponding to the slice axis - return 'time' - - def _update_relevant(self, *args): - # whether the plugin is currently relevant and should appear in the tray - # TODO: if adding a button in the slice plugin to create an image viewer, then this - # should be changed to check for cube data in the data-collection instead - for data in self.app.data_collection: - if data.ndim == 3: - self.irrelevant_msg = '' - return - self.irrelevant_msg = 'Slice plugin is only relevant when cube-like data (e.g., TPF data) is loaded in the app' # noqa - - @property - def user_api(self): - api = super().user_api - # can be removed after deprecated upstream attributes for wavelength/wavelength_value - # are removed in the lowest supported version of jdaviz - api._expose = [e for e in api._expose if e not in ('wavelength', 'wavelength_value', 'show_wavelength')] # noqa - return api - - def _watch_viewer(self, viewer, watch=True): - super()._watch_viewer(viewer, watch=watch) - # image viewer watching handled upstream - if isinstance(viewer, BqplotScatterView): - if self._x_all is None and len(viewer.data()): - # cache values (wavelengths/freqs) so that value <> slice conversion can efficient - self._update_data(viewer.data()[0].time) - - if viewer not in self._indicator_viewers: - self._indicator_viewers.append(viewer) - # if the units (or data) change, we need to update internally -# need to subscribe to add_data instead of reference_data.... -# viewer.state.add_callback("reference_data", -# self._update_reference_data) - - def _update_reference_data(self, reference_data): - if reference_data is None: - return # pragma: no cover - self._update_data(reference_data.get_object().time) diff --git a/lcviz/plugins/time_selector/__init__.py b/lcviz/plugins/time_selector/__init__.py new file mode 100644 index 00000000..0983e6d5 --- /dev/null +++ b/lcviz/plugins/time_selector/__init__.py @@ -0,0 +1 @@ +from .time_selector import * # noqa diff --git a/lcviz/plugins/time_selector/time_selector.py b/lcviz/plugins/time_selector/time_selector.py new file mode 100644 index 00000000..8f97dcc8 --- /dev/null +++ b/lcviz/plugins/time_selector/time_selector.py @@ -0,0 +1,57 @@ +from jdaviz.configs.cubeviz.plugins import Slice +from jdaviz.core.registries import tray_registry + +from lcviz.viewers import CubeView + +__all__ = ['TimeSelector'] + + +@tray_registry('time-selector', label="Time Selector") +class TimeSelector(Slice): + """ + See the :ref:`Time Selector Plugin Documentation ` for more details. + + Only the following attributes and methods are available through the + :ref:`public plugin API `: + + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.show` + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.open_in_tray` + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.close_in_tray` + * ``value`` Time of the indicator. When setting this directly, it will + update automatically to the value corresponding to the nearest slice, if ``snap_to_slice`` is + enabled and a cube is loaded. + * ``show_indicator`` + Whether to show indicator in spectral viewer when slice tool is inactive. + * ``show_value`` + Whether to show slice value in label to right of indicator. + * ``snap_to_slice`` + Whether the indicator (and ``value``) should snap to the value of the nearest slice in the + cube (if one exists). + """ + _cube_viewer_cls = CubeView + _cube_viewer_default_label = 'image' + + def __init__(self, *args, **kwargs): + """ + + """ + super().__init__(*args, **kwargs) + self.docs_link = f"https://lcviz.readthedocs.io/en/{self.vdocs}/plugins.html#time-selector" + self.docs_description = "Select time to sync across all viewers (as an indicator in all time/phase viewers or to select the active slice in any image/cube viewers). The slice can also be changed interactively in any time viewer by activating the slice tool." # noqa + self.value_label = 'Time' + self.value_unit = 'd' + self.allow_disable_snapping = True + + @property + def slice_axis(self): + # global display unit "axis" corresponding to the slice axis + return 'time' + + @property + def user_api(self): + api = super().user_api + # can be removed after deprecated upstream attributes for wavelength/wavelength_value + # are removed in the lowest supported version of jdaviz + api._expose = [e for e in api._expose if e not in ('slice', 'wavelength', + 'wavelength_value', 'show_wavelength')] + return api diff --git a/lcviz/tools.py b/lcviz/tools.py index 30b905f0..badb5aa2 100644 --- a/lcviz/tools.py +++ b/lcviz/tools.py @@ -4,9 +4,6 @@ from glue.viewers.common.tool import Tool from jdaviz.core.tools import SidebarShortcutPlotOptions, SidebarShortcutExportPlot -from jdaviz.configs.cubeviz.plugins.tools import SelectSlice - -from lcviz.viewers import CubeView ICON_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), 'data', 'icons')) @@ -18,16 +15,6 @@ __all__ = ['ViewerClone'] -def _slice_is_visible(self): - if getattr(self.viewer, 'jdaviz_helper', None) is None: - return False - return len([viewer for viewer in self.viewer.jdaviz_helper.viewers.values() - if isinstance(viewer._obj, CubeView)]) > 0 - - -SelectSlice.is_visible = _slice_is_visible - - @viewer_tool class ViewerClone(Tool): icon = os.path.join(ICON_DIR, 'viewer_clone') diff --git a/lcviz/viewers.py b/lcviz/viewers.py index c2fdb50a..25465cb1 100644 --- a/lcviz/viewers.py +++ b/lcviz/viewers.py @@ -12,7 +12,8 @@ from jdaviz.core.events import NewViewerMessage from jdaviz.core.registries import viewer_registry -from jdaviz.configs.cubeviz.plugins.viewers import CubevizImageView, WithSliceIndicator +from jdaviz.configs.cubeviz.plugins.viewers import (CubevizImageView, + WithSliceIndicator, WithSliceSelection) from jdaviz.configs.default.plugins.viewers import JdavizViewerMixin from jdaviz.configs.specviz.plugins.viewers import SpecvizProfileView @@ -90,6 +91,12 @@ def __init__(self, *args, **kwargs): self._clean_error = lambda: SpecvizProfileView._clean_error(self) self.density_map = kwargs.get('density_map', False) + @property + def slice_component_label(self): + # label of the component in the lightcurves corresponding to the slice axis + # calling data_collection_item.get_component(slice_component_label) must work + return 'dt' + def data(self, cls=None): data = [] @@ -275,9 +282,13 @@ def times_to_phases(self, times): return ephem.times_to_phases(times, ephem_component=self.ephemeris_component) + def _set_slice_indicator_value(self, value): + # NOTE: on first call, this will initialize the indicator itself + self.slice_indicator.value = self.times_to_phases(value) + @viewer_registry("lcviz-cube-viewer", label="cube") -class CubeView(CloneViewerMixin, CubevizImageView): +class CubeView(CloneViewerMixin, CubevizImageView, WithSliceSelection): # categories: zoom resets, zoom, pan, subset, select tools, shortcuts tools_nested = [ ['jdaviz:homezoom', 'jdaviz:prevzoom'], @@ -307,6 +318,18 @@ def __init__(self, *args, **kwargs): # * _default_flux_viewer_reference_name # * _default_uncert_viewer_reference_name + @property + def slice_component_label(self): + # label of the component in the cubes corresponding to the slice axis + # calling data_collection_item.get_component(slice_component_label) on any + # input cube-data must work + return 'dt' + + @property + def slice_index(self): + # index in viewer.slices corresponding to the slice axis + return 0 + def _initial_x_axis(self, *args): # Make sure that the x_att/y_att is correct on data load # called via a callback set upstream in CubevizImageView when reference_data is changed From 6058edfeb026c319d8db4e5a7623339054b157a6 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 1 Mar 2024 09:54:26 -0500 Subject: [PATCH 6/9] bump jdaviz requirement --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ae426903..e5bc6eb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ ] dependencies = [ "astropy>=5.2", - "jdaviz==3.8.*", + "jdaviz==3.9.*", "lightkurve>=2.4.1", ] dynamic = [ From a2d22eec3289eabbd1e505b35a8366e4214ea6b5 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Mon, 4 Mar 2024 10:24:04 -0500 Subject: [PATCH 7/9] override valid_slice_att_names --- lcviz/plugins/time_selector/time_selector.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lcviz/plugins/time_selector/time_selector.py b/lcviz/plugins/time_selector/time_selector.py index 8f97dcc8..9ac85cc1 100644 --- a/lcviz/plugins/time_selector/time_selector.py +++ b/lcviz/plugins/time_selector/time_selector.py @@ -47,6 +47,10 @@ def slice_axis(self): # global display unit "axis" corresponding to the slice axis return 'time' + @property + def valid_slice_att_names(self): + return ["time", "dt"] + @property def user_api(self): api = super().user_api From df47f7cf0cb54d92f36949f51c87b824b9500617 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Wed, 6 Mar 2024 14:25:47 -0500 Subject: [PATCH 8/9] add time_selector to api docs --- docs/reference/api_plugins.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/reference/api_plugins.rst b/docs/reference/api_plugins.rst index a366d19a..20d0818e 100644 --- a/docs/reference/api_plugins.rst +++ b/docs/reference/api_plugins.rst @@ -29,3 +29,6 @@ Plugins API .. automodapi:: lcviz.plugins.subset_plugin.subset_plugin :no-inheritance-diagram: + +.. automodapi:: lcviz.plugins.time_selector.time_selector + :no-inheritance-diagram: From ac02954284674f502583b29e8b1b188c64b64869 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Wed, 6 Mar 2024 15:25:34 -0500 Subject: [PATCH 9/9] allow dragging time indicator in phase-space --- lcviz/plugins/time_selector/time_selector.py | 11 ++++++++++- lcviz/viewers.py | 16 +++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/lcviz/plugins/time_selector/time_selector.py b/lcviz/plugins/time_selector/time_selector.py index 9ac85cc1..9620e361 100644 --- a/lcviz/plugins/time_selector/time_selector.py +++ b/lcviz/plugins/time_selector/time_selector.py @@ -1,7 +1,7 @@ from jdaviz.configs.cubeviz.plugins import Slice from jdaviz.core.registries import tray_registry -from lcviz.viewers import CubeView +from lcviz.viewers import CubeView, PhaseScatterView __all__ = ['TimeSelector'] @@ -59,3 +59,12 @@ def user_api(self): api._expose = [e for e in api._expose if e not in ('slice', 'wavelength', 'wavelength_value', 'show_wavelength')] return api + + def _on_select_slice_message(self, msg): + viewer = msg.sender.viewer + if isinstance(viewer, PhaseScatterView): + prev_phase = viewer.times_to_phases(self.value) + new_phase = msg.value + self.value = self.value + (new_phase - prev_phase) * viewer.ephemeris.get('period', 1.0) + else: + super()._on_select_slice_message(msg) diff --git a/lcviz/viewers.py b/lcviz/viewers.py index 25465cb1..71a9f88c 100644 --- a/lcviz/viewers.py +++ b/lcviz/viewers.py @@ -256,19 +256,17 @@ def apply_roi(self, roi, use_current=False): @viewer_registry("lcviz-phase-viewer", label="phase-vs-time") class PhaseScatterView(TimeScatterView): - # categories: zoom resets, zoom, pan, subset, select tools, shortcuts - tools_nested = [ - ['jdaviz:homezoom', 'jdaviz:prevzoom'], - ['jdaviz:boxzoom', 'jdaviz:xrangezoom', 'jdaviz:yrangezoom'], - ['jdaviz:panzoom', 'jdaviz:panzoom_x', 'jdaviz:panzoom_y'], - ['bqplot:xrange', 'bqplot:yrange', 'bqplot:rectangle'], - ['lcviz:viewer_clone', 'jdaviz:sidebar_plot', 'jdaviz:sidebar_export'] - ] - @property def ephemeris_component(self): return self.reference.split('[')[0].split(':')[-1] + @property + def ephemeris(self): + ephem = self.jdaviz_helper.plugins.get('Ephemeris', None) + if ephem is None: + raise ValueError("must have ephemeris plugin loaded to access ephemeris") + return ephem.ephemerides.get(self.ephemeris_component) + 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