From 2e23a90ddc7eb78fa2b3be455eb9105b8c62b923 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Fri, 19 Jan 2024 14:11:12 -0500 Subject: [PATCH] WIP: TPF cube viewer --- lcviz/parsers.py | 32 +++++++++++--- lcviz/viewers.py | 111 +++++++++++++++++++++++++++++++---------------- 2 files changed, 99 insertions(+), 44 deletions(-) diff --git a/lcviz/parsers.py b/lcviz/parsers.py index c915da22..c0af8d97 100644 --- a/lcviz/parsers.py +++ b/lcviz/parsers.py @@ -36,6 +36,11 @@ def light_curve_parser(app, file_obj, data_label=None, show_in_viewer=True, **kw # handle flux_origin default flux_origin = light_curve.meta.get('FLUX_ORIGIN', None) # i.e. PDCSAP or SAP + if isinstance(light_curve, lightkurve.targetpixelfile.TargetPixelFile): + new_data_label += '[TPF]' + elif flux_origin is not None: + new_data_label += f'[{flux_origin}]' + if flux_origin == 'flux' or (flux_origin is None and 'flux' in getattr(light_curve, 'columns', [])): # noqa # then make a copy of this column so it won't be lost when changing with the flux_column # plugin @@ -47,14 +52,27 @@ def light_curve_parser(app, file_obj, data_label=None, show_in_viewer=True, **kw data = _data_with_reftime(app, light_curve) app.add_data(data, new_data_label) - if show_in_viewer: - app.add_data_to_viewer(time_viewer_reference_name, new_data_label) + if isinstance(light_curve, lightkurve.targetpixelfile.TargetPixelFile): + # ensure a TPF viewer exists + # TODO: move this to an event listener on add_data so that we can also remove when empty? + from jdaviz.core.events import NewViewerMessage + from lcviz.viewers import CubeView + viewer_reference_name = 'tpf' + if viewer_reference_name not in app._viewer_store.keys(): + app._on_new_viewer(NewViewerMessage(CubeView, data=None, sender=app), + vid='tpf', name='tpf') + if show_in_viewer: + app.add_data_to_viewer(viewer_reference_name, new_data_label) + + else: + if show_in_viewer: + app.add_data_to_viewer(time_viewer_reference_name, new_data_label) - # add to any known phase viewers - ephem_plugin = app._jdaviz_helper.plugins.get('Ephemeris', None) - if ephem_plugin is not None: - for viewer_id in ephem_plugin._obj.phase_viewer_ids: - app.add_data_to_viewer(viewer_id, new_data_label) + # add to any known phase viewers + ephem_plugin = app._jdaviz_helper.plugins.get('Ephemeris', None) + if ephem_plugin is not None: + for viewer_id in ephem_plugin._obj.phase_viewer_ids: + app.add_data_to_viewer(viewer_id, new_data_label) def _data_with_reftime(app, light_curve): diff --git a/lcviz/viewers.py b/lcviz/viewers.py index 76d5f6d5..98f462c9 100644 --- a/lcviz/viewers.py +++ b/lcviz/viewers.py @@ -6,6 +6,7 @@ from glue.core.subset_group import GroupedSubset from glue_jupyter.bqplot.scatter import BqplotScatterView +from glue_jupyter.bqplot.image import BqplotImageView from astropy import units as u from astropy.time import Time @@ -19,12 +20,47 @@ from lightkurve import LightCurve +__all__ = ['TimeScatterView', 'PhaseScatterView', 'CubeView'] -__all__ = ['TimeScatterView', 'PhaseScatterView'] + +class CloneViewerMixin: + def _get_clone_viewer_reference(self): + base_name = self.reference.split("[")[0] + name = base_name + ind = 0 + while name in self.jdaviz_helper.viewers.keys(): + ind += 1 + name = f"{base_name}[{ind}]" + return name + + def clone_viewer(self): + name = self._get_clone_viewer_reference() + + self.jdaviz_app._on_new_viewer(NewViewerMessage(self.__class__, + data=None, + sender=self.jdaviz_app), + vid=name, name=name) + + this_viewer_item = self.jdaviz_app._get_viewer_item(self.reference) + this_state = self.state.as_dict() + for data in self.jdaviz_app.data_collection: + data_id = self.jdaviz_app._data_id_from_label(data.label) + visible = this_viewer_item['selected_data_items'].get(data_id, 'hidden') + self.jdaviz_app.set_data_visibility(name, data.label, visible == 'visible') + # TODO: don't revert color when adding same data to a new viewer + # (same happens when creating a phase-viewer from ephemeris plugin) + + new_viewer = self.jdaviz_helper.viewers[name]._obj + for k, v in this_state.items(): + if k in ('layers',): + continue + setattr(new_viewer.state, k, v) + + return new_viewer.user_api @viewer_registry("lcviz-time-viewer", label="flux-vs-time") -class TimeScatterView(JdavizViewerMixin, BqplotScatterView): +class TimeScatterView(JdavizViewerMixin, CloneViewerMixin, BqplotScatterView): # categories: zoom resets, zoom, pan, subset, select tools, shortcuts tools_nested = [ ['jdaviz:homezoom', 'jdaviz:prevzoom'], @@ -43,7 +79,6 @@ def __init__(self, *args, **kwargs): self.display_mask = False self.time_unit = kwargs.get('time_unit', u.d) - self._subscribe_to_layers_update() self.initialize_toolbar() self._subscribe_to_layers_update() # hack to inherit a small subset of methods from SpecvizProfileView @@ -210,40 +245,6 @@ def apply_roi(self, roi, use_current=False): super().apply_roi(roi, use_current=use_current) - def _get_clone_viewer_reference(self): - base_name = self.reference.split("[")[0] - name = base_name - ind = 0 - while name in self.jdaviz_helper.viewers.keys(): - ind += 1 - name = f"{base_name}[{ind}]" - return name - - def clone_viewer(self): - name = self._get_clone_viewer_reference() - - self.jdaviz_app._on_new_viewer(NewViewerMessage(self.__class__, - data=None, - sender=self.jdaviz_app), - vid=name, name=name) - - this_viewer_item = self.jdaviz_app._get_viewer_item(self.reference) - this_state = self.state.as_dict() - for data in self.jdaviz_app.data_collection: - data_id = self.jdaviz_app._data_id_from_label(data.label) - visible = this_viewer_item['selected_data_items'].get(data_id, 'hidden') - self.jdaviz_app.set_data_visibility(name, data.label, visible == 'visible') - # TODO: don't revert color when adding same data to a new viewer - # (same happens when creating a phase-viewer from ephemeris plugin) - - new_viewer = self.jdaviz_helper.viewers[name]._obj - for k, v in this_state.items(): - if k in ('layers',): - continue - setattr(new_viewer.state, k, v) - - return new_viewer.user_api - @viewer_registry("lcviz-phase-viewer", label="phase-vs-time") class PhaseScatterView(TimeScatterView): @@ -263,3 +264,39 @@ def times_to_phases(self, times): raise ValueError("must have ephemeris plugin loaded to convert") return ephem.times_to_phases(times, ephem_component=self.ephemeris_component) + + +@viewer_registry("lcviz-cube-viewer", label="cube") +class CubeView(JdavizViewerMixin, CloneViewerMixin, BqplotImageView): + # categories: zoom resets, zoom, pan, subset, select tools, shortcuts + tools_nested = [ + ['jdaviz:homezoom', 'jdaviz:prevzoom'], + ['jdaviz:boxzoom'], + ['jdaviz:panzoom'], + ['bqplot:rectangle'], + ['lcviz:viewer_clone', 'jdaviz:sidebar_plot', 'jdaviz:sidebar_export'] + ] +# default_class = TargetPixelFile # TODO: does this need to use the factory? +# _state_cls = ScatterViewerState + +# _native_mark_classnames = ('Image', 'ImageGL', 'Scatter', 'ScatterGL') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.display_mask = False + self.time_unit = kwargs.get('time_unit', u.d) + self.initialize_toolbar() + self._subscribe_to_layers_update() + + # Hide axes by default + self.state.show_axes = False + + def data(self, cls=None): + # TODO: generalize upstream in jdaviz. + # This method is generalized from + # jdaviz/configs/cubeviz/plugins/viewers.py + return [layer_state.layer + for layer_state in self.state.layers + if hasattr(layer_state, 'layer') and + isinstance(layer_state.layer, BaseData)]