Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flux Column plugin #77

Merged
merged 10 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

* Clone viewer tool. [#74]

* Flux origin plugin to choose which column is treated as the flux column for each dataset. [#77]

* Flatten plugin no longer creates new data entries, but instead appends a new column to the input
light curve and selects as the flux origin. [#77]

0.1.0 (12-14-2023)
------------------

Expand Down
35 changes: 35 additions & 0 deletions docs/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,41 @@ This plugin allows viewing of any metadata associated with the selected data.
:ref:`Jdaviz Metadata Viewer <jdaviz:imviz_metadata-viewer>`
Jdaviz documentation on the Metadata Viewer plugin.

.. _flux-origin:

Flux Origin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know what you mean by origin but the term used throughout lightkurve is column. Is there a reason to introduce another term?

Copy link
Member Author

@kecnry kecnry Jan 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm I actually originally had column, but then changed it because its exposed as "FLUX_COLUMN" in the metadata and the string representation of the LightCurve object 🤔

I guess this and this both use "column", but this tutorial shows how many times "origin" also appears.

Ultimately it's not too difficult to change (now or before this makes a release - significantly more difficult after a release)... so we can either decide which one we think is better or exposed more publicly or ask if they'd ever consider moving to be more self-consistent within lightkurve and go with that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Personally, I'd reserve "origin" for the origin of the axis, which might be, e.g., zero or one, depending on units and normalization.

===========

This plugin allows choosing which column in the underlying data should be used as the flux origin
throughout the app (when plotting and in any data analysis plugins).


.. admonition:: User API Example
:class: dropdown

See the :class:`~lcviz.plugins.plot_options.plot_options.PlotOptions` user API documentation for more details.

.. code-block:: python

from lcviz import LCviz
lc = search_lightcurve("HAT-P-11", mission="Kepler",
cadence="long", quarter=10).download().flatten()
lcviz = LCviz()
lcviz.load_data(lc)
lcviz.show()

flux_origin = lcviz.plugins['Flux Origin']
print(flux_origin.flux_origin.choices)
flux_origin.flux_origin = 'sap_flux'


.. seealso::

This plugin reproduces the behavior also available in ``lightkurve`` as:

* :meth:`lightkurve.LightCurve.select_flux`


.. _plot-options:

Plot Options
Expand Down
1 change: 1 addition & 0 deletions lcviz/components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .components import * # noqa
117 changes: 117 additions & 0 deletions lcviz/components/components.py
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 44 in lcviz/components/components.py

View check run for this annotation

Codecov / codecov/patch

lcviz/components/components.py#L44

Added line #L44 was not covered by tests
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

Check warning on line 54 in lcviz/components/components.py

View check run for this annotation

Codecov / codecov/patch

lcviz/components/components.py#L54

Added line #L54 was not covered by tests
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 = ''

Check warning on line 60 in lcviz/components/components.py

View check run for this annotation

Codecov / codecov/patch

lcviz/components/components.py#L60

Added line #L60 was not covered by tests

def _on_flux_origin_changed_msg(self, msg):
if msg.dataset != self.dataset.selected:
return

Check warning on line 64 in lcviz/components/components.py

View check run for this annotation

Codecov / codecov/patch

lcviz/components/components.py#L64

Added line #L64 was not covered by tests

# 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

Check warning on line 73 in lcviz/components/components.py

View check run for this annotation

Codecov / codecov/patch

lcviz/components/components.py#L73

Added line #L73 was not covered by tests

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')
5 changes: 4 additions & 1 deletion lcviz/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ def light_curve_like_kepler_quarter(seed=42):

quality = np.zeros(len(time), dtype=np.int32)

return LightCurve(
lc = LightCurve(
time=time, flux=flux, flux_err=flux_err, quality=quality
)
lc['flux_alt'] = flux + 1
lc['flux_alt_err'] = flux_err
return lc


try:
Expand Down
12 changes: 11 additions & 1 deletion lcviz/events.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from glue.core.message import Message

__all__ = ['EphemerisComponentChangedMessage',
'EphemerisChangedMessage']
'EphemerisChangedMessage',
'FluxOriginChangedMessage']


class EphemerisComponentChangedMessage(Message):
Expand All @@ -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
24 changes: 15 additions & 9 deletions lcviz/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ class LCviz(ConfigHelper):
'dense_toolbar': False,
'context': {'notebook': {'max_height': '600px'}}},
'toolbar': ['g-data-tools', 'g-subset-tools', 'lcviz-coords-info'],
'tray': ['lcviz-metadata-viewer', 'lcviz-plot-options', 'lcviz-subset-plugin',
'tray': ['lcviz-metadata-viewer', 'flux-origin',
'lcviz-plot-options', 'lcviz-subset-plugin',
'lcviz-markers', 'flatten', 'frequency-analysis', 'ephemeris',
'binning', 'lcviz-export-plot'],
'viewer_area': [{'container': 'col',
Expand Down Expand Up @@ -155,12 +156,17 @@ 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})
if component_label in self._component_ids:
component_id = self._component_ids[component_label]
else:
data.add_component(values, self._component_ids[component_label])

data.add_component(values, self._component_ids[component_label])
existing_components = [component.label for component in data.components]
if component_label in existing_components:
component_id = data.components[existing_components.index(component_label)]
else:
component_id = ComponentID(component_label)
self._component_ids[component_label] = component_id

if component_id in data.components:
data.update_components({component_id: values})
else:
data.add_component(values, component_id)
11 changes: 9 additions & 2 deletions lcviz/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,16 @@ def light_curve_parser(app, file_obj, data_label=None, show_in_viewer=True, **kw
new_data_label = f'{data_label}'
else:
new_data_label = light_curve.meta.get('OBJECT', 'Light curve')

# handle flux_origin default
flux_origin = light_curve.meta.get('FLUX_ORIGIN', None) # i.e. PDCSAP or SAP
if flux_origin is not None:
new_data_label += f'[{flux_origin}]'
if flux_origin == 'flux' or (flux_origin is None and 'flux' in light_curve.columns):
# then make a copy of this column so it won't be lost when changing with the flux_column
# plugin
light_curve['flux:orig'] = light_curve['flux']
if 'flux_err' in light_curve.columns:
light_curve['flux:orig_err'] = light_curve['flux_err']
light_curve.meta['FLUX_ORIGIN'] = 'flux:orig'

data = _data_with_reftime(app, light_curve)
app.add_data(data, new_data_label)
Expand Down
1 change: 1 addition & 0 deletions lcviz/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .ephemeris.ephemeris import * # noqa
from .export_plot.export_plot import * # noqa
from .flatten.flatten import * # noqa
from .flux_origin.flux_origin import * # noqa
from .frequency_analysis.frequency_analysis import * # noqa
from .markers.markers import * # noqa
from .metadata_viewer.metadata_viewer import * # noqa
Expand Down
7 changes: 5 additions & 2 deletions lcviz/plugins/binning/binning.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
with_spinner)
from jdaviz.core.user_api import PluginUserApi

from lcviz.components import FluxOriginSelectMixin
from lcviz.events import EphemerisChangedMessage
from lcviz.helper import _default_time_viewer_reference_name
from lcviz.marks import LivePreviewBinning
Expand All @@ -24,7 +25,8 @@


@tray_registry('binning', label="Binning")
class Binning(PluginTemplateMixin, DatasetSelectMixin, EphemerisSelectMixin, AddResultsMixin):
class Binning(PluginTemplateMixin, FluxOriginSelectMixin, DatasetSelectMixin,
EphemerisSelectMixin, AddResultsMixin):
"""
See the :ref:`Binning Plugin Documentation <binning>` for more details.

Expand Down Expand Up @@ -150,7 +152,8 @@ def _toggle_marks(self, event={}):
# then the marks themselves need to be updated
self._live_update(event)

@observe('dataset_selected', 'ephemeris_selected',
@observe('flux_origin_selected', 'dataset_selected',
'ephemeris_selected',
'n_bins', 'previews_temp_disable')
@skip_if_no_updates_since_last_active()
def _live_update(self, event={}):
Expand Down
Loading
Loading