Instruction:
Here you have a function in Python 3, explain what it does
def phasedlc_features(times,
                      mags,
                      errs,
                      period,
                      nbrtimes=None,
                      nbrmags=None,
                      nbrerrs=None):
    '''This calculates various phased LC features for the object.

    Some of the features calculated here come from:

    Kim, D.-W., Protopapas, P., Bailer-Jones, C. A. L., et al. 2014, Astronomy
    and Astrophysics, 566, A43, and references therein (especially Richards, et
    al. 2011).

    Parameters
    ----------

    times,mags,errs : np.array
        The input mag/flux time-series to calculate the phased LC features for.

    period : float
        The period used to phase the input mag/flux time-series.

    nbrtimes,nbrmags,nbrerrs : np.array or None
        If `nbrtimes`, `nbrmags`, and `nbrerrs` are all provided, they should be
        ndarrays with `times`, `mags`, `errs` of this object's closest neighbor
        (close within some small number x FWHM of telescope to check for
        blending). This function will then also calculate extra features based
        on the neighbor's phased LC using the `period` provided for the target
        object.

    Returns
    -------

    dict
        Returns a dict with phased LC features.

    '''
    # get the finite values
    finind = np.isfinite(times) & np.isfinite(mags) & np.isfinite(errs)
    ftimes, fmags, ferrs = times[finind], mags[finind], errs[finind]

    # get nonzero errors
    nzind = np.nonzero(ferrs)
    ftimes, fmags, ferrs = ftimes[nzind], fmags[nzind], ferrs[nzind]

    # only operate on LC if enough points
    if ftimes.size > 49:

        # get the MAD of the unphased light curve
        lightcurve_median = np.median(fmags)
        lightcurve_mad = np.median(np.abs(fmags - lightcurve_median))

        # get p2p for raw lightcurve
        p2p_unphasedlc = lightcurve_ptp_measures(ftimes, fmags, ferrs)
        inveta_unphasedlc = 1.0/p2p_unphasedlc['eta_normal']

        # phase the light curve with the given period, assume epoch is
        # times.min()
        phasedlc = lcmath.phase_magseries_with_errs(ftimes, fmags, ferrs,
                                                    period, ftimes.min(),
                                                    wrap=False)

        phase = phasedlc['phase']
        pmags = phasedlc['mags']
        perrs = phasedlc['errs']

        # get ptp measures for best period
        ptp_bestperiod = lightcurve_ptp_measures(phase,pmags,perrs)

        # phase the light curve with the given periodx2, assume epoch is
        # times.min()
        phasedlc = lcmath.phase_magseries_with_errs(ftimes, fmags, ferrs,
                                                    period*2.0, ftimes.min(),
                                                    wrap=False)

        phasex2 = phasedlc['phase']
        pmagsx2 = phasedlc['mags']
        perrsx2 = phasedlc['errs']

        # get ptp measures for best periodx2
        ptp_bestperiodx2 = lightcurve_ptp_measures(phasex2,pmagsx2,perrsx2)

        # eta_phasedlc_bestperiod - calculate eta for the phased LC with best
        # period
        inveta_bestperiod = 1.0/ptp_bestperiod['eta_normal']

        # eta_phasedlc_bestperiodx2 - calculate eta for the phased LC with best
        #                             period x 2
        inveta_bestperiodx2 = 1.0/ptp_bestperiodx2['eta_normal']

        # eta_phased_ratio_eta_raw - eta for best period phased LC / eta for raw
        # LC
        inveta_ratio_phased_unphased = inveta_bestperiod/inveta_unphasedlc

        # eta_phasedx2_ratio_eta_raw - eta for best periodx2 phased LC/eta for
        # raw LC
        inveta_ratio_phasedx2_unphased = inveta_bestperiodx2/inveta_unphasedlc

        # freq_model_max_delta_mags - absval of magdiff btw model phased LC
        #                             maxima using period x 2. look at points
        #                             more than 10 points away for maxima
        phasedx2_maxval_ind = argrelmax(pmagsx2, order=10)
        if phasedx2_maxval_ind[0].size > 1:
            phasedx2_magdiff_maxval = (
                np.max(np.abs(np.diff(pmagsx2[phasedx2_maxval_ind[0]])))
            )
        else:
            phasedx2_magdiff_maxval = np.nan

        # freq_model_min_delta_mags - absval of magdiff btw model phased LC
        #                             minima using period x 2. look at points
        #                             more than 10 points away for minima
        phasedx2_minval_ind = argrelmin(pmagsx2, order=10)
        if phasedx2_minval_ind[0].size > 1:
            phasedx2_magdiff_minval = (
                np.max(np.abs(np.diff(pmagsx2[phasedx2_minval_ind[0]])))
            )
        else:
            phasedx2_magdiff_minval = np.nan

        # p2p_scatter_pfold_over_mad - MAD of successive absolute mag diffs of
        #                              the phased LC using best period divided
        #                              by the MAD of the unphased LC
        phased_magdiff = np.diff(pmags)
        phased_magdiff_median = np.median(phased_magdiff)
        phased_magdiff_mad = np.median(np.abs(phased_magdiff -
                                              phased_magdiff_median))

        phasedx2_magdiff = np.diff(pmagsx2)
        phasedx2_magdiff_median = np.median(phasedx2_magdiff)
        phasedx2_magdiff_mad = np.median(np.abs(phasedx2_magdiff -
                                                phasedx2_magdiff_median))

        phased_magdiffmad_unphased_mad_ratio = phased_magdiff_mad/lightcurve_mad
        phasedx2_magdiffmad_unphased_mad_ratio = (
            phasedx2_magdiff_mad/lightcurve_mad
        )

        # get the percentiles of the slopes of the adjacent mags for phasedx2
        phasedx2_slopes = np.diff(pmagsx2)/np.diff(phasex2)
        phasedx2_slope_percentiles = np.ravel(np.nanpercentile(phasedx2_slopes,
                                                               [10.0,90.0]))
        phasedx2_slope_10percentile = phasedx2_slope_percentiles[0]
        phasedx2_slope_90percentile = phasedx2_slope_percentiles[1]

        # check if nbrtimes, _mags, _errs are available
        if ((nbrtimes is not None) and
            (nbrmags is not None) and
            (nbrerrs is not None)):

            # get the finite values
            nfinind = (np.isfinite(nbrtimes) &
                       np.isfinite(nbrmags) &
                       np.isfinite(nbrerrs))
            nftimes, nfmags, nferrs = (nbrtimes[nfinind],
                                       nbrmags[nfinind],
                                       nbrerrs[nfinind])

            # get nonzero errors
            nnzind = np.nonzero(nferrs)
            nftimes, nfmags, nferrs = (nftimes[nnzind],
                                       nfmags[nnzind],
                                       nferrs[nnzind])

            # only operate on LC if enough points
            if nftimes.size > 49:

                # get the phased light curve using the same period and epoch as
                # the actual object
                nphasedlc = lcmath.phase_magseries_with_errs(
                    nftimes, nfmags, nferrs,
                    period, ftimes.min(),
                    wrap=False
                )

                # normalize the object and neighbor phased mags
                norm_pmags = pmags - np.median(pmags)
                norm_npmags = nphasedlc['mags'] - np.median(nphasedlc['mags'])

                # phase bin them both so we can compare LCs easily
                phabinned_objectlc = lcmath.phase_bin_magseries(phase,
                                                                norm_pmags,
                                                                minbinelems=1)
                phabinned_nbrlc = lcmath.phase_bin_magseries(nphasedlc['phase'],
                                                             norm_npmags,
                                                             minbinelems=1)

                absdiffs = []

                for pha, phamag in zip(phabinned_objectlc['binnedphases'],
                                       phabinned_objectlc['binnedmags']):

                    try:

                        # get the matching phase from the neighbor phased LC
                        phadiffs = np.abs(pha - phabinned_nbrlc['binnedphases'])
                        minphadiffind = np.where(
                            (phadiffs < 1.0e-4) &
                            (phadiffs == np.min(phadiffs))
                        )
                        absmagdiff = np.abs(
                            phamag - phabinned_nbrlc['binnedmags'][
                                minphadiffind
                            ]
                        )
                        if absmagdiff.size > 0:
                            absdiffs.append(absmagdiff.min())

                    except Exception as e:
                        continue

                # sum of absdiff between the normalized to 0.0 phased LC of this
                # object and that of the closest neighbor phased with the same
                # period and epoch
                if len(absdiffs) > 0:
                    sum_nbr_phasedlc_magdiff = sum(absdiffs)
                else:
                    sum_nbr_phasedlc_magdiff = np.nan

            else:

                sum_nbr_phasedlc_magdiff = np.nan

        else:
            sum_nbr_phasedlc_magdiff = np.nan

        return {
            'inveta_unphasedlc':inveta_unphasedlc,
            'inveta_bestperiod':inveta_bestperiod,
            'inveta_bestperiodx2':inveta_bestperiodx2,
            'inveta_ratio_phased_unphased':inveta_ratio_phased_unphased,
            'inveta_ratio_phasedx2_unphased':inveta_ratio_phasedx2_unphased,
            'phasedx2_magdiff_maxima':phasedx2_magdiff_maxval,
            'phasedx2_magdiff_minina':phasedx2_magdiff_minval,
            'phased_unphased_magdiff_mad_ratio':(
                phased_magdiffmad_unphased_mad_ratio
            ),
            'phasedx2_unphased_magdiff_mad_ratio':(
                phasedx2_magdiffmad_unphased_mad_ratio
            ),
            'phasedx2_slope_10percentile':phasedx2_slope_10percentile,
            'phasedx2_slope_90percentile':phasedx2_slope_90percentile,
            'sum_nbr_phasedlc_magdiff':sum_nbr_phasedlc_magdiff,
        }

    else:

        return {
            'inveta_unphasedlc':np.nan,
            'inveta_bestperiod':np.nan,
            'inveta_bestperiodx2':np.nan,
            'inveta_ratio_phased_unphased':np.nan,
            'inveta_ratio_phasedx2_unphased':np.nan,
            'phasedx2_magdiff_maxima':np.nan,
            'phasedx2_magdiff_minina':np.nan,
            'phased_unphased_magdiff_mad_ratio':np.nan,
            'phasedx2_unphased_magdiff_mad_ratio':np.nan,
            'phasedx2_slope_10percentile':np.nan,
            'phasedx2_slope_90percentile':np.nan,
            'sum_nbr_phasedlc_magdiff':np.nan,
        }

Response: This function calculates the phased LC features for a given object.


Instruction:
Create a Python 3 function to
calculate the Stellingwerf PDM theta value at a test frequency.

Response:
def stellingwerf_pdm_theta(times, mags, errs, frequency,
                           binsize=0.05, minbin=9):
    '''
    This calculates the Stellingwerf PDM theta value at a test frequency.

    Parameters
    ----------

    times,mags,errs : np.array
        The input time-series and associated errors.

    frequency : float
        The test frequency to calculate the theta statistic at.

    binsize : float
        The phase bin size to use.

    minbin : int
        The minimum number of items in a phase bin to consider in the
        calculation of the statistic.

    Returns
    -------

    theta_pdm : float
        The value of the theta statistic at the specified `frequency`.

    '''

    period = 1.0/frequency
    fold_time = times[0]

    phased = phase_magseries(times,
                             mags,
                             period,
                             fold_time,
                             wrap=False,
                             sort=True)

    phases = phased['phase']
    pmags = phased['mags']
    bins = nparange(0.0, 1.0, binsize)

    binnedphaseinds = npdigitize(phases, bins)

    binvariances = []
    binndets = []
    goodbins = 0

    for x in npunique(binnedphaseinds):

        thisbin_inds = binnedphaseinds == x
        thisbin_mags = pmags[thisbin_inds]

        if thisbin_mags.size > minbin:
            thisbin_variance = npvar(thisbin_mags,ddof=1)
            binvariances.append(thisbin_variance)
            binndets.append(thisbin_mags.size)
            goodbins = goodbins + 1

    # now calculate theta
    binvariances = nparray(binvariances)
    binndets = nparray(binndets)

    theta_top = npsum(binvariances*(binndets - 1)) / (npsum(binndets) -
                                                      goodbins)
    theta_bot = npvar(pmags,ddof=1)
    theta = theta_top/theta_bot

    return theta


Instruction:
Can you generate the documentation for the following Python 3 function
def analytic_false_alarm_probability(lspinfo,
                                     times,
                                     conservative_nfreq_eff=True,
                                     peakvals=None,
                                     inplace=True):
    '''This returns the analytic false alarm probabilities for periodogram
    peak values.

    FIXME: this doesn't actually work. Fix later.

    The calculation follows that on page 3 of Zechmeister & Kurster (2009)::

        FAP = 1 − [1 − Prob(z > z0)]**M

    where::

        M is the number of independent frequencies
        Prob(z > z0) is the probability of peak with value > z0
        z0 is the peak value we're evaluating

    For PDM, the Prob(z > z0) is described by the beta distribution, according
    to:

    - Schwarzenberg-Czerny (1997;
      https://ui.adsabs.harvard.edu/#abs/1997ApJ...489..941S)

    - Zalian, Chadid, and Stellingwerf (2013;
      http://adsabs.harvard.edu/abs/2014MNRAS.440...68Z)

    This is given by::

        beta( (N-B)/2, (B-1)/2; ((N-B)/(B-1))*theta_pdm )

    Where::

        N = number of observations
        B = number of phase bins

    This translates to a scipy.stats call to the beta distribution CDF::

        x = ((N-B)/(B-1))*theta_pdm_best
        prob_exceeds_val = scipy.stats.beta.cdf(x, (N-B)/2.0, (B-1.0)/2.0)

    Which we can then plug into the false alarm prob eqn above with the
    calculation of M.

    Parameters
    ----------

    lspinfo : dict
        The dict returned by the
        :py:func:`~astrobase.periodbase.spdm.stellingwerf_pdm` function.

    times : np.array
        The times for which the periodogram result in ``lspinfo`` was
        calculated.

    conservative_nfreq_eff : bool
        If True, will follow the prescription given in Schwarzenberg-Czerny
        (2003):

        http://adsabs.harvard.edu/abs/2003ASPC..292..383S

        and estimate the effective number of independent frequences M_eff as::

            min(N_obs, N_freq, DELTA_f/delta_f)

    peakvals : sequence or None
        The peak values for which to evaluate the false-alarm probability. If
        None, will calculate this for each of the peak values in the
        ``nbestpeaks`` key of the ``lspinfo`` dict.

    inplace : bool
        If True, puts the results of the FAP calculation into the ``lspinfo``
        dict as a list available as ``lspinfo['falsealarmprob']``.

    Returns
    -------

    list
        The calculated false alarm probabilities for each of the peak values in
        ``peakvals``.

    '''

    from scipy.stats import beta

    frequencies = 1.0/lspinfo['periods']

    M = independent_freq_count(frequencies,
                               times,
                               conservative=conservative_nfreq_eff)

    if peakvals is None:
        peakvals = lspinfo['nbestlspvals']

    nphasebins = nparange(0.0, 1.0, lspinfo['kwargs']['phasebinsize']).size
    ndet = times.size

    false_alarm_probs = []

    for peakval in peakvals:

        prob_xval = ((ndet-nphasebins)/(nphasebins-1.0))*peakval
        prob_exceeds_val = beta.cdf(prob_xval,
                                    (ndet-nphasebins)/2.0,
                                    (nphasebins-1.0)/2.0)
        import ipdb; ipdb.set_trace()

        false_alarm_probs.append(1.0 - (1.0 - prob_exceeds_val)**M)

    if inplace:
        lspinfo['falsealarmprob'] = false_alarm_probs

    return false_alarm_probs

Response: This function returns the analytic false alarm probability for the given set of time intervals and peak values.


Instruction:
Can you generate the documentation for the following Python 3 function
def keplermag_to_sdssr(keplermag, kic_sdssg, kic_sdssr):
    '''Converts magnitude measurements in Kepler band to SDSS r band.

    Parameters
    ----------

    keplermag : float or array-like
        The Kepler magnitude value(s) to convert to fluxes.

    kic_sdssg,kic_sdssr : float or array-like
        The SDSS g and r magnitudes of the object(s) from the Kepler Input
        Catalog. The .llc.fits MAST light curve file for a Kepler object
        contains these values in the FITS extension 0 header.

    Returns
    -------

    float or array-like
        SDSS r band magnitude(s) converted from the Kepler band magnitude.

    '''
    kic_sdssgr = kic_sdssg - kic_sdssr

    if kic_sdssgr < 0.8:
        kepsdssr = (keplermag - 0.2*kic_sdssg)/0.8
    else:
        kepsdssr = (keplermag - 0.1*kic_sdssg)/0.9
    return kepsdssr

Response: Converts magnitude measurements in Kepler band to SDSS r band.


Instruction:
Explain what the following Python 3 code does
def consolidate_kepler_fitslc(keplerid,
                              lcfitsdir,
                              normalize=True,
                              headerkeys=LCHEADERKEYS,
                              datakeys=LCDATAKEYS,
                              sapkeys=LCSAPKEYS,
                              pdckeys=LCPDCKEYS,
                              topkeys=LCTOPKEYS,
                              apkeys=LCAPERTUREKEYS):
    '''This gets all Kepler/K2 light curves for the given `keplerid`
    in `lcfitsdir`.

    Searches recursively in `lcfitsdir` for all of the files belonging to the
    specified `keplerid`. Sorts the light curves by time. Returns an
    `lcdict`. This is meant to be used to consolidate light curves for a single
    object across Kepler quarters.

    NOTE: `keplerid` is an integer (without the leading zeros). This is usually
    the KIC ID.

    NOTE: if light curve time arrays contain `nans`, these and their associated
    measurements will be sorted to the end of the final combined arrays.

    Parameters
    ----------

    keplerid : int
        The Kepler ID of the object to consolidate LCs for, as an integer
        without any leading zeros. This is usually the KIC or EPIC ID.

    lcfitsdir : str
        The directory to look in for LCs of the specified object.

    normalize : bool
        If True, then each component light curve's SAP_FLUX and PDCSAP_FLUX
        measurements will be normalized to 1.0 by dividing out the median flux
        for the component light curve.

    headerkeys : list
        A list of FITS header keys that will be extracted from the FITS light
        curve file. These describe the observations. The default value for this
        is given in `LCHEADERKEYS` above.

    datakeys : list
        A list of FITS column names that correspond to the auxiliary
        measurements in the light curve. The default is `LCDATAKEYS` above.

    sapkeys : list
        A list of FITS column names that correspond to the SAP flux
        measurements in the light curve. The default is `LCSAPKEYS` above.

    pdckeys : list
        A list of FITS column names that correspond to the PDC flux
        measurements in the light curve. The default is `LCPDCKEYS` above.

    topkeys : list
        A list of FITS header keys that describe the object in the light
        curve. The default is `LCTOPKEYS` above.

    apkeys : list
        A list of FITS header keys that describe the flux measurement apertures
        used by the Kepler/K2 pipeline. The default is `LCAPERTUREKEYS` above.

    Returns
    -------

    lcdict
        Returns an `lcdict` (this is useable by most astrobase functions for LC
        processing).

    '''

    LOGINFO('looking for Kepler light curve FITS in %s for %s...' % (lcfitsdir,
                                                                     keplerid))

    # for Python 3.5 and up, use recursive glob, it appears to be absurdly
    # faster than os.walk
    if sys.version_info[:2] > (3,4):

        matching = glob.glob(os.path.join(lcfitsdir,
                                          '**',
                                          'kplr%09i-*_llc.fits' % keplerid),
                             recursive=True)
        LOGINFO('found %s files: %s' % (len(matching), repr(matching)))

    # for Python < 3.5, use os.walk and glob
    else:

        # use the os.walk function to start looking for files in lcfitsdir
        walker = os.walk(lcfitsdir)
        matching = []
        for root, dirs, _files in walker:
            for sdir in dirs:
                searchpath = os.path.join(root,
                                          sdir,
                                          'kplr%09i-*_llc.fits' % keplerid)
                foundfiles = glob.glob(searchpath)

                if foundfiles:
                    matching.extend(foundfiles)
                    LOGINFO('found %s in dir: %s' % (repr(foundfiles),
                                                     os.path.join(root,sdir)))

    # now that we've found everything, read them all in
    if len(matching) > 0:

        LOGINFO('consolidating...')

        # the first file
        consolidated = read_kepler_fitslc(matching[0],
                                          headerkeys=headerkeys,
                                          datakeys=datakeys,
                                          sapkeys=sapkeys,
                                          pdckeys=pdckeys,
                                          topkeys=topkeys,
                                          apkeys=apkeys,
                                          normalize=normalize)

        # get the rest of the files
        for lcf in matching:
            consolidated = read_kepler_fitslc(lcf,
                                              appendto=consolidated,
                                              headerkeys=headerkeys,
                                              datakeys=datakeys,
                                              sapkeys=sapkeys,
                                              pdckeys=pdckeys,
                                              topkeys=topkeys,
                                              apkeys=apkeys,
                                              normalize=normalize)

        # get the sort indices
        # we use time for the columns and quarters for the headers
        LOGINFO('sorting by time...')

        # NOTE: nans in time will be sorted to the end of the array
        finiteind = npisfinite(consolidated['time'])
        if npsum(finiteind) < consolidated['time'].size:
            LOGWARNING('some time values are nan! '
                       'measurements at these times will be '
                       'sorted to the end of the column arrays.')

        # get the sort index
        column_sort_ind = npargsort(consolidated['time'])

        # sort the columns by time
        for col in consolidated['columns']:
            if '.' in col:
                key, subkey = col.split('.')
                consolidated[key][subkey] = (
                    consolidated[key][subkey][column_sort_ind]
                )
            else:
                consolidated[col] = consolidated[col][column_sort_ind]

        # now sort the headers by quarters
        header_sort_ind = npargsort(consolidated['quarter']).tolist()

        # this is a bit convoluted, but whatever: list -> array -> list

        for key in ('quarter', 'season', 'datarelease', 'obsmode'):
            consolidated[key] = (
                nparray(consolidated[key])[header_sort_ind].tolist()
            )

        for key in ('timesys','bjdoffset','exptime','lcaperture',
                    'aperpixused','aperpixunused','pixarcsec',
                    'channel','skygroup','module','output','ndet'):
            consolidated['lcinfo'][key] = (
                nparray(consolidated['lcinfo'][key])[header_sort_ind].tolist()
            )

        for key in ('cdpp3_0','cdpp6_0','cdpp12_0','pdcvar','pdcmethod',
                    'aper_target_total_ratio','aper_target_frac'):
            consolidated['varinfo'][key] = (
                nparray(consolidated['varinfo'][key])[header_sort_ind].tolist()
            )

        # finally, return the consolidated lcdict
        return consolidated

    # if we didn't find anything, complain
    else:

        LOGERROR('could not find any light curves '
                 'for %s in %s or its subdirectories' % (keplerid,
                                                         lcfitsdir))
        return None

Response: This function returns a dict that associates all of the light curves for a given Kepler object with the given Kepler object.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def _epd_residual(coeffs, fluxes, xcc, ycc, bgv, bge):
    '''This is the residual function to minimize using scipy.optimize.leastsq.

    Parameters
    ----------

    coeffs : array-like of floats
        Contains the EPD coefficients that will be used to generate the EPD fit
        function.

    fluxes : array-like
        The flux measurement array being used.

    xcc,ycc : array-like
        Arrays of the x and y coordinates associated with each measurement in
        `fluxes`.

    bgv,bge : array-like
        Arrays of the flux background value and the flux background error
        associated with each measurement in `fluxes`.

    Returns
    -------

    np.array
        Contains the fit function residual evaluated at each flux measurement
        value.

    '''

    f = _epd_function(coeffs, fluxes, xcc, ycc, bgv, bge)
    residual = fluxes - f
    return residual

Response: This function computes the residual function to minimize using scipy. optimize. leastsq.


Instruction:
Given the following Python 3 function, write the documentation
def rfepd_kepler_lightcurve(
        lcdict,
        xccol='mom_centr1',
        yccol='mom_centr2',
        timestoignore=None,
        filterflags=True,
        writetodict=True,
        epdsmooth=23,
        decorr='xcc,ycc',
        nrftrees=200
):
    '''This uses a `RandomForestRegressor` to fit and decorrelate Kepler light
    curves.

    Fits the X and Y positions, the background, and background error.

    By default, this function removes points in the Kepler LC that have ANY
    quality flags set.

    Parameters
    ----------

    lcdict : lcdict
        An `lcdict` produced by `consolidate_kepler_fitslc` or
        `read_kepler_fitslc`.

    xcol,ycol : str
        Indicates the x and y coordinate column names to use from the Kepler LC
        in the EPD fit.

    timestoignore : list of tuples
        This is of the form::

            [(time1_start, time1_end), (time2_start, time2_end), ...]

        and indicates the start and end times to mask out of the final
        lcdict. Use this to remove anything that wasn't caught by the quality
        flags.

    filterflags : bool
        If True, will remove any measurements that have non-zero quality flags
        present. This usually indicates an issue with the instrument or
        spacecraft.

    writetodict : bool
        If writetodict is True, adds the following columns to the lcdict::

            rfepd_time = time array
            rfepd_sapflux = uncorrected flux before EPD
            rfepd_epdsapflux = corrected flux after EPD
            rfepd_epdsapcorr = EPD flux corrections
            rfepd_bkg = background array
            rfepd_bkg_err = background errors array
            rfepd_xcc = xcoord array
            rfepd_ycc = ycoord array
            rfepd_quality = quality flag array

        and updates the 'columns' list in the lcdict as well.

    epdsmooth : int
        Sets the number of light curve points to smooth over when generating the
        EPD fit function.

    decorr : {'xcc,ycc','bgv,bge','xcc,ycc,bgv,bge'}
        Indicates whether to use the x,y coords alone; background value and
        error alone; or x,y coords and background value, error in combination as
        the features to training the `RandomForestRegressor` on and perform the
        fit.

    nrftrees : int
        The number of trees to use in the `RandomForestRegressor`.

    Returns
    -------

    tuple
        Returns a tuple of the form: (times, corrected_fluxes, flux_corrections)

    '''

    times, fluxes, background, background_err = (
        lcdict['time'],
        lcdict['sap']['sap_flux'],
        lcdict['sap']['sap_bkg'],
        lcdict['sap']['sap_bkg_err']
    )
    xcc = lcdict[xccol]
    ycc = lcdict[yccol]
    flags = lcdict['sap_quality']

    # filter all bad LC points as noted by quality flags
    if filterflags:

        nbefore = times.size

        filterind = flags == 0

        times = times[filterind]
        fluxes = fluxes[filterind]
        background = background[filterind]
        background_err = background_err[filterind]
        xcc = xcc[filterind]
        ycc = ycc[filterind]
        flags = flags[filterind]

        nafter = times.size
        LOGINFO('applied quality flag filter, ndet before = %s, '
                'ndet after = %s'
                % (nbefore, nafter))

    # remove nans
    find = (npisfinite(xcc) & npisfinite(ycc) &
            npisfinite(times) & npisfinite(fluxes) &
            npisfinite(background) & npisfinite(background_err))

    nbefore = times.size

    times = times[find]
    fluxes = fluxes[find]
    background = background[find]
    background_err = background_err[find]
    xcc = xcc[find]
    ycc = ycc[find]
    flags = flags[find]

    nafter = times.size
    LOGINFO('removed nans, ndet before = %s, ndet after = %s'
            % (nbefore, nafter))

    # exclude all times in timestoignore
    if (timestoignore and
        isinstance(timestoignore, list) and
        len(timestoignore) > 0):

        exclind = npfull_like(times,True)

        nbefore = times.size

        # apply all the masks
        for ignoretime in timestoignore:
            time0, time1 = ignoretime[0], ignoretime[1]
            thismask = (times > time0) & (times < time1)
            exclind = exclind & thismask

        # quantities after masks have been applied
        times = times[exclind]
        fluxes = fluxes[exclind]
        background = background[exclind]
        background_err = background_err[exclind]
        xcc = xcc[exclind]
        ycc = ycc[exclind]
        flags = flags[exclind]

        nafter = times.size
        LOGINFO('removed timestoignore, ndet before = %s, ndet after = %s'
                % (nbefore, nafter))

    # now that we're all done, we can do EPD

    # set up the regressor
    RFR = RandomForestRegressor(n_estimators=nrftrees)

    if decorr == 'xcc,ycc,bgv,bge':
        # collect the features and target variable
        features = npcolumn_stack((xcc,ycc,background,background_err))
    elif decorr == 'xcc,ycc':
        # collect the features and target variable
        features = npcolumn_stack((xcc,ycc))
    elif decorr == 'bgv,bge':
        # collect the features and target variable
        features = npcolumn_stack((background,background_err))
    else:
        LOGERROR("couldn't understand decorr, not decorrelating...")
        return None

    # smooth the light curve
    if epdsmooth:
        smoothedfluxes = median_filter(fluxes, size=epdsmooth)
    else:
        smoothedfluxes = fluxes

    # fit, then generate the predicted values, then get corrected values
    RFR.fit(features, smoothedfluxes)
    flux_corrections = RFR.predict(features)
    corrected_fluxes = npmedian(fluxes) + fluxes - flux_corrections

    # remove the random forest to save RAM
    del RFR

    # write these to the dictionary if requested
    if writetodict:

        lcdict['rfepd'] = {}
        lcdict['rfepd']['time'] = times
        lcdict['rfepd']['sapflux'] = fluxes
        lcdict['rfepd']['epdsapflux'] = corrected_fluxes
        lcdict['rfepd']['epdsapcorr'] = flux_corrections
        lcdict['rfepd']['bkg'] = background
        lcdict['rfepd']['bkg_err'] = background_err
        lcdict['rfepd']['xcc'] = xcc
        lcdict['rfepd']['ycc'] = ycc
        lcdict['rfepd']['quality'] = flags

        for newcol in ['rfepd.time','rfepd.sapflux',
                       'rfepd.epdsapflux','rfepd.epdsapcorr',
                       'rfepd.bkg','rfepd.bkg.err',
                       'rfepd.xcc','rfepd.ycc',
                       'rfepd.quality']:

            if newcol not in lcdict['columns']:
                lcdict['columns'].append(newcol)

    return times, corrected_fluxes, flux_corrections

Response: This function uses a RandomForestRegressor to fit and decorrelate Kepler lightcurve curves.


Instruction:
Make a summary of the following Python 3 code
def _get_legendre_deg_ctd(npts):
    '''This is a helper function for centroid detrending.

    '''

    from scipy.interpolate import interp1d

    degs = nparray([4,5,6,10,15])
    pts = nparray([1e2,3e2,5e2,1e3,3e3])
    fn = interp1d(pts, degs, kind='linear',
                  bounds_error=False,
                  fill_value=(min(degs), max(degs)))
    legendredeg = int(npfloor(fn(npts)))

    return legendredeg

Response: This is a helper function for centroid detrending.


Instruction:
Can you tell what is the following Python 3 function doing
def parallel_timebin(lclist,
                     binsizesec,
                     maxobjects=None,
                     outdir=None,
                     lcformat='hat-sql',
                     lcformatdir=None,
                     timecols=None,
                     magcols=None,
                     errcols=None,
                     minbinelems=7,
                     nworkers=NCPUS,
                     maxworkertasks=1000):
    '''This time-bins all the LCs in the list using the specified bin size.

    Parameters
    ----------

    lclist : list of str
        The input LCs to process.

    binsizesec : float
        The time bin size to use in seconds.

    maxobjects : int or None
        If provided, LC processing will stop at `lclist[maxobjects]`.

    outdir : str or None
        The directory where output LCs will be written. If None, will write to
        the same directory as the input LCs.

    lcformat : str
        This is the `formatkey` associated with your light curve format, which
        you previously passed in to the `lcproc.register_lcformat`
        function. This will be used to look up how to find and read the light
        curve file.

    lcformatdir : str or None
        If this is provided, gives the path to a directory when you've stored
        your lcformat description JSONs, other than the usual directories lcproc
        knows to search for them in. Use this along with `lcformat` to specify
        an LC format JSON file that's not currently registered with lcproc.

    timecols,magcols,errcols : lists of str
        The keys in the lcdict produced by your light curve reader function that
        correspond to the times, mags/fluxes, and associated measurement errors
        that will be used as inputs to the binning process. If these are None,
        the default values for `timecols`, `magcols`, and `errcols` for your
        light curve format will be used here.

    minbinelems : int
        The minimum number of time-bin elements required to accept a time-bin as
        valid for the output binned light curve.

    nworkers : int
        Number of parallel workers to launch.

    maxworkertasks : int
        The maximum number of tasks a parallel worker will complete before being
        replaced to guard against memory leaks.

    Returns
    -------

    dict
        The returned dict contains keys = input LCs, vals = output LCs.

    '''

    if outdir and not os.path.exists(outdir):
        os.mkdir(outdir)

    if maxobjects is not None:
        lclist = lclist[:maxobjects]

    tasks = [(x, binsizesec, {'outdir':outdir,
                              'lcformat':lcformat,
                              'lcformatdir':lcformatdir,
                              'timecols':timecols,
                              'magcols':magcols,
                              'errcols':errcols,
                              'minbinelems':minbinelems}) for x in lclist]

    pool = mp.Pool(nworkers, maxtasksperchild=maxworkertasks)
    results = pool.map(timebinlc_worker, tasks)
    pool.close()
    pool.join()

    resdict = {os.path.basename(x):y for (x,y) in zip(lclist, results)}

    return resdict

Response: This function processes the given list of LCs and returns a list of LCs in the output LCs in parallel with the specified time - bin size.


Instruction:
Can you generate the documentation for the following Python 3 function
def parallel_timebin_lcdir(lcdir,
                           binsizesec,
                           maxobjects=None,
                           outdir=None,
                           lcformat='hat-sql',
                           lcformatdir=None,
                           timecols=None,
                           magcols=None,
                           errcols=None,
                           minbinelems=7,
                           nworkers=NCPUS,
                           maxworkertasks=1000):
    '''
    This time bins all the light curves in the specified directory.

    Parameters
    ----------

    lcdir : list of str
        Directory containing the input LCs to process.

    binsizesec : float
        The time bin size to use in seconds.

    maxobjects : int or None
        If provided, LC processing will stop at `lclist[maxobjects]`.

    outdir : str or None
        The directory where output LCs will be written. If None, will write to
        the same directory as the input LCs.

    lcformat : str
        This is the `formatkey` associated with your light curve format, which
        you previously passed in to the `lcproc.register_lcformat`
        function. This will be used to look up how to find and read the light
        curve file.

    lcformatdir : str or None
        If this is provided, gives the path to a directory when you've stored
        your lcformat description JSONs, other than the usual directories lcproc
        knows to search for them in. Use this along with `lcformat` to specify
        an LC format JSON file that's not currently registered with lcproc.

    timecols,magcols,errcols : lists of str
        The keys in the lcdict produced by your light curve reader function that
        correspond to the times, mags/fluxes, and associated measurement errors
        that will be used as inputs to the binning process. If these are None,
        the default values for `timecols`, `magcols`, and `errcols` for your
        light curve format will be used here.

    minbinelems : int
        The minimum number of time-bin elements required to accept a time-bin as
        valid for the output binned light curve.

    nworkers : int
        Number of parallel workers to launch.

    maxworkertasks : int
        The maximum number of tasks a parallel worker will complete before being
        replaced to guard against memory leaks.

    Returns
    -------

    dict
        The returned dict contains keys = input LCs, vals = output LCs.

    '''
    try:
        formatinfo = get_lcformat(lcformat,
                                  use_lcformat_dir=lcformatdir)
        if formatinfo:
            (fileglob, readerfunc,
             dtimecols, dmagcols, derrcols,
             magsarefluxes, normfunc) = formatinfo
        else:
            LOGERROR("can't figure out the light curve format")
            return None
    except Exception as e:
        LOGEXCEPTION("can't figure out the light curve format")
        return None

    lclist = sorted(glob.glob(os.path.join(lcdir, fileglob)))

    return parallel_timebin(lclist,
                            binsizesec,
                            maxobjects=maxobjects,
                            outdir=outdir,
                            lcformat=lcformat,
                            timecols=timecols,
                            magcols=magcols,
                            errcols=errcols,
                            minbinelems=minbinelems,
                            nworkers=nworkers,
                            maxworkertasks=maxworkertasks)

Response: This function processes the given directory of time - bins and returns a list of str objects that are available in the output directory.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def _varfeatures_worker(task):
    '''
    This wraps varfeatures.

    '''

    try:
        (lcfile, outdir, timecols, magcols, errcols,
         mindet, lcformat, lcformatdir) = task
        return get_varfeatures(lcfile, outdir,
                               timecols=timecols,
                               magcols=magcols,
                               errcols=errcols,
                               mindet=mindet,
                               lcformat=lcformat,
                               lcformatdir=lcformatdir)

    except Exception as e:
        return None

Response:
This wraps varfeatures.
    This wraps varfeatures.


Instruction:
Given the following Python 3 function, write the documentation
def serial_varfeatures(lclist,
                       outdir,
                       maxobjects=None,
                       timecols=None,
                       magcols=None,
                       errcols=None,
                       mindet=1000,
                       lcformat='hat-sql',
                       lcformatdir=None):
    '''This runs variability feature extraction for a list of LCs.

    Parameters
    ----------

    lclist : list of str
        The list of light curve file names to process.

    outdir : str
        The directory where the output varfeatures pickle files will be written.

    maxobjects : int
        The number of LCs to process from `lclist`.

    timecols : list of str or None
        The timecol keys to use from the lcdict in calculating the features.

    magcols : list of str or None
        The magcol keys to use from the lcdict in calculating the features.

    errcols : list of str or None
        The errcol keys to use from the lcdict in calculating the features.

    mindet : int
        The minimum number of LC points required to generate variability
        features.

    lcformat : str
        This is the `formatkey` associated with your light curve format, which
        you previously passed in to the `lcproc.register_lcformat`
        function. This will be used to look up how to find and read the light
        curves specified in `basedir` or `use_list_of_filenames`.

    lcformatdir : str or None
        If this is provided, gives the path to a directory when you've stored
        your lcformat description JSONs, other than the usual directories lcproc
        knows to search for them in. Use this along with `lcformat` to specify
        an LC format JSON file that's not currently registered with lcproc.

    Returns
    -------

    list of str
        List of the generated variability features pickles for the input LCs,
        with results for each magcol in the input `magcol` or light curve
        format's default `magcol` list.

    '''

    if maxobjects:
        lclist = lclist[:maxobjects]

    tasks = [(x, outdir, timecols, magcols, errcols,
              mindet, lcformat, lcformatdir)
             for x in lclist]

    for task in tqdm(tasks):
        result = _varfeatures_worker(task)

    return result

Response: This function runs the variability feature extraction for a list of LCs and outputs a list of varfeatures pickles for each magcol in the input LCs.


Instruction:
Make a summary of the following Python 3 code
def parallel_varfeatures(lclist,
                         outdir,
                         maxobjects=None,
                         timecols=None,
                         magcols=None,
                         errcols=None,
                         mindet=1000,
                         lcformat='hat-sql',
                         lcformatdir=None,
                         nworkers=NCPUS):
    '''This runs variable feature extraction in parallel for all LCs in `lclist`.

    Parameters
    ----------

    lclist : list of str
        The list of light curve file names to process.

    outdir : str
        The directory where the output varfeatures pickle files will be written.

    maxobjects : int
        The number of LCs to process from `lclist`.

    timecols : list of str or None
        The timecol keys to use from the lcdict in calculating the features.

    magcols : list of str or None
        The magcol keys to use from the lcdict in calculating the features.

    errcols : list of str or None
        The errcol keys to use from the lcdict in calculating the features.

    mindet : int
        The minimum number of LC points required to generate variability
        features.

    lcformat : str
        This is the `formatkey` associated with your light curve format, which
        you previously passed in to the `lcproc.register_lcformat`
        function. This will be used to look up how to find and read the light
        curves specified in `basedir` or `use_list_of_filenames`.

    lcformatdir : str or None
        If this is provided, gives the path to a directory when you've stored
        your lcformat description JSONs, other than the usual directories lcproc
        knows to search for them in. Use this along with `lcformat` to specify
        an LC format JSON file that's not currently registered with lcproc.

    nworkers : int
        The number of parallel workers to launch.

    Returns
    -------

    dict
        A dict with key:val pairs of input LC file name : the generated
        variability features pickles for each of the input LCs, with results for
        each magcol in the input `magcol` or light curve format's default
        `magcol` list.

    '''
    # make sure to make the output directory if it doesn't exist
    if not os.path.exists(outdir):
        os.makedirs(outdir)

    if maxobjects:
        lclist = lclist[:maxobjects]

    tasks = [(x, outdir, timecols, magcols, errcols, mindet,
              lcformat, lcformatdir) for x in lclist]

    with ProcessPoolExecutor(max_workers=nworkers) as executor:
        resultfutures = executor.map(varfeatures_worker, tasks)

    results = [x for x in resultfutures]
    resdict = {os.path.basename(x):y for (x,y) in zip(lclist, results)}

    return resdict

Response: This function runs varfeatures extraction in parallel for all LCs in lclist.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def parallel_varfeatures_lcdir(lcdir,
                               outdir,
                               fileglob=None,
                               maxobjects=None,
                               timecols=None,
                               magcols=None,
                               errcols=None,
                               recursive=True,
                               mindet=1000,
                               lcformat='hat-sql',
                               lcformatdir=None,
                               nworkers=NCPUS):
    '''This runs parallel variable feature extraction for a directory of LCs.

    Parameters
    ----------

    lcdir : str
        The directory of light curve files to process.

    outdir : str
        The directory where the output varfeatures pickle files will be written.

    fileglob : str or None
        The file glob to use when looking for light curve files in `lcdir`. If
        None, the default file glob associated for this LC format will be used.

    maxobjects : int
        The number of LCs to process from `lclist`.

    timecols : list of str or None
        The timecol keys to use from the lcdict in calculating the features.

    magcols : list of str or None
        The magcol keys to use from the lcdict in calculating the features.

    errcols : list of str or None
        The errcol keys to use from the lcdict in calculating the features.

    mindet : int
        The minimum number of LC points required to generate variability
        features.

    lcformat : str
        This is the `formatkey` associated with your light curve format, which
        you previously passed in to the `lcproc.register_lcformat`
        function. This will be used to look up how to find and read the light
        curves specified in `basedir` or `use_list_of_filenames`.

    lcformatdir : str or None
        If this is provided, gives the path to a directory when you've stored
        your lcformat description JSONs, other than the usual directories lcproc
        knows to search for them in. Use this along with `lcformat` to specify
        an LC format JSON file that's not currently registered with lcproc.

    nworkers : int
        The number of parallel workers to launch.

    Returns
    -------

    dict
        A dict with key:val pairs of input LC file name : the generated
        variability features pickles for each of the input LCs, with results for
        each magcol in the input `magcol` or light curve format's default
        `magcol` list.

    '''

    try:
        formatinfo = get_lcformat(lcformat,
                                  use_lcformat_dir=lcformatdir)
        if formatinfo:
            (dfileglob, readerfunc,
             dtimecols, dmagcols, derrcols,
             magsarefluxes, normfunc) = formatinfo
        else:
            LOGERROR("can't figure out the light curve format")
            return None
    except Exception as e:
        LOGEXCEPTION("can't figure out the light curve format")
        return None

    if not fileglob:
        fileglob = dfileglob

    # now find the files
    LOGINFO('searching for %s light curves in %s ...' % (lcformat, lcdir))

    if recursive is False:
        matching = glob.glob(os.path.join(lcdir, fileglob))

    else:
        # use recursive glob for Python 3.5+
        if sys.version_info[:2] > (3,4):

            matching = glob.glob(os.path.join(lcdir,
                                              '**',
                                              fileglob),
                                 recursive=True)

        # otherwise, use os.walk and glob
        else:

            # use os.walk to go through the directories
            walker = os.walk(lcdir)
            matching = []

            for root, dirs, _files in walker:
                for sdir in dirs:
                    searchpath = os.path.join(root,
                                              sdir,
                                              fileglob)
                    foundfiles = glob.glob(searchpath)

                    if foundfiles:
                        matching.extend(foundfiles)

    # now that we have all the files, process them
    if matching and len(matching) > 0:

        LOGINFO('found %s light curves, getting varfeatures...' %
                len(matching))

        return parallel_varfeatures(matching,
                                    outdir,
                                    maxobjects=maxobjects,
                                    timecols=timecols,
                                    magcols=magcols,
                                    errcols=errcols,
                                    mindet=mindet,
                                    lcformat=lcformat,
                                    lcformatdir=lcformatdir,
                                    nworkers=nworkers)

    else:

        LOGERROR('no light curve files in %s format found in %s' % (lcformat,
                                                                    lcdir))
        return None

Response: This function runs the parallel varfeatures extraction for a directory of LCs.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def checkplot_pickle_to_png(
        checkplotin,
        outfile,
        extrarows=None
):
    '''This reads the checkplot pickle or dict provided, and writes out a PNG.

    The output PNG contains most of the information in the input checkplot
    pickle/dict, and can be used to quickly glance through the highlights
    instead of having to review the checkplot with the `checkplotserver`
    webapp. This is useful for exporting read-only views of finalized checkplots
    from the `checkplotserver` as well, to share them with other people.

    The PNG has 4 x N tiles::

        [    finder    ] [  objectinfo  ] [ varinfo/comments ] [ unphased LC  ]
        [ periodogram1 ] [ phased LC P1 ] [   phased LC P2   ] [ phased LC P3 ]
        [ periodogram2 ] [ phased LC P1 ] [   phased LC P2   ] [ phased LC P3 ]
                                         .
                                         .
        [ periodogramN ] [ phased LC P1 ] [ phased LC P2 ] [ phased LC P3 ]

    for N independent period-finding methods producing:

    - periodogram1,2,3...N: the periodograms from each method

    - phased LC P1,P2,P3: the phased lightcurves using the best 3 peaks in each
      periodogram

    Parameters
    ----------

    checkplotin : dict or str
        This is either a checkplotdict produced by
        :py:func:`astrobase.checkplot.pkl.checkplot_dict` or a checkplot pickle
        file produced by :py:func:`astrobase.checkplot.pkl.checkplot_pickle`.

    outfile : str
        The filename of the output PNG file to create.

    extrarows : list of tuples
        This is a list of 4-element tuples containing paths to PNG files that
        will be added to the end of the rows generated from the checkplotin
        pickle/dict. Each tuple represents a row in the final output PNG
        file. If there are less than 4 elements per tuple, the missing elements
        will be filled in with white-space. If there are more than 4 elements
        per tuple, only the first four will be used.

        The purpose of this kwarg is to incorporate periodograms and phased LC
        plots (in the form of PNGs) generated from an external period-finding
        function or program (like VARTOOLS) to allow for comparison with
        astrobase results.

        NOTE: the PNG files specified in `extrarows` here will be added to those
        already present in the input checkplotdict['externalplots'] if that is
        None because you passed in a similar list of external plots to the
        :py:func:`astrobase.checkplot.pkl.checkplot_pickle` function earlier. In
        this case, `extrarows` can be used to add even more external plots if
        desired.

        Each external plot PNG will be resized to 750 x 480 pixels to fit into
        an output image cell.

        By convention, each 4-element tuple should contain:

        - a periodiogram PNG
        - phased LC PNG with 1st best peak period from periodogram
        - phased LC PNG with 2nd best peak period from periodogram
        - phased LC PNG with 3rd best peak period from periodogram

        Example of extrarows::

            [('/path/to/external/bls-periodogram.png',
              '/path/to/external/bls-phasedlc-plot-bestpeak.png',
              '/path/to/external/bls-phasedlc-plot-peak2.png',
              '/path/to/external/bls-phasedlc-plot-peak3.png'),
             ('/path/to/external/pdm-periodogram.png',
              '/path/to/external/pdm-phasedlc-plot-bestpeak.png',
              '/path/to/external/pdm-phasedlc-plot-peak2.png',
              '/path/to/external/pdm-phasedlc-plot-peak3.png'),
            ...]

    Returns
    -------

    str
        The absolute path to the generated checkplot PNG.

    '''

    # figure out if the checkplotpickle is a filename
    # python 3
    if sys.version_info[:2] > (3,2):

        if (isinstance(checkplotin, str) and os.path.exists(checkplotin)):
            cpd = _read_checkplot_picklefile(checkplotin)
        elif isinstance(checkplotin, dict):
            cpd = checkplotin
        else:
            LOGERROR('checkplotin: %s of type %s is not a '
                     'valid checkplot filename (or does not exist), or a dict' %
                     (os.path.abspath(checkplotin), type(checkplotin)))
            return None

    # check for unicode in python 2.7
    else:

        # get the current checkplotdict
        if ((isinstance(checkplotin, str) or
             isinstance(checkplotin, unicode)) and
            os.path.exists(checkplotin)):
            cpd = _read_checkplot_picklefile(checkplotin)
        elif isinstance(checkplotin,dict):
            cpd = checkplotin
        else:
            LOGERROR('checkplotin: %s of type %s is not a '
                     'valid checkplot filename (or does not exist), or a dict' %
                     (os.path.abspath(checkplotin), type(checkplotin)))
            return None

    # figure out the dimensions of the output png
    # each cell is 750 x 480 pixels
    # a row is made of four cells
    # - the first row is for object info
    # - the rest are for periodograms and phased LCs, one row per method
    # if there are more than three phased LC plots per method, we'll only plot 3
    if 'pfmethods' in cpd:
        cplspmethods = cpd['pfmethods']
    else:
        cplspmethods = []
        for pfm in METHODSHORTLABELS:
            if pfm in cpd:
                cplspmethods.append(pfm)

    cprows = len(cplspmethods)

    # add in any extra rows from neighbors
    if 'neighbors' in cpd and cpd['neighbors'] and len(cpd['neighbors']) > 0:
        nbrrows = len(cpd['neighbors'])
    else:
        nbrrows = 0

    # add in any extra rows from keyword arguments
    if extrarows and len(extrarows) > 0:
        erows = len(extrarows)
    else:
        erows = 0

    # add in any extra rows from the checkplot dict
    if ('externalplots' in cpd and
        cpd['externalplots'] and
        len(cpd['externalplots']) > 0):
        cpderows = len(cpd['externalplots'])
    else:
        cpderows = 0

    totalwidth = 3000
    totalheight = 480 + (cprows + erows + nbrrows + cpderows)*480

    # this is the output PNG
    outimg = Image.new('RGBA',(totalwidth, totalheight),(255,255,255,255))

    # now fill in the rows of the output png. we'll use Pillow to build up the
    # output image from the already stored plots and stuff in the checkplot
    # dict.

    ###############################
    # row 1, cell 1: finder chart #
    ###############################

    if cpd['finderchart']:
        finder = Image.open(
            _base64_to_file(cpd['finderchart'], None, writetostrio=True)
        )
        bigfinder = finder.resize((450,450), Image.ANTIALIAS)
        outimg.paste(bigfinder,(150,20))

    #####################################
    # row 1, cell 2: object information #
    #####################################

    # find the font we need from the package data
    fontpath = os.path.abspath(
        os.path.join(os.path.dirname(__file__),
                     '..',
                     'cpserver',
                     'cps-assets',
                     'DejaVuSans.ttf')
    )
    # load the font
    if os.path.exists(fontpath):
        cpfontnormal = ImageFont.truetype(fontpath, 20)
        cpfontlarge = ImageFont.truetype(fontpath, 28)
    else:
        LOGWARNING('could not find bundled '
                   'DejaVu Sans font in the astrobase package '
                   'data, using ugly defaults...')
        cpfontnormal = ImageFont.load_default()
        cpfontlarge = ImageFont.load_default()

    # the image draw object
    objinfodraw = ImageDraw.Draw(outimg)

    # write out the object information

    # objectid
    objinfodraw.text(
        (625, 25),
        cpd['objectid'] if cpd['objectid'] else 'no objectid',
        font=cpfontlarge,
        fill=(0,0,255,255)
    )
    # twomass id
    if 'twomassid' in cpd['objectinfo']:
        objinfodraw.text(
            (625, 60),
            ('2MASS J%s' % cpd['objectinfo']['twomassid']
             if cpd['objectinfo']['twomassid']
             else ''),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )
    # ndet
    if 'ndet' in cpd['objectinfo']:
        objinfodraw.text(
            (625, 85),
            ('LC points: %s' % cpd['objectinfo']['ndet']
             if cpd['objectinfo']['ndet'] is not None
             else ''),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )
    else:
        objinfodraw.text(
            (625, 85),
            ('LC points: %s' % cpd['magseries']['times'].size),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )
    # coords and PM
    objinfodraw.text(
        (625, 125),
        ('Coords and PM'),
        font=cpfontnormal,
        fill=(0,0,0,255)
    )
    if 'ra' in cpd['objectinfo'] and 'decl' in cpd['objectinfo']:
        objinfodraw.text(
            (900, 125),
            (('RA, Dec: %.3f, %.3f' %
              (cpd['objectinfo']['ra'], cpd['objectinfo']['decl']))
             if (cpd['objectinfo']['ra'] is not None and
                 cpd['objectinfo']['decl'] is not None)
             else ''),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )
    else:
        objinfodraw.text(
            (900, 125),
            'RA, Dec: nan, nan',
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

    if 'propermotion' in cpd['objectinfo']:
        objinfodraw.text(
            (900, 150),
            (('Total PM: %.5f mas/yr' % cpd['objectinfo']['propermotion'])
             if (cpd['objectinfo']['propermotion'] is not None)
             else ''),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )
    else:
        objinfodraw.text(
            (900, 150),
            'Total PM: nan',
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

    if 'rpmj' in cpd['objectinfo']:
        objinfodraw.text(
            (900, 175),
            (('Reduced PM [Jmag]: %.3f' % cpd['objectinfo']['rpmj'])
             if (cpd['objectinfo']['rpmj'] is not None)
             else ''),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )
    else:
        objinfodraw.text(
            (900, 175),
            'Reduced PM [Jmag]: nan',
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

    # here, we have to deal with two generations of objectinfo dicts

    # first, deal with the new generation of objectinfo dicts
    if 'available_dereddened_bands' in cpd['objectinfo']:

        #
        # first, we deal with the bands and mags
        #
        # magnitudes
        objinfodraw.text(
            (625, 200),
            'Magnitudes',
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

        # process the various bands
        # if dereddened mags aren't available, use the observed mags
        if len(cpd['objectinfo']['available_bands']) > 0:

            # we'll get all the available mags
            for bandind, band, label in zip(
                    range(len(cpd['objectinfo']['available_bands'])),
                    cpd['objectinfo']['available_bands'],
                    cpd['objectinfo']['available_band_labels']
            ):

                thisbandmag = cpd['objectinfo'][band]

                # we'll draw stuff in three rows depending on the number of
                # bands we have to use
                if bandind in (0,1,2,3,4):

                    thispos = (900+125*bandind, 200)

                    objinfodraw.text(
                        thispos,
                        '%s: %.3f' % (label, thisbandmag),
                        font=cpfontnormal,
                        fill=(0,0,0,255)
                    )

                elif bandind in (5,6,7,8,9):

                    rowbandind = bandind - 5

                    thispos = (900+125*rowbandind, 225)

                    objinfodraw.text(
                        thispos,
                        '%s: %.3f' % (label, thisbandmag),
                        font=cpfontnormal,
                        fill=(0,0,0,255)
                    )

                else:

                    rowbandind = bandind - 10

                    thispos = (900+125*rowbandind, 250)

                    objinfodraw.text(
                        thispos,
                        '%s: %.3f' % (label, thisbandmag),
                        font=cpfontnormal,
                        fill=(0,0,0,255)
                    )

        #
        # next, deal with the colors
        #
        # colors
        if ('dereddened' in cpd['objectinfo'] and
            cpd['objectinfo']['dereddened'] is True):
            deredlabel = "(dereddened)"
        else:
            deredlabel = ""

        objinfodraw.text(
            (625, 275),
            'Colors %s' % deredlabel,
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

        if len(cpd['objectinfo']['available_colors']) > 0:

            # we'll get all the available mags (dereddened versions preferred)
            for colorind, color, colorlabel in zip(
                    range(len(cpd['objectinfo']['available_colors'])),
                    cpd['objectinfo']['available_colors'],
                    cpd['objectinfo']['available_color_labels']
            ):

                thiscolor = cpd['objectinfo'][color]

                # we'll draw stuff in three rows depending on the number of
                # bands we have to use
                if colorind in (0,1,2,3,4):

                    thispos = (900+150*colorind, 275)

                    objinfodraw.text(
                        thispos,
                        '%s: %.3f' % (colorlabel, thiscolor),
                        font=cpfontnormal,
                        fill=(0,0,0,255)
                    )

                elif colorind in (5,6,7,8,9):

                    thisrowind = colorind - 5
                    thispos = (900+150*thisrowind, 300)

                    objinfodraw.text(
                        thispos,
                        '%s: %.3f' % (colorlabel, thiscolor),
                        font=cpfontnormal,
                        fill=(0,0,0,255)
                    )

                elif colorind in (10,11,12,13,14):

                    thisrowind = colorind - 10
                    thispos = (900+150*thisrowind, 325)

                    objinfodraw.text(
                        thispos,
                        '%s: %.3f' % (colorlabel, thiscolor),
                        font=cpfontnormal,
                        fill=(0,0,0,255)
                    )

                else:

                    thisrowind = colorind - 15
                    thispos = (900+150*thisrowind, 350)

                    objinfodraw.text(
                        thispos,
                        '%s: %.3f' % (colorlabel, thiscolor),
                        font=cpfontnormal,
                        fill=(0,0,0,255)
                    )

    # otherwise, deal with older generation of checkplots
    else:

        objinfodraw.text(
            (625, 200),
            ('Magnitudes'),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

        objinfodraw.text(
            (900, 200),
            ('gri: %.3f, %.3f, %.3f' %
             ((cpd['objectinfo']['sdssg'] if
               ('sdssg' in cpd['objectinfo'] and
                cpd['objectinfo']['sdssg'] is not None)
               else npnan),
              (cpd['objectinfo']['sdssr'] if
               ('sdssr' in cpd['objectinfo'] and
                cpd['objectinfo']['sdssr'] is not None)
               else npnan),
              (cpd['objectinfo']['sdssi'] if
               ('sdssi' in cpd['objectinfo'] and
                cpd['objectinfo']['sdssi'] is not None)
               else npnan))),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )
        objinfodraw.text(
            (900, 225),
            ('JHK: %.3f, %.3f, %.3f' %
             ((cpd['objectinfo']['jmag'] if
               ('jmag' in cpd['objectinfo'] and
                cpd['objectinfo']['jmag'] is not None)
               else npnan),
              (cpd['objectinfo']['hmag'] if
               ('hmag' in cpd['objectinfo'] and
                cpd['objectinfo']['hmag'] is not None)
               else npnan),
              (cpd['objectinfo']['kmag'] if
               ('kmag' in cpd['objectinfo'] and
                cpd['objectinfo']['kmag'] is not None)
               else npnan))),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )
        objinfodraw.text(
            (900, 250),
            ('BV: %.3f, %.3f' %
             ((cpd['objectinfo']['bmag'] if
               ('bmag' in cpd['objectinfo'] and
                cpd['objectinfo']['bmag'] is not None)
               else npnan),
              (cpd['objectinfo']['vmag'] if
               ('vmag' in cpd['objectinfo'] and
                cpd['objectinfo']['vmag'] is not None)
               else npnan))),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

        # colors
        if ('dereddened' in cpd['objectinfo'] and
            cpd['objectinfo']['dereddened'] is True):
            deredlabel = "(dereddened)"
        else:
            deredlabel = ""

        objinfodraw.text(
            (625, 275),
            'Colors %s' % deredlabel,
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

        objinfodraw.text(
            (900, 275),
            ('B - V: %.3f, V - K: %.3f' %
             ( (cpd['objectinfo']['bvcolor'] if
                ('bvcolor' in cpd['objectinfo'] and
                 cpd['objectinfo']['bvcolor'] is not None)
                else npnan),
               (cpd['objectinfo']['vkcolor'] if
                ('vkcolor' in cpd['objectinfo'] and
                 cpd['objectinfo']['vkcolor'] is not None)
                else npnan) )),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )
        objinfodraw.text(
            (900, 300),
            ('i - J: %.3f, g - K: %.3f' %
             ( (cpd['objectinfo']['ijcolor'] if
                ('ijcolor' in cpd['objectinfo'] and
                 cpd['objectinfo']['ijcolor'] is not None)
                else npnan),
               (cpd['objectinfo']['gkcolor'] if
                ('gkcolor' in cpd['objectinfo'] and
                 cpd['objectinfo']['gkcolor'] is not None)
                else npnan) )),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )
        objinfodraw.text(
            (900, 325),
            ('J - K: %.3f' %
             ( (cpd['objectinfo']['jkcolor'] if
                ('jkcolor' in cpd['objectinfo'] and
                 cpd['objectinfo']['jkcolor'] is not None)
                else npnan),) ),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

    #
    # rest of the object information
    #

    # color classification
    if ('color_classes' in cpd['objectinfo'] and
        cpd['objectinfo']['color_classes']):

        objinfodraw.text(
            (625, 375),
            ('star classification by color: %s' %
             (', '.join(cpd['objectinfo']['color_classes']))),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

    # GAIA neighbors
    if ( ('gaia_neighbors' in cpd['objectinfo']) and
         (cpd['objectinfo']['gaia_neighbors'] is not None) and
         (np.isfinite(cpd['objectinfo']['gaia_neighbors'])) and
         ('searchradarcsec' in cpd['objectinfo']) and
         (cpd['objectinfo']['searchradarcsec']) ):

        objinfodraw.text(
            (625, 400),
            ('%s GAIA close neighbors within %.1f arcsec' %
             (cpd['objectinfo']['gaia_neighbors'],
              cpd['objectinfo']['searchradarcsec'])),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

    # closest GAIA neighbor
    if ( ('gaia_closest_distarcsec' in cpd['objectinfo']) and
         (cpd['objectinfo']['gaia_closest_distarcsec'] is not None) and
         (np.isfinite(cpd['objectinfo']['gaia_closest_distarcsec'])) and
         ('gaia_closest_gmagdiff' in cpd['objectinfo']) and
         (cpd['objectinfo']['gaia_closest_gmagdiff'] is not None) and
         (np.isfinite(cpd['objectinfo']['gaia_closest_gmagdiff'])) ):

        objinfodraw.text(
            (625, 425),
            ('closest GAIA neighbor is %.1f arcsec away, '
             'GAIA mag (obj-nbr): %.3f' %
             (cpd['objectinfo']['gaia_closest_distarcsec'],
              cpd['objectinfo']['gaia_closest_gmagdiff'])),
            font=cpfontnormal,
            fill=(0,0,0,255)
        )

    # object tags
    if 'objecttags' in cpd['objectinfo'] and cpd['objectinfo']['objecttags']:

        objtagsplit = cpd['objectinfo']['objecttags'].split(',')

        # write three tags per line
        nobjtaglines = int(np.ceil(len(objtagsplit)/3.0))

        for objtagline in range(nobjtaglines):
            objtagslice = ','.join(objtagsplit[objtagline*3:objtagline*3+3])
            objinfodraw.text(
                (625, 450+objtagline*25),
                objtagslice,
                font=cpfontnormal,
                fill=(135, 54, 0, 255)
            )

    ################################################
    # row 1, cell 3: variability info and comments #
    ################################################

    # objectisvar
    objisvar = cpd['varinfo']['objectisvar']

    if objisvar == '0':
        objvarflag = 'Variable star flag not set'
    elif objisvar == '1':
        objvarflag = 'Object is probably a variable star'
    elif objisvar == '2':
        objvarflag = 'Object is probably not a variable star'
    elif objisvar == '3':
        objvarflag = 'Not sure if this object is a variable star'
    elif objisvar is None:
        objvarflag = 'Variable star flag not set'
    elif objisvar is True:
        objvarflag = 'Object is probably a variable star'
    elif objisvar is False:
        objvarflag = 'Object is probably not a variable star'
    else:
        objvarflag = 'Variable star flag: %s' % objisvar

    objinfodraw.text(
        (1650, 125),
        objvarflag,
        font=cpfontnormal,
        fill=(0,0,0,255)
    )

    # period
    objinfodraw.text(
        (1650, 150),
        ('Period [days]: %.6f' %
         (cpd['varinfo']['varperiod']
          if cpd['varinfo']['varperiod'] is not None
          else np.nan)),
        font=cpfontnormal,
        fill=(0,0,0,255)
    )

    # epoch
    objinfodraw.text(
        (1650, 175),
        ('Epoch [JD]: %.6f' %
         (cpd['varinfo']['varepoch']
          if cpd['varinfo']['varepoch'] is not None
          else np.nan)),
        font=cpfontnormal,
        fill=(0,0,0,255)
    )

    # variability tags
    if cpd['varinfo']['vartags']:

        vartagsplit = cpd['varinfo']['vartags'].split(',')

        # write three tags per line
        nvartaglines = int(np.ceil(len(vartagsplit)/3.0))

        for vartagline in range(nvartaglines):
            vartagslice = ','.join(vartagsplit[vartagline*3:vartagline*3+3])
            objinfodraw.text(
                (1650, 225+vartagline*25),
                vartagslice,
                font=cpfontnormal,
                fill=(135, 54, 0, 255)
            )

    # object comments
    if 'comments' in cpd and cpd['comments']:

        commentsplit = cpd['comments'].split(' ')

        # write 10 words per line
        ncommentlines = int(np.ceil(len(commentsplit)/10.0))

        for commentline in range(ncommentlines):
            commentslice = ' '.join(
                commentsplit[commentline*10:commentline*10+10]
            )
            objinfodraw.text(
                (1650, 325+commentline*25),
                commentslice,
                font=cpfontnormal,
                fill=(0,0,0,255)
            )

    # this handles JSON-ified checkplots returned by LCC server
    elif 'objectcomments' in cpd and cpd['objectcomments']:

        commentsplit = cpd['objectcomments'].split(' ')

        # write 10 words per line
        ncommentlines = int(np.ceil(len(commentsplit)/10.0))

        for commentline in range(ncommentlines):
            commentslice = ' '.join(
                commentsplit[commentline*10:commentline*10+10]
            )
            objinfodraw.text(
                (1650, 325+commentline*25),
                commentslice,
                font=cpfontnormal,
                fill=(0,0,0,255)
            )

    #######################################
    # row 1, cell 4: unphased light curve #
    #######################################

    if (cpd['magseries'] and
        'plot' in cpd['magseries'] and
        cpd['magseries']['plot']):
        magseries = Image.open(
            _base64_to_file(cpd['magseries']['plot'], None, writetostrio=True)
        )
        outimg.paste(magseries,(750*3,0))

    # this handles JSON-ified checkplots from LCC server
    elif ('magseries' in cpd and isinstance(cpd['magseries'],str)):

        magseries = Image.open(
            _base64_to_file(cpd['magseries'], None, writetostrio=True)
        )
        outimg.paste(magseries,(750*3,0))

    ###############################
    # the rest of the rows in cpd #
    ###############################
    for lspmethodind, lspmethod in enumerate(cplspmethods):

        ###############################
        # the periodogram comes first #
        ###############################

        if (cpd[lspmethod] and cpd[lspmethod]['periodogram']):

            pgram = Image.open(
                _base64_to_file(cpd[lspmethod]['periodogram'], None,
                                writetostrio=True)
            )
            outimg.paste(pgram,(0,480 + 480*lspmethodind))

        #############################
        # best phased LC comes next #
        #############################

        if (cpd[lspmethod] and 0 in cpd[lspmethod] and cpd[lspmethod][0]):

            plc1 = Image.open(
                _base64_to_file(cpd[lspmethod][0]['plot'], None,
                                writetostrio=True)
            )
            outimg.paste(plc1,(750,480 + 480*lspmethodind))

        # this handles JSON-ified checkplots from LCC server
        elif (cpd[lspmethod] and 'phasedlc0' in cpd[lspmethod] and
              isinstance(cpd[lspmethod]['phasedlc0']['plot'], str)):

            plc1 = Image.open(
                _base64_to_file(cpd[lspmethod]['phasedlc0']['plot'], None,
                                writetostrio=True)
            )
            outimg.paste(plc1,(750,480 + 480*lspmethodind))

        #################################
        # 2nd best phased LC comes next #
        #################################

        if (cpd[lspmethod] and 1 in cpd[lspmethod] and cpd[lspmethod][1]):

            plc2 = Image.open(
                _base64_to_file(cpd[lspmethod][1]['plot'], None,
                                writetostrio=True)
            )
            outimg.paste(plc2,(750*2,480 + 480*lspmethodind))

        # this handles JSON-ified checkplots from LCC server
        elif (cpd[lspmethod] and 'phasedlc1' in cpd[lspmethod] and
              isinstance(cpd[lspmethod]['phasedlc1']['plot'], str)):

            plc2 = Image.open(
                _base64_to_file(cpd[lspmethod]['phasedlc1']['plot'], None,
                                writetostrio=True)
            )
            outimg.paste(plc2,(750*2,480 + 480*lspmethodind))

        #################################
        # 3rd best phased LC comes next #
        #################################

        if (cpd[lspmethod] and 2 in cpd[lspmethod] and cpd[lspmethod][2]):

            plc3 = Image.open(
                _base64_to_file(cpd[lspmethod][2]['plot'], None,
                                writetostrio=True)
            )
            outimg.paste(plc3,(750*3,480 + 480*lspmethodind))

        # this handles JSON-ified checkplots from LCC server
        elif (cpd[lspmethod] and 'phasedlc2' in cpd[lspmethod] and
              isinstance(cpd[lspmethod]['phasedlc2']['plot'], str)):

            plc3 = Image.open(
                _base64_to_file(cpd[lspmethod]['phasedlc2']['plot'], None,
                                writetostrio=True)
            )
            outimg.paste(plc3,(750*3,480 + 480*lspmethodind))

    ################################
    ## ALL DONE WITH BUILDING PNG ##
    ################################

    #########################
    # add in any extra rows #
    #########################

    # from the keyword arguments
    if erows > 0:

        for erowind, erow in enumerate(extrarows):

            # make sure we never go above 4 plots in a row
            for ecolind, ecol in enumerate(erow[:4]):

                eplot = Image.open(ecol)
                eplotresized = eplot.resize((750,480), Image.ANTIALIAS)
                outimg.paste(eplotresized,
                             (750*ecolind,
                              (cprows+1)*480 + 480*erowind))

    # from the checkplotdict
    if cpderows > 0:

        for cpderowind, cpderow in enumerate(cpd['externalplots']):

            # make sure we never go above 4 plots in a row
            for cpdecolind, cpdecol in enumerate(cpderow[:4]):

                cpdeplot = Image.open(cpdecol)
                cpdeplotresized = cpdeplot.resize((750,480), Image.ANTIALIAS)
                outimg.paste(cpdeplotresized,
                             (750*cpdecolind,
                              (cprows+1)*480 + (erows*480) + 480*cpderowind))

    # from neighbors:
    if nbrrows > 0:

        # we have four tiles
        # tile 1: neighbor objectid, ra, decl, distance, unphased LC
        # tile 2: phased LC for gls
        # tile 3: phased LC for pdm
        # tile 4: phased LC for any other period finding method
        #         the priority is like so: ['bls','mav','aov','win']

        for nbrind, nbr in enumerate(cpd['neighbors']):

            # figure out which period finding methods are available for this
            # neighbor. make sure to match the ones from the actual object in
            # order of priority: 'gls','pdm','bls','aov','mav','acf','win'
            nbrlspmethods = []

            for lspmethod in cpd['pfmethods']:
                if lspmethod in nbr:
                    nbrlspmethods.append(lspmethod)

            # restrict to top three in priority
            nbrlspmethods = nbrlspmethods[:3]

            try:

                # first panel: neighbor objectid, ra, decl, distance, unphased
                # LC
                nbrlc = Image.open(
                    _base64_to_file(
                        nbr['magseries']['plot'], None, writetostrio=True
                    )
                )
                outimg.paste(nbrlc,
                             (750*0,
                              (cprows+1)*480 + (erows*480) + (cpderows*480) +
                              480*nbrind))

                # overlay the objectinfo
                objinfodraw.text(
                    (98,
                     (cprows+1)*480 + (erows*480) + (cpderows*480) +
                     480*nbrind + 15),
                    ('N%s: %s' % (nbrind + 1, nbr['objectid'])),
                    font=cpfontlarge,
                    fill=(0,0,255,255)
                )
                # overlay the objectinfo
                objinfodraw.text(
                    (98,
                     (cprows+1)*480 + (erows*480) + (cpderows*480) +
                     480*nbrind + 50),
                    ('(RA, DEC) = (%.3f, %.3f), distance: %.1f arcsec' %
                     (nbr['ra'], nbr['decl'], nbr['dist'])),
                    font=cpfontnormal,
                    fill=(0,0,255,255)
                )

                # second panel: phased LC for gls
                lsp1lc = Image.open(
                    _base64_to_file(
                        nbr[nbrlspmethods[0]][0]['plot'], None,
                        writetostrio=True
                    )
                )
                outimg.paste(lsp1lc,
                             (750*1,
                              (cprows+1)*480 + (erows*480) + (cpderows*480) +
                              480*nbrind))

                # second panel: phased LC for gls
                lsp2lc = Image.open(
                    _base64_to_file(
                        nbr[nbrlspmethods[1]][0]['plot'], None,
                        writetostrio=True
                    )
                )
                outimg.paste(lsp2lc,
                             (750*2,
                              (cprows+1)*480 + (erows*480) + (cpderows*480) +
                              480*nbrind))

                # second panel: phased LC for gls
                lsp3lc = Image.open(
                    _base64_to_file(
                        nbr[nbrlspmethods[2]][0]['plot'], None,
                        writetostrio=True
                    )
                )
                outimg.paste(lsp3lc,
                             (750*3,
                              (cprows+1)*480 + (erows*480) + (cpderows*480) +
                              480*nbrind))

            except Exception as e:

                LOGERROR('neighbor %s does not have a magseries plot, '
                         'measurements are probably all nan' % nbr['objectid'])

                # overlay the objectinfo
                objinfodraw.text(
                    (98,
                     (cprows+1)*480 + (erows*480) + (cpderows*480) +
                     480*nbrind + 15),
                    ('N%s: %s' %
                     (nbrind + 1, nbr['objectid'])),
                    font=cpfontlarge,
                    fill=(0,0,255,255)
                )

                if 'ra' in nbr and 'decl' in nbr and 'dist' in nbr:

                    # overlay the objectinfo
                    objinfodraw.text(
                        (98,
                         (cprows+1)*480 + (erows*480) + (cpderows*480) +
                         480*nbrind + 50),
                        ('(RA, DEC) = (%.3f, %.3f), distance: %.1f arcsec' %
                         (nbr['ra'], nbr['decl'], nbr['dist'])),
                        font=cpfontnormal,
                        fill=(0,0,255,255)
                    )

                elif 'objectinfo' in nbr:

                    # overlay the objectinfo
                    objinfodraw.text(
                        (98,
                         (cprows+1)*480 + (erows*480) + (cpderows*480) +
                         480*nbrind + 50),
                        ('(RA, DEC) = (%.3f, %.3f), distance: %.1f arcsec' %
                         (nbr['objectinfo']['ra'],
                          nbr['objectinfo']['decl'],
                          nbr['objectinfo']['distarcsec'])),
                        font=cpfontnormal,
                        fill=(0,0,255,255)
                    )

    #####################
    ## WRITE FINAL PNG ##
    #####################

    # check if the output filename is actually an instance of StringIO
    if sys.version_info[:2] < (3,0):

        is_strio = isinstance(outfile, cStringIO.InputType)

    else:

        is_strio = isinstance(outfile, StrIO)

    if not is_strio:

        # check if we've stupidly copied over the same filename as the input
        # pickle to expected output file
        if outfile.endswith('pkl'):
            LOGWARNING('expected output PNG filename ends with .pkl, '
                       'changed to .png')
            outfile = outfile.replace('.pkl','.png')

    outimg.save(outfile, format='PNG', optimize=True)

    if not is_strio:
        if os.path.exists(outfile):
            LOGINFO('checkplot pickle -> checkplot PNG: %s OK' % outfile)
            return outfile
        else:
            LOGERROR('failed to write checkplot PNG')
            return None

    else:
        LOGINFO('checkplot pickle -> StringIO instance OK')
        return outfile

Response: This function reads the checkplot pickle or dict provided and writes out a PNG file.


Instruction:
Here you have a function in Python 3, explain what it does
def flare_model(flareparams, times, mags, errs):
    '''This is a flare model function, similar to Kowalski+ 2011.

    From the paper by Pitkin+ 2014:
    http://adsabs.harvard.edu/abs/2014MNRAS.445.2268P

    Parameters
    ----------

    flareparams : list of float
        This defines the flare model::

            [amplitude,
             flare_peak_time,
             rise_gaussian_stdev,
             decay_time_constant]

        where:

        `amplitude`: the maximum flare amplitude in mags or flux. If flux, then
        amplitude should be positive. If mags, amplitude should be negative.

        `flare_peak_time`: time at which the flare maximum happens.

        `rise_gaussian_stdev`: the stdev of the gaussian describing the rise of
        the flare.

        `decay_time_constant`: the time constant of the exponential fall of the
        flare.

    times,mags,errs : np.array
        The input time-series of measurements and associated errors for which
        the model will be generated. The times will be used to generate
        model mags.

    Returns
    -------

    (modelmags, times, mags, errs) : tuple
        Returns the model mags evaluated at the input time values. Also returns
        the input `times`, `mags`, and `errs`.

    '''

    (amplitude, flare_peak_time,
     rise_gaussian_stdev, decay_time_constant) = flareparams

    zerolevel = np.median(mags)
    modelmags = np.full_like(times, zerolevel)

    # before peak gaussian rise...
    modelmags[times < flare_peak_time] = (
        mags[times < flare_peak_time] +
        amplitude * np.exp(
            -((times[times < flare_peak_time] -
               flare_peak_time) *
              (times[times < flare_peak_time] -
               flare_peak_time)) /
            (2.0*rise_gaussian_stdev*rise_gaussian_stdev)
        )
    )

    # after peak exponential decay...
    modelmags[times > flare_peak_time] = (
        mags[times > flare_peak_time] +
        amplitude * np.exp(
            -((times[times > flare_peak_time] -
               flare_peak_time)) /
            (decay_time_constant)
        )
    )

    return modelmags, times, mags, errs

Response: This function is a flare model function that is similar to Kowalski + 2011. This function is a flare model function similar to Kowalski + 2011.


Instruction:
Can you tell what is the following Python 3 function doing
def flare_model_residual(flareparams, times, mags, errs):
    '''
    This returns the residual between model mags and the actual mags.

    Parameters
    ----------

    flareparams : list of float
        This defines the flare model::

            [amplitude,
             flare_peak_time,
             rise_gaussian_stdev,
             decay_time_constant]

        where:

        `amplitude`: the maximum flare amplitude in mags or flux. If flux, then
        amplitude should be positive. If mags, amplitude should be negative.

        `flare_peak_time`: time at which the flare maximum happens.

        `rise_gaussian_stdev`: the stdev of the gaussian describing the rise of
        the flare.

        `decay_time_constant`: the time constant of the exponential fall of the
        flare.

    times,mags,errs : np.array
        The input time-series of measurements and associated errors for which
        the model will be generated. The times will be used to generate
        model mags.

    Returns
    -------

    np.array
        The residuals between the input `mags` and generated `modelmags`,
        weighted by the measurement errors in `errs`.

    '''

    modelmags, _, _, _ = flare_model(flareparams, times, mags, errs)

    return (mags - modelmags)/errs

Response: This function returns the residual between the model mags and the actual mags.


Instruction:
Explain what the following Python 3 code does
def shutdown_check_handler():

    url = 'http://169.254.169.254/latest/meta-data/spot/instance-action'

    try:
        resp = requests.get(url, timeout=1.0)
        resp.raise_for_status()

        stopinfo = resp.json()
        if 'action' in stopinfo and stopinfo['action'] in ('stop',
                                                           'terminate',
                                                           'hibernate'):
            stoptime = stopinfo['time']
            LOGWARNING('instance is going to %s at %s' % (stopinfo['action'],
                                                          stoptime))

            resp.close()
            return True
        else:
            resp.close()
            return False

    except HTTPError as e:
        resp.close()
        return False

    except Exception as e:
        resp.close()
        return False

Response: This function checks the AWS instance data URL to see if there s a pending shutdown for the instance. If it is we ll check the instance data URL to see if the instance is still safe.