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)