From 0bff08d08fb1a03974315b9e518158eed80eb4d2 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 29 Dec 2023 12:34:34 -0500 Subject: [PATCH] rework flatten plugin to use flux columns --- lcviz/components/__init__.py | 117 ++++++++++++++++++++++ lcviz/events.py | 12 ++- lcviz/plugins/flatten/flatten.py | 73 +++++++++----- lcviz/plugins/flatten/flatten.vue | 42 ++++---- lcviz/plugins/flux_origin/flux_origin.py | 75 +------------- lcviz/plugins/flux_origin/flux_origin.vue | 4 +- lcviz/tests/test_plugin_flatten.py | 21 +--- lcviz/tests/test_plugin_flux_origin.py | 6 +- 8 files changed, 210 insertions(+), 140 deletions(-) diff --git a/lcviz/components/__init__.py b/lcviz/components/__init__.py index e69de29b..33450407 100644 --- a/lcviz/components/__init__.py +++ b/lcviz/components/__init__.py @@ -0,0 +1,117 @@ +from astropy import units as u +from ipyvuetify import VuetifyTemplate +from glue.core import HubListener +from traitlets import List, Unicode + +from jdaviz.core.template_mixin import SelectPluginComponent + +from lcviz.events import FluxOriginChangedMessage + +__all__ = ['FluxOriginSelect', 'FluxOriginSelectMixin'] + + +class FluxOriginSelect(SelectPluginComponent): + def __init__(self, plugin, items, selected, dataset): + super().__init__(plugin, + items=items, + selected=selected, + dataset=dataset) + + self.add_observe(selected, self._on_change_selected) + self.add_observe(self.dataset._plugin_traitlets['selected'], + self._on_change_dataset) + + # sync between instances in different plugins + self.hub.subscribe(self, FluxOriginChangedMessage, + handler=self._on_flux_origin_changed_msg) + + def _on_change_dataset(self, *args): + def _include_col(lk_obj, col): + if col == 'flux' and lk_obj.meta.get('FLUX_ORIGIN') != 'flux': + # this is the currently active column (and should be copied elsewhere unless) + return False + if col in ('time', 'cadn', 'cadenceno', 'quality'): + return False + if col.startswith('phase:'): + # internal jdaviz ephemeris phase columns + return False + if col.startswith('time'): + return False + if col.startswith('centroid'): + return False + if col.startswith('cbv'): + # cotrending basis vector + return False + if col.endswith('_err'): + return False + if col.endswith('quality'): + return False + # TODO: need to think about flatten losing units in the flux column + return lk_obj[col].unit != u.pix + + lk_obj = self.dataset.selected_obj + if lk_obj is None: + return + self.choices = [col for col in lk_obj.columns if _include_col(lk_obj, col)] + flux_origin = lk_obj.meta.get('FLUX_ORIGIN') + if flux_origin in self.choices: + self.selected = flux_origin + else: + self.selected = '' + + def _on_flux_origin_changed_msg(self, msg): + if msg.dataset != self.dataset.selected: + return + + # need to clear the cache due to the change in metadata made to the data-collection entry + self.dataset._clear_cache('selected_obj', 'selected_dc_item') + self._on_change_dataset() + self.selected = msg.flux_origin + + def _on_change_selected(self, *args): + if self.selected == '': + return + + dc_item = self.dataset.selected_dc_item + old_flux_origin = dc_item.meta.get('FLUX_ORIGIN') + if self.selected == old_flux_origin: + # nothing to do here! + return + + # instead of using lightkurve's select_flux and having to reparse the data entry, we'll + # manipulate the arrays in the data-collection directly, and modify FLUX_ORIGIN so that + # exporting back to a lightkurve object works as expected + self.app._jdaviz_helper._set_data_component(dc_item, 'flux', dc_item[self.selected]) + self.app._jdaviz_helper._set_data_component(dc_item, 'flux_err', dc_item[self.selected+"_err"]) # noqa + dc_item.meta['FLUX_ORIGIN'] = self.selected + + self.hub.broadcast(FluxOriginChangedMessage(dataset=self.dataset.selected, + flux_origin=self.selected, + sender=self)) + + def add_new_flux_column(self, flux, flux_err, label, selected=False): + dc_item = self.dataset.selected_dc_item + self.app._jdaviz_helper._set_data_component(dc_item, + label, + flux) + self.app._jdaviz_helper._set_data_component(dc_item, + f"{label}_err", + flux_err) + + # broadcast so all instances update to get the new column and selection (if applicable) + self.hub.broadcast(FluxOriginChangedMessage(dataset=self.dataset.selected, + flux_origin=label if selected else self.selected, # noqa + sender=self)) + + +class FluxOriginSelectMixin(VuetifyTemplate, HubListener): + flux_origin_items = List().tag(sync=True) + flux_origin_selected = Unicode().tag(sync=True) + # assumes DatasetSelectMixin is also used (DatasetSelectMixin must appear after in inheritance) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.flux_origin = FluxOriginSelect(self, + 'flux_origin_items', + 'flux_origin_selected', + dataset='dataset') diff --git a/lcviz/events.py b/lcviz/events.py index 3ca014c6..724471f4 100644 --- a/lcviz/events.py +++ b/lcviz/events.py @@ -1,7 +1,8 @@ from glue.core.message import Message __all__ = ['EphemerisComponentChangedMessage', - 'EphemerisChangedMessage'] + 'EphemerisChangedMessage', + 'FluxOriginChangedMessage'] class EphemerisComponentChangedMessage(Message): @@ -27,3 +28,12 @@ class EphemerisChangedMessage(Message): in the ephemeris plugin""" def __init__(self, ephemeris_label, *args, **kwargs): self.ephemeris_label = ephemeris_label + + +class FluxOriginChangedMessage(Message): + """Message emitted by the FluxOriginSelect component when the selection has been changed. + To subscribe to a change for a particular dataset, consider using FluxOriginSelect directly + and observing the traitlet, rather than subscribing to this message""" + def __init__(self, dataset, flux_origin, *args, **kwargs): + self.dataset = dataset + self.flux_origin = flux_origin diff --git a/lcviz/plugins/flatten/flatten.py b/lcviz/plugins/flatten/flatten.py index 66b8e94c..55ae8773 100644 --- a/lcviz/plugins/flatten/flatten.py +++ b/lcviz/plugins/flatten/flatten.py @@ -7,11 +7,13 @@ from jdaviz.core.events import ViewerAddedMessage from jdaviz.core.registries import tray_registry from jdaviz.core.template_mixin import (PluginTemplateMixin, - DatasetSelectMixin, AddResultsMixin, + DatasetSelectMixin, + AutoTextField, skip_if_no_updates_since_last_active, with_spinner) from jdaviz.core.user_api import PluginUserApi +from lcviz.components import FluxOriginSelectMixin from lcviz.marks import LivePreviewTrend, LivePreviewFlattened from lcviz.utils import data_not_folded from lcviz.viewers import TimeScatterView, PhaseScatterView @@ -21,7 +23,7 @@ @tray_registry('flatten', label="Flatten") -class Flatten(PluginTemplateMixin, DatasetSelectMixin, AddResultsMixin): +class Flatten(PluginTemplateMixin, FluxOriginSelectMixin, DatasetSelectMixin): """ See the :ref:`Flatten Plugin Documentation ` for more details. @@ -32,16 +34,17 @@ class Flatten(PluginTemplateMixin, DatasetSelectMixin, AddResultsMixin): Whether to show the live-preview of the (unnormalized) flattened light curve * ``show_trend_preview`` : bool Whether to show the live-preview of the trend curve used to flatten the light curve - * ``default_to_overwrite`` * ``dataset`` (:class:`~jdaviz.core.template_mixin.DatasetSelect`): Dataset to flatten. - * ``add_results`` (:class:`~jdaviz.core.template_mixin.AddResults`) * ``window_length`` * ``polyorder`` * ``break_tolerance`` * ``niters`` * ``sigma`` * ``unnormalize`` + * ``flux_label`` (:class:`~jdaviz.core.template_mixin.AutoTextField`): + Label for the resulting flux column added to ``dataset`` and automatically selected as the new + flux origin. * :meth:`flatten` """ template_file = __file__, "flatten.vue" @@ -49,7 +52,6 @@ class Flatten(PluginTemplateMixin, DatasetSelectMixin, AddResultsMixin): show_live_preview = Bool(True).tag(sync=True) show_trend_preview = Bool(True).tag(sync=True) - default_to_overwrite = Bool(True).tag(sync=True) flatten_err = Unicode().tag(sync=True) window_length = IntHandleEmpty(101).tag(sync=True) @@ -59,12 +61,22 @@ class Flatten(PluginTemplateMixin, DatasetSelectMixin, AddResultsMixin): sigma = FloatHandleEmpty(3).tag(sync=True) unnormalize = Bool(False).tag(sync=True) + flux_label_label = Unicode().tag(sync=True) + flux_label_default = Unicode().tag(sync=True) + flux_label_auto = Bool(True).tag(sync=True) + flux_label_invalid_msg = Unicode('').tag(sync=True) + flux_label_overwrite = Bool(False).tag(sync=True) + last_live_time = Float(0).tag(sync=True) previews_temp_disable = Bool(False).tag(sync=True) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.flux_label = AutoTextField(self, 'flux_label_label', + 'flux_label_default', 'flux_label_auto', + 'flux_label_invalid_msg') + # do not support flattening data in phase-space self.dataset.add_filter(data_not_folded) @@ -72,12 +84,14 @@ def __init__(self, *args, **kwargs): # those marks self.hub.subscribe(self, ViewerAddedMessage, handler=lambda _: self._live_update()) + self._set_default_label() + @property def user_api(self): - expose = ['show_live_preview', 'show_trend_preview', 'default_to_overwrite', - 'dataset', 'add_results', + expose = ['show_live_preview', 'show_trend_preview', + 'dataset', 'window_length', 'polyorder', 'break_tolerance', - 'niters', 'sigma', 'unnormalize', 'flatten'] + 'niters', 'sigma', 'unnormalize', 'flux_label', 'flatten'] return PluginUserApi(self, expose=expose) @property @@ -108,18 +122,26 @@ def marks(self): return trend_marks, flattened_marks - @observe('default_to_overwrite', 'dataset_selected') - def _set_default_results_label(self, event={}): + @observe('dataset_selected', 'flux_origin_selected') + def _set_default_label(self, event={}): '''Generate a label and set the results field to that value''' if not hasattr(self, 'dataset'): # pragma: no cover return - self.add_results.label_whitelist_overwrite = [self.dataset_selected] - - if self.default_to_overwrite: - self.results_label_default = self.dataset_selected + # TODO: have an option to create new data entry and drop other columns? + # (or should that just go through future data cloning) + self.flux_label.default = f"{self.flux_origin_selected}_flattened" + + @observe('flux_label_label', 'dataset') + def _update_label_valid(self, event={}): + if self.flux_label.value in self.flux_origin.choices: + self.flux_label.invalid_msg = '' + self.flux_label_overwrite = True + elif self.flux_label.value in getattr(self.dataset.selected_obj, 'columns', []): + self.flux_label.invalid_msg = 'name already in use' else: - self.results_label_default = f"{self.dataset_selected} (flattened)" + self.flux_label.invalid_msg = '' + self.flux_label_overwrite = False @with_spinner() def flatten(self, add_data=True): @@ -129,8 +151,8 @@ def flatten(self, add_data=True): Parameters ---------- add_data : bool - Whether to add the resulting trace to the application, according to the options - defined in the plugin. + Whether to add the resulting light curve as a flux column and select that as the new + flux origin for that data entry. Returns ------- @@ -157,9 +179,13 @@ def flatten(self, add_data=True): output_lc.meta['NORMALIZED'] = False if add_data: - # add data to the collection/viewer + # add data as a new flux and corresponding err columns in the existing data entry + # and select as flux origin data = _data_with_reftime(self.app, output_lc) - self.add_results.add_results_from_plugin(data) + self.flux_origin.add_new_flux_column(flux=data['flux'], + flux_err=data['flux_err'], + label=self.flux_label.value, + selected=True) return output_lc, trend_lc @@ -186,13 +212,16 @@ def _toggle_marks(self, event={}): # then the marks themselves need to be updated self._live_update(event) - @observe('dataset_selected', + @observe('dataset_selected', 'flux_origin_selected', 'window_length', 'polyorder', 'break_tolerance', 'niters', 'sigma', 'previews_temp_disable') @skip_if_no_updates_since_last_active() def _live_update(self, event={}): if self.previews_temp_disable: return + if self.dataset_selected == '' or self.flux_origin_selected == '': + self._clear_marks() + return start = time() try: @@ -232,7 +261,3 @@ def vue_apply(self, *args, **kwargs): self.flatten_err = str(e) else: self.flatten_err = '' - if self.add_results.label_overwrite: - # then this will change the input data without triggering a - # change to dataset_selected - self._live_update() diff --git a/lcviz/plugins/flatten/flatten.vue b/lcviz/plugins/flatten/flatten.vue index b0e503a9..a71bed3f 100644 --- a/lcviz/plugins/flatten/flatten.vue +++ b/lcviz/plugins/flatten/flatten.vue @@ -33,14 +33,6 @@ persistent-hint > - - - @@ -138,6 +130,14 @@ Live preview is unnormalized, but flattening will normalize. + + Live-updating is temporarily disabled (last update took {{last_live_time}}s) @@ -156,21 +156,17 @@ - + + + + Flatten{{flux_label_overwrite ? ' (Overwrite)' : ''}} + + + diff --git a/lcviz/plugins/flux_origin/flux_origin.py b/lcviz/plugins/flux_origin/flux_origin.py index 964f44e6..e6386495 100644 --- a/lcviz/plugins/flux_origin/flux_origin.py +++ b/lcviz/plugins/flux_origin/flux_origin.py @@ -1,15 +1,15 @@ -from traitlets import List, Unicode, observe - from jdaviz.core.registries import tray_registry from jdaviz.core.template_mixin import (PluginTemplateMixin, - DatasetSelectMixin, SelectPluginComponent) + DatasetSelectMixin) from jdaviz.core.user_api import PluginUserApi +from lcviz.components import FluxOriginSelectMixin + __all__ = ['FluxOrigin'] @tray_registry('flux-origin', label="Flux Origin") -class FluxOrigin(PluginTemplateMixin, DatasetSelectMixin): +class FluxOrigin(PluginTemplateMixin, FluxOriginSelectMixin, DatasetSelectMixin): """ See the :ref:`Flux Origin Plugin Documentation ` for more details. @@ -21,75 +21,10 @@ class FluxOrigin(PluginTemplateMixin, DatasetSelectMixin): """ template_file = __file__, "flux_origin.vue" - origin_items = List().tag(sync=True) - origin_selected = Unicode().tag(sync=True) - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.origin = SelectPluginComponent(self, - items='origin_items', - selected='origin_selected') - @property def user_api(self): - expose = ['dataset', 'origin'] + expose = ['dataset', 'flux_origin'] return PluginUserApi(self, expose=expose) - - @observe('dataset_selected') - def _on_change_dataset(self, *args): - def _include_col(lk_obj, col): - if col == 'flux' and lk_obj.meta.get('FLUX_ORIGIN') != 'flux': - # this is the currently active column (and should be copied elsewhere unless) - return False - if col in ('time', 'cadn', 'cadenceno', 'quality'): - return False - if col.startswith('phase:'): - # internal jdaviz ephemeris phase columns - return False - if col.startswith('time'): - return False - if col.startswith('centroid'): - return False - if col.startswith('cbv'): - # cotrending basis vector - return False - if col.endswith('_err'): - return False - if col.endswith('quality'): - return False - # TODO: need to think about flatten losing units in the flux column (and that other - # columns still exist but are not flattened) - return lk_obj[col].unit == lk_obj['flux'].unit - - lk_obj = self.dataset.selected_obj - if lk_obj is None: - return - self.origin.choices = [col for col in lk_obj.columns if _include_col(lk_obj, col)] - flux_origin = lk_obj.meta.get('FLUX_ORIGIN') - if flux_origin in self.origin.choices: - self.origin.selected = flux_origin - else: - self.origin.selected = '' - - @observe('origin_selected') - def _on_change_origin(self, *args): - if self.origin_selected == '': - return - - dc_item = self.dataset.selected_dc_item - old_flux_origin = dc_item.meta.get('FLUX_ORIGIN') - if self.origin.selected == old_flux_origin: - # nothing to do here! - return - - # instead of using lightkurve's select_flux and having to reparse the data entry, we'll - # manipulate the arrays in the data-collection directly, and modify FLUX_ORIGIN so that - # exporting back to a lightkurve object works as expected - dc_item = self.dataset.selected_dc_item - - self.app._jdaviz_helper._set_data_component(dc_item, 'flux', dc_item[self.origin.selected]) - dc_item.meta['FLUX_ORIGIN'] = self.origin.selected - # need to clear the cache manually due to the change in metadata made to the data-collection - # entry - self.dataset._clear_cache('selected_obj') diff --git a/lcviz/plugins/flux_origin/flux_origin.vue b/lcviz/plugins/flux_origin/flux_origin.vue index 9ae32805..f8dd95a7 100644 --- a/lcviz/plugins/flux_origin/flux_origin.vue +++ b/lcviz/plugins/flux_origin/flux_origin.vue @@ -19,8 +19,8 @@