Skip to content

Commit

Permalink
enable coords-info and markers plugin (#22)
Browse files Browse the repository at this point in the history
* enable coords-info and markers plugin
* basic test coverage
  • Loading branch information
kecnry authored Jun 1, 2023
1 parent 045bb4a commit 8077908
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 5 deletions.
6 changes: 3 additions & 3 deletions lcviz/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ class LCviz(ConfigHelper):
'tab_headers': True},
'dense_toolbar': False,
'context': {'notebook': {'max_height': '600px'}}},
'toolbar': ['g-data-tools', 'g-subset-tools'],
'tray': ['g-metadata-viewer', 'g-subset-plugin',
'lcviz-plot-options', 'ephemeris', 'g-export-plot'],
'toolbar': ['g-data-tools', 'g-subset-tools', 'lcviz-coords-info'],
'tray': ['g-metadata-viewer', 'lcviz-plot-options', 'g-subset-plugin',
'lcviz-markers', 'ephemeris', 'g-export-plot'],
'viewer_area': [{'container': 'col',
'children': [{'container': 'row',
'viewers': [{'name': 'flux-vs-time',
Expand Down
4 changes: 3 additions & 1 deletion lcviz/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
from .coords_info.coords_info import * # noqa
from .ephemeris.ephemeris import * # noqa
from .plot_options.plot_options import * # noqa
from .markers.markers import * # noqa
from .plot_options.plot_options import * # noqa
1 change: 1 addition & 0 deletions lcviz/plugins/coords_info/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .coords_info import * # noqa
124 changes: 124 additions & 0 deletions lcviz/plugins/coords_info/coords_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import numpy as np

from glue.core.subset_group import GroupedSubset
from jdaviz.configs.imviz.plugins.coords_info import CoordsInfo
from jdaviz.core.registries import tool_registry

from lcviz.viewers import TimeScatterView, PhaseScatterView


__all__ = ['CoordsInfo']


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

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

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

is_phase = isinstance(viewer, PhaseScatterView)
# TODO: update with display_unit when supported in lcviz
x_unit = '' if is_phase else str(viewer.time_unit)
y_unit = str(viewer.data()[0].flux.unit)

def _cursor_fallback():
self._dict['axes_x'] = x
self._dict['axes_x:unit'] = x_unit
self._dict['axes_y'] = y
self._dict['axes_y:unit'] = y_unit

self._dict['data_label'] = ''
self._dict['time'] = x if not is_phase else np.nan
self._dict['phase'] = x if is_phase else np.nan
self._dict['flux'] = y
self._dict['ephemeris'] = ''

self.row2_title = ''
self.row2_text = ''
self.row3_title = ''
self.row3_text = ''
self.icon = 'mdi-cursor-default'
self.marks[viewer._reference_id].visible = False

self.row1a_title = 'Cursor'
self.row1a_text = f'{x:10.5e}, {y:10.5e}'

# show the locked marker/coords only if either no tool or the default tool is active
if self.dataset.selected == 'none':
_cursor_fallback()
return

xrange = abs(viewer.state.x_max - viewer.state.x_min)
yrange = abs(viewer.state.y_max - viewer.state.y_min)

# Snap to the closest data point
closest_distsq = None
closest_x = None
closest_y = None
closest_icon = None
closest_lyr = None
for lyr in viewer.layers:
if isinstance(lyr.layer, GroupedSubset):
continue
if self.dataset.selected == 'auto' and not lyr.visible:
continue
if self.dataset.selected != 'auto' and self.dataset.selected != lyr.layer.label:
continue

scatter = lyr.scatter
lyr_x, lyr_y = scatter.x, scatter.y
# NOTE: unlike specviz which determines the closest point in x per-layer,
# this determines the closest point in x/y per-layer in pixel-space
# (making it easier to get the snapping point into shallow eclipses, etc)
distsqs = ((lyr_x - x)/xrange)**2 + ((lyr_y - y)/yrange)**2
cur_i = np.nanargmin(distsqs)
cur_x, cur_y = float(lyr_x[cur_i]), float(lyr_y[cur_i])
cur_distsq = distsqs[cur_i]

if (closest_distsq is None) or (cur_distsq < closest_distsq):
closest_distsq = cur_distsq
closest_i = cur_i
closest_x = cur_x
closest_y = cur_y
closest_icon = self.app.state.layer_icons.get(lyr.layer.label, '')
closest_lyr = lyr
self._dict['data_label'] = lyr.layer.label

if closest_lyr is None:
_cursor_fallback()
return

self.row2_title = 'Phase' if is_phase else 'Time'
if is_phase:
self.row2_text = f'{closest_x:0.05f}'
component_labels = [comp.label for comp in closest_lyr.layer.components]
time_comp = closest_lyr.layer.components[component_labels.index('World 0')]
times = closest_lyr.layer.get_data(time_comp)
self._dict['time'] = float(times[closest_i])
self._dict['phase'] = closest_x
self._dict['ephemeris'] = viewer.reference.split(':')[1]
else:
self.row2_text = f'{closest_x:10.5e} {x_unit}'
self._dict['time'] = closest_x
self._dict['phase'] = np.nan
self._dict['ephemeris'] = ''

self._dict['axes_x'] = closest_x
self._dict['axes_x:unit'] = x_unit
self._dict['index'] = float(closest_i)

self.row3_title = 'Flux'
self.row3_text = f'{closest_y:10.5e} {y_unit}'
self._dict['axes_y'] = closest_y
self._dict['axes_y:unit'] = y_unit
self._dict['flux'] = closest_y

self.icon = closest_icon

self.marks[viewer._reference_id].update_xy([closest_x], [closest_y]) # noqa
self.marks[viewer._reference_id].visible = True
1 change: 1 addition & 0 deletions lcviz/plugins/markers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .markers import * # noqa
22 changes: 22 additions & 0 deletions lcviz/plugins/markers/markers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import numpy as np

from jdaviz.configs.default.plugins import Markers
from jdaviz.core.registries import tray_registry

__all__ = ['Markers']


@tray_registry('lcviz-markers', label="Markers")
class Markers(Markers):
_default_table_values = {'time': np.nan,
'phase': np.nan,
'ephemeris': '',
'flux': np.nan}

def __init__(self, *args, **kwargs):
kwargs['headers'] = ['time', 'phase', 'ephemeris', 'flux', 'viewer']
super().__init__(*args, **kwargs)

@property
def coords_info(self):
return self.app.session.application._tools['lcviz-coords-info']
103 changes: 103 additions & 0 deletions lcviz/tests/test_plugin_markers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import numpy as np
from numpy.testing import assert_allclose

from jdaviz.core.marks import MarkersMark


def _get_markers_from_viewer(viewer):
return [m for m in viewer.figure.marks if isinstance(m, MarkersMark)][0]


def _assert_dict_allclose(dict1, dict2):
assert dict1.keys() == dict2.keys()
for k, v in dict1.items():
if isinstance(v, float):
assert_allclose(v, dict2.get(k))
elif isinstance(v, (tuple, list)):
assert_allclose(np.asarray(v), np.asarray(dict2.get(k)))
else:
assert v == dict2.get(k)


def test_plugin_markers(helper, light_curve_like_kepler_quarter):
helper.load_data(light_curve_like_kepler_quarter)
tv = helper.app.get_viewer(helper._default_time_viewer_reference_name)

mp = helper.plugins['Markers']
label_mouseover = mp._obj.coords_info
mp.open_in_tray()

# test event in flux-vs-time viewer
label_mouseover._viewer_mouse_event(tv,
{'event': 'mousemove',
'domain': {'x': 0, 'y': 0}})

assert label_mouseover.as_text() == ('Cursor 0.00000e+00, 0.00000e+00',
'Time 5.45833e+00 d',
'Flux 9.67587e-01')

_assert_dict_allclose(label_mouseover.as_dict(), {'data_label': 'Light curve',
'time': 5.4583335,
'phase': np.nan,
'ephemeris': '',
'axes_x': 5.4583335,
'axes_x:unit': 'd',
'index': 262.0,
'axes_y': 0.96758735,
'axes_y:unit': '',
'flux': 0.96758735})

mp._obj._on_viewer_key_event(tv, {'event': 'keydown',
'key': 'm'})
assert len(mp.export_table()) == 1
assert len(_get_markers_from_viewer(tv).x) == 1

ephem = helper.plugins['Ephemeris']
pv = ephem.create_phase_viewer()

# test event in flux-vs-phase viewer
label_mouseover._viewer_mouse_event(pv,
{'event': 'mousemove',
'domain': {'x': 0.5, 'y': 0}})

assert label_mouseover.as_text() == ('Cursor 5.00000e-01, 0.00000e+00',
'Phase 0.45833',
'Flux 9.67587e-01')

_assert_dict_allclose(label_mouseover.as_dict(), {'data_label': 'Light curve',
'time': 5.458333374001086,
'phase': 0.4583333730697632,
'ephemeris': 'default',
'axes_x': 0.4583333730697632,
'axes_x:unit': '',
'index': 262.0,
'axes_y': 0.9675873517990112,
'axes_y:unit': '',
'flux': 0.9675873517990112})

mp._obj._on_viewer_key_event(pv, {'event': 'keydown',
'key': 'm'})
assert len(mp.export_table()) == 2
assert len(_get_markers_from_viewer(tv).x) == 1
assert len(_get_markers_from_viewer(pv).x) == 1

# test event in flux-vs-phase viewer (with cursor only)
label_mouseover.dataset.selected = 'none'
label_mouseover._viewer_mouse_event(pv,
{'event': 'mousemove',
'domain': {'x': 0.6, 'y': 0}})

print(label_mouseover.as_text())
assert label_mouseover.as_text() == ('Cursor 6.00000e-01, 0.00000e+00',
'',
'')

_assert_dict_allclose(label_mouseover.as_dict(), {'axes_x': 0.6,
'axes_x:unit': '',
'axes_y': 0,
'axes_y:unit': '',
'data_label': '',
'time': np.nan,
'phase': 0.6,
'flux': 0,
'ephemeris': ''})
2 changes: 1 addition & 1 deletion lcviz/viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from lightkurve import LightCurve


__all__ = ['TimeScatterView']
__all__ = ['TimeScatterView', 'PhaseScatterView']


@viewer_registry("lcviz-time-viewer", label="flux-vs-time")
Expand Down

0 comments on commit 8077908

Please sign in to comment.