Esempio n. 1
0
def resample_with_wcs(targetwcs,
                      wcs,
                      Limages=[],
                      L=3,
                      spline=True,
                      splineFallback=True,
                      splineStep=25,
                      splineMargin=12,
                      table=True,
                      cinterp=True,
                      intType=np.int32):
    '''
    Returns (Yo,Xo, Yi,Xi, ims)

    Use the results like:

    target[Yo,Xo] = nearest_neighbour[Yi,Xi]
    # or
    target[Yo,Xo] = ims[i]


    raises NoOverlapError if the target and input WCSes do not
    overlap.  Raises SmallOverlapError if they do not overlap "enough"
    (as described below).

    targetwcs, wcs: duck-typed WCS objects that must have:
       - properties "imagew", "imageh"
       - methods  "r,d = pixelxy2radec(x, y)"
       -          "ok,x,y = radec2pixelxy(ra, dec)"

    The WCS functions are expected to operate in FITS pixel-indexing.

    The WCS function must support 1-d, broadcasting, vectorized
    pixel<->radec calls.

    Limages: list of images to Lanczos-interpolate at the given Lanczos order.
    If empty, just returns nearest-neighbour indices.

    L: int, lanczos order

    spline: bool: use a spline interpolator to reduce the number of
    WCS calls.

    splineFallback: bool: the spline requires a certain amount of
    spatial overlap.  With splineFallback = True, fall back to
    non-spline version.  With splineFallback = False, just raises
    SmallOverlapError.

    splineStep: approximate grid size

    table: use Lanczos3 look-up table?

    intType: type to return for integer pixel coordinates.
    (however, Yi,Xi may still be returned as int32)
    '''
    ### DEBUG
    #ps = PlotSequence('resample')
    ps = None

    H, W = int(targetwcs.imageh), int(targetwcs.imagew)
    h, w = int(wcs.imageh), int(wcs.imagew)

    for im in Limages:
        assert (im.shape == (h, w))

    # First find the approximate bbox of the input image in
    # the target image so that we don't ask for way too
    # many out-of-bounds pixels...
    XY = []
    for x, y in [(0, 0), (w - 1, 0), (w - 1, h - 1), (0, h - 1)]:
        # [-2:]: handle ok,ra,dec or ra,dec
        ok, xw, yw = targetwcs.radec2pixelxy(
            *(wcs.pixelxy2radec(float(x + 1), float(y + 1))[-2:]))
        XY.append((xw - 1, yw - 1))
    XY = np.array(XY)

    x0, y0 = np.rint(XY.min(axis=0))
    x1, y1 = np.rint(XY.max(axis=0))

    if spline:
        # Now we build a spline that maps "target" pixels to "input" pixels
        margin = splineMargin
        step = splineStep
        xlo = max(0, x0 - margin)
        xhi = min(W - 1, x1 + margin)
        ylo = max(0, y0 - margin)
        yhi = min(H - 1, y1 + margin)
        if xlo > xhi or ylo > yhi:
            raise NoOverlapError()
        nx = int(np.ceil(float(xhi - xlo) / step)) + 1
        xx = np.linspace(xlo, xhi, nx)
        ny = int(np.ceil(float(yhi - ylo) / step)) + 1
        yy = np.linspace(ylo, yhi, ny)

        if ps:

            def expand_axes():
                M = 100
                ax = plt.axis()
                plt.axis([ax[0] - M, ax[1] + M, ax[2] - M, ax[3] + M])
                plt.axis('scaled')

            plt.clf()
            plt.plot(XY[:, 0], XY[:, 1], 'ro')
            plt.plot(xx, np.zeros_like(xx), 'b.')
            plt.plot(np.zeros_like(yy), yy, 'c.')
            plt.plot(xx, np.zeros_like(xx) + max(yy), 'b.')
            plt.plot(max(xx) + np.zeros_like(yy), yy, 'c.')
            plt.plot([0, W, W, 0, 0], [0, 0, H, H, 0], 'k-')
            plt.title('A: Target image: bbox')
            expand_axes()
            ps.savefig()

        if (len(xx) == 0) or (len(yy) == 0):
            raise NoOverlapError()

        if (len(xx) <= 3) or (len(yy) <= 3):
            #print 'Not enough overlap between input and target WCSes'
            if splineFallback:
                spline = False
            else:
                raise SmallOverlapError()

    if spline:
        # spline inputs  -- pixel coords in the 'target' image
        #    (xx, yy)
        # spline outputs -- pixel coords in the 'input' image
        #    (XX, YY)
        # We use vectorized radec <-> pixelxy functions here

        R = targetwcs.pixelxy2radec(xx[np.newaxis, :] + 1,
                                    yy[:, np.newaxis] + 1)
        if len(R) == 3:
            ok = R[0]
            assert (np.all(ok))
        ok, XX, YY = wcs.radec2pixelxy(*(R[-2:]))
        del R
        XX -= 1.
        YY -= 1.
        assert (np.all(ok))
        del ok

        if ps:
            plt.clf()
            plt.plot(Xo, Yo, 'b.')
            plt.plot([0, w, w, 0, 0], [0, 0, h, h, 0], 'k-')
            plt.title('B: Input image')
            expand_axes()
            ps.savefig()

        import scipy.interpolate as interp
        xspline = interp.RectBivariateSpline(xx, yy, XX.T)
        yspline = interp.RectBivariateSpline(xx, yy, YY.T)
        del XX
        del YY

    else:
        margin = 0

    # Now, build the full pixel grid (in the ouput image) we want to
    # interpolate...
    ixo = np.arange(max(0, x0 - margin),
                    min(W, x1 + margin + 1),
                    dtype=intType)
    iyo = np.arange(max(0, y0 - margin),
                    min(H, y1 + margin + 1),
                    dtype=intType)

    if len(ixo) == 0 or len(iyo) == 0:
        raise NoOverlapError()

    if spline:
        # And run the interpolator.
        # [xy]spline() does a meshgrid-like broadcast, so fxi,fyi have
        # shape n(iyo),n(ixo)
        #
        # f[xy]i: floating-point pixel coords in the input image
        fxi = xspline(ixo, iyo).T.astype(np.float32)
        fyi = yspline(ixo, iyo).T.astype(np.float32)

        if ps:
            plt.clf()
            plt.plot(ixo, np.zeros_like(ixo), 'r,')
            plt.plot(np.zeros_like(iyo), iyo, 'm,')
            plt.plot(ixo, max(iyo) + np.zeros_like(ixo), 'r,')
            plt.plot(max(ixo) + np.zeros_like(iyo), iyo, 'm,')
            plt.plot([0, W, W, 0, 0], [0, 0, H, H, 0], 'k-')
            plt.title('C: Target image; i*o')
            expand_axes()
            ps.savefig()
            plt.clf()
            plt.plot(fxi, fyi, 'r,')
            plt.plot([0, w, w, 0, 0], [0, 0, h, h, 0], 'k-')
            plt.title('D: Input image, f*i')
            expand_axes()
            ps.savefig()

    else:
        # Use 2-d broadcasting pixel <-> radec functions here.
        # This can be rather expensive, with lots of WCS calls!

        R = targetwcs.pixelxy2radec(ixo[np.newaxis, :] + 1.,
                                    iyo[:, np.newaxis] + 1.)
        if len(R) == 3:
            # ok,ra,dec
            R = R[1:]
        ok, fxi, fyi = wcs.radec2pixelxy(*R)
        assert (np.all(ok))
        del ok
        fxi -= 1.
        fyi -= 1.

    # i[xy]i: int coords in the input image.
    itype = intType
    if len(Limages) and cinterp:
        # the lanczos3_interpolate function below requires int32!
        itype = np.int32

    # (f + 0.5).astype(int) is often faster than round().astype(int) or rint!
    ixi = (fxi + 0.5).astype(itype)
    iyi = (fyi + 0.5).astype(itype)

    # Cut to in-bounds pixels.
    I, J = np.nonzero((ixi >= 0) * (ixi < w) * (iyi >= 0) * (iyi < h))
    ixi = ixi[I, J]
    iyi = iyi[I, J]
    fxi = fxi[I, J]
    fyi = fyi[I, J]

    # i[xy]o: int coords in the target image.
    # These were 1-d arrays that got broadcasted
    iyo = iyo[0] + I.astype(intType)
    ixo = ixo[0] + J.astype(intType)
    del I, J

    if spline and ps:
        plt.clf()
        plt.plot(ixo, iyo, 'r,')
        plt.plot([0, W, W, 0, 0], [0, 0, H, H, 0], 'k-')
        plt.title('E: Target image; i*o')
        expand_axes()
        ps.savefig()
        plt.clf()
        plt.plot(fxi, fyi, 'r,')
        plt.plot([0, w, w, 0, 0], [0, 0, h, h, 0], 'k-')
        plt.title('F: Input image, f*i')
        expand_axes()
        ps.savefig()

    assert (np.all(ixo >= 0))
    assert (np.all(iyo >= 0))
    assert (np.all(ixo < W))
    assert (np.all(iyo < H))

    assert (np.all(ixi >= 0))
    assert (np.all(iyi >= 0))
    assert (np.all(ixi < w))
    assert (np.all(iyi < h))

    if len(Limages):
        dx = (fxi - ixi).astype(np.float32)
        dy = (fyi - iyi).astype(np.float32)
        del fxi
        del fyi

        # Lanczos interpolation.
        # number of pixels
        nn = len(ixo)
        NL = 2 * L + 1
        # accumulators for each input image
        laccs = [np.zeros(nn, np.float32) for im in Limages]

        if cinterp:
            from astrometry.util.util import lanczos3_interpolate
            rtn = lanczos3_interpolate(
                ixi, iyi, dx, dy, laccs,
                [lim.astype(np.float32) for lim in Limages])
        else:
            _lanczos_interpolate(L,
                                 ixi,
                                 iyi,
                                 dx,
                                 dy,
                                 laccs,
                                 Limages,
                                 table=table)
        rims = laccs
    else:
        rims = []

    return (iyo, ixo, iyi, ixi, rims)
Esempio n. 2
0
        psfimg = psfex.instantiateAt(s.x, s.y)

        origpsfimgs.append(psfimg)

        if True:
            from astrometry.util.util import lanczos3_interpolate
            dx, dy = s.x - s.ix, s.y - s.iy
            #print 'dx,dy', dx,dy
            ph, pw = psfimg.shape
            ix, iy = np.meshgrid(np.arange(pw), np.arange(ph))
            ix = ix.ravel().astype(np.int32)
            iy = iy.ravel().astype(np.int32)
            nn = len(ix)
            laccs = [np.zeros(nn, np.float32)]
            rtn = lanczos3_interpolate(ix, iy,
                                       -dx + np.zeros(len(ix), np.float32),
                                       -dy + np.zeros(len(ix), np.float32),
                                       laccs, [psfimg.astype(np.float32)])
            psfimg = laccs[0].reshape(psfimg.shape)

        unitpsfimgs.append(psfimg)
        psfimg = psfimg * flux + sigoff
        psfimgs.append(psfimg)

        mx = maxes[i]
        #mx = psfimg.max()
        logmx = np.log10(mx)
        dimshow(np.log10(np.maximum(psfimg, mx * 1e-16)),
                vmin=0,
                vmax=logmx,
                ticks=False)
    ps.savefig()
Esempio n. 3
0
def make_coadds(tims, bands, targetwcs,
                mods=None, blobmods=None,
                xy=None, apertures=None, apxy=None,
                ngood=False, detmaps=False, psfsize=False,
                allmasks=True, anymasks=False,
                get_max=False, sbscale=True,
                psf_images=False,
                callback=None, callback_args=None,
                plots=False, ps=None,
                lanczos=True, mp=None,
                satur_val=10.):
    from astrometry.util.ttime import Time
    t0 = Time()

    if callback_args is None:
        callback_args = []

    class Duck(object):
        pass
    C = Duck()

    W = int(targetwcs.get_width())
    H = int(targetwcs.get_height())

    # always, for patching SATUR, etc pixels?
    unweighted=True

    C.coimgs = []
    # the pixelwise inverse-variances (weights) of the "coimgs".
    C.cowimgs = []
    if detmaps:
        C.galdetivs = []
        C.psfdetivs = []
    if mods is not None:
        C.comods = []
        C.coresids = []
    if blobmods is not None:
        C.coblobmods = []
        C.coblobresids = []
    if apertures is not None:
        C.AP = fits_table()
    if allmasks:
        C.allmasks = []
    if anymasks:
        C.anymasks = []
    if max:
        C.maximgs = []
    if psf_images:
        C.psf_imgs = []

    if xy:
        ix,iy = xy
        C.T = fits_table()
        C.T.nobs    = np.zeros((len(ix), len(bands)), np.int16)
        C.T.anymask = np.zeros((len(ix), len(bands)), np.int16)
        C.T.allmask = np.zeros((len(ix), len(bands)), np.int16)
        if psfsize:
            C.T.psfsize = np.zeros((len(ix), len(bands)), np.float32)
        if detmaps:
            C.T.psfdepth = np.zeros((len(ix), len(bands)), np.float32)
            C.T.galdepth = np.zeros((len(ix), len(bands)), np.float32)

    if lanczos:
        debug('Doing Lanczos resampling')

    for tim in tims:
        # surface-brightness correction
        tim.sbscale = (targetwcs.pixel_scale() / tim.subwcs.pixel_scale())**2

    # We create one iterator per band to do the tim resampling.  These all run in
    # parallel when multi-processing.
    imaps = []
    for band in bands:
        args = []
        for itim,tim in enumerate(tims):
            if tim.band != band:
                continue
            if mods is None:
                mo = None
            else:
                mo = mods[itim]
            if blobmods is None:
                bmo = None
            else:
                bmo = blobmods[itim]
            args.append((itim,tim,mo,bmo,lanczos,targetwcs,sbscale))
        if mp is not None:
            imaps.append(mp.imap_unordered(_resample_one, args))
        else:
            imaps.append(map(_resample_one, args))

    # Args for aperture photometry
    apargs = []

    if xy:
        # To save the memory of 2 x float64 maps, we instead do arg min/max maps

        # append a 0 to the list of mjds so that mjds[-1] gives 0.
        mjds = np.array([tim.time.toMjd() for tim in tims] + [0])
        mjd_argmins = np.empty((H,W), np.int16)
        mjd_argmaxs = np.empty((H,W), np.int16)
        mjd_argmins[:,:] = -1
        mjd_argmaxs[:,:] = -1

    if plots:
        allresids = []

    tinyw = 1e-30
    for iband,(band,timiter) in enumerate(zip(bands, imaps)):
        debug('Computing coadd for band', band)

        # coadded weight map (moo)
        cow    = np.zeros((H,W), np.float32)
        # coadded weighted image map
        cowimg = np.zeros((H,W), np.float32)

        kwargs = dict(cowimg=cowimg, cow=cow)

        if detmaps:
            # detection map inverse-variance (depth map)
            psfdetiv = np.zeros((H,W), np.float32)
            C.psfdetivs.append(psfdetiv)
            kwargs.update(psfdetiv=psfdetiv)
            # galaxy detection map inverse-variance (galdepth map)
            galdetiv = np.zeros((H,W), np.float32)
            C.galdetivs.append(galdetiv)
            kwargs.update(galdetiv=galdetiv)

        if mods is not None:
            # model image
            cowmod = np.zeros((H,W), np.float32)
            # chi-squared image
            cochi2 = np.zeros((H,W), np.float32)
            kwargs.update(cowmod=cowmod, cochi2=cochi2)

        if blobmods is not None:
            # model image
            cowblobmod = np.zeros((H,W), np.float32)
            kwargs.update(cowblobmod=cowblobmod)

        if unweighted:
            # unweighted image
            coimg  = np.zeros((H,W), np.float32)
            if mods is not None:
                # unweighted model
                comod  = np.zeros((H,W), np.float32)
            if blobmods is not None:
                coblobmod  = np.zeros((H,W), np.float32)
            # number of exposures
            con    = np.zeros((H,W), np.int16)
            # inverse-variance
            coiv   = np.zeros((H,W), np.float32)
            kwargs.update(coimg=coimg, coiv=coiv)

        # Note that we have 'congood' as well as 'nobs':
        # * 'congood' is used for the 'nexp' *image*.
        #   It counts the number of "good" (unmasked) exposures
        # * 'nobs' is used for the per-source measurements
        #   It counts the total number of exposures, including masked pixels
        #
        # (you want to know the number of observations within the
        # source footprint, not just the peak pixel which may be
        # saturated, etc.)

        if ngood:
            congood = np.zeros((H,W), np.int16)
            kwargs.update(congood=congood)

        if xy or allmasks or anymasks:
            # These match the type of the "DQ" images.
            # "any" mask
            ormask  = np.zeros((H,W), np.int16)
            # "all" mask
            andmask = np.empty((H,W), np.int16)
            from functools import reduce
            allbits = reduce(np.bitwise_or, DQ_BITS.values())
            andmask[:,:] = allbits
            kwargs.update(ormask=ormask, andmask=andmask)
        if xy:
            # number of observations
            nobs = np.zeros((H,W), np.int16)
            kwargs.update(nobs=nobs)

        if psfsize:
            psfsizemap = np.zeros((H,W), np.float32)
            # like "cow", but constant invvar per-CCD;
            # only required for psfsizemap
            flatcow = np.zeros((H,W), np.float32)
            kwargs.update(psfsize=psfsizemap)

        if max:
            maximg = np.zeros((H,W), np.float32)
            C.maximgs.append(maximg)

        if psf_images:
            psf_img = 0.

        for R in timiter:
            if R is None:
                continue
            itim,Yo,Xo,iv,im,mo,bmo,dq = R
            tim = tims[itim]

            if plots:
                _make_coadds_plots_1(im, band, mods, mo, iv, unweighted,
                                     dq, satur_val, allresids, ps, H, W,
                                     tim, Yo, Xo)
            # invvar-weighted image
            cowimg[Yo,Xo] += iv * im
            cow   [Yo,Xo] += iv

            goodpix = None
            if unweighted:
                if dq is None:
                    goodpix = 1
                else:
                    # include SATUR pixels if no other
                    # pixels exists
                    okbits = 0
                    for bitname in ['satur']:
                        okbits |= DQ_BITS[bitname]
                    brightpix = ((dq & okbits) != 0)
                    if satur_val is not None:
                        # HACK -- force SATUR pix to be bright
                        im[brightpix] = satur_val
                    # Include these pixels if none other exist??
                    for bitname in ['interp']: #, 'bleed']:
                        okbits |= DQ_BITS[bitname]
                    goodpix = ((dq & ~okbits) == 0)

                coimg[Yo,Xo] += goodpix * im
                con  [Yo,Xo] += goodpix
                coiv [Yo,Xo] += goodpix * 1./(tim.sig1 * tim.sbscale)**2  # ...ish

            if xy or allmasks or anymasks:
                if dq is not None:
                    ormask [Yo,Xo] |= dq
                    andmask[Yo,Xo] &= dq
            if xy:
                # raw exposure count
                nobs[Yo,Xo] += 1
                # mjd_min/max
                update = np.logical_or(mjd_argmins[Yo,Xo] == -1,
                                       (mjd_argmins[Yo,Xo] > -1) *
                                       (mjds[itim] < mjds[mjd_argmins[Yo,Xo]]))
                mjd_argmins[Yo[update],Xo[update]] = itim
                update = np.logical_or(mjd_argmaxs[Yo,Xo] == -1,
                                       (mjd_argmaxs[Yo,Xo] > -1) *
                                       (mjds[itim] > mjds[mjd_argmaxs[Yo,Xo]]))
                mjd_argmaxs[Yo[update],Xo[update]] = itim
                del update

            if psfsize:
                # psfnorm is in units of 1/pixels.
                # (eg, psfnorm for a gaussian is 1./(2.*sqrt(pi) * psf_sigma) )
                # Neff is in pixels**2
                neff = 1./tim.psfnorm**2
                # Narcsec is in arcsec**2
                narcsec = neff * tim.wcs.pixel_scale()**2
                # Make smooth maps -- don't ignore CRs, saturated pix, etc
                iv1 = 1./tim.sig1**2
                psfsizemap[Yo,Xo] += iv1 * (1. / narcsec)
                flatcow   [Yo,Xo] += iv1

            if psf_images:
                from astrometry.util.util import lanczos3_interpolate
                h,w = tim.shape
                patch = tim.psf.getPointSourcePatch(w//2, h//2).patch
                patch /= np.sum(patch)
                # In case the tim and coadd have different pixel scales,
                # resample the PSF stamp.
                ph,pw = patch.shape
                pscale = tim.imobj.pixscale / targetwcs.pixel_scale()
                coph = int(np.ceil(ph * pscale))
                copw = int(np.ceil(pw * pscale))
                coph = 2 * (coph//2) + 1
                copw = 2 * (copw//2) + 1
                # want input image pixel coords that change by 1/pscale
                # and are centered on pw//2, ph//2
                cox = np.arange(copw) * 1./pscale
                cox += pw//2 - cox[copw//2]
                coy = np.arange(coph) * 1./pscale
                coy += ph//2 - coy[coph//2]
                fx,fy = np.meshgrid(cox,coy)
                fx = fx.ravel()
                fy = fy.ravel()
                ix = (fx + 0.5).astype(np.int32)
                iy = (fy + 0.5).astype(np.int32)
                dx = (fx - ix).astype(np.float32)
                dy = (fy - iy).astype(np.float32)
                copsf = np.zeros(coph*copw, np.float32)
                rtn = lanczos3_interpolate(ix, iy, dx, dy, [copsf], [patch])
                assert(rtn == 0)
                copsf = copsf.reshape((coph,copw))
                copsf /= copsf.sum()
                if plots:
                    _make_coadds_plots_2(patch, copsf, psf_img, tim, band, ps)

                psf_img += copsf / tim.sig1**2

            if detmaps:
                # point-source depth
                detsig1 = tim.sig1 / tim.psfnorm
                psfdetiv[Yo,Xo] += (iv > 0) * (1. / detsig1**2)
                # Galaxy detection map
                gdetsig1 = tim.sig1 / tim.galnorm
                galdetiv[Yo,Xo] += (iv > 0) * (1. / gdetsig1**2)

            if ngood:
                congood[Yo,Xo] += (iv > 0)

            if mods is not None:
                # straight-up
                comod[Yo,Xo] += goodpix * mo
                # invvar-weighted
                cowmod[Yo,Xo] += iv * mo
                # chi-squared
                cochi2[Yo,Xo] += iv * (im - mo)**2
                del mo

            if blobmods is not None:
                # straight-up
                coblobmod[Yo,Xo] += goodpix * bmo
                # invvar-weighted
                cowblobmod[Yo,Xo] += iv * bmo
                del bmo
            del goodpix

            if max:
                maximg[Yo,Xo] = np.maximum(maximg[Yo,Xo], im * (iv>0))

            del Yo,Xo,im,iv
            # END of loop over tims
        # Per-band:
        cowimg /= np.maximum(cow, tinyw)
        C.coimgs.append(cowimg)
        C.cowimgs.append(cow)
        if mods is not None:
            cowmod  /= np.maximum(cow, tinyw)
            C.comods.append(cowmod)
            coresid = cowimg - cowmod
            coresid[cow == 0] = 0.
            C.coresids.append(coresid)

        if blobmods is not None:
            cowblobmod  /= np.maximum(cow, tinyw)
            C.coblobmods.append(cowblobmod)
            coblobresid = cowimg - cowblobmod
            coblobresid[cow == 0] = 0.
            C.coblobresids.append(coblobresid)

        if allmasks:
            C.allmasks.append(andmask)
        if anymasks:
            C.anymasks.append(ormask)

        if psf_images:
            C.psf_imgs.append(psf_img / np.sum(psf_img))

        if unweighted:
            coimg  /= np.maximum(con, 1)
            del con

            if plots:
                _make_coadds_plots_3(cowimg, cow, coimg, band, ps)

            cowimg[cow == 0] = coimg[cow == 0]
            if mods is not None:
                cowmod[cow == 0] = comod[cow == 0]
            if blobmods is not None:
                cowblobmod[cow == 0] = coblobmod[cow == 0]

        if xy:
            C.T.nobs   [:,iband] = nobs   [iy,ix]
            C.T.anymask[:,iband] = ormask [iy,ix]
            C.T.allmask[:,iband] = andmask[iy,ix]
            # unless there were no images there...
            C.T.allmask[nobs[iy,ix] == 0, iband] = 0
            if detmaps:
                C.T.psfdepth[:,iband] = psfdetiv[iy, ix]
                C.T.galdepth[:,iband] = galdetiv[iy, ix]

        if psfsize:
            # psfsizemap is accumulated in units of iv * (1 / arcsec**2)
            # take out the weighting
            psfsizemap /= np.maximum(flatcow, tinyw)
            # Correction factor to get back to equivalent of Gaussian sigma
            tosigma = 1./(2. * np.sqrt(np.pi))
            # Conversion factor to FWHM (2.35)
            tofwhm = 2. * np.sqrt(2. * np.log(2.))
            # Scale back to units of linear arcsec.
            with np.errstate(divide='ignore'):
                psfsizemap[:,:] = (1. / np.sqrt(psfsizemap)) * tosigma * tofwhm
            psfsizemap[flatcow == 0] = 0.
            if xy:
                C.T.psfsize[:,iband] = psfsizemap[iy,ix]

        if apertures is not None:
            # Aperture photometry
            # photutils.aperture_photometry: mask=True means IGNORE
            mask = (cow == 0)
            with np.errstate(divide='ignore'):
                imsigma = 1.0/np.sqrt(cow)
            imsigma[mask] = 0.

            for irad,rad in enumerate(apertures):
                apargs.append((irad, band, rad, cowimg, imsigma, mask,
                               True, apxy))
                if mods is not None:
                    apargs.append((irad, band, rad, coresid, None, None,
                                   False, apxy))
                if blobmods is not None:
                    apargs.append((irad, band, rad, coblobresid, None, None,
                                   False, apxy))

        if callback is not None:
            callback(band, *callback_args, **kwargs)
        # END of loop over bands

    t2 = Time()
    debug('coadds: images:', t2-t0)

    if plots:
        _make_coadds_plots_4(allresids, mods, ps)

    if xy is not None:
        C.T.mjd_min = mjds[mjd_argmins[iy,ix]]
        C.T.mjd_max = mjds[mjd_argmaxs[iy,ix]]
        del mjd_argmins
        del mjd_argmaxs

    if apertures is not None:
        # Aperture phot, in parallel
        if mp is not None:
            apresults = mp.map(_apphot_one, apargs)
        else:
            apresults = map(_apphot_one, apargs)
        del apargs
        apresults = iter(apresults)

        for iband,band in enumerate(bands):
            apimg = []
            apimgerr = []
            apmask = []
            if mods is not None:
                apres = []
            if blobmods is not None:
                apblobres = []
            for irad,rad in enumerate(apertures):
                (airad, aband, isimg, ap_img, ap_err, ap_mask) = next(apresults)
                assert(airad == irad)
                assert(aband == band)
                assert(isimg)
                apimg.append(ap_img)
                apimgerr.append(ap_err)
                apmask.append(ap_mask)

                if mods is not None:
                    (airad, aband, isimg, ap_img, ap_err, ap_mask) = next(apresults)
                    assert(airad == irad)
                    assert(aband == band)
                    assert(not isimg)
                    apres.append(ap_img)
                    assert(ap_err is None)
                    assert(ap_mask is None)

                if blobmods is not None:
                    (airad, aband, isimg, ap_img, ap_err, ap_mask) = next(apresults)
                    assert(airad == irad)
                    assert(aband == band)
                    assert(not isimg)
                    apblobres.append(ap_img)
                    assert(ap_err is None)
                    assert(ap_mask is None)

            ap = np.vstack(apimg).T
            ap[np.logical_not(np.isfinite(ap))] = 0.
            C.AP.set('apflux_img_%s' % band, ap)
            with np.errstate(divide='ignore'):
                ap = 1./(np.vstack(apimgerr).T)**2
            ap[np.logical_not(np.isfinite(ap))] = 0.
            C.AP.set('apflux_img_ivar_%s' % band, ap)
            ap = np.vstack(apmask).T
            ap[np.logical_not(np.isfinite(ap))] = 0.
            C.AP.set('apflux_masked_%s' % band, ap)
            if mods is not None:
                ap = np.vstack(apres).T
                ap[np.logical_not(np.isfinite(ap))] = 0.
                C.AP.set('apflux_resid_%s' % band, ap)
            if blobmods is not None:
                ap = np.vstack(apblobres).T
                ap[np.logical_not(np.isfinite(ap))] = 0.
                C.AP.set('apflux_blobresid_%s' % band, ap)

        t3 = Time()
        debug('coadds apphot:', t3-t2)

    return C
Esempio n. 4
0
def unwise_forcedphot(cat, tiles, band=1, roiradecbox=None,
                      use_ceres=True, ceres_block=8,
                      save_fits=False, get_models=False, ps=None,
                      psf_broadening=None,
                      pixelized_psf=False,
                      get_masks=None,
                      move_crpix=False,
                      modelsky_dir=None):
    '''
    Given a list of tractor sources *cat*
    and a list of unWISE tiles *tiles* (a fits_table with RA,Dec,coadd_id)
    runs forced photometry, returning a FITS table the same length as *cat*.

    *get_masks*: the WCS to resample mask bits into.
    '''
    from tractor import NanoMaggies, PointSource, Tractor, ExpGalaxy, DevGalaxy, FixedCompositeGalaxy

    if not pixelized_psf and psf_broadening is None:
        # PSF broadening in post-reactivation data, by band.
        # Newer version from Aaron's email to decam-chatter, 2018-06-14.
        broadening = { 1: 1.0405, 2: 1.0346, 3: None, 4: None }
        psf_broadening = broadening[band]

    if False:
        from astrometry.util.plotutils import PlotSequence
        ps = PlotSequence('wise-forced-w%i' % band)
    plots = (ps is not None)
    if plots:
        import pylab as plt
    
    wantims = (plots or save_fits or get_models)
    wanyband = 'w'
    if get_models:
        models = {}

    wband = 'w%i' % band

    fskeys = ['prochi2', 'pronpix', 'profracflux', 'proflux', 'npix',
              'pronexp']

    Nsrcs = len(cat)
    phot = fits_table()
    # Filled in based on unique tile overlap
    phot.wise_coadd_id = np.array(['        '] * Nsrcs)
    phot.set(wband + '_psfdepth', np.zeros(len(phot), np.float32))

    ra  = np.array([src.getPosition().ra  for src in cat])
    dec = np.array([src.getPosition().dec for src in cat])

    nexp = np.zeros(Nsrcs, np.int16)
    mjd  = np.zeros(Nsrcs, np.float64)
    central_flux = np.zeros(Nsrcs, np.float32)

    fitstats = {}
    tims = []

    if get_masks:
        mh,mw = get_masks.shape
        maskmap = np.zeros((mh,mw), np.uint32)
    
    for tile in tiles:
        print('Reading WISE tile', tile.coadd_id, 'band', band)

        tim = get_unwise_tractor_image(tile.unwise_dir, tile.coadd_id, band,
                                       bandname=wanyband, roiradecbox=roiradecbox)
        if tim is None:
            print('Actually, no overlap with tile', tile.coadd_id)
            continue

        if plots:
            sig1 = tim.sig1
            plt.clf()
            plt.imshow(tim.getImage(), interpolation='nearest', origin='lower',
                       cmap='gray', vmin=-3 * sig1, vmax=10 * sig1)
            plt.colorbar()
            tag = '%s W%i' % (tile.coadd_id, band)
            plt.title('%s: tim data' % tag)
            ps.savefig()

            plt.clf()
            plt.hist((tim.getImage() * tim.inverr)[tim.inverr > 0].ravel(),
                     range=(-5,10), bins=100)
            plt.xlabel('Per-pixel intensity (Sigma)')
            plt.title(tag)
            ps.savefig()

        if move_crpix and band in [1, 2]:
            realwcs = tim.wcs.wcs
            x,y = realwcs.crpix
            tile_crpix = tile.get('crpix_w%i' % band)
            dx = tile_crpix[0] - 1024.5
            dy = tile_crpix[1] - 1024.5
            realwcs.set_crpix(x+dx, y+dy)
            #print('CRPIX', x,y, 'shift by', dx,dy, 'to', realwcs.crpix)

        if modelsky_dir and band in [1, 2]:
            fn = os.path.join(modelsky_dir, '%s.%i.mod.fits' % (tile.coadd_id, band))
            if not os.path.exists(fn):
                raise RuntimeError('WARNING: does not exist:', fn)
            x0,x1,y0,y1 = tim.roi
            bg = fitsio.FITS(fn)[2][y0:y1, x0:x1]
            #print('Read background map:', bg.shape, bg.dtype, 'vs image', tim.shape)

            if plots:
                plt.clf()
                plt.subplot(1,2,1)
                plt.imshow(tim.getImage(), interpolation='nearest', origin='lower',
                           cmap='gray', vmin=-3 * sig1, vmax=5 * sig1)
                plt.subplot(1,2,2)
                plt.imshow(bg, interpolation='nearest', origin='lower',
                           cmap='gray', vmin=-3 * sig1, vmax=5 * sig1)
                tag = '%s W%i' % (tile.coadd_id, band)
                plt.suptitle(tag)
                ps.savefig()

                plt.clf()
                ha = dict(range=(-5,10), bins=100, histtype='step')
                plt.hist((tim.getImage() * tim.inverr)[tim.inverr > 0].ravel(),
                         color='b', label='Original', **ha)
                plt.hist(((tim.getImage()-bg) * tim.inverr)[tim.inverr > 0].ravel(),
                         color='g', label='Minus Background', **ha)
                plt.axvline(0, color='k', alpha=0.5)
                plt.xlabel('Per-pixel intensity (Sigma)')
                plt.legend()
                plt.title(tag + ': background')
                ps.savefig()

            # Actually subtract the background!
            tim.data -= bg

        # Floor the per-pixel variances
        if band in [1,2]:
            # in Vega nanomaggies per pixel
            floor_sigma = {1: 0.5, 2: 2.0}
            with np.errstate(divide='ignore'):
                new_ie = 1. / np.hypot(1./tim.inverr, floor_sigma[band])
            new_ie[tim.inverr == 0] = 0.

            if plots:
                plt.clf()
                plt.plot((1. / tim.inverr[tim.inverr>0]).ravel(), (1./new_ie[tim.inverr>0]).ravel(), 'b.')
                plt.title('unWISE per-pixel error: %s band %i' % (tile.coadd_id, band))
                plt.xlabel('original')
                plt.ylabel('floored')
                ps.savefig()

            tim.inverr = new_ie

        # Read mask file?
        if get_masks:
            from astrometry.util.resample import resample_with_wcs, OverlapError
            # unwise_dir can be a colon-separated list of paths
            tilemask = None
            for d in tile.unwise_dir.split(':'):
                fn = os.path.join(d, tile.coadd_id[:3], tile.coadd_id,
                                  'unwise-%s-msk.fits.gz' % tile.coadd_id)
                if os.path.exists(fn):
                    print('Reading unWISE mask file', fn)
                    x0,x1,y0,y1 = tim.roi
                    tilemask = fitsio.FITS(fn)[0][y0:y1,x0:x1]
                    break
            if tilemask is None:
                print('unWISE mask file for tile', tile.coadd_id, 'does not exist')
            else:
                try:
                    tanwcs = tim.wcs.wcs
                    assert(tanwcs.shape == tilemask.shape)
                    Yo,Xo,Yi,Xi,_ = resample_with_wcs(get_masks, tanwcs, intType=np.int16)
                    # Only deal with mask pixels that are set.
                    I, = np.nonzero(tilemask[Yi,Xi] > 0)
                    # Trim to unique area for this tile
                    rr,dd = get_masks.pixelxy2radec(Yo[I]+1, Xo[I]+1)
                    good = radec_in_unique_area(rr, dd, tile.ra1, tile.ra2, tile.dec1, tile.dec2)
                    I = I[good]
                    maskmap[Yo[I],Xo[I]] = tilemask[Yi[I], Xi[I]]
                except OverlapError:
                    # Shouldn't happen by this point
                    print('No overlap between WISE tile', tile.coadd_id, 'and brick')

        # The tiles have some overlap, so zero out pixels outside the
        # tile's unique area.
        th,tw = tim.shape
        xx,yy = np.meshgrid(np.arange(tw), np.arange(th))
        rr,dd = tim.wcs.wcs.pixelxy2radec(xx+1, yy+1)
        unique = radec_in_unique_area(rr, dd, tile.ra1, tile.ra2, tile.dec1, tile.dec2)
        #print(np.sum(unique), 'of', (th*tw), 'pixels in this tile are unique')
        tim.inverr[unique == False] = 0.
        del xx,yy,rr,dd,unique

        if plots:
            sig1 = tim.sig1
            plt.clf()
            plt.imshow(tim.getImage() * (tim.inverr > 0),
                       interpolation='nearest', origin='lower',
                       cmap='gray', vmin=-3 * sig1, vmax=10 * sig1)
            plt.colorbar()
            tag = '%s W%i' % (tile.coadd_id, band)
            plt.title('%s: tim data (unique)' % tag)
            ps.savefig()

        if pixelized_psf:
            import unwise_psf
            if (band == 1) or (band == 2):
                # we only have updated PSFs for W1 and W2
                psfimg = unwise_psf.get_unwise_psf(band, tile.coadd_id, 
                                                   modelname='neo4_unwisecat')
            else:
                psfimg = unwise_psf.get_unwise_psf(band, tile.coadd_id)

            if band == 4:
                # oversample (the unwise_psf models are at native W4 5.5"/pix,
                # while the unWISE coadds are made at 2.75"/pix.
                ph,pw = psfimg.shape
                subpsf = np.zeros((ph*2-1, pw*2-1), np.float32)
                from astrometry.util.util import lanczos3_interpolate
                xx,yy = np.meshgrid(np.arange(0., pw-0.51, 0.5, dtype=np.float32),
                                    np.arange(0., ph-0.51, 0.5, dtype=np.float32))
                xx = xx.ravel()
                yy = yy.ravel()
                ix = xx.astype(np.int32)
                iy = yy.astype(np.int32)
                dx = (xx - ix).astype(np.float32)
                dy = (yy - iy).astype(np.float32)
                psfimg = psfimg.astype(np.float32)
                rtn = lanczos3_interpolate(ix, iy, dx, dy, [subpsf.flat], [psfimg])

                if plots:
                    plt.clf()
                    plt.imshow(psfimg, interpolation='nearest', origin='lower')
                    plt.title('Original PSF model')
                    ps.savefig()
                    plt.clf()
                    plt.imshow(subpsf, interpolation='nearest', origin='lower')
                    plt.title('Subsampled PSF model')
                    ps.savefig()

                psfimg = subpsf
                del xx, yy, ix, iy, dx, dy

            from tractor.psf import PixelizedPSF
            psfimg /= psfimg.sum()
            fluxrescales = {1: 1.04, 2: 1.005, 3: 1.0, 4: 1.0}
            psfimg *= fluxrescales[band]
            tim.psf = PixelizedPSF(psfimg)

        if psf_broadening is not None and not pixelized_psf:
            # psf_broadening is a factor by which the PSF FWHMs
            # should be scaled; the PSF is a little wider
            # post-reactivation.
            psf = tim.getPsf()
            from tractor import GaussianMixturePSF
            if isinstance(psf, GaussianMixturePSF):
                #
                print('Broadening PSF: from', psf)
                p0 = psf.getParams()
                pnames = psf.getParamNames()
                p1 = [p * psf_broadening**2 if 'var' in name else p
                      for (p, name) in zip(p0, pnames)]
                psf.setParams(p1)
                print('Broadened PSF:', psf)
            else:
                print('WARNING: cannot apply psf_broadening to WISE PSF of type', type(psf))

        wcs = tim.wcs.wcs
        ok,x,y = wcs.radec2pixelxy(ra, dec)
        x = np.round(x - 1.).astype(int)
        y = np.round(y - 1.).astype(int)
        good = (x >= 0) * (x < tw) * (y >= 0) * (y < th)
        # Which sources are in this brick's unique area?
        usrc = radec_in_unique_area(ra, dec, tile.ra1, tile.ra2, tile.dec1, tile.dec2)
        I, = np.nonzero(good * usrc)

        nexp[I] = tim.nuims[y[I], x[I]]
        if hasattr(tim, 'mjdmin') and hasattr(tim, 'mjdmax'):
            mjd[I] = (tim.mjdmin + tim.mjdmax) / 2.
        phot.wise_coadd_id[I] = tile.coadd_id

        central_flux[I] = tim.getImage()[y[I], x[I]]
        del x,y,good,usrc

        # PSF norm for depth
        psf = tim.getPsf()
        h,w = tim.shape
        patch = psf.getPointSourcePatch(h//2, w//2).patch
        psfnorm = np.sqrt(np.sum(patch**2))
        # To handle zero-depth, we return 1/nanomaggies^2 units rather than mags.
        psfdepth = 1. / (tim.sig1 / psfnorm)**2
        phot.get(wband + '_psfdepth')[I] = psfdepth

        tim.tile = tile
        tims.append(tim)

    if plots:
        plt.clf()
        mn,mx = 0.1, 20000
        plt.hist(np.log10(np.clip(central_flux, mn, mx)), bins=100,
                 range=(np.log10(mn), np.log10(mx)))
        logt = np.arange(0, 5)
        plt.xticks(logt, ['%i' % i for i in 10.**logt])
        plt.title('Central fluxes (W%i)' % band)
        plt.axvline(np.log10(20000), color='k')
        plt.axvline(np.log10(1000), color='k')
        ps.savefig()

    # Eddie's non-secret recipe:
    #- central pixel <= 1000: 19x19 pix box size
    #- central pixel in 1000 - 20000: 59x59 box size
    #- central pixel > 20000 or saturated: 149x149 box size
    #- object near "bright star": 299x299 box size
    nbig = nmedium = nsmall = 0
    for src,cflux in zip(cat, central_flux):
        if cflux > 20000:
            R = 100
            nbig += 1
        elif cflux > 1000:
            R = 30
            nmedium += 1
        else:
            R = 15
            nsmall += 1
        if isinstance(src, PointSource):
            src.fixedRadius = R
        else:
            ### FIXME -- sizes for galaxies..... can we set PSF size separately?
            galrad = 0
            # RexGalaxy is a subclass of ExpGalaxy
            if isinstance(src, (ExpGalaxy, DevGalaxy)):
                galrad = src.shape.re
            elif isinstance(src, FixedCompositeGalaxy):
                galrad = max(src.shapeExp.re, src.shapeDev.re)
            pixscale = 2.75
            src.halfsize = int(np.hypot(R, galrad * 5 / pixscale))

    #print('Set WISE source sizes:', nbig, 'big', nmedium, 'medium', nsmall, 'small')

    minsb = 0.
    fitsky = False

    tractor = Tractor(tims, cat)
    if use_ceres:
        from tractor.ceres_optimizer import CeresOptimizer
        tractor.optimizer = CeresOptimizer(BW=ceres_block, BH=ceres_block)
    tractor.freezeParamsRecursive('*')
    tractor.thawPathsTo(wanyband)

    kwa = dict(fitstat_extras=[('pronexp', [tim.nims for tim in tims])])
    t0 = Time()

    R = tractor.optimize_forced_photometry(
        minsb=minsb, mindlnp=1., sky=fitsky, fitstats=True,
        variance=True, shared_params=False,
        wantims=wantims, **kwa)
    print('unWISE forced photometry took', Time() - t0)

    if use_ceres:
        term = R.ceres_status['termination']
        # Running out of memory can cause failure to converge
        # and term status = 2.
        # Fail completely in this case.
        if term != 0:
            print('Ceres termination status:', term)
            raise RuntimeError(
                'Ceres terminated with status %i' % term)

    if wantims:
        ims1 = R.ims1
    flux_invvars = R.IV
    if R.fitstats is not None:
        for k in fskeys:
            x = getattr(R.fitstats, k)
            fitstats[k] = np.array(x).astype(np.float32)

    if save_fits:
        for i,tim in enumerate(tims):
            tile = tim.tile
            (dat, mod, ie, chi, roi) = ims1[i]
            wcshdr = fitsio.FITSHDR()
            tim.wcs.wcs.add_to_header(wcshdr)
            tag = 'fit-%s-w%i' % (tile.coadd_id, band)
            fitsio.write('%s-data.fits' %
                         tag, dat, clobber=True, header=wcshdr)
            fitsio.write('%s-mod.fits' % tag,  mod,
                         clobber=True, header=wcshdr)
            fitsio.write('%s-chi.fits' % tag,  chi,
                         clobber=True, header=wcshdr)

    if plots:
        # Create models for just the brightest sources
        bright_cat = [src for src in cat
                      if src.getBrightness().getBand(wanyband) > 1000]
        print('Bright soures:', len(bright_cat))
        btr = Tractor(tims, bright_cat)
        for tim in tims:
            mod = btr.getModelImage(tim)
            tile = tim.tile
            tag = '%s W%i' % (tile.coadd_id, band)
            sig1 = tim.sig1
            plt.clf()
            plt.imshow(mod, interpolation='nearest', origin='lower',
                       cmap='gray', vmin=-3 * sig1, vmax=25 * sig1)
            plt.colorbar()
            plt.title('%s: bright-star models' % tag)
            ps.savefig()

    if get_models:
        for i,tim in enumerate(tims):
            tile = tim.tile
            (dat, mod, ie, chi, roi) = ims1[i]
            models[(tile.coadd_id, band)] = (mod, dat, ie, tim.roi, tim.wcs.wcs)

    if plots:
        for i,tim in enumerate(tims):
            tile = tim.tile
            tag = '%s W%i' % (tile.coadd_id, band)
            (dat, mod, ie, chi, roi) = ims1[i]
            sig1 = tim.sig1
            plt.clf()
            plt.imshow(dat, interpolation='nearest', origin='lower',
                       cmap='gray', vmin=-3 * sig1, vmax=25 * sig1)
            plt.colorbar()
            plt.title('%s: data' % tag)
            ps.savefig()
            plt.clf()
            plt.imshow(mod, interpolation='nearest', origin='lower',
                       cmap='gray', vmin=-3 * sig1, vmax=25 * sig1)
            plt.colorbar()
            plt.title('%s: model' % tag)
            ps.savefig()

            plt.clf()
            plt.imshow(chi, interpolation='nearest', origin='lower',
                       cmap='gray', vmin=-5, vmax=+5)
            plt.colorbar()
            plt.title('%s: chi' % tag)
            ps.savefig()


    nm = np.array([src.getBrightness().getBand(wanyband) for src in cat])
    nm_ivar = flux_invvars
    # Sources out of bounds, eg, never change from their default
    # (1-sigma or whatever) initial fluxes.  Zero them out instead.
    nm[nm_ivar == 0] = 0.

    phot.set(wband + '_nanomaggies', nm.astype(np.float32))
    phot.set(wband + '_nanomaggies_ivar', nm_ivar.astype(np.float32))
    dnm = np.zeros(len(nm_ivar), np.float32)
    okiv = (nm_ivar > 0)
    dnm[okiv] = (1. / np.sqrt(nm_ivar[okiv])).astype(np.float32)
    okflux = (nm > 0)
    mag = np.zeros(len(nm), np.float32)
    mag[okflux] = (NanoMaggies.nanomaggiesToMag(nm[okflux])
                   ).astype(np.float32)
    dmag = np.zeros(len(nm), np.float32)
    ok = (okiv * okflux)
    dmag[ok] = (np.abs((-2.5 / np.log(10.)) * dnm[ok] / nm[ok])
                ).astype(np.float32)
    mag[np.logical_not(okflux)] = np.nan
    dmag[np.logical_not(ok)] = np.nan

    phot.set(wband + '_mag', mag)
    phot.set(wband + '_mag_err', dmag)

    for k in fskeys:
        phot.set(wband + '_' + k, fitstats[k])
    phot.set(wband + '_nexp', nexp)
    if not np.all(mjd == 0):
        phot.set(wband + '_mjd', mjd)

    rtn = wphotduck()
    rtn.phot = phot
    rtn.models = None
    rtn.maskmap = None
    if get_models:
        rtn.models = models
    if get_masks:
        rtn.maskmap = maskmap
    return rtn
Esempio n. 5
0
def unwise_forcedphot(cat,
                      tiles,
                      band=1,
                      roiradecbox=None,
                      use_ceres=True,
                      ceres_block=8,
                      save_fits=False,
                      get_models=False,
                      ps=None,
                      psf_broadening=None,
                      pixelized_psf=False,
                      get_masks=None,
                      move_crpix=False,
                      modelsky_dir=None,
                      tag=None):
    '''
    Given a list of tractor sources *cat*
    and a list of unWISE tiles *tiles* (a fits_table with RA,Dec,coadd_id)
    runs forced photometry, returning a FITS table the same length as *cat*.

    *get_masks*: the WCS to resample mask bits into.
    '''
    from tractor import PointSource, Tractor, ExpGalaxy, DevGalaxy
    from tractor.sersic import SersicGalaxy

    if tag is None:
        tag = ''
    else:
        tag = tag + ': '
    if not pixelized_psf and psf_broadening is None:
        # PSF broadening in post-reactivation data, by band.
        # Newer version from Aaron's email to decam-chatter, 2018-06-14.
        broadening = {1: 1.0405, 2: 1.0346, 3: None, 4: None}
        psf_broadening = broadening[band]

    if False:
        from astrometry.util.plotutils import PlotSequence
        ps = PlotSequence('wise-forced-w%i' % band)
    plots = (ps is not None)
    if plots:
        import pylab as plt

    wantims = (plots or save_fits or get_models)
    wanyband = 'w'
    if get_models:
        models = []

    wband = 'w%i' % band

    Nsrcs = len(cat)
    phot = fits_table()
    # Filled in based on unique tile overlap
    phot.wise_coadd_id = np.array(['        '] * Nsrcs, dtype='U8')
    phot.wise_x = np.zeros(Nsrcs, np.float32)
    phot.wise_y = np.zeros(Nsrcs, np.float32)
    phot.set('psfdepth_%s' % wband, np.zeros(Nsrcs, np.float32))
    nexp = np.zeros(Nsrcs, np.int16)
    mjd = np.zeros(Nsrcs, np.float64)
    central_flux = np.zeros(Nsrcs, np.float32)

    ra = np.array([src.getPosition().ra for src in cat])
    dec = np.array([src.getPosition().dec for src in cat])

    fskeys = ['prochi2', 'profracflux']
    fitstats = {}

    if get_masks:
        mh, mw = get_masks.shape
        maskmap = np.zeros((mh, mw), np.uint32)

    tims = []
    for tile in tiles:
        info(tag + 'Reading WISE tile', tile.coadd_id, 'band', band)
        tim = get_unwise_tractor_image(tile.unwise_dir,
                                       tile.coadd_id,
                                       band,
                                       bandname=wanyband,
                                       roiradecbox=roiradecbox)
        if tim is None:
            debug('Actually, no overlap with WISE coadd tile', tile.coadd_id)
            continue

        if plots:
            sig1 = tim.sig1
            plt.clf()
            plt.imshow(tim.getImage(),
                       interpolation='nearest',
                       origin='lower',
                       cmap='gray',
                       vmin=-3 * sig1,
                       vmax=10 * sig1)
            plt.colorbar()
            tag = '%s W%i' % (tile.coadd_id, band)
            plt.title('%s: tim data' % tag)
            ps.savefig()
            plt.clf()
            plt.hist((tim.getImage() * tim.inverr)[tim.inverr > 0].ravel(),
                     range=(-5, 10),
                     bins=100)
            plt.xlabel('Per-pixel intensity (Sigma)')
            plt.title(tag)
            ps.savefig()

        if move_crpix and band in [1, 2]:
            realwcs = tim.wcs.wcs
            x, y = realwcs.crpix
            tile_crpix = tile.get('crpix_w%i' % band)
            dx = tile_crpix[0] - 1024.5
            dy = tile_crpix[1] - 1024.5
            realwcs.set_crpix(x + dx, y + dy)
            debug('unWISE', tile.coadd_id, 'band', band, 'CRPIX', x, y,
                  'shift by', dx, dy, 'to', realwcs.crpix)

        if modelsky_dir and band in [1, 2]:
            fn = os.path.join(modelsky_dir,
                              '%s.%i.mod.fits' % (tile.coadd_id, band))
            if not os.path.exists(fn):
                raise RuntimeError('WARNING: does not exist:', fn)
            x0, x1, y0, y1 = tim.roi
            bg = fitsio.FITS(fn)[2][y0:y1, x0:x1]
            assert (bg.shape == tim.shape)

            if plots:
                plt.clf()
                plt.subplot(1, 2, 1)
                plt.imshow(tim.getImage(),
                           interpolation='nearest',
                           origin='lower',
                           cmap='gray',
                           vmin=-3 * sig1,
                           vmax=5 * sig1)
                plt.subplot(1, 2, 2)
                plt.imshow(bg,
                           interpolation='nearest',
                           origin='lower',
                           cmap='gray',
                           vmin=-3 * sig1,
                           vmax=5 * sig1)
                tag = '%s W%i' % (tile.coadd_id, band)
                plt.suptitle(tag)
                ps.savefig()
                plt.clf()
                ha = dict(range=(-5, 10), bins=100, histtype='step')
                plt.hist((tim.getImage() * tim.inverr)[tim.inverr > 0].ravel(),
                         color='b',
                         label='Original',
                         **ha)
                plt.hist(((tim.getImage() - bg) *
                          tim.inverr)[tim.inverr > 0].ravel(),
                         color='g',
                         label='Minus Background',
                         **ha)
                plt.axvline(0, color='k', alpha=0.5)
                plt.xlabel('Per-pixel intensity (Sigma)')
                plt.legend()
                plt.title(tag + ': background')
                ps.savefig()

            # Actually subtract the background!
            tim.data -= bg

        # Floor the per-pixel variances,
        # and add Poisson contribution from sources
        if band in [1, 2]:
            # in Vega nanomaggies per pixel
            floor_sigma = {1: 0.5, 2: 2.0}
            poissons = {1: 0.15, 2: 0.3}
            with np.errstate(divide='ignore'):
                new_ie = 1. / np.sqrt(
                    (1. / tim.inverr)**2 + floor_sigma[band] +
                    poissons[band]**2 * np.maximum(0., tim.data))
            new_ie[tim.inverr == 0] = 0.

            if plots:
                plt.clf()
                plt.plot((1. / tim.inverr[tim.inverr > 0]).ravel(),
                         (1. / new_ie[tim.inverr > 0]).ravel(), 'b.')
                plt.title('unWISE per-pixel error: %s band %i' %
                          (tile.coadd_id, band))
                plt.xlabel('original')
                plt.ylabel('floored')
                ps.savefig()

            assert (np.all(np.isfinite(new_ie)))
            assert (np.all(new_ie >= 0.))
            tim.inverr = new_ie

            # Expand a 3-pixel radius around weight=0 (saturated) pixels
            # from Eddie via crowdsource
            # https://github.com/schlafly/crowdsource/blob/7069da3e7d9d3124be1cbbe1d21ffeb63fc36dcc/python/wise_proc.py#L74
            ## FIXME -- W3/W4 ??
            satlimit = 85000
            msat = ((tim.data > satlimit) | ((tim.nims == 0) &
                                             (tim.nuims > 1)))
            from scipy.ndimage.morphology import binary_dilation
            xx, yy = np.mgrid[-3:3 + 1, -3:3 + 1]
            dilate = xx**2 + yy**2 <= 3**2
            msat = binary_dilation(msat, dilate)
            nbefore = np.sum(tim.inverr == 0)
            tim.inverr[msat] = 0
            nafter = np.sum(tim.inverr == 0)
            debug('Masking an additional', (nafter - nbefore),
                  'near-saturated pixels in unWISE', tile.coadd_id, 'band',
                  band)

        # Read mask file?
        if get_masks:
            from astrometry.util.resample import resample_with_wcs, OverlapError
            # unwise_dir can be a colon-separated list of paths
            tilemask = None
            for d in tile.unwise_dir.split(':'):
                fn = os.path.join(d, tile.coadd_id[:3], tile.coadd_id,
                                  'unwise-%s-msk.fits.gz' % tile.coadd_id)
                if os.path.exists(fn):
                    debug('Reading unWISE mask file', fn)
                    x0, x1, y0, y1 = tim.roi
                    tilemask = fitsio.FITS(fn)[0][y0:y1, x0:x1]
                    break
            if tilemask is None:
                info('unWISE mask file for tile', tile.coadd_id,
                     'does not exist')
            else:
                try:
                    tanwcs = tim.wcs.wcs
                    assert (tanwcs.shape == tilemask.shape)
                    Yo, Xo, Yi, Xi, _ = resample_with_wcs(get_masks,
                                                          tanwcs,
                                                          intType=np.int16)
                    # Only deal with mask pixels that are set.
                    I, = np.nonzero(tilemask[Yi, Xi] > 0)
                    # Trim to unique area for this tile
                    rr, dd = get_masks.pixelxy2radec(Xo[I] + 1, Yo[I] + 1)
                    good = radec_in_unique_area(rr, dd, tile.ra1, tile.ra2,
                                                tile.dec1, tile.dec2)
                    I = I[good]
                    maskmap[Yo[I], Xo[I]] = tilemask[Yi[I], Xi[I]]
                except OverlapError:
                    # Shouldn't happen by this point
                    print('Warning: no overlap between WISE tile',
                          tile.coadd_id, 'and brick')

            if plots:
                plt.clf()
                plt.imshow(tilemask, interpolation='nearest', origin='lower')
                plt.title('Tile %s: mask' % tile.coadd_id)
                ps.savefig()
                plt.clf()
                plt.imshow(maskmap, interpolation='nearest', origin='lower')
                plt.title('Tile %s: accumulated maskmap' % tile.coadd_id)
                ps.savefig()

        # The tiles have some overlap, so zero out pixels outside the
        # tile's unique area.
        th, tw = tim.shape
        xx, yy = np.meshgrid(np.arange(tw), np.arange(th))
        rr, dd = tim.wcs.wcs.pixelxy2radec(xx + 1, yy + 1)
        unique = radec_in_unique_area(rr, dd, tile.ra1, tile.ra2, tile.dec1,
                                      tile.dec2)
        debug('Tile', tile.coadd_id, '- total of', np.sum(unique),
              'unique pixels out of', len(unique.flat), 'total pixels')
        if get_models:
            # Save the inverr before blanking out non-unique pixels, for making coadds with no gaps!
            # (actually, slightly more subtly, expand unique area by 1 pixel)
            from scipy.ndimage.morphology import binary_dilation
            du = binary_dilation(unique)
            tim.coadd_inverr = tim.inverr * du
        tim.inverr[unique == False] = 0.
        del xx, yy, rr, dd, unique

        if plots:
            sig1 = tim.sig1
            plt.clf()
            plt.imshow(tim.getImage() * (tim.inverr > 0),
                       interpolation='nearest',
                       origin='lower',
                       cmap='gray',
                       vmin=-3 * sig1,
                       vmax=10 * sig1)
            plt.colorbar()
            tag = '%s W%i' % (tile.coadd_id, band)
            plt.title('%s: tim data (unique)' % tag)
            ps.savefig()

        if pixelized_psf:
            from unwise_psf import unwise_psf
            if (band == 1) or (band == 2):
                # we only have updated PSFs for W1 and W2
                psfimg = unwise_psf.get_unwise_psf(band,
                                                   tile.coadd_id,
                                                   modelname='neo6_unwisecat')
            else:
                psfimg = unwise_psf.get_unwise_psf(band, tile.coadd_id)

            if band == 4:
                # oversample (the unwise_psf models are at native W4 5.5"/pix,
                # while the unWISE coadds are made at 2.75"/pix.
                ph, pw = psfimg.shape
                subpsf = np.zeros((ph * 2 - 1, pw * 2 - 1), np.float32)
                from astrometry.util.util import lanczos3_interpolate
                xx, yy = np.meshgrid(
                    np.arange(0., pw - 0.51, 0.5, dtype=np.float32),
                    np.arange(0., ph - 0.51, 0.5, dtype=np.float32))
                xx = xx.ravel()
                yy = yy.ravel()
                ix = xx.astype(np.int32)
                iy = yy.astype(np.int32)
                dx = (xx - ix).astype(np.float32)
                dy = (yy - iy).astype(np.float32)
                psfimg = psfimg.astype(np.float32)
                rtn = lanczos3_interpolate(ix, iy, dx, dy, [subpsf.flat],
                                           [psfimg])

                if plots:
                    plt.clf()
                    plt.imshow(psfimg, interpolation='nearest', origin='lower')
                    plt.title('Original PSF model')
                    ps.savefig()
                    plt.clf()
                    plt.imshow(subpsf, interpolation='nearest', origin='lower')
                    plt.title('Subsampled PSF model')
                    ps.savefig()

                psfimg = subpsf
                del xx, yy, ix, iy, dx, dy

            from tractor.psf import PixelizedPSF
            psfimg /= psfimg.sum()
            fluxrescales = {1: 1.04, 2: 1.005, 3: 1.0, 4: 1.0}
            psfimg *= fluxrescales[band]
            tim.psf = PixelizedPSF(psfimg)

        if psf_broadening is not None and not pixelized_psf:
            # psf_broadening is a factor by which the PSF FWHMs
            # should be scaled; the PSF is a little wider
            # post-reactivation.
            psf = tim.getPsf()
            from tractor import GaussianMixturePSF
            if isinstance(psf, GaussianMixturePSF):
                debug('Broadening PSF: from', psf)
                p0 = psf.getParams()
                pnames = psf.getParamNames()
                p1 = [
                    p * psf_broadening**2 if 'var' in name else p
                    for (p, name) in zip(p0, pnames)
                ]
                psf.setParams(p1)
                debug('Broadened PSF:', psf)
            else:
                print(
                    'WARNING: cannot apply psf_broadening to WISE PSF of type',
                    type(psf))

        wcs = tim.wcs.wcs
        _, fx, fy = wcs.radec2pixelxy(ra, dec)
        x = np.round(fx - 1.).astype(int)
        y = np.round(fy - 1.).astype(int)
        good = (x >= 0) * (x < tw) * (y >= 0) * (y < th)
        # Which sources are in this brick's unique area?
        usrc = radec_in_unique_area(ra, dec, tile.ra1, tile.ra2, tile.dec1,
                                    tile.dec2)
        I, = np.nonzero(good * usrc)

        nexp[I] = tim.nuims[y[I], x[I]]
        if hasattr(tim, 'mjdmin') and hasattr(tim, 'mjdmax'):
            mjd[I] = (tim.mjdmin + tim.mjdmax) / 2.
        phot.wise_coadd_id[I] = tile.coadd_id
        phot.wise_x[I] = fx[I] - 1.
        phot.wise_y[I] = fy[I] - 1.

        central_flux[I] = tim.getImage()[y[I], x[I]]
        del x, y, good, usrc

        # PSF norm for depth
        psf = tim.getPsf()
        h, w = tim.shape
        patch = psf.getPointSourcePatch(h // 2, w // 2).patch
        psfnorm = np.sqrt(np.sum(patch**2))
        # To handle zero-depth, we return 1/nanomaggies^2 units rather than mags.
        # In the small empty patches of the sky (eg W4 in 0922p702), we get sig1 = NaN
        if np.isfinite(tim.sig1):
            phot.get('psfdepth_%s' % wband)[I] = 1. / (tim.sig1 / psfnorm)**2

        tim.tile = tile
        tims.append(tim)

    if plots:
        plt.clf()
        mn, mx = 0.1, 20000
        plt.hist(np.log10(np.clip(central_flux, mn, mx)),
                 bins=100,
                 range=(np.log10(mn), np.log10(mx)))
        logt = np.arange(0, 5)
        plt.xticks(logt, ['%i' % i for i in 10.**logt])
        plt.title('Central fluxes (W%i)' % band)
        plt.axvline(np.log10(20000), color='k')
        plt.axvline(np.log10(1000), color='k')
        ps.savefig()

    # Eddie's non-secret recipe:
    #- central pixel <= 1000: 19x19 pix box size
    #- central pixel in 1000 - 20000: 59x59 box size
    #- central pixel > 20000 or saturated: 149x149 box size
    #- object near "bright star": 299x299 box size
    nbig = nmedium = nsmall = 0
    for src, cflux in zip(cat, central_flux):
        if cflux > 20000:
            R = 100
            nbig += 1
        elif cflux > 1000:
            R = 30
            nmedium += 1
        else:
            R = 15
            nsmall += 1
        if isinstance(src, PointSource):
            src.fixedRadius = R
        else:
            ### FIXME -- sizes for galaxies..... can we set PSF size separately?
            galrad = 0
            # RexGalaxy is a subclass of ExpGalaxy
            if isinstance(src, (ExpGalaxy, DevGalaxy, SersicGalaxy)):
                galrad = src.shape.re
            pixscale = 2.75
            src.halfsize = int(np.hypot(R, galrad * 5 / pixscale))
    debug('Set WISE source sizes:', nbig, 'big', nmedium, 'medium', nsmall,
          'small')

    tractor = Tractor(tims, cat)
    if use_ceres:
        from tractor.ceres_optimizer import CeresOptimizer
        tractor.optimizer = CeresOptimizer(BW=ceres_block, BH=ceres_block)
    tractor.freezeParamsRecursive('*')
    tractor.thawPathsTo(wanyband)

    t0 = Time()
    R = tractor.optimize_forced_photometry(fitstats=True,
                                           variance=True,
                                           shared_params=False,
                                           wantims=wantims)
    info(tag + 'unWISE forced photometry took', Time() - t0)

    if use_ceres:
        term = R.ceres_status['termination']
        # Running out of memory can cause failure to converge and term
        # status = 2.  Fail completely in this case.
        if term != 0:
            info(tag + 'Ceres termination status:', term)
            raise RuntimeError('Ceres terminated with status %i' % term)

    if wantims:
        ims1 = R.ims1
        # can happen if empty source list (we still want to generate coadds)
        if ims1 is None:
            ims1 = R.ims0

    flux_invvars = R.IV
    if R.fitstats is not None:
        for k in fskeys:
            x = getattr(R.fitstats, k)
            fitstats[k] = np.array(x).astype(np.float32)

    if save_fits:
        for i, tim in enumerate(tims):
            tile = tim.tile
            (dat, mod, _, chi, _) = ims1[i]
            wcshdr = fitsio.FITSHDR()
            tim.wcs.wcs.add_to_header(wcshdr)
            tag = 'fit-%s-w%i' % (tile.coadd_id, band)
            fitsio.write('%s-data.fits' % tag,
                         dat,
                         clobber=True,
                         header=wcshdr)
            fitsio.write('%s-mod.fits' % tag, mod, clobber=True, header=wcshdr)
            fitsio.write('%s-chi.fits' % tag, chi, clobber=True, header=wcshdr)

    if plots:
        # Create models for just the brightest sources
        bright_cat = [
            src for src in cat if src.getBrightness().getBand(wanyband) > 1000
        ]
        debug('Bright soures:', len(bright_cat))
        btr = Tractor(tims, bright_cat)
        for tim in tims:
            mod = btr.getModelImage(tim)
            tile = tim.tile
            tag = '%s W%i' % (tile.coadd_id, band)
            sig1 = tim.sig1
            plt.clf()
            plt.imshow(mod,
                       interpolation='nearest',
                       origin='lower',
                       cmap='gray',
                       vmin=-3 * sig1,
                       vmax=25 * sig1)
            plt.colorbar()
            plt.title('%s: bright-star models' % tag)
            ps.savefig()

    if get_models:
        for i, tim in enumerate(tims):
            tile = tim.tile
            (dat, mod, _, _, _) = ims1[i]
            models.append(
                (tile.coadd_id, band, tim.wcs.wcs, dat, mod, tim.coadd_inverr))

    if plots:
        for i, tim in enumerate(tims):
            tile = tim.tile
            tag = '%s W%i' % (tile.coadd_id, band)
            (dat, mod, _, chi, _) = ims1[i]
            sig1 = tim.sig1
            plt.clf()
            plt.imshow(dat,
                       interpolation='nearest',
                       origin='lower',
                       cmap='gray',
                       vmin=-3 * sig1,
                       vmax=25 * sig1)
            plt.colorbar()
            plt.title('%s: data' % tag)
            ps.savefig()
            plt.clf()
            plt.imshow(mod,
                       interpolation='nearest',
                       origin='lower',
                       cmap='gray',
                       vmin=-3 * sig1,
                       vmax=25 * sig1)
            plt.colorbar()
            plt.title('%s: model' % tag)
            ps.savefig()

            plt.clf()
            plt.imshow(chi,
                       interpolation='nearest',
                       origin='lower',
                       cmap='gray',
                       vmin=-5,
                       vmax=+5)
            plt.colorbar()
            plt.title('%s: chi' % tag)
            ps.savefig()

    nm = np.array([src.getBrightness().getBand(wanyband) for src in cat])
    nm_ivar = flux_invvars
    # Sources out of bounds, eg, never change from their initial
    # fluxes.  Zero them out instead.
    nm[nm_ivar == 0] = 0.

    phot.set('flux_%s' % wband, nm.astype(np.float32))
    phot.set('flux_ivar_%s' % wband, nm_ivar.astype(np.float32))
    for k in fskeys:
        phot.set(k + '_' + wband,
                 fitstats.get(k, np.zeros(len(phot), np.float32)))
    phot.set('nobs_%s' % wband, nexp)
    phot.set('mjd_%s' % wband, mjd)

    rtn = wphotduck()
    rtn.phot = phot
    rtn.models = None
    rtn.maskmap = None
    if get_models:
        rtn.models = models
    if get_masks:
        rtn.maskmap = maskmap
    return rtn
Esempio n. 6
0
        psfimg = psfex.instantiateAt(s.x, s.y)

        origpsfimgs.append(psfimg)
        
        if True:
            from astrometry.util.util import lanczos3_interpolate
            dx,dy = s.x - s.ix, s.y - s.iy
            #print 'dx,dy', dx,dy
            ph,pw = psfimg.shape
            ix,iy = np.meshgrid(np.arange(pw), np.arange(ph))
            ix = ix.ravel().astype(np.int32)
            iy = iy.ravel().astype(np.int32)
            nn = len(ix)
            laccs = [np.zeros(nn, np.float32)]
            rtn = lanczos3_interpolate(ix, iy,
                                       -dx+np.zeros(len(ix),np.float32),
                                       -dy+np.zeros(len(ix),np.float32),
                                       laccs, [psfimg.astype(np.float32)])
            psfimg = laccs[0].reshape(psfimg.shape)

        unitpsfimgs.append(psfimg)
        psfimg = psfimg * flux + sigoff
        psfimgs.append(psfimg)
        
        mx = maxes[i]
        #mx = psfimg.max()
        logmx = np.log10(mx)
        dimshow(np.log10(np.maximum(psfimg, mx*1e-16)), vmin=0, vmax=logmx,
                ticks=False)
    ps.savefig()

    # plt.clf()
Esempio n. 7
0
def resample_with_wcs(targetwcs, wcs, Limages=[], L=3, spline=True,
                      splineFallback=True,
                      splineStep=25,
                      splineMargin=12,
                      table=True,
                      cinterp=True,
                      intType=np.int32):
    '''
    Returns (Yo,Xo, Yi,Xi, ims)

    Use the results like:

    target[Yo,Xo] = nearest_neighbour[Yi,Xi]
    # or
    target[Yo,Xo] = ims[i]


    raises NoOverlapError if the target and input WCSes do not
    overlap.  Raises SmallOverlapError if they do not overlap "enough"
    (as described below).

    targetwcs, wcs: duck-typed WCS objects that must have:
       - properties "imagew", "imageh"
       - methods  "r,d = pixelxy2radec(x, y)"
       -          "ok,x,y = radec2pixelxy(ra, dec)"

    The WCS functions are expected to operate in FITS pixel-indexing.

    The WCS function must support 1-d, broadcasting, vectorized
    pixel<->radec calls.

    Limages: list of images to Lanczos-interpolate at the given Lanczos order.
    If empty, just returns nearest-neighbour indices.

    L: int, lanczos order

    spline: bool: use a spline interpolator to reduce the number of
    WCS calls.

    splineFallback: bool: the spline requires a certain amount of
    spatial overlap.  With splineFallback = True, fall back to
    non-spline version.  With splineFallback = False, just raises
    SmallOverlapError.

    splineStep: approximate grid size

    table: use Lanczos3 look-up table?

    intType: type to return for integer pixel coordinates.
    (however, Yi,Xi may still be returned as int32)
    '''
    ### DEBUG
    #ps = PlotSequence('resample')
    ps = None

    H,W = int(targetwcs.imageh), int(targetwcs.imagew)
    h,w = int(      wcs.imageh), int(      wcs.imagew)

    for im in Limages:
        assert(im.shape == (h,w))
    
    # First find the approximate bbox of the input image in
    # the target image so that we don't ask for way too
    # many out-of-bounds pixels...
    XY = []
    for x,y in [(0,0), (w-1,0), (w-1,h-1), (0, h-1)]:
        # [-2:]: handle ok,ra,dec or ra,dec
        ok,xw,yw = targetwcs.radec2pixelxy(
            *(wcs.pixelxy2radec(float(x + 1), float(y + 1))[-2:]))
        XY.append((xw - 1, yw - 1))
    XY = np.array(XY)

    x0,y0 = np.rint(XY.min(axis=0))
    x1,y1 = np.rint(XY.max(axis=0))

    if spline:
        # Now we build a spline that maps "target" pixels to "input" pixels
        margin = splineMargin
        step = splineStep
        xlo = max(0, x0-margin)
        xhi = min(W-1, x1+margin)
        ylo = max(0, y0-margin)
        yhi = min(H-1, y1+margin)
        if xlo > xhi or ylo > yhi:
            raise NoOverlapError()
        nx = int(np.ceil(float(xhi - xlo) / step)) + 1
        xx = np.linspace(xlo, xhi, nx)
        ny = int(np.ceil(float(yhi - ylo) / step)) + 1
        yy = np.linspace(ylo, yhi, ny)

        if ps:
            def expand_axes():
                M = 100
                ax = plt.axis()
                plt.axis([ax[0]-M, ax[1]+M, ax[2]-M, ax[3]+M])
                plt.axis('scaled')
            plt.clf()
            plt.plot(XY[:,0], XY[:,1], 'ro')
            plt.plot(xx, np.zeros_like(xx), 'b.')
            plt.plot(np.zeros_like(yy), yy, 'c.')
            plt.plot(xx, np.zeros_like(xx)+max(yy), 'b.')
            plt.plot(max(xx) + np.zeros_like(yy), yy, 'c.')
            plt.plot([0,W,W,0,0], [0,0,H,H,0], 'k-')
            plt.title('A: Target image: bbox')
            expand_axes()
            ps.savefig()

        if (len(xx) == 0) or (len(yy) == 0):
            raise NoOverlapError()

        if (len(xx) <= 3) or (len(yy) <= 3):
            #print 'Not enough overlap between input and target WCSes'
            if splineFallback:
                spline = False
            else:
                raise SmallOverlapError()

    if spline:
        # spline inputs  -- pixel coords in the 'target' image
        #    (xx, yy)
        # spline outputs -- pixel coords in the 'input' image
        #    (XX, YY)
        # We use vectorized radec <-> pixelxy functions here

        R = targetwcs.pixelxy2radec(xx[np.newaxis,:] + 1,
                                    yy[:,np.newaxis] + 1)
        if len(R) == 3:
            ok = R[0]
            assert(np.all(ok))
        ok,XX,YY = wcs.radec2pixelxy(*(R[-2:]))
        del R
        XX -= 1.
        YY -= 1.
        assert(np.all(ok))
        del ok
        
        if ps:
            plt.clf()
            plt.plot(Xo, Yo, 'b.')
            plt.plot([0,w,w,0,0], [0,0,h,h,0], 'k-')
            plt.title('B: Input image')
            expand_axes()
            ps.savefig()
    
        import scipy.interpolate as interp
        xspline = interp.RectBivariateSpline(xx, yy, XX.T)
        yspline = interp.RectBivariateSpline(xx, yy, YY.T)
        del XX
        del YY

    else:
        margin = 0

    # Now, build the full pixel grid (in the ouput image) we want to
    # interpolate...
    ixo = np.arange(max(0, x0-margin), min(W, x1+margin+1), dtype=intType)
    iyo = np.arange(max(0, y0-margin), min(H, y1+margin+1), dtype=intType)

    if len(ixo) == 0 or len(iyo) == 0:
        raise NoOverlapError()

    if spline:
        # And run the interpolator.
        # [xy]spline() does a meshgrid-like broadcast, so fxi,fyi have
        # shape n(iyo),n(ixo)
        #
        # f[xy]i: floating-point pixel coords in the input image
        fxi = xspline(ixo, iyo).T.astype(np.float32)
        fyi = yspline(ixo, iyo).T.astype(np.float32)

        if ps:
            plt.clf()
            plt.plot(ixo, np.zeros_like(ixo), 'r,')
            plt.plot(np.zeros_like(iyo), iyo, 'm,')
            plt.plot(ixo, max(iyo) + np.zeros_like(ixo), 'r,')
            plt.plot(max(ixo) + np.zeros_like(iyo), iyo, 'm,')
            plt.plot([0,W,W,0,0], [0,0,H,H,0], 'k-')
            plt.title('C: Target image; i*o')
            expand_axes()
            ps.savefig()
            plt.clf()
            plt.plot(fxi, fyi, 'r,')
            plt.plot([0,w,w,0,0], [0,0,h,h,0], 'k-')
            plt.title('D: Input image, f*i')
            expand_axes()
            ps.savefig()

    else:
        # Use 2-d broadcasting pixel <-> radec functions here.
        # This can be rather expensive, with lots of WCS calls!

        R = targetwcs.pixelxy2radec(ixo[np.newaxis,:] + 1.,
                                    iyo[:,np.newaxis] + 1.)
        if len(R) == 3:
            # ok,ra,dec
            R = R[1:]
        ok,fxi,fyi = wcs.radec2pixelxy(*R)
        assert(np.all(ok))
        del ok
        fxi -= 1.
        fyi -= 1.

    # i[xy]i: int coords in the input image.
    itype = intType
    if len(Limages) and cinterp:
        # the lanczos3_interpolate function below requires int32!
        itype = np.int32

    # (f + 0.5).astype(int) is often faster than round().astype(int) or rint!
    ixi = (fxi + 0.5).astype(itype)
    iyi = (fyi + 0.5).astype(itype)

    # Cut to in-bounds pixels.
    I,J = np.nonzero((ixi >= 0) * (ixi < w) * (iyi >= 0) * (iyi < h))
    ixi = ixi[I,J]
    iyi = iyi[I,J]
    fxi = fxi[I,J]
    fyi = fyi[I,J]

    # i[xy]o: int coords in the target image.
    # These were 1-d arrays that got broadcasted
    iyo = iyo[0] + I.astype(intType)
    ixo = ixo[0] + J.astype(intType)
    del I,J

    if spline and ps:
        plt.clf()
        plt.plot(ixo, iyo, 'r,')
        plt.plot([0,W,W,0,0], [0,0,H,H,0], 'k-')
        plt.title('E: Target image; i*o')
        expand_axes()
        ps.savefig()
        plt.clf()
        plt.plot(fxi, fyi, 'r,')
        plt.plot([0,w,w,0,0], [0,0,h,h,0], 'k-')
        plt.title('F: Input image, f*i')
        expand_axes()
        ps.savefig()

    assert(np.all(ixo >= 0))
    assert(np.all(iyo >= 0))
    assert(np.all(ixo < W))
    assert(np.all(iyo < H))

    assert(np.all(ixi >= 0))
    assert(np.all(iyi >= 0))
    assert(np.all(ixi < w))
    assert(np.all(iyi < h))

    if len(Limages):
        dx = (fxi - ixi).astype(np.float32)
        dy = (fyi - iyi).astype(np.float32)
        del fxi
        del fyi

        # Lanczos interpolation.
        # number of pixels
        nn = len(ixo)
        NL = 2*L+1
        # accumulators for each input image
        laccs = [np.zeros(nn, np.float32) for im in Limages]

        if cinterp:
            from astrometry.util.util import lanczos3_interpolate
            rtn = lanczos3_interpolate(ixi, iyi, dx, dy, laccs,
                                       [lim.astype(np.float32) for lim in Limages])
        else:
            _lanczos_interpolate(L, ixi, iyi, dx, dy, laccs, Limages, table=table)
        rims = laccs
    else:
        rims = []

    return (iyo,ixo, iyi,ixi, rims)