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

Add method for creating lightcurve for object #464

Merged
merged 4 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies = [
"ipyfilechooser",
"ipywidgets",
"jupyter-app-launcher >=0.3.0",
"lightkurve",
"matplotlib",
"papermill",
"pandas",
Expand Down Expand Up @@ -193,5 +194,7 @@ filterwarnings = [
# papermill is using deprecated jupyter paths
'ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning',
# papermill is also using a deprecated method of getting current time
'ignore:.*is deprecated and scheduled for removal.*:DeprecationWarning'
'ignore:.*is deprecated and scheduled for removal.*:DeprecationWarning',
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know how happy I am with ignoring this warning, given the warning includes "scheduled for removal". :/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally agree, but also can't control 😬

# lightkurve is using a deprecated numpy interface
'ignore:.*numpy.core.einsumfunc is deprecated and has been renamed.*:DeprecationWarning'
]
22 changes: 22 additions & 0 deletions stellarphot/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import pytest
from astropy.coordinates import SkyCoord
from astropy.table import Table
from astropy.utils.data import get_pkg_data_filename
from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS

from stellarphot import PhotometryData

# astropy-specific-stuff


Expand Down Expand Up @@ -57,3 +60,22 @@ def tess_tic_expected_values():
tic_id=236158940,
expected_coords=SkyCoord(ra=313.41953739, dec=34.35164717, unit="degree"),
)


@pytest.fixture
def simple_photometry_data():
# Grab the test photometry file and simplify it a bit.
data_file = get_pkg_data_filename("tests/data/test_photometry_data.ecsv")
pd_input = PhotometryData.read(data_file)

# Keep stars 1, 6, 9, 12, first time slice only
# These stars have no NaNs in them and we only need one image to generate
# more test data from that.
first_slice = pd_input["file"] == "wasp-10-b-S001-R001-C099-r.fit"
ids = [1, 6, 9, 12]
good_star = pd_input["star_id"] == ids[0]

for an_id in ids[1:]:
good_star = good_star | (pd_input["star_id"] == an_id)

return pd_input[good_star & first_slice]
77 changes: 77 additions & 0 deletions stellarphot/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import re

import lightkurve as lk
import numpy as np
import pandas as pd
from astropy import units as u
Expand Down Expand Up @@ -576,6 +577,82 @@
# Return BJD at midpoint of exposure at each location
return Time(time_barycenter + self["exposure"] / 2, scale="tdb")

def lightcurve_for(
self, star_id=None, coordinates=None, name=None, flux_column="mag_inst"
):
"""
Return the light curve for a single star as a `lightkurve.LightCurve` object.
One of the parameters `star_id`, `coordinates` or `name` must be specified.

Parameters
----------
star_id : str, optional
The star_id of the star in the photometry data. If not provided, coordinates
or name must be provided.

coordinates : `astropy.coordinates.SkyCoord`, optional
The coordinates of the star in the photometry data. If not provided, star_id
or name must be provided.

name : str, optional
The name of the star in Simbad. If not provided, star_id or coordinates must
be provided.

flux_column : str, optional
The name of the column to use as the flux. Default is 'mag_inst'. This need
not actually be a flux.

Returns
-------
`lightkurve.LightCurve`
The light curve for the star. This includes all of the columns in the
`stellarphot.`PhotometryData` object and columns ``time``, ``flux``, and
``flux_err``.
"""
num_args = sum([star_id is not None, coordinates is not None, name is not None])

if num_args == 0:
raise ValueError("Either star_id, coordinates or name must be provided.")

if num_args > 1:
raise ValueError(
"Only one of star_id, coordinates, or name can be provided."
)

if name is not None:
coordinates = SkyCoord.from_name(name)

if coordinates is not None:
# Find the star_id for the closest coordinate match
my_coordinates = SkyCoord(self["ra"], self["dec"])
idx, d2d, _ = coordinates.match_to_catalog_sky(my_coordinates)
star_id = self["star_id"][idx]
if d2d > 1 * u.arcsec:
raise ValueError(
"No star in the photometry data is close enough to the "
"provided coordinates."
)

star_data = self[self["star_id"] == star_id]

# Create the columns that light curve needs, adding metadata about where each
# column came from.
star_data["time"] = star_data["bjd"]
star_data.meta["time"] = "BJD at midpoint of exposure, column bjd"

star_data["flux"] = star_data[flux_column]
star_data.meta["flux"] = "Instrumental magnitude, column mag_inst"

# Why value? Because the instrumental magnitude error is fubar,
# see #463
flux_error_col = (
"mag_error" if flux_column == "mag_inst" else flux_column + "_error"
)

Check warning on line 650 in stellarphot/core.py

View check run for this annotation

Codecov / codecov/patch

stellarphot/core.py#L650

Added line #L650 was not covered by tests
star_data["flux_err"] = star_data[flux_error_col].value
star_data.meta["flux_err"] = "Error in instrumental magnitude, column mag_error"

return lk.LightCurve(star_data)


class CatalogData(BaseEnhancedTable):
"""
Expand Down
81 changes: 80 additions & 1 deletion stellarphot/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from astropy.coordinates import SkyCoord
from astropy.io import ascii, fits
from astropy.nddata import CCDData
from astropy.table import Table
from astropy.table import Table, vstack
from astropy.time import Time
from astropy.utils.data import get_pkg_data_filename
from astropy.wcs import WCS
Expand Down Expand Up @@ -1163,3 +1163,82 @@ def test_sourcelist_slicing():
# Checking attributes survive slicing
assert slicing_test.has_ra_dec
assert slicing_test.has_x_y


@pytest.mark.parametrize("target_by", ["star_id", "coordinates"])
@pytest.mark.parametrize("target_star_id", [1, 6])
def test_to_lightcurve(simple_photometry_data, target_by, target_star_id):
# Make a few more rows of data, changing only date_obs, then update the BJD
delta_t = 3 * u.minute
new_data = [simple_photometry_data.copy()]
t_init = simple_photometry_data["date-obs"][0]

for i in range(1, 5):
data = simple_photometry_data.copy()
data["date-obs"] = t_init + i * delta_t
new_data.append(data)

# Make a new table with the new data
new_table = vstack(new_data)

select_star_id = simple_photometry_data["star_id"] == target_star_id
star_id_coords = SkyCoord(
simple_photometry_data["ra"][select_star_id],
simple_photometry_data["dec"][select_star_id],
)

selectors = dict(
star_id=target_star_id,
coordinates=star_id_coords,
)

assert isinstance(new_table, PhotometryData)

# Grab just the keywords we need for this test
selector = {target_by: selectors[target_by]}
lc = new_table.lightcurve_for(**selector)

# We made 5 times, so there should be 5 rows in the lightcurve
assert len(lc) == 5
assert (lc["star_id"] == target_star_id).all()


@pytest.mark.remote_data
def test_to_lightcurve_from_name(simple_photometry_data):
# Make a few more rows of data, changing only date_obs, then update the BJD
delta_t = 3 * u.minute
new_data = [simple_photometry_data.copy()]
t_init = simple_photometry_data["date-obs"][0]

for i in range(1, 5):
data = simple_photometry_data.copy()
data["date-obs"] = t_init + i * delta_t
new_data.append(data)

# Make a new table with the new data
new_table = vstack(new_data)

assert isinstance(new_table, PhotometryData)

lc = new_table.lightcurve_for(name="wasp 10")

# We made 5 times, so there should be 5 rows in the lightcurve
assert len(lc) == 5
# wasp 10 is star_id 1
assert (lc["star_id"] == 1).all()


def test_to_lightcurve_argument_logic(simple_photometry_data):
# providing no argument should raise an error
with pytest.raises(
ValueError, match="Either star_id, coordinates or name must be provided"
):
simple_photometry_data.lightcurve_for()

# providing two arguments should raise an error
with pytest.raises(
ValueError, match="Only one of star_id, coordinates, or name can be provided"
):
simple_photometry_data.lightcurve_for(
star_id=1, coordinates=SkyCoord(0, 0, unit="deg")
)