Example #1
0
def get_tractor_image_dr7(run, camcol, field, bandname,
                          sdssobj=None, release='DR7',
                          retrieve=True, curl=False,
                          psf='kl-gm', useMags=False,
                          roi=None,
                          roiradecsize=None,
                          roiradecbox=None,
                          nanomaggies=False,
                          savepsfimg=None, zrange=[-3, 10]):
    '''
    Creates a tractor.Image given an SDSS field identifier.

    If not None, roi = (x0, x1, y0, y1) defines a region-of-interest
    in the image, in zero-indexed pixel coordinates.  x1,y1 are
    NON-inclusive; roi=(0,100,0,100) will yield a 100 x 100 image.

    psf can be:
      "dg" for double-Gaussian
      "kl-gm" for SDSS KL-decomposition approximated as a Gaussian mixture

    "roiradecsize" = (ra, dec, half-size in pixels) indicates that you
    want to grab a ROI around the given RA,Dec.

    Returns:
      (tractor.Image, dict)

    dict contains useful details like:
      'sky'
      'skysig'
    '''
    from astrometry.sdss import DR7, band_index

    if sdssobj is None:
        # Ugly
        if release != 'DR7':
            raise RuntimeError('We only support DR7 currently')
        sdss = DR7(curl=curl)
    else:
        sdss = sdssobj

    valid_psf = ['dg', 'kl-gm']
    if psf not in valid_psf:
        raise RuntimeError('PSF must be in ' + str(valid_psf))
    # FIXME
    rerun = 0

    bandnum = band_index(bandname)

    _check_sdss_files(sdss, run, camcol, field, bandname,
                      ['fpC', 'tsField', 'psField', 'fpM'],
                      retrieve=retrieve)
    fpC = sdss.readFpC(run, camcol, field, bandname)
    hdr = fpC.getHeader()
    fpC = fpC.getImage()
    fpC = fpC.astype(np.float32) - sdss.softbias
    image = fpC
    (H, W) = image.shape

    info = dict()
    tai = hdr.get('TAI')
    stripe = hdr.get('STRIPE')
    strip = hdr.get('STRIP')
    obj = hdr.get('OBJECT')
    info.update(tai=tai, stripe=stripe, strip=strip, object=obj, hdr=hdr)

    tsf = sdss.readTsField(run, camcol, field, rerun)
    astrans = tsf.getAsTrans(bandnum)
    wcs = SdssWcs(astrans)
    #print('Created SDSS Wcs:', wcs)

    X = interpret_roi(wcs, (H, W), roi=roi, roiradecsize=roiradecsize,
                      roiradecbox=roiradecbox)
    if X is None:
        return None, None
    roi, hasroi = X
    info.update(roi=roi)
    x0, x1, y0, y1 = roi

    # Mysterious half-pixel shift.  asTrans pixel coordinates?
    wcs.setX0Y0(x0 + 0.5, y0 + 0.5)

    if nanomaggies:
        zp = tsf.get_zeropoint(bandnum)
        photocal = LinearPhotoCal(NanoMaggies.zeropointToScale(zp),
                                  band=bandname)
    elif useMags:
        photocal = SdssMagsPhotoCal(tsf, bandname)
    else:
        photocal = SdssFluxPhotoCal()

    psfield = sdss.readPsField(run, camcol, field)
    sky = psfield.getSky(bandnum)
    skysig = sqrt(sky)
    skyobj = ConstantSky(sky)
    zr = sky + np.array(zrange) * skysig
    info.update(sky=sky, skysig=skysig, zr=zr)

    fpM = sdss.readFpM(run, camcol, field, bandname)
    gain = psfield.getGain(bandnum)
    darkvar = psfield.getDarkVariance(bandnum)
    skyerr = psfield.getSkyErr(bandnum)
    invvar = sdss.getInvvar(fpC, fpM, gain, darkvar, sky, skyerr)

    dgpsf = psfield.getDoubleGaussian(bandnum, normalize=True)
    info.update(dgpsf=dgpsf)

    if roi is not None:
        roislice = (slice(y0, y1), slice(x0, x1))
        image = image[roislice].copy()
        invvar = invvar[roislice].copy()

    if psf == 'kl-gm':
        from emfit import em_fit_2d
        from fitpsf import em_init_params

        # Create Gaussian mixture model PSF approximation.
        H, W = image.shape
        klpsf = psfield.getPsfAtPoints(bandnum, x0 + W / 2, y0 + H / 2)
        S = klpsf.shape[0]
        # number of Gaussian components
        K = 3
        w, mu, sig = em_init_params(K, None, None, None)
        II = klpsf.copy()
        II /= II.sum()
        # HIDEOUS HACK
        II = np.maximum(II, 0)
        #print('Multi-Gaussian PSF fit...')
        xm, ym = -(S / 2), -(S / 2)
        if savepsfimg is not None:
            plt.clf()
            plt.imshow(II, interpolation='nearest', origin='lower')
            plt.title('PSF image to fit with EM')
            plt.savefig(savepsfimg)
        res = em_fit_2d(II, xm, ym, w, mu, sig)
        print('em_fit_2d result:', res)
        if res == 0:
            # print('w,mu,sig', w,mu,sig)
            mypsf = GaussianMixturePSF(w, mu, sig)
            mypsf.computeRadius()
        else:
            # Failed!  Return 'dg' model instead?
            print('PSF model fit', psf, 'failed!  Returning DG model instead')
            psf = 'dg'
    if psf == 'dg':
        print('Creating double-Gaussian PSF approximation')
        (a, s1, b, s2) = dgpsf
        mypsf = NCircularGaussianPSF([s1, s2], [a, b])

    timg = Image(data=image, invvar=invvar, psf=mypsf, wcs=wcs,
                 sky=skyobj, photocal=photocal,
                 name=('SDSS (r/c/f/b=%i/%i/%i/%s)' %
                       (run, camcol, field, bandname)))
    timg.zr = zr
    return timg, info
Example #2
0
def get_tractor_image(run, camcol, field, bandname, 
                      sdssobj=None, release='DR7',
                      retrieve=True, curl=False,
                      psf='kl-gm', useMags=False,
                      roi=None,
                      roiradecsize=None,
                      roiradecbox=None,
                      nanomaggies=False,
                      savepsfimg=None, zrange=[-3,10]):
    '''
    Creates a tractor.Image given an SDSS field identifier.

    If not None, roi = (x0, x1, y0, y1) defines a region-of-interest
    in the image, in zero-indexed pixel coordinates.  x1,y1 are
    NON-inclusive; roi=(0,100,0,100) will yield a 100 x 100 image.
    
    psf can be:
      "dg" for double-Gaussian
      "kl-gm" for SDSS KL-decomposition approximated as a Gaussian mixture

    "roiradecsize" = (ra, dec, half-size in pixels) indicates that you
    want to grab a ROI around the given RA,Dec.

    Returns:
      (tractor.Image, dict)

    dict contains useful details like:
      'sky'
      'skysig'
    '''
    from astrometry.sdss import DR7, band_index

    if sdssobj is None:
        # Ugly
        if release != 'DR7':
            raise RuntimeError('We only support DR7 currently')
        sdss = DR7(curl=curl)
    else:
        sdss = sdssobj

    valid_psf = ['dg', 'kl-gm']
    if psf not in valid_psf:
        raise RuntimeError('PSF must be in ' + str(valid_psf))
    # FIXME
    rerun = 0

    bandnum = band_index(bandname)

    _check_sdss_files(sdss, run, camcol, field, bandname,
                      ['fpC', 'tsField', 'psField', 'fpM'],
                      retrieve=retrieve)
    fpC = sdss.readFpC(run, camcol, field, bandname)
    hdr = fpC.getHeader()
    fpC = fpC.getImage()
    fpC = fpC.astype(np.float32) - sdss.softbias
    image = fpC
    (H,W) = image.shape

    info = dict()
    tai = hdr.get('TAI')
    stripe = hdr.get('STRIPE')
    strip = hdr.get('STRIP')
    obj = hdr.get('OBJECT')
    info.update(tai=tai, stripe=stripe, strip=strip, object=obj, hdr=hdr)

    tsf = sdss.readTsField(run, camcol, field, rerun)
    astrans = tsf.getAsTrans(bandnum)
    wcs = SdssWcs(astrans)
    #print('Created SDSS Wcs:', wcs)

    X = interpret_roi(wcs, (H,W), roi=roi, roiradecsize=roiradecsize,
                      roiradecbox=roiradecbox)
    if X is None:
        return None,None
    roi,hasroi = X
    info.update(roi=roi)
    x0,x1,y0,y1 = roi
        
    # Mysterious half-pixel shift.  asTrans pixel coordinates?
    wcs.setX0Y0(x0 + 0.5, y0 + 0.5)

    if nanomaggies:
        zp = tsf.get_zeropoint(bandnum)
        photocal = LinearPhotoCal(NanoMaggies.zeropointToScale(zp),
                                  band=bandname)
    elif useMags:
        photocal = SdssMagsPhotoCal(tsf, bandname)
    else:
        photocal = SdssFluxPhotoCal()

    psfield = sdss.readPsField(run, camcol, field)
    sky = psfield.getSky(bandnum)
    skysig = sqrt(sky)
    skyobj = ConstantSky(sky)
    zr = sky + np.array(zrange) * skysig
    info.update(sky=sky, skysig=skysig, zr=zr)

    fpM = sdss.readFpM(run, camcol, field, bandname)
    gain = psfield.getGain(bandnum)
    darkvar = psfield.getDarkVariance(bandnum)
    skyerr = psfield.getSkyErr(bandnum)
    invvar = sdss.getInvvar(fpC, fpM, gain, darkvar, sky, skyerr)

    dgpsf = psfield.getDoubleGaussian(bandnum, normalize=True)
    info.update(dgpsf=dgpsf)
    
    if roi is not None:
        roislice = (slice(y0,y1), slice(x0,x1))
        image = image[roislice].copy()
        invvar = invvar[roislice].copy()

    if psf == 'kl-gm':
        from emfit import em_fit_2d
        from fitpsf import em_init_params

        # Create Gaussian mixture model PSF approximation.
        H,W = image.shape
        klpsf = psfield.getPsfAtPoints(bandnum, x0+W/2, y0+H/2)
        S = klpsf.shape[0]
        # number of Gaussian components
        K = 3
        w,mu,sig = em_init_params(K, None, None, None)
        II = klpsf.copy()
        II /= II.sum()
        # HIDEOUS HACK
        II = np.maximum(II, 0)
        #print('Multi-Gaussian PSF fit...')
        xm,ym = -(S/2), -(S/2)
        if savepsfimg is not None:
            plt.clf()
            plt.imshow(II, interpolation='nearest', origin='lower')
            plt.title('PSF image to fit with EM')
            plt.savefig(savepsfimg)
        res = em_fit_2d(II, xm, ym, w, mu, sig)
        print('em_fit_2d result:', res)
        if res == 0:
            # print('w,mu,sig', w,mu,sig)
            mypsf = GaussianMixturePSF(w, mu, sig)
            mypsf.computeRadius()
        else:
            # Failed!  Return 'dg' model instead?
            print('PSF model fit', psf, 'failed!  Returning DG model instead')
            psf = 'dg'
    if psf == 'dg':
        print('Creating double-Gaussian PSF approximation')
        (a,s1, b,s2) = dgpsf
        mypsf = NCircularGaussianPSF([s1, s2], [a, b])
        
    timg = Image(data=image, invvar=invvar, psf=mypsf, wcs=wcs,
                 sky=skyobj, photocal=photocal,
                 name=('SDSS (r/c/f/b=%i/%i/%i/%s)' %
                       (run, camcol, field, bandname)))
    timg.zr = zr
    return timg,info
Example #3
0
def _get_tractor_image_dr8(run, camcol, field, bandname, sdss=None,
                          roi=None, psf='kl-gm', roiradecsize=None,
                          roiradecbox=None,
                          savepsfimg=None, curl=False,
                          nanomaggies=False,
                          zrange=[-3,10],
                          invvarIgnoresSourceFlux=False,
                          invvarAtCenter=False,
                          invvarAtCenterImage=False,
                          imargs={}):
    # retry_retrieve=True,
    '''
    Creates a tractor.Image given an SDSS field identifier.

    If not None, roi = (x0, x1, y0, y1) defines a region-of-interest
    in the image, in zero-indexed pixel coordinates.  x1,y1 are
    NON-inclusive; roi=(0,100,0,100) will yield a 100 x 100 image.

    psf can be:
      "dg" for double-Gaussian
      "kl-gm" for SDSS KL-decomposition approximated as a Gaussian mixture

      "bright-*", "*" one of the above PSFs, with special handling at
      the bright end.

    "roiradecsize" = (ra, dec, half-size in pixels) indicates that you
    want to grab a ROI around the given RA,Dec.

    "roiradecbox" = (ra0, ra1, dec0, dec1) indicates that you
    want to grab a ROI containing the given RA,Dec ranges.

    "invvarAtCentr" -- get a scalar constant inverse-variance

    "invvarAtCenterImage" -- get a scalar constant inverse-variance
    but still make an image out of it.

    Returns: (tractor.Image, dict)

    dict contains useful details like:
      'sky'
      'skysig'
    '''
    from astrometry.sdss import band_index

    origpsf = psf
    if psf.startswith('bright-'):
        psf = psf[7:]
        brightpsf = True
        print('Setting bright PSF handling')
    else:
        brightpsf = False

    valid_psf = ['dg', 'kl-gm', 'kl-pix']
    if psf not in valid_psf:
        raise RuntimeError('PSF must be in ' + str(valid_psf))

    if sdss is None:
        from astrometry.sdss import DR8
        sdss = DR8(curl=curl)

    bandnum = band_index(bandname)

    for ft in ['psField', 'fpM']:
        fn = sdss.retrieve(ft, run, camcol, field, bandname)
    fn = sdss.retrieve('frame', run, camcol, field, bandname)

    # http://data.sdss3.org/datamodel/files/BOSS_PHOTOOBJ/frames/RERUN/RUN/CAMCOL/frame.html
    frame = sdss.readFrame(run, camcol, field, bandname, filename=fn)

    #image = frame.getImage().astype(np.float32)
    #(H,W) = image.shape

    H,W = frame.getImageShape()
    
    info = dict()
    hdr = frame.getHeader()
    tai = hdr.get('TAI')
    stripe = hdr.get('STRIPE')
    strip = hdr.get('STRIP')
    obj = hdr.get('OBJECT')
    info.update(tai=tai, stripe=stripe, strip=strip, object=obj, hdr=hdr)

    astrans = frame.getAsTrans()
    wcs = SdssWcs(astrans)
    #print('Created SDSS Wcs:', wcs)
    #print('(x,y) = 1,1 -> RA,Dec', wcs.pixelToPosition(1,1))

    X = interpret_roi(wcs, (H,W), roi=roi, roiradecsize=roiradecsize,
                               roiradecbox=roiradecbox)
    if X is None:
        return None,None
    roi,hasroi = X
    info.update(roi=roi)
    x0,x1,y0,y1 = roi

    # Mysterious half-pixel shift.  asTrans pixel coordinates?
    wcs.setX0Y0(x0 + 0.5, y0 + 0.5)

    if nanomaggies:
        photocal = LinearPhotoCal(1., band=bandname)
    else:
        photocal = SdssNanomaggiesPhotoCal(bandname)

    sky = 0.
    skyobj = ConstantSky(sky)

    calibvec = frame.getCalibVec()

    invvarAtCenter = invvarAtCenter or invvarAtCenterImage

    psfield = sdss.readPsField(run, camcol, field)
    iva = dict(ignoreSourceFlux=invvarIgnoresSourceFlux)
    if invvarAtCenter:
        if hasroi:
            iva.update(constantSkyAt=((x0+x1)/2., (y0+y1)/2.))
        else:
            iva.update(constantSkyAt=(W/2., H/2.))
    invvar = frame.getInvvar(psfield, bandnum, **iva)
    invvar = invvar.astype(np.float32)
    if not invvarAtCenter:
        assert(invvar.shape == (H,W))

    # Could get this from photoField instead
    # http://data.sdss3.org/datamodel/files/BOSS_PHOTOOBJ/RERUN/RUN/photoField.html
    gain = psfield.getGain(bandnum)
    darkvar = psfield.getDarkVariance(bandnum)

    meansky = np.mean(frame.sky)
    meancalib = np.mean(calibvec)
    skysig = sqrt((meansky / gain) + darkvar) * meancalib

    info.update(sky=sky, skysig=skysig)
    zr = np.array(zrange)*skysig + sky
    info.update(zr=zr)

    # http://data.sdss3.org/datamodel/files/PHOTO_REDUX/RERUN/RUN/objcs/CAMCOL/fpM.html
    fpM = sdss.readFpM(run, camcol, field, bandname)

    if not hasroi:
        image = frame.getImage()

    else:
        roislice = (slice(y0,y1), slice(x0,x1))
        image = frame.getImageSlice(roislice).astype(np.float32)
        if invvarAtCenterImage:
            invvar = invvar + np.zeros(image.shape, np.float32)
        elif invvarAtCenter:
            pass
        else:
            invvar = invvar[roislice].copy()
        H,W = image.shape
            
    if (not invvarAtCenter) or invvarAtCenterImage:
        for plane in [ 'INTERP', 'SATUR', 'CR', 'GHOST' ]:
            fpM.setMaskedPixels(plane, invvar, 0, roi=roi)

    dgpsf = psfield.getDoubleGaussian(bandnum, normalize=True)
    info.update(dgpsf=dgpsf)
            
    if psf == 'kl-pix':
        # Pixelized KL-PSF
        klpsf = psfield.getPsfAtPoints(bandnum, x0+W/2, y0+H/2)
        # Trim symmetric zeros
        sh,sw = klpsf.shape
        while True:
            if (np.all(klpsf[0,:] == 0.) and
                np.all(klpsf[:,0] == 0.) and
                np.all(klpsf[-1,:] == 0.) and
                np.all(klpsf[:,-1] == 0.)):
                klpsf = klpsf[1:-1, 1:-1]
            else:
                break

        mypsf = PixelizedPSF(klpsf)
        
    elif psf == 'kl-gm':
        from emfit import em_fit_2d
        from fitpsf import em_init_params
        
        # Create Gaussian mixture model PSF approximation.
        klpsf = psfield.getPsfAtPoints(bandnum, x0+W/2, y0+H/2)
        S = klpsf.shape[0]
        # number of Gaussian components
        K = 3
        w,mu,sig = em_init_params(K, None, None, None)
        II = klpsf.copy()
        II /= II.sum()
        # HIDEOUS HACK
        II = np.maximum(II, 0)
        #print('Multi-Gaussian PSF fit...')
        xm,ym = -(S/2), -(S/2)
        if savepsfimg is not None:
            plt.clf()
            plt.imshow(II, interpolation='nearest', origin='lower')
            plt.title('PSF image to fit with EM')
            plt.savefig(savepsfimg)
        res = em_fit_2d(II, xm, ym, w, mu, sig)
        #print('em_fit_2d result:', res)
        if res == 0:
            # print('w,mu,sig', w,mu,sig)
            mypsf = GaussianMixturePSF(w, mu, sig)
            mypsf.computeRadius()
        else:
            # Failed!  Return 'dg' model instead?
            print('PSF model fit', psf, 'failed!  Returning DG model instead')
            psf = 'dg'
    if psf == 'dg':
        print('Creating double-Gaussian PSF approximation')
        (a,s1, b,s2) = dgpsf
        mypsf = NCircularGaussianPSF([s1, s2], [a, b])

    if brightpsf:
        print('Wrapping PSF in SdssBrightPSF')
        (a1,s1, a2,s2, a3,sigmap,beta) = psfield.getPowerLaw(bandnum)
        mypsf = SdssBrightPSF(mypsf, a1,s1,a2,s2,a3,sigmap,beta)
        print('PSF:', mypsf)

    timg = Image(data=image, invvar=invvar, psf=mypsf, wcs=wcs,
                 sky=skyobj, photocal=photocal,
                 name=('SDSS (r/c/f/b=%i/%i/%i/%s)' %
                       (run, camcol, field, bandname)),
                 time=TAITime(tai),
                 **imargs)
    timg.zr = zr
    return timg,info
Example #4
0
def _get_tractor_image_dr8(run,
                           camcol,
                           field,
                           bandname,
                           sdss=None,
                           roi=None,
                           psf='kl-gm',
                           roiradecsize=None,
                           roiradecbox=None,
                           savepsfimg=None,
                           curl=False,
                           nanomaggies=False,
                           zrange=[-3, 10],
                           invvarIgnoresSourceFlux=False,
                           invvarAtCenter=False,
                           invvarAtCenterImage=False,
                           imargs={}):
    # retry_retrieve=True,
    '''
    Creates a tractor.Image given an SDSS field identifier.

    If not None, roi = (x0, x1, y0, y1) defines a region-of-interest
    in the image, in zero-indexed pixel coordinates.  x1,y1 are
    NON-inclusive; roi=(0,100,0,100) will yield a 100 x 100 image.

    psf can be:
      "dg" for double-Gaussian
      "kl-gm" for SDSS KL-decomposition approximated as a Gaussian mixture

      "bright-*", "*" one of the above PSFs, with special handling at
      the bright end.

    "roiradecsize" = (ra, dec, half-size in pixels) indicates that you
    want to grab a ROI around the given RA,Dec.

    "roiradecbox" = (ra0, ra1, dec0, dec1) indicates that you
    want to grab a ROI containing the given RA,Dec ranges.

    "invvarAtCentr" -- get a scalar constant inverse-variance

    "invvarAtCenterImage" -- get a scalar constant inverse-variance
    but still make an image out of it.

    Returns: (tractor.Image, dict)

    dict contains useful details like:
      'sky'
      'skysig'
    '''
    from astrometry.sdss import band_index

    origpsf = psf
    if psf.startswith('bright-'):
        psf = psf[7:]
        brightpsf = True
        print('Setting bright PSF handling')
    else:
        brightpsf = False

    valid_psf = ['dg', 'kl-gm', 'kl-pix']
    if psf not in valid_psf:
        raise RuntimeError('PSF must be in ' + str(valid_psf))

    if sdss is None:
        from astrometry.sdss import DR8
        sdss = DR8(curl=curl)

    bandnum = band_index(bandname)

    for ft in ['psField', 'fpM']:
        fn = sdss.retrieve(ft, run, camcol, field, bandname)
    fn = sdss.retrieve('frame', run, camcol, field, bandname)

    # http://data.sdss3.org/datamodel/files/BOSS_PHOTOOBJ/frames/RERUN/RUN/CAMCOL/frame.html
    frame = sdss.readFrame(run, camcol, field, bandname, filename=fn)

    #image = frame.getImage().astype(np.float32)
    #(H,W) = image.shape

    H, W = frame.getImageShape()

    info = dict()
    hdr = frame.getHeader()
    tai = hdr.get('TAI')
    stripe = hdr.get('STRIPE')
    strip = hdr.get('STRIP')
    obj = hdr.get('OBJECT')
    info.update(tai=tai, stripe=stripe, strip=strip, object=obj, hdr=hdr)

    astrans = frame.getAsTrans()
    wcs = SdssWcs(astrans)
    #print('Created SDSS Wcs:', wcs)
    #print('(x,y) = 1,1 -> RA,Dec', wcs.pixelToPosition(1,1))

    X = interpret_roi(wcs, (H, W),
                      roi=roi,
                      roiradecsize=roiradecsize,
                      roiradecbox=roiradecbox)
    if X is None:
        return None, None
    roi, hasroi = X
    info.update(roi=roi)
    x0, x1, y0, y1 = roi

    # Mysterious half-pixel shift.  asTrans pixel coordinates?
    wcs.setX0Y0(x0 + 0.5, y0 + 0.5)

    if nanomaggies:
        photocal = LinearPhotoCal(1., band=bandname)
    else:
        photocal = SdssNanomaggiesPhotoCal(bandname)

    sky = 0.
    skyobj = ConstantSky(sky)

    calibvec = frame.getCalibVec()

    invvarAtCenter = invvarAtCenter or invvarAtCenterImage

    psfield = sdss.readPsField(run, camcol, field)
    iva = dict(ignoreSourceFlux=invvarIgnoresSourceFlux)
    if invvarAtCenter:
        if hasroi:
            iva.update(constantSkyAt=((x0 + x1) / 2., (y0 + y1) / 2.))
        else:
            iva.update(constantSkyAt=(W / 2., H / 2.))
    invvar = frame.getInvvar(psfield, bandnum, **iva)
    invvar = invvar.astype(np.float32)
    if not invvarAtCenter:
        assert (invvar.shape == (H, W))

    # Could get this from photoField instead
    # http://data.sdss3.org/datamodel/files/BOSS_PHOTOOBJ/RERUN/RUN/photoField.html
    gain = psfield.getGain(bandnum)
    darkvar = psfield.getDarkVariance(bandnum)

    meansky = np.mean(frame.sky)
    meancalib = np.mean(calibvec)
    skysig = sqrt((meansky / gain) + darkvar) * meancalib

    info.update(sky=sky, skysig=skysig)
    zr = np.array(zrange) * skysig + sky
    info.update(zr=zr)

    # http://data.sdss3.org/datamodel/files/PHOTO_REDUX/RERUN/RUN/objcs/CAMCOL/fpM.html
    fpM = sdss.readFpM(run, camcol, field, bandname)

    if not hasroi:
        image = frame.getImage()

    else:
        roislice = (slice(y0, y1), slice(x0, x1))
        image = frame.getImageSlice(roislice).astype(np.float32)
        if invvarAtCenterImage:
            invvar = invvar + np.zeros(image.shape, np.float32)
        elif invvarAtCenter:
            pass
        else:
            invvar = invvar[roislice].copy()
        H, W = image.shape

    if (not invvarAtCenter) or invvarAtCenterImage:
        for plane in ['INTERP', 'SATUR', 'CR', 'GHOST']:
            fpM.setMaskedPixels(plane, invvar, 0, roi=roi)

    dgpsf = psfield.getDoubleGaussian(bandnum, normalize=True)
    info.update(dgpsf=dgpsf)

    if psf == 'kl-pix':
        # Pixelized KL-PSF
        klpsf = psfield.getPsfAtPoints(bandnum, x0 + W / 2, y0 + H / 2)
        # Trim symmetric zeros
        sh, sw = klpsf.shape
        while True:
            if (np.all(klpsf[0, :] == 0.) and np.all(klpsf[:, 0] == 0.)
                    and np.all(klpsf[-1, :] == 0.)
                    and np.all(klpsf[:, -1] == 0.)):
                klpsf = klpsf[1:-1, 1:-1]
            else:
                break

        mypsf = PixelizedPSF(klpsf)

    elif psf == 'kl-gm':
        from emfit import em_fit_2d
        from fitpsf import em_init_params

        # Create Gaussian mixture model PSF approximation.
        klpsf = psfield.getPsfAtPoints(bandnum, x0 + W / 2, y0 + H / 2)
        S = klpsf.shape[0]
        # number of Gaussian components
        K = 3
        w, mu, sig = em_init_params(K, None, None, None)
        II = klpsf.copy()
        II /= II.sum()
        # HIDEOUS HACK
        II = np.maximum(II, 0)
        #print('Multi-Gaussian PSF fit...')
        xm, ym = -(S / 2), -(S / 2)
        if savepsfimg is not None:
            plt.clf()
            plt.imshow(II, interpolation='nearest', origin='lower')
            plt.title('PSF image to fit with EM')
            plt.savefig(savepsfimg)
        res = em_fit_2d(II, xm, ym, w, mu, sig)
        #print('em_fit_2d result:', res)
        if res == 0:
            # print('w,mu,sig', w,mu,sig)
            mypsf = GaussianMixturePSF(w, mu, sig)
            mypsf.computeRadius()
        else:
            # Failed!  Return 'dg' model instead?
            print('PSF model fit', psf, 'failed!  Returning DG model instead')
            psf = 'dg'
    if psf == 'dg':
        print('Creating double-Gaussian PSF approximation')
        (a, s1, b, s2) = dgpsf
        mypsf = NCircularGaussianPSF([s1, s2], [a, b])

    if brightpsf:
        print('Wrapping PSF in SdssBrightPSF')
        (a1, s1, a2, s2, a3, sigmap, beta) = psfield.getPowerLaw(bandnum)
        mypsf = SdssBrightPSF(mypsf, a1, s1, a2, s2, a3, sigmap, beta)
        print('PSF:', mypsf)

    timg = Image(data=image,
                 invvar=invvar,
                 psf=mypsf,
                 wcs=wcs,
                 sky=skyobj,
                 photocal=photocal,
                 name=('SDSS (r/c/f/b=%i/%i/%i/%s)' %
                       (run, camcol, field, bandname)),
                 time=TAITime(tai),
                 **imargs)
    timg.zr = zr
    return timg, info