tglc 0.6.5__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/run.py ADDED
@@ -0,0 +1,95 @@
1
+ # export OPENBLAS_NUM_THREADS=1
2
+ # export MKL_NUM_THREADS=1
3
+ # export NUMEXPR_NUM_THREADS=1
4
+ # export OMP_NUM_THREADS=1
5
+ # https://dev.to/kapilgorve/set-environment-variable-in-windows-and-wsl-linux-in-terminal-3mg4
6
+
7
+ import os
8
+
9
+ os.environ["OPENBLAS_NUM_THREADS"] = "1"
10
+ os.environ["MKL_NUM_THREADS"] = "1"
11
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
12
+ os.environ["OMP_NUM_THREADS"] = "1"
13
+
14
+ import pickle
15
+ import numpy as np
16
+ import matplotlib.pyplot as plt
17
+ import multiprocessing
18
+
19
+ from astropy.io import fits
20
+ from tglc.target_lightcurve import epsf
21
+ from multiprocessing import Pool
22
+ from functools import partial
23
+ from glob import glob
24
+
25
+
26
+ def lc_per_cut(i, camccd='', local_directory=''):
27
+ cut_x = i // 14
28
+ cut_y = i % 14
29
+ with open(f'{local_directory}source/{camccd}/source_{cut_x:02d}_{cut_y:02d}.pkl', 'rb') as input_:
30
+ source = pickle.load(input_)
31
+ epsf(source, psf_size=11, factor=2, cut_x=cut_x, cut_y=cut_y, sector=source.sector, power=1.4,
32
+ local_directory=local_directory, limit_mag=16, save_aper=False, no_progress_bar=True)
33
+ # residual =
34
+ # np.save(f'/home/tehan/cosmos/MKI/Roland/{cut_x:02d}_{cut_y:02d}.npy', residual)
35
+ # fig = plt.figure(constrained_layout=False, figsize=(8, 8))
36
+ # gs = fig.add_gridspec(2, 2)
37
+ # gs.update(wspace=0.4, hspace=0.2)
38
+ # wcs = source.wcs
39
+ # ax0 = fig.add_subplot(gs[0:2, 0:2], projection=wcs)
40
+ # ax0.imshow(residual, origin='bottom')
41
+ # ax0.coords['pos.eq.ra'].set_axislabel('Right Ascension')
42
+ # ax0.coords['pos.eq.ra'].set_axislabel_position('l')
43
+ # ax0.coords['pos.eq.ra'].set_ticklabel(rotation=90)
44
+ # ax0.coords['pos.eq.dec'].set_axislabel('Declination')
45
+ # ax0.coords['pos.eq.dec'].set_axislabel_position('b')
46
+ # ax0.coords.grid(color='k', ls='dotted')
47
+ # ax0.tick_params(axis='x', labelbottom=True)
48
+ # ax0.tick_params(axis='y', labelleft=True)
49
+ # ax0.set_title(f'Sector 42 Camera 1 CCD 1, cut {cut_x:02d}_{cut_y:02d}, cadence {source.cadence[2817]}, time {source.time[2817]}')
50
+ # plt.savefig(f'/home/tehan/cosmos/MKI/Roland/figs/{cut_x:02d}_{cut_y:02d}.png')
51
+
52
+ def lc_per_ccd(camccd='1-1', local_directory=''):
53
+ os.makedirs(f'{local_directory}epsf/{camccd}/', exist_ok=True)
54
+ with Pool() as p:
55
+ p.map(partial(lc_per_cut, camccd=camccd, local_directory=local_directory), range(196))
56
+
57
+
58
+ def plot_epsf(sector=1, camccd='', local_directory=''):
59
+ fig = plt.figure(constrained_layout=False, figsize=(20, 9))
60
+ gs = fig.add_gridspec(14, 30)
61
+ gs.update(wspace=0.05, hspace=0.05)
62
+ for i in range(196):
63
+ cut_x = i // 14
64
+ cut_y = i % 14
65
+ psf = np.load(f'{local_directory}epsf/{camccd}/epsf_{cut_x:02d}_{cut_y:02d}_sector_{sector}_{camccd}.npy')
66
+ cmap = 'bone'
67
+ if np.isnan(psf).any():
68
+ cmap = 'inferno'
69
+ ax = fig.add_subplot(gs[13 - cut_y, cut_x])
70
+ ax.imshow(psf[0, :23 ** 2].reshape(23, 23), cmap=cmap, origin='lower')
71
+ ax.set_yticklabels([])
72
+ ax.set_xticklabels([])
73
+ ax.tick_params(axis='x', bottom=False)
74
+ ax.tick_params(axis='y', left=False)
75
+ input_files = glob(f'{local_directory}ffi/*{camccd}-????-?_ffic.fits')
76
+ with fits.open(input_files[0], mode='denywrite') as hdul:
77
+ flux = hdul[1].data[0:2048, 44:2092]
78
+ ax_1 = fig.add_subplot(gs[:, 16:])
79
+ ax_1.imshow(np.log10(flux), origin='lower')
80
+ fig.text(0.25, 0.08, 'CUT X (0-13)', ha='center')
81
+ fig.text(0.09, 0.5, 'CUT Y (0-13)', va='center', rotation='vertical')
82
+ fig.suptitle(f'ePSF for sector:{sector} camera-ccd:{camccd}', x=0.5, y=0.92, size=20)
83
+ plt.savefig(f'{local_directory}log/epsf_sector_{sector}_{camccd}.png', bbox_inches='tight', dpi=300)
84
+
85
+
86
+ if __name__ == '__main__':
87
+ print("Number of cpu : ", multiprocessing.cpu_count())
88
+ sector = 1
89
+ local_directory = f'/home/tehan/data/sector{sector:04d}/'
90
+ for i in range(16):
91
+ name = f'{1 + i // 4}-{1 + i % 4}'
92
+ lc_per_ccd(camccd=name, local_directory=local_directory)
93
+ plot_epsf(sector=sector, camccd=name, local_directory=local_directory)
94
+ # name = f'1-3'
95
+ # lc_per_cut(169, camccd=name, local_directory=local_directory)
tglc/source_output.py ADDED
@@ -0,0 +1,85 @@
1
+ import os
2
+
3
+ os.environ["OPENBLAS_NUM_THREADS"] = "8"
4
+ os.environ["MKL_NUM_THREADS"] = "8"
5
+ os.environ["NUMEXPR_NUM_THREADS"] = "8"
6
+ os.environ["OMP_NUM_THREADS"] = "8"
7
+ import glob
8
+ from tglc.ffi import *
9
+ from multiprocessing import Pool
10
+ from functools import partial
11
+ import logging
12
+ import warnings
13
+ import astroquery
14
+ import matplotlib.pyplot as plt
15
+
16
+ logging.getLogger(astroquery.__name__).setLevel(logging.ERROR)
17
+ warnings.simplefilter('ignore', UserWarning)
18
+
19
+
20
+ def median_mask(sector_num=26):
21
+ mask = np.ones((sector_num, 16, 2048))
22
+ for i in range(sector_num):
23
+ for j in range(16):
24
+ mask[i, j] = np.load(
25
+ f'/mnt/c/users/tehan/desktop/mask/mask_sector{i + 1:04d}_cam{1 + j // 4}_ccd{1 + j % 4}.npy')
26
+ plt.figure(figsize=(10, 3))
27
+ plt.imshow(mask[i], origin='lower', aspect=30)
28
+ plt.colorbar()
29
+ plt.title(f'sector_{i + 1}')
30
+ plt.xlabel(f'column')
31
+ plt.ylabel(f'camera-ccd')
32
+ plt.savefig(f'/mnt/c/users/tehan/desktop/mask/fig/sector_{i + 1}.png', dpi=300)
33
+ plt.close()
34
+ med_mask = np.nanmedian(mask, axis=0)
35
+
36
+ plt.figure(figsize=(10, 3))
37
+ plt.imshow(med_mask, origin='lower', aspect=30, vmin=np.min(np.nonzero(med_mask)), vmax=np.max(med_mask))
38
+ plt.colorbar()
39
+ plt.xlabel(f'column')
40
+ plt.ylabel(f'camera-ccd')
41
+ plt.title(f'median_mask_first_{sector_num}_sectors')
42
+ plt.savefig(f'/mnt/c/users/tehan/desktop/mask/fig/median_mask_primary.png', dpi=300)
43
+ plt.close()
44
+ hdu = fits.PrimaryHDU(med_mask)
45
+ hdu.writeto('/mnt/c/users/tehan/desktop/mask/median_mask.fits')
46
+ return med_mask
47
+
48
+
49
+ def cut_ffi_(i, sector=1, size=150, local_directory=''):
50
+ ffi(camera=1 + i // 4, ccd=1 + i % 4, sector=sector, size=size, local_directory=local_directory,
51
+ producing_mask=False)
52
+
53
+
54
+ def ffi_to_source(sector=1, local_directory=''):
55
+ """
56
+ Cut calibrated FFI to source.pkl
57
+ :param sector: int, required
58
+ TESS sector number
59
+ :param local_directory: string, required
60
+ output directory
61
+ """
62
+ os.makedirs(f'{local_directory}lc/', exist_ok=True)
63
+ os.makedirs(f'{local_directory}epsf/', exist_ok=True)
64
+ os.makedirs(f'{local_directory}ffi/', exist_ok=True)
65
+ os.makedirs(f'{local_directory}source/', exist_ok=True)
66
+ os.makedirs(f'{local_directory}log/', exist_ok=True)
67
+ os.makedirs(f'{local_directory}mask/', exist_ok=True)
68
+
69
+ with Pool(1) as p:
70
+ p.map(partial(cut_ffi_, sector=sector, size=150, local_directory=local_directory), range(16))
71
+
72
+ # for i in range(16):
73
+ # ffi(camera=1 + i // 4, ccd=1 + i % 4, sector=sector, size=150, local_directory=local_directory)
74
+
75
+
76
+ if __name__ == '__main__':
77
+ sector = 56
78
+ ffi_to_source(sector=sector, local_directory=f'/home/tehan/data/sector{sector:04d}/')
79
+ # med_mask = median_mask(sector_num=26)
80
+ # ffi_to_source(sector=sector, local_directory=f'/pdo/users/tehan/sector{sector:04d}/')
81
+ # files = glob.glob(f'/home/tehan/data/sector{sector:04d}/source/*/source_00_00.pkl')
82
+ # for i in range(len(files)):
83
+ # with open(files[i], 'rb') as input_:
84
+ # source = pickle.load(input_)
85
+ # print(np.min(np.diff(source.cadence)), files[i])
@@ -0,0 +1,362 @@
1
+ # export OPENBLAS_NUM_THREADS=1
2
+ # https://dev.to/kapilgorve/set-environment-variable-in-windows-and-wsl-linux-in-terminal-3mg4
3
+
4
+ import os
5
+ import warnings
6
+ import numpy as np
7
+ import numpy.ma as ma
8
+ import tglc
9
+ import matplotlib.pyplot as plt
10
+ from astropy.io import fits
11
+ from tqdm import trange
12
+ from os.path import exists
13
+ from tglc.effective_psf import get_psf, fit_psf, fit_lc, fit_lc_float_field, bg_mod
14
+ from tglc.ffi import Source
15
+ from tglc.ffi_cut import Source_cut
16
+
17
+ warnings.simplefilter('always', UserWarning)
18
+
19
+
20
+ def lc_output(source, local_directory='', index=0, time=None, psf_lc=None, cal_psf_lc=None, aper_lc=None,
21
+ cal_aper_lc=None, bg=None, tess_flag=None, tglc_flag=None, cadence=None, aperture=None,
22
+ cut_x=None, cut_y=None, star_x=2, star_y=2, x_aperture=None, y_aperture=None, near_edge=False,
23
+ local_bg=None, save_aper=False, portion=1, prior=None, transient=None):
24
+ """
25
+ lc output to .FITS file in MAST HLSP standards
26
+ :param tglc_flag: np.array(), required
27
+ TGLC quality flags
28
+ :param source: tglc.ffi_cut.Source or tglc.ffi_cut.Source_cut, required
29
+ Source or Source_cut object
30
+ :param local_directory: string, required
31
+ output directory
32
+ :param index: int, required
33
+ star index
34
+ :param time: list, required
35
+ epochs of FFI
36
+ :param lc: list, required
37
+ ePSF light curve fluxes
38
+ :param cal_lc: list, required
39
+ ePSF light curve fluxes, detrended
40
+ :param cadence: list, required
41
+ list of cadences of TESS FFI
42
+ :return:
43
+ """
44
+ if transient is None:
45
+ objid = [int(s) for s in (source.gaia[index]['DESIGNATION']).split() if s.isdigit()][0]
46
+ else:
47
+ objid = transient[0]
48
+ source_path = f'{local_directory}hlsp_tglc_tess_ffi_gaiaid-{objid}-s{source.sector:04d}-cam{source.camera}-ccd{source.ccd}_tess_v1_llc.fits'
49
+ source_exists = exists(source_path)
50
+ # if source_exists and (os.path.getsize(source_path) > 0):
51
+ # print('LC exists, please (re)move the file if you wish to overwrite.')
52
+ # return
53
+ if np.isnan(source.gaia[index]['phot_bp_mean_mag']) or ma.is_masked(source.gaia[index]['phot_bp_mean_mag']):
54
+ gaia_bp = 'NaN'
55
+ else:
56
+ gaia_bp = source.gaia[index]['phot_bp_mean_mag']
57
+ if np.isnan(source.gaia[index]['phot_rp_mean_mag']) or ma.is_masked(source.gaia[index]['phot_rp_mean_mag']):
58
+ gaia_rp = 'NaN'
59
+ else:
60
+ gaia_rp = source.gaia[index]['phot_rp_mean_mag']
61
+ psf_err = 1.4826 * np.nanmedian(np.abs(psf_lc - np.nanmedian(psf_lc)))
62
+ if np.isnan(psf_err):
63
+ psf_err = 'NaN'
64
+ aper_err = 1.4826 * np.nanmedian(np.abs(aper_lc - np.nanmedian(aper_lc)))
65
+ if np.isnan(aper_err):
66
+ aper_err = 'NaN'
67
+ cal_psf_err = 1.4826 * np.nanmedian(np.abs(cal_psf_lc - np.nanmedian(cal_psf_lc)))
68
+ if np.isnan(cal_psf_err):
69
+ cal_psf_err = 'NaN'
70
+ cal_aper_err = 1.4826 * np.nanmedian(np.abs(cal_aper_lc - np.nanmedian(cal_aper_lc)))
71
+ if np.isnan(cal_aper_err):
72
+ cal_aper_err = 'NaN'
73
+ try:
74
+ ticid = str(source.tic['TIC'][np.where(source.tic['dr3_source_id'] == objid)][0])
75
+ except:
76
+ ticid = ''
77
+ try:
78
+ raw_flux = np.nanmedian(source.flux[:, star_y, star_x])
79
+ except:
80
+ raw_flux = None
81
+ if save_aper:
82
+ primary_hdu = fits.PrimaryHDU(aperture)
83
+ else:
84
+ primary_hdu = fits.PrimaryHDU()
85
+
86
+ primary_hdu.header = fits.Header(cards=[
87
+ fits.Card('SIMPLE', True, 'conforms to FITS standard'),
88
+ fits.Card('EXTEND', True),
89
+ fits.Card('NEXTEND', 1, 'number of standard extensions'),
90
+ fits.Card('EXTNAME', 'PRIMARY', 'name of extension'),
91
+ fits.Card('EXTDATA', 'aperture', 'decontaminated FFI cut for aperture photometry'),
92
+ fits.Card('EXTVER', 1, 'extension version'),
93
+ fits.Card('TIMESYS', 'TDB', 'TESS Barycentric Dynamical Time'),
94
+ fits.Card('BUNIT', 'e-/s', 'flux unit'),
95
+ fits.Card('STAR_X', x_aperture, 'star x position in cut'),
96
+ fits.Card('STAR_Y', y_aperture, 'star y position in cut'),
97
+ fits.Card('COMMENT', 'hdul[0].data[:,star_y,star_x]=lc'),
98
+ fits.Card('ORIGIN', 'UCSB/TGLC', 'institution responsible for creating this file'),
99
+ fits.Card('TELESCOP', 'TESS', 'telescope'),
100
+ fits.Card('INSTRUME', 'TESS Photometer', 'detector type'),
101
+ fits.Card('FILTER', 'TESS', 'the filter used for the observations'),
102
+ fits.Card('OBJECT', source.gaia[index]['DESIGNATION'], 'string version of Gaia DR3 ID'),
103
+ fits.Card('GAIADR3', objid, 'integer version of Gaia DR3 ID'),
104
+ fits.Card('TICID', ticid, 'TESS Input Catalog ID'),
105
+ fits.Card('SECTOR', source.sector, 'observation sector'),
106
+ fits.Card('CAMERA', source.camera, 'camera No.'),
107
+ fits.Card('CCD', source.ccd, 'CCD No.'),
108
+ fits.Card('CUT_x', cut_x, 'FFI cut x index'),
109
+ fits.Card('CUT_y', cut_y, 'FFI cut y index'),
110
+ fits.Card('CUTSIZE', source.size, 'FFI cut size'),
111
+ fits.Card('RADESYS', 'ICRS', 'reference frame of celestial coordinates'),
112
+ fits.Card('RA_OBJ', source.gaia[index]['ra'], '[deg] right ascension, J2000'),
113
+ fits.Card('DEC_OBJ', source.gaia[index]['dec'], '[deg] declination, J2000'),
114
+ fits.Card('TESSMAG', source.gaia[index]['tess_mag'], 'TESS magnitude, fitted by Gaia DR3 bands'),
115
+ fits.Card('GAIA_G', source.gaia[index]['phot_g_mean_mag'], 'Gaia DR3 g band magnitude'),
116
+ fits.Card('GAIA_bp', gaia_bp, 'Gaia DR3 bp band magnitude'),
117
+ fits.Card('GAIA_rp', gaia_rp, 'Gaia DR3 rp band magnitude'),
118
+ fits.Card('RAWFLUX', raw_flux, 'median flux of raw FFI'),
119
+ fits.Card('CALIB', 'TGLC', 'pipeline used for image calibration')])
120
+ if save_aper:
121
+ primary_hdu.header.comments['NAXIS1'] = "Time (hdul[1].data['time'])"
122
+ primary_hdu.header.comments['NAXIS2'] = 'x size of cut'
123
+ primary_hdu.header.comments['NAXIS3'] = 'y size of cut'
124
+
125
+ t_start = source.time[0]
126
+ t_stop = source.time[-1]
127
+ if source.sector < 27: # primary
128
+ exposure_time = 1800
129
+ elif source.sector < 56: # first extended
130
+ exposure_time = 600
131
+ else: # second extended
132
+ exposure_time = 200
133
+ c1 = fits.Column(name='time', array=np.array(time), format='D')
134
+ c2 = fits.Column(name='psf_flux', array=np.array(psf_lc), format='E') # psf factor
135
+ # c3 = fits.Column(name='psf_flux_err',
136
+ # array=1.4826 * np.median(np.abs(psf_lc - np.median(psf_lc))) * np.ones(len(psf_lc)), format='E')
137
+ c4 = fits.Column(name='aperture_flux', array=aper_lc / portion, format='E')
138
+ # c5 = fits.Column(name='aperture_flux_err',
139
+ # array=1.4826 * np.median(np.abs(aper_lc - np.median(aper_lc))) * np.ones(len(aper_lc)), format='E')
140
+ c6 = fits.Column(name='cal_psf_flux', array=np.array(cal_psf_lc), format='E')
141
+ # c7 = fits.Column(name='cal_psf_flux_err',
142
+ # array=1.4826 * np.median(np.abs(cal_psf_lc - np.median(cal_psf_lc))) * np.ones(len(cal_psf_lc)),
143
+ # format='E')
144
+ c8 = fits.Column(name='cal_aper_flux', array=np.array(cal_aper_lc), format='E')
145
+ # c9 = fits.Column(name='cal_aper_flux_err',
146
+ # array=1.4826 * np.median(np.abs(cal_aper_lc - np.median(cal_aper_lc))) * np.ones(len(cal_aper_lc)),
147
+ # format='E')
148
+ c10 = fits.Column(name='background', array=bg, format='E') # add tilt
149
+ c11 = fits.Column(name='cadence_num', array=np.array(cadence), format='J') # 32 bit int
150
+ c12 = fits.Column(name='TESS_flags', array=np.array(tess_flag), format='I') # 16 bit int
151
+ c13 = fits.Column(name='TGLC_flags', array=tglc_flag, format='I')
152
+ table_hdu = fits.BinTableHDU.from_columns([c1, c2, c4, c6, c8, c10, c11, c12, c13])
153
+ table_hdu.header.append(('INHERIT', 'T', 'inherit the primary header'), end=True)
154
+ table_hdu.header.append(('EXTNAME', 'LIGHTCURVE', 'name of extension'), end=True)
155
+ table_hdu.header.append(('EXTVER', 1, 'extension version'), # TODO: version?
156
+ end=True)
157
+ table_hdu.header.append(('TELESCOP', 'TESS', 'telescope'), end=True)
158
+ table_hdu.header.append(('INSTRUME', 'TESS Photometer', 'detector type'), end=True)
159
+ table_hdu.header.append(('FILTER', 'TESS', 'the filter used for the observations'), end=True)
160
+ table_hdu.header.append(('OBJECT', source.gaia[index]['DESIGNATION'], 'string version of Gaia DR3 ID'),
161
+ end=True)
162
+ table_hdu.header.append(('GAIADR3', objid, 'integer version of GaiaDR3 designation'), end=True)
163
+ table_hdu.header.append(('RADESYS', 'ICRS', 'reference frame of celestial coordinates'), end=True)
164
+ table_hdu.header.append(('RA_OBJ', source.gaia[index]['ra'], '[deg] right ascension, J2000'), end=True)
165
+ table_hdu.header.append(('DEC_OBJ', source.gaia[index]['dec'], '[deg] declination, J2000'), end=True)
166
+ table_hdu.header.append(('TIMEREF', 'SOLARSYSTEM', 'barycentric correction applied to times'), end=True)
167
+ table_hdu.header.append(('TASSIGN', 'SPACECRAFT', 'where time is assigned'), end=True)
168
+ table_hdu.header.append(('BJDREFI', 2457000, 'integer part of BJD reference date'), end=True)
169
+ table_hdu.header.append(('BJDREFR', 0.0, 'fraction of the day in BJD reference date'), end=True)
170
+ table_hdu.header.append(('TIMESYS', 'TDB', 'TESS Barycentric Dynamical Time'), end=True)
171
+ table_hdu.header.append(('TIMEUNIT', 'd', 'time unit for TIME'), end=True)
172
+ # table_hdu.header.append(('BUNIT', 'e-/s', 'psf_flux unit'), end=True)
173
+ table_hdu.header.append(('TELAPS', t_stop - t_start, '[d] TSTOP-TSTART'), end=True)
174
+ table_hdu.header.append(('TSTART', t_start, '[d] observation start time in TBJD'), end=True)
175
+ table_hdu.header.append(('TSTOP', t_stop, '[d] observation end time in TBJD'), end=True)
176
+ table_hdu.header.append(('MJD_BEG', t_start + 56999.5, '[d] start time in barycentric MJD'), end=True)
177
+ table_hdu.header.append(('MJD_END', t_stop + 56999.5, '[d] end time in barycentric MJD'), end=True)
178
+ table_hdu.header.append(('TIMEDEL', (t_stop - t_start) / len(source.time), '[d] time resolution of data'),
179
+ end=True)
180
+ table_hdu.header.append(('XPTIME', exposure_time, '[s] exposure time'), end=True)
181
+ table_hdu.header.append(('PSF_ERR', psf_err, '[e-/s] PSF flux error'), end=True)
182
+ table_hdu.header.append(('APER_ERR', aper_err, '[e-/s] aperture flux error'), end=True)
183
+ table_hdu.header.append(('CPSF_ERR', cal_psf_err, '[e-/s] calibrated PSF flux error'), end=True)
184
+ table_hdu.header.append(('CAPE_ERR', cal_aper_err, '[e-/s] calibrated aperture flux error'), end=True)
185
+ table_hdu.header.append(('NEAREDGE', near_edge, 'distance to edges of FFI <= 2'), end=True)
186
+ table_hdu.header.append(('LOC_BG', local_bg, '[e-/s] locally modified background'), end=True)
187
+ table_hdu.header.append(('COMMENT', "TRUE_BG = hdul[1].data['background'] + LOC_BG"), end=True)
188
+ table_hdu.header.append(('WOTAN_WL', 1, 'wotan detrending window length'), end=True)
189
+ table_hdu.header.append(('WOTAN_MT', 'biweight', 'wotan detrending method'), end=True)
190
+ if type(prior) == float:
191
+ table_hdu.header.append(('PRIOR', prior, 'prior of field stars'), end=True)
192
+
193
+ hdul = fits.HDUList([primary_hdu, table_hdu])
194
+ hdul.writeto(
195
+ f'{local_directory}hlsp_tglc_tess_ffi_gaiaid-{objid}-s{source.sector:04d}-cam{source.camera}-ccd{source.ccd}_tess_v1_llc.fits',
196
+ overwrite=True)
197
+ return
198
+
199
+
200
+ def epsf(source, psf_size=11, factor=2, local_directory='', target=None, cut_x=0, cut_y=0, sector=0,
201
+ limit_mag=16, edge_compression=1e-4, power=1.4, name=None, save_aper=False, no_progress_bar=False, prior=None):
202
+ """
203
+ User function that unites all necessary steps
204
+ :param source: TGLC.ffi_cut.Source or TGLC.ffi_cut.Source_cut, required
205
+ Source or Source_cut object
206
+ :param factor: int, optional
207
+ effective PSF oversampling factor
208
+ :param local_directory: string, required
209
+ output directory
210
+ :param sector: int, required
211
+ TESS sector number
212
+ :param limit_mag: int, required
213
+ upper limiting magnitude of the lightcurves that are outputted.
214
+ :param edge_compression: float, optional
215
+ parameter for edge compression
216
+ :param power: float, optional
217
+ power for weighting bright stars' contribution to the fit. 1 means same contribution from all stars,
218
+ <1 means emphasizing dimmer stars
219
+ :return:
220
+ """
221
+ if target is None:
222
+ target = f'{cut_x:02d}_{cut_y:02d}'
223
+ A, star_info, over_size, x_round, y_round = get_psf(source, psf_size=psf_size, factor=factor,
224
+ edge_compression=edge_compression)
225
+ lc_directory = f'{local_directory}lc/{source.camera}-{source.ccd}/'
226
+ epsf_loc = f'{local_directory}epsf/{source.camera}-{source.ccd}/epsf_{target}_sector_{sector}_{source.camera}-{source.ccd}.npy'
227
+ if type(source) == Source_cut:
228
+ bg_dof = 3
229
+ lc_directory = f'{local_directory}lc/'
230
+ epsf_loc = f'{local_directory}epsf/epsf_{target}_sector_{sector}.npy'
231
+ else:
232
+ bg_dof = 6
233
+ os.makedirs(lc_directory, exist_ok=True)
234
+ # sim_image = np.dot(A[:source.size ** 2, :], fit_psf(A, source, over_size, power=power, time=2817).T)
235
+ # # residual = np.abs(source.flux[2817].flatten() - sim_image)
236
+ # residual = source.flux[2817].flatten() - sim_image
237
+ # return residual.reshape((source.size, source.size))
238
+
239
+ epsf_exists = exists(epsf_loc)
240
+ if epsf_exists:
241
+ e_psf = np.load(epsf_loc)
242
+ print(f'Loaded ePSF {target} from directory. ')
243
+ else:
244
+ e_psf = np.zeros((len(source.time), over_size ** 2 + bg_dof))
245
+ for i in trange(len(source.time), desc='Fitting ePSF', disable=no_progress_bar):
246
+ e_psf[i] = fit_psf(A, source, over_size, power=power, time=i)
247
+ if np.isnan(e_psf).any():
248
+ warnings.warn(
249
+ f"TESS FFI cut includes Nan values. Please shift the center of the cutout to remove Nan near edge. Target: {target}")
250
+ np.save(epsf_loc, e_psf)
251
+ # contamination_8 = np.dot(A[:source.size ** 2, :], e_psf[0].T)
252
+ # np.save('/mnt/c/users/tehan/desktop/7654/contamination_8_.npy', contamination_8)
253
+ # TODO: quality use which background?
254
+ background = np.dot(A[:source.size ** 2, -bg_dof:], e_psf[:, -bg_dof:].T)
255
+ quality_raw = np.zeros(len(source.time), dtype=np.int16)
256
+ sigma = 1.4826 * np.nanmedian(np.abs(e_psf[:, -1] - np.nanmedian(e_psf[:, -1])))
257
+ quality_raw[abs(e_psf[:, -1] - np.nanmedian(e_psf[:, -1])) >= 3 * sigma] += 1
258
+ index_1 = np.where(np.array(source.quality) == 0)[0]
259
+ index_2 = np.where(quality_raw == 0)[0]
260
+ index = np.intersect1d(index_1, index_2)
261
+ if type(source) == Source_cut:
262
+ in_frame = np.where(np.invert(np.isnan(source.flux[0])))
263
+ x_left = np.min(in_frame[1]) - 0.5
264
+ x_right = source.size - np.max(in_frame[1]) + 0.5
265
+ y_left = np.min(in_frame[0]) - 0.5
266
+ y_right = source.size - np.max(in_frame[0]) + 0.5
267
+ else:
268
+ x_left = 1.5 if cut_x != 0 else -0.5
269
+ x_right = 2.5 if cut_x != 13 else 0.5
270
+ y_left = 1.5 if cut_y != 0 else -0.5
271
+ y_right = 2.5 if cut_y != 13 else 0.5
272
+
273
+ num_stars = np.array(source.gaia['tess_mag']).searchsorted(limit_mag, 'right')
274
+ x_aperture = source.gaia[f'sector_{source.sector}_x'] - np.maximum(0, x_round - 2)
275
+ y_aperture = source.gaia[f'sector_{source.sector}_y'] - np.maximum(0, y_round - 2)
276
+
277
+ start = 0
278
+ end = num_stars
279
+ if name is not None:
280
+ try:
281
+ start = int(np.where(source.gaia['DESIGNATION'] == 'Gaia DR3 ' +
282
+ str(source.tic['dr3_source_id'][np.where(source.tic['TIC'] == name)][0]))[0][0])
283
+ end = start + 1
284
+ except:
285
+ try:
286
+ start = int(np.where(source.gaia['DESIGNATION'] == name)[0][0])
287
+ end = start + 1
288
+ except IndexError:
289
+ print(
290
+ f'Target not found in the requested sector (Sector {sector}). This can be caused by a lack of Gaia '
291
+ f'ID or an incomplete TESS to Gaia crossmatch table. Please check whether the output light curve Gaia'
292
+ f' DR3 ID agrees with your target.')
293
+ start = 0
294
+ end = 1
295
+ for i in trange(start, end, desc='Fitting lc', disable=no_progress_bar):
296
+ if x_left <= x_round[i] < source.size - x_right and y_left <= y_round[i] < source.size - y_right:
297
+ if type(source) == Source:
298
+ x_left = 1.5
299
+ x_right = 2.5
300
+ y_left = 1.5
301
+ y_right = 2.5
302
+ if x_left + 2 <= x_round[i] < source.size - (x_right + 2) and y_left + 2 <= y_round[i] < source.size - (
303
+ y_right + 2):
304
+ near_edge = False
305
+ else:
306
+ near_edge = True
307
+
308
+ if type(prior) == float or type(prior) == np.float64:
309
+ aperture, psf_lc, star_y, star_x, portion = \
310
+ fit_lc_float_field(A, source, star_info=star_info, x=x_round, y=y_round, star_num=i, e_psf=e_psf,
311
+ near_edge=near_edge, prior=prior)
312
+ else:
313
+ aperture, psf_lc, star_y, star_x, portion = \
314
+ fit_lc(A, source, star_info=star_info, x=x_round[i], y=y_round[i], star_num=i, e_psf=e_psf,
315
+ near_edge=near_edge)
316
+ aper_lc = np.sum(
317
+ aperture[:, max(0, star_y - 1):min(5, star_y + 2), max(0, star_x - 1):min(5, star_x + 2)],
318
+ axis=(1, 2))
319
+ if source.sector < 27: # primary
320
+ exposure_time = 1800
321
+ elif source.sector < 56: # first extended
322
+ exposure_time = 600
323
+ else: # second extended
324
+ exposure_time = 200
325
+ saturated_arg_aper = np.where(aper_lc > 1e5 * 9 * 2e5 / exposure_time) # saturation is 2e5 e-
326
+ aper_lc[saturated_arg_aper] = np.nan
327
+ saturated_arg_psf = np.where(psf_lc > 1e5 * 9 * 2e5 / exposure_time)
328
+ psf_lc[saturated_arg_psf] = np.nan
329
+
330
+ local_bg, aper_lc, psf_lc, cal_aper_lc, cal_psf_lc = bg_mod(source, q=index, portion=portion,
331
+ psf_lc=psf_lc,
332
+ aper_lc=aper_lc,
333
+ near_edge=near_edge, star_num=i)
334
+ background_ = background[x_round[i] + source.size * y_round[i], :]
335
+ quality = np.zeros(len(source.time), dtype=np.int16)
336
+ sigma = 1.4826 * np.nanmedian(np.abs(background_ - np.nanmedian(background_)))
337
+ quality[abs(background_ - np.nanmedian(background_)) >= 5 * sigma] += 1
338
+ # if cut_x == 7:
339
+ # lc_directory = f'{local_directory}lc/{source.camera}-{source.ccd}_extra/'
340
+ # os.makedirs(lc_directory, exist_ok=True)
341
+ if np.isnan(aper_lc).all():
342
+ continue
343
+ else:
344
+ if type(source) == Source:
345
+ # if cut_x >= 7:
346
+ # lc_directory = f'{local_directory}lc/{source.camera}-{source.ccd}_extra/'
347
+ lc_output(source, local_directory=lc_directory, index=i,
348
+ tess_flag=source.quality, cut_x=cut_x, cut_y=cut_y, cadence=source.cadence,
349
+ aperture=aperture.astype(np.float32), star_y=y_round[i], star_x=x_round[i], tglc_flag=quality,
350
+ bg=background_, time=source.time, psf_lc=psf_lc, cal_psf_lc=cal_psf_lc, aper_lc=aper_lc,
351
+ cal_aper_lc=cal_aper_lc, local_bg=local_bg, x_aperture=x_aperture[i],
352
+ y_aperture=y_aperture[i], near_edge=near_edge, save_aper=save_aper, portion=portion,
353
+ prior=prior)
354
+ else:
355
+ lc_output(source, local_directory=lc_directory, index=i,
356
+ tess_flag=source.quality, cut_x=cut_x, cut_y=cut_y, cadence=source.cadence,
357
+ aperture=aperture.astype(np.float32), star_y=y_round[i], star_x=x_round[i],
358
+ tglc_flag=quality,
359
+ bg=background_, time=source.time, psf_lc=psf_lc, cal_psf_lc=cal_psf_lc, aper_lc=aper_lc,
360
+ cal_aper_lc=cal_aper_lc, local_bg=local_bg, x_aperture=x_aperture[i],
361
+ y_aperture=y_aperture[i], near_edge=near_edge, save_aper=save_aper, portion=portion,
362
+ prior=prior, transient=source.transient)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Te Han
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.1
2
+ Name: tglc
3
+ Version: 0.6.5
4
+ Summary: TESS-Gaia Light Curve
5
+ Home-page: https://github.com/TeHanHunter/TESS_Gaia_Light_Curve
6
+ Author: Te Han
7
+ Author-email: tehanhunter@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/x-rst
13
+ License-File: LICENSE
14
+ Requires-Dist: astropy >=5.1
15
+ Requires-Dist: astroquery
16
+ Requires-Dist: matplotlib
17
+ Requires-Dist: numpy
18
+ Requires-Dist: oauthlib
19
+ Requires-Dist: requests
20
+ Requires-Dist: scipy
21
+ Requires-Dist: threadpoolctl
22
+ Requires-Dist: tqdm
23
+ Requires-Dist: wheel
24
+ Requires-Dist: wotan
25
+
26
+ ==================================
27
+ Introduction
28
+ ==================================
29
+
30
+ TESS-Gaia Light Curve (`TGLC <https://archive.stsci.edu/hlsp/tglc>`_) is a dataset of TESS full-frame image light curves publicly available via the MAST portal. It is fitted with effective PSF and decontaminated with Gaia DR3 and achieved percent-level photometric precision down to 16th TESS magnitude! It unlocks astrophysics to a vast number of dim stars below 12th TESS magnitude. A package called tglc is pip-installable for customized light curve fits.
31
+
32
+ ==================================
33
+ Usage
34
+ ==================================
35
+ There are four fluxes in each FITS file: aperture flux, PSF flux, calibrated aperture flux, and calibrated PSF flux.
36
+ If you are uncertain which to use:
37
+
38
+ * Calibrated psf flux is better in **deblending** targets. Use this if you need to deblend a target near a variable source. The best deblending can be achieved with tglc package by setting a non-zero prior. It also gives the more accurate **transit depth** in most cases, especially when fitting with an optimized prior.
39
+ * Calibrated aperture flux usually has slightly **higher SNR**. The transit depth (or variation amplitude), however, can be imperfect since the normalization depends on the PSF fitting which is imperfect. This imperfection can be minimized by using a bigger aperture than the default aperture (3*3). One need to use the tglc package and set tglc_lc(save_aper=True) to access the 5*5 aperture. In the presence of a bright but "constant" contamination (several magnitudes brighter), the calibrated aperture flux is better in removing the constant contamination.
40
+ * The aperture flux and PSF flux are not detrended or normalized. Use this if you are doing stellar variability science with long baseline. Or, if the detrending is not optimal (default detrending has a window length of 1 day; see Known Problems below), start with the aperture flux or PSF flux and detrend carefully!
41
+ * **If you are uncertain, start with calibrated aperture flux!**
42
+
43
+ The `tutorial <tutorial/TGLC_tutorial.ipynb>`_ shows the syntaxes and differences among these light curves in several examples.
44
+
45
+ ==================================
46
+ Data Access
47
+ ==================================
48
+ There are three data access methods:
49
+
50
+ * MAST Portal: Easiest for acquiring light curves for a few stars. However, new sectors are updated relatively slowly.
51
+ * MAST bulk download: Best for downloading light curves for all stars (<16 TESS magnitude) in a sectors.
52
+ * tglc package: Capable of producing similar quality light curves for any sector and any star with custom options.
53
+
54
+ MAST Portal/bulk download
55
+ ----------------------------
56
+ The easiest usage requires no package installation. Simply follow the `TGLC HLSP page <https://archive.stsci.edu/hlsp/tglc>`_ to download light curves from MAST or use `MAST Portal <https://mast.stsci.edu/portal/Mashup/Clients/Mast/Portal.html>`_. Light curves are being fitted sector by sector and will be available on MAST gradually. MAST hosts all Gaia DR3 stars down to 16th magnitude. Each .fits file includes PSF and aperture light curves and their calibrated versions.
57
+
58
+ MAST available sectors: `sector worklist <https://docs.google.com/spreadsheets/d/1FhHElWb1wmx9asWiZecAJ2umN0-P_aXn55OBVB34_rg/edit?usp=sharing>`_
59
+
60
+
61
+ tglc package
62
+ ----------------------------
63
+ Users can also fit light curves using the package tglc. Using tglc, one can specify a region, sector(s), and customized aperture shape if needed. It can also allow all field stars to float by assigning Gaussian priors, which can help decontaminate variable field stars. tglc is currently only available for linux. Run::
64
+
65
+ pip install tglc
66
+
67
+ for the latest tglc release. After installation, follow the `tutorial <tutorial/TGLC_tutorial.ipynb>`_ to fit light curves. If there is a problem, please leave a comment in the Issues section to help us improve. Thank you!
68
+
69
+
70
+ ==================================
71
+ Known Problems
72
+ ==================================
73
+ There are several imperfections we noticed in the MAST TGLC light curves and tglc package:
74
+
75
+ * If the star is very dim (~< 15 Tmag) near a variable source, it can make the aperture and/or PSF light curve negative for some cadences. The detrending algorithm could malfunction and result in bad cal_aper_flux and/or cal_psf_flux. This is now fixed for tglc package, but this problem remains for the primary mission light curves published on MAST. Please detrend again if necessary. The extended mission light curves on MAST will not be affected. This is a very rare scenario, but could be important.
76
+
77
+ ==================================
78
+ Reference
79
+ ==================================
80
+ If you find the TGLC light curves or the tglc package useful in your research, please cite `our paper <https://iopscience.iop.org/article/10.3847/1538-3881/acaaa7>`_ published on the Astronomical Journal.