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
Beispiel #2
0
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
Beispiel #3
0
    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()
Beispiel #4
0
    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
Beispiel #5
0
    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)
Beispiel #6
0
    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
                    ]]))
Beispiel #7
0
 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)
Beispiel #8
0
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)
Beispiel #9
0
    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)
Beispiel #10
0
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
Beispiel #11
0
    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
Beispiel #13
0
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