From 1b794ca5a39c34e1f77fd851e229a9ba57c277ea Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Tue, 28 May 2024 14:20:15 -0400 Subject: [PATCH 1/2] WIP: unit conversion plugin --- docs/plugins.rst | 28 ++++++++++ docs/reference/api_plugins.rst | 3 ++ lcviz/helper.py | 2 +- lcviz/plugins/__init__.py | 1 + lcviz/plugins/unit_conversion/__init__.py | 1 + .../unit_conversion/unit_conversion.py | 53 +++++++++++++++++++ lcviz/viewers.py | 5 +- 7 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 lcviz/plugins/unit_conversion/__init__.py create mode 100644 lcviz/plugins/unit_conversion/unit_conversion.py diff --git a/docs/plugins.rst b/docs/plugins.rst index ea766aa..247ab0a 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -72,6 +72,34 @@ This plugin allows choosing which column in the underlying data should be used a * :meth:`lightkurve.LightCurve.select_flux` +.. _unit-conversion + +Unit Conversion +=============== + +This plugin allows choosing app-wide units to use for the time and flux axes. + + +.. admonition:: User API Example + :class: dropdown + + See the :class:`~lcviz.plugins.unit_conversion.unit_conversion.UnitConversion` user API documentation for more details. + + .. code-block:: python + + from lcviz import LCviz + from lightkurve import search_lightcurve + lc = search_lightcurve("HAT-P-11", mission="Kepler", + cadence="long", quarter=10).download().flatten() + lcviz = LCviz() + lcviz.load_data(lc) + lcviz.show() + + units = lcviz.plugins['Unit Conversion'] + print(units.time_unit.choices) + units.time_unit = 'hr' + + .. _plot-options: Plot Options diff --git a/docs/reference/api_plugins.rst b/docs/reference/api_plugins.rst index 680393a..956eb5e 100644 --- a/docs/reference/api_plugins.rst +++ b/docs/reference/api_plugins.rst @@ -38,3 +38,6 @@ Plugins API .. automodapi:: lcviz.plugins.time_selector.time_selector :no-inheritance-diagram: + +.. automodapi:: lcviz.plugins.unit_conversion.unit_conversion + :no-inheritance-diagram: \ No newline at end of file diff --git a/lcviz/helper.py b/lcviz/helper.py index 9e41ca9..ed3bfe1 100644 --- a/lcviz/helper.py +++ b/lcviz/helper.py @@ -103,7 +103,7 @@ class LCviz(ConfigHelper): 'dense_toolbar': False, 'context': {'notebook': {'max_height': '600px'}}}, 'toolbar': ['g-data-tools', 'g-subset-tools', 'g-viewer-creator', 'lcviz-coords-info'], - 'tray': ['lcviz-metadata-viewer', 'flux-column', + 'tray': ['lcviz-metadata-viewer', 'flux-column', 'lcviz-unit-conversion', 'lcviz-plot-options', 'lcviz-subset-plugin', 'lcviz-markers', 'time-selector', 'stitch', 'flatten', 'frequency-analysis', 'ephemeris', diff --git a/lcviz/plugins/__init__.py b/lcviz/plugins/__init__.py index bdde8f1..e1a1b16 100644 --- a/lcviz/plugins/__init__.py +++ b/lcviz/plugins/__init__.py @@ -13,3 +13,4 @@ from .plot_options.plot_options import * # noqa from .stitch.stitch import * # noqa from .subset_plugin.subset_plugin import * # noqa +from .unit_conversion.unit_conversion import * # noqa diff --git a/lcviz/plugins/unit_conversion/__init__.py b/lcviz/plugins/unit_conversion/__init__.py new file mode 100644 index 0000000..b58a77d --- /dev/null +++ b/lcviz/plugins/unit_conversion/__init__.py @@ -0,0 +1 @@ +from .unit_conversion import * # noqa diff --git a/lcviz/plugins/unit_conversion/unit_conversion.py b/lcviz/plugins/unit_conversion/unit_conversion.py new file mode 100644 index 0000000..9af821a --- /dev/null +++ b/lcviz/plugins/unit_conversion/unit_conversion.py @@ -0,0 +1,53 @@ +from traitlets import List, Unicode + +from jdaviz.core.template_mixin import UnitSelectPluginComponent +from jdaviz.configs.specviz.plugins import UnitConversion +from jdaviz.core.registries import tray_registry + +__all__ = ['UnitConversion'] + + +@tray_registry('lcviz-unit-conversion', label="Unit Conversion") +class UnitConversion(UnitConversion): + """ + See the :ref:`Unit Conversion Plugin Documentation ` for more details. + + For a full list of exposed attributes, call ``dir(plugin)``. Note that some attributes are + applicable depending on the selection of ``viewer`` and/or ``layer``. Below are + a list of some common 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` + """ + time_unit_items = List().tag(sync=True) + time_unit_selected = Unicode().tag(sync=True) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.docs_link = f"https://lcviz.readthedocs.io/en/{self.vdocs}/plugins.html#unit-conversion" + self.docs_description = "Choose units for the time and flux axes." + self.disabled_msg = "" # otherwise disabled upstream - remove once upstream no longer has a config check + + self.time_unit = UnitSelectPluginComponent(self, + items='time_unit_items', + selected='time_unit_selected') + + @property + def spectrum_viewer(self): + if hasattr(self, '_default_time_viewer_reference_name'): + viewer_reference = self._default_time_viewer_reference_name + else: + viewer_reference = self.app._get_first_viewer_reference_name( + ) + + return self.app.get_viewer(viewer_reference) + + @property + def user_api(self): + api = super().user_api + expose = ['time_unit', 'flux_unit'] + api._expose = expose + + return api diff --git a/lcviz/viewers.py b/lcviz/viewers.py index 1d0e74d..33e2c7c 100644 --- a/lcviz/viewers.py +++ b/lcviz/viewers.py @@ -176,7 +176,10 @@ def _set_plot_x_axes(self, dc, component_labels, light_curve): 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')] + try: + self.state.y_att = dc[0].components[component_labels.index('flux')] + except ValueError: + pass y_unit = light_curve.flux.unit y_unit_physical_type = str(y_unit.physical_type).title() From 1e8bd93842ada6b998b68f72a449b562bd7e5f44 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Tue, 1 Oct 2024 13:02:28 -0400 Subject: [PATCH 2/2] WIP: update to use upstream generalization https://github.com/spacetelescope/jdaviz/pull/3166 --- .../unit_conversion/unit_conversion.py | 86 +++++++++++++++---- lcviz/state.py | 1 + lcviz/viewers.py | 2 +- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/lcviz/plugins/unit_conversion/unit_conversion.py b/lcviz/plugins/unit_conversion/unit_conversion.py index 9af821a..9895952 100644 --- a/lcviz/plugins/unit_conversion/unit_conversion.py +++ b/lcviz/plugins/unit_conversion/unit_conversion.py @@ -1,9 +1,11 @@ -from traitlets import List, Unicode - -from jdaviz.core.template_mixin import UnitSelectPluginComponent -from jdaviz.configs.specviz.plugins import UnitConversion +from jdaviz.configs.specviz.plugins.unit_conversion.unit_conversion import (UnitConversion, + _valid_glue_display_unit, # noqa + _flux_to_sb_unit) +from jdaviz.core.events import GlobalDisplayUnitChanged from jdaviz.core.registries import tray_registry +from lcviz.viewers import (CubeView, TimeScatterView) + __all__ = ['UnitConversion'] @@ -21,18 +23,14 @@ class UnitConversion(UnitConversion): * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.open_in_tray` * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.close_in_tray` """ - time_unit_items = List().tag(sync=True) - time_unit_selected = Unicode().tag(sync=True) - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.docs_link = f"https://lcviz.readthedocs.io/en/{self.vdocs}/plugins.html#unit-conversion" self.docs_description = "Choose units for the time and flux axes." self.disabled_msg = "" # otherwise disabled upstream - remove once upstream no longer has a config check - self.time_unit = UnitSelectPluginComponent(self, - items='time_unit_items', - selected='time_unit_selected') + self.has_time = True + self.has_flux = True @property def spectrum_viewer(self): @@ -44,10 +42,66 @@ def spectrum_viewer(self): return self.app.get_viewer(viewer_reference) - @property - def user_api(self): - api = super().user_api - expose = ['time_unit', 'flux_unit'] - api._expose = expose + def _on_add_data_to_viewer(self, msg): + viewer = msg.viewer + + if (not len(self.time_unit_selected) or not len(self.flux_unit_selected)): + # TODO: default based on the native units of the data (LC or TPF) + self.time_unit.choices = ['d', 'hr', 'min', 's'] + self.time_unit_selected = 'd' + self.flux_unit.choices = ['erg / (Angstrom cm2 s)'] + self.flux_unit_selected = 'erg / (Angstrom cm2 s)' + # setting default values will trigger the observes to set the units + # in _on_unit_selected, so return here to avoid setting twice + return + + # TODO: when enabling unit-conversion in rampviz, this may need to be more specific + # or handle other cases for ramp profile viewers + if isinstance(viewer, TimeScatterView): + if (viewer.state.x_display_unit == self.time_unit_selected + and viewer.state.y_display_unit == self.flux_unit_selected): + # data already existed in this viewer and display units were already set + return + + # this spectral viewer was empty (did not have display units set yet),˜ + # but global selections are available in the plugin, + # so we'll set them to the viewer here + viewer.state.x_display_unit = self.time_unit_selected + viewer.state.y_display_unit = self.flux_unit_selected + + elif isinstance(viewer, CubeView): + # set the attribute display unit (contour and stretch units) for the new layer + # NOTE: this assumes that all image data is coerced to surface brightness units + layers = [lyr for lyr in msg.viewer.layers if lyr.layer.data.label == msg.data.label] + self._handle_attribute_display_unit(self.flux_unit_selected, layers=layers) + self._clear_cache('image_layers') + + def _on_unit_selected(self, msg): + """ + When any user selection is made, update the relevant viewer(s) with the new unit, + and then emit a GlobalDisplayUnitChanged message to notify other plugins of the change. + """ + print("*** _on_unit_selected", msg.get('name'), msg.get('new')) + if not len(msg.get('new', '')): + # empty string, nothing to set yet + return + + axis = msg.get('name').split('_')[0] + + if axis == 'time': + xunit = _valid_glue_display_unit(self.time_unit.selected, self.spectrum_viewer, 'x') + # TODO: iterate over all TimeScatterViewers + self.spectrum_viewer.state.x_display_unit = xunit + self.spectrum_viewer.set_plot_axes() + + elif axis == 'flux': + yunit = _valid_glue_display_unit(self.flux_unit.selected, self.spectrum_viewer, 'y') + # TODO: iterate over all Time and Phase Scatter Viewers + self.spectrum_viewer.state.y_display_unit = yunit + self.spectrum_viewer.set_plot_axes() + + # TODO: handle setting surface brightness units for CubeView - return api + # axis (first) argument will be one of: time, flux + self.hub.broadcast(GlobalDisplayUnitChanged(axis, + msg.new, sender=self)) diff --git a/lcviz/state.py b/lcviz/state.py index fbab96c..4d6adcd 100644 --- a/lcviz/state.py +++ b/lcviz/state.py @@ -35,6 +35,7 @@ def _reset_y_limits(self, *event): self._reset_att_limits('y') def reset_limits(self, *event): + # TODO: this is not working correctly when units are changed (probably the same for _reset_att_limits) x_min, x_max = np.inf, -np.inf y_min, y_max = np.inf, -np.inf diff --git a/lcviz/viewers.py b/lcviz/viewers.py index 33e2c7c..1c0321e 100644 --- a/lcviz/viewers.py +++ b/lcviz/viewers.py @@ -164,7 +164,7 @@ def set_plot_axes(self): def _set_plot_x_axes(self, dc, component_labels, light_curve): self.state.x_att = dc[0].components[component_labels.index('dt')] - x_unit = self.time_unit + x_unit = self.time_unit # TODO: use get display unit instead? Or at least update self.time_unit reference_time = light_curve.meta.get('reference_time', None) if reference_time is not None: