Skip to content

Commit

Permalink
First working subset fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
bmorris3 committed May 10, 2023
1 parent 7150e8e commit 009023b
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 72 deletions.
15 changes: 2 additions & 13 deletions lcviz/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import numpy as np
import pytest
from astropy import units as u
from astropy.utils.masked import Masked
from lightkurve import LightCurve

from lcviz import __version__, LCviz
Expand All @@ -22,7 +21,6 @@ def light_curve_like_kepler_quarter(seed=42):
"""
np.random.seed(seed)
exp_per_day = (30 * u.min).to_value(u.day)
masked_fraction = 0.01

# approx start and stop JD for Kepler Quarter 10:
time = np.arange(2455739, 2455833, exp_per_day)
Expand All @@ -32,19 +30,10 @@ def light_curve_like_kepler_quarter(seed=42):
) * u.dimensionless_unscaled
flux_err = scale * np.ones_like(flux)

# randomly apply mask to fraction of the data:
mask_indices = np.random.randint(
low=0,
high=len(flux),
size=int(masked_fraction * len(flux))
)
mask = np.zeros(len(flux), dtype=bool)
mask[mask_indices] = True
quality = np.zeros(len(time), dtype=np.int32)

flux = Masked(flux, mask)
flux_err = Masked(flux_err, mask)
return LightCurve(
time=time, flux=flux, flux_err=flux_err
time=time, flux=flux, flux_err=flux_err, quality=quality
)


Expand Down
35 changes: 31 additions & 4 deletions lcviz/helper.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
from jdaviz.core.helpers import ConfigHelper
import astropy.units as u

from lightkurve import LightCurve

from jdaviz.core.helpers import ConfigHelper

__all__ = ['LCviz']


def _get_range_subset_bounds(self, subset_state, *args, **kwargs):
# Instead of overriding the jdaviz version of this method on jdaviz.Application,
# we could put in jdaviz by (1) checking if helper has a
# _default_time_viewer_reference_name, (2) using the LCviz version if so, and (3)
# using the jdaviz version otherwise.
viewer = self.get_viewer(self._jdaviz_helper._default_time_viewer_reference_name)
reference_time = viewer.state.reference_data.meta['reference_time']
if viewer:
units = u.Unit(viewer.state.x_display_unit)
else:
raise ValueError("Unable to find time axis units")

region = reference_time + u.Quantity([subset_state.lo * units, subset_state.hi * units])

return [{"name": subset_state.__class__.__name__,
"glue_state": subset_state.__class__.__name__,
"region": region,
"subset_state": subset_state}]


class LCviz(ConfigHelper):
_default_configuration = {
'settings': {'configuration': 'lcviz',
Expand All @@ -25,6 +48,11 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._default_time_viewer_reference_name = 'time-viewer'

# override jdaviz behavior to support temporal subsets
self.app._get_range_subset_bounds = (
lambda *args, **kwargs: _get_range_subset_bounds(self.app, *args, **kwargs)
)

def load_data(self, data, data_label=None):
"""
Load data into LCviz.
Expand Down Expand Up @@ -58,7 +86,7 @@ def get_data(self, data_label=None, cls=LightCurve, subset_to_apply=None):
----------
data_label : str, optional
Provide a label to retrieve a specific data set from data_collection.
cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional
cls : light curve class, optional
The type that data will be returned as.
subset_to_apply : str, optional
Subset that is to be applied to data before it is returned.
Expand All @@ -67,6 +95,5 @@ def get_data(self, data_label=None, cls=LightCurve, subset_to_apply=None):
-------
data : cls
Data is returned as type cls with subsets applied.
"""
return super()._get_data(data_label=data_label, cls=cls, subset_to_apply=subset_to_apply)
return super()._get_data(data_label=data_label, cls=cls, subset_to_apply=subset_to_apply)
34 changes: 30 additions & 4 deletions lcviz/tests/test_parser.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# import pytest
import numpy as np
from glue.core.roi import XRangeROI
from astropy.time import Time
# from astropy.utils.data import download_file
from lightkurve import LightCurve
# from lightkurve.io import kepler
from gwcs.wcs import WCS
import astropy.units as u

from lcviz.utils import TimeCoordinates

# @pytest.mark.remote_data
# def test_kepler_via_mast_local_file(helper):
Expand All @@ -23,7 +24,7 @@
# flux_unit = u.Unit(data.get_component('flux').units)
# flux = flux_arr * flux_unit
#
# assert isinstance(data.coords, WCS)
# assert isinstance(data.coords, TimeCoordinates)
# assert isinstance(flux, u.Quantity)
# assert flux.unit.is_equivalent(u.electron / u.s)
#
Expand All @@ -43,7 +44,7 @@
# flux_unit = u.Unit(data.get_component('flux').units)
# flux = flux_arr * flux_unit
#
# assert isinstance(data.coords, WCS)
# assert isinstance(data.coords, TimeCoordinates)
# assert isinstance(flux, u.Quantity)
# assert flux.unit.is_equivalent(u.electron / u.s)

Expand All @@ -60,6 +61,31 @@ def test_synthetic_lc(helper):
flux_unit = u.Unit(data.get_component('flux').units)
flux = flux_arr * flux_unit

assert isinstance(data.coords, WCS)
assert isinstance(data.coords, TimeCoordinates)
assert isinstance(flux, u.Quantity)
assert flux.unit.is_equivalent(u.electron / u.s)


def test_apply_xrangerois(helper, light_curve_like_kepler_quarter):
lc = light_curve_like_kepler_quarter
helper.load_data(lc)
viewer = helper.app.get_viewer("time-viewer")
subset_plugin = helper.plugins['Subset Tools']

# the min/max of temporal regions can be defined in two ways:
time_ranges = [
[6, 8], # in same units as the x axis, OR
Time(['2011-07-19', '2011-07-23']) # directly with a Time object
]

for time_range in time_ranges:
subset_plugin._obj.subset_selected = "Create New"
viewer.apply_roi(XRangeROI(*time_range))

subsets = helper.app.get_subsets()

subset_1_bounds_jd = subsets['Subset 1'][0]['region'].jd
subset_2_bounds_jd = subsets['Subset 2'][0]['region'].jd

np.testing.assert_allclose(subset_1_bounds_jd, [2455745., 2455747.])
np.testing.assert_allclose(subset_2_bounds_jd, [2455761.50076602, 2455765.50076602])
90 changes: 47 additions & 43 deletions lcviz/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import datetime
from glue.config import data_translator
from glue.core import Data, Subset
from ipyvue import watch
Expand All @@ -9,13 +8,14 @@
from glue.core.coordinates import Coordinates
import numpy as np
from gwcs.wcs import WCS

from astropy import units as u
from astropy.time import Time
from astropy.utils.masked import Masked
from lightkurve import LightCurve

__all__ = ['TimeCoordinates']
from lightkurve import (
LightCurve, KeplerLightCurve, TessLightCurve
)

__all__ = ['TimeCoordinates', 'LightCurveHandler']


class TimeCoordinates(Coordinates):
Expand All @@ -35,11 +35,7 @@ def __init__(self, times, reference_time=None, unit=u.d):
self.reference_time = times[0]

delta_t = (times - self.reference_time).to(unit)
self._values = delta_t # times.datetime64

# self._values = (
# times.datetime64.astype(datetime.datetime).astype(int) / 1e9 * u.day
# )
self._values = delta_t

super().__init__(n_dim=1)

Expand All @@ -53,7 +49,7 @@ def time_axis(self):

@property
def world_axis_units(self):
return (self._values.unit.to_string('vounit'),)
return tuple(self._values.unit.to_string('vounit'))

def world_to_pixel_values(self, *world):
"""
Expand Down Expand Up @@ -91,11 +87,8 @@ def pixel_to_world_values(self, *pixel):
class LightCurveHandler:

def to_data(self, obj):
time = obj.time
# ttc = TimeTableCoordinate(time)
# gwcs = ttc.wcs
time_coord = TimeCoordinates(obj.time)
# delta_t = (obj.time - obj.time[0]).to(u.d)
delta_t = (obj.time - obj.time[0]).to(u.d)
data = Data(coords=time_coord)

data.meta.update(obj.meta)
Expand All @@ -104,17 +97,19 @@ def to_data(self, obj):
flux = obj.flux
flux_err = obj.flux_err

if hasattr(flux, 'unmasked'):
flux = flux.unmasked
flux_err = obj.flux_err.unmasked

data['flux'] = flux
data.get_component('flux').units = str(flux.unit)

data['uncertainty'] = flux_err
data.get_component('uncertainty').units = str(flux_err.unit)
data.meta.update({'uncertainty_type': 'std'})

data['dt'] = delta_t
data.get_component('dt').units = str(delta_t.unit)

if hasattr(obj, 'quality'):
data['quality'] = obj.quality

return data

def to_object(self, data_or_subset, attribute=None):
Expand All @@ -139,19 +134,17 @@ def to_object(self, data_or_subset, attribute=None):
# Copy over metadata
kwargs = {'meta': data.meta.copy()}

# convert gwcs coordinates to Time object.

# ``gwcs`` will store the transformation from pixel coordinates, which are
# integer indices in the input time object, to astropy.time.Time objects.
# Here we extract the time coordinates directly from the lookup table:
reference_time = data.meta['reference_time']
# extract a Time object out of the TimeCoordinates object:
kwargs['time'] = data.coords.time_axis

if isinstance(data.coords, WCS):
gwcs = data.coords
input_times = gwcs._pipeline[0].transform.lookup_table
kwargs['time'] = input_times + reference_time
if subset_state is None:
# pass through mask of all True's if no glue subset is chosen
glue_mask = np.ones(len(kwargs['time'])).astype(bool)
else:
kwargs['time'] = data.coords.time_axis
# get the subset mask from glue:
glue_mask = data.get_mask(subset_state=subset_state)
# apply the subset mask to the time array:
kwargs['time'] = kwargs['time'][glue_mask]

if isinstance(attribute, str):
attribute = data.id[attribute]
Expand All @@ -162,46 +155,45 @@ def to_object(self, data_or_subset, attribute=None):
attribute = data.main_components[0]
# If no specific attribute is defined, attempt to retrieve
# the flux and uncertainty, if available
elif any([x.label in ('flux', 'uncertainty') for x in data.components]):
elif any([x.label in ('flux', 'uncertainty', 'quality') for x in data.components]):
attribute = [data.find_component_id(x)
for x in ('flux', 'uncertainty')
for x in ('flux', 'uncertainty', 'quality')
if data.find_component_id(x) is not None]
else:
raise ValueError("Data object has more than one attribute, so "
"you will need to specify which one to use as "
"the flux for the spectrum using the "
"attribute= keyword argument.")
print('attributes', attribute)

def parse_attributes(attributes):
data_kwargs = {}
lc_init_keys = {'flux': 'flux', 'uncertainty': 'flux_err'}
lc_init_keys = {'flux': 'flux', 'uncertainty': 'flux_err', 'quality': 'quality'}
for attribute in attributes:
component = data.get_component(attribute)

# Get mask if there is one defined, or if this is a subset
if subset_state is None:
mask = None
else:
mask = data.get_mask(subset_state=subset_state)
# mask = ~mask
print('mask', mask, np.any(mask))
# Collapse values and mask to profile
values = data.get_data(attribute)
attribute_label = attribute.label

if attribute in ('flux', 'uncertainty'):
if attribute_label in ('flux', 'uncertainty'):
values = u.Quantity(values, unit=component.units)
elif attribute_label in ('quality', ):
values = np.array(values, dtype=np.int32)

init_kwarg = lc_init_keys[attribute_label]
data_kwargs.update({init_kwarg: values})

# apply the glue subset mask to the Data components:
data_kwargs.update({init_kwarg: values[glue_mask]})

return data_kwargs

data_kwargs = parse_attributes(
[attribute] if not hasattr(attribute, '__len__') else attribute)

return LightCurve(**data_kwargs, **kwargs)


<<<<<<< HEAD
def enable_hot_reloading(watch_jdaviz=True):
"""
Use ``watchdog`` to perform hot reloading.
Expand All @@ -220,3 +212,15 @@ def enable_hot_reloading(watch_jdaviz=True):
print((
'Watchdog module, needed for hot reloading, not found.'
' Please install with `pip install watchdog`'))
=======
@data_translator(KeplerLightCurve)
class KeplerLightCurveHandler(LightCurveHandler):
# Works the same as LightCurve
pass


@data_translator(TessLightCurve)
class TessLightCurveHandler(LightCurveHandler):
# Works the same as LightCurve
pass
>>>>>>> ff59ea9 (First working subset fixes)
Loading

0 comments on commit 009023b

Please sign in to comment.