Esempio n. 1
0
def get_unwise_tractor_image(basedir,
                             tile,
                             band,
                             bandname=None,
                             masked=True,
                             **kwargs):
    '''
    masked: read "-m" images, or "-u"?

    bandname: PhotoCal band name to use: default: "w%i" % band
    '''

    if bandname is None:
        bandname = 'w%i' % band

    mu = 'm' if masked else 'u'

    # Allow multiple colon-separated unwise-coadd directories.
    basedirs = basedir.split(':')
    foundFiles = False
    for basedir in basedirs:
        thisdir = get_unwise_tile_dir(basedir, tile)
        base = os.path.join(thisdir, 'unwise-%s-w%i-' % (tile, band))

        imfn = base + 'img-%s.fits' % mu
        ivfn = base + 'invvar-%s.fits.gz' % mu
        # ppfn = base + 'std-%s.fits.gz'    % mu
        nifn = base + 'n-%s.fits.gz' % mu
        nufn = base + 'n-u.fits.gz'

        if not os.path.exists(imfn):
            print('Does not exist:', imfn)
            continue
        print('Reading', imfn)
        wcs = Tan(imfn)
        twcs = ConstantFitsWcs(wcs)

        F = fitsio.FITS(imfn)
        img = F[0]
        hdr = img.read_header()
        H, W = img.get_info()['dims']
        H, W = int(H), int(W)

        roi = interpret_roi(twcs, (H, W), **kwargs)
        if roi is None:
            # No overlap with ROI
            return None
        # interpret_roi returns None or a tuple; drop the second element in the tuple.
        roi, nil = roi
        (x0, x1, y0, y1) = roi

        wcs = wcs.get_subimage(x0, y0, x1 - x0, y1 - y0)
        twcs = ConstantFitsWcs(wcs)
        roislice = (slice(y0, y1), slice(x0, x1))
        img = img[roislice]

        if not os.path.exists(ivfn) and os.path.exists(
                ivfn.replace('.fits.gz', '.fits')):
            ivfn = ivfn.replace('.fits.gz', '.fits')
        if not os.path.exists(nifn) and os.path.exists(
                nifn.replace('.fits.gz', '.fits')):
            nifn = nifn.replace('.fits.gz', '.fits')
        if not os.path.exists(nufn) and os.path.exists(
                nufn.replace('.fits.gz', '.fits')):
            nufn = nufn.replace('.fits.gz', '.fits')

        if not (os.path.exists(ivfn) and os.path.exists(nifn)
                and os.path.exists(nufn)):
            print('Files do not exist:', ivfn, nifn, nufn)
            continue

        foundFiles = True
        break

    if not foundFiles:
        raise IOError('unWISE files not found in ' + str(basedirs) +
                      ' for tile ' + tile)

    print('Reading', ivfn)
    invvar = fitsio.FITS(ivfn)[0][roislice]

    if band == 4:
        # due to upsampling, effective invvar is smaller (the pixels
        # are correlated)
        invvar *= 0.25

    #print 'Reading', ppfn
    #pp = fitsio.FITS(ppfn)[0][roislice]
    print('Reading', nifn)
    nims = fitsio.FITS(nifn)[0][roislice]

    if nufn == nifn:
        nuims = nims
    else:
        print('Reading', nufn)
        nuims = fitsio.FITS(nufn)[0][roislice]

    #print 'Median # ims:', np.median(nims)
    good = (nims > 0)
    invvar[np.logical_not(good)] = 0.
    sig1 = 1. / np.sqrt(np.median(invvar[good]))

    # Load the average PSF model (generated by wise_psf.py)
    psffn = os.path.join(os.path.dirname(__file__), 'wise-psf-avg.fits')
    print('Reading', psffn)
    P = fits_table(psffn, hdu=band)
    psf = GaussianMixturePSF(P.amp, P.mean, P.var)

    sky = 0.
    tsky = ConstantSky(sky)

    # if opt.errfrac > 0:
    #     nz = (iv > 0)
    #     iv2 = np.zeros_like(invvar)
    #     iv2[nz] = 1./(1./invvar[nz] + (img[nz] * opt.errfrac)**2)
    #     print 'Increasing error estimate by', opt.errfrac, 'of image flux'
    #     invvar = iv2

    tim = Image(data=img,
                invvar=invvar,
                psf=psf,
                wcs=twcs,
                sky=tsky,
                photocal=LinearPhotoCal(1., band=bandname),
                name='unWISE %s W%i' % (tile, band))
    tim.sig1 = sig1
    tim.roi = roi
    tim.nims = nims
    tim.nuims = nuims
    tim.hdr = hdr

    if 'MJDMIN' in hdr and 'MJDMAX' in hdr:
        from tractor.tractortime import TAITime
        tim.mjdmin = hdr['MJDMIN']
        tim.mjdmax = hdr['MJDMAX']
        tim.time = TAITime(None, mjd=(tim.mjdmin + tim.mjdmax) / 2.)

    return tim
Esempio n. 2
0
    def get_tractor_image(self,
                          slc=None,
                          radecpoly=None,
                          gaussPsf=False,
                          pixPsf=False,
                          hybridPsf=False,
                          splinesky=False,
                          nanomaggies=True,
                          subsky=True,
                          tiny=10,
                          dq=True,
                          invvar=True,
                          pixels=True,
                          constant_invvar=False):
        '''
        Returns a tractor.Image ("tim") object for this image.
        
        Options describing a subimage to return:

        - *slc*: y,x slice objects
        - *radecpoly*: numpy array, shape (N,2), RA,Dec polygon describing
            bounding box to select.

        Options determining the PSF model to use:

        - *gaussPsf*: single circular Gaussian PSF based on header FWHM value.
        - *pixPsf*: pixelized PsfEx model.
        - *hybridPsf*: combo pixelized PsfEx + Gaussian approx.

        Options determining the sky model to use:
        
        - *splinesky*: median filter chunks of the image, then spline those.

        Options determining the units of the image:

        - *nanomaggies*: convert the image to be in units of NanoMaggies;
          *tim.zpscale* contains the scale value the image was divided by.

        - *subsky*: instantiate and subtract the initial sky model,
          leaving a constant zero sky model?

        '''
        get_dq = dq
        get_invvar = invvar

        band = self.band
        imh, imw = self.get_image_shape()
        wcs = self.get_wcs()
        x0, y0 = 0, 0
        x1 = x0 + imw
        y1 = y0 + imh

        # Clip to RA,Dec polygon?
        if slc is None and radecpoly is not None:
            from astrometry.util.miscutils import clip_polygon
            imgpoly = [(1, 1), (1, imh), (imw, imh), (imw, 1)]
            ok, tx, ty = wcs.radec2pixelxy(radecpoly[:-1, 0], radecpoly[:-1,
                                                                        1])
            tpoly = zip(tx, ty)
            clip = clip_polygon(imgpoly, tpoly)
            clip = np.array(clip)
            if len(clip) == 0:
                return None
            x0, y0 = np.floor(clip.min(axis=0)).astype(int)
            x1, y1 = np.ceil(clip.max(axis=0)).astype(int)
            slc = slice(y0, y1 + 1), slice(x0, x1 + 1)
            if y1 - y0 < tiny or x1 - x0 < tiny:
                print('Skipping tiny subimage')
                return None
        # Slice?
        if slc is not None:
            sy, sx = slc
            y0, y1 = sy.start, sy.stop
            x0, x1 = sx.start, sx.stop

        # Is part of this image bad?
        old_extent = (x0, x1, y0, y1)
        new_extent = self.get_good_image_slice((x0, x1, y0, y1),
                                               get_extent=True)
        if new_extent != old_extent:
            x0, x1, y0, y1 = new_extent
            print('Applying good subregion of CCD: slice is', x0, x1, y0, y1)
            if x0 >= x1 or y0 >= y1:
                return None
            slc = slice(y0, y1), slice(x0, x1)

        # Read image pixels
        if pixels:
            print('Reading image slice:', slc)
            img, imghdr = self.read_image(header=True, slice=slc)
            self.check_image_header(imghdr)
        else:
            img = np.zeros((imh, imw), np.float32)
            imghdr = self.read_image_header()
            if slc is not None:
                img = img[slc]
        assert (np.all(np.isfinite(img)))

        # Read inverse-variance (weight) map
        if get_invvar:
            invvar = self.read_invvar(slice=slc, clipThresh=0.)
        else:
            invvar = np.ones_like(img)
        assert (np.all(np.isfinite(invvar)))
        if np.all(invvar == 0.):
            print('Skipping zero-invvar image')
            return None
        # Negative invvars (from, eg, fpack decompression noise) cause havoc
        assert (np.all(invvar >= 0.))

        # Read data-quality (flags) map and zero out the invvars of masked pixels
        if get_dq:
            dq = self.read_dq(slice=slc)
            if dq is not None:
                invvar[dq != 0] = 0.
            if np.all(invvar == 0.):
                print('Skipping zero-invvar image (after DQ masking)')
                return None

        # header 'FWHM' is in pixels
        assert (self.fwhm > 0)
        psf_fwhm = self.fwhm
        psf_sigma = psf_fwhm / 2.35
        primhdr = self.read_image_primary_header()

        sky = self.read_sky_model(splinesky=splinesky,
                                  slc=slc,
                                  primhdr=primhdr,
                                  imghdr=imghdr)
        skysig1 = getattr(sky, 'sig1', None)

        midsky = 0.
        if subsky:
            print('Instantiating and subtracting sky model')
            from tractor.sky import ConstantSky
            skymod = np.zeros_like(img)
            sky.addTo(skymod)
            img -= skymod
            midsky = np.median(skymod)
            zsky = ConstantSky(0.)
            zsky.version = getattr(sky, 'version', '')
            zsky.plver = getattr(sky, 'plver', '')
            del skymod
            sky = zsky
            del zsky

        orig_zpscale = zpscale = NanoMaggies.zeropointToScale(self.ccdzpt)
        if nanomaggies:
            # Scale images to Nanomaggies
            img /= zpscale
            invvar = invvar * zpscale**2
            if not subsky:
                sky.scale(1. / zpscale)
            zpscale = 1.

        # Compute 'sig1', scalar typical per-pixel noise
        if get_invvar:
            sig1 = 1. / np.sqrt(np.median(invvar[invvar > 0]))
        elif skysig1 is not None:
            sig1 = skysig1
            if nanomaggies:
                # skysig1 is in the native units
                sig1 /= orig_zpscale
        else:
            # Estimate per-pixel noise via Blanton's 5-pixel MAD
            slice1 = (slice(0, -5, 10), slice(0, -5, 10))
            slice2 = (slice(5, None, 10), slice(5, None, 10))
            mad = np.median(np.abs(img[slice1] - img[slice2]).ravel())
            sig1 = 1.4826 * mad / np.sqrt(2.)
            print('sig1 estimate:', sig1)
            invvar *= (1. / sig1**2)
        assert (np.isfinite(sig1))

        if constant_invvar:
            print('Setting constant invvar', 1. / sig1**2)
            invvar[invvar > 0] = 1. / sig1**2

        if subsky:
            # Warn if the subtracted sky doesn't seem to work well
            # (can happen, eg, if sky calibration product is inconsistent with
            #  the data)
            imgmed = np.median(img[invvar > 0])
            if np.abs(imgmed) > sig1:
                print('WARNING: image median', imgmed, 'is more than 1 sigma',
                      'away from zero!')

        # tractor WCS object
        twcs = self.get_tractor_wcs(wcs,
                                    x0,
                                    y0,
                                    primhdr=primhdr,
                                    imghdr=imghdr)
        if hybridPsf:
            pixPsf = False
        psf = self.read_psf_model(x0,
                                  y0,
                                  gaussPsf=gaussPsf,
                                  pixPsf=pixPsf,
                                  hybridPsf=hybridPsf,
                                  psf_sigma=psf_sigma,
                                  cx=(x0 + x1) / 2.,
                                  cy=(y0 + y1) / 2.)

        tim = Image(img,
                    invvar=invvar,
                    wcs=twcs,
                    psf=psf,
                    photocal=LinearPhotoCal(zpscale, band=band),
                    sky=sky,
                    name=self.name + ' ' + band)
        assert (np.all(np.isfinite(tim.getInvError())))

        # PSF norm
        psfnorm = self.psf_norm(tim)

        # Galaxy-detection norm
        tim.band = band
        galnorm = self.galaxy_norm(tim)

        # CP (DECam) images include DATE-OBS and MJD-OBS, in UTC.
        import astropy.time
        mjd_tai = astropy.time.Time(self.mjdobs, format='mjd',
                                    scale='utc').tai.mjd
        tim.time = TAITime(None, mjd=mjd_tai)
        tim.slice = slc
        tim.zpscale = orig_zpscale
        tim.midsky = midsky
        tim.sig1 = sig1
        tim.psf_fwhm = psf_fwhm
        tim.psf_sigma = psf_sigma
        tim.propid = self.propid
        tim.psfnorm = psfnorm
        tim.galnorm = galnorm
        tim.sip_wcs = wcs
        tim.x0, tim.y0 = int(x0), int(y0)
        tim.imobj = self
        tim.primhdr = primhdr
        tim.hdr = imghdr
        tim.plver = primhdr.get('PLVER', '').strip()
        tim.skyver = (getattr(sky, 'version', ''), getattr(sky, 'plver', ''))
        tim.wcsver = (getattr(wcs, 'version', ''), getattr(wcs, 'plver', ''))
        tim.psfver = (getattr(psf, 'version', ''), getattr(psf, 'plver', ''))
        if get_dq:
            tim.dq = dq
        tim.dq_saturation_bits = self.dq_saturation_bits
        subh, subw = tim.shape
        tim.subwcs = tim.sip_wcs.get_subimage(tim.x0, tim.y0, subw, subh)
        return tim
Esempio n. 3
0
def stage_fit_on_coadds(survey=None,
                        targetwcs=None,
                        pixscale=None,
                        bands=None,
                        tims=None,
                        brickname=None,
                        version_header=None,
                        coadd_tiers=None,
                        apodize=True,
                        subsky=True,
                        ubercal_sky=False,
                        subsky_radii=None,
                        nsatur=None,
                        fitoncoadds_reweight_ivar=True,
                        plots=False,
                        plots2=False,
                        ps=None,
                        coadd_bw=False,
                        W=None,
                        H=None,
                        brick=None,
                        blobs=None,
                        lanczos=True,
                        ccds=None,
                        write_metrics=True,
                        mp=None,
                        record_event=None,
                        **kwargs):

    from legacypipe.coadds import make_coadds
    from legacypipe.bits import DQ_BITS
    from legacypipe.survey import LegacySurveyWcs
    from legacypipe.coadds import get_coadd_headers

    from tractor.image import Image
    from tractor.basics import LinearPhotoCal
    from tractor.sky import ConstantSky
    from tractor.psf import PixelizedPSF
    from tractor.tractortime import TAITime
    import astropy.time
    import fitsio
    if plots or plots2:
        import pylab as plt
        from legacypipe.survey import get_rgb

    # Custom sky-subtraction for large galaxies.
    skydict = {}
    if not subsky:
        if ubercal_sky:
            from astrometry.util.plotutils import PlotSequence
            ps = PlotSequence('fitoncoadds-{}'.format(brickname))
            tims, skydict = ubercal_skysub(tims,
                                           targetwcs,
                                           survey,
                                           brickname,
                                           bands,
                                           mp,
                                           subsky_radii=subsky_radii,
                                           plots=True,
                                           plots2=False,
                                           ps=ps,
                                           verbose=True)
        else:
            print('Skipping sky-subtraction entirely.')

    # Create coadds and then build custom tims from them.
    for tim in tims:
        ie = tim.inverr
        if np.any(ie < 0):
            print('Negative inverse error in image {}'.format(tim.name))

    CC = []
    if coadd_tiers:
        # Sort by band and sort them into tiers.
        tiers = [[] for i in range(coadd_tiers)]
        for b in bands:
            btims = []
            seeing = []
            for tim in tims:
                if tim.band != b:
                    continue
                btims.append(tim)
                seeing.append(tim.psf_fwhm * tim.imobj.pixscale)
            I = np.argsort(seeing)
            btims = [btims[i] for i in I]
            seeing = [seeing[i] for i in I]
            N = min(coadd_tiers, len(btims))
            splits = np.round(np.arange(N + 1) * float(len(btims)) /
                              N).astype(int)
            print('Splitting', len(btims), 'images into', N, 'tiers: splits:',
                  splits)
            print('Seeing limits:',
                  [seeing[min(s,
                              len(seeing) - 1)] for s in splits])
            for s0, s1, tt in zip(splits, splits[1:], tiers):
                tt.extend(btims[s0:s1])

        for itier, tier in enumerate(tiers):
            print('Producing coadds for tier', (itier + 1))
            C = make_coadds(
                tier,
                bands,
                targetwcs,
                detmaps=True,
                ngood=True,
                lanczos=lanczos,
                allmasks=True,
                anymasks=True,
                psf_images=True,
                nsatur=2,
                mp=mp,
                plots=plots2,
                ps=ps,  # note plots2 here!
                callback=None)
            if plots:
                plt.clf()
                for iband, (band, psf) in enumerate(zip(bands, C.psf_imgs)):
                    plt.subplot(1, len(bands), iband + 1)
                    plt.imshow(psf, interpolation='nearest', origin='lower')
                    plt.title('Coadd PSF image: band %s' % band)
                plt.suptitle('Tier %i' % (itier + 1))
                ps.savefig()

                # for band,img in zip(bands, C.coimgs):
                #     plt.clf()
                #     plt.imshow(img,
                plt.clf()
                plt.imshow(get_rgb(C.coimgs, bands), origin='lower')
                plt.title('Tier %i' % (itier + 1))
                ps.savefig()
            CC.append(C)

    else:
        C = make_coadds(
            tims,
            bands,
            targetwcs,
            detmaps=True,
            ngood=True,
            lanczos=lanczos,
            allmasks=True,
            anymasks=True,
            psf_images=True,
            mp=mp,
            plots=plots2,
            ps=ps,  # note plots2 here!
            callback=None)
        CC.append(C)

    cotims = []
    for C in CC:
        if plots2:
            for band, iv in zip(bands, C.cowimgs):
                pass
                # plt.clf()
                # plt.imshow(np.sqrt(iv), interpolation='nearest', origin='lower')
                # plt.title('Coadd Inverr: band %s' % band)
                # ps.savefig()

            for band, psf in zip(bands, C.psf_imgs):
                plt.clf()
                plt.imshow(psf, interpolation='nearest', origin='lower')
                plt.title('Coadd PSF image: band %s' % band)
                ps.savefig()

            for band, img, iv in zip(bands, C.coimgs, C.cowimgs):
                from scipy.ndimage.filters import gaussian_filter
                # plt.clf()
                # plt.hist((img * np.sqrt(iv))[iv>0], bins=50, range=(-5,8), log=True)
                # plt.title('Coadd pixel values (sigmas): band %s' % band)
                # ps.savefig()

                psf_sigma = np.mean([
                    (tim.psf_sigma * tim.imobj.pixscale / pixscale)
                    for tim in tims if tim.band == band
                ])
                gnorm = 1. / (2. * np.sqrt(np.pi) * psf_sigma)
                psfnorm = gnorm  #np.sqrt(np.sum(psfimg**2))
                detim = gaussian_filter(img, psf_sigma) / psfnorm**2
                cosig1 = 1. / np.sqrt(np.median(iv[iv > 0]))
                detsig1 = cosig1 / psfnorm
                # plt.clf()
                # plt.subplot(2,1,1)
                # plt.hist(detim.ravel() / detsig1, bins=50, range=(-5,8), log=True)
                # plt.title('Coadd detection map values / sig1 (sigmas): band %s' % band)
                # plt.subplot(2,1,2)
                # plt.hist(detim.ravel() / detsig1, bins=50, range=(-5,8))
                # ps.savefig()

                # # as in detection.py
                # detiv = np.zeros_like(detim) + (1. / detsig1**2)
                # detiv[iv == 0] = 0.
                # detiv = gaussian_filter(detiv, psf_sigma)
                #
                # plt.clf()
                # plt.hist((detim * np.sqrt(detiv)).ravel(), bins=50, range=(-5,8), log=True)
                # plt.title('Coadd detection map values / detie (sigmas): band %s' % band)
                # ps.savefig()

        for iband, (band, img, iv, allmask, anymask, psfimg) in enumerate(
                zip(bands, C.coimgs, C.cowimgs, C.allmasks, C.anymasks,
                    C.psf_imgs)):
            mjd = np.mean(
                [tim.imobj.mjdobs for tim in tims if tim.band == band])
            mjd_tai = astropy.time.Time(mjd, format='mjd', scale='utc').tai.mjd
            tai = TAITime(None, mjd=mjd_tai)
            twcs = LegacySurveyWcs(targetwcs, tai)
            #print('PSF sigmas (in pixels) for band', band, ':',
            #      ['%.2f' % tim.psf_sigma for tim in tims if tim.band == band])
            print(
                'PSF sigmas in coadd pixels:', ', '.join([
                    '%.2f' % (tim.psf_sigma * tim.imobj.pixscale / pixscale)
                    for tim in tims if tim.band == band
                ]))
            psf_sigma = np.mean([
                (tim.psf_sigma * tim.imobj.pixscale / pixscale) for tim in tims
                if tim.band == band
            ])
            print('Using average PSF sigma', psf_sigma)

            psf = PixelizedPSF(psfimg)
            gnorm = 1. / (2. * np.sqrt(np.pi) * psf_sigma)

            psfnorm = np.sqrt(np.sum(psfimg**2))
            print('Gaussian PSF norm', gnorm, 'vs pixelized', psfnorm)

            # if plots:
            #     from collections import Counter
            #     plt.clf()
            #     plt.imshow(mask, interpolation='nearest', origin='lower')
            #     plt.colorbar()
            #     plt.title('allmask')
            #     ps.savefig()
            #     print('allmask for band', band, ': values:', Counter(mask.ravel()))
            # Scale invvar to take into account that we have resampled (~double-counted) pixels
            tim_pixscale = np.mean(
                [tim.imobj.pixscale for tim in tims if tim.band == band])
            cscale = tim_pixscale / pixscale
            print('average tim pixel scale / coadd scale:', cscale)
            iv /= cscale**2

            if fitoncoadds_reweight_ivar:
                # We first tried setting the invvars constant per tim -- this
                # makes things worse, since we *remove* the lowered invvars at
                # the cores of galaxies.
                #
                # Here we're hacking the relative weights -- squaring the
                # weights but then making the median the same, ie, squaring
                # the dynamic range or relative weights -- ie, downweighting
                # the cores even more than they already are from source
                # Poisson terms.
                median_iv = np.median(iv[iv > 0])
                assert (median_iv > 0)
                iv = iv * np.sqrt(iv) / np.sqrt(median_iv)
                assert (np.all(np.isfinite(iv)))
                assert (np.all(iv >= 0))

            cotim = Image(img,
                          invvar=iv,
                          wcs=twcs,
                          psf=psf,
                          photocal=LinearPhotoCal(1., band=band),
                          sky=ConstantSky(0.),
                          name='coadd-' + band)
            cotim.band = band
            cotim.subwcs = targetwcs
            cotim.psf_sigma = psf_sigma
            cotim.sig1 = 1. / np.sqrt(np.median(iv[iv > 0]))

            # Often, SATUR masks on galaxies / stars are surrounded by BLEED pixels.  Soak these into
            # the SATUR mask.
            from scipy.ndimage.morphology import binary_dilation
            anymask |= np.logical_and(((anymask & DQ_BITS['bleed']) > 0),
                                      binary_dilation(
                                          ((anymask & DQ_BITS['satur']) > 0),
                                          iterations=10)) * DQ_BITS['satur']

            # Saturated in any image -> treat as saturated in coadd
            # (otherwise you get weird systematics in the weighted coadds, and weird source detection!)
            mask = allmask
            mask[(anymask & DQ_BITS['satur'] > 0)] |= DQ_BITS['satur']

            if coadd_tiers:
                # nsatur -- reset SATUR bit
                mask &= ~DQ_BITS['satur']
                mask |= DQ_BITS['satur'] * C.satmaps[iband]

            cotim.dq = mask
            cotim.dq_saturation_bits = DQ_BITS['satur']
            cotim.psfnorm = gnorm
            cotim.galnorm = 1.0  # bogus!
            cotim.imobj = Duck()
            cotim.imobj.fwhm = 2.35 * psf_sigma
            cotim.imobj.pixscale = pixscale
            cotim.time = tai
            cotim.primhdr = fitsio.FITSHDR()
            get_coadd_headers(cotim.primhdr, tims, band, coadd_headers=skydict)
            cotims.append(cotim)

            if plots:
                plt.clf()
                bitmap = dict([(v, k) for k, v in DQ_BITS.items()])
                k = 1
                for i in range(12):
                    bitval = 1 << i
                    if not bitval in bitmap:
                        continue
                    # only 9 bits are actually used
                    plt.subplot(3, 3, k)
                    k += 1
                    plt.imshow((cotim.dq & bitval) > 0,
                               vmin=0,
                               vmax=1.5,
                               cmap='hot',
                               origin='lower')
                    plt.title(bitmap[bitval])
                plt.suptitle('Coadd mask planes %s band' % band)
                ps.savefig()

                plt.clf()
                h, w = cotim.shape
                rgb = np.zeros((h, w, 3), np.uint8)
                rgb[:, :, 0] = (cotim.dq & DQ_BITS['satur'] > 0) * 255
                rgb[:, :, 1] = (cotim.dq & DQ_BITS['bleed'] > 0) * 255
                plt.imshow(rgb, origin='lower')
                plt.suptitle('Coadd DQ band %s: red = SATUR, green = BLEED' %
                             band)
                ps.savefig()

            # Save an image of the coadd PSF
            # copy version_header before modifying it.
            hdr = fitsio.FITSHDR()
            for r in version_header.records():
                hdr.add_record(r)
            hdr.add_record(
                dict(name='IMTYPE',
                     value='coaddpsf',
                     comment='LegacySurveys image type'))
            hdr.add_record(
                dict(name='BAND', value=band,
                     comment='Band of this coadd/PSF'))
            hdr.add_record(
                dict(name='PSF_SIG',
                     value=psf_sigma,
                     comment='Average PSF sigma (coadd pixels)'))
            hdr.add_record(
                dict(name='PIXSCAL',
                     value=pixscale,
                     comment='Pixel scale of this PSF (arcsec)'))
            hdr.add_record(
                dict(name='INPIXSC',
                     value=tim_pixscale,
                     comment='Native image pixscale scale (average, arcsec)'))
            hdr.add_record(
                dict(name='MJD', value=mjd, comment='Average MJD for coadd'))
            hdr.add_record(
                dict(name='MJD_TAI',
                     value=mjd_tai,
                     comment='Average MJD (in TAI) for coadd'))
            with survey.write_output('copsf', brick=brickname,
                                     band=band) as out:
                out.fits.write(psfimg, header=hdr)

    # EVIL
    return dict(tims=cotims, coadd_headers=skydict)
Esempio n. 4
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'])
Esempio n. 5
0
    def get_tractor_image(self, slc=None, radecpoly=None,
                          gaussPsf=False, const2psf=False, pixPsf=False,
                          splinesky=False,
                          nanomaggies=True, subsky=True, tiny=5,
                          dq=True, invvar=True, pixels=True):
        '''
        Returns a tractor.Image ("tim") object for this image.
        
        Options describing a subimage to return:

        - *slc*: y,x slice objects
        - *radecpoly*: numpy array, shape (N,2), RA,Dec polygon describing bounding box to select.

        Options determining the PSF model to use:

        - *gaussPsf*: single circular Gaussian PSF based on header FWHM value.
        - *const2Psf*: 2-component general Gaussian fit to PsfEx model at image center.
        - *pixPsf*: pixelized PsfEx model at image center.

        Options determining the sky model to use:
        
        - *splinesky*: median filter chunks of the image, then spline those.

        Options determining the units of the image:

        - *nanomaggies*: convert the image to be in units of NanoMaggies;
          *tim.zpscale* contains the scale value the image was divided by.

        - *subsky*: instantiate and subtract the initial sky model,
          leaving a constant zero sky model?

        '''
        from astrometry.util.miscutils import clip_polygon
        get_dq = dq
        get_invvar = invvar
        
        band = self.band
        imh,imw = self.get_image_shape()
        wcs = self.get_wcs()
        x0,y0 = 0,0
        x1 = x0 + imw
        y1 = y0 + imh
        #if don't comment out tim = NoneType b/c clips all pixels out
        #if slc is None and radecpoly is not None:
        #    imgpoly = [(1,1),(1,imh),(imw,imh),(imw,1)]
        #    ok,tx,ty = wcs.radec2pixelxy(radecpoly[:-1,0], radecpoly[:-1,1])
        #    tpoly = zip(tx,ty)
        #    clip = clip_polygon(imgpoly, tpoly)
        #    clip = np.array(clip)
        #    if len(clip) == 0:
        #        return None
        #    x0,y0 = np.floor(clip.min(axis=0)).astype(int)
        #    x1,y1 = np.ceil (clip.max(axis=0)).astype(int)
        #    slc = slice(y0,y1+1), slice(x0,x1+1)
        #    if y1 - y0 < tiny or x1 - x0 < tiny:
        #        print('Skipping tiny subimage')
        #        return None
        #if slc is not None:
        #    sy,sx = slc
        #    y0,y1 = sy.start, sy.stop
        #    x0,x1 = sx.start, sx.stop

        #old_extent = (x0,x1,y0,y1)
        #new_extent = self.get_good_image_slice((x0,x1,y0,y1), get_extent=True)
        #if new_extent != old_extent:
        #    x0,x1,y0,y1 = new_extent
        #    print('Applying good subregion of CCD: slice is', x0,x1,y0,y1)
        #    if x0 >= x1 or y0 >= y1:
        #        return None
        #    slc = slice(y0,y1), slice(x0,x1)
        if pixels:
            print('Reading image slice:', slc)
            img,imghdr = self.read_image(header=True, slice=slc)
            #print('SATURATE is', imghdr.get('SATURATE', None))
            #print('Max value in image is', img.max())
            # check consistency... something of a DR1 hangover
            #e = imghdr['EXTNAME']
            #assert(e.strip() == self.ccdname.strip())
        else:
            img = np.zeros((imh, imw))
            imghdr = dict()
            if slc is not None:
                img = img[slc]
            
        if get_invvar:
            invvar = self.read_invvar(slice=slc, clipThresh=0.)
        else:
            invvar = np.ones_like(img)
            
        if get_dq:
            dq = self.read_dq(slice=slc)
            invvar[dq != 0] = 0.
        if np.all(invvar == 0.):
            print('Skipping zero-invvar image')
            return None
        assert(np.all(np.isfinite(img)))
        assert(np.all(np.isfinite(invvar)))
        assert(not(np.all(invvar == 0.)))

        # header 'FWHM' is in pixels
        # imghdr['FWHM']
        psf_fwhm = self.fwhm 
        psf_sigma = psf_fwhm / 2.35
        primhdr = self.read_image_primary_header()

        sky = self.read_sky_model(splinesky=splinesky, slc=slc)
        midsky = 0.
        if subsky:
            print('Instantiating and subtracting sky model...')
            from tractor.sky import ConstantSky
            skymod = np.zeros_like(img)
            sky.addTo(skymod)
            img -= skymod
            midsky = np.median(skymod)
            zsky = ConstantSky(0.)
            zsky.version = sky.version
            zsky.plver = sky.plver
            del skymod
            del sky
            sky = zsky
            del zsky

        magzp = self.survey.get_zeropoint_for(self)
        orig_zpscale = zpscale = NanoMaggies.zeropointToScale(magzp)
        if nanomaggies:
            # Scale images to Nanomaggies
            img /= zpscale
            invvar *= zpscale**2
            if not subsky:
                sky.scale(1./zpscale)
            zpscale = 1.

        assert(np.sum(invvar > 0) > 0)
        if get_invvar:
            sig1 = 1./np.sqrt(np.median(invvar[invvar > 0]))
        else:
            # Estimate from the image?
            # # Estimate per-pixel noise via Blanton's 5-pixel MAD
            slice1 = (slice(0,-5,10),slice(0,-5,10))
            slice2 = (slice(5,None,10),slice(5,None,10))
            mad = np.median(np.abs(img[slice1] - img[slice2]).ravel())
            sig1 = 1.4826 * mad / np.sqrt(2.)
            print('sig1 estimate:', sig1)
            invvar *= (1. / sig1**2)
            
        assert(np.all(np.isfinite(img)))
        assert(np.all(np.isfinite(invvar)))
        assert(np.isfinite(sig1))

        if subsky:
            ##
            imgmed = np.median(img[invvar>0])
            if np.abs(imgmed) > sig1:
                print('WARNING: image median', imgmed, 'is more than 1 sigma away from zero!')
                # Boom!
                assert(False)

        twcs = ConstantFitsWcs(wcs)
        if x0 or y0:
            twcs.setX0Y0(x0,y0)

        #print('gaussPsf:', gaussPsf, 'pixPsf:', pixPsf, 'const2psf:', const2psf)
        psf = self.read_psf_model(x0, y0, gaussPsf=gaussPsf, pixPsf=pixPsf,
                                  psf_sigma=psf_sigma,
                                  cx=(x0+x1)/2., cy=(y0+y1)/2.)

        tim = Image(img, invvar=invvar, wcs=twcs, psf=psf,
                    photocal=LinearPhotoCal(zpscale, band=band),
                    sky=sky, name=self.name + ' ' + band)
        assert(np.all(np.isfinite(tim.getInvError())))
        
        # PSF norm
        psfnorm = self.psf_norm(tim)
        print('PSF norm', psfnorm, 'vs Gaussian',
              1./(2. * np.sqrt(np.pi) * psf_sigma))

        # Galaxy-detection norm
        tim.band = band
        galnorm = self.galaxy_norm(tim)
        print('Galaxy norm:', galnorm)
        
        # CP (DECam) images include DATE-OBS and MJD-OBS, in UTC.
        import astropy.time
        #mjd_utc = mjd=primhdr.get('MJD-OBS', 0)
        mjd_tai = astropy.time.Time(primhdr['DATE-OBS']).tai.mjd
        tim.slice = slc
        tim.time = TAITime(None, mjd=mjd_tai)
        tim.zpscale = orig_zpscale
        tim.midsky = midsky
        tim.sig1 = sig1
        tim.psf_fwhm = psf_fwhm
        tim.psf_sigma = psf_sigma
        tim.propid = self.propid
        tim.psfnorm = psfnorm
        tim.galnorm = galnorm
        tim.sip_wcs = wcs
        tim.x0,tim.y0 = int(x0),int(y0)
        tim.imobj = self
        tim.primhdr = primhdr
        tim.hdr = imghdr
        tim.plver = str(primhdr['PTFVERSN']).strip()
        tim.skyver = (sky.version, sky.plver)
        tim.wcsver = ('-1','-1') #wcs.version, wcs.plver)
        tim.psfver = (psf.version, psf.plver)
        if get_dq:
            tim.dq = dq
        tim.dq_saturation_bits = 0
        tim.saturation = imghdr.get('SATURATE', None)
        tim.satval = tim.saturation or 0.
        if subsky:
            tim.satval -= midsky
        if nanomaggies:
            tim.satval /= orig_zpscale
        subh,subw = tim.shape
        tim.subwcs = tim.sip_wcs.get_subimage(tim.x0, tim.y0, subw, subh)
        return tim