tglc 0.6.5__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tglc/__init__.py +2 -2
- tglc/barycentric_correction.py +107 -0
- tglc/effective_psf.py +15 -3
- tglc/ephemeris_data/20180720_tess_ephem.csv +10385 -0
- tglc/ephemeris_data/20190101_tess_ephem.csv +9635 -0
- tglc/ephemeris_data/20200101_tess_ephem.csv +9659 -0
- tglc/ephemeris_data/20210101_tess_ephem.csv +9275 -0
- tglc/ephemeris_data/20211215_tess_ephem.csv +12512 -0
- tglc/ephemeris_data/20221201_tess_ephem.csv +10308 -0
- tglc/ephemeris_data/20231201_tess_ephem.csv +10339 -0
- tglc/ephemeris_data/20241201_tess_ephem.csv +10314 -0
- tglc/ephemeris_data/README.md +48 -0
- tglc/ephemeris_data/plot_ephemerides.py +54 -0
- tglc/ffi.py +3 -3
- tglc/ffi_cut.py +125 -22
- tglc/quick_lc.py +243 -124
- tglc/target_lightcurve.py +39 -11
- {tglc-0.6.5.dist-info → tglc-0.7.0.dist-info}/METADATA +42 -13
- tglc-0.7.0.dist-info/RECORD +28 -0
- {tglc-0.6.5.dist-info → tglc-0.7.0.dist-info}/WHEEL +1 -1
- tglc-0.6.5.dist-info/RECORD +0 -17
- {tglc-0.6.5.dist-info → tglc-0.7.0.dist-info/licenses}/LICENSE +0 -0
- {tglc-0.6.5.dist-info → tglc-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# TESS Ephemeris Files
|
|
2
|
+
|
|
3
|
+
This directory contains files with TESS ephemeris data used for barycentric time correction by QLP. The files are CSV files with X,Y,Z coordinates in AU from the solar system barycenter and the reference frame is ICRF.
|
|
4
|
+
|
|
5
|
+
Each file has a set of sectors it is meant to be used for:
|
|
6
|
+
|
|
7
|
+
- Sectors 1-5: [20180720_tess_ephem.csv](20180720_tess_ephem.csv)
|
|
8
|
+
- Sectors 6-19: [20190101_tess_ephem.csv](20190101_tess_ephem.csv)
|
|
9
|
+
- Sectors 20-32: [20200101_tess_ephem.csv](20200101_tess_ephem.csv)
|
|
10
|
+
- Sectors 33-45: [20210101_tess_ephem.csv](20210101_tess_ephem.csv)
|
|
11
|
+
- Sectors 46-59: [20211215_tess_ephem.csv](20211215_tess_ephem.csv)
|
|
12
|
+
- Sectors 60-73: [20221201_tess_ephem.csv](20221201_tess_ephem.csv)
|
|
13
|
+
- Sectors 74-87: [20231201_tess_ephem.csv](20231201_tess_ephem.csv)
|
|
14
|
+
- Sectors 88-101: [20241201_tess_ephem.csv](20241201_tess_ephem.csv)
|
|
15
|
+
|
|
16
|
+
## Downloading the files
|
|
17
|
+
|
|
18
|
+
At the start of every year, we need to add a TESS predicted ephemeris file so we can correct spacecraft time to BJD TDB.These ephemerides are downloaded from the horizons website: <https://ssd.jpl.nasa.gov/horizons.cgi>
|
|
19
|
+
|
|
20
|
+
**Ephemeris Type:** Vector Table
|
|
21
|
+
|
|
22
|
+
**Target Body:** TESS
|
|
23
|
+
|
|
24
|
+
**Coordinate Origin:** Solar System Barycenter (SSB)
|
|
25
|
+
|
|
26
|
+
**Time Span:** The required time space for the year. We want the step size to be at least one hour
|
|
27
|
+
|
|
28
|
+
**Table Settings:**
|
|
29
|
+
|
|
30
|
+
- Quantities code = 4 (position, LT, range, and range-rate)
|
|
31
|
+
- Reference frame = ICRF
|
|
32
|
+
- Reference plane = x-y axes of reference frame
|
|
33
|
+
- Vector correction = geometric states
|
|
34
|
+
- Output units = AU and days
|
|
35
|
+
- CSV format = YES
|
|
36
|
+
- Object summary = up to you. The summary will need to be commented out anyways, so I prefer to not include it.
|
|
37
|
+
|
|
38
|
+
Make sure you comment out the header and footer information in the csv file. Also, when last generated (Jan 15 2023) I needed to add the following line before the values:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
JDTDB,Calendar Date (TDB),X,Y,Z,LT,RG,RR,
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
(see the format of the other .csv files in the directory for guidance)
|
|
45
|
+
|
|
46
|
+
After adding the file to this directory, plot all the ephemerides by running [plot_ephemerides.py](plot_ephemerides.py). This ensures the files can all be read successfully. Examine the plot it produces and check that the transition between years is smooth, and that the new file continues the existing sine wave patterns.
|
|
47
|
+
|
|
48
|
+
If you have a function that specifies where the ephemeris file is for each sector (like in the [notebook in this repository](/TESS%20Time%20Correction.ipynb)), make sure to update that as well.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plot X,Y,Z coordinates from JPL horizons TESS ephemeris files to ensure the
|
|
3
|
+
transitions are smooth.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def plot_ephemerides(
|
|
14
|
+
ephemeris_directory: Optional[Path] = None, output_file: Optional[Path] = None
|
|
15
|
+
) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Plot ephemerides from all files in given directory.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
ephemeris_directory : Path, optional
|
|
22
|
+
Directory containing files with ephemeris data to plot
|
|
23
|
+
output_file : Path, optional
|
|
24
|
+
File to save plot to. If omitted, calls plt.show()
|
|
25
|
+
"""
|
|
26
|
+
if ephemeris_directory is None:
|
|
27
|
+
ephemeris_directory = Path(__file__).parent
|
|
28
|
+
ephemeris_files = sorted(list(ephemeris_directory.iterdir()))
|
|
29
|
+
ephemerides = [
|
|
30
|
+
pd.read_csv(ephemeris_file, comment="#")
|
|
31
|
+
for ephemeris_file in ephemeris_files
|
|
32
|
+
if ephemeris_file.suffix == ".csv"
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
fig, (ax_x, ax_y, ax_z) = plt.subplots(3, 1, figsize=(9, 6), layout="constrained")
|
|
36
|
+
|
|
37
|
+
for ephemeris, path in zip(ephemerides, ephemeris_files):
|
|
38
|
+
ax_x.plot(ephemeris["JDTDB"], ephemeris["X"], label=path.stem, alpha=0.8)
|
|
39
|
+
ax_y.plot(ephemeris["JDTDB"], ephemeris["Y"], label=path.stem, alpha=0.8)
|
|
40
|
+
ax_z.plot(ephemeris["JDTDB"], ephemeris["Z"], label=path.stem, alpha=0.8)
|
|
41
|
+
|
|
42
|
+
ax_x.set(xlabel="JD TDB", ylabel="TESS X coordinate [AU]")
|
|
43
|
+
ax_y.set(xlabel="JD TDB", ylabel="TESS Y coordinate [AU]")
|
|
44
|
+
ax_z.set(xlabel="JD TDB", ylabel="TESS Z coordinate [AU]")
|
|
45
|
+
fig.suptitle("JPL TESS Ephemerides for QLP")
|
|
46
|
+
|
|
47
|
+
if output_file is None:
|
|
48
|
+
plt.show()
|
|
49
|
+
else:
|
|
50
|
+
fig.savefig(output_file)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
plot_ephemerides(Path(__file__).parent)
|
tglc/ffi.py
CHANGED
|
@@ -4,7 +4,7 @@ import pickle
|
|
|
4
4
|
import sys
|
|
5
5
|
import astropy.units as u
|
|
6
6
|
import numpy as np
|
|
7
|
-
import
|
|
7
|
+
import importlib_resources
|
|
8
8
|
import requests
|
|
9
9
|
import time
|
|
10
10
|
|
|
@@ -86,7 +86,7 @@ def convert_gaia_id(catalogdata_tic):
|
|
|
86
86
|
FROM gaiadr3.dr2_neighbourhood
|
|
87
87
|
WHERE dr2_source_id IN {gaia_ids}
|
|
88
88
|
"""
|
|
89
|
-
gaia_array = np.array(catalogdata_tic['GAIA'])
|
|
89
|
+
gaia_array = np.array([str(item) for item in catalogdata_tic['GAIA']], dtype=object)
|
|
90
90
|
gaia_array = gaia_array[gaia_array != 'None']
|
|
91
91
|
# np.save('gaia_array.npy', gaia_array)
|
|
92
92
|
segment = (len(gaia_array) - 1) // 10000
|
|
@@ -327,7 +327,7 @@ def ffi(ccd=1, camera=1, sector=1, size=150, local_directory='', producing_mask=
|
|
|
327
327
|
np.save(f'{local_directory}mask/mask_sector{sector:04d}_cam{camera}_ccd{ccd}.npy', mask)
|
|
328
328
|
return
|
|
329
329
|
# load mask
|
|
330
|
-
mask =
|
|
330
|
+
mask = importlib_resources.files(__package__).joinpath("background_mask/median_mask.fits").open("rb")
|
|
331
331
|
mask = fits.open(mask)[0].data[(camera - 1) * 4 + (ccd - 1), :]
|
|
332
332
|
mask = np.repeat(mask.reshape(1, 2048), repeats=2048, axis=0)
|
|
333
333
|
bad_pixels = np.zeros(np.shape(flux[0]))
|
tglc/ffi_cut.py
CHANGED
|
@@ -4,6 +4,10 @@ import sys
|
|
|
4
4
|
import pickle
|
|
5
5
|
import numpy as np
|
|
6
6
|
import astropy.units as u
|
|
7
|
+
import requests
|
|
8
|
+
import time
|
|
9
|
+
import contextlib
|
|
10
|
+
import threading
|
|
7
11
|
|
|
8
12
|
from os.path import exists
|
|
9
13
|
from astroquery.gaia import Gaia
|
|
@@ -22,8 +26,34 @@ Gaia.ROW_LIMIT = -1
|
|
|
22
26
|
Gaia.MAIN_GAIA_TABLE = "gaiadr3.gaia_source"
|
|
23
27
|
|
|
24
28
|
|
|
29
|
+
@contextlib.contextmanager
|
|
30
|
+
def _dot_wait(message, interval=0.6, done_format=None):
|
|
31
|
+
stop_event = threading.Event()
|
|
32
|
+
start = time.time()
|
|
33
|
+
|
|
34
|
+
def _run():
|
|
35
|
+
dots = 0
|
|
36
|
+
while not stop_event.is_set():
|
|
37
|
+
dots = (dots % 3) + 1
|
|
38
|
+
print(f"\r{message} {'.' * dots}", end="", flush=True)
|
|
39
|
+
time.sleep(interval)
|
|
40
|
+
|
|
41
|
+
thread = threading.Thread(target=_run, daemon=True)
|
|
42
|
+
thread.start()
|
|
43
|
+
try:
|
|
44
|
+
yield
|
|
45
|
+
finally:
|
|
46
|
+
stop_event.set()
|
|
47
|
+
thread.join()
|
|
48
|
+
if done_format is None:
|
|
49
|
+
done_format = "{message} ... done in {elapsed:.1f}s."
|
|
50
|
+
done_line = done_format.format(message=message, elapsed=time.time() - start)
|
|
51
|
+
print(f"\r{done_line} ")
|
|
52
|
+
|
|
53
|
+
|
|
25
54
|
class Source_cut(object):
|
|
26
|
-
def __init__(self, name, size=50, sector=None, cadence=None, limit_mag=None, transient=None
|
|
55
|
+
def __init__(self, name, size=50, sector=None, cadence=None, limit_mag=None, transient=None, ffi='TICA',
|
|
56
|
+
mast_timeout=3600):
|
|
27
57
|
"""
|
|
28
58
|
Source_cut object that includes all data from TESS and Gaia DR3
|
|
29
59
|
:param name: str, required
|
|
@@ -51,35 +81,100 @@ class Source_cut(object):
|
|
|
51
81
|
self.quality = []
|
|
52
82
|
self.mask = []
|
|
53
83
|
self.transient = transient
|
|
84
|
+
self.ffi = ffi
|
|
85
|
+
Tesscut._service_api_connection.TIMEOUT = mast_timeout
|
|
86
|
+
print(f'MAST Tesscut timeout set to {mast_timeout}s.')
|
|
87
|
+
|
|
88
|
+
def _parse_tic_id(t):
|
|
89
|
+
if not isinstance(t, str):
|
|
90
|
+
return None
|
|
91
|
+
s = t.strip()
|
|
92
|
+
if s.upper().startswith('TIC'):
|
|
93
|
+
parts = s.split()
|
|
94
|
+
if len(parts) > 1 and parts[1].isdigit():
|
|
95
|
+
return int(parts[1])
|
|
96
|
+
s = s[3:].strip()
|
|
97
|
+
return int(s) if s.isdigit() else None
|
|
98
|
+
|
|
99
|
+
def _is_tic_id(t):
|
|
100
|
+
if not isinstance(t, str):
|
|
101
|
+
return False
|
|
102
|
+
s = t.strip()
|
|
103
|
+
return s.upper().startswith('TIC') or s.isdigit()
|
|
104
|
+
|
|
105
|
+
target = None
|
|
106
|
+
is_tic = _is_tic_id(self.name) and _parse_tic_id(self.name) is not None
|
|
107
|
+
try:
|
|
108
|
+
target = Catalogs.query_object(self.name, radius=21 * 0.707 / 3600, catalog="Gaia", version=2)
|
|
109
|
+
except requests.exceptions.RequestException as e:
|
|
110
|
+
warnings.warn(f'MAST name lookup failed for "{self.name}": {e}')
|
|
111
|
+
|
|
112
|
+
if target is None or len(target) == 0:
|
|
113
|
+
try:
|
|
114
|
+
target = Catalogs.query_object(self.name, radius=5 * 21 * 0.707 / 3600, catalog="Gaia", version=2)
|
|
115
|
+
except requests.exceptions.RequestException as e:
|
|
116
|
+
warnings.warn(f'MAST name lookup failed for "{self.name}": {e}')
|
|
117
|
+
|
|
118
|
+
if target is None or len(target) == 0:
|
|
119
|
+
if is_tic:
|
|
120
|
+
raise RuntimeError(
|
|
121
|
+
f'MAST name lookup failed for TIC target "{self.name}". Please retry when MAST is available.'
|
|
122
|
+
)
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
f'Unable to resolve target "{self.name}". MAST name lookup appears unavailable.'
|
|
125
|
+
)
|
|
54
126
|
|
|
55
|
-
target = Catalogs.query_object(self.name, radius=21 * 0.707 / 3600, catalog="Gaia", version=2)
|
|
56
|
-
if len(target) == 0:
|
|
57
|
-
target = Catalogs.query_object(self.name, radius=5 * 21 * 0.707 / 3600, catalog="Gaia", version=2)
|
|
58
127
|
ra = target[0]['ra']
|
|
59
128
|
dec = target[0]['dec']
|
|
129
|
+
target_designation = target[0].get('designation', None)
|
|
60
130
|
coord = SkyCoord(ra=ra, dec=dec, unit=(u.degree, u.degree), frame='icrs')
|
|
61
131
|
radius = u.Quantity((self.size + 6) * 21 * 0.707 / 3600, u.deg)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
132
|
+
if target_designation:
|
|
133
|
+
print(f'Target Gaia: {target_designation}')
|
|
134
|
+
with _dot_wait('Querying Gaia DR3 cone search'):
|
|
135
|
+
catalogdata = Gaia.cone_search_async(
|
|
136
|
+
coord,
|
|
137
|
+
radius=radius,
|
|
138
|
+
columns=['DESIGNATION', 'phot_g_mean_mag', 'phot_bp_mean_mag',
|
|
139
|
+
'phot_rp_mean_mag', 'ra', 'dec', 'pmra', 'pmdec']
|
|
140
|
+
).get_results()
|
|
66
141
|
print(f'Found {len(catalogdata)} Gaia DR3 objects.')
|
|
67
|
-
|
|
142
|
+
with _dot_wait('Querying TIC around target'):
|
|
143
|
+
catalogdata_tic = tic_advanced_search_position_rows(
|
|
144
|
+
ra=ra,
|
|
145
|
+
dec=dec,
|
|
146
|
+
radius=(self.size + 2) * 21 * 0.707 / 3600,
|
|
147
|
+
limit_mag=limit_mag
|
|
148
|
+
)
|
|
68
149
|
print(f'Found {len(catalogdata_tic)} TIC objects.')
|
|
69
|
-
|
|
150
|
+
with _dot_wait('Crossmatching TIC -> Gaia DR3 (this may take a while)'):
|
|
151
|
+
self.tic = convert_gaia_id(catalogdata_tic)
|
|
70
152
|
sector_table = Tesscut.get_sectors(coordinates=coord)
|
|
71
153
|
if len(sector_table) == 0:
|
|
72
154
|
warnings.warn('TESS has not observed this position yet :(')
|
|
155
|
+
wait_note = 'later sectors with 200s cadence can take ~20 minutes'
|
|
73
156
|
if sector is None:
|
|
74
|
-
|
|
157
|
+
wait_message = f'Requesting Tesscut cutouts (all sectors). Waiting on MAST response ({wait_note})'
|
|
158
|
+
with _dot_wait(wait_message):
|
|
159
|
+
hdulist = Tesscut.get_cutouts(coordinates=coord, size=self.size, product=ffi)
|
|
75
160
|
elif sector == 'first':
|
|
76
|
-
|
|
161
|
+
wait_message = f'Requesting Tesscut cutouts (first sector). Waiting on MAST response ({wait_note})'
|
|
162
|
+
with _dot_wait(wait_message):
|
|
163
|
+
hdulist = Tesscut.get_cutouts(
|
|
164
|
+
coordinates=coord, size=self.size, product=ffi, sector=sector_table['sector'][0]
|
|
165
|
+
)
|
|
77
166
|
sector = sector_table['sector'][0]
|
|
78
167
|
elif sector == 'last':
|
|
79
|
-
|
|
168
|
+
wait_message = f'Requesting Tesscut cutouts (last sector). Waiting on MAST response ({wait_note})'
|
|
169
|
+
with _dot_wait(wait_message):
|
|
170
|
+
hdulist = Tesscut.get_cutouts(
|
|
171
|
+
coordinates=coord, size=self.size, product=ffi, sector=sector_table['sector'][-1]
|
|
172
|
+
)
|
|
80
173
|
sector = sector_table['sector'][-1]
|
|
81
174
|
else:
|
|
82
|
-
|
|
175
|
+
wait_message = f'Requesting Tesscut cutouts (sector {sector}). Waiting on MAST response ({wait_note})'
|
|
176
|
+
with _dot_wait(wait_message):
|
|
177
|
+
hdulist = Tesscut.get_cutouts(coordinates=coord, size=self.size, product=ffi, sector=sector)
|
|
83
178
|
self.catalogdata = catalogdata
|
|
84
179
|
self.sector_table = sector_table
|
|
85
180
|
self.camera = int(sector_table[0]['camera'])
|
|
@@ -114,8 +209,14 @@ class Source_cut(object):
|
|
|
114
209
|
self.ccd = int(hdu[0].header['CCD'])
|
|
115
210
|
wcs = WCS(hdu[2].header)
|
|
116
211
|
data_time = hdu[1].data['TIME']
|
|
117
|
-
|
|
118
|
-
|
|
212
|
+
if self.ffi == 'SPOC':
|
|
213
|
+
data_flux = hdu[1].data['FLUX']
|
|
214
|
+
data_flux_err = hdu[1].data['FLUX_ERR']
|
|
215
|
+
elif self.ffi == 'TICA':
|
|
216
|
+
data_flux = hdu[1].data['FLUX'] / (200 * 0.8 * 0.99)
|
|
217
|
+
data_flux_err = hdu[1].data['FLUX_ERR'] / (200 * 0.8 * 0.99)
|
|
218
|
+
else:
|
|
219
|
+
raise Exception(f'FFI type {self.ffi} not supported')
|
|
119
220
|
data_quality = hdu[1].data['QUALITY']
|
|
120
221
|
# data_time = data_time[np.where(data_quality == 0)]
|
|
121
222
|
# data_flux = data_flux[np.where(data_quality == 0), :, :][0]
|
|
@@ -245,7 +346,8 @@ class Source_cut_pseudo(object):
|
|
|
245
346
|
self.gaia = gaia_targets
|
|
246
347
|
|
|
247
348
|
|
|
248
|
-
def ffi_cut(target='', local_directory='', size=90, sector=None, limit_mag=None, transient=None
|
|
349
|
+
def ffi_cut(target='', local_directory='', size=90, sector=None, limit_mag=None, transient=None, ffi='TICA',
|
|
350
|
+
mast_timeout=3600):
|
|
249
351
|
"""
|
|
250
352
|
Function to generate Source_cut objects
|
|
251
353
|
:param target: string, required
|
|
@@ -259,13 +361,13 @@ def ffi_cut(target='', local_directory='', size=90, sector=None, limit_mag=None,
|
|
|
259
361
|
:return: tglc.ffi_cut.Source_cut
|
|
260
362
|
"""
|
|
261
363
|
if sector is None:
|
|
262
|
-
source_name = f'source_{target}'
|
|
364
|
+
source_name = f'source_{ffi}_{target}'
|
|
263
365
|
elif sector == 'first':
|
|
264
|
-
source_name = f'source_{target}_earliest_sector'
|
|
366
|
+
source_name = f'source_{ffi}_{target}_earliest_sector'
|
|
265
367
|
elif sector == 'last':
|
|
266
|
-
source_name = f'source_{target}_last_sector'
|
|
368
|
+
source_name = f'source_{ffi}_{target}_last_sector'
|
|
267
369
|
else:
|
|
268
|
-
source_name = f'source_{target}_sector_{sector}'
|
|
370
|
+
source_name = f'source_{ffi}_{target}_sector_{sector}'
|
|
269
371
|
source_exists = exists(f'{local_directory}source/{source_name}.pkl')
|
|
270
372
|
if source_exists and os.path.getsize(f'{local_directory}source/{source_name}.pkl') > 0:
|
|
271
373
|
with open(f'{local_directory}source/{source_name}.pkl', 'rb') as input_:
|
|
@@ -274,6 +376,7 @@ def ffi_cut(target='', local_directory='', size=90, sector=None, limit_mag=None,
|
|
|
274
376
|
print('Loaded ffi_cut from directory. ')
|
|
275
377
|
else:
|
|
276
378
|
with open(f'{local_directory}source/{source_name}.pkl', 'wb') as output:
|
|
277
|
-
source = Source_cut(target, size=size, sector=sector, limit_mag=limit_mag, transient=transient
|
|
379
|
+
source = Source_cut(target, size=size, sector=sector, limit_mag=limit_mag, transient=transient, ffi=ffi,
|
|
380
|
+
mast_timeout=mast_timeout)
|
|
278
381
|
pickle.dump(source, output, pickle.HIGHEST_PROTOCOL)
|
|
279
382
|
return source
|