Ejemplo n.º 1
0
def galex_tractor_image(tile, band, galex_dir, radecbox, bandname):
    from tractor import (NanoMaggies, Image, LinearPhotoCal,
                         ConstantFitsWcs, ConstantSky)

    assert(band in ['n','f'])

    #nicegbands = ['NUV', 'FUV']
    #zps = dict(n=20.08, f=18.82)
    #zp = zps[band]
    
    imfn = os.path.join(galex_dir, tile.tilename.strip(),
                        '%s-%sd-intbgsub.fits.gz' % (tile.visitname.strip(), band))
    gwcs = Tan(*[float(f) for f in
                 [tile.crval1, tile.crval2, tile.crpix1, tile.crpix2,
                  tile.cdelt1, 0., 0., tile.cdelt2, 3840., 3840.]])
    (r0,r1,d0,d1) = radecbox
    H,W = gwcs.shape
    ok,xx,yy = gwcs.radec2pixelxy([r0,r0,r1,r1], [d0,d1,d1,d0])
    #print('GALEX WCS pixel positions of RA,Dec box:', xx, yy)
    if np.any(np.logical_not(ok)):
        return None
    x0 = np.clip(np.floor(xx-1).astype(int).min(), 0, W-1)
    x1 = np.clip(np.ceil (xx-1).astype(int).max(), 0, W)
    if x1-x0 <= 1:
        return None
    y0 = np.clip(np.floor(yy-1).astype(int).min(), 0, H-1)
    y1 = np.clip(np.ceil (yy-1).astype(int).max(), 0, H)
    if y1-y0 <= 1:
        return None
    debug('Reading GALEX subimage x0,y0', x0,y0, 'size', x1-x0, y1-y0)
    gwcs = gwcs.get_subimage(x0, y0, x1 - x0, y1 - y0)
    twcs = ConstantFitsWcs(gwcs)
    roislice = (slice(y0, y1), slice(x0, x1))
    
    fitsimg = fitsio.FITS(imfn)[0]
    hdr = fitsimg.read_header()
    img = fitsimg[roislice]

    inverr = np.ones_like(img)
    inverr[img == 0.] = 0.

    zp = tile.get('%s_zpmag' % band)
    
    photocal = LinearPhotoCal(NanoMaggies.zeropointToScale(zp), band=bandname)

    tsky = ConstantSky(0.)

    name = 'GALEX ' + hdr['OBJECT'] + ' ' + band

    psfimg = galex_psf(band, galex_dir)
    tpsf = PixelizedPSF(psfimg)

    tim = Image(data=img, inverr=inverr, psf=tpsf, wcs=twcs,
                sky=tsky, photocal=photocal, name=name)
    tim.roi = [x0,x1,y0,y1]
    return tim
Ejemplo n.º 2
0
def main():
    # Where are the data?
    datadir = os.path.join(os.path.dirname(__file__), 'data-decam')
    name = 'decam-520206-S16'
    imagefn = os.path.join(datadir, '%s-image-sub.fits' % name)
    invvarfn = os.path.join(datadir, '%s-invvar-sub.fits' % name)
    psfexfn = os.path.join(datadir, '%s-psfex.fits' % name)
    catfn = os.path.join(datadir, 'tractor-1816p325-sub.fits')

    # Read the image and inverse-variance maps.
    image = fitsio.read(imagefn)
    invvar = fitsio.read(invvarfn)
    # The DECam inverse-variance maps are unfortunately corrupted
    # by fpack, causing zeros to become negative.  Fix those.
    invvar[invvar < np.median(invvar) * 0.1] = 0.
    H, W = image.shape
    print('Subimage size:', image.shape)

    # For the PSF model, we need to know what subimage region this is:
    subimage_offset = (35, 1465)
    # We also need the calibrated zeropoint.
    zeropoint = 24.7787

    # What filter was this image taken in?  (z)
    prim_header = fitsio.read_header(imagefn)
    band = prim_header['FILTER'].strip()[0]
    print('Band:', band)
    # These DECam images were calibrated so that the zeropoints need
    # an exposure-time factor, so add that in.
    exptime = prim_header['EXPTIME']
    zeropoint += 2.5 * np.log10(exptime)

    # Read the PsfEx model file
    psf = PixelizedPsfEx(psfexfn)
    # Instantiate a constant pixelized PSF at the image center
    # (of the subimage)
    x0, y0 = subimage_offset
    psf = psf.constantPsfAt(x0 + W / 2., y0 + H / 2.)

    # Load the WCS model from the header
    # We convert from the RA---TPV type to RA---SIP
    header = fitsio.read_header(imagefn, ext=1)
    wcsobj = wcs_pv2sip_hdr(header, stepsize=10)

    # We'll just use a rough sky estimate...
    skyval = np.median(image)

    # Create the Tractor Image (tim).
    tim = Image(data=image,
                invvar=invvar,
                psf=psf,
                wcs=ConstantFitsWcs(wcsobj),
                sky=ConstantSky(skyval),
                photocal=LinearPhotoCal(
                    NanoMaggies.zeropointToScale(zeropoint), band=band))

    # Read the official DECaLS DR3 catalog -- it has only two sources in this subimage.
    catalog = fits_table(catfn)
    print('Read', len(catalog), 'sources')
    print('Source types:', catalog.type)

    # Create Tractor sources corresponding to these two catalog
    # entries.

    # In DECaLS, the "SIMP" type is a round Exponential galaxy with a
    # fixed 0.45" radius, but we'll treat it as a general Exp galaxy.

    sources = []
    for c in catalog:
        # Create a "position" object given the catalog RA,Dec
        position = RaDecPos(c.ra, c.dec)
        # Create a "brightness" object; in the catalog, the fluxes are
        # stored in a [ugrizY] array, so pull out the right index
        band_index = 'ugrizY'.index(band)
        flux = c.decam_flux[band_index]
        brightness = NanoMaggies(**{band: flux})

        # Depending on the source classification in the catalog, pull
        # out different fields for the galaxy shape, and for the
        # galaxy type.  The DECaLS catalogs, conveniently, store
        # galaxy shapes as (radius, e1, e2) ellipses.
        if c.type.strip() == 'DEV':
            shape = EllipseE(c.shapedev_r, c.shapedev_e1, c.shapedev_e2)
            galclass = DevGalaxy
        elif c.type.strip() == 'SIMP':
            shape = EllipseE(c.shapeexp_r, c.shapeexp_e1, c.shapeexp_e2)
            galclass = ExpGalaxy
        else:
            assert (False)
        # Create the tractor galaxy object
        source = galclass(position, brightness, shape)
        print('Created', source)
        sources.append(source)

    # Create the Tractor object -- a list of tractor Images and a list of tractor sources.
    tractor = Tractor([tim], sources)

    # Render the initial model image.
    print('Getting initial model...')
    mod = tractor.getModelImage(0)
    make_plot(tim, mod, 'Initial Scene', 'mod0.png')

    # Instantiate a new source at the location of the unmodelled peak.
    print('Adding new source...')
    # Find the peak very naively...
    ipeak = np.argmax((image - mod) * tim.inverr)
    iy, ix = np.unravel_index(ipeak, tim.shape)
    print('Residual peak at', ix, iy)
    # Compute the RA,Dec location of the peak...
    radec = tim.getWcs().pixelToPosition(ix, iy)
    print('RA,Dec', radec)

    # Try modelling it as a point source.
    # We'll initialize the brightness arbitrarily to 1 nanomaggy (= mag 22.5)
    brightness = NanoMaggies(**{band: 1.})
    source = PointSource(radec, brightness)

    # Add it to the catalog!
    tractor.catalog.append(source)

    # Render the new model image with this source added.
    mod = tractor.getModelImage(0)
    make_plot(tim, mod, 'New Source (Before Fit)', 'mod1.png')

    print('Fitting new source...')
    # Now we're going to fit for the properties of the new source we
    # added.
    # We don't want to fit for any of the image calibration properties:
    tractor.freezeParam('images')
    # And we don't (yet) want to fit the existing sources.  The new
    # source is index number 2, so freeze everything else in the catalog.
    tractor.catalog.freezeAllBut(2)

    print('Fitting parameters:')
    tractor.printThawedParams()

    # Do the actual optimization:
    tractor.optimize_loop()

    mod = tractor.getModelImage(0)
    make_plot(tim, mod, 'New Source Fit', 'mod2.png')

    print('Fitting sources simultaneously...')
    # Now let's unfreeze all the sources and fit them simultaneously.
    tractor.catalog.thawAllParams()
    tractor.printThawedParams()

    tractor.optimize_loop()

    mod = tractor.getModelImage(0)
    make_plot(tim, mod, 'Simultaneous Fit', 'mod3.png')
Ejemplo n.º 3
0
def one_brick(X):
    (ibrick, brick) = X
    bands = ['g', 'r', 'z']

    print('Brick', brick.brickname)
    wcs = wcs_for_brick(brick, W=94, H=94, pixscale=10.)
    BH, BW = wcs.shape
    targetrd = np.array([
        wcs.pixelxy2radec(x, y)
        for x, y in [(1, 1), (BW, 1), (BW, BH), (1, BH), (1, 1)]
    ])
    survey = LegacySurveyData()
    C = survey.ccds_touching_wcs(wcs)
    if C is None:
        print('No CCDs touching brick')
        return None
    I = np.flatnonzero(C.ccd_cuts == 0)
    if len(I) == 0:
        print('No good CCDs touching brick')
        return None
    C.cut(I)
    print(len(C), 'CCDs touching brick')

    depths = {}
    for band in bands:
        d = np.zeros((BH, BW), np.float32)
        depths[band] = d

    npix = dict([(band, 0) for band in bands])
    nexps = dict([(band, 0) for band in bands])

    # survey.get_approx_wcs(ccd)
    for ccd in C:
        #im = survey.get_image_object(ccd)
        awcs = survey.get_approx_wcs(ccd)

        imh, imw = ccd.height, ccd.width
        x0, y0 = 0, 0
        x1 = x0 + imw
        y1 = y0 + imh
        imgpoly = [(1, 1), (1, imh), (imw, imh), (imw, 1)]
        ok, tx, ty = awcs.radec2pixelxy(targetrd[:-1, 0], targetrd[:-1, 1])
        tpoly = list(zip(tx, ty))
        clip = clip_polygon(imgpoly, tpoly)
        clip = np.array(clip)
        if len(clip) == 0:
            continue
        x0, y0 = np.floor(clip.min(axis=0)).astype(int)
        x1, y1 = np.ceil(clip.max(axis=0)).astype(int)
        #slc = slice(y0,y1+1), slice(x0,x1+1)
        awcs = awcs.get_subimage(x0, y0, x1 - x0, y1 - y0)
        ah, aw = awcs.shape

        #print('Image', ccd.expnum, ccd.ccdname, ccd.filter, 'overlap', x0,x1, y0,y1, '->', (1+x1-x0),'x',(1+y1-y0))

        # Find bbox in brick space
        r, d = awcs.pixelxy2radec([1, 1, aw, aw], [1, ah, ah, 1])
        ok, bx, by = wcs.radec2pixelxy(r, d)
        bx0 = np.clip(np.round(bx.min()).astype(int) - 1, 0, BW - 1)
        bx1 = np.clip(np.round(bx.max()).astype(int) - 1, 0, BW - 1)
        by0 = np.clip(np.round(by.min()).astype(int) - 1, 0, BH - 1)
        by1 = np.clip(np.round(by.max()).astype(int) - 1, 0, BH - 1)

        #print('Brick', bx0,bx1,by0,by1)

        band = ccd.filter[0]
        assert (band in bands)

        ccdzpt = ccd.ccdzpt + 2.5 * np.log10(ccd.exptime)

        psf_sigma = ccd.fwhm / 2.35
        psfnorm = 1. / (2. * np.sqrt(np.pi) * psf_sigma)
        orig_zpscale = zpscale = NanoMaggies.zeropointToScale(ccdzpt)
        sig1 = ccd.sig1 / orig_zpscale
        detsig1 = sig1 / psfnorm

        # print('Image', ccd.expnum, ccd.ccdname, ccd.filter,
        #       'PSF depth', -2.5 * (np.log10(5.*detsig1) - 9), 'exptime', ccd.exptime,
        #       'sig1', ccd.sig1, 'zpt', ccd.ccdzpt, 'fwhm', ccd.fwhm,
        #       'filename', ccd.image_filename.strip())

        depths[band][by0:by1 + 1, bx0:bx1 + 1] += (1. / detsig1**2)
        npix[band] += (y1 + 1 - y0) * (x1 + 1 - x0)
        nexps[band] += 1

    for band in bands:
        det = np.median(depths[band])
        # compute stats for 5-sigma detection
        with np.errstate(divide='ignore'):
            depth = 5. / np.sqrt(det)
        # that's flux in nanomaggies -- convert to mag
        depth = -2.5 * (np.log10(depth) - 9)
        if not np.isfinite(depth):
            depth = 0.

        depths[band] = depth
        #bricks.get('psfdepth_' + band)[ibrick] = depth
        print(brick.brickname, 'median PSF depth', band, ':', depth, 'npix',
              npix[band], 'nexp', nexps[band])
        #'npix', bricks.get('npix_'+band)[ibrick],
        #'nexp', bricks.get('nexp_'+band)[ibrick])

    return (npix, nexps, depths)
Ejemplo n.º 4
0
                coimg[Yo, Xo] += wt * img[Yi, Xi]
                cowt[Yo, Xo] += wt

                x0 = min(Xi)
                x1 = max(Xi)
                y0 = min(Yi)
                y1 = max(Yi)
                subwcs = gwcs.get_subimage(x0, y0, x1 - x0 + 1, y1 - y0 + 1)
                twcs = ConstantFitsWcs(subwcs)
                timg = img[y0:y1 + 1, x0:x1 + 1]
                tie = np.ones_like(timg)  ## HACK!
                #hdr = fitsio.read_header(fn)
                #zp = hdr['
                zps = dict(n=20.08, f=18.82)
                zp = zps[band]
                photocal = LinearPhotoCal(NanoMaggies.zeropointToScale(zp),
                                          band=band)
                tsky = ConstantSky(0.)

                # HACK -- circular Gaussian PSF of fixed size...
                # in arcsec
                #fwhms = dict(NUV=6.0, FUV=6.0)
                # -> sigma in pixels
                #sig = fwhms[band] / 2.35 / twcs.pixel_scale()
                sig = 6.0 / 2.35 / twcs.pixel_scale()
                tpsf = NCircularGaussianPSF([sig], [1.])

                tim = Image(data=timg,
                            inverr=tie,
                            psf=tpsf,
                            wcs=twcs,
Ejemplo n.º 5
0
def galex_coadds(onegal,
                 galaxy=None,
                 radius_mosaic=30,
                 radius_mask=None,
                 pixscale=1.5,
                 ref_pixscale=0.262,
                 output_dir=None,
                 galex_dir=None,
                 log=None,
                 centrals=True,
                 verbose=False):
    '''Generate custom GALEX cutouts.
    
    radius_mosaic and radius_mask in arcsec
    
    pixscale: GALEX pixel scale in arcsec/pixel.

    '''
    import fitsio
    import matplotlib.pyplot as plt

    from astrometry.libkd.spherematch import match_radec
    from astrometry.util.resample import resample_with_wcs, OverlapError
    from tractor import (Tractor, NanoMaggies, Image, LinearPhotoCal,
                         NCircularGaussianPSF, ConstantFitsWcs, ConstantSky)

    from legacypipe.survey import imsave_jpeg
    from legacypipe.catalog import read_fits_catalog

    if galaxy is None:
        galaxy = 'galaxy'

    if galex_dir is None:
        galex_dir = os.environ.get('GALEX_DIR')

    if output_dir is None:
        output_dir = '.'

    if radius_mask is None:
        radius_mask = radius_mosaic
        radius_search = 5.0  # [arcsec]
    else:
        radius_search = radius_mask

    W = H = np.ceil(2 * radius_mosaic / pixscale).astype('int')  # [pixels]
    targetwcs = Tan(onegal['RA'], onegal['DEC'], (W + 1) / 2.0, (H + 1) / 2.0,
                    -pixscale / 3600.0, 0.0, 0.0, pixscale / 3600.0, float(W),
                    float(H))

    # Read the custom Tractor catalog
    tractorfile = os.path.join(output_dir, '{}-tractor.fits'.format(galaxy))
    if not os.path.isfile(tractorfile):
        print('Missing Tractor catalog {}'.format(tractorfile))
        return 0

    cat = fits_table(tractorfile)
    print('Read {} sources from {}'.format(len(cat), tractorfile),
          flush=True,
          file=log)

    keep = np.ones(len(cat)).astype(bool)
    if centrals:
        # Find the large central galaxy and mask out (ignore) all the models
        # which are within its elliptical mask.

        # This algorithm will have to change for mosaics not centered on large
        # galaxies, e.g., in galaxy groups.
        m1, m2, d12 = match_radec(cat.ra,
                                  cat.dec,
                                  onegal['RA'],
                                  onegal['DEC'],
                                  radius_search / 3600.0,
                                  nearest=False)
        if len(m1) == 0:
            print('No central galaxies found at the central coordinates!',
                  flush=True,
                  file=log)
        else:
            pixfactor = ref_pixscale / pixscale  # shift the optical Tractor positions
            for mm in m1:
                morphtype = cat.type[mm].strip()
                if morphtype == 'EXP' or morphtype == 'COMP':
                    e1, e2, r50 = cat.shapeexp_e1[mm], cat.shapeexp_e2[
                        mm], cat.shapeexp_r[mm]  # [arcsec]
                elif morphtype == 'DEV' or morphtype == 'COMP':
                    e1, e2, r50 = cat.shapedev_e1[mm], cat.shapedev_e2[
                        mm], cat.shapedev_r[mm]  # [arcsec]
                else:
                    r50 = None

                if r50:
                    majoraxis = r50 * 5 / pixscale  # [pixels]
                    ba, phi = SGA.misc.convert_tractor_e1e2(e1, e2)
                    these = SGA.misc.ellipse_mask(W / 2, W / 2, majoraxis,
                                                  ba * majoraxis,
                                                  np.radians(phi),
                                                  cat.bx * pixfactor,
                                                  cat.by * pixfactor)
                    if np.sum(these) > 0:
                        #keep[these] = False
                        pass
                print('Hack!')
                keep[mm] = False

            #srcs = read_fits_catalog(cat)
            #_srcs = np.array(srcs)[~keep].tolist()
            #mod = SGA.misc.srcs2image(_srcs, ConstantFitsWcs(targetwcs), psf_sigma=3.0)
            #import matplotlib.pyplot as plt
            ##plt.imshow(mod, origin='lower') ; plt.savefig('junk.png')
            #plt.imshow(np.log10(mod), origin='lower') ; plt.savefig('junk.png')
            #pdb.set_trace()

    srcs = read_fits_catalog(cat)
    for src in srcs:
        src.freezeAllBut('brightness')
    #srcs_nocentral = np.array(srcs)[keep].tolist()

    # Find all overlapping GALEX tiles and then read the tims.
    galex_tiles = _read_galex_tiles(targetwcs,
                                    galex_dir,
                                    log=log,
                                    verbose=verbose)

    gbands = ['n', 'f']
    nicegbands = ['NUV', 'FUV']

    zps = dict(n=20.08, f=18.82)

    coimgs, comods, coresids, coimgs_central, comods_nocentral = [], [], [], [], []
    for niceband, band in zip(nicegbands, gbands):
        J = np.flatnonzero(galex_tiles.get('has_' + band))
        print(len(J), 'GALEX tiles have coverage in band', band)

        coimg = np.zeros((H, W), np.float32)
        comod = np.zeros((H, W), np.float32)
        cowt = np.zeros((H, W), np.float32)

        comod_nocentral = np.zeros((H, W), np.float32)

        for src in srcs:
            src.setBrightness(NanoMaggies(**{band: 1}))

        for j in J:
            brick = galex_tiles[j]
            fn = os.path.join(
                galex_dir, brick.tilename.strip(),
                '%s-%sd-intbgsub.fits.gz' % (brick.brickname, band))
            #print(fn)

            gwcs = Tan(*[
                float(f) for f in [
                    brick.crval1, brick.crval2, brick.crpix1, brick.crpix2,
                    brick.cdelt1, 0., 0., brick.cdelt2, 3840., 3840.
                ]
            ])
            img = fitsio.read(fn)
            #print('Read', img.shape)

            try:
                Yo, Xo, Yi, Xi, nil = resample_with_wcs(targetwcs, gwcs, [], 3)
            except OverlapError:
                continue

            K = np.flatnonzero(img[Yi, Xi] != 0.)
            if len(K) == 0:
                continue
            Yo, Xo, Yi, Xi = Yo[K], Xo[K], Yi[K], Xi[K]

            wt = brick.get(band + 'exptime')
            coimg[Yo, Xo] += wt * img[Yi, Xi]
            cowt[Yo, Xo] += wt

            x0, x1, y0, y1 = min(Xi), max(Xi), min(Yi), max(Yi)
            subwcs = gwcs.get_subimage(x0, y0, x1 - x0 + 1, y1 - y0 + 1)
            twcs = ConstantFitsWcs(subwcs)
            timg = img[y0:y1 + 1, x0:x1 + 1]

            tie = np.ones_like(timg)  ## HACK!
            #hdr = fitsio.read_header(fn)
            #zp = hdr['']
            zp = zps[band]
            photocal = LinearPhotoCal(NanoMaggies.zeropointToScale(zp),
                                      band=band)
            tsky = ConstantSky(0.0)

            # HACK -- circular Gaussian PSF of fixed size...
            # in arcsec
            #fwhms = dict(NUV=6.0, FUV=6.0)
            # -> sigma in pixels
            #sig = fwhms[band] / 2.35 / twcs.pixel_scale()
            sig = 6.0 / np.sqrt(8 * np.log(2)) / twcs.pixel_scale()
            tpsf = NCircularGaussianPSF([sig], [1.])

            tim = Image(data=timg,
                        inverr=tie,
                        psf=tpsf,
                        wcs=twcs,
                        sky=tsky,
                        photocal=photocal,
                        name='GALEX ' + band + brick.brickname)

            ## Build the model image with and without the central galaxy model.
            tractor = Tractor([tim], srcs)
            mod = tractor.getModelImage(0)
            tractor.freezeParam('images')
            tractor.optimize_forced_photometry(priors=False,
                                               shared_params=False)
            mod = tractor.getModelImage(0)

            srcs_nocentral = np.array(srcs)[keep].tolist()
            #srcs_nocentral = np.array(srcs)[nocentral].tolist()
            tractor_nocentral = Tractor([tim], srcs_nocentral)
            mod_nocentral = tractor_nocentral.getModelImage(0)

            comod[Yo, Xo] += wt * mod[Yi - y0, Xi - x0]
            comod_nocentral[Yo, Xo] += wt * mod_nocentral[Yi - y0, Xi - x0]

        coimg /= np.maximum(cowt, 1e-18)
        comod /= np.maximum(cowt, 1e-18)
        comod_nocentral /= np.maximum(cowt, 1e-18)

        coresid = coimg - comod

        # Subtract the model image which excludes the central (comod_nocentral)
        # from the data (coimg) to isolate the light of the central
        # (coimg_central).
        coimg_central = coimg - comod_nocentral

        coimgs.append(coimg)
        comods.append(comod)
        coresids.append(coresid)

        comods_nocentral.append(comod_nocentral)
        coimgs_central.append(coimg_central)

        # Write out the final images with and without the central, making sure
        # to apply the zeropoint to go from counts/s to AB nanomaggies.
        # https://asd.gsfc.nasa.gov/archive/galex/FAQ/counts_background.html
        for thisimg, imtype in zip((coimg, comod, comod_nocentral),
                                   ('image', 'model', 'model-nocentral')):
            fitsfile = os.path.join(
                output_dir, '{}-{}-{}.fits'.format(galaxy, imtype, niceband))
            if verbose:
                print('Writing {}'.format(fitsfile))
            fitsio.write(fitsfile,
                         thisimg * 10**(-0.4 * (zp - 22.5)),
                         clobber=True)

    # Build a color mosaic (but note that the images here are in units of
    # background-subtracted counts/s).

    #_galex_rgb = _galex_rgb_moustakas
    #_galex_rgb = _galex_rgb_dstn
    _galex_rgb = _galex_rgb_official

    for imgs, imtype in zip(
        (coimgs, comods, coresids, comods_nocentral, coimgs_central),
        ('image', 'model', 'resid', 'model-nocentral', 'image-central')):
        rgb = _galex_rgb(imgs)
        jpgfile = os.path.join(output_dir,
                               '{}-{}-FUVNUV.jpg'.format(galaxy, imtype))
        if verbose:
            print('Writing {}'.format(jpgfile))
        imsave_jpeg(jpgfile, rgb, origin='lower')

    return 1