Skip to content

Commit

Permalink
Prototype fix for CID uniqueness (#1)
Browse files Browse the repository at this point in the history
* fix for CID uniqueness
* fix missing phase_comp_lbl reference
* fix for binning test

---------

Co-authored-by: Kyle Conroy <[email protected]>
  • Loading branch information
bmorris3 and kecnry committed Sep 14, 2023
1 parent 530d9ab commit 4b28364
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 49 deletions.
17 changes: 17 additions & 0 deletions lcviz/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from lightkurve import LightCurve

from glue.core.component_id import ComponentID
from glue.core.link_helpers import LinkSame
from jdaviz.core.helpers import ConfigHelper
from lcviz.events import ViewerRenamedMessage
Expand Down Expand Up @@ -123,6 +124,8 @@ class LCviz(ConfigHelper):
'plot': 'lcviz-time-viewer',
'reference': 'flux-vs-time'}]}]}]}

_component_ids = {}

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._default_time_viewer_reference_name = _default_time_viewer_reference_name
Expand Down Expand Up @@ -191,3 +194,17 @@ def get_data(self, data_label=None, cls=LightCurve, subset=None):
Data is returned as type cls with subsets applied.
"""
return super()._get_data(data_label=data_label, mask_subset=subset, cls=cls)

def _phase_comp_lbl(self, component):
return f'phase:{component}'

def _set_data_component(self, data, component_label, values):
if component_label not in self._component_ids:
self._component_ids[component_label] = ComponentID(component_label)

if self._component_ids[component_label] in data.components:
data.update_components({self._component_ids[component_label]: values})
else:
data.add_component(values, self._component_ids[component_label])

data.add_component(values, self._component_ids[component_label])
24 changes: 23 additions & 1 deletion lcviz/plugins/binning/binning.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from astropy.time import Time
from traitlets import Bool, observe
from glue.config import data_translator

from jdaviz.core.custom_traitlets import IntHandleEmpty
from jdaviz.core.events import (ViewerAddedMessage, ViewerRemovedMessage)
Expand Down Expand Up @@ -184,10 +185,31 @@ def bin(self, add_data=True):
scale=input_lc.time_original.scale)
lc.add_column(time_col, name="time_original", index=len(lc._required_columns))

lc.meta.update({'_LCVIZ_BINNED': True})

# convert to glue Data manually, so we may edit the `phase` component:
handler, _ = data_translator.get_handler_for(lc)
data = handler.to_data(lc)
phase_comp_lbl = self.app._jdaviz_helper._phase_comp_lbl(self.ephemeris_selected)

# here we use the `value` attribute of `lc.time`, which has units of *phase*:
self.app._jdaviz_helper._set_data_component(data, phase_comp_lbl, lc.time.value)

else:
data = None

if add_data:
# add data to the collection/viewer
# NOTE: lc will have _LCVIZ_EPHEMERIS set if phase-folded
self.add_results.add_results_from_plugin(lc)
self._set_results_viewer()
self.add_results.add_results_from_plugin(data or lc)

if self.ephemeris_selected != 'No ephemeris':
# prevent phase axis from becoming a time axis:
viewer_id = self.ephemeris_plugin._obj.phase_viewer_id
pv = self.app.get_viewer(viewer_id)
phase_comp_lbl = self.app._jdaviz_helper._phase_comp_lbl(self.ephemeris_selected)
pv.state.x_att = self.app._jdaviz_helper._component_ids[phase_comp_lbl]

return lc

Expand Down
91 changes: 44 additions & 47 deletions lcviz/plugins/ephemeris/ephemeris.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ class Ephemeris(PluginTemplateMixin, DatasetSelectMixin):
template_file = __file__, "ephemeris.vue"

# EPHEMERIS
phase_cids = {}
component_mode = Unicode().tag(sync=True)
component_edit_value = Unicode().tag(sync=True)
component_items = List().tag(sync=True)
Expand Down Expand Up @@ -128,7 +127,11 @@ def user_api(self):
return PluginUserApi(self, expose=expose)

def _phase_comp_lbl(self, component):
return f'phase:{component}'
if self.app._jdaviz_helper is None:
# duplicate logic from helper in case this is ever called before the helper
# is fully intialized
return f'phase:{component}'
return self.app._jdaviz_helper._phase_comp_lbl(component)

@property
def phase_comp_lbl(self):
Expand Down Expand Up @@ -185,19 +188,19 @@ def _callable(times):

return _callable

def times_to_phases(self, times, component=None):
if component is None:
component = self.component.selected
def times_to_phases(self, times, ephem_component=None):
if ephem_component is None:
ephem_component = self.component.selected

return self._times_to_phases_callable(component)(times)
return self._times_to_phases_callable(ephem_component)(times)

def phases_to_times(self, phases, component=None):
if component is None:
component = self.component.selected
def phases_to_times(self, phases, ephem_component=None):
if ephem_component is None:
ephem_component = self.component.selected

# this is not used internally, so we don't need the traitlet
# and callable optimizations
ephem = self.ephemerides.get(component, {})
ephem = self.ephemerides.get(ephem_component, {})
t0 = ephem.get('t0', _default_t0)
period = ephem.get('period', _default_period)
dpdt = ephem.get('dpdt', _default_dpdt)
Expand All @@ -207,18 +210,22 @@ def phases_to_times(self, phases, component=None):
else:
return t0 + (phases)*period

def _update_all_phase_arrays(self, *args, component=None):
if component is None:
for component in self.component.choices:
self._update_all_phase_arrays(component=component)
def _update_all_phase_arrays(self, *args, ephem_component=None):
# `ephem_component` is the name given to the
# *ephemeris* component in the orbiting system, e.g. "default",
# rather than the glue Data Component ID:

if ephem_component is None:
for ephem_component in self.component.choices:
self._update_all_phase_arrays(ephem_component=ephem_component)
return

dc = self.app.data_collection

phase_comp_lbl = self._phase_comp_lbl(component)
phase_comp_lbl = self._phase_comp_lbl(ephem_component)

# we'll create the callable function for this component once so it can be re-used
_times_to_phases = self._times_to_phases_callable(component)
_times_to_phases = self._times_to_phases_callable(ephem_component)

new_links = []
for i, data in enumerate(dc):
Expand All @@ -228,21 +235,11 @@ def _update_all_phase_arrays(self, *args, component=None):

times = data.get_component('World 0').data
phases = _times_to_phases(times)
if component not in self.phase_cids:
self.phase_cids[component] = ComponentID(phase_comp_lbl)

if self.phase_cids[component] in data.components:
data.update_components({self.phase_cids[component]: phases})
else:
data.add_component(phases, self.phase_cids[component])

# this loop catches phase components generated automatically by
# when add_results is triggered in other plugins:
for comp in data.components:
if phase_comp_lbl == comp.label:
data.remove_component(phase_comp_lbl)
self.app._jdaviz_helper._set_data_component(
data, phase_comp_lbl, phases
)

data.add_component(phases, self.phase_cids[component])
if i != 0:
ref_data = dc[0]
new_link = LinkSame(
Expand All @@ -260,7 +257,7 @@ def _update_all_phase_arrays(self, *args, component=None):

# update any plugin markers
# TODO: eventually might need to loop over multiple matching viewers
phase_viewer_id = self._phase_viewer_id(component)
phase_viewer_id = self._phase_viewer_id(ephem_component)
if phase_viewer_id in self.app.get_viewer_ids():
phase_viewer = self.app.get_viewer(phase_viewer_id)
for mark in phase_viewer.custom_marks:
Expand Down Expand Up @@ -298,7 +295,7 @@ def create_phase_viewer(self):
pv = self.app.get_viewer(phase_viewer_id)
if create_phase_viewer:
pv.state.x_min, pv.state.x_max = (self.wrap_at-1, self.wrap_at)
pv.state.x_att = self.phase_cids[self.component_selected]
pv.state.x_att = self.app._jdaviz_helper._component_ids[self.phase_comp_lbl]
return pv

def vue_create_phase_viewer(self, *args):
Expand Down Expand Up @@ -387,7 +384,7 @@ def _change_component(self, *args):
# otherwise, this is a new component and there is no need.
self._ephem_traitlet_changed()

def update_ephemeris(self, component=None, t0=None, period=None, dpdt=None, wrap_at=None):
def update_ephemeris(self, ephem_component=None, t0=None, period=None, dpdt=None, wrap_at=None):
"""
Update the ephemeris for a given component.
Expand All @@ -408,22 +405,22 @@ def update_ephemeris(self, component=None, t0=None, period=None, dpdt=None, wrap
-------
dictionary of ephemeris corresponding to ``component``
"""
if component is None:
component = self.component_selected
if ephem_component is None:
ephem_component = self.component_selected

if component not in self.component.choices: # pragma: no cover
if ephem_component not in self.component.choices: # pragma: no cover
raise ValueError(f"component must be one of {self.component.choices}")

existing_ephem = self._ephemerides.get(component, {})
existing_ephem = self._ephemerides.get(ephem_component, {})
for name, value in {'t0': t0, 'period': period, 'dpdt': dpdt, 'wrap_at': wrap_at}.items():
if value is not None:
existing_ephem[name] = value
if component == self.component_selected:
if ephem_component == self.component_selected:
setattr(self, name, value)

self._ephemerides[component] = existing_ephem
self._update_all_phase_arrays(component=component)
self.hub.broadcast(EphemerisChangedMessage(ephemeris_label=component,
self._ephemerides[ephem_component] = existing_ephem
self._update_all_phase_arrays(ephem_component=ephem_component)
self.hub.broadcast(EphemerisChangedMessage(ephemeris_label=ephem_component,
sender=self))
return existing_ephem

Expand All @@ -448,7 +445,7 @@ def round_to_1(x):
self.update_ephemeris(**{event.get('name'): event.get('new')})
# will call _update_all_phase_arrays
else:
self._update_all_phase_arrays(component=self.component_selected)
self._update_all_phase_arrays(ephem_component=self.component_selected)

# update zoom-limits if wrap_at was changed
if event.get('name') == 'wrap_at':
Expand Down Expand Up @@ -505,18 +502,18 @@ def _update_periodogram(self, *args):
def vue_adopt_period_at_max_power(self, *args):
self.period = self.period_at_max_power

def get_data(self, dataset, component=None):
def get_data(self, dataset, ephem_component=None):
# TODO: support subset_to_apply and then include a wrapper at the helper-level?
# (would need to catch when cls does not result in a lightkurve object or write
# behaviors for other cases as well)
if component is None:
component = self.component.selected
if ephem_component is None:
ephem_component = self.component.selected

lc = self.app._jdaviz_helper.get_data(dataset)
data = next((x for x in self.app.data_collection if x.label == dataset))

comps = {str(comp): comp for comp in data.components}
xcomp = f'phase:{component}'
xcomp = f'phase:{ephem_component}'
phases = data.get_component(comps.get(xcomp)).data

# the following code is adopted directly from lightkurve
Expand All @@ -532,8 +529,8 @@ def get_data(self, dataset, component=None):
phlc.add_column(lc.time.copy(), name="time_original", index=len(lc._required_columns))

# Add extra column and meta data specific to FoldedLightCurve
ephemeris = self.ephemerides.get(component)
phlc.meta["_LCVIZ_EPHEMERIS"] = {'ephemeris': component, **ephemeris}
ephemeris = self.ephemerides.get(ephem_component)
phlc.meta["_LCVIZ_EPHEMERIS"] = {'ephemeris': ephem_component, **ephemeris}
phlc.meta["PERIOD"] = ephemeris.get('period')
phlc.meta["EPOCH_TIME"] = ephemeris.get('t0')
phlc.sort("time")
Expand Down
2 changes: 1 addition & 1 deletion lcviz/viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,4 @@ def times_to_phases(self, times):
if ephem is None:
raise ValueError("must have ephemeris plugin loaded to convert")

return ephem.times_to_phases(times, component=self.ephemeris_component)
return ephem.times_to_phases(times, ephem_component=self.ephemeris_component)

0 comments on commit 4b28364

Please sign in to comment.