diff --git a/CHANGES.rst b/CHANGES.rst index de050b7..03ccb87 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,8 @@ * Prevent duplicate sub-intervals (quarter/sector/campaign) in data labels. [#120] +* Photometric extraction plugin. [#115] + * Add feature to query the NASA Exoplanet Archive for exoplanet ephemerides. [#127] 0.4.3 (09.05.2024) @@ -41,7 +43,6 @@ * Default data labels no longer include flux-origin, but do include quarter/campaign/sector. [#111] * Basic stitch plugin to combine light curves into a single entry. [#107] - * Metadata plugin: show undefined entries as empty string instead of object repr. [#108] * Raise error when parser can't identify file_obj [#106] diff --git a/docs/plugins.rst b/docs/plugins.rst index ea766aa..383179b 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -218,6 +218,39 @@ The time selector plugin allows defining the time indicated in all light curve v Jdaviz documentation on the Slice plugin. +.. _photometric-extraction: + +Photometric Extraction +====================== + +Note that this plugin is only available if TPF data is loaded into the app. + +.. admonition:: User API Example + :class: dropdown + + See the :class:`~lcviz.plugins.stitch.stitch.Stitch` user API documentation for more details. + + .. code-block:: python + + from lcviz import LCviz + from lightkurve import search_targetpixelfile + tpf = search_targetpixelfile("KIC 001429092", + mission="Kepler", + cadence="long", + quarter=10).download() + lcviz = LCviz() + lcviz.load_data(tpf) + lcviz.show() + + ext = lcviz.plugins['Photometric Extraction'] + ext.open_in_tray() + + +.. seealso:: + + This plugin uses the following ``lightkurve`` implementations: + + * :meth:`lightkurve.KeplerTargetPixelFile.extract_aperture_photometry` .. _stitch: diff --git a/docs/reference/api_plugins.rst b/docs/reference/api_plugins.rst index 680393a..ade666a 100644 --- a/docs/reference/api_plugins.rst +++ b/docs/reference/api_plugins.rst @@ -27,6 +27,9 @@ Plugins API .. automodapi:: lcviz.plugins.metadata_viewer.metadata_viewer :no-inheritance-diagram: +.. automodapi:: lcviz.plugins.photometric_extraction.photometric_extraction + :no-inheritance-diagram: + .. automodapi:: lcviz.plugins.plot_options.plot_options :no-inheritance-diagram: diff --git a/lcviz/helper.py b/lcviz/helper.py index 9e41ca9..fb604b4 100644 --- a/lcviz/helper.py +++ b/lcviz/helper.py @@ -105,7 +105,7 @@ class LCviz(ConfigHelper): 'toolbar': ['g-data-tools', 'g-subset-tools', 'g-viewer-creator', 'lcviz-coords-info'], 'tray': ['lcviz-metadata-viewer', 'flux-column', 'lcviz-plot-options', 'lcviz-subset-plugin', - 'lcviz-markers', 'time-selector', + 'lcviz-markers', 'time-selector', 'photometric-extraction', 'stitch', 'flatten', 'frequency-analysis', 'ephemeris', 'binning', 'lcviz-export'], 'viewer_area': [{'container': 'col', diff --git a/lcviz/plugins/__init__.py b/lcviz/plugins/__init__.py index bdde8f1..331b9f2 100644 --- a/lcviz/plugins/__init__.py +++ b/lcviz/plugins/__init__.py @@ -10,6 +10,7 @@ from .markers.markers import * # noqa from .time_selector.time_selector import * # noqa from .metadata_viewer.metadata_viewer import * # noqa +from .photometric_extraction.photometric_extraction import * # noqa from .plot_options.plot_options import * # noqa from .stitch.stitch import * # noqa from .subset_plugin.subset_plugin import * # noqa diff --git a/lcviz/plugins/photometric_extraction/__init__.py b/lcviz/plugins/photometric_extraction/__init__.py new file mode 100644 index 0000000..5c40516 --- /dev/null +++ b/lcviz/plugins/photometric_extraction/__init__.py @@ -0,0 +1 @@ +from .photometric_extraction import * # noqa diff --git a/lcviz/plugins/photometric_extraction/photometric_extraction.py b/lcviz/plugins/photometric_extraction/photometric_extraction.py new file mode 100644 index 0000000..6f704be --- /dev/null +++ b/lcviz/plugins/photometric_extraction/photometric_extraction.py @@ -0,0 +1,98 @@ +from astropy import units as u +from traitlets import Bool, Unicode, observe +from lightkurve import LightCurve + +from jdaviz.core.registries import tray_registry +from jdaviz.configs.cubeviz.plugins import SpectralExtraction +from jdaviz.core.user_api import PluginUserApi + + +__all__ = ['PhotometricExtraction'] + + +@tray_registry('photometric-extraction', label="Photometric Extraction") +class PhotometricExtraction(SpectralExtraction): + """ + See the :ref:`Photometric Extraction Plugin Documentation ` + for more details. + + Only the following attributes and methods are available through the + :ref:`public plugin API `: + + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.show` + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.open_in_tray` + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.close_in_tray` + * ``dataset`` (:class:`~jdaviz.core.template_mixin.DatasetSelect`): + Dataset to extract. + * ``add_results`` (:class:`~jdaviz.core.template_mixin.AddResults`) + * :meth:`extract` + """ + resulting_product_name = Unicode("light curve").tag(sync=True) + do_auto_extraction = False + wavelength_dependent_available = Bool(False).tag(sync=True) + bg_export_available = Bool(False).tag(sync=True) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.docs_link = f"https://lcviz.readthedocs.io/en/{self.vdocs}/plugins.html#photometric-extraction" # noqa + self.docs_description = "Extract light curve from target pixel file cube." # noqa + + def is_tpf(data): + return len(data.shape) == 3 + self.dataset.filters = [is_tpf] + + # only allow for Sum + self.function._manual_options = ['Sum'] + self.function.items = [{"label": "Sum"}] + + self._set_relevant() # move upstream? + + @property + def user_api(self): + expose = ['dataset', 'aperture', + 'background', + 'add_results', 'extract', + 'aperture_method'] + + return PluginUserApi(self, expose=expose) + + @observe('dataset_items') + def _set_relevant(self, *args): + # NOTE: upstream will set disabled_msg to something similar, but mentioning + if len(self.dataset_items) < 1: + self.irrelevant_msg = 'Requires at least one TPF cube to be loaded' + else: + self.irrelevant_msg = '' + + def _on_global_display_unit_changed(self, msg=None): + if msg is None: + self.flux_units = str(self.app._get_display_unit('flux')) + self.time_units = str(self.app._get_display_unit('time')) + elif msg.axis == 'flux': + self.flux_units = str(msg.unit) + elif msg.axis == 'time': + self.time_units = str(msg.unit) + else: + # ignore + return + # update results_units based on flux_units, sb_units, and currently selected function + self._update_results_units() + + @property + def slice_display_unit_name(self): + return 'time' + + @property + def spatial_axes(self): + return (1, 2) + + def _return_extracted(self, cube, wcs, collapsed_nddata): + lc = LightCurve(time=cube.get_object(LightCurve).time, flux=collapsed_nddata.data) + return lc + + def _preview_x_from_extracted(self, extracted): + return extracted.time.value - self.dataset.selected_obj.meta.get('reference_time', + 0.0 * u.d).value + + def _preview_y_from_extracted(self, extracted): + return extracted.flux.value diff --git a/lcviz/plugins/photometric_extraction/photometric_extraction.vue b/lcviz/plugins/photometric_extraction/photometric_extraction.vue new file mode 100644 index 0000000..8a7ee50 --- /dev/null +++ b/lcviz/plugins/photometric_extraction/photometric_extraction.vue @@ -0,0 +1,55 @@ + diff --git a/lcviz/plugins/plot_options/plot_options.py b/lcviz/plugins/plot_options/plot_options.py index fee6892..ced7acd 100644 --- a/lcviz/plugins/plot_options/plot_options.py +++ b/lcviz/plugins/plot_options/plot_options.py @@ -3,6 +3,9 @@ from traitlets import observe from jdaviz.configs.default.plugins import PlotOptions from jdaviz.core.registries import tray_registry +from jdaviz.utils import get_subset_type + +from lcviz.viewers import CubeView __all__ = ['PlotOptions'] @@ -33,6 +36,17 @@ class PlotOptions(PlotOptions): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + def not_spatial_subset_in_scatter_viewer(lyr): + # note: have to check the classname instead of isinstance to avoid circular import + if np.any([isinstance(viewer, CubeView) + for viewer in self.layer.viewer_objs]): + return True + # at this point, NO selected viewers are TPF Cube viewers, + # so we want to exclude spatial subsets + return get_subset_type(lyr) != 'spatial' + + self.layer.add_filter(not_spatial_subset_in_scatter_viewer) + @observe('vdocs') def _update_docs_link(self, *args): self.docs_link = f"https://lcviz.readthedocs.io/en/{self.vdocs}/plugins.html#plot-options" diff --git a/lcviz/plugins/stitch/stitch.py b/lcviz/plugins/stitch/stitch.py index 47bafbf..5664f8a 100644 --- a/lcviz/plugins/stitch/stitch.py +++ b/lcviz/plugins/stitch/stitch.py @@ -45,6 +45,7 @@ def __init__(self, *args, **kwargs): self.dataset.add_filter(data_not_folded, is_not_tpf) self.results_label_default = 'stitched' + self._set_relevant() @property def user_api(self): @@ -52,7 +53,7 @@ def user_api(self): return PluginUserApi(self, expose=expose) @observe('dataset_items') - def _set_relevent(self, *args): + def _set_relevant(self, *args): if len(self.dataset_items) < 2: self.irrelevant_msg = 'Requires at least two datasets loaded into viewers' else: diff --git a/lcviz/utils.py b/lcviz/utils.py index 27245da..33df223 100644 --- a/lcviz/utils.py +++ b/lcviz/utils.py @@ -357,7 +357,7 @@ def to_data(self, obj, reference_time=None, unit=u.d): data[cid] = component_data if hasattr(component_data, 'unit'): try: - data.get_component(cid).units = str(component_data.unit) + data.get_component(cid).units = str(component_data.unit/u.pix**2) except KeyError: # pragma: no cover continue diff --git a/lcviz/viewers.py b/lcviz/viewers.py index 1d0e74d..080bdc8 100644 --- a/lcviz/viewers.py +++ b/lcviz/viewers.py @@ -151,6 +151,13 @@ def _apply_layer_defaults(self, layer_state): layer_state.size = 5 layer_state.points_mode = 'markers' + def _layer_included_in_legend(self, layer, subset_type): + print("***", layer.layer.label, subset_type) + if subset_type == 'spatial': + # do not show spatial subsets in time or phase viewers + return False + return super()._layer_included_in_legend(layer, subset_type) + def set_plot_axes(self): # set which components should be plotted dc = self.jdaviz_app.data_collection @@ -361,6 +368,13 @@ def _on_layers_update(self, layers=None): if hasattr(layer, 'attribute') and layer.attribute != flux_comp: layer.attribute = flux_comp + def _layer_included_in_legend(self, layer, subset_type): + print("***", layer.layer.label, subset_type) + if subset_type == 'spectral': # NOTE: spectral here means xrange (i.e. not spatial) + # ONLY show spatial subsets in image/cube viewer + return False + return super()._layer_included_in_legend(layer, subset_type) + def data(self, cls=None): # TODO: generalize upstream in jdaviz. # This method is generalized from