Exemplo n.º 1
0
def get_ps1_chip_image(filename, offset=(0, 0), npix=None):
    filename_base = filename[:-5]
    im = pyfits.getdata(filename_base + '.fits')
    wt = pyfits.getdata(filename_base + '.wt.fits')
    mk = pyfits.getdata(filename_base + '.mk.fits')

    psffile = filename_base[:-3] + '.psf'
    psffp = tempfile.NamedTemporaryFile()
    psffp.close()
    subprocess.call([
        "dannyVizPSF",
        str(im.shape[0]),
        str(im.shape[1]),
        str(im.shape[0] / 2),
        str(im.shape[1] / 2), "51", "51", psffile, psffp.name
    ])
    psfstamp = pyfits.getdata(psffp.name)

    smffn = get_smf_filename(os.path.basename(filename_base)[:11])
    im[mk != 0] = 0
    wt[mk != 0] = 0
    if npix is None:
        npix = im.shape
    im = im[offset[0]:offset[0] + npix[0], offset[1]:offset[1] + npix[1]]
    wt = wt[offset[0]:offset[0] + npix[0], offset[1]:offset[1] + npix[1]]
    mk = mk[offset[0]:offset[0] + npix[0], offset[1]:offset[1] + npix[1]]
    invvar = (1. / (wt + (wt == 0))) * (wt != 0)

    hsmf = pyfits.getheader(smffn)
    zp = hsmf['MAG_ZP']

    hchip = pyfits.getheader(filename_base + '.fits', 1)
    chip = hchip['FPPOS'].upper().strip()

    filterid = 'PS1_' + hsmf['filterid'][0]

    tpsf = tractor.GaussianMixturePSF.fromStamp(psfstamp, N=3)

    twcs = wcs_ps1(smffn, chip, offset=offset)
    tsky = tractor.ConstantSky(0)
    photocal = tractor.MagsPhotoCal(
        filterid, zp + 2.5 * numpy.log10(hchip['exptime']) + 0.5)

    tim = tractor.Image(data=im,
                        invvar=invvar,
                        psf=tpsf,
                        wcs=twcs,
                        photocal=photocal,
                        sky=tsky,
                        name='PS1 %s %s' % (filterid, filename_base))
    tim.zr = [-100, 100]
    tim.extent = [0, im.shape[0], 0, im.shape[1]]

    return tim
Exemplo n.º 2
0
def get_galnorm(seeing, pixsc):
    from tractor.patch import ModelMask
    S = 32
    W,H = S*2+1, S*2+1
    psf_sigma = seeing / 2.35 / pixsc
    tim = tractor.Image(data=np.zeros((H,W)), inverr=np.ones((H,W)),
                        psf=tractor.NCircularGaussianPSF([psf_sigma], [1.]),
                        wcs=tractor.NullWCS(pixscale=pixsc))
    gal = SimpleGalaxy(tractor.PixPos(S,S), tractor.Flux(1.))
    mm = ModelMask(0, 0, W, H)
    galmod = gal.getModelPatch(tim, modelMask=mm).patch
    galmod = np.maximum(0, galmod)
    galmod /= galmod.sum()
    return np.sqrt(np.sum(galmod**2))
Exemplo n.º 3
0
    def fit_general_gaussian(self,
                             img,
                             sig1,
                             xi,
                             yi,
                             fluxi,
                             psf_r=15,
                             ps=None):
        import tractor
        H, W = img.shape
        ix = int(np.round(xi))
        iy = int(np.round(yi))
        xlo = max(0, ix - psf_r)
        xhi = min(W, ix + psf_r + 1)
        ylo = max(0, iy - psf_r)
        yhi = min(H, iy + psf_r + 1)
        xx, yy = np.meshgrid(np.arange(xlo, xhi), np.arange(ylo, yhi))
        r2 = (xx - xi)**2 + (yy - yi)**2
        keep = (r2 < psf_r**2)
        pix = img[ylo:yhi, xlo:xhi].copy()
        ie = np.zeros_like(pix)
        ie[keep] = 1. / sig1

        psf = tractor.NCircularGaussianPSF([4.], [1.])
        tim = tractor.Image(data=pix, inverr=ie, psf=psf)
        src = tractor.PointSource(tractor.PixPos(xi - xlo, yi - ylo),
                                  tractor.Flux(fluxi))
        tr = tractor.Tractor([tim], [src])

        src.pos.addGaussianPrior('x', 0., 1.)

        doplot = (ps is not None)
        if doplot:
            mod0 = tr.getModelImage(0)

        tim.freezeAllBut('psf')
        psf.freezeAllBut('sigmas')

        # print('Optimizing params:')
        # tr.printThawedParams()

        #print('Parameter step sizes:', tr.getStepSizes())
        optargs = dict(priors=False, shared_params=False)
        for step in range(50):
            dlnp, x, alpha = tr.optimize(**optargs)
            if dlnp == 0:
                break

        # Now fit only the PSF size
        tr.freezeParam('catalog')
        # print('Optimizing params:')
        # tr.printThawedParams()

        for step in range(50):
            dlnp, x, alpha = tr.optimize(**optargs)
            if dlnp == 0:
                break

        # fwhms.append(psf.sigmas[0] * 2.35 * self.pixscale)

        if doplot:
            mod1 = tr.getModelImage(0)
            chi1 = tr.getChiImage(0)

        # Now switch to a non-isotropic PSF
        s = psf.sigmas[0]
        #print('Isotropic fit sigma', s)
        s = np.clip(s, 1., 5.)
        tim.psf = tractor.GaussianMixturePSF(1., 0., 0., s**2, s**2, 0.)

        #print('Optimizing params:')
        #tr.printThawedParams()

        try:
            for step in range(50):
                dlnp, x, alpha = tr.optimize(**optargs)
                #print('PSF:', tim.psf)
                if dlnp == 0:
                    break
        except:
            import traceback
            print('Error during fitting PSF in a focus frame; not to worry')
            traceback.print_exc()
            print(
                '(The above was just an error during fitting one star in a focus frame; not to worry.)'
            )

        # Don't need to re-fit source params because PSF ampl and mean
        # can fit for flux and position.

        if doplot:
            mod2 = tr.getModelImage(0)
            chi2 = tr.getChiImage(0)
            kwa = dict(vmin=-3 * sig1, vmax=50 * sig1, cmap='gray')

            plt.clf()
            plt.subplot(2, 3, 1)
            plt.title('Image')
            dimshow(pix, ticks=False, **kwa)
            plt.subplot(2, 3, 2)
            plt.title('Initial model')
            dimshow(mod0, ticks=False, **kwa)
            plt.subplot(2, 3, 3)
            plt.title('Isotropic model')
            dimshow(mod1, ticks=False, **kwa)
            plt.subplot(2, 3, 4)
            plt.title('Final model')
            dimshow(mod2, ticks=False, **kwa)
            plt.subplot(2, 3, 5)
            plt.title('Isotropic chi')
            dimshow(chi1, vmin=-10, vmax=10, ticks=False)
            plt.subplot(2, 3, 6)
            plt.title('Final chi')
            dimshow(chi2, vmin=-10, vmax=10, ticks=False)
            plt.suptitle('PSF fit')
            ps.savefig()

        return tim.psf.getParams()[-3:]
Exemplo n.º 4
0
def main():

    # In LSST meas-deblend (on lsst6):
    # python examples/suprimePlot.py --data ~dstn/lsst/ACT-data -v 126969 -c 5 --data-range -100 300 --roi 0 500 0 500 --psf psf.fits --image img.fits --sources srcs.fits

    from optparse import OptionParser
    import sys

    parser = OptionParser(usage=('%prog <img> <psf> <srcs>'))
    parser.add_option('-v',
                      '--verbose',
                      dest='verbose',
                      action='count',
                      default=0,
                      help='Make more verbose')
    opt, args = parser.parse_args()

    if len(args) != 3:
        parser.print_help()
        sys.exit(-1)

    if opt.verbose == 0:
        lvl = logging.INFO
    else:
        lvl = logging.DEBUG
    logging.basicConfig(level=lvl, format='%(message)s', stream=sys.stdout)

    imgfn, psffn, srcfn = args

    pimg = pyfits.open(imgfn)
    if len(pimg) != 4:
        print('Image must have 3 extensions')
        sys.exit(-1)
    img = pimg[1].data
    mask = pimg[2].data
    maskhdr = pimg[2].header
    var = pimg[3].data
    del pimg

    print('var', var.shape)
    #print var
    print('mask', mask.shape)
    #print mask
    print('img', img.shape)
    #print img

    mask = mask.astype(np.int16)
    for bit in range(16):
        on = ((mask & (1 << bit)) != 0)
        print('Bit', bit, 'has', np.sum(on), 'pixels set')
    '''
	MP_BAD  =                    0
	Bit 0 has 2500 pixels set
	MP_SAT  =                    1
	Bit 1 has 5771 pixels set
	MP_INTRP=                    2
	Bit 2 has 11269 pixels set
	MP_CR   =                    3
	Bit 3 has 136 pixels set
	MP_EDGE =                    4
	Bit 4 has 11856 pixels set
	HIERARCH MP_DETECTED =       5
	Bit 5 has 37032 pixels set
	'''

    print('Mask header:', maskhdr)
    maskplanes = {}
    print('Mask planes:')
    for card in maskhdr.ascardlist():
        if not card.key.startswith('MP_'):
            continue
        print(card.value, card.key)
        maskplanes[card.key[3:]] = card.value

    print('Variance range:', var.min(), var.max())

    print('Image median:', np.median(img.ravel()))

    invvar = 1. / var
    invvar[var == 0] = 0.
    invvar[var < 0] = 0.

    sig = np.sqrt(np.median(var))
    H, W = img.shape
    for k, v in maskplanes.items():
        plt.clf()

        I = ((mask & (1 << v)) != 0)
        rgb = np.zeros((H, W, 3))
        clipimg = np.clip((img - (-3. * sig)) / (13. * sig), 0, 1)
        cimg = clipimg.copy()
        cimg[I] = 1
        rgb[:, :, 0] = cimg
        cimg = clipimg.copy()
        cimg[I] = 0
        rgb[:, :, 1] = cimg
        rgb[:, :, 2] = cimg
        plt.imshow(rgb, interpolation='nearest', origin='lower')
        plt.title(k)
        plt.savefig('mask-%s.png' % k.lower())

    badmask = sum([(1 << maskplanes[k])
                   for k in ['BAD', 'SAT', 'INTRP', 'CR']])
    # HACK -- left EDGE sucks
    badmask += (1 << maskplanes['EDGE'])
    #badmask = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3)
    #badmask |= (1 << 4)
    print('Masking out: 0x%x' % badmask)
    invvar[(mask & badmask) != 0] = 0.

    assert (all(np.isfinite(img.ravel())))
    assert (all(np.isfinite(invvar.ravel())))

    psf = pyfits.open(psffn)[0].data
    print('psf', psf.shape)
    psf /= psf.sum()

    from tractor.emfit import em_fit_2d
    from tractor.fitpsf import em_init_params

    # Create Gaussian mixture model PSF approximation.
    S = psf.shape[0]
    # number of Gaussian components
    K = 3
    w, mu, sig = em_init_params(K, None, None, None)
    II = psf.copy()
    II /= II.sum()
    # HIDEOUS HACK
    II = np.maximum(II, 0)
    print('Multi-Gaussian PSF fit...')
    xm, ym = -(S / 2), -(S / 2)
    em_fit_2d(II, xm, ym, w, mu, sig)
    print('w,mu,sig', w, mu, sig)
    mypsf = tractor.GaussianMixturePSF(w, mu, sig)

    P = mypsf.getPointSourcePatch(S / 2, S / 2)
    mn, mx = psf.min(), psf.max()
    ima = dict(interpolation='nearest', origin='lower', vmin=mn, vmax=mx)
    plt.clf()
    plt.subplot(1, 2, 1)
    plt.imshow(psf, **ima)
    plt.subplot(1, 2, 2)
    pimg = np.zeros_like(psf)
    P.addTo(pimg)
    plt.imshow(pimg, **ima)
    plt.savefig('psf.png')

    sig = np.sqrt(np.median(var))

    plt.clf()
    plt.hist(img.ravel(), 100, range=(-3. * sig, 3. * sig))
    plt.savefig('imghist.png')

    srcs = fits_table(srcfn)
    print('Initial:', len(srcs), 'sources')
    # Trim sources with x=0 or y=0
    srcs = srcs[(srcs.x != 0) * (srcs.y != 0)]
    print('Trim on x,y:', len(srcs), 'sources left')
    # Zero out nans & infs
    for c in ['theta', 'a', 'b']:
        I = np.logical_not(np.isfinite(srcs.get(c)))
        srcs.get(c)[I] = 0.
    # Set sources with flux=NaN to something more sensible...
    I = np.logical_not(np.isfinite(srcs.flux))
    srcs.flux[I] = 1.
    # Sort sources by flux.
    srcs = srcs[np.argsort(-srcs.flux)]

    # Trim sources that are way outside the image.
    margin = 8. * np.maximum(srcs.a, srcs.b)
    H, W = img.shape
    srcs = srcs[(srcs.x > -margin) * (srcs.y > -margin) *
                (srcs.x < (W + margin) * (srcs.y < (H + margin)))]
    print('Trim out-of-bounds:', len(srcs), 'sources left')

    wcs = tractor.FitsWcs(Sip(imgfn, 1))
    #wcs = tractor.NullWCS()

    timg = tractor.Image(data=img,
                         invvar=invvar,
                         psf=mypsf,
                         wcs=wcs,
                         sky=tractor.ConstantSky(0.),
                         photocal=tractor.NullPhotoCal(),
                         name='image')

    inverr = timg.getInvError()
    assert (all(np.isfinite(inverr.ravel())))

    tsrcs = []
    for s in srcs:
        #pos = tractor.PixPos(s.x, s.y)
        pos = tractor.RaDecPos(s.ra, s.dec)
        if s.a > 0 and s.b > 0:
            eflux = tractor.Flux(s.flux / 2.)
            dflux = tractor.Flux(s.flux / 2.)
            re, ab, phi = s.a, s.b / s.a, 90. - s.theta
            eshape = gal.GalaxyShape(re, ab, phi)
            dshape = gal.GalaxyShape(re, ab, phi)
            print('Fluxes', eflux, dflux)
            tsrc = gal.CompositeGalaxy(pos, eflux, eshape, dflux, dshape)
        else:
            flux = tractor.Flux(s.flux)
            print('Flux', flux)
            tsrc = tractor.PointSource(pos, flux)
        tsrcs.append(tsrc)

    chug = tractor.Tractor([timg])
    for src in tsrcs:
        if chug.getModelPatch(timg, src) is None:
            print('Dropping non-overlapping source:', src)
            continue
        chug.addSource(src)
    print('Kept a total of', len(chug.catalog), 'sources')

    ima = dict(interpolation='nearest',
               origin='lower',
               vmin=-3. * sig,
               vmax=10. * sig)
    chia = dict(interpolation='nearest', origin='lower', vmin=-5., vmax=5.)

    plt.clf()
    plt.imshow(img, **ima)
    plt.colorbar()
    plt.savefig('img.png')

    plt.clf()
    plt.imshow(invvar, interpolation='nearest', origin='lower')
    plt.colorbar()
    plt.savefig('invvar.png')

    mod = chug.getModelImages()[0]
    plt.clf()
    plt.imshow(mod, **ima)
    plt.colorbar()
    plt.savefig('mod-0.png')

    chi = chug.getChiImage(0)
    plt.clf()
    plt.imshow(chi, **chia)
    plt.colorbar()
    plt.savefig('chi-0.png')

    for step in range(5):
        cat = chug.getCatalog()
        for src in cat:
            if chug.getModelPatch(timg, src) is None:
                print('Dropping non-overlapping source:', src)
                chug.removeSource(src)
        print('Kept a total of', len(chug.catalog), 'sources')

        #cat = chug.getCatalog()
        #for i,src in enumerate([]):
        #for i,src in enumerate(chug.getCatalog()):
        #for i in range(len(cat)):
        i = 0
        while i < len(cat):
            src = cat[i]

            #print 'Step', i
            #for j,s in enumerate(cat):
            #	x,y = timg.getWcs().positionToPixel(s, s.getPosition())
            #	print '  ',
            #	if j == i:
            #		print '*',
            #	print '(%6.1f, %6.1f)'%(x,y), s

            print('Optimizing source', i, 'of', len(cat))

            x, y = timg.getWcs().positionToPixel(src.getPosition(), src)
            print('(%6.1f, %6.1f)' % (x, y), src)
            # pre = src.getModelPatch(timg)

            s1 = str(src)
            print('src1 ', s1)
            dlnp1, X, a = chug.optimizeCatalogFluxes(srcs=[src])
            s2 = str(src)
            dlnp2, X, a = chug.optimizeCatalogAtFixedComplexityStep(srcs=[src],
                                                                    sky=False)
            s3 = str(src)

            #post = src.getModelPatch(timg)

            print('src1 ', s1)
            print('src2 ', s2)
            print('src3 ', s3)
            print('dlnp', dlnp1, dlnp2)

            if chug.getModelPatch(timg, src) is None:
                print('After optimizing, no overlap!')
                print('Removing source', src)
                chug.removeSource(src)
                i -= 1
            i += 1

            # plt.clf()
            # plt.subplot(2,2,1)
            # img = timg.getImage()
            # (x0,x1,y0,y1) = pre.getExtent()
            # plt.imshow(img, **ima)
            # ax = plt.axis()
            # plt.plot([x0,x0,x1,x1,x0], [y0,y1,y1,y0,y0], 'k-', lw=2)
            # plt.axis(ax)
            # plt.subplot(2,2,3)
            # plt.imshow(pre.getImage(), **ima)
            # plt.subplot(2,2,4)
            # plt.imshow(post.getImage(), **ima)
            # plt.savefig('prepost-s%i-s%03i.png' % (step, i))
            #
            # mod = chug.getModelImages()[0]
            # plt.clf()
            # plt.imshow(mod, **ima)
            # plt.colorbar()
            # plt.savefig('mod-s%i-s%03i.png' % (step, i))
            # chi = chug.getChiImage(0)
            # plt.clf()
            # plt.imshow(chi, **chia)
            # plt.colorbar()
            # plt.savefig('chi-s%i-s%03i.png' % (step, i))

        #dlnp,x,a = chug.optimizeCatalogFluxes()
        #print 'fluxes: dlnp', dlnp
        #dlnp,x,a = chug.optimizeCatalogAtFixedComplexityStep()
        #print 'opt: dlnp', dlnp

        mod = chug.getModelImages()[0]
        plt.clf()
        plt.imshow(mod, **ima)
        plt.colorbar()
        plt.savefig('mod-%i.png' % (step + 1))

        chi = chug.getChiImage(0)
        plt.clf()
        plt.imshow(chi, **chia)
        plt.colorbar()
        plt.savefig('chi-%i.png' % (step + 1))

    return

    for step in range(5):
        chug.optimizeCatalogFluxes()
        mod = chug.getModelImages()[0]
        plt.clf()
        plt.imshow(mod, **ima)
        plt.colorbar()
        plt.savefig('mod-s%i.png' % step)

        chi = chug.getChiImage(0)
        plt.clf()
        plt.imshow(chi, **chia)
        plt.colorbar()
        plt.savefig('chi-s%i.png' % step)
Exemplo n.º 5
0
'''
if __name__ == '__main__':
    H, W = 100, 100

    pixscale = 0.262
    ra, dec = 40., 10.
    psf_sigma = 1.4  # pixels
    v = psf_sigma**2

    ps = pixscale / 3600.
    wcs = Tan(ra, dec, W / 2. + 0.5, H / 2. + 0.5, -ps, 0., 0., ps, float(W),
              float(H))

    tim = tractor.Image(data=np.zeros((H, W), np.float32),
                        inverr=np.ones((H, W), np.float32),
                        psf=tractor.GaussianMixturePSF(1., 0., 0., v, v, 0.),
                        wcs=tractor.ConstantFitsWcs(wcs))
    src = RexGalaxy(tractor.RaDecPos(ra, dec), tractor.Flux(100.),
                    LogRadius(0.))

    tr = tractor.Tractor([tim], [src])
    mod = tr.getModelImage(0)

    plt.clf()
    plt.imshow(mod, interpolation='nearest', origin='lower')
    plt.savefig('rex.png')

    # add noise with std 1.
    noisy = mod + np.random.normal(size=mod.shape)
    # make that the tim's data
    tim.data = noisy
Exemplo n.º 6
0
    def run(self, ps=None, focus=False, momentsize=5, n_fwhm=100):
        import pylab as plt
        from astrometry.util.plotutils import dimshow, plothist
        from legacyanalysis.ps1cat import ps1cat
        import photutils
        import tractor

        fn = self.fn
        ext = self.ext
        pixsc = self.pixscale

        F = fitsio.FITS(fn)
        primhdr = F[0].read_header()
        self.primhdr = primhdr
        img, hdr = self.read_raw(F, ext)
        self.hdr = hdr

        # pre sky-sub
        mn, mx = np.percentile(img.ravel(), [25, 98])
        self.imgkwa = dict(vmin=mn, vmax=mx, cmap='gray')

        if self.debug and ps is not None:
            plt.clf()
            dimshow(img, **self.imgkwa)
            plt.title('Raw image')
            ps.savefig()

            M = 200
            plt.clf()
            plt.subplot(2, 2, 1)
            dimshow(img[-M:, :M], ticks=False, **self.imgkwa)
            plt.subplot(2, 2, 2)
            dimshow(img[-M:, -M:], ticks=False, **self.imgkwa)
            plt.subplot(2, 2, 3)
            dimshow(img[:M, :M], ticks=False, **self.imgkwa)
            plt.subplot(2, 2, 4)
            dimshow(img[:M, -M:], ticks=False, **self.imgkwa)
            plt.suptitle('Raw image corners')
            ps.savefig()

        img, trim_x0, trim_y0 = self.trim_edges(img)

        fullH, fullW = img.shape

        if self.debug and ps is not None:
            plt.clf()
            dimshow(img, **self.imgkwa)
            plt.title('Trimmed image')
            ps.savefig()

            M = 200
            plt.clf()
            plt.subplot(2, 2, 1)
            dimshow(img[-M:, :M], ticks=False, **self.imgkwa)
            plt.subplot(2, 2, 2)
            dimshow(img[-M:, -M:], ticks=False, **self.imgkwa)
            plt.subplot(2, 2, 3)
            dimshow(img[:M, :M], ticks=False, **self.imgkwa)
            plt.subplot(2, 2, 4)
            dimshow(img[:M, -M:], ticks=False, **self.imgkwa)
            plt.suptitle('Trimmed corners')
            ps.savefig()

        band = self.get_band(primhdr)
        exptime = primhdr['EXPTIME']
        airmass = primhdr['AIRMASS']
        print('Band', band, 'Exptime', exptime, 'Airmass', airmass)

        zp0 = self.nom.zeropoint(band, ext=self.ext)
        sky0 = self.nom.sky(band)
        kx = self.nom.fiducial_exptime(band).k_co

        # Find the sky value and noise level
        sky, sig1 = self.get_sky_and_sigma(img)

        sky1 = np.median(sky)
        skybr = -2.5 * np.log10(sky1 / pixsc / pixsc / exptime) + zp0
        print('Sky brightness: %8.3f mag/arcsec^2' % skybr)
        print('Fiducial:       %8.3f mag/arcsec^2' % sky0)

        img -= sky

        self.remove_sky_gradients(img)

        # Post sky-sub
        mn, mx = np.percentile(img.ravel(), [25, 98])
        self.imgkwa = dict(vmin=mn, vmax=mx, cmap='gray')

        if ps is not None:
            plt.clf()
            dimshow(img, **self.imgkwa)
            plt.title('Sky-sub image: %s-%s' % (os.path.basename(fn).replace(
                '.fits', '').replace('.fz', ''), ext))
            plt.colorbar()
            ps.savefig()

        # Read WCS header and compute boresight
        wcs = self.get_wcs(hdr)
        ra_ccd, dec_ccd = wcs.pixelxy2radec((fullW + 1) / 2., (fullH + 1) / 2.)

        # Detect stars
        psfsig = self.nominal_fwhm / 2.35
        detsn = self.detection_map(img, sig1, psfsig, ps)

        slices = self.detect_sources(detsn, self.det_thresh, ps)
        print(len(slices), 'sources detected')
        if len(slices) < 20:
            slices = self.detect_sources(detsn, 10., ps)
            print(len(slices), 'sources detected')
        ndetected = len(slices)

        camera = primhdr.get('INSTRUME', '').strip().lower()
        # -> "decam" / "mosaic3"
        meas = dict(band=band,
                    airmass=airmass,
                    skybright=skybr,
                    pixscale=pixsc,
                    primhdr=primhdr,
                    hdr=hdr,
                    wcs=wcs,
                    ra_ccd=ra_ccd,
                    dec_ccd=dec_ccd,
                    extension=ext,
                    camera=camera,
                    ndetected=ndetected)

        if ndetected == 0:
            print('NO SOURCES DETECTED')
            return meas

        xx, yy = [], []
        fx, fy = [], []
        mx2, my2, mxy = [], [], []
        wmx2, wmy2, wmxy = [], [], []
        # "Peak" region to centroid
        P = momentsize
        H, W = img.shape

        for i, slc in enumerate(slices):
            y0 = slc[0].start
            x0 = slc[1].start
            subimg = detsn[slc]
            imax = np.argmax(subimg)
            y, x = np.unravel_index(imax, subimg.shape)
            if (x0 + x) < P or (x0 + x) > W - 1 - P or (y0 + y) < P or (
                    y0 + y) > H - 1 - P:
                #print('Skipping edge peak', x0+x, y0+y)
                continue
            xx.append(x0 + x)
            yy.append(y0 + y)
            pkarea = detsn[y0 + y - P:y0 + y + P + 1,
                           x0 + x - P:x0 + x + P + 1]

            from scipy.ndimage.measurements import center_of_mass
            cy, cx = center_of_mass(pkarea)
            #print('Center of mass', cx,cy)
            fx.append(x0 + x - P + cx)
            fy.append(y0 + y - P + cy)
            #print('x,y', x0+x, y0+y, 'vs centroid', x0+x-P+cx, y0+y-P+cy)

            ### HACK -- measure source ellipticity
            # go back to the image (not detection map)
            #subimg = img[slc]
            subimg = img[y0 + y - P:y0 + y + P + 1,
                         x0 + x - P:x0 + x + P + 1].copy()
            subimg /= subimg.sum()
            ph, pw = subimg.shape
            px, py = np.meshgrid(np.arange(pw), np.arange(ph))
            mx2.append(np.sum(subimg * (px - cx)**2))
            my2.append(np.sum(subimg * (py - cy)**2))
            mxy.append(np.sum(subimg * (px - cx) * (py - cy)))
            # Gaussian windowed version
            s = 1.
            wimg = subimg * np.exp(-0.5 * ((px - cx)**2 + (py - cy)**2) / s**2)
            wimg /= np.sum(wimg)
            wmx2.append(np.sum(wimg * (px - cx)**2))
            wmy2.append(np.sum(wimg * (py - cy)**2))
            wmxy.append(np.sum(wimg * (px - cx) * (py - cy)))

        mx2 = np.array(mx2)
        my2 = np.array(my2)
        mxy = np.array(mxy)
        wmx2 = np.array(wmx2)
        wmy2 = np.array(wmy2)
        wmxy = np.array(wmxy)

        # semi-major/minor axes and position angle
        theta = np.rad2deg(np.arctan2(2 * mxy, mx2 - my2) / 2.)
        theta = np.abs(theta) * np.sign(mxy)
        s = np.sqrt(((mx2 - my2) / 2.)**2 + mxy**2)
        a = np.sqrt((mx2 + my2) / 2. + s)
        b = np.sqrt((mx2 + my2) / 2. - s)
        ell = 1. - b / a

        wtheta = np.rad2deg(np.arctan2(2 * wmxy, wmx2 - wmy2) / 2.)
        wtheta = np.abs(wtheta) * np.sign(wmxy)
        ws = np.sqrt(((wmx2 - wmy2) / 2.)**2 + wmxy**2)
        wa = np.sqrt((wmx2 + wmy2) / 2. + ws)
        wb = np.sqrt((wmx2 + wmy2) / 2. - ws)
        well = 1. - wb / wa

        fx = np.array(fx)
        fy = np.array(fy)
        xx = np.array(xx)
        yy = np.array(yy)

        if ps is not None:

            plt.clf()
            dimshow(detsn, vmin=-3, vmax=50, cmap='gray')
            ax = plt.axis()
            plt.plot(fx, fy, 'go', mec='g', mfc='none', ms=10)
            plt.colorbar()
            plt.title('Detected sources')
            plt.axis(ax)
            ps.savefig()

            # show centroids too
            # plt.plot(xx, yy, 'go', mec='g', mfc='none', ms=8)
            # plt.axis(ax)
            # ps.savefig()

        # if ps is not None:
        #     plt.clf()
        #     plt.subplot(2,1,1)
        #     mx = np.percentile(np.append(mx2,my2), 99)
        #     ha = dict(histtype='step', range=(0,mx), bins=50)
        #     plt.hist(mx2, color='b', label='mx2', **ha)
        #     plt.hist(my2, color='r', label='my2', **ha)
        #     plt.hist(mxy, color='g', label='mxy', **ha)
        #     plt.legend()
        #     plt.xlim(0,mx)
        #     plt.subplot(2,1,2)
        #     mx = np.percentile(np.append(wmx2,wmy2), 99)
        #     ha = dict(histtype='step', range=(0,mx), bins=50, lw=3, alpha=0.3)
        #     plt.hist(wmx2, color='b', label='wx2', **ha)
        #     plt.hist(wmy2, color='r', label='wy2', **ha)
        #     plt.hist(wmxy, color='g', label='wxy', **ha)
        #     plt.legend()
        #     plt.xlim(0,mx)
        #     plt.suptitle('Source moments')
        #     ps.savefig()
        #
        #     #mx = np.percentile(np.abs(np.append(mxy,wmxy)), 99)
        #     plt.clf()
        #     plt.subplot(2,1,1)
        #     ha = dict(histtype='step', range=(0,1), bins=50)
        #     plt.hist(ell, color='g', label='ell', **ha)
        #     plt.hist(well, color='g', lw=3, alpha=0.3, label='windowed ell', **ha)
        #     plt.legend()
        #     plt.subplot(2,1,2)
        #     ha = dict(histtype='step', range=(-90,90), bins=50)
        #     plt.hist(theta, color='g', label='theta', **ha)
        #     plt.hist(wtheta, color='g', lw=3, alpha=0.3,
        #              label='windowed theta', **ha)
        #     plt.xlim(-90,90)
        #     plt.legend()
        #     plt.suptitle('Source ellipticities & angles')
        #     ps.savefig()

        # Cut down to stars whose centroids are within 1 pixel of their peaks...
        #keep = (np.hypot(fx - xx, fy - yy) < 2)
        #print(sum(keep), 'of', len(keep), 'stars have centroids within 2 of peaks')
        #print('mean dx', np.mean(fx-xx), 'dy', np.mean(fy-yy), 'pixels')
        #assert(float(sum(keep)) / len(keep) > 0.9)
        #fx = fx[keep]
        #fy = fy[keep]

        apxy = np.vstack((fx, fy)).T
        ap = []
        aprad_pix = self.aprad / pixsc
        aper = photutils.CircularAperture(apxy, aprad_pix)
        p = photutils.aperture_photometry(img, aper)
        apflux = p.field('aperture_sum')

        # Manual aperture photometry to get clipped means in sky annulus
        sky_inner_r, sky_outer_r = [r / pixsc for r in self.skyrad]
        sky = []
        for xi, yi in zip(fx, fy):
            ix = int(np.round(xi))
            iy = int(np.round(yi))
            skyR = int(np.ceil(sky_outer_r))
            xlo = max(0, ix - skyR)
            xhi = min(W, ix + skyR + 1)
            ylo = max(0, iy - skyR)
            yhi = min(H, iy + skyR + 1)
            xx, yy = np.meshgrid(np.arange(xlo, xhi), np.arange(ylo, yhi))
            r2 = (xx - xi)**2 + (yy - yi)**2
            inannulus = ((r2 >= sky_inner_r**2) * (r2 < sky_outer_r**2))
            skypix = img[ylo:yhi, xlo:xhi][inannulus]
            #print('ylo,yhi, xlo,xhi', ylo,yhi, xlo,xhi, 'img subshape', img[ylo:yhi, xlo:xhi].shape, 'inann shape', inannulus.shape)
            s, nil = sensible_sigmaclip(skypix)
            sky.append(s)
        sky = np.array(sky)

        apflux2 = apflux - sky * (np.pi * aprad_pix**2)
        good = (apflux2 > 0) * (apflux > 0)
        apflux = apflux[good]
        apflux2 = apflux2[good]
        fx = fx[good]
        fy = fy[good]

        # Read in the PS1 catalog, and keep those within 0.25 deg of CCD center
        # and those with main sequence colors
        pscat = ps1cat(ccdwcs=wcs)
        stars = pscat.get_stars()
        #print('Got PS1 stars:', len(stars))

        # we add the color term later
        ps1band = ps1cat.ps1band[band]
        stars.mag = stars.median[:, ps1band]

        ok, px, py = wcs.radec2pixelxy(stars.ra, stars.dec)
        px -= 1
        py -= 1

        if ps is not None:
            #kwa = dict(vmin=-3*sig1, vmax=50*sig1, cmap='gray')
            # Add to the 'detected sources' plot
            # mn,mx = np.percentile(img.ravel(), [50,99])
            # kwa = dict(vmin=mn, vmax=mx, cmap='gray')
            # plt.clf()
            # dimshow(img, **kwa)
            ax = plt.axis()
            #plt.plot(fx, fy, 'go', mec='g', mfc='none', ms=10)
            K = np.argsort(stars.mag)
            plt.plot(px[K[:10]] - trim_x0,
                     py[K[:10]] - trim_y0,
                     'o',
                     mec='m',
                     mfc='none',
                     ms=12,
                     mew=2)
            plt.plot(px[K[10:]] - trim_x0,
                     py[K[10:]] - trim_y0,
                     'o',
                     mec='m',
                     mfc='none',
                     ms=8)
            plt.axis(ax)
            plt.title('PS1 stars')
            #plt.colorbar()
            ps.savefig()

        # we trimmed the image before running detection; re-add that margin
        fullx = fx + trim_x0
        fully = fy + trim_y0

        # Match PS1 to our detections, find offset
        radius = self.maxshift / pixsc

        I, J, dx, dy = self.match_ps1_stars(px, py, fullx, fully, radius,
                                            stars)
        print(len(I), 'spatial matches with large radius', self.maxshift,
              'arcsec,', radius, 'pix')

        bins = 2 * int(np.ceil(radius))
        #print('Histogramming with', bins, 'bins')
        histo, xe, ye = np.histogram2d(dx,
                                       dy,
                                       bins=bins,
                                       range=((-radius, radius), (-radius,
                                                                  radius)))
        # smooth histogram before finding peak -- fuzzy matching
        from scipy.ndimage.filters import gaussian_filter
        histo = gaussian_filter(histo, 1.)
        histo = histo.T
        mx = np.argmax(histo)
        my, mx = np.unravel_index(mx, histo.shape)
        shiftx = (xe[mx] + xe[mx + 1]) / 2.
        shifty = (ye[my] + ye[my + 1]) / 2.

        if ps is not None:
            plt.clf()
            plothist(dx, dy, range=((-radius, radius), (-radius, radius)))
            plt.xlabel('dx (pixels)')
            plt.ylabel('dy (pixels)')
            plt.title('Offsets to PS1 stars')
            ax = plt.axis()
            plt.axhline(0, color='b')
            plt.axvline(0, color='b')
            plt.plot(shiftx, shifty, 'o', mec='m', mfc='none', ms=15, mew=3)
            plt.axis(ax)
            ps.savefig()

        # Refine with smaller search radius
        radius2 = 3. / pixsc
        I, J, dx, dy = self.match_ps1_stars(px, py, fullx + shiftx,
                                            fully + shifty, radius2, stars)
        print(len(J), 'matches to PS1 with small radius', 3, 'arcsec')
        shiftx2 = np.median(dx)
        shifty2 = np.median(dy)
        #print('Stage-1 shift', shiftx, shifty)
        #print('Stage-2 shift', shiftx2, shifty2)
        sx = shiftx + shiftx2
        sy = shifty + shifty2
        print('Astrometric shift (%.0f, %.0f) pixels' % (sx, sy))

        if self.debug and ps is not None:
            plt.clf()
            plothist(dx, dy, range=((-radius2, radius2), (-radius2, radius2)))
            plt.xlabel('dx (pixels)')
            plt.ylabel('dy (pixels)')
            plt.title('Offsets to PS1 stars')
            ax = plt.axis()
            plt.axhline(0, color='b')
            plt.axvline(0, color='b')
            plt.plot(shiftx2, shifty2, 'o', mec='m', mfc='none', ms=15, mew=3)
            plt.axis(ax)
            ps.savefig()

        if ps is not None:
            mn, mx = np.percentile(img.ravel(), [50, 99])
            kwa2 = dict(vmin=mn, vmax=mx, cmap='gray')
            plt.clf()
            dimshow(img, **kwa2)
            ax = plt.axis()
            plt.plot(fx[J], fy[J], 'go', mec='g', mfc='none', ms=10, mew=2)
            plt.plot(px[I] - sx - trim_x0,
                     py[I] - sy - trim_y0,
                     'm+',
                     ms=10,
                     mew=2)
            plt.axis(ax)
            plt.title('Matched PS1 stars')
            plt.colorbar()
            ps.savefig()

            plt.clf()
            dimshow(img, **kwa2)
            ax = plt.axis()
            plt.plot(fx[J], fy[J], 'go', mec='g', mfc='none', ms=10, mew=2)
            K = np.argsort(stars.mag)
            plt.plot(px[K[:10]] - sx - trim_x0,
                     py[K[:10]] - sy - trim_y0,
                     'o',
                     mec='m',
                     mfc='none',
                     ms=12,
                     mew=2)
            plt.plot(px[K[10:]] - sx - trim_x0,
                     py[K[10:]] - sy - trim_y0,
                     'o',
                     mec='m',
                     mfc='none',
                     ms=8,
                     mew=2)
            plt.axis(ax)
            plt.title('All PS1 stars')
            plt.colorbar()
            ps.savefig()

        # Now cut to just *stars* with good colors
        stars.gicolor = stars.median[:, 0] - stars.median[:, 2]
        keep = (stars.gicolor > 0.4) * (stars.gicolor < 2.7)
        stars.cut(keep)
        if len(stars) == 0:
            print('No overlap or too few stars in PS1')
            return None
        px = px[keep]
        py = py[keep]
        # Re-match
        I, J, dx, dy = self.match_ps1_stars(px, py, fullx + sx, fully + sy,
                                            radius2, stars)
        print('Cut to', len(stars), 'PS1 stars with good colors; matched',
              len(I))

        nmatched = len(I)

        meas.update(dx=sx, dy=sy, nmatched=nmatched)

        if focus:
            meas.update(img=img,
                        hdr=hdr,
                        primhdr=primhdr,
                        fx=fx,
                        fy=fy,
                        px=px - trim_x0 - sx,
                        py=py - trim_y0 - sy,
                        sig1=sig1,
                        stars=stars,
                        moments=(mx2, my2, mxy, theta, a, b, ell),
                        wmoments=(wmx2, wmy2, wmxy, wtheta, wa, wb, well),
                        apflux=apflux,
                        apflux2=apflux2)
            return meas

        #print('Mean astrometric shift (arcsec): delta-ra=', -np.mean(dy)*0.263, 'delta-dec=', np.mean(dx)*0.263)

        # Compute photometric offset compared to PS1
        # as the PS1 minus observed mags
        colorterm = self.colorterm_ps1_to_observed(stars.median, band)
        stars.mag += colorterm
        ps1mag = stars.mag[I]

        if False and ps is not None:
            plt.clf()
            plt.semilogy(ps1mag, apflux2[J], 'b.')
            plt.xlabel('PS1 mag')
            plt.ylabel('DECam ap flux (with sky sub)')
            ps.savefig()

            plt.clf()
            plt.semilogy(ps1mag, apflux[J], 'b.')
            plt.xlabel('PS1 mag')
            plt.ylabel('DECam ap flux (no sky sub)')
            ps.savefig()

        apmag2 = -2.5 * np.log10(apflux2) + zp0 + 2.5 * np.log10(exptime)
        apmag = -2.5 * np.log10(apflux) + zp0 + 2.5 * np.log10(exptime)

        if ps is not None:
            plt.clf()
            plt.plot(ps1mag, apmag[J], 'b.', label='No sky sub')
            plt.plot(ps1mag, apmag2[J], 'r.', label='Sky sub')
            # ax = plt.axis()
            # mn = min(ax[0], ax[2])
            # mx = max(ax[1], ax[3])
            # plt.plot([mn,mx], [mn,mx], 'k-', alpha=0.1)
            # plt.axis(ax)
            plt.xlabel('PS1 mag')
            plt.ylabel('DECam ap mag')
            plt.legend(loc='upper left')
            plt.title('Zeropoint')
            ps.savefig()

        dm = ps1mag - apmag[J]
        dmag, dsig = sensible_sigmaclip(dm, nsigma=2.5)
        print('Mag offset: %8.3f' % dmag)
        print('Scatter:    %8.3f' % dsig)

        if not np.isfinite(dmag) or not np.isfinite(dsig):
            print('FAILED TO GET ZEROPOINT!')
            meas.update(zp=None)
            return meas

        from scipy.stats import sigmaclip
        goodpix, lo, hi = sigmaclip(dm, low=3, high=3)
        dmagmed = np.median(goodpix)
        print(len(goodpix), 'stars used for zeropoint median')
        print('Using median zeropoint:')
        zp_med = zp0 + dmagmed
        trans_med = 10.**(-0.4 * (zp0 - zp_med - kx * (airmass - 1.)))
        print('Zeropoint %6.3f' % zp_med)
        print('Transparency: %.3f' % trans_med)

        dm = ps1mag - apmag2[J]
        dmag2, dsig2 = sensible_sigmaclip(dm, nsigma=2.5)
        #print('Sky-sub mag offset', dmag2)
        #print('Scatter', dsig2)

        if ps is not None:
            plt.clf()
            plt.plot(ps1mag,
                     apmag[J] + dmag - ps1mag,
                     'b.',
                     label='No sky sub')
            plt.plot(ps1mag, apmag2[J] + dmag2 - ps1mag, 'r.', label='Sky sub')
            plt.xlabel('PS1 mag')
            plt.ylabel('DECam ap mag - PS1 mag')
            plt.legend(loc='upper left')
            plt.ylim(-0.25, 0.25)
            plt.axhline(0, color='k', alpha=0.25)
            plt.title('Zeropoint')
            ps.savefig()

        zp_obs = zp0 + dmag
        transparency = 10.**(-0.4 * (zp0 - zp_obs - kx * (airmass - 1.)))
        meas.update(zp=zp_obs, transparency=transparency)

        print('Zeropoint %6.3f' % zp_obs)
        print('Fiducial  %6.3f' % zp0)
        print('Transparency: %.3f' % transparency)

        # print('Using sky-subtracted values:')
        # zp_sky = zp0 + dmag2
        # trans_sky = 10.**(-0.4 * (zp0 - zp_sky - kx * (airmass - 1.)))
        # print('Zeropoint %6.3f' % zp_sky)
        # print('Transparency: %.3f' % trans_sky)

        fwhms = []
        psf_r = 15
        if n_fwhm not in [0, None]:
            Jf = J[:n_fwhm]

        for i, (xi, yi, fluxi) in enumerate(zip(fx[Jf], fy[Jf], apflux[Jf])):
            #print('Fitting source', i, 'of', len(Jf))
            ix = int(np.round(xi))
            iy = int(np.round(yi))
            xlo = max(0, ix - psf_r)
            xhi = min(W, ix + psf_r + 1)
            ylo = max(0, iy - psf_r)
            yhi = min(H, iy + psf_r + 1)
            xx, yy = np.meshgrid(np.arange(xlo, xhi), np.arange(ylo, yhi))
            r2 = (xx - xi)**2 + (yy - yi)**2
            keep = (r2 < psf_r**2)
            pix = img[ylo:yhi, xlo:xhi].copy()
            ie = np.zeros_like(pix)
            ie[keep] = 1. / sig1
            #print('fitting source at', ix,iy)
            #print('number of active pixels:', np.sum(ie > 0), 'shape', ie.shape)

            psf = tractor.NCircularGaussianPSF([4.], [1.])
            tim = tractor.Image(data=pix, inverr=ie, psf=psf)
            src = tractor.PointSource(tractor.PixPos(xi - xlo, yi - ylo),
                                      tractor.Flux(fluxi))
            tr = tractor.Tractor([tim], [src])

            #print('Posterior before prior:', tr.getLogProb())
            src.pos.addGaussianPrior('x', 0., 1.)
            #print('Posterior after prior:', tr.getLogProb())

            doplot = (i < 5) * (ps is not None)
            if doplot:
                mod0 = tr.getModelImage(0)

            tim.freezeAllBut('psf')
            psf.freezeAllBut('sigmas')

            # print('Optimizing params:')
            # tr.printThawedParams()

            #print('Parameter step sizes:', tr.getStepSizes())
            optargs = dict(priors=False, shared_params=False)
            for step in range(50):
                dlnp, x, alpha = tr.optimize(**optargs)
                #print('dlnp', dlnp)
                #print('src', src)
                #print('psf', psf)
                if dlnp == 0:
                    break
            # Now fit only the PSF size
            tr.freezeParam('catalog')
            # print('Optimizing params:')
            # tr.printThawedParams()

            for step in range(50):
                dlnp, x, alpha = tr.optimize(**optargs)
                #print('dlnp', dlnp)
                #print('src', src)
                #print('psf', psf)
                if dlnp == 0:
                    break

            fwhms.append(psf.sigmas[0] * 2.35 * pixsc)

            if doplot:
                mod1 = tr.getModelImage(0)
                chi1 = tr.getChiImage(0)

                plt.clf()
                plt.subplot(2, 2, 1)
                plt.title('Image')
                dimshow(pix, **self.imgkwa)
                plt.subplot(2, 2, 2)
                plt.title('Initial model')
                dimshow(mod0, **self.imgkwa)
                plt.subplot(2, 2, 3)
                plt.title('Final model')
                dimshow(mod1, **self.imgkwa)
                plt.subplot(2, 2, 4)
                plt.title('Final chi')
                dimshow(chi1, vmin=-10, vmax=10)
                plt.suptitle('PSF fit')
                ps.savefig()

        fwhms = np.array(fwhms)
        fwhm = np.median(fwhms)
        print('Median FWHM: %.3f' % np.median(fwhms))
        meas.update(seeing=fwhm)

        if False and ps is not None:
            lo, hi = np.percentile(fwhms, [5, 95])
            lo -= 0.1
            hi += 0.1
            plt.clf()
            plt.hist(fwhms, 25, range=(lo, hi), histtype='step', color='b')
            plt.xlabel('FWHM (arcsec)')
            ps.savefig()

        if ps is not None:
            plt.clf()
            for i, (xi, yi) in enumerate(zip(fx[J], fy[J])[:50]):
                ix = int(np.round(xi))
                iy = int(np.round(yi))
                xlo = max(0, ix - psf_r)
                xhi = min(W, ix + psf_r + 1)
                ylo = max(0, iy - psf_r)
                yhi = min(H, iy + psf_r + 1)
                pix = img[ylo:yhi, xlo:xhi]

                slc = pix[iy - ylo, :].copy()
                slc /= np.sum(slc)
                p1 = plt.plot(slc, 'b-', alpha=0.2)
                slc = pix[:, ix - xlo].copy()
                slc /= np.sum(slc)
                p2 = plt.plot(slc, 'r-', alpha=0.2)
                ph, pw = pix.shape
                cx, cy = pw / 2, ph / 2
                if i == 0:
                    xx = np.linspace(0, pw, 300)
                    dx = xx[1] - xx[0]
                    sig = fwhm / pixsc / 2.35
                    yy = np.exp(-0.5 * (xx - cx)**2 / sig**2)  # * np.sum(pix)
                    yy /= (np.sum(yy) * dx)
                    p3 = plt.plot(xx, yy, 'k-', zorder=20)
            #plt.ylim(-0.2, 1.0)
            plt.legend([p1[0], p2[0], p3[0]],
                       ['image slice (y)', 'image slice (x)', 'fit'])
            plt.title('PSF fit')
            ps.savefig()

        return meas
Exemplo n.º 7
0
def main():
    # I read a DESI DR8 target catalog, cut to ELGs, then took a narrow
    # r-mag slice around the peak of that distribution;
    # desi/target/catalogs/dr8/0.31.1/targets/main/resolve/targets-dr8-hp-1,5,11,50,55,60,83,84,86,89,91,98,119,155,158,174,186,187,190.fits')
    # Then took the median g and z mags
    # And opened the coadd invvar mags for a random brick in that footprint
    # (0701p000) to find the median per-pixel invvars.

    r = 23.0
    g = 23.4
    z = 22.2

    # Selecting EXPs, the peak of the shapeexp_r was ~ 0.75".
    r_e = 0.75

    # Image properties:

    giv = 77000.
    riv = 27000.
    ziv = 8000.

    # PSF sizes were roughly equal, 1.16, 1.17, 1.19"
    # -> sigma = 1.9 DECam pixels
    psf_sigma = 1.9

    H, W = 1000, 1000

    seed = 42
    np.random.seed(seed)

    ra, dec = 70., 1.
    brick = BrickDuck(ra, dec, 'custom-0700p010')
    wcs = wcs_for_brick(brick, W=W, H=H)
    bands = 'grz'

    tims = []
    for band, iv in zip(bands, [giv, riv, ziv]):
        img = np.zeros((H, W), np.float32)
        tiv = np.zeros_like(img) + iv
        s = psf_sigma**2
        psf = GaussianMixturePSF(1., 0., 0., s, s, 0.)
        twcs = ConstantFitsWcs(wcs)
        sky = ConstantSky(0.)
        photocal = LinearPhotoCal(1., band=band)

        tai = TAITime(None, mjd=55700.)

        tim = tractor.Image(data=img,
                            invvar=tiv,
                            psf=psf,
                            wcs=twcs,
                            sky=sky,
                            photocal=photocal,
                            name='fake %s' % band,
                            time=tai)
        tim.skyver = ('1', '1')
        tim.psfver = ('1', '1')
        tim.plver = '1'
        tim.x0 = tim.y0 = 0
        tim.subwcs = wcs
        tim.psfnorm = 1. / (2. * np.sqrt(np.pi) * psf_sigma)
        tim.galnorm = tim.psfnorm
        tim.propid = '2020A-000'
        tim.band = band
        tim.dq = None
        tim.sig1 = 1. / np.sqrt(iv)
        tim.psf_sigma = psf_sigma
        tim.primhdr = fitsio.FITSHDR()

        tims.append(tim)

    # Simulated catalog
    gflux = NanoMaggies.magToNanomaggies(g)
    rflux = NanoMaggies.magToNanomaggies(r)
    zflux = NanoMaggies.magToNanomaggies(z)
    box = 50
    CX, CY = np.meshgrid(np.arange(box // 2, W, box),
                         np.arange(box // 2, H, box))
    ny, nx = CX.shape
    BA, PHI = np.meshgrid(np.linspace(0.1, 1.0, nx), np.linspace(0., 180., ny))
    cat = []
    for cx, cy, ba, phi in zip(CX.ravel(), CY.ravel(), BA.ravel(),
                               PHI.ravel()):
        #print('x,y %.1f,%.1f, ba %.2f, phi %.2f' % (cx, cy, ba, phi))
        r, d = wcs.pixelxy2radec(cx + 1, cy + 1)
        src = ExpGalaxy(RaDecPos(r, d),
                        NanoMaggies(order=bands, g=gflux, r=rflux, z=zflux),
                        EllipseE.fromRAbPhi(r_e, ba, phi))
        cat.append(src)

    from legacypipe.catalog import prepare_fits_catalog
    TT = fits_table()
    TT.bx = CX.ravel()
    TT.by = CY.ravel()
    TT.ba = BA.ravel()
    TT.phi = PHI.ravel()

    tcat = tractor.Catalog(*cat)
    T2 = prepare_fits_catalog(tcat, None, TT, bands, save_invvars=False)
    T2.writeto('sim-cat.fits')

    tr = Tractor(tims, cat)

    for band, tim in zip(bands, tims):
        mod = tr.getModelImage(tim)
        mod += np.random.standard_normal(
            size=tim.shape) * 1. / tim.getInvError()
        fitsio.write('sim-%s.fits' % band, mod, clobber=True)
        tim.data = mod

    ccds = fits_table()
    ccds.filter = np.array([f for f in bands])
    ccds.ccd_cuts = np.zeros(len(ccds), np.int16)
    ccds.imgfn = np.array([tim.name for tim in tims])
    ccds.propid = np.array(['2020A-000'] * len(ccds))
    ccds.fwhm = np.zeros(len(ccds), np.float32) + psf_sigma * 2.35
    ccds.mjd_obs = np.zeros(len(ccds))
    ccds.camera = np.array(['fake'] * len(ccds))

    survey = FakeLegacySurvey(ccds, tims)

    import logging
    verbose = False
    if verbose == 0:
        lvl = logging.INFO
    else:
        lvl = logging.DEBUG
    logging.basicConfig(level=lvl, format='%(message)s', stream=sys.stdout)
    # tractor logging is *soooo* chatty
    logging.getLogger('tractor.engine').setLevel(lvl + 10)

    run_brick(None,
              survey,
              radec=(ra, dec),
              width=W,
              height=H,
              do_calibs=False,
              gaia_stars=False,
              large_galaxies=False,
              tycho_stars=False,
              forceall=True,
              outliers=False)  #, stages=['image_coadds'])
Exemplo n.º 8
0
def read_cfht_coadd(imgfn, weightfn, roi=None, radecroi=None, filtermap=None):
    '''
    Given filenames for CFHT coadd image and weight files, produce
    a tractor.Image object.

    *roi*: (x0,x1, y0,y1): a region-of-interest in pixel space;
           returns the subimage [x0,x1), [y0,y1).
    *radecroi*: (ra0, ra1, dec0, dec1): a region-of-interest in RA,Dec space;
           returns the subimage bounding the given RA,Dec box [ra0,ra1], [dec0,dec1].
    *filtermap*: dict, eg,  { 'i.MP9701': 'i' }, to map from the FILTER header keyword to
           a standard filter name.
    '''

    P = pyfits.open(imgfn)
    print 'Read', P[0].data.shape, 'image'
    img = P[0].data
    imgheader = P[0].header

    # WCS: the image file has a WCS header
    # we should be able to do:
    #twcs = tractor.FitsWcs(imgfn)
    # ARGH!  Memory issues reading the file; HACK: copy header...
    f, tempfn = tempfile.mkstemp()
    os.close(f)
    pyfits.writeto(tempfn, None, header=imgheader, clobber=True)
    twcs = tractor.FitsWcs(tempfn)

    # Cut down to the region-of-interest, if given.
    if roi is not None:
        x0, x1, y0, y1 = roi
    elif radecroi is not None:
        ralo, rahi, declo, dechi = radecroi
        xy = [
            twcs.positionToPixel(tractor.RaDecPos(r, d))
            for r, d in [(ralo, declo), (ralo, dechi), (rahi,
                                                        declo), (rahi, dechi)]
        ]
        xy = np.array(xy)
        x0, x1 = xy[:, 0].min(), xy[:, 0].max()
        y0, y1 = xy[:, 1].min(), xy[:, 1].max()
        print 'RA,Dec ROI', ralo, rahi, declo, dechi, 'becomes x,y ROI', x0, x1, y0, y1

        # Clip to image size...
        H, W = data.shape
        x0 = max(0, min(x0, W - 1))
        x1 = max(0, min(x1, W))
        y0 = max(0, min(y0, H - 1))
        y1 = max(0, min(y1, H))
        print ' clipped to', x0, x1, y0, y1

    else:
        H, W = img.shape
        x0, x1, y0, y1 = 0, W, 0, H

    if roi is not None or radecroi is not None:
        # Actually cut the pixels
        img = img[y0:y1, x0:x1].copy()
        # Also tell the WCS to apply an offset.
        twcs.setX0Y0(x0, y0)

    print 'Image:', img.shape

    # HACK, tell the WCS how big the image is...
    # (needed because of the previous HACK, copying the header)
    twcs.wcs.set_imagesize(x1 - x0, y1 - y0)
    print twcs

    # Argh, this doesn't work: the files are .fz compressed
    #P = pyfits.open(weightfn)
    #weight = P[1].data[y0:y1, x0:x1]
    # HACK: use "imcopy" to uncompress to a temp file!
    #print 'Writing to temp file', tempfn
    cmd = "imcopy '%s[%i:%i,%i:%i]' '!%s'" % (weightfn, x0 + 1, x1, y0 + 1, y1,
                                              tempfn)
    print 'running', cmd
    os.system(cmd)
    P = pyfits.open(tempfn)
    weight = P[0].data
    print 'Read', weight.shape, 'weight image'

    # PSF model: FAKE IT for now
    tpsf = tractor.GaussianMixturePSF(np.array([0.9, 0.1]), np.zeros((2, 2)),
                                      np.array([1, 2]))

    # SKY level: assume zero
    #sky = np.median(img)
    #print 'Image median value:', sky
    sky = 0.
    tsky = tractor.ConstantSky(sky)

    # Photometric calibration: the FITS header says:
    '''
    FILTER  = 'r.MP9601'           / Filter
    PHOTZP  =               30.000 / photometric zeropoint
    COMMENT AB magnitude = -2.5 * log10(flux) + PHOTZP
    COMMENT r.MP9601=r_SDSS-0.024*(g_SDSS-r_SDSS)
    '''
    # Grab the filter name, and apply the filtermap (if given)
    filter = imgheader['FILTER']
    if filtermap:
        filter = filtermap.get(filter, filter)
    zp = imgheader['PHOTZP']
    # Simple photocal object
    photocal = tractor.MagsPhotoCal(filter, zp)

    # For plotting: find the approximate standard deviation
    #print 'Median weight:', np.median(weight)
    sigma1 = 1. / np.sqrt(np.median(weight))
    zr = np.array([-3, 10]) * sigma1 + sky

    name = 'CFHT ' + imgheader.get('OBJECT', '')

    tim = tractor.Image(data=img,
                        invvar=weight,
                        psf=tpsf,
                        wcs=twcs,
                        sky=tsky,
                        photocal=photocal,
                        name=name,
                        zr=zr)
    tim.extent = [x0, x1, y0, y1]
    return tim
Exemplo n.º 9
0
    ('Case 4: Different noise, different PSF', [(1., 2.), (0.5, 4.)]),
]:

    print()
    print(name)
    print()

    # We're simulating a single isolated point source
    src = tractor.PointSource(tractor.PixPos(W // 2, H // 2),
                              tractor.Flux(trueflux))

    # Produce tractor image objects with noise model, PSF model, etc
    tims = []
    for noise, psf_size in imageset:
        tim = tractor.Image(np.zeros((H, W), np.float32),
                            inverr=np.ones((H, W), np.float32) * (1. / noise),
                            psf=tractor.NCircularGaussianPSF([psf_size], [1.]),
                            photocal=tractor.LinearPhotoCal(1.))
        # Create noiseless model image (simulated image)
        tr = tractor.Tractor([tim], [src])
        mod = tr.getModelImage(0)
        tim.data = mod
        tims.append(tim)

    # First we'll run without any noise added to the images, to get estimates of ideal performance.

    # Run the Simultaneous Fitting method
    tr = tractor.Tractor(tims, [src])
    src.brightness.setParams([0.])
    # Freeze the source position -- only fit for flux
    src.freezeParam('pos')
    # Freeze the image calibration parameters (PSF model, sky background, photometric calibration, etc)
Exemplo n.º 10
0
def Deal(scifiles, varfiles, SURVEY='PS1', vb=False):

    images = []
    bands = []
    epochs = []
    total_mags = []

    for scifile, varfile in zip(scifiles, varfiles):

        name = scifile.replace('_sci.fits', '')
        if vb:
            print " "
            print "Making Tractor image from " + name + "_*.fits:"

        # Read in sci and wht images. Note assumptions about file format:
        sci, invvar, hdr, total_flux = Read_in_data(scifile, varfile, vb)

        if total_flux == 0.0:
            print "No flux found in image from " + scifile
            print "Skipping to next image!"
            continue

        # Initialize a PSF object (single Gaussian by default), first
        # getting FWHM from somewhere. Start with FWHM a little small,
        # then refine it:

        if SURVEY == 'PS1':
            FWHM = lenstractor.PS1_IQ(hdr)
        elif SURVEY == 'KIDS':
            FWHM = lenstractor.KIDS_IQ(hdr)
        else:
            Raise("Unrecognised survey %s" % SURVEY)
        if vb: print "  PSF FWHM =", FWHM, "pixels"

        # MAGIC 0.7 shrinkage factor:
        psf = Initial_PSF(0.7 * FWHM)
        if vb: print psf

        # Now get the photometric calibration from the image header.

        if SURVEY == 'PS1':
            band, photocal = lenstractor.PS1_photocal(hdr)
        elif SURVEY == 'KIDS':
            band, photocal = lenstractor.KIDS_photocal(hdr)
        else:
            Raise("Unrecognised survey %s" % SURVEY)
        if vb: print photocal
        bands.append(band)
        if SURVEY == 'PS1':
            epochs.append(lenstractor.PS1_epoch(hdr))
        elif SURVEY == 'KIDS':
            epochs.append(lenstractor.KIDS_epoch(hdr))
        # Use photocal to return a total magnitude:
        total_mag = photocal.countsToMag(total_flux)
        if vb: print "Total brightness of image (mag):", total_mag
        total_mags.append(total_mag)

        # Set up sky to be varied:
        median = np.median(sci[invvar > 0])
        sky = tractor.ConstantSky(median)
        delta = 0.1 * np.sqrt(1.0 / np.sum(invvar))
        assert delta > 0
        sky.stepsize = delta
        if vb: print sky

        # Get WCS from FITS header:
        if SURVEY == 'PS1':
            wcs = lenstractor.PS1WCS(hdr)
        elif SURVEY == 'KIDS':
            wcs = lenstractor.KIDSWCS(hdr)
        if vb: print wcs

        # Make a tractor Image object out of all this stuff, and add it to the array:
        images.append(
            tractor.Image(data=sci,
                          invvar=invvar,
                          name=name,
                          psf=psf,
                          wcs=wcs,
                          sky=sky,
                          photocal=photocal))

    # Figure out the unique band names and epochs:
    uniqbands = np.unique(np.array(bands))
    if vb:
        print " "
        print "Read in", len(images), "image datasets"
        print "  in", len(uniqbands), "bands:", uniqbands
        print "  at", len(epochs), "epochs"
        print " "

    return images, np.array(total_mags), np.array(bands)
Exemplo n.º 11
0
        photcal = tractor.MagsPhotoCal(band, zpt)

        csky_level_sig = photcal.brightnessToCounts(sky_level_sig)

        ##  The rms of the noise in ADU.
        ##  noise      = galsim.PoissonNoise(rng, sky_level=sky_level_pixel)
        ##  Gaussian approximation for large N.
        ##  noise      = galsim.GaussianNoise(rng, sigma=sky_level_sig)
        ##  Rendered in counts.
        noise = np.random.normal(loc=csky_level_sig,
                                 scale=np.sqrt(csky_level_sig),
                                 size=(H, W))

        tim = tractor.Image(data=np.zeros((H, W), np.float32),
                            inverr=np.ones((H, W), np.float32),
                            psf=psf,
                            wcs=wcs,
                            photcal=photcal)

        ##  _tr            = tractor.Tractor([tim], [src])
        ##  mod            = _tr.getModelImage(0)

        tim.data = tim.data + noise.data  ##  + mod.data
        tims.append(tim)

    cat = tractor.Catalog(src)

    ##
    tr = tractor.Tractor(tims, cat)

    # Evaluate likelihood.
Exemplo n.º 12
0
def read_wise_level1b(basefn, radecroi=None, radecrad=None, filtermap=None,
                      nanomaggies=False, mask_gz=False, unc_gz=False,
                      sipwcs=False, constantInvvar=False,
                      roi=None,
                      zrsigs=[-3, 10],
                      ):
    if filtermap is None:
        filtermap = {}

    intfn = basefn + '-int-1b.fits'
    maskfn = basefn + '-msk-1b.fits'

    if mask_gz:
        maskfn = maskfn + '.gz'
    uncfn = basefn + '-unc-1b.fits'
    if unc_gz:
        uncfn = uncfn + '.gz'

    logger.debug('intensity image   %s' % intfn)
    logger.debug('mask image        %s' % maskfn)
    logger.debug('uncertainty image %s' % uncfn)

    if sipwcs:
        wcs = Sip(intfn, 0)
        twcs = tractor.ConstantFitsWcs(wcs)
    else:
        twcs = tractor.ConstantFitsWcs(intfn)

    # Read enough of the image to get its size
    Fint = fitsio.FITS(intfn)
    H, W = Fint[0].get_info()['dims']

    if radecrad is not None:
        r, d, rad = radecrad
        x, y = twcs.positionToPixel(tractor.RaDecPos(r, d))
        pixrad = rad / (twcs.pixel_scale() / 3600.)
        print('Tractor WCS:', twcs)
        print('RA,Dec,rad', r, d, rad, 'becomes x,y,pixrad', x, y, pixrad)
        roi = (x - pixrad, x + pixrad + 1, y - pixrad, y + pixrad + 1)

    if radecroi is not None:
        ralo, rahi, declo, dechi = radecroi
        xy = [twcs.positionToPixel(tractor.RaDecPos(r, d))
              for r, d in [(ralo, declo), (ralo, dechi), (rahi, declo), (rahi, dechi)]]
        xy = np.array(xy)
        x0, x1 = xy[:, 0].min(), xy[:, 0].max()
        y0, y1 = xy[:, 1].min(), xy[:, 1].max()
        print('RA,Dec ROI', ralo, rahi, declo, dechi,
              'becomes x,y ROI', x0, x1, y0, y1)
        roi = (x0, x1 + 1, y0, y1 + 1)

    if roi is not None:
        x0, x1, y0, y1 = roi
        x0 = int(np.floor(x0))
        x1 = int(np.ceil(x1))
        y0 = int(np.floor(y0))
        y1 = int(np.ceil(y1))
        roi = (x0, x1, y0, y1)
        # Clip to image size...
        x0 = np.clip(x0, 0, W)
        x1 = np.clip(x1, 0, W)
        y0 = np.clip(y0, 0, H)
        y1 = np.clip(y1, 0, H)
        if x0 == x1 or y0 == y1:
            print('ROI is empty')
            return None
        assert(x0 < x1)
        assert(y0 < y1)
        #roi = (x0,x1,y0,y1)
        twcs.setX0Y0(x0, y0)

    else:
        x0, x1, y0, y1 = 0, W, 0, H
        roi = (x0, x1, y0, y1)

    ihdr = Fint[0].read_header()
    data = Fint[0][y0:y1, x0:x1]
    logger.debug('Read %s intensity' % (str(data.shape)))
    band = ihdr['BAND']

    F = fitsio.FITS(uncfn)
    assert(F[0].get_info()['dims'] == [H, W])
    unc = F[0][y0:y1, x0:x1]

    F = fitsio.FITS(maskfn)
    assert(F[0].get_info()['dims'] == [H, W])
    mask = F[0][y0:y1, x0:x1]

    # HACK -- circular Gaussian PSF of fixed size...
    # in arcsec
    fwhms = {1: 6.1, 2: 6.4, 3: 6.5, 4: 12.0}
    # -> sigma in pixels
    sig = fwhms[band] / 2.35 / twcs.pixel_scale()
    # print 'PSF sigma', sig, 'pixels'
    tpsf = tractor.NCircularGaussianPSF([sig], [1.])

    filter = 'w%i' % band
    if filtermap:
        filter = filtermap.get(filter, filter)
    zp = ihdr['MAGZP']
    if nanomaggies:
        photocal = tractor.LinearPhotoCal(tractor.NanoMaggies.zeropointToScale(zp),
                                          band=filter)
    else:
        photocal = tractor.MagsPhotoCal(filter, zp)

    # print 'Image median:', np.median(data)
    # print 'unc median:', np.median(unc)

    sky = np.median(data)
    tsky = tractor.ConstantSky(sky)

    name = 'WISE ' + ihdr['FRSETID'] + ' W%i' % band

    # Mask bits, from
    # http://wise2.ipac.caltech.edu/docs/release/allsky/expsup/sec4_4a.html#maskdef
    # 0 from static mask: excessively noisy due to high dark current alone
    # 1 from static mask: generally noisy [includes bit 0]
    # 2 from static mask: dead or very low responsivity
    # 3 from static mask: low responsivity or low dark current
    # 4 from static mask: high responsivity or high dark current
    # 5 from static mask: saturated anywhere in ramp
    # 6 from static mask: high, uncertain, or unreliable non-linearity
    # 7 from static mask: known broken hardware pixel or excessively noisy responsivity estimate [may include bit 1]
    # 8 reserved
    # 9 broken pixel or negative slope fit value (downlink value = 32767)
    # 10 saturated in sample read 1 (down-link value = 32753)
    # 11 saturated in sample read 2 (down-link value = 32754)
    # 12 saturated in sample read 3 (down-link value = 32755)
    # 13 saturated in sample read 4 (down-link value = 32756)
    # 14 saturated in sample read 5 (down-link value = 32757)
    # 15 saturated in sample read 6 (down-link value = 32758)
    # 16 saturated in sample read 7 (down-link value = 32759)
    # 17 saturated in sample read 8 (down-link value = 32760)
    # 18 saturated in sample read 9 (down-link value = 32761)
    # 19 reserved
    # 20 reserved
    # 21 new/transient bad pixel from dynamic masking
    # 22 reserved
    # 23 reserved
    # 24 reserved
    # 25 reserved
    # 26 non-linearity correction unreliable
    # 27 contains cosmic-ray or outlier that cannot be classified (from temporal outlier rejection in multi-frame pipeline)
    # 28 contains positive or negative spike-outlier
    # 29 reserved
    # 30 reserved
    # 31 not used: sign bit

    goodmask = ((mask & sum([1 << bit for bit in [0, 1, 2, 3, 4, 5, 6, 7, 9,
                                                  10, 11, 12, 13, 14, 15, 16, 17, 18,
                                                  21, 26, 27, 28]])) == 0)
    sigma1 = np.median(unc[goodmask])
    zr = np.array(zrsigs) * sigma1 + sky

    # constant
    cinvvar = np.zeros_like(data)
    cinvvar[goodmask] = 1. / (sigma1**2)
    # varying
    vinvvar = np.zeros_like(data)
    vinvvar[goodmask] = 1. / (unc[goodmask])**2

    bad = np.flatnonzero(np.logical_not(np.isfinite(vinvvar)))
    if len(bad):
        vinvvar.flat[bad] = 0.
        cinvvar.flat[bad] = 0.
        data.flat[bad] = sky

    if constantInvvar:
        invvar = cinvvar
    else:
        invvar = vinvvar

    # avoid NaNs
    data[np.logical_not(goodmask)] = sky

    mjd = ihdr['MJD_OBS']
    time = TAITime(None, mjd=mjd)

    tim = tractor.Image(data=data, invvar=invvar, psf=tpsf, wcs=twcs,
                        sky=tsky, photocal=photocal, time=time, name=name, zr=zr,
                        domask=False)
    tim.extent = [x0, x1, y0, y1]
    tim.sigma1 = sigma1
    #tim.roi = roi

    # FIXME
    tim.maskplane = mask
    tim.uncplane = unc
    tim.goodmask = goodmask

    # carry both around for retrofitting
    tim.vinvvar = vinvvar
    tim.cinvvar = cinvvar

    return tim
Exemplo n.º 13
0
def read_wise_level3(basefn, radecroi=None, filtermap=None,
                     nanomaggies=False):
    if filtermap is None:
        filtermap = {}
    intfn = basefn + '-int-3.fits'
    uncfn = basefn + '-unc-3.fits'

    print('intensity image', intfn)
    print('uncertainty image', uncfn)

    P = pyfits.open(intfn)
    ihdr = P[0].header
    data = P[0].data
    print('Read', data.shape, 'intensity')
    band = ihdr['BAND']

    P = pyfits.open(uncfn)
    unc = P[0].data
    print('Read', unc.shape, 'uncertainty')

    ''' cov:
    BAND    =                    1 / wavelength band number
    WAVELEN =                3.368 / [microns] effective wavelength of band
    COADDID = '3342p000_ab41'      / atlas-image identifier
    MAGZP   =                 20.5 / [mag] relative photometric zero point
    MEDINT  =      4.0289044380188 / [DN] median of intensity pixels
    '''
    ''' int:
    BUNIT   = 'DN      '           / image pixel units
    CTYPE1  = 'RA---SIN'           / Projection type for axis 1
    CTYPE2  = 'DEC--SIN'           / Projection type for axis 2
    CRPIX1  =          2048.000000 / Axis 1 reference pixel at CRVAL1,CRVAL2
    CRPIX2  =          2048.000000 / Axis 2 reference pixel at CRVAL1,CRVAL2
    CDELT1  =  -0.0003819444391411 / Axis 1 scale at CRPIX1,CRPIX2 (deg/pix)
    CDELT2  =   0.0003819444391411 / Axis 2 scale at CRPIX1,CRPIX2 (deg/pix)
    CROTA2  =             0.000000 / Image twist: +axis2 W of N, J2000.0 (deg)
    '''
    ''' unc:
    FILETYPE= '1-sigma uncertainty image' / product description
    '''

    twcs = tractor.WcslibWcs(intfn)
    print('WCS', twcs)
    # twcs.debug()
    print('pixel scale', twcs.pixel_scale())

    # HACK -- circular Gaussian PSF of fixed size...
    # in arcsec
    fwhms = {1: 6.1, 2: 6.4, 3: 6.5, 4: 12.0}
    # -> sigma in pixels
    sig = fwhms[band] / 2.35 / twcs.pixel_scale()
    print('PSF sigma', sig, 'pixels')
    tpsf = tractor.NCircularGaussianPSF([sig], [1.])

    if radecroi is not None:
        ralo, rahi, declo, dechi = radecroi
        xy = [twcs.positionToPixel(tractor.RaDecPos(r, d))
              for r, d in [(ralo, declo), (ralo, dechi), (rahi, declo), (rahi, dechi)]]
        xy = np.array(xy)
        x0, x1 = xy[:, 0].min(), xy[:, 0].max()
        y0, y1 = xy[:, 1].min(), xy[:, 1].max()
        print('RA,Dec ROI', ralo, rahi, declo, dechi,
              'becomes x,y ROI', x0, x1, y0, y1)

        # Clip to image size...
        H, W = data.shape
        x0 = max(0, min(x0, W - 1))
        x1 = max(0, min(x1, W))
        y0 = max(0, min(y0, H - 1))
        y1 = max(0, min(y1, H))
        print(' clipped to', x0, x1, y0, y1)

        data = data[y0:y1, x0:x1]
        unc = unc[y0:y1, x0:x1]
        twcs.setX0Y0(x0, y0)
        print('Cut data to', data.shape)

    else:
        H, W = data.shape
        x0, x1, y0, y1 = 0, W, 0, H

    filt = 'w%i' % band
    if filtermap:
        filt = filtermap.get(filt, filt)
    zp = ihdr['MAGZP']

    if nanomaggies:
        photocal = tractor.LinearPhotoCal(tractor.NanoMaggies.zeropointToScale(zp),
                                          band=filt)
    else:
        photocal = tractor.MagsPhotoCal(filt, zp)

    print('Image median:', np.median(data))
    print('unc median:', np.median(unc))

    sky = np.median(data)
    tsky = tractor.ConstantSky(sky)

    sigma1 = np.median(unc)
    zr = np.array([-3, 10]) * sigma1 + sky

    name = 'WISE ' + ihdr['COADDID'] + ' W%i' % band

    tim = tractor.Image(data=data, invvar=1. / (unc**2), psf=tpsf, wcs=twcs,
                        sky=tsky, photocal=photocal, name=name, zr=zr,
                        domask=False)
    tim.extent = [x0, x1, y0, y1]
    return tim