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/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ __version__ = "0.6.5"
2
+ __author__ = 'Te Han, Timothy Brandt'
3
+ __credits__ = 'University of California, Santa Barbara'
File without changes
Binary file
tglc/effective_psf.py ADDED
@@ -0,0 +1,484 @@
1
+ import numpy as np
2
+ from wotan import flatten
3
+ import tglc
4
+
5
+
6
+ def bilinear(x, y, repeat=1):
7
+ '''
8
+ A bilinear formula
9
+ np.array([1 - x - y + x * y, x - x * y, y - x * y, x * y] * repeat)
10
+ b, d = array[1]
11
+ a, c = array[0]
12
+ :param x: x
13
+ :param y: y
14
+ :param repeat: side length of epsf
15
+ :return: bilinear interpolation
16
+ '''
17
+ return np.array([1 - x - y + x * y, x - x * y, y - x * y, x * y] * repeat)
18
+
19
+
20
+ def get_psf(source, factor=2, psf_size=11, edge_compression=1e-4, c=np.array([0, 0, 0])):
21
+ """
22
+ Generate matrix for PSF fitting
23
+ :param source: tglc.ffi_cut.Source or tglc.ffi_cut.Source_cut, required
24
+ Source or Source_cut object
25
+ :param factor: int, optional
26
+ effective PSF oversampling factor
27
+ :param psf_size: int, optional
28
+ effective PSF side length
29
+ :param edge_compression: float, optional
30
+ parameter for edge compression
31
+ :param c: np.ndarray, optional
32
+ manual modification of Gaia positions in the format of [x, y, theta]
33
+ :return: A, star_info, over_size, x_round, y_round
34
+ A: 2d matrix for least_square
35
+ star_info: star parameters
36
+ over_size: size of oversampled grid of ePSF
37
+ x_round: star horizontal pixel coordinates rounded
38
+ y_round: star vertical pixel coordinates rounded
39
+ """
40
+ # even only
41
+ if factor % 2 != 0:
42
+ raise ValueError('Factor must be even.')
43
+ psf_size = psf_size
44
+ half_size = int((psf_size - 1) / 2)
45
+ over_size = psf_size * factor + 1
46
+ size = source.size # TODO: must be even?
47
+ flux_ratio = np.array(source.gaia['tess_flux_ratio'])
48
+ # flux_ratio = 0.9998 * flux_ratio + 0.0002
49
+ # x_shift = np.array(source.gaia[f'sector_{source.sector}_x'])
50
+ # y_shift = np.array(source.gaia[f'sector_{source.sector}_y'])
51
+
52
+ x_shift = np.array(source.gaia[f'sector_{source.sector}_x'])
53
+ y_shift = np.array(source.gaia[f'sector_{source.sector}_y'])
54
+
55
+ # x_shift = (x_ - c[0]) * np.cos(c[2]) - (y_ - c[1]) * np.sin(c[2])
56
+ # y_shift = (x_ - c[0]) * np.sin(c[2]) + (y_ - c[1]) * np.cos(c[2])
57
+
58
+ x_round = np.round(x_shift).astype(int)
59
+ y_round = np.round(y_shift).astype(int)
60
+
61
+ left = np.maximum(0, x_round - half_size)
62
+ right = np.minimum(size, x_round + half_size) + 1
63
+ down = np.maximum(0, y_round - half_size)
64
+ up = np.minimum(size, y_round + half_size) + 1
65
+ x_residual = x_shift % (1 / factor) * factor
66
+ y_residual = y_shift % (1 / factor) * factor
67
+
68
+ x_p = np.arange(size)
69
+ y_p = np.arange(size)
70
+ coord = np.arange(size ** 2).reshape(size, size)
71
+ xx, yy = np.meshgrid((np.arange(size) - (size - 1) / 2), (np.arange(size) - (size - 1) / 2))
72
+
73
+ if type(source) == tglc.ffi.Source:
74
+ bg_dof = 6
75
+ A = np.zeros((size ** 2, over_size ** 2 + bg_dof))
76
+ A[:, -1] = np.ones(size ** 2)
77
+ A[:, -2] = yy.flatten()
78
+ A[:, -3] = xx.flatten()
79
+ A[:, -4] = source.mask.data.flatten()
80
+ A[:, -5] = (source.mask.data * xx).flatten()
81
+ A[:, -6] = (source.mask.data * yy).flatten()
82
+ else:
83
+ bg_dof = 3
84
+ A = np.zeros((size ** 2, over_size ** 2 + bg_dof))
85
+ A[:, -1] = np.ones(size ** 2)
86
+ A[:, -2] = yy.flatten()
87
+ A[:, -3] = xx.flatten()
88
+ star_info = []
89
+ for i in range(len(source.gaia)):
90
+ # if i == 8:
91
+ # continue
92
+ x_psf = factor * (x_p[left[i]:right[i]] - x_round[i] + half_size) + (x_shift[i] % 1) // (1 / factor)
93
+ y_psf = factor * (y_p[down[i]:up[i]] - y_round[i] + half_size) + (y_shift[i] % 1) // (1 / factor)
94
+ x_psf, y_psf = np.meshgrid(x_psf, y_psf) # super slow here
95
+ a = np.array(x_psf + y_psf * over_size, dtype=np.int64).flatten()
96
+ index = coord[down[i]:up[i], left[i]:right[i]]
97
+ A[np.repeat(index, 4), np.array([a, a + 1, a + over_size, a + over_size + 1]).flatten(order='F')] += \
98
+ flux_ratio[i] * bilinear(x_residual[i], y_residual[i], repeat=len(a))
99
+ # star_info.append(
100
+ # (np.repeat(index, 4), np.array([a, a + 1, a + over_size, a + over_size + 1]).flatten(order='F'),
101
+ # flux_ratio[i] * bilinear(x_residual[i], y_residual[i], repeat=len(a))))
102
+ star_info.append(
103
+ (index, a, flux_ratio[i] * bilinear(x_residual[i], y_residual[i])))
104
+ coord_ = np.arange(- psf_size * factor / 2 + 1, psf_size * factor / 2 + 2)
105
+ x_coord, y_coord = np.meshgrid(coord_, coord_)
106
+ variance = psf_size
107
+ dist = (1 - np.exp(- 0.5 * (x_coord ** 4 + y_coord ** 4) / variance ** 4)) * edge_compression # 1e-3
108
+ A_mod = np.diag(dist.flatten())
109
+ A_mod = np.concatenate((A_mod, (np.zeros((over_size ** 2, bg_dof)))), axis=-1)
110
+ A = np.append(A, A_mod, axis=0)
111
+ return A, star_info, over_size, x_round, y_round
112
+
113
+
114
+ def fit_psf(A, source, over_size, power=0.8, time=0):
115
+ """
116
+ fit_psf using least_square (improved performance by changing to np.linalg.solve)
117
+ :param A: np.ndarray, required
118
+ 2d matrix for least_square
119
+ :param source: tglc.ffi_cut.Source or tglc.ffi_cut.Source_cut, required
120
+ Source or Source_cut object
121
+ :param over_size: int, required
122
+ size of oversampled grid of ePSF
123
+ :param power: float, optional
124
+ power for weighting bright stars' contribution to the fit. 1 means same contribution from all stars,
125
+ <1 means emphasizing dimmer stars
126
+ :param time: int, required
127
+ time index of this ePSF fit
128
+ :return: fit result
129
+ """
130
+ saturated_index = source.mask.mask.flatten()
131
+ flux = source.flux[time].flatten()
132
+ saturated_index[flux < 0.8 * np.nanmedian(flux)] = True
133
+
134
+ b = np.delete(flux, saturated_index)
135
+ scaler = np.abs(np.delete(flux, saturated_index)) ** power
136
+ b = np.append(b, np.zeros(over_size ** 2))
137
+ scaler = np.append(scaler, np.ones(over_size ** 2))
138
+
139
+ # fit = np.linalg.lstsq(A / scaler[:, np.newaxis], b / scaler, rcond=None)[0]
140
+ a = np.delete(A, np.where(saturated_index), 0) / scaler[:, np.newaxis]
141
+ b = b / scaler
142
+ alpha = np.dot(a.T, a)
143
+ beta = np.dot(a.T, b)
144
+ try:
145
+ fit = np.linalg.solve(alpha, beta)
146
+ except np.linalg.LinAlgError:
147
+ fit = np.full(np.shape(a)[1], np.nan)
148
+ # fit = np.linalg.lstsq(a, b, rcond=None)[0]
149
+ return fit
150
+
151
+
152
+ def fit_lc(A, source, star_info=None, x=0., y=0., star_num=0, factor=2, psf_size=11, e_psf=None, near_edge=False):
153
+ """
154
+ Produce matrix for least_square fitting without a certain target
155
+ :param A: np.ndarray, required
156
+ 2d matrix for least_square
157
+ :param source: tglc.ffi_cut.Source or tglc.ffi_cut.Source_cut, required
158
+ Source or Source_cut object
159
+ :param star_info: np.ndarray, required
160
+ star parameters
161
+ :param x: float, required
162
+ target horizontal pixel coordinate
163
+ :param y: float, required
164
+ target vertical pixel coordinate
165
+ :param star_num: int, required
166
+ target star index
167
+ :param factor: int, optional
168
+ effective PSF oversampling factor
169
+ :param psf_size: int, optional
170
+ effective PSF side length
171
+ :param e_psf: np.ndarray, required
172
+ effective PSF as a 3d array as a timeseries
173
+ :param near_edge: boolean, required
174
+ whether the star is 2 pixels or closer to the edge of a CCD
175
+ :return: aperture lightcurve, PSF lightcurve, vertical pixel coord, horizontal pixel coord, portion of light in aperture
176
+ """
177
+ over_size = psf_size * factor + 1
178
+ a = star_info[star_num][1]
179
+ star_info_num = (np.repeat(star_info[star_num][0], 4),
180
+ np.array([a, a + 1, a + over_size, a + over_size + 1]).flatten(order='F'),
181
+ np.tile(star_info[star_num][2], len(a)))
182
+ size = source.size # TODO: must be even?
183
+ # star_position = int(x + source.size * y - 5 * size - 5)
184
+ # aper_lc
185
+ cut_size = 5
186
+ in_frame = np.where(np.invert(np.isnan(source.flux[0])))
187
+ left = np.maximum(np.min(in_frame[1]), x - cut_size // 2)
188
+ right = np.minimum(np.max(in_frame[1]), x + cut_size // 2 + 1)
189
+ down = np.maximum(np.min(in_frame[0]), y - cut_size // 2)
190
+ up = np.minimum(np.max(in_frame[0]), y + cut_size // 2 + 1)
191
+ coord = np.arange(size ** 2).reshape(size, size)
192
+ index = np.array(coord[down:up, left:right]).flatten()
193
+ A_cut = np.zeros((len(index), np.shape(A)[1]))
194
+ for i in range(len(index)):
195
+ A_ = np.zeros(np.shape(A)[-1])
196
+ star_pos = np.where(star_info_num[0] == index[i])[0]
197
+ A_[star_info_num[1][star_pos]] = star_info_num[2][star_pos]
198
+ A_cut[i] = A[index[i], :] - A_
199
+ aperture = np.zeros((len(source.time), len(index)))
200
+ for j in range(len(source.time)):
201
+ aperture[j] = np.array(source.flux[j][down:up, left:right]).flatten() - np.dot(A_cut, e_psf[j])
202
+ aperture = aperture.reshape((len(source.time), up - down, right - left))
203
+ # np.save(f'_residual_{source.sector}.npy', aperture)
204
+
205
+ # psf_lc
206
+ over_size = psf_size * factor + 1
207
+ if near_edge: # TODO: near_edge
208
+ psf_lc = np.zeros(len(source.time))
209
+ psf_lc[:] = np.NaN
210
+ e_psf_1d = np.nanmedian(e_psf[:, :over_size ** 2], axis=0).reshape(over_size, over_size)
211
+ portion = (36 / 49) * np.nansum(e_psf_1d[8:15, 8:15]) / np.nansum(e_psf_1d) # only valid for factor = 2
212
+ return aperture, psf_lc, y - down, x - left, portion
213
+ left_ = left - x + 5
214
+ right_ = right - x + 5
215
+ down_ = down - y + 5
216
+ up_ = up - y + 5
217
+
218
+ left_11 = np.maximum(- x + 5, 0)
219
+ right_11 = np.minimum(size - x + 5, 11)
220
+ down_11 = np.maximum(- y + 5, 0)
221
+ up_11 = np.minimum(size - y + 5, 11)
222
+ coord = np.arange(psf_size ** 2).reshape(psf_size, psf_size)
223
+ index = coord[down_11:up_11, left_11:right_11]
224
+ if type(source) == tglc.ffi.Source:
225
+ bg_dof = 6
226
+ else:
227
+ bg_dof = 3
228
+ A = np.zeros((psf_size ** 2, over_size ** 2 + bg_dof))
229
+ A[np.repeat(index, 4), star_info_num[1]] = star_info_num[2]
230
+ psf_shape = np.dot(e_psf, A.T).reshape(len(source.time), psf_size, psf_size)
231
+ psf_sim = psf_shape[:, down_:up_, left_: right_]
232
+ # psf_sim = np.transpose(psf_shape[:, down_:up_, left_: right_], (0, 2, 1))
233
+
234
+ psf_lc = np.zeros(len(source.time))
235
+ A_ = np.zeros((cut_size ** 2, 4))
236
+ xx, yy = np.meshgrid((np.arange(cut_size) - (cut_size - 1) / 2),
237
+ (np.arange(cut_size) - (cut_size - 1) / 2))
238
+ A_[:, -1] = np.ones(cut_size ** 2)
239
+ A_[:, -2] = yy.flatten()
240
+ A_[:, -3] = xx.flatten()
241
+ edge_pixel = np.array([0, 1, 2, 3, 4, 5, 9, 10, 14, 15, 19, 20, 21, 22, 23, 24])
242
+ # edge_pixel = np.array([0, 1, 2, 3, 4, 5, 6,
243
+ # 7, 8, 9, 10, 11, 12, 13,
244
+ # 14, 15, 19, 20,
245
+ # 21, 22, 26, 27,
246
+ # 28, 29, 33, 34,
247
+ # 35, 36, 37, 38, 39, 40, 41,
248
+ # 42, 43, 44, 45, 46, 47, 48])
249
+ med_aperture = np.median(aperture, axis=0).flatten()
250
+ outliers = np.abs(med_aperture[edge_pixel] - np.nanmedian(med_aperture[edge_pixel])) > 1 * np.std(
251
+ med_aperture[edge_pixel])
252
+ epsf_sum = np.sum(np.nanmedian(psf_shape, axis=0))
253
+ for j in range(len(source.time)):
254
+ if np.isnan(psf_sim[j, :, :]).any():
255
+ psf_lc[j] = np.nan
256
+ else:
257
+ aper_flat = aperture[j, :, :].flatten()
258
+ A_[:, 0] = psf_sim[j, :, :].flatten() / epsf_sum
259
+ a = np.delete(A_, edge_pixel[outliers], 0)
260
+ aper_flat = np.delete(aper_flat, edge_pixel[outliers])
261
+ psf_lc[j] = np.linalg.lstsq(a, aper_flat)[0][0]
262
+ portion = np.nansum(psf_shape[:, 4:7, 4:7]) / np.nansum(psf_shape)
263
+ # print(np.nansum(psf_shape[:, 5, 5]) / np.nansum(psf_shape))
264
+ # np.save(f'toi-5344_psf_{source.sector}.npy', psf_shape)
265
+ return aperture, psf_lc, y - down, x - left, portion
266
+
267
+
268
+ def fit_lc_float_field(A, source, star_info=None, x=np.array([]), y=np.array([]), star_num=0, factor=2, psf_size=11,
269
+ e_psf=None, near_edge=False, prior=0.001):
270
+ """
271
+ Produce matrix for least_square fitting without a certain target
272
+ :param A: np.ndarray, required
273
+ 2d matrix for least_square
274
+ :param source: tglc.ffi_cut.Source or tglc.ffi_cut.Source_cut, required
275
+ Source or Source_cut object
276
+ :param star_info: np.ndarray, required
277
+ star parameters
278
+ :param x: float, required
279
+ target horizontal pixel coordinate
280
+ :param y: float, required
281
+ target vertical pixel coordinate
282
+ :param star_num: int, required
283
+ target star index
284
+ :param factor: int, optional
285
+ effective PSF oversampling factor
286
+ :param psf_size: int, optional
287
+ effective PSF side length
288
+ :param e_psf: np.ndarray, required
289
+ effective PSF as a 3d array as a timeseries
290
+ :param near_edge: boolean, required
291
+ whether the star is 2 pixels or closer to the edge of a CCD
292
+ :return: aperture lightcurve, PSF lightcurve, vertical pixel coord, horizontal pixel coord, portion of light in aperture
293
+ """
294
+ over_size = psf_size * factor + 1
295
+ a = star_info[star_num][1]
296
+ star_info_num = (np.repeat(star_info[star_num][0], 4),
297
+ np.array([a, a + 1, a + over_size, a + over_size + 1]).flatten(order='F'),
298
+ np.tile(star_info[star_num][2], len(a)))
299
+ size = source.size # TODO: must be even?
300
+ # star_position = int(x + source.size * y - 5 * size - 5)
301
+ # aper_lc
302
+ cut_size = 5
303
+ in_frame = np.where(np.invert(np.isnan(source.flux[0])))
304
+ left = np.maximum(np.min(in_frame[1]), x[star_num] - cut_size // 2)
305
+ right = np.minimum(np.max(in_frame[1]), x[star_num] + cut_size // 2 + 1)
306
+ down = np.maximum(np.min(in_frame[0]), y[star_num] - cut_size // 2)
307
+ up = np.minimum(np.max(in_frame[0]), y[star_num] + cut_size // 2 + 1)
308
+ coord = np.arange(size ** 2).reshape(size, size)
309
+ index = np.array(coord[down:up, left:right]).flatten()
310
+ A_cut = np.zeros((len(index), np.shape(A)[1]))
311
+ for i in range(len(index)):
312
+ A_ = np.zeros(np.shape(A)[-1])
313
+ star_pos = np.where(star_info_num[0] == index[i])[0]
314
+ A_[star_info_num[1][star_pos]] = star_info_num[2][star_pos]
315
+ A_cut[i] = A[index[i], :] - A_
316
+ aperture = np.zeros((len(source.time), len(index)))
317
+ for j in range(len(source.time)):
318
+ aperture[j] = np.array(source.flux[j][down:up, left:right]).flatten() - np.dot(A_cut, e_psf[j])
319
+ aperture = aperture.reshape((len(source.time), up - down, right - left))
320
+
321
+ # psf_lc
322
+ over_size = psf_size * factor + 1
323
+ if near_edge: # TODO: near_edge
324
+ psf_lc = np.zeros(len(source.time))
325
+ psf_lc[:] = np.NaN
326
+ e_psf_1d = np.nanmedian(e_psf[:, :over_size ** 2], axis=0).reshape(over_size, over_size)
327
+ portion = (36 / 49) * np.nansum(e_psf_1d[8:15, 8:15]) / np.nansum(e_psf_1d) # only valid for factor = 2
328
+ return aperture, psf_lc, y[star_num] - down, x[star_num] - left, portion
329
+ # left_ = left - x[star_num] + 5
330
+ # right_ = right - x[star_num] + 5
331
+ # down_ = down - y[star_num] + 5
332
+ # up_ = up - y[star_num] + 5
333
+ if type(source) == tglc.ffi.Source:
334
+ bg_dof = 6
335
+ else:
336
+ bg_dof = 3
337
+ field_star_num = []
338
+ for j in range(len(source.gaia)):
339
+ if np.abs(x[j] - x[star_num]) < 5 and np.abs(y[j] - y[star_num]) < 5:
340
+ field_star_num.append(j)
341
+
342
+ psf_lc = np.zeros(len(source.time))
343
+ A_ = np.zeros((cut_size ** 2 + len(field_star_num), len(field_star_num) + 3))
344
+ xx, yy = np.meshgrid((np.arange(cut_size) - (cut_size - 1) / 2),
345
+ (np.arange(cut_size) - (cut_size - 1) / 2))
346
+ A_[:(cut_size ** 2), -1] = np.ones(cut_size ** 2)
347
+ A_[:(cut_size ** 2), -2] = yy.flatten()
348
+ A_[:(cut_size ** 2), -3] = xx.flatten()
349
+ psf_sim = np.zeros((len(source.time), 11 ** 2 + len(field_star_num), len(field_star_num)))
350
+ for j, star in enumerate(field_star_num):
351
+ a = star_info[star][1]
352
+ star_info_star = (np.repeat(star_info[star][0], 4),
353
+ np.array([a, a + 1, a + over_size, a + over_size + 1]).flatten(order='F'),
354
+ np.tile(star_info[star][2], len(a)))
355
+ delta_x = x[star_num] - x[star]
356
+ delta_y = y[star_num] - y[star]
357
+ # for psf_sim
358
+ left_shift = np.maximum(delta_x, 0)
359
+ right_shift = np.minimum(11 + delta_x, 11)
360
+ down_shift = np.maximum(delta_y, 0)
361
+ up_shift = np.minimum(11 + delta_y, 11)
362
+ # for psf_shape
363
+ left_shift_ = np.maximum(-delta_x, 0)
364
+ right_shift_ = np.minimum(11 - delta_x, 11)
365
+ down_shift_ = np.maximum(-delta_y, 0)
366
+ up_shift_ = np.minimum(11 - delta_y, 11)
367
+
368
+ left_11 = np.maximum(- x[star] + 5, 0)
369
+ right_11 = np.minimum(size - x[star] + 5, 11)
370
+ down_11 = np.maximum(- y[star] + 5, 0)
371
+ up_11 = np.minimum(size - y[star] + 5, 11)
372
+
373
+ coord = np.arange(psf_size ** 2).reshape(psf_size, psf_size)
374
+ index = coord[down_11:up_11, left_11:right_11]
375
+ A = np.zeros((psf_size ** 2, over_size ** 2 + bg_dof))
376
+ A[np.repeat(index, 4), star_info_star[1]] = star_info_star[2]
377
+ psf_shape = np.dot(e_psf, A.T).reshape(len(source.time), psf_size, psf_size)
378
+ epsf_sum = np.sum(np.nanmedian(psf_shape, axis=0))
379
+ psf_sim_index = coord[down_shift:up_shift, left_shift:right_shift].flatten()
380
+ psf_sim[:, psf_sim_index, j] = psf_shape[:, down_shift_:up_shift_, left_shift_:right_shift_].reshape(
381
+ len(source.time), -1) / epsf_sum
382
+ if star != star_num:
383
+ psf_sim[:, 11 ** 2 + j, j] = np.ones(len(source.time)) / (
384
+ prior * 1.5e4 * 10 ** ((10 - source.gaia[star]['tess_mag']) / 2.5))
385
+ else:
386
+ portion = np.nansum(psf_shape[:, 4:7, 4:7]) / np.nansum(psf_shape)
387
+
388
+ star_index = np.where(np.array(field_star_num) == star_num)[0]
389
+ field_star = psf_sim[0, np.arange(11 ** 2).reshape(11, 11)[3:8, 3:8], :].reshape(cut_size ** 2,
390
+ len(field_star_num)) * \
391
+ source.gaia['tess_flux_ratio'][field_star_num]
392
+ field_star[:, star_index] = 0
393
+ for j in range(len(source.time)):
394
+ if np.isnan(psf_sim[j, :, :]).any():
395
+ psf_lc[j] = np.nan
396
+ else:
397
+ aper_flat = aperture[j, :, :].flatten()
398
+ aper_flat = np.append(aper_flat, np.zeros(len(field_star_num) - 1)) # / prior
399
+ aper_flat[cut_size ** 2 + star_index] = 0
400
+ postcards = psf_sim[j, np.arange(11 ** 2).reshape(11, 11)[3:8, 3:8], :].reshape(cut_size ** 2,
401
+ len(field_star_num))
402
+ A_[:cut_size ** 2, :len(field_star_num)] = postcards
403
+ field_star = postcards * source.gaia['tess_flux_ratio'][field_star_num]
404
+ field_star[:, star_index] = 0
405
+ # A_[:(cut_size ** 2), -4] = np.sum(field_star, axis=1)
406
+ A_[cut_size ** 2:, :len(field_star_num)] = psf_sim[j, 11 ** 2:, :].reshape(len(field_star_num),
407
+ len(field_star_num))
408
+ a = np.delete(A_, cut_size ** 2 + star_index, 0)
409
+ psf_lc[j] = np.linalg.lstsq(a, aper_flat)[0][star_index]
410
+ return aperture, psf_lc, y[star_num] - down, x[star_num] - left, portion
411
+
412
+
413
+ def bg_mod(source, q=None, aper_lc=None, psf_lc=None, portion=None, star_num=0, near_edge=False):
414
+ '''
415
+ background modification
416
+ :param source: tglc.ffi_cut.Source or tglc.ffi_cut.Source_cut, required
417
+ Source or Source_cut object
418
+ :param q: list, optional
419
+ list of booleans that filter the data points
420
+ :param aper_lc: np.ndarray, required
421
+ aperture light curve
422
+ :param psf_lc: np.ndarray, required
423
+ PSF light curve
424
+ :param portion: float, required
425
+ portion of light in aperture
426
+ :param star_num: int, required,
427
+ star index
428
+ :param near_edge: boolean, required
429
+ whether the star is 2 pixels or closer to the edge of a CCD
430
+ :return: local background, modified aperture light curve, modified PSF light curve
431
+ '''
432
+ bar = 15000 * 10 ** ((source.gaia['tess_mag'][star_num] - 10) / -2.5)
433
+ # print(bar)
434
+ # med_epsf = np.nanmedian(e_psf[:, :23 ** 2].reshape(len(source.time), 23, 23), axis=0)
435
+ # centroid_to_aper_ratio = 4/9 * np.sum(med_epsf[10:13, 10:13]) / np.sum(med_epsf)
436
+ # centroid_to_aper_ratio = np.nanmedian(ratio)
437
+ # flux_bar = aperture_bar * centroid_to_aper_ratio
438
+ # lightcurve = lightcurve + (flux_bar - np.nanmedian(lightcurve[q]))
439
+ aperture_bar = bar * portion
440
+ # print(bar)
441
+ local_bg = np.nanmedian(aper_lc[q]) - aperture_bar
442
+ if np.isnan(local_bg):
443
+ local_bg = 0
444
+ aper_lc = aper_lc - local_bg
445
+ psf_bar = bar
446
+ local_bg = np.nanmedian(psf_lc[q]) - psf_bar
447
+ if np.isnan(local_bg):
448
+ local_bg = 0
449
+ psf_lc = psf_lc - local_bg
450
+ negative_arg_aper = np.where(aper_lc <= 0) # Negative frames
451
+ aper_lc[negative_arg_aper] = np.nan
452
+ negative_arg_psf = np.where(psf_lc <= 0)
453
+ psf_lc[negative_arg_psf] = np.nan
454
+ # removes very large outliers to prevent wotan to freeze
455
+ cal_aper_lc = aper_lc / np.nanmedian(aper_lc)
456
+ cal_aper_lc[np.where(cal_aper_lc > 100)] = np.nan
457
+ if np.isnan(cal_aper_lc).all():
458
+ print('Calibrated aperture flux are not accessible or processed incorrectly. ')
459
+ else:
460
+ _, trend = flatten(source.time, cal_aper_lc - np.nanmin(cal_aper_lc) + 1000,
461
+ window_length=1, method='biweight', return_trend=True)
462
+ cal_aper_lc = (cal_aper_lc - np.nanmin(cal_aper_lc) + 1000 - trend) / np.nanmedian(cal_aper_lc) + 1
463
+ # cal_aper_lc = flatten(source.time, cal_aper_lc, window_length=1, method='biweight',
464
+ # return_trend=False)
465
+ if near_edge:
466
+ cal_psf_lc = psf_lc
467
+ return local_bg, aper_lc, psf_lc, cal_aper_lc, cal_psf_lc
468
+ else:
469
+ cal_psf_lc = psf_lc / np.nanmedian(psf_lc)
470
+ cal_psf_lc[np.where(cal_psf_lc > 100)] = np.nan
471
+ if np.isnan(cal_psf_lc).all():
472
+ print('Calibrated PSF flux are not accessible or processed incorrectly. ')
473
+ else:
474
+ _, trend = flatten(source.time, cal_psf_lc - np.nanmin(cal_psf_lc) + 1000,
475
+ window_length=1, method='biweight', return_trend=True)
476
+ cal_psf_lc = (cal_psf_lc - np.nanmin(cal_psf_lc) + 1000 - trend) / np.nanmedian(cal_psf_lc) + 1
477
+ # cal_psf_lc = flatten(source.time, cal_psf_lc, window_length=1, method='biweight',
478
+ # return_trend=False)
479
+ # aper_mad = 1.4826 * np.nanmedian(np.abs(cal_aper_lc - 1))
480
+ # if aper_mad > 0.02:
481
+ # psf_mad = 1.4826 * np.nanmedian(np.abs(cal_psf_lc - 1))
482
+ # cal_psf_lc /= psf_mad / aper_mad
483
+ # cal_psf_lc += 1 - np.median(cal_psf_lc)
484
+ return local_bg, aper_lc, psf_lc, cal_aper_lc, cal_psf_lc