AstroToolkit 1.0.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.
@@ -0,0 +1,847 @@
1
+ """
2
+ This is a python GUI program to display and analyse a time series. It uses routines developed by
3
+ Alex Schwarzenberg-Czerny to compute periodograms and pre-whitening (and alternatively a Fourier transform
4
+ routine that is included).
5
+
6
+ It can either by called using a command line interface or as a function call:
7
+ The function call (display_light_curve) is completely general and takes an astropy timeseries
8
+ object as its parameter (with specific metadata - see function definition)
9
+
10
+ The command line is designed to process generic data and accepts 2 parameters (filename and path);
11
+ if these are not provided a dialog is initiated to select the values. The last path and format
12
+ used are saved in a .ini file located in the users home directory.
13
+
14
+ In addition there is a test capability that generates a synthetic light curve of two sine waves plus noise.
15
+ This is initiated by changing the value of the variable "test" to 1 and then
16
+ running the program.
17
+
18
+ Prerequisites:
19
+ This is python code using standard modules that
20
+ are included in the Anaconda distribution.
21
+
22
+ It requires the implementation of the "Python GUI for F95 AOV periodogram routines"
23
+ (https://users.camk.edu.pl/alex/) and calls the pyaov package which is included.
24
+ This code has been designed to be portable and should work on
25
+ Linux, Windows and Mac. It has been tested using Python 3.8 under
26
+ Ubuntu 18.04 and also under Windows 10.
27
+
28
+ Coding notes
29
+ This code is based on the QT (version QT5) widget toolkit
30
+ (https://doc.qt.io/qtforpython/). This has been chosen rather than
31
+ Tkinter because it is more portable and provides improved functionality
32
+ (https://dev.to/amigosmaker/python-gui-pyqt-vs-tkinter-5hdd)
33
+ A useful book is https://pythoncourses.gumroad.com/l/pysqtsamples
34
+
35
+ Inevitably the structure of the program is driven by the GUI. display_light_curve
36
+ initiates an instance of Window which will initiate an instance of Window1. Window1
37
+ can initiate an instance of Window2 which in turn can initiate an instance of Window3.
38
+
39
+
40
+
41
+
42
+
43
+
44
+ Change history
45
+
46
+ 20/10/2022 Keith Inight Initial version
47
+
48
+
49
+ """
50
+ import sys
51
+ import os
52
+ from PyQt5.QtWidgets import QDialog, QApplication, QVBoxLayout, QHBoxLayout,\
53
+ QPushButton, QFileDialog, QSpinBox, QLabel, QCheckBox, QButtonGroup, QRadioButton
54
+ from PyQt5.QtCore import QCoreApplication, Qt
55
+ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
56
+ from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
57
+ from matplotlib import gridspec
58
+ import matplotlib.pyplot as plt
59
+ import numpy as np
60
+ from numpy.polynomial import Chebyshev
61
+ from astropy.convolution import convolve, Box1DKernel
62
+ from sklearn.gaussian_process import GaussianProcessRegressor
63
+ from sklearn.gaussian_process.kernels import RBF, ConstantKernel as C, WhiteKernel as W
64
+ from sklearn.exceptions import ConvergenceWarning
65
+ from scipy import optimize
66
+ from warnings import simplefilter
67
+ from numba import jit
68
+ import pprint
69
+ from ..Timeseries import pyaov
70
+ from bokeh.plotting import figure
71
+
72
+ pp = pprint.PrettyPrinter(width=130)
73
+
74
+ @jit(nopython=True)
75
+ def fourier_transform(nout, fmin, fmax, times, mags):
76
+ """
77
+ Purpose
78
+ To compute the Fourier transform (power curve) of a set of data.
79
+
80
+ Based on algorithm - https://ui.adsabs.harvard.edu/abs/1975Ap%26SS..36..137D/abstract
81
+ Parameters
82
+
83
+ nout: int Number of frequency bins in output
84
+ fmin : float The minimum frequency of the frequency bins ( the frequency
85
+ unit is the inverse of the unit of time)
86
+ fmax : float The maximum frequency of the frequency bins ( the frequency
87
+ unit is the inverse of the unit of time)
88
+ times: np.ndarray of float values of time variable
89
+ mags: np.ndarray of float values of magnitude variable
90
+
91
+ Returns
92
+ A list of 4 objects:-
93
+ freqs np.ndarray of float value of frequency for each bin
94
+ periods np.ndarray of float value of period for each bin (in same unit as times)
95
+ FF np.ndarray of float amplitude squared for each bin
96
+ GG np.ndarray of powers normalized amplitude squared for each bin
97
+ History
98
+ 11/2/20 Keith Inight. Initial version
99
+ """
100
+
101
+ ndata = len(times)
102
+ fr = np.zeros(nout)
103
+ fi = np.zeros(nout)
104
+ D = np.zeros(nout)
105
+ G = np.zeros(nout)
106
+ FF = np.zeros(nout)
107
+ GG = np.zeros(nout)
108
+ freqs = np.linspace(fmin, fmax, nout)
109
+ periods = 1/freqs
110
+ angular_freqs = (2*np.pi*freqs)
111
+ for k in range(nout):
112
+ for i in range(ndata):
113
+ A = angular_freqs[k]*times[i]
114
+ c = np.cos(A)
115
+ s = np.sin(A)
116
+ fr[k] += mags[i]*c
117
+ fi[k] += mags[i]*s
118
+ D[k] += c
119
+ G[k] += s
120
+ FF[k] = (fr[k]*fr[k]+fi[k]*fi[k])/(ndata**2)
121
+ GG[k] = (D[k]*D[k]+G[k]*G[k])/(ndata**2)
122
+ return [freqs, periods, FF, GG]
123
+
124
+
125
+ class Window3(QDialog):
126
+ def __init__(self, parent=None, ts_detrended=[]):
127
+
128
+ super(Window3, self).__init__(parent)
129
+ self.ts_detrended = ts_detrended
130
+ self.fpeak = self.ts_detrended.meta['fpeak']
131
+ self.fpeak_err = self.ts_detrended.meta['fpeak_err']
132
+ self.setWindowTitle("{} ".format(
133
+ self.ts_detrended.meta['object_name']))
134
+ # a figure instance to plot on
135
+ self.figure = plt.figure(dpi=80)
136
+ self.setGeometry(200, 200, 1000, 600)
137
+ # this is the Canvas Widget that displays the `figure`
138
+ # it takes the `figure` instance as a parameter to __init__
139
+
140
+ self.canvas3 = FigureCanvas(self.figure)
141
+
142
+ self.toolbar3 = NavigationToolbar(self.canvas3, self)
143
+
144
+ period_fold = 1/self.fpeak
145
+
146
+ self.phase = self.ts_detrended.time.value/period_fold - \
147
+ (self.ts_detrended.time.value/period_fold).astype('int')
148
+ if len(self.ts_detrended['y3']) > 50:
149
+ bins = 30
150
+ else:
151
+ bins = 10
152
+ np.linspace(0, 1-1/bins, num=bins)
153
+ self.bin_phase = []
154
+ self.bin_y = []
155
+ self.bin_y_err = []
156
+ for bphase in np.linspace(0, 1-1/bins, num=bins):
157
+ mask = (self.phase < bphase+1/bins) & (self.phase >= bphase)
158
+ if len(self.ts_detrended['y3'][mask]) > 0:
159
+ self.bin_phase += [bphase+0.5/bins]
160
+ baverage, norm = np.average(self.ts_detrended['y3'][mask].value, weights=(
161
+ 1/self.ts_detrended['y_err'][mask].value)**2, returned=True)
162
+ self.bin_y += [baverage]
163
+ self.bin_y_err += [1/np.sqrt(norm)]
164
+ self.plot()
165
+
166
+ # set the layout
167
+ layout3 = QVBoxLayout()
168
+ layout3.addWidget(self.toolbar3)
169
+ layout3.addWidget(self.canvas3)
170
+ self.setLayout(layout3)
171
+
172
+ def closeEvent(self, event):
173
+ plt.close()
174
+ super(Window3, self).closeEvent(event)
175
+
176
+ def plot(self):
177
+ self.figure.clear()
178
+ gs = gridspec.GridSpec(2, 2, height_ratios=[4, 1])
179
+ y3 = self.ts_detrended['y3'].value
180
+ err = self.ts_detrended['y_err'].value
181
+
182
+ # create an axis
183
+ ax = plt.subplot(gs[0, 0])
184
+ # plot data
185
+ ax.errorbar(np.concatenate((self.phase, self.phase+1)),
186
+ np.concatenate((y3, y3)), yerr=np.concatenate((err, err)), fmt='.', ecolor='grey', elinewidth=.3)
187
+
188
+ ax.set_title('f = {:.6f}'.format(self.fpeak)+' d$^{-1}$' +
189
+ ' P={:.6f} $\pm$ {:.8f}'.format(24/self.fpeak, 24*self.fpeak_err/((self.fpeak)**2)) +
190
+ ' h ({:.6f} $\pm$ {:.10f} days)'.format(1/self.fpeak, self.fpeak_err/(self.fpeak**2)))
191
+
192
+ ax.set_xlabel('Phase')
193
+ ax.set_ylabel(str(self.ts_detrended['y3'].unit))
194
+ ymin, ymax = ax.get_ylim()
195
+ ax.set_ylim(ymax, ymin)
196
+ # refresh canvas
197
+ ax1 = plt.subplot(gs[0, 1])
198
+ bp = np.array(self.bin_phase)
199
+ ax1.errorbar(np.concatenate([bp, bp+1]),
200
+ self.bin_y + self.bin_y,
201
+ self.bin_y_err + self.bin_y_err, fmt='.', ecolor='grey', elinewidth=.3)
202
+
203
+ ymin, ymax = ax1.get_ylim()
204
+ ax1.set_ylim(ymax, ymin)
205
+ ax1.set_title('Binned plot')
206
+ ax1.set_xlabel('Phase')
207
+ ax2 = plt.subplot(gs[1, 0:2])
208
+ ax2.text(0, 0.95, pp.pformat({i: self.ts_detrended.meta[i] for i in self.ts_detrended.meta if i in
209
+ ['frange', 'crop', 'prewhiten', 'trend', 'Gaussian_Process_regression', 'method','peak_type', 'nh2', 'fpeak']}), va='top')
210
+ ax2.axis('off')
211
+
212
+ self.canvas3.draw()
213
+
214
+
215
+ class Window2(QDialog):
216
+ def __init__(self, parent=None, ts_detrended=None):
217
+
218
+ super(Window2, self).__init__(parent)
219
+
220
+ self.ts_detrended = ts_detrended
221
+ self.setWindowTitle("{} ".format(ts_detrended.meta['object_name']))
222
+ # a figure instance to plot on
223
+ self.figure = plt.figure(dpi=80)
224
+ self.setGeometry(150, 150, 800, 400)
225
+ # this is the Canvas Widget that displays the `figure`
226
+ # it takes the `figure` instance as a parameter to __init__
227
+ self.canvas1 = FigureCanvas(self.figure)
228
+ # this is the Navigation widget
229
+ # it takes the Canvas widget and a parent
230
+ self.toolbar1 = NavigationToolbar(self.canvas1, self)
231
+ self.times = self.ts_detrended.time.value-self.ts_detrended.time.value.min()
232
+ self.y = self.ts_detrended['y3'].value
233
+ self.y_err = self.ts_detrended['y_err'].value
234
+ method = self.ts_detrended.meta['method']
235
+ self.nh2 = self.ts_detrended.meta['nh2']
236
+ self.binsize = self.ts_detrended.meta['binsize']
237
+
238
+ fmin, fmax, fbins = self.ts_detrended.meta['frange'][-1]
239
+
240
+
241
+ if method == "amhw":
242
+ self.per, self.freq, self.fpeak = pyaov.amhw(
243
+ self.times, self.y, self.y_err, fmax, (fmax-fmin)/fbins, fr0=fmin, nh2=self.nh2)
244
+
245
+ elif method == "pspw":
246
+
247
+ self.per, self.freq, self.fpeak = pyaov.pspw(
248
+ self.times, self.y, self.y_err, fmax, (fmax-fmin)/fbins, fr0=fmin)
249
+
250
+ elif method == "atrw":
251
+
252
+ self.per, self.freq, self.fpeak = pyaov.atrw(
253
+ self.times, self.y, self.y_err, fmax, (fmax-fmin)/fbins, fr0=fmin, nh2=self.nh2)
254
+
255
+ elif method == "aovw":
256
+
257
+ self.per, self.freq, self.fpeak = pyaov.aovw(
258
+ self.times, self.y, self.y_err, fmax, (fmax-fmin)/fbins, fr0=fmin, nh2=self.nh2)
259
+
260
+ elif method == "f_mw":
261
+
262
+ self.per, self.freq, self.fpeak = pyaov.f_mw(
263
+ self.times, self.y, self.y_err, fmax, (fmax-fmin)/fbins, fr0=fmin)
264
+
265
+ elif method == "lomw":
266
+
267
+ self.per, self.freq, self.fpeak = pyaov.lomw(
268
+ self.times, self.y, self.y_err, fmax, (fmax-fmin)/fbins, fr0=fmin)
269
+
270
+ else:
271
+ print("Invalid method ", method)
272
+ raise ValueError("Invalid method "+str(method))
273
+
274
+ self.plot(self.freq, self.per, title=method)
275
+
276
+ self.Fold_button = QPushButton("Fold")
277
+ self.Fold_button.clicked.connect(self.Phase_fold)
278
+
279
+ self.Fold_half_button = QPushButton("Fold "+"\u00BD"+"f")
280
+ self.Fold_half_button.clicked.connect(self.Phase_fold_half)
281
+
282
+ self.Recalculate_button = QPushButton("Recalculate")
283
+ self.Recalculate_button.clicked.connect(self.Recalculate)
284
+
285
+ self.Prewhiten_button = QPushButton("Prewhiten")
286
+ self.Prewhiten_button.clicked.connect(self.Prewhiten)
287
+
288
+ self.row_layout = QHBoxLayout()
289
+ self.row_layout.addWidget(self.toolbar1)
290
+ self.row_layout.addWidget(self.Fold_button)
291
+ self.row_layout.addWidget(self.Fold_half_button)
292
+ self.row_layout.addWidget(self.Recalculate_button)
293
+ self.row_layout.addWidget(self.Prewhiten_button)
294
+ # set the layout
295
+ layout1 = QVBoxLayout()
296
+ layout1.addLayout(self.row_layout)
297
+ layout1.addWidget(self.canvas1)
298
+ self.setLayout(layout1)
299
+
300
+ def closeEvent(self, event):
301
+
302
+ plt.close()
303
+ super(Window2, self).closeEvent(event)
304
+
305
+ def Phase_fold(self):
306
+ def sine_fit(x, a, b, c):
307
+ return a * np.sin(b * x+c)
308
+ axes = self.canvas1.figure.axes[0]
309
+ freq_lo, freq_hi = axes.get_xlim()
310
+
311
+ mask = (self.freq > freq_lo) & (self.freq < freq_hi)
312
+ if (self.fpeak > freq_lo) & (self.fpeak< freq_hi):
313
+ self.ts_detrended.meta['fpeak']=self.fpeak
314
+ else:
315
+ self.ts_detrended.meta['fpeak'] = self.freq[mask][self.per[mask].argmax()] # seed value
316
+ if self.ts_detrended.meta['peak_type']=='Parabola':
317
+ xm, fm, dx = pyaov.peak(self.per[mask])
318
+
319
+ self.ts_detrended.meta['fpeak'] = self.freq[mask][0]+xm*self.binsize
320
+ self.ts_detrended.meta['fpeak_err'] = dx*self.binsize
321
+ else:
322
+ print("OTHER")
323
+
324
+ try:
325
+ print("fpeak",self.ts_detrended.meta['fpeak'])
326
+ self.params, self.params_covariance = optimize.curve_fit(sine_fit, self.ts_detrended.time.value, self.ts_detrended['y3'].value,
327
+ p0=[1, 2*np.pi*self.ts_detrended.meta['fpeak'], 0])
328
+ print(self.params)
329
+ print(self.params_covariance)
330
+ except:
331
+ print('failed')
332
+ self.params = np.array([0, 2*np.pi*self.ts_detrended.meta['fpeak'], 0])
333
+ self.params_covariance = np.array(
334
+ [[0, 0, 0], [0, 0, 0], [0, 0, 0]])
335
+
336
+
337
+ self.ts_detrended.meta['fpeak'] = self.params[1]/(2*np.pi)
338
+ self.ts_detrended.meta['fpeak_err'] = np.sqrt(self.params_covariance[1,1])/(2*np.pi)
339
+ print("output",self.ts_detrended.meta['fpeak'])
340
+
341
+ main3 = Window3(self, ts_detrended=self.ts_detrended)
342
+ main3.show()
343
+
344
+ def Phase_fold_half(self):
345
+
346
+ axes = self.canvas1.figure.axes[0]
347
+ freq_lo, freq_hi = axes.get_xlim()
348
+ mask = (self.freq > freq_lo) & (self.freq < freq_hi)
349
+ xm, fm, dx = pyaov.peak(self.per[mask])
350
+ self.ts_detrended.meta['fpeak'] = (
351
+ self.freq[mask][0]+xm*self.binsize)*0.5
352
+ self.ts_detrended.meta['fpeak_err'] = dx*self.binsize
353
+ main3 = Window3(self, ts_detrended=self.ts_detrended)
354
+ main3.show()
355
+
356
+ def Recalculate(self):
357
+
358
+ axes = self.canvas1.figure.axes[0]
359
+ freq_lo, freq_hi = axes.get_xlim()
360
+ fbins = self.ts_detrended.meta['frange'][-1][2]
361
+
362
+ self.ts_detrended.meta['frange'] += [[freq_lo, freq_hi, fbins]]
363
+
364
+ self.ts_detrended.meta['binsize'] = (freq_hi -
365
+ freq_lo)/fbins
366
+
367
+ main2a = Window2(self, ts_detrended=self.ts_detrended)
368
+ main2a.show()
369
+
370
+ def Prewhiten(self):
371
+ axes = self.canvas1.figure.axes[0]
372
+ freq_lo, freq_hi = axes.get_xlim()
373
+ per_lo, per_hi = axes.get_ylim()
374
+ mask = (self.freq > freq_lo) & (self.freq < freq_hi) & (
375
+ self.per > per_lo) & (self.per < per_hi)
376
+
377
+ xm, fm, dx = pyaov.peak(self.per[mask])
378
+
379
+ foundfreq = self.freq[mask][0]+xm*self.binsize
380
+ # already got this frequency in the list
381
+
382
+ frout,dfrout,valout=pyaov.prew(self.times, self.y, self.y_err, foundfreq, nh2=11)
383
+ print('frout={} dfrout={}'.format(frout,dfrout))
384
+
385
+ if frout in self.ts_detrended.meta['prewhiten']:
386
+ return
387
+
388
+ self.ts_detrended.meta['fpeak'] =frout
389
+
390
+ self.ts_prewhite = self.ts_detrended
391
+
392
+ self.ts_prewhite.meta['prewhiten'] += [foundfreq]
393
+ print(self.ts_prewhite.meta['prewhiten'])
394
+
395
+ self.ts_prewhite['y3'] = valout*self.ts_detrended['y3'].unit
396
+
397
+ self.ts_prewhite.meta['frange'] = [self.ts_detrended.meta['frange'][0]]
398
+ main2b = Window2(self, ts_detrended=self.ts_prewhite)
399
+ main2b.show()
400
+
401
+ def plot(self, freq, per, title=''):
402
+
403
+ self.figure.clear()
404
+
405
+ # create an axis
406
+ ax = self.figure.add_subplot(111)
407
+ # plot data
408
+ ax.plot(freq, per, '-', linewidth=0.5)
409
+ ax.set_xlabel('Frequency(1/D)')
410
+ ax.set_ylabel('Power')
411
+ ax.set_title(title)
412
+ # refresh canvas
413
+ self.canvas1.draw()
414
+
415
+
416
+ class Window1(QDialog):
417
+ def __init__(self, parent=None, ts_cropped=None):
418
+ super(Window1, self).__init__(parent)
419
+ self.ts_cropped = ts_cropped
420
+ self.setWindowTitle("{} ".format(
421
+ str(self.ts_cropped.meta['object_name'])))
422
+ # a figure instance to plot on
423
+ # self.ts_cropped.sort('time')
424
+
425
+ self.figure = plt.figure(dpi=80)
426
+ self.setGeometry(100, 100, 1000, 400)
427
+ # this is the Canvas Widget that displays the `figure`
428
+ # it takes the `figure` instance as a parameter to __init__
429
+ self.canvas = FigureCanvas(self.figure)
430
+
431
+ # this is the Navigation widget
432
+ # it takes the Canvas widget and a parent
433
+
434
+ #self.toolbar = NavigationToolbar(self.canvas, self)
435
+
436
+ self.label1 = QLabel("Trend")
437
+ self.label1.alignment()
438
+ self.label1.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
439
+
440
+ self.trendbox = QSpinBox()
441
+ self.trendbox.setGeometry(0, 0, 30, 20)
442
+ self.trendbox.setValue(0)
443
+ self.trendbox.setMinimum(0)
444
+ self.trendbox.valueChanged.connect(self.redraw)
445
+ # -------------------------
446
+ self.label2 = QLabel("Boxcar")
447
+ self.label2.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
448
+ self.label2.setToolTip("This sets the length of the boxcar")
449
+
450
+ self.boxcarbox = QSpinBox()
451
+ self.boxcarbox.setGeometry(0, 0, 30, 20)
452
+ self.boxcarbox.setValue(1)
453
+ self.boxcarbox.setMinimum(1)
454
+ self.boxcarbox.setSingleStep(2)
455
+ self.boxcarbox.valueChanged.connect(self.redraw)
456
+
457
+ # -------------------------
458
+ self.Gaussian_Process_Regression = QCheckBox(
459
+ "Gaussian Process Regression")
460
+ self.Gaussian_Process_Regression.setLayoutDirection(Qt.RightToLeft)
461
+ self.Gaussian_Process_Regression.stateChanged.connect(self.redraw)
462
+ # -------------------------
463
+
464
+ self.label3 = QLabel("nh")
465
+ self.label3.alignment()
466
+ self.label3.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
467
+
468
+ self.nhbox = QSpinBox()
469
+ self.nhbox.setGeometry(0, 0, 30, 20)
470
+ self.nhbox.setValue(2)
471
+ self.nhbox.setMinimum(2)
472
+
473
+ # -------------------------
474
+ self.row_layout = QHBoxLayout()
475
+ self.row_layout.addWidget(self.label1)
476
+ self.row_layout.addWidget(self.trendbox)
477
+ self.row_layout.addWidget(self.label2)
478
+ self.row_layout.addWidget(self.boxcarbox)
479
+ self.row_layout.addWidget(self.Gaussian_Process_Regression)
480
+ self.row_layout.addWidget(self.label3)
481
+ self.row_layout.addWidget(self.nhbox)
482
+
483
+ self.fpeaktype=QButtonGroup()
484
+ self.fpeaktype.setExclusive(True)
485
+
486
+ self.radio = QRadioButton("Parabola")
487
+ self.fpeaktype.addButton(self.radio, 0)
488
+ self.radio.setChecked(True)
489
+ self.radio.setLayoutDirection(Qt.RightToLeft)
490
+ self.row_layout.addWidget(self.radio)
491
+ self.row_layout.setAlignment(self.radio, Qt.AlignVCenter)
492
+ self.radio = QRadioButton("Sine")
493
+ self.fpeaktype.addButton(self.radio, 1)
494
+ self.radio.setChecked(False)
495
+ self.radio.setLayoutDirection(Qt.RightToLeft)
496
+ self.row_layout.addWidget(self.radio)
497
+ self.row_layout.setAlignment(self.radio, Qt.AlignVCenter)
498
+
499
+ #self.fpeaktype.buttonClicked.connect(self.on_draw_per)
500
+
501
+
502
+
503
+ # Define the second row of widgets
504
+
505
+ # self.nhbox.valueChanged.connect(self.nh2)
506
+ # self.nh2=2
507
+
508
+ self.listOfChoices = ["amhw", "pspw", "atrw",
509
+ "aovw", "f_mw", "lomw"]
510
+ self.choice_tooltips = {"amhw": "Returns multiharmonic AOV periodogram,\n"
511
+ "obtained by fitting data with a series of trigonometric polynomials.\n"
512
+ "For default nh2=3 this is Lomb-Scargle periodogram\n"
513
+ "corrected for constant shift",
514
+ "pspw": "Returns Power Spectrum. Principal use for calculation of window function.\n"
515
+ "For periodogram we recommend amhv routine with nh2=2 instead",
516
+ "atrw": "Returns AOV periodogram for search of transits/eclipses.\n"
517
+ "For this purpose data are fitted with a box-like \n"
518
+ "(inverted top-hat) function",
519
+ "aovw": "Analysis of variance taking account of weights\n"
520
+ "i.e. observastional errors",
521
+ "f_mw": "Returns generalized Lomb-Scargle periodogram\n"
522
+ "of data, using the original Ferraz-Mello (1981) algorithm.",
523
+ "lomw": "Returns AOV periodogram, obtained by phase-folding and binning of data."}
524
+ self.row2_layout = QHBoxLayout()
525
+
526
+ self.radioGroup = QButtonGroup()
527
+ self.radioGroup.setExclusive(True)
528
+
529
+ for i, row in enumerate(self.listOfChoices):
530
+ self.radio = QRadioButton(row)
531
+ self.radio.setToolTip(self.choice_tooltips[row])
532
+ self.radioGroup.addButton(self.radio, i)
533
+ # if i==0:
534
+ # self.radio.setChecked(True)
535
+
536
+ self.row2_layout.addWidget(self.radio)
537
+ self.row2_layout.setAlignment(self.radio, Qt.AlignVCenter)
538
+ self.radioGroup.buttonClicked.connect(self.on_draw_per)
539
+
540
+ # set the top level layout
541
+ layout = QVBoxLayout()
542
+ layout.addLayout(self.row_layout)
543
+ layout.addLayout(self.row2_layout)
544
+ layout.addWidget(self.canvas)
545
+
546
+ self.setLayout(layout)
547
+ self.plot()
548
+ simplefilter('ignore', category=ConvergenceWarning)
549
+
550
+ def on_draw_per(self, meth):
551
+ print(self.fpeaktype.checkedButton().text())
552
+ self.ts_cropped.meta['peak_type']=self.fpeaktype.checkedButton().text()
553
+ self.ts_cropped.meta['method'] = meth.text()
554
+ main2 = Window2(self, ts_detrended=self.get_detrended_subset())
555
+ main2.show()
556
+
557
+ def closeEvent(self, event):
558
+
559
+ plt.close()
560
+ super(Window1, self).closeEvent(event)
561
+
562
+ def get_detrended_subset(self):
563
+ # calculate best fit trend line
564
+ global dummy
565
+ self.ts_detrended = self.ts_cropped.copy()
566
+
567
+ self.ts_cropped.meta['trend'] = self.trendbox.value()
568
+ self.ts_cropped.meta['boxcar'] = self.boxcarbox.value()
569
+ self.ts_cropped.meta['nh2'] = self.nhbox.value()
570
+ self.ts_cropped.meta['Gaussian_Process_Regression'] = self.Gaussian_Process_Regression.isChecked(
571
+ )
572
+
573
+ c = Chebyshev.fit(self.ts_detrended.time.value,
574
+ self.ts_detrended['y'].value, deg=self.trendbox.value())
575
+ self.ts_detrended['y1'] = self.ts_detrended['y'] - \
576
+ c(self.ts_detrended.time.value)*self.ts_detrended['y'].unit
577
+
578
+ if self.boxcarbox.value() > 1:
579
+ box_kernel = Box1DKernel(self.boxcarbox.value())
580
+ smoothed = convolve(self.ts_detrended['y1'].value, box_kernel)
581
+ self.ts_detrended['y2'] = self.ts_detrended['y1'] - \
582
+ smoothed*self.ts_detrended['y1'].unit
583
+ for i in range(int(self.boxcarbox.value()/2)):
584
+ self.ts_detrended['y2'][i] = 0
585
+ self.ts_detrended['y2'][len(self.ts_detrended['y2'])-1-i] = 0
586
+ else:
587
+ self.ts_detrended['y2'] = self.ts_detrended['y1']
588
+
589
+ # self.ts_detrended.sort('time')
590
+
591
+ if self.Gaussian_Process_Regression.isChecked():
592
+ kernel = C(1.0, (1e-3, 1e3)) * RBF(10, (1, 1e2)) + \
593
+ W(noise_level=1e-3, noise_level_bounds=(1e-06, 1))
594
+ gp = GaussianProcessRegressor(
595
+ kernel=kernel, alpha=self.ts_cropped['y_err'].value ** 2, n_restarts_optimizer=3)
596
+ time2d = np.atleast_2d(self.ts_detrended.time.value).T
597
+ gp.fit(time2d, self.ts_detrended['y2'].value)
598
+ mag_pred, sigma_mag_pred = gp.predict(time2d, return_std=True)
599
+ self.ts_detrended['y3'] = self.ts_detrended['y2'] - \
600
+ mag_pred * self.ts_detrended['y2'].unit
601
+ else:
602
+ self.ts_detrended['y3'] = self.ts_detrended['y2']
603
+ return self.ts_detrended
604
+
605
+ def Fourier(self):
606
+ self.ts_cropped.meta['method'] = 'Fourier'
607
+ main2 = Window2(self, ts_detrended=self.get_detrended_subset())
608
+ main2.show()
609
+
610
+ def redraw(self):
611
+ # Update display to include the updated trend/boxcar parameters to be
612
+ # displayed
613
+ self.plot()
614
+
615
+ def plot(self):
616
+
617
+ self.ts_detrended = self.get_detrended_subset()
618
+
619
+ xdata = self.ts_cropped.time.value
620
+ # calculate the "trend" that has been used
621
+ ydata = self.ts_detrended['y']-self.ts_detrended['y3']
622
+
623
+ self.figure.clear()
624
+ # create an axis
625
+ ax = self.figure.add_subplot(111)
626
+ # plot data
627
+ ax.errorbar(self.ts_cropped.time.value,
628
+ self.ts_cropped['y'].value, yerr=self.ts_cropped['y_err'].value, fmt='.', color='magenta')
629
+ ax.plot(xdata, ydata, '-k')
630
+ ymin, ymax = ax.get_ylim()
631
+ ax.set_ylim(ymax, ymin)
632
+
633
+ ax.set_xlabel('MJD')
634
+ ax.set_ylabel('Magnitude')
635
+ ax.set_title(str(self.ts_cropped.meta['object_name']))
636
+ # refresh canvas
637
+
638
+ self.canvas.draw()
639
+
640
+ class Window(QDialog):
641
+ def __init__(self, ts, parent=None):
642
+ super(Window, self).__init__(parent)
643
+ self.ts = ts
644
+ self.setWindowTitle("{} ".format(str(self.ts.meta['object_name'])))
645
+ # a figure instance to plot on
646
+
647
+ self.figure = plt.figure(dpi=80)
648
+ self.setGeometry(50, 50, 1000, 400)
649
+ # this is the Canvas Widget that displays the `figure`
650
+ # it takes the `figure` instance as a parameter to __init__
651
+ self.canvas = FigureCanvas(self.figure)
652
+ # this is the Navigation widget
653
+ # it takes the Canvas widget and a parent
654
+ self.toolbar = NavigationToolbar(self.canvas, self)
655
+
656
+ self.analyse_button = QPushButton("Analyse")
657
+ self.analyse_button.clicked.connect(self.Analyse)
658
+
659
+ self.row_layout = QHBoxLayout()
660
+ self.row_layout.addWidget(self.toolbar)
661
+ self.row_layout.addWidget(self.analyse_button)
662
+
663
+ # set the top level layout
664
+ layout = QVBoxLayout()
665
+ layout.addLayout(self.row_layout)
666
+ layout.addWidget(self.canvas)
667
+
668
+ self.setLayout(layout)
669
+ self.plot()
670
+
671
+ def closeEvent(self, event):
672
+
673
+ plt.close()
674
+ super(Window, self).closeEvent(event)
675
+
676
+ def get_subset(self):
677
+
678
+ try:
679
+ axes = self.canvas.figure.axes[0]
680
+ mjd1, mjd2 = axes.get_xlim()
681
+ y_hi, y_lo = axes.get_ylim()
682
+ mask = (self.ts.time.value > mjd1) &\
683
+ (self.ts.time.value < mjd2) &\
684
+ (self.ts['y'].value > y_lo) &\
685
+ (self.ts['y'].value < y_hi)
686
+ self.new_ts = self.ts.copy()[mask]
687
+ self.new_ts.meta['crop'] = [mjd1, mjd2, y_lo, y_hi]
688
+ except:
689
+ self.new_ts = self.ts.copy()
690
+ return self.new_ts
691
+
692
+ def Analyse(self):
693
+
694
+ main1 = Window1(self, self.get_subset())
695
+ main1.show()
696
+
697
+ def redraw(self):
698
+ # Update display to include the updated trend/boxcar parameters to be
699
+ # displayed
700
+ self.plot()
701
+
702
+ def plot(self):
703
+ self.figure.clear()
704
+
705
+ # create an axis
706
+ ax = self.figure.add_subplot(111)
707
+ # plot data
708
+ ax.errorbar(self.ts.time.value, self.ts['y'].value,
709
+ yerr=self.ts['y_err'].value, fmt='.', color='magenta')
710
+ ymin, ymax = ax.get_ylim()
711
+ ax.set_ylim(ymax, ymin)
712
+ ax.set_xlabel(str(self.ts.time.format))
713
+ ax.set_ylabel(str(self.ts['y'].unit))
714
+ ax.set_title(str(self.ts.meta['object_name']))
715
+ # refresh canvas
716
+ self.canvas.draw()
717
+
718
+
719
+ def display_light_curve(original_ts):
720
+ """
721
+ Parameters
722
+ ----------
723
+ original_ts : Timeseries object
724
+ This is a standard astropy time series object with (dimensioned) parameters
725
+ time,y and y_err. In addition it includes a metadata dictionary (meta) with
726
+ parameters
727
+ 'object path': path to folder where time series data is held
728
+ 'object_name': name of object
729
+ 'frange': list of lists. The lowest level list has three values applicable
730
+ to periodograms ; lowest frequency, highest frequency and number of bins
731
+
732
+ Returns
733
+ -------
734
+ None
735
+ """
736
+ ts = original_ts.copy()
737
+ ts.sort('time')
738
+ if 'object_name' not in ts.meta:
739
+ ts.meta['object_name'] = ''
740
+ ts.meta['crop'] = []
741
+ if 'prewhiten' not in ts.meta:
742
+ ts.meta['prewhiten'] = []
743
+ if 'frange' not in ts.meta:
744
+ period_low = 0.5 # shortest period evaluated (hours)
745
+ period_hi = 30 # longest period evaluated
746
+ fmin = 24/period_hi
747
+ fmax = 24/period_low
748
+ fbins = 150000
749
+ ts.meta['frange'] = [[fmin, fmax, fbins]]
750
+ ts.meta['binsize'] = (ts.meta['frange'][0][1] -
751
+ ts.meta['frange'][0][0])/ts.meta['frange'][0][2]
752
+
753
+ # Cannot change order of these
754
+ app = QApplication(sys.argv)
755
+ main = Window(ts)
756
+ main.show()
757
+ app.exec_()
758
+
759
+ def getanalysis(data):
760
+ from astropy.time import Time
761
+ from astropy.timeseries import TimeSeries
762
+ from astropy.units import cds
763
+ from astropy import units as u
764
+ from astropy.io import ascii, fits
765
+
766
+ ts_filename='lightcurve_data.csv'
767
+ data.to_csv(ts_filename,index=False)
768
+
769
+ ts_path = os.path.join(os.getcwd(),ts_filename)
770
+
771
+ flux_unit=u.def_unit("Flux")
772
+ no_unit=u.def_unit("Unspecified")
773
+
774
+ '''
775
+ _, appname = os.path.split(sys.argv[0])
776
+
777
+ default_file = os.sep.join([str(Path.home()), 'KI app parameters', appname[:-3]+'.def'])
778
+ '''
779
+
780
+ data.sort_values('hjd',inplace=True)
781
+ xd=data['hjd']-2400000.5
782
+
783
+ yd=data['mag']
784
+ ed=data['magerr']
785
+
786
+ y_unit=u.mag
787
+
788
+ filters = list(set(data['filtercode']))
789
+ first_filter = filters[0]
790
+ med_first_filter = data.query('filtercode==@first_filter')['mag'].median()
791
+
792
+ for filt in filters:
793
+ med_filtered = data.query('filtercode==@filt')['mag'].median()
794
+ data.loc[data['filtercode'] == filt,'mag'] += (med_first_filter-med_filtered)
795
+ yd=data['mag']
796
+
797
+ ts_time = Time(xd.to_numpy(), format='mjd')
798
+ ts_timeseries = TimeSeries(time=ts_time, meta={'object path': ts_path, 'object_name': ts_filename, 'frange': [[2, 60, 1500000]]})
799
+
800
+ ts_timeseries.add_column(yd.to_numpy()*y_unit, name='y')
801
+ ts_timeseries.add_column(ed.to_numpy()*cds.mag, name='y_err')
802
+
803
+ display_light_curve(ts_timeseries)
804
+
805
+ '''
806
+ f = open(default_file, 'w')
807
+ f.write(ts_path)
808
+ f.close()
809
+ '''
810
+
811
+ def getpowerspectrum(data):
812
+ from astropy.time import Time
813
+ from astropy.timeseries import TimeSeries
814
+ from astropy.units import cds
815
+ from astropy import units as u
816
+
817
+ file_name='lightcurve_data.csv'
818
+ data.to_csv(file_name,index=False)
819
+
820
+ file_path=os.path.join(os.getcwd(),file_name)
821
+
822
+ data.sort_values('hjd',inplace=True)
823
+ xd=data['hjd']-2400000.5
824
+ yd=data['mag']
825
+ ed=data['magerr']
826
+
827
+ y_unit=u.mag
828
+
829
+ filters = list(set(data['filtercode']))
830
+ first_filter = filters[0]
831
+ med_first_filter = data.query('filtercode==@first_filter')['mag'].median()
832
+
833
+ for filt in filters:
834
+ med_filtered = data.query('filtercode==@filt')['mag'].median()
835
+ data.loc[data['filtercode'] == filt,'mag'] += (med_first_filter-med_filtered)
836
+ yd=data['mag']
837
+
838
+ start_freq=0
839
+ final_freq=50
840
+ step_size=(final_freq-start_freq)/250000
841
+
842
+ th,freqs,frmax=pyaov.amhw(xd,yd,ed,final_freq,step_size)
843
+
844
+ plot=figure(width=400,height=400,title='ZTF Power Spectrum',x_axis_label=r'\[\text{Frequency [days}^{-1}]\]',y_axis_label=r'\[\text{Power}\]')
845
+ plot.line(x=freqs,y=th,color='black',line_width=0.5)
846
+
847
+ return plot