コード例 #1
0
ファイル: hole_filler.py プロジェクト: bbw7561135/g309.2-0.6
def main():
    """File modification I/O"""
    parser = argparse.ArgumentParser(description=("Given an image"
                " in tangent-projected sky coordinates and"
                " an ASC-REGION-FITS list of point source exclusions in"
                " celestial RA/dec coordinates,"
                " fill in the image gaps by drawing counts from a"
                " Poisson distribution with mean count rate derived from"
                " an annulus surrounding each point source"))
    parser.add_argument('image', metavar='FITS',
                        help=("FITS image in tangent-projected sky coords"))
    parser.add_argument('exposure', metavar='FITS',
                        help=("FITS exposure image in tangent-projected sky coords"))
    parser.add_argument('--annulus-width', metavar='pixels', type=float,
                        help=("Width (pixels) of sampling annulus around each"
                              " point source."))
    parser.add_argument('--mask', metavar='FITS',
                        help=("ASC-REGION-FITS sources in un-projected RA/DEC,"
                            " radii in arcmin."))
    parser.add_argument('--debug', action='store_true',
                        help=("Create debugging output w/ sources and annuli"
                              " highlighted by artificially high/low values"))
    parser.add_argument('--clobber', action='store_true',
                        help=("Clobber any existing output file"))
    parser.add_argument('--out', metavar='output.fits',
                        help=("Output filename, image w/ filled holes"))
    args = parser.parse_args()

    F_INPUT = args.image
    F_EXPOSURE = args.exposure
    F_MASK = args.mask
    F_OUTPUT = args.out
    ANNULUS_WIDTH = args.annulus_width
    OPT_DEBUG = args.debug
    OPT_CLOBBER = args.clobber

    R_EPSILON = 0.5  # Radius error (pixels) for each point source

    unit_tests()
    assert F_INPUT != F_OUTPUT

    fits_mask = fits.open(F_MASK)
    mask = fits_mask[1]
    assert mask.header['HDUCLASS'] == 'ASC'
    assert mask.header['HDUCLAS1'] == 'REGION'
    assert mask.header['HDUCLAS2'] == 'STANDARD'
    assert mask.header['MTYPE1'] == 'pos'
    assert mask.header['MFORM1'] == 'RA,DEC'
    assert all(map(lambda x: x == '!CIRCLE', mask.data['SHAPE']))

    fits_image = fits.open(F_INPUT)
    phdu = fits_image[0]
    assert phdu.is_image
    assert phdu.header['CTYPE1'] == 'RA---TAN'
    assert phdu.header['CUNIT1'] == 'deg'
    assert phdu.header['CTYPE2'] == 'DEC--TAN'
    assert phdu.header['CUNIT2'] == 'deg'
    assert phdu.header['CDELT1'] == -1 * phdu.header['CDELT2']
    assert phdu.header['CDELT2'] > 0
    # XMM ESAS image convention: X scales inversely w/ RA, Y increases w/ dec.
    # X,Y pixel sizes are equal in deg.; the projection transformation accounts
    # for RA "compression" at high declination

    dat = phdu.data
    x0 = phdu.header['CRPIX1']
    y0 = phdu.header['CRPIX2']
    ra0 = phdu.header['CRVAL1']
    dec0 = phdu.header['CRVAL2']
    scale = phdu.header['CDELT2']  # pixel size == deg./pixel

    fits_exposure = fits.open(F_EXPOSURE)
    exposure = fits_exposure[0]
    for key in ['CTYPE1', 'CUNIT1', 'CTYPE2', 'CUNIT2', 'CDELT1', 'CDELT2']:
        assert exposure.header[key] == phdu.header[key]

    dat_exp = exposure.data

    # Convert point sources to current image projection
    x_srcs, y_srcs = radec2tanproj(mask.data['RA'][:,0], mask.data['DEC'][:,0],
                                 ra0, dec0, x0, y0, 1/scale)
    r_srcs = mask.data['R'][:,0] / (abs(scale) * 60)  # scale*60 = arcmin/px

    n_overlaps = any_sources_overlap(x_srcs, y_srcs, r_srcs)
    if n_overlaps:
        print "\nWARNING: {} pairwise overlap(s), source filling may be inconsistent!\n".format(n_overlaps)

    dat_filled = np.copy(dat)
    max_abs_dat = np.max(np.abs(dat))

    # Get all pixels in annulus of radii (r_pt + 0.5, r_pt + 0.5 + ANN_WIDTH)
    # centered on each point source.
    for x_pt, y_pt, r_pt in zip(x_srcs, y_srcs, r_srcs):

        ann_r1 = r_pt + R_EPSILON  # Excise extra edge pixels
        ann_r2 = r_pt + R_EPSILON + ANNULUS_WIDTH

        # Inspect all pixels in a box around source + annulus
        # Bounds for x, y search: either annulus edge or image boundary
        search_x = (max(1, np.floor(x_pt - ann_r2)),
                    min(dat.shape[0], np.ceil(x_pt + ann_r2)))
        search_y = (max(1, np.floor(y_pt - ann_r2)),
                    min(dat.shape[1], np.ceil(y_pt + ann_r2)))
        search_x = map(int, search_x)
        search_y = map(int, search_y)

        ann_count_rate = 0
        ann_px = 0
        mean_exp = 0

        for x in range(search_x[0], search_x[1] + 1):
            for y in range(search_y[0], search_y[1] + 1):
                # Order of conditionals matters for short-circuiting:
                # distance check is O(1)
                # vs. overlapping source check is O(n_sources)
                d = distance((x, y), (x_pt, y_pt))
                if d > ann_r1 and d < ann_r2 and (not point_overlaps_sources(x, y, x_srcs, y_srcs, r_srcs + R_EPSILON)):
                    # Prevent division by zero.  Counts should be zero in these pixels anyways.
                    if dat_exp[y-1, x-1] <= 0:
                        continue
                    ann_count_rate += (dat[y-1, x-1] / dat_exp[y-1, x-1])  # shift 1- to 0-based indices
                    ann_px += 1
                    mean_exp += dat_exp[y-1, x-1]

        mean_exp = mean_exp / ann_px  # Average exposure for a given pixel

        print "Source at ({:g},{:g}), r = {:g}. Search X in {}, Y in {}.".format(
                x_pt, y_pt, r_pt, search_x, search_y)
        print "  Annulus count rate: {:g} cts/s".format(ann_count_rate)
        print "       usable pixels: {:g} px".format(ann_px)
        print "          count flux: {:g} cts/px/s".format(ann_count_rate/ann_px)
        print "  Total cts mean exp: {:g}".format(ann_count_rate * mean_exp)
        if ann_px < 100:
            print "\n  ***WARNING: <100 pixels sampled; adjust --annulus-width***\n"
        if ann_count_rate * mean_exp < 1:
            print "\n  ***WARNING: <1 count in annulus for mean exposure***\n"

        flux = ann_count_rate/ann_px  # counts / sec / pixel
        if flux < 0:
            print "\n  ***WARNING: negative count rate, forcing to zero!***\n"
            flux = 0

        # Insert new values into image

        for x in range(search_x[0], search_x[1] + 1):
            for y in range(search_y[0], search_y[1] + 1):
                d = distance((x, y), (x_pt, y_pt))
                if OPT_DEBUG:
                    if d <= ann_r1:
                        dat_filled[y-1, x-1] = 10 * max_abs_dat
                    elif d > ann_r1 and d < ann_r2:
                        dat_filled[y-1, x-1] = -10 * max_abs_dat
                else:
                    if d <= ann_r1:
                        # Scale flux to counts/px for current pixel's exposure
                        dat_filled[y-1, x-1] = np.random.poisson(flux * dat_exp[y-1, x-1])

    phdu.data = dat_filled
    fits_image.writeto(F_OUTPUT, clobber=OPT_CLOBBER)

    print "\nWrote output to {:s}.  Check image for reasonable annuli".format(F_OUTPUT)
    print "(e.g., overlapping sources OK; annuli not sampling beyond sky FOV)."
コード例 #2
0
def main():
    """File conversion I/O"""
    parser = argparse.ArgumentParser(description=("Convert an"
                " ASC-REGION-FITS point source exclusion (!CIRCLE) file"
                " from RA/dec celestial coordinates with radii in arcmin."
                " to tangent-projected sky coordinates (xy)"))
    parser.add_argument('input', metavar='input-radec.fits',
                        help=("ASC-REGION-FITS sources in un-projected RA/dec,"
                              " radii in arcmin."))
    parser.add_argument('--template', metavar='reference.fits',
                        help=("Reference ASC-REGION-FITS file (with desired"
                              " projection and header keywords"))
    parser.add_argument('--out', metavar='output-sky.fits',
                        help=("Output filename, converted ASC-REGION-FITS in"
                              " tangent-projected sky coordinates"))
    args = parser.parse_args()
    F_INPUT = args.input
    F_TEMPLATE = args.template
    F_OUTPUT = args.out

    # Parse and validate input

    if not re.match(".*bkg_region-sky.*\.fits", F_TEMPLATE):
        warn(("--template {:s} doesn't follow ESAS naming").format(F_TEMPLATE))
    if not re.match(".*bkg_region-sky.*\.fits", F_OUTPUT):
        warn(("--out {:s} doesn't follow ESAS naming").format(F_OUTPUT))

    #shutil.copy(F_TEMPLATE, F_OUT)

    fits_input = fits.open(F_INPUT)
    fits_template = fits.open(F_TEMPLATE)

    t_in = fits_input[1]  # first BinTable
    t_template = fits_template[1]

    for t in [t_in, t_template]:
        assert t.header['HDUCLASS'] == 'ASC'
        assert t.header['HDUCLAS1'] == 'REGION'
        assert t.header['HDUCLAS2'] == 'STANDARD'
    assert t_in.header['MTYPE1'] == 'pos'
    assert t_in.header['MFORM1'] == 'RA,DEC'
    assert all(map(lambda x: x == '!CIRCLE', t_in.data['SHAPE']))

    # Set up projection center
    assert t_template.header['TCTYP2'] == "RA---TAN"
    assert t_template.header['TCUNI2'] == "deg"
    x0 = t_template.header['TCRPX2']
    ra0 = t_template.header['TCRVL2']

    assert t_template.header['TCTYP3'] == "DEC--TAN"
    assert t_template.header['TCUNI3'] == "deg"
    y0 = t_template.header['TCRPX3']
    dec0 = t_template.header['TCRVL3']

    # Square coordinates
    assert t_template.header['TCDLT2'] == -1 * t_template.header['TCDLT3']
    assert t_template.header['TCDLT3'] > 0
    scale = t_template.header['TCDLT3']

    # Perform the coordinate conversion (vectorized)
    x, y = radec2tanproj(t_in.data['RA'][:,0], t_in.data['DEC'][:,0],
                         ra0, dec0, x0, y0, 1/scale)
    # 1/scale needed to get pixel/deg., as FITS sky coords records deg./pixel

    radius = t_in.data['R'][:,0] / (abs(scale) * 60)  # scale*60 = arcmin/px

    # Construct output FITS file in same format as output by SAS task region
    # (except use 'E' instead of '4E' for X, Y, R, ROTANG)
    x_pad = pad_E_to_4E(x)
    y_pad = pad_E_to_4E(y)
    radius_pad = pad_E_to_4E(radius)

    bhdu = fits.BinTableHDU.from_columns(
        [fits.Column(name='SHAPE', format=t_in.columns['SHAPE'].format,
                     array=t_in.data['SHAPE']),
         fits.Column(name='X', format='4E', array=x_pad),
         fits.Column(name='Y', format='4E', array=y_pad),
         fits.Column(name='R', format='4E', array=radius_pad),
         fits.Column(name='ROTANG', format='4E', array=t_in.data['ROTANG']),
         fits.Column(name='COMPONENT', format='J', array=t_in.data['COMPONENT'])
         ]
        )
    bhdu.name = 'REGION'

    # Loosely following ASC-REGION-FITS spec
    bhdu.header['HDUVERS'] = '1.2.0'
    bhdu.header['HDUCLASS'] = 'ASC'
    bhdu.header['HDUCLAS1'] = 'REGION'
    bhdu.header['HDUCLAS2'] = 'STANDARD'
    bhdu.header['HDUDOC'] = 'ASC-FITS-REGION-1.2: Rots, McDowell'

    bhdu.header['MTYPE1'] = 'pos'
    bhdu.header['MFORM1'] = 'X,Y'
    for kw in ['TCTYP2', 'TCRPX2', 'TCRVL2', 'TCUNI2', 'TCDLT2',
               'TCTYP3', 'TCRPX3', 'TCRVL3', 'TCUNI3', 'TCDLT3']:
        # Coordinate transformation parameters already validated above
        if ("TCRPX" in kw) or ("TCRVL" in kw) or ("TCDLT" in kw):
            bhdu.header[kw] = "{:.14e}".format(t_template.header[kw]).upper()
        else:
            bhdu.header[kw] = t_template.header[kw]


    phdu = fits_template[0]  # Work off of template header
    phdu.header['HISTORY'] = ('Rewritten by {} (atran@cfa)'
                             ' using RA/dec list {} on date {}').format(
                            os.path.basename(__file__),
                            os.path.basename(F_INPUT),
                            datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%Sz'))
    # RFC3339/ISO8601 date as used by XMM tools; add 'Z' to indicate timezone

    f_out = fits.HDUList([phdu, bhdu])
    f_out.writeto(F_OUTPUT, clobber=True)