def boxleastsquares(time, relFlux, relFluxErr, acfBP): model = BoxLeastSquares(time.values, relFlux.values, dy=relFluxErr.values) duration = [20 / 1440, 40 / 1440, 80 / 1440, .1] periodogram = model.power(period=[.5 * acfBP, acfBP, 2 * acfBP], duration=duration, objective='snr') period = periodogram.period power = periodogram.power maxPower = np.max(periodogram.power) bestPeriod = periodogram.period[np.argmax(periodogram.power)] return period, power, bestPeriod, maxPower
def calcBls(flux, time, bls_durs, minP=None, maxP=None, min_trans=3): """ Take a bls and return the spectrum. """ bls = BoxLeastSquares(time, flux) period_grid = bls.autoperiod(bls_durs,minimum_period=minP, \ maximum_period=maxP, minimum_n_transit=min_trans, \ frequency_factor=0.8) bls_power = bls.power(period_grid, bls_durs, oversample=20) return bls_power
def period_finder(self, nt=5, min_p=1, max_p=100, n_f=10000, auto=True, method='LS_astropy'): self.pmin = min_p self.pmax = max_p self.method = method if self.method == 'LS_astropy': if auto: ls = LombScargle(self.df.t.values, self.df.m.values, self.df.e.values, nterms=nt) self.frequency, self.power = ls.autopower( minimum_frequency=1. / self.pmax, maximum_frequency=1. / self.pmin) else: self.frequency = np.linspace(1. / self.pmax, 1. / self.pmin, n_f) self.power = LombScargle(self.df.t.values, self.df.m.values, self.df.e.values).power( self.frequency) elif self.method == 'BLS_astropy': model = BoxLeastSquares(self.df.t.values * u.day, self.df.m.values, dy=self.df.e.values) if auto: periodogram = model.autopower(0.2) self.frequency = 1. / periodogram.period self.power = periodogram.power else: periods = np.linspace(self.pmin, self.pmax, 10) periodogram = model.power(periods, 0.2) self.frequency = 1. / periodogram.period self.power = periodogram.power else: print('Method should be chosen between these options:') print('LS_astropy, BLS_astropy') sys.exit() self.set_period() self.plot_ls()
def __findBestSigma(self, maxperiod, window_length, sigma_step, sigma_max, debug_mode=False): i = 3 #starting point self.sigArr = [] self.lenArr = [] self.maxdataArr = [] while (sigma_max >= i): flat = self.rawlc.flatten( window_length=window_length).remove_outliers(sigma=i) model = BoxLeastSquares(flat.time, flat.flux, dy=0.01) testperiods = np.arange(1, maxperiod, 0.001) periodogram = model.power(testperiods, 0.16) maxID = np.argmax(periodogram.power) stat = model.compute_stats(periodogram.period[maxID], periodogram.duration[maxID], periodogram.transit_time[maxID]) self.sigArr.append(sum(stat['per_transit_log_likelihood'])) self.lenArr.append(len(stat['per_transit_log_likelihood'])) if debug_mode: print([ i, sum(stat['per_transit_log_likelihood']), len(stat['per_transit_log_likelihood']), periodogram.period[maxID] ]) #Debug i += sigma_step maxLLikeIndex = np.argwhere(self.lenArr == np.amax(self.lenArr)) for i in maxLLikeIndex: self.maxdataArr.append(self.sigArr[i.item(0)]) bestFit = np.argmax(self.maxdataArr) - 1 if debug_mode: print("Index of LogLikelihoods") print(maxLLikeIndex) print("Best Sigma") print(bestFit + i) return bestFit + i
def get_ref_vals(lightcurve, p_ref=None): t = lightcurve.time y = lightcurve.flux dy = lightcurve.flux_err bls = BoxLeastSquares(t, y, dy) durations = [0.05, 0.1, 0.2] if p_ref is None: periodogram = bls.autopower(durations) else: periods = np.linspace(p_ref * 0.9, p_ref * 1.1, 5000) periodogram = bls.power(periods, durations) max_power = np.argmax(periodogram.power) stats = bls.compute_stats(periodogram.period[max_power], periodogram.duration[max_power], periodogram.transit_time[max_power]) num_transits = len(stats['transit_times']) t0 = periodogram.transit_time[max_power] p = periodogram.period[max_power] return (t0, p, num_transits)
def _create_interact_ui(doc, minp=minimum_period, maxp=maximum_period, resolution=resolution): """Create BLS interact user interface.""" if minp is None: minp = 0.3 if maxp is None: maxp = (lc.time[-1] - lc.time[0]) / 2 time_format = '' if lc.time_format == 'bkjd': time_format = ' - 2454833 days' if lc.time_format == 'btjd': time_format = ' - 2457000 days' # Some sliders duration_slider = Slider(start=0.01, end=0.5, value=0.05, step=0.01, title="Duration [Days]", width=400) npoints_slider = Slider(start=500, end=10000, value=resolution, step=100, title="BLS Resolution", width=400) # Set up the period values, BLS model and best period period_values = np.logspace(np.log10(minp), np.log10(maxp), npoints_slider.value) period_values = period_values[(period_values > duration_slider.value) & (period_values < maxp)] model = BoxLeastSquares(lc.time, lc.flux) result = model.power(period_values, duration_slider.value) loc = np.argmax(result.power) best_period = result.period[loc] best_t0 = result.transit_time[loc] # Some Buttons double_button = Button(label="Double Period", button_type="danger", width=100) half_button = Button(label="Half Period", button_type="danger", width=100) text_output = Paragraph(text="Period: {} days, T0: {}{}".format( np.round(best_period, 7), np.round(best_t0, 7), time_format), width=350, height=40) # Set up BLS source bls_source = prepare_bls_datasource(result, loc) bls_help_source = prepare_bls_help_source(bls_source, npoints_slider.value) # Set up the model LC mf = model.model(lc.time, best_period, duration_slider.value, best_t0) mf /= np.median(mf) mask = ~(convolve(np.asarray(mf == np.median(mf)), Box1DKernel(2)) > 0.9) model_lc = LightCurve(lc.time[mask], mf[mask]) model_lc = model_lc.append( LightCurve([(lc.time[0] - best_t0) + best_period / 2], [1])) model_lc = model_lc.append( LightCurve([(lc.time[0] - best_t0) + 3 * best_period / 2], [1])) model_lc_source = ColumnDataSource( data=dict(time=np.sort(model_lc.time), flux=model_lc.flux[np.argsort(model_lc.time)])) # Set up the LC nb = int(np.ceil(len(lc.flux) / 5000)) lc_source = prepare_lightcurve_datasource(lc[::nb]) lc_help_source = prepare_lc_help_source(lc) # Set up folded LC nb = int(np.ceil(len(lc.flux) / 10000)) f = lc.fold(best_period, best_t0) f_source = prepare_folded_datasource(f[::nb]) f_help_source = prepare_f_help_source(f) f_model_lc = model_lc.fold(best_period, best_t0) f_model_lc = LightCurve([-0.5], [1]).append(f_model_lc) f_model_lc = f_model_lc.append(LightCurve([0.5], [1])) f_model_lc_source = ColumnDataSource( data=dict(phase=f_model_lc.time, flux=f_model_lc.flux)) def _update_light_curve_plot(event): """If we zoom in on LC plot, update the binning.""" mint, maxt = fig_lc.x_range.start, fig_lc.x_range.end inwindow = (lc.time > mint) & (lc.time < maxt) nb = int(np.ceil(inwindow.sum() / 5000)) temp_lc = lc[inwindow] lc_source.data = { 'time': temp_lc.time[::nb], 'flux': temp_lc.flux[::nb] } def _update_folded_plot(event): loc = np.argmax(bls_source.data['power']) best_period = bls_source.data['period'][loc] best_t0 = bls_source.data['transit_time'][loc] # Otherwise, we can just update the best_period index minphase, maxphase = fig_folded.x_range.start, fig_folded.x_range.end f = lc.fold(best_period, best_t0) inwindow = (f.time > minphase) & (f.time < maxphase) nb = int(np.ceil(inwindow.sum() / 10000)) f_source.data = { 'phase': f[inwindow].time[::nb], 'flux': f[inwindow].flux[::nb] } # Function to update the widget def _update_params(all=False, best_period=None, best_t0=None): if all: # If we're updating everything, recalculate the BLS model minp, maxp = fig_bls.x_range.start, fig_bls.x_range.end period_values = np.logspace(np.log10(minp), np.log10(maxp), npoints_slider.value) ok = (period_values > duration_slider.value) & (period_values < maxp) if ok.sum() == 0: return period_values = period_values[ok] result = model.power(period_values, duration_slider.value) ok = np.isfinite(result['power']) & np.isfinite(result['duration']) &\ np.isfinite(result['transit_time']) & np.isfinite(result['period']) bls_source.data = dict(period=result['period'][ok], power=result['power'][ok], duration=result['duration'][ok], transit_time=result['transit_time'][ok]) loc = np.nanargmax(bls_source.data['power']) best_period = bls_source.data['period'][loc] best_t0 = bls_source.data['transit_time'][loc] minpow, maxpow = bls_source.data['power'].min( ) * 0.95, bls_source.data['power'].max() * 1.05 fig_bls.y_range.start = minpow fig_bls.y_range.end = maxpow # Otherwise, we can just update the best_period index minphase, maxphase = fig_folded.x_range.start, fig_folded.x_range.end f = lc.fold(best_period, best_t0) inwindow = (f.time > minphase) & (f.time < maxphase) nb = int(np.ceil(inwindow.sum() / 10000)) f_source.data = { 'phase': f[inwindow].time[::nb], 'flux': f[inwindow].flux[::nb] } mf = model.model(lc.time, best_period, duration_slider.value, best_t0) mf /= np.median(mf) mask = ~(convolve(np.asarray(mf == np.median(mf)), Box1DKernel(2)) > 0.9) model_lc = LightCurve(lc.time[mask], mf[mask]) model_lc_source.data = { 'time': np.sort(model_lc.time), 'flux': model_lc.flux[np.argsort(model_lc.time)] } f_model_lc = model_lc.fold(best_period, best_t0) f_model_lc = LightCurve([-0.5], [1]).append(f_model_lc) f_model_lc = f_model_lc.append(LightCurve([0.5], [1])) f_model_lc_source.data = { 'phase': f_model_lc.time, 'flux': f_model_lc.flux } vertical_line.update(location=best_period) fig_folded.title.text = 'Period: {} days \t T0: {}{}'.format( np.round(best_period, 7), np.round(best_t0, 7), time_format) text_output.text = "Period: {} days, \t T0: {}{}".format( np.round(best_period, 7), np.round(best_t0, 7), time_format) # Callbacks def _update_upon_period_selection(attr, old, new): """When we select a period we should just update a few things, but we should not recalculate model """ if len(new) > 0: new = new[0] best_period = bls_source.data['period'][new] best_t0 = bls_source.data['transit_time'][new] _update_params(best_period=best_period, best_t0=best_t0) def _update_model_slider(attr, old, new): """If the duration slider is updated, then update the whole model set.""" _update_params(all=True) def _update_model_slider_EVENT(event): """If we update the duration slider, we should update the whole model set. This is the same as the _update_model_slider but it has a different call signature... """ _update_params(all=True) def _double_period_event(): fig_bls.x_range.start *= 2 fig_bls.x_range.end *= 2 _update_params(all=True) def _half_period_event(): fig_bls.x_range.start /= 2 fig_bls.x_range.end /= 2 _update_params(all=True) # Help Hover Call Backs def _update_folded_plot_help_reset(event): f_help_source.data['phase'] = [ (np.max(f.time) - np.min(f.time)) * 0.98 + np.min(f.time) ] f_help_source.data['flux'] = [ (np.max(f.flux) - np.min(f.flux)) * 0.98 + np.min(f.flux) ] def _update_folded_plot_help(event): f_help_source.data['phase'] = [ (fig_folded.x_range.end - fig_folded.x_range.start) * 0.95 + fig_folded.x_range.start ] f_help_source.data['flux'] = [ (fig_folded.y_range.end - fig_folded.y_range.start) * 0.95 + fig_folded.y_range.start ] def _update_lc_plot_help_reset(event): lc_help_source.data['time'] = [ (np.max(lc.time) - np.min(lc.time)) * 0.98 + np.min(lc.time) ] lc_help_source.data['flux'] = [ (np.max(lc.flux) - np.min(lc.flux)) * 0.9 + np.min(lc.flux) ] def _update_lc_plot_help(event): lc_help_source.data['time'] = [ (fig_lc.x_range.end - fig_lc.x_range.start) * 0.95 + fig_lc.x_range.start ] lc_help_source.data['flux'] = [ (fig_lc.y_range.end - fig_lc.y_range.start) * 0.9 + fig_lc.y_range.start ] def _update_bls_plot_help_event(event): bls_help_source.data['period'] = [ bls_source.data['period'][int(npoints_slider.value * 0.95)] ] bls_help_source.data['power'] = [ (np.max(bls_source.data['power']) - np.min(bls_source.data['power'])) * 0.98 + np.min(bls_source.data['power']) ] def _update_bls_plot_help(attr, old, new): bls_help_source.data['period'] = [ bls_source.data['period'][int(npoints_slider.value * 0.95)] ] bls_help_source.data['power'] = [ (np.max(bls_source.data['power']) - np.min(bls_source.data['power'])) * 0.98 + np.min(bls_source.data['power']) ] # Create all the figures. fig_folded = make_folded_figure_elements(f, f_model_lc, f_source, f_model_lc_source, f_help_source) fig_folded.title.text = 'Period: {} days \t T0: {}{}'.format( np.round(best_period, 7), np.round(best_t0, 5), time_format) fig_bls, vertical_line = make_bls_figure_elements( result, bls_source, bls_help_source) fig_lc = make_lightcurve_figure_elements(lc, model_lc, lc_source, model_lc_source, lc_help_source) # Map changes # If we click a new period, update bls_source.selected.on_change('indices', _update_upon_period_selection) # If we change the duration, update everything, including help button for BLS duration_slider.on_change('value', _update_model_slider) duration_slider.on_change('value', _update_bls_plot_help) # If we increase resolution, update everything npoints_slider.on_change('value', _update_model_slider) # Make sure the vertical line always goes to the best period. vertical_line.update(location=best_period) # If we pan in the BLS panel, update everything fig_bls.on_event(PanEnd, _update_model_slider_EVENT) fig_bls.on_event(Reset, _update_model_slider_EVENT) # If we pan in the LC panel, rebin the points fig_lc.on_event(PanEnd, _update_light_curve_plot) fig_lc.on_event(Reset, _update_light_curve_plot) # If we pan in the Folded panel, rebin the points fig_folded.on_event(PanEnd, _update_folded_plot) fig_folded.on_event(Reset, _update_folded_plot) # Deal with help button fig_bls.on_event(PanEnd, _update_bls_plot_help_event) fig_bls.on_event(Reset, _update_bls_plot_help_event) fig_folded.on_event(PanEnd, _update_folded_plot_help) fig_folded.on_event(Reset, _update_folded_plot_help_reset) fig_lc.on_event(PanEnd, _update_lc_plot_help) fig_lc.on_event(Reset, _update_lc_plot_help_reset) # Buttons double_button.on_click(_double_period_event) half_button.on_click(_half_period_event) # Layout the widget doc.add_root( layout([[fig_bls, fig_folded], fig_lc, [ Spacer(width=70), duration_slider, Spacer(width=50), npoints_slider ], [ Spacer(width=70), double_button, Spacer(width=70), half_button, Spacer(width=300), text_output ]]))
def __generatePeriodogram(self, maxperiod): model = BoxLeastSquares(self.flat.time, self.flat.flux, dy=0.01) testperiods = np.arange(1, maxperiod, 0.001) self.periodogram = model.power(testperiods, 0.16)
class BLSStep(OTSStep): name = "bls" def __init__(self, ts): super().__init__(ts) self._periods = None self._durations = array([0.25, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 ]) / 24 self.bls = None self.result = None self.period = None # Best-fit period self.zero_epoch = None # Best-fit zero epoch self.duration = None # Best-fit duration self.depth = None # Best-fit depth self.snr = None # Best-fit Signal to noise ratio def __call__(self, *args, **kwargs): self.logger = getLogger( f"{self.name}:{self.ts.name.lower().replace('_','-')}") self.logger.info("Running BLS periodogram") self._periods = linspace(self.ts.pmin, self.ts.pmax, self.ts.nper) self.bls = BoxLeastSquares(self.ts.time * u.day, self.ts.flux, self.ts.ferr) self.result = self.bls.power(self._periods, self._durations, objective='snr') for p in self.ts.masked_periods: self.result.depth_snr *= maskf(self._periods, p, .1) self.result.log_likelihood *= maskf(self._periods, p, .1) i = argmax(self.result.depth_snr) self.period = self.result.period[i].value self.snr = self.result.depth_snr[i] self.duration = self.result.duration[i].value self.depth = self.result.depth[i] t0 = self.result.transit_time[i].value ep = epoch(self.ts.time.min(), t0, self.period) self.zero_epoch = t0 + ep * self.period self.ts.update_ephemeris(self.zero_epoch, self.period, self.duration, self.depth) self.logger.info( f"BLS SNR {self.snr:.2f} period {self.period:.2f} d, duration {24*self.duration:.2f} h" ) def add_to_fits(self, hdul: HDUList): if self.bls is not None: h = hdul[0].header h.append(Card('COMMENT', '======================')) h.append(Card('COMMENT', ' BLS results ')) h.append(Card('COMMENT', '======================')) h.append(Card('bls_snr', self.snr, 'BLS depth signal to noise ratio'), bottom=True) h.append(Card('period', self.period, 'Orbital period [d]'), bottom=True) h.append(Card('epoch', self.zero_epoch, 'Zero epoch [BJD]'), bottom=True) h.append(Card('duration', self.duration, 'Transit duration [d]'), bottom=True) h.append(Card('depth', self.depth, 'Transit depth'), bottom=True) @bplot def plot_snr(self, ax=None): if self.period < 1.: ax.semilogx(self._periods, self.result.depth_snr, drawstyle='steps-mid') else: ax.plot(self._periods, self.result.depth_snr, drawstyle='steps-mid') ax.axvline(self.period, alpha=0.15, c='orangered', ls='-', lw=10, zorder=-100) setp(ax, xlabel='Period [d]', ylabel='Depth SNR') ax.autoscale(axis='x', tight=True)
def from_lightcurve(lc, **kwargs): """Creates a Periodogram from a LightCurve using the Box Least Squares (BLS) method.""" # BoxLeastSquares was added to `astropy.stats` in AstroPy v3.1 and then # moved to `astropy.timeseries` in v3.2, which makes the import below # somewhat complicated. try: from astropy.timeseries import BoxLeastSquares except ImportError: try: from astropy.stats import BoxLeastSquares except ImportError: raise ImportError("BLS requires AstroPy v3.1 or later") # Validate user input for `lc` # (BoxLeastSquares will not work if flux or flux_err contain NaNs) lc = lc.remove_nans() if np.isfinite(lc.flux_err).all(): dy = lc.flux_err else: dy = None # Validate user input for `duration` duration = kwargs.pop("duration", 0.25) if duration is not None and ~np.all(np.isfinite(duration)): raise ValueError("`duration` parameter contains illegal nan or inf value(s)") # Validate user input for `period` period = kwargs.pop("period", None) minimum_period = kwargs.pop("minimum_period", None) maximum_period = kwargs.pop("maximum_period", None) if period is not None and ~np.all(np.isfinite(period)): raise ValueError("`period` parameter contains illegal nan or inf value(s)") if minimum_period is None: if period is None: minimum_period = np.max([np.median(np.diff(lc.time)) * 4, np.max(duration) + np.median(np.diff(lc.time))]) else: minimum_period = np.min(period) if maximum_period is None: if period is None: maximum_period = (np.max(lc.time) - np.min(lc.time)) / 3. else: maximum_period = np.max(period) # Validate user input for `time_unit` time_unit = (kwargs.pop("time_unit", "day")) if time_unit not in dir(u): raise ValueError('{} is not a valid value for `time_unit`'.format(time_unit)) # Validate user input for `frequency_factor` frequency_factor = kwargs.pop("frequency_factor", 10) df = frequency_factor * np.min(duration) / (np.max(lc.time) - np.min(lc.time))**2 npoints = int(((1/minimum_period) - (1/maximum_period))/df) if npoints > 1e7: raise ValueError('`period` contains {} points.' 'Periodogram is too large to evaluate. ' 'Consider setting `frequency_factor` to a higher value.' ''.format(np.round(npoints, 4))) elif npoints > 1e5: log.warning('`period` contains {} points.' 'Periodogram is likely to be large, and slow to evaluate. ' 'Consider setting `frequency_factor` to a higher value.' ''.format(np.round(npoints, 4))) # Create BLS object and run the BLS search bls = BoxLeastSquares(lc.time, lc.flux, dy) if period is None: period = bls.autoperiod(duration, minimum_period=minimum_period, maximum_period=maximum_period, frequency_factor=frequency_factor) result = bls.power(period, duration, **kwargs) if not isinstance(result.period, u.quantity.Quantity): result.period = u.Quantity(result.period, time_unit) if not isinstance(result.power, u.quantity.Quantity): result.power = result.power * u.dimensionless_unscaled if not isinstance(result.duration, u.quantity.Quantity): result.duration = u.Quantity(result.duration, time_unit) return BoxLeastSquaresPeriodogram(frequency=1. / result.period, power=result.power, default_view='period', label=lc.label, targetid=lc.targetid, transit_time=result.transit_time, duration=result.duration, depth=result.depth, bls_result=result, snr=result.depth_snr, bls_obj=bls, time=lc.time, flux=lc.flux, time_unit=time_unit)
def find_and_mask_transits(time, flux, flux_err, periods, durations, nplanets=1, plot=False): """ Iteratively find and mask transits in the flattened light curve. Args: time (array): The time array. flux (array): The flux array. You'll get the best results if this is flattened. flux_err (array): The array of flux uncertainties. periods (array): The array of periods to search over for BLS. For example, periods = np.linspace(0.5, 20, 10) durations (array): The array of durations to search over for BLS. For example, durations = np.linspace(0.05, 0.2, 10) nplanets (Optional[int]): The number of planets you'd like to search for. This function will interatively find and remove nplanets. Default is 1. Returns: transit_masks (list): a list of masks that correspond to the in transit points of each light curve. To mask out transits do time[~transit_masks[index]], etc. """ cum_transit = np.ones(len(time), dtype=bool) _time, _flux, _flux_err = time * 1, flux * 1, flux_err * 1 t0s, durs, porbs = [np.zeros(nplanets) for i in range(3)] transit_masks = [] for i in range(nplanets): bls = BoxLeastSquares(t=_time, y=_flux, dy=_flux_err) bls.power(periods, durations) periods = bls.autoperiod(durations, minimum_n_transit=3, frequency_factor=5.0) results = bls.autopower(durations, frequency_factor=5.0) # Find the period of the peak period = results.period[np.argmax(results.power)] # Extract the parameters of the best-fit model index = np.argmax(results.power) porbs[i] = results.period[index] t0s[i] = results.transit_time[index] durs[i] = results.duration[index] if plot: # Plot the periodogram fig, ax = plt.subplots(1, 1, figsize=(10, 5)) ax.plot(results.period, results.power, "k", lw=0.5) ax.set_xlim(results.period.min(), results.period.max()) ax.set_xlabel("period [days]") ax.set_ylabel("log likelihood") # Highlight the harmonics of the peak period ax.axvline(period, alpha=0.4, lw=4) for n in range(2, 10): ax.axvline(n * period, alpha=0.4, lw=1, linestyle="dashed") ax.axvline(period / n, alpha=0.4, lw=1, linestyle="dashed") # plt.show() # plt.plot(_time, _flux, ".") # plt.xlim(1355, 1360) in_transit = bls.transit_mask(_time, porbs[i], durs[i], t0s[i]) transit_masks.append(in_transit) _time, _flux, _flux_err = _time[~in_transit], _flux[~in_transit], \ _flux_err[~in_transit] return transit_masks, t0s, durs, porbs
def _create_interact_ui(doc, minp=minimum_period, maxp=maximum_period, resolution=resolution): """Create BLS interact user interface.""" if minp is None: minp = 0.3 if maxp is None: maxp = (lc.time[-1].value - lc.time[0].value) / 2 # TODO: consider to accept Time as minp / maxp, and convert it to unitless days time_format = "" if lc.time.format == "bkjd": time_format = " - 2454833 days" if lc.time.format == "btjd": time_format = " - 2457000 days" # Some sliders duration_slider = Slider( start=0.01, end=0.5, value=0.05, step=0.01, title="Duration [Days]", width=400, ) npoints_slider = Slider( start=500, end=10000, value=resolution, step=100, title="BLS Resolution", width=400, ) # Set up the period values, BLS model and best period period_values = np.logspace(np.log10(minp), np.log10(maxp), npoints_slider.value) period_values = period_values[(period_values > duration_slider.value) & (period_values < maxp)] model = BoxLeastSquares(lc.time, lc.flux) result = model.power(period_values, duration_slider.value) loc = np.argmax(result.power) best_period = result.period[loc] best_t0 = result.transit_time[loc] # Some Buttons double_button = Button(label="Double Period", button_type="danger", width=100) half_button = Button(label="Half Period", button_type="danger", width=100) text_output = Paragraph( text="Period: {} days, T0: {}{}".format( _round_strip_unit(best_period, 7), _round_strip_unit(best_t0, 7), time_format, ), width=350, height=40, ) # Set up BLS source bls_source = prepare_bls_datasource(result, loc) bls_source_units = dict( transit_time_format=result["transit_time"].format, transit_time_scale=result["transit_time"].scale, period=result["period"].unit, ) bls_help_source = prepare_bls_help_source(bls_source, npoints_slider.value) # Set up the model LC mf = model.model(lc.time, best_period, duration_slider.value, best_t0) mf /= np.median(mf) mask = ~(convolve(np.asarray(mf == np.median(mf)), Box1DKernel(2)) > 0.9) model_lc = _to_lc(lc.time[mask], mf[mask]) model_lc_source = _to_ColumnDataSource( data=dict(time=model_lc.time, flux=model_lc.flux)) # Set up the LC nb = int(np.ceil(len(lc.flux) / 5000)) lc_source = prepare_lightcurve_datasource(lc[::nb]) lc_help_source = prepare_lc_help_source(lc) # Set up folded LC nb = int(np.ceil(len(lc.flux) / 10000)) f = lc.fold(best_period, best_t0) f_source = prepare_folded_datasource(f[::nb]) f_help_source = prepare_f_help_source(f) f_model_lc = model_lc.fold(best_period, best_t0) f_model_lc = _to_lc(_as_1d(f.time.min()), [1]).append(f_model_lc) f_model_lc = f_model_lc.append(_to_lc(_as_1d(f.time.max()), [1])) f_model_lc_source = _to_ColumnDataSource( data=dict(phase=f_model_lc.time, flux=f_model_lc.flux)) def _update_light_curve_plot(event): """If we zoom in on LC plot, update the binning.""" mint, maxt = fig_lc.x_range.start, fig_lc.x_range.end inwindow = (lc.time.value > mint) & (lc.time.value < maxt) nb = int(np.ceil(inwindow.sum() / 5000)) temp_lc = lc[inwindow] _update_source(lc_source, { "time": temp_lc.time[::nb], "flux": temp_lc.flux[::nb] }) def _update_folded_plot(event): loc = np.argmax(bls_source.data["power"]) best_period = bls_source.data["period"][loc] best_t0 = bls_source.data["transit_time"][loc] # Otherwise, we can just update the best_period index minphase, maxphase = fig_folded.x_range.start, fig_folded.x_range.end f = lc.fold(best_period, best_t0) inwindow = (f.time > minphase) & (f.time < maxphase) nb = int(np.ceil(inwindow.sum() / 10000)) _update_source( f_source, { "phase": f[inwindow].time[::nb], "flux": f[inwindow].flux[::nb] }, ) # Function to update the widget def _update_params(all=False, best_period=None, best_t0=None): if all: # If we're updating everything, recalculate the BLS model minp, maxp = fig_bls.x_range.start, fig_bls.x_range.end period_values = np.logspace(np.log10(minp), np.log10(maxp), npoints_slider.value) ok = (period_values > duration_slider.value) & (period_values < maxp) if ok.sum() == 0: return period_values = period_values[ok] result = model.power(period_values, duration_slider.value) ok = (_isfinite(result["power"]) & _isfinite(result["duration"]) & _isfinite(result["transit_time"]) & _isfinite(result["period"])) ok_result = dict( period=result["period"] [ok], # useful for accessing values with units needed later power=result["power"][ok], duration=result["duration"][ok], transit_time=result["transit_time"][ok], ) _update_source(bls_source, ok_result) loc = np.nanargmax(ok_result["power"]) best_period = ok_result["period"][loc] best_t0 = ok_result["transit_time"][loc] minpow, maxpow = ( bls_source.data["power"].min() * 0.95, bls_source.data["power"].max() * 1.05, ) fig_bls.y_range.start = minpow fig_bls.y_range.end = maxpow # Otherwise, we can just update the best_period index minphase, maxphase = fig_folded.x_range.start, fig_folded.x_range.end f = lc.fold(best_period, best_t0) inwindow = (f.time > minphase) & (f.time < maxphase) nb = int(np.ceil(inwindow.sum() / 10000)) _update_source( f_source, { "phase": f[inwindow].time[::nb], "flux": f[inwindow].flux[::nb] }, ) mf = model.model(lc.time, best_period, duration_slider.value, best_t0) mf /= np.median(mf) mask = ~(convolve(np.asarray(mf == np.median(mf)), Box1DKernel(2)) > 0.9) model_lc = _to_lc(lc.time[mask], mf[mask]) _update_source(model_lc_source, { "time": model_lc.time, "flux": model_lc.flux }) f_model_lc = model_lc.fold(best_period, best_t0) f_model_lc = _to_lc(_as_1d(f.time.min()), [1]).append(f_model_lc) f_model_lc = f_model_lc.append(_to_lc(_as_1d(f.time.max()), [1])) _update_source(f_model_lc_source, { "phase": f_model_lc.time, "flux": f_model_lc.flux }) vertical_line.update(location=best_period.value) fig_folded.title.text = "Period: {} days \t T0: {}{}".format( _round_strip_unit(best_period, 7), _round_strip_unit(best_t0, 7), time_format, ) text_output.text = "Period: {} days, \t T0: {}{}".format( _round_strip_unit(best_period, 7), _round_strip_unit(best_t0, 7), time_format, ) # Callbacks def _update_upon_period_selection(attr, old, new): """When we select a period we should just update a few things, but we should not recalculate model""" if len(new) > 0: new = new[0] best_period = (bls_source.data["period"][new] * bls_source_units["period"]) best_t0 = Time( bls_source.data["transit_time"][new], format=bls_source_units["transit_time_format"], scale=bls_source_units["transit_time_scale"], ) _update_params(best_period=best_period, best_t0=best_t0) def _update_model_slider(attr, old, new): """If the duration slider is updated, then update the whole model set.""" _update_params(all=True) def _update_model_slider_EVENT(event): """If we update the duration slider, we should update the whole model set. This is the same as the _update_model_slider but it has a different call signature... """ _update_params(all=True) def _double_period_event(): fig_bls.x_range.start *= 2 fig_bls.x_range.end *= 2 _update_params(all=True) def _half_period_event(): fig_bls.x_range.start /= 2 fig_bls.x_range.end /= 2 _update_params(all=True) # Help Hover Call Backs def _update_folded_plot_help_reset(event): f_help_source.data["phase"] = [_at_ratio(f.time, 0.95)] f_help_source.data["flux"] = [_at_ratio(f.flux, 0.95)] def _update_folded_plot_help(event): f_help_source.data["phase"] = [_at_ratio(fig_folded.x_range, 0.95)] f_help_source.data["flux"] = [_at_ratio(fig_folded.y_range, 0.95)] def _update_lc_plot_help_reset(event): lc_help_source.data["time"] = [_at_ratio(lc.time, 0.98)] lc_help_source.data["flux"] = [_at_ratio(lc.flux, 0.95)] def _update_lc_plot_help(event): lc_help_source.data["time"] = [_at_ratio(fig_lc.x_range, 0.98)] lc_help_source.data["flux"] = [_at_ratio(fig_lc.y_range, 0.95)] def _update_bls_plot_help_event(event): # cannot use _at_ratio helper for period, because period is log scaled. bls_help_source.data["period"] = [ bls_source.data["period"][int(npoints_slider.value * 0.95)] ] bls_help_source.data["power"] = [ _at_ratio(bls_source.data["power"], 0.98) ] def _update_bls_plot_help(attr, old, new): bls_help_source.data["period"] = [ bls_source.data["period"][int(npoints_slider.value * 0.95)] ] bls_help_source.data["power"] = [ _at_ratio(bls_source.data["power"], 0.98) ] # Create all the figures. fig_folded = make_folded_figure_elements(f, f_model_lc, f_source, f_model_lc_source, f_help_source) fig_folded.title.text = "Period: {} days \t T0: {}{}".format( _round_strip_unit(best_period, 7), _round_strip_unit(best_t0, 5), time_format, ) fig_bls, vertical_line = make_bls_figure_elements( result, bls_source, bls_help_source) fig_lc = make_lightcurve_figure_elements(lc, model_lc, lc_source, model_lc_source, lc_help_source) # Map changes # If we click a new period, update bls_source.selected.on_change("indices", _update_upon_period_selection) # If we change the duration, update everything, including help button for BLS duration_slider.on_change("value", _update_model_slider) duration_slider.on_change("value", _update_bls_plot_help) # If we increase resolution, update everything npoints_slider.on_change("value", _update_model_slider) # Make sure the vertical line always goes to the best period. vertical_line.update(location=best_period.value) # If we pan in the BLS panel, update everything fig_bls.on_event(PanEnd, _update_model_slider_EVENT) fig_bls.on_event(Reset, _update_model_slider_EVENT) # If we pan in the LC panel, rebin the points fig_lc.on_event(PanEnd, _update_light_curve_plot) fig_lc.on_event(Reset, _update_light_curve_plot) # If we pan in the Folded panel, rebin the points fig_folded.on_event(PanEnd, _update_folded_plot) fig_folded.on_event(Reset, _update_folded_plot) # Deal with help button fig_bls.on_event(PanEnd, _update_bls_plot_help_event) fig_bls.on_event(Reset, _update_bls_plot_help_event) fig_folded.on_event(PanEnd, _update_folded_plot_help) fig_folded.on_event(Reset, _update_folded_plot_help_reset) fig_lc.on_event(PanEnd, _update_lc_plot_help) fig_lc.on_event(Reset, _update_lc_plot_help_reset) # Buttons double_button.on_click(_double_period_event) half_button.on_click(_half_period_event) # Layout the widget doc.add_root( layout([ [fig_bls, fig_folded], fig_lc, [ Spacer(width=70), duration_slider, Spacer(width=50), npoints_slider, ], [ Spacer(width=70), double_button, Spacer(width=70), half_button, Spacer(width=300), text_output, ], ]))
def _package_results( tpf, target, contaminator, aper, contaminant_aper, transit_pixels, transit_pixels_err, period, t0, duration, plot=False, ): """Helper function for packaging up results""" # def get_coords(thumb, err, aper, count=400): # Y, X = np.mgrid[: tpf.shape[1], : tpf.shape[2]] # cxs, cys = [], [] # for count in range(count): # err1 = np.random.normal(0, err[aper]) # cxs.append(np.average(X[aper], weights=thumb[aper] + err1)) # cys.append(np.average(Y[aper], weights=thumb[aper] + err1)) # cxs, cys = np.asarray(cxs), np.asarray(cys) # cras, cdecs = tpf.wcs.wcs_pix2world(np.asarray([cxs, cys]).T, 1).T # return cras, cdecs def get_coords(thumb, err, aper=None): if aper is None: aper = np.ones(tpf.flux.shape[1:], bool) with np.errstate(divide="ignore"): Y, X = np.mgrid[:tpf.shape[1], :tpf.shape[2]] aper = create_threshold_mask(thumb / err, 3) & aper cxs, cys = [], [] for count in range(500): w = np.random.normal(loc=np.abs(thumb[aper]), scale=err[aper]) cxs.append(np.average(X[aper], weights=w)) cys.append(np.average(Y[aper], weights=w)) cxs, cys = np.asarray(cxs), np.asarray(cys) k = (cxs > 0) & (cxs < tpf.shape[2]) & (cys > 0) & (cys < tpf.shape[1]) cxs, cys = cxs[k], cys[k] cras, cdecs = tpf.wcs.all_pix2world(np.asarray([cxs, cys]).T, 1).T return cras, cdecs thumb = np.nanmean(np.nan_to_num(tpf.flux.value), axis=0) err = (np.sum(np.nan_to_num(tpf.flux_err.value)**2, axis=0)**0.5) / len( tpf.time) ra_target, dec_target = get_coords(thumb, err, aper=aper) bls = BoxLeastSquares(target.time, target.flux, target.flux_err) depths = [] for i in range(50): bls.y = target.flux + np.random.normal(0, target.flux_err) depths.append(bls.power(period, duration)["depth"][0]) target_depth = (np.mean(depths), np.std(depths)) res = {"target_depth": target_depth} res["target_ra"] = np.median(ra_target), np.std(ra_target) res["target_dec"] = np.median(dec_target), np.std(dec_target) res["target_lc"] = target res["target_aper"] = aper if contaminant_aper.any(): ra_contaminant, dec_contaminant = get_coords(transit_pixels, transit_pixels_err) bls = BoxLeastSquares(contaminator.time, contaminator.flux, contaminator.flux_err) depths = [] for i in range(50): bls.y = contaminator.flux + np.random.normal( 0, contaminator.flux_err) depths.append(bls.power(period, duration)["depth"][0]) contaminator_depth = (np.mean(depths), np.std(depths)) res["contaminator_ra"] = np.median(ra_contaminant), np.std( ra_contaminant) res["contaminator_dec"] = np.median(dec_contaminant), np.std( dec_contaminant) res["contaminator_depth"] = contaminator_depth res["contaminator_lc"] = contaminator res["contaminator_aper"] = contaminant_aper d, de = (contaminator_depth[0] - target_depth[0]), np.hypot( contaminator_depth[1], target_depth[1]) res["delta_transit_depth[sigma]"] = d / de dra = res["contaminator_ra"][0] - res["target_ra"][0] ddec = res["contaminator_dec"][0] - res["target_dec"][0] edra = (res["contaminator_ra"][1]**2 + res["target_ra"][1]**2)**0.5 eddec = (res["contaminator_dec"][1]**2 + res["target_dec"][1]**2)**0.5 centroid_shift = (((dra**2 + ddec**2)**0.5) * u.deg).to(u.arcsecond) ecentroid_shift = (centroid_shift * ((2 * edra / dra)**2 + (2 * eddec / ddec)**2)**0.5) res["centroid_shift"] = (centroid_shift, ecentroid_shift) res["period"] = period res["t0"] = t0 res["duration"] = duration res["transit_depth"] = transit_pixels res["transit_depth_err"] = transit_pixels_err if plot: res["fig"] = _make_plot(tpf, res) return res
def run_BLS(fl): t, f, e = np.genfromtxt(fl, usecols=(0,1,2), unpack=True) mask = cleaner(t,f) t = t[~mask] f = f[~mask] e = e[~mask] lc = TessLightCurve(time=t, flux=f, flux_err=e).flatten(window_length=51, polyorder=2, niters=5) #Test Fill ''' diffs = np.diff(lc.time) stdd = np.nanstd(diffs) medd = np.nanmedian(diffs) maskgaps = diffs > 0.2#np.abs(diffs-medd) > stdd maskgaps = np.concatenate((maskgaps,[False])) ''' ''' for mg in np.where(maskgaps)[0]: addtime = np.arange(lc.time[mg]+0.05, lc.time[mg+1], 0.05) addflux = np.random.normal(1, 8e-4, len(addtime)) lc.time = np.concatenate((lc.time, addtime)) lc.flux = np.concatenate((lc.flux, addflux)) addorder = np.argsort(lc.time) lc.time = lc.time[addorder] lc.flux = lc.flux[addorder] ''' #fmed = np.nanmedian(lc.flux) #fstd = np.nanstd(lc.flux) #stdm = lc.flux < 0.97#np.abs(lc.flux-fmed) > 3*fstd periods = np.exp(np.linspace(np.log(args.min_period), np.log(args.max_period), 5000)) durations = np.linspace(0.05, 0.15, 20)# * u.day model = BLS(lc.time,lc.flux) if not args.TLS else transitleastsquares(lc.time.value, lc.flux, lc.flux_err) #result = model.power(periods, durations, oversample=20)#, objective='snr') result = model.power(period_min=args.min_period, oversampling_factor=2, n_transits_min=1, use_threads=1, show_progress_bar=False) #try: #result = model.autopower(durations, frequency_factor=2.0, maximum_period=args.max_period) #except: # print(fl) idx = np.argmax(result.power) period = result.period[idx] t0 = result.transit_time[idx] dur = result.duration[idx] depth = result.depth[idx] snr = result.depth_snr[idx] ''' period = result.period t0 = result.T0 dur = result.duration depth = 1 - result.depth snr = result.snr ''' try: stats = model.compute_stats(period, dur, t0) depth_even = stats['depth_even'][0] depth_odd = stats['depth_odd'][0] depth_half = stats['depth_half'][0] t0, t1 = stats['transit_times'][:2] ntra = len(stats['transit_times']) except: depth_even = 0 depth_odd = 0 depth_half = 0 t1 = 0 ntra = 0 if args.target is not None: return fl, period, t0, dur, depth, snr, depth_even, depth_odd, depth_half, t1, ntra, result.period, result.power, lc.time, lc.flux, diffs else: return fl, period, t0, dur, depth, snr, depth_even, depth_odd, depth_half, t1, ntra