-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
basic frequency analysis plugin (#30)
* basic frequency analysis plugin * requires dev version of jdaviz for plot plugin subcomponent * non-interactive * BLS/LS only * test coverage
- Loading branch information
Showing
7 changed files
with
328 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
from .coords_info.coords_info import * # noqa | ||
from .ephemeris.ephemeris import * # noqa | ||
from .flatten.flatten import * # noqa | ||
from .frequency_analysis.frequency_analysis import * # noqa | ||
from .markers.markers import * # noqa | ||
from .plot_options.plot_options import * # noqa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .frequency_analysis import * # noqa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
from functools import cached_property | ||
from traitlets import Bool, Float, List, Unicode, observe | ||
|
||
from lightkurve import periodogram | ||
|
||
from jdaviz.core.custom_traitlets import FloatHandleEmpty | ||
from jdaviz.core.registries import tray_registry | ||
from jdaviz.core.template_mixin import (PluginTemplateMixin, | ||
DatasetSelectMixin, SelectPluginComponent, PlotMixin) | ||
from jdaviz.core.user_api import PluginUserApi | ||
|
||
|
||
__all__ = ['FrequencyAnalysis'] | ||
|
||
|
||
@tray_registry('frequency analysis', label="Frequency Analysis") | ||
class FrequencyAnalysis(PluginTemplateMixin, DatasetSelectMixin, PlotMixin): | ||
""" | ||
See the :ref:`Frequency Analysis Plugin Documentation <frequency_ananlysis>` for more details. | ||
Only the following attributes and methods are available through the | ||
public plugin API. | ||
* ``dataset`` (:class:`~jdaviz.core.template_mixin.DatasetSelect`): | ||
Dataset to use for analysis. | ||
* ``method`` (:class:`~jdaviz.core.template_mixing.SelectPluginComponent`): | ||
Method/algorithm to determine the period. | ||
* ``xunit`` (:class:`~jdaviz.core.template_mixing.SelectPluginComponent`): | ||
Whether to plot power vs fequency or period. | ||
* ``auto_range`` | ||
* ``minimum`` | ||
* ``maximum`` | ||
* :meth:``periodogram`` | ||
""" | ||
template_file = __file__, "frequency_analysis.vue" | ||
|
||
method_items = List().tag(sync=True) | ||
method_selected = Unicode().tag(sync=True) | ||
|
||
xunit_items = List().tag(sync=True) | ||
xunit_selected = Unicode().tag(sync=True) | ||
|
||
auto_range = Bool(True).tag(sync=True) | ||
minimum = FloatHandleEmpty(0.1).tag(sync=True) # frequency | ||
minimum_step = Float(0.1).tag(sync=True) | ||
maximum = FloatHandleEmpty(1).tag(sync=True) # frequency | ||
maximum_step = Float(0.1).tag(sync=True) | ||
|
||
spinner = Bool().tag(sync=True) | ||
err = Unicode().tag(sync=True) | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
|
||
self._ignore_auto_update = False | ||
|
||
self.method = SelectPluginComponent(self, | ||
items='method_items', | ||
selected='method_selected', | ||
manual_options=['Lomb-Scargle', 'Box Least Squares']) | ||
|
||
self.xunit = SelectPluginComponent(self, | ||
items='xunit_items', | ||
selected='xunit_selected', | ||
manual_options=['frequency', 'period']) | ||
|
||
self.plot.add_line('line', color='gray', marker_size=12) | ||
self.plot.figure.axes[1].label = 'power' | ||
self._update_xunit() | ||
|
||
# TODO: remove if/once inherited from jdaviz | ||
# (https://github.com/spacetelescope/jdaviz/pull/2253) | ||
def _clear_cache(self, *attrs): | ||
""" | ||
provide convenience function to clearing the cache for cached_properties | ||
""" | ||
if not len(attrs): | ||
attrs = self._cached_properties | ||
for attr in attrs: | ||
if attr in self.__dict__: | ||
del self.__dict__[attr] | ||
|
||
@property | ||
def user_api(self): | ||
expose = ['dataset', 'method', 'xunit', 'auto_range', 'minimum', 'maximum', 'periodogram'] | ||
return PluginUserApi(self, expose=expose) | ||
|
||
@cached_property | ||
def periodogram(self): | ||
# TODO: support multiselect on self.dataset and combine light curves (or would that be a | ||
# dedicated plugin of its own)? | ||
self.spinner = True | ||
self.err = '' | ||
if self.auto_range: | ||
min_period, max_period = None, None | ||
elif self.xunit_selected == 'period': | ||
min_period, max_period = self.minimum, self.maximum | ||
else: | ||
min_period, max_period = self.maximum ** -1, self.minimum ** -1 | ||
if self.method == 'Box Least Squares': | ||
try: | ||
per = periodogram.BoxLeastSquaresPeriodogram.from_lightcurve(self.dataset.selected_obj, # noqa | ||
minimum_period=min_period, # noqa | ||
maximum_period=max_period) # noqa | ||
except Exception as err: | ||
self.spinner = False | ||
self.err = str(err) | ||
self.plot.clear_all_marks() | ||
return None | ||
elif self.method == 'Lomb-Scargle': | ||
try: | ||
per = periodogram.LombScarglePeriodogram.from_lightcurve(self.dataset.selected_obj, | ||
minimum_period=min_period, | ||
maximum_period=max_period) | ||
except Exception as err: | ||
self.spinner = False | ||
self.err = str(err) | ||
self.plot.clear_all_marks() | ||
return None | ||
else: | ||
self.spinner = False | ||
raise NotImplementedError(f"periodogram not implemented for {self.method}") | ||
|
||
self._update_periodogram_labels(per) | ||
self.spinner = False | ||
return per | ||
|
||
@observe('xunit_selected') | ||
def _update_xunit(self, *args): | ||
per = self.periodogram | ||
if per is not None: | ||
self.plot.marks['line'].x = getattr(per, self.xunit_selected) | ||
else: | ||
self.plot.clear_all_marks() | ||
|
||
self._update_periodogram_labels(per) | ||
|
||
# convert minimum/maximum for next computation | ||
self._ignore_auto_update = True | ||
old_min, old_max = self.minimum, self.maximum | ||
self.minimum = old_max ** -1 if old_max > 0 else 0 | ||
self.maximum = old_min ** -1 if old_min > 0 else 0 | ||
self._ignore_auto_update = False | ||
|
||
def _update_periodogram_labels(self, per=None): | ||
per = per if per is not None else self.periodogram | ||
if per is not None: | ||
self.plot.figure.axes[0].label = f"{self.xunit_selected} ({getattr(per, self.xunit_selected).unit})" # noqa | ||
self.plot.figure.axes[1].label = f"power ({per.power.unit})" if per.power.unit != "" else "power" # noqa | ||
else: | ||
self.plot.figure.axes[0].label = self.xunit_selected | ||
self.plot.figure.axes[1].label = "power" | ||
|
||
@observe('dataset_selected', 'method_selected', 'auto_range', 'minimum', 'maximum') | ||
def _update_periodogram(self, *args): | ||
if not (hasattr(self, 'method') and hasattr(self, 'dataset')): | ||
return | ||
if self._ignore_auto_update: | ||
return | ||
|
||
# TODO: avoid clearing cache if change was to min/max but auto_range is True? | ||
self._clear_cache('periodogram') | ||
|
||
per = self.periodogram | ||
if per is not None: | ||
line = self.plot.marks['line'] | ||
line.x, line.y = getattr(per, self.xunit_selected), per.power | ||
self._update_periodogram_labels(per) | ||
else: | ||
self.plot.clear_all_marks() |
110 changes: 110 additions & 0 deletions
110
lcviz/plugins/frequency_analysis/frequency_analysis.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<template> | ||
<j-tray-plugin | ||
description='Frequency/period analysis.' | ||
:link="'https://lcviz.readthedocs.io/en/'+vdocs+'/plugins.html#frequency_analysis'" | ||
:popout_button="popout_button"> | ||
|
||
<plugin-dataset-select | ||
:items="dataset_items" | ||
:selected.sync="dataset_selected" | ||
:show_if_single_entry="false" | ||
label="Data" | ||
hint="Select the light curve as input." | ||
/> | ||
|
||
<v-row> | ||
<v-select | ||
:menu-props="{ left: true }" | ||
attach | ||
:items="method_items.map(i => i.label)" | ||
v-model="method_selected" | ||
label="Algorithm/Method" | ||
:hint="'Method to determine power at each '+xunit_selected+'.'" | ||
persistent-hint | ||
></v-select> | ||
</v-row> | ||
|
||
<v-row> | ||
<v-select | ||
:menu-props="{ left: true }" | ||
attach | ||
:items="xunit_items.map(i => i.label)" | ||
v-model="xunit_selected" | ||
label="X Units" | ||
hint="Whether to plot in frequency or period-space." | ||
persistent-hint | ||
></v-select> | ||
</v-row> | ||
|
||
<v-row> | ||
<v-switch | ||
v-model="auto_range" | ||
:label="'Auto '+xunit_selected+' range'" | ||
:hint="'Whether to automatically or manually set the range on sampled '+xunit_selected+'s.'" | ||
persistent-hint | ||
></v-switch> | ||
</v-row> | ||
|
||
<v-row> | ||
<v-text-field | ||
v-if="!auto_range" | ||
ref="min" | ||
type="number" | ||
:label="'Minimum '+xunit_selected" | ||
v-model.number="minimum" | ||
:step="minimum_step" | ||
type="number" | ||
:hint="'Minimum '+xunit_selected+' to search.'" | ||
persistent-hint | ||
:rules="[() => minimum!=='' || 'This field is required']" | ||
></v-text-field> | ||
</v-row> | ||
|
||
<v-row> | ||
<v-text-field | ||
v-if="!auto_range" | ||
ref="max" | ||
type="number" | ||
:label="'Maximum '+xunit_selected" | ||
v-model.number="maximum" | ||
:step="maximum_step" | ||
type="number" | ||
:hint="'Maximum '+xunit_selected+' to search.'" | ||
persistent-hint | ||
:rules="[() => maximum!=='' || 'This field is required']" | ||
></v-text-field> | ||
</v-row> | ||
|
||
|
||
<div style="display: grid"> <!-- overlay container --> | ||
<div style="grid-area: 1/1"> | ||
|
||
<v-row v-if="err.length > 0"> | ||
<v-alert color="warning">{{ err }}</v-alert> | ||
</v-row> | ||
<v-row v-else> | ||
<jupyter-widget :widget="plot_widget"/> | ||
</v-row> | ||
|
||
</div> | ||
<div v-if="spinner" | ||
class="text-center" | ||
style="grid-area: 1/1; | ||
z-index:2; | ||
margin-left: -24px; | ||
margin-right: -24px; | ||
padding-top: 6px; | ||
background-color: rgb(0 0 0 / 20%)"> | ||
<v-progress-circular | ||
indeterminate | ||
color="spinner" | ||
size="50" | ||
width="6" | ||
></v-progress-circular> | ||
</div> | ||
</div> | ||
|
||
</div> | ||
|
||
</j-tray-plugin> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from numpy.testing import assert_allclose | ||
|
||
from lightkurve.periodogram import LombScarglePeriodogram, BoxLeastSquaresPeriodogram | ||
|
||
|
||
def test_plugin_frequency_analysis(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) | ||
|
||
freq = helper.plugins['Frequency Analysis'] | ||
freq.open_in_tray() | ||
|
||
assert freq.method == 'Lomb-Scargle' | ||
assert freq._obj.err == '' | ||
assert isinstance(freq.periodogram, LombScarglePeriodogram) | ||
|
||
freq.method = 'Box Least Squares' | ||
assert freq._obj.err == '' | ||
assert isinstance(freq.periodogram, BoxLeastSquaresPeriodogram) | ||
|
||
assert freq.xunit == 'frequency' | ||
assert freq._obj.plot.figure.axes[0].label == 'frequency (1 / d)' | ||
|
||
freq.xunit = 'period' | ||
assert freq._obj.plot.figure.axes[0].label == 'period (d)' | ||
line_x = freq._obj.plot.marks['line'].x | ||
assert_allclose((line_x.min(), line_x.max()), (0.3508333334885538, 31.309906458683404)) | ||
|
||
freq.auto_range = False | ||
assert_allclose((freq.minimum, freq.maximum), (1, 10)) | ||
while freq._obj.spinner: | ||
pass | ||
line_x = freq._obj.plot.marks['line'].x | ||
assert_allclose((line_x.min(), line_x.max()), (1, 10.00141)) | ||
|
||
freq.xunit = 'frequency' | ||
assert_allclose((freq.minimum, freq.maximum), (0.1, 1)) | ||
while freq._obj.spinner: | ||
pass | ||
line_x = freq._obj.plot.marks['line'].x | ||
assert_allclose((line_x.min(), line_x.max()), (0.0999859, 1)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters