コード例 #1
0
def simpleSnr(time, flux, results):
    """
    calculate a simple snr on the planet based on the depth and scatter
    after you remove the planet model from the data.
    """

    model = BoxLeastSquares(time, flux)
    fmodel = model.model(time, results[0], results[3], results[1])
    flatten = median_subtract(flux - fmodel, 12)

    noise = np.std(flatten)
    snr = results[2] / noise

    return snr
コード例 #2
0
def plot_bls_folds(time,flux,results,ticid, zoom=True):
    
    num=4  #max number of possible planet candidates
    plt.figure(figsize=(8,12))
    s=np.std(flux)
    
    for i,r in enumerate(results):
        plt.subplot(num,1,i+1)
        phase = (time - r[1] + 0.5*r[0]) % r[0] - 0.5*r[0]
        
        model = BoxLeastSquares(time,flux)
        fmodel = model.model(time,results[i,0],results[i,3],results[i,1])
        order=np.argsort(phase)
        plt.plot(phase[order]*24,fmodel[order],'g.-', ms=3)
        plt.plot(phase*24,flux,'k.',label="TIC %u P=%6.2f d" % (ticid, results[i,0]))
        #plt.title(str(ticid) + " Per: " + str(results[i,0]) )
        if zoom:
            plt.xlim(-5.7*results[i,3]*24,5.7*results[i,3]*24)
            plt.ylim(-2.9*r[2], 4*s)
        
        plt.legend(fontsize=8)
コード例 #3
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
                    ]]))
コード例 #4
0
def generate_plots_s3(ticid,sector,cam,ccd, \
                   bls_bucket="tesssearchresults", \
                   detrend_bucket="tesssearchresults", \
                   ffilc_bucket="straw-lightcurves", \
                   outpath="/Users/smullally/TESS/lambdaSearch/strawTests/blsResults/s0001-kdwarf/plots/"):
    """
    Given a TICID, sector, camera, ccd and the bucket locations.
    generate a one page plot for each signal found by the bls using 
    the information in those files.
    bls_bucket contains the search results csv file.
    detrend_bucket contains the detrend fits file.
    ffi_bucket contains the raw light curve fits files.
    returns hdus and a dictionary of the csv.
    """

    rootname = "tic%012u_s%04u-%1u-%1u" % (ticid, sector, cam, ccd)
    path = "tic%012u/" % ticid
    bls_name = "%s_plsearch.csv" % rootname
    det_name = "%s_detrend.fits" % rootname
    lc_name = "%s_stlc.fits" % rootname

    if detrend_bucket[0] == "/":
        det_hdu = fits.open(detrend_bucket + path + det_name)
        bls = np.loadtxt(bls_bucket + path + bls_name, delimiter=',')
    else:
        det_hdu = loadFitsFromUri(detrend_bucket, path, det_name)
        bls = loadCsvFromUri(bls_bucket, path, bls_name)
    if ffilc_bucket[0] == "/":
        raw_hdu = fits.open(ffilc_bucket + path + lc_name)
    else:
        raw_hdu = loadFitsFromUri(ffilc_bucket, path, lc_name)

    #Get the number of signals found by the bls.
    if len(bls.shape) == 1:
        N = 1
        bls = bls.reshape((1, 7))
        #print(bls.shape)
    else:
        N = bls.shape[0]

    time_raw = raw_hdu[1].data['TIME']
    raw = raw_hdu[1].data['SAP_FLUX']
    time_det = det_hdu[1].data['TIME']
    detrend = det_hdu[1].data['DETREND_FLUX']

    #plt.plot(time_det,detrend,'.')

    head = raw_hdu[1].header
    try:
        ave_im = raw_hdu[2].data
    except:
        ave_im = np.zeros((10, 10))
    meta = {}
    meta['sector'] = sector
    meta['cam'] = cam
    meta['ccd'] = ccd
    try:
        meta['imloc'] = (head['CUBECOL'], head['CUBEROW'])
    except:
        meta['imloc'] = (head['APCEN_Y'], head['APCEN_X'])

    meta['radius'] = head['AP_RAD']

    for i in range(N):
        #print(i)

        meta['period'] = bls[i, 0]
        meta['dur'] = bls[i, 3]
        meta['epoch'] = bls[i, 1]
        meta['snr'] = bls[i, 4]
        meta['depth'] = bls[i, 2]
        meta['ntrans'] = bls[i, 5]
        meta['id'] = ticid
        meta['pn'] = i + 1

        #print(meta)

        bls_object = BoxLeastSquares(time_det, detrend)
        model = bls_object.model(time_det, meta['period'], \
                                      meta['dur'], meta['epoch'])
        out_name = "%s-%02i_plot.png" % (rootname, meta['pn'])
        output = outpath + out_name
        plt.figure(figsize=(10, 12))
        report.summaryPlot1(time_raw, raw, time_det, detrend, model, ave_im,
                            meta)

        plt.savefig(output)
        print(output)
コード例 #5
0
ファイル: interact_bls.py プロジェクト: jorgemarpa/lightkurve
    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,
                ],
            ]))