示例#1
0
def detedgewarning(bin_ix, events, verbose=0, valid_detrad=0.5):
    """
    Assigns warning flags if any of the events of interest are adjacent
        to the detector edge as defined by a radius of valid_detrad in degrees.

    :param bin_ix: Array indices designating which events are in the time bin
        of interest.

    :type bin_ix: numpy.ndarray

    :param events: Set of photon events to check if they are near the detector
        edge.

    :type events: dict

    :param verbose: Verbosity level, a value of 0 is minimum verbosity.

    :type verbose: int

    :param valid_detrad: The radius, in degrees, beyond which an edge warning is
        raised.

    :type valid_detrad: float

    :returns: bool -- Returns True/False whether a given set of events are too
        close to the edge of the detector.
    """

    ix = np.where(mc.distance(events['photons']['col'][bin_ix],
                              events['photons']['row'][bin_ix], 400, 400)*
                  gxt.aper2deg(4) >= valid_detrad)

    return True if len(ix[0]) else False
示例#2
0
def detedgewarning(bin_ix, events, verbose=0, valid_detrad=0.5):
    """
    Assigns warning flags if any of the events of interest are adjacent
        to the detector edge as defined by a radius of valid_detrad in degrees.

    :param bin_ix: Array indices designating which events are in the time bin
        of interest.

    :type bin_ix: numpy.ndarray

    :param events: Set of photon events to check if they are near the detector
        edge.

    :type events: dict

    :param verbose: Verbosity level, a value of 0 is minimum verbosity.

    :type verbose: int

    :param valid_detrad: The radius, in degrees, beyond which an edge warning is
        raised.

    :type valid_detrad: float

    :returns: bool -- Returns True/False whether a given set of events are too
        close to the edge of the detector.
    """

    ix = np.where(
        mc.distance(events['photons']['col'][bin_ix], events['photons']['row']
                    [bin_ix], 400, 400) * gxt.aper2deg(4) >= valid_detrad)

    return True if len(ix[0]) else False
示例#3
0
def apcorrect_cps(lc, band, aper=gt.aper2deg(7)):
    """ Apply the aperture correction in units of linear counts-per-second.
    Aperture correction is linear in magnitude units, so convert the count rate
    into AB mag, correct it, and then convert it back.
    """
    return (gt.mag2counts(
        gt.counts2mag(lc['cps'].values, band) - gt.apcorrect1(aper, band),
        band))
示例#4
0
def aper_photons(photon_data,
                 skypos=(24.76279, -17.94948),
                 aper=gt.aper2deg(7)):
    """ Extract the events within the aperture. """
    image = photon_data
    angsep = gPhoton.MCUtils.angularSeparation(skypos[0], skypos[1],
                                               np.array(image['ra']),
                                               np.array(image['dec']))
    ix = np.where((angsep <= aper) & (np.isfinite(angsep))
                  & (np.array(image['flags'], dtype='int16') == 0))
    return ix
示例#5
0
def calculate_flare_energy(lc,
                           frange,
                           distance,
                           binsize=30,
                           band='NUV',
                           effective_widths={
                               'NUV': 729.94,
                               'FUV': 255.45
                           },
                           quiescence=None):
    """ Calculates the energy of a flare in erg. """
    if not quiescence:
        q, _ = get_inff(lc)
        # Convert to aperture-corrected flux
        q = gt.mag2counts(
            gt.counts2mag(q, band) - gt.apcorrect1(gt.aper2deg(6), band), band)
    else:
        q = quiescence[0]

    # Convert from parsecs to cm
    distance_cm = distance * 3.086e+18
    if 'cps_apcorrected' in lc.keys():
        # Converting from counts / sec to flux units.
        flare_flux = (np.array(
            gt.counts2flux(np.array(lc.iloc[frange]['cps_apcorrected']), band))
                      - gt.counts2flux(q, band))
    else:
        # Really need to have aperture-corrected counts/sec.
        raise ValueError("Need aperture-corrected cps fluxes to continue.")
    # Zero any flux values where the flux is below the INFF so that we don't subtract from the total flux!
    flare_flux = np.array([0 if f < 0 else f for f in flare_flux])
    flare_flux_err = gt.counts2flux(np.array(lc.iloc[frange]['cps_err']), band)
    tbins = (np.array(lc.iloc[frange]['t1'].values) -
             np.array(lc.iloc[frange]['t0'].values))
    # Caluclate the area under the curve.
    integrated_flux = (binsize * flare_flux).sum()
    """
    GALEX effective widths from
    http://svo2.cab.inta-csic.es/svo/theory/fps3/index.php?id=GALEX/GALEX.NUV
    width = 729.94 A
    http://svo2.cab.inta-csic.es/svo/theory/fps3/index.php?id=GALEX/GALEX.FUV
    width = 255.45 A
    """
    # Convert integrated flux to a fluence using the GALEX effective widths.
    fluence = integrated_flux * effective_widths[band]
    fluence_err = (np.sqrt(
        ((gt.counts2flux(lc.iloc[frange]['cps_err'], band) * binsize)**
         2).sum()) * effective_widths[band])
    energy = (4 * np.pi * (distance_cm**2) * fluence)
    energy_err = (4 * np.pi * (distance_cm**2) * fluence_err)
    return energy, energy_err
示例#6
0
def recenter(events,
             skypos=(24.76279, -17.94948),
             aper=gt.aper2deg(7),
             n_iters=5):
    """Given a position on the sky, iteratively recenter on the median photon
    position."""
    for i in np.arange(n_iters):
        # iterate to recenter on the star
        angsep = gPhoton.MCUtils.angularSeparation(skypos[0], skypos[1],
                                                   np.array(events['ra']),
                                                   np.array(events['dec']))
        ix = np.where((angsep <= aper) & (np.isfinite(angsep))
                      & (np.array(events['flags'], dtype='int16') == 0))
        skypos = [
            np.median(np.array(events['ra'])[ix]),
            np.median(np.array(events['dec'])[ix])
        ]
    return skypos
示例#7
0
def suggest_parameters(band, skypos, verbose=0):
    """
    Provide suggested coordinates and photometric apertures for a source
        given the location of known MCAT sources nearby.

    :param band: The band to use, either 'FUV' or 'NUV'.

    :type band: str

    :param skypos: The right ascension and declination, in degrees.

    :type skypos: list

    :param verbose: Verbosity level, a value of 0 is minimum verbosity.

    :type verbose: int

    :returns: tuple -- A five-element tuple containing the suggested right
        ascension, declination, photometric aperture, inner annulus, and outer
        annulus, all in degrees.
    """

    mcat = get_mcat_data(skypos, 0.01)
    ix = np.where((mcat[band]['mag'] > 0) & (mcat[band]['fwhm'] > 0))
    pos, fwhm = None, None

    if mcat['objid'].any(): # There is a known star at the target position!
        pos = [mcat['ra'][ix].mean(), mcat['dec'][ix].mean()]
        radius = 2*mcat[band]['fwhm'][ix].mean()
        if verbose:
            print('Recentering on {pos}.'.format(pos=pos))
            print('Using aperture radius of {rad} degrees.'.format(rad=fwhm))
    else: # There is no known star at the target position...
        pos = skypos
        radius = aper2deg(4)
    annulus = [3*radius, 5*radius]

    return pos[0], pos[1], radius, annulus[0], annulus[1]
示例#8
0
def suggest_parameters(band, skypos, verbose=0):
    """
    Provide suggested coordinates and photometric apertures for a source
        given the location of known MCAT sources nearby.

    :param band: The band to use, either 'FUV' or 'NUV'.

    :type band: str

    :param skypos: The right ascension and declination, in degrees.

    :type skypos: list

    :param verbose: Verbosity level, a value of 0 is minimum verbosity.

    :type verbose: int

    :returns: tuple -- A five-element tuple containing the suggested right
        ascension, declination, photometric aperture, inner annulus, and outer
        annulus, all in degrees.
    """

    mcat = get_mcat_data(skypos, 0.01)
    ix = np.where((mcat[band]['mag'] > 0) & (mcat[band]['fwhm'] > 0))
    pos, fwhm = None, None

    if mcat['objid'].any():  # There is a known star at the target position!
        pos = [mcat['ra'][ix].mean(), mcat['dec'][ix].mean()]
        radius = 2 * mcat[band]['fwhm'][ix].mean()
        if verbose:
            print('Recentering on {pos}.'.format(pos=pos))
            print('Using aperture radius of {rad} degrees.'.format(rad=fwhm))
    else:  # There is no known star at the target position...
        pos = skypos
        radius = aper2deg(4)
    annulus = [3 * radius, 5 * radius]

    return pos[0], pos[1], radius, annulus[0], annulus[1]
示例#9
0
def check_radius(args):
    """
    Checks the radius value.

    :param args: The command-line arguments.

    :type args: argparse.ArgumentParser Namespace

    :returns: argparse.ArgumentParser Namespace -- The updated command-line
        arguments.
    """

    if not (args.radius or args.suggest or args.aperradius):
        print("Must specify an aperture radius.")
        raise SystemExit

    if args.radius and args.aperradius:
        print("Must not specify both --aperture and --mcataper.")
        raise SystemExit

    if args.aperradius and not args.radius:
        args.radius = aper2deg(args.aperradius)

    return args
示例#10
0
def check_radius(args):
    """
    Checks the radius value.

    :param args: The command-line arguments.

    :type args: argparse.ArgumentParser Namespace

    :returns: argparse.ArgumentParser Namespace -- The updated command-line
        arguments.
    """

    if not (args.radius or args.suggest or args.aperradius):
        print("Must specify an aperture radius.")
        raise SystemExit

    if args.radius and args.aperradius:
        print("Must not specify both --aperture and --mcataper.")
        raise SystemExit

    if args.aperradius and not args.radius:
        args.radius = aper2deg(args.aperradius)

    return args
示例#11
0
def datamaker(band,
              skypos,
              outfile,
              maglimit=20.,
              margin=0.005,
              searchradius=0.1,
              radius=gt.aper2deg(4),
              annulus=[0.0083, 0.025],
              verbose=0):
    """
    Generate gAperture photometry for MCAT sources within a specified region.

    :param band: The band to use, either 'FUV' or 'NUV'.

    :type band: str

    :param skypos: The right ascension and declination, in degrees.

    :type skypos: list

    :param outfile: Name of output file to make.

    :type outfile: str

    :param maglimit: Faint limit to use, in AB Mag.

    :type maglimit: float

    :param margin: The margin within which two sources are consider "the same,"
        in degrees.

    :type margin: float

    :param searchradius: The radius within which to search for sources, degrees.

    :type searchradius: float

    :param radius: The size of the aperture to measure fluxes with, in degrees.

    :type radius: float

    :param annulus: The inner and outer radii of the background annulus
        in degrees.

    :type annulus: float

    :param verbose: Verbosity level, a value of 0 is minimum verbosity.

    :type verbose: int
    """

    extant_objids = file_setup(outfile)

    if extant_objids == False:
        print('NOT RUNNING!!*!')
        return False

    uniques = dt.find_unique_sources(band,
                                     skypos[0],
                                     skypos[1],
                                     searchradius,
                                     maglimit=maglimit)

    if uniques is None:
        print('No sources at this position.')
        return

    for pos in uniques:
        mcat = dt.get_mcat_data(pos, margin)
        if not mcat:
            print('Nothing at {pos}.'.format(pos=pos))
            continue
        extant_objids = file_setup(outfile)
        for i, objid in enumerate(mcat['objid']):
            if mcat[band]['ra'][i] == -99. and mcat[band]['dec'][i] == -99.:
                print('No {b} source'.format(b=band))
                continue
            if objid in extant_objids:
                print('Already processed.')
                continue
            #exp = dt.exp_from_objid(objid)
            if mcat[band]['t0'][i] < 0:
                print('No MCAT exposure: skipping')
                continue
            print([mcat[band]['ra'][i], mcat[band]['dec'][i]])
            print([mcat[band]['t0'][i], mcat[band]['t1'][i]])
            data = gAperture(band, [mcat[band]['ra'][i], mcat[band]['dec'][i]],
                             radius,
                             annulus=annulus,
                             verbose=verbose,
                             coadd=True,
                             trange=[mcat[band]['t0'][i], mcat[band]['t1'][i]],
                             detsize=1.25)
            try:
                csv_construct = construct_row(i, band, objid, mcat, data)
                print(csv_construct)
                with open(outfile, 'a') as csvfile:
                    spreadsheet = csv.writer(csvfile,
                                             delimiter=',',
                                             quotechar='|',
                                             quoting=csv.QUOTE_MINIMAL)
                    spreadsheet.writerow(csv_construct)
            except TypeError:
                continue

    return
示例#12
0
def makemap(band, skypos, trange, skyrange, response=False, verbose=0,
            detsize=1.1):
    """
    Generate a single image frame.

    :param band: The band to use, either 'FUV' or 'NUV'.

    :type band: str

    :param skypos: The right ascension and declination, in degrees.

    :type skypos: list

    :param trange: Minimum and maximum time to use, in GALEX time seconds.

    :type trange: list

    :param skyrange: RA and Dec extent of the region of interest in degrees.

    :type skyrange: list

    :param response: Apply the response correction.

    :type response: bool

    :param verbose: Verbosity level, a value of 0 is minimum verbosity.

    :type verbose: int

    :param detsize: Effective diameter, in degrees, of the field-of-view.

    :type detsize: float

    :returns: numpy.ndarray - The bi-dimensional histogram of ra and dec.
    """

    imsz = gxt.deg2pix(skypos, skyrange)

    photons = np.array(gQuery.getArray(
        gQuery.skyrect(band, skypos[0], skypos[1], trange[0], trange[1],
                       skyrange[0], skyrange[1]), verbose=verbose),
                       dtype='float64')
    try:
        events = {'t':photons[:, 0]/tscale, 'ra':photons[:, 1],
                  'dec':photons[:, 2], 'xi':photons[:, 3], 'eta':photons[:, 4],
                  'x':photons[:, 5], 'y':photons[:, 6]}
    except IndexError:
        if verbose > 2:
            print('No events found at {s} +/- {r} in {t}.'.format(
                s=skypos, r=skyrange, t=trange))
        return np.zeros(np.array(imsz, dtype='int32'))

    # Trim the data on detsize
    col, row = ct.xieta2colrow(events['xi'], events['eta'], band)
    ix = np.where(gxt.aper2deg(4)*mc.distance(col, row, 400, 400) <= detsize)
    n = len(ix[0])
    m = len(col)

    if n == 0:
        return np.zeros(np.int(imsz))

    for k in list(events.keys()):
        events[k] = events[k][ix]

    events = ct.hashresponse(band, events)
    wcs = define_wcs(skypos, skyrange)
    coo = list(zip(events['ra'], events['dec']))
    foc = wcs.sip_pix2foc(wcs.wcs_world2pix(coo, 1), 1)
    weights = 1./events['response'] if response else None
    H, xedges, yedges = np.histogram2d(foc[:, 1]-0.5, foc[:, 0]-0.5, bins=imsz,
                                       range=([[0, imsz[0]], [0, imsz[1]]]),
                                       weights=weights)

    return H
示例#13
0
def datamaker(band, skypos, outfile, maglimit=20., margin=0.005,
              searchradius=0.1, radius=gt.aper2deg(4), annulus=[0.0083, 0.025],
              verbose=0):
    """
    Generate gAperture photometry for MCAT sources within a specified region.

    :param band: The band to use, either 'FUV' or 'NUV'.

    :type band: str

    :param skypos: The right ascension and declination, in degrees.

    :type skypos: list

    :param outfile: Name of output file to make.

    :type outfile: str

    :param maglimit: Faint limit to use, in AB Mag.

    :type maglimit: float

    :param margin: The margin within which two sources are consider "the same,"
        in degrees.

    :type margin: float

    :param searchradius: The radius within which to search for sources, degrees.

    :type searchradius: float

    :param radius: The size of the aperture to measure fluxes with, in degrees.

    :type radius: float

    :param annulus: The inner and outer radii of the background annulus
        in degrees.

    :type annulus: float

    :param verbose: Verbosity level, a value of 0 is minimum verbosity.

    :type verbose: int
    """

    extant_objids = file_setup(outfile)

    if extant_objids == False:
        print('NOT RUNNING!!*!')
        return False

    uniques = dt.find_unique_sources(band, skypos[0], skypos[1], searchradius,
                                     maglimit=maglimit)

    if uniques is None:
        print('No sources at this position.')
        return

    for pos in uniques:
        mcat = dt.get_mcat_data(pos, margin)
        if not mcat:
            print('Nothing at {pos}.'.format(pos=pos))
            continue
        extant_objids = file_setup(outfile)
        for i, objid in enumerate(mcat['objid']):
            if mcat[band]['ra'][i] == -99. and mcat[band]['dec'][i] == -99.:
                print('No {b} source'.format(b=band))
                continue
            if objid in extant_objids:
                print('Already processed.')
                continue
            #exp = dt.exp_from_objid(objid)
            if mcat[band]['t0'][i] < 0:
                print('No MCAT exposure: skipping')
                continue
            print([mcat[band]['ra'][i], mcat[band]['dec'][i]])
            print([mcat[band]['t0'][i], mcat[band]['t1'][i]])
            data = gAperture(band, [mcat[band]['ra'][i], mcat[band]['dec'][i]],
                             radius, annulus=annulus, verbose=verbose,
                             coadd=True, trange=[mcat[band]['t0'][i],
                                                 mcat[band]['t1'][i]],
                             detsize=1.25)
            try:
                csv_construct = construct_row(i, band, objid, mcat, data)
                print(csv_construct)
                with open(outfile, 'ab') as csvfile:
                    spreadsheet = csv.writer(csvfile, delimiter=',',
                                             quotechar='|',
                                             quoting=csv.QUOTE_MINIMAL)
                    spreadsheet.writerow(csv_construct)
            except TypeError:
                continue

    return
示例#14
0
文件: gCalrun.py 项目: parkus/gPhoton
def calrun(outfile, band, nsamples=10, seed=323, rarange=[0., 360.],
           decrange=[-90., 90.], exprange=[0., 5000.], maglimit=24., verbose=0,
           radius=gt.aper2deg(4), annulus=[0.0083, 0.025]):
    """
    Generate a bunch of magnitudes with comparisons against MCAT values for
        random points on the sky within given legal ranges. Write it to a CSV.

    :param outfile: Name of the output file.

    :type outfile: str

    :param band: The band being used, either 'FUV' or 'NUV'.

    :type band: str

    :param nsamples: Number of random positions to sample.

    :type nsamples: int

    :param seed: The seed to use when generating the random sample.

    :type seed: int

    :param rarange: The minimum and maximum RA range to sample from.

    :type rarange: list

    :param decrange: The minimum and maximum DEC range to sample from.

    :type decrange: list

    :param exprange: The minimum and maximum exposure time to sample,
        in seconds.

    :type exprange: list

    :param maglimit: The faintest source to consider.

    :type maglimit: float

    :param verbose: Verbosity level, a value of 0 is minimum verbosity.

    :type verbose: int

    :param radius: Photometric aperture radius, in degrees.

    :type radius: float

    :param annulus: Inner and outer extent of background annulus, in degrees.

    :type annulus: list
    """

    (ra, dec) = find_random_positions(rarange=rarange, decrange=decrange,
                                      nsamples=nsamples, seed=seed)

    if verbose:
        print('Running {n} random samples with seed of {seed}.'.format(
            n=nsamples, seed=seed))
        print('Bounded by RA:[{r0},{r1}] and Dec:[{d0},{d1}]'.format(
            r0=rarange[0], r1=rarange[1], d0=decrange[0], d1=decrange[1]))
        print('Actual positions used will be:')
        print('{pos}'.format(pos=list(zip(ra, dec))))

    for skypos in zip(ra, dec):
        expt = gFind(skypos=skypos, band=band, quiet=True)[band]['expt']
        if exprange[0] <= expt <= exprange[1]:
            print(skypos, expt, True)
            datamaker(band, skypos, outfile, maglimit=maglimit, verbose=verbose,
                      searchradius=0.01)
        else:
            print(skypos, expt, False)

    return
示例#15
0
def calrun(outfile,
           band,
           nsamples=10,
           seed=323,
           rarange=[0., 360.],
           decrange=[-90., 90.],
           exprange=[0., 5000.],
           maglimit=24.,
           verbose=0,
           radius=gt.aper2deg(4),
           annulus=[0.0083, 0.025]):
    """
    Generate a bunch of magnitudes with comparisons against MCAT values for
        random points on the sky within given legal ranges. Write it to a CSV.

    :param outfile: Name of the output file.

    :type outfile: str

    :param band: The band being used, either 'FUV' or 'NUV'.

    :type band: str

    :param nsamples: Number of random positions to sample.

    :type nsamples: int

    :param seed: The seed to use when generating the random sample.

    :type seed: int

    :param rarange: The minimum and maximum RA range to sample from.

    :type rarange: list

    :param decrange: The minimum and maximum DEC range to sample from.

    :type decrange: list

    :param exprange: The minimum and maximum exposure time to sample,
        in seconds.

    :type exprange: list

    :param maglimit: The faintest source to consider.

    :type maglimit: float

    :param verbose: Verbosity level, a value of 0 is minimum verbosity.

    :type verbose: int

    :param radius: Photometric aperture radius, in degrees.

    :type radius: float

    :param annulus: Inner and outer extent of background annulus, in degrees.

    :type annulus: list
    """

    (ra, dec) = find_random_positions(rarange=rarange,
                                      decrange=decrange,
                                      nsamples=nsamples,
                                      seed=seed)

    if verbose:
        print('Running {n} random samples with seed of {seed}.'.format(
            n=nsamples, seed=seed))
        print('Bounded by RA:[{r0},{r1}] and Dec:[{d0},{d1}]'.format(
            r0=rarange[0], r1=rarange[1], d0=decrange[0], d1=decrange[1]))
        print('Actual positions used will be:')
        print('{pos}'.format(pos=list(zip(ra, dec))))

    for skypos in zip(ra, dec):
        expt = gFind(skypos=skypos, band=band, quiet=True)[band]['expt']
        if exprange[0] <= expt <= exprange[1]:
            print(skypos, expt, True)
            datamaker(band,
                      skypos,
                      outfile,
                      maglimit=maglimit,
                      verbose=verbose,
                      searchradius=0.01)
        else:
            print(skypos, expt, False)

    return
示例#16
0
def makemap(band, skypos, trange, skyrange, response=False, verbose=0,
            detsize=1.1):
    """
    Generate a single image frame.

    :param band: The band to use, either 'FUV' or 'NUV'.

    :type band: str

    :param skypos: The right ascension and declination, in degrees.

    :type skypos: list

    :param trange: Minimum and maximum time to use, in GALEX time seconds.

    :type trange: list

    :param skyrange: RA and Dec extent of the region of interest in degrees.

    :type skyrange: list

    :param response: Apply the response correction.

    :type response: bool

    :param verbose: Verbosity level, a value of 0 is minimum verbosity.

    :type verbose: int

    :param detsize: Effective diameter, in degrees, of the field-of-view.

    :type detsize: float

    :returns: numpy.ndarray - The bi-dimensional histogram of ra and dec.
    """

    imsz = gxt.deg2pix(skypos, skyrange)

    photons = np.array(gQuery.getArray(
        gQuery.skyrect(band, skypos[0], skypos[1], trange[0], trange[1],
                       skyrange[0], skyrange[1]), verbose=verbose),
                       dtype='float64')
    try:
        events = {'t':photons[:, 0]/tscale, 'ra':photons[:, 1],
                  'dec':photons[:, 2], 'xi':photons[:, 3], 'eta':photons[:, 4],
                  'x':photons[:, 5], 'y':photons[:, 6]}
    except IndexError:
        if verbose > 2:
            print('No events found at {s} +/- {r} in {t}.'.format(
                s=skypos, r=skyrange, t=trange))
        return np.zeros(np.array(imsz, dtype='int32'))

    # Trim the data on detsize
    col, row = ct.xieta2colrow(events['xi'], events['eta'], band)
    ix = np.where(gxt.aper2deg(4)*mc.distance(col, row, 400, 400) <= detsize)
    n = len(ix[0])
    m = len(col)

    if n == 0:
        return np.zeros(np.int(imsz))

    for k in list(events.keys()):
        events[k] = events[k][ix]

    events = ct.hashresponse(band, events)
    wcs = define_wcs(skypos, skyrange)
    coo = list(zip(events['ra'], events['dec']))
    foc = wcs.sip_pix2foc(wcs.wcs_world2pix(coo, 1), 1)
    weights = 1./events['response'] if response else None
    H, xedges, yedges = np.histogram2d(foc[:, 1]-0.5, foc[:, 0]-0.5, bins=imsz,
                                       range=([[0, imsz[0]], [0, imsz[1]]]),
                                       weights=weights)

    return H
示例#17
0
def make_lightcurve(photon_file,
                    band,
                    stepsz=30.,
                    skypos=(24.76279, -17.94948),
                    aper=gt.aper2deg(7),
                    fixed_t0=False,
                    makefile=False,
                    quiet=False,
                    filetag='',
                    lc_filename=None):
    """ Generate a light curve of a specific target. """
    if lc_filename is None:
        lc_filename = photon_file.replace(
            '.csv', '-{stepsz}s{filetag}.csv'.format(stepsz=int(stepsz),
                                                     filetag=filetag))
    if os.path.exists(lc_filename) and not makefile:
        if not quiet:
            print_inline('    Pre-exists, reading in file...')
        return pd.read_csv(lc_filename)
    else:
        if not quiet:
            print_inline('Generating {fn}'.format(fn=lc_filename))
    events = calibrate_photons(photon_file, band)
    # Below is a calculation of the re-centering, if desired.
    skypos_recentered = recenter(events, skypos=skypos)
    c1 = SkyCoord(ra=skypos[0] * u.degree, dec=skypos[1] * u.degree)
    c2 = SkyCoord(ra=skypos_recentered[0] * u.degree,
                  dec=skypos_recentered[1] * u.degree)
    if not quiet:
        print('Recentering aperture on [{ra}, {dec}]'.format(
            ra=skypos_recentered[0], dec=skypos_recentered[1]))
        print("Recenter shift (arcsec): " + str(c1.separation(c2).arcsec))
    ix = aper_photons(events, skypos=skypos_recentered, aper=aper)
    if len(ix[0]) == 0:
        return [], [], [], []
    trange = [
        np.floor(np.array(events['t'])[ix].min()),
        np.ceil(np.array(events['t'])[ix].max())
    ]
    if fixed_t0:
        # Use this to force NUV and FUV to have the same bins
        trange[0] = fixed_t0
    expt = compute_exptime_array(np.array(events['t'].values), band, trange,
                                 stepsz, np.array(events['flags'].values))
    counts, tbins, detrads = [], [], []
    col, row = np.array(events['col']), np.array(events['row'])
    detrad = np.sqrt((col - 400)**2 + (row - 400)**2)
    for t0 in np.arange(trange[0], trange[1], stepsz):
        tix = np.where((np.array(events['t'])[ix] >= t0)
                       & (np.array(events['t']) < t0 + stepsz)[ix]
                       & (np.array(events['flags'], dtype='int16')[ix] == 0))
        tbins += [t0]
        detrads += [detrad[ix][tix].mean()]
        if len(tix[0]) == 0:
            counts += [0.]
        else:
            counts += [np.array(events['response'])[ix][tix].sum()]
    cps = np.array(counts) / np.array(expt)
    cps_err = np.sqrt(counts) / np.array(expt)
    lc = pd.DataFrame({
        't0': tbins,
        't1': list(np.array(tbins) + stepsz),
        'cps': cps,
        'cps_err': cps_err,
        'flux': gt.counts2flux(cps, band),
        'flux_err': gt.counts2flux(cps_err, band),
        'counts': counts,
        'expt': expt,
        'detrad': detrads
    })
    lc['cps_apcorrected'] = apcorrect_cps(lc, band, aper=aper)
    lc['flux_apcorrected'] = gt.counts2flux(lc['cps_apcorrected'], band)
    lc.to_csv(lc_filename)
    return lc