Source code for lcviz.plugins.time_selector.time_selector
from traitlets import observe
from jdaviz.core.template_mixin import ViewerSelectMixin
from jdaviz.configs.cubeviz.plugins import BaseSlicePlugin
from jdaviz.core.registries import tray_registry
from lcviz.events import EphemerisChangedMessage
from lcviz.viewers import CubeView, TimeScatterView, PhaseScatterView
__all__ = ['TimeSelector']
[docs]
@tray_registry('time-selector', label="Time Selector", category='app:options')
class TimeSelector(BaseSlicePlugin, ViewerSelectMixin):
"""
See the :ref:`Time Selector Plugin Documentation <time-selector>` for more details.
Only the following attributes and methods are available through the
:ref:`public plugin API <plugin-apis>`:
* :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.plugin_key = "Time Selector"
self._plugin_description = 'Select active time across all viewers.'
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
self.session.hub.subscribe(self, EphemerisChangedMessage,
handler=self._on_ephemeris_changed)
# Remove the is_slice_selection_viewer filter from parent class since
# TimeScatterView/PhaseScatterView use WithSliceIndicator not WithSliceSelection
self.viewer.remove_filter('is_slice_selection_viewer')
self.viewer.add_filter(lambda viewer: isinstance(viewer, (TimeScatterView, PhaseScatterView, CubeView))) # noqa
self._set_relevant()
@observe('vdocs')
def _update_docs_link(self, *args):
self.docs_link = f"https://lcviz.readthedocs.io/en/{self.vdocs}/plugins/time_selector.html"
@property
def slice_display_unit_name(self):
# global display unit "axis" corresponding to the slice axis
return 'time'
@property
def valid_slice_att_names(self):
return ["time", "dt"]
def _initialize_location(self, *args):
# override to handle scatter viewers that don't have x_att_pixel
# (introduced in jdaviz main for profile viewers)
if not self.value_unit:
for viewer in self.slice_indicator_viewers:
if getattr(viewer.state, 'x_display_unit', None) is not None:
self.value_unit = viewer.state.x_display_unit
break
if self._indicator_initialized:
return
self._clear_cache()
for viewer in self.slice_indicator_viewers:
x_att = str(viewer.state.x_att) if viewer.state.x_att is not None else ''
x_att_pixel = getattr(viewer.state, 'x_att_pixel', None)
x_att_pixel_str = str(x_att_pixel) if x_att_pixel is not None else ''
if (x_att not in self.valid_slice_att_names and
x_att_pixel_str not in self.valid_slice_att_names):
continue
# NOTE: unlike parent class, we don't check for empty display units
# because lcviz viewers work with native units
slice_values = viewer.slice_values
if len(slice_values):
self.value = float(slice_values[int(len(slice_values)/2)])
self._indicator_initialized = True
return
@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
def _on_select_slice_message(self, msg):
# If the message originated from a tool in a non-lcviz viewer (e.g. the spectral
# slice tool in a CubevizProfileView), ignore it so that it does not affect the
# time indicator. Messages sent from the programmatic API (sender has no .viewer)
# are always accepted.
sender_viewer = getattr(msg.sender, 'viewer', None)
if sender_viewer is not None and type(sender_viewer) not in (
TimeScatterView, PhaseScatterView, CubeView):
return
if type(sender_viewer) is PhaseScatterView:
prev_phase = sender_viewer.times_to_phases(self.value)
new_phase = msg.value
self.value = self.value + (new_phase - prev_phase) * sender_viewer.ephemeris.get('period', 1.0) # noqa
else:
super()._on_select_slice_message(msg)
@property
def slice_selection_viewers(self):
# Restrict to lcviz CubeView only; avoids picking up CubevizImageView instances
# if spectral cubes are also loaded in a deconfigged app.
return [v for v in self._app._viewer_store.values() if isinstance(v, CubeView)]
@property
def slice_indicator_viewers(self):
# Restrict to lcviz scatter viewers; avoids picking up CubevizProfileView instances
# if spectral cubes are also loaded in a deconfigged app.
return [v for v in self._app._viewer_store.values()
if isinstance(v, (TimeScatterView, PhaseScatterView))]
def _check_if_cube_viewer_exists(self, *args):
# self.viewer.choices includes scatter viewers (to have slice indicator)
self.cube_viewer_exists = len(self.slice_selection_viewers) > 0
def _on_ephemeris_changed(self, msg):
for viewer in self.slice_indicator_viewers:
if not isinstance(viewer, PhaseScatterView):
continue
if viewer._ephemeris_component != msg.ephemeris_label:
continue
viewer._set_slice_indicator_value(self.value)