Skip to content

Commit

Permalink
TPF viewer (#81)
Browse files Browse the repository at this point in the history
* TPF cube viewer
* set correct attributes for axes in cube viewer
* enable mouseover display for cube viewer
* fix tracebacks when unloading all data from viewer
  • Loading branch information
kecnry authored Jan 29, 2024
1 parent 8c680ca commit c6dbb63
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 52 deletions.
32 changes: 25 additions & 7 deletions lcviz/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 an image/cube/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 = 'image'
if viewer_reference_name not in app._viewer_store.keys():
app._on_new_viewer(NewViewerMessage(CubeView, data=None, sender=app),
vid='image', name='image')
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):
Expand Down
22 changes: 14 additions & 8 deletions lcviz/plugins/coords_info/coords_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
from jdaviz.core.events import ViewerRenamedMessage
from jdaviz.core.registries import tool_registry

from lcviz.viewers import TimeScatterView, PhaseScatterView
from lcviz.viewers import TimeScatterView, PhaseScatterView, CubeView

__all__ = ['CoordsInfo']


@tool_registry('lcviz-coords-info')
class CoordsInfo(CoordsInfo):
_supported_viewer_classes = (TimeScatterView, PhaseScatterView)
_supported_viewer_classes = (TimeScatterView, PhaseScatterView, CubeView)
_viewer_classes_with_marker = (TimeScatterView, PhaseScatterView)

def __init__(self, *args, **kwargs):
Expand All @@ -25,12 +25,7 @@ def __init__(self, *args, **kwargs):
def _viewer_renamed(self, msg):
self._marks[msg.new_viewer_ref] = self._marks.pop(msg.old_viewer_ref)

def update_display(self, viewer, x, y):
self._dict = {}

if not len(viewer.state.layers):
return

def _lc_viewer_update(self, viewer, x, y):
is_phase = isinstance(viewer, PhaseScatterView)
# TODO: update with display_unit when supported in lcviz
x_unit = '' if is_phase else str(viewer.time_unit)
Expand Down Expand Up @@ -138,3 +133,14 @@ def _cursor_fallback():

self.marks[viewer._reference_id].update_xy([closest_x], [closest_y]) # noqa
self.marks[viewer._reference_id].visible = True

def update_display(self, viewer, x, y):
self._dict = {}

if not len(viewer.state.layers):
return

if isinstance(viewer, (TimeScatterView, PhaseScatterView)):
self._lc_viewer_update(viewer, x, y)
elif isinstance(viewer, CubeView):
self._image_viewer_update(viewer, x, y)
134 changes: 97 additions & 37 deletions lcviz/viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,55 @@

from jdaviz.core.events import NewViewerMessage
from jdaviz.core.registries import viewer_registry
from jdaviz.configs.cubeviz.plugins.viewers import CubevizImageView
from jdaviz.configs.default.plugins.viewers import JdavizViewerMixin
from jdaviz.configs.specviz.plugins.viewers import SpecvizProfileView

from lcviz.state import ScatterViewerState

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'],
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -263,3 +264,62 @@ 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(CloneViewerMixin, CubevizImageView):
# categories: zoom resets, zoom, pan, subset, select tools, shortcuts
tools_nested = [
['jdaviz:homezoom', 'jdaviz:prevzoom'],
['jdaviz:boxzoom'],
['jdaviz:panzoom'],
['bqplot:rectangle'],
['jdaviz:sidebar_plot', 'jdaviz:sidebar_export']
]
# TODO: can we vary this default_class based on Kepler vs TESS, etc?
# see https://github.com/spacetelescope/lcviz/pull/81#discussion_r1469721009
default_class = None

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

# TODO: refactor upstream so lcviz can inherit cubeviewer methods/setup with jdaviz-specific
# logic:
# * _default_spectrum_viewer_reference_name
# * _default_flux_viewer_reference_name
# * _default_uncert_viewer_reference_name

def _initial_x_axis(self, *args):
# Make sure that the x_att/y_att is correct on data load
# called via a callback set upstream in CubevizImageView when reference_data is changed
ref_data = self.state.reference_data
if ref_data is not None:
self.state.x_att = ref_data.id['Pixel Axis 2 [x]']
self.state.y_att = ref_data.id['Pixel Axis 1 [y]']

def _on_layers_update(self, layers=None):
super()._on_layers_update(layers=layers)
ref_data = self.state.reference_data
if ref_data is None:
return
flux_comp = ref_data.id['flux']
for layer in self.state.layers:
if hasattr(layer, 'attribute') and layer.attribute != flux_comp:
layer.attribute = flux_comp

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)]

0 comments on commit c6dbb63

Please sign in to comment.