signature:
def sigclip_magseries ( times , mags , errs , sigclip = None , iterative = False , niterations = None , meanormedian = 'median' , magsarefluxes = False ) :
    '''Sigma - clips a magnitude or flux time - series .
 Selects the finite times , magnitudes ( or fluxes ) , and errors from the passed
 values , and apply symmetric or asymmetric sigma clipping to them .
 Parameters
 times , mags , errs : np . array
 The magnitude or flux time - series arrays to sigma - clip . This doesn ' t
 assume all values are finite or if they ' re positive / negative . All of
 these arrays will have their non - finite elements removed , and then will
 be sigma - clipped based on the arguments to this function .
 ` errs ` is optional . Set it to None if you don ' t have values for these . A
 ' faked ' ` errs ` array will be generated if necessary , which can be
 ignored in the output as well .
 sigclip : float or int or sequence of two floats / ints or None
 If a single float or int , a symmetric sigma - clip will be performed using
 the number provided as the sigma - multiplier to cut out from the input
 time - series .
 If a list of two ints / floats is provided , the function will perform an
 ' asymmetric ' sigma - clip . The first element in this list is the sigma
 value to use for fainter flux / mag values ; the second element in this
 list is the sigma value to use for brighter flux / mag values . For
 example , ` sigclip = [ 10 . , 3 . ] ` , will sigclip out greater than 10 - sigma
 dimmings and greater than 3 - sigma brightenings . Here the meaning of
 " dimming " and " brightening " is set by * physics * ( not the magnitude
 system ) , which is why the ` magsarefluxes ` kwarg must be correctly set .
 If ` sigclip ` is None , no sigma - clipping will be performed , and the
 time - series ( with non - finite elems removed ) will be passed through to
 the output .
 iterative : bool
 If this is set to True , will perform iterative sigma - clipping . If
 ` niterations ` is not set and this is True , sigma - clipping is iterated
 until no more points are removed .
 niterations : int
 The maximum number of iterations to perform for sigma - clipping . If None ,
 the ` iterative ` arg takes precedence , and ` iterative = True ` will
 sigma - clip until no more points are removed . If ` niterations ` is not
 None and ` iterative ` is False , ` niterations ` takes precedence and
 iteration will occur for the specified number of iterations .
 meanormedian : { ' mean ' , ' median ' }
 Use ' mean ' for sigma - clipping based on the mean value , or ' median ' for
 sigma - clipping based on the median value . Default is ' median ' .
 magsareflux : bool
 True if your " mags " are in fact fluxes , i . e . if " fainter " corresponds to
 ` mags ` getting smaller .
 Returns
 ( stimes , smags , serrs ) : tuple
 The sigma - clipped and nan - stripped time - series .'''

implementation:
returnerrs = True
# fake the errors if they don ' t exist
# this is inconsequential to sigma - clipping
# we don ' t return these dummy values if the input errs are None
if errs is None : # assume 0.1 % errors if not given
# this should work for mags and fluxes
    errs = 0.001 * mags
    returnerrs = False
# filter the input times , mags , errs ; do sigclipping and normalization
find = npisfinite ( times ) & npisfinite ( mags ) & npisfinite ( errs )
ftimes , fmags , ferrs = times [ find ] , mags [ find ] , errs [ find ]
# get the center value and stdev
if meanormedian == 'median' : # stddev = 1.483 x MAD
    center_mag = npmedian ( fmags )
    stddev_mag = ( npmedian ( npabs ( fmags - center_mag ) ) ) * 1.483
elif meanormedian == 'mean' :
    center_mag = npmean ( fmags )
    stddev_mag = npstddev ( fmags )
else :
    LOGWARNING ( "unrecognized meanormedian value given to " "sigclip_magseries: %s, defaulting to 'median'" % meanormedian )
    meanormedian = 'median'
    center_mag = npmedian ( fmags )
    stddev_mag = ( npmedian ( npabs ( fmags - center_mag ) ) ) * 1.483
# sigclip next for a single sigclip value
if sigclip and isinstance ( sigclip , ( float , int ) ) :
    if not iterative and niterations is None :
        sigind = ( npabs ( fmags - center_mag ) ) < ( sigclip * stddev_mag )
        stimes = ftimes [ sigind ]
        smags = fmags [ sigind ]
        serrs = ferrs [ sigind ]
    else : # iterative version adapted from scipy . stats . sigmaclip
    # First , if niterations is not set , iterate until covergence
        if niterations is None :
            delta = 1
            this_times = ftimes
            this_mags = fmags
            this_errs = ferrs
            while delta :
                if meanormedian == 'mean' :
                    this_center = npmean ( this_mags )
                    this_stdev = npstddev ( this_mags )
                elif meanormedian == 'median' :
                    this_center = npmedian ( this_mags )
                    this_stdev = ( npmedian ( npabs ( this_mags - this_center ) ) ) * 1.483
                this_size = this_mags . size
                # apply the sigclip
                tsi = ( ( npabs ( this_mags - this_center ) ) < ( sigclip * this_stdev ) )
                # update the arrays
                this_times = this_times [ tsi ]
                this_mags = this_mags [ tsi ]
                this_errs = this_errs [ tsi ]
                # update delta and go to the top of the loop
                delta = this_size - this_mags . size
        else : # If iterating only a certain number of times
            this_times = ftimes
            this_mags = fmags
            this_errs = ferrs
            iter_num = 0
            delta = 1
            while iter_num < niterations and delta :
                if meanormedian == 'mean' :
                    this_center = npmean ( this_mags )
                    this_stdev = npstddev ( this_mags )
                elif meanormedian == 'median' :
                    this_center = npmedian ( this_mags )
                    this_stdev = ( npmedian ( npabs ( this_mags - this_center ) ) ) * 1.483
                this_size = this_mags . size
                # apply the sigclip
                tsi = ( ( npabs ( this_mags - this_center ) ) < ( sigclip * this_stdev ) )
                # update the arrays
                this_times = this_times [ tsi ]
                this_mags = this_mags [ tsi ]
                this_errs = this_errs [ tsi ]
                # update the number of iterations and delta and
                # go to the top of the loop
                delta = this_size - this_mags . size
                iter_num += 1
        # final sigclipped versions
        stimes , smags , serrs = this_times , this_mags , this_errs
# this handles sigclipping for asymmetric + ve and - ve clip values
elif sigclip and isinstance ( sigclip , ( list , tuple ) ) and len ( sigclip ) == 2 : # sigclip is passed as [ dimmingclip , brighteningclip ]
    dimmingclip = sigclip [ 0 ]
    brighteningclip = sigclip [ 1 ]
    if not iterative and niterations is None :
        if magsarefluxes :
            nottoodimind = ( ( fmags - center_mag ) > ( - dimmingclip * stddev_mag ) )
            nottoobrightind = ( ( fmags - center_mag ) < ( brighteningclip * stddev_mag ) )
        else :
            nottoodimind = ( ( fmags - center_mag ) < ( dimmingclip * stddev_mag ) )
            nottoobrightind = ( ( fmags - center_mag ) > ( - brighteningclip * stddev_mag ) )
        sigind = nottoodimind & nottoobrightind
        stimes = ftimes [ sigind ]
        smags = fmags [ sigind ]
        serrs = ferrs [ sigind ]
    else : # iterative version adapted from scipy . stats . sigmaclip
        if niterations is None :
            delta = 1
            this_times = ftimes
            this_mags = fmags
            this_errs = ferrs
            while delta :
                if meanormedian == 'mean' :
                    this_center = npmean ( this_mags )
                    this_stdev = npstddev ( this_mags )
                elif meanormedian == 'median' :
                    this_center = npmedian ( this_mags )
                    this_stdev = ( npmedian ( npabs ( this_mags - this_center ) ) ) * 1.483
                this_size = this_mags . size
                if magsarefluxes :
                    nottoodimind = ( ( this_mags - this_center ) > ( - dimmingclip * this_stdev ) )
                    nottoobrightind = ( ( this_mags - this_center ) < ( brighteningclip * this_stdev ) )
                else :
                    nottoodimind = ( ( this_mags - this_center ) < ( dimmingclip * this_stdev ) )
                    nottoobrightind = ( ( this_mags - this_center ) > ( - brighteningclip * this_stdev ) )
                # apply the sigclip
                tsi = nottoodimind & nottoobrightind
                # update the arrays
                this_times = this_times [ tsi ]
                this_mags = this_mags [ tsi ]
                this_errs = this_errs [ tsi ]
                # update delta and go to top of the loop
                delta = this_size - this_mags . size
        else : # If iterating only a certain number of times
            this_times = ftimes
            this_mags = fmags
            this_errs = ferrs
            iter_num = 0
            delta = 1
            while iter_num < niterations and delta :
                if meanormedian == 'mean' :
                    this_center = npmean ( this_mags )
                    this_stdev = npstddev ( this_mags )
                elif meanormedian == 'median' :
                    this_center = npmedian ( this_mags )
                    this_stdev = ( npmedian ( npabs ( this_mags - this_center ) ) ) * 1.483
                this_size = this_mags . size
                if magsarefluxes :
                    nottoodimind = ( ( this_mags - this_center ) > ( - dimmingclip * this_stdev ) )
                    nottoobrightind = ( ( this_mags - this_center ) < ( brighteningclip * this_stdev ) )
                else :
                    nottoodimind = ( ( this_mags - this_center ) < ( dimmingclip * this_stdev ) )
                    nottoobrightind = ( ( this_mags - this_center ) > ( - brighteningclip * this_stdev ) )
                # apply the sigclip
                tsi = nottoodimind & nottoobrightind
                # update the arrays
                this_times = this_times [ tsi ]
                this_mags = this_mags [ tsi ]
                this_errs = this_errs [ tsi ]
                # update the number of iterations and delta
                # and go to top of the loop
                delta = this_size - this_mags . size
                iter_num += 1
        # final sigclipped versions
        stimes , smags , serrs = this_times , this_mags , this_errs
else :
    stimes = ftimes
    smags = fmags
    serrs = ferrs
if returnerrs :
    return stimes , smags , serrs
else :
    return stimes , smags , None


signature:
def sigclip_magseries_with_extparams ( times , mags , errs , extparams , sigclip = None , iterative = False , magsarefluxes = False ) :
    '''Sigma - clips a magnitude or flux time - series and associated measurement
 arrays .
 Selects the finite times , magnitudes ( or fluxes ) , and errors from the passed
 values , and apply symmetric or asymmetric sigma clipping to them . Uses the
 same array indices as these values to filter out the values of all arrays in
 the ` extparams ` list . This can be useful for simultaneously sigma - clipping a
 magnitude / flux time - series along with their associated values of external
 parameters , such as telescope hour angle , zenith distance , temperature , moon
 phase , etc .
 Parameters
 times , mags , errs : np . array
 The magnitude or flux time - series arrays to sigma - clip . This doesn ' t
 assume all values are finite or if they ' re positive / negative . All of
 these arrays will have their non - finite elements removed , and then will
 be sigma - clipped based on the arguments to this function .
 ` errs ` is optional . Set it to None if you don ' t have values for these . A
 ' faked ' ` errs ` array will be generated if necessary , which can be
 ignored in the output as well .
 extparams : list of np . array
 This is a list of all external parameter arrays to simultaneously filter
 along with the magnitude / flux time - series . All of these arrays should
 have the same length as the ` times ` , ` mags ` , and ` errs ` arrays .
 sigclip : float or int or sequence of two floats / ints or None
 If a single float or int , a symmetric sigma - clip will be performed using
 the number provided as the sigma - multiplier to cut out from the input
 time - series .
 If a list of two ints / floats is provided , the function will perform an
 ' asymmetric ' sigma - clip . The first element in this list is the sigma
 value to use for fainter flux / mag values ; the second element in this
 list is the sigma value to use for brighter flux / mag values . For
 example , ` sigclip = [ 10 . , 3 . ] ` , will sigclip out greater than 10 - sigma
 dimmings and greater than 3 - sigma brightenings . Here the meaning of
 " dimming " and " brightening " is set by * physics * ( not the magnitude
 system ) , which is why the ` magsarefluxes ` kwarg must be correctly set .
 If ` sigclip ` is None , no sigma - clipping will be performed , and the
 time - series ( with non - finite elems removed ) will be passed through to
 the output .
 iterative : bool
 If this is set to True , will perform iterative sigma - clipping . If
 ` niterations ` is not set and this is True , sigma - clipping is iterated
 until no more points are removed .
 magsareflux : bool
 True if your " mags " are in fact fluxes , i . e . if " fainter " corresponds to
 ` mags ` getting smaller .
 Returns
 ( stimes , smags , serrs ) : tuple
 The sigma - clipped and nan - stripped time - series in ` stimes ` , ` smags ` ,
 ` serrs ` and the associated values of the ` extparams ` in ` sextparams ` .'''

implementation:
returnerrs = True
# fake the errors if they don ' t exist
# this is inconsequential to sigma - clipping
# we don ' t return these dummy values if the input errs are None
if errs is None : # assume 0.1 % errors if not given
# this should work for mags and fluxes
    errs = 0.001 * mags
    returnerrs = False
# filter the input times , mags , errs ; do sigclipping and normalization
find = npisfinite ( times ) & npisfinite ( mags ) & npisfinite ( errs )
ftimes , fmags , ferrs = times [ find ] , mags [ find ] , errs [ find ]
# apply the same indices to the external parameters
for epi , eparr in enumerate ( extparams ) :
    extparams [ epi ] = eparr [ find ]
# get the median and stdev = 1.483 x MAD
median_mag = npmedian ( fmags )
stddev_mag = ( npmedian ( npabs ( fmags - median_mag ) ) ) * 1.483
# sigclip next for a single sigclip value
if sigclip and isinstance ( sigclip , ( float , int ) ) :
    if not iterative :
        sigind = ( npabs ( fmags - median_mag ) ) < ( sigclip * stddev_mag )
        stimes = ftimes [ sigind ]
        smags = fmags [ sigind ]
        serrs = ferrs [ sigind ]
        # apply the same indices to the external parameters
        for epi , eparr in enumerate ( extparams ) :
            extparams [ epi ] = eparr [ sigind ]
    else : # iterative version adapted from scipy . stats . sigmaclip
        delta = 1
        this_times = ftimes
        this_mags = fmags
        this_errs = ferrs
        while delta :
            this_median = npmedian ( this_mags )
            this_stdev = ( npmedian ( npabs ( this_mags - this_median ) ) ) * 1.483
            this_size = this_mags . size
            # apply the sigclip
            tsi = ( npabs ( this_mags - this_median ) ) < ( sigclip * this_stdev )
            # update the arrays
            this_times = this_times [ tsi ]
            this_mags = this_mags [ tsi ]
            this_errs = this_errs [ tsi ]
            # apply the same indices to the external parameters
            for epi , eparr in enumerate ( extparams ) :
                extparams [ epi ] = eparr [ tsi ]
            # update delta and go to the top of the loop
            delta = this_size - this_mags . size
        # final sigclipped versions
        stimes , smags , serrs = this_times , this_mags , this_errs
# this handles sigclipping for asymmetric + ve and - ve clip values
elif sigclip and isinstance ( sigclip , ( list , tuple ) ) and len ( sigclip ) == 2 : # sigclip is passed as [ dimmingclip , brighteningclip ]
    dimmingclip = sigclip [ 0 ]
    brighteningclip = sigclip [ 1 ]
    if not iterative :
        if magsarefluxes :
            nottoodimind = ( ( fmags - median_mag ) > ( - dimmingclip * stddev_mag ) )
            nottoobrightind = ( ( fmags - median_mag ) < ( brighteningclip * stddev_mag ) )
        else :
            nottoodimind = ( ( fmags - median_mag ) < ( dimmingclip * stddev_mag ) )
            nottoobrightind = ( ( fmags - median_mag ) > ( - brighteningclip * stddev_mag ) )
        sigind = nottoodimind & nottoobrightind
        stimes = ftimes [ sigind ]
        smags = fmags [ sigind ]
        serrs = ferrs [ sigind ]
        # apply the same indices to the external parameters
        for epi , eparr in enumerate ( extparams ) :
            extparams [ epi ] = eparr [ sigind ]
    else : # iterative version adapted from scipy . stats . sigmaclip
        delta = 1
        this_times = ftimes
        this_mags = fmags
        this_errs = ferrs
        while delta :
            this_median = npmedian ( this_mags )
            this_stdev = ( npmedian ( npabs ( this_mags - this_median ) ) ) * 1.483
            this_size = this_mags . size
            if magsarefluxes :
                nottoodimind = ( ( this_mags - this_median ) > ( - dimmingclip * this_stdev ) )
                nottoobrightind = ( ( this_mags - this_median ) < ( brighteningclip * this_stdev ) )
            else :
                nottoodimind = ( ( this_mags - this_median ) < ( dimmingclip * this_stdev ) )
                nottoobrightind = ( ( this_mags - this_median ) > ( - brighteningclip * this_stdev ) )
            # apply the sigclip
            tsi = nottoodimind & nottoobrightind
            # update the arrays
            this_times = this_times [ tsi ]
            this_mags = this_mags [ tsi ]
            this_errs = this_errs [ tsi ]
            # apply the same indices to the external parameters
            for epi , eparr in enumerate ( extparams ) :
                extparams [ epi ] = eparr [ tsi ]
            # update delta and go to top of the loop
            delta = this_size - this_mags . size
        # final sigclipped versions
        stimes , smags , serrs = this_times , this_mags , this_errs
else :
    stimes = ftimes
    smags = fmags
    serrs = ferrs
if returnerrs :
    return stimes , smags , serrs , extparams
else :
    return stimes , smags , None , extparams


signature:
def phase_magseries ( times , mags , period , epoch , wrap = True , sort = True ) :
    '''Phases a magnitude / flux time - series using a given period and epoch .
 The equation used is : :
 phase = ( times - epoch ) / period - floor ( ( times - epoch ) / period )
 This phases the given magnitude timeseries using the given period and
 epoch . If wrap is True , wraps the result around 0.0 ( and returns an array
 that has twice the number of the original elements ) . If sort is True ,
 returns the magnitude timeseries in phase sorted order .
 Parameters
 times , mags : np . array
 The magnitude / flux time - series values to phase using the provided
 ` period ` and ` epoch ` . Non - fiinite values will be removed .
 period : float
 The period to use to phase the time - series .
 epoch : float
 The epoch to phase the time - series . This is usually the time - of - minimum
 or time - of - maximum of some periodic light curve
 phenomenon . Alternatively , one can use the minimum time value in
 ` times ` .
 wrap : bool
 If this is True , the returned phased time - series will be wrapped around
 phase 0.0 , which is useful for plotting purposes . The arrays returned
 will have twice the number of input elements because of this wrapping .
 sort : bool
 If this is True , the returned phased time - series will be sorted in
 increasing phase order .
 Returns
 dict
 A dict of the following form is returned : :
 { ' phase ' : the phase values ,
 ' mags ' : the mags / flux values at each phase ,
 ' period ' : the input ` period ` used to phase the time - series ,
 ' epoch ' : the input ` epoch ` used to phase the time - series }'''

implementation:
# find all the finite values of the magnitudes and times
finiteind = np . isfinite ( mags ) & np . isfinite ( times )
finite_times = times [ finiteind ]
finite_mags = mags [ finiteind ]
magseries_phase = ( ( finite_times - epoch ) / period - np . floor ( ( ( finite_times - epoch ) / period ) ) )
outdict = { 'phase' : magseries_phase , 'mags' : finite_mags , 'period' : period , 'epoch' : epoch }
if sort :
    sortorder = np . argsort ( outdict [ 'phase' ] )
    outdict [ 'phase' ] = outdict [ 'phase' ] [ sortorder ]
    outdict [ 'mags' ] = outdict [ 'mags' ] [ sortorder ]
if wrap :
    outdict [ 'phase' ] = np . concatenate ( ( outdict [ 'phase' ] - 1.0 , outdict [ 'phase' ] ) )
    outdict [ 'mags' ] = np . concatenate ( ( outdict [ 'mags' ] , outdict [ 'mags' ] ) )
return outdict


signature:
def time_bin_magseries ( times , mags , binsize = 540.0 , minbinelems = 7 ) :
    '''Bins the given mag / flux time - series in time using the bin size given .
 Parameters
 times , mags : np . array
 The magnitude / flux time - series to bin in time . Non - finite elements will
 be removed from these arrays . At least 10 elements in each array are
 required for this function to operate .
 binsize : float
 The bin size to use to group together measurements closer than this
 amount in time . This is in seconds .
 minbinelems : int
 The minimum number of elements required per bin to include it in the
 output .
 Returns
 dict
 A dict of the following form is returned : :
 { ' jdbin _ indices ' : a list of the index arrays into the nan - filtered
 input arrays per each bin ,
 ' jdbins ' : list of bin boundaries for each bin ,
 ' nbins ' : the number of bins generated ,
 ' binnedtimes ' : the time values associated with each time bin ;
 this is the median of the times in each bin ,
 ' binnedmags ' : the mag / flux values associated with each time bin ;
 this is the median of the mags / fluxes in each bin }'''

implementation:
# check if the input arrays are ok
if not ( times . shape and mags . shape and len ( times ) > 9 and len ( mags ) > 9 ) :
    LOGERROR ( "input time/mag arrays don't have enough elements" )
    return
# find all the finite values of the magnitudes and times
finiteind = np . isfinite ( mags ) & np . isfinite ( times )
finite_times = times [ finiteind ]
finite_mags = mags [ finiteind ]
# convert binsize in seconds to JD units
binsizejd = binsize / ( 86400.0 )
nbins = int ( np . ceil ( ( np . nanmax ( finite_times ) - np . nanmin ( finite_times ) ) / binsizejd ) + 1 )
minjd = np . nanmin ( finite_times )
jdbins = [ ( minjd + x * binsizejd ) for x in range ( nbins ) ]
# make a KD - tree on the JDs so we can do fast distance calculations . we
# need to add a bogus y coord to make this a problem that KD - trees can
# solve .
time_coords = np . array ( [ [ x , 1.0 ] for x in finite_times ] )
jdtree = cKDTree ( time_coords )
binned_finite_timeseries_indices = [ ]
collected_binned_mags = { }
for jd in jdbins : # find all bin indices close to within binsizejd of this point
# using the cKDTree query . we use the p - norm = 1 ( I think this
# means straight - up pairwise distance ? FIXME : check this )
    bin_indices = jdtree . query_ball_point ( np . array ( [ jd , 1.0 ] ) , binsizejd / 2.0 , p = 1.0 )
    # if the bin _ indices have already been collected , then we ' re
    # done with this bin , move to the next one . if they haven ' t ,
    # then this is the start of a new bin .
    if ( bin_indices not in binned_finite_timeseries_indices and len ( bin_indices ) >= minbinelems ) :
        binned_finite_timeseries_indices . append ( bin_indices )
# convert to ndarrays
binned_finite_timeseries_indices = [ np . array ( x ) for x in binned_finite_timeseries_indices ]
collected_binned_mags [ 'jdbins_indices' ] = binned_finite_timeseries_indices
collected_binned_mags [ 'jdbins' ] = jdbins
collected_binned_mags [ 'nbins' ] = len ( binned_finite_timeseries_indices )
# collect the finite _ times
binned_jd = np . array ( [ np . median ( finite_times [ x ] ) for x in binned_finite_timeseries_indices ] )
collected_binned_mags [ 'binnedtimes' ] = binned_jd
collected_binned_mags [ 'binsize' ] = binsize
# median bin the magnitudes according to the calculated indices
collected_binned_mags [ 'binnedmags' ] = ( np . array ( [ np . median ( finite_mags [ x ] ) for x in binned_finite_timeseries_indices ] ) )
return collected_binned_mags


signature:
def phase_bin_magseries ( phases , mags , binsize = 0.005 , minbinelems = 7 ) :
    '''Bins a phased magnitude / flux time - series using the bin size provided .
 Parameters
 phases , mags : np . array
 The phased magnitude / flux time - series to bin in phase . Non - finite
 elements will be removed from these arrays . At least 10 elements in each
 array are required for this function to operate .
 binsize : float
 The bin size to use to group together measurements closer than this
 amount in phase . This is in units of phase .
 minbinelems : int
 The minimum number of elements required per bin to include it in the
 output .
 Returns
 dict
 A dict of the following form is returned : :
 { ' phasebin _ indices ' : a list of the index arrays into the
 nan - filtered input arrays per each bin ,
 ' phasebins ' : list of bin boundaries for each bin ,
 ' nbins ' : the number of bins generated ,
 ' binnedphases ' : the phase values associated with each phase bin ;
 this is the median of the phase value in each bin ,
 ' binnedmags ' : the mag / flux values associated with each phase bin ;
 this is the median of the mags / fluxes in each bin }'''

implementation:
# check if the input arrays are ok
if not ( phases . shape and mags . shape and len ( phases ) > 10 and len ( mags ) > 10 ) :
    LOGERROR ( "input time/mag arrays don't have enough elements" )
    return
# find all the finite values of the magnitudes and phases
finiteind = np . isfinite ( mags ) & np . isfinite ( phases )
finite_phases = phases [ finiteind ]
finite_mags = mags [ finiteind ]
nbins = int ( np . ceil ( ( np . nanmax ( finite_phases ) - np . nanmin ( finite_phases ) ) / binsize ) + 1 )
minphase = np . nanmin ( finite_phases )
phasebins = [ ( minphase + x * binsize ) for x in range ( nbins ) ]
# make a KD - tree on the PHASEs so we can do fast distance calculations . we
# need to add a bogus y coord to make this a problem that KD - trees can
# solve .
time_coords = np . array ( [ [ x , 1.0 ] for x in finite_phases ] )
phasetree = cKDTree ( time_coords )
binned_finite_phaseseries_indices = [ ]
collected_binned_mags = { }
for phase in phasebins : # find all bin indices close to within binsize of this point using the
# cKDTree query . we use the p - norm = 1 for pairwise Euclidean distance .
    bin_indices = phasetree . query_ball_point ( np . array ( [ phase , 1.0 ] ) , binsize / 2.0 , p = 1.0 )
    # if the bin _ indices have already been collected , then we ' re
    # done with this bin , move to the next one . if they haven ' t ,
    # then this is the start of a new bin .
    if ( bin_indices not in binned_finite_phaseseries_indices and len ( bin_indices ) >= minbinelems ) :
        binned_finite_phaseseries_indices . append ( bin_indices )
# convert to ndarrays
binned_finite_phaseseries_indices = [ np . array ( x ) for x in binned_finite_phaseseries_indices ]
collected_binned_mags [ 'phasebins_indices' ] = ( binned_finite_phaseseries_indices )
collected_binned_mags [ 'phasebins' ] = phasebins
collected_binned_mags [ 'nbins' ] = len ( binned_finite_phaseseries_indices )
# collect the finite _ phases
binned_phase = np . array ( [ np . median ( finite_phases [ x ] ) for x in binned_finite_phaseseries_indices ] )
collected_binned_mags [ 'binnedphases' ] = binned_phase
collected_binned_mags [ 'binsize' ] = binsize
# median bin the magnitudes according to the calculated indices
collected_binned_mags [ 'binnedmags' ] = ( np . array ( [ np . median ( finite_mags [ x ] ) for x in binned_finite_phaseseries_indices ] ) )
return collected_binned_mags


signature:
def fill_magseries_gaps ( times , mags , errs , fillgaps = 0.0 , sigclip = 3.0 , magsarefluxes = False , filterwindow = 11 , forcetimebin = None , verbose = True ) :
    '''This fills in gaps in a light curve .
 This is mainly intended for use in ACF period - finding , but maybe useful
 otherwise ( i . e . when we figure out ARMA stuff for LCs ) . The main steps here
 are :
 - normalize the light curve to zero
 - remove giant outliers
 - interpolate gaps in the light curve
 ( since ACF requires evenly spaced sampling )
 From McQuillan + 2013a ( https : / / doi . org / 10.1093 / mnras / stt536 ) :
 " The ACF calculation requires the light curves to be regularly sampled
 and normalized to zero . We divided the flux in each quarter by its
 median and subtracted unity . Gaps in the light curve longer than the
 Kepler long cadence were filled using linear interpolation with added
 white Gaussian noise . This noise level was estimated using the variance
 of the residuals following subtraction of a smoothed version of the
 flux . To smooth the flux , we applied an iterative non - linear filter
 which consists of a median filter followed by a boxcar filter , both with
 11 - point windows , with iterative 3σ clipping of outliers . "
 Parameters
 times , mags , errs : np . array
 The magnitude / flux time - series and associated measurement errors to
 operate on . Non - finite elements will be removed from these arrays . At
 least 10 elements in each array are required for this function to
 operate .
 fillgaps : { ' noiselevel ' , ' nan ' } or float
 If ` fillgap = ' noiselevel ' ` , fills the gaps with the noise level obtained
 via the procedure above . If ` fillgaps = ' nan ' ` , fills the gaps with
 ` np . nan ` . Otherwise , if ` fillgaps ` is a float , will use that value to
 fill the gaps . The default is to fill the gaps with 0.0 ( as in
 McQuillan + 2014 ) to " . . . prevent them contributing to the ACF " .
 sigclip : float or int or sequence of two floats / ints or None
 If a single float or int , a symmetric sigma - clip will be performed using
 the number provided as the sigma - multiplier to cut out from the input
 time - series .
 If a list of two ints / floats is provided , the function will perform an
 ' asymmetric ' sigma - clip . The first element in this list is the sigma
 value to use for fainter flux / mag values ; the second element in this
 list is the sigma value to use for brighter flux / mag values . For
 example , ` sigclip = [ 10 . , 3 . ] ` , will sigclip out greater than 10 - sigma
 dimmings and greater than 3 - sigma brightenings . Here the meaning of
 " dimming " and " brightening " is set by * physics * ( not the magnitude
 system ) , which is why the ` magsarefluxes ` kwarg must be correctly set .
 If ` sigclip ` is None , no sigma - clipping will be performed , and the
 time - series ( with non - finite elems removed ) will be passed through to
 the output .
 magsareflux : bool
 True if your " mags " are in fact fluxes , i . e . if " fainter " corresponds to
 ` mags ` getting smaller .
 filterwindow : int
 The number of time - series points to include in the Savitsky - Golay filter
 operation when smoothing the light curve . This should be an odd integer .
 forcetimebin : float or None
 If ` forcetimebin ` is a float , this value will be used to generate the
 interpolated time series , effectively binning the light curve to this
 cadence . If ` forcetimebin ` is None , the mode of the gaps ( the forward
 difference between successive time values in ` times ` ) in the provided
 light curve will be used as the effective cadence . NOTE : ` forcetimebin `
 must be in the same units as ` times ` , e . g . if times are JD then
 ` forcetimebin ` must be in days as well
 verbose : bool
 If this is True , will indicate progress at various stages in the
 operation .
 Returns
 dict
 A dict of the following form is returned : :
 { ' itimes ' : the interpolated time values after gap - filling ,
 ' imags ' : the interpolated mag / flux values after gap - filling ,
 ' ierrs ' : the interpolated mag / flux values after gap - filling ,
 ' cadence ' : the cadence of the output mag / flux time - series }'''

implementation:
# remove nans
finind = np . isfinite ( times ) & np . isfinite ( mags ) & np . isfinite ( errs )
ftimes , fmags , ferrs = times [ finind ] , mags [ finind ] , errs [ finind ]
# remove zero errs
nzind = np . nonzero ( ferrs )
ftimes , fmags , ferrs = ftimes [ nzind ] , fmags [ nzind ] , ferrs [ nzind ]
# sigma - clip
stimes , smags , serrs = sigclip_magseries ( ftimes , fmags , ferrs , magsarefluxes = magsarefluxes , sigclip = sigclip )
# normalize to zero
if magsarefluxes :
    smags = smags / np . median ( smags ) - 1.0
else :
    smags = smags - np . median ( smags )
if isinstance ( fillgaps , float ) :
    gapfiller = fillgaps
elif isinstance ( fillgaps , str ) and fillgaps == 'noiselevel' : # figure out the gaussian noise level by subtracting a Savitsky - Golay
# filtered version of the light curve
    smoothed = smags - savgol_filter ( smags , filterwindow , 2 )
    noiselevel = 1.483 * np . median ( np . abs ( smoothed - np . median ( smoothed ) ) )
    gapfiller = noiselevel
elif isinstance ( fillgaps , str ) and fillgaps == 'nan' :
    gapfiller = np . nan
# figure out the gap size and where to interpolate . we do this by figuring
# out the most common gap ( this should be the cadence ) . to do this , we need
# to calculate the mode of the gap distribution .
# get the gaps
gaps = np . diff ( stimes )
# just use scipy . stats . mode instead of our hacked together nonsense earlier .
gapmoderes = scipy . stats . mode ( gaps )
gapmode = gapmoderes [ 0 ] . item ( )
LOGINFO ( 'auto-cadence for mag series: %.5f' % gapmode )
# sort the gaps
if forcetimebin :
    LOGWARNING ( 'forcetimebin is set, forcing cadence to %.5f' % forcetimebin )
    gapmode = forcetimebin
if gapmode == 0.0 :
    LOGERROR ( 'the smallest cadence of this light curve appears to be 0.0, ' 'the automatic cadence finder probably failed. ' 'try setting forcetimebin?' )
    return None
starttime , endtime = np . min ( stimes ) , np . max ( stimes )
ntimes = int ( np . ceil ( ( endtime - starttime ) / gapmode ) + 1 )
if verbose :
    LOGINFO ( 'generating new time series with %s measurements' % ntimes )
# first , generate the full time series
interpolated_times = np . linspace ( starttime , endtime , ntimes )
interpolated_mags = np . full_like ( interpolated_times , gapfiller )
interpolated_errs = np . full_like ( interpolated_times , gapfiller )
for ind , itime in enumerate ( interpolated_times [ : - 1 ] ) :
    nextitime = itime + gapmode
    # find the mags between this and the next time bin
    itimeind = np . where ( ( stimes > itime ) & ( stimes < nextitime ) )
    # if there ' s more than one elem in this time bin , median them
    if itimeind [ 0 ] . size > 1 :
        interpolated_mags [ ind ] = np . median ( smags [ itimeind [ 0 ] ] )
        interpolated_errs [ ind ] = np . median ( serrs [ itimeind [ 0 ] ] )
    # otherwise , if there ' s only one elem in this time bin , take it
    elif itimeind [ 0 ] . size == 1 :
        interpolated_mags [ ind ] = smags [ itimeind [ 0 ] ]
        interpolated_errs [ ind ] = serrs [ itimeind [ 0 ] ]
return { 'itimes' : interpolated_times , 'imags' : interpolated_mags , 'ierrs' : interpolated_errs , 'cadence' : gapmode }


signature:
def get_snr_of_dip ( times , mags , modeltimes , modelmags , atol_normalization = 1e-8 , indsforrms = None , magsarefluxes = False , verbose = True , transitdepth = None , npoints_in_transit = None ) :
    '''Calculate the total SNR of a transit assuming gaussian uncertainties .
 ` modelmags ` gets interpolated onto the cadence of ` mags ` . The noise is
 calculated as the 1 - sigma std deviation of the residual ( see below ) .
 Following Carter et al . 2009 : :
 Q = sqrt ( Γ T ) * δ / σ
 for Q the total SNR of the transit in the r - > 0 limit , where : :
 r = Rp / Rstar ,
 T = transit duration ,
 δ = transit depth ,
 σ = RMS of the lightcurve in transit .
 Γ = sampling rate
 Thus Γ * T is roughly the number of points obtained during transit .
 ( This doesn ' t correctly account for the SNR during ingress / egress , but this
 is a second - order correction ) .
 Note this is the same total SNR as described by e . g . , Kovacs et al . 2002,
 their Equation 11.
 NOTE : this only works with fluxes at the moment .
 Parameters
 times , mags : np . array
 The input flux time - series to process .
 modeltimes , modelmags : np . array
 A transiting planet model , either from BLS , a trapezoid model , or a
 Mandel - Agol model .
 atol _ normalization : float
 The absolute tolerance to which the median of the passed model fluxes
 must be equal to 1.
 indsforrms : np . array
 A array of bools of ` len ( mags ) ` used to select points for the RMS
 measurement . If not passed , the RMS of the entire passed timeseries is
 used as an approximation . Genearlly , it ' s best to use out of transit
 points , so the RMS measurement is not model - dependent .
 magsarefluxes : bool
 Currently forced to be True because this function only works with
 fluxes .
 verbose : bool
 If True , indicates progress and warns about problems .
 transitdepth : float or None
 If the transit depth is known , pass it in here . Otherwise , it is
 calculated assuming OOT flux is 1.
 npoints _ in _ transits : int or None
 If the number of points in transit is known , pass it in here . Otherwise ,
 the function will guess at this value .
 Returns
 ( snr , transit _ depth , noise ) : tuple
 The returned tuple contains the calculated SNR , transit depth , and noise
 of the residual lightcurve calculated using the relation described
 above .'''

implementation:
if magsarefluxes :
    if not np . isclose ( np . nanmedian ( modelmags ) , 1 , atol = atol_normalization ) :
        raise AssertionError ( 'snr calculation assumes modelmags are ' 'median-normalized' )
else :
    raise NotImplementedError ( 'need to implement a method for identifying in-transit points when' 'mags are mags, and not fluxes' )
if not transitdepth : # calculate transit depth from whatever model magnitudes are passed .
    transitdepth = np . abs ( np . max ( modelmags ) - np . min ( modelmags ) )
# generally , mags ( data ) and modelmags are at different cadence .
# interpolate modelmags onto the cadence of mags .
if not len ( mags ) == len ( modelmags ) :
    from scipy . interpolate import interp1d
    fn = interp1d ( modeltimes , modelmags , kind = 'cubic' , bounds_error = True , fill_value = np . nan )
    modelmags = fn ( times )
    if verbose :
        LOGINFO ( 'interpolated model timeseries onto the data timeseries' )
subtractedmags = mags - modelmags
if isinstance ( indsforrms , np . ndarray ) :
    subtractedrms = np . std ( subtractedmags [ indsforrms ] )
    if verbose :
        LOGINFO ( 'using selected points to measure RMS' )
else :
    subtractedrms = np . std ( subtractedmags )
    if verbose :
        LOGINFO ( 'using all points to measure RMS' )
def _get_npoints_in_transit ( modelmags ) : # assumes median - normalized fluxes are input
    if np . nanmedian ( modelmags ) == 1 :
        return len ( modelmags [ ( modelmags != 1 ) ] )
    else :
        raise NotImplementedError
if not npoints_in_transit :
    npoints_in_transit = _get_npoints_in_transit ( modelmags )
snr = np . sqrt ( npoints_in_transit ) * transitdepth / subtractedrms
if verbose :
    LOGINFO ( '\npoints in transit: {:d}' . format ( npoints_in_transit ) + '\ndepth: {:.2e}' . format ( transitdepth ) + '\nrms in residual: {:.2e}' . format ( subtractedrms ) + '\n\t SNR: {:.2e}' . format ( snr ) )
return snr , transitdepth , subtractedrms


signature:
def estimate_achievable_tmid_precision ( snr , t_ingress_min = 10 , t_duration_hr = 2.14 ) :
    '''Using Carter et al . 2009 ' s estimate , calculate the theoretical optimal
 precision on mid - transit time measurement possible given a transit of a
 particular SNR .
 The relation used is : :
 sigma _ tc = Q ^ { - 1 } * T * sqrt ( θ / 2)
 Q = SNR of the transit .
 T = transit duration , which is 2.14 hours from discovery paper .
 θ = τ / T = ratio of ingress to total duration
 ~ = ( few minutes [ guess ] ) / 2.14 hours
 Parameters
 snr : float
 The measured signal - to - noise of the transit , e , g . from
 : py : func : ` astrobase . periodbase . kbls . bls _ stats _ singleperiod ` or from
 running the ` . compute _ stats ( ) ` method on an Astropy BoxLeastSquares
 object .
 t _ ingress _ min : float
 The ingress duration in minutes . This is t _ I to t _ II in Winn ( 2010)
 nomenclature .
 t _ duration _ hr : float
 The transit duration in hours . This is t _ I to t _ IV in Winn ( 2010)
 nomenclature .
 Returns
 float
 Returns the precision achievable for transit - center time as calculated
 from the relation above . This is in days .'''

implementation:
t_ingress = t_ingress_min * u . minute
t_duration = t_duration_hr * u . hour
theta = t_ingress / t_duration
sigma_tc = ( 1 / snr * t_duration * np . sqrt ( theta / 2 ) )
LOGINFO ( 'assuming t_ingress = {:.1f}' . format ( t_ingress ) )
LOGINFO ( 'assuming t_duration = {:.1f}' . format ( t_duration ) )
LOGINFO ( 'measured SNR={:.2f}\n\t' . format ( snr ) + '-->theoretical sigma_tc = {:.2e} = {:.2e} = {:.2e}' . format ( sigma_tc . to ( u . minute ) , sigma_tc . to ( u . hour ) , sigma_tc . to ( u . day ) ) )
return sigma_tc . to ( u . day ) . value


signature:
def get_transit_times ( blsd , time , extra_maskfrac , trapd = None , nperiodint = 1000 ) :
    '''Given a BLS period , epoch , and transit ingress / egress points ( usually
 from : py : func : ` astrobase . periodbase . kbls . bls _ stats _ singleperiod ` ) , return
 the times within transit durations + ` extra _ maskfrac ` of each transit .
 Optionally , can use the ( more accurate ) trapezoidal fit period and epoch , if
 it ' s passed . Useful for inspecting individual transits , and masking them
 out if desired .
 Parameters
 blsd : dict
 This is the dict returned by
 : py : func : ` astrobase . periodbase . kbls . bls _ stats _ singleperiod ` .
 time : np . array
 The times from the time - series of transit observations used to calculate
 the initial period .
 extra _ maskfrac : float
 This is the separation from in - transit points you desire , in units of
 the transit duration . ` extra _ maskfrac = 0 ` if you just want points
 inside transit ( see below ) .
 trapd : dict
 This is a dict returned by
 : py : func : ` astrobase . lcfit . transits . traptransit _ fit _ magseries ` containing
 the trapezoid transit model .
 nperiodint : int
 This indicates how many periods backwards / forwards to try and identify
 transits from the epochs reported in ` blsd ` or ` trapd ` .
 Returns
 ( tmids _ obsd , t _ starts , t _ ends ) : tuple of np . array
 The returned items are : :
 tmids _ obsd ( np . ndarray ) : best guess of transit midtimes in
 lightcurve . Has length number of transits in lightcurve .
 t _ starts ( np . ndarray ) : t _ Is - extra _ maskfrac * tdur , for t _ Is transit
 first contact point .
 t _ ends ( np . ndarray ) : t _ Is + extra _ maskfrac * tdur , for t _ Is transit
 first contact point .'''

implementation:
if trapd :
    period = trapd [ 'fitinfo' ] [ 'finalparams' ] [ 0 ]
    t0 = trapd [ 'fitinfo' ] [ 'fitepoch' ]
    transitduration_phase = trapd [ 'fitinfo' ] [ 'finalparams' ] [ 3 ]
    tdur = period * transitduration_phase
else :
    period = blsd [ 'period' ]
    t0 = blsd [ 'epoch' ]
    tdur = ( period * ( blsd [ 'transegressbin' ] - blsd [ 'transingressbin' ] ) / blsd [ 'nphasebins' ] )
    if not blsd [ 'transegressbin' ] > blsd [ 'transingressbin' ] :
        raise NotImplementedError ( 'careful of the width. ' 'this edge case must be dealt with separately.' )
tmids = [ t0 + ix * period for ix in range ( - nperiodint , nperiodint ) ]
sel = ( tmids > np . nanmin ( time ) ) & ( tmids < np . nanmax ( time ) )
tmids_obsd = np . array ( tmids ) [ sel ]
t_Is = tmids_obsd - tdur / 2
t_IVs = tmids_obsd + tdur / 2
# focus on the times around transit
t_starts = t_Is - extra_maskfrac * tdur
t_ends = t_IVs + extra_maskfrac * tdur
return tmids_obsd , t_starts , t_ends


signature:
def given_lc_get_transit_tmids_tstarts_tends ( time , flux , err_flux , blsfit_savpath = None , trapfit_savpath = None , magsarefluxes = True , nworkers = 1 , sigclip = None , extra_maskfrac = 0.03 ) :
    '''Gets the transit start , middle , and end times for transits in a given
 time - series of observations .
 Parameters
 time , flux , err _ flux : np . array
 The input flux time - series measurements and their associated measurement
 errors
 blsfit _ savpath : str or None
 If provided as a str , indicates the path of the fit plot to make for a
 simple BLS model fit to the transit using the obtained period and epoch .
 trapfit _ savpath : str or None
 If provided as a str , indicates the path of the fit plot to make for a
 trapezoidal transit model fit to the transit using the obtained period
 and epoch .
 sigclip : float or int or sequence of two floats / ints or None
 If a single float or int , a symmetric sigma - clip will be performed using
 the number provided as the sigma - multiplier to cut out from the input
 time - series .
 If a list of two ints / floats is provided , the function will perform an
 ' asymmetric ' sigma - clip . The first element in this list is the sigma
 value to use for fainter flux / mag values ; the second element in this
 list is the sigma value to use for brighter flux / mag values . For
 example , ` sigclip = [ 10 . , 3 . ] ` , will sigclip out greater than 10 - sigma
 dimmings and greater than 3 - sigma brightenings . Here the meaning of
 " dimming " and " brightening " is set by * physics * ( not the magnitude
 system ) , which is why the ` magsarefluxes ` kwarg must be correctly set .
 If ` sigclip ` is None , no sigma - clipping will be performed , and the
 time - series ( with non - finite elems removed ) will be passed through to
 the output .
 magsarefluxes : bool
 This is by default True for this function , since it works on fluxes only
 at the moment .
 nworkers : int
 The number of parallel BLS period - finder workers to use .
 extra _ maskfrac : float
 This is the separation ( N ) from in - transit points you desire , in units
 of the transit duration . ` extra _ maskfrac = 0 ` if you just want points
 inside transit , otherwise : :
 t _ starts = t _ Is - N * tdur , t _ ends = t _ IVs + N * tdur
 Thus setting N = 0.03 masks slightly more than the guessed transit
 duration .
 Returns
 ( tmids _ obsd , t _ starts , t _ ends ) : tuple
 The returned items are : :
 tmids _ obsd ( np . ndarray ) : best guess of transit midtimes in
 lightcurve . Has length number of transits in lightcurve .
 t _ starts ( np . ndarray ) : t _ Is - extra _ maskfrac * tdur , for t _ Is transit
 first contact point .
 t _ ends ( np . ndarray ) : t _ Is + extra _ maskfrac * tdur , for t _ Is transit
 first contact point .'''

implementation:
# first , run BLS to get an initial epoch and period .
endp = 1.05 * ( np . nanmax ( time ) - np . nanmin ( time ) ) / 2
blsdict = kbls . bls_parallel_pfind ( time , flux , err_flux , magsarefluxes = magsarefluxes , startp = 0.1 , endp = endp , maxtransitduration = 0.3 , nworkers = nworkers , sigclip = sigclip )
blsd = kbls . bls_stats_singleperiod ( time , flux , err_flux , blsdict [ 'bestperiod' ] , magsarefluxes = True , sigclip = sigclip , perioddeltapercent = 5 )
# plot the BLS model .
if blsfit_savpath :
    make_fit_plot ( blsd [ 'phases' ] , blsd [ 'phasedmags' ] , None , blsd [ 'blsmodel' ] , blsd [ 'period' ] , blsd [ 'epoch' ] , blsd [ 'epoch' ] , blsfit_savpath , magsarefluxes = magsarefluxes )
ingduration_guess = blsd [ 'transitduration' ] * 0.2
# a guesstimate .
transitparams = [ blsd [ 'period' ] , blsd [ 'epoch' ] , blsd [ 'transitdepth' ] , blsd [ 'transitduration' ] , ingduration_guess ]
# fit a trapezoidal transit model ; plot the resulting phased LC .
if trapfit_savpath :
    trapd = traptransit_fit_magseries ( time , flux , err_flux , transitparams , magsarefluxes = magsarefluxes , sigclip = sigclip , plotfit = trapfit_savpath )
# use the trapezoidal model ' s epoch as the guess to identify ( roughly ) in
# and out of transit points
tmids , t_starts , t_ends = get_transit_times ( blsd , time , extra_maskfrac , trapd = trapd )
return tmids , t_starts , t_ends


signature:
def given_lc_get_out_of_transit_points ( time , flux , err_flux , blsfit_savpath = None , trapfit_savpath = None , in_out_transit_savpath = None , sigclip = None , magsarefluxes = True , nworkers = 1 , extra_maskfrac = 0.03 ) :
    '''This gets the out - of - transit light curve points .
 Relevant during iterative masking of transits for multiple planet system
 search .
 Parameters
 time , flux , err _ flux : np . array
 The input flux time - series measurements and their associated measurement
 errors
 blsfit _ savpath : str or None
 If provided as a str , indicates the path of the fit plot to make for a
 simple BLS model fit to the transit using the obtained period and epoch .
 trapfit _ savpath : str or None
 If provided as a str , indicates the path of the fit plot to make for a
 trapezoidal transit model fit to the transit using the obtained period
 and epoch .
 in _ out _ transit _ savpath : str or None
 If provided as a str , indicates the path of the plot file that will be
 made for a plot showing the in - transit points and out - of - transit points
 tagged separately .
 sigclip : float or int or sequence of two floats / ints or None
 If a single float or int , a symmetric sigma - clip will be performed using
 the number provided as the sigma - multiplier to cut out from the input
 time - series .
 If a list of two ints / floats is provided , the function will perform an
 ' asymmetric ' sigma - clip . The first element in this list is the sigma
 value to use for fainter flux / mag values ; the second element in this
 list is the sigma value to use for brighter flux / mag values . For
 example , ` sigclip = [ 10 . , 3 . ] ` , will sigclip out greater than 10 - sigma
 dimmings and greater than 3 - sigma brightenings . Here the meaning of
 " dimming " and " brightening " is set by * physics * ( not the magnitude
 system ) , which is why the ` magsarefluxes ` kwarg must be correctly set .
 If ` sigclip ` is None , no sigma - clipping will be performed , and the
 time - series ( with non - finite elems removed ) will be passed through to
 the output .
 magsarefluxes : bool
 This is by default True for this function , since it works on fluxes only
 at the moment .
 nworkers : int
 The number of parallel BLS period - finder workers to use .
 extra _ maskfrac : float
 This is the separation ( N ) from in - transit points you desire , in units
 of the transit duration . ` extra _ maskfrac = 0 ` if you just want points
 inside transit , otherwise : :
 t _ starts = t _ Is - N * tdur , t _ ends = t _ IVs + N * tdur
 Thus setting N = 0.03 masks slightly more than the guessed transit
 duration .
 Returns
 ( times _ oot , fluxes _ oot , errs _ oot ) : tuple of np . array
 The ` times ` , ` flux ` , ` err _ flux ` values from the input at the time values
 out - of - transit are returned .'''

implementation:
tmids_obsd , t_starts , t_ends = ( given_lc_get_transit_tmids_tstarts_tends ( time , flux , err_flux , blsfit_savpath = blsfit_savpath , trapfit_savpath = trapfit_savpath , magsarefluxes = magsarefluxes , nworkers = nworkers , sigclip = sigclip , extra_maskfrac = extra_maskfrac ) )
in_transit = np . zeros_like ( time ) . astype ( bool )
for t_start , t_end in zip ( t_starts , t_ends ) :
    this_transit = ( ( time > t_start ) & ( time < t_end ) )
    in_transit |= this_transit
out_of_transit = ~ in_transit
if in_out_transit_savpath :
    _in_out_transit_plot ( time , flux , in_transit , out_of_transit , in_out_transit_savpath )
return time [ out_of_transit ] , flux [ out_of_transit ] , err_flux [ out_of_transit ]


signature:
def _pycompress_sqlitecurve ( sqlitecurve , force = False ) :
    '''This just compresses the sqlitecurve . Should be independent of OS .'''

implementation:
outfile = '%s.gz' % sqlitecurve
try :
    if os . path . exists ( outfile ) and not force :
        os . remove ( sqlitecurve )
        return outfile
    else :
        with open ( sqlitecurve , 'rb' ) as infd :
            with gzip . open ( outfile , 'wb' ) as outfd :
                shutil . copyfileobj ( infd , outfd )
        if os . path . exists ( outfile ) :
            os . remove ( sqlitecurve )
            return outfile
except Exception as e :
    return None


signature:
def _pyuncompress_sqlitecurve ( sqlitecurve , force = False ) :
    '''This just uncompresses the sqlitecurve . Should be independent of OS .'''

implementation:
outfile = sqlitecurve . replace ( '.gz' , '' )
try :
    if os . path . exists ( outfile ) and not force :
        return outfile
    else :
        with gzip . open ( sqlitecurve , 'rb' ) as infd :
            with open ( outfile , 'wb' ) as outfd :
                shutil . copyfileobj ( infd , outfd )
        # do not remove the intput file yet
        if os . path . exists ( outfile ) :
            return outfile
except Exception as e :
    return None


signature:
def _gzip_sqlitecurve ( sqlitecurve , force = False ) :
    '''This just compresses the sqlitecurve in gzip format .
 FIXME : this doesn ' t work with gzip < 1.6 or non - GNU gzip ( probably ) .'''

implementation:
# - k to keep the input file just in case something explodes
if force :
    cmd = 'gzip -k -f %s' % sqlitecurve
else :
    cmd = 'gzip -k %s' % sqlitecurve
try :
    outfile = '%s.gz' % sqlitecurve
    if os . path . exists ( outfile ) and not force : # get rid of the . sqlite file only
        os . remove ( sqlitecurve )
        return outfile
    else :
        subprocess . check_output ( cmd , shell = True )
        # check if the output file was successfully created
        if os . path . exists ( outfile ) :
            return outfile
        else :
            return None
except subprocess . CalledProcessError :
    return None


signature:
def _gunzip_sqlitecurve ( sqlitecurve ) :
    '''This just uncompresses the sqlitecurve in gzip format .
 FIXME : this doesn ' t work with gzip < 1.6 or non - GNU gzip ( probably ) .'''

implementation:
# - k to keep the input . gz just in case something explodes
cmd = 'gunzip -k %s' % sqlitecurve
try :
    subprocess . check_output ( cmd , shell = True )
    return sqlitecurve . replace ( '.gz' , '' )
except subprocess . CalledProcessError :
    return None


signature:
def _validate_sqlitecurve_filters ( filterstring , lccolumns ) :
    '''This validates the sqlitecurve filter string .
 This MUST be valid SQL but not contain any commands .'''

implementation:
# first , lowercase , then _ squeeze to single spaces
stringelems = _squeeze ( filterstring ) . lower ( )
# replace shady characters
stringelems = filterstring . replace ( '(' , '' )
stringelems = stringelems . replace ( ')' , '' )
stringelems = stringelems . replace ( ',' , '' )
stringelems = stringelems . replace ( "'" , '"' )
stringelems = stringelems . replace ( '\n' , ' ' )
stringelems = stringelems . replace ( '\t' , ' ' )
stringelems = _squeeze ( stringelems )
# split into words
stringelems = stringelems . split ( ' ' )
stringelems = [ x . strip ( ) for x in stringelems ]
# get rid of all numbers
stringwords = [ ]
for x in stringelems :
    try :
        float ( x )
    except ValueError as e :
        stringwords . append ( x )
# get rid of everything within quotes
stringwords2 = [ ]
for x in stringwords :
    if not ( x . startswith ( '"' ) and x . endswith ( '"' ) ) :
        stringwords2 . append ( x )
stringwords2 = [ x for x in stringwords2 if len ( x ) > 0 ]
# check the filterstring words against the allowed words
wordset = set ( stringwords2 )
# generate the allowed word set for these LC columns
allowedwords = SQLITE_ALLOWED_WORDS + lccolumns
checkset = set ( allowedwords )
validatecheck = list ( wordset - checkset )
# if there are words left over , then this filter string is suspicious
if len ( validatecheck ) > 0 : # check if validatecheck contains an elem with % in it
    LOGWARNING ( "provided SQL filter string '%s' " "contains non-allowed keywords" % filterstring )
    return None
else :
    return filterstring


signature:
def read_and_filter_sqlitecurve ( lcfile , columns = None , sqlfilters = None , raiseonfail = False , returnarrays = True , forcerecompress = False , quiet = True ) :
    '''This reads a HAT sqlitecurve and optionally filters it .
 Parameters
 lcfile : str
 The path to the HAT sqlitecurve file .
 columns : list
 A list of columns to extract from the ligh curve file . If None , then
 returns all columns present in the latest ` columnlist ` in the light
 curve .
 sqlfilters : list of str
 If no None , it must be a list of text SQL filters that apply to the
 columns in the lightcurve .
 raiseonfail : bool
 If this is True , an Exception when reading the LC will crash the
 function instead of failing silently and returning None as the result .
 returnarrays : bool
 If this is True , the output lcdict contains columns as np . arrays instead
 of lists . You generally want this to be True .
 forcerecompress : bool
 If True , the sqlitecurve will be recompressed even if a compressed
 version of it is found . This usually happens when sqlitecurve opening is
 interrupted by the OS for some reason , leaving behind a gzipped and
 un - gzipped copy . By default , this function refuses to overwrite the
 existing gzipped version so if the un - gzipped version is corrupt but
 that one isn ' t , it can be safely recovered .
 quiet : bool
 If True , will not warn about any problems , even if the light curve
 reading fails ( the only clue then will be the return value of
 None ) . Useful for batch processing of many many light curves .
 Returns
 tuple : ( lcdict , status _ message )
 A two - element tuple is returned , with the first element being the
 lcdict .'''

implementation:
# we ' re proceeding with reading the LC . . .
try : # if this file is a gzipped sqlite3 db , then gunzip it
    if '.gz' in lcfile [ - 4 : ] :
        lcf = _uncompress_sqlitecurve ( lcfile )
    else :
        lcf = lcfile
    db = sql . connect ( lcf )
    cur = db . cursor ( )
    # get the objectinfo from the sqlitecurve
    query = ( "select * from objectinfo" )
    cur . execute ( query )
    objectinfo = cur . fetchone ( )
    # get the lcinfo from the sqlitecurve
    query = ( "select * from lcinfo " "order by version desc limit 1" )
    cur . execute ( query )
    lcinfo = cur . fetchone ( )
    ( lcversion , lcdatarelease , lccols , lcsortcol , lcapertures , lcbestaperture , objinfocols , objidcol , lcunixtime , lcgitrev , lccomment ) = lcinfo
    # load the JSON dicts
    lcapertures = json . loads ( lcapertures )
    lcbestaperture = json . loads ( lcbestaperture )
    objectinfokeys = objinfocols . split ( ',' )
    objectinfodict = { x : y for ( x , y ) in zip ( objectinfokeys , objectinfo ) }
    objectid = objectinfodict [ objidcol ]
    # need to generate the objectinfo dict and the objectid from the lcinfo
    # columns
    # get the filters from the sqlitecurve
    query = ( "select * from filters" )
    cur . execute ( query )
    filterinfo = cur . fetchall ( )
    # validate the requested columns
    if columns and all ( [ x in lccols . split ( ',' ) for x in columns ] ) :
        LOGINFO ( 'retrieving columns %s' % columns )
        proceed = True
    elif columns is None :
        columns = lccols . split ( ',' )
        proceed = True
    else :
        proceed = False
    # bail out if there ' s a problem and tell the user what happened
    if not proceed : # recompress the lightcurve at the end
        if '.gz' in lcfile [ - 4 : ] and lcf :
            _compress_sqlitecurve ( lcf , force = forcerecompress )
        LOGERROR ( 'requested columns are invalid!' )
        return None , "requested columns are invalid"
    # create the lcdict with the object , lc , and filter info
    lcdict = { 'objectid' : objectid , 'objectinfo' : objectinfodict , 'objectinfokeys' : objectinfokeys , 'lcversion' : lcversion , 'datarelease' : lcdatarelease , 'columns' : columns , 'lcsortcol' : lcsortcol , 'lcapertures' : lcapertures , 'lcbestaperture' : lcbestaperture , 'lastupdated' : lcunixtime , 'lcserver' : lcgitrev , 'comment' : lccomment , 'filters' : filterinfo }
    # validate the SQL filters for this LC
    if ( ( sqlfilters is not None ) and ( isinstance ( sqlfilters , str ) or isinstance ( sqlfilters , unicode ) ) ) : # give the validator the sqlfilters string and a list of lccols in
    # the lightcurve
        validatedfilters = _validate_sqlitecurve_filters ( sqlfilters , lccols . split ( ',' ) )
        if validatedfilters is not None :
            LOGINFO ( 'filtering LC using: %s' % validatedfilters )
            filtersok = True
        else :
            filtersok = False
    else :
        validatedfilters = None
        filtersok = None
    # now read all the required columns in the order indicated
    # we use the validated SQL filter string here
    if validatedfilters is not None :
        query = ( "select {columns} from lightcurve where {sqlfilter} " "order by {sortcol} asc" ) . format ( columns = ',' . join ( columns ) , # columns is always a list
        sqlfilter = validatedfilters , sortcol = lcsortcol )
        lcdict [ 'lcfiltersql' ] = validatedfilters
    else :
        query = ( "select %s from lightcurve order by %s asc" ) % ( ',' . join ( columns ) , lcsortcol )
    cur . execute ( query )
    lightcurve = cur . fetchall ( )
    if lightcurve and len ( lightcurve ) > 0 :
        lightcurve = list ( zip ( * lightcurve ) )
        lcdict . update ( { x : y for ( x , y ) in zip ( lcdict [ 'columns' ] , lightcurve ) } )
        lcok = True
        # update the ndet after filtering
        lcdict [ 'objectinfo' ] [ 'ndet' ] = len ( lightcurve [ 0 ] )
    else :
        LOGWARNING ( 'LC for %s has no detections' % lcdict [ 'objectid' ] )
        # fill the lightcurve with empty lists to indicate that it is empty
        lcdict . update ( { x : y for ( x , y ) in zip ( lcdict [ 'columns' ] , [ [ ] for x in lcdict [ 'columns' ] ] ) } )
        lcok = False
    # generate the returned lcdict and status message
    if filtersok is True and lcok :
        statusmsg = 'SQL filters OK, LC OK'
    elif filtersok is None and lcok :
        statusmsg = 'no SQL filters, LC OK'
    elif filtersok is False and lcok :
        statusmsg = 'SQL filters invalid, LC OK'
    else :
        statusmsg = 'LC retrieval failed'
    returnval = ( lcdict , statusmsg )
    # recompress the lightcurve at the end
    if '.gz' in lcfile [ - 4 : ] and lcf :
        _compress_sqlitecurve ( lcf , force = forcerecompress )
    # return ndarrays if that ' s set
    if returnarrays :
        for column in lcdict [ 'columns' ] :
            lcdict [ column ] = np . array ( [ x if x is not None else np . nan for x in lcdict [ column ] ] )
except Exception as e :
    if not quiet :
        LOGEXCEPTION ( 'could not open sqlitecurve %s' % lcfile )
    returnval = ( None , 'error while reading lightcurve file' )
    # recompress the lightcurve at the end
    if '.gz' in lcfile [ - 4 : ] and lcf :
        _compress_sqlitecurve ( lcf , force = forcerecompress )
    if raiseonfail :
        raise
return returnval


signature:
def describe ( lcdict , returndesc = False , offsetwith = None ) :
    '''This describes the light curve object and columns present .
 Parameters
 lcdict : dict
 The input lcdict to parse for column and metadata info .
 returndesc : bool
 If True , returns the description string as an str instead of just
 printing it to stdout .
 offsetwith : str
 This is a character to offset the output description lines by . This is
 useful to add comment characters like ' # ' to the output description
 lines .
 Returns
 str or None
 If returndesc is True , returns the description lines as a str , otherwise
 returns nothing .'''

implementation:
# transparently read LCC CSV format description
if 'lcformat' in lcdict and 'lcc-csv' in lcdict [ 'lcformat' ] . lower ( ) :
    return describe_lcc_csv ( lcdict , returndesc = returndesc )
# figure out the columndefs part of the header string
columndefs = [ ]
for colind , column in enumerate ( lcdict [ 'columns' ] ) :
    if '_' in column :
        colkey , colap = column . split ( '_' )
        coldesc = COLUMNDEFS [ colkey ] [ 0 ] % colap
    else :
        coldesc = COLUMNDEFS [ column ] [ 0 ]
    columndefstr = '%03i - %s - %s' % ( colind , column , coldesc )
    columndefs . append ( columndefstr )
columndefs = '\n' . join ( columndefs )
# figure out the filterdefs
filterdefs = [ ]
for row in lcdict [ 'filters' ] :
    filterid , filtername , filterdesc = row
    filterdefstr = '%s - %s - %s' % ( filterid , filtername , filterdesc )
    filterdefs . append ( filterdefstr )
filterdefs = '\n' . join ( filterdefs )
# figure out the apertures
aperturedefs = [ ]
for key in sorted ( lcdict [ 'lcapertures' ] . keys ( ) ) :
    aperturedefstr = '%s - %.2f px' % ( key , lcdict [ 'lcapertures' ] [ key ] )
    aperturedefs . append ( aperturedefstr )
aperturedefs = '\n' . join ( aperturedefs )
# now fill in the description
description = DESCTEMPLATE . format ( objectid = lcdict [ 'objectid' ] , hatid = lcdict [ 'objectinfo' ] [ 'hatid' ] , twomassid = lcdict [ 'objectinfo' ] [ 'twomassid' ] . strip ( ) , ra = lcdict [ 'objectinfo' ] [ 'ra' ] , decl = lcdict [ 'objectinfo' ] [ 'decl' ] , pmra = lcdict [ 'objectinfo' ] [ 'pmra' ] , pmra_err = lcdict [ 'objectinfo' ] [ 'pmra_err' ] , pmdecl = lcdict [ 'objectinfo' ] [ 'pmdecl' ] , pmdecl_err = lcdict [ 'objectinfo' ] [ 'pmdecl_err' ] , jmag = lcdict [ 'objectinfo' ] [ 'jmag' ] , hmag = lcdict [ 'objectinfo' ] [ 'hmag' ] , kmag = lcdict [ 'objectinfo' ] [ 'kmag' ] , bmag = lcdict [ 'objectinfo' ] [ 'bmag' ] , vmag = lcdict [ 'objectinfo' ] [ 'vmag' ] , sdssg = lcdict [ 'objectinfo' ] [ 'sdssg' ] , sdssr = lcdict [ 'objectinfo' ] [ 'sdssr' ] , sdssi = lcdict [ 'objectinfo' ] [ 'sdssi' ] , ndet = lcdict [ 'objectinfo' ] [ 'ndet' ] , lcsortcol = lcdict [ 'lcsortcol' ] , lcbestaperture = json . dumps ( lcdict [ 'lcbestaperture' ] , ensure_ascii = True ) , network = lcdict [ 'objectinfo' ] [ 'network' ] , stations = lcdict [ 'objectinfo' ] [ 'stations' ] , lastupdated = lcdict [ 'lastupdated' ] , datarelease = lcdict [ 'datarelease' ] , lcversion = lcdict [ 'lcversion' ] , lcserver = lcdict [ 'lcserver' ] , comment = lcdict [ 'comment' ] , lcfiltersql = ( lcdict [ 'lcfiltersql' ] if 'lcfiltersql' in lcdict else '' ) , lcnormcols = ( lcdict [ 'lcnormcols' ] if 'lcnormcols' in lcdict else '' ) , filterdefs = filterdefs , columndefs = columndefs , aperturedefs = aperturedefs )
if offsetwith is not None :
    description = textwrap . indent ( description , '%s ' % offsetwith , lambda line : True )
    print ( description )
else :
    print ( description )
if returndesc :
    return description


signature:
def _smartcast ( castee , caster , subval = None ) :
    '''This just tries to apply the caster function to castee .
 Returns None on failure .'''

implementation:
try :
    return caster ( castee )
except Exception as e :
    if caster is float or caster is int :
        return nan
    elif caster is str :
        return ''
    else :
        return subval


signature:
def _parse_csv_header ( header ) :
    '''This parses the CSV header from the CSV HAT sqlitecurve .
 Returns a dict that can be used to update an existing lcdict with the
 relevant metadata info needed to form a full LC .'''

implementation:
# first , break into lines
headerlines = header . split ( '\n' )
headerlines = [ x . lstrip ( '# ' ) for x in headerlines ]
# next , find the indices of the metadata sections
objectstart = headerlines . index ( 'OBJECT' )
metadatastart = headerlines . index ( 'METADATA' )
camfilterstart = headerlines . index ( 'CAMFILTERS' )
photaperturestart = headerlines . index ( 'PHOTAPERTURES' )
columnstart = headerlines . index ( 'COLUMNS' )
lcstart = headerlines . index ( 'LIGHTCURVE' )
# get the lines for the header sections
objectinfo = headerlines [ objectstart + 1 : metadatastart - 1 ]
metadatainfo = headerlines [ metadatastart + 1 : camfilterstart - 1 ]
camfilterinfo = headerlines [ camfilterstart + 1 : photaperturestart - 1 ]
photapertureinfo = headerlines [ photaperturestart + 1 : columnstart - 1 ]
columninfo = headerlines [ columnstart + 1 : lcstart - 1 ]
# parse the header sections and insert the appropriate key - val pairs into
# the lcdict
metadict = { 'objectinfo' : { } }
# first , the objectinfo section
objectinfo = [ x . split ( ';' ) for x in objectinfo ]
for elem in objectinfo :
    for kvelem in elem :
        key , val = kvelem . split ( ' = ' , 1 )
        metadict [ 'objectinfo' ] [ key . strip ( ) ] = ( _smartcast ( val , METAKEYS [ key . strip ( ) ] ) )
# the objectid belongs at the top level
metadict [ 'objectid' ] = metadict [ 'objectinfo' ] [ 'objectid' ] [ : ]
del metadict [ 'objectinfo' ] [ 'objectid' ]
# get the lightcurve metadata
metadatainfo = [ x . split ( ';' ) for x in metadatainfo ]
for elem in metadatainfo :
    for kvelem in elem :
        try :
            key , val = kvelem . split ( ' = ' , 1 )
            # get the lcbestaperture into a dict again
            if key . strip ( ) == 'lcbestaperture' :
                val = json . loads ( val )
            # get the lcversion and datarelease as integers
            if key . strip ( ) in ( 'datarelease' , 'lcversion' ) :
                val = int ( val )
            # get the lastupdated as a float
            if key . strip ( ) == 'lastupdated' :
                val = float ( val )
            # put the key - val into the dict
            metadict [ key . strip ( ) ] = val
        except Exception as e :
            LOGWARNING ( 'could not understand header element "%s",' ' skipped.' % kvelem )
# get the camera filters
metadict [ 'filters' ] = [ ]
for row in camfilterinfo :
    filterid , filtername , filterdesc = row . split ( ' - ' )
    metadict [ 'filters' ] . append ( ( int ( filterid ) , filtername , filterdesc ) )
# get the photometric apertures
metadict [ 'lcapertures' ] = { }
for row in photapertureinfo :
    apnum , appix = row . split ( ' - ' )
    appix = float ( appix . rstrip ( ' px' ) )
    metadict [ 'lcapertures' ] [ apnum . strip ( ) ] = appix
# get the columns
metadict [ 'columns' ] = [ ]
for row in columninfo :
    colnum , colname , coldesc = row . split ( ' - ' )
    metadict [ 'columns' ] . append ( colname )
return metadict


signature:
def _parse_csv_header_lcc_csv_v1 ( headerlines ) :
    '''This parses the header of the LCC CSV V1 LC format .'''

implementation:
# the first three lines indicate the format name , comment char , separator
commentchar = headerlines [ 1 ]
separator = headerlines [ 2 ]
headerlines = [ x . lstrip ( '%s ' % commentchar ) for x in headerlines [ 3 : ] ]
# next , find the indices of the various LC sections
metadatastart = headerlines . index ( 'OBJECT METADATA' )
columnstart = headerlines . index ( 'COLUMN DEFINITIONS' )
lcstart = headerlines . index ( 'LIGHTCURVE' )
metadata = ' ' . join ( headerlines [ metadatastart + 1 : columnstart - 1 ] )
columns = ' ' . join ( headerlines [ columnstart + 1 : lcstart - 1 ] )
metadata = json . loads ( metadata )
columns = json . loads ( columns )
return metadata , columns , separator


signature:
def read_lcc_csvlc ( lcfile ) :
    '''This reads a CSV LC produced by an ` LCC - Server
 < https : / / github . com / waqasbhatti / lcc - server > ` _ instance .
 Parameters
 lcfile : str
 The LC file to read .
 Returns
 dict
 Returns an lcdict that ' s readable by most astrobase functions for
 further processing .'''

implementation:
# read in the file and split by lines
if '.gz' in os . path . basename ( lcfile ) :
    infd = gzip . open ( lcfile , 'rb' )
else :
    infd = open ( lcfile , 'rb' )
lctext = infd . read ( ) . decode ( )
infd . close ( )
lctextlines = lctext . split ( '\n' )
lcformat = lctextlines [ 0 ]
commentchar = lctextlines [ 1 ]
lcstart = lctextlines . index ( '%s LIGHTCURVE' % commentchar )
headerlines = lctextlines [ : lcstart + 1 ]
lclines = lctextlines [ lcstart + 1 : ]
metadata , columns , separator = _parse_csv_header_lcc_csv_v1 ( headerlines )
# break out the objectid and objectinfo
objectid = metadata [ 'objectid' ] [ 'val' ]
objectinfo = { key : metadata [ key ] [ 'val' ] for key in metadata }
# figure out the column dtypes
colnames = [ ]
colnum = [ ]
coldtypes = [ ]
# generate the args for np . genfromtxt
for k in columns :
    coldef = columns [ k ]
    colnames . append ( k )
    colnum . append ( coldef [ 'colnum' ] )
    coldtypes . append ( coldef [ 'dtype' ] )
coldtypes = ',' . join ( coldtypes )
# read in the LC
recarr = np . genfromtxt ( lclines , comments = commentchar , delimiter = separator , usecols = colnum , autostrip = True , names = colnames , dtype = coldtypes )
lcdict = { x : recarr [ x ] for x in colnames }
lcdict [ 'lcformat' ] = lcformat
lcdict [ 'objectid' ] = objectid
lcdict [ 'objectinfo' ] = objectinfo
lcdict [ 'columns' ] = colnames
lcdict [ 'coldefs' ] = columns
lcdict [ 'metadata' ] = metadata
return lcdict


signature:
def describe_lcc_csv ( lcdict , returndesc = False ) :
    '''This describes the LCC CSV format light curve file .
 Parameters
 lcdict : dict
 The input lcdict to parse for column and metadata info .
 returndesc : bool
 If True , returns the description string as an str instead of just
 printing it to stdout .
 Returns
 str or None
 If returndesc is True , returns the description lines as a str , otherwise
 returns nothing .'''

implementation:
metadata_lines = [ ]
coldef_lines = [ ]
if 'lcformat' in lcdict and 'lcc-csv' in lcdict [ 'lcformat' ] . lower ( ) :
    metadata = lcdict [ 'metadata' ]
    metakeys = lcdict [ 'objectinfo' ] . keys ( )
    coldefs = lcdict [ 'coldefs' ]
    for mk in metakeys :
        metadata_lines . append ( '%20s | %s' % ( mk , metadata [ mk ] [ 'desc' ] ) )
    for ck in lcdict [ 'columns' ] :
        coldef_lines . append ( 'column %02d | %8s | numpy dtype: %3s | %s' % ( coldefs [ ck ] [ 'colnum' ] , ck , coldefs [ ck ] [ 'dtype' ] , coldefs [ ck ] [ 'desc' ] ) )
    desc = LCC_CSVLC_DESCTEMPLATE . format ( objectid = lcdict [ 'objectid' ] , metadata_desc = '\n' . join ( metadata_lines ) , metadata = pformat ( lcdict [ 'objectinfo' ] ) , columndefs = '\n' . join ( coldef_lines ) )
    print ( desc )
    if returndesc :
        return desc
else :
    LOGERROR ( "this lcdict is not from an LCC CSV, can't figure it out..." )
    return None


signature:
def read_csvlc ( lcfile ) :
    '''This reads a HAT data server or LCC - Server produced CSV light curve
 into an lcdict .
 This will automatically figure out the format of the file
 provided . Currently , it can read :
 - legacy HAT data server CSV LCs ( e . g . from
 https : / / hatsouth . org / planets / lightcurves . html ) with an extension of the
 form : ` . hatlc . csv . gz ` .
 - all LCC - Server produced LCC - CSV - V1 LCs ( e . g . from
 https : / / data . hatsurveys . org ) with an extension of the form : ` - csvlc . gz ` .
 Parameters
 lcfile : str
 The light curve file to read .
 Returns
 dict
 Returns an lcdict that can be read and used by many astrobase processing
 functions .'''

implementation:
# read in the file and split by lines
if '.gz' in os . path . basename ( lcfile ) :
    LOGINFO ( 'reading gzipped HATLC: %s' % lcfile )
    infd = gzip . open ( lcfile , 'rb' )
else :
    LOGINFO ( 'reading HATLC: %s' % lcfile )
    infd = open ( lcfile , 'rb' )
# this transparently reads LCC CSVLCs
lcformat_check = infd . read ( 12 ) . decode ( )
if 'LCC-CSVLC' in lcformat_check :
    infd . close ( )
    return read_lcc_csvlc ( lcfile )
else :
    infd . seek ( 0 )
# below is reading the HATLC v2 CSV LCs
lctext = infd . read ( ) . decode ( )
# argh Python 3
infd . close ( )
# figure out the header and get the LC columns
lcstart = lctext . index ( '# LIGHTCURVE\n' )
lcheader = lctext [ : lcstart + 12 ]
lccolumns = lctext [ lcstart + 13 : ] . split ( '\n' )
lccolumns = [ x for x in lccolumns if len ( x ) > 0 ]
# initialize the lcdict and parse the CSV header
lcdict = _parse_csv_header ( lcheader )
# tranpose the LC rows into columns
lccolumns = [ x . split ( ',' ) for x in lccolumns ]
lccolumns = list ( zip ( * lccolumns ) )
# argh more Python 3
# write the columns to the dict
for colind , col in enumerate ( lcdict [ 'columns' ] ) :
    if ( col . split ( '_' ) [ 0 ] in LC_MAG_COLUMNS or col . split ( '_' ) [ 0 ] in LC_ERR_COLUMNS or col . split ( '_' ) [ 0 ] in LC_FLAG_COLUMNS ) :
        lcdict [ col ] = np . array ( [ _smartcast ( x , COLUMNDEFS [ col . split ( '_' ) [ 0 ] ] [ 2 ] ) for x in lccolumns [ colind ] ] )
    elif col in COLUMNDEFS :
        lcdict [ col ] = np . array ( [ _smartcast ( x , COLUMNDEFS [ col ] [ 2 ] ) for x in lccolumns [ colind ] ] )
    else :
        LOGWARNING ( 'lcdict col %s has no formatter available' % col )
        continue
return lcdict


signature:
def find_lc_timegroups ( lctimes , mingap = 4.0 ) :
    '''This finds the time gaps in the light curve , so we can figure out which
 times are for consecutive observations and which represent gaps
 between seasons .
 Parameters
 lctimes : np . array
 This is the input array of times , assumed to be in some form of JD .
 mingap : float
 This defines how much the difference between consecutive measurements is
 allowed to be to consider them as parts of different timegroups . By
 default it is set to 4.0 days .
 Returns
 tuple
 A tuple of the form below is returned , containing the number of time
 groups found and Python slice objects for each group : :
 ( ngroups , [ slice ( start _ ind _ 1 , end _ ind _ 1 ) , . . . ] )'''

implementation:
lc_time_diffs = [ ( lctimes [ x ] - lctimes [ x - 1 ] ) for x in range ( 1 , len ( lctimes ) ) ]
lc_time_diffs = np . array ( lc_time_diffs )
group_start_indices = np . where ( lc_time_diffs > mingap ) [ 0 ]
if len ( group_start_indices ) > 0 :
    group_indices = [ ]
    for i , gindex in enumerate ( group_start_indices ) :
        if i == 0 :
            group_indices . append ( slice ( 0 , gindex + 1 ) )
        else :
            group_indices . append ( slice ( group_start_indices [ i - 1 ] + 1 , gindex + 1 ) )
    # at the end , add the slice for the last group to the end of the times
    # array
    group_indices . append ( slice ( group_start_indices [ - 1 ] + 1 , len ( lctimes ) ) )
# if there ' s no large gap in the LC , then there ' s only one group to worry
# about
else :
    group_indices = [ slice ( 0 , len ( lctimes ) ) ]
return len ( group_indices ) , group_indices


signature:
def normalize_lcdict ( lcdict , timecol = 'rjd' , magcols = 'all' , mingap = 4.0 , normto = 'sdssr' , debugmode = False , quiet = False ) :
    '''This normalizes magcols in ` lcdict ` using ` timecol ` to find timegroups .
 Parameters
 lcdict : dict
 The input lcdict to process .
 timecol : str
 The key in the lcdict that is to be used to extract the time column .
 magcols : ' all ' or list of str
 If this is ' all ' , all of the columns in the lcdict that are indicated to
 be magnitude measurement columns are normalized . If this is a list of
 str , must contain the keys of the lcdict specifying which magnitude
 columns will be normalized .
 mingap : float
 This defines how much the difference between consecutive measurements is
 allowed to be to consider them as parts of different timegroups . By
 default it is set to 4.0 days .
 normto : { ' globalmedian ' , ' zero ' , ' jmag ' , ' hmag ' , ' kmag ' , ' bmag ' , ' vmag ' , ' sdssg ' , ' sdssr ' , ' sdssi ' }
 This indicates which column will be the normalization target . If this is
 ' globalmedian ' , the normalization will be to the global median of each
 LC column . If this is ' zero ' , will normalize to 0.0 for each LC
 column . Otherwise , will normalize to the value of one of the other keys
 in the lcdict [ ' objectinfo ' ] [ magkey ] , meaning the normalization will be
 to some form of catalog magnitude .
 debugmode : bool
 If True , will indicate progress as time - groups are found and processed .
 quiet : bool
 If True , will not emit any messages when processing .
 Returns
 dict
 Returns the lcdict with the magnitude measurements normalized as
 specified . The normalization happens IN PLACE .'''

implementation:
# check if this lc has been normalized already . return as - is if so
if 'lcnormcols' in lcdict and len ( lcdict [ 'lcnormcols' ] ) > 0 :
    if not quiet :
        LOGWARNING ( 'this lightcurve is already normalized, returning...' )
    return lcdict
# first , get the LC timegroups
if timecol in lcdict :
    times = lcdict [ timecol ]
elif 'rjd' in lcdict :
    times = lcdict [ 'rjd' ]
# if there aren ' t any time columns in this lcdict , then we can ' t do any
# normalization , return it as - is
else :
    LOGERROR ( "can't figure out the time column to use, lcdict cols = %s" % lcdict [ 'columns' ] )
    return lcdict
ngroups , timegroups = find_lc_timegroups ( np . array ( times ) , mingap = mingap )
# HATLC V2 format
if 'lcapertures' in lcdict :
    apertures = sorted ( lcdict [ 'lcapertures' ] . keys ( ) )
# LCC - CSV - V1 format HATLC
elif 'objectinfo' in lcdict and 'lcapertures' in lcdict [ 'objectinfo' ] :
    apertures = sorted ( lcdict [ 'objectinfo' ] [ 'lcapertures' ] . keys ( ) )
aimcols = [ ( 'aim_%s' % x ) for x in apertures if ( 'aim_%s' % x ) in lcdict ]
armcols = [ ( 'arm_%s' % x ) for x in apertures if ( 'arm_%s' % x ) in lcdict ]
aepcols = [ ( 'aep_%s' % x ) for x in apertures if ( 'aep_%s' % x ) in lcdict ]
atfcols = [ ( 'atf_%s' % x ) for x in apertures if ( 'atf_%s' % x ) in lcdict ]
psimcols = [ x for x in [ 'psim' , 'psrm' , 'psep' , 'pstf' ] if x in lcdict ]
irmcols = [ ( 'irm_%s' % x ) for x in apertures if ( 'irm_%s' % x ) in lcdict ]
iepcols = [ ( 'iep_%s' % x ) for x in apertures if ( 'iep_%s' % x ) in lcdict ]
itfcols = [ ( 'itf_%s' % x ) for x in apertures if ( 'itf_%s' % x ) in lcdict ]
# next , find all the mag columns to normalize
if magcols == 'all' :
    cols_to_normalize = ( aimcols + armcols + aepcols + atfcols + psimcols + irmcols + iepcols + itfcols )
elif magcols == 'redmags' :
    cols_to_normalize = ( irmcols + ( [ 'psrm' ] if 'psrm' in lcdict else [ ] ) + irmcols )
elif magcols == 'epdmags' :
    cols_to_normalize = ( aepcols + ( [ 'psep' ] if 'psep' in lcdict else [ ] ) + iepcols )
elif magcols == 'tfamags' :
    cols_to_normalize = ( atfcols + ( [ 'pstf' ] if 'pstf' in lcdict else [ ] ) + itfcols )
elif magcols == 'epdtfa' :
    cols_to_normalize = ( aepcols + ( [ 'psep' ] if 'psep' in lcdict else [ ] ) + iepcols + atfcols + ( [ 'pstf' ] if 'pstf' in lcdict else [ ] ) + itfcols )
else :
    cols_to_normalize = magcols . split ( ',' )
    cols_to_normalize = [ x . strip ( ) for x in cols_to_normalize ]
colsnormalized = [ ]
# now , normalize each column
for col in cols_to_normalize :
    if col in lcdict :
        mags = lcdict [ col ]
        mags = [ ( nan if x is None else x ) for x in mags ]
        mags = np . array ( mags )
        colsnormalized . append ( col )
        # find all the non - nan indices
        finite_ind = np . isfinite ( mags )
        if any ( finite_ind ) : # find the global median
            global_mag_median = np . median ( mags [ finite_ind ] )
            # go through the groups and normalize them to the median for
            # each group
            for tgind , tg in enumerate ( timegroups ) :
                finite_ind = np . isfinite ( mags [ tg ] )
                # find this timegroup ' s median mag and normalize the mags in
                # it to this median
                group_median = np . median ( ( mags [ tg ] ) [ finite_ind ] )
                mags [ tg ] = mags [ tg ] - group_median
                if debugmode :
                    LOGDEBUG ( '%s group %s: elems %s, ' 'finite elems %s, median mag %s' % ( col , tgind , len ( mags [ tg ] ) , len ( finite_ind ) , group_median ) )
        else :
            LOGWARNING ( 'column %s is all nan, skipping...' % col )
            continue
        # now that everything is normalized to 0.0 , add the global median
        # offset back to all the mags and write the result back to the dict
        if normto == 'globalmedian' :
            mags = mags + global_mag_median
        elif normto in ( 'jmag' , 'hmag' , 'kmag' , 'bmag' , 'vmag' , 'sdssg' , 'sdssr' , 'sdssi' ) :
            if ( normto in lcdict [ 'objectinfo' ] and lcdict [ 'objectinfo' ] [ normto ] is not None ) :
                mags = mags + lcdict [ 'objectinfo' ] [ normto ]
            else :
                if not quiet :
                    LOGWARNING ( 'no %s available in lcdict, ' 'normalizing to global mag median' % normto )
                normto = 'globalmedian'
                mags = mags + global_mag_median
        lcdict [ col ] = mags
    else :
        if not quiet :
            LOGWARNING ( 'column %s is not present, skipping...' % col )
        continue
# add the lcnormcols key to the lcdict
lcnormcols = ( 'cols normalized: %s - ' 'min day gap: %s - ' 'normalized to: %s' ) % ( repr ( colsnormalized ) , mingap , normto )
lcdict [ 'lcnormcols' ] = lcnormcols
return lcdict


signature:
def normalize_lcdict_byinst ( lcdict , magcols = 'all' , normto = 'sdssr' , normkeylist = ( 'stf' , 'ccd' , 'flt' , 'fld' , 'prj' , 'exp' ) , debugmode = False , quiet = False ) :
    '''This is a function to normalize light curves across all instrument
 combinations present .
 Use this to normalize a light curve containing a variety of :
 - HAT station IDs ( ' stf ' )
 - camera IDs ( ' ccd ' )
 - filters ( ' flt ' )
 - observed field names ( ' fld ' )
 - HAT project IDs ( ' prj ' )
 - exposure times ( ' exp ' )
 Parameters
 lcdict : dict
 The input lcdict to process .
 magcols : ' all ' or list of str
 If this is ' all ' , all of the columns in the lcdict that are indicated to
 be magnitude measurement columns are normalized . If this is a list of
 str , must contain the keys of the lcdict specifying which magnitude
 columns will be normalized .
 normto : { ' zero ' , ' jmag ' , ' hmag ' , ' kmag ' , ' bmag ' , ' vmag ' , ' sdssg ' , ' sdssr ' , ' sdssi ' }
 This indicates which column will be the normalization target . If this is
 ' zero ' , will normalize to 0.0 for each LC column . Otherwise , will
 normalize to the value of one of the other keys in the
 lcdict [ ' objectinfo ' ] [ magkey ] , meaning the normalization will be to some
 form of catalog magnitude .
 normkeylist : list of str
 These are the column keys to use to form the normalization
 index . Measurements in the specified ` magcols ` with identical
 normalization index values will be considered as part of a single
 measurement ' era ' , and will be normalized to zero . Once all eras have
 been normalized this way , the final light curve will be re - normalized as
 specified in ` normto ` .
 debugmode : bool
 If True , will indicate progress as time - groups are found and processed .
 quiet : bool
 If True , will not emit any messages when processing .
 Returns
 dict
 Returns the lcdict with the magnitude measurements normalized as
 specified . The normalization happens IN PLACE .'''

implementation:
# check if this lc has been normalized already . return as - is if so
if 'lcinstnormcols' in lcdict and len ( lcdict [ 'lcinstnormcols' ] ) > 0 :
    if not quiet :
        LOGWARNING ( 'this lightcurve is already ' 'normalized by instrument keys, ' 'returning...' )
    return lcdict
# generate the normalization key
normkeycols = [ ]
availablenormkeys = [ ]
for key in normkeylist :
    if key in lcdict and lcdict [ key ] is not None :
        normkeycols . append ( lcdict [ key ] )
        availablenormkeys . append ( key )
# transpose to turn these into rows
normkeycols = list ( zip ( * normkeycols ) )
# convert to a string rep for each key and post - process for simplicity
allkeys = [ repr ( x ) for x in normkeycols ]
allkeys = [ a . replace ( '(' , '' ) . replace ( ')' , '' ) . replace ( "'" , '' ) . replace ( ' ' , '' ) for a in allkeys ]
# turn these into a numpy array and get the unique values
allkeys = np . array ( allkeys )
normkeys = np . unique ( allkeys )
# figure out the apertures
# HATLC V2 format
if 'lcapertures' in lcdict :
    apertures = sorted ( lcdict [ 'lcapertures' ] . keys ( ) )
# LCC - CSV - V1 format HATLC
elif 'objectinfo' in lcdict and 'lcapertures' in lcdict [ 'objectinfo' ] :
    apertures = sorted ( lcdict [ 'objectinfo' ] [ 'lcapertures' ] . keys ( ) )
# put together the column names
aimcols = [ ( 'aim_%s' % x ) for x in apertures if ( 'aim_%s' % x ) in lcdict ]
armcols = [ ( 'arm_%s' % x ) for x in apertures if ( 'arm_%s' % x ) in lcdict ]
aepcols = [ ( 'aep_%s' % x ) for x in apertures if ( 'aep_%s' % x ) in lcdict ]
atfcols = [ ( 'atf_%s' % x ) for x in apertures if ( 'atf_%s' % x ) in lcdict ]
psimcols = [ x for x in [ 'psim' , 'psrm' , 'psep' , 'pstf' ] if x in lcdict ]
irmcols = [ ( 'irm_%s' % x ) for x in apertures if ( 'irm_%s' % x ) in lcdict ]
iepcols = [ ( 'iep_%s' % x ) for x in apertures if ( 'iep_%s' % x ) in lcdict ]
itfcols = [ ( 'itf_%s' % x ) for x in apertures if ( 'itf_%s' % x ) in lcdict ]
# next , find all the mag columns to normalize
if magcols == 'all' :
    cols_to_normalize = ( aimcols + armcols + aepcols + atfcols + psimcols + irmcols + iepcols + itfcols )
elif magcols == 'redmags' :
    cols_to_normalize = ( irmcols + ( [ 'psrm' ] if 'psrm' in lcdict else [ ] ) + irmcols )
elif magcols == 'epdmags' :
    cols_to_normalize = ( aepcols + ( [ 'psep' ] if 'psep' in lcdict else [ ] ) + iepcols )
elif magcols == 'tfamags' :
    cols_to_normalize = ( atfcols + ( [ 'pstf' ] if 'pstf' in lcdict else [ ] ) + itfcols )
elif magcols == 'epdtfa' :
    cols_to_normalize = ( aepcols + ( [ 'psep' ] if 'psep' in lcdict else [ ] ) + iepcols + atfcols + ( [ 'pstf' ] if 'pstf' in lcdict else [ ] ) + itfcols )
else :
    cols_to_normalize = magcols . split ( ',' )
    cols_to_normalize = [ x . strip ( ) for x in cols_to_normalize ]
colsnormalized = [ ]
# go through each column and normalize them
for col in cols_to_normalize :
    if col in lcdict : # note : this requires the columns in ndarray format
    # unlike normalize _ lcdict
        thismags = lcdict [ col ]
        # go through each key in normusing
        for nkey in normkeys :
            thisind = allkeys == nkey
            # make sure we have at least 3 elements in the matched set of
            # magnitudes corresponding to this key . also make sure that the
            # magnitudes corresponding to this key aren ' t all nan .
            thismagsize = thismags [ thisind ] . size
            thismagfinite = np . where ( np . isfinite ( thismags [ thisind ] ) ) [ 0 ] . size
            if thismagsize > 2 and thismagfinite > 2 : # do the normalization and update the thismags in the lcdict
                medmag = np . nanmedian ( thismags [ thisind ] )
                lcdict [ col ] [ thisind ] = lcdict [ col ] [ thisind ] - medmag
                if debugmode :
                    LOGDEBUG ( 'magcol: %s, currkey: "%s", nelem: %s, ' 'medmag: %s' % ( col , nkey , len ( thismags [ thisind ] ) , medmag ) )
            # we remove mags that correspond to keys with less than 3
            # ( finite ) elements because we can ' t get the median mag
            # correctly and renormalizing them to zero would just set them
            # to zero
            else :
                lcdict [ col ] [ thisind ] = np . nan
        # everything should now be normalized to zero
        # add back the requested normto
        if normto in ( 'jmag' , 'hmag' , 'kmag' , 'bmag' , 'vmag' , 'sdssg' , 'sdssr' , 'sdssi' ) :
            if ( normto in lcdict [ 'objectinfo' ] and lcdict [ 'objectinfo' ] [ normto ] is not None ) :
                lcdict [ col ] = lcdict [ col ] + lcdict [ 'objectinfo' ] [ normto ]
            else :
                if not quiet :
                    LOGWARNING ( 'no %s available in lcdict, ' 'normalizing to 0.0' % normto )
                normto = 'zero'
        # update the colsnormalized list
        colsnormalized . append ( col )
    else :
        if not quiet :
            LOGWARNING ( 'column %s is not present, skipping...' % col )
        continue
# add the lcnormcols key to the lcdict
lcinstnormcols = ( 'cols normalized: %s - ' 'normalized to: %s - ' 'norm keys used: %s' ) % ( repr ( colsnormalized ) , normto , repr ( availablenormkeys ) )
lcdict [ 'lcinstnormcols' ] = lcinstnormcols
return lcdict


signature:
def main ( ) :
    '''This is called when we ' re executed from the commandline .
 The current usage from the command - line is described below : :
 usage : hatlc [ - h ] [ - - describe ] hatlcfile
 read a HAT LC of any format and output to stdout
 positional arguments :
 hatlcfile path to the light curve you want to read and pipe to stdout
 optional arguments :
 - h , - - help show this help message and exit
 - - describe don ' t dump the columns , show only object info and LC metadata'''

implementation:
# handle SIGPIPE sent by less , head , et al .
import signal
signal . signal ( signal . SIGPIPE , signal . SIG_DFL )
import argparse
aparser = argparse . ArgumentParser ( description = 'read a HAT LC of any format and output to stdout' )
aparser . add_argument ( 'hatlcfile' , action = 'store' , type = str , help = ( "path to the light curve you want to read and pipe to stdout" ) )
aparser . add_argument ( '--describe' , action = 'store_true' , default = False , help = ( "don't dump the columns, show only object info and LC metadata" ) )
args = aparser . parse_args ( )
filetoread = args . hatlcfile
if not os . path . exists ( filetoread ) :
    LOGERROR ( "file provided: %s doesn't seem to exist" % filetoread )
    sys . exit ( 1 )
# figure out the type of LC this is
filename = os . path . basename ( filetoread )
# switch based on filetype
if filename . endswith ( '-hatlc.csv.gz' ) or filename . endswith ( '-csvlc.gz' ) :
    if args . describe :
        describe ( read_csvlc ( filename ) )
        sys . exit ( 0 )
    else :
        with gzip . open ( filename , 'rb' ) as infd :
            for line in infd :
                print ( line . decode ( ) , end = '' )
elif filename . endswith ( '-hatlc.sqlite.gz' ) :
    lcdict , msg = read_and_filter_sqlitecurve ( filetoread )
    # dump the description
    describe ( lcdict , offsetwith = '#' )
    # stop here if describe is True
    if args . describe :
        sys . exit ( 0 )
    # otherwise , continue to parse the cols , etc .
    # get the aperture names
    apertures = sorted ( lcdict [ 'lcapertures' ] . keys ( ) )
    # update column defs per aperture
    for aper in apertures :
        COLUMNDEFS . update ( { '%s_%s' % ( x , aper ) : COLUMNDEFS [ x ] for x in LC_MAG_COLUMNS } )
        COLUMNDEFS . update ( { '%s_%s' % ( x , aper ) : COLUMNDEFS [ x ] for x in LC_ERR_COLUMNS } )
        COLUMNDEFS . update ( { '%s_%s' % ( x , aper ) : COLUMNDEFS [ x ] for x in LC_FLAG_COLUMNS } )
    formstr = ',' . join ( [ COLUMNDEFS [ x ] [ 1 ] for x in lcdict [ 'columns' ] ] )
    ndet = lcdict [ 'objectinfo' ] [ 'ndet' ]
    for ind in range ( ndet ) :
        line = [ lcdict [ x ] [ ind ] for x in lcdict [ 'columns' ] ]
        formline = formstr % tuple ( line )
        print ( formline )
else :
    LOGERROR ( 'unrecognized HATLC file: %s' % filetoread )
    sys . exit ( 1 )


signature:
def coord_features ( objectinfo ) :
    '''Calculates object coordinates features , including :
 - galactic coordinates
 - total proper motion from pmra , pmdecl
 - reduced J proper motion from propermotion and Jmag
 Parameters
 objectinfo : dict
 This is an objectinfo dict from a light curve file read into an
 ` lcdict ` . The format and the minimum keys required are : :
 { ' ra ' : the right ascension of the object in decimal degrees ,
 ' decl ' : the declination of the object in decimal degrees ,
 ' pmra ' : the proper motion in right ascension in mas / yr ,
 ' pmdecl ' : the proper motion in declination in mas / yr ,
 ' jmag ' : the 2MASS J mag of this object }
 Returns
 dict
 A dict containing the total proper motion'''

implementation:
retdict = { 'propermotion' : np . nan , 'gl' : np . nan , 'gb' : np . nan , 'rpmj' : np . nan }
if ( 'ra' in objectinfo and objectinfo [ 'ra' ] is not None and np . isfinite ( objectinfo [ 'ra' ] ) and 'decl' in objectinfo and objectinfo [ 'decl' ] is not None and np . isfinite ( objectinfo [ 'decl' ] ) ) :
    retdict [ 'gl' ] , retdict [ 'gb' ] = coordutils . equatorial_to_galactic ( objectinfo [ 'ra' ] , objectinfo [ 'decl' ] )
if ( 'pmra' in objectinfo and objectinfo [ 'pmra' ] is not None and np . isfinite ( objectinfo [ 'pmra' ] ) and 'pmdecl' in objectinfo and objectinfo [ 'pmdecl' ] is not None and np . isfinite ( objectinfo [ 'pmdecl' ] ) and 'decl' in objectinfo and objectinfo [ 'decl' ] is not None and np . isfinite ( objectinfo [ 'decl' ] ) ) :
    retdict [ 'propermotion' ] = coordutils . total_proper_motion ( objectinfo [ 'pmra' ] , objectinfo [ 'pmdecl' ] , objectinfo [ 'decl' ] )
if ( 'jmag' in objectinfo and objectinfo [ 'jmag' ] is not None and np . isfinite ( objectinfo [ 'jmag' ] ) and np . isfinite ( retdict [ 'propermotion' ] ) ) :
    retdict [ 'rpmj' ] = coordutils . reduced_proper_motion ( objectinfo [ 'jmag' ] , retdict [ 'propermotion' ] )
return retdict