Ejemplo n.º 1
0
def phase_trajectory (table, sid, period='auto', outfile='', season=123, 
                      offset=0):
    """ Does just the trajectory window from earlier. """
    # Loading up the relevant datapoints to plot (note I set flags to 0)
    s_table = season_cut(table, sid, season, flags=0)

    if len(s_table) == 0:
        print "no data here"
        return

    date = s_table.MEANMJDOBS - 54579

    jcol = s_table.JAPERMAG3
    hcol = s_table.HAPERMAG3
    kcol = s_table.KAPERMAG3
    jmh =  s_table.JMHPNT
    hmk =  s_table.HMKPNT

    jerr = s_table.JAPERMAG3ERR
    herr = s_table.HAPERMAG3ERR
    kerr = s_table.KAPERMAG3ERR
    jmherr=s_table.JMHPNTERR
    hmkerr=s_table.HMKPNTERR

# Let's figure out the period.
    if period == 'auto':
        period = 1./test_analyze(date, jcol, jerr)
        print period
    elif period == 'lsp':
        lomb = lsp(date,jcol,6.,6.)
        lsp_freq = lomb[0]
        lsp_power= lomb[1]
        Jmax = lsp_mask( lsp_freq, lsp_power)
        lsp_per = 1./ lomb[0][Jmax]
        period = lsp_per
        print period
        

    if period < 1:
        period_string = "%f hours" % (period*24)
        print period_string
    else:
        period_string = "%f days" % period

    phase = ((date % period) / period + offset) % 1.

    fig = plt.figure(figsize = (10, 6), dpi=80,
                     facecolor='w', edgecolor='k')

    ax_jhk = plt.subplot(1,1,1)
    plot_trajectory_core( ax_jhk, hmk, jmh, phase , label='Phase')
    ax_jhk.set_xlabel( "H-K" )
    ax_jhk.set_ylabel( "J-H")#, {'rotation':'horizontal'})

    return period
Ejemplo n.º 2
0
def plot_page_periods(table, sid, outfile="", name="?", season=123, png_too=False):
    """
    Plot one comprehensive page of periodicity information for one source
    in WFCAM time-series JHK data.

     INPUTS:
             table: An atpy table with WFCAM time-series photometry
             sid: A 13-digit WFCAM source ID

     OPTIONAL INPUTS:
             outfile: an output filename, including path and filetype 
                 extension, to save plot to (rather than plot interactively)
             name: a nickname/designation for your source
             season: which season to plot? 1, 2, 3, or all

     OUTPUTS:
             Returns nothing; optionally saves an output plot.

     PROCEDURE:
             This function calls the Lomb Scargle period-finding algorithm and
             the Palmer fast chi-square minimization period-finding algorithm
             to search for periods in the source, in each band.
             Then it plots the periodogram and the lightcurve folded by the
             two best periods (lomb period and palmer period).
             
    """

    # 1. Loading up the relevant datapoints to plot (note I set flags to 0)
    s_table = season_cut(table, sid, season, flags=0)

    if len(s_table) < 2:
        print "no data here"
        return

    date = s_table.MEANMJDOBS - 54579

    class Band:
        pass

    j = Band()
    h = Band()
    k = Band()
    jmh = Band()
    hmk = Band()
    kdex = Band()

    bands = [j, h, k, jmh, kdex, hmk]

    j.col = s_table.JAPERMAG3
    h.col = s_table.HAPERMAG3
    k.col = s_table.KAPERMAG3
    jmh.col = s_table.JMHPNT
    hmk.col = s_table.HMKPNT

    j.err = s_table.JAPERMAG3ERR
    h.err = s_table.HAPERMAG3ERR
    k.err = s_table.KAPERMAG3ERR
    jmh.err = s_table.JMHPNTERR
    hmk.err = s_table.HMKPNTERR

    kdex.col = 1.71 * hmk.col - jmh.col
    kdex.err = np.sqrt(j.err ** 2 + h.err ** 2 + k.err ** 2)
    # Done loading up data.

    # 2. Compute periods and basic statistics

    # a. Overall stetson variability

    stet = stetson.S(j.col, j.err, h.col, h.err, k.col, k.err)

    for b in bands:

        # b. Lomb-scargle periodogram for each band
        b.lsp = lsp(date, b.col, 6.0, 6.0)

        b.lsp_freq = b.lsp[0]
        b.lsp_power = b.lsp[1]

        Jmax = lsp_mask(b.lsp_freq, b.lsp_power)
        b.lsp_per = 1.0 / b.lsp[0][Jmax]
        print "actually using timing 2"
        print b.lsp_per
        # deprecated:
        # b.lsp_per = 1./ b.lsp[0][b.lsp[3]]

        # c. Fast Chi-squared period for each band

        b.fx2_per = 1.0 / test_analyze(date, b.col, b.err)  # confirmed syntax

    # ok, now as a test let's print these quantities that we just calculated

    #    print "Stetson index: " + str(stet)
    #    for b, n in zip(bands, ('j','h','k', 'j-h','h-k','kdex')):
    #        print n.upper() + " band LSP period: " + str(b.lsp_per)
    #        print n.upper() + " band fx2 period: " + str(b.fx2_per)

    # I gotta silence the output of chi whatever. This may involve some serious popen whatever shit. (or just removing a print statement somewhere...)

    # 3. Create the canvas
    fig = plt.figure(num=None, figsize=(8.5, 11), dpi=80, facecolor="w", edgecolor="k")

    # My first approach: using subplots rather than custom coding

    qs = 3 * np.arange(6)

    for b, q in zip(bands, qs):  # qs: something about dimension parameters:
        #        b.ax1 = fig.add_axes
        b.ax1 = fig.add_subplot(6, 3, q + 1)
        b.ax2 = fig.add_subplot(6, 3, q + 2)
        b.ax3 = fig.add_subplot(6, 3, q + 3)

        b.ax1.plot(1.0 / b.lsp_freq, b.lsp_power)
        b.ax1.set_xscale("log")

    colors = ("b", "g", "r")
    for b, c in zip((j, h, k), colors):

        plot_phase_core(b.ax2, date, b.col, b.err, b.lsp_per, color=c)
        plot_phase_core(b.ax3, date, b.col, b.err, b.fx2_per, color=c)

        b.ax2.invert_yaxis()
        b.ax3.invert_yaxis()

    for b in (jmh, kdex, hmk):
        plot_phase_core(b.ax2, date, b.col, b.err, b.lsp_per)
        plot_phase_core(b.ax3, date, b.col, b.err, b.fx2_per)

        if b is kdex:
            # plot a dotted line:
            xs = [-0.25, 1.25]
            ys = [0.1, 0.1]
            b.ax2.plot(xs, ys, "r--")
            b.ax3.plot(xs, ys, "r--")
            # plot red dots on disky nights:
            disk = np.where(kdex.col > 0.1)

            if date[disk].size > 0:
                plot_phase_core(b.ax2, date[disk], b.col[disk], b.err[disk], b.lsp_per, color="r")
                plot_phase_core(b.ax3, date[disk], b.col[disk], b.err[disk], b.fx2_per, color="r")

    jmean = j.col.mean()
    hmean = h.col.mean()
    kmean = k.col.mean()
    jrms, hrms, krms = j.col.std(), h.col.std(), k.col.std()

    sra, sdec = s_table.RA[0], s_table.DEC[0]
    sPosition = coords.Position((sra, sdec), units="rad")
    sPositionString = sPosition.hmsdms()

    # I'm testing an invisible big axes thing for my title
    #    bigAxes = plt.axes(frameon=False)
    #    plt.xticks([])
    #    plt.yticks([])

    big_title = (
        ("Object %s.\t" % name)
        + (r"$J_{mean} =$ %.2f, $H_{mean} =$ %.2f, $K_{mean} =$ %.2f, " % (jmean, hmean, kmean))
        + "\nSeason %d \t" % season
        + r"$J_{RMS} =$ %.3f, $H_{RMS} =$ %.3f, $K_{RMS} =$ %.3f" % (jrms, hrms, krms)
    )

    mean_per = np.mean([j.lsp_per, h.lsp_per, k.lsp_per, j.fx2_per, h.fx2_per, k.fx2_per])

    second_title = ("Stetson Index: %.1f \n" % stet) + ("Average Period: %.2f days\n" % mean_per)

    #    plt.title(big_title)

    j.ax1.annotate(
        big_title, xy=(0.025, 0.965), xycoords="figure fraction", horizontalalignment="left", verticalalignment="top"
    )

    j.ax3.annotate(
        second_title,
        xy=(0.925, 0.965),
        xycoords="figure fraction",
        horizontalalignment="right",
        verticalalignment="top",
    )

    # Use text and bbox to draw the fitted periods
    for b in bands:
        if b.lsp_per > 0.9:
            b.lsp_per_str = "period: %.2f days" % b.lsp_per
        else:
            b.lsp_per_str = "period: %.2f hours" % (b.lsp_per * 24)

        if b.fx2_per > 0.9:
            b.fx2_per_str = "period: %.2f days" % b.fx2_per
        else:
            b.fx2_per_str = "period: %.2f hours" % (b.fx2_per * 24)

        plt.text(
            0.05,
            0.05,
            b.lsp_per_str,
            horizontalalignment="left",
            verticalalignment="bottom",
            bbox=dict(facecolor="white"),
            transform=b.ax2.transAxes,
        )

        plt.text(
            0.05,
            0.05,
            b.fx2_per_str,
            horizontalalignment="left",
            verticalalignment="bottom",
            bbox=dict(facecolor="white", alpha=0.3),
            transform=b.ax3.transAxes,
        )

    plt.suptitle("Position: %s,       Source ID %d." % (sPositionString, sid))

    j.ax1.set_title("Lomb-Scargle Periodogram", fontsize=10)
    j.ax2.set_title("Best LSP period", fontsize=10)
    j.ax3.set_title("Best fX2 period", fontsize=10)
    hmk.ax1.set_xlabel("Period (days)")
    hmk.ax2.set_xlabel("Phase")
    hmk.ax3.set_xlabel("Phase")

    if outfile == "":
        plt.show()
    else:
        if png_too:
            plt.savefig(outfile + ".pdf")
            plt.savefig(outfile + ".png")
            plt.close()
        else:
            plt.savefig(outfile)
            plt.close()

    return
Ejemplo n.º 3
0
def lsp_power (table, sid, outfile='', name='', season=123, png_too=False):
    """ 
    Plots J, H, K periodograms for one star.

    Inputs:
      table -- atpy table with time series photometry
      sid -- WFCAM source ID of star to plot
      
    Optional inputs:
      outfile -- a place to save the file 
      name -- a short string to display as a name atop the plot
      season -- the usual
      png_too -- if True, saves both a PDF and a PNG of the outfile
                 (note - iff True, don't give a filename extension)
                 """
    
    # Loading up the relevant datapoints to plot (note I set flags to 0)
    s_table = season_cut(table, sid, season, flags=0)

    if len(s_table) < 2:
        print "no data here"
        return

    date = s_table.MEANMJDOBS - 54579

    jcol = s_table.JAPERMAG3
    hcol = s_table.HAPERMAG3
    kcol = s_table.KAPERMAG3
#    jmh =  s_table.JMHPNT
#    hmk =  s_table.HMKPNT

#    jerr = s_table.JAPERMAG3ERR
#    herr = s_table.HAPERMAG3ERR
#    kerr = s_table.KAPERMAG3ERR
#    jmherr=s_table.JMHPNTERR
#    hmkerr=s_table.HMKPNTERR

    jlsp = lsp(date, jcol, 6., 6.)
    hlsp = lsp(date, hcol, 6., 6.)
    klsp = lsp(date, kcol, 6., 6.)

    j_lsp_freq = jlsp[0]
    h_lsp_freq = hlsp[0]
    k_lsp_freq = klsp[0]

    j_lsp_power = jlsp[1]
    h_lsp_power = hlsp[1]
    k_lsp_power = klsp[1]

    # best periods, filtered by the lsp_mask
    j_lsp_per = 1./ j_lsp_freq[ lsp_mask( j_lsp_freq, j_lsp_power) ]    
    h_lsp_per = 1./ h_lsp_freq[ lsp_mask( h_lsp_freq, h_lsp_power) ]
    k_lsp_per = 1./ k_lsp_freq[ lsp_mask( k_lsp_freq, k_lsp_power) ]
    

    fig = plt.figure(figsize = (10, 6), dpi=80,
                     facecolor='w', edgecolor='k')

    ax_j = fig.add_subplot(3,1,1)
    ax_h = fig.add_subplot(3,1,2, sharex=ax_j)
    ax_k = fig.add_subplot(3,1,3, sharex=ax_j)

    ax_j.plot(1./j_lsp_freq, j_lsp_power, 'b')
    ax_h.plot(1./h_lsp_freq, h_lsp_power, 'g')
    ax_k.plot(1./k_lsp_freq, k_lsp_power, 'r')
    
    ax_j.set_xscale('log')
    ax_h.set_xscale('log')
    ax_k.set_xscale('log')

    ax_j.set_title(name)
    ax_k.set_xlabel("Period (days)")
    ax_h.set_ylabel("Periodogram Power")

    # bottom = 0.1
    # height = .25
    # left = 0.075
    # width = 0.5

    # ax_k = fig.add_axes( (left, bottom, width, height) )
    # ax_h = fig.add_axes( (left, bottom+.3, width, height), sharex=ax_k )
    # ax_j = fig.add_axes( (left, bottom+.6, width, height), sharex=ax_k )
    
    # ax_jhk = fig.add_axes( (.65, bottom, .3, .375) )
    # ax_khk = fig.add_axes( (.65, bottom+.475, .3, .375) )

    # # Plot J-band:
    # ax_j.errorbar( date, jcol, yerr=jerr, fmt='bo', ecolor='k')
    # ax_j.invert_yaxis()

    # # Plot H-band:
    # ax_h.errorbar( date, hcol, yerr=herr, fmt='go', ecolor='k' )
    # ax_h.invert_yaxis()

    # # Plot K-band:
    # ax_k.errorbar( date, kcol, yerr=kerr, fmt='ro', ecolor='k' )
    # ax_k.invert_yaxis()

    # # Plot J-H vs H-K
    # plot_trajectory_core( ax_jhk, hmk, jmh, date )

    # # Plot K vs H-K
    # plot_trajectory_core( ax_khk, hmk, kcol, date , ms=False, ctts=False) # gonna update this so that it properly uses K-band main sequence line
    # ax_khk.invert_yaxis()

    # # Hide the bad labels...
    # plt.setp(ax_j.get_xticklabels(), visible=False)
    # plt.setp(ax_h.get_xticklabels(), visible=False)

    # # Label stuff
    # ax_k.set_xlabel( "Time (JD since 04/23/2008)" )

    # ax_j.set_ylabel( "J",{'rotation':'horizontal', 'fontsize':'large'} )
    # ax_h.set_ylabel( "H",{'rotation':'horizontal', 'fontsize':'large'} )
    # ax_k.set_ylabel( "K",{'rotation':'horizontal', 'fontsize':'large'} )

    # ax_jhk.set_xlabel( "H-K" )
    # ax_jhk.set_ylabel( "J-H")#, {'rotation':'horizontal'})
    # ax_khk.set_xlabel( "H-K" )
    # ax_khk.set_ylabel( "K")#, {'rotation':'horizontal'})


    if outfile == '':
        plt.show()
    else:
        if png_too:
            plt.savefig(outfile+".pdf")
            plt.savefig(outfile+".png")
            plt.close()
        else:
            plt.savefig(outfile)
            plt.close()
Ejemplo n.º 4
0
def phase (table, sid, period='auto', outfile='', season=123, offset=0, 
           flags=0, png_too=False):
    """ 
    Plots J, H, K lightcurves, as well as JHK color-color and color-mag
    trajectories, for one star.

    Inputs:
      table -- atpy table with time series photometry
      sid -- WFCAM source ID of star to plot
      
    Optional inputs:
      outfile -- a place to save the file 
      name -- a short string to display as a name atop the plot
      season -- the usual
      png_too -- if True, saves both a PDF and a PNG of the outfile
                 (note - iff True, don't give a filename extension)
      flags -- whether to remove bad observations from plotting, 
               and where to draw the cutoff.

                 """
    
    # Loading up the relevant datapoints to plot 
    # (note I set 'flags' as a keyword)
    s_table = season_cut(table, sid, season, flags=flags)

    if len(s_table) == 0:
        print "no data here"
        return

    date = s_table.MEANMJDOBS - 54579

    jcol = s_table.JAPERMAG3
    hcol = s_table.HAPERMAG3
    kcol = s_table.KAPERMAG3
    jmh =  s_table.JMHPNT
    hmk =  s_table.HMKPNT

    jerr = s_table.JAPERMAG3ERR
    herr = s_table.HAPERMAG3ERR
    kerr = s_table.KAPERMAG3ERR
    jmherr=s_table.JMHPNTERR
    hmkerr=s_table.HMKPNTERR

# Let's figure out the period.
    if period == 'auto':
        period = 1./test_analyze(date, jcol, jerr)
        print period
    elif period == 'lsp':
        lomb = lsp(date,jcol,6.,6.)
        lsp_freq = lomb[0]
        lsp_power= lomb[1]
        Jmax = lsp_mask( lsp_freq, lsp_power)
        lsp_per = 1./ lomb[0][Jmax]
        period = lsp_per
        print period
        

    if period < 1:
        period_string = "%f hours" % (period*24)
        print period_string
    else:
        period_string = "%f days" % period

    phase = ((date % period) / period + offset) % 1.

    fig = plt.figure(figsize = (10, 6), dpi=80,
                     facecolor='w', edgecolor='k')

    bottom = 0.1
    height = .25
    left = 0.075
    width = 0.5

    ax_k = fig.add_axes( (left, bottom, width, height) )
    ax_h = fig.add_axes( (left, bottom+.3, width, height), sharex=ax_k )
    ax_j = fig.add_axes( (left, bottom+.6, width, height), sharex=ax_k )
    
    ax_jhk = fig.add_axes( (.65, bottom, .3, .375) )
    ax_khk = fig.add_axes( (.65, bottom+.475, .3, .375) )

    # Plot J-band:
    plot_phase_core( ax_j, date, jcol, jerr, period, offset=offset, color='b')
#    ax_j.errorbar( date, jcol, yerr=jerr, fmt='bo', ecolor='k')
    ax_j.invert_yaxis()

    # Plot H-band:
    plot_phase_core( ax_h, date, hcol, herr, period, offset=offset, color='g')
#    ax_h.errorbar( date, hcol, yerr=herr, fmt='go', ecolor='k' )
    ax_h.invert_yaxis()

    # Plot K-band:
    plot_phase_core( ax_k, date, kcol, kerr, period, offset=offset, color='r')
#    ax_k.errorbar( date, kcol, yerr=kerr, fmt='ro', ecolor='k' )
    ax_k.invert_yaxis()

    # Plot J-H vs H-K
    plot_trajectory_core( ax_jhk, hmk, jmh, phase , label='Phase')

    # Plot K vs H-K
    plot_trajectory_core( ax_khk, hmk, kcol, phase, label='Phase', ms=False, ctts=False) # gonna update this so that it properly uses K-band main sequence line
    ax_khk.invert_yaxis()

    # Hide the bad labels...
    plt.setp(ax_j.get_xticklabels(), visible=False)
    plt.setp(ax_h.get_xticklabels(), visible=False)

    # Label stuff
    ax_k.set_xlabel( "Phase (Period = %s)" % period_string )

    ax_j.set_ylabel( "J",{'rotation':'horizontal', 'fontsize':'large'} )
    ax_h.set_ylabel( "H",{'rotation':'horizontal', 'fontsize':'large'} )
    ax_k.set_ylabel( "K",{'rotation':'horizontal', 'fontsize':'large'} )

    ax_jhk.set_xlabel( "H-K" )
    ax_jhk.set_ylabel( "J-H")#, {'rotation':'horizontal'})
    ax_khk.set_xlabel( "H-K" )
    ax_khk.set_ylabel( "K")#, {'rotation':'horizontal'})


    if outfile == '':
        plt.show()
    else:
        if png_too:
            plt.savefig(outfile+".pdf")
            plt.savefig(outfile+".png")
            plt.savefig(outfile+".eps")
            plt.close()
        else:
            plt.savefig(outfile)
            plt.close()

    return period
Ejemplo n.º 5
0
def arraystat_2 (table, sid, season=0, rob=True, per=True, flags=0) :
    """ Calculates a complicated number of parameters for a given star.

    Inputs:
      table -- an ATpy table with time-series photometry
      sid -- a WFCAM source ID.
      
    Optional inputs:
      season -- which season to select (1,2,3, or other=All)
      rob -- also use Robust statistics? (takes longer, default True)
      per -- run period-finding? (takes longer, default True)
      flags -- Maximum ppErrBit quality flags to use (default 0)

    Returns:
      ret -- a data structure containing the computed values.
      """
    
    s_table = data_cut( table, [sid], season=season, flags=flags )

    if len(s_table) < 1:
        print "no data for %d!" % sid
        return None
    
    jcol = s_table.JAPERMAG3; jerr = s_table.JAPERMAG3ERR
    hcol = s_table.HAPERMAG3; herr = s_table.HAPERMAG3ERR
    kcol = s_table.KAPERMAG3; kerr = s_table.KAPERMAG3ERR
    jmhcol=s_table.JMHPNT   ; jmherr = s_table.JMHPNTERR
    hmkcol=s_table.HMKPNT   ; hmkerr = s_table.HMKPNTERR
    racol= s_table.RA
    decol= s_table.DEC

    date = s_table.MEANMJDOBS 

    messy_table = data_cut( table, [sid], season=-1 )
    jppcol=messy_table.JPPERRBITS
    hppcol=messy_table.HPPERRBITS
    kppcol=messy_table.KPPERRBITS

    # make an empty data structure and just assign it information, then return 
    # the object itself!!! then there's no more worrying about indices.
    class Empty():
        pass

    ret = Empty()
    
    ret.N = len(s_table)
    ret.RA = racol.mean()
    ret.DEC = decol.mean()
    
    ret.chip = get_chip(date[0], np.degrees(racol[0]), np.degrees(decol[0]))
    if ret.N > 4:
        ret.one_chip = ( get_chip(date[0], racol[0], decol[0]) ==
                         get_chip(date[1], racol[1], decol[1]) ==
                         get_chip(date[2], racol[2], decol[2]) ==
                         get_chip(date[3], racol[3], decol[3]) )
    else:
        ret.one_chip = True
    
    ret.Stetson = stetson.S(jcol, jerr, hcol, herr, kcol, kerr)
    
    ret.j = Empty();   ret.j.data = jcol;   ret.j.err = jerr
    ret.h = Empty();   ret.h.data = hcol;   ret.h.err = herr
    ret.k = Empty();   ret.k.data = kcol;   ret.k.err = kerr
    ret.jmh = Empty(); ret.jmh.data=jmhcol; ret.jmh.err = jmherr
    ret.hmk = Empty(); ret.hmk.data=hmkcol; ret.hmk.err = hmkerr

    bands = [ ret.j, ret.h, ret.k, ret.jmh, ret.hmk ]

    for b in bands:
        # use b.data, b.err
        
        b.rchi2 = reduced_chisq( b.data, b.err )

        b.mean = b.data.mean()
        b.rms = b.data.std()
        b.min = b.data.min()
        b.max = b.data.max()
        b.peak_trough = b.max - b.min

        b.mean_err = b.err.mean()

        # Robust quantifiers simply have an "r" at the end of their names
        if rob:
            b.datar = rb.removeoutliers(b.data, 3, niter=2)
            
            b.meanr = rb.meanr(b.data)
            b.rmsr = rb.stdr(b.data)
            b.minr = b.datar.min()
            b.maxr = b.datar.max()
            b.peak_troughr = b.maxr - b.minr

        # Period finding... is a little dodgy still, and might take forever
        if per:
            
            b.lsp = lsp(date, b.data, 6., 6.) # apologies if this is cluttered
            Jmax = lsp_mask(b.lsp[0], b.lsp[1])
            b.lsp_per = 1./ b.lsp[0][Jmax]
            b.lsp_pow = b.lsp[1][Jmax]
            b.fx2_per = 1./ test_analyze( date, b.data, b.err )

    # Finally we'll want to do the whole slope, distance on the JMH graph
    # (until I get the fitting done, we'll have to use hmk and jmh naively)
    ret.color_slope = (ret.jmh.peak_trough / ret.hmk.peak_trough)
    


    # and the pp_max, using the messy table
    ret.jpp_max = jppcol.max()
    ret.hpp_max = hppcol.max()
    ret.kpp_max = kppcol.max()

    return ret
Ejemplo n.º 6
0
def statcruncher (table, sid, season=0, rob=True, per=True, 
                  graded=False, colorslope=False, flags=0) :
    """ 
    Calculates several statistical properties for a given star.

    Will work with "lonely" datapoints (i.e. not all JHK mags are 
    well-defined). Optionally works with graded data, too!

    Parameters
    ----------
    table : atpy.Table
        Table with time-series photometry
    sid : int
        13-digit WFCAM source ID of star to plot
    season : int, optional
        Which observing season of our dataset (1, 2, 3, or all).
        Any value that is not the integers (1, 2, or 3) will be 
        treated as "no season", and no time-cut will be made.
        Note that this is the default behavior.
    rob : bool, optional 
        Use robust statistics, in addition to normal ones?
        (takes longer, default True)
    per : bool, optional 
        Run period-finding? Uses fast chi-squared and lomb-scargle.
        (takes longer, default True)
    graded : bool, optional
        Also calculate Stetson indices using quality grades as weights?
        Uses stetson_graded; requires that the data has been graded by
        night_cleanser.null_cleanser_grader().
    colorslope : bool, optional
        Calculate color slopes? Runs them over (JvJ-H, KvH-K, J-HvH-K).
        Make sure your data has been color-error-corrected! Default False.
    flags : int, optional 
        Maximum ppErrBit quality flags to use (default 0)

    Returns
    -------
    ret : data structure 
        Contains the computed values.
        They can be accessed as attributes 
        (e.g., "ret.j_mean" or "ret.Stetson").

    """
    
    s_table = data_cut ( table, sid, season=season)

    if len(s_table) < 1:
        print "no data for %d!" % sid
        return None

    # First, let's compute single-band statistics. This will require
    # separate data_cuts on each band.

    full_jtable = band_cut(s_table, 'j')
    full_htable = band_cut(s_table, 'h')
    full_ktable = band_cut(s_table, 'k')

    j_table = band_cut(s_table, 'j', max_flag=flags)
    h_table = band_cut(s_table, 'h', max_flag=flags)
    k_table = band_cut(s_table, 'k', max_flag=flags)

    jmh_table = band_cut(j_table, 'h', max_flag=flags)
    hmk_table = band_cut(h_table, 'k', max_flag=flags)
    
    # jhk_table used only for colorslope
    jhk_table = band_cut( jmh_table, 'k', max_flag=flags)

    # get a date (x-axis) for each 
    jdate = j_table.MEANMJDOBS
    hdate = h_table.MEANMJDOBS
    kdate = k_table.MEANMJDOBS
    jmhdate = jmh_table.MEANMJDOBS
    hmkdate = hmk_table.MEANMJDOBS
#    date = s_table.MEANMJDOBS 
    
    # get a magnitude and magnitude error for each band
    jcol = j_table.JAPERMAG3; jerr = j_table.JAPERMAG3ERR
    hcol = h_table.HAPERMAG3; herr = h_table.HAPERMAG3ERR
    kcol = k_table.KAPERMAG3; kerr = k_table.KAPERMAG3ERR
    jmhcol= jmh_table.JMHPNT; jmherr = jmh_table.JMHPNTERR
    hmkcol= hmk_table.HMKPNT; hmkerr = hmk_table.HMKPNTERR

    # get the RA and DEC columns, checking for sensible values
    racol= s_table.RA[(s_table.RA > 0) & (s_table.RA < 7)]
    decol= s_table.DEC[(s_table.DEC > -4) & (s_table.DEC < 4)]

    # Now let's get some ability to track errorful data.
    # messy_table_j = band_cut( s_table, 'j')
    # messy_table_h = band_cut( s_table, 'h')
    # messy_table_k = band_cut( s_table, 'k')
    # jppcol = messy_table_j.JPPERRBITS
    # hppcol = messy_table_h.HPPERRBITS
    # kppcol = messy_table_k.KPPERRBITS

    # make an empty data structure and just assign it information, then return 
    # the object itself! then there's no more worrying about indices.
    class Empty():
        pass

    ret = Empty()
    
    # How many nights have observations in each band?
    ret.N_j = len(j_table)
    ret.N_h = len(h_table)
    ret.N_k = len(k_table)

    # What's the distribution of flags and nights?
    js = full_jtable.JPPERRBITS
    hs = full_htable.HPPERRBITS
    ks = full_ktable.KPPERRBITS

    ret.N_j_noflag = len(js[js == 0])
    ret.N_h_noflag = len(hs[hs == 0])
    ret.N_k_noflag = len(ks[ks == 0])

    ret.N_j_info = len(js[(js < 256) & (js > 0)])
    ret.N_h_info = len(hs[(hs < 256) & (hs > 0)])
    ret.N_k_info = len(ks[(ks < 256) & (ks > 0)])

    ret.N_j_warn = len(js[ js >= 256 ])
    ret.N_h_warn = len(hs[ hs >= 256 ])
    ret.N_k_warn = len(ks[ ks >= 256 ])


    # Mean position of this source
    ret.RA = racol.mean()
    ret.DEC = decol.mean()
    
    # Calculate the Stetson index...
    S, choice, stetson_nights = Stetson_machine (s_table, flags)
    
    ret.Stetson = S
    ret.Stetson_choice = choice
    ret.Stetson_N = stetson_nights

    if graded:
        # Calculate the graded Stetson index...
        g_S, g_choice, g_stetson_nights = (
            graded_Stetson_machine (s_table, flags) )
    
        ret.graded_Stetson = g_S
        ret.graded_Stetson_choice = g_choice
        ret.graded_Stetson_N = g_stetson_nights


    # Calculate PSTAR parameters
    ret.pstar_mean = s_table.PSTAR.mean()
    ret.pstar_median = np.median(s_table.PSTAR)
    ret.pstar_rms = s_table.PSTAR.std()

    # Create parallel data structures for each band, so we can iterate
    ret.j = Empty(); ret.j.data = jcol; ret.j.err = jerr; ret.j.date = jdate   
    ret.h = Empty(); ret.h.data = hcol; ret.h.err = herr; ret.h.date = hdate
    ret.k = Empty(); ret.k.data = kcol; ret.k.err = kerr; ret.k.date = kdate
    ret.jmh = Empty(); ret.jmh.data=jmhcol; ret.jmh.err = jmherr 
    ret.hmk = Empty(); ret.hmk.data=hmkcol; ret.hmk.err = hmkerr
    ret.jmh.date = jmhdate; ret.hmk.date = hmkdate

    ret.j.N = ret.N_j ; ret.h.N = ret.N_h ; ret.k.N = ret.N_k
    ret.jmh.N = len(jmh_table) ; ret.hmk.N = len(hmk_table)

    bands = [ ret.j, ret.h, ret.k, ret.jmh, ret.hmk ]

    for b in bands:
        # use b.data, b.err
        
        # if this band is empty, don't try to do the following assignments
        if b.N == 0: continue

        b.rchi2 = reduced_chisq( b.data, b.err )

        b.mean = b.data.mean()
        b.median = np.median(b.data) # dao
        b.rms = b.data.std()
        b.min = b.data.min()
        b.max = b.data.max()
        b.range = b.max - b.min

        b.err_mean = b.err.mean() #dao
        b.err_median = np.median(b.err) #dao
        b.err_rms = b.err.std() #dao
        b.err_min = b.err.min() #dao
        b.err_max = b.err.max() #dao
        b.err_range = b.err_max - b.err_min #dao


        # Robust quantifiers simply have an "r" at the end of their names
        if rob:
            b.datar, b.indr = rb.removeoutliers(b.data, 3, niter=2, retind=True)
            b.errr = b.err[b.indr]
            
            b.meanr = rb.meanr(b.data)
            b.medianr = rb.medianr(b.data) # dao
            b.rmsr = rb.stdr(b.data)
            b.minr = b.datar.min()
            b.maxr = b.datar.max()
            b.ranger = b.maxr - b.minr

            b.err_meanr = b.errr.mean() # dao
            b.err_medianr = np.median(b.errr) #dao
            b.err_rmsr = b.errr.std() #dao
            b.err_minr = b.errr.min() #dao
            b.err_maxr = b.errr.max() #dao
            b.err_ranger = b.err_maxr - b.err_minr #dao

        # Period finding... is a little dodgy still, and might take forever
        if per==True and b.N > 2:

            hifac = lsp_tuning(b.date)
            
            b.lsp = lsp(b.date, b.data, 6., hifac) 
            Jmax = lsp_mask(b.lsp[0], b.lsp[1])
            b.lsp_per = 1./ b.lsp[0][Jmax]
            b.lsp_pow = b.lsp[1][Jmax]
            b.lsp_sig = getSignificance(b.lsp[0], b.lsp[1], b.lsp[2], 6.)[Jmax]

            best_freq, chimin = test_analyze( b.date, b.data, b.err, 
                                              ret_chimin=True )

            b.fx2_per, b.fx2_chimin = 1./best_freq, chimin
            

    if colorslope:
        # J vs J-H : use jmh_table exclusively
        (ret.jjh_slope, a, ret.jjh_slope_err) = (
            slope( jmh_table.JMHPNT, jmh_table.JAPERMAG3, 
                   jmh_table.JMHPNTERR, jmh_table.JAPERMAG3ERR, 
                   verbose=False) )
        # K vs H-K : use hmk_table exclusively
        (ret.khk_slope, a, ret.khk_slope_err) = (
            slope( hmk_table.HMKPNT, hmk_table.KAPERMAG3, 
                   hmk_table.HMKPNTERR, hmk_table.KAPERMAG3ERR,
                   verbose=False) )
        # J-H vs H-K : use jhk_table exclusively
        (ret.jhk_slope, a, ret.jhk_slope_err) = (
            slope( jhk_table.HMKPNT, jhk_table.JMHPNT, 
                   jhk_table.HMKPNTERR, jhk_table.JMHPNTERR,
                   verbose=False) )
        
    # and the pp_max, using the messy table
    # (slated for a re-implementation)
    # ret.jpp_max = jppcol.max()
    # ret.hpp_max = hppcol.max()
    # ret.kpp_max = kppcol.max()

    return ret